the_open_door
coffee
ESEN
← Back to index

LaTeX Injection

ATT&CKT1190

LaTeX injection appears when an application compiles user-controlled text to PDF (reports, invoices, CVs) by passing it to a real LaTeX engine. LaTeX is not just a typesetting language: its primitives can read files on the server and even execute commands, so unsanitised input turns into arbitrary file read or RCE.

For file reading, the obvious route is \input/\include, but those are usually blacklisted. You get around it with low-level read primitives that naive blacklists forget:

\input{/etc/passwd}                         % direct read (if not filtered)
\lstinputlisting{/etc/passwd}               % from the listings package
\newread\file \openin\file=/etc/passwd \read\file to\line \text{\line} \closein\file

When the content has characters LaTeX cannot typeset (breaking compilation), you read it in base64 to carry it intact and decode it outside:

\immediate\write18{cat /etc/passwd | base64 > /var/www/html/compile/out.txt}

That \write18 is the door to RCE: it runs a system command, as long as compilation is done with --shell-escape (or --enable-write18) enabled. The result is recovered from the compilation error itself or by writing it to a file served by the web app:

\immediate\write18{id}

Requirements. The server must compile the input with a LaTeX engine without sanitising the commands. File read works almost always; command execution via \write18 requires compilation to run with --shell-escape enabled (off by default in modern distributions, but common in homemade PDF generators).

How to detect it. In the application log, the signature is submissions to the PDF generator containing LaTeX file or execution primitives (\input, \openin, \lstinputlisting, \write18, \immediate), especially with system paths like /etc/passwd. On the host, the trace is the pdflatex/latexmk process spawning unexpected children (sh -c, cat, base64) when \write18 is abused.

Defence. Do not compile untrusted LaTeX: use a generator that does not invoke a full engine, or compile in restricted mode without --shell-escape and inside a sandbox (ephemeral container, unprivileged user, read-only filesystem). Filter by a whitelist of allowed commands, not a blacklist (there is always a forgotten primitive). Since it ends in file read and execution, it overlaps with traversal/LFI and command injection.