PHP安全相关的笔记

Published: 六 13 八月 2022

In Vuln.

PHP是WEB爷入门的东西,所以我打算好好学学习它,并把相关内容记下来,慢慢来...

clipart2934061

危险函数

命令执行

system(), passthru(), exec(), shell_exec(, ` , popen(), proc_open(), pcntl_exec(

代码执行

eval\s*\(, asert\s*\(preg_replace\s*\(, call_user_func\s*\(, include, requirecall_user_func_array(, create_function(, array_map(, ob_start(, mail(, usort, uasort

还有个preg_replace_callback可以用来执行代码,写后门用吧

反序列化

反序列化可构造任意值,而一些魔法方法(__destruct__toString__sleep__wakeup__call__construct)若存在问题可执行危险操作[2],若存在框架可看phpggc是否有已知Gadget,主要找unserializephar利用点。

这里重点看phar,它可类比为java中的jar文件,其具体存储可用zip/tar/phar格式,其中phar是专为该功能设计的格式,它们含如下内容

1.stub为一段bootstrap代码,当在执行上下文(如include文件时)会执行它,它里面至少要有<?php ... __HALT_COMPILER();让引擎不再解析后面的内容,这在phar中位于文件首部,而在zip/tar中位于.phar/stub.php文件里

2.manifest含各种属性信息,里面最需要关注的是元数据metadata,它是序列化后的数据并且会被自动反序列化,元数据在zip中会被存放在单个文件或整体的注释(comment)里

3.contents没啥说的就是数据内容,根据存储格式可被bzip2/gzip按单个文件或整体压缩

4.signatuer是只存在于phar格式的签名信息(其实是摘要),包括签名值,签名算法和魔数GBMB,这意味着phar文件首部任意但尾部得合法(该部分可配置为不校验)

phar通常通过phar://流封装来触发,其利用条件如下:

1.能控制本地文件的全部或部分内容,例如通过文件上传方式或往session写内容等将恶意构造的phar文件写入,文件扩展名随意,文件可以被压缩去绕过某些检测

2.有使用文件流的地方且路径前半部可控,这样可通过phar://去操作恶意文件,这里若它对phar有检测,可尝试用php://filter/resource=phar:///compress.[bzip2|zlib]://phar://等方式来绕过,至于文件流相关函数见下文(也可参考[1])

3.存在反序列化利用链,注意由于这里的元数据不会在其他地方使用因此只有__wakeup__destruct能作为链的入口[0]

参考

[0] File Operation Induced Unserialization via the “phar://” Stream Wrapper (PPT)

[1] Phar与Stream Wrapper造成PHP RCE的深入挖掘

[2] 一篇文章带你深入理解漏洞之 PHP 反序列化漏洞

变量覆盖

当使用$$, extract没接收结果, parse_str没提供第二个参数, import_request_variables, register_globals=ON(配置文件)时可能存在变量覆盖漏洞,向$GLOBAL中任意写也会存在变量覆盖。PHP会解析GET/POST方式传入的字典,因此利用该漏洞可以覆盖很多危险点,如用?_SERVER[REMOTE_ADDR]=$(id)来控制原本不可控的点从而实现地址伪造或进行注入

文件操作

主要关心文件读写操作:file_get_contents, file_put_contentsmove_uploaded_file, renameunlink, rmdirfopen, fread, fwrite, fputs, fgets, fgetc, readfile, highlight_file, show_source, parse_ini_file

注:几乎所有能输入文件名的函数都可解析伪协议,利用此可绕过一些限制(如路径后缀)或实现其他攻击(如反序列化)...

解码操作

base64_decode, urldecode, decodeaes, des, sm4, (a)rc4, blowfish, crypt, decryptmd5, auth, rand, srand, seed

注:在使用rand时,若存在足够的样本可用php_mt_rand反推出种子

其他

安全相关函数

1.addslashes: 为'/"/\/NUL添加\转义,由于它只懂PHP语意并不适用于其他组件(如不适合MySql过滤,可能存在无'时绕过或宽子节注入)

2.htmlspecialchars/htmlentities: HTML实体编码,可以防XSS

3.strip_tags: 删除字符串中的HTML/PHP/JS等标记,仅此而已

超全局变量

有9个超全局变量,如$GLOBAL是全局变量的引用,不过这里主要关注$_SERVER,它里面有很多字段:

1.SERVER_NAME/SERVER_PORT: 如果webserver没设置,则使用HOST头里的值

2.PHP_SELF是当前URI(包括PATH_INFO但不包括QUERY_STRING),PATH_INFO是URI中.php之后的内容,SCRIPT_NAME是PHP_SELF去掉PHP_INFO的内容

3.SCRIPT_FILENAME是当前脚本的绝对地址(符号连接未解析),PATH_TRANSLATED是解析符号连接后的绝对地址

特殊配置

配置 说明
register_globals 早期支持,会把请求注册到全局
magic_quotes_gpc 设置后GPC的'"\0x00会被转意
disable_functions 限制了能使用的函数,通过其他其他组件绕
open_basedir 限定了能打开的目录,可通过DirectoryIterator+Glob绕

版本特性

这里记录每个版本间与安全相关的变化...

5.X

版本 新增或修改 删除
4.0->5.0 include_once/require_once在windows下不区分路径大小写 -
5.1->5.2 添加了data:流; libxml_disable_entity_loader可设置不加载外部实体,libxml 2.9.0开始默认不处理实体了; 新增了filter模块用于对变量进行过滤; 新增allow_url_include选项来禁止远程文件包含,且默认禁止 -
5.2->5.3 新增glob://phar://流封装; 新增dechunk流过滤器,bz2.decompress支持串联了; 新增了内置Phar -
5.3->5.4 <?=形式的短标签始终可用 不再支持register_globals选项; 不再支持安全模式safe mode; 不再支持magic_quotes_[gpc|runtime|sybase]选项
5.5->5.6 php://input现在可多次打开来重用; 添加hash_equals函数进行安全的字符串比较 -

7.X

版本 新增或修改 删除
5.6->7.0 string处理时,16进制字符串不再被认为是数字,因此在一些比较时将会失败; int转换时8进制时若存在无效数字现在将直接出错而不是截断; 整数溢出会产生E_WARNING错误并返回null而不是截断; unserialize支持设置允许反序列化的类(默认允许所有); assert现在是一个语言结构而非函数了 不再支持preg_replace()/e选项; 不再支持<%%>/<%= %>/<script language="php"> </script>标签; 不再$HTTP_RAW_POST_DATA
7.1->7.2 从7.2.34/7.3.23/7.4.11开始cookie名称不再被url解码 不再支持create_funcion
7.3->7.4 新增__serialize__unserialize用于自定义序列化过程; proc_open支持数组形式的命令,此时PHP自动处理转义 反序列化时不再支持o类型

8.X

版本 新增或修改 删除
7.4->8.0 assert第一个参数不再被当作PHP代码执行了; 数字和非数字==比较时会先把数字转换成字符串再比较; data://不再可写; parse_str第二个参数必须存在(变量覆盖问题没了); 要求libxml最小版本为2.9.0确保默认禁止外部实体加载; 数字字符串允许出现尾随空格和前导空格; phar里的元数据不再自动反序列化; 执行exit时析构函数不再被调用 -

附数字与字符串非严格比较变化:

image-20221120202232678

参考

[0] php Unsupported Branches / php appendices

Trick

require_once重复加载

require_once对同一个文件只会加载一次,但有时需要重复加载,此时可通过变换路径来实现[1]。

伪协议

PHP使用来统一处理文件和套接字等资源,支持定义伪协议,并且已经内置了多种协议与伪协议(Wrapper),这里记录几个常用的:

1.file://是默认的封装协议, http://ftp://用于访问远程文件,需allow_url_fopen=true

2.php://的功能特别丰富,它是提供一些杂项IO,如php://[stdin|stdout|stderr|fd]可打开标准输入输出错误或指定fd,而php://[input|output]可读(写)http的请求(响应)体,php://[memory|temp]可存储临时数据,最最重要的是它支持过滤器php://filter,见下面儿

3.zlib://,compress.zlib://compress.bzip2用于操作压缩流,使用path#file来读取压缩包内的特定文件可绕过一些限制

4.data://其实在网页里内嵌图片用的很多,它的格式为data:[<mediatype>][;base64],<data>即可以直接嵌入数据,例如可替换一些利用时需要用到php://input的场景 (它也需要allow_url_fopen=true)

5.glob://用于获取路径匹配的文件列表,在低版本中可用于绕目录限制

6.phar://操作phar文件,在文件上传和反序列化场景会用到(见上文)

7.expect://处理交互式流,就是可以直接执行命令但是前提得手动启用EXPECT扩展

和伪协议经常出现的是过滤器,PHP支持自定义过滤器且已经内置了一些常用的,通常需要stream_filter_append/stream_filter_prepend来将过滤器附加在指定流上,但php://filter功能支持all-in-one功能,即能把资源,输入输出过滤器组合起来,在进行一些绕过时相当滴好用。

当涉及到文件流操作与绕过时,可以考虑用伪协议与过滤器,也再看看[0]

现在问题来了,哪些操作是流操作,有前人已经总结了很多了,但自己要找的话就是若感觉某函数涉及到资源操作,都可以在PHP的php_stream_open_wrapper中打个断点验证下!

后缀

服务器会把哪些扩展交给php处理呢?看设置,有一些常见的由php处理的后缀.phtm?l?/.php?[2345678s]?/.php4/.php5/.phtml可以试试,具体能不能行害得看配置...

短标签

除了标准的<?php ?>外还有几个短标签,<% %>/<? ?>/<script language="php"> </script>这三个需要手动开启,而<?= ?>总是有效,不过这些短标签从7.0开始都不再被支持...

弱类型

就是=====,字符串与数字比较等,0与false, 0e等,但是从8.0开始字符串与数字的非严格比较已经有些变化可能不再相等了...

零字符截断

在旧版中\x00会被截断(Windows也会有),iconv在转换时遇到非法字符也可能会截断

参考

[0] PHP Wrappers

[1] php源码分析 require_once 绕过不能重复包含文件的限制

PHP内部

PHP本身的调试

下载源码,安装工具链,我又xcode所以记录下过程中遇到的额外步骤:

git clone https://github.com/php/php-src.git
cd php-src
git checkout PHP-5.6

brew install bison@2.7
export PATH="/opt/homebrew/opt/bison@2.7/bin:$PATH"
export LDFLAGS="-L/opt/homebrew/opt/bison@2.7/lib"
export CFLAGS="-Wno-implicit-function-declaration"
./buildconf
./configure --enable-debug --without-iconv
make -j8

之后依然是用clion创建一个make application:

image-20221122175451109

PHP文件远程调试

如果只是想简单调试可直接用var_dump或它的升级版kint库,更大型的害得xdebug或zenddebugger,简单说下xdebug调试原理,在单用户调试模式下,用户发送带有调试头字段的请求时,PHP会根据配置向指定位置发起连接请求,IDE监听该连接即可开始调试,具体步骤如下:

先新建phpinfo.php查看php信息并下载对应的xdebug,根据它的描述放在对应位置并修改配置,注意网上大多是xdebug2的配置,现在xdebug3为:

[xdebug]
zend_extension = xdebug;
xdebug.client_host=192.168.202.1  ; IDE所在的IP
xdebug.client_port=9100                     ; IDE上监听的端口,xdebug3默认为9003可以保持不修改
xdebug.mode=debug                                   ;

设置后记得重启服务!再在phpstorm上新建调试配置,如果上面修改了端口,则还要在settings->php->debug->xdebug里修改对应端口,之后在发包时请求带Cookie: XDEBUG_SESSION即可开始回调,远程调试第一次会要求设置目录映射。

除了调试,有时还有执行追踪的需求,xdebug支持函数级的追踪(包括函数调用参数返回值),若想实现代码行级别的追踪则需要自己完成,如利用它的代码覆盖率分析功能手动映射代码。

代码保护

附:由于PHP代码保护已经比较成熟,很多产品会使用一些保护方案,此时可尝试用https://www.dezender.net/,https://www.unzend.com/,http://www.dezend.ir/,https://easytoyou.eu/等去解密。

参考

[0] 漫谈 PHP 反汇编器/反编译器