commit 5e381e3878b5da87ee7542f7e51c3c1a7fd84b89 Author: John MacFarlane Date: Tue Jun 20 13:50:13 2023 -0700 Fix a security vulnerability in MediaBag and T.P.Class.IO.writeMedia. This vulnerability, discovered by Entroy C, allows users to write arbitrary files to any location by feeding pandoc a specially crafted URL in an image element. The vulnerability is serious for anyone using pandoc to process untrusted input. The vulnerability does not affect pandoc when run with the `--sandbox` flag. --- pandoc-2.14.0.3/src/Text/Pandoc/Class/IO.hs.orig 2021-06-11 07:26:17.000000000 +0800 +++ pandoc-2.14.0.3/src/Text/Pandoc/Class/IO.hs 2024-03-22 16:39:03.445837785 +0800 @@ -48,7 +48,7 @@ import Network.HTTP.Client.TLS (mkManagerSettings) import Network.HTTP.Types.Header ( hContentType ) import Network.Socket (withSocketsDo) -import Network.URI (unEscapeString) +import Network.URI (URI(..), parseURI) import System.Directory (createDirectoryIfMissing) import System.Environment (getEnv) import System.FilePath ((), takeDirectory, normalise) @@ -119,11 +119,11 @@ openURL :: (PandocMonad m, MonadIO m) => Text -> m (B.ByteString, Maybe MimeType) openURL u - | Just u'' <- T.stripPrefix "data:" u = do - let mime = T.takeWhile (/=',') u'' - let contents = UTF8.fromString $ - unEscapeString $ T.unpack $ T.drop 1 $ T.dropWhile (/=',') u'' - return (decodeLenient contents, Just mime) + | Just (URI{ uriScheme = "data:", + uriPath = upath }) <- parseURI (T.unpack u) = do + let (mime, rest) = break (== '.') upath + let contents = UTF8.fromString $ drop 1 rest + return (decodeLenient contents, Just (T.pack mime)) | otherwise = do let toReqHeader (n, v) = (CI.mk (UTF8.fromText n), UTF8.fromText v) customHeaders <- map toReqHeader <$> getsCommonState stRequestHeaders --- pandoc-2.14.0.3/src/Text/Pandoc/MediaBag.hs.orig 2021-06-19 04:15:43.000000000 +0800 +++ pandoc-2.14.0.3/src/Text/Pandoc/MediaBag.hs 2024-03-22 16:33:37.600005389 +0800 @@ -33,7 +33,7 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Digest.Pure.SHA (sha1, showDigest) -import Network.URI (URI (..), parseURI) +import Network.URI (URI (..), parseURI, isURI, unEscapeString) data MediaItem = MediaItem @@ -52,9 +52,12 @@ instance Show MediaBag where show bag = "MediaBag " ++ show (mediaDirectory bag) --- | We represent paths with /, in normalized form. +-- | We represent paths with /, in normalized form. Percent-encoding +-- is resolved. canonicalize :: FilePath -> Text -canonicalize = T.replace "\\" "/" . T.pack . normalise +canonicalize fp + | isURI fp = T.pack fp + | otherwise = T.replace "\\" "/" . T.pack . normalise . unEscapeString $ fp -- | Delete a media item from a 'MediaBag', or do nothing if no item corresponds -- to the given path. @@ -77,17 +80,18 @@ , mediaContents = contents , mediaMimeType = mt } fp' = canonicalize fp + fp'' = T.unpack fp' uri = parseURI fp - newpath = if isRelative fp + newpath = if isRelative fp'' && isNothing uri - && ".." `notElem` splitPath fp - then T.unpack fp' + && not (".." `T.isInfixOf` fp') + then fp'' else showDigest (sha1 contents) <> "." <> ext - fallback = case takeExtension fp of - ".gz" -> getMimeTypeDef $ dropExtension fp - _ -> getMimeTypeDef fp + fallback = case takeExtension fp'' of + ".gz" -> getMimeTypeDef $ dropExtension fp'' + _ -> getMimeTypeDef fp'' mt = fromMaybe fallback mbMime - path = maybe fp uriPath uri + path = maybe fp'' (unEscapeString . uriPath) uri ext = case takeExtension path of '.':e -> e _ -> maybe "" T.unpack $ extensionFromMimeType mt