代码审计
PHP
前期准备
SQL注入篇
文件操作函数汇总
命令执行篇
XML实体注入篇
前端漏洞篇
反序列化篇
小技巧篇
其它资料
本文档使用 MrDoc 发布
-
+
首页
SQL注入篇
# SQL注入篇 ## 代码审计流程 ### 反向查找流程 1. 先通过可用变量(输入点)回溯危险函数 2. 再查找危险函数确定可控变量 3. 最终传递的过程中出发漏洞 #### 反向查找案例 - 网站 > https://bugs.leavesongs.com/ - 这里的案例 > Z-BLOG Blind-XXE造成任意文件读取,链接如下 > https://bugs.leavesongs.com/PHP/Z-BLOG-Blind-XXE%E9%80%A0%E6%88%90%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E8%AF%BB%E5%8F%96/ ```php $zbp->Load(); Add_Filter_Plugin('Filter_Plugin_Zbp_ShowError','RespondError'); $xmlstring = file_get_contents( 'php://input' ); // 用户可控,http 数据包 body 中获取的内容。 //Logs($xmlstring); $xml = simplexml_load_string($xmlstring); ``` - **file_get_contents**:将整个文件读入一个字符串 - **php://input**:访问原始 POST 数据 - 举例: ```php <?php # 尝试通过 php://input 协议获取原始 POST 数据 $raw_data = file_get_contents('php://input'); var_dump($raw_data); # 通过输出 HTML 标签 </br> 实现换行功能 echo '</br>'; # 经 PHP 整理的 POST 数据 var_dump($_POST); ``` - 这个时候 post 请求包的 body 如下: ``` name=test&nation=China ``` - 结果是 ``` string(22) "name=test&nation=China" array(2) {["name] => string(4) "test" ["nation"]=> string(5) "China"} ``` - 由此可见 php://input 和 $_POST 的区别。 #### 反向查找流程特点 - 根源:危险函数导致漏洞 - 上下文无关 - 危险函数,调用即漏洞 **特点:** - 简单暴力快速 - 命中率低:简单的漏洞越来越少 - 无法挖掘逻辑漏洞:逻辑漏洞多数不存在危险函数,或危险函数的参数“看似”不可控 - 适应性较差:不适合存在全局过滤的站点 #### 使用反向查找流程挖掘漏洞 - 危险函数全局查找 - 自动化审计工具(RISP,VCG,Fortify SCA,Cobra,grepbugs,Sky wolf,Taint) ### 正向查找流程 1. 从入口函数出发 2. 找到控制器,理解URI派发机制(MVC架构:M是model,V是View,C是Controller,C就是控制器主要功能是处理请求网站的逻辑,M是处理与数据库相关的操作,V用来显示给用户) 3. 跟踪控制器调用,以理解代码为目标进行代码阅读 4. 阅读代码过程中,可能发现漏洞 - 根源:程序员疏忽或逻辑问题导致漏洞 **特点:** - 复杂:需要了解目标源码的功能和架构 - 跳跃性大:设计M/V/C/Service/Dao等多个层面 - 漏洞的组合:通常是多个漏洞的组合,很可能存在逻辑相关的漏洞 - 潜力无限:安全人员的宝库 ### 双向查找流程 - 根源:理解程序执行流程,寻找危险逻辑 1. 略读代码,了解架构 2. 是由有全局过滤机制? - 有:是否可以绕过 - 可以:寻找漏洞触发点 - 不可以:寻找没有过滤的变量 - 没有:那么它是如何处理的 - 完全没有处理:可以挖成筛子 - 有处理:寻找遗漏的处理点 3. 找到了漏洞点,漏洞利用是否有坑? - 否:成功利用! - 是:利用所致的语言知识解决问题 **特点:** - 高效 - 知识面广 #### 双向查找绕过 ```php <?php waf($_REQUEST,$_GET,$_POST); // 但是 $_SERVER变量过滤。 ?> ``` ## SQL注入漏洞 ### PHP+Mysql连接方式 - Mysql(废弃):没有预编译 - Mysqli - PDO ### SQL注入漏洞常用过滤方法 - intval/addslashes/mysql_real_escape - mysqli_escape_string/mysqli_real_escape_string/mysqli::escape_string - PDO::quote - 参数化查询 ```php <?php // intval:只接收数字,如果输入12dd,那么结果就是12;如果输入dd12,结果为0。 $id = intval($_GET["id"]); echo $id; // addslashes:自动转义。 $id1 = addslashes($_GET["id1"]); // 存在宽字节注入的问题。 echo $id1; // mysql_real_escape:避免一些宽字节注入的问题。 ?> ``` #### 被addslashes/mysql_real_escape过滤后如何查找sql漏洞 - addslashes/mysql_real_escape转义机制: ``` ' => \' \ => \\ " => \" \x00 => \0 ``` - **寻找过滤后仍然存在字符串操作的地方** - urldecode - base64_decode - iconv - json_decode - stripshasles - simple_xml_loadstring ```php <?php $id1 = addslashes($_GET["id1"]); // 后续字符串操作后,可能又引入的单引号之类的 $id1 = base64_decode($id1); echo $id1; ?> // URI参数是?id1=YCcndGVzdA== 观察结果可以看到单引号未被转义。 ``` - **数字型sql注入** ```php <?php $id1 = addslashes($_GET["id1"]); echo "SELECT * FROM table1 WHERE id = '$id1'"; // 这是字符串类型 echo "<br>"; echo "SELECT * FROM table1 WHERE id = $id1"; // 这是数字类型 ?> // URI参数是?id1=1%20union%20select1,2,3%20from%20usertable%23 可以看到下面不需要单引号来闭合,所以过滤无效。 ``` - **宽字节注入** - 在注入闭合的单引号前加入%df,经过PHP当中的addslashes函数转义为仍然是%df。 - 转义符 \ 在url中是%5c, 单引号'对应的url编码是%27,假设我们输入的闭合是%df%27,那么经过addslashes函数转移后是%df%5c%27。 - 若mysql使用的编码是GBK,就会认为%df%5c%27是一个宽字节。%df%5c会合并成一个字符(因为宽字节是占两个字节),也就是“縗” 。后面的单引号未被转义,完成sql注入的闭合。 - 除了%df也可以使用其他的宽字节,只要满足字符串编码的要求均可。 - 常见使用的宽字节就是%df,其实当我们输入第一个ascii大于128就可以,转换是将其转换成16进制,eg:129转换0x81,然后在前面加上%就是%81 - GBK 首字节对应0x81-0xfe(129-239),尾字节对应0x40-0xfe(64-126)(除了0x7f【128】),这个范围内均可。 #### mysqli::escape_string/PDO::quote - 与addslashes差别:会主动在字符串两边加引号包裹,防止数字型sql注入的问题。 #### 参数化查询 - 寻找非sql值位置:SELECT `name` FROM `users` WHERE `id`=? ORDER BY `login_time` LIMIT 1 - 上述只有?的位置是sql值,剩下的`name`,`users`,`id`,`login_ime`,1都不是sql值,预编译只能预编译值,非值的部分如果用户可控依旧存在sql注入。 - 注意这里不是单引号,而是反引号。 #### SQL注入过滤漏洞案例 - **没有过滤的参数** > https://wy.zone.ci/bug_detail.php?wybug_id=wooyun-2015-0132596 第二处 - 略读代码,了解架构 - 非MVC架构 - 全局GPC转义,但是没有对$_SERVER进行过滤。 - 目标:找到没有进行过滤的输入点 - 结果:$_SERVER[HTTP_*]均无过滤,导致注入 - 入手点 - 开发者不熟悉的边缘功能 - 常被复制粘贴代码的功能 - **过滤无效的利用点** > https://wy.zone.ci/bug_detail.php/wooyun_zone/bug_detail.php?wybug_id=wooyun-2014-077877 - 略读代码,了解架构 - 基于ThinkPHP3.1开发 - MVC架构 - 没有全局过滤,但利用t函数过滤变量 - 目标:找到t函数过滤完成以后也可以注入的点 - 结果:表名位置SQL注入漏洞 - t函数的处理逻辑:(\core\OpenSociax\functions.inc.php 630行) ```php /** * t函数用于过滤标签,输出没有html的干净的文本 * @param string text 文本内容 * @return string 处理后内容 */ function t($text){ $text = nl2br($text); //nl2br将所有新行行之前插入HTML换行标记,和安全没啥关系 $text = real_strip_tags($text); $text = addslashes($text); // 转义函数 $text = trim($text); // 去除字符串首尾空白字符,和安全没啥关系 return $text; } ``` - t函数实现中在addslashes处理前还会先调用real_strip_tags函数进行处理 - 看下real_strip_tags函数的实现:(\core\OpenSociax\functions.inc.php 2274行) ```php function real_strip_tags($str, $allowable_tags="") { $str = html_entity_decode($str,ENT_QUOTES,'UTF-8'); return strip_tags($str, $allowable_tags); // 从字符串中去除HTML和PHP标记,针对XSS的 } ``` - 通过如上分析,其实t函数就和addslashes作用一样进行转义,但是**表名的位置不是使用的单引号,而是反引号\`**。 - **过滤可以被绕过的利用点** > http://wooyun.2xss.cc/bug_detail.php?wybug_id=wooyun-2013-021062 - 略读代码,了解架构 - 非MVC架构 - 全局覆盖的方式注册变量 - 全局GPC转义 - 目标:获取绕过全局GPC的方法 - 结果:利用base64_decode来引入单引号。 ```php if($p){ $array = explode('.',base64_decode($p)); $sql="SELECT * FROM $met_admin_table WHERE admin_id='".$array[0]."'"; $sqlarray = $db->get_one($sql); } ``` #### SQL注入挖掘思路总结 - 开发者容易遗漏的输入点 - HTTP头 - X-Forwarded_For - User_Agent - Referer - PHP_SELF:用户请求的URI路径最后的文件名,echo $_SERVER["PHP_SELF"];,比如/index.php/aaaaa'aaa - REQUEST_URI:完整的路径,echo $_SERVER["REQUEST_URI"]; - 文件名 $_FILES[][name] - php://input:用户的Body中的内容。echo file_get_contents("php://input"); - 引入单引号(转义符)的方法 - stripslashes - base64_decode - urldecode - substr - iconv - str_replace('0','',$sql) - xml - json_encode - 案例 ```php // common.php <?php try { $dbhost="localhost"; $dbname="example"; $dbuser="root"; $dbpass="root"; $option=[ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ]; $conn = new PDO("mysql:host=$dbhost;dbname=$dbname",$dbuser,$dbpass,$option); }catch (PDOException $e) { echo "ERROR : " . $e->getMessage() . "<br>"; die(); } ?> // 1.php <?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->execute(); $stmt->bindColumn('email', $email); while($row=$stmt->fetch(PDO::FETCH_BOUND)) { echo "$email" ."<br>"; } } catch(PDOException $e){ echo $e->getMessage(); } ?> // 明显存在 SQL 注入漏洞,没有任何过滤。 // 2.php <?php include_once './common.php'; try { $name = addslashes($_GET['name']); $query = "SELECT name,age,email,country FROM user_details where name = '{$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(); } ?> // 不是数字型,没有SQL注入,除非是GBK的话,会有宽字节注入。 // 3.php <?php include_once './common.php'; try { $name = htmlspecialchars($_GET['name']); $query = "SELECT name,age,email,country FROM user_details where name = '{$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(); } ?> // htmlspecialchars:把预定义的字符 "<" (小于)和 ">" (大于)转换为 HTML 实体 // 这个函数一般处理xss,注意:可以处理单引号,需要设置了ENT_QUOTES才会转义单引号。 // 存在 SQL 注入漏洞 // 4.php <?php include_once './common.php'; try { $age = addslashes($_GET['age']); $query = "SELECT name,age,email,country FROM user_details where age > {$age};"; $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(); } ?> // 数字型sql注入 // 5.php <?php include_once './common.php'; try { $name = str_replace("'","\\'",$_GET['name']); $query = "SELECT name,age,email,country FROM user_details where name = '{$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(); } ?> // str_replace:子字符串替换 // 有SQL注入,用户传入\',这样转义后就会产生\\',\\在mysql中就是一个反斜线,单引号没有转义。 // 6.php <?php include_once './common.php'; try { $id = intval($_GET['id']); $query = "SELECT name,age,email,country FROM user_details where id = {$id};"; $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(); } ?> // 不存在SQL注入漏洞 // 7.php <?php include_once './common.php'; try { $id = intval($_GET['id']); $query = "SELECT name,age,email,country FROM user_details where id = {$_GET['id']};"; $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(); } ?> // 存在漏洞,重新获取了$_GET['id'],看似过滤了,实际上没将过滤后的内容传给$_GET['id']。 // 8.php <?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']};"; $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:检测变量是否是数字或数字字符串 // 有sql注入,php的header函数会给用户返回一个页面,但是这个页面不会组织php解释器继续运行。 // 9.php <?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']};"; $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(); } ?> // 没有sql注入 // 10.php <?php include_once './common.php'; try { $order = addslashes($_GET['order']); $query = "SELECT name,age,email,country FROM user_details ORDER BY id {$order};"; $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(); } ?> // 有。SQL语句只有有值的位置才能过滤,这里传入的不是值的位置。 // 11.php <?php include_once './common.php'; try { $order = addslashes($_GET['order']); if (!preg_match('/DESC|ASC/i',$order)) { // 这里的/i不区分大小写 exit("bad order"); } $query = "SELECT name,age,email,country FROM user_details ORDER BY id {$order};"; $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(); } ?> // 有,正则表达式有问题,这里是包含就可以了,不是一定是这两个值,如 union select 1,2,3# DESC // 正则改为/^(DESC|ASC)$/i就行了。 // 12.php <?php include_once './common.php'; try { $name = $_GET['name']; $query = "SELECT name,age,email,country FROM user_details where name = ?;"; $stmt = $conn->prepare($query); $stmt->bindValue(1,$name); $stmt->execute(); $stmt->bindColumn('email', $email); while($row=$stmt->fetch(PDO::FETCH_BOUND)) { echo "$email" ."<br>"; } } catch(PDOException $e){ echo $e->getMessage(); } ?> // 预编译,没有漏洞。预编译就是给定一个sql语句,语句结构已经固定,用户输入的内容只能是其中的?部分,不能成为新的sql语句结构。 // 13.php <?php include_once './common.php'; try { $name = addslashes($_GET['name']); $name = urldecode($_GET['name']); $query = "SELECT name,age,email,country FROM user_details where name = '{$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(); } ?> // 有,两次URL编码,自动解码一次,然后解码一下。 ``` - **注意点总结** - 宽字节注入 - 数字型注入 - 过滤函数没有成功过滤 - 检测函数检测后,sql仍然成功执行。 - 添加\'后,替换后出现漏洞执行 - 正则有问题 - 过滤后仍然出现了字符串变换函数
别卷了
2024年9月23日 17:01
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码