PXL Security LTD, Sofia, Bulgaria Offensive security since 2014[email protected]
MethodologyActive DirectoryDefence

We almost never need an exploit

By PXL Security7 October 202618 min read

We have been testing other people's systems since 2014, and the uncomfortable pattern in our own data is that the findings which actually end an engagement badly almost never involve an exploit. They involve a credential that should not have existed, a permission nobody re-read after a migration, and a trust relationship that was convenient in year one and load-bearing by year four.

The gap between how people imagine a breach and how one happens

Ask an engineering team to picture a compromise and you get the same film. Someone finds a bug in a parser, writes something clever, bypasses a mitigation, and lands code execution on a box that was fully patched last Tuesday. It is a flattering image, because it implies the defender lost to genius rather than to admin — and it implies a defence: patch faster, buy the scanner, watch the advisory feeds.

What we see on engagements looks nothing like that. The usual shape is a credential in a place credentials are not supposed to live, used against a service that accepts it without complaint, by a principal whose permissions are broader than anyone realised, inside a network where one system vouches for another for historical reasons. Nobody writes shellcode. The requests are well-formed, authenticated and indistinguishable from the ones the application serves all day.

Let us be precise, because the headline overstates it on purpose. We are not saying memory corruption is irrelevant or that you can ignore a critical remote vulnerability in an internet-facing service. Exploits matter enormously in specific places: unauthenticated edge devices, long-lived appliances, anything where patching is slow and exposure permanent. Occasionally we need one, and when we do it is usually a public one against something that should have been retired.

The claim is narrower and, we think, harder to argue with: the risk concentrates elsewhere. Across our engagements, the findings that let us move from nothing to a serious business impact are overwhelmingly about how systems are configured, who they believe you are, and what they trust. That is a statement about distribution, not about possibility. If you are allocating finite remediation capacity, distribution is the only thing that matters.

What our own numbers say

We publish an anonymised dataset so this argument can be checked rather than asserted. The current set covers 21 engagements and 190 findings. Of those, 180 carried a CVSS-based severity rating, and they split as follows.

Severity of CVSS-rated findings (n = 180)

  Critical         5    3%
  High            26   14%
  Medium          50   28%
  Low             80   44%
  Informational   19   11%

Note the shape. Severe findings are rare — 31 at High or Critical, roughly one in six of what we rated — while the mass of the distribution sits at Low and Medium, where most of the hours and most of the arguments go. Note also that the five Critical findings are the whole report as far as the business is concerned, and none of them came from finding a novel bug.

The finding classes tell you more than the severities do. Here is how often each class appeared, expressed as the share of engagements in which we reported at least one instance of it.

  • Information disclosure — 76%. Systems volunteering internal detail: versions, stack traces, naming, object metadata — individually harmless, collectively a map.
  • Security misconfiguration — 57%. A component deployed with settings that were fine for a demo and never tightened for production.
  • Missing security headers — 52%. Cheap, well-documented browser-side controls simply absent.
  • Unpatched or end-of-life components — 43%. Software past the point where anyone will ever fix it again.
  • Broken access control — 38%. Authenticated requests reaching data or actions belonging to someone else.
  • Authentication weaknesses — 38%. Weak factors, recoverable sessions, flows that can be rejoined part-way through.
  • Weak cryptography — 33%. Deprecated algorithms, undersized parameters, protections that would not survive scrutiny.

Read that list against the film in people's heads. Most of the common classes are not vulnerabilities in the software-defect sense at all; they are decisions. One of them — unpatched and end-of-life components, at 43% — is the patching story, and it is genuinely common. But even there the usual problem is not a missing update on a supported product; it is a product nobody is updating because it has no supported version left. That is a lifecycle and ownership failure wearing a vulnerability's clothing. Methodology and definitions are in our findings report.

Note. Two honesty caveats, because the figures invite over-reading. First, the severity split covers only the 180 CVSS-rated findings. Red-team work does not produce findings a vector string describes usefully — the output is a narrative of how an objective was reached, with the risk in the chain rather than any one step — so those findings are reported separately and are deliberately excluded from the severity table. Second, 21 engagements and 190 findings is a modest sample drawn from the clients who hire us, which is not a random sample of anything. The percentages describe our work; they do not measure the industry.

Configuration, identity, trust

When we sort the serious findings — the ones that changed what a client did the following month — they fall into three categories with remarkable consistency.

