前言:这个漏洞我复现的不是很完善,按网上的教程,传入图片码是可以被解析为php文件,从而实现攻击,但是我尝试了几次都不成功,目前还不知道是为什么。
影响版本
5.0.0 <= Thinkphp <= 5.0.18
5.1.0 <= ThinkPHP <= 5.1.10
环境搭建
这里我是在网上直接找的thinkphp5.0.18源码,然后用phpstudy进行搭建的。
源码地址:https://www.codejie.net/5913.html
漏洞复现
首先是进入 application\index\controller\index.php 文件中, 将代码修改为以下内容
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
public function index()
{
$this->assign(request()->get());
return $this->fetch(); // 当前模块/默认视图目录/当前控制器(小写)/当前操作(小写).html
}
}
之后在创建 application\index\view\index\index.html 其中index.html的内容可以随意写,只要你能认得出来是这个文件就行。如果没有这个模板文件的话会报错。它的作用其实就是起一个被替换的作用,之后会讲解。
之后就是把图片码放入 public 目录下,以此模拟文件上传,不过我的图片码可能生成或别的原因,无法起作用,这里就放入一个一句话木马文件,仅做演示。
之后访问 http://localhost/public/?cacheFile=1.php
代码审计
接下来就是一步一步理解其代码是如何执行的。
application\index\controller\index.php
1. 我们先跟进 assign() 函数
Controller.php
可以看到它又指向了其他文件中的 assign() 函数,那 view 的赋值是什么呢。
这里我们可以看到 view 有 View 和 Config两种,这里可以通过后面的方法名意思来判断,或者全局搜索
就这四个文件中有 assign() 方法,但 View.php 有两个,至于选哪个,如果你做过thinkphp的饭序列化,可以明白文件之间要靠 namespace 和 use 来联系,这里就不讲了,你们可以看看 Controller.php 和 这两个 View.php 前面的 namespace 和 use 差异来判断。这里选择的就是第一个View.php。
View.php
这里就可以知道他把我们输入的参数转为了array型。
如:我们传入 ?file=1.jpg
它会将其转为 array("file" => "1.jpg")
这里assign()方法就审完了。
2. get()方法就没必要跟进了,它其实就是get传参,而request()函数是一个全局函数,它的作用就是获取HTTP的请求数据的。
这里可以知道的就是跟进get()函数, 会在后面进入 filerEXP() 函数中进行数据过滤,不过不会影响到我们。
3. fetch()函数
Controller.php
这里又是进行一次跳转。
View.php
第一段意思就是把我们 get传参的参数存入$data中
第二段意思就是又进行一次跳转
这里可以判断出跳转到了 Think.php文件中
Think.php
又进行了跳转
Template.php
查找storage
File.php
很明显发现在这里可能会产生文件包含漏洞。但问题也来了,$cacheFile 这个变量的值要怎么赋值呢。这里就利用extract()这个函数的特性。它的作用就是可以把一个数组类型,其键值取出单独作为一个变量,之后对应的值就作为这个变量的值。
例:
<?php
$a = "Original";
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a; \$b = $b; \$c = $c";
?>
//运行结果: $a = Cat; $b = Dog; $c = Horse
所以这也就是为什么我们要传 ?cacheFile=1.php 因为它们先会被转为数组类型 array("cacheFile" => "1.php"), 然后利用 extract 函数就成功控制 $cacheFile 变量,给其赋值。
漏洞修复
File.php文件的read方法中修改为以下内容
加上这两行。其作用就是在还没进行extract函数之前,先把$cacheFile的值赋给$this->cacheFile,之后再对$this->cacheFile进行包含,这样即使$cacheFile的值发生改变了,也没有什么影响。
补充:
这里我们可以单独打印以下$this->cacheFile和$cacheFile的值
可以看到其实第二个才是进入read方法时$cacheFile的值,其赋值过程在 Template.php,fetch方法中