PHPYUN人才系统一个正常函数不正常用法引发的逻辑隐患(审计思路)
字数 922 2025-08-25 22:58:46

PHPYUN人才系统审计:正常函数的不安全用法引发的逻辑漏洞

漏洞概述

本文详细分析了PHPYUN人才系统中由于update_once函数的不安全使用导致的逻辑漏洞,该漏洞允许攻击者越权修改其他用户的简历信息,包括置顶状态和所属用户等敏感字段。

系统架构分析

入口文件分析

系统入口文件index.php首先包含global.php

include(dirname(__FILE__).'/global.php');

全局配置文件

global.php定义了多个路径常量:

define('APP_PATH',dirname(__FILE__).'/'); 
define('CONFIG_PATH',APP_PATH.'/config/');
define('DATA_PATH',APP_PATH.'/data/');
define('LIB_PATH',APP_PATH.'/app/include/'); 
define('TPL_PATH',APP_PATH.'/app/template/');
define('MODEL_PATH',APP_PATH.'/model/');
define('PLUS_PATH',DATA_PATH.'/plus/');
define('ALL_PS','conn');

数据库配置文件

/config/db.config.php包含数据库连接信息:

$db_config = array(
    'dbtype'=>'mysql',
    'dbhost'=>'localhost',
    'dbuser'=>'root',
    'dbpass'=>'root',
    'dbname'=>'phpyun',
    'def'=>'phpyun_',
    'charset'=>'utf8',
    'timezone'=>'PRC',
    'coding'=>'2c8c4d53878158c06481a39e6d352dbc', //生成cookie加密
);

安全过滤机制

/config/db.safety.php实现了全局过滤:

foreach($_POST as $id=>$v){
    if($id != 'uimage'){
        $str = html_entity_decode($v,ENT_QUOTES);
        $v = common_htmlspecialchars($id,$v,$str,$config);
        safesql($id,$v,"POST",$config);
        $id = sfkeyword($id,$config);
        $v = sfkeyword($v,$config);
    }
    if(trim($id)){
        $_POST[$id] = $v;
    }
}

过滤函数分析

  1. common_htmlspecialchars函数:
function common_htmlspecialchars($key,$str,$str2,$config){
    if(is_array($str)){
        foreach($str as $str_k=>$str_v){
            $str[$str_k] = common_htmlspecialchars($str_k,$str_v,$str2,$config);
        }
    }else{
        $str = preg_replace('/([\x00-\x08\x0b-\x0c\x0e-\x19])/', '', $str);
        if(!in_array($key,array('content','config','group_power','description','body','job_desc','eligible','other','code','intro','doc','traffic','media','packages','booth','participate'))){
            $str = strip_tags($str);
            $str = gpc2sql($str,$str2);
        }else{
            if(!isset($_SESSION)){
                session_start();
            }
            if($_SESSION['xsstooken'] != sha1($config['sy_safekey'])){
                $str = RemoveXSS(urldecode($str));
                $str = gpc2sql($str,$str2);  
            }
        }
    }
    return $str;
}
  1. gpc2sql函数(SQL注入防护):
function gpc2sql($str,$str2) {
    if(preg_match("/select|insert|update|delete|load_file|outfile/is", $str)){
        exit(safe_pape());
    }
    $arr=array("sleep"=>"Sleep"," and "=>" an d "," or "=>" OOr ","xor"=>"xOr","%20"=>" ","select"=>"Select","update"=>"UUpdate","count"=>"Count","chr"=>"Chr","truncate"=>"Truncate","union"=>"UUnion","delete"=>"Delete","insert"=>"IInsert","\""=>"“","'"=>"“","--"=>"- -",""=>"(",""=>")","00000000"=>"OOOOOOOO","0x"=>"Ox");
    foreach($arr as $key=>$v){
        $str = preg_replace('/'.$key.'/isU',$v,$str);
    }
    return $str;
}
  1. RemoveXSS函数(XSS防护):
function RemoveXSS($val) {
    $val = preg_replace('/([\x00-\x08\x0b-\x0c\x0e-\x19])/', '', $val);
    // ... 详细XSS过滤逻辑 ...
    return $val;
}

漏洞分析

漏洞位置

漏洞存在于/member/user/model/expectq.class.php文件的save_action方法中:

function save_action(){
    $IntegralM=$this->MODEL('integral');
    if($_POST['submit']){
        $_POST=$this->post_trim($_POST);
        $eid=(int)$_POST['eid'];
        $data['doc']=str_replace("&","&",$_POST['doc']);
        $_POST['lastupdate']=mktime();
        $_POST['integrity']=100;
        unset($_POST['eid']);
        unset($_POST['submit']);
        unset($_POST['doc']);
        if(!$eid){
            // 新增逻辑
        }else{
            $_POST['height_status']='0';
            $this->obj->update_once("resume_expect",$_POST,array("id"=>$eid));
            $nid=$this->obj->update_once("resume_doc",$data,array("uid"=>$this->uid,"eid"=>$eid));
            if($nid){
                $this->obj->update_once('resume',array('lastupdate'=>time()),array('uid'=>$this->uid));
                $this->obj->member_log("更新粘贴简历",2,2);
                $this->ACT_layer_msg("更新成功!",9,"index.php?c=resume");
            }else{
                $this->ACT_layer_msg("更新失败!",8,"index.php?c=resume");
            }
        }
    }
}

