Skip to content

Add Cache Poisoning Vulnerability#534

Open
luks-santos wants to merge 19 commits intoSasanLabs:masterfrom
luks-santos:feature/cache-poisoning
Open

Add Cache Poisoning Vulnerability#534
luks-santos wants to merge 19 commits intoSasanLabs:masterfrom
luks-santos:feature/cache-poisoning

Conversation

@luks-santos
Copy link
Copy Markdown

@luks-santos luks-santos commented Mar 16, 2026

Summary

Closes #516.
This PR delivers the full level progression for the Cache Poisoning vulnerability, from the basic route-only cache key issue to the secure implementation, and adds the supporting frontend, hints, i18n, and automated tests
needed for the lab experience.

Implemented levels

  • LEVEL_1: reflected banner query parameter is rendered unescaped (also exploitable as stored XSS via the shared cache) while the cache key uses only the route.
  • LEVEL_2: naive filtering of the banner parameter does not fix the underlying issue because the cache key is still route-only — alternative unkeyed inputs keep the poisoning viable.
  • LEVEL_3: the query parameter is now part of the cache key, but the page remains vulnerable through an unkeyed X-Forwarded-Host header used to build absolute asset URLs.
  • LEVEL_4: forwarded host headers are ignored, but personalized cookie-driven content (including derived email and tracked IP) is still cached publicly, demonstrating cross-user PII leakage from a shared cache.
  • LEVEL_5: secure variant — ignores untrusted forwarding headers, escapes reflected content, uses a trusted asset host, and returns personalized responses with Cache-Control: private, no-store.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 92.06349% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.80%. Comparing base (45d14fc) to head (cd2b4a4).

Files with missing lines Patch % Lines
...ty/cachePoisoning/CachePoisoningVulnerability.java 94.69% 3 Missing and 3 partials ⚠️
...e/vulnerability/cachePoisoning/CachedResponse.java 63.63% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #534      +/-   ##
============================================
+ Coverage     52.01%   53.80%   +1.78%     
- Complexity      465      504      +39     
============================================
  Files            69       71       +2     
  Lines          2676     2801     +125     
  Branches        285      293       +8     
============================================
+ Hits           1392     1507     +115     
- Misses         1153     1159       +6     
- Partials        131      135       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@luks-santos luks-santos marked this pull request as ready for review March 22, 2026 19:35
@luks-santos luks-santos changed the title Add cache poisoning level 1 vulnerability (Draft) Add cache poisoning level 1 vulnerability Mar 23, 2026
…ature/cache-poisoning

# Conflicts:
#	src/main/java/org/sasanlabs/vulnerability/types/VulnerabilityType.java
#	src/main/resources/i18n/messages.properties
@luks-santos luks-santos changed the title Add cache poisoning level 1 vulnerability Add Cache Poisoning Vulnerability Mar 23, 2026
@luks-santos luks-santos marked this pull request as draft March 23, 2026 19:56
@luks-santos luks-santos marked this pull request as ready for review March 24, 2026 12:55
Comment thread src/main/resources/i18n/messages.properties Outdated
Comment thread src/main/resources/i18n/messages.properties Outdated
Comment thread src/main/resources/i18n/messages.properties
Comment thread src/main/resources/static/templates/CachePoisoning/LEVEL_1/CachePoisoning.js Outdated
Comment thread src/main/resources/static/templates/CachePoisoning/LEVEL_4/CachePoisoning.js Outdated
Comment thread src/main/resources/attackvectors/CachePoisoningPayload.properties
Copy link
Copy Markdown
Member

@preetkaran20 preetkaran20 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luks-santos I must say this is amazing code change. Very thorough PR. I really liked the PR completeness. I have added small comments, can you please address?

@luks-santos
Copy link
Copy Markdown
Author

@preetkaran20 Thanks a lot for the feedback!
I’ll review your comments and make the updates

@luks-santos luks-santos marked this pull request as draft April 3, 2026 00:41
@luks-santos luks-santos marked this pull request as ready for review April 17, 2026 11:54
@luks-santos
Copy link
Copy Markdown
Author

Hi @preetkaran20

I've pushed the changes addressing the feedback. Summary of what was implemented:

Frontend / UX

  • Moved level-specific templates into a common directory to reduce duplication (230afec).
  • Refactored levels 1–5 to reuse doGetAjaxCall and pass xhr to callbacks (6049137).
  • Added a "reset cache" button on every level so the lab is reproducible without restarting (3bae76d).

Vulnerability depth

  • Made Level 1 exploitable as XSS through the poisoned cached response (179777a).
  • Exposed multiple attack vectors per level with matching hints/payloads (5afcb79, f0f30a9).
  • Enriched Level 4 PII leak (derived email + tracked IP) and tightened the overall lab (447b47c).
  • Improved Level 3 visualization and fixed the simulation (ba62756, 2673d64).

