本公众号发布的文章均转载自互联网或经作者投稿授权的原创,文末已注明出处,其内容和图片版权归原网站或作者本人所有,并不代表安世加的观点,若有无意侵权或转载不当之处请联系我们处理,谢谢合作!
欢迎各位添加微信号:asj-jacky
加入安世加 交流群 和大佬们一起交流安全技术
在PHP中,我们往往通过phpinfo()
函数(及可选选项)来检查配置设置和预定义变量,返回结果输出关于PHP的配置信息,其中包含了 PHP 编译选项、启用的扩展、PHP 版本、服务器信息和环境变量(如果编译为一个模块的话)、PHP环境变量、操作系统版本信息、path 变量、配置选项的本地值和主值、HTTP 头和PHP授权信息(License)。根据phpinfo()
的可选选项,重点分析部分选项信息。
选项名称 | 值 | 选项描述 |
---|---|---|
INFO_GENERAL | 1 | 配置的命令行、 php.ini 的文件位置、建立的时间、Web 服务器、系统及更多其他信息 |
INFO_CREDITS | 2 | PHP 贡献者名单 |
INFO_CONFIGURATION | 4 | 当前PHP指令的本地值和主值 |
INFO_MODULES | 8 | 已加载的模块和模块相应的设置 |
INFO_ENVIRONMENT | 16 | 环境变量信息 |
INFO_VARIABLES | 32 | 显示所有来自 EGPCS (Environment, GET, POST, Cookie, Server) 的 预定义变量 |
INFO_LICENSE | 64 | PHP的LICENSE信息 |
INFO_ALL | -1 | 显示以上所有信息,默认 |
一般信息(INFO_GENERAL)
版本
首先很明显就是PHP的版本了。截至写下这篇文章,PHP已经发布到PHP 8.0.3,但目前应用最广泛的仍然还是5和7版本了。PHP各个版本之间有很多特定的安全利用的trick,以下列举PHP 5到8版本之间与安全性的部分改动信息。
PHP5.2及以前
-
__autoload
自动加载类,但只能一次调用;spl_autoload_register
加载类,__autoload
的实现
PHP5.3
-
修复空字符截断 -
新增全局变量 __DIR__
-
phar://
流包装,绕过后缀限制、文件操作触发反序列化 -
glob://
绕过open_dir
列目录
PHP5.4
-
删除注册全局变量、魔术引号、和安全模式 -
新增 session.upload_progress.enabled
,默认为1,可用来文件包含
PHP5.5
-
废除 preg_replace
中可代码执行的e
修饰符 -
容易引发变量覆盖的函数 import_request_variables()
不可用
PHP5.6
-
运算符...实现函数中支持可变数量的参数列表
PHP7.0
-
移除script标签 <script language="php"></script>
、asp标签<% ... %>
、<%= ... %>
-
含十六进制字符串不再被认为是数字 -
assert()
成为一种语言构造,而不是一个函数,意味着很多assert一句话都会失效 -
移除 preg_replace
中可代码执行的e
修饰符
PHP7.1
-
废除 mb_ereg_replace()
和mb_eregi_replace()
的e模式修饰符
PHP7.2
-
禁用assert以字符串作为第一个参数 -
废除可以动态执行字符串的 create_function
-
废除容易导致变量覆盖的 parse_str()
无设置参数的行为
PHP8.0
-
字符串数字弱类型比较优化 -
assert()
不再支持执行代码 -
移除 mb_ereg_replace()
的e模式 -
移除 create_function
-
移除 php://filter
中的string.strip_tags
这里仅仅列举各版本之间部分的改动信息,更多内容可在官方文档中进行查看。
操作系统版本和SAPI
操作系统版本信息可用于后续提权
SAPI
可以明显地判断PHP的连接方式
Apache 2.0 Handler
是通过Apache服务器的mod_php模块部署PHP服务的运行方式
FPM(FastCGI流程管理器)则是PHP FastCGI的一种替代实现。FPM有一个设计缺陷,由于两个进程间的通信没有进行安全性验证,可以伪造Nginx发送给FastCGI封装的数据给PHP-FPM去进行解析
配置文件路径和加载路径
Configuration File (php.ini) Path
指的是PHP默认的配置文件路径,Loaded Configuration File
是实际加载的配置文件。这个php.ini文件可有或可无真实的文件(比如docker),其加载的配置文件会表示为"none"。
有无加载php.ini之间,其默认配置会有一些区别。phith0n师傅在小密圈通过Discuz 7.x/6.x的全局变量防御绕过导致代码执行这个例子,提出了这样一个差异,request_order
项在apt安装(有php.ini)和docker(无php.ini)下的默认值分别为GP和CGP。
由于这里显示的是apt安装自己设定好的情况,查看安装的模块配置文件是放在Scan this dir for additional .ini files
目录,显示加载到的模块详情在Additional .ini files parsed
查看,源码安装其值则为none。当源码安装PHP或安装扩展时候,使用设置--with-config-file-path
或--with-config-file-scan-dir
选项来完成,那么在运行时,PHP将扫描加载所有以.ini为后缀的文件,并在显示在Additional .ini files parsed
项。假设以上路径可控,即可读可写,那么可控制覆盖PHP配置进行提权等操作。
协议/包装器(Registered PHP Streams)
php://
是php特有的协议,用于访问PHP的输入输出流、标准输入输出和错误描述符等功能
-
php://input
可访问请求的原始数据的只读流,即POST请求的情况下,php://input可以获取到POST数据,但使用enctype="multipart/form-data"
的时候,php://input是无效的 -
php://output
只写的数据流,允许允许以print
和echo
一样的方式写入到输出缓冲区 -
php://filter
常用到的伪协议,设计用于数据流打开时的筛选过滤应用(即Registered Stream Filters
),读文件、读源码等有明显效果
file://
访问本地文件系统(绝对路径)guan,比如浏览器打开file:///d:/www/example.html
、读取本地文件readfile('file:///etc/passwd');
glob://
查找匹配的文件路径模式,查找的文件路径也需要限定在open_basedir
范围内
data://
可分为三部分,比如data://text/plain;base64,cGhwaW5mbw==
-
第一部分就是 data
协议头,标识了这个内容为一个data URI资源 -
第二部分为 MIME
类型,表示这个内容以怎样的方式去展现,比如text/plain
即以文本类型展示 -
第三部分为编码设置,默认编码是 charset=US-ASCII,以上示例即为base64
http
、https
、ftp
和ftps
这些再熟悉不过了,略...
zip
PHP的读写zip文件的压缩流,zip文件压缩包可以压缩存放一个webshell,利用文件读取或者包含漏洞,比如file=zip:///var/www/html/upload/abc.zip#shell.php
可执行利用。除此之外,类似的压缩流协议还有compress.bzip2://
和compress.zlib://
等。
phar
PHP的读写归档,在不经过解压的情况下能够被php所访问并且执行。phar配合文件操作直接拓展了PHP反序列化攻击的利用面,并且还能绕过一大部分文件上传检测。
流过滤器(Registered Stream Filters)
PHP在Registered Stream Filters项默认提供了可用过滤器列表,以用于在调用php://filter
打开数据流进行读写时,应用相应的流过滤器对数据筛选。
可用过滤器列表如下
-
压缩过滤器 zlib
和bzip2
,与协议相比,压缩过滤器更加通用,能在任何时候压缩任何数据流。例如解压zlib.deflate的数据readfile('php://filter/zlib.inflate/resource=test.deflated');
还有zlib.inflate流压缩数据突破libxml解析器限制的实体长度php://filter/zlib.deflate/convert.base64-encode/resource=/etc/passwd
-
字符串过滤器 string.*
,字符串转换,string.rot13
对字符串rot13编码,string.toupper
转换为大写,string.tolower
转换为小写,string.strip_tags
去除标签 -
转换过滤器 convert.*
,CTF中最常用的就是convert.base64-encode
读源码读文件php://filter/read=convert.base64-encode/recource=flag.php
核心配置(Core)
allow_url_fopen
选项激活了URL形式的fopen封装协议使得可以访问URL对象例如文件。默认的封装协议提供用ftp和http协议来访问远程文件,一些扩展库例如zlib可能会注册更多的封装协议。
allow_url_include
允许将具有URL意识的fopen包装器与文件包含函数(include,include_once, require,require_once)一起使用。当该选项与allow_url_fopen
同时开启,将实现远程文件包含。
注意这里的两项配置项
-
auto_prepend_file
在目前页面执行之前,先require指定文件 -
auto_append_file
在执行完目标页面之后,再require指定文件
disable_functions
禁用某些内部函数。特别是webshell无法命令执行时,其中一种原因是php.ini使用此指令禁用了能执行系统命令的函数,比如以上的system、exec、shell_exec、passthru、popen等函数。列举几种绕过disable_functions
的方法。
-
尝试被遗漏未禁用的函数,比如pcntl_exec、proc_popen、dl等 -
利用有漏洞的组件,比如ImageMagick()、bash shellshock(CVE-2014-6271)等 -
利用环境变量LD_PRELOAD,mail、sendmail、putenv等
此外,AntSword针对该指令开了一个Bypass Disable Functions专题,并提供了相应的插件,有兴趣可以测试一下(https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypassdisablefunctions/)
open_basedir
将PHP可访问的活动范围限定在指定的目录,比如open_basedir=/var/www/app1/:/tmp/
,那么通过app1访问服务器的用户除了/var/www/html/app1/和/tmp/以外,不可访问其他目录及其文件。但需要注意的是,open_basedir
指定的限定实际上是目录名,而不是前缀,例如open_basedir=/var/www/app
,那么/var/www/app1和/var/www/app2都是可以访问的,容易造成跨站问题。
cgi-fcgi
cgi是服务器后端编程语言(如php、java)与web服务器(如nginx)之间数据交换的通信协议
fastcgi就是升级版的cgi,本质上跟cgi也是协议。nginx根据fastcgi协议拓展了一个模块,也叫fastcgi,虽然同名但两者有本质区别
php-cgi就是php实现的自带fastcgi的管理器,它本身只能解析请求返回结果,但不会进程管理
php-fpm则是fastcgi协议解析器,用来管理php解释器php-cgi的。php-fpm通过生成新的子进程可以实现php.ini修改后的平滑重启
我们可以这样理解,nginx本不支持对PHP程序的直接调用或者解析,必须间接通过fastcgi调用,假设请求一个php程序,nginx会将请求信息按照fastcgi协议封装成数据包转发给php-fpm,php-fpm拿到以后调度管理php-cgi解析php程序代码
这里是一个Nginx服务器通过php-fpm运行的phpinfo,可以看到的该状态是active
看到cgi.fix_pathinfo=On
,你应该会联想到Nginx解析漏洞,但它与php、nginx的版本都无关,属于用户配置不当造成的解析漏洞。PHP获取当前URL请求http://127.0.0.1/index.php/index/index/test
的路径信息一般是通过环境变量$_SERVER['PATH_INFO']
来获取的,key名PATH_INFO是一个cgi 1.1的标准,cgi.fix_pathinfo选项则与这个value值的获取相关。然而,php先前是将设置PATH_TRANSLATED
为SCRIPT_FILENAME
,而不是现在的PATH_INFO
。
默认情况下,cgi.fix_pathinfo是打开的,设置为Off或者为0会导致像以前一样运行。举个例子,以国内最流行的thinkphp框架为例,框架使用PATH_INFO来作为路由载体,该请求是的实际执行脚本SCRIPT_FILENAME是index.php,PATH_INFO为/index/index/test,即访问index模块的index控制器的test方法;当关闭cgi.fix_pathinfo时,访问的则是web根目录下的index.php/index/index/test文件
回到漏洞本身,比如如下的nginx.conf
location ~ .php($|/) {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
set $script $uri;
set $path_info "";
if ($uri ~ "^(.+.php)(/.*)") {
set $script $1;
set $path_info $2;
}
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$script;
fastcgi_param SCRIPT_NAME $script;
fastcgi_param PATH_INFO $path_info;
}
php.ini设置(默认开启)
cgi.fix_pathinfo=1
URL请求http://127.0.0.1/evil.jpg/xxx.php
将触发以下逻辑
if (script_path_translated &&
(script_path_translated_len = strlen(script_path_translated)) > 0 &&
(script_path_translated[script_path_translated_len-1] == '/' ||
(real_path = tsrm_realpath(script_path_translated, NULL)) == NULL)
) {......//略}
以上xxx.php结尾的请求通过了nginx的正则匹配,然后丢给php-fpm,php.ini开启了cgi.fix_pathinfo,将/xxx.php作为PATH_INFO的值,那么evil.jpg就是SCRIPT_FILENAME,php把evil.jpg当作php文件来执行
php从5.3.9开始,php-fpm配置(php-fpm.d/www.conf)新增了一个选项security.limit_extensions
来限制值fpm允许解析的脚本扩展名,以预防web服务器配置的错误使用其他扩展名运行php代码
; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; exectute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
;security.limit_extensions = .php .php3 .php4 .php5 .php7
会话(session)
php内置了很多中处理器用于存取$_SESSION
数据时,会对数据进行序列化和反序列化,Registered serializer handlers
配置项可以设置序列化及反序列化时使用的处理器,常用的有以下的三种,对应不同的处理格式
处理器 | 对应的存储格式 |
---|---|
php | 键名 + 竖线 | + 经过serialize()函数序列化处理的值 |
php_binary | 键名的长度对应的ASCII字符 + 键名 + 经过serialize()函数序列化处理的值 |
php_serialize(php>=5.5.4) | 经过serialize()函数序列化处理的数组 |
一般地,默认使用php处理器
引发的安全问题:PHP bug 71101
如果php在反序列存储的$_SESSION数据时使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的构造,可以伪造任意数据
举个例子,当存储时php_serialize处理,然后调用时php去处理。假设这个时候注入的数据是这样的
<?php
session_start(['serialize_handler' => 'php_serialize',]);
class test{}
$_SESSION['a'] = '|O:4:"test":0:{}';
session_write_close();
那么session中的内容就是a:1:{s:1:"a";s:16:"|O:4:"test":0:{}";}
,根据反序列化解释,其中a:1:{s:1:"a";s:16:"
在经过php解析后被看成了键名,则后面就是构造了一个实例化的test对象注入
php.ini默认关闭session.auto_start
,开启与关闭的区别在于时候需要调用函数session_start()
开启会话,当值等于On时,执行session_start()将产生新的session_id
session.upload_progress.enabled
这个参数是默认开启的,在设置为On的时候,就会在上传的过程中生成上传进度文件,它的存储路径为session.save_path
,即基本表现为/var/lib/php/session/sess_{PHPSESSID}
session.upload_progress.cleanup
开启时,在清除所有的POST数据后,清除上传进度信息。也就是说,文件上传完毕,这个session会自动被清除
上面提及session.auto_start默认是关闭的,当session.upload_progress.enabled开启,在处理一个POST上传,php会执行$_POST[ini_get("session.upload_progress.name")]
,成功则启用会话,记录上传进度
session.use_strict_mode
可防止会话模块使用未初始化的session ID,即会话模块仅接受由它自己创建的有效的会话 ID, 而拒绝由用户自己提供的会话 ID。php默认是未启用session.use_strict_mode
的,这就相当于session文件名可控
session与文件包含
不妨回顾一下HITCON CTF 2018的一道题目One Line PHP Challenge
<?php
($_=@$_GET['orange'] && @substr(file($_)[0],0,6)) ==='@<?php' ? include($_) : highlight_file(__FILE__);
curl http://127.0.0.1/session_upload.php -H 'Cookie: PHPSESSID=test' -F 'PHP_SESSION_UPLOAD_PROGRESS=hhh' -F 'file=@/etc/passwd'
ls -a /var/lib/php/session/
# 输出结果sess_test
因为默认开启session.upload_progress.cleanup,在清除session内容之前,其内容大致为session.upload_progress.prefix + hhh(PHP_SESSION_UPLOAD_PROGRESS=hhh) + | + session序列
,hhh即为可控的写入内容。因此需要条件竞争,在文件清除之前包含利用
当然,回到题目中,还需要结合利用php://filter和base64容错fuzz,以满足包含条件
文件包含phpinfo获取session.save_path的Local Value 先来看看一个登录功能的代码段
if (isset($_POST['username']) && isset($_POST['password'])) {
$query = $mysqli->prepare('select name, password, uuid from user where name = ? and password = sha1(?)');
$query->bind_param('ss', $_POST['username'], $_POST['password']);
$query->execute();
$query->bind_result($name, $password, $uuid);
if ($query->fetch()) {
$_SESSION['user'] = $name;
$_SESSION['uuid'] = $uuid;
}
当session记录了用户信息,意味着session内容可控,可通过注册一个包含恶意代码的username传入到session中,然后尝试包含。问题在于,session的文件保存路径未必为默认值,假如同时存在一个phpinfo.php页面,使用当前会话包含该phpinfo页面可获取到session.save_path的Local Value
包含session文件
环境变量
此处主要关注两个变量PHP_ADMIN_VALUE
和PHP_VALUE
,
PHP_ADMIN_VALUE
可以设定指定的配置项的值,php_value
可以设定指定的值,但只能用于PHP_INI_ALL或PHP_INI_PERDIR类型的配置项,这两个变量经常出现在攻击php-fpm的场景中,这里的phpinfo环境变量信息是通过PHP-FPM未授权访问漏洞远程代码执行打印出来的
在核心配置(Core)一节中提到auto_prepend_file
,在执行目标文件之前先包含指定文件。该漏洞实现代码执行的部分就利用到前面提到SAPI
中FPM/FastCGI的安全缺陷,伪造一个fastcgi协议的数据包,这个数据包其中传入的环境变量如下
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
即通过PHP_VALUE和PHP_ADMIN_VALUE作为FPM的环境变量“覆盖”了配置信息,auto_prepend_file则设置了在执行目标文件之前,先包含request.body内容,最后将包丢给FPM去执行body中的代码
本地文件包含与phpinfo问题
核心配置(Core)中有一个配置项file_uploads
默认是开启的,php允许用户向服务器上任意php文件以form-data方式提交请求上传数据
文件被上传后,首先默认地会被储存到服务端的默认临时目录中,可以修改php.ini中的upload_tmp_dir
设置为其它的路径。服务端的默认临时目录也可以通过更改PHP运行环境的环境变量TMPDIR来重新设置。一般地,临时目录是/tmp,可以通过函数sys_get_temp_dir()
进行查询
然而,即使得知临时目录,也不知道临时文件名。假如同时存在一个phpinfo页面,往phpinfo页面上传文件,通过_FILES变量来获取上传的文件信息,然后在这个临时文件在极短时间被删除的时候,竞争时间包含这个临时文件
<!doctype html>
<html>
<body>
<form action="http://192.168.175.140/info.php" method="post" enctype="multipart/form-data">
<h3>LFI with phpinfo</h3>
<label for="file">filename</label>
<input type="file" name="file">
<input type="submit" name="submit" value="submit"/>
</body>
</html>
在被php删除临时文件之前,竞争时间包含执行这个tmp_name文件
参考
(https://www.php.net/manual/zh/index.php)
PHP: PHP 8.0.0 Release Announcement (https://www.php.net/releases/8.0/en.php)
AntSword-Labs/bypassdisablefunctions (https://github.com/AntSwordProject/AntSword-Labs/tree/master/bypassdisablefunctions/)
Nginx(PHP/fastcgi)的PATH_INFO问题 (https://www.laruence.com/2009/11/13/1138.html)
PHP :: Bug #71101 :: PHP Session Data Injection Vulnerability (https://bugs.php.net/bug.php?id=71101)
Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写 (https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html)
LFI WITH PHPINFO() ASSISTANCE (https://insomniasec.com/downloads/publications/LFI%20With%20PHPInfo%20Assistance.pdf)
原文始发于微信公众号(安世加):技术干货 | phpinfo里面有什么?
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论