Configuration is the state a system is actually in, as opposed to the state its documentation describes. Defaults never revisited. A permissive option enabled during a migration and never reverted. Verbose diagnostics left on because they were useful once. Storage made reachable to solve a Friday problem. Configuration is where the gap between intent and reality lives, and it drifts continuously: every deployment and every incident fix nudges it.

Identity is the question of who a request is allowed to be, and what that identity may do. Accounts and tokens that outlived their purpose. Service principals granted broad rights because narrowing them required understanding the application. Shared credentials. Secrets in build artefacts. Roles that accumulate permissions and never shed them, because removing one risks breaking something and adding one never does. Identity sprawl is the most reliable route we have from a small foothold to a large one.

Trust is what one system assumes about another without checking. Internal services that accept any caller already inside the perimeter. Integrations where one party's compromise is transitively the other's. Segments nominally separated and practically adjacent. Management planes reachable from places nobody drew on the diagram. Trust relationships are rarely documented as such, which is precisely why they survive audits.

All three are systematically under-prioritised relative to patching, for structural rather than careless reasons. Patching has an identifier, a vendor advisory, a scanner that flags it, a dashboard that counts it and a ticket that closes cleanly. Configuration, identity and trust have none of that. No feed tells you a role became over-permissioned last quarter, no number goes down when you remove a stale integration, and there is no obvious owner — these problems sit in the seams between platform, application and operations teams, where everyone can reasonably believe someone else holds them. They also degrade silently: a correct configuration becomes an incorrect one through ordinary work, with nobody present at the moment it turns.

How to harden this

  • Make configuration a tested artefact, not a state. Assert the settings that matter — authentication required, diagnostics off, storage private, cipher policy — as checks that run on every deployment and fail it, not as a hardening document somebody read once.
  • Give every non-human identity an owner and an expiry. Tokens, keys, service accounts and integration credentials all need a named owner and a date after which they stop working. Anything without both is an unmanaged liability.
  • Review permissions by what was used, not by what was requested. Pull actual usage per principal over a representative window and remove the rest. Reviews that ask whether a permission is still wanted always answer yes.
  • Write down your trust relationships. For each service, list who may call it, what it accepts as proof of identity, and what it can reach. The ones you cannot articulate are the ones to test first.
  • Treat end-of-life as a control with a deadline. Track supported-until dates as first-class inventory, and set the retirement date before support ends rather than after the finding lands.
  • Hunt for secrets where they accumulate — repositories, build logs, images, backups, support tickets. Rotate what you find, then fix the path that put it there.

Why this is the opposite of reassuring

There is a reading of all this that sounds like good news: if attackers need no exploits against us, we are not facing exotic capability, and our problems are mundane. We hear it often, and it is exactly backwards. "No exploit needed" is the worse finding, for three reasons.

It removes the skill barrier. Writing a reliable exploit against a modern target requires expertise, time and tolerance for failure, which limits how many people can do it and how often they bother. Using a valid credential against a service that accepts it requires none of those things. Anyone who can run a commodity toolkit, buy access, or phish one employee can operate at the level our serious findings describe. A weakness that needs no specialist skill has an attacker population orders of magnitude larger than one that does.

It removes the evidence. Exploitation is noisy: crashes, malformed input, anomalous process behaviour, the kind of thing detection engineering is built around. The activity we are describing produces none of it. An over-permissioned token used to read data it technically may read generates an authenticated, authorised, well-formed request with a 200 response. It appears in logs as normal use, because by the system's own rules it is normal use. The only distinguishing features are contextual — this principal, this volume, this hour, this object it has never touched before — and almost nobody logs with enough structure to ask those questions.

It removes the distance. An exploit needs a reachable vulnerable version of a specific thing. Configuration, identity and trust weaknesses need only a foothold — any foothold. One phished session, one exposed credential, one compromised supplier integration, and the accumulated set becomes available at once, because these weaknesses are not localised to an edge. They are distributed through the environment, so a foothold anywhere is adjacent to most of them. That is why the gap between "someone got in" and "someone got everything" is so often measured in hours.

Put together: the weaknesses that dominate our findings are easier to use, harder to see, and closer to any starting point than the ones most programmes are organised around. What follows are the concrete patterns, what the defenders who caught us did differently, and why the fix order most organisations use is upside down.

Credentials that outlived everything around them

