前言
考完试终于有时间来进行一波网安的学了,ciscn几道框架题看得我头皮发麻,正好趁着暑假开始学习框架,我们先从php框架开始。
关于php框架
php的开源框架有很多,目前在ctf中遇到的比较多的框架有Thinkphp、zendframework(Laminas)等等,而我们就从资料比较多、难度也没那么大的Thinkphp5.0框架开始。
目录结构
1 | application 应用目录 |
运行原理

mvc设计模式
m(模型)v(视图)c(控制器)是Thinkphp的一大特色,c通过调度m获取数据,加载v将数据返回给客户端。
模型层(m)
在m层中,比较复杂的项目设计需要区分数据层、逻辑层、服务层等不同的模型层,因此可以在模块目录下面创建Model、Logic和Service目录,把对用户表的所有模型操作分成三层:
- 数据层:Model/UserModel 用于定义数据相关的自动验证、自动完成和数据存取
- 逻辑层:Logic/UserLogic 用于定义用户相关的业务逻辑
- 服务层:Service/UserService 用于定义用户相关的服务接口等
这三个模型操作类统一都要继承Model类。
视图层(v)
以首页设置来说:
1 | class Index extends Controller{ |
fetch()传入的参数是模板名,用模板文件来输出。如果 fetch() 不传参数,程序会自动寻找 view/index/index.html渲染输出。如果传参数,比如传入” hello“,那么程序会寻找view/index/hello.html来渲染输出。
而如果使用display()来代替fetch()的话,会直接输出传递的内容,如果没有传递参数,会渲染出Layout,但不会有任何内容。如果传递参数, 比如 “hello”,那么页面会直接输出字符串 “hello”。
而view()的使用与fetch()相同,但写法上有一定不同:
1 | class Index extends Controller{ |
控制器层(c)
c层由核心控制器和业务控制器组成,核心控制器由系统内部的App类完成,负责应用(包括模块、控制器和操作)的调度控制,包括HTTP请求拦截、转发、加载配置等;而业务控制器则由用户定义的控制器类完成。
同样在复杂项目中可以对业务控制器进行分层,分为访问控制器和事件控制器,访问控制器负责外部交互响应,通过URL请求响应,例如 http://域名/Home/User/login.html;事件控制器负责内部的事件响应,并且只能在内部调用,所以是和外部隔离的,确切的说,访问控制器之外的分层控制器都只能内部实例化调用。
url访问
1 | 不支持普通的模式 http://www.tp5.com/index.php?m=index&c=Index&a=add |
路由
1 | (1)普通使用: |
代码审计
我们以Thinkphp5的远程代码RCE为例进行漏洞分析。首先查看thinkphp/library/think/App.php:540:
1 | // 路由检测(根据路由定义返回不同的URL调度) |
由于没有在配置文件中定义任何路由,因此按照路由到模块/控制器的方法进行解析调度,接下来进行跟进thinkphp/library/think/Route.php:1238:
1 | // 解析操作 |
可以发现解析url的时候框架知识将URL按照分割符进行分割,并没有进行安全检测。继续跟进thinkphp/library/think/App.php:333:
1 | // 模块初始化 |
在攻击时注意使用一个已存在的module,否则会抛出异常,无法继续运行。
接下来对控制器进行实例化的部分进行跟进thinkphp/library/think/Loader.php::
1 | public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) |
正常情况下应该获取到对应控制器类的实例化对象,而我们现在得到了一个\think\App的实例化对象,进而通过url调用其任意的public方法,同时解析url中的额外参数,当作方法的参数传入。
payload:
1 | ?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=id |
总结
整个流程跟进分析下来感觉还是很吃力的,对于框架的漏洞挖掘可以看得出还是相当有难度的。之后应该也会刷刷vulhub学习一些漏洞。