Remittance
InvoiceVault lets freelancers manage invoices and export their account data. A convenience field in the export API trusts the client a little too much.
Room Description

https://dashboard.webverselabs-pro.com/challenges/remittance
Scenario
During a routine code audit of InvoiceVault's new account-export feature, a backend engineer noticed that the bulk-export endpoint accepted a parameter that was never supposed to reach production. The ticket was filed, then buried under a sprint freeze. The parameter is still there.
Objective
InvoiceVault lets freelancers manage invoices and export their account data. A convenience field in the export API trusts the client a little too much.
Initial Analysis
This is another product page that has a login portal, let's take a look at the product description through the landing page.


So we have a client record feature, payment tracking, invoices and exporting data, I can see how this could go wrong.
The endpoints from the navigation menu:
<nav>
<a href="/" class="nav-logo">
<div class="nav-logo-mark">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#F4C430" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/>
</svg>
</div>
InvoiceVault
</a>
<div class="nav-links">
<a href="/features" class="nav-link">Features</a>
<a href="/pricing" class="nav-link">Pricing</a>
</div>
<div class="nav-right">
<a href="/login" class="btn-ghost-sm">Sign in</a>
<a href="/register" class="btn-gold-sm">Start free</a>
</div>
</nav>
Finding the bug
We can just straight for it, we saw the features, time to create an account to try this product out.

We don't send a role during registration to try some broken access control, so we gotta look through the application post login.

Hm, we have 0 invoices, and as far as I can tell, there is no way to add an invoice. Let's take a look at settings.

Hmm, a feature called Export account data, that seems quite interesting.
Exploitation
We should try and export our own data and see the request.
This is the script that gets called that we can see in the page source:
<script>
var ACCOUNT = { user_id: 3, email: "[email protected]" };
async function exportData() {
var btn = document.getElementById('export-btn');
btn.disabled = true;
btn.textContent = 'Preparing…';
try {
var res = await fetch('/api/account/export', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: ACCOUNT.user_id })
});
if (!res.ok) throw new Error('Export failed');
var blob = await res.blob();
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'invoicevault-export.zip';
a.click();
URL.revokeObjectURL(url);
} catch(e) {
alert('Export failed. Please try again.');
} finally {
btn.disabled = false;
btn.textContent = 'Download export';
}
}
</script>
When we get our export the request sent is:

Aha, we have control over the "user_id" parameter, in this case our user is ID 3, let's try to change it to 1 and see if we have authorization to do so.

We do! Let's right click on the response, open in Browser to get the files.

Hmm, apparently user_id: 1 isn't the one we are after, how about 2?

That's the one!