在防止SQL注入的方法中,预编译是十分有效的,它在很大程度上解决了SQL注入问题。
SQL注入简析
数据库查询语句未对SQL注入做任何防护时,语句基本如下:
$name=$_POST['name']; $pass=$_POST['pass']; $sql="SELECT * FROM user WHERE name='$name' AND pass='$pass'";
当我们提交name=-1' union select 1,user()#
,pass=12
时,后端查询语句变为:
$sql="SELECT * FROM user WHERE name='-1' union select 1,user()#' AND pass='12'";
等价于
$sql="SELECT * FROM user WHERE name='-1' union select 1,user()
由于数据库中没有id='-1’的数据项,此半句运行结果为FALSE,页面不会显示任何内容;后半句union select 1, 2, user(),表示联合查询当前的用户名,此半句运行结果为True,此时页面显示当前登录数据库的用户名(此为敏感信息)
也就是说,这导致了SQL的语法结构被更改,从而实现了命令执行:
而预编译能防止SQL语法结构被更改,它是怎么实现的呢?
在此之前,我们需要了解什么是预编译。
预编译
预编译又称为预处理,顾名思义,就是为代码编译做的预备工作。预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。
举个例子,很多情况下,一条SQL语句可能会反复执行,或者每次执行的时候只有个别的参数值不同,如:
SELECT username, password FROM users WHERE id=121; SELECT username, password FROM users WHERE id=1532; SELECT username, password FROM users WHERE id=1123; SELECT username, password FROM users WHERE id=121; SELECT username, password FROM users WHERE id=121; ...
这些语句的语法树相同,但每次都要进行重复的编译,导致数据库运行效率低下。
预编译语句将以上语句中的值用占位符?
替代,即将SQL语句模板化或者参数化,SQL语句先交由数据库预处理,构建语法树,再传入真正的字段值多次执行,省却了重复解析和优化相同语法树的时间,提升了SQL执行的效率。
简要来说就是:一次编译多次运行
预编译如何防止SQL注入
那么这和预防SQL注入有何联系?
在预编译的机制下,用户在向原有SQL语句传入值之前,原有SQL语句的语法树就已经构建完成,因此无论用户输入什么样的内容,都无法再更改语法树的结构。至此,任何输入的内容都只会被当做值来看待,不会再出现非预期的查询,这便是预编译能够防御SQL注入的根本原因。
预编译代码如下:
$name = $_POST['name']; $pass = $_POST['pass']; $stmt = $conn->prepare("SELECT * FROM user WHERE name=? AND pass=?");// 准备 SQL 查询语句,用于验证用户名和密码 $stmt->bind_param("ss", $name, $pass);// 绑定参数,防止 SQL 注入攻击 $stmt->execute();// 执行查询 $result = $stmt->get_result();// 获取查询结果集 if ($result->num_rows > 0) { // 用户验证成功
当我们提交name=1' union select 1,user()#
,pass=12
时,由于预编译的存在,后端查询语句变为:
SELECT * FROM user WHERE name='1' union select 1,user()#' AND pass='12'
由于输入的内容都只会被当做值来看待,所以并不会导致恶意查询。
读者可以将sqli-labs的第一关(Less-1)的index.php修改为:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Less-1 **Error Based- String**</title> </head> <body bgcolor="#000000"> <div style=" margin-top:70px;color:#FFF; font-size:23px; text-align:center">Welcome <font color="#FF0000"> Dhakkan </font><br> <font size="3" color="#FFFF00"> <?php //including the Mysql connect parameters. $sql_server = "localhost"; $sql_username = "root"; $sql_password = "root"; $sql_database = "security"; $mysqli = new mysqli($sql_server, $sql_username, $sql_password, $sql_database); error_reporting(0); // take the variables if(isset($_GET['id'])) { $id=$_GET['id']; //logging the connection parameters to a file for analysis. $fp=fopen('result.txt','a'); fwrite($fp,'ID:'.$id."\n"); fclose($fp); $sql="SELECT * FROM security.users WHERE id= ? LIMIT 0,1"; $mysqli_stmt = $mysqli->prepare($sql); //创建预处理对象 $mysqli_stmt->bind_param('i',$id); //绑定参数 $mysqli_stmt->bind_result($id,$username,$password); //绑定结果集 $mysqli_stmt->execute(); //执行 while($mysqli_stmt->fetch()) { echo "<font size='5' color= '#99FF00'>"; echo 'Your Login name:' . $username; echo "<br>"; echo 'Your Password:' . $password; echo "</font>"; } } else { echo "Please input the ID as parameter with numeric value";} ?> </font> </div></br></br></br><center> <img src="../images/Less-1.jpg" /></center> </body> </html>
尝试注入,可以发现并不能成功。
然而,预编译语句并不适用于所有参数。在某些特定的场景下,如动态表名、动态列名或排序方式等,无法使用占位符进行替代,因为它们不属于参数值。一种常见的做法是执行输入验证和过滤,确保用户提供的数据符合预期的格式和规范。例如,可以使用白名单机制来限制可接受的表名和列名,或者通过预定义的选项来控制排序方式。