Analiza pe 3D Secure de la Banca Comerciala Romana (BCR) – eToken

Dupa analiza facuta pe ce ofera Banca Transilvania la capitolul 3D Secure protocol, acum a venit randul si pentru un raport indreptat spre ce ofera pe partea de 3D Secure Banca Comerciala Romana.

Cazul BT a fost restrans pana la pasul al doilea din acel protocol, dar imbunatarile au venit chiar in timp real si se poate spune ca acea analiza tehnica a avut un impact benefic pentru multa lume ce are layerul de securitate activ pe card atunci cand efectueaza plati online. Chiar daca aceste module de 3D secure nu sunt mentinute direct de catre banci ci in general sunt servicii oferite prin RomCard, asta nu inseamna ca banca e lipsita de vina in momentul in care apar fraude ce pacalesc acest nivel de securitate. Banca are directa responsabilitate sa se asigure constant de acel serviciu chiar daca il inmaneaza unei alte firme externe.

Ideea acestei analize este de a simula mai multe tipuri de atacuri pentru a vedea daca se poate trece peste validarea tranzactiei in aplicatia eToken.

Cum arata un screen de 3D Secure asociat cu BCR:

La prima vedere ce se poate observa la nivel functional sunt detaliile tranzactiei, suma, data, si ultimele 4 cifre ale cardului. Dac nu exista o parola setata deja, el se foloseste de un alt mecanism pentru a detecta statusul unei tranzactii.

La nivel tehnic avem asa la intrarea in portal:

Un prim POST catre:

https://www.secure11gw.ro/portal/cgi-bin/ccprocess.php

Cu un payload:

CARD: xxxx xxxx xxxx xxxx
CardLength: 1xx
EXP: xx
EXP_YEAR: xx
CVC2: xxx
CVC2_RC: 1
display_suma: xxxx
AMOUNT: xxxxx
CURRENCY: RON
MERCH_NAME: XISOFT Servicii
RCDID: xxxx
ORDER: xxxx
secunde: 27
TERMINAL: 60000631
TIMESTAMP: xxxx
FORM_ID: 4177589598d447675e6e68337cf7e1c21ea9ec9a

Un GET catre:

wss://www.secure5gw.ro/way4acs/ws

Care deschide o conexiune de Web Socket prin care se comunica cu aplicatia mobila(partea de aprobare a tranzactiei in timp real)

var socket = null;
		function connect() {
			socket = new WebSocket("wss://www.secure5gw.ro:443/way4acs/ws");

			socket.onopen    = function(evt) { socket.send("D93BEA4F42F75159F3D74EEC1066EAE9"); setTimeout(heartbeat, 10000); };
			socket.onclose   = function(evt) { socket.send("D93BEA4F42F75159F3D74EEC1066EAE9"); setTimeout(showFormButton, 3000, "btnSubmitItem"); };
			socket.onerror   = function(evt) { socket.send("D93BEA4F42F75159F3D74EEC1066EAE9"); setTimeout(showFormButton, 3000, "btnSubmitItem"); };
			socket.onmessage = function(evt) { onMessage(evt); };
		}

Ce primeste un eveniment definit de mai multe tipuri:

function onMessage(evt) {
		    if ("D93BEA4F42F75159F3D74EEC1066EAE9" == evt.data) {
		        return true;
        	} else if ("OW_HEART_BEAT" == evt.data) {
                setTimeout(heartbeat, 10000);
                return true;
            } else if ("OK" == evt.data) {				    // Behaviour for online authorisation
				isCheckUnload = false;
				document.frm.submit();
				return true;
			} else if ("ERROR_FALLBACK" == evt.data) {     // Go to fallback
			    OnBackToAuthList("btnFallBack", "pa.fallback");
			    return true;
			}

			var obj = JSON.parse(evt.data);
			if (obj.Type == "QRMethod") {		// Json request type for QR method
				document.getElementById("QRItem").style.display = "block";
				document.getElementById("btnSubmitItem").style.display = "block";
				document.getElementById('QRData').setAttribute('src', "data:image/png;base64," + obj.QRData);
			} else {
				alert("Not defined type: " + obj.Type);
				document.frm.submit();
			}
		}

