梦之光芒是一个以Web安全为主的在线闯关式CTF挑战,共设有15个关卡,其中只涉及简单的Web安全问题和少量逆向、加解密基础知识,自2007年上线以来已经有上千人成功通关。下面,一起从这里出发,从拿下“First Blood”开始,让网络安全梦想的光芒一步步照进现实吧!
进入梦之光芒首页,可以看到提示有链接的地方实际并没有可以点击的链接。单击鼠标右键,在弹出的菜单中单击“查看页面源代码”按钮,很快发现下面的代码块,具体如下。
<div class="info"> 欢迎来到梦之光芒的小游戏。<br> 玩这个游戏,您需要有JS加解密基础,SQL注入基本常识等...<br> 如果您参加本游戏,则视为您已经同意<strong>“这仅仅是个小游戏”</strong>这个原则,所以请不要在技术上过于较真,谢谢!<br> 本游戏所有权归Monyer所有,但您每过一关,您有权利在不通知Monyer的情况下保留代码。不当的地方还请批评指教!<br> <br>请点击链接进入第1关: <span>连接在左边→</span> <a href="first.php"></a> <span>←连接在右边</span><br> </div>
猜测这里的first.php就是第1关的地址,访问后发现顺利进入第1关。
在第1关中,直接要求输入密码(即flag)。
没有提供其他线索,则先查看页面的源代码,用户能够注意到下面的JavaScript函数。
<script type="text/javascript"> function check(){ if(document.getElementById('txt').value==" "){ window.location.href="hello.php"; }else{ alert("密码错误"); } } </script>
容易看出,正确的密码为两个空格,之后将跳转到下一关的地址hello.php。在密码框中输入两个空格,单击“提交”按钮即可过关。
在第2关中,仍然是直接要求输入密码。
与上文所述的思路一样,还是先查看页面的源代码,但在单击鼠标右键时发现不同,该页面的鼠标右键被禁用了!此时可以在键盘上按下“F12”键打开浏览器的“开发人员工具”选项卡,在“元素”选项卡中查看网页的源代码。最终发现下面的 JavaScript代码。
<script type="text/javascript"> document.oncontextmenu=function(){return false}; var a,b,c,d,e,f,g; a = 3.14; b = a * 2; c = a + b; d = c / b + a; e = c - d * b + a; f = e + d /c -b * a; g = f * e - d + c * b + a; a = g * g; a = Math.floor(a); function check(){ if(document.getElementById("txt").value==a){ window.location.href=a + ".php"; }else{ alert("密码错误"); return false; } } </script>
对代码进行简单的分析,第一行代码禁用了鼠标右键,后面的代码对变量 a 进行了一系列的计算,最终 a 的值就是要输入的密码,也是下一关的地址。这里 a 的值可以手动计算,也可以将代码复制到“开发人员工具”的“控制台”选项卡中进行计算。将计算结果输入密码框,单击“提交”按钮即可过关。
在第3关中,还是要求直接输入密码。
单击鼠标右键查看页面的源代码,注意到下面的JavaScript代码。
<script type="text/javascript"> eval(String.fromCharCode(102,117,110,99,116,105,111,110,32,99,104,101,99,107,40,4 1,123,13,10,09,118,97,114,32,97,32,61,32,39,100,52,103,39,59,13,10,09,105,102,40, 100,111,99,117,109,101,110,116,46,103,101,116,69,108,101,109,101,110,116,66,121,7 3,100,40,39,116,120,116,39,41,46,118,97,108,117,101,61,61,97,41,123,13,10,09,09,1 19,105,110,100,111,119,46,108,111,99,97,116,105,111,110,46,104,114,101,102,61,97, 43,34,46,112,104,112,34,59,13,10,09,125,101,108,115,101,123,13,10,09,09,97,108,10 1,114,116,40,34,23494,30721,38169,35823,34,41,59,13,10,09,125,13,10,125)); </script>
这里采用Unicode编码对一个字符串进行了简单的混淆,使用fromCharCode()方法能够返回指定的Unicode值所对应的字符串,使用eval()方法则可以将参数字符串当作JavaScript代码来执行。应首先利用控制台明确被混淆的字符串。
>String.fromCharCode(102,117,110,99,116,105,111,110,32,99,104,101,99,107,40,41,12 3,13,10,09,118,97,114,32,97,32,61,32,39,100,52,103,39,59,13,10,09,105,102,40,100, 111,99,117,109,101,110,116,46,103,101,116,69,108,101,109,101,110,116,66,121,73,10 0,40,39,116,120,116,39,41,46,118,97,108,117,101,61,61,97,41,123,13,10,09,09,119,1 05,110,100,111,119,46,108,111,99,97,116,105,111,110,46,104,114,101,102,61,97,43,3 4,46,112,104,112,34,59,13,10,09,125,101,108,115,101,123,13,10,09,09,97,108,101,11 4,116,40,34,23494,30721,38169,35823,34,41,59,13,10,09,125,13,10,125) <`function check(){ var a = 'd4g'; if(document.getElementById('txt').value==a){ window.location.href=a+".php"; }else{ alert("密码错误"); } }`
可以看到,被混淆的字符串实际上为一个JavaScript函数,该函数表明这一关的正确密码为“d4g”,且下一关的地址就是d4g.php。将“d4g”输入密码框,单击“提交”按钮即可过关。
第4关的页面加载完成后会立刻跳转回第3关,此时可以通过迅速按下键盘上的“Esc”键来强制停止跳转,从而留在第4关的页面。
在第4关中,仍然要求直接输入密码。如前文所述,单击鼠标右键查看页面源代码,发现两段奇怪的JavaScript代码,具体如下。
<script type="text/javascript"> eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--)d[c.toString(a)]=k[c]||c.to String(a);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('a="e";d c(){b(9.8(\'7\').6==a){5.4.3 =a+".2"}1{0("密码错误")}}',15,15,'alert|else|php| href|location|window|value|txt| getElementById|document||if|check|function|3bhe'.split('|'),0,{})) </script> <script type="text/javascript"> eval("\141\75\141\56\164\157\125\160\160\145\162\103\141\163\145\50\51\53\61\73"); </script>
第1段代码阅读起来非常费力,第2段代码则干脆不知所云,可以看出是开发者有意为之。此时不妨利用一个简单的小技巧,将执行代码的 eval()方法换成能够在对话框中显示纯文本的alert()方法,并放入控制台中尝试执行。
从第1段和第2段代码结果可以看出,第4关的密码是“3BHE1”(将字符串3bhe转换为大写后再与数字1连接),且下一关的地址为3BHE1.php。将“3BHE1”输入密码框,单击“提交”按钮即可过关。
在第5关中,仍然要求直接输入密码,同时给出了一条提示信息。
根据提示信息查看并分析页面的源代码,发现代码非常简单。尝试刷新页面,并在“开发人员工具”的“网络”选项卡中查看其HTTP请求及响应,最后在响应头中找到了密码。
将“asdf”输入密码框,单击“提交”按钮即可过关。
在第6关中,给出了一幅图片,要求直接输入密码。
首先还是需要查看页面的源代码,发现提示通过图片线索寻找密码。仔细观察可以看出图片展示了某个关键词的 Google 搜索结果,猜测该搜索关键词就是密码。通过图片最下方Wikipedia 的链接可知,这里的搜索关键词可能是一本杂志的名字,而第一条搜索结果中摘抄的文本信息也许能够帮我们定位到该杂志。在任意搜索引擎中输入该段文本信息,得到的搜索结果。
最终确定该杂志名为 seventeen ,将“seventeen”输入密码框,单击“提交”按钮即可过关。
在第7关中,仍然要求直接输入密码,同时给出了3条提示信息。
首先查看页面的源代码,没有发现任何提示,则只能从给出的MD5值入手。尝试通过MD5在线解密网站对其进行破解,很容易就可以得到明文eighteen8。将其输入密码框,单击“提交”按钮,发现跳转到了一个错误页面。
难道密码不是eighteen8吗?结合提示1和提示2,猜测下一关的地址就是eighteen8.php,不妨先试试能否继续解题。
查看eighteen8.php的源代码,发现下面的代码块。
<p style="display:none"> 第8关 朋友您好,第8关欢迎您! 我对您的聪明才智感到惊讶! 相信我,现在世界上85%以上的人都在你之下, 所以你可以大步向前,义无反顾地进行你的事业了。 因为只要你肯努力,不畏惧挫折,这个世界上没有难倒你的事。 那么继续我们的约定,我将告诉你第9关的入口: 10000以内所有质数和.php </p>
由此可见,第8关的页面确实为 eighteen8.php。而根据提示,下一关的地址是10 000以内所有质数的和,通过运行下面的Python代码计算该值。
# 8.py count = 0 for i in range(2, 10000): if i == 2: count += i continue for j in range(2, i): if i%j == 0: break if j == i-1: count += i break print(count)
执行“8.py”,最终得到10 000以内所有质数的和为5 736 396,即下一关的地址为5736396.php。
在第9关中,给出了一幅图片,提示通关密码也在图片里,猜测这可能是一个简单的图像隐写题目。将图片下载到本地,用文本编辑器打开,在文件尾部发现了密码信息,其中,图片上方的“*****”表示一堆乱码。
将“MonyerLikeYou_the10level”输入密码框,单击“提交”按钮即可过关。
在第10关中,要求直接输入密码,同时给出了一条关于当前用户身份的提示信息。
一般来说,用户的身份信息是由客户端的Cookie或服务端的Session来记录的,因此想到利用开发人员工具查看HTTP请求头中的Cookie,结果如下。
Accept: text/html,application/xhtml+xml,application/xml;... Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 Cache-Control: max-age=0 Connection: keep-alive Cookie: username=simpleuser; PHPSESSID=1kno75b2ls338qqkm8o4phs9k2; ... Host: monyer.com Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 Edg/96.0.1054.34
可以看到Cookie中记录的username果然是simpleuser。在控制台中利用JavaScript代码将username的值修改为admin,具体如下。
> document.cookie='username=admin' < 'username=admin'
刷新页面即可得到通关密码。
将“doyouknow”输入密码框,单击“提交”按钮即可过关。
在第11关中,要求直接输入密码,同时给出了一条与前一关类似的提示信息。
尽管我们知道将Session修改为Passer就可以看到密码,但Session与Cookie不同,它是由服务端保存的,客户端无法直接对其进行修改,因此使用前一关的方法是行不通的。此时用户注意到这一关的URL地址带有请求参数,据此猜测,不同的客户端请求参数可能会让服务端设置不同的 Session。尝试将原本的请求参数 show_login_false 修改为show_login_true,果然顺利得到了通关密码。
将“smartboy”输入密码框,单击“提交”按钮即可过关。
在第12关中,要求直接输入密码,并给出了一串奇怪的字符串。
查看页面的源代码,没有发现什么端倪。改从给出的字符串入手,根据字符串的形式猜测它可能经过了Base64编码。尝试通过在线解码工具进行Base64解码,得到下面的结果。
Base64: JTRBJTU0JTYzJTdBJTRBJTU0JTVBJTQ3JTRBJTU0JTU5JTc5JTRBJTU0JTU5JTMxJTRBJTU0JTU5JTc4J TRBJTU0JTYzJTMxJTRBJTU0JTYzJTMwJTRBJTU0JTU5JTM1JTRBJTU0JTU5JTMyJTRBJTU0JTYzJTMxJT RBJTU0JTVBJTQ0JTRBJTU0JTRBJTQ2JTRBJTU0JTYzJTc3JTRBJTU0JTU5JTM0JTRBJTU0JTYzJTc3 明文: %4A%54%63%7A%4A%54%5A%47%4A%54%59%79%4A%54%59%31%4A%54%59%78%4A%54%63%31%4A%54%63 %30%4A%54%59%35%4A%54%59%32%4A%54%63%31%4A%54%5A%44%4A%54%4A%46%4A%54%63%77%4A%54 %59%34%4A%54%63%77
解码成功后,在得到的明文中含有大量的%,怀疑可能经过了 URL 编码。尝试通过在线解码工具进行URL解码,得到下面的结果。
URLEncode: %4A%54%63%7A%4A%54%5A%47%4A%54%59%79%4A%54%59%31%4A%54%59%78%4A%54%63%31%4A%54%63 %30%4A%54%59%35%4A%54%59%32%4A%54%63%31%4A%54%5A%44%4A%54%4A%46%4A%54%63%77%4A%54 %59%34%4A%54%63%77 明文: JTczJTZGJTYyJTY1JTYxJTc1JTc0JTY5JTY2JTc1JTZDJTJFJTcwJTY4JTcw
又得到一串看上去像是Base64编码的字符串。再次通过在线解码工具进行Base 64解码,得到下面的结果。
Base64: JTczJTZGJTYyJTY1JTYxJTc1JTc0JTY5JTY2JTc1JTZDJTJFJTcwJTY4JTcw 明文: %73%6F%62%65%61%75%74%69%66%75%6C%2E%70%68%70
所得到的明文中仍然含有大量的%。再次通过在线解码工具进行URL解码,得到下面的结果。
URLEncode: %73%6F%62%65%61%75%74%69%66%75%6C%2E%70%68%70 明文: sobeautiful.php
合理推测,sobeautiful.php 就是下一关的地址。然而通过浏览器访问该地址,却发现并没有进入下一关,且页面提示禁止盗链,由此分析使用了防盗链技术。防盗链技术会检测访问者是从哪个页面跳转到目标页面的,只有从特定页面跳转才能正常访问目标页面。即必须通过第12关的页面进行跳转才能正常访问第13关的页面。
考虑如何从第12关的页面进行跳转。首先尝试将字符串 sobeautiful.php 和 sobeautiful输入密码框,单击“提交”按钮,发现并不会触发跳转,但会在页面上显示输入的字符串。
由此想到,利用这里的密码框可以向页面中写入跳转链接,单击该链接就可以从第12关的页面跳转到第13关的页面,以进行正常访问,这实际上是一个简单的反射型XSS攻击。在密码框中输入字符串<a href="sobeautiful.php">第13关</a> ,单击“提交”按钮,页面上果然出现了期望的链接。
单击该链接即可绕过防盗链机制,顺利进入第13关。
在第13关中,要求直接输入密码,同时给出了一条提示信息。
首先还是查看页面的源代码,很容易看到一段被注释掉的数据库操作,具体如下。
<!- dim connect Response.Expires=0 '系统数据库连接 Set connect=Server.CreateObject("ADODB.Connection") connect.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & server.MapPath("/Database.mdb") & ";Mode=ReadWrite|Share Deny None;Persist Security Info=False" set rss=server.createobject("adodb.recordset") sqlstr="select password,pwd from [user] where pwd='"&request("pwd")&"'" rss.open sqlstr,connect,1,1 if rss.bof and rss.eof then response.write("密码错误") else response.write(rss("password")) end if rss.close set rss=nothing connect.close set connect=nothing -->
不难想到,为了得到通关密码,只需要令数据库查询语句的返回值为真即可,这实际上是一个简单的SQL注入攻击。在密码框中输入字符串' or 1=1 ,单击“提交”按钮,页面上显示下一关密码为“whatyouneverknow”。
将“whatyouneverknow”输入密码框,再次单击“提交”按钮即可过关。
在第14关中,给出了一个Crackme程序,这是一道简单的逆向分析题。
下载该程序,发现其是一个在 Windows 操作系统下的可执行程序,且会被安全防护软件查杀。事实上,不少 CTF 竞赛中的赛题程序都可能被安全防护软件查杀或报告为威胁,这也是我们常常在虚拟机中构建模拟环境的原因之一。参照2.2节中的虚拟机安装方法安装一台 Windows 虚拟机,关闭系统自带的安全防护软件,就可以开始对本题中的程序进行逆向分析了。
首先尝试运行该程序,发现要求输入一串16位的注册码,随意输入一个16位的字符串,提示注册失败。
尝试用常见的静态分析工具IDA Pro对程序进行静态分析,IDA Pro会弹出警告。
出现警告信息意味着该程序是加过壳的,单击“OK”按钮让IDA Pro继续加载,根据函数窗口的信息可以简单判断所加的是UPX压缩壳。
尝试利用UPX官方的脱壳工具直接进行脱壳,具体如下。
F:\>upx -d crackme.exe -o crackme_unpacked.exe Ultimate Packer for eXecutables Copyright (C) 1996 - 2020 UPX 3.96w Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020 File size Ratio Format Name -------------------- ------ ----------- ---------- 400384 <- 162304 40.54% win32/pe crackme_unpacked.exe Unpacked 1 file.
脱壳成功后,crackme_unpacked.exe就是加壳之前的程序。仍然通过IDA Pro对其进行分析,不妨从提示注册失败的对话框入手,在IDA Pro中搜索字符串“注册失败!”,很快定位到下面的可疑代码段。
... CODE:00453868 _str_9eeee9eb50eff97 dd 0FFFFFFFFh ; _top CODE:00453868 ; DATA XREF: _TForm1_Button1Click+27↑o CODE:00453868 dd 16 ; Len CODE:00453868 db '9eeee9eb50eff979',0 ; Text CODE:00453881 align 4 CODE:00453884 _str_dd 0FFFFFFFFh ; _top CODE:00453884 ; DATA XREF: _TForm1_Button1Click+33↑o CODE:00453884 dd 10 ; Len CODE:00453884 db '注册失败!',0 ; Text CODE:00453897 align 4 CODE:00453898 _str_dd 0FFFFFFFFh ; _top CODE:00453898 ; DATA XREF: _TForm1_Button1Click:loc_45382C↑o CODE:00453898 dd 10 ; Len CODE:00453898 db '注册成功!',0 ; Text CODE:004538AB align 4 CODE:004538AC _str_ipasscrackme_as dd 0FFFFFFFFh ; _top CODE:004538AC ; DATA XREF: _TForm1_Button1Click+56↑o CODE:004538AC dd 16 ; Len CODE:004538AC db 'ipasscrackme.asp',0 ; Text ...
在上述代码段中,共有两个16位长度的字符串“9eeee9eb50eff979”和“ipasscrackme.asp”,含义不明。尝试将它们分别输入到程序中,发现当且仅当输入字符串“9eeee9eb50eff979”时提示注册成功,且此时字符串“ipasscrackme.asp”会显示在程序对话框的空白处。
因此,推测“9eeee9eb50eff979”为注册码,“ipasscrackme.asp”为通关密码。在页面上的密码框中输入字符串“ipasscrackme.asp”,单击“提交”按钮,发现并没有进入第15关,而是跳转到了一个错误页面。此时想到题目所在的网站明显是基于 PHP 环境的,不应该出现.asp 字样,因此尝试将提交的字符串改为“ipasscrackme.php”“ipasscrackme”,最后发现在密码框中输入“ipasscrackme”即可顺利进入下一关。
终于来到第15关,在这一关中不涉及任何安全问题,在文本框里输入相应的内容,单击“提交”按钮就可以让自己的名字出现在通关列表中。
至此,读者已经完成所有15个小的CTF挑战,拿到自己的“First Blood”。在挑战的过程中,相信读者已经发现 CTF 竞赛其实并没有想象中的那么困难、艰深,也已经体会到利用书本上的理论知识去解决实际的安全问题是一件多么有趣的事情。当然,这里的挑战还只是 CTF 竞赛世界的冰山一角,所涉及安全问题也不像真实世界中的安全问题那样影响力巨大。在后续的章节中,根据不同的题目类型,对 CTF 竞赛中可能涉及的安全问题逐一进行深入、细致的讲解,希望能够帮助读者在网络安全的求索之路上迈出坚实的一步。