前言
代码审计流程
- -根源:理解程序执行过程,找寻危险逻辑
- -特点:
- -- 高效:如挖隧道,双向开工,时间减半
- -- 知识面广:需要同时掌握正向,反向挖掘技巧,并进行结合
- -- 以及所有正向、反向的优点
PHP-SQL注入漏洞挖掘技巧
<?php
$id = addslashes($_GET['id']);
// ' ==> \'
// \ ==> \\
// " ==> \"
// \x00 ==> \0
$sql = "SELECT * FROM dual WHERE id = '$id';";
echo $sql;
?>
-
-mysqli::escape_string / PDO::quote
- -- 与addslashes差别: 是否会主动加引号包裹
- -- 宽字节注入
-
-参数化查询
- -- 寻找非sql值的位置
- -- SELECT name from users where id = ? ORDER BY Login_time Limit 1
-
-案例:贷齐乐系统header注入
-
-略读代码,了解架构
-
-目标:找到没有进行过滤的输入点
-
-结果:$SERVER[HTTP\*]均无过滤导致注入
-
-入手点
- -- 开发者不熟悉的边缘功能
- -- 常被复制粘贴代码的功能
-
-案例:ThinkSNS某版本SQL注入漏洞
-
-略读代码,了解架构
- -- 基于ThinkPHP3.1开发
- -- MVC架构
- -- 利用t函数过滤变量
-
-目标:找到t函数过滤完成以后也可以注入的点
-
-结果:表名位置SQL注入漏洞
-
-案例:Metinfo企业网站管理系统SQL注入漏洞
-
-略读代码,了解架构
- -- 非MVC架构
- -- 全局覆盖的方式注册变量
- -- 全局GPC转义
-
-目标:获取绕过全局GPC的方法
-
-结果:利用base64_decode来引入单引号
上方部分总结
- -开发者容易遗漏的输入点
- -HTTP头
- -- X-Forwarded-For
- -- User-Agent
- -- Referer
- -PHP_SELF
-
-文件名 $_FILES[][name]
-
-php://input
-
-引入单引号(转义符)的方法
- -- stripslashes #(\' 转换为 ' 等等)
- -- base64_decode
- -- urldecode
- -- substr
- -- iconv
- -- str_replace('0','',$sql)
- -- xml
- -- json_encode
简单分析SQL注入的诱因
前言配置
我的common.php中配置
<?php
$mysql_server="localhost";
$mysql_username="root";
$mysql_password="root";
$mysql_database="test";
$conn = mysqli_connect($mysql_server,$mysql_username,$mysql_password,$mysql_database) or die("数据库链接错误");
?>
我的mysql中表的值
第一个SQL代码分析
<?php
include_once('./common.php');
try{
$name = $_GET['name'];
$query = "SELECT name, age, email, country FROM user_details where name = '{$name}';";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
这题明显没有任何过滤,并且是最基础的sql注入漏洞直接联合查询就可以搞过
第二个SQL代码分析
<?php
include_once('./common.php');
try{
$name = addslashes($_GET['name']);
$query = "SET NAMES gbk;SELECT name, age, email, country FROM user_details where name = '{$name}';";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
这题加了一个addslashes将单引号转义了,在单引号前面加了个\所以这题是无法注入的,宽字节注入得是上下文gbk编码什么能够宽字节编码的,所以这题也无法注入,应该说,以我技术看不出来。
第三个SQL注入代码分析
<?php
include_once('./common.php');
try{
$name = htmlspecialchars($_GET['name']);
$query = "SELECT name, age, email, country FROM user_details where name = '{$name}';";
echo $query."<br>";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
分析一下htmlspecialchars
,查文档说将特殊符号转换为html实体编码,处理xss漏洞的一个防御函数
开发者因为没有足够理解这个函数的关于单引号的触发机制导致漏洞,因为必须得设置ENT_QUOTES参数设置后才能转单引号,所以可以直接sql注入
第四个SQL注入代码分析
<?php
include_once('./common.php');
try{
$age = addslashes($_GET['age']);
$query = "SELECT name, age, email, country FROM user_details where age > {$age};";
echo $query."<br>";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
很明显的开发者因为数字比较,忽略单引号闭合,所以也不需要用单引号逃逸,所以这题也存在盲注和联合查询注入,addslashes相当于无用了
第五个SQL注入代码分析
<?php
include_once('./common.php');
try{
$name = str_replace("'","\\'",$_GET['name']);
$query = "SELECT name, age, email, country FROM user_details where name = '{$name}';";
echo $query."<br>";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
和addslashes差不多的意思,str_replace
函数将子字符串的替换,将单引号替换成\加上单引号。但是和addslashes也有很大的区别,因为它会将\也变成双\,但是str_replace就只会变一次
举例
addslashes会将我们传入的数据?name=一号\‘
转换成 ==> 一号\\\' (导致我们无法逃逸单引号)
但是开发者用str_replace时,会导致我们可以sql注入
当传入?name=一号\'
会解析成 ==> 一号\\' (单引号前的\会被转义让我们的'得以逃逸,形成sql注入)
第六个SQL注入代码分析
<?php
include_once('./common.php');
try{
$id = intval($_GET['id']);
$query = "SELECT name, age, email, country FROM user_details where id = '{$id}';";
echo $query."<br>";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
这个也并没有发现能够注入点,intval会将其中的字符给去掉。
第七个SQL注入代码分析
<?php
include_once('./common.php');
try{
if (intval($_GET['id'])){
$query = "SELECT name, age, email, country FROM user_details where id = {$_GET['id']};";
echo $query."<br>";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
}
} catch (PDOException $e) {
echo $e->getMessage();
}
并没有将过滤后的语句传入sql语句,所以这个intval等于无用,直接注入。
第八个SQL注入代码分析
<?php
include_once('./common.php');
try{
if (!is_numeric($_GET['id'])){
header("Status: 404 Not Found");
}
$query = "SELECT name, age, email, country FROM user_details where id = {$_GET['id']};";
echo $query."<br>";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
逻辑错误的分析,is_numeric会检测,如果是数字就可以执行,如果不是数字,就会弹404页面。但是解析器会继续向下运行,所以会导致SQL注入
第九个SQL注入代码分析
<?php
include_once('./common.php');
try{
if (!is_numeric($_GET['id'])){
header("Status: 404 Not found");
exit;
}
$query = "SELECT name, age, email, country FROM user_details where id = {$_GET['id']};";
echo $query."<br>";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
在if中加了个exit,让解析器无法继续执行,所以这个是无法注入的
第十个SQL注入代码分析
<?php
include_once('./common.php');
try{
$order = addslashes($_GET['order']);
$query = "SELECT name, age, email, country FROM user_details ORDER BY id {$order};";
echo $query."<br>";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
分析:因为这里的$order
传参是控制 查询出来的结果是升序(asc)或者降序(desc)
这里我们可以控制order导致sql盲注,因为这里没有报错信息,所以无法报错注入,利用,添加排序字段实现注入语句使用
,if(1=1,sleep(1),0)
或者是
and(if(1=1,sleep(1),0)) # 注意这里得用括号
第十一个SQL注入代码分析
<?php
include_once('./common.php');
try{
$order = addslashes($_GET['order']);
if (!preg_match('/DESC|ASC/i', $order)){
exit("Bad order");
}
$query = "SELECT name, age, email, country FROM user_details ORDER BY id {$order};";
echo $query."<br>";
$stmt = $conn->prepare($query); // stmt = PDOStatement 预处理状态
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
分析:就是第十个代码的添加了一个过滤,必须包含asc或者desc,但是这过滤并不全,并且也没有太多限制,用,添加多一个asc就可以绕过了,如果你想让这个代码变得无法SQL注入的话
payload=asc,if(1=1,sleep(1),1)
修改代码使其无法注入
!preg_match('/^(DESC|ASC)$/i', $order)
第十二个SQL注入代码分析
<?php
include_once('./common.php');
try{
$name = $_GET['name'];
$query = "SELECT name, age, email, country FROM user_details where name = ?;";
echo $query."<br>";
$stmt = $conn->prepare($query);
$stmt->bindValue("name", $name);
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
标准的预编译代码,我无法注入
第十三个SQL注入代码分析
<?php
include_once('./common.php');
try{
$name = addslashes($_GET['name']);
$name = urldecode($name);
$query = "SELECT name, age, email, country FROM user_details where name = '{$name}';";
echo $query."<br>";
$stmt = $conn->prepare($query);
$stmt->execute();
$stmt->bind_result($name, $age,$email,$country);
while ($stmt->fetch()){
echo "$email"."<br>";
}
} catch (PDOException $e) {
echo $e->getMessage();
}
分析:这题其实不加urldecode是无法注入的,但是urldecode可以让我们绕过addslashes,从而绕过单引号
’ ==> %27 #单次url编码 因为浏览器会自动解码一次
‘ ==> %25%32%37 #双次url编码
payload=一号%25%32%37 union select 1,2,user(),4 %23