Copy to clipboard as plain text
Asked Answered
F

1

17

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?

Foghorn answered 2/8, 2014 at 21:5 Comment(0)
P
22

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>');
Piccoloist answered 12/8, 2014 at 22:30 Comment(6)
Thanks. Could you explain how an attacker would trigger executeCopy?Foghorn
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.Piccoloist
Would an attacker be able to call executeCopy without first editing the HTML of the active page?Foghorn
@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...)Piccoloist
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.Piccoloist
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.Foghorn

© 2022 - 2025 — McMap. All rights reserved.