The most useful artefact we recover on an internal engagement is rarely a vulnerable binary. It is a password nobody has been brave enough to change. In one environment we found a privileged service account whose password had last been set roughly seventeen years earlier. It still worked, still held the rights granted at creation plus whatever had accreted since, and nothing in the estate could notice it had become a fossil.

The reason it survived is not negligence. Rotation is believed to be risky: nobody is certain which processes bind the credential, which scheduled job will fail silently, which integration will break at quarter end. The people who built the dependency have moved on, so the safest-looking decision each year is to leave it alone. Seventeen of those decisions in a row produce something that cannot be reasoned about at all.

What makes this more than a hygiene complaint is how a credential of that age compounds:

  • It predates current policy. Chosen under whatever rules existed then, it is usually short enough to fall to an offline attack within hours once a hash is in hand.
  • It is in the documentation. Runbooks, build guides and handover notes left by three generations of operators — every copy a place the secret can be read by someone never meant to have it.
  • It is in the backups. Every restore point and archived machine image from the whole period still holds material that unlocks the production account today. The exposure window is not "now"; it is the estate's entire history.
  • It is reused. A password that old was almost always set on sibling accounts, local administrator accounts or an appliance, and nothing has since forced it apart.

Age measures how many copies of a credential exist outside the controls you believe you have. The same engagement surfaced dozens of accounts missing the Kerberos pre-authentication flag — the adjacent failure, which lets an attacker request crackable material for those accounts without authenticating at all. Alone, a policy gap. Together, a path.

How to harden this

  • Inventory service accounts by password age. Treat anything beyond a few years as an unmanaged secret.
  • Answer the fear of rotation with observability, not courage: a few weeks of per-account authentication logging tells you what binds the credential.
  • Prefer account types where the platform rotates the secret for you, so the decision is made once.
  • Fix accounts that do not require pre-authentication; it removes a free offline-attack primitive.
  • Assume any credential older than your backup retention is already disclosed. Rotating it is containment, not maintenance.

Protocol defaults that were never turned on

The second pattern is quieter, because nothing is missing. The control exists, it shipped with the product, and it is off — as it has been since installation, because enabling it was a deliberate act no project ever owned.

The list repeats across engagements. Signing and channel protections on file-sharing and directory protocols left disabled, so an attacker who can influence name resolution relays authentication rather than merely observing it. Default credentials still live on a database service, untouched from the installation guide. A database account with rights far beyond anything the application uses, because granting everything was quickest. Plaintext secrets in configuration files, readable by more accounts than intended.

The clearest case was a business-to-business file-transfer service exposed to the Internet. It completed TLS handshakes without ever requesting a client certificate: mutual TLS was supported and documented, and had not been configured. Partner authentication had therefore collapsed, silently, from possession of a private key to knowledge of a shared secret — and a shared secret can be replayed from anywhere by anyone who recovers it once, from a ticket, a script, or a configuration file like the ones above.

recovered configuration secret
  → authenticate to Internet-facing transfer listener
  → server never requests client certificate
  → partner identity asserted, not proved
  → authorised file drop into downstream processing

None of these are defects. Each is a feature that exists in the product, ships disabled for compatibility, and is never enabled because the system works without it. Nothing breaks and no alert fires. The control is in the architecture diagram and absent from the running configuration, and that gap is where most of our access comes from.

How to harden this

  • Treat "supported but not enabled" as a finding class of its own: signing, channel binding, mutual authentication, certificate validation.
  • Require signing and channel protections on directory and file-sharing protocols, and measure which legacy clients break before the change rather than treating them as a reason not to make it.
  • On any partner-facing listener, require a client certificate and verify issuer and subject. Where a shared secret is the only authenticator, the trust boundary is weaker than the contract claims it is.
  • Scan configuration stores, repositories and build artefacts for plaintext secrets on a recurring basis.
  • Give database accounts only the rights the application demonstrably uses, and replace defaults during the build.

Trust that points outward

When we report a foothold, we are usually asked what it reached. That is the wrong axis. The more consequential question is what it let us become.

From one set of recovered material we held three outward-facing identities: credentials for partner file-transfer accounts, a deployment key for a repository that deployed automatically on commit, and a migration key for legacy back-end nodes. None is interesting as a destination; each is interesting as an impersonation. The partner identities allow forged file drops into someone else's pipeline, where data arrives pre-authenticated and is trusted more than anything at a front door. The deployment key allows a commit that becomes running code with no human in the path — removing that friction is the pipeline's purpose.

