{
	"id": "c37b3cbf-4b84-470c-9a75-8a0d9617fdef",
	"created_at": "2026-04-06T00:08:52.73876Z",
	"updated_at": "2026-04-10T13:12:49.620114Z",
	"deleted_at": null,
	"sha1_hash": "eeb279bf4ecacd5ec7e071d89a9de6e93a2b2e4b",
	"title": "Ticket resellers infected with a credit card skimmer – Max Kersten",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 76593,
	"plain_text": "Ticket resellers infected with a credit card skimmer – Max Kersten\r\nPublished: 2020-01-20 · Archived: 2026-04-02 12:41:10 UTC\r\nFirst and foremost I’d like to thank Jacob Pimental since he posted the initial lead, after which we joined forces to\r\ndive into this case. In his now deleted Tweet, he asked if anybody could help out with a potential credit card\r\nskimmer on the OlympicTickets2020 website.\r\nBackground information\r\nBefore diving into this case, I’ll highlight the modus operandi that most credit card skimmers use. There are\r\ncertain JavaScript files that are loaded on all web pages. Prime examples of this are jQuery or Bootstrap libraries.\r\nSince they are present on all pages, these files are the perfect target to also contain the JavaScript based credit card\r\nskimmer code. Essentially, the visitor will run this code on every web page that is visited.\r\nEnumerating and breaching websites takes time, and the skimmer’s life span is of unknown duration. The code\r\ncould be detected within a few hours, or stay there for months. To increase the effectiveness of the skimmer, actors\r\ntry to breach content delivery networks that host the commonly used libraries. Whilst this approach brings greater\r\nincome, it will likely lead to a faster detection, as the online presence of the script is much bigger. Note that not all\r\nsites that use the infected library are e-commerce shops.\r\nAt last, the skimmers are generally obfuscated to make it harder for detection mechanisms to detect the code.\r\nBack to the case\r\nBased on Jacob’s suspicion, I took a look at the given JavaScript file. This file (located at /dist/slippry.min.js)\r\ncontained a small description, together with code. The description is given below.\r\n/** @preserve\r\n *\r\n * slippry v1.4.0 - Responsive content slider for jQuery\r\n * http://slippry.com\r\n *\r\n * Authors: Lukas Jakob Hafner - @saftsaak\r\n * Thomas Hurd - @SeenNotHurd\r\n *\r\n * Copyright 2016, booncon oy - http://booncon.com\r\n *\r\n *\r\n * Released under the MIT license - http://opensource.org/licenses/MIT\r\n */\r\nhttps://maxkersten.nl/2020/01/20/ticket-resellers-infected-with-a-credit-card-skimmer/\r\nPage 1 of 6\n\nPer the header, the script should contain a responsive content slider for jQuery. After beautifying the script, the\r\naforementioned modus operandi became apparent. An existing piece of JavaScript was abused to hide the\r\nmalicious code. In this case, the library was hosted on the targeted site itself. There is no information as to how the\r\nmalicious code got appended to the library.\r\nStage 1 – The loader\r\nThe complete file slippry file can be defined in the below given pseudo code.\r\n/**\r\n * Description\r\n */\r\nfunction sliderfunctionX() { ... }\r\nfunction sliderfunctionY() { ... }\r\n//...\r\n(function skimmer() { ... })()\r\nThe original code was left untouched, and the skimmer function would be executed the moment the rest of the\r\nJavaScript was loaded.\r\nWhen retrieving the function definition of the skimmer, it reminded me of a blog post I wrote about the Magecart\r\nskimmer in March 2019. The structure of the loader is, aside from the random variable names and script content,\r\nexactly the same. For a complete and in-depth overview, I can advise to read the above-mentioned write-up.\r\nThe complete first stage consists garbage code, string obfuscation, string concatenation operations, and a\r\ndecryption routine. The structure is given below in pseudo code.\r\nvar a = \"encryptedScriptPart1\" + \"encryptedScriptPart2\";\r\nvar b = \"encryptedScriptPart3\";\r\nvar encryptedScript = document[\"cr\" + (74 \u003e 4 ? \"\\x65\" : \"\\x60\") + \"ateEle\" + \"m\" + (81 \u003e 6 ? \"\\x65\"\r\nvar encryptedScript = a + b;\r\nvar script = \"\";\r\nfor (var i = 0; i \u003c script.length; i++) {\r\n //decryption routine to fill script with the decrypted content of encryptedScript\r\n}\r\nconstructor(script);\r\nPractically, this looked a little bit less readable, as can be seen in the excerpt below.\r\nvar I80 = \"0a0w0w0w0w0w0w0w0w0w0w0w0w2u39322r382x333 20w2w2\" + \"w14382t3c38153f0a0w0w0w0w0w0w0w0w0w0w\r\nvar yc8 = \"z2l1b2v180y0y1515152. j0y380y17141j1h1q1f1k1r\";;\r\nvar C46 = document[\"cr\" + (74 \u003e 4 ? \"\\x65\" : \"\\x60\") + \"ateEle\" + \"m\" + (81 \u003e 6 ? \"\\x65\" : \"\\x5b\") +\r\nhttps://maxkersten.nl/2020/01/20/ticket-resellers-infected-with-a-credit-card-skimmer/\r\nPage 2 of 6\n\nvar TLM = \"\";;\r\nfor (var W5A = (\"KL4b\u003eq5\\x7fE%\\x87XHl=i\" [\"charCodeAt\"](2) * 0 + 0.0); W5A \u003c C46[\"l\" + (59 \u003e 0 ? \"\\x6\r\n TLM += String[\"fromC\" + String.fromCharCode(104) + \"ar\" + \"Cod\" + (93 \u003e 16 ? \"\\x65\" : \"\\x5b\"\r\n};\r\nW4L = \"34302p2r2t0y2l141b2j1l391u262k2j321g2k1q2q. 2\";;\r\nih3[\"\" + String.fromCharCode(116) + \"oStri\" + \"\" + String.fromCharCode(110) + \"g\"] = NbZ[\"c\" + String\r\nThe variable named TLM contains all the code for the second stage. As such, one can print the content of the\r\nvariable and remove the line where the constructor is invoked. Runing the code in the browser’s console or using\r\nnode from the terminal suffices.\r\nStage 2 – The skimmer\r\nThe second stage contains the actual skimmer in obfuscated form, together with an integrity check, string\r\nobfuscation, and dead code insertion.\r\nThe integrity check is done to verify that the script is not altered. In the code below, the hashing function is given,\r\ntogether with the integrity check.\r\nfunction hh(text) {\r\n if (text.length == 0) return 0;\r\n var hash = 0;\r\n for (var i = 0; i \u003c text.length; i++) {\r\n hash = ((hash \u003c\u003c 5) - hash) + text.charCodeAt(i);\r\n hash = hash \u0026 hash;\r\n }\r\n return hash % 255;\r\n}\r\nvar body = window.bAQ.toString().replace(/[^a-zA-Z0-9\\-\"]+/g, \"\");\r\nvar crc = body.match(/aeh3jiqq7nr76my8hfmg([\\w\\d\\-]+)\"/g)[0].replace(\"aeh3jiqq7nr76my8hfmg\", \"\");\r\ncrc = crc.substr(0, crc.length - 1);\r\nbody = hh(body.replace(\"aeh3jiqq7nr76my8hfmg\" + crc, \"aeh3jiqq7nr76my8hfmg\")) == crc ? 1 : window[\"st\r\nThe dead code can easily be recognised, as it is not used anywhere else. A variable that is only declared and\r\ninstantiated can just as well be removed. The string obfuscation can easily be removed by copying code into the\r\nbrowser’s console and using the generated output. An example is given below.\r\nJZO = (\"nLe+.\" [\"length\"] * 63 + 0.0);\r\nvar fl7 = (\"C\\x88bSIM\\x81-c3\" [\"charCodeAt\"](4) * 6 + 39.0);\r\nvar ufN = \"(ht;X5y\u003crSmX-F6gOq3zOI\" [\"replace\"](/[\\\u003cOrh6m\\;\\(5\\-3]/g, \"\");\r\nThe variables names of the original script weren’t randomised and contained names like gatelink or nowwork. The\r\ndeobfuscated result is a clear skimmer. So clean, that directly sharing it feels irresponsible, as one only needs to\r\nalter two variables to make it work again. As such,I’ll only provide the pseudo code to explain the inner workings.\r\nhttps://maxkersten.nl/2020/01/20/ticket-resellers-infected-with-a-credit-card-skimmer/\r\nPage 3 of 6\n\nvar gatelink = \"https://opendoorcdn.com/cdn/font.js\";\r\nvar method = \"POST\"\r\nvar thisdomain = window[\"location\"][\"host\"] || \"nodomain\";\r\nvar datacollect = false;\r\nvar cachelenght = 50;\r\nvar nowwork = true;\r\nfunction removeLocalStorageData() { ... }\r\nfunction resetLocalStorageData() { ... }\r\nfunction getValuesFromFields() { ... }\r\nfunction exfiltrateData() { ... }\r\nfunction addEventListeners() { ... }\r\nif ((\"/onepage|checkout|store|cart|pay|panier|kasse|order|billing|purchase|basket/\")[\"test\"](window[\r\n nowwork = true;\r\n if (nowwork) {\r\n addEventListeners();\r\n if (localStorage[\"getItem\"](\"_google.verify.cache.001\") !== null \u0026\u0026 localStorage[\"getItem\"](\r\n resetLocalStorageData();\r\n window[\"addEventListener\"](\"unload\", exfiltrateData());\r\n }\r\n } else {\r\n removeLocalStorageData();\r\n }\r\n}\r\nNote that the keyword function is not executable in the current form. This is done intentionally, as this pseudo\r\ncode only serves to show the skimmer’s functionality. It is not intended to serve as a proof-of-concept piece of\r\ncode.\r\nOnly if the current website contains one of the trigger words, the skimmer becomes active. It adds event listeners\r\nto all forms on the website, base64 encodes the data. If the amount of captured data is more than 50 characters, an\r\nadditional event listener is created to execute the exfiltrateData function just before the page unloads. Upon\r\nconfirming the order, and just before leaving the page, the data is exfiltrated to the given domain using a POST\r\nrequest. As a result, the customer’s details are skimmed.\r\nOn the payment page of the targeted sites, the word order is present in the URL, meaning the skimmer would\r\nactivate.\r\nConnecting the dots\r\nhttps://maxkersten.nl/2020/01/20/ticket-resellers-infected-with-a-credit-card-skimmer/\r\nPage 4 of 6\n\nAfter making sure that the skimmer was indeed present, there were two questions left. Since the infection did not\r\noccur via a CDN, it is hard to find out how widespread this infection is. Secondly, we wanted to know how long\r\nthe skimmer had been in place.\r\nFinding other sites\r\nJacob searched for the JavaScript library’s hash on UrlScan, which can be found here. This shows that this specific\r\nfile is also present on another site: eurotickets2020.com. The lay-out and looks of the two sites are rather familiar.\r\nWhen looking on the about page, the same owner can be found. Additionally, the phone number for customer\r\nsupport is also the same.\r\nDating back the infection\r\nTo find the first observations of the skimmer in the wild, I tried my luck on the Wayback Machine and found one\r\nhit for each site. The skimmer on the OlympicTickets site was indexed on the third of December 2019, as can be\r\nseen here. The skimmer on the EuroTickets site was indexed on the seventh of January 2020, as can be seen here.\r\nAt the time of writing (the 21st of January 2020), this skimmer has been active for 50 days (or 15 days in case of\r\nthe EuroTickets site). The skimmer was taken down from both OlympicTickets and EuroTickets during the writing\r\nof this article, making the mentioned days the total amount of days that the skimmer was present.\r\nAs one can see in this copy in the Wayback Machine, the website’s layout is partially broken due to the lack of the\r\njQuery library. It is still referenced in the code, but it does not exist on the server right now.\r\nThe next step was to contact the site’s owners with our findings in a responsible manner.\r\nResponsible disclosure\r\nBefore posting these details online, one should contact the company to resolve the problem at hand. This also\r\ngives them some time to issue a statement to their customers. On the other hand, you cannot wait for an extended\r\nperiod of time if the company does not respond (or does not acknowledge the problem), as this puts the customers\r\nin danger longer than need be.\r\nDirectly after our findings, we e-mailed them via the provided e-mail address, we Tweeted to both OlympicTickets\r\nand EuroTickets to inform them of the skimmer.\r\nSince there was no response on any of these during the weekend, I contacted them on Monday morning via their\r\nlive chat support system. The first contact was not followed up upon after leaving my phone number. The second\r\ncontact via the live chat provided us with the information that the security team could not find anything, after\r\nwhich the case was closed. Jacob gave them a call with the request to look into it again.\r\nThe day after, I contacted them again via the live chat system. Despite our instructions, the security team could not\r\nfind the skimmer. This again lead to the closure of the ticket. During that evening, the script got removed from the\r\nsite.\r\nConclusion\r\nhttps://maxkersten.nl/2020/01/20/ticket-resellers-infected-with-a-credit-card-skimmer/\r\nPage 5 of 6\n\nIf you have shopped at either olympictickets2020.com or eurotickets2020.com between the third of December\r\n2019 and the 21st of January 2020, your credit card credentials are likely to be compromised. Please request a new\r\ncredit card and contact your bank accordingly. Also note that all information that was entered on the site’s\r\npayment form was stolen by the credit card skimmer and should be considered compromised.\r\nI’d like to thank Jacob for the splendid cooperation during our work on this case. Even though we live in different\r\ntime zones, as well as the fact that we only conversed via chat, we worked on the case simultaneously and in\r\nparallel where possible.\r\nTo contact me, you can e-mail me at [info][at][maxkersten][dot][nl], or DM me on BlueSky @maxkersten.nl.\r\nSource: https://maxkersten.nl/2020/01/20/ticket-resellers-infected-with-a-credit-card-skimmer/\r\nhttps://maxkersten.nl/2020/01/20/ticket-resellers-infected-with-a-credit-card-skimmer/\r\nPage 6 of 6",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://maxkersten.nl/2020/01/20/ticket-resellers-infected-with-a-credit-card-skimmer/"
	],
	"report_names": [
		"ticket-resellers-infected-with-a-credit-card-skimmer"
	],
	"threat_actors": [
		{
			"id": "d90307b6-14a9-4d0b-9156-89e453d6eb13",
			"created_at": "2022-10-25T16:07:23.773944Z",
			"updated_at": "2026-04-10T02:00:04.746188Z",
			"deleted_at": null,
			"main_name": "Lead",
			"aliases": [
				"Casper",
				"TG-3279"
			],
			"source_name": "ETDA:Lead",
			"tools": [
				"Agentemis",
				"BleDoor",
				"Cobalt Strike",
				"CobaltStrike",
				"RbDoor",
				"RibDoor",
				"Winnti",
				"cobeacon"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "5a0483f5-09b3-4673-bb5a-56d41eaf91ed",
			"created_at": "2023-01-06T13:46:38.814104Z",
			"updated_at": "2026-04-10T02:00:03.110104Z",
			"deleted_at": null,
			"main_name": "MageCart",
			"aliases": [],
			"source_name": "MISPGALAXY:MageCart",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434132,
	"ts_updated_at": 1775826769,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/eeb279bf4ecacd5ec7e071d89a9de6e93a2b2e4b.pdf",
		"text": "https://archive.orkl.eu/eeb279bf4ecacd5ec7e071d89a9de6e93a2b2e4b.txt",
		"img": "https://archive.orkl.eu/eeb279bf4ecacd5ec7e071d89a9de6e93a2b2e4b.jpg"
	}
}