PHP代码审计基础
环境准备
开发环境
- phpstudy
- 集成了Mysql、PHP、Apache环境
- Windows/Linux虚拟机
审计工具
- seay
- D盾
- vscode/phpstorm
动态调试
- Xdebug+vscode配置
基本流程
- D盾扫描可疑内置后门
- Seay扫描危险函数点
- 静态分析
- Vscode静态分析
- 搭建环境 或者 寻找互联网环境进行漏洞复现
- 动态分析
- 本地搭建环境
- 可疑程序点断点分析
- 形成POC/EXP
漏洞类型
获取数据类
- SQL注入
- 文件读取
- 文件下载
- 硬编码和信息泄露
获取权限类
- 文件上传
- 文件写入
- 文件包含
- 命令执行&代码执行
- 反序列化漏洞
- 越权
水坑类
- XSS
- 受限文件上传
获取数据类
背景
- 一般来说,我们需要获取的数据无非以下几点
- 数据库内容、组件配置信息、登录凭证
- 那么SQL注入、读取文件这类能够直接获取到信息的漏洞便是我们需要重点关注的
白盒漏洞挖掘细节
SQL注入
- 我们知道,SQL注入的形成原理是 直接使用参数和字符串拼接形成SQL语句进行查询,而非使用过滤或预编译的方式
- 如下图

- 或者
- 在框架中,如ThinkPHP,则是类似这样的
- 当然,也有不是直接拼接的,如下


