Lyhyesti: SQL-injektion PHP:ssä estetään luotettavasti, kun arvoja ei koskaan rakenneta SQL-tekstiin vaan ne välitetään aina Prepared Statementeilla paikanvaraajien kautta. Sarake- ja taulukkonimille, joita ei voi välittää paikanvaraajina, pätee kiinteä sallittujen arvojen luettelo. Lisäksi minimaalisilla oikeuksilla varustettu tietokantakäyttäjä rajoittaa vahinkoa, jos aukko jää.
Miksi SQL-injektiota ei saa unohtaa PHP2Shellin jälkeen
Lokakuussa 2026 moni asia pyörii PHP2Shellin ympärillä: kaksi kriittistä haavoittuvuutta PHP-tulkissa, korjattu 21.10.2026 alkaen versioissa PHP 8.5.12, 8.4.27, 8.3.36 ja 8.2.35. Monet tiimit ovat näinä päivinä päivittäneet palvelimia ja käynnistäneet prosesseja uudelleen. Se on oikein ja kiireellistä.
Ajantasainen PHP-tila ei kuitenkaan kerro mitään omasta koodista. SQL-injektio on vuosia kuulunut verkkosovellusten yleisimpiin heikkouksiin, ja OWASP Top 10 käsittelee sitä Injection-luokassa. Joka suunnittelee joka tapauksessa tietoturvatarkistuksia, kannattaa käydä samalla läpi sovelluksen tietokantakäyttö.
Hyvä uutinen: SQL-injektio on harvoja heikkouksia, jotka voi sulkea lähes kokonaan pois yksinkertaisella, johdonmukaisesti noudatetulla säännöllä.
Miten SQL-injektio syntyy PHP:ssä?
SQL-injektio syntyy, kun sovellus käsittelee käyttäjän syötteitä osana SQL-komentoa eikä pelkkänä tietona. Tyypillistä on kyselyn kokoaminen tekstipalasista ja muuttujista. Jos kävijä voi vaikuttaa tällaisen muuttujan sisältöön, hän voi mahdollisesti muuttaa komennon merkityksen.
Seuraukset ulottuvat vieraiden asiakastietojen lukemisesta hintojen tai oikeuksien muuttamiseen ja kokonaisten taulujen poistamiseen. Tietokannasta ja oikeuksista riippuen aukko voi mahdollistaa myös pääsyn tietokannan ulkopuolelle. Jos henkilötiedot ovat vaarantuneet, GDPR:n mukainen ilmoitusvelvollisuus voi syntyä.
Syötteitä eivät ole vain lomakekentät. Myös URL-parametrit, evästeet, HTTP-otsakkeet, ladatut tiedostot, vieraiden rajapintojen vastaukset ja jo tallennetut tiedot omasta tietokannasta kuuluvat epäluotettaviin.
Prepared Statements PDO:lla ja mysqli:llä
Prepared Statements lähettävät SQL-komennon ja arvot erikseen tietokannalle. Arvoja ei koskaan tulkita osana komentoa. PDO:lla turvallinen kysely näyttää tältä:
<?php
$pdo = new PDO($dsn, $benutzer, $passwort, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare('SELECT id, name, email FROM kunden WHERE id = :id AND status = :status');
$stmt->execute([
'id' => (int) $_GET['id'],
'status' => 'aktiv',
]);
$kunde = $stmt->fetch();
Asetus PDO::ATTR_EMULATE_PREPARES => false varmistaa, että tietokanta käyttää aitoja Prepared Statementeja, jos ajuri tukee sitä. mysqli:llä pätee sama periaate:
<?php
$stmt = $mysqli->prepare('SELECT id, name FROM produkte WHERE kategorie = ? AND preis <= ?');
$stmt->bind_param('sd', $kategorie, $maxPreis);
$stmt->execute();
$ergebnis = $stmt->get_result();
Myös IN (...)-luettelot voi toteuttaa turvallisesti luomalla kullekin arvolle oman paikanvaraajan ja välittämällä arvot erikseen.
<?php
$ids = array_map('intval', $ausgewaehlteIds);
$platzhalter = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT id, name FROM produkte WHERE id IN ($platzhalter)");
$stmt->execute($ids);
Erikoistapaukset: sarakenimet, lajittelu ja raaka SQL
Paikanvaraajat toimivat vain arvoille, ei taulukkonimille, sarakenimille eikä lajittelusuunnille. Juuri tässä syntyy käytännössä paljon aukkoja, esimerkiksi lajiteltavissa taulukoissa tai suodattimissa. Ratkaisu on kiinteä luettelo sallituista arvoista:
<?php
$erlaubteSpalten = ['name' => 'name', 'preis' => 'preis', 'datum' => 'erstellt_am'];
$spalte = $erlaubteSpalten[$_GET['sort'] ?? ''] ?? 'name';
$richtung = (($_GET['dir'] ?? '') === 'desc') ? 'DESC' : 'ASC';
$stmt = $pdo->query("SELECT id, name, preis FROM produkte ORDER BY $spalte $richtung");
Viitekehyksillä ja CMS-järjestelmillä on omat työkalunsa. WordPressissä $wpdb->prepare() hoitaa tämän tehtävän, Laravelissa ja Symfonyssa query builder ja ORM:t. Heti kun käytätte siellä raakaa SQL:ää, esimerkiksi funktioilla, joiden nimessä on ”raw”, pätevät jälleen tämän kohdan säännöt. Laravel-vinkkejä löydätte sivulta Laravel-sovellusten suojaaminen.
Etsikää koodista kohdennetusti SQL-tekstejä, joihin muuttujia sijoitetaan. Helppo aloitus on haku query( ja ->raw yhdistettynä muuttujiin $_GET, $_POST tai $_REQUEST.
SQL-injektion jäljittäminen vanhassa koodissa
Uudessa ohjelmistossa sääntöä voi noudattaa alusta asti. Vaikeampaa se on sovelluksissa, jotka ovat kasvaneet vuosien mittaan. Siellä kannattaa tehdä järjestelmällinen haku kohtiin, joissa SQL-tekstit kootaan muuttujista:
# SQL-komennot, joihin sijoitetaan muuttujia
grep -rnE "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\\$[a-zA-Z_]" src/ --include="*.php" | head -50
# Pyyntötietojen suora käyttö kyselyfunktioissa
grep -rnE "(query|exec|raw)\s*\(.*\\\$_(GET|POST|REQUEST|COOKIE)" src/ --include="*.php"
Kaikki osumat eivät ole aukkoja, esimerkiksi jos muuttuja sisältää vain kiinteän taulukkonimen. Jokainen löytö kannattaa kuitenkin tarkistaa kerran tietoisesti ja tarvittaessa muuttaa Prepared Statementeiksi. Staattisen koodianalyysin työkalut auttavat tunnistamaan tällaiset mallit pysyvästi build-putkessa, jolloin uudet virheet eivät pääse tuotantoon.
Vahingon rajoittaminen: oikeudet, virheilmoitukset, tarkistus
Turvallinen koodi on ensimmäinen puolustuslinja. Muut tasot rajoittavat vahinkoa, jos aukko kuitenkin jää huomaamatta:
- Minimaaliset oikeudet: Verkkosivuston tietokantakäyttäjä tarvitsee yleensä vain oikeudet
SELECT,INSERT,UPDATEjaDELETEomaan tietokantaan, ei oikeuksia käyttäjien luomiseen tai muiden tietokantojen käyttöön. - Ei virheilmoituksia ulospäin:
display_errors = Offphp.ini-tiedostossa. Tietokantavirheet kuuluvat lokiin, ei selaimeen. - Erotus sovelluksittain: Jokainen verkkosivusto saa oman tietokantakäyttäjän.
- Pitäkää riippuvuudet ajan tasalla: Aukkoja on myös kirjastoissa, kuten opas composer audit osoittaa.
- Teettäkää tarkistus: Koodikatselmointi ja automaattinen analyysi löytävät monta kohtaa, penetraatiotesti näyttää, mikä on todella tavoitettavissa ulkopuolelta.
-- Esimerkki: tietokantakäyttäjä vain tarvittavilla oikeuksilla
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY '<starkes Passwort>';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';
Aiheeseen liittyvä heikkous koskee tietojen tulostamista selaimessa. Miten sen varmistatte, selittää opas Cross-Site Scriptingin estäminen PHP:ssä.
Miten PHP SOS auttaa konkreettisesti?
- Tarkistamme sovelluksenne tietokantakäytön järjestelmällisesti turvattomien kyselyjen varalta.
- Priorisoimme löydökset tavoitettavuuden ja koskettujen tietojen mukaan ja näytämme konkreettiset korjaukset.
- Nostamme tietokanta-oikeudet ja virheenkäsittelyn turvalliselle tasolle.
- Testaamme korjauksen jälkeen uudelleen ja dokumentoimme tuloksen.
Riippumattoman tarkistuksen kuvaa palvelu Tietoturva-auditointi ja penetraatiotesti.
Usein kysytyt kysymykset
Riittääkö, että syötteet käsitellään addslashes- tai real_escape_string-funktiolla?
Ei, se on virhealtista. Escapointi riippuu merkistöstä ja yhteydestä, ja se unohtuu helposti jostain kohdasta. Prepared Statements erottavat komennon ja tiedot periaatteessa toisistaan, ja siksi ne ovat suositeltu tapa.
Suojaako ORM automaattisesti SQL-injektiolta?
Pitkälti, kunhan käytätte ORM:n kyselytoimintoja. Heti kun kirjoitatte raakaa SQL:ää tai sijoitatte arvoja lajitteluihin ja sarakenimiin, pätevät samat säännöt kuin ilman ORM:ää.
Auttaako Web Application Firewall SQL-injektiota vastaan?
WAF voi pysäyttää tunnettuja malleja ja tuoda lisäaikaa. Se ei kuitenkaan korvaa turvallista koodia, sillä pyyntötietoihin perustuvat säännöt voi kiertää ja ne osuvat joskus laillisiin syötteisiin.
Sulkeeko PHP2Shellia vastaan tehtävä PHP-päivitys myös SQL-injektiohaavoittuvuudet?
Ei. PHP2Shell koskee PHP-tulkkia. SQL-injektio on sovelluskoodin virhe, ja se säilyy riippumatta siitä, mikä PHP-versio on käytössä.