

CISA Alert – Threat actors are actively exploiting a remote code‑execution flaw in Oracle WebLogic Server (CVE‑2026‑21962).
This guide shows how developers can detect the presence of the vulnerable component in their environments using safe, read‑only checks. No exploit payloads are included.
<a name="step-1-prerequisites"></a>
| Item | Why you need it | Recommended version |
|---|---|---|
| Oracle WebLogic Server (any version) | Target of the scan – you need network access to the admin console or any public‑facing endpoint. | N/A (just reachable) |
| Python 3.9+ | Runs the detection script. | python --version ≥ 3.9 |
| Node.js 18+ (for JS/TS) | Runs the Node/TS detection script. | node --version ≥ 18 |
| pip (Python package manager) | Install requests, tenacity, etc. | N/A |
| npm (Node package manager) | Install axios, dotenv, etc. | N/A |
| Git (optional) | Clone the example repo if you prefer. | N/A |
| Access to the target (HTTP/HTTPS) | The script sends harmless GET/HEAD requests to typical WebLogic URLs. | Ensure firewall allows outbound traffic to target ports (usually 7001, 7002, 8001, 8002, 9001, etc.) |
| API key (optional) | If you want to feed results into a SIEM or ticketing system (e.g., Splunk, Jira). | Provided by your SIEM vendor. |
Note – The detection logic only checks for known version strings and a specific HTTP header that Oracle adds in vulnerable releases. It does not attempt to exploit the vulnerability.
<a name="step-2-installation-and-setup"></a>
# 1️⃣ Clone the repo (optional)
git clone https://github.com/icarax/weblogic-cve-detector.git
cd weblogic-cve-detector/python
# 2️⃣ Create a virtual environment (recommended)
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 3️⃣ Install dependencies
pip install --upgrade pip
pip install requests tenacity python-dotenv
# 1️⃣ Clone the repo (optional)
git clone https://github.com/icarax/weblogic-cve-detector.git
cd weblogic-cve-detector/js # or ./ts for TypeScript
# 2️⃣ Initialize npm project (if not already)
npm init -y
# 3️⃣ Install dependencies
npm install axios dotenv
# For TypeScript users:
npm install --save-dev typescript @types/node ts-node
npx tsc --init # creates tsconfig.json
Tip – Keep the
.envfile (see Step 4) out of version control (git ignore .env).
<a name="step-3-basic-implementation"></a>
Below are complete, copy‑and‑paste ready scripts that:
GET /console/login/LoginForm.jsp request (the WebLogic console login page).Server header that Oracle sometimes includes (WebLogic Server).12.2.1.4.0 or 14.1.1.0.0 – adjust per CVE advisory).The scripts do not send any exploit payloads; they only perform reconnaissance.
File: detect_weblogic.py
#!/usr/bin/env python3
"""
CVE‑2026-21962 Oracle WebLogic detection script (safe, read‑only).
Usage:
python detect_weblogic.py --targets 10.0.0.5:7001 10.0.0.6:7002
# or provide a file with one target per line: python detect_weblogic.py -f targets.txt
"""
import argparse
import sys
from typing import List, Tuple
import requests
from tenacity import retry, stop_after_attempt, wait_fixed
from dotenv import load_dotenv
import os
# ----------------------------------------------------------------------
# Load configuration from .env (optional)
# ----------------------------------------------------------------------
load_dotenv() # reads .env in the cwd
TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "5"))
MAX_RETRIES = int(os.getenv("MAX_RETRIES", "3"))
USER_AGENT = os.getenv(
"USER_AGENT",
"WebLogic-CVE-Detector/1.0 (+https://github.com/icarax/weblogic-cve-detector)",
)
# ----------------------------------------------------------------------
# Constants – adjust if CISA updates the advisory
# ----------------------------------------------------------------------
VULNERABLE_VERSION_PATTERNS = [
# Examples from the advisory – replace with exact strings if known
r"12\.2\.1\.4\.0",
r"14\.1\.1\.0\.0",
# Add more as needed
]
# ----------------------------------------------------------------------
# Helper functions
# ----------------------------------------------------------------------
@retry(stop=stop_after_attempt(MAX_RETRIES), wait=wait_fixed(1))
def _safe_get(url: str) -> requests.Response:
"""Perform a GET with retry logic and a custom User‑Agent."""
headers = {"User-Agent": USER_AGENT}
resp = requests.get(url, headers=headers, timeout=TIMEOUT, verify=False)
# Disable SSL warnings for self‑signed certs (common in internal labs)
requests.packages.urllib3.disable_warnings()
return resp
def parse_targets(args: argparse.Namespace) -> List[Tuple[str, int]]:
"""Return a list of (host, port) tuples from CLI args or a file."""
targets: List[Tuple[str, int]] = []
if args.targets:
for t in args.targets:
if ":" not in t:
parser.error(f"Target '{t}' must be in host:port format")
host, port_str = t.split(":", 1)
targets.append((host.strip(), int(port_str)))
elif args.file:
with open(args.file, "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
if ":" not in line:
parser.error(f"Invalid line in {args.file}: '{line}'")
host, port_str = line.split(":", 1)
targets.append((host.strip(), int(port_str)))
else:
parser.error("Either --targets or --file must be supplied")
return targets
def check_weblogic(host: str, port: int, use_https: bool = False) -> dict:
"""
Perform a single check against host:port.
Returns a dict with keys: host, port, vulnerable (bool), reason (str).
"""
scheme = "https" if use_https else "http"
base_url = f"{scheme}://{host}:{port}"
# Common WebLogic console login page – harmless to request
check_url = f"{base_url}/console/login/LoginForm.jsp"
try:
resp = _safe_get(check_url)
except requests.RequestException as exc:
return {
"host": host,
"port": port,
"vulnerable": False,
"reason": f"Request failed: {exc}",
}
# 1️⃣ Look for WebLogic-specific header
server_header = resp.headers.get("Server", "")
is_weblogic = "WebLogic Server" in server_header
# 2️⃣ Extract version from header or body (simple regex)
import re
version_str = ""
# Try Server header first
if server_header:
m = re.search(r"WebLogic Server\s+([\d\.]+)", server_header, re.I)
if m:
version_str = m.group(1)
# Fallback: search HTML comment or meta tag (some versions expose it)
if not version_str:
m = re.search(r"WebLogic\s+[\d\.]+", resp.text, re.I)
if m:
version_str = m.group(0).split()[-1]
# 3️⃣ Match against known vulnerable patterns
vulnerable = False
reason_parts = []
if is_weblogic:
reason_parts.append("WebLogic Server detected")
for pat in VULNERABLE_VERSION_PATTERNS:
if re.search(pat, version_str):
vulnerable = True
reason_parts.append(f"version {version_str} matches vulnerable pattern {pat}")
break
if not vulnerable:
reason_parts.append(f"version {version_str or 'unknown'} does NOT match known vulnerable patterns")
else:
reason_parts.append("No WebLogic Server header found")
return {
"host": host,
"port": port,
"vulnerable": vulnerable,
"reason": "; ".join(reason_parts),
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Detect Oracle WebLogic CVE‑2026-21962 exposure (safe check)."
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"-t",
"--targets",
nargs="+",
help="Space‑separated list of host:port targets",
)
group.add_argument(
"-f",
"--file",
type=str,
help="Path to a file containing host:port entries (one per line)",
)
parser.add_argument(
"--https",
action="store_true",
help="Use HTTPS instead of HTTP for the check",
)
parser.add_argument(
"-o",
"--output",
choices=["text", "json"],
default="text",
help="Output format",
)
args = parser.parse_args()
targets = parse_targets(args)
results = [check_weblogic(h, p, args.https) for h, p in targets]
if args.output == "json":
import json
print(json.dumps(results, indent=2))
else:
for r in results:
status = "VULNERABLE" if r["vulnerable"] else "SAFE"
print(f"[{status}] {r['host']}:{r['port']} – {r['reason']}")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
sys.exit("\nInterrupted by user")
How to run
# Example with a list of targets
python detect_weblogic.py --targets 10.0.0.5:7001 10.0.0.6:7002 --https
# Or from a file
python detect_weblogic.py -f targets.txt
File: detect_weblogic.js
/**
* CVE‑2026-21962 Oracle WebLogic detection (Node.js) – safe, read‑only.
*
* Usage:
* node detect_weblogic.js --targets 10.0.0.5:7001 10.0.0.6:7002
* # or
* node detect_weblogic.js --file targets.txt
*/
require("dotenv").config(); // loads .env
const axios = require("axios");
const { parse } = require("path");
const fs = require("fs");
// ----------------------------------------------------------------------
// Configuration from environment (with sensible defaults)
// ----------------------------------------------------------------------
const TIMEOUT = parseInt(process.env.REQUEST_TIMEOUT || "5", 10); // seconds
const MAX_RETRIES = parseInt(process.env.MAX_RETRIES || "3", 10);
const USER_AGENT =
process.env.USER_AGENT ||
"WebLogic-CVE-Detector/1.0 (+https://github.com/icarax/weblogic-cve-detector)";
// Disable strict SSL for self‑signed certs (common in labs)
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
// ----------------------------------------------------------------------
// Known vulnerable version patterns (adjust per CISA advisory)
// ----------------------------------------------------------------------
const VULNERABLE_PATTERNS = [
/12\.2\.1\.4\.0/,
/14\.1\.1\.0\.0/,
];
// ----------------------------------------------------------------------
// Helper: exponential backoff retry wrapper for axios
// ----------------------------------------------------------------------
async function axiosRetry(requestFn, retries = MAX_RETRIES, delay = 1000) {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
return await requestFn();
} catch (err) {
if (attempt === retries) throw err;
// wait before retry
await new Promise((res) => setTimeout(res, delay * Math.pow(2, attempt)));
}
}
}
// ----------------------------------------------------------------------
// Parse CLI arguments
// ----------------------------------------------------------------------
const args = process.argv.slice(2);
const parsed = {
targets: [],
file: null,
https: false,
output: "text",
};
let i = 0;
while (i < args.length) {
const a = args[i];
if (a === "--targets" || a === "-t") {
i++;
while (i < args.length && !args[i].startsWith("--")) {
parsed.targets.push(args[i]);
i++;
}
} else if (a === "--file" || a === "-f") {
i++;
parsed.file = args[i];
i++;
} else if (a === "--https") {
parsed.https = true;
i++;
} else if (a === "--output" || a === "-o") {
i++;
parsed.output = args[i];
i++;
} else {
console.error(`Unknown argument: ${a}`);
process.exit(1);
}
}
if (!parsed.targets.length && !parsed.file) {
console.error("Error: Either --targets or --file must be supplied.");
process.exit(1);
}
// ----------------------------------------------------------------------
// Build list of (host, port) tuples
// ----------------------------------------------------------------------
function parseTargetString(str) {
if (!str.includes(":")) {
throw new Error(`Target '${str}' must be in host:port format`);
}
const [host, portStr] = str.split(":", 1);
return { host: host.trim(), port: parseInt(portStr, 10) };
}
let targets = [];
if (parsed.targets.length) {
targets = parsed.targets.map(parseTargetString);
} else {
const content = fs.readFileSync(parsed.file, "utf8");
targets = content
.split("\n")
.map((l) => l.trim())
.filter((l) => l && !l.startsWith("#"))
.map(parseTargetString);
}
// ----------------------------------------------------------------------
// Core detection logic
// ----------------------------------------------------------------------
async function checkTarget({ host, port }) {
const scheme = parsed.https ? "https" : "http";
const baseUrl = `${scheme}://${host}:${port}`;
const checkUrl = `${baseUrl}/console/login/LoginForm.jsp`;
try {
const response = await axiosRetry(() =>
axios.get(checkUrl, {
timeout: TIMEOUT * 1000,
headers: { "User-Agent": USER_AGENT },
validateStatus: () => true, // treat any HTTP status as ok for inspection
})
);
const serverHeader = response.headers.server || "";
const isWebLogic = /weblogic server/i.test(serverHeader);
let versionStr = "";
// Extract version from Server header if present
const headerMatch = serverHeader.match(/weblogic server\s+([\d\.]+)/i);
if (headerMatch) {
versionStr = headerMatch[1];
} else {
// Fallback: search HTML body for a version string
const bodyMatch = response.data.match(/WebLogic\s+([\d\.]+)/i);
if (bodyMatch) versionStr = bodyMatch[1];
}
let vulnerable = false;
const reasons = [];
if (isWebLogic) {
reasons.push("WebLogic Server detected");
const matched = VULNERABLE_PATTERNS.some((re) => re.test(versionStr));
if (matched) {
vulnerable = true;
reasons.push(`version ${versionStr} matches a vulnerable pattern`);
} else {
reasons.push(
`version ${versionStr || "unknown"} does NOT match known vulnerable patterns`
);
}
} else {
reasons.push("No WebLogic Server header found");
}
return {
host,
port,
vulnerable,
reason: reasons.join("; "),
};
} catch (err) {
return {
host,
port,
vulnerable: false,
reason: `Request failed: ${err.message}`,
};
}
}
// ----------------------------------------------------------------------
// Main driver
// ----------------------------------------------------------------------
(async () => {
try {
const results = [];
for (const t of targets) {
results.push(await checkTarget(t));
}
if (parsed.output === "json") {
console.log(JSON.stringify(results, null, 2));
} else {
for (const r of results) {
const status = r.vulnerable ? "VULNERABLE" : "SAFE";
console.log(
`[${status}] ${r.host}:${r.port} – ${r.reason}`
);
}
}
} catch (err) {
console.error("Unexpected error:", err);
process.exit(1);
}
})();
How to run
# Install dependencies first (see Step 2)
npm install
# Direct target list
node detect_weblogic.js --targets 10.0.0.5:7001 10.0.0.6:7002 --https
# From a file
node detect_weblogic.js -f targets.txt
<a name="step-4-configuration"></a>
| Variable | Description | Example |
|---|---|---|
REQUEST_TIMEOUT | Seconds to wait for a response before aborting. | 5 |
MAX_RETRIES | Number of retry attempts on transient network errors. | 3 |
USER_AGENT | Custom User‑Agent string sent with each request (helps with logging). | WebLogic-CVE-Detector/1.0 (+https://github.com/icarax/weblogic-cve-detector) |
TARGETS_FILE (optional) | Path to a file containing host:port lines (used if you prefer not to pass via CLI). | ./targets.txt |
OUTPUT_FORMAT (optional) | text (default) or json. | json |
Create a .env file (never commit this to source control):
# .env – place in the project root
REQUEST_TIMEOUT=5
MAX_RETRIES=3
USER_AGENT=WebLogic-CVE-Detector/1.0 (+https://github.com/icarax/weblogic-cve-detector)
Security tip – If you plan to send results to a SIEM, add the SIEM endpoint and API key as additional env vars (e.g.,
SIEM_URL,SIEM_TOKEN) and extend the script to POST the JSON payload.
<a name="step-5-common-patterns"></a>
For large inventories, run checks in parallel (Node) or with a thread pool (Python).
Python (using concurrent.futures)
from concurrent.futures import ThreadPoolExecutor, as_completed
def scan_all(targets, use_https=False, max_workers=20):
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_target = {
executor.submit(check_weblogic, h, p, use_https): (h, p)
for h, p in targets
}
for future in as_completed(future_to_target):
host, port = future_to_target[future]
try:
result = future.result()
print(
f"[{'VULNERABLE' if result['vulnerable'] else 'SAFE'}] {host}:{port} – {result['reason']}"
)
except Exception as exc:
print(f"[ERROR] {host}:{port} generated an exception: {exc}")
JavaScript (using p-limit)
npm install p-limit
const pLimit = require("p-limit");
const limit = pLimit(10); // max 10 concurrent requests
async function scanConcurrently(targets) {
const promises = targets.map((t) =>
limit(() => checkTarget(t))
);
const results = await Promise.all(promises);
results.forEach((r) => {
console.log(
`[${r.vulnerable ? "VULNERABLE" : "SAFE"}] ${r.host}:${r.port} – ${r.reason}`
);
});
}
import json, requests
def send_to_siem(payload, siem_url, siem_token):
headers = {"Authorization": f"Splunk {siem_token}", "Content-Type": "application/json"}
resp = requests.post(siem_url, json=payload, headers=headers, timeout=5)
resp.raise_for_status()
Add a call after each check or batch.
<a name="step-6-troubleshooting"></a>
| Symptom | Likely Cause | Fix |
|---|---|---|
ConnectionError: Max retries exceeded | Target host unreachable or firewall blocking port. | Verify network reachability (telnet host port or nc -zv host port). Adjust firewall or VPN. |
SSLCertVerificationError | Self‑signed cert on HTTPS endpoint. | Either set verify=False (Python) / NODE_TLS_REJECT_UNAUTHORIZED=0 (Node) only in trusted environments, or add the CA cert to the trust store. |
Empty Server header, detection says SAFE but you know it’s WebLogic | Some versions hide the header or use a custom listener. | Try alternative URLs: /_async/AsyncResponseService, /wls-wsat/CoordinatorPortType, or check for the cookie JSESSIONID with WLProxySSL flag. |
| Script reports VULNERABLE on a patched server | Version string in header matches a pattern but the server actually patched the flaw (back‑ported version). | Cross‑check with Oracle’s patch metadata or run a version‑specific check (e.g., query /console/login/LoginForm.jsp for a known patched JS file hash). |
Output is garbled JSON when using --output json | Non‑JSON print statements slipped into the flow. | Ensure all logging goes to stderr (console.error) and only the final result is printed to stdout. |
| High CPU usage when scanning many targets | No concurrency limit causing thousands of simultaneous connections. | Introduce a worker pool (ThreadPoolExecutor in Python, p-limit in Node) and tune max_workers/limit. |
<a name="step-7-production-checklist"></a>
Before deploying the detection script in a production environment (e.g., as a cron job, Lambda function, or Kubernetes CronJob):
Least‑privilege network access
TLS verification
verify=True (Python) or leave NODE_TLS_REJECT_UNAUTHORIZED unset (Node).Secure secret handling
.env or raw keys to source control.Idempotent execution & deduplication
host:port + timestamp as a dedup key.Logging & monitoring
VULNERABLE result (PagerDuty, Opsgenie, email).Rate limiting & throttling
time.sleep / setTimeout).Version‑pattern maintenance
VULNERABLE_VERSION_PATTERNS / VULNERABLE_PATTERNS.Fail‑safe defaults
Testing in a staging environment
Documentation & runbooks
Copy the Python or JavaScript/TypeScript code above, adjust the environment variables to match your infrastructure, and start scanning for the exposed Oracle WebLogic CVE‑2026‑21962 instance.
Stay safe, keep your dependencies up‑to‑date, and remember: detection is the first line of defense—patching remains the ultimate remediation.
Generated for the ICARAX tech blog – 2025
Feel free to contribute improvements or report issues on the GitHub repo:
https://github.com/icarax/weblogic-cve-detector
Source: Security Week AI
Follow ICARAX for more AI insights and tutorials.
