Transbank Webpay < 1.14.0 - Unauthenticated Stored Cross-Site Scripting
Description
The Transbank Webpay plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to 1.14.0 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<1.14.0What Changed in the Fix
Changes introduced in v1.14.0
Source Code
WordPress.org SVNThis research plan targets **CVE-2026-6858**, a high-severity unauthenticated stored Cross-Site Scripting (XSS) vulnerability in the Transbank Webpay plugin for WordPress. ### 1. Vulnerability Summary The vulnerability exists in the plugin's logging mechanism. The plugin captures unauthenticated pa…
Show full research plan
This research plan targets CVE-2026-6858, a high-severity unauthenticated stored Cross-Site Scripting (XSS) vulnerability in the Transbank Webpay plugin for WordPress.
1. Vulnerability Summary
The vulnerability exists in the plugin's logging mechanism. The plugin captures unauthenticated payment gateway callbacks and logs the entire request payload (GET/POST parameters) to a file. When an administrator views these logs in the WordPress dashboard, the plugin renders the log contents in a template without applying any HTML sanitization or output escaping. This allows an attacker to inject malicious scripts into the log file via a specially crafted HTTP request, which will then execute in the context of the administrator's session.
2. Attack Vector Analysis
- Vulnerable Endpoint: Any endpoint that triggers the
CommitWebpayController::process()orFinishOneclickController::process()methods. - Primary Target: The WooCommerce API callback endpoint:
/?wc-api=WC_Gateway_Transbank_Webpay_Plus_Rest(inferred action name). - Vulnerable Parameter: Any parameter sent in the GET or POST request to this endpoint (e.g.,
token_ws,TBK_TOKEN, or arbitrary custom parameters). - Authentication: No authentication is required to send data to the callback endpoint.
- Preconditions: The Transbank Webpay plugin must be active and WooCommerce must be installed.
3. Code Flow
- Entry Point: An unauthenticated attacker sends a request to the WooCommerce API callback handled by the plugin.
- Trigger: The request is routed to
Transbank\WooCommerce\WebpayRest\Controllers\CommitWebpayController::process(). - Source (Logging): Inside
process(), the controller retrieves the request method and payload:// CommitWebpayController.php lines 74-76 $requestMethod = $_SERVER['REQUEST_METHOD']; $request = $requestMethod === 'POST' ? $_POST : $_GET; // CommitWebpayController.php line 79 $this->log->logInfo("Request payload", $request); - Storage: The
PluginLogger::logInfo()method (inshared/Helpers/PluginLogger.php) uses Monolog to write this data to a.logfile inwp-content/uploads/transbank_webpay_plus_rest/logs/. The payload is JSON-encoded into the "context" part of the log line. - Sink (Rendering): An administrator navigates to the "Logs" tab in the plugin settings. This loads the
templates/admin/log.phptemplate. - Unescaped Output: The template reads the log file and iterates through the lines:
// templates/admin/log.php lines 105-114 $chunks = explode(' > ', $line); $message = $chunks[2] ?? null; // The log message + JSON context if (!is_null($date) && !is_null($level) && !is_null($message)) { $logContent .= '<pre class="log-line">' . $date . ' > ' . '<span class="log-' . strtolower($level) . '">' . $level . '</span> > ' . $message . '</pre>'; } // templates/admin/log.php line 118 echo $logContent; // XSS SINK: Unescaped output to the browser
4. Nonce Acquisition Strategy
The injection phase involves an unauthenticated callback intended for the Transbank payment gateway.
- Injection Endpoint:
CommitWebpayController::process()does not perform any nonce verification or authentication check before logging the request payload. Therefore, no nonce is needed to inject the malicious payload. - Admin View: Accessing the logs page requires administrative privileges. If the exploit requires a nonce for an admin-side CSRF chain, the agent should:
- Create a test page to trigger plugin script loading (if needed).
- Navigate to the Transbank settings:
/wp-admin/admin.php?page=transbank_webpay_plus_rest&tbk_tab=logs. - Extract the nonce from the
btnDownloadlogic if needed for further actions (though the primary goal is stored XSS execution).
5. Exploitation Strategy
The goal is to inject a script that will execute when the admin views the logs.
Step 1: Inject the XSS Payload
Send an HTTP POST request to the WooCommerce API callback endpoint.
- Tool:
http_request - URL:
{{BASE_URL}}/?wc-api=WC_Gateway_Transbank_Webpay_Plus_Rest - Method:
POST - Headers:
{"Content-Type": "application/x-www-form-urlencoded"} - Body:
token_ws=<script>alert(document.domain)</script>&TBK_TOKEN=dummy
Note: Monolog will JSON-encode the payload, but the template will echo the resulting string (e.g., {"token_ws":"<script>..."}) directly into the HTML, where the browser will parse the script tags.
Step 2: Trigger the XSS
As an administrator, navigate to the logs viewer.
- Tool:
browser_navigate - URL:
{{BASE_URL}}/wp-admin/admin.php?page=transbank_webpay_plus_rest&tbk_tab=logs
6. Test Data Setup
- Install & Activate: WooCommerce and the Transbank Webpay plugin (v1.13.0).
- Configure: Minimal configuration of Transbank is usually required for the callback routes to be active. Ensure logs are enabled (usually default).
- Admin User: Ensure an admin user exists to view the logs.
7. Expected Results
- The injection request should return a
200 OKor a500 Error(it doesn't matter, as logging occurs before the error). - The log file in the
uploadsdirectory should contain the literal string<script>alert(document.domain)</script>. - When the administrator visits the logs page, an alert box showing the document domain should appear.
8. Verification Steps
- Check Log File Content:
Confirm the payload is present in the output.wp eval 'echo file_get_contents(wp_upload_dir()["basedir"] . "/transbank_webpay_plus_rest/logs/" . get_transient("transbank_log_file_name"));' - Verify Admin Execution: Use
browser_evalon the logs page to check if the payload was rendered:// Check if any script contains our payload document.body.innerHTML.includes('<script>alert(document.domain)</script>')
9. Alternative Approaches
- Oneclick Controller: If
CommitWebpayControlleris disabled, try the Oneclick endpoint:{{BASE_URL}}/?wc-api=WC_Gateway_Transbank_Oneclick_Rest
Payload:TBK_TOKEN=<script>console.log("XSS")</script>&TBK_ID_SESION=123 - Log Injection via Headers: Since
Monologcan log HTTP headers in some configurations, try injecting into theUser-AgentorRefererheaders, though the controller explicitly logs the$requestarray.
Summary
The Transbank Webpay plugin for WordPress is vulnerable to unauthenticated Stored Cross-Site Scripting due to improper handling of request data in its logging mechanism. Attackers can inject malicious JavaScript into log files via payment gateway callback parameters, which is then executed in an administrator's browser when they view the logs in the plugin settings page.
Vulnerable Code
// src/Controllers/CommitWebpayController.php lines 74-79 $requestMethod = $_SERVER['REQUEST_METHOD']; $request = $requestMethod === 'POST' ? $_POST : $_GET; $this->log->logInfo('Procesando retorno desde formulario de Webpay.'); $this->log->logInfo("Request method: {$requestMethod}"); $this->log->logInfo("Request payload", $request); --- // templates/admin/log.php lines 107-123 foreach ($logLines as $line) { $chunks = explode(' > ', $line); $date = $chunks[0]; $level = $chunks[1] ?? null; $message = $chunks[2] ?? null; if (!is_null($date) && !is_null($level) && !is_null($message)) { $logContent .= '<pre class="log-line">' . $date . ' > ' . '<span class="log-' . strtolower($level) . '">' . $level . '</span> > ' . $message . '</pre>'; } } $logContent .= '</div>'; echo $logContent;
Security Fix
@@ -92,6 +92,38 @@ $this->logger->error($msg, $context); } + public static function sanitizeContextForLogs(array $context): array + {...} + + private static function sanitizeLogValue(mixed $value, int $depth = 0): mixed + { + if ($depth > self::MAX_LOG_DEPTH) { + return '[array too deep]'; + } + + $sanitizedValue = null; + + if (is_array($value)) { + $sanitizedValue = array_map(function ($nestedValue) use ($depth) { + return self::sanitizeLogValue($nestedValue, $depth + 1); + }, $value); + } elseif (is_null($value) || is_bool($value) || is_int($value) || is_float($value)) { + $sanitizedValue = $value; + } else { + $sanitizedValue = sanitize_text_field(wp_strip_all_tags((string) $value)); + } + + return $sanitizedValue; + } + public function getInfo() { $files = glob($this->config->getLogDir() . '/*.log'); @@ -55,12 +56,25 @@ public function process(): void { try { - $requestMethod = $_SERVER['REQUEST_METHOD']; - $request = $requestMethod === 'POST' ? $_POST : $_GET; + $requestMethod = RequestInputHelper::resolveRequestMethod($_SERVER); + $rawRequest = $requestMethod === 'POST' ? $_POST : $_GET; + $request = RequestInputHelper::sanitizeExpectedFields($rawRequest, [ + 'token_ws', + 'TBK_TOKEN', + 'TBK_ID_SESION', + 'TBK_ORDEN_COMPRA', + ]); + $webpayFlow = $this->getWebpayFlow($request); $this->log->logInfo('Procesando retorno desde formulario de Webpay.'); - $this->log->logInfo("Request method: {$requestMethod}"); - $this->log->logInfo("Request payload", $request); - $this->handleFormReturn($request); + $this->log->logInfo('Resumen de retorno de Webpay', PluginLogger::sanitizeContextForLogs([ + 'method' => $requestMethod, + 'flow' => $webpayFlow, + 'token_ws' => $request['token_ws'] ?? null, + 'TBK_TOKEN' => $request['TBK_TOKEN'] ?? null, + 'TBK_ID_SESION' => $request['TBK_ID_SESION'] ?? null, + 'TBK_ORDEN_COMPRA' => $request['TBK_ORDEN_COMPRA'] ?? null, + ])); + $this->handleFormReturn($request, $webpayFlow); } catch (\Exception | \Error $e) { $this->log->logError('Error en el proceso de validación de pago', ['error' => $e->getMessage()]); $this->setPaymentErrorPage(self::WEBPAY_EXCEPTION_FLOW_MESSAGE);
Exploit Outline
1. An unauthenticated attacker identifies the Transbank callback endpoint (e.g., `/?wc-api=WC_Gateway_Transbank_Webpay_Plus_Rest`). 2. The attacker sends a POST or GET request to this endpoint containing a parameter like `token_ws` or `TBK_TOKEN` populated with a malicious XSS payload (e.g., `<script>alert(document.domain)</script>`). 3. The `CommitWebpayController` (or `FinishOneclickController`) captures the raw request payload and passes it to the `PluginLogger` without sanitization. 4. Monolog writes the JSON-encoded payload into a `.log` file within the `uploads/transbank_webpay_plus_rest/logs/` directory. 5. An administrator navigates to the plugin settings and selects the "Logs" tab, which triggers `templates/admin/log.php` to render the log content. 6. Because the template echoes the log message directly without HTML escaping, the browser parses and executes the injected script.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.