如何防止PHP中的SQL注入?

如何防止PHP中的SQL注入?,php,mysql,sql,security,sql-injection,Php,Mysql,Sql,Security,Sql Injection,如果未经修改就将用户输入插入到SQL查询中,则应用程序易受攻击,如以下示例所示: $safe_variable=$_POST['user_input']; mysql_查询(“插入到`table`(`column`)值(`unsafe_variable'); 这是因为用户可以输入类似于value')的内容;升降台--,则查询变为: INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--') 如何防止这种情况发生?

如果未经修改就将用户输入插入到SQL查询中,则应用程序易受攻击,如以下示例所示:

$safe_variable=$_POST['user_input'];
mysql_查询(“插入到`table`(`column`)值(`unsafe_variable');
这是因为用户可以输入类似于
value')的内容;升降台--,则查询变为:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
如何防止这种情况发生?

我建议使用(PHP数据对象)来运行参数化SQL查询

这不仅可以防止SQL注入,还可以加快查询速度


通过使用PDO而不是
mysql
mysqli
pgsql
函数,您可以使您的应用程序从数据库中更抽象一点,而很少需要切换数据库提供程序。

您可以执行以下基本操作:

$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])
SELECT ... WHERE id = UNHEX('2d312075...3635');
这并不能解决所有问题,但它是一个很好的垫脚石。我遗漏了一些明显的项目,比如检查变量的存在性、格式(数字、字母等)

不推荐的警告: 这个答案的示例代码(与问题的示例代码一样)使用PHP的
MySQL
扩展,该扩展在PHP5.5.0中被弃用,在PHP7.0.0中被完全删除

安全警告:此答案不符合安全最佳做法,使用事先准备好的语句。使用下面概述的策略,风险自负。(另外,
mysql\u real\u escape\u string()
在PHP7中被删除。)

如果您使用的是最新版本的PHP,下面列出的
mysql\u real\u escape\u string
选项将不再可用(尽管
mysqli::escape\u string
是一个现代等效选项)。如今,
mysql\u real\u escape\u string
选项只适用于旧版本PHP上的遗留代码


您有两种选择—转义
不安全变量中的特殊字符
,或使用参数化查询。两者都可以防止SQL注入。参数化查询被认为是更好的做法,但在使用它之前,需要更改为PHP中较新的MySQL扩展

我们将首先讨论下冲击管柱

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect
另请参见函数的详细信息

要使用参数化查询,您需要使用而不是函数。为了重写您的示例,我们需要如下内容

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

您需要了解的关键功能是

此外,正如其他人所建议的,您可能会发现使用以下内容来提升抽象层很有用/更容易

请注意,您询问的案例相当简单,更复杂的案例可能需要更复杂的方法。特别是:

  • 如果您想根据用户输入改变SQL的结构,参数化查询将不会有帮助,而且所需的转义不在
    mysql\u real\u escape\u string
    中。在这种情况下,最好通过白名单传递用户的输入,以确保只允许“安全”值通过
  • 如果您在条件中使用用户输入的整数,并采用
    mysql\u real\u escape\u string
    方法,您将遇到下面注释中描述的问题。这种情况更为棘手,因为整数不会被引号包围,所以您可以通过验证用户输入只包含数字来处理
  • 可能还有其他我不知道的案例。您可能会发现,在您可能遇到的一些更微妙的问题上,这是一个有用的资源

使用准备好的语句和参数化查询。这些SQL语句与任何参数分开发送到数据库服务器并由数据库服务器解析。这样,攻击者就不可能注入恶意SQL

