PHP代码审计一条龙思路

 

00×0 前言

最近也是边挖src边审计代码,总结下最近的php代码审计的一些思路,我一般按照顺序往下做,限于能力水平,可能会有不对或者欠缺的地方,希望各位师傅能够指导。

 

00×1 前期工作,需要的工具(我使用的)

PHPStorm|是PHP编程语言开发的集成环境。

Fotify|代码审计静态扫描工具,商业化静态代码扫描工具,误报率相对较低。

seay|源代码审计工具

CodeQl | 高效的QL非商业的开源代码自动化审计工具。

xcheck | Xcheck 是一款静态应用安全测试工具,旨在及时发现业务代码中的安全风险,尤其是由不受信输入所触发的安全漏洞。检测范围覆盖主流 Web 安全漏洞,具备速度快、误报低和准确率高等优点。

chrome & HackerBar插件

。。。

 

00×3 明确目标

在审计之前,我们首先先确定自己此次审计的目地,我觉得会有三种情况

  1. 为了提升自己的审计经验
  2. 项目中为了审计出能进一步利用的漏洞,一般需要getshell、ssrf这种级别的。
  3. 为了挖点洞,去换钱或者换cve&cnvd。

有什么区别呢?

为了提升审计经验,我会去重点关注历史漏洞,并去复现。

如果是为了能审出漏洞,去用作渗透中的进一步利用,那么我觉得,可以重点使用xcheck、Fotify等自动化代码审计,然后关注下面的文件上传、包含、sql注入等等有严重危害的漏洞

如果是为了挖0day,搞证书什么的,那么全方位按步骤过一遍,是不错的选择。

 

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框架

文章地址:https://blog.csdn.net/jiangnanqbey/article/details/80746252

目录怎么变,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. 强制模式

开启路由,并设置必须定义路由才能访问:

‘url_route_on’ => true,
‘url_route_must’=> true,

这种方式下面必须严格给每一个访问地址定义路由规则,否则将抛出异常。

首页的路由规则是 /

其实,在实际审计过程中,我一般会先去黑盒访问一遍功能点,分析后差不多也能知道路由怎样构成,如果有的地方不清楚,可以去源码中找路由文件

一般带有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注入

来自 梅子酒师傅的 《对PHP类CMS审计的一点总结

  1. 如果使用了框架,可以分辨一下框架名称以及版本,去搜索一下该版本的框架是否存在漏洞,如果存在再去cms中验证。因为本篇文章主要讲我自己在cms审计上的一些经验,因此不多深入框架的审计部分。
  2. 如果没有使用框架,则需要仔细的观察数据库函数,一般来说,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语句关键词,然后定位到具体的语句,然后查看里面有没有拼接的变量,回溯可不可控。如果可控并且存在字符串拼接,很有可能就存在漏洞。

使用了框架的就是搜索的关键词不一样,还是得看是否存在字符串拼接,可不可控。

即使使用了预编译,但是如果在预编译之前字符串拼接了,那照样没有鸟用,该注入还是能注入。

下面提供一般我会搜索的关键词(框架的根据你审计项目的框架的手册,自行搜索。)师傅们有想补充的也可以补充。

insert
create
delete
update
order by
group by
where
from
limit
desc
asc
union
select

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的问题。

可以尝试全局搜索

csrf-token
csrf_token
csrftoken
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://协议去搞内网。

列一下,我经常搜索的关键词

file_get_contents
fsockopen
curl_exec
get_headers
fopen
readfile

注意

  1. 一般情况下PHP不会开启fopen的gopher wrapper
  2. file_get_contents的gopher协议不能URL编码
  3. file_get_contents关于Gopher的302跳转会出现bug,导致利用失败
  4. curl/libcurl 7.43 上gopher协议存在bug(%00截断) 经测试7.49 可用
  5. curl_exec() //默认不跟踪跳转,
  6. 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的函数,看看是不是封装了

function upload
function file

如果封装了,那么就看这些封装好的函数,有没有上面提到的审计要素的漏洞。

