Command Injection Preventie
SQL Zonder Slaaptekort
Webrisico is zelden mysterieus. Het zit meestal in voorspelbare fouten die onder tijdsdruk blijven staan.
Bij Command Injection Preventie gaat het om strikte invoergrenzen, parameterized queries en reviews die query-risico vroeg stoppen.
Dat maakt security minder een losse controle achteraf en meer een standaardkwaliteit van je product.
Directe maatregelen (15 minuten)
Waarom dit telt
De kern van Command Injection Preventie is risicoreductie in de praktijk. Technische context ondersteunt de maatregelkeuze, maar implementatie en borging staan centraal.
Verdediging: hoe je dit voorkomt
Nu even serieus. Want al die aanvalstechnieken zijn leuk en aardig, maar als je een developer bent (of een developer aanstuurt), moet je dit probleem ook oplossen.
Regel 1: Gebruik geen system calls
Dit is de enige regel die je echt nodig hebt. Als je nooit
os.system(), subprocess.call() met
shell=True, exec(), system(), of
welke functie dan ook aanroept die een shell-commando uitvoert met user
input, dan heb je geen command injection. Punt. Klaar. Einde
verhaal.
“Maar ik moet een ping uitvoeren!” Nee, dat moet je niet. Gebruik een
library. In Python: import ping3. In PHP: gebruik
fsockopen() voor netwerk- connectiviteit. In Java: gebruik
InetAddress.isReachable(). Er is altijd een library die
doet wat je wilt zonder dat je een shell hoeft aan te roepen.
“Maar ik moet een PDF genereren!” Gebruik een library.
reportlab in Python, wkhtmltopdf via een
wrapper library, iText in Java. Geen reden om
os.system("wkhtmltopdf " + filename) aan te roepen.
“Maar ik moet ImageMagick aanroepen!” Gebruik de Python-binding
Wand, of de PHP Imagick extensie, of de Java
im4java library. Allemaal roepen ImageMagick aan zonder dat
je een shell nodig hebt.
Regel 2: Als je toch een system call moet doen
Soms is er echt geen alternatief. Je moet nmap
aanroepen, of pandoc, of een obscure legacy tool waar geen
library voor bestaat. In dat geval:
Gebruik parameterized execution:
# FOUT:
os.system("ping -c 4 " + user_input)
# FOUT (shell=True):
subprocess.call("ping -c 4 " + user_input, shell=True)
# GOED:
subprocess.call(["ping", "-c", "4", user_input])De cruciale parameter is shell=False (de default in
Python’s subprocess). Als je de argumenten als een lijst
meegeeft in plaats van als een string, dan worden ze niet door een shell
geinterpreteerd. De speciale tekens ;, |,
&&, etc. worden behandeld als letterlijke
karakters, niet als operatoren.
In PHP:
// FOUT:
system("ping -c 4 " . $_GET['ip']);
// GOED:
$ip = escapeshellarg($_GET['ip']);
system("ping -c 4 " . $ip);
// BETER:
$output = [];
exec("ping -c 4 " . escapeshellarg($_GET['ip']), $output);escapeshellarg() wikkelt de input in enkele quotes en
escaped alle bestaande quotes. Niet waterdicht, maar veel beter dan
niets.
Regel 3: Whitelist input
Valideer je input tegen een whitelist. Als je een IP-adres verwacht, controleer dan of de input er ook daadwerkelijk uitziet als een IP-adres:
import re
def is_valid_ip(ip):
pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
if not re.match(pattern, ip):
return False
parts = ip.split('.')
return all(0 <= int(p) <= 255 for p in parts)
user_ip = request.form.get('ip', '')
if not is_valid_ip(user_ip):
return "Ongeldig IP-adres", 400
subprocess.call(["ping", "-c", "4", user_ip])Dit is defens-in-depth. Zelfs als er een bug zit in hoe je het commando aanroept, kan een aanvaller er niets mee als de input beperkt is tot geldige IP-adressen.
Regel 4: Least privilege
Draai je webapplicatie niet als root. Draai het als een beperkte gebruiker met minimale rechten. Als iemand toch command injection vindt, kan die persoon dan tenminste niet meteen het hele systeem overnemen.
In de praktijk: een eigen service-account, geen schrijfrechten buiten de applicatie-directory, geen sudo-rechten, beperkte netwerk-toegang.
Regel 5: Sandboxing
Containers (Docker), seccomp-profielen, AppArmor, SELinux – gebruik ze. Ze voorkomen command injection niet, maar ze beperken de schade. Een aanvaller in een Docker-container kan veel minder dan een aanvaller op de bare metal host.
De ongemakkelijke waarheid
En dan nu even een eerlijk woord over developers die
os.system() gebruiken.
Ik snap het. Ik snap het echt. Je hebt een deadline. Je manager wil
die feature morgen live hebben. Je moet even snel een ping uitvoeren,
een bestand converteren, een rapport genereren. En
os.system("ping " + ip) is zo verdomd makkelijk. Het werkt.
Het doet precies wat je wilt. In drie regels code heb je het voor
elkaar, terwijl de “juiste” manier met libraries en input-validatie en
subprocess met argumenten-als-lijst drie keer zo lang duurt.
Maar weet je wat ook makkelijk is? Je voordeur open laten staan. Dat bespaart je elke dag vijf seconden zoeken naar je sleutels. En op 364 dagen per jaar gaat dat prima. Maar op die ene dag dat er iemand binnenloopt die er niet hoort te zijn, heb je een probleem. En dan zeg je: “Maar het was zo makkelijk om de deur open te laten!”
Het is dezelfde logica. Dezelfde luiheid. Hetzelfde kortzichtige
denken dat ervoor zorgt dat we in 2026 nog steeds command injection
kwetsbaarheden vinden in productie-applicaties. We weten al sinds de
jaren negentig hoe dit werkt. We weten al dertig jaar hoe je het
voorkomt. En toch, elke keer weer, pakt een developer
os.system() en plakt er user input achter.
Het is niet eens incompetentie op dit punt. Het is traditie. Het is
een ambacht dat van generatie op generatie wordt doorgegeven: de kunst
van het slordig programmeren. Ergens op een universiteit zit een
professor die studenten leert hoe ze system() moeten
gebruiken, en die vergeet erbij te vertellen dat het een geladen pistool
is. En die studenten worden developers. En die developers bouwen
applicaties. En die applicaties draaien in productie. En dan komen wij
langs met een puntkomma en een id-commando, en dan is het:
“Oh nee, hoe kan dit? Wie had dit kunnen voorzien?”
Iedereen. Iedereen had dit kunnen voorzien. Want het staat in elke security training, elke OWASP-lijst, elk boek over veilig programmeren. Het staat waarschijnlijk ook op de muur van de koffieruimte bij het bedrijf dat je heeft ingehuurd om een pentest te doen. Maar niemand leest die muur. Net zoals niemand de voorwaarden leest. Net zoals niemand de documentatie leest.
En dan vragen ze ons: “Is het erg?”
Ja. Het is erg. Je hebt iemand root-toegang gegeven tot je server via een webformulier. Dat is als een bank die een gat in de kluisdeur boort zodat klanten makkelijker bij hun geld kunnen.
Referentietabel
Injection operatoren
| Operator | Syntax | Werking | Platform |
|---|---|---|---|
| Semicolon | cmd1;cmd2 |
Voer beide uit (ongeacht resultaat) | Linux |
| Ampersand | cmd1 & cmd2 |
Voer beide uit (ongeacht resultaat) | Windows |
| Pipe | cmd1\|cmd2 |
Output cmd1 als input cmd2 | Beide |
| AND | cmd1&&cmd2 |
cmd2 alleen als cmd1 slaagt | Beide |
| OR | cmd1\|\|cmd2 |
cmd2 alleen als cmd1 faalt | Beide |
| Backtick | `cmd` |
Inline substitutie | Linux |
| Dollar | $(cmd) |
Inline substitutie | Linux, PS |
| Newline | %0a |
Commandoscheiding | Beide |
URL-encoding voor HTTP parameters
| Teken | Encoding |
|---|---|
& |
%26 |
\| |
%7c |
; |
%3b |
| Spatie | + of %20 |
' |
%27 |
" |
%22 |
` |
%60 |
$ |
%24 |
( |
%28 |
) |
%29 |
{ |
%7b |
} |
%7d |
\n |
%0a |
Spatie-filter hardening
| Techniek | Voorbeeld | Werking |
|---|---|---|
| IFS | cat${IFS}/etc/passwd |
Internal Field Separator |
| Input redirect | cat</etc/passwd |
Redirect als input |
| Brace expansion | {cat,/etc/passwd} |
Bash brace expansion |
| Hex spatie | X=$'\x20';cat${X}file |
Hex-encoded spatie in variabele |
| Tab | cat%09/etc/passwd |
Tab als whitespace |
Keyword-filter hardening
| Techniek | Voorbeeld | Werking |
|---|---|---|
| Enkele quotes | c'a't file |
Lege quotes worden gestript |
| Dubbele quotes | c"a"t file |
Lege quotes worden gestript |
| Backslash | c\at file |
Backslash voor normaal karakter |
| Wildcard | /bin/c?t file |
? matcht een karakter |
| Variabele | a=/etc/passwd;cat $a |
Waarde in variabele |
| Base64 | echo aWQ=\|base64 -d\|bash |
Base64 decodering |
| Hex | echo -e '\x69\x64'\|bash |
Hex decodering |
Veilige command-executie validatie
Gebruik in productie uitsluitend defensieve validatietests (allowlist-validatie, logging-controle en error-handling), zonder shell-payloads of offensieve testcommando’s.
Verdedigingsmaatregelen
| Maatregel | Prioriteit | Effectiviteit |
|---|---|---|
| Geen system calls gebruiken | Kritiek | Elimineert het probleem |
Parameterized execution (shell=False) |
Hoog | Voorkomt operator-interpretatie |
| Input whitelisting | Hoog | Beperkt aanvalsvlak |
escapeshellarg() (PHP) |
Medium | Escaped speciale tekens |
| Least privilege | Medium | Beperkt schade |
| Sandboxing (Docker, seccomp) | Medium | Beperkt post-exploitation |
| WAF-regels | Laag | Te omzeilen, maar vertraagt aanvaller |
Volgende hoofdstuk: Cross-Site Request Forgery – of hoe je iemand anders jouw vuile werk laat doen.
Verder lezen in de kennisbank
Deze artikelen in het portaal geven je meer achtergrond en praktische context:
- API's — de onzichtbare lijm van het internet
- SSL/TLS — waarom dat slotje in je browser ertoe doet
- Encryptie — de kunst van het onleesbaar maken
- Wachtwoord-hashing — hoe websites je wachtwoord opslaan
- Penetratietesten vs. vulnerability scans
Je hebt een account nodig om de kennisbank te openen. Inloggen of registreren.
Gerelateerde securitymaatregelen
Deze artikelen bieden aanvullende context en verdieping: