PHP代码审计之----如何挖PHP的SQL注入漏洞

发布于 2021-05-12 22:35 ,所属分类:区块连和PHP开发学习资料

代码审计流程

反向查找流程

  1. 全局查找危险函数,某些危险函数的出现容易产生漏洞
  2. 确定用户输入是否可以传入到危险函数

之前整理的关于Z-Blogxxe漏洞就是一个典型的这种例子:https://mp.weixin.qq.com/s/R6mDpyeQjSUrY_40pgDzZg

特点

  • 上下文无关
  • 危险函数,调用即漏洞
  • 适用于自动化代码审计工具
  • 适应性差,不适合存在全局过滤的站点

注:这种漏洞相对来说越来越少。

常见的自动化审计工具:

  • RISP
  • VCG
  • Fortify SCA
  • Cobra
  • grepbugs
  • Sky wolf
  • Taint

正向查找流程

  1. 从入口点函数出发
  2. 找到控制器,理解URL派发规则
  3. 跟踪控制器调用,以理解代码为目标进行源码阅读
  4. 发现漏洞

特点

  • 需要了解目标源码的功能与架构
  • 需要了解常见的框架模式如:MVCMTV
  • 涉及到多个漏洞的组合,很可能存在组合漏洞,逻辑漏洞
  • 自动化工具不好挖掘,更加需要挖洞者理解业务逻辑

注:这种漏洞潜力无限,很可能就会存在潜在的漏洞

双向查找流程

实际代码审计过程中通常会将反向查找流程和正向查找流程进行结合。

  • 略读代码,了解架构

  • 看是否有全局过滤机制

    • 完全没有处理:直接可以完成筛子
    • 有处理:寻找遗漏的处理点
    • 可以:寻找漏洞触发点
    • 不可以:寻找没有过滤的变量
    • 有过滤机制:是否可以绕过?
    • 没有过滤机制:

特点

  • 需要理解程序执行过程,寻找危险逻辑点
  • 高效,双向开工
  • 需要掌握的知识面广,同时掌握正向和方向挖掘

PHP SQL注入漏洞挖掘技巧

PHP项目经典的项目架构:LAMP或者LNMP , 所以经常使用到的数据库就是MySQL

PHP连接MySQL的方式

  • Mysql(已经废弃,可能特别老的项目还在使用)
  • Mysqli
  • PDO

PHPSQL注入常见的过滤方法

  • intval/addslashes/mysql_real_escape
  • mysqli_escape_string / mysqli_real_escape_string / mysqli::escape_string
  • PDO::quote
  • 参数化查询,即预编译

intval : 获取变量的整数值

addslashes: 该字符串为了数据库查询语句等的需要在某些字符前加上了反斜线。这些字符包括:单引号(')、双引号(")、反斜线(\)与 NULnull 字符)。

mysql_real_escape_string: 自 PHP 5.5.0 起已废弃, 「mysql_real_escape_string()」 调用mysql库的函数 mysql_real_escape_string, 在以下字符前添加反斜杠: \x00, \n, \r, \, ', "\x1a.

当使用了 addslashes 或者 mysql_real_escape的时候并不是一定就不存在注入了,思路有:

  • 宽字节注入
  • 是否存在字符串转换函数:
    • urldecode
    • base54_decode
    • iconv
    • json_decode
    • stripshasles
    • simple_xml_loadstring

使用了 mysqli::escape_string / PDO::quote 也是同样的,思路:

  • 宽字节注入
  • 是否会主动加引号包裹

参数化查询,也是会有漏网之鱼,思路:

  • 寻找非SQL值的位置, 因为在预编译的时候只有SQL值的地方会进行预编译,那么剩下的非值的地方依然是有可能存在SQL注入

哪些地方可能存在SQL注入?

主要包含两个方面:

  • 开发者容易遗漏的点
  • 引入单引号(转义符)的方法

开发者容易遗漏的点有:

  • HTTP头:X-Forwarded-For, User-Agent, Referer
  • PHP_SELF
  • REQUEST_URI
  • 文件名
  • php://input

引入单引号(转义符)的方法 的点有:

  • stripslashes
  • base64_decode
  • urldecode
  • substr
  • iconv
  • str_replace('0', $sql)
  • xml
  • json_encode

举个栗子

common.php 代码如下:

<?php

try{
$dbhost='127.0.0.1';
$dbname='test_sql';
$dbuser='root';
$dbpass='123456'
$option=[
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
];
$conn=newPDO("mysql:host=$dbhost;dbname=$dbname",$dbuser,$dbpass,$option);
}catch(PDOException$e){
echo"Error:".$e->getMessage()."<br/>";
die();
}

栗子1

1.php 代码如下:

<?php
include_once'./common.php';
try{
$name=$_GET["name"];
$query="SELECTname,age,email,countryFROMuser_detailsWHEREname='{$name}';";
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}
}catch(PDOException$e){
echo$e->getMessage();
}

很明显,这个代码中并未传入的name字段进行过滤,所以是存在过滤的,例如我们通过如下方式即可进行注入:

http://192.168.78.135:40004/1.php?name=aaa%27%20%20union%20select%201,2,database(),4--%20#

栗子2

2.php 代码如下:

<?php
include_once'./common.php';
try{
$name=addslashes($_GET["name"]);
$query="SELECTname,age,email,countryFROMuser_detailsWHEREname='{$name}';";
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}
}catch(PDOException$e){
echo$e->getMessage();
}

抛开宽字节的问题,当前代码其实是没有注入的

栗子3

3.php代码如下:

<?php
include_once'./common.php';
try{
$name=htmlspecialchars($_GET["name"]);
$query="SELECTname,age,email,countryFROMuser_detailsWHEREname='{$name}';";
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}
}catch(PDOException$e){
echo$e->getMessage();
}

这段代码其实还是存在注入的,注入的方式:

http://192.168.78.135:40004/1.php?name=aaa%27%20%20union%20select%201,2,database(),4--%20#

栗子4

4.php代码如下:

<?php
include_once'./common.php';
try{
$age=addslashes($_GET["age"]);
$query="SELECTname,age,email,countryFROMuser_detailsWHEREage>{$age};";
echo$query;
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}
}catch(PDOException$e){
echo$e->getMessage();
}

这段代码虽然使用了addslashes,但是sql语句中的变量并没有使用引号,所以addslashes没有起作用,还是存在注入的,注入的方式:

http://192.168.78.135:40004/4.php?age=18%20union%20select%201,2,database(),4

栗子5

5.php代码如下:

<?php
include_once'./common.php';
try{
$name=str_replace("'","\\'",$_GET["name"]);
$query="SELECTname,age,email,countryFROMuser_detailsWHEREname='{$name}';";
echo$query;
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}
}catch(PDOException$e){
echo$e->getMessage();
}

这段代码是一些开发者可能经常会犯的一个错误,就是认为只要把单引号进行了替换为\' 就没事了,但是却忽略了如果攻击者传入的是\' 这个时候其实会被替换成 \\',从而使我们依然可以通过如下进行注入:

http://192.168.78.135:40004/5.php?name=aaa\%27%20%20union%20select%201,2,database(),4--%20#

栗子6

6.php代码如下:

<?php
include_once'./common.php';
try{
$id=intval($_GET["id"]);
$query="SELECTname,age,email,countryFROMuser_detailsWHEREid>{$id};";
echo$query;
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}
}catch(PDOException$e){
echo$e->getMessage();
}

这个代码是没有注入漏洞的

栗子7

7.php 代码如下:

<?php
include_once'./common.php';
try{
if(intval($_GET["id"])){
$query="SELECTname,age,email,countryFROMuser_detailsWHEREid>$_GET["id"];";
echo$query;
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}
}

}catch(PDOException$e){
echo$e->getMessage();
}

这段代码是存在注入的,虽然intval对参数进行了转换,但是这里并不会进行判断当前参数是否是数字

栗子8

8.php 代码如下:

<?php
include_once'./common.php';
try{
if(!is_numeric($_GET["id"])){
header("status:404notfound");
}
$query="SELECTname,age,email,countryFROMuser_detailsWHEREid>$_GET["id"];";
echo$query;
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}

}catch(PDOException$e){
echo$e->getMessage();
}

这段代码的问题在于,虽然使用了is_numeric对参数进行的判断,但是这里使用了header函数之后并没有退出,而是接着往下执行,导致依然会存在注入

栗子9

9.php的代码如下:

<?php
include_once'./common.php';
try{
$order=addslashes($_GET['order']);
$query="SELECTname,age,email,countryFROMuser_detailsORDERBYid{$order};";
echo$query;
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}
}catch(PDOException$e){
echo$e->getMessage();
}

这个代码依然存在注入漏洞

栗子10

10.php代码如下:

<?php
include_once'./common.php';
try{
$name=addslashes($_GET['order']);
$name=urldecode($name);
$query="SELECTname,age,email,countryFROMuser_detailsWHEREname='{$name}';";
echo$query;
$stmt=$conn->prepare($query);
$stmt->execute();
$stmt->bindColumn('email',$email);
while($row=$stmt->fetch(PDO::FETCH_BOUND)){
echo"$email"."<br/>";
}
}catch(PDOException$e){
echo$e->getMessage();
}

虽然使用了addslashes 但是在下文使用了urldecode 那么就会依然导致注入漏洞的存在


相关资源