简介
在数据交互中,前端的数据传入到后台处理时,由于后端没有没有做过滤或过滤不严,则会导致其传入的恶意“数据”拼接到SQL语句中,被当作SQL语句的一部分执行。漏洞产生于脚本,注入是针对数据库进行。
利用条件:
- 参数用户可控:从前端传给后端的参数内容是用户可以控制的。
- 参数带入数据库查询:传入的参数拼接到 SQL 语句,且带入数据库查询。
检索关键词
在 PHP 代码审计中,为了定位潜在的 SQL 注入漏洞,可以检索一些特定的关键词和代码模式。以下是一些常用的关键词和代码模式,它们可能表明存在 SQL 注入的风险:
直接拼接 SQL 语句:
- . 或 .=:用于字符串拼接的运算符,可能表明 SQL 语句是直接拼接的。
- mysql_query:这是一个过时的 MySQL 函数,它不提供参数绑定,容易导致 SQL 注入。
- $db->query 或 $db->exec:这些是数据库抽象层(如 PDO 或 MySQLi)的方法,如果没有使用参数绑定,也可能导致 SQL 注入。
未转义的用户输入:
- $_GET、$_POST、$_REQUEST、$_COOKIE:这些超级全局变量直接包含用户输入,如果没有经过适当的处理,可能会导致 SQL 注入。
- $_SERVER 中的某些变量(如 $_SERVER['PHP_SELF'])也可能包含用户输入,需要谨慎处理。
未使用预处理语句:
- 缺少 prepare 和 bindParam 或 bindValue:这些是预处理语句的关键方法,如果代码中没有使用它们,可能存在 SQL 注入风险。
未验证的用户输入:
- 缺少输入验证或过滤函数,如 filter_input、filter_var、preg_match 等。
特定 SQL 函数和操作符:
- SELECT、INSERT、UPDATE、DELETE:这些是 SQL 查询的关键词,如果它们后面直接跟用户输入,可能存在风险。
- LIKE:在使用 LIKE 进行模糊查询时,如果没有正确处理通配符,也可能导致 SQL 注入。
数据库连接和查询函数:
- mysqli_query、PDO::query:这些函数用于执行 SQL 查询,如果没有使用参数绑定,可能存在风险。
- mysqli_real_escape_string、PDO::quote:这些函数用于转义字符串,但它们不是防止 SQL 注入的最佳方法,因为它们不能防止所有类型的注入。
动态构建 SQL 语句:
- if 、switch 等条件语句中动态构建 SQL 语句,如果没有正确处理用户输入,可能存在风险。
错误处理:
- 如果代码中有显示数据库错误信息的逻辑,这可能会帮助攻击者发现注入点。
无过滤
数字型
$user_id = $_GET['id']; // 用户输入的 ID
$sql = "SELECT * FROM users WHERE id = $user_id";
$result = mysqli_query($conn, $sql);
字符型
$user_id = $_GET['id']; // 用户输入的 ID
$sql = "SELECT * FROM users WHERE id = '$user_id'";
$result = mysqli_query($conn, $sql);
搜索型
$search_term = $_GET['search']; // 用户输入的搜索关键字
$sql = "SELECT * FROM products WHERE name LIKE '%$search_term%'";
$result = mysqli_query($conn, $sql);
存在过滤或过滤不严谨
magic_quotes_gpc
magic_quotes_gpc 是 PHP 早期版本中的一个配置选项,用于自动对从 GET、POST 和 COOKIE 中获取的数据进行转义,以防止 SQL 注入和跨站脚本攻击等安全问题。
当 magic_quotes_gpc 被启用时( magic_quotes_gpc = On),PHP 会自动在 GET、POST 和 COOKIE 数据中的单引号(')、双引号(")、反斜杠(\)和 NULL 字符前添加反斜杠(\),以转义这些特殊字符。
例如:
如果用户提交了一个包含单引号的字符串 A'pple
那么 PHP 会自动将其转换为 A\'pple
配置选项文件名:php.ini
然而,magic_quotes_gpc 在 PHP 5.4.0 版本中已经被废弃,并在 PHP 7.0.0 版本中被移除。
这是因为 magic_quotes_gpc 存在一些问题,例如:
- 它可能会破坏数据的完整性,因为它会自动对数据进行转义,而有时我们并不需要这样做。
- 它可能会导致安全问题,因为它只是简单地对特殊字符进行转义,而没有对数据进行严格的验证和过滤。
- 它可能会导致性能问题,因为它会对所有的 GET、POST 和 COOKIE 数据进行转义,而不管这些数据是否需要转义。
在日常程序的开发过程中,都会对其magic_quotes_gpc是否开启,进行判断,如果没有开启,则使用所设置的过滤函数,常会使用addslashes函数对其传入的参数值进行过滤,降低SQL注入产生的风险。
常见过滤函数
addslashes():将特殊字符(单引号、双引号、反斜杠和 NULL 字符)前添加反斜杠。
htmlspecialchars():将特殊字符(&、"、'、<、>)转换为 HTML 实体。
htmlentities():将所有可转换的字符转换为 HTML 实体。
strip_tags():去除字符串中的 HTML 和 PHP 标签。
filter_var():使用过滤器过滤变量。
mysqli_real_escape_string():在 SQL 语句中转义特殊字符。
PDO::quote():在 SQL 语句中转义特殊字符。
过滤函数缺陷
addslashes()
addslashes()函数虽然能对传入参数中的特殊字符进行过滤,但若执行的SQL语句是数字类型的,不需要进行闭合,从而可以直接执行插入的恶意代码
$user_id = addslashes($_GET['id']); // 用户输入的 ID
$sql = "SELECT * FROM users WHERE id = $user_id";
$result = mysqli_query($conn, $sql);
htmlspecialchars()
htmlspecialchars() 是用于将特殊字符转换为 HTML 实体,例如将 < 转换为 <,将 > 转换为 >。这个函数主要用于防止 XSS(跨站脚本)攻击,而不是 SQL 注入。 所以根本起不到过滤特殊字符,防范SQL注入的作用。
$user_id = htmlspecialchars($_GET['id']); // 用户输入的 ID
$sql = "SELECT * FROM users WHERE id = $user_id";
$result = mysqli_query($conn, $sql);
字符集不匹配导致单引号的逃逸
在 SQL 注入攻击中,攻击者通常试图通过在输入中插入特殊字符(如单引号 ')来破坏 SQL 查询的结构。为了防止这种情况,应用程序通常会对这些特殊字符进行转义。然而,如果转义处理不当,尤其是在处理宽字符时,攻击者可能通过添加特定的字符序列来绕过转义,从而导致注入。
产生原因: 当应用程序的字符集与数据库连接的字符集不匹配时,可能会导致宽字节注入。
例如,如果应用程序使用 UTF-8 编码,但数据库连接设置为使用单字节字符集(如 latin1),那么在将用户输入传递给数据库之前,可能会错误地处理宽字符。
set character_set_client = gbk
设置 character_set_client = gbk 本身并不会直接导致 SQL 注入,但它可能会增加 SQL 注入的风险,尤其是在处理多字节字符集(如 GBK)时。这种设置可能会影响应用程序处理用户输入的方式,特别是在字符编码转换和 SQL 查询构建方面。
当 character_set_client 设置为 gbk 时,MySQL 服务器期望客户端发送的数据是 GBK 编码的。如果应用程序的默认编码不是 GBK,那么在将用户输入发送到数据库之前,可能需要进行编码转换。如果这个转换过程处理不当,可能会引入 SQL 注入的风险。
$conn = mysql_connect('localhost', 'username', 'password');
mysql_select_db('database', $conn);
方法一
mysql_query("SET NAMES 'gbk'", $conn);
方法二
mysql_set_charset('gbk', $conn);
或
set character_set_client = gbk
漏洞示例
假设一个 PHP 应用程序的默认编码是 UTF-8,但数据库连接设置 character_set_client = gbk。如果应用程序直接将 UTF-8 编码的用户输入发送到数据库,而不进行适当的转换,可能会导致字符解析错误,从而可能被利用来进行 SQL 注入。
$username = $_POST['username']; // 用户输入,假设是 UTF-8 编码
$sql = "SELECT * FROM users WHERE username = '$username'";
mysqli_query($conn, $sql);
iconv()
iconv() 是 PHP 中的一个函数,用于在不同的字符编码之间转换字符串。这个函数非常有用,特别是在处理多语言网站或需要与不同编码的数据库或系统交互时。
使用方法
$converted = iconv(from_encoding, to_encoding, $string);
- from_encoding:源字符编码。
- to_encoding:目标字符编码。
- $string:需要转换的字符串。
漏洞示例
假设有一个 PHP 应用程序,它使用 iconv()
函数将用户输入从一种编码转换到另一种编码,然后将转换后的字符串直接用于 SQL 查询。如果这个转换过程没有正确处理特殊字符,可能会导致 SQL 注入漏洞。
$username = $_POST['username'];
$username = iconv("UTF-8", "gbk", $username); // 假设这是错误的转换
$sql = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $sql);
mb_convert_encoding()
mb_convert_encoding() 是 PHP 中的一个多字节字符处理函数,用于将一个字符串从一种字符编码转换到另一种字符编码。这个函数特别适用于处理包含多字节字符集(如 UTF-8、GBK、Shift-JIS 等)的字符串。
使用方法
string mb_convert_encoding ( string $str , string $to_encoding [, mixed $from_encoding = mb_internal_encoding() ] )
- $str:要转换编码的字符串。
- $to_encoding:目标编码。
- $from_encoding:可选参数,指定源编码。可以是一个编码名称的字符串,也可以是一个编码名称的数组。如果未指定,则使用 mb_internal_encoding() 返回的编码。
漏洞示例
假设有一个 PHP 应用程序,它使用 mb_convert_encoding()
函数将用户输入从一种编码转换到另一种编码,然后将转换后的字符串直接用于 SQL 查询。如果这个转换过程没有正确处理特殊字符,可能会导致 SQL 注入漏洞。
$username = $_POST['username'];
$username = mb_convert_encoding($username, "UTF-8", "gbk"); // 假设这是错误的转换
$sql = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $sql);
解码函数不恰当的使用导致的注入
urldecode()
urldecode() 是 PHP 中的一个内置函数,用于解码已编码的 URL 字符串。URL 编码是一种将特殊字符转换为可以在 URL 中安全传输的格式的过程。urldecode() 函数将这些编码的字符转换回它们的原始形式。
使用方法
string urldecode ( string $str )
- $str:要解码的 URL 编码字符串。
urldecode() 函数在 PHP 中用于解码已编码的 URL 字符串,它本身并不直接导致 SQL 注入,但如果使用不当,特别是在处理用户输入并将其用于构建 SQL 查询时,可能会引入 SQL 注入的风险。
漏洞示例
假设有一个 PHP 应用程序,它从 URL 参数中获取用户名,并使用 urldecode() 函数解码该参数,然后将解码后的字符串直接用于 SQL 查询。
$username = urldecode($_GET['username']);
$sql = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $sql);
base64_decode()
base64_decode() 是 PHP 中的一个内置函数,用于解码 base64 编码的字符串。Base64 编码是一种将二进制数据转换为 ASCII 字符串的方法,常用于在不支持二进制传输的介质上传输数据。
使用方法
string base64_decode ( string $encoded_string )
- $encoded_string:要解码的 base64 编码字符串。
漏洞示例
假设有一个 PHP 应用程序,它从用户输入中获取一个 base64 编码的字符串,使用 base64_decode() 函数解码该字符串,然后将解码后的字符串直接用于 SQL 查询。
$encoded_username = $_GET['username'];
$username = base64_decode($encoded_username);
$sql = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $sql);
json_encode()
json_encode() 是 PHP 中的一个内置函数,用于将 PHP 数据转换为 JSON 格式。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。
使用方法
string json_encode ( mixed $value [, int $options = 0 [, int $depth = 512 ]] )
- $value:要编码的值。该函数只对数组或对象有效。
- $options:可选参数,用于设置编码选项。可以是以下常量的组合:
-
- JSON_HEX_TAG
- JSON_HEX_AMP
- JSON_HEX_APOS
- JSON_HEX_QUOT
- JSON_FORCE_OBJECT
- JSON_NUMERIC_CHECK
- JSON_PRETTY_PRINT
- JSON_UNESCAPED_SLASHES
- JSON_UNESCAPED_UNICODE
- 等等
- $depth:可选参数,指定最大深度。必须大于 0。
漏洞示例
假设你有一个应用程序,它接收用户输入并将其存储在数据库中。如果用户输入的数据在存储之前被 json_encode() 处理,然后直接用于构建 SQL 查询,那么可能会发生 SQL 注入。
$user_input = $_POST['data']; // 用户输入的数据
$json_data = json_encode($user_input); // 将用户输入转换为 JSON
// 假设这是你的数据库插入代码
$sql = "INSERT INTO users (data) VALUES ('$json_data')";
$result = mysqli_query($conn, $sql);
弱类型函数不恰当的使用导致的注入
intval()
intval() 是 PHP 中的一个内置函数,用于将变量转换为整数类型。这个函数非常有用,尤其是在需要确保变量是整数的情况下,例如在处理数据库查询中的 ID 或其他数值参数时。
使用方法
int intval ( mixed $var [, int $base = 10 ] )
- $var:要转换的变量。
- $base:可选参数,用于指定转换的基数。默认值为 10。
$var = "1234";
$int = intval($var);
echo $int; // 输出:1234
$var = "1234abc";
$int = intval($var);
echo $int; // 输出:1234
$var = "abc1234";
$int = intval($var);
echo $int; // 输出:0
$var = "1234abc5678";
$int = intval($var);
echo $int; // 输出:1234
$var = "0x1A"; // 十六进制
$int = intval($var, 0);
echo $int; // 输出:26
$var = "0123"; // 八进制
$int = intval($var, 8);
echo $int; // 输出:83
漏洞示例
假设你有一个应用程序,它接收用户输入并将其存储在数据库中。如果用户输入的数据在存储之前被 intval() 处理,然后直接用于构建 SQL 查询,那么可能会发生 SQL 注入。
$id = isset($_GET['id'])?$_GET['id']:1;
if(intval($id)>99){
die("不符合条件");
}
$sql = "SELECT * FROM users WHERE id = $id";
$result = mysqli_query($conn, $sql);
is_numeric()
is_numeric() 是 PHP 中的一个内置函数,用于检查一个变量是否为数字或数字字符串。这个函数非常有用,尤其是在需要验证用户输入或外部数据是否可以安全地作为数字处理时。
使用方法
bool is_numeric ( mixed $var )
- $var:要检查的变量。
返回值:
- 如果变量是数字或数字字符串,则返回 true。
- 如果变量不是数字或数字字符串,则返回 false。
$var1 = 123;
$var2 = "123";
$var3 = "123abc";
$var4 = "abc123";
$var5 = 12.34;
$var6 = "12.34";
$var7 = "0x1A"; // 十六进制
$var8 = "0123"; // 八进制
var_dump(is_numeric($var1)); // 输出:bool(true)
var_dump(is_numeric($var2)); // 输出:bool(true)
var_dump(is_numeric($var3)); // 输出:bool(true)
var_dump(is_numeric($var4)); // 输出:bool(false)
var_dump(is_numeric($var5)); // 输出:bool(true)
var_dump(is_numeric($var6)); // 输出:bool(true)
var_dump(is_numeric($var7)); // 输出:bool(true)
var_dump(is_numeric($var8)); // 输出:bool(true)
漏洞示例
假设你有一个应用程序,它接收用户输入并将其存储在数据库中。如果用户输入的数据在存储之前被 is_numeric() 处理,然后直接用于构建 SQL 查询,那么可能会发生 SQL 注入。
$id = $_GET['id']; // 用户输入的数据
if (is_numeric($id)) {
$sql = "SELECT * FROM users WHERE id = $id"; // 假设这是你的数据库查询代码
$result = mysqli_query($conn, $sql);
}
in_array()
in_array() 是 PHP 中的一个内置函数,用于检查一个值是否存在于数组中。这个函数非常有用,尤其是在需要验证某个值是否包含在数组中时。
使用方法
bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
- $needle:要在数组中搜索的值。
- $haystack:要搜索的数组。
- $strict:可选参数,如果设置为 true,in_array() 将在比较时检查数据类型,这意味着字符串和整数等不同类型的值不会被认为是匹配的。默认值为 false。
返回值:
- 如果找到 $needle,则返回 true。
- 如果未找到 $needle,则返回 false。
漏洞示例
假设你有一个应用程序,它接收用户输入并将其用于构建 SQL 查询。如果用户输入的数据在用于 SQL 查询之前没有被正确处理,那么即使使用了 in_array(),也可能发生 SQL 注入。
$user_input = $_GET['fruit']; // 用户输入的数据
$allowed_fruits = array("apple", "banana", "cherry");
if (in_array($user_input, $allowed_fruits)) {
$sql = "SELECT * FROM fruits WHERE name = '$user_input'";
$result = mysqli_query($conn, $sql);
}
array_search()
array_search() 是 PHP 中的一个内置函数,用于在数组中搜索给定的值,并返回它的键。如果找到多个匹配项,array_search() 将只返回第一个匹配项的键。
使用方法
mixed array_search ( mixed $needle , array $haystack [, bool $strict = false ] )
- $needle:要在数组中搜索的值。
- $haystack:要搜索的数组。
- $strict:可选参数,如果设置为 true,array_search() 将在比较时检查数据类型,这意味着字符串和整数等不同类型的值不会被认为是匹配的。默认值为 false。
返回值:
- 如果找到 $needle,则返回它的键(索引)。
- 如果未找到 $needle,则返回 false。
漏洞示例
假设你有一个应用程序,它接收用户输入并将其用于构建 SQL 查询。如果用户输入的数据在用于 SQL 查询之前没有被正确处理,那么即使使用了array_search(),也可能发生 SQL 注入
$user_input = $_GET['fruit']; // 用户输入的数据
$allowed_fruits = array("apple", "banana", "cherry");
if (array_search($user_input, $allowed_fruits)) {
$sql = "SELECT * FROM fruits WHERE name = '$user_input'";
$result = mysqli_query($conn, $sql);
}
switch
在 PHP 中,switch 语句是一种流程控制结构,用于根据不同的条件执行不同的代码块。switch 语句通常用于替代多个 if...else 语句,使代码更加清晰和易读。
使用方法
switch (expression) {
case value1:
// 当 expression 等于 value1 时执行的代码
break;
case value2:
// 当 expression 等于 value2 时执行的代码
break;
// 更多 case 语句...
default:
// 当 expression 不匹配任何 case 时执行的代码
}
- expression:要进行比较的表达式。
- case:定义不同的值,expression 将与这些值进行比较。
- break:在执行完一个 case 后,break 语句用于跳出 switch 语句,防止执行后续的 case。
- default:可选,当 expression 不匹配任何 case 时执行的代码块。
漏洞示例
假设你有一个应用程序,它接收用户输入并根据输入的类型执行不同的 SQL 查询。如果用户输入的数据在用于 SQL 查询之前没有被正确处理,那么即使使用了 switch 语句,也可能发生 SQL 注入。
$user_input = $_GET['type']; // 用户输入的数据
switch ($user_input) {
case 'get_user':
$sql = "SELECT * FROM users WHERE username = '$user_input'";
break;
case 'get_product':
$sql = "SELECT * FROM products WHERE name = '$user_input'";
break;
// 更多 case 语句...
default:
echo "Invalid input!";
}
$result = mysqli_query($conn, $sql);
==
=(赋值运算符):
- = 是赋值运算符,用于将右边的值赋给左边的变量。
- 例如:$x = 10; 表示将数值 10 赋给变量 $x。
==(等于运算符):
- == 是比较运算符,用于检查两个值是否相等,但不考虑它们的类型。
- 如果两个值在松散比较下相等,则返回 true;否则返回 false。
- 例如:$x == $y 如果 $x 和 $y 的值相等,则返回 true,即使它们的类型不同。例如,1 == '1' 返回 true。
===(全等于运算符):
- === 是比较运算符,用于检查两个值是否相等,并且它们的类型也相同。
- 如果两个值在严格比较下相等(即值和类型都相等),则返回 true;否则返回 false。
- 例如:$x === $y 如果 $x 和 $y 的值和类型都相等,则返回 true。例如,1 === '1' 返回 false,因为虽然值相等,但类型不同(一个是整数,一个是字符串)。
使用方法
$x = 10; // 赋值
$y = '10';
if ($x == $y) {
echo '$x 和 $y 在松散比较下相等<br>';
}
if ($x === $y) {
echo '$x 和 $y 在严格比较下相等<br>';
} else {
echo '$x 和 $y 在严格比较下不相等<br>';
}
漏洞示例
假设你有一个应用程序,它接收用户输入并根据输入的类型执行不同的 SQL 查询。如果用户输入的数据在用于 SQL 查询之前没有被正确处理,那么即使使用了 == 进行比较,也可能发生 SQL 注入。
$user_input = $_GET['type']; // 用户输入的数据
if ($user_input == 'get_user') {
$sql = "SELECT * FROM users WHERE username = '$user_input'";
} elseif ($user_input == 'get_product') {
$sql = "SELECT * FROM products WHERE name = '$user_input'";
} else {
echo "Invalid input!";
}
$result = mysqli_query($conn, $sql);
常被忽略的http信息传入导致的注入
HTTP 头部 SQL 注入是一种特殊类型的 SQL 注入攻击,它发生在应用程序从 HTTP 头部(如 Cookie、User-Agent、Referer 等)中获取数据,并将其直接用于构造 SQL 查询时,没有对这些数据进行适当的验证和转义。 最根本原因,是因为程序员会设置全局过滤防止SQL注入,但开启全局过滤只会对GET,POST,COOKIE这些传入的值都会进行过滤。而对于http的头部信息来说,是无法直接获取的,需要通过 $_SERVER 或 getenv() 函数进行获取。
例如:获取ip 可以使用 getenv('HTTP_X_FORWARDED_FOR')或$_SERVER['HTTP_X_FORWARDED_FOR']
以下是一些常用的 HTTP 请求头信息及其在 $_SERVER 中的对应键名:
'HTTP_HOST':请求头中的 Host 字段。
'HTTP_USER_AGENT':请求头中的 User-Agent 字段,通常包含浏览器的名称和版本。
'HTTP_ACCEPT':请求头中的 Accept 字段,指定客户端可以接受的内容类型。
'HTTP_ACCEPT_LANGUAGE':请求头中的 Accept-Language 字段,指定客户端偏好的语言。
'HTTP_ACCEPT_ENCODING':请求头中的 Accept-Encoding 字段,指定客户端支持的内容编码。
'HTTP_CONNECTION':请求头中的 Connection 字段,指定连接的类型,如 keep-alive。
'HTTP_COOKIE':请求头中的 Cookie 字段,包含客户端发送的 cookie 信息。
'HTTP_REFERER':请求头中的 Referer 字段,指定链接到当前页面的前一页面的 URL。
漏洞示例
下面是一个 HTTP 头部 SQL 注入的示例,其中使用了用户代理(User-Agent)头部来构建 SQL 查询:
$user_agent = $_SERVER['HTTP_USER_AGENT']; //获取客户端浏览器信息
$query = "SELECT * FROM users WHERE user_agent = '$user_agent'";
$result = mysqli_query($conn, $sql); // 执行 SQL 查询