CVE-2026-6858

Transbank Webpay < 1.14.0 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
1.14.0
Patched in
2d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<1.14.0
PublishedJune 22, 2026
Last updatedJune 23, 2026

What Changed in the Fix

Changes introduced in v1.14.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

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 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() or FinishOneclickController::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

  1. Entry Point: An unauthenticated attacker sends a request to the WooCommerce API callback handled by the plugin.
  2. Trigger: The request is routed to Transbank\WooCommerce\WebpayRest\Controllers\CommitWebpayController::process().
  3. 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);
    
  4. Storage: The PluginLogger::logInfo() method (in shared/Helpers/PluginLogger.php) uses Monolog to write this data to a .log file in wp-content/uploads/transbank_webpay_plus_rest/logs/. The payload is JSON-encoded into the "context" part of the log line.
  5. Sink (Rendering): An administrator navigates to the "Logs" tab in the plugin settings. This loads the templates/admin/log.php template.
  6. 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:
    1. Create a test page to trigger plugin script loading (if needed).
    2. Navigate to the Transbank settings: /wp-admin/admin.php?page=transbank_webpay_plus_rest&tbk_tab=logs.
    3. Extract the nonce from the btnDownload logic 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

  1. Install & Activate: WooCommerce and the Transbank Webpay plugin (v1.13.0).
  2. Configure: Minimal configuration of Transbank is usually required for the callback routes to be active. Ensure logs are enabled (usually default).
  3. Admin User: Ensure an admin user exists to view the logs.

7. Expected Results

  • The injection request should return a 200 OK or a 500 Error (it doesn't matter, as logging occurs before the error).
  • The log file in the uploads directory 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

  1. Check Log File Content:
    wp eval 'echo file_get_contents(wp_upload_dir()["basedir"] . "/transbank_webpay_plus_rest/logs/" . get_transient("transbank_log_file_name"));'
    
    Confirm the payload is present in the output.
  2. Verify Admin Execution: Use browser_eval on 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 CommitWebpayController is 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 Monolog can log HTTP headers in some configurations, try injecting into the User-Agent or Referer headers, though the controller explicitly logs the $request array.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/transbank-webpay-plus-rest/1.13.0/shared/Helpers/PluginLogger.php /home/deploy/wp-safety.org/data/plugin-versions/transbank-webpay-plus-rest/1.14.0/shared/Helpers/PluginLogger.php
--- /home/deploy/wp-safety.org/data/plugin-versions/transbank-webpay-plus-rest/1.13.0/shared/Helpers/PluginLogger.php	2026-03-31 15:17:56.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/transbank-webpay-plus-rest/1.14.0/shared/Helpers/PluginLogger.php	2026-04-28 14:28:36.000000000 +0000
@@ -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');
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/transbank-webpay-plus-rest/1.13.0/src/Controllers/CommitWebpayController.php /home/deploy/wp-safety.org/data/plugin-versions/transbank-webpay-plus-rest/1.14.0/src/Controllers/CommitWebpayController.php
--- /home/deploy/wp-safety.org/data/plugin-versions/transbank-webpay-plus-rest/1.13.0/src/Controllers/CommitWebpayController.php	2026-03-31 15:17:56.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/transbank-webpay-plus-rest/1.14.0/src/Controllers/CommitWebpayController.php	2026-04-28 14:28:36.000000000 +0000
@@ -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.