wkhtmltopdf today · Security
wkhtmltopdf CVE-2022-35583: what to do
CVE-2022-35583, rated 9.8 (critical) by the NVD on 22 August 2022: wkhtmltopdf 0.12.6 lets the HTML it renders reach internal addresses, for instance through an iframe. A server-side request forgery (SSRF) that no release will fix: wkhtmltopdf is archived.
Who is exposed
Any server that converts HTML holding text it does not control: a customer’s name, an address, a comment. Left unescaped, that text can carry an <iframe> or an <img> pointing to http://169.254.169.254/ (cloud metadata) or to a service on localhost. The conversion then sends the request from inside the network.
What pdfkiln does
- By default, what wkhtmltopdf 0.12.6 does, the compatibility contract: a page loads whatever it wants from the network, and reads no local file but itself unless
--enable-local-file-accessor--allowsays so; neither..nor a symbolic link gets around--allow. --kiln-block-private-networkrefuses loopback, private and link-local addresses, cloud metadata included, after DNS resolution: a name that resolves to127.0.0.1is refused too. Checked on Debian 13 with pdfkiln 1.0.3: an image served on127.0.0.1is printed without the option, left out with it.- Chromium’s sandbox is on, and
--kiln-timeoutstops a conversion after 120 s by default.
The earlier flaw, CVE-2020-21365 (rated 7.5: reading local files in wkhtmltopdf 0.12.5 and before), was closed by 0.12.6’s default, which pdfkiln keeps.
Turn the option on
On the command line:
wkhtmltopdf --kiln-block-private-network page.html page.pdf
With pdfkit (Python), in the options:
pdfkit.from_string(html, 'out.pdf', options={'kiln-block-private-network': ''})
knp-snappy, and the libraries that refuse options they do not know, get a script that adds it, in place of the program:
printf '#!/bin/sh\nexec /usr/bin/wkhtmltopdf --kiln-block-private-network "$@"\n' \
| sudo tee /usr/local/bin/wkhtmltopdf-safe > /dev/null
sudo chmod +x /usr/local/bin/wkhtmltopdf-safe
$pdf = new Knp\Snappy\Pdf('/usr/local/bin/wkhtmltopdf-safe');
The script passes everything else on: wkhtmltopdf-safe --version answers as wkhtmltopdf does.
And in the templates
The cause stays in the template: text that comes from users must be escaped there, as most template engines of web frameworks do by default. The option is the safety net.