I have TeX Live 2019 installed on my server and generate PDFs from a LaTeX-Template with a server-side scripting language (PHP 7). I have to insert data from the database (untrusted data) and also some data from the users (also untrusted data) into that template (dynamic LaTeX document) and then compile it to PDF. This data is used in tables, captions, and texts, etc. I cannot use a blacklist/whitelist approach for characters, because users should be able to use all of them.

How can I escape that untrusted data in such a way so all UTF-8 characters and also characters that have special meaning in LaTeX would still work (ä,ö,ü,á,ß,°,\,%,`...) ? Is there something similar to PDO for LaTeX? I'm familar with verbatim package, but from by experience it doesn't guarantee for 100% that one could not bypass such protection ("Defenses against attacks" in the paper).

Here is a list of character replacement that i came up with (incomplete):

enter image description here

You can probaly already imagine that there is problem with replacing characters - because it should be done in the right order and needs to be done very carefully so one would not be able to bypass it.

LaTeX-Injection articles:

  • 4
    Most people just use a 'disposable' approach, for example a Docker image or similar, or alternatively use p mode for TeX Live – Joseph Wright Jul 9 '20 at 12:48
  • @JosephWright I understand that such approach would mitigate the nastiest vulnerabilies like RCE, file discolusure, info leaks (system variables, ...) and would "fix" the vulnerability just by sandboxing my app, but it wouldn't fix the problem with broken PDF documents. There might be use cases where user doesn't actually attack, but special characters just have to work flawlessly, because in the first place this is a bug in my application (that special chars are interpreted as LaTeX code) and only additionally this is a serious vulnerability (which can be fixed but those bugs are still there) – Awaaaaarghhh Jul 9 '20 at 12:55
  • That's a very different question from 'how I prevent attack' (both the methods I mention are covered in the links you provide, BTW). 'Special' characters not appearing is always going to be an issue with LaTeX, as it's designed for typesetting, which means if a font lacks a character, the system doesn't try to 'guess' a replacement: human intervention is required to make design decisions. – Joseph Wright Jul 9 '20 at 12:57
  • 1
    Form some of the signs you have shown, replacement is absolutely unnecessary because they can be handled by an appropriate font in a UTF-8 engine. Choosing LuaLaTeX for instance would let you drop replacements for UTF-8 characters and allow you to install a callback to preprocess the input stream. You could register a custom catcode regime which disables "active" characters like &, $ etc. Then the only real problems are backslashes. Does your database text contain LaTeX commands? – TeXnician Jul 9 '20 at 13:11
  • 2
    Okay, then using LuaLaTeX and preprocessing your content sounds about right. UTF-8 is not a problem then and printing everything with a custom catcode table (everything 12, iirc -2 as argument to tex.print), you can even avoid LaTeX commands. – TeXnician Jul 9 '20 at 13:20

What you are describing is basically just verbatim input with no user-accessible delimiter. You can define commands with verbatim parameters e.g. with xparse. (Compile with LuaLaTeX in order to avoid encoding issues):

    \untrustedInput|Do don't have to trust this input. This can be \something_evil
    and everything is just interpreted as text.|

In this example, the verbatim block is delimited by + which of course would be insecure because the untrusted data might contain a +. But you can use any codepoint you want as delimiter, so you just have to choose one which is not allowed in your input. A good candidate would be an invalid Unicode codepoint like U+D800 (UTF-8 encoded as 0xED 0xA0 0x80) You can first scan you input for this byte sequence. If it appears, the encoding is invalid and you can directly issue an error. (U+D800 is a UTF-16 high surrogate and never allowed in UTF-8 data) Otherwise put the three bytes 0xED 0xA0 0x80 on both sides of your input and pass the text as argument for \untrustedInput to LuaTeX. (LuaTeX doesn't care that D800 is invalid as long as you don't try to actually typeset it.)

The command \untrustedInput will not be usable inside other arguments. That can not be directly avoided because the other argument would try to interpret the text first, potentially interpreting dangerous characters. But you can use the command to save your untrusted text into a macro which can be used for freely: (Example again with +)

\NewDocumentCommand\defineWithUntrustedInput{m +v}{\newcommand#1{#2}}
  \defineWithUntrustedInput\theText+Do don't have to trust this input. This can be \something_evil

  and everything is just interpreted as text.+
  • 1
    Even easier than Lua :) – TeXnician Jul 9 '20 at 13:56
  • Now, where are catcode tables when you need them ;) – Joseph Wright Jul 9 '20 at 13:59
  • Seems not to work with \multicolumn command: The command '\untrustedInput ' takes a verbatim argument. It may not appear within the argument of another function. It received an illegal token. – Awaaaaarghhh Jul 10 '20 at 11:03
  • @Awaaaaarghhh See the edit, you can use it to define a command or do something else with the text if necesssary. – Marcel Krüger Jul 10 '20 at 11:15
  • 1
    You don't want to do this kind of thing in pdfLaTeX. There you need active characters just to interpret UTF-8, so you can't generally disable them. So you need to individually decide if each character is reasonably safe. Even then, using this in other text is more complicated because the difference is no longer encoded in catcodes. The code would work with XeLaTeX though. – Marcel Krüger Jul 10 '20 at 11:19

A naive starter with LuaLaTeX:

ignore everything

Please note that this has quite a few caveats: Everything is catcode 12 except spaces (catcode 10). As you will see in this example, paragraphs are ignored. Many characters will depend on the font you use. But again, this is intended as a starter.

% arara: lualatex

    local file = io.open("evil.txt")
    if file then
      local content = file:read("*all")
      tex.print(-2, content)

Test here


Another test

with evil.txt:

This \bye test is evil ? ^ ²³¼ þ Þ ’¢“„ % {quack}


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.