可以知道想要快速找到这类SQL注入漏洞,我们需要重点关注拼接的部分,比如下面这些关键词
'.$、"'"、'"'、".$、.'(、."(、"')"、')'、")"、."'、'{、}'
直接搜索这些关键词即可
在进行本地代码审计时,可以将SQL语句打印出来手动调试,遇到框架时,可以开启Debug模式或者使用内置的函数将指定的SQL打印出来,比如ThinkPHP中的
$model->getLastSql();
tips
- 大多数情况下,我们遇到的都是一些盲注,或者开启报错显示的时候可以进行报错注入
- 根据不同的系统,存在注入的功能模块也有区别
- 商城系统
- 团购处
- 这里可能会存在
IN操作符,比如IN (1,2,可控这种的SQL - 查询括号结构可能会有收获
- 后台管理系统的用户登录处
- 检查是否存在用户处
- 有些开发会留一些漏洞作为后门,比如在用户登录的时候,先检查是否存在该用户,这里可能会留注入
- 站在开发者的角度想,一个后台管理系统,绝大多数功能都需要鉴权,而负责登录的地方往往不会先鉴权
- 商城系统
文件读取
- 文件读取在php中一般是由
file_get_contents,fopen或者一些ssrf漏洞、文件包含漏洞引起的 - 一般来说需要用到file_get_contents的场景有如下
- 读取配置文件
- 单纯对某个外链发起HTTP请求
- 总体来说SSRF引起的文件读取漏洞偏多,而直接进行文件读取的函数很有可能会限制目录或者拓展名
- 因为这些网站不可避免的会去对外部资源发起一些请求
- 利用第三方接口查询用户IP
- 请求云端最新版本以检查自己的更新状态
- 第三方接口登录/鉴权(如QQ、WX)
- 获取第三方接口提供的数据(如开奖数据)

- 一般来说,封装一个curl_init的函数就类似上面的内容
- 我们可以直接去寻找调用了这些函数的地方,然后看能否利用file伪协议读取文件
- 这类型的漏洞相对较少,但是如果遇到后,可能会有下面几种需要绕过的情况
file_get_contents 限制读取目录
- 常规方法就是进行目录穿越,直接
../../进行目录穿越 - 偶尔会有限制
../../连续出现的WAF,这种可以尝试.././../混合使用
SSRF限制后缀
比如强行给你传入的参数添加
.json可以尝试使用描点符号
file:///../../../../etc/shells%23这种来绕过或者将后缀处理成参数值
file:///../../../../etc/shells?id=需要注意的是在windows环境下应该用
\\来作为目录分隔符这类漏洞的关键字
file_get_contents、fread、fopen、curl、buffer、file(、fgets
文件下载
- 在黑灰产场景中,这类漏洞相对稀少
- 一般来说黑灰产需要下载数据的部分集中在后台,但是类似FastAdmin、ThinkAdmin这种后台管理系统,其下载功能都被模块化规范化了
- 但是在一些接口服务中可能遇到的会多一些
- 比如提供一个接口用于下载自动备份好的数据库文件
- 下载远程的插件/更新包
- 漏洞的核心原理还是文件读取,可以直接用文件读取的相关函数来搜索,也可以尝试使用以下关键字
down、load
硬编码和信息泄露
我们知道在黑盒测试中,信息泄露种类很多,可能是一个git泄露目录,可能是一个报错拿到绝对路径,也可能是一些框架的debug模式下直接把数据库地址账号密码给显示出来。在白盒测试中,我们主要挖掘的是一些在代码中的敏感信息
其实无论是什么语言,大致有如下几类
默认账号密码
- 管理员注册后的初始密码

- 数据库的连接信息(Mysql、Redis)
- 云资源的配置信息,大多数是OSS存储桶
- 随带在源码压缩包中的sql文件中的初始管理员账号信息
- 许多源码是被多次倒手的,里面存在有一些开发的初始账号,且很多时候不会只有一个账号,因为开发会为了测试不同账号的权限是否不同,会初始化一些不同角色的账号,比如开发、测试、客服、财务等等,也许一个干黑灰产的人拿到源码并搭建好后,会修改掉管理员的默认密码,但是其他账号就会被遗忘
- 这类信息有时候可能会有奇效,看源码注册逻辑,解一下这些数据库中的Hash得到密码,利用测绘找目标批量尝试登录也是一种快速进后台的思路。
- 当然,在现在做包网的
默认key
- jwt的key

- 用以加密的盐值(salt)

- 一些给管理员预留接口做的一次性鉴权

除了上述这些通用的部分,还有一些我们可能会忽略的
- 日志文件
众所周知ThinkPHP的日志、缓存等信息一般在runtime目录中,那么这些log文件可能会告诉我们什么信息呢
首先是能看到一些曾经的IP和域名,网站的绝对路径

假如拿到一套代码后,通过对这些日志信息的挖掘发现了一个历史的IP、域名或者绝对路径,这可能就是我们扩线的好方式
- 首先是如果IP和域名还存活着(可能性很小,除非代码来源特殊),在加CDN前,里面早期的IP可能就是真实IP
- 其次是通过测绘去看目标的历史信息,比如前端信息,可以借鉴这些信息使用body语法进行测绘搜索,寻找还存活的一些资产目标,省下了我们搭建环境的步骤
- 然后是IP一般不是随随便便用的,如果一个IDC的主机被用来搞黑灰产,那么这个IDC可能已经有一堆在上面搞黑灰产的了,直接拿着这个IP去扫,说不定能找到不少好东西
- 最后是文件的一些绝对路径,这些信息可以配合其他漏洞一起使用,比如你控制了3306,想要通过写日志的方法getshell时,知道一个绝对路径或者知道绝对路径的构成规则就显得很可贵
然后是数据包信息


- 能够看出居然有用户名和密码Hash,甚至还有登录IP
- 甚至说如果是通过文件读取拿到的log内容,说不定Cookie都还能用
- 常见的一些敏感文件有
| web.conf / web.config | settings.json | application.properties |
|---|---|---|
| database.php | common.inc.php | database.properties |
| config.php | config.inc.php | application.yml |
| db.php | .env | conn.php |
| app.config | web.xml | pom.xml |
获取权限类
背景
- 上文的SQL注入虽然能获取一些数据,但是一旦遇到只能盲注时,特别是时间盲注,想要将整个庞大的数据库内容全量获取终究是不太现实的。所以我们一般会去想办法进后台,但若是管理员密码Hash解不开或者一些其他原因,我们没法进后台。又或者是我们想要通过挂水坑的方式进一步探测管理员的身份时,我们就十分需要能够直接获取一个WebShell权限。
白盒漏洞挖掘细节
文件上传
文件上传是最直接的获取webshell的方式,白盒审计的优势在于我们不再需要去尝试和猜解后端上传的验证逻辑
在黑灰产系统中出现的上传方式一般有以下三种
直接使用一些上传组件,多是一些CMS集成好的富文本编辑器,比如UEditor、FCKEditor等等
- 这类基本上除了上传组件自身一些版本存在漏洞外,想要直接绕过几乎不可能
调用框架的上传方法
比如ThinkPHP的move方法
1
2
3
4$file = request()->file('image');
if ($file) {
$info = $file->validate(['ext' => 'jpg,png,gif,jpeg'])->move(config('upload.rootPath'));
......

值得一提的是,
又或者TP官方手册提到的如下上传和验证方式

自己写的上传方法
- 使用php原生的
move_uploaded_file - 使用base64结合file_put_contents上传,这种一般会使用正则匹配取image后面的内容作为文件拓展名
- 比如下方的
$type_img = $result[2];就是获取到了image后面的内容,如果我们修改这里的内容为php就可以传入恶意的后缀,一旦没有对该后缀进行检查,就能够尝试上传恶意的Webshell 
- 比如下方的

- 使用php原生的
很多时候,开发或者源码的二道贩子喜欢在源码里面留文件上传的后门,这种后门一般来说和其他写得严谨安全的代码反差感很大,很容易识别到这是一个后门。如果想要尝试快速GetShell,可以拿到源码后直奔上传的功能点
搜索关键词
upload、['file']、file_put_contents、\s*image、putFile、move(、move_uploaded_file
文件写入
- 文件写入可以看成是上传漏洞的根本原因,因此大多数情况下上传和写入可以看作一体,但是我们这里要说的是非有意上传类的,比如以下几种场景
- 开发者在Debug时将某些内容写入一个临时的日志文件中,尔后忘记注释掉或删除
- 生成一些二维码图片时进行了缓存
- 利用第三方登录时,同步并缓存了三方账号的头像到本地
- 同样的,核心函数还是
file_put_contents、fwrite等 - 我们写入文件的目的不只是直接写入WebShell文件,还包括配合一些其他漏洞,比如包含或者phar反序列化
文件包含
- 文件包含漏洞其实并不算多,原因在于现在很多MVC框架下,包含视图、配置文件等操作都被框架处理了,除非开发自己写了一些包含操作引起了包含漏洞,但相对较少
- 并且很多包含点,会把传入的参数进行处理
- 传入参数不允许有
..,这导致跨目录无效 - 传入参数会被限定前缀,常见的是当前文件的绝对路径
- 传入参数会被限定后缀,比如拼接上
.php
- 传入参数不允许有
- 关键词
include、include_once、require、require_once
命令执行&代码执行
能够直接执行命令在黑灰产系统中较少,且一般来说会利用
escapeshellarg或escapeshellcmd来进行防护如果发现能直接执行命令的地方,大致有以下四个点
- 调用系统目录备份或清除
- 下载插件或者更新包

- 后门程序
- 定时任务
一般来说php中的常见的命令执行会有以下函数,其他的一些函数较为少见
- 常见:
system、shell_exec、exec - 少见:
passthru、popen、proc_open、反引号(同shell_exec)
- 常见:
这类漏洞用D盾或者Seay一扫就能出来,发现后直接利用即可,大多数情况下都是被留的后门
而代码执行与命令执行大同小异,在黑灰产中也常常以后门的形式存在,常见方法有
eval、assert、call_user_func、call_user_func_array、array_map、array_filter、create_function、uasort、usort
冷门而容易被忽略的一些有


对于这些漏洞可以直接使用D盾扫描,或者尝试去搜索相关函数,看参数位是否符合要求且内容是否可控
当然也不排除后门程序使用了一些加密方法来隐藏自己,如
ionCube、Zend Guard、mzphp等,比如下图就是mzphp

- 有时候加密后的文件顶部会写上加密来源,比如
加密技术提供: https://xxx.xxx,去加密的地方解密即可 - 部分加密后的特征如下图

反序列化漏洞
- 反序列化漏洞在PHP中花样较多,当然前提是有触发点,比如有个完全可控的unserialize,或者存在内容可控的phar反序列化触发函数
- 先说直接unserialize的漏洞,其实在我遇到的情况中很少,一般代码会去读一个文件内容,然后传入unserialize进行反序列化,这种一般是系统在对缓存的数据进行操作,但是如果遇到了,且内容可控,那么场景就是很可能传入的内容来自于类似Cookie这种字段

- 想要直接传入内容被反序列化,大概是CTF里面较多
1 |
|
- 不过phar反序列化漏洞遇到的就相对较多了,主要原因在于phar反序列化的触发函数众多,网上搜到的如下图
| file_get_contents | mkdir | is_dir |
|---|---|---|
| unlink | fopen | stat |
| fstat | fseek | rename |
| opendir | rmdir | file_put_contents |
| file_exists | include | fileinode |
| require | include_once | require_once |
| filemtime | fileowner | fileperms |
| filesize | scandir | rmdir |
| fileatime | filectime | file |
| filegroup | is_executable | is_file |
| is_link | is_readable | is_writeable |
| parse_ini_file | copy | readfile |
- phar反序列化漏洞的原理在于利用
phar://伪协议读取phar文件,反序列化meta-data存储的信息,详细原理我就不赘诉了 - 在有利用链的情况下,能够控制一个phar反序列化触发函数内容,且能够将phar文件传到目标服务器上,当然这是最理想的情况下
- 一般来说会有这些函数是容易作为触发点的
- file_get_contents
- is_dir
- is_file
- unlink
- 比如下图

- 对于需要使用的链子,如果是框架,可以先去搜索一下看是否存在一些公开的利用链,比如TP5的RCE和文件写入
- 如果没有公开POP链,需要自己手动挖,可以先从下面两个魔术方法出发,剩下的就靠自己对各种魔术方法的理解程度进行挖掘了
__destruct__wakeup
越权
修改密码
我们知道越权漏洞在黑盒中常常出现在用户管理等地方,比如修改密码。在白盒代码审计中,漏洞代码如下

可以看到没有验证旧密码是否存在,且这个接口也没有鉴权,就是一个很明显的修改密码越权,直接将管理员的密码修改掉就可以登入后台,但是这种漏洞慎用,容易引起目标警觉
固定token
除了这种方式外,根据不同的代码逻辑,有的越权方式就显得较为简单了,比如用户完成注册且第一次登录时,返回一个token,这个token有可能直接存入数据库且不失效或者失效时间很长,这种情况下直接从数据库文件中拿到token就能完成身份伪造并登录了,比如下面

IP伪造
- IP伪造在CTF的基础题中很常见,做题方法也很清晰,手动添加请求头内容或者使用浏览器插件,在请求中附带上下面的内容就有机会绕过IP限制
- X-Forwarded-For
- X-Originating-IP
- X-Remote-Addr
- X-Remote-IP
- ……
- 当然原因与后端代码逻辑密不可分,经常审PHP就会发现很多站点获取IP的逻辑与下面代码大同小异

- 很容易看出来
X-Forwarded-For优先级很高,是因为他们不喜欢用REMOTE_ADDR获取准确的IP吗,其实主要是因为很多站点会以反向代理的形式进行部署,如果盲目使用REMOTE_ADDR,那么后端拿到的是反代服务器的IP,显然是不符合应用场景的。 - 在我们做的一些目标里面,很多都可以用伪造XFF的方式来绕过IP,但是也有对IP做二次验证的,绕过了还要和数据库中的管理员IP、或者Redis中的IP记录进行比对,因此具体情况还是要看代码是如何实现的
水坑类
背景
- 这类漏洞只适合代码无其他明显漏洞的时候使用,优先级较低,主要原因还是因为水坑是需要与人进行交互的,效果不一定好。
XSS
反射型XSS其实整体来看还挺多的,就是框架一般会防御得比较死。不过这种相对鸡肋,毕竟需要发给目标人,很多情况下只能找到客服
存储型XSS作用可能大很多,白盒挖掘这类漏洞可以重点关注以下功能点,比如注册
- 用户信息在进入数据库时有哪些字段没有被实体化编码处理
- 有哪些字段没有经过格式校验直接进入数据库
白盒代码审计对于利用XSS的好处在于我们能够筛选出较为优质的XSS点,能够对漏洞的利用结果有一个较好的预期
完成XSS后跳转到假的登录界面抓取管理员登录账号密码,或者直接跳转到水坑页面都可以。
PHP中常见的输出函数有
echo、print_r、var_dump、sprintf
常见的内容入口有
$_GET、$_POST、$_SERVER、$_REQUEST
常见的过滤函数有
htmlspecialchars、htmlentities、strip_tags、配合FILTER_SANITIZE_SPECIAL_CHARS使用的filter_var
受限文件上传
- 很多时候通过代码审计,我们会发现文件上传点的验证逻辑一般有以下几种
- 代码中直接取文件名最后一个点后面的字符串,和白名单数组进行匹配
- 从数据库中获取配置信息,将数据库中获取到的拓展名和白名单合并
- 但是有可能将php等危险文件拓展名单独过滤掉,比如ThinkCMF
- 不管上传拓展名,直接上传到OSS上
- 只要不是第一种情况,我们都能尝试一下上传一些其他能够用作钓鱼的文件,比如html、zip、rar、exe等,或者利用浏览器漏洞,然后去尝试钓鱼