Acea cheie “D93BEA4F42F75159F3D74EEC1066EAE9” nu e pusa la intamplare, ea se ascunde in cadrul paginii generate la intrarea in portal, diferita de fiecare data:

<input type="hidden" name="jsessionid" value="D93BEA4F42F75159F3D74EEC1066EAE9"/></form>

Ce se poate observa din functia onMessage e ca pe ramura

if ("OW_HEART_BEAT" == evt.data) 

se face un apel spre functia setTimout

var timeout = (dateResult*1000)+1500;
        // var timeout = 3000;
	console.log("Diferenta intre2: "+timeout);
	setTimeout(function(){
		//OnBackToAuthList("btnBack", "pa.back");
		alert("Operatiunea a expirat, reia plata."); 
	}, timeout);

OW_HEART_BEAT se transmite pe websocket connection la fiecare heartbeat al conexiunii, cam la 10secunde

function heartbeat() {
            if (!socket) return;
            if (socket.readyState !== 1) return;
            socket.send("OW_HEART_BEAT");
        }

Dar ideea in partea de cod de mai sus e ca intr-un mod dubios OnBackAuthList apare comentat, structura functiei fiind:

function OnBackToAuthList(elementId, value) {
            isCheckUnload = false;
            removeElement(document.getElementById("psw_id"));
            var btnBack = document.getElementById(elementId);
            if (btnBack) btnBack.disabled = true;
            document.frm.formaction.value = value;
            if (socket) socket.close();
            document.frm.submit();
            return true;
        }

Care se mai apeleaza pe ramura

} else if ("ERROR_FALLBACK" == evt.data) {     // Go to fallback
			    OnBackToAuthList("btnFallBack", "pa.fallback");
			    return true;
			}

din functia onMessage pentru evenimente pe websocket de tip ERROR_FALLBACK

Deschiderea altor conexiuni de Web Socket si trimiterea mesajelor forjate prin web socket

Prima ideea care reiese in ultima implementarii e sa daca printr-o modalitate s-ar putea redeschide o conexiune de websocket ce sa declanseze resetarea sesiunii si implicit creearea unei noi sesiuni pentru validarea in aplicatia 3d Token la “Tranzactii in asteptare”

S-a observat ca in momentul in care se redeschide o noua conexiune de websocket prin:

socket = new WebSocket("wss://www.secure5gw.ro:443/way4acs/ws");

Se apeleaza functia onLoad() de fiecare data:

function OnLoad() {
			var loginTries = 0;
            var desc = document.getElementById("desc");
            if (desc) {
				if (loginTries > 0) {
					if ("5".length != 8)
						 desc.innerHTML = "<p>Ai introdus o parola gresita. Te rugam sa reincerci.<br>Incercari ramase: 5</p>";
					else desc.innerHTML = "<p>You submitted a wrong password. Please try again.</p>";
				} else {
				//	desc2 = "For added security, you will be authenticated with your application. This information is not shared with the merchant.";
				//	desc.className = "desc";
				//	desc.innerHTML = "<p>" + desc2 + "</p>";
				}
            }

            if ("%DISPLAY_TEXT%".length != 14) {
                var addInfo = document.getElementById("addInfo");
                if (addInfo) {
                    addInfo.className = "desc";
                    addInfo.innerHTML = "<p>pa_oob_desc3</p>";
                }
            }

            var qrCode = "%QRCODE%";
            var qrData = "%QRData%";
            if (qrCode.length != 8) {
                showQrData(qrCode);
            } else if("" === "pa.submit" && qrData.length != 8) {
				showQrData(qrData);
			} else {
				setTimeout(connect, 0);
				setTimeout(showFormButton, 0, "btnFallBack");
			}
        }

Iar la fiecare resetare de counter se intra pe ultimul else branch:

