CVE-2023-38745 (Other, published 2023-07-25T04:15Z)
Description: value: Pandoc before 3.1.6 allows arbitrary file write: this can be triggered by providing a crafted image element in the input when generating files via the --extract-media option or outputting to PDF format. This allows an attacker to create or overwrite arbitrary files, depending on the privileges of the process running Pandoc. It only affects systems that pass untrusted user input to Pandoc and allow Pandoc to be used to produce a PDF or with the --extract-media option. NOTE: this issue exists because of an incomplete fix for CVE-2023-35936 (failure to properly account for double encoded path names).
Severity: nan (CVSS v3 base score: 6.3)
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/jgm/pandoc
Files changed:
src/Text/Pandoc/Class/IO.hs
src/Text/Pandoc/MediaBag.hs
test/Tests/MediaBag.hs
Commit message: Fix new variant of the vulnerability in CVE-2023-35936.

Guilhem Moulin noticed that the fix to CVE-2023-35936 was incomplete.
An attacker could get around it by double-encoding the malicious
extension to create or override arbitrary files.

    $ echo '![](data://image/png;base64,cHJpbnQgImhlbGxvIgo=;.lua+%252f%252e%252e%252f%252e%252e%252fb%252elua)' >b.md
    $ .cabal/bin/pandoc b.md --extract-media=bar
    <p><img
    src="bar/2a0eaa89f43fada3e6c577beea4f2f8f53ab6a1d.lua+%2f%2e%2e%2f%2e%2e%2fb%2elua" /></p>
    $ cat b.lua
    print "hello"
    $ find bar
    bar/
    bar/2a0eaa89f43fada3e6c577beea4f2f8f53ab6a1d.lua+

This commit adds a test case for this more complex attack and fixes
the vulnerability.  (The fix is quite simple: if the URL-unescaped
filename or extension contains a '%', we just use the sha1 hash of the
contents as the canonical name, just as we do if the filename contains
'..'.)


Vulnerable code:
                     else showDigest (sha1 contents) <> "." <> ext
                '.':e -> e
                _ -> maybe "" T.unpack $ extensionFromMimeType mt
                  B.para (B.image "data://image/png;base64,cHJpbnQgImhlbGxvIgo=;.lua+%2f%2e%2e%2f%2e%2e%2fa%2elua" "" mempty) <>

Fixed code:
  -- we unescape URI encoding, but given how insertMedia
  -- is written, we shouldn't have any % in a canonical media name...
                       && '%' `notElem` fp''
                     else showDigest (sha1 contents) <> ext
                '.':e | '%' `notElem` e -> '.':e
                _ -> maybe "" (\x -> '.':T.unpack x) $ extensionFromMimeType mt
                  B.para (B.image "data:image/png;base64,cHJpbnQgImhlbGxvIgo=;.lua+%2f%2e%2e%2f%2e%2e%2fa%2elua" "" mempty) <>
        -- double-encoded version:
        let e = B.doc $
                  B.para (B.image "data:image/png;base64,cHJpbnQgInB3bmVkIgo=;.lua+%252f%252e%252e%252f%252e%252e%252fb%252elua" "" mempty)
        runIOorExplode $ do
          fillMediaBag e
          extractMedia "bar" e
        exists6 <- doesFileExist ("bar" </> "772ceca21a2751863ec46cb23db0e7fc35b9cff8.png")
        exists7 <- doesFileExist "b.lua"
        assertBool "data uri with double-encoded malicious payload gets written outside of destination dir"
          (exists6 && not exists7)