The PR description has been updated to reflect the final scope. Let me know if there's anything else you'd like me to adjust!

}

function initLevel() {
const level = vulnerabilityLevelSelected || "LEVEL_1";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is breaking in Modern UI. Instead of using this, use below function to get the selectedLevel:
getCurrentVulnerabilityLevel(), this is present in both legacy and modern ui.

In order to test with modern ui use: https://github.com/SasanLabs/VulnerableApp#testing-with-modern-ui

});

// Configure Signals
if (config.signals.includes("cacheControl"))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like code became very complex after merging templates. It becomes very heavy on JS side. I think we should have different templates or group of templates for each level. Sorry, I felt easier initially but now it feels little difficult.

if (xmlHttpRequest.status == 200 || xmlHttpRequest.status == 401) {
if (isJson) {
callBack(JSON.parse(xmlHttpRequest.responseText));
callBack(JSON.parse(xmlHttpRequest.responseText), xmlHttpRequest);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this change. Really appreciate this.

CACHE_POISONING_LEVEL_1_XSS=Cross-Site Scripting (XSS) via Cache Poisoning. Since the banner parameter is reflected without sanitization and the cache only keys on the route, an attacker can store a malicious script in the cache that will execute for all subsequent users.
CACHE_POISONING_LEVEL_2_NAIVE_FILTER=This level attempts to fix the issue by stripping script tags from the input, but the cache key still only uses the route. Attackers can still poison the cache with misleading content or other HTML payloads that bypass the naive filter.
CACHE_POISONING_LEVEL_2_XSS=XSS via Cache Poisoning with Filter Bypass. The application strips obvious <script> tags, but does not sanitize other HTML attributes. Attackers can use event handlers like 'onerror' in tags like <img> to execute scripts and poison the cache.
CACHE_POISONING_LEVEL_3_JS_INJECTION=JavaScript Injection via Cache Poisoning. Since the application trusts the X-Forwarded-Host header to build asset URLs, an attacker can poison the cache to point to a malicious domain, causing all users to load and execute external scripts.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to be not in sync with messages.properties. can you please fix it?

return "https://" + StringEscapeUtils.escapeHtml4(host) + "/assets/cache-poisoning-demo.js";
}

void clearCache() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should just clear the cache of that level, not other levels, but for now it is ok as this PR is having a lot of changes. we can take it as a newer issue.

boolean resetCache,
HttpServletRequest request) {
if (resetCache) {
clearCache();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resetCache flag is present in all the levels. I think we can have a newer function with spring annotations and expose just clearcache as an api call example:

@PostMapping("/clearCache")
public String clearCache() {
cache.clear();
return "Cache cleared";
}

then the URL will become something like /vulnerableapp/cachepoisioning/clearCache. you can create a newer function in vulnerableApp.js to return base url for the vulnerability and add clearCache to it.

In case you please to add a function to VulnerableApp.js, please add it to https://github.com/SasanLabs/VulnerableApp-facade/blob/main/facade-app/public/GlobalUtility.js as well

htmlTemplate = "Common/CachePoisoning")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel1(
@RequestParam(value = BANNER_QUERY_PARAMETER, required = false) String banner,
@RequestParam(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried testing clearCache but it is not working. Seems like some bug. On clicking clear cache, if i again make a victim request, it is still returning me cached values.

Pattern.compile("(?is)<\\s*/?\\s*script\\b[^>]*>");
private static final Pattern JAVASCRIPT_SCHEME_PATTERN = Pattern.compile("(?i)javascript\\s*:");

private final ConcurrentHashMap<String, CachedResponse> cache = new ConcurrentHashMap<>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to make it static final, else it is not working as per expected.

static final String EMPTY_CACHE_KEY = "-";
static final String CACHE_STATUS_HIT = "HIT";
static final String CACHE_STATUS_MISS = "MISS";
static final String CACHE_CONTROL_PUBLIC = "public, max-age=60";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is causing a bit of difficulty to understand. this is a double edged sword.

I am thinking of adding extra levels where it is always fetches from the server i.e. no caching on browser layer for students and levels where browser side cache is there for scanners to find out that poisioning can be a candidate.

Any thoughts? shall we add multiple levels or add a flag within the level where users can choose browser level cache along with server level cache or just server cache?

Copy link
Copy Markdown
Member

@preetkaran20 preetkaran20 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, @luks-santos this is really a great PR. I have added a few comments, I think once they are resolved, i think we can merge it quickly. Just a few thoughts on browser side caching issues which we need to work on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Cache Poisoning Vulnerability

3 participants