sql >> Base de Datos >  >> RDS >> Mysql

Cambiar a declaraciones preparadas

He estado en la misma situación. También estaba usando declaraciones concatenadas, luego cambié mi aplicación a declaraciones preparadas.

las malas noticias es que va a cambiar cada instrucción SQL creada mediante la concatenación de datos del cliente a la instrucción SQL, que casi serán todas las instrucciones SQL que tenga en sus 50 archivos fuente.

las buenas noticias es la ganancia de cambiar a declaraciones preparadas no tiene precio, por ejemplo:

1-nunca estarás preocupado por algo llamado "ataque de inyección SQL"

el manual dice

Para mí, esa razón -tranquilidad- es suficiente para pagar el costo de cambiar mi código fuente. , ahora sus clientes pueden escribir en un campo de nombre de formulario robert; DROP table students; -- ;) y te sientes seguro de que nada va a pasar

2- ya no necesita escapar de los parámetros del cliente. puede usarlos directamente en la instrucción SQL, algo como:

$query = "SELECT FROM user WHERE id = ?";
$vars[] = $_POST['id'];

en lugar de

$id = $mysqli->real_escape_string($_POST['id']);
$query = "SELECT FROM user WHERE id = $id";

que es algo que tenía que hacer antes de usar declaraciones preparadas, lo que lo ponía en peligro de olvidarse de escapar de un parámetro como un ser humano normal. y todo lo que necesita un atacante para corromper su sistema es solo 1 parámetro sin escape.

Cambiar el código

Por lo general, cambiar los archivos de origen siempre es arriesgado y doloroso, especialmente si el diseño de su software es malo y si no tiene un plan de prueba obvio. pero te diré lo que hice para que sea lo más fácil posible.

Hice una función que va a usar cada código de interacción con la base de datos, así que puedes cambiar lo que quieras más tarde en un lugar -esa función- puedes hacer algo como esto

class SystemModel
{
    /**
     * @param string $query
     * @param string $types
     * @param array $vars
     * @param \mysqli $conn
     * @return boolean|$stmt
     */
    public function preparedQuery($query,$types, array $vars, $conn)
    {
        if (count($vars) > 0) {
            $hasVars = true;
        }
        array_unshift($vars, $types);
        $stmt = $conn->prepare($query);
        if (! $stmt) {
            return false;
        }
        if (isset($hasVars)) {
            if (! call_user_func_array(array( $stmt, 'bind_param'), $this->refValues($vars))) {
                return false;
            }
        }
        $stmt->execute();
        return $stmt;
    }

    /* used only inside preparedQuery */
    /* code taken from: https://stackoverflow.com/a/13572647/5407848 */
    protected function refValues($arr)
    {
        if (strnatcmp(phpversion(), '5.3') >= 0) {
            $refs = array();
            foreach ($arr as $key => $value)
                $refs[$key] = &$arr[$key];
                return $refs;
        }
        return $arr;
    }
}

Ahora, puede usar esta interfaz en cualquier lugar que desee en sus archivos fuente, por ejemplo, cambiemos sus declaraciones SQL actuales que ha proporcionado en la pregunta. Cambiemos esto

$mysqli = new mysqli('localhost', "root", "", "testdb");
$addresult = "
                SELECT a.firstnames, a.surname, a.schoolrole, a.datejoined 
                FROM teachers a LEFT JOIN schools b ON a.schoolid = b.id 
                WHERE b.id = '".$inputvalues['schoolid']."'";

if( $result = $mysqli->query($addresult) ) {
    while($row = $result->fetch_all())
    {
        $returnResult = $row;
    }
}

En esto

$mysqli = new mysqli('localhost', "root", "", "testdb");
$sysModel = new SystemModel();
$addresult = "
                SELECT a.firstnames, a.surname, a.schoolrole, a.datejoined
                FROM teachers a LEFT JOIN schools b ON a.schoolid = b.id
                WHERE b.id = ?";
$types = "i"; // for more information on paramters types, please check :
//https://php.net/manual/en/mysqli-stmt.bind-param.php
$vars = [];
$vars[] = $inputvalues['schoolid'];

$stmt = $sysModel->preparedQuery($addresult, $types, $vars, $mysqli);
if (!$stmt || $stmt->errno) {
   die('error'); // TODO: change later for a better illustrative output
}
$result = $stmt->get_result();
$returnResult = [];
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
    $returnResult[] = $row;
}

Sí, el ataque de inyección Sql se aplica concatenando una cadena incorrecta a su declaración SQL. donde es un INSERT , SELECT , DELETE , UPDATE . por ejemplo

$query = "SELECT * FROM user WHERE name = '{$_GET['name']}' AND password = '{$_GET['pass']}'"

algo así podría ser explotado por

// exmaple.com?name=me&pass=1' OR 1=1; -- 

lo que resultará en una declaración SQL

$query = "SELECT * FROM user WHERE name = 'me' AND password = '1' OR 1=1; -- '"
//executing the SQL statement and getting the result
if($result->num_rows){
    //user is authentic
}else{
    //wrong password
}
// that SQL will always get results from the table which will be considered a correct password

Buena suerte con el cambio de su software a declaraciones preparadas, y recuerde que la tranquilidad que obtendrá al saber que, pase lo que pase, está a salvo de los ataques de inyección SQL vale la pena el costo de cambiar los archivos fuente