前言

学得越多,才发现自己懂得越少。

从刚开始交了几个 cnvd 的喜悦到现在专注于技术的积累。慢慢发现,大学时期和几个小伙伴一起做研究的时间是很美好的 : ) 有段时间,因为审计没什么产出而感到些许的烦躁。到现在就算审不出什么也很平静,保持审计的手感与思路,虽然没有产出,但是会为代码能力的不断提高而感到开心。因为暑假在四叶草的实习,还有和很多师傅们的交流,发自己可能对 java 安全更加感兴趣,所以便慢慢开始入门 java 安全相关的东西,写写 spring,看看开发 … 这也导致原来立下的 flag 没有完成,还记得暑假和 rt 说把 laravel 提上日程,到现在也只跟过一个 laravel 的反序列化的洞哈哈。

再谈谈 ctf,在这学期开学才加入的协会,看到师傅们都很厉害,才发现自己懂得很少。关于 ctf,其实我不怎么打,只是会偶尔看看 wp,有时间就会复现学习。这周末正好有两个比赛,一个是 360 的个人赛 3ctf,还有就是 bytectf 的 awd 赛。周末嘛,睡了个懒觉起来发现错过了 3ctf 的理论赛,看了下题没啥想法,看到一个 java 的审计,感觉有一些思路,但是不知道是不是题目挂了,一直不能访问。然后就没做了 … 后来看到 car0ta 进了复赛,又想起前天晚上看了一会他的博客,能明显感觉到 car0ta 的成长 : )(不知道是不是因为买了 mac)。晚上的时候,向协会师傅要了 bytectf 的代码,碰巧审出了两个洞,才有了这篇文章。

下面分析使用的均是官网下载的代码,不是 bytectf 实际赛题。

任意文件包含

opensns 是想天科技开发的一款综合性社交软件。基于 thinkphp 3.x。你可以使用 opensns 快速搭建一个类似于新浪微博一样的功能强大的社交网站。

opensns 6.1.0:http://www.opensns.cn/home/index/download.html

在 tp 中,我们使用 assign() 方法对模板变量进行赋值。

01

assign() 第一个参数指定模板变量的名字,一般来说,该位置是不可控的,都是被硬编码成指定的变量名。但是在 opensns 中,存在一处 assign() 方法的第一个参数完全可控的地方。

Application/Home/Controller/IndexController.class.php line 82

02

search() 方法从 post 中取出 keywords,当作第一个参数传入 assign(),跟进 assign()。

ThinkPHP/Library/Think/View.class.php line 37

03

可以看到如果我们传入的 keywords 为一个数组,则会 merge 到 tVar 属性中,相当于 tVar 这个属性我们可控。跟入 search() 方法最后的 display() 方法。

ThinkPHP/Library/Think/View.class.php line 70

04

ThiknPHP/Library/Think/View.class.php line 111

05

在 fetch() 方法中,将我们可控的 tVar 属性丢进了 params 变量中,并传入 Hook::listen() 方法。

ThinkPHP/Library/Think/Hook.class.php line 81

06

ThinkPHP/Library/Think/Hook.class.php line 113

07

ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php line 20

08

这里 _data 数组的 var 指向的值就是我们可控的 tVar 的内容,跟进 fetch() 方法。

ThinkPHP/Library/Think/Template.class.php line 74

09

我们可控的内容作为 load() 的第二个参数传入,跟进 Storage::load() 方法。

ThinkPHP/Library/Think/Storage/Driver/File.class.php line 76

10

在 load() 方法中,因为 vars 参数可控,导致了变量覆盖。我本来想覆盖实际包含的模板文件中的变量实现利用,但是好像没找到什么好的利用点。但是我们可以覆盖 _filename 参数,实现任意文件包含、任意文件读取。

当然,如果我们结合图片上传点的话,就可以 getshell 啦。

11

结合文件包含。

12

任意代码执行

豆信是一个专注于微信公众号开发的开源框架,基于 tp 3.x,具备简洁、高效、优雅、易扩展等特点。

douchat:https://github.com/douchat/douchat

这个位置其实是协会师傅的想法,但是实际漏洞点和一开始想的不一样。

App/Mp/Controller/MobileBaseController.class.php line 192

13

可以看到,进入 parent::display() 方法的所有参数我们可控,但是这里有一个限制。templateFile 指定的文件要存在。因为 templateFile 不是我们的利用点,所以只要存在即可,进不进 switch 无所谓。但是,如果不进 switch 的话比较方便构造,只要 templateFile 参数存在 2 个 / 即可。

1
2
payload: 
index.php?s=/Mp/MobileBase/display&templateFile=././index.php&content=%3C?php%20system(%27ls%27);&prefix=../../../

跟进 parent::display() 方法。

ThinkPHP/Library/Think/View.class.php line 67

14

ThinkPHP/Library/Think/View.class.php line 106

15

ThinkPHP/Library/Think/Hook.class.php line 81

16

ThinkPHP/Library/Behavior/ParseTemplateBehavior.class.php line 20

17

ThinkPHP/Library/Think/Template.class.php line 74

18

到这里,和第一个洞的分析差不多。下面我们看一下 loadTemplate() 方法。

ThinkPHP/Library/Think/Template.class.php line 88

19

在 loadTemplate() 方法中,先生成了一个缓存文件名,其中我们可控的 prefix 参数会被拼接进去,这样我们可以将缓存文件写在任意目录。然后将我们可控的 templateFile 参数,即 php 代码,赋给 tmplContent 变量,经过 compiler() 方法解析后写入缓存文件中。最后,将缓存文件名返回。我们跟入 compiler() 方法。

ThinkPHP/Library/Think/Template.class.php line 124

20

主要关注图中两行,在写入缓存文件的内容中前面会加上<?php if(!defined('THINK_PATH')) exit();?>,并过滤掉?><?php。compiler() 方法解析完后,将内容写入缓存文件,loadTemplate() 方法将缓存文件名返回。此时,缓存文件已经写入了我们的 php 代码。

21

ThinkPHP/Library/Think/Template.class.php line 74

22

缓存文件名被传入 Storage::load() 方法中,该方法就是前面第一个漏洞的变量覆盖处。

ThinkPHP/Library/Think/Storage/Driver/File.class.php line 76

23

这里通过 include 包含了缓存文件,我们写入的 php 代码将会执行。

24

xsrc 漏洞

说到 tp,正好前段时间开源的 xsrc 也是基于 tp 的。团队师傅 @京亟 审出了一个因为 tp 使用不当导致的逻辑漏洞。因为 create() 方法默认取 post 中的全部参数,且未使用 field() 方法指定插入数据库的字段名,而产生的逻辑问题。详细分析关注红日安全团队~

总结

关于开发,可以制定一些统一的规范,比如 assign() 为模板变量赋值时,要指定模板变量名。在向数据库插入数据时,要通过 filed() 指定字段名等。这样可能就能减少一部分的安全问题。