Devo fare ulteriori controlli se utilizzo istruzioni preparate per interi?

7

Attualmente sto usando PDO con istruzioni preparate per alcuni valori interi (vedi PDO::PARAM_INT ). Ciò significa che chiamo PDO in questo modo:

$stmt = $conn->prepare("SELECT 'lastMove' FROM ".GAMES_TABLE.
                " WHERE 'id'=:gameID AND ('whiteUserID' = ".USER_ID.
                " OR 'blackUserID' = ".USER_ID.") LIMIT 1");
$stmt->bindValue(':gameID', $_POST['gameID'], PDO::PARAM_INT);
$stmt->execute();

Devo fare qualche controllo su $_POST['gameID'] ? Quali vulnerabilità potrebbero esistere?

PDO e interi

Krzysztof Kotowicz ha fatto un grande commento. Ha detto che PDO tratta gli interi come stringhe. Così ho abilitato MySQL general_log:

  1. apri /etc/mysql/my.cnf
  2. imposta general_log su 1 e aggiungi il percorso a general_log_file (ad esempio /var/log/mysql/mysql.log )
  3. Riavvia il tuo server MySQL: sudo /etc/init.d/mysql restart
  4. Dai un'occhiata al file: tail -f /var/log/mysql/mysql.log
  5. Chiama il seguente frammento:

test.php:

$stmt = $conn->prepare("SELECT 'lastMove' FROM ".GAMES_TABLE.
                " WHERE 'id'=:gameID AND ('whiteUserID' = :uid".
                " OR 'blackUserID' = :uid) LIMIT 1");
$stmt->bindValue(':gameID', "abc", PDO::PARAM_INT);
$stmt->bindValue(':uid', "1'", PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);

Questo è il risultato:

111114 17:32:54   241 Connect   chessuser@localhost on chess
          241 Query SELECT 'user_id' FROM 'chess_users' WHERE 'user_id'='1'  LIMIT 1
          241 Query SELECT 'lastMove' FROM chess_games WHERE 'id'='abc' AND ('whiteUserID' = '1\'' OR 'blackUserID' = '1\'') LIMIT 1
          241 Quit  

Quindi in modo odioso non arriva ad un int sul lato PHP (PHP versione 5.3.2-1ubuntu4.10)

    
posta Martin Thoma 14.11.2011 - 06:53
fonte

1 risposta

7

Primo: non stai facendo pieno uso di dichiarazioni preparate. Ciò potrebbe esporvi a problemi di SQL injection.

Non dovresti utilizzare la concatenazione di stringhe per creare l'argomento a $conn->prepare() . Invece, dovresti utilizzare una costante di stringa come query SQL e fare affidamento su bindValue() per riempire i valori dinamici. In un mondo ideale potresti scrivere qualcosa del genere:

$stmt = $conn->prepare("SELECT 'lastMove' FROM :table".
            " WHERE 'id'=:gameID AND ('whiteUserID' = :userid".
            " OR 'blackUserID' = :userid) LIMIT 1");
$stmt->bindValue(':gameID', $_POST['gameID'], PDO::PARAM_INT);
$stmt->bindValue(':table', GAMES_TABLE, PDO::PARAM_STR);
$stmt->bindValue(':userid', USER_ID, PDO::PARAM_STR);
$stmt->execute();

Tuttavia, le istruzioni preparate da PD non consentono di specificare il nome della tabella in questo modo, quindi sei costretto a utilizzare la concatenazione di stringhe per il nome della tabella, in questo modo:

$stmt = $conn->prepare("SELECT 'lastMove' FROM ".
            mysql_real_escape_string(GAMES_TABLE).
            " WHERE 'id'=:gameID AND ('whiteUserID' = :userid".
            " OR 'blackUserID' = :userid) LIMIT 1");
$stmt->bindValue(':gameID', $_POST['gameID'], PDO::PARAM_INT);
$stmt->bindValue(':userid', USER_ID, PDO::PARAM_STR);
$stmt->execute();

L'utilizzo della concatenazione di stringhe con qualsiasi stringa non costante per creare il primo parametro in prepare() introduce il rischio di attacchi SQL injection, quindi è necessario fare attenzione qui. Devi controllare attentamente che GAMES_TABLE sia una costante e non possa mai essere influenzata dall'attaccante (potresti prendere in considerazione di integrarlo). Inoltre, potresti voler disinfettare / fuggire nel caso , come nel codice che ho mostrato sopra. È un limite fastidioso di affermazioni preparate che non possono gestirlo per te.

Regola empirica: le istruzioni preparate sono al sicuro dall'iniezione SQL, purché la query SQL stessa sia una stringa costante.

Sfortunatamente, potresti scoprire alcuni casi in cui le istruzioni preparate sono troppo limitanti, come i parametri dinamici ORDER BY o LIMIT, così come i nomi dinamici delle tabelle, come illustrato sopra. In questi casi, probabilmente dovrai utilizzare la concatenazione di stringhe. In questi casi, devi stare estremamente attento, poiché l'utilizzo della concatenazione di stringhe reintroduce il rischio di vulnerabilità di SQL injection.

In ogni caso, dovresti comunque utilizzare le istruzioni preparate per tutto ciò che puoi, come per USER_ID , poiché è meno soggetto a errori rispetto alla concatenazione di stringhe tu stesso. Inoltre, poiché le istruzioni preparate rendono più semplice verificare che siano stati eliminati i difetti di SQL injection, è vantaggioso utilizzare istruzioni preparate anziché concatenazioni di stringhe ovunque sia possibile.

Controlli aggiuntivi: per rispondere alla tua domanda originale, se PDO implementava correttamente la sua API, non avresti bisogno di altri controlli su un valore intero per evitare problemi di SQL injection. Sfortunatamente, @Krzysztof Kotowicz ha sottolineato che la libreria PHP PDO è bacata: ignora l'argomento PDO::PARAM_INT e tratta tutto come una stringa, a prescindere. Pertanto, probabilmente dovresti fare la codifica aggiuntiva necessaria per recuperare questo difetto nella libreria PDO. In particolare, dovresti forzare (cast) il valore a un numero intero, prima di passarlo. Quindi, suggerisco qualcosa di simile al seguente:

// don't use $_POST['gameID'] elsewhere; use $gameID
$gameID = (int) $_POST['gameID'];

$stmt = $conn->prepare("SELECT 'lastMove' FROM ".
            mysql_real_escape_string(GAMES_TABLE).
            " WHERE 'id'=:gameID AND ('whiteUserID' = :userid".
            " OR 'blackUserID' = :userid) LIMIT 1");
$stmt->bindValue(':gameID', $gameID, PDO::PARAM_INT);
$stmt->bindValue(':userid', USER_ID, PDO::PARAM_STR);
$stmt->execute();

Separatamente, è necessario assicurarsi che, se un utente malintenzionato può indovinare un ID di gioco valido, non aiuta l'utente malintenzionato a ignorare eventuali controlli di controllo dell'accesso nel codice. Vedi, ad esempio, la categoria di vulnerabilità di riferimento diretto di

.

Per i valori stringa, potrebbe anche essere prudente disinfettare i dati per assicurarsi che corrisponda ad alcune espressioni regolari che sono appropriate per quel valore.

    
risposta data 14.11.2011 - 11:25
fonte

Leggi altre domande sui tag