Skip to content
Offensive Security in Banking Enterprise
Red Team & Penetration Testing

Anatomy of an Enterprise Core Banking Penetration Test: The Consultant Mindset

A profound, realistic look inside a high-stakes banking penetration test. This narrative bridges the gap between raw technical execution and strategic risk, showing exactly how an operator executes payloads and translates them into C-level business impact when a single misstep could disrupt a multi-billion dollar infrastructure.

Pros

  • Demonstrates the complete lifecycle of a high-stakes penetration test in a zero-downtime environment
  • Combines practical exploit commands with strategic risk analysis and decision matrices
  • Illustrates the maturity difference between simply 'running exploits' and acting as a trusted security advisor
  • Provides deep insight into how technical flaws (like JWT and TLS configs) translate directly into financial liability
  • Showcases collaborative red-blue team dynamics and expert reporting methodologies including remediation code

Cons

  • Assumes advanced knowledge of application architecture, authentication, and cryptography
  • Limits focus purely to logical attack paths rather than generic automated scanning
  • Requires understanding of compliance mandates (PCI-DSS, GLBA) to fully grasp the risk logic

On a normal engagement, knocking over a QA server costs you an awkward email and twenty minutes of someone’s afternoon. In a Tier-1 bank, interrupting an overnight batch run means the reconciliation window is missed, a regulator is notified by somebody whose job depends on notifying them, and your firm’s name appears in the incident record. Nobody cares that the payload was harmless in isolation. The outage is the finding.

That constraint shapes everything about how you work here. What follows is a composite walkthrough — the architecture, the flaws and the decisions are drawn from real banking assessments, with details changed and combined so nothing identifies a client. It covers both the technical execution and, more usefully, the judgement calls: what you test, what you deliberately do not, and how a finding gets translated into a number a board will act on.

1. Rules of Engagement: Understanding the Blast Radius

The target is a newly built payment reconciliation API. Testing runs remotely through a hardened VDI jumpbox, and both the SIEM and the IDS are watching — which is useful information rather than an obstacle, as section 14 will show.

Before a single packet goes out, the Rules of Engagement have to be settled in writing. The most dangerous assumption in banking is that the staging environment is isolated from production. It very often is not. Mainframe connection strings, message queue endpoints and downstream service URLs get hardcoded and copied between DEV, UAT and PROD by developers under deadline, and the separation that exists on the architecture diagram exists nowhere in the configuration. The UAT instance you have permission to test may hold a live connection to a production ledger. Nobody will tell you this, because nobody currently knows it.

So the first hours go into the architecture diagram, tracing how this API reaches the legacy core banking system, and then into confirming that the diagram is true — asking which downstream systems the UAT instance can actually reach, and getting someone to check rather than assert. The reason is concrete: a SQL injection payload that acquires a lock on a production table during an overnight batch run is an outage you caused, in a window where the bank has no room to absorb it. That risk sets the whole approach. No aggressive fuzzing, no automated payload sets running unattended overnight. Surgical, measured testing that establishes the vulnerability exists and stops there.

This costs coverage, and it is worth naming that honestly. A slow, careful assessment finds fewer issues than an unconstrained one, and some genuine vulnerabilities will go undiscovered because confirming them was not worth the operational risk. The client is buying assurance about the highest-impact paths, not exhaustiveness — and if the report implies otherwise, it is lying.

2. Understanding the Business Logic

Open ports are not the interesting question here. This API reconciles ledger discrepancies throughout the trading day, and the vulnerabilities that matter are the ones that let the wrong person do a legitimate operation.

The load balancers and the WAF can wait. What matters first is understanding what the application is for:

  • Where is authentication enforced?
  • Does it use JWTs, external identity providers like Okta, or legacy session cookies?
  • When you change an account parameter, does it trigger an automated batch job, or is it a direct database write?

That third question is the one worth asking twice. An API that writes directly to a table has one blast radius; an API that enqueues a job consumed by a batch process at 23:00 has a completely different one, and a payload that looks inert during business hours may execute somewhere else entirely six hours later, when nobody is watching and you have logged off.

