
Spotting Oracle WebLogic Zero-Day Exploitation Patterns with a Node.js Detection Script
Introduction
The public reporting on this Oracle incident is the kind of headline that makes people chase a perfect IOC list. I would not.
My view is simpler: if a critical Oracle zero-day was used against real organizations, defenders should expect the first solid signal to be behavioral. That means access logs, reverse proxy logs, and host telemetry, not just hashes and fixed paths. A small Node.js detection script can get you from rumor to triage faster than a hand-built signature pack.
This post is about that script and the detection logic behind it. The source material I have is thin, so I am careful about what is confirmed and what is inference.
What the reporting confirms about the Oracle WebLogic incident
The public claim is specific: a critical Oracle zero-day was used to breach more than 100 companies
The reporting seed I was given makes one concrete claim: hackers exploited a critical Oracle zero-day and allegedly breached more than 100 companies.
That matters because it changes the defender’s job. You are not just looking for an exposed admin panel. You are looking for a live exploitation pattern that may already have moved from initial access into staging, persistence, and lateral movement.
If WebLogic is the affected surface, the early logs are usually dull at first: odd requests to admin paths, strange status-code sequences, and a host event that only makes sense in hindsight.
What we can confirm from the source, and what remains unverified
| Status | Claim |
|---|---|
| Confirmed by the public reporting snippet | A critical Oracle zero-day was allegedly used in a breach campaign affecting more than 100 companies. |
| Not confirmed in the material I received | The exact CVE, affected WebLogic version, exploit chain, and intrusion window. |
| Inference | WebLogic-style management traffic, reverse proxies, and host telemetry are the right places to hunt for this class of incident. |
That last line is an inference, not a fact. I would not present it as anything else. I would use it to decide where to look first.
Why WebLogic exploitation is hard to spot in logs
The request patterns defenders usually see first
In WebLogic incidents, the first web request is often not obviously malicious. It may be a GET against a management-looking path, a POST that should never come from the internet, or a short burst of retries that looks like a broken client until you line it up by source IP.
The early patterns I usually watch for are:
- requests to administrative or management paths from internet-facing addresses
- method mismatches, like GET where the application normally expects POST
- 401, 403, 404, 405, or 500 responses in a short burst
- a second-stage request that lands minutes later from the same source
- a host event that follows the web request: process spawn, webroot write, or unusual archive extraction
The bug class is not “one bad request.” It is “one bad request plus what happened next.”
Why simple signatures miss web shell staging and follow-on activity
A pure IOC signature tends to miss the part that matters most: staging.
If the attacker uses a fresh path, a slightly different payload, or a variant of a public exploit, path matching alone will not save you. Even when the web request looks odd, the real compromise may only show up in host telemetry:
javaspawning a shell- a new
.jspor.jspxfile appearing in a web-accessible directory - a temporary deployment artifact being left behind
- a service account using an unexpected child process
That is why I prefer a script that scores behavior instead of firing on a single string match.
Designing a Node.js detection script that looks for behavior, not just IOCs
Inputs to support: access logs, reverse proxy logs, and host telemetry
I would design the script to accept three kinds of input:
- Access logs from WebLogic itself or the fronting web server.
- Reverse proxy logs from Nginx, Apache, HAProxy, or a WAF.
- Host telemetry in newline-delimited JSON, ideally from EDR or audit tooling.
The point is correlation. A weird request is only weak evidence by itself. A weird request followed by a shell spawn is much stronger.
Detection logic: path patterns, method mismatches, unusual status codes, and bursty retries
The script should score a request when several weak signals line up:
- path hits a management surface
- method looks wrong for that path
- status code suggests probing or partial success
- the same source repeats requests in a short window
- host telemetry adds a second-stage event
A simple threshold model works well enough for triage:
- 0–39: ignore or log
- 40–69: review
- 70+: alert
- 85+: treat as likely incident until disproven
Scoring alerts so one weird request does not become a false incident
I do not trust any detector that turns a single 404 into a page at 3 a.m.
A better model is additive:
- admin path hit: +30
- wrong method for a sensitive endpoint: +15
- error status: +10
- repeated hits from same source within two minutes: +15
- suspicious host event: +40 to +50
That way one noisy request stays boring, while a real sequence crosses the line.
Walking through the script structure
Safe file reading and log parsing in Node.js
I like line-by-line parsing with readline so the script can handle large logs without loading everything into memory.
Here is a trimmed version of the core parser and scorer:
#!/usr/bin/env node
const fs = require("fs");
const readline = require("readline");
const ADMIN_PATHS = [
/\/console(?:\/|$)/i,
/\/em(?:\/|$)/i,
/\/wls-wsat(?:\/|$)/i,
/\/bea_wls_internal(?:\/|$)/i
];
const SENSITIVE_METHODS = new Set(["POST", "PUT", "DELETE"]);
const ERROR_STATUSES = new Set([401, 403, 404, 405, 500, 502, 503]);
const recentHits = new Map();
function parseAccess(line) {
const m = line.match(/^(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) [^"]+" (\d{3})/);
if (!m) return null;
return {
type: "access",
src: m[1],
ts: new Date(m[2]),
method: m[3],
path: m[4],
status: Number(m[5]),
raw: line
};
}
function parseHost(line) {
try {
const obj = JSON.parse(line);
return { type: "host", ...obj, ts: new Date(obj.ts), raw: line };
} catch {
return null;
}
}
I keep the parser boring on purpose. The interesting part is the scoring, not the syntax.
Rule matching with regular expressions and small lookup tables
function scoreAccess(e) {
let score = 0;
const reasons = [];
if (ADMIN_PATHS.some(rx => rx.test(e.path))) {
score += 30;
reasons.push("admin-path");
}
if (!SENSITIVE_METHODS.has(e.method) && /login|j_security_check|console/i.test(e.path)) {
score += 15;
reasons.push("method-mismatch");
}
if (ERROR_STATUSES.has(e.status)) {
score += 10;
reasons.push(`status-${e.status}`);
}
const key = `${e.src}|${e.path}`;
const bucket = recentHits.get(key) || [];
const now = e.ts.getTime();
const recent = bucket.filter(ts => now - ts < 120000);
recent.push(now);
recentHits.set(key, recent);
if (recent.length >= 4) {
score += 15;
reasons.push("bursty-retries");
}
return { score, reasons };
}
function scoreHost(e) {
let score = 0;
const reasons = [];
if (e.type === "process_start" && e.parent === "java" && /(?:sh|bash|cmd\.exe|powershell)$/i.test(e.child || "")) {
score += 50;
reasons.push("java-spawned-shell");
}
if (e.type === "file_write" && /\.(jsp|jspx)$/i.test(e.file || "")) {
score += 40;
reasons.push("webshell-like-file-write");
}
return { score, reasons };
}
This is not a full EDR. It is a triage filter that says, “Look here first.”
Emitting evidence lines, timestamps, and confidence scores
When the script fires, I want the output to explain itself. A score without context is barely useful.
function confidence(score) {
if (score >= 85) return "critical";
if (score >= 70) return "high";
if (score >= 40) return "review";
return "low";
}
function emit(e, score, reasons) {
if (score < 40) return;
console.log(
`${e.ts.toISOString()} score=${score} confidence=${confidence(score)} source=${e.src || e.host || "unknown"}`
);
console.log(` reasons=${reasons.join(",")}`);
console.log(` evidence=${e.path || e.child || e.file || e.type}`);
}
That format makes it easier to paste into an incident channel or ticket without losing the reason it fired.
Example detections and what they mean
A suspicious administrative path hit from an internet-facing source
A single request to a management surface is not proof of compromise. But it is a real signal if the source is external and the status code is one of the noisy ones.
Example synthetic access log:
198.51.100.23 - - [19/Jun/2026:11:12:03 +0000] "GET /console/login/LoginForm.jsp HTTP/1.1" 404 612 "-" "Mozilla/5.0"
198.51.100.23 - - [19/Jun/2026:11:12:11 +0000] "POST /console/j_security_check HTTP/1.1" 200 1492 "-" "Mozilla/5.0"
198.51.100.23 - - [19/Jun/2026:11:12:19 +0000] "POST /console/j_security_check HTTP/1.1" 200 1492 "-" "Mozilla/5.0"
198.51.100.23 - - [19/Jun/2026:11:12:27 +0000] "POST /console/j_security_check HTTP/1.1" 200 1492 "-" "Mozilla/5.0"
That burst looks like probing or replay behavior. It is not enough to claim a breach, but it is enough to keep digging.
A successful request followed by odd process or file activity
This is the point where the case gets much stronger.
Synthetic host telemetry:
{"ts":"2026-06-19T11:12:31Z","host":"weblogic-01","type":"process_start","parent":"java","child":"/bin/sh","ppid":2214}
{"ts":"2026-06-19T11:12:34Z","host":"weblogic-01","type":"file_write","file":"/u01/app/oracle/user_projects/domains/base_domain/servers/AdminServer/tmp/stage/a.jsp"}
If I see those events after a suspicious web request, I stop treating it as a scan and start treating it as an incident.
Sample terminal output from a synthetic test log set
$ node weblogic-hunt.js sample-access.log sample-host.jsonl
2026-06-19T11:12:27.000Z score=70 confidence=high source=198.51.100.23
reasons=admin-path,method-mismatch,status-200,bursty-retries
evidence=/console/j_security_check
2026-06-19T11:12:31.000Z score=90 confidence=critical source=weblogic-01
reasons=java-spawned-shell
evidence=/bin/sh
2026-06-19T11:12:34.000Z score=40 confidence=review source=weblogic-01
reasons=webshell-like-file-write
evidence=/u01/app/oracle/user_projects/domains/base_domain/servers/AdminServer/tmp/stage/a.jsp
The exact thresholds are adjustable. The useful part is that the script explains why it escalated.
Testing, tuning, and reducing false positives
Normal WebLogic traffic to baseline before you alert
Baseline first. Always.
If your admins legitimately hit /console from a VPN range, allowlist that source. If your monitoring system polls a health endpoint, carve that out too. The goal is not to eliminate all noise. The goal is to make the remaining noise meaningful.
Thresholds, allowlists, and environment-specific exceptions
A good tuning loop is:
- run the script in log-only mode for a week
- collect the top sources and paths
- label the known-good traffic
- raise the score for combinations you care about
- lower the score for routine admin activity you cannot remove
I would not use a global allowlist that hides everything from a single subnet. That is how a compromised jump host becomes invisible.
What I would not trust without host-level confirmation
I would not declare compromise based on:
- one admin-path request
- one 404 to a management endpoint
- one 500 from a reverse proxy
- one burst from a scanner IP
I would trust the alert much more if it includes:
- a suspicious request sequence
- a source that is not your normal admin population
- a host process spawn from
java - an unexpected file write in a web-accessible area
That is the line between noise and evidence.
Response steps when the script fires
First-pass triage: confirm exposure, source IPs, and time window
Start with the basics:
- Is the host internet-facing?
- Is the source IP external or a known internal tool?
- Did the event occur before or after patching?
- Are there nearby auth failures, process spawns, or file writes?
If the answer is “yes” to exposure and “yes” to host activity, the incident is already serious.
Containment: isolate the host, rotate credentials, and preserve logs
If the alert is high confidence:
- isolate the server from the network
- preserve access logs, proxy logs, and host telemetry
- rotate any credentials that touched the box
- review service accounts, API keys, and deployment secrets
- capture volatile evidence before rebooting if policy allows it
Do not patch first and ask questions later. Patching can destroy evidence.
Recovery and hardening: patching, exposure review, and monitoring gaps
After containment, do the boring work:
- apply Oracle’s latest security updates
- remove internet exposure from admin surfaces
- put WebLogic behind a proxy or VPN where possible
- keep access and host telemetry in one place
- test the detector against known-good admin behavior
If this incident happened once, assume the environment was harder to observe than you thought.
Conclusion
The practical position: this is a detection-and-response problem, not only a patching problem
The public reporting says a critical Oracle zero-day was used in a broad breach campaign. I am not going to pretend that an IOC list alone will protect you from that.
My view is that WebLogic-style exploitation is best hunted with behavior: management-path requests, method mismatches, error bursts, and host activity that follows the web hit. A small Node.js script can surface those chains early enough to matter.
That is the right posture here: patch fast, yes, but also detect like the attacker expects you to miss the middle of the story.


