Direct naar inhoud

OS command injection

CWE-78OWASP A03:2021Bijgewerkt 31 augustus 20266 min leestijd

OS command injection is een kwetsbaarheid waarbij een aanvaller eigen commando's van het besturingssysteem laat uitvoeren, doordat zijn invoer via een shell in een systeemaanroep terechtkomt. Leestekens als de puntkomma of de pipe starten daar een tweede commando. De structurele oplossing is het programma aanroepen met een argumentarray, zonder shell.

Veel applicaties roepen achter de schermen een programma van het besturingssysteem aan: een ping voor een netwerkcontrole, ImageMagick om een afbeelding te verkleinen, een converter die een document naar PDF omzet. Zodra gebruikersinvoer in zo’n aanroep belandt zonder strikte scheiding tussen commando en argument, ontstaat OS command injection. Hieronder leest u hoe dat werkt en wat u eraan doet.

Wat is OS command injection?

OS command injection is een kwetsbaarheid waarbij een aanvaller eigen commando’s op het besturingssysteem van de server laat uitvoeren, doordat zijn invoer via een shell in een systeemaanroep terechtkomt. De afkorting OS staat voor operating system: het gaat om commando’s die naast uw applicatie op de server draaien, met de rechten van het applicatieproces.

Een alledaagse vergelijking: u geeft een koerier een briefje mee met een adres erop. Staat daar alleen een adres, dan levert hij het pakket netjes af. Schrijft iemand achter dat adres “en geef daarna de sleutel van het magazijn af”, dan voert een koerier die het briefje regel voor regel afwerkt ook die tweede opdracht uit. Een shell is die koerier: hij leest een tekstregel en ziet in een leesteken als de puntkomma het begin van een nieuwe opdracht.

Die leestekens heten shell-metatekens: de puntkomma, de pipe, de ampersand, een regeleinde en de constructies met backticks of met een dollarteken gevolgd door haakjes. Ze scheiden commando’s of voeren een commando uit binnen een ander. Zolang uw invoer als losse tekst bij een shell aankomt, kan die onmogelijk weten dat u die tekens als gewone data bedoelde.

Command injection wordt vaak in één adem genoemd met remote code execution (RCE). Command injection beschrijft de route: invoer bereikt een shell en start daar een tweede commando. RCE beschrijft de uitkomst: willekeurige code op uw server, ongeacht de route erheen. Command injection is dus één van de wegen naar RCE; onveilige deserialisatie of een ongecontroleerde bestandsupload zijn andere.

Hoe werkt een OS command injection-aanval?

Neem een beheerderspaneel met een netwerkcheck. De gebruiker vult een hostnaam in, de server pingt die host en toont de uitvoer. De ontwikkelaar bouwt de commandoregel op als tekst en geeft die aan de shell.

Kwetsbaar:

const { exec } = require("node:child_process");

app.get("/diagnostics/ping", (req, res) => {
  const host = req.query.host;

  // De volledige tekstregel gaat naar /bin/sh
  exec("ping -c 1 " + host, (err, stdout) => {
    res.type("text/plain").send(stdout);
  });
});

Een gewone gebruiker vult example.com in en krijgt het verwachte resultaat. Een aanvaller laat achter de hostnaam een puntkomma volgen, met daarachter zijn eigen commando. In de HTTP-aanvraag ziet dat er zo uit, met de puntkomma URL-gecodeerd als %3B:

GET /diagnostics/ping?host=example.com%3Bid HTTP/1.1
Host: app.example.com

De applicatie plakt die waarde ongewijzigd achter ping -c 1. Wat er daadwerkelijk wordt uitgevoerd, is dit:

ping -c 1 example.com;id

De shell ziet twee opdrachten in plaats van één: hij pingt de host en voert daarna id uit, waarvan de uitvoer in de respons terechtkomt. Vanaf dat moment is alles mogelijk wat het applicatieproces mag: bestanden lezen, een omgekeerde verbinding opzetten, sleutels uit omgevingsvariabelen halen.

De oplossing is het programma niet via een shell aan te roepen, maar rechtstreeks, met een lijst van argumenten. Elk element van die lijst is per definitie één argument; leestekens erin hebben geen speciale betekenis meer. In Node.js doet execFile dat, in Python subprocess.run met een lijst en zonder shell=True, in Java de ProcessBuilder.

Veilig:

const { execFile } = require("node:child_process");

app.get("/diagnostics/ping", (req, res) => {
  const host = req.query.host;

  // Allowlist: begint met een letter of cijfer, verder alleen punt en koppelteken
  if (typeof host !== "string" || !/^[a-z0-9][a-z0-9.-]{0,253}$/i.test(host)) {
    return res.status(400).send("Ongeldige hostnaam");
  }

  // Geen shell: host blijft precies één argument
  execFile("ping", ["-c", "1", host], (err, stdout) => {
    res.type("text/plain").send(stdout);
  });
});

Nu is example.com;id simpelweg een hostnaam die niet bestaat. Er is geen shell die de puntkomma interpreteert, en de allowlist weigert de waarde al bij de voordeur. De argumentarray haalt de betekenis uit de metatekens, de validatie beperkt wat er binnenkomt.

Ook zonder shell blijft één risico staan: een waarde die met een koppelteken begint, kan door het aangeroepen programma als optie in plaats van als argument worden gelezen. Een aanvaller stuurt daarmee het gedrag van het programma, bijvoorbeeld door een uitvoerbestand af te dwingen. Sluit een koppelteken aan het begin dus expliciet uit in uw validatie.

