Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Mudando para Declarações Preparadas


Eu estive na mesma situação. Eu também estava usando instruções concatenadas, então mudei meu aplicativo para instruções preparadas.

as más notícias é que você vai alterar todas as instruções SQL criadas concatenando dados do cliente para a instrução SQL, que será quase todas as instruções SQL que você tem em seus 50 arquivos de origem.

as boas notícias é o ganho de mudar para declarações preparadas é inestimável, por exemplo:

1-você nunca vai se preocupar com algo chamado "ataque de injeção de SQL"

o php manual diz

Para mim, essa razão - paz de espírito - é suficiente para pagar o custo de mudar meu código-fonte. , agora seus clientes podem digitar um campo de nome de formulário robert; DROP table students; -- ;) e você se sente seguro de que nada vai acontecer

2- você não precisa mais escapar dos parâmetros do cliente. você pode usá-los diretamente na instrução SQL, algo como:
$query = "SELECT FROM user WHERE id = ?";
$vars[] = $_POST['id'];

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

que é algo que você tinha que fazer antes de usar instruções preparadas, o que estava colocando você em perigo de esquecer de escapar de um parâmetro como um ser humano normal. e tudo o que é necessário para um invasor corromper seu sistema é apenas 1 parâmetro sem escape.

Alterando o Código


normalmente alterar os arquivos de origem é sempre arriscado e doloroso, especialmente se o design do software for ruim e se você não tiver um plano de teste óbvio. mas vou contar o que fiz para facilitar ao máximo.

Eu fiz uma função que todo código de interação do banco de dados vai usar, então você pode alterar o que quiser mais tarde em um lugar - essa função - você pode fazer algo assim
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;
    }
}

Agora, você pode usar essa interface em qualquer lugar que desejar em seus arquivos de origem, por exemplo, vamos alterar suas instruções SQL atuais que você forneceu na pergunta. Vamos mudar isso
$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;
    }
}

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

Sim, o ataque Sql Injection é aplicado concatenando uma string inválida à sua instrução SQL. para onde está um INSERT , SELECT , DELETE , UPDATE . por exemplo
$query = "SELECT * FROM user WHERE name = '{$_GET['name']}' AND password = '{$_GET['pass']}'"

algo assim poderia ser explorado por
// exmaple.com?name=me&pass=1' OR 1=1; -- 

que resultará em uma instrução 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

Boa sorte com a mudança de seu software para instruções preparadas e lembre-se de que a tranquilidade que você terá ao saber que, aconteça o que acontecer, você está protegido contra ataques de injeção de SQL vale o custo de alterar os arquivos de origem