Site cover image

Site icon image vicevirus’ Blog

Yo, welcome to my blog! I write tech stuff and play CTFs for fun. (still a noob)

🔎 TeX-ing the Limits: When a Patch Falls Short (CVE-2025-26525)

Introduction


As we know, Moodle is known for being one of the most popular open-source learning management systems (LMS), widely used by educational institutions and organizations worldwide. Its robust feature set, including support for custom plugins and extensibility, makes it a go-to choice for managing online learning. However, like any complex platform, Moodle is not without its security challenges.

In this post, we’ll get into an interesting case involving Moodle’s TeX filter. A feature designed for rendering mathematical expressions and how a patch intended to address a prior vulnerability fell short, leaving the door open for exploitation. By revisiting a previous CVE, we’ll explore how the issue persists and demonstrate how an attacker can exploit this flaw for local file read.

Note: Only Moodle instances with the TeX notation filter enabled and TeX Live installed are affected.

Revisiting a past CVE (CVE-2024-43426)


In CVE-2024-43426, a flaw was discovered in pdfTeX, a widely used tool for processing TeX files. The issue stemmed from insufficient sanitization in the TeX notation filter, which led to a vulnerability allowing arbitrary file reads on systems where pdfTeX is available.

The vulnerability seems to be using the \pdffiledump primitive, a command in pdfTeX that provides direct access to the contents of local files, as demonstrated in the Git commit shown below.

// the patch/commit tries to block \pdffiledump
@@ -84,7 +84,7 @@ function filter_tex_sanitize_formula(string $texexp): string {
         '\afterassignment', '\expandafter', '\noexpand', '\special',
         '\let', '\futurelet', '\else', '\fi', '\chardef', '\makeatletter', '\afterground',
         '\noexpand', '\line', '\mathcode', '\item', '\section', '\mbox', '\declarerobustcommand',
-        '\ExplSyntaxOn',
+        '\ExplSyntaxOn', '\pdffiledump',
     ];

Since pdfTeX is included in the default distribution of TeX Live, this vulnerability is a significant risk for systems using TeX Live with applications like Moodle. Without proper input sanitization, \pdffiledump can be exploited to access local files, exposing sensitive data.

How is the primitives really being blocked?


As we can see in the patch above, it seems to be adding \pdffiledump to a list of deny rules.

Here's a step-by-step breakdown of how the sanitization is implemented in the filter_tex_sanitize_formula() function in /filter/tex/lib.php.

The denylist is defined as an array of unsafe commands.

The patch includes \pdffiledump in this denylist:

$denylist = [
    'include', 'command', 'loop', 'repeat', 'open', 'toks', 'output',
    'input', 'catcode', 'name', '^^',
    '\def', '\edef', '\gdef', '\xdef',
    '\every', '\errhelp', '\errorstopmode', '\scrollmode', '\nonstopmode',
    '\batchmode', '\read', '\write', 'csname', '\newhelp', '\uppercase',
    '\lowercase', '\relax', '\aftergroup',
    '\afterassignment', '\expandafter', '\noexpand', '\special',
    '\let', '\futurelet', '\else', '\fi', '\chardef', '\makeatletter', '\afterground',
    '\noexpand', '\line', '\mathcode', '\item', '\section', '\mbox', '\declarerobustcommand',
    '\ExplSyntaxOn', '\pdffiledump',
];

The denylist is then converted into regex patterns to match occurrences of unsafe commands in the input:

$denylist = array_map(function($value) {
    return '/' . preg_quote($value, '/') . '/i';
}, $denylist);

For example, the entry \pdffiledump becomes a case-insensitive regex pattern like /\\pdffiledump/i.

Next, the function uses preg_replace_callback to replace any denylisted keywords in the input TeX formula with the prefix forbiddenkeyword_:

$texexp = preg_replace_callback($denylist, function($matches) {
    error_log("FORBIDDEN: " . $matches[0]);
    return 'forbiddenkeyword_' . $matches[0];
}, $texexp);

If the input formula contains \pdffiledump, it will be replaced with forbiddenkeyword_\pdffiledump, effectively preventing its execution (or is it?)

Finally, an allowlist is defined to prevent unintended blocking of safe commands. If a denylisted command was incorrectly mangled and is on the allowlist, it is restored:

$allowlist = ['inputenc'];

$allowlist = array_map(function($value) {
    return '/\bforbiddenkeyword_(' . preg_quote($value, '/') . ')\b/i';
}, $allowlist);

$texexp = preg_replace_callback($allowlist, function($matches) {
    return $matches[1];
}, $texexp);

Where does it fall short?


As we can see, if we enter the input \pdffiledump, it will be converted to forbiddenkeyword_\pdffiledump due to the sanitization process.

However, in TeX, the _ character is treated as a subscript operator. For example, in TeX, if you write text_32, it will render as text with 32 as a subscript (i.e., text₃₂).

Similarly, when the sanitized string forbiddenkeyword_\pdffiledump is processed by TeX, the _ causes it to treat forbiddenkeyword as the main text and \pdffiledump as the subscript.

Thus, this demonstrates that the patch is insufficient, as we are still able to read files via this primitive despite the intended sanitization.

Patched in Moodle ver. 4.5.2, 4.4.6, 4.3.10 and 4.1.16


The latest patch implements removal of backslashes to make commands impotent as shown here.

$texexp = preg_replace_callback($denylist,
    function($matches) {
        // Remove backslashes to make commands impotent.
        $noslashes = str_replace('\\', '', $matches[0]);
        return 'forbiddenkeyword_' . $noslashes;
    },
    $texexp
);

Thus, using \pdffiledump returns forbiddenkeyword_pdffiledump instead of forbiddenkeyword_\pdffiledump. Safe to say, it’s fixed for now?

Timeline


  • 16 December 2024 - Reported to Moodle via Bugcrowd.
  • 9 Jan 2025 - Acknowledged by the developers.
  • 18 Feb 2025 - Devs pushed a patch and the vulnerability was assigned CVE-2025-26525.