您基本上有两个选项来实现这一点:

  • 使用(对于任何受支持的数据库驱动程序):

    $stmt=$pdo->prepare('SELECT*FROM employees WHERE name=:name');
    $stmt->execute(['name'=>$name]);
    foreach($stmt作为$row){
    //用$row做点什么
    }
    
  • 使用(用于MySQL):

    $stmt=$dbConnection->prepare('SELECT*FROM employees WHERE name=?');
    $stmt->bind_参数('s',$name);/'s'指定变量类型=>'string'
    $stmt->execute();
    $result=$stmt->get_result();
    而($row=$result->fetch_assoc()){
    //用$row做点什么
    }
    
  • 如果要连接到MySQL以外的数据库,则可以参考特定于驱动程序的第二个选项(例如,PostgreSQL的
    pg_prepare()
    pg_execute()
    )。PDO是通用选项


    正确设置连接 请注意,当使用
    PDO
    访问MySQL数据库时,默认情况下不会使用real-prepared语句。要解决此问题,必须禁用对已准备语句的模拟。使用PDO创建连接的示例如下:

    $dbConnection=newpdo('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8','user','password');
    $dbConnection->setAttribute(PDO::ATTR\u EMULATE\u PREPARES,false);
    $dbConnection->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_异常);
    
    在上面的示例中,错误模式并非绝对必要,但建议添加它。这样,当出现错误时,脚本不会因出现
    致命错误而停止。它使开发人员有机会
    捕获
    任何错误,这些错误是
    抛出的
    n作为
    PDOException
    s

    然而,必需的是第一行
    setAttribute()
    ,它告诉PDO禁用模拟的准备语句,并使用真正的准备语句。这确保语句和值不会被解析
    $name_bad = "' OR 1'"; 
    
    $name_bad = mysql_real_escape_string($name_bad);
    
    $query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
    echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";
    
    
    $name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 
    
    $name_evil = mysql_real_escape_string($name_evil);
    
    $query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
    echo "Escaped Evil Injection: <br />" . $query_evil;
    
    $offset = isset($_GET['o']) ? $_GET['o'] : 0;
    $offset = mysql_real_escape_string($offset);
    RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");
    
    $order = isset($_GET['o']) ? $_GET['o'] : 'userid';
    $order = mysql_real_escape_string($order);
    RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");
    
    $orders  = array("name", "price", "qty"); // Field names
    $key = array_search($_GET['sort'], $orders)); // if we have such a name
    $orderby = $orders[$key]; // If not, first one will be set automatically. 
    $query = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe
    
    $orderby = white_list($_GET['orderby'], "name", ["name","price","qty"], "Invalid field name");
    $query  = "SELECT * FROM `table` ORDER BY `$orderby`"; // sound and safe
    
    $unsafe_variable = $_POST['user_id'];
    
    $safe_variable = (int)$unsafe_variable ;
    
    mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
    
    $query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";
    
    $_POST['email']= admin@emali.com' OR '1=1
    
    $query="select * from users where email='admin@emali.com' OR '1=1';
    
    SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'
    
    wHERE 1=1   or  LIMIT 1
    
    SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1
    
    string mysqli_real_escape_string ( mysqli $link , string $escapestr )
    
    $iId = $mysqli->real_escape_string("1 OR 1=1");
    $mysqli->query("SELECT * FROM table WHERE id = $iId");
    
    $count = DB::column('SELECT COUNT(*) FROM `user`);
    
    $pairs = DB::pairs('SELECT `id`, `username` FROM `user`);
    
    $user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));
    
    $banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));
    
    SELECT password FROM users WHERE name = 'root';
    
    SELECT password FROM users WHERE name = 0x726f6f74;
    
    SELECT password FROM users WHERE name = UNHEX('726f6f74');
    
    "SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])
    
    SELECT ... WHERE id = -1 UNION ALL SELECT table_name FROM information_schema.tables;
    
    SELECT ... WHERE id = -1 UNION ALL SELECT column_name FROM information_schema.column WHERE table_name = __0x61727469636c65__;
    
    SELECT ... WHERE id = UNHEX('2d312075...3635');
    
    $request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");
    
    $request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);
    
    $request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");
    
    $request = $mysqliConnection->prepare('
           SELECT * FROM trainers
           WHERE name = ?
           AND email = ?
           AND last_login > ?');
    
        $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
        $query->execute();
    
     GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';
    
    FLUSH PRIVILEGES; 
    
    select * from mysql.user where User='username';
    
    [1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'
    
    $user = "''1''"; // Malicious keyword
    $sql = 'SELECT * FROM awa_user WHERE userame =:username';
    $sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    $sth->execute(array(':username' => $user));
    
        189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
        189 Quit
    
    $stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
    $stmt->bind_param("s", $user);
    $user = "''1''";
    $stmt->execute();
    
        188 Prepare   SELECT * FROM awa_user WHERE username =?
        188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
        188 Quit
    
    RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
    RewriteRule ^(.*) ^/track.php
    
    $mysqli = new mysqli('host', 'user', 'password', 'database');
    $mysqli->set_charset('charset');
    
    $string = $mysqli->real_escape_string($string);
    $mysqli->query("INSERT INTO table (column) VALUES ('$string')");
    
    $stmt = $mysqli->prepare("INSERT INTO table (column1, column2) VALUES (?,?)");
    
    $stmt->bind_param("is", $integer, $string);
    
    $stmt->execute();
    
    $string = "x' OR name LIKE '%John%";
    $integer = '5 OR id != 0';
    
    $query = sprintf( "SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);
    
    echo $query;
    // SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 5
    
    $integer = '99999999999999999999';
    $query = sprintf("SELECT id, email, pass, name FROM members WHERE email ='%s' AND id = %d", $mysqli->real_escape_string($string), $integer);
    
    echo $query;
    // SELECT id, email, pass, name FROM members WHERE email ='x\' OR name LIKE \'%John%' AND id = 2147483647
    
    $unsafe_variable = mysql_real_escape_string($_POST['user_input']);
    
    $unsafe_variable = (is_string($_POST['user_input']) ? $_POST['user_input'] : '');
    
    $unsafe_variable = (is_numeric($_POST['user_input']) ? $_POST['user_input'] : '');
    
    $conn = oci_connect($username, $password, $connection_string);
    $stmt = oci_parse($conn, 'UPDATE table SET field = :xx WHERE ID = 123');
    oci_bind_by_name($stmt, ':xx', $fieldval);
    oci_execute($stmt);
    
    function sqlvprintf($query, $args)
    {
        global $DB_LINK;
        $ctr = 0;
        ensureConnection(); // Connect to database if not connected already.
        $values = array();
        foreach ($args as $value)
        {
            if (is_string($value))
            {
                $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
            }
            else if (is_null($value))
            {
                $value = 'NULL';
            }
            else if (!is_int($value) && !is_float($value))
            {
                die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
            }
            $values[] = $value;
            $ctr++;
        }
        $query = preg_replace_callback(
            '/{(\\d+)}/', 
            function($match) use ($values)
            {
                if (isset($values[$match[1]]))
                {
                    return $values[$match[1]];
                }
                else
                {
                    return $match[0];
                }
            },
            $query
        );
        return $query;
    }
    
    function runEscapedQuery($preparedQuery /*, ...*/)
    {
        $params = array_slice(func_get_args(), 1);
        $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
        return $results;
    }
    
    runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);
    
    $user = ORM::for_table('user')
    ->where_equal('username', 'j4mie')
    ->find_one();
    
    $user->first_name = 'Jamie';
    $user->save();
    
    $tweets = ORM::for_table('tweet')
        ->select('tweet.*')
        ->join('user', array(
            'user.id', '=', 'tweet.user_id'
        ))
        ->where_equal('user.username', 'j4mie')
        ->find_many();
    
    foreach ($tweets as $tweet) {
        echo $tweet->text;
    }