This is a building directory left unlocked, not the vault door itself
CVE-2026-9152 is an unauthenticated exposure in a legacy Altium 365 SearchService SOAP endpoint. Per Altium's advisory, it affected all Altium 365 cloud workspaces, including commercial and government cloud regions, while on-prem Altium Enterprise Server was not affected. An attacker who knows or can guess a target workspace identifier can query indexed content and can also inject, modify, or delete search index entries without presenting a token, session, or identity.
The raw technical shape screams severe because it is unauthenticated, network-reachable, and cross-tenant. But reality matters: the impact is constrained to the search index layer, not the underlying vault data or platform control plane. That keeps this out of CRITICAL for defenders prioritizing real-world risk, even though the exposed surface is ugly and the confidentiality exposure is still meaningful.
3 steps from start to impact.
Identify a target workspace
- Attacker has internet reachability to Altium 365 cloud endpoints
- Attacker can obtain or infer a target workspace identifier
- Workspace identifiers are not always public
- Enterprises with tightly controlled external sharing reduce passive discovery
Call the legacy SOAP endpoint without a token
curl, Burp Suite, SoapUI, or a custom Python SOAP client, the attacker sends requests directly to the legacy SearchService endpoint. The bug is that the service accepts search index operations without bearer tokens, cookies, or session validation, so the attacker reaches cross-tenant indexed data with no login step.- A reachable vulnerable SearchService endpoint exists
- The service has not yet been remediated at Altium's side
- Undocumented legacy SOAP paths are harder to discover than first-class REST/GraphQL APIs
- Reverse proxies, WAFs, or service retirement can break naive exploit attempts
Read or poison the search index
- The endpoint exposes read and/or write search index operations
- The target workspace has sensitive metadata in the index
- Impact stops at the search layer rather than modifying source-of-truth vault objects
- Some indexed content may be low sensitivity in mature environments
MFAdoes not help against a path that never asks for authentication in the first place.EDRon engineer laptops does not meaningfully detect or block abuse that stays inside the SaaS provider's service plane.Patch your endpointsis irrelevant here because there is no customer-side software fix for Altium 365 cloud tenants.Perimeter WAFon your corporate edge does not protect a third-party SaaS endpoint you do not front.
The supporting signals.
| In-the-wild status | No confirmed active exploitation found in public sources reviewed as of 2026-06-06; not listed in CISA KEV. |
|---|---|
| Public PoC availability | No public GitHub, Exploit-DB, or Metasploit PoC was identified in the reviewed sources as of 2026-06-06. |
| EPSS | Prompt-supplied EPSS is 0.00079 (~0.079%), which is extremely low and argues against broad opportunistic exploitation right now. |
| KEV status | Absent from the CISA KEV catalog as checked on 2026-06-06. |
| Assessed CVSS interpretation | My working vector is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L: internet-reachable, no auth, no user action, cross-tenant confidentiality impact, but integrity/availability damage is mostly limited to the search index. |
| Affected scope | Per Altium: all Altium 365 cloud workspaces in commercial and government cloud were affected; Altium Enterprise Server on-prem was not affected. |
| Fixed state | Altium says the issue was remediated at the service level and no customer action is required; there is no customer-installable fixed version. |
| Exposure reality | This was a SaaS-wide internet surface rather than a niche internal admin port. Reachability was broad, but exploitability still depended on finding the legacy SOAP path and a target workspace identifier. |
| Scanning / exposure data | I found no reliable public Shodan/Censys/FOFA fingerprint for this specific SearchService endpoint in the reviewed sources. That means exposure quantification is poor, but the vendor advisory already confirms the affected population was cloud-wide. |
| Disclosure / reporter / baseline discrepancy | Disclosure date is 2026-05-21 and Altium credits Joris Aerts. Note: your prompt says there is no vendor/authority score, but Altium's advisory page currently displays CVSS 10.0; I still mark this as assessed per your instruction because the goal here is a first-principles real-world severity call. |
Sources.
Why this verdict
- Unauthenticated remote access pushes this up: no credentials, no prior foothold, no user interaction, and network reachability from the internet are strong severity amplifiers.
- Cross-tenant SaaS exposure pushes this up again: the bug crosses workspace boundaries in a shared cloud platform, so the reachable population before remediation was broad rather than niche.
- Search-index-only impact pulls it down: Altium explicitly says the operations affect the search index, not the underlying vault data. That is the biggest downward pressure on severity.
- Workspace-ID knowledge adds friction: an attacker still needs a target workspace identifier, which implies some discovery work and keeps this from being the easiest spray-and-pray internet bug.
- Threat intel is cold: no KEV listing, no public PoC found, and the prompt-supplied EPSS of 0.00079 all argue against widespread weaponization today.
Why not higher?
This is not a platform takeover, auth bypass to the primary data plane, or remote code execution. The attacker can expose sensitive indexed metadata and corrupt search results, but the advisory is clear that the underlying vault objects are not directly changed through this flaw. That limitation is too important to ignore.
Why not lower?
You do not get to call an unauthenticated, internet-facing, cross-tenant SaaS flaw 'medium' just because the write path stops at the search layer. Confidentiality loss still matters here, especially for project names, component data, folder names, and user metadata that can reveal active programs, suppliers, or regulated work.
Crowdsourced verification payload.
Run this from an auditor workstation with internet access to your Altium 365 workspace, not from the target host—this is a SaaS check. Invoke it as python3 altium365_searchservice_check.py https://yourworkspace.365.altium.com; no admin rights are needed. It is a heuristic validator because Altium does not publicly document the legacy SearchService path in the reviewed sources.
#!/usr/bin/env python3
# altium365_searchservice_check.py
# Heuristic check for CVE-2026-9152-style unauthenticated SearchService exposure
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN
import sys
import urllib.request
import urllib.error
import urllib.parse
import ssl
import re
CANDIDATE_PATHS = [
"/SearchService.svc?wsdl",
"/SearchService.svc?singleWsdl",
"/SearchService.svc",
"/search/SearchService.svc?wsdl",
"/search/SearchService.svc?singleWsdl",
"/search/SearchService.svc",
"/services/SearchService.svc?wsdl",
"/services/SearchService.svc",
]
SOAP_PROBE = b'''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body />
</soap:Envelope>'''
AUTH_PATTERNS = [
re.compile(r'login', re.I),
re.compile(r'sign\s*in', re.I),
re.compile(r'authorization', re.I),
re.compile(r'bearer', re.I),
]
SERVICE_PATTERNS = [
re.compile(r'<wsdl:definitions', re.I),
re.compile(r'<definitions', re.I),
re.compile(r'SearchService', re.I),
re.compile(r'soap:Envelope', re.I),
re.compile(r'tempuri\.org', re.I),
re.compile(r'http://schemas\.xmlsoap\.org/wsdl/', re.I),
]
def normalize_base(url: str) -> str:
if not url.startswith("http://") and not url.startswith("https://"):
url = "https://" + url
parsed = urllib.parse.urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError("Invalid workspace URL")
return f"{parsed.scheme}://{parsed.netloc}"
def fetch(url: str, method: str = "GET", data: bytes = None, headers: dict = None, timeout: int = 10):
req = urllib.request.Request(url, data=data, method=method)
for k, v in (headers or {}).items():
req.add_header(k, v)
ctx = ssl.create_default_context()
try:
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
body = resp.read(4096).decode("utf-8", errors="replace")
return resp.getcode(), dict(resp.headers), body, None
except urllib.error.HTTPError as e:
try:
body = e.read(4096).decode("utf-8", errors="replace")
except Exception:
body = ""
return e.code, dict(e.headers), body, None
except Exception as e:
return None, {}, "", str(e)
def looks_like_auth_gate(code: int, headers: dict, body: str) -> bool:
if code in (401, 403):
return True
loc = headers.get("Location", "")
if code in (301, 302, 303, 307, 308) and any(p.search(loc) for p in AUTH_PATTERNS):
return True
if any(p.search(body) for p in AUTH_PATTERNS):
return True
return False
def looks_like_exposed_service(code: int, body: str) -> bool:
if code != 200:
return False
return any(p.search(body) for p in SERVICE_PATTERNS)
def main():
if len(sys.argv) != 2:
print("UNKNOWN - usage: python3 altium365_searchservice_check.py https://yourworkspace.365.altium.com")
sys.exit(2)
try:
base = normalize_base(sys.argv[1])
except Exception as e:
print(f"UNKNOWN - invalid input: {e}")
sys.exit(2)
saw_network_error = False
saw_auth_or_notfound = False
findings = []
for path in CANDIDATE_PATHS:
url = base + path
code, headers, body, err = fetch(url, method="GET", headers={"User-Agent": "noisgate-check/1.0"})
if err:
saw_network_error = True
findings.append(f"GET {url} -> network error: {err}")
continue
findings.append(f"GET {url} -> HTTP {code}")
if looks_like_exposed_service(code, body) and not looks_like_auth_gate(code, headers, body):
print("VULNERABLE - unauthenticated service metadata exposed")
for f in findings:
print(f)
sys.exit(1)
if code in (401, 403, 404) or looks_like_auth_gate(code, headers, body):
saw_auth_or_notfound = True
continue
if code == 200:
# Try a blank SOAP POST; vulnerable services often respond with SOAP faults without auth
pcode, pheaders, pbody, perr = fetch(
url.split("?")[0],
method="POST",
data=SOAP_PROBE,
headers={
"User-Agent": "noisgate-check/1.0",
"Content-Type": "text/xml; charset=utf-8",
"SOAPAction": '""'
}
)
if perr:
saw_network_error = True
findings.append(f"POST {url.split('?')[0]} -> network error: {perr}")
continue
findings.append(f"POST {url.split('?')[0]} -> HTTP {pcode}")
if looks_like_exposed_service(pcode, pbody) and not looks_like_auth_gate(pcode, pheaders, pbody):
print("VULNERABLE - unauthenticated SOAP interaction appears possible")
for f in findings:
print(f)
sys.exit(1)
if pcode in (401, 403, 404) or looks_like_auth_gate(pcode, pheaders, pbody):
saw_auth_or_notfound = True
if saw_auth_or_notfound and not saw_network_error:
print("PATCHED - tested candidate paths require auth, redirect to login, or are absent")
for f in findings:
print(f)
sys.exit(0)
print("UNKNOWN - no positive exposure found, but endpoint path could not be confidently determined")
for f in findings:
print(f)
sys.exit(2)
if __name__ == "__main__":
main()