Business context is what determines where the real vulnerabilities are. A reflected XSS in an internal admin panel is a finding you write up and nobody prioritises. What is worth hunting for here is authorisation: IDOR, broken object-level authorisation, role checks enforced in the front end and nowhere else — anything that lets one authenticated party act on ledger records belonging to another. In a reconciliation system, that is not a data exposure issue. It is an unauthorised financial transaction with a valid audit trail pointing at the wrong person.

Advertisement

3. Initial Access Strategy

This is an internal engagement — the assumed-breach model, where the starting position is an attacker who already has a foothold on the corporate network. That is the right threat model for a bank, because it is the one that keeps happening. Enumeration therefore starts from inside, unauthenticated.

The tactical challenge: Speed vs. Detection. Point ffuf at this with a thousand threads and the F5 in front of it sees a 404 spike, the SOC gets an alert, and the VDI session is terminated inside a few minutes. That costs a day of scheduling to get reinstated, and it burns credibility. So the approach is slower and more selective.

Authentication endpoints come first, for a specific structural reason: WAF rulesets and rate limits are overwhelmingly tuned for unauthenticated traffic. Once a request carries a valid-looking session, inspection tends to relax and downstream services stop asking questions. Breaking the authentication mechanism does not just grant access — it moves you into the part of the stack where nobody is looking.

Burp Suite’s Intruder runs in slow mode, roughly one request every three seconds, purely to map application structure. The trade-off is time: what an unconstrained scanner would enumerate in four minutes takes most of a day. That is the price of staying connected, and it is the correct trade in this environment.

4. Reconnaissance: Finding the Weak Points

Mapping the flows by hand surfaces a reporting endpoint:

GET /api/v1/reports/generate?report_type=summary&format=pdf HTTP/1.1
Host: internal-recon.bank.local
Authorization: Bearer <low_priv_token>

A scanner flags report_type as potential parameter tampering, scores it Low, and moves on. The parameter worth caring about is format=pdf. Server-side PDF generation is almost always a headless browser — Puppeteer, or an older wkhtmltopdf build that stopped receiving updates when the project was archived — and what that means is that supplying report content hands you partial control over a fully featured HTTP client running inside the bank’s network, with no proxy, no egress filtering applied to it, and no user behind it.

That is why the payloads target Server-Side Request Forgery and Local File Inclusion specifically rather than fuzzing broadly:

GET /api/v1/reports/generate?report_type=<iframe src="file:///etc/passwd"></iframe>&format=pdf

If the generator renders that iframe, the contents of /etc/passwd come back embedded in the returned PDF. The higher-value target is the cloud instance metadata service at 169.254.169.254, which on IMDSv1 will hand over the instance role’s temporary credentials to anything that asks. Note the qualifier: IMDSv2 requires a PUT to obtain a token first and rejects requests carrying an X-Forwarded-For header, which defeats most SSRF attempts outright. Whether this works at all comes down to a single configuration flag set by whoever built the AMI, probably years ago, possibly by copying a blog post.

5. Separating Signal from Noise

Two issues surface:

  1. Noise: The API returns a Server: Apache/2.4.41 banner.
  2. The Real Problem: The JWT has no expiry claim (exp), and the server accepts unsigned tokens.

Scanners rate the banner as a Medium information disclosure and some testers dutifully copy that into the report. Behind three layers of reverse proxy, on an internal-only service, it is noise. Version disclosure matters when it tells an attacker which exploit to fetch; here nothing is directly reachable and the version is not vulnerable to anything they could reach anyway. Reporting it as Medium is not a harmless bit of thoroughness — it costs you the credibility you will need in section 12, when a genuine Critical is challenged and the client remembers you also called an HTTP header a Medium.

The JWT is the real finding, and the missing exp claim compounds it: even without the signature bypass, a token captured from a log file, a proxy cache or a support ticket would remain valid indefinitely.

Original Extracted JWT Header & Payload (Decoded):

// Header
{"alg": "RS256", "typ": "JWT"}
// Payload
{"user": "jdoe", "role": "readonly_analyst", "iat": 1712610000}

6. Crafting the Exploit

The token is intercepted in Burp and rewritten: readonly_analyst becomes reconciliation_admin. Without the server’s RSA private key there is no way to produce a legitimate signature, so the algorithm is forced to none — a value the JWT specification permits and that any correctly configured verifier must reject outright. The vulnerability is not in the standard. It is a library that accepts the token’s own header as instruction on how to verify it, which is the cryptographic equivalent of letting the message decide whether it needs checking.