else {
				setTimeout(connect, 0);
				setTimeout(showFormButton, 0, "btnFallBack");
			}

functia setTimeout a fost explicata mai sus unde era comentata linia //OnBackToAuthList(“btnBack”, “pa.back”);

setTimeout(function(){
		//OnBackToAuthList("btnBack", "pa.back");
		alert("Operatiunea a expirat, reia plata."); 
	}, timeout);

insa aici in acest apel al functiei setTimeout ce este important e ca se face si un callback spre functia connect unde se deschide conexiunea de Web Socket

Deci intr-un mod logic ne gandim ca dupa ce “expira” timpul definit undeva la 10minute per sesiune activa, ar trebui sa intre pe setTimeout, eventual sa execute si OnBackToAuthList care ar si inchide sesiune de websocket activa. sa faca un form submit fortat si sa iasa din pagina cu redirect spre locul de unde s-a incercat plata.

S-a mai observat adaugarea unui buton btSubmit cu apel pe OnSubmit care nu are absolut nicio legatura in aceasta pagina, dar folosit in metodele OnSubmit, OnExit

<button class="acs-btn submit" id="btnSubmit" name="btSubmit" value="Confirma" onclick="return OnSubmit(document.frm, true);">Confirma</button>
            <td><div class='language-ro'><form method=POST class=lang action='https://www.secure5gw.ro/way4acs/challengedispatcher' onsubmit='isCheckUnload=false;return true;'><input type=hidden name=language value=ro><input type=hidden name=formaction value=reload><button class='language-ro' style='background:transparent;border: 1px solid#dadada;display: block;align-items: center;padding: .375rem .5rem;font-size: .75rem;'><a><input type=image  style='width: 14px;margin-right: 6px;' src='/BCRv245test/assets/img/lang-ro.svg'>RO</a></button><input type="hidden" name="jsessionid" value="39B6F5AA6A4A7A05F6F0950296995062"/></form></div></td><td><div class='language-en'><form method=POST class=lang action='https://www.secure5gw.ro/way4acs/challengedispatcher' onsubmit='isCheckUnload=false;return true;'><input type=hidden name=language value=en><input type=hidden name=formaction value=reload><button class='language-en' style='background:transparent;border: 1px solid#dadada;display: block;align-items: center;padding: .375rem .5rem;font-size: .75rem;'><a><input type=image  style='width: 14px;margin-right: 6px;' src='/BCRv245test/assets/img/lang-en.svg'>EN</a></button><input type="hidden" name="jsessionid" value="39B6F5AA6A4A7A05F6F0950296995062"/></form></div></td>
            <form method="POST" ONSUBMIT="return OnSubmit(this, false);" name="frm" accept-charset="utf-8" action="https://www.secure5gw.ro/way4acs/challengedispatcher">
function OnSubmit(f, doSubmit) {
            isCheckUnload = false;
            document.frm.formaction.value = "pa.submit";
            document.getElementById("btnSubmit").disabled = true;
            if (doSubmit) document.frm.submit();
            return true;
        }

Chestia foarte interesanta descoperita pe parcursul raportului e ca acea clasa cu btSubmit apare doar in momentul incare se incearca diferite mesaje pe conexiunea de Web Socket. Pentru ca evenimentul transmis pe websocket intra pe ramura de else din acest bloc, fiind netratat:

var obj = JSON.parse(evt.data);
			if (obj.Type == "QRMethod") {		// Json request type for QR method
				document.getElementById("QRItem").style.display = "block";
				document.getElementById("btnSubmitItem").style.display = "block";
				document.getElementById('QRData').setAttribute('src', "data:image/png;base64," + obj.QRData);
			} else {
				alert("Not defined type: " + obj.Type);
				document.frm.submit();
			}

A doua metoda de a forja conexiunea de Web Socket cu mesaje tratate

