Mysql
 sql >> Base de données >  >> RDS >> Mysql

Passer aux relevés préparés

J'ai été dans la même situation. J'utilisais également des instructions concaténées, puis j'ai basculé mon application vers des instructions préparées.

la mauvaise nouvelle est que vous allez modifier chaque instruction SQL construite en concaténant les données client à l'instruction SQL, qui sera presque chaque instruction SQL que vous avez dans vos 50 fichiers source.

la bonne nouvelle est-ce que le gain du passage aux relevés préparés est inestimable, par exemple :

1-vous ne vous inquiéterez jamais de ce qu'on appelle "l'attaque par injection SQL"

le manuel dit

Pour moi, cette raison -la tranquillité d'esprit- est suffisante pour payer le coût de la modification de mon code source. , maintenant vos clients peuvent saisir un champ de nom de formulaire robert; DROP table students; -- ;) et vous vous sentez en sécurité que rien ne se passera

2- vous n'avez plus besoin d'échapper aux paramètres du client. vous pouvez les utiliser directement dans l'instruction SQL, quelque chose comme :

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

au lieu de

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

ce que vous deviez faire avant d'utiliser des instructions préparées, ce qui vous mettait en danger d'oublier d'échapper à un paramètre en tant qu'être humain normal. et tout ce qu'il faut à un attaquant pour corrompre votre système, c'est juste 1 paramètre non échappé.

Changer le code

généralement, la modification des fichiers source est toujours risquée et pénible, surtout si la conception de votre logiciel est mauvaise et si vous n'avez pas de plan de test évident. mais je vais vous dire ce que j'ai fait pour que ce soit le plus simple possible.

J'ai créé une fonction que chaque code d'interaction de base de données va utiliser, afin que vous puissiez modifier ce que vous voulez plus tard à un endroit - cette fonction - vous pouvez faire quelque chose comme ça

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

Maintenant, vous pouvez utiliser cette interface où vous voulez dans vos fichiers source, par exemple changeons vos instructions SQL actuelles que vous avez fournies dans la question. Changeons cela

$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;
    }
}

Dans ce

$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;
}

Oui, l'attaque Sql Injection est appliquée en concaténant une mauvaise chaîne à votre instruction SQL. où il s'agit d'un INSERT , SELECT , DELETE , UPDATE . par exemple

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

quelque chose comme ça pourrait être exploité par

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

qui se traduira par une instruction 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

Bonne chance pour passer votre logiciel aux instructions préparées, et rappelez-vous que la tranquillité d'esprit que vous obtiendrez en sachant que quoi qu'il arrive, vous êtes à l'abri des attaques par injection SQL vaut le coût de la modification des fichiers source