Forged JWT:

// Header
{"alg": "none", "typ": "JWT"}
// Payload
{"user": "jdoe", "role": "reconciliation_admin", "iat": 1712610000}
// Signature: (none)

Base64-url encoded:

eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VyIjogImpkb2UiLCAicm9
sZSI6ICJyZWNvbmNpbGlhdGlvbl9hZG1pbiIsICJpYXQiOiAxNzEyNjEwMDAwfQ.

The Critical Decision Point: The forged token goes to /api/v1/admin/users. HTTP 200, with a full list of system administrators.

The temptation now is to go further. Delete a user, modify a ledger entry, prove impact rather than assert it. The answer is no, and it is not a close call.

A read that returns 200 on an administrative endpoint has already established the whole finding. Everything past that point adds evidence nobody asked for and risk the client never agreed to. Write operations against a reconciliation system may enqueue batch jobs, trip fraud controls, or generate records that a regulated organisation now has to explain in its own audit trail — and “the penetration tester did it” is an explanation that gets written down permanently. The 200 response and the request that produced it are captured, and exploitation stops there.

This is the line that separates the discipline from the hobby. Impact is argued in the report; it is not demonstrated by causing it.

7. Cryptography Analysis

Internal service-to-service traffic is where cryptography goes to rot, because nobody outside the organisation ever sees it and no browser ever complains about it:

sslyze --regular internal-db-mq.bank.local:5671

The middleware talks to RabbitMQ over TLS 1.0 with weak CBC ciphers (TLS_RSA_WITH_AES_128_CBC_SHA). Two separate problems are stacked there. TLS_RSA key exchange provides no forward secrecy, so anyone who captures this traffic now and obtains the server’s private key later — from a backup, a departing engineer, a compromised certificate store — decrypts every session retrospectively. And CBC in TLS 1.0 is the family that gave us BEAST and Lucky13.

Why it matters here is the threat model the bank has already accepted everywhere else in its architecture: one phished employee means an attacker is on the internal network. From that position, weak internal transport encryption turns passive network access into credential and payment-data access. This also fails PCI DSS Requirement 4, which mandates strong cryptography for cardholder data in transit — worth stating precisely, because the compliance framing is what unlocks the remediation budget, and an imprecise citation gets the whole finding argued down.

Expect resistance on this one, and expect it to be reasonable. The message broker is a shared dependency; raising the TLS floor breaks any legacy client that cannot negotiate anything better, and in a bank there is always at least one, usually running something nobody has the source for. The realistic path is inventory first, then a staged cutover, not a configuration change on a Friday.

8. Prioritizing the Findings

Three vulnerabilities are now confirmed:

  1. Weak TLS 1.0 on internal message queues
  2. Blind SSRF in the PDF generation endpoint
  3. JWT algorithm bypass allowing privilege escalation

Which one matters most?

Prioritisation here is a question about preconditions, not about severity labels. The SSRF is blind, needs careful exploitation, and egress filtering constrains what it can actually reach — real, but it takes skill and time. The TLS weakness requires an attacker to already hold a network position that lets them capture that specific inter-service traffic, which is a meaningful prerequisite.

The JWT bypass has no preconditions worth the name. Any authenticated internal user, with Burp and ten minutes, edits one header and becomes an administrator of the payment reconciliation system. No exploit code, no timing, no network position, no second vulnerability required. Twelve thousand people can do this today and the system will record every one of those actions as legitimate.

That last part is what makes it worse than a straightforward compromise. The audit log will not show an intrusion. It will show jdoe performing administrative reconciliation actions, correctly authenticated, exactly as designed.

9. Risk Assessment: Beyond CVSS Scores

CVSS base scores deliberately carry no business context — that is what the environmental metrics exist for, and almost nobody fills them in. Reporting the base score alone and calling it risk assessment is where a lot of otherwise competent reports fail.

  • Technical Severity: Complete authentication bypass
  • Business Impact: Critical. This API reconciles payment batches. An admin attacker could suppress alerts on fraudulent outgoing SWIFT transfers
  • Exploitability: Trivial—just modify a JWT header

Final Rating: CRITICAL

Yes, the application is internal-only, and yes, reaching it requires corporate network access. Neither fact reduces the rating, because both describe a condition that is already satisfied in every breach scenario the bank plans for. An insider, or an attacker on any one of twelve thousand workstations, gains unsupervised control of payment reconciliation — which is the specific control that exists to catch fraudulent transfers.

One caveat kept in the report rather than buried: an insider with legitimate access to the reconciliation system could already cause much of this damage. The vulnerability’s real contribution is removing the separation of duties that distinguishes a read-only analyst from an approver, and doing so without leaving a trace that looks anything other than routine. That is the honest framing, and it survives scrutiny better than the maximalist version.

10. Practical Remediation Steps

“Upgrade the JWT library” is technically correct and operationally useless. This organisation has quarter-end deployment freezes, an ITIL change process measured in weeks, and a vendor dependency somewhere in the authentication stack. Advice that ignores all three gets filed and not actioned. What they need is something deployable this week, something deployable this quarter, and a direction of travel.

Immediate (24 Hours): Add an F5 iRule that blocks any JWT presenting alg:none:

when HTTP_REQUEST {
    if { [HTTP::header exists "Authorization"] } {
        set auth_header [HTTP::header "Authorization"]
        if { $auth_header contains "eyJhbGciOiAibm9uZS" } {
            HTTP::respond 401 content "Unauthorized"
            return
        }
    }
}

Be explicit with the client about what this is: a substring match on a base64-encoded header, and therefore a tourniquet, not a fix. Alternative encodings, whitespace variations and mixed-case algorithm names all sail past it, and anyone who reads this iRule can construct a bypass in under an hour. Its purpose is to raise the cost for an opportunistic attacker during the days before the real fix ships. Deploy it, and put a date on removing it — because the failure mode here is that the tourniquet becomes the control, the finding gets marked mitigated, and the underlying flaw ships to the next three services built on the same authentication library.

Next Sprint: Enforce signature validation in the application itself, with the algorithm pinned server-side (RS256) rather than read from the token header. The token must never be permitted to influence how it is verified. While the code is open, add and enforce exp, plus iss and aud validation, since a token that is valid forever is the next finding whether or not the signature holds.

Long-Term: Move financial APIs off long-lived stateless JWTs to short-lived tokens with server-side introspection. The trade is real and should be stated: introspection adds a round trip to every request and makes the authorisation server a hard availability dependency. What it buys is instant revocation — and on a system that moves money, being unable to revoke a compromised session until it expires on its own is not a position anyone should have to defend afterwards.

11. Reporting That Actually Matters

A junior tester sorts findings by technical category: injection, then cryptography, then configuration. A consultant sorts them by who has to act and how quickly.

The executive summary contains no mention of JWTs, algorithms or base64. It says: “Any internal employee, or any attacker controlling an employee’s workstation, can approve payment reconciliations without oversight. This defeats separation of duties and creates direct exposure to insider fraud.”

That sentence is the entire finding for the audience that controls the budget. It names who can do it, what they can do, and which control it defeats — and every word of it is defensible under challenge.

The technical section goes to the architects and carries everything needed to reproduce and verify: exact payloads, the endpoint, the response, the iRule, the code-level fix. Two audiences, two levels of detail, one set of facts. What must never differ between them is the severity — a Critical in the technical annex that has softened into “an area for improvement” in the summary is how findings die, and the person who softened it is usually the one who wanted to keep the client comfortable.

12. Defending the Finding

At the debrief, DevOps pushes back: “How is that Critical? The API is only reachable from the internal VPN.”

This is a reasonable challenge and it deserves a real answer rather than a defensive one. Arguing CVSS vectors here loses the room. Arguing threat models wins it:

“The VPN has twelve thousand users. One of them clicks a phishing link, and the attacker is on the same segment as this API with a legitimate session. Network location is not authentication — and the bank’s own incident response plan is written on the assumption that this exact thing will happen.”

The useful move is pointing at a document the organisation already agreed to. Their assumed-breach posture is not something you are asserting; it is in their own strategy, signed off by someone senior to the person objecting. A finding that survives because the client’s own threat model requires it to survive is a finding that stays in the report after you leave.

The rating stands.

13. Preventing the Same Mistake Twice

Finding the JWT flaw is worth one report. Understanding why it shipped is worth every report after it.

Nobody chose to accept unsigned tokens. A developer used a library’s default verification call, the default trusted the header’s algorithm field, code review looked at business logic rather than cryptographic configuration, and nothing in the pipeline was capable of noticing. The same pattern is almost certainly present in the other services built by the same team from the same internal template.

The systemic fix has two halves. Add an authentication test to CI that presents a token with alg:none and a token with a tampered signature, and fails the build if either is accepted — a test that takes an afternoon to write and runs forever. Then fix the shared template, because the pipeline check catches the next occurrence while the template is what keeps producing them.

Being straight about the limits: DAST will not catch this reliably on its own. It exercises deployed endpoints, this class of flaw depends on library configuration, and a scanner that does not happen to attempt this specific manipulation reports nothing — a clean pass that looks identical to a secure service. The targeted test is the control; the scanner is supporting evidence.

14. When Detection Works

Midway through, the SSRF payloads start timing out. Then the VDI session drops.

The SOC caught it. A short run of requests containing file:///etc/passwd tripped a rule, an analyst looked at it, and access was severed inside a few minutes.

This goes in the report prominently rather than quietly. Half the value of an engagement is measuring the defence, and a detection with a timestamp is the most objective evidence a SOC will ever get about its own performance — far better than a tabletop exercise, because nobody was expecting it. The write-up records what fired, how long the gap was between the first anomalous request and containment, and what the analyst actually did.

Two honest caveats belong alongside the praise, or it becomes flattery. The detection triggered on a literal /etc/passwd string, which is a well-known indicator that a competent attacker would never send in that form; it is worth asking whether the same activity, base64-encoded or aimed at an internal hostname instead of a local file, would have produced anything at all. And the response was to cut the session — correct here, and worth thinking through against a real intrusion, where terminating an attacker’s access tells them they have been seen and often converts a patient operator into a destructive one.

Both points went to the SOC lead as questions rather than findings. They were the most productive part of the debrief.

15. What a Real Attacker Would Do

Everything above is close to how a ransomware affiliate behaves after establishing a foothold, with one important difference: they are not on a clock, and they are not trying to avoid breaking things.

The Attacker’s Playbook: Use the JWT bypass to move quietly through payment routing systems, learn how reconciliation and approval actually work, establish persistence somewhere unremarkable, exfiltrate what has leverage, and only then encrypt — because the extortion demand is far stronger when it is backed by data and by demonstrated understanding of the business.

Your Defense: Endpoint detection contributes very little here. A forged JWT is a well-formed HTTPS request from a managed workstation to an approved internal service, and there is no malicious binary, no unusual process tree and no suspicious network destination for the sensor to object to. That is the point worth taking away: this attack is invisible at exactly the layer most security budget is concentrated in.

What does work is behavioural analysis on the identity layer — an account that has never touched administrative endpoints suddenly enumerating them — application-layer logging that records the claimed role alongside the authenticated user so a mismatch is detectable at all, and protocol validation at the gateway. Note that all three require someone to be looking. Each generates volume, each generates false positives, and every one of them fails silently if the log source stops shipping, which nothing will alert you to unless you have alerted on the absence of logs specifically.

16. The Real Difference

One version of this job runs a scanner, exports the findings, sorts by CVSS and delivers a spreadsheet. It is not worthless — it catches the missing patch that would otherwise have been missed — but it is a commodity, and it is priced accordingly.

The other version holds a different question in mind throughout: what could this cost the organisation, and who would have to explain it. That is why the Apache banner did not make the report, why the JWT bypass outranked two other genuine vulnerabilities, why exploitation stopped at an HTTP 200, and why the remediation advice began with something deployable inside an existing change process rather than something architecturally correct and eighteen months away.

None of that is more technically sophisticated than running the scanner. It is mostly restraint, plus a willingness to defend a rating in a room where several people would prefer a lower one. In banking, where the cost of both a missed finding and an unnecessary outage lands on the same balance sheet, that judgement is most of the value being bought.


Share article

Subscribe to my newsletter

Receive my case study and the latest articles on my WhatsApp Channel.

Warning

Ask CyberROX AI