基于存储_POST_单引号_二次注入
这是一个相对完整的网站,第一次贴近实战。
我有预感这篇同样会很长…
这个也太硬核了吧…
0x01. 网站结构分析与PHP审计
黑盒测试
在不看源码的情况下尝试分析:
- 登录界面,具有登录功能,同时有忘记密码和注册的链接。
- 注册界面,具有注册功能。
- 忘记密码界面,非常不友好…只有一张打脸的图。
- 修改密码界面,登陆成功后显示,有登出的链接。
- 登录失败界面,同样不友好。
经过测试,初步判断注入点应该在注册界面,这里似乎对输入没有做任何检查,甚至能创建一个用户名和密码都为空的账户(查看 MySQL 后台数据证实)。
白盒测试
实际上与数据库有操作的只有这样三个文件,分别用于登录、注册与修改密码,剩下的五个文件分别对应上述几个显示的界面,与黑盒测试基本一致。
代码审计
界面文件
几乎所有的界面头部都有这样的代码:
session_start();
if (isset($_SESSION['username']) && isset($_COOKIE['Auth']))
header('Location: logged-in.php');
这里的知识点是session
,我们在 JavaEE 和 .Net 的课上都学过,这里也不多介绍。
逻辑就是进入界面后判断是否在登录的有效期。
login.php
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
可以看到对username
和password
都做了过滤,很难注入。
SELECT * FROM users WHERE username='$username' and password='$password'
$_SESSION["username"] = $login;
setcookie("Auth", 1, time()+3600); /* expire in 15 Minutes */
header('Location: logged-in.php');
登录成功后创建session
和cookie
,session
存储username
,但session
是建立在服务器上的对象,所以无法获取;cookie
只是个登录标记,几乎没有任何用处。
login_create.php
$username = mysql_escape_string($_POST['username']);
$pass = mysql_escape_string($_POST['password']);
$re_pass = mysql_escape_string($_POST['re_password']);
这里要注意到:mysql_escape_string()
和mysql_real_escape_string()
是不同的,前者早在PHP 5.3
中被弃用,在官方文档里对它的介绍也就一句话,就说处理特殊字符用于 MySQL 查询。
对这两个的资料也少的可怜,查到一个:mysql_escape_string()
没办法判断当前的编码,mysql_real_escape_string()
之所以能够防注入是因为同时指定了服务端的编码和客户端的编码。
即使这样,这里的三个字段的特殊字符还是被转义了的。注意:是转义并非过滤。
SELECT count(*) FROM users WHERE username='$username'
INSERT INTO users (username,password) values (\"$username\", \"$pass\")
创建用户前先查询是否已经存在该账号。
pass_change.php
$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']);
UPDATE users SET PASSWORD='$pass' WHERE username='$username' and password='$curr_pass'
这里就有惊喜了:从session
中获得了当前用户名,且被直接用于更新语句并未做检查。
从根本上来说,插入数据时没有过滤,只是做了转义处理。
若当前用户名中含有注释,便可以修改当前用户名中包含的另一用户的密码。
这便是 PHP 审计与 SQLI 结合的题型。
0x02. 二次注入
二次注入也称为存储型注入,就是将可能导致 SQL 注入的字符先存入到数据库中,当再次调用这个恶意构造的字符时,就可以触发 SQL 注入。
二次注入的一般过程:
- 通过构造数据的形式,在浏览器或者其他软件中提交 HTTP 数据报文请求到服务
端进行处理,提交的数据报文请求中可能包含了构造的 SQL 语句或者命令。 - 服务端应用程序会将提交的数据信息进行存储,通常是保存在数据库中,保存的
数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。 - 向服务端发送第二个与第一次不相同的请求数据信息。
- 服务端接收到提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致在第一次请求中构造的 SQL 语句或者命令在服务端环境中执行。
- 服务端返回执行的处理结果数据信息,便可以通过返回的结果数据信息判断二次注入漏洞利用是否成功。
0x03. 注入过程
步骤1:确定注入逻辑
注入点在修改密码处:
UPDATE users SET PASSWORD='$pass' WHERE username='$username' and password='$curr_pass'
要将其变为:
UPDATE users SET PASSWORD='$pass' WHERE username='$username'-- and password='$curr_pass'
且已知注册时未有任何过滤,闭合查询语句为单引号,即在注册用户时构造admin'-- (有空格)
或admin'#
。
步骤2:注册构造用户名
注册前数据库:
注册时密码随便设置:
注册后数据库:
步骤3:登录与修改密码
我依然很讨厌这个欢迎界面:
当前密码为刚才注册时输入,新密码同样随便设置即可,为即将用admin
登录的密码。
修改后数据库:
步骤4:修改用户登录
HACKED
0x04. 吐槽
PHP 审计和 SQL 注入真好玩嘻嘻嘻。
也没有想象的那么长嘛。