问题函数分析

update_once函数实现:

function update_once($table,$data=array(),$w=''){
    $this->db->connect();
    $value=array();
    $where=array();
    include(PLUS_PATH.'dbstruct.cache.php');
    $TableFullName=$this->def.$table;
    
    if(is_array(
$$
TableFullName)){ 
        $fields=array_keys(
$$
TableFullName);
    }
    
    if(is_array($fields)){
        if(is_array($data)){
            foreach($data as $key=>$v){
                if(in_array($key,$fields)){
                    $v = $this->FilterStr($v);
                    $value[]="`".$key."`='".$this->db->escape_string($v)."'";
                }
            }
        }
        
        if(is_array($w)){
            foreach($w as $key=>$v){
                if(in_array($key,$fields)){
                    $v = $this->FilterStr($v);
                    $where[]="`".$key."`='".$this->db->escape_string($v)."'";
                }
            }
            $where=@implode(" and ",$where);
        }else{
            $where = $w;
        }
        
        $value=@implode(",",$value);
        return $this->DB_update_all($table,$value,$where);
    }
}

漏洞成因

  1. 不安全的WHERE条件update_once函数仅使用id作为条件,没有验证用户ID,导致越权修改:

    $this->obj->update_once("resume_expect",$_POST,array("id"=>$eid));
    
  2. 字段可控:攻击者可以控制POST参数修改任意表字段,包括:

    • top - 置顶状态
    • topdate - 置顶时间
    • uid - 简历所属用户ID
  3. 缺乏权限验证:没有检查当前用户是否有权限修改指定ID的简历

漏洞利用

利用条件

  1. 必须为POST请求且包含submit参数
  2. eid参数必须为有效的简历ID
  3. 传递的参数必须是目标表的字段才能修改成功

攻击示例

  1. 修改简历置顶状态

    POST /path/to/vulnerable/endpoint
    submit=1&eid=3&top=1&topdate=1620000000
    
  2. 修改简历所属用户(将ID为3的简历归属改为用户2):

    POST /path/to/vulnerable/endpoint
    submit=1&eid=3&uid=2
    

修复建议

  1. 添加权限验证:在WHERE条件中加入用户ID验证

    $this->obj->update_once("resume_expect",$_POST,array("id"=>$eid, "uid"=>$this->uid));
    
  2. 字段白名单:限制可修改的字段范围

    $allowed_fields = ['field1', 'field2', 'field3']; // 允许修改的字段
    $_POST = array_intersect_key($_POST, array_flip($allowed_fields));
    
  3. 加强日志记录:记录所有简历修改操作,便于审计

总结

该漏洞展示了即使使用看似安全的函数(如update_once),如果使用不当(缺少必要的权限验证),仍然会导致严重的安全问题。在开发过程中,必须始终遵循最小权限原则,对所有数据修改操作进行严格的权限验证。

PHPYUN人才系统审计:正常函数的不安全用法引发的逻辑漏洞 漏洞概述 本文详细分析了PHPYUN人才系统中由于 update_once 函数的不安全使用导致的逻辑漏洞,该漏洞允许攻击者越权修改其他用户的简历信息,包括置顶状态和所属用户等敏感字段。 系统架构分析 入口文件分析 系统入口文件 index.php 首先包含 global.php : 全局配置文件 global.php 定义了多个路径常量: 数据库配置文件 /config/db.config.php 包含数据库连接信息: 安全过滤机制 /config/db.safety.php 实现了全局过滤: 过滤函数分析 common_htmlspecialchars 函数: gpc2sql 函数(SQL注入防护): RemoveXSS 函数(XSS防护): 漏洞分析 漏洞位置 漏洞存在于 /member/user/model/expectq.class.php 文件的 save_action 方法中: 问题函数分析 update_once 函数实现: 漏洞成因 不安全的WHERE条件 : update_once 函数仅使用 id 作为条件,没有验证用户ID,导致越权修改: 字段可控 :攻击者可以控制POST参数修改任意表字段,包括: top - 置顶状态 topdate - 置顶时间 uid - 简历所属用户ID 缺乏权限验证 :没有检查当前用户是否有权限修改指定ID的简历 漏洞利用 利用条件 必须为POST请求且包含 submit 参数 eid 参数必须为有效的简历ID 传递的参数必须是目标表的字段才能修改成功 攻击示例 修改简历置顶状态 : 修改简历所属用户 (将ID为3的简历归属改为用户2): 修复建议 添加权限验证 :在WHERE条件中加入用户ID验证 字段白名单 :限制可修改的字段范围 加强日志记录 :记录所有简历修改操作,便于审计 总结 该漏洞展示了即使使用看似安全的函数(如 update_once ),如果使用不当(缺少必要的权限验证),仍然会导致严重的安全问题。在开发过程中,必须始终遵循最小权限原则,对所有数据修改操作进行严格的权限验证。