如果没封装,一般是move_uploaded_file这个函数,全局搜索,这个函数,回溯查看这些漏洞存不存在。(白盒黑盒一起搞比较好。)

6.2.8. 变量覆盖

审计要素

  • 是否存在造成变量覆盖的函数,例如:extract()、parse_str()、import_request_variables和$$等。
  • 是否存在可以完整利用的攻击链。

一般就这几个函数和关键词

extract
parse_str
import_request_variables
mb_parse_str
$$

不过还有个特殊的配置,也可能造成变量覆盖

下面的部分来自mi1k7ea师傅PHP变量覆盖漏洞

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,变量未被初始化且能够用户所控制时,就会存在变量覆盖漏洞:

<?php
echo “Register_globals: “ . (int)ini_get(“register_globals”) . “<br/>“;

if ($a) {
echo “Hacked!”;
}
?>

通过GET和POST方式输入变量a的值:

当然,也可以从COOKIE中输入:

6.2.9. 代码执行漏洞

审计要素

  • php.ini文件中的disable_function是否有禁用函数。
  • 是否存在代码执行的敏感函数。
  • 是否输入变量可控。

全局搜索下面的关键词,回溯参数可不可控。

eval
asser
preg_replace
create_function
array_map
call_user_func
call_user_func_array
array_filter
usort
uasort
$a($b)(动态函数)

6.2.10. 命令执行漏洞

审计要素

  • 参数是否用户可控
  • 是否配置了全局过滤器,过滤规则是否符合安全规范
  • 是否所有的命令执行参数都经过了过滤器,或受白名单限制

全局搜索下面的关键词,回溯参数可不可控。

exec
passthru
proc_open
shell_exec
system
pcntl_exec
popen
“(被反引号包裹的变量也可以执行)

6.2.11. 任意文件下载/下载漏洞审计

审计要素

  • 是否存在../、.、..\等特殊字符过滤。
  • 参数是否用户可控
  • 是否配置了相对路径或者绝对路径。

查询这些关键词,查看变量是否可控,是否有过滤

fgets
fgetss
file_get_contents
readfile
parse_ini_file
highlight_file
file
fopen
readfile
fread

Tip:前两天遇到个,过滤了config/database.php这样的正则匹配,还过滤了..,目的是防止目录穿越,读取服务器其他目录的文件,可是没过滤一个.

这样我使用config/./database.php绕过了正则,照样把敏感文件读取出来了。。。

6.2.12. 任意文件删除

和上面的下载一样

搜索的关键词变了

rmdir
unlink

6.2.13. 任意文件写入

还是一样,关键词为

copy
file_put_contents
fwrite

6.2.14. 会话认证漏洞

会话认证漏洞实际上涉及的方面比较广,如cookie、session、sso、oauth等,当然这个漏洞比较常见是在cookie上,服务端直接取用cookie中的数据而没有校验,其次是cookie加密数据在可预测的情况下。

审计要素

  • 是否cookie中的加密数据可预测。
  • 是否cookie中的数据可预测。
  • 服务端是否只依赖cookie来判断用户身份。

全局去寻找cookie生成的逻辑,判断是否可预测,判断用户身份是否只依赖cookie,而不是随机的,比如

鉴权是只通过cookie中的userid来判断,如果我遍历userid,可以达到登录绕过或越权的目地。

6.2.15. 反序列化漏洞

一般实际审计的时候,项目中见的比较少,框架中见的比较多。

全局搜索serialize。看看存不存在可控变量。

很早之前写过这个系列的小结

序列化(一)利用-phar-拓展-php-反序列化漏洞攻击面

序列化(二)session反序列化

序列化(三)原生类反序列化

 

0x07 参考

对PHP类CMS审计的一点总结

http://www.oriole.fun/index.php/archives/69/

PHP变量覆盖漏洞小结

还有狼组wiki中的文章

 

0x08 总结

这篇文章,就是自己总结下审计的流程,或许有很多不足、缺漏,希望师傅们谅解及指正。

审计是一件很难一概而论的事情,有时会很枯燥,看代码看的头疼,但自己找到0day的感觉,是非常不一样的,加油吧,师傅们。

(完)