Insights · Pentesting

Two Lows and a Medium: How Attack Chains Turn Minor Findings into Critical Exploits

Doug Collins · · 7 min read

Three findings rated low, medium and low, connected in sequence to a single critical outcome: customer records readable

Your last security report had no criticals. That is not the same thing as having no critical risk.

Most vulnerability reports are lists. Each finding gets its own row, its own severity score, and its own place in the remediation queue. Criticals get fixed this sprint. Highs get fixed this quarter. Lows get a ticket that ages quietly in the backlog, because there is always something more urgent.

Attackers don’t read your report one row at a time. They read it the way a locksmith reads a building: which door leads to which hallway, and which hallway leads to the room that matters. A finding that is harmless on its own can be the first step of a path that ends somewhere very harmful.

That path is an attack chain, and it is one of the most consistent blind spots in how security testing results are produced and prioritized.

Severity is scored in isolation

The industry’s standard scoring system says so directly. FIRST’s user guide for CVSS v4.0 states that “CVSS is designed to classify and rate individual vulnerabilities,” and that the base score “represents only the intrinsic characteristics of a vulnerability.” The guide does describe how an analyst can assess a chain of vulnerabilities, but it is explicit that this is “not a formal metric.” It is guidance for someone who has already found the chain.

That’s not a flaw in the scoring system. A score has to describe something, and the cleanest thing to describe is one vulnerability on its own. The problem is what happens downstream, when a list of individually scored findings becomes a to-do list ranked by those scores. A low gets treated as low risk, when all the score really says is that the finding is low risk by itself.

Real intrusions are rarely one step. MITRE’s CWE program describes a chain as “a sequence of two or more separate weaknesses” in which one weakness “can directly create the conditions that are necessary” for the next. Each step changes what the attacker can see and reach, which changes what the next finding is worth.

Public advisories show the pattern regularly. In May 2025, Ivanti disclosed two flaws in its Endpoint Manager Mobile product, which it described as “one medium and one high severity vulnerability.” Its advisory continued: “When chained together, successful exploitation could lead to unauthenticated remote code execution.” Neither piece was rated critical on its own. Together they let an attacker run code without logging in, and CISA added both to its Known Exploited Vulnerabilities catalog within a week.

A worked example

Here’s how that plays out. This is an illustrative composite, not a customer engagement, but every piece of it is a pattern that shows up in real applications.

Finding 1: source maps are publicly accessible. Rated low. The production web app serves its JavaScript source maps. Nothing secret is in them, no keys or passwords. They do reveal the original, readable front-end code, including a generated API client that lists every route the backend exposes, among them an administrative export endpoint used only by internal staff. On its own, this is information disclosure. Most reports rate it low, and most teams leave it.

Finding 2: an internal API trusts a request header. Rated medium. The admin routes are meant to be reachable only from inside the company network. Instead of checking the network path, the API checks for a header that the internal load balancer adds. Send that header yourself, from anywhere, and the API treats you as internal. In isolation this is a real weakness, but a tester who doesn’t know which internal routes exist has little to aim it at, so with no sensitive route known, it can end up reported as medium.

Finding 3: the export endpoint isn’t scoped to a tenant. Rated low. The export job was written for internal operations staff, so it returns records across all customer accounts, with no per-tenant filter. Since only internal callers were ever supposed to reach it, the missing filter looks like a defense-in-depth gap. Low.

Chained: customer records readable from the internet. Critical. Finding 1 tells an attacker the export route exists. Finding 2 lets them reach it from outside. Finding 3 means that what comes back is every customer’s data. Three tickets, none rated above medium, add up to a data exposure that would top any risk register.

No single step required anything exotic. What it required was holding the output of one step in mind while trying the next: the route name from step one, reused in step two, aimed at the behavior from step three.

Why scanners miss chains

Signature-based scanners are built to answer one question many times: does this request, sent to this endpoint, produce a response that matches a known-vulnerable pattern? Each check is independent. Even a scanner that finds the route in the source map won’t think to replay it with the internal header, because those are different checks, run by different modules, and neither knows the other ran.

Chaining needs two things a list of independent checks doesn’t have.

State. Credentials, tokens, discovered endpoints, and earlier responses have to be retained and reused. A tool that starts fresh on every check forgets step one before it reaches step two.

Reasoning about the specific application. The useful question is not “is this endpoint vulnerable to a known pattern?” It’s “given what I now know about this application, what does this finding let me do next?” That’s a planning problem, and it is different for every application.

This is a big part of why skilled human pentesters find things scanners don’t. It is also why chain-finding has historically been expensive: it takes a person, time, and an engagement window.

Chains form between your tests

Here’s where it connects to how often you test.

In the example above, nothing says those three findings shipped at the same time. Suppose the source maps went out in a front-end build in March. The header-based check came with an infrastructure change in June. The export endpoint shipped for the operations team in August. If your penetration test was in February, none of them existed on test day. If your next one is next February, the chain has been live for months before anyone looks for it.

And because each piece arrived separately, each one may have been reviewed separately, and each one looked fine. The risk didn’t exist in any single change. It came into existence when the third piece landed.

That’s the velocity gap at its sharpest. A point-in-time test can only evaluate the combinations that exist on the day it runs. Every deploy after that adds pieces, and new pieces mean new combinations.

What to do about it

Stop auto-closing lows. Before a low or informational finding goes to the bottom of the backlog, ask one question: what does this make easier? Information disclosure findings deserve the most suspicion, because they are often the map for everything else.

Test paths, not just endpoints. Whatever testing you use, ask whether it carries state from one step to the next and whether it tries to combine findings. If its results can only ever be a list of independent rows, it can’t find chains.

Test after changes, not just on a calendar. Chains assemble over time. Testing that runs when you ship catches the moment the last piece lands, rather than months later.

Let proof settle the argument. A chain on a whiteboard is a theory, and theories lose to sprint deadlines. A chain proven end to end, with the actual requests and responses, gets fixed.

How EdgeBreach approaches it

EdgeBreach is built around attack-chain reasoning. Our own reasoning model, trained specifically for software vulnerabilities, plans attacks for the application in front of it, and the system keeps a persistent, stateful session with the target for the whole test. Credentials, tokens, endpoints, and responses discovered along the way are retained and reused, which is what lets it combine individually low-severity findings into a single attack path and then prove that path end to end.

Proof is bounded by design. In an example like the one above, the evidence is a read-only request and a small redacted sample, never a bulk download, and if a step can’t be classified as safe, it isn’t sent. Findings that can’t be fully proven are still reported, with a confidence level and the evidence gathered so far.

And it runs as often as you ship, so the chain gets tested when its last piece lands, not at next year’s pentest.


EdgeBreach is continuous penetration testing built on our own reasoning model, trained specifically for software vulnerabilities. To see what it finds in your environment, book a demo.

Sources