php代码审计总结(上)
一、审计前的准备
1、核心配置详解
register_globals | 全局变量注册开关 | PHP5.4.0以下可用 |
---|---|---|
allow_url_include | 是否允许远程文件包含 | php5.2.0及以后默认off |
magic_quotes_gpc | 魔术引号自动过滤,GET/POST/COOKIE的变量符号前加\进行转义 | php5.4以下版本可用 |
magic_quotes_runtime | 可由程序员从代码处设置开启和关闭,当 magic_quotes_runtime开启时,从数据库或者fread函数读取的字符会进行反斜杠转义,从而避免溢出 | php5.4以下版本可用 |
safe_mode | 使用后所有的文件函数会受到限制,通过命令函数执行命令或程序时会报错 | php5.4以后版本可用 |
open_basedir | php可访问目录,限制php只访问目录 | |
disable function | 禁止一些敏感函数的使用 | |
display_errors & error_reporting | 前者是显示脚本内部错误选项,后者是设置错误回显 |
2、代码审计工具
php的代码审计工具我一般常用的是两个,分别是seay 源码审计工具和RIPS 。前者是基于C#开发的工具,后者是基于php开发的审计工具。此处可以下载这两个工具:https://www.freebuf.com/sectool/11126.html
http://rips-scanner.sourceforge.net/
二、代码审计思路
此处的思路借鉴了seay源码审计书中的方法,比较全面。但是其实代码审计还是看个人读代码和理解代码的习惯,根据个人的编程习惯来进行代码审计效率会更高。
1、通读全文
这里的全文通读并不是盲目的从头读所有代码,而且有技巧的全文通读,首先要看代码的大致结构,主目录(查找文件入口点和一些公共函数文件)、配置目录以及文件的创建日期和文件大小等信息,看了这些信息就大概知道核心文件在什么位置了,重点关注一下几个文件:
函数集文件:通常命名中包含function和common等关键字,这些文件里面是一些公共的函数,提供给其他文件统一调用
配置文件:命名包含config关键字,包含web运行的配置选项和数据库相关的配置信息
安全过滤文件:通常命名中有filter、safe、check等关键字,这类文件主要是对参数进行过滤。
index文件:index是一个程序的入口文件,所以通常只要读一遍index文件就可以大致的了解整个程序的架构,运行的流程,包含到的文件等。
2、查找可控变量
查找可控变量,正向追踪变量传递的过程,查找可能存在安全漏洞的变量,从变量处发现安全问题。
3、寻找敏感功能点
通读功能点的代码,尤其关注于易出现漏洞的功能点,如:文件上传、留言板、登录认证功能、找回密码等。通过从敏感功能点入手来查找安全问题。
三、漏洞挖掘与利用
1、安装问题
[1] 一般网站会通过生成一个.lock文件确认网站是否安装成功,当lock文件被删除时,网站检测不到该文件则会自动重新安装,这样就会导致数据库内容被覆盖重写。
[2] 当网站不通过lock文件判断结束也没有自动删除文件时,误启动安装向导时,便会造成网站自动安装,数据库被覆盖重写
[3] 当通过lock文件判断结束,若存在,则header到index.php,但是在index文件中并没有进行exit,所以并不会退出。
[4] 通过变量覆盖函数覆盖掉变量$insLockfile,从而使检测file_exist为false便不会退出。
[5] 安装文件的代码逻辑问题。通过直接修改url中的step参数,直接可以跳过部分安装步骤
[6] 在安装完成后将install.php重命名为install.php.bak。由于apache的变量名解析漏洞,bak无法解析时后向上寻找,所以还是将其当做php文件解析,则又会重装。
2、文件上传
首先搜索上传函数move_uploaded_file()寻找上传点
[1] 没有进行后缀名验证或者是仅由客户端代码进行验证,可直接上传
[2] 扩展名黑名单/白名单验证。
is_array()查看字符串是否在字符数组中 strrchr() 查看字符最后一次出现在字符串中的位置,并且截取到直到字符串末尾 trim()去掉字符两边的空格 $_FILES['upfile']['name'] 客户端文件的原名称;$_FILES['upfile']['type']文件类型;$_FILES['upfile']['tmp_name']文件存储服务器后存储的临时文件名
绕过方式:
大小写绕过
扩展名绕过(黑名单内没有的)如:Php: php3 phtml
利用windows特性。(如果没用trim可以给后缀名加空格)(upload.php::$DATA的方式)(小数点绕过upload.php.)
截断绕过 %00(验证了上传后缀但是文件名不重命名,upload.php%00.jpg)
解析漏洞(apache:upload.php.xxx)(nginx:upload.jpg/1.php)
.htaccess文件。通过.htaccess文件调用php解析器去解析一个文件名中只要包含'haha'这个字符串的任意文件,无论扩展名是什么(没有也行),都以php的方式来解析
.user.ini文件。只要在.user.ini中添加auto_prepend_file=aa.jpg
这句话,就可以让其他php文件执行前自动包含aa.jpg,和require()类似。
[3] MIME验证,验证文件头的content-type,可通过抓包修改
[4] 文件幻数和文件相关内容验证
绕过方式
在文件首部加上如下幻数即可绕过
JFIF FF D8 FF E0 00 10 4A 46 49 46 GIF89a 47 49 46 38 39 61 PNG 89 50 4E 47
文件相关内容检测通常用的是getimagesize(),只需要在幻数上添加一些文件信息即可绕过
[5]上传路径可控
3、文件包含
文件包含常用函数:
include() include_once() require() require_once() ......
文件包含分为本地文件包含和远程文件包含。
[1] 本地文件包含一般都限制了包含的后缀名,比如:$a.’.php’,可以通过各种截断进行绕过
绕过方式
%00截断(gpc off && php <5.3.4)
长文件名截断 (windows和linux长度不同)
转换字符集截断(iconv(),使用iconv处理文件名时,若编码错误则会导致截断)
利用PHP协议绕过(使用zip或者phar绕过,phar协议字php5.3.0开始有效)(data:php5.2以后版本)
新建shell.php代码内容:
<?phpinclude 'phar://test.rar/test.txt';?>
新建test.txt里的内容:
<?phpphpinfo();?>
则运行shell.php文件时,运行结果为phpinfo的内容。
[2]远程文件包含,可包含远程服务器的文件
allows_url_include 为on的情况下可以实现远程文件包含,默认为off
可用?进行截断,不受gpc和版本控制
可使用协议php://input 和php://filter
4、找回密码
[1] 验证token。在找回密码的时候生成一个token,然后存储在数据库中,然后把找回密码的地址发到邮箱中,url中就含有token,由用户点开就能修改密码
[2] rand函数生成token
$resetpwd = md5(rand()) 对rand函数的结果进行MD5加密
$encryptstring = md5($this->time.$verification.$auth);
$timetemp=date('Y-m-d H:i:s',$this->time); $auth=util::strcode($timetemp,'ENCODE');
算法的 KEY 并没有初始化,如果知道了这个时间,就可以生成加密的字符串
[3] 延伸
一些cms的密码的加密方式很难破解,有时拿到了管理的密码破解不掉。一般解决方法:一般找回密码都用的是邮箱,首先把管理的邮箱注入出来,然后再去找回密码,再把数据库的token注入出来,构造一下地址就可以重置密码了。
5、文件读取
文件读取常用的函数
file_get_contents() highlight_file() fopen() readfile() fread() fgetss() fgets() parse_ini_file() show_source() ......
[1] file_get_contents()与php://filter一起利用可以读取页面源代码。前提是存在include()
file_put_contents('php://filter/write=string.rot13/resource=example.txt','hello world'); file_get_contents('php://filter/read=convert.base64-encode/resource=1.php')
6、其他的文件操作
如:任意文件删除、任意文件复制、任意文件下载等。
一般步骤就是:首先尝试拿到配置文件的数据库用户名和密码,通过外向连接来拿到想要的信息,还有就是通过配置文件,得到加密的key,生成加密字符串
7、加密函数
拿到加密函数的key,通过key加密一些字符串生成加密字符串。
[1] 加密可逆
弱算法,知道明文或者知道密文,可以进行逆运算。拿到加密函数的key生成自己想要的加密后的字符串
[2] 加密可控
要加密的内容是可控的,密文会输出,这个可控点可以引入特殊字符串,然后在生成加密字符串后又在某个地方解密后输出,在这个可控点也可以获取信息进行利用
[3] key泄露
PHPcms v9 存在key泄漏的漏洞
8、命令执行
指的是可以执行系统或者应用指令(如CMD命令或者bash命令)的漏洞。主要是基于一些函数的参数过滤不严
可以执行命令的函数:
system():显示shell命令输出的结果,命令执行状态为0时表示执行成功 exec(): 返回shell结果的最后一行,命令执行状态为0时表示执行成功 shell_exec() passthru() pcntl_exec() popen() ......
[1] 一般的防御方式有两种,一种是黑白名单,一种是命令防注入函数。
escapeshellcmd() :过滤整条命令。过滤的字符包含:& ; * ? ~ < > ^ () [ ] { } $ \ \x0A \xff % ’ '’ 。windows下的过滤就是给字符前加^,linux下就是给字符前加\
escapesshellarg():过滤参数。将参数限制在双引号里,将双引号替换为空格
[2] 反引号(’)也可以执行命令,实际上这种方式也是调用shell_exec函数
绕过方式
若在linux下,先使用escapesshellarg()再使用escapeshellcmd()可以用来逃逸单引号
可以将恶意的系统命令拼接在正常命令后
& 依次执行 && 前一个执行成功才执行 | 管道符号 || 前一个执行失败才执行 %0a 换行
当具体命令被限制时,可以使用正则来绕过,如
/???/??? => /bin/cat
但是这样的缺点是会产生大量的干扰输出
可以通过Bash环境下相邻字符自动连接特性来进行绕过
$ /bin/cat /etc/passwd $ /bin/cat /e'tc'/pa'ss'wd $ /bin/c'at' /e'tc'/pa'ss'wd $ /b'i'n/c'a't /e't'c/p'a's's'w'd'
9、代码执行
应用程序本身过滤不严,用户可以通过请求将代码注入到应用中去执行
代码执行函数有以下几种:
eval()需要分号结尾 assert()不需要分号结尾 preg_replace() 将目标字符中符合正则规则的字符替换为替换字符,如果正则规则中使用/e修饰符,则存在代码执行漏洞 call_user_func() call_user_func_array() array_map() ......
[1] 除了上面的代码执行函数为,还可以通过” “双引号,在双引号下变量自动解析
[2] ${}可以执行代码并将代码返回值作为变量,无下划线shell构造
[3] 动态函数
$_GET['a']($_GET['b']);
绕过方式:
双引号、单引号被过滤时,可以使用heredoc和nowdoc
可以结合命令执行函数
10、变量覆盖
是指用自定义的参数替换程序原有的变量值
[1] extract():将数组中的键值对注册成变量
[2] parse_str():解析字符串并且注册变量,在注册之前不会验证变量是否存在
[3] important_request_variables():把GET/POST/COOKIE的参数注册成变量(前提条件是:register_globals为off且php版本在4.1-5.4之间)
[4] 全局变量覆盖:传递过来的值会被直接的注册为全局变量直接使用。(前提是register_globals为on)
[5] $$导致的变量覆盖问题
防御
使用原始变量
验证变量是否存在