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;
}
}
过滤函数分析
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;
}
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;
}
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);
}
}
漏洞成因
-
不安全的WHERE条件:
update_once函数仅使用id作为条件,没有验证用户ID,导致越权修改:$this->obj->update_once("resume_expect",$_POST,array("id"=>$eid)); -
字段可控:攻击者可以控制POST参数修改任意表字段,包括:
top- 置顶状态topdate- 置顶时间uid- 简历所属用户ID
-
缺乏权限验证:没有检查当前用户是否有权限修改指定ID的简历
漏洞利用
利用条件
- 必须为POST请求且包含
submit参数 eid参数必须为有效的简历ID- 传递的参数必须是目标表的字段才能修改成功
攻击示例
-
修改简历置顶状态:
POST /path/to/vulnerable/endpoint submit=1&eid=3&top=1&topdate=1620000000 -
修改简历所属用户(将ID为3的简历归属改为用户2):
POST /path/to/vulnerable/endpoint submit=1&eid=3&uid=2
修复建议
-
添加权限验证:在WHERE条件中加入用户ID验证
$this->obj->update_once("resume_expect",$_POST,array("id"=>$eid, "uid"=>$this->uid)); -
字段白名单:限制可修改的字段范围
$allowed_fields = ['field1', 'field2', 'field3']; // 允许修改的字段 $_POST = array_intersect_key($_POST, array_flip($allowed_fields)); -
加强日志记录:记录所有简历修改操作,便于审计
总结
该漏洞展示了即使使用看似安全的函数(如update_once),如果使用不当(缺少必要的权限验证),仍然会导致严重的安全问题。在开发过程中,必须始终遵循最小权限原则,对所有数据修改操作进行严格的权限验证。