Skip to content

Contrast Labs: Google Sheets Stored XSS Vulnerability in COVID-19 Table

    

 

TLDR: On March 23, 2020, I found a publicly exposed and editable Google Sheets document that provided information to various NBC-owned local news stations. I reached out to the Data Visualization and Multimedia team at NBCUniversal Media, and they responded very quickly; the document was locked down by close of business the same day! Kudos to NBC for acting quickly and resolving this issue!

Governor Larry Hogan of Maryland issued an order for nonessential businesses in Maryland to close on March 23, 2020. With the current COVID-19 pandemic front and center, I decided to browse to my local NBC news website to check out the details.

maryland-orders-coronavirus-diagnoses

Anomalies in COVID-19 State of Maryland Table

As I was reading through the article, I found the table where they provided the number of known COVID-19 cases and deaths per county quite interesting. It provided useful information that was easy to follow. However, there was an odd null entry in one of the table cells:

covid-19-cases

I immediately thought this was a little strange. This piqued my software engineer mindset, and I sought to debug it to see if I could identify the details to lead me to a fix (or at least a valid number). I dug a little deeper by opening the Chrome developer tools and inspected the HTML page element for the corresponding table:

<p>Hogan has already urged residents to stay home and socially distance themselves to protect from the
spread of disease.</p>
<iframe id="myconmd" src="https://****************.html" width="100%" height="600"
frameborder="0"></iframe>
<h2 class="wp-block-nbc-section-heading">"Unprecedented Actions" to Fight Coronavirus</h2>

The inspector details led me to an HTML Inline Frame element (iframe) that referenced another document—a reference to a different HTML page:

<script type="text/javascript" src="js/miso.ds.deps.min.0.4.1.js"></script>
<script>
    var tabNums = [12];
    var tabNames = ["MD"];
    var lang = "eng";
</script>
<script src="*******************.js"></script>

Tracking the Source Vulnerability

After inspecting this document, I noticed yet another JavaScript file that was referenced as part of the HTML page. Surely this JavaScript was what was loading the information to create the table:

function loadData(which) {
//LOAD DATA WITH MISO
    ds = new Miso.Dataset({
        importer : Miso.Dataset.Importers.GoogleSpreadsheet,
        parser : Miso.Dataset.Parsers.GoogleSpreadsheet,
        key : "*******************************", //CHANGE TO YOUR KEY HERE
        worksheet : which
    });
    ds.fetch({
        success : function() {
//console.log("So say we all! Loading " + currentStateSp);
            parseData();
        },
        error : function() {
//console.log("What the frak?");
        }
    });
}

But something caught my attention in this file: I noticed a reference to a Google Sheets importer class. This was very interesting, specifically the key value. All Google documents have a unique key associated with them, and I began to wonder if the Google Sheets document corresponding to that key was accessible. One way to find out involved browsing to the document with that key ID, so I browsed to it:

google-sheets

Sure enough, the Google Sheets document was available, which made sense. However, in further examination of the document, I noticed that the document was “world editable.” This meant any internet user who stumbled on the same path, as I did, could edit the fields in the document at their will. I also discovered that the data was used by several NBC-owned local news networks. As you can see in the below screenshot, the properties of the file were set to world editable and also listed the owner of the document.

google-sheets-2

Assessing the Risk of the Google Sheet Vulnerability

This was concerning, as it appeared that the Google Sheets document could be accessed by anyone. Obviously, the first thought was that anyone could manipulate any of the numbers to create incorrect information and a false narrative.      

Yet, as a security software engineer, I immediately had a much different concern. In addition to modifying the numbers to misinform, a bad actor could enter and hide malicious JavaScript in a location or note cell by putting a lot of spaces between the “visible” text and the script and then formatting the columns to visually truncate the cell.

This malicious code could lead to a Stored Cross-Site Scripting (XSS) attack. Stored XSS is a particularly pernicious attack that could lead to defacement of the web application, redirection of users to a look-alike application, performing phishing attacks, and in some cases even installing malware or keyloggers on an end-user’s machine. The malicious JavaScript would be loaded and “honored” by the browser anytime the corresponding cell was rendered by the JavaScript used to render the HTML table:

function makeTable() { 
var theTableConent = "";
for (var i=1; i<totalEntries; i++) {
theTableConent += "<tr><td>" + allData[i][0].location + "</td><td>" + 
numberWithCommas(allData[i][0].cases) + "</td><td>" + numberWithCommas(allData[i][0].deaths) + "
</td><td>" + allData[i][0].notes + "</td></tr>";
}
if (lang =="esp") {
$("#theTable" + currentStateSp).html("<table class='table table-striped table-sm'><thead><tr>
<th>Ubicación</th><th>Casos</th><th>Muertes</th><th>Notas</th></tr></thead><tbody> " + 
theTableConent + "</tbody></table>");
} else {
$("#theTable" + currentStateSp).html("<table class='table table-striped table-sm'><thead><tr>
<th>Location</th><th>Cases</th><th>Deaths</th><th>Notes</th></tr></thead><tbody> " + 
theTableConent + "</tbody></table>");
}
$("#total" + currentStateSp).text(numberWithCommas(allData[0][0].totalcases));
$("#death" + currentStateSp).text(numberWithCommas(allData[0][0].totaldeaths));
$("#updated" + currentStateSp).text("Updated " + allData[0][0].updated)
if (tabCount < totalTabs-1) {
tabCount ++;
currentStateSp = tabNames[tabCount];
loadData(tabNums[tabCount]);
}
xtalk.signalIframe();
}

Resolving the Google Sheet Vulnerability

Obviously concerned, I immediately reached out to the Data Visualization and Multimedia team at NBCUniversal Media, and they responded within minutes. The team looked into the issue immediately, and the document was locked down the same day. Kudos to NBC for acting quickly to resolve the issue! The Contrast team is pleased to help close this vulnerability.

data-visualization

google-sheets-permission

 

Contrast Marketing

Contrast Marketing