July 14, 2026
How I Broke a Financial App’s Pricing — By Sending Back What It Gave Me
No injection. No XSS. No brute force.
By Dheeraj
2 min read
Just… copying fields from a response and pasting them into a request.
While testing a transaction creation workflow during a bug bounty program, I came across something interesting.
The app had a standard flow:
- Create a quotation (locks in pricing)
- Use that quotation ID to create a transaction
Simple enough.
I submitted a completely normal transaction using a valid quotation ID.
The request looked like this:
{
"quotation_id": "QT-XXXXX",
"recipient_id": "RCP-XXXXX",
"amount": 500
}{
"quotation_id": "QT-XXXXX",
"recipient_id": "RCP-XXXXX",
"amount": 500
}No fees. No rates. No tax fields. Just the basics.
Transaction created successfully. ✅
🔍 But Then I Read the Response
The server came back with a few extra fields I never sent:
{
"retail_fee": 35,
"retail_rate": 82.51,
"retail_fee_currency": "USD",
"retail_tax": 5
}{
"retail_fee": 35,
"retail_rate": 82.51,
"retail_fee_currency": "USD",
"retail_tax": 5
}These were generated by the backend. The quotation engine calculated them.
I didn't send any of these.
At this point, most people would scroll past.
But I asked myself one simple question:
👉 "If the server generated these fields… what happens if I send them back?"
🧪 The Experiment
I created another transaction.
Intercepted the request in Burp.
Manually added those pricing fields into the request body.
Changed the values to something crazy:
{
"quotation_id": "QT-YYYYY",
"recipient_id": "RCP-YYYYY",
"amount": 500,
"retail_fee": 1000,
"retail_rate": 1000,
"retail_fee_currency": "USD",
"retail_tax": 100
}{
"quotation_id": "QT-YYYYY",
"recipient_id": "RCP-YYYYY",
"amount": 500,
"retail_fee": 1000,
"retail_rate": 1000,
"retail_fee_currency": "USD",
"retail_tax": 100
}A $1000 fee on a $500 transaction. Makes no sense.
I expected the server to reject it.
Or at least ignore those fields.
🚨 It Accepted Everything
200 OK.
The response reflected MY values. Not the quotation's values. Mine.
{
"retail_fee": 1000,
"retail_rate": 1000,
"retail_tax": 100
}{
"retail_fee": 1000,
"retail_rate": 1000,
"retail_tax": 100
}Hmm. But maybe the approval workflow would catch it?
🔓 It Didn't
The app had a Maker-Checker workflow. One person creates, another approves.
I approved the transaction.
No warning. No recalculation. No error.
My fake pricing survived.
Then I checked everywhere:
- ✅ Transaction detail page — my values
- ✅ API response — my values
- ✅ Generated receipt — my values
- ✅ PDF export — my values
- ✅ CSV export — my values
Every downstream system trusted the transaction record.
And the transaction record trusted whatever I sent.
💀 What Just Happened?
The application trusted the client too much.
These pricing fields were supposed to be server-owned. The quotation engine should have been the only source of truth.
But the backend never checked whether the client was allowed to set those fields. It just deserialized the entire JSON body and used whatever it found.
So I basically turned:
❌ Pricing controlled by quotation engine
into:
✅ Pricing controlled by me
📉 Why This Is Dangerous
Think about it.
If a Maker user can inject arbitrary pricing into every transaction:
- Financial records get corrupted
- Reports and CSV exports contain fake numbers
- Receipts and PDFs show manipulated fees
- Audit trails become unreliable
- Reconciliation breaks
The quotation existed for a reason. It was supposed to lock the pricing. But the server let the client override it without any validation.
🧠 The Real Lesson
This wasn't about complex exploitation.
It was about reading the response.
The server showed me fields it generated internally.
I sent them back with different values.
And it accepted them as truth.
👉 The system trusted the client for data only the server should own.
👉 And that's where it broke.
🏁 Outcome
Reported it through the bug bounty program.
Validated and accepted with a bounty of $200
🔥 Final Thought
I didn't use any scanner for this.
No wordlists. No payloads. No automation.
I just looked at what the server gave me and asked:
"What if I give it back?"
Sometimes the best bugs aren't hidden behind complex exploits.
They're sitting right there in the response body.
You just have to read it.