foothold on a reachable host
  → recover stored integration keys
  → commit to auto-deploying repository
  → code runs in production without review
  → blast radius now includes every downstream consumer

Blast radius therefore stops being an internal measure: the organisation is no longer only the victim but the delivery mechanism, and the harm lands on counterparties who were never tested and never consented. The risk also shifts from technical to contractual and regulatory — data-processing agreements, notification duties that may belong to several parties at once, supplier assurances you can no longer make truthfully. Reported accurately, it is the part of an engagement a board can price.

How to harden this

  • Maintain a register of outward trust: every key and account by which you can act as yourself inside someone else's system, and who holds it.
  • Keep deployment credentials off general-purpose hosts; scope them to one repository and environment, and make them short-lived.
  • Require a human approval gate between commit and production deployment for anything that crosses a boundary.
  • Sign the payloads you send to partners, not only the channel, so a stolen transport identity cannot by itself produce a trusted file.
  • Rehearse the notification path for an incident in which you are the vector; the legal position is harder to work out during one.

What the defenders had right

It would be dishonest to leave the impression that these environments were poorly run. The same engagements produced a consistent set of things done well, several of which stopped us.

  • External TLS posture. Perimeter configuration graded in the top band: modern protocol versions, sensible ciphers, correct chain handling. Once a reliable source of findings, and largely no longer one.
  • A tight database extension surface. Only what was needed was installed, so several well-known escalation routes inside the engine lacked the components they rely on.
  • No container socket exposed in a database container. The most common container-to-host escape we look for was not there. Somebody had thought about it.
  • Hardened application managers. The administrative interfaces that usually run straight from a weak password to code execution were locked down or out of reach.
  • Rotated breach-corpus passwords. Where the organisation's users appeared in public breach data, their credentials had been changed. Spraying known material produced nothing.
  • Network placement that ended an escalation path. A certificate authority was unreachable from the tested network. A well-understood route existed on paper and could not be walked — not because the weakness was patched, but because the topology denied us the conversation.

That last one is the clearest example of defence in depth actually working. The weakness was present; the path to it was not. Segmentation did the job it is sold as doing, which is rarer than the marketing suggests. The rotation and the extension surface make a related point about where effort pays: both are unglamorous, both are invisible when they succeed, and both removed whole categories of attack rather than individual findings.

We report these alongside the failures as method, not courtesy. A test that lists only what broke leaves a security lead unable to tell which investments are working, and so unable to defend them. It also misjudges severity, because a finding whose exploitation path is closed by segmentation is not the same finding as one whose path is open.

Note. The controls that stop us are rarely the impressive ones: a placement decision, an uninstalled component, a rotation actually carried out.

What to fix first, and why the order is counterintuitive

The instinctive remediation order starts with the patch backlog, because it is numbered, measurable and visibly shrinking. We would put it last of four.

First, identity and credential hygiene. Age-audit service accounts, rotate the ancient ones, fix pre-authentication flags, split reused passwords apart, move secrets out of configuration files. Credentials are the only asset whose exposure is retroactive: a key recovered today unlocks systems as they are now, and every historical copy stays live until rotation. Patching repairs a future; rotation repairs a past.

Second, enable the protections you have already bought. Signing, channel protections, mutual TLS on partner listeners, least privilege on database accounts, defaults replaced. Nothing here needs procuring: the cost is a configuration review and a compatibility test, and the effect is to remove relay, replay and impersonation as techniques rather than as instances. You are already paying for these features.

Third, map outward trust. Enumerate the identities by which your systems can act inside other organisations' systems, then scope, shorten and gate them. Do this before patching because it sets what your worst day looks like: two environments at identical patch levels have different blast radii if one keeps a live deployment key on a general-purpose host.

Fourth, patch. It matters, and end-of-life hosts running cleartext services genuinely must go. But the backlog is the most visible and least decisive of the four: it is never finished, so it absorbs effort without reaching a resting state, and its items are ranked by score rather than by reachability, so work flows to vulnerabilities no path leads to. And it is rarely the first link in the chain. Memory corruption is not how we get in. Something we were given is.

The claim is about sequencing under a fixed budget: the first three tiers remove techniques, the fourth removes instances. Do the work that changes what an attacker is able to do.

Where would an attacker actually get in?

A penetration test answers that with proof rather than a patch list — the paths that work, and the order to close them.

Scope a penetration test