In momentul in care o tranzactie este validata prin 3d Token pe websocket se transmite un mesaj de tip “OK” care intra ca eveniment pe functia onMessage()

} else if ("OK" == evt.data) {				    // Behaviour for online authorisation
				isCheckUnload = false;
				document.frm.submit();
				return true;

Au fost incercate diferite modalitati de a fi transmis prin clientul de web socket un mesaj forjat de acel tip pentru a se observa daca el valideaza in vreun fel tranzactia doar prin nivelul de client, insa in momentul in care mesajul intra ca eveniment, se intra pe branch-ul de else mai sus mentionat insa:

Dupa intrarea pe acel document.frm.submit() se reapeleaza OnLoad() la intrarea in aceeasi pagina unde connect() instantiaza o alta conexiune de websocket prin:

} else {
setTimeout(connect, 0);

//din OnLoad()

Prin acest tip de atac se reueste un comportament extrem de asemanator cu admiterea unei tranzactii valide printr-un bypass la 3D Token, insa in acest caz se face un POST cu:

https://www.secure5gw.ro/way4acs/challengedispatcher

Cu un payload asemanator:

PASSWORD: @f9Q
PASSWORD: 
formaction: pa.submit
jsessionid: 93DFF8AFDC937163553D323623A0CEE7

Avem mesajul pe websocket, se trateaza, se trimite si request-ul cu sessionId-ul catre server, insa pe partea de server e ceva ce blocheaza si ii raspunde la request tot un pagina de la starea initiala. *Atata timp cand timeout-ul definit pe server nu e depasit. Cand se depaseste acel sessionId nu mai apare ca fiind interpretabil. Cel mai probabil asa se face autorizarea, parola e aceeasi la fiecare request, insa sessionId e generat la initializarea paginii si transmis spre validare.

Pentru ca acest raspuns pe /challengedispatcher nu vine ca in cazul unei tranzactii acceptate intr-un flow normal

<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Return to Merchant's site</title>
<SCRIPT>
                    function onLoadHandler() { document["CResForm"].submit(); }
                </SCRIPT>
</head>
<body onLoad="onLoadHandler();">
<BR>
<BR>Processing...

                <FORM NAME="CResForm" METHOD="post" ACTION="https://3ds.payten.com.tr/mdpaympi/MerchantServer/msgid/165234788">
<INPUT NAME="notificationURL" TYPE="hidden" VALUE="https://3ds.payten.com.tr/mdpaympi/MerchantServer/msgid/165234788"><INPUT NAME="cres" TYPE="hidden" VALUE="eyJtZXNzYWdlVHlwZSI6IkNSZXMiLCJtZXNzYWdlVmVyc2lvbiI6IjIuMS4wIiwidGhyZWVEU1NlcnZlclRyYW5zSUQiOiJhMTFmZmRiMi0xNDM0LTU5ZjYtODAwMC0wMDAwMDlkOTQ4NjQiLCJhY3NUcmFuc0lEIjoiNmMxY2I1MGYtY2E1Ny00ZGRjLTk3MmMtMmUzZWRmZTk3NTVmIiwiY2hhbGxlbmdlQ29tcGxldGlvbkluZCI6IlkiLCJ0cmFuc1N0YXR1cyI6IlkifQ"><INPUT NAME="threeDSSessionData" TYPE="hidden" VALUE="cDJEQVlxQ2tJZ0JFNUsvcGc2ZXRkOUtkK0luRVlETUZNbzNJM3ZjQXRldWNYdno2SW45clRLbCtCRnQrUlNCOWliWGtHWXVEVWJ1OG1IMWg3TVNSSE5CMVozUFRsRUQxeGE0elpCdURJbHAvZC9RaWVUM1gwdHM0QVg4cFJBTGV2NHdrNXJIbXdTaUZDdC9tRFpVNThBPT0">
</FORM>
</body>
</html>

Ce ar provoca un POST pe endpointul /brw/challenge?id=trans cu acea cheie de pe threeDSSessionData

creq: ewogICAiYWNzVHJhbnNJRCIgOiAiNmMxY2I1MGYtY2E1Ny00ZGRjLTk3MmMtMmUzZWRmZTk3NTVmIiwKICAgImNoYWxsZW5nZVdpbmRvd1NpemUiIDogIjAzIiwKICAgIm1lc3NhZ2VUeXBlIiA6ICJDUmVxIiwKICAgIm1lc3NhZ2VWZXJzaW9uIiA6ICIyLjEuMCIsCiAgICJ0aHJlZURTU2VydmVyVHJhbnNJRCIgOiAiYTExZmZkYjItMTQzNC01OWY2LTgwMDAtMDAwMDA5ZDk0ODY0Igp9
threeDSSessionData: cDJEQVlxQ2tJZ0JFNUsvcGc2ZXRkOUtkK0luRVlETUZNbzNJM3ZjQXRldWNYdno2SW45clRLbCtCRnQrUlNCOWliWGtHWXVEVWJ1OG1IMWg3TVNSSE5CMVozUFRsRUQxeGE0elpCdURJbHAvZC9RaWVUM1gwdHM0QVg4cFJBTGV2NHdrNXJIbXdTaUZDdC9tRFpVNThBPT0
TermUrl: https://3ds.payten.com.tr/mdpaympi/MerchantServer/msgid/165234788

Mentinerea conexiunii intre client si eToken – timeout bypass

Un al treilea tip de atac a fost incercarea mentinerii conexiunii intre client si aplicatia eToken unde sunt tinute tranzactiile ce urmeaza a fi validate/invalidate intr-un un anumit interval de timp.

Ceea ce a fost observat in acest tip de atac este ca conexiunea de client nu are nici o legatura cu cea de server, nu sunt legate la nivel de inchidere de sesiune. Sesiunea de client se poate incheia inainte de acel timeout setat la nivel de server. Iar chiar daca e incheiata ea inca e mentinuta pe partea de server pana acel timeout e consumat in totalitate.

Acest atac a fost bazat pe lucrurile gasite mai sus si in principal pe faptul ca la fiecare intrare in portal functia OnLoad() se executa ce deschide si o alta conexiune de Web Socket. Avand in vedere faptul ca am descoperit ca prin transmiterea unor mesaje forjate prin Web Socket se poate reinitializa conexiunea la fiecare mesaj exista o posibilitate ca si conexiunea de pe server sa fie initializata din nou.

Inchiderea fortata a conexiunii dintre client si eToken

Acest aspect face parte din categoria cu cele mai importante lucruri gasite si se bazeaza pe ceea ce e explicat in paragrafele de mai sus.

Stiind faptul ca tot mecanismul e bazat pe o conexiune de web socket inseamna ca orice atac care ar surveni inseamna sa taie conexiunea pentru ca detinatorul cardului sa nu isi poata vedea tranzactia in lista din eToken si mai ales sa nu mai poata face nicio modificare pe ea, adica sa o refuze.

Am adus la cunostinta mai sus cum se initializeaza acea conexiune de fiecare data cand se intra in pagina de 3D Secure si foloseste un heartbeat la fiecare 10 secunde ca un ping pong intre client si server. Insa tot mai sus am amintit cum ca partea de 3D Secure din client nu foloseste aceeasi logica de timeout, deci inseamna ca cele doua fronturi s-ar putea desprinde unul de celalalt la nivel de conexiune.

Folosind informatia din metoda connect():

socket = new WebSocket("wss://www.secure5gw.ro:443/way4acs/ws");

			socket.onopen    = function(evt) { socket.send("A9C276D2226A52F863011D8A7B488C28"); setTimeout(heartbeat, 10000); };
			socket.onclose   = function(evt) { socket.send("A9C276D2226A52F863011D8A7B488C28"); setTimeout(showFormButton, 3000, "btnSubmitItem"); };
			socket.onerror   = function(evt) { socket.send("A9C276D2226A52F863011D8A7B488C28"); setTimeout(showFormButton, 3000, "btnSubmitItem"); };
			socket.onmessage = function(evt) { onMessage(evt); };

Si de faptul ca de fiecare data se apeleaza OnLoad() cu apel pe connect() unde se instantiaza o noua conexiune, e destul de usor sa ne gandim cum se poate inchide aceasta conexiune fara un alt apel de OnLoad() si implicit de connect().

Odata inchisa conexiunea, partea de eToken ramane in aer cu acea tranzactie inca activa in lista, dar nicio functionalitate nu mai ajunge la client. Refuzul acelei tranzactii se poate face, ea e confirmata in aplicatie, insa atacatorul ramane in acelasi ecran avand sansa sa pivoteze inspre un alt atac linistit.

Aici e si una dintre cele mai mari probleme gasite. Ideea cum ca se poate inchide fortat conexiunea de web socket si daca cineva realizeaza ca asa taie macaroana spre eToken ii da sansa sa pacaleasca detinatorul cardului legat de ce se intampla cu adevarat mai departe.

Propunerea pentru a imbunatati securitatea in acest caz este ca pentru fiecare eveniment de:

socket.onclose()

sa se apeleze OnLoad() cu apel de connect() unde e tratat acel onclose() pe ramura:

socket.onclose   = function(evt) { socket.send("A9C276D2226A52F863011D8A7B488C28"); setTimeout(showFormButton, 3000, "btnSubmitItem"); };

astfel in nicio circumstanta conexiune dintre client si eToken nu se poate pierde. Si cand conexiunea se pierde clientul sa fie fortat sa invalideze tranzactia.

Procesarea unei tranzactii fara a folosi codul OTP

Acesasta ramura a fost lasata spre sfarsit, insa e cel mai important lucru gasit. A fost observat faptul ca unele pagini ce ar trebui sa trateze mesajul primit din gateway-ul de 3D Secure il proceseaza total gresit. De exemplu pentru un POST pe /way4acs/challengedispatcher cu:

PASSWORD: 
NEWPASSWORD: 
CONFIRM: 
formaction: pa.exit
jsessionid: C1D1B381AE741D5EA6C71D3D5A6F2378

El primeste la procesare un atribut de “Approved”:

TERMINAL: xxxxxxx
TRTYPE: xx
ORDER: xxxxxx
AMOUNT: xxxxx
CURRENCY: xxxx
DESC: xxx
ACTION: xxx
RC: xx
MESSAGE: Approved
RRN: xxx
INT_REF: xxxx
APPROVAL: xxxx
TIMESTAMP: xxxx
NONCE: xxxx
P_SIGN: xxxxx

Acest comportament a fost observat pe cateva pagini unde implementarea dintre pagina de 3D secure si procesatorul de plati nu raspunde corect in momentul in care se transmite un exit fortat. E un breach foarte important care momentan nu poate fi descris.

Practic in aceste cazuri toata logica bazata pe request/response prin Web Socket si toate masurile de siguranta gandite in cadrul gateway-ului de 3D Secure sunt degeaba. Dar aici vina se pune pe cel ce proceseaza plata care implicit pune o bulina si la banca asociata cardului ca nu isi urecheaza toti procesatorii care au implementate modalitatile de plata online.

IESIREA FORTATA DIN PORTAL

Tot pe ideea de mai sus insa cu niste pasi care nu se pot explica la acest moment, s-a observat ca la iesirea fortata din pagina prin functia OnExit() payloadul trimis spre /challengedispatcher apare cu proprietatea de formaction: pa.exit

PASSWORD: @f9Q
PASSWORD: 
formaction: pa.exit
jsessionid: 93DFF8AFDC937163553D323623A0CEE7

Pentru o tranzactie neacceptata din eToken, se transmite un eveniment asemanator:

evt: MessageEvent
bubbles: false
cancelBubble: false
cancelable: false
composed: false
currentTarget: WebSocket {url: "wss://www.secure5gw.ro/way4acs/ws", readyState: 1, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}
data: "OK"
defaultPrevented: false
eventPhase: 2
isTrusted: true
lastEventId: ""
origin: "wss://www.secure5gw.ro"
path: []
ports: []
returnValue: true
source: null
srcElement: WebSocket {url: "wss://www.secure5gw.ro/way4acs/ws", readyState: 1, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}
target: WebSocket {url: "wss://www.secure5gw.ro/way4acs/ws", readyState: 1, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}
timeStamp: 92688.30000001192
type: "message"

Insa cu un payload diferit trimis pe challengedispatcher endpoint unde parola nu e transmisa:

PASSWORD: 
PASSWORD: 
formaction: pa.submit
jsessionid: E203A92168DF934F10CFA2223F6167C3

*Ce se intampla descris pe aceasta ramura se intampla in majoritatea procesatoarelor de tranzactii. Insa au fost descoperite cazuri in care pentru un exit fortat tranzactia se valideaza. In zona explicata la “Procesarea unei tranzactii fara a folosi codul OTP“.

Comportamentul unei tranzactii acceptate prin eToken

Dupa toate aceste incercari prezentate mai sus, avem aici si analiza unei tranzactii pe un flow normal, validata prin eToken, la care se trimite pe web socket un mesaj “OK” si se intra pe functia onMessage() cu un eveniment de forma:

evt: MessageEvent
bubbles: false
cancelBubble: false
cancelable: false
composed: false
currentTarget: WebSocket {url: "wss://www.secure5gw.ro/way4acs/ws", readyState: 1, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}
data: "OK"
defaultPrevented: false
eventPhase: 2
isTrusted: true
lastEventId: ""
origin: "wss://www.secure5gw.ro"
path: []
ports: []
returnValue: true
source: null
srcElement: WebSocket {url: "wss://www.secure5gw.ro/way4acs/ws", readyState: 1, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}
target: WebSocket {url: "wss://www.secure5gw.ro/way4acs/ws", readyState: 1, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}
timeStamp: 109859.40000003576
type: "message"
userActivation: null

Cu un POST pe API:

https://www.secure5gw.ro/way4acs/challengedispatcher
PASSWORD: xxxx
PASSWORD: 
formaction: pa.submit
jsessionid: xxxx

Ce are ca response data on apelare pe functia onLoadHandler() si cu:

<INPUT NAME="notificationURL" TYPE="hidden" VALUE="https://www.secure7gw.ro/tdsv2/notify/"><INPUT NAME="cres" TYPE="hidden" VALUE="eyJtZXNzYWdlVHlwZSI6IkNSZXMiLCJtZXNzYWdlVmVyc2lvbiI6IjIuMS4wIiwidGhyZWVEU1NlcnZlclRyYW5zSUQiOiIyN2IxMzQ1Yi1lOTY5LTQ5M2YtOTMzNS1mYzZkNmU0MTQzMTciLCJhY3NUcmFuc0lEIjoiZmM3ODgxZDUtOWMxMi00NWU0LTllODQtM2UzZTJlNmMwZWFmIiwiY2hhbGxlbmdlQ29tcGxldGlvbkluZCI6IlkiLCJ0cmFuc1N0YXR1cyI6IlkifQ"><INPUT NAME="threeDSSessionData" TYPE="hidden" VALUE="MTFiNzAyMGZlYzAzY2Q1MTY5ODUzMzZmMWIzNzNmNjE5NDM0MWZhMzFhMmIyOTU3MzNlMjMyMjI5YjFlMWQyZjc3T1RjMU5qVXlNak10T0VKQlJrRTJNVFpCTVRFeU1VWkZSUXxodHRwczovL3d3dy5zZWN1cmUxMWd3LnJvL3BvcnRhbC9jZ2ktYmluL3JlcGx5LnBocD90dHlwZT1pczE3MXxodHRwczovLzE3Mi4yMC4xLjE3MS9jZ2ktYmluL2NnaV9saW5r">

Ce nu e altceva decat un JWT:

{
  "messageType": "CRes",
  "messageVersion": "2.1.0",
  "threeDSServerTransID": "27b1345b-e969-493f-9335-fc6d6e414317",
  "acsTransID": "fc7881d5-9c12-45e4-9e84-3e3e2e6c0eaf",
  "challengeCompletionInd": "Y",
  "transStatus": "Y"
}

Concluzii:

Prima problema majora e ca au fost descoperite cazuri in care procesarea unei plati care a fost invalidata in portalul 3D secure e procesata ca valabila si pe server cu toate ca aceasta nu a fost confirmata prin eToken. E un mod inacceptabil ca lucrurile sa nu fie invalidate si in procesator dupa ce au fost invalidate prin 3D Secure.

A doua dintre problemele considerate majore e ca accesul la a inchide conexiunea de web socket dintre eToken si client ar trebui cumva tratat prin ceea ce s-a explicat pe parcursul acestui raport. Orice atac ulterior ar fi bazat cu siguranta pe acest aspect.

S-a mai observat in urma acestui raport ca tranzactiile tinute in partea de 3D Token nu au legatura cu ce se intampla in partea de client. Timeout spre exemplu, in partea de client e total diferit fata de ceea ce se intampla in 3d Token. O sincronizare intre cele doua este extrem de necesara.

Un alt lucru absolut necesar este sa nu se mai accepte o resetare a timeout-ului prin transmiterea unor mesaje prin conexiunea de web socket. Si chiar adoptarea unei alte logici pe partea de client.

Raportul a fost incheiat dupa cateva saptamani deoarece devenea din ce in ce mai greu de mentinut deoarece modificarile erau de pe o zi pe alta, fara ca cineva sa le raporteze direct catre ei.Dar, din nou, ca si in cazul raportului efectuat pe Banca Transilvania, detinatorul cardului nu a fost sesizat ca ceva dubios se intampla cu cardul lui. Probabil va fi extins in urmatoarea perioada si in directia modificarilor ce au fost aduse.

In cadrul concluziilor, o chestiune finala si absolut dezamagitoare este faptul ca detinatorul cardului nu a fost anuntat in nici un moment pe parcursul acestui raport ca exista o activitate extrem de suspecta pe acel card. Asteptarea a fost ca cineva sa ia legatura cu detinatorul si sa il intrebe daca stie de acea activitate, iar la un raspuns negativ sa se ia imediat masuri pe acel card. Exista urme pe acel sistem care pot fi folosite si o pista se poate alcatui la o analiza atenta cu ce anume se doreste in urma activitatii pentru ca acele urme nu vor indica niciodata o bunica ce apasa aiurea pe butoane. Acest aspect va fi impins in cadrul unor discutii directe deoarece este unul absolut esential, chiar si inainte de a face orice imbunatatire la nivel de gateway.

*In acest raport s-au adunat informatii care nu dezvaluie vreo bresa importanta in sistem, orice problema majora a fost fixata in timpul acestei analize. Scopul nu e de a denigra modul in care s-a facut aceasta logica pentru ceea ce ofera BCR, ci pur si simplu informativ pentru a vedea pana unde se poate merge in cazul unui atac real si pentru a se urmari timpii de reactie in cazul unor activitati suspecte.

*Toate informatiile prezentate au fost atent selectionate astfel incat prin ele sa nu se poata dezvolta un atac real. Insa orice mutare care reiese din aceste lucruri trebuie sa fie asumata pe deplin, la fel si consecintele ce ar putea aparea in urma acestora. Orice legatura cu aceste informatii prezentate se scoate din discutia unui viitor atac cu prejudicii reale.

**Cum analiza nu a fost una planuita/orchestrata ori finantata de catre o speta competitoare, raufacatoare, nici chiar de Banca Comerciala Romana, si nici nu s-au urmarit brese ce sa aduca prejudicii, este bine ca lucrurile au fost revizuite de catre ei(BCR si Romcard) intr-un timp relativ bun si ca acest raport a adus multe plusuri din punct de vedere al securitatii imbunatatite pentru toti clientii Bancii Comerciale Romane ce efectueaza tranzactii online.

Leave a Reply