jan-karel.nl
Home / Securitymaatregelen / Webbeveiliging / Command Injection Preventie

Command Injection Preventie

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.

Op de hoogte blijven?

Ontvang maandelijks cybersecurity-inzichten in je inbox.

← Webbeveiliging ← Home