Wat is de impact van OS command injection?

De impact is vrijwel altijd zwaar, omdat de aanvaller de applicatielaag verlaat en op de server zelf terechtkomt. Hij voert commando’s uit met de rechten van het applicatieproces: configuratiebestanden en sleutels lezen, databasewachtwoorden uit omgevingsvariabelen halen, broncode kopiëren. Meestal volgt een tweede fase, want de server is een vertrouwd punt in uw netwerk en mag praten met interne systemen die van buitenaf onbereikbaar zijn: databases, beheerinterfaces, de metadata-service van uw cloudprovider.

Zakelijk vertaalt dat zich naar het volledige spectrum: gegevensdiefstal met meldplicht, manipulatie van gegevens, uitval van de dienst of ransomware die zich via deze ingang naar binnen werkt. Omdat de ernst afhangt van de rechten van het proces en van wat de server verder mag bereiken, loopt de classificatie van hoog tot kritiek. Een injectie in een onbevoegd proces in een strak afgeschermde container is beheersbaar; dezelfde fout in een proces met ruime rechten en vrij uitgaand verkeer is dat niet.

Hoe spoort u OS command injection op?

Begin bij functionaliteit die naar een extern programma ruikt: netwerkdiagnostiek, thumbnails genereren, documenten omzetten, archieven uitpakken, back-ups en PDF-generatie. Kijk in de code naar de bekende aanroepen: exec, system, popen, shell_exec, passthru, Runtime.exec en elke variant met de optie om een shell te gebruiken.

Testen doet u door aan zo’n parameter een scheidingsteken en een onschuldig commando toe te voegen, bijvoorbeeld een puntkomma gevolgd door id, en daarna dezelfde constructie met een pipe of met backticks. Ziet u de uitvoer niet terug, dan is het geval blind. Dan werkt u met een meetbaar neveneffect: sleep 10 en de responstijd meten, of een uitgaand verzoek naar een domein dat u zelf beheert, waarna uw eigen DNS-logboek verraadt of de server contact zocht.

Scanners herkennen de standaardgevallen, maar missen de injecties achter authenticatie of in asynchrone verwerkingsstappen. Een grondige penetratietest combineert daarom automatisering met handmatig onderzoek en een blik op de code. AssistSec neemt dit onderzoek mee in een penetratietest en toont per bevinding aan welk commando daadwerkelijk werd uitgevoerd.

Hoe voorkomt u OS command injection?

  • Vermijd de shell volledig. Vraag u eerst af of dat externe programma nodig is; een bibliotheek in uw eigen taal die hetzelfde doet, kent dit probleem niet.
  • Roep programma’s aan met een argumentarray. Gebruik execFile of spawn in Node.js, subprocess.run met een lijst in Python, de ProcessBuilder in Java. Zet de shell-optie nooit aan.
  • Valideer invoer tegen een allowlist. Beschrijf wat een geldige waarde is en wijs al het andere af. Een zwarte lijst van tekens is altijd incompleet.
  • Werk met indirecte verwijzingen. Laat de gebruiker kiezen uit een vaste lijst en vertaal die keuze serverzijdig naar het echte pad of de echte optie.
  • Draai met minimale rechten en beperk uitgaand verkeer. Een onbevoegd account in een afgeschermde container maakt de vervolgstap na een injectie aanzienlijk moeilijker.
  • Log het aanmaken van processen en laat de code testen. Een webserver die plotseling curl of sh start, verdient onderzoek; een code review op alle systeemaanroepen en een gerichte penetratietest vangen wat scanners overslaan.

Bronnen

Veelgestelde vragen

Wat is het verschil tussen command injection en remote code execution?

Command injection beschrijft de route: invoer bereikt een shell en start daar een extra commando. Remote code execution beschrijft de uitkomst: een aanvaller voert willekeurige code uit op uw server. Elke command injection is dus een vorm van RCE, maar niet elke RCE ontstaat door command injection; onveilige deserialisatie of een kwetsbare template-engine leiden langs een andere weg tot hetzelfde resultaat.

Is command injection hetzelfde als SQL injection?

De oorzaak is identiek, het doelwit niet. Bij SQL injection eindigt de invoer in een databasequery, bij command injection in de shell van het besturingssysteem. Command injection is meestal ernstiger, omdat de aanvaller direct op de server terechtkomt en niet alleen in de database.

Beschermt escapeshellarg tegen command injection?

Deels. Een functie als escapeshellarg in PHP zet één waarde tussen aanhalingstekens en is nuttig, maar hij helpt niet als u de waarde buiten die aanhalingstekens plaatst en voorkomt niet dat een waarde als extra optie wordt gelezen. Het programma zonder shell aanroepen, met een lijst argumenten, is betrouwbaarder.

Hoe test ik of een parameter kwetsbaar is voor command injection?

Voeg aan een parameter die naar een systeemprogramma lijkt te gaan een scheidingsteken en een onschuldig commando toe, bijvoorbeeld een puntkomma gevolgd door id. Ziet u geen uitvoer, gebruik dan een vertraging zoals sleep 10 en meet de responstijd, of laat het commando een DNS-verzoek doen naar een domein dat u zelf beheert.

Verwante artikelen

Druk op / om te zoeken · Esc