¿Tengo que verificar más si uso sentencias preparadas para enteros?

7

Actualmente estoy usando PDO con declaraciones preparadas para algunos valores enteros (consulte PDO::PARAM_INT ). Eso significa que llamo PDO así:

$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();

¿Tengo que realizar comprobaciones en $_POST['gameID'] ? ¿Qué vulnerabilidades podrían existir?

DOP y enteros

Krzysztof Kotowicz hizo un gran comentario. Dijo que la DOP trata a los enteros como cadenas. Así que habilité el MySQL general_log:

  1. abre /etc/mysql/my.cnf
  2. establezca general_log en 1 y agregue la ruta a general_log_file (por ejemplo, /var/log/mysql/mysql.log )
  3. Reinicie su servidor MySQL: sudo /etc/init.d/mysql restart
  4. Eche un vistazo al archivo: tail -f /var/log/mysql/mysql.log
  5. Llame al siguiente fragmento de código:

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);

Este es el resultado:

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  

Obviamente, no llega a un int en el lado de PHP (PHP Versión 5.3.2-1ubuntu4.10)

    
pregunta Martin Thoma 14.11.2011 - 06:53
fuente

1 respuesta

7

Primero que nada: no estás haciendo un uso completo de las declaraciones preparadas. Eso podría exponerlo a problemas de inyección SQL.

No deberías estar usando concatenación de cadenas para construir el argumento a $conn->prepare() . En su lugar, debe utilizar una constante de cadena como la consulta SQL y confiar en bindValue() para completar los valores dinámicos. En un mundo ideal, podrías escribir algo como esto:

$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();

Sin embargo, las declaraciones de la DOP preparadas no le permiten especificar el nombre de la tabla de esta manera, por lo que se ve obligado a utilizar la concatenación de cadenas para el nombre de la tabla, de esta manera:

$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();

El uso de la concatenación de cadenas con cualquier cadena no constante para crear el primer parámetro para prepare() introduce un peligro de ataques de inyección de SQL, por lo que debe tener cuidado aquí. Debe comprobar cuidadosamente que GAMES_TABLE es una constante y el atacante nunca puede influenciarlo (podría considerar incorporarlo). Además, es posible que desee sanitize / escape it por si acaso , como en el código que he mostrado arriba. Es un límite molesto de declaraciones preparadas que no pueden manejar esto por usted.

Regla de oro: las declaraciones preparadas están a salvo de la inyección de SQL, siempre que la consulta SQL en sí sea una cadena constante.

Lamentablemente, puede descubrir algunos casos en los que las declaraciones preparadas son demasiado limitadas, como los parámetros dinámicos ORDER BY o LIMIT, así como los nombres de tablas dinámicas, como se ilustra arriba. Para estos casos, probablemente necesitará utilizar concatenación de cadenas. En estos casos, debe ser extremadamente cuidadoso, ya que el uso de concatenación de cadenas reintroduce el riesgo de vulnerabilidades de inyección de SQL.

En cualquier caso, aún debe usar declaraciones preparadas para todo lo que pueda, como por ejemplo, USER_ID , ya que es menos propenso a errores que hacer la concatenación de cadenas usted mismo. Además, dado que las declaraciones preparadas facilitan la verificación de que ha evitado las fallas de inyección de SQL, le conviene utilizar declaraciones preparadas en lugar de concatenación de cadenas en todos los lugares posibles.

Verificaciones adicionales: Para responder a su pregunta original, si PDO implementó su API correctamente, no necesitaría ninguna otra verificación en un valor entero para evitar problemas de inyección de SQL. Desafortunadamente, @Krzysztof Kotowicz ha señalado que la biblioteca de PDO de PHP tiene errores: ignora el argumento PDO::PARAM_INT y trata todo como una cadena, independientemente. Por lo tanto, probablemente debería hacer la codificación adicional que se necesita para compensar esta falla en la biblioteca de DOP. En particular, debe forzar (emitir) el valor a un entero, antes de pasarlo. Por lo tanto, sugiero algo como lo siguiente:

// 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();

Por separado, debe asegurarse de que, si un atacante puede adivinar un ID de juego válido, no lo ayude a evitar ninguna verificación de control de acceso en su código. Consulte, por ejemplo, la categoría de vulnerabilidad del objeto directo inseguro de OWASP.

Para los valores de cadena, también podría ser prudente sanear los datos para asegurarse de que coincidan con alguna expresión regular que sea apropiada para ese valor.

    
respondido por el D.W. 14.11.2011 - 11:25
fuente

Lea otras preguntas en las etiquetas