代码审计思路之 PHP 代码审计
00×0 前言
最近也是边挖 src 边审计代码,总结下最近的 php 代码审计的一些思路,我一般按照顺序往下做,限于能力水平,可能会有不对或者欠缺的地方,希望各位师傅能够指导。
00×1 前期工作,需要的工具(我使用的)
PHPStorm|是 PHP 编程语言开发的集成环境。
Fotify|代码审计静态扫描工具,商业化静态代码扫描工具,误报率相对较低。
seay|源代码审计工具
CodeQl | 高效的 QL 非商业的开源代码自动化审计工具。
xcheck| Xcheck 是一款静态应用安全测试工具,旨在及时发现业务代码中的安全风险,尤其是由不受信输入所触发的安全漏洞。检测范围覆盖主流 Web 安全漏洞,具备速度快、误报低和准确率高等优点。
chrome & HackerBar 插件
00×3 明确目标
在审计之前,我们首先先确定自己此次审计的目地,我觉得会有三种情况
为了提升自己的审计经验
项目中为了审计出能进一步利用的漏洞,一般需要 getshell、ssrf 这种级别的。
为了挖点洞,去换钱或者换 cve&cnvd。
有什么区别呢?
为了提升审计经验,我会去重点关注历史漏洞,并去复现。
如果是为了能审出漏洞,去用作渗透中的进一步利用,那么我觉得,可以重点使用 xcheck、Fotify 等自动化代码审计,然后关注下面的文件上传、包含、sql 注入等等有严重危害的漏洞
如果是为了挖 0day,搞证书什么的,那么全方位按步骤过一遍,是不错的选择。
【一>所有资源获取<一】1、200 份很多已经买不到的绝版电子书 2、30G 安全大厂内部的视频资料 3、100 份 src 文档 4、常见安全面试题 5、ctf 大赛经典题目解析 6、全套工具包 7、应急响应笔记 8、网络安全学习路线
00×4 判断是否是用了框架
判断是否使用了框架,是蛮重要的,能帮助我们快速定位有用的函数集,筛选不需要去看的代码。
一般来说,我觉得使用了框架的更好审计一点,因为使用了框架的,他的函数集文件(各种方法 function)会比较规整,在某些固定文件夹中,清晰可见,当然需要我们先对框架有所了解。
目前比较主流的设计模式是 MVC,即多层模型(M)、视图(V)、控制器(C),在此不多赘述,php 的主流框架几乎都使用了 MVC 设计模式。
PHP 底下的开发框架目前见的比较多的有 Laravel,ThinkPHP,yii 等。
4.1. ThinkPHP 框架
ThinkPHP 这里需要区分 TP3 和 TP5 的差别,首先我们先来看看 TP3 的目录结构。(现在基于 TP3 的系统都很少了。。。了解一下就好
其中,Application 和 Public 目录下面都是空的。
Application 是存放项目中的重要的一些函数集,Public 是公共文件夹,供用户访问的,重要的函数集千万不能放在此文件夹下。
Application 目录默认是空的,但是第一次访问入口文件会自动生成,参考后面的入口文件部分。其中框架目录 ThinkPHP 的结构如下:
另外 TP5 和 TP3 实际上差距有点大,先看看 TP5 下载下来的默认文件结构。其中在 public 文件下有个 route.php 文件,它的作用是用于 php 自带 webserver 支持,可用于快速测试,启动命令:php -S localhost:8888 router.php。而它的相关网站功能目录也需要从根目录下的 index.php 入手。
以下为 TP5 的目录结构。
一般如果是审计基于框架的 cms,我不会去看框架系统目录,就是上面的 ThinkPHP 文件夹下的东西,第三方类库 vendor 也不会去先看,除非是在审计过程中流向了这些文件中,才会大概看一看,而重点在 Application 文件夹下做文章。
既然是 MVC 框架的,那么我们真正关心的是其中的控制器(C),因为功能点大部分都在 C 上,我们能找到的大部分漏洞也都在 C 上
下图为基于 TP6 的 ThinkAdmin 项目目录
app(也就是 application),下面有 admin、data、index、wechat 几个文件夹,每个文件夹代表了一个应用,比如 admin 一般来说都是后台的服务,wechat 为微信应用服务,每个应用下面都有 Controller(控制器)、Module(模型)、View(视图,一般是 html 文件)
现在目录很明确,目标就很明确,拿到这样基于框架的 cms,就应该知道,该重点审计的地方在哪里。
4.2. Laravel 框架
目录怎么变,MVC 架构的重点还是在 Controllers 里
4.3. 如果没用框架
没用框架的话,先搞明白目录结构,一般来说
审计过程中需要关注几个点:(在我们后面开始审计的过程中,自己要注意这些地方,经常想一想)
1)函数集文件,通常命名包含 function 或者 common 等关键字,这些文件里面是一些公共的函数,提供其他文件统一调用,所以大多数文件都会在文件头部包含到其他文件。寻找这些文件一个非常好用的技巧就是去打开 index.php 或者一些功能性文件,在头部一般都能找到。
2)配置文件,通常命名中包括 config 关键字,配置文件包括 web 程序运行必须的功能性配置选项以及数据库等配置信息。从这个文件中可以了解程序的小部分功能,另外看这个文件的时候注意观察配置文件中参数值是单引号还是用双引号括起来,如果是双引号可能就存在代码执行的问题了。
3)安全过滤文件,安全过滤文件对代码审计至关重要,这关系到我们挖掘到的可以点能否直接利用,通常命名中带有 filter、safe、check 等关键字,这类文件主要是对参数进行过滤,大多数的应用其实会在参数的输入做一下 addslashes()函数的过滤。
4)index 文件,index 是一个程序的入口,所以通常我们只要读一读 index 文件就可以大致了解整个程序的架构、运行的流程、包含到的文件,其中核心的文件有哪些。而不同目录的 index 文件也有不同的实现方式,建议最好将几个核心目录的 index 文件都通读一遍。
00×5 了解路由
我很喜欢 Thinkphp 这类框架的原因是,他们的路由很好摸清,如果在哪个方法中找到了漏洞,我就能直接根据路由访问这个方法,直接利用。
了解路由也是为了能快速定位漏洞位置,要不然,你通过审计源码找到的漏洞,却不知道在浏览器中用什么样的 url 去访问,这不是件很尴尬的事儿吗?
比如 Thinkphp 的路由有三种方式
5.1. 普通模式
关闭路由,完全使用默认的 pathinfo 方式 URL:
‘url_route_on’ => false,
路由关闭后,不会解析任何路由规则,采用默认的 PATH_INFO 模式访问 URL:
module/controller/action/param/value/…
module
就是使用的应用。
controller
是控制器,跟文件名一致。
action
是方法,某控制器下的方法。
param
是需要的变量
value
是参数
但仍然可以通过 Action 参数绑定、空控制器和空操作等特性实现 URL 地址的简化
5.2. 混合模式
开启路由,并使用路由+默认 PATH_INFO 方式的混合:
‘url_route_on’ => true,
该方式下面,只需要对需要定义路由规则的访问地址定义路由规则,其它的仍然按照默认的 PATH_INFO 模式访问 URL。
5.3. 强制模式
开启路由,并设置必须定义路由才能访问:
这种方式下面必须严格给每一个访问地址定义路由规则,否则将抛出异常。
首页的路由规则是 /
。
其实,在实际审计过程中,我一般会先去黑盒访问一遍功能点,分析后差不多也能知道路由怎样构成,如果有的地方不清楚,可以去源码中找路由文件
一般带有 route 关键词的文件,或文件夹与路由有关。
分析好路径,之后就可以真正的开始审计。
00×6 审计
在人工审计之前,可以使用我之前提到的 xcheck、Fotify、codeql 等自动化审计工具先审计一遍,根据报告,验证一遍,再往下去根据下面的步骤审一遍,一个项目,也就能审个七七八八了,深层次的利用也就得看自身的实力与经验了。
如果使用了框架,可以先看看此项目还有没有框架的漏洞存在,我就不再赘述了。
6.1. 鉴权
首先对于项目整体的一个权限认证做一个判断,判断是否存在越权,未授权访问的情况。
一般来说,需要权限认证的地方,是后台管理,即 admin 应用下的。
所以对于 admin 下的控制器这些方法,需要判断是否可以未授权访问。
目前对于整个后台管理鉴权的方式,一般是采用写一个基类,比如 Base.php 或者 common.php,其中存在鉴权方法,然后在每个控制器类继承这个类。
比如 xiaohuanxiong 漫画 cms 的后台,就是采用了这种方法。
不过我也看到了,有的比较好的项目,自己二开框架,做了自己的组件,然后,每个类都继承了此组件,也是同样的原理
比如 ThinkAdmin,继承了自己组件的 controller。
我们知道了鉴权的方式,所以我们首先看的是,如果他没有这些鉴权方式,或者其他鉴权方式也没有,那么他就会存在未授权访问,即不登录也能访问后台功能。这是很危险的,一个是管理员才能看到的敏感信息,未授权就能看到,更危险的是,结合后台的漏洞,直接未授权 getshell 也是很有可能的,所以鉴权我们首先去看,而且容易去看的地方。
6.2. 按照漏洞类型审计
我认为对于我来说,比较好的审计方法是黑盒白盒一起,根据漏洞类型一个一个的去找寻可能存在漏洞的地方,然后再回溯查看是否用户可控,以此快速定位漏洞。
所以一般我是根据漏洞类型,以及每个漏洞可能涉及的危险函数,去快速定位。
那一般看的地方有SQL注入、XSS、CSRF、SSRF、XML
外部实体注入等等
6.2.1. sql 注入
如果使用了框架,可以分辨一下框架名称以及版本,去搜索一下该版本的框架是否存在漏洞,如果存在再去 cms 中验证。因为本篇文章主要讲我自己在 cms 审计上的一些经验,因此不多深入框架的审计部分。
如果没有使用框架,则需要仔细的观察数据库函数,一般来说,cms 是将 select、insert 等函数进行了封装的,比如
$db->table(‘test’)->where(“name=admin”)
便是select * from test where name=admin
这种格式,而此时若是发现 cms 使用的是过滤+拼接,那么很有可能会出现问题,而如果使用了 PDO,则继续跟进涉及到 table,order by 等字段的拼接去,因为这些字段是无法使用 PDO 的。
审计要素:
参数是否用户可控
是否使用了预编译
那么首先,如果没有使用框架封装的 sql 语句,那么全局搜索 insert、select 等 sql 语句关键词,然后定位到具体的语句,然后查看里面有没有拼接的变量,回溯可不可控。如果可控并且存在字符串拼接,很有可能就存在漏洞。
使用了框架的就是搜索的关键词不一样,还是得看是否存在字符串拼接,可不可控。
即使使用了预编译,但是如果在预编译之前字符串拼接了,那照样没有鸟用,该注入还是能注入。
下面提供一般我会搜索的关键词(框架的根据你审计项目的框架的手册,自行搜索。)师傅们有想补充的也可以补充。
6.2.2. xss 漏洞
审计要素
是否存在全局参数过滤器,过滤规则是否符合安全要求,是否存在需过滤和不需过滤两种输出,页面是否控制恰当。
输出时是否进行编码(HTML、JS 等)。
前端是否采用了 Angularjs、React、vue.js 等具有 XSS 防护功能的前端框架进行数据输出。
这个的话,我就不会关键词搜了,我就是会在寻找其他漏洞的过程中,注意有没有直接把输入原样输出的地方,也没有特别关注这一块。
如果想特意挖掘这一块,可以
查看是否配置了全局的拦截器、过滤器。检查数据输出函数,例如常用的输出函数有print、print_r、echo、printf、sprintf、die、var_dump、var_export
。
6.2.3. CSRF 漏洞
与 XSS 攻击相比,CSRF 攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比 XSS 更具危险性。
审计要素
是否在表单处存在随机 token。
是否存在敏感操作的表单。
CSRF 主要利用场景实际上是一些越权的操作,或者一些敏感功能存在的地方,例如管理后台、会员中心等地方。我们可以尝试搜索表单位置,查看是否会生成随机 token,在查看后端代码中是否会先验证这部分的 token。如果没有验证 token,再进一步看看是否有 refer 的相关验证,如果没有,那么就存在 CSRF 的问题。
可以尝试全局搜索
下面是一个更新密码的操作,假设构造一个链接为 http://127.0.0.1/index.php?password_new=password&password_conf=password&Change=Change#
的链接,直接发送给受害者点击,那么当前情况下,可以直接修改受害者的密码,因为没有进行任何的验证措施。当然一般代码不会这么写,只是拿 DVWA 的 CSRF 举个例子。
6.2.4. SSRF 漏洞
ssrf 是利用存在缺陷的 web 应用作为代理攻击远程和本地的服务器。常见的方式如下:
1.可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的 banner 信息;
2.攻击运行在内网或本地的应用程序(比如溢出);
3.对内网 web 应用进行指纹识别,通过访问默认文件实现;
4.攻击内外网的 web 应用,主要是使用 get 参数就可以实现的攻击(比如 struts2,sqli 等);
5.利用 file 协议读取本地文件等。
审计要素:
是否存在可以产生 SSRF 漏洞的函数。
是否存在内网 ip 地址正则过滤,且正则是否严谨。
是否存在限制请求的方式只能为 HTTP 或者 HTTPS。
当然 PHP 底下经常可能会出现 SSRF 漏洞的主要有几个函数,它们分别是 file_get_contents()、fsockopen()、curl_exec()、get_headers()。通过全文关键函数搜索,在看是否限制了访问端口,访问协议,内网 ip 地址等。
利用file://、http/https:// 、dict://、gopher://
协议去搞内网。
列一下,我经常搜索的关键词
注意
一般情况下 PHP 不会开启 fopen 的 gopher wrapper
file_get_contents 的 gopher 协议不能 URL 编码
file_get_contents 关于 Gopher 的 302 跳转会出现 bug,导致利用失败
curl/libcurl 7.43 上 gopher 协议存在 bug(%00 截断) 经测试 7.49 可用
curl_exec() //默认不跟踪跳转,
file_get_contents() // file_get_contents 支持 php://input 协议
各种绕过,我就不在这说了。
6.2.5. XML 外部实体注入
审计要素
参数是否用户可控
是否 libxml 版本为 2.9.0 以上
是否禁用了外部实体
这个一般我关注的少,仅仅是搜索“DOMDocument”,“SimpleXMLElement”和“simplexml_load_string”
等关键词,分析下是否存在参数拼接的 XML 字符串,或未做限制的批量解析方法。对参数进行回溯,判断其是否用户可控。
6.2.6. 文件包含漏洞
审计要素
参数是否用户可控
是否存在
include,require,include_once, require_once
等函数。
文件包含算是拿 shell 最快的方法了,所以一般要重点关注。
无非是include,require,include_once, require_once
这四个函数,全局搜索这四个函数,一个一个去看,去回溯,查看变量可不可控。
6.2.7. 文件上传漏洞
审计要素
是否检查了上传文件的文件类型
是否限制了文件上传路径
是否对文件进行了重命名
文件大小是否限制
是否返回了文件路径或文件路径很好猜测
有的项目,会对文件上传下载进行分装,所以可以全局搜索有关 upload、file 的函数,看看是不是封装了
如果封装了,那么就看这些封装好的函数,有没有上面提到的审计要素的漏洞。
如果没封装,一般是move_uploaded_file
这个函数,全局搜索,这个函数,回溯查看这些漏洞存不存在。(白盒黑盒一起搞比较好。)
6.2.8. 变量覆盖
审计要素
是否存在造成变量覆盖的函数,例如:
extract()、parse_str()、import_request_variables
和 $$等。是否存在可以完整利用的攻击链。
一般就这几个函数和关键词
不过还有个特殊的配置,也可能造成变量覆盖
register_globals 全局变量覆盖
php.ini 中有一项为register_globals
,即注册全局变量,当register_globals=On
时,传递过来的值会被直接的注册为全局变量直接使用,而register_globals=Off
时,我们需要到特定的数组里去得到它。
注意:register_globals 已自 PHP 5.3.0 起废弃并将自 PHP 5.4.0 起移除。
当 register_globals=On,变量未被初始化且能够用户所控制时,就会存在变量覆盖漏洞:
通过 GET 和 POST 方式输入变量 a 的值:
当然,也可以从 COOKIE 中输入:
6.2.9. 代码执行漏洞
审计要素
php.ini 文件中的 disable_function 是否有禁用函数。
是否存在代码执行的敏感函数。
是否输入变量可控。
全局搜索下面的关键词,回溯参数可不可控。
6.2.10. 命令执行漏洞
审计要素
参数是否用户可控
是否配置了全局过滤器,过滤规则是否符合安全规范
是否所有的命令执行参数都经过了过滤器,或受白名单限制
全局搜索下面的关键词,回溯参数可不可控。
“(被反引号包裹的变量也可以执行)
6.2.11. 任意文件下载/下载漏洞审计
审计要素
是否存在../、.、..\等特殊字符过滤。
参数是否用户可控
是否配置了相对路径或者绝对路径。
查询这些关键词,查看变量是否可控,是否有过滤
**Tip:**前两天遇到个,过滤了 config/database.php 这样的正则匹配,还过滤了..,目的是防止目录穿越,读取服务器其他目录的文件,可是没过滤一个.
这样我使用 config/./database.php 绕过了正则,照样把敏感文件读取出来了。。。
6.2.12. 任意文件删除
和上面的下载一样
搜索的关键词变了
6.2.13. 任意文件写入
还是一样,关键词为
6.2.14. 会话认证漏洞
会话认证漏洞实际上涉及的方面比较广,如 cookie、session、sso、oauth 等,当然这个漏洞比较常见是在 cookie 上,服务端直接取用 cookie 中的数据而没有校验,其次是 cookie 加密数据在可预测的情况下。
审计要素
是否 cookie 中的加密数据可预测。
是否 cookie 中的数据可预测。
服务端是否只依赖 cookie 来判断用户身份。
全局去寻找 cookie 生成的逻辑,判断是否可预测,判断用户身份是否只依赖 cookie,而不是随机的,比如
鉴权是只通过 cookie 中的 userid 来判断,如果我遍历 userid,可以达到登录绕过或越权的目地。
6.2.15. 反序列化漏洞
一般实际审计的时候,项目中见的比较少,框架中见的比较多。
全局搜索 serialize。看看存不存在可控变量。
评论