16

I'm using this code in background.js in a Chrome extension to copy text to the user's clipboard:

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        if (request.command == "copy") {
            executeCopy(request.text);
            sendResponse({farewell: "copy request received"});
        }
    }
);

function executeCopy(text){
    var copyDiv = document.createElement('div');
    copyDiv.contentEditable = true;
    document.body.appendChild(copyDiv);
    copyDiv.innerHTML = text;
    copyDiv.unselectable = "off";
    copyDiv.focus();
    document.execCommand('SelectAll');
    document.execCommand("Copy", false, null);
    document.body.removeChild(copyDiv);
}

It copies the text with formatting. How can I copy the text in plain text with no formatting?

Joe Mornin
  • 8,766
  • 18
  • 57
  • 82

1 Answers1

20

Your question's code contains a common security issue known as XSS. Because you take untrusted input and assign it to .innerHTML, you're allowing attackers to insert arbitrary HTML in the context of your document.

Fortunately, attackers cannot run scripts in the context of your extension because the extension's default Content security policy forbid inline scripts. This CSP is enforced in Chrome extensions exactly because of situations like this, to prevent XSS vulnerabilities.

The correct way to convert HTML to text is via the DOMParser API. The following two functions show how to copy text as text, or for your case HTML as text:

// Copy text as text
function executeCopy(text) {
    var input = document.createElement('textarea');
    document.body.appendChild(input);
    input.value = text;
    input.focus();
    input.select();
    document.execCommand('Copy');
    input.remove();
}

// Copy HTML as text (without HTML tags)
function executeCopy2(html) {
    var doc = new DOMParser().parseFromString(html, 'text/html');
    var text = doc.body.textContent;
    return executeCopy(text);
}

Note that .textContent completely ignores HTML tags. If you want to interpret <br>s as line breaks, use the non-standard (but supported in Chrome) .innerText property instead of .textContent.

Here are two of the many examples of how XSS could be abused using the executeCopy function from your question:

// This does not only copy "Text", but also trigger a network request
// to example.com!
executeCopy('<img src="http://example.com/">Text');

// If you step through with a debugger, this will show an "alert" dialog
// (an arbitrary script supplied by the attacker!!)
debugger;
executeCopy('<iframe src="data:text/html,<script>alert(/XXS-ed!/);<\/script>"></iframe>');
Ian Storm Taylor
  • 8,520
  • 12
  • 55
  • 72
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • Thanks. Could you explain how an attacker would trigger `executeCopy`? – Joe Mornin Sep 04 '14 at 01:18
  • For testing you can just paste the code in the console of the background page. For attackers: I assumed that the input can be controlled by them since the message is coming from the content script, which presumably takes the HTML from the web page. – Rob W Sep 04 '14 at 07:57
  • Would an attacker be able to call `executeCopy` without first editing the HTML of the active page? – Joe Mornin Sep 04 '14 at 19:35
  • @JosephMornin The attacker is *never* able to call `executeCopy` directly, because it can only be called by your extension. What I *do* know is that the function is called from the content script, *presumably* with the HTML directly from the page. In your question, you have *not* stated how the function is used, so I had to make some worst-case assumptions to be realistic. Further, you have not specified whether the "text" in your question is text or HTML, that's why the first revision of my answer assumed that you really wanted to copy text instead of HTML (to be continued...) – Rob W Sep 04 '14 at 19:42
  • I still think that you want to copy text instead of HTML, since if you want to copy the content of a page in a contentscript, then you can use `element.textContent` in the content scipt instead of `element.innerHTML`. Note that I had to make many assumptions because of the lack of details in your question. Next time you ask a question, please provide the full context of the question, so that the readers don't have to try and read your mind. – Rob W Sep 04 '14 at 19:43
  • Thanks for clarifying. You're correct that I want to copy text instead of HTML. Your approach appears more secure in principle, so I've marked it as the correct answer. But I'm still having trouble seeing the XSS vulnerability in the original approach, since an attacker can never call the function that contains the vulnerability. Apologies if I'm missing something obvious. – Joe Mornin Sep 04 '14 at 23:45