¿Cómo puedo protegerme de este tipo de abuso del portapapeles?

170

Abuso del portapapeles de los sitios web

Muchos sitios web utilizan JavaScript o CSS para insertar o reemplazar sigilosamente el texto en el portapapeles del usuario cada vez que copian información de la página. Por lo que sé, esto se usa principalmente con fines publicitarios, pero se ha demostrado el PoC para explotaciones.

Sin embargo, descubrí que uno ni siquiera necesita JS o CSS para crear un exploit que tenga efectos maliciosos cuando se pega en un terminal. Pegar caracteres de retroceso ocultos puede cambiar todo el significado de un comando de shell. Pegar en un editor basado en el término tampoco es seguro. Al pegar Esc entonces :! puede hacer que una instancia de Vim en ejecución ejecute un comando de shell. Al pegar ^X^C se cerrará Emacs y / o incluso cat . Al pegar ^Z , se detendrá la mayoría de los editores basados en términos y volverá al shell.

Lo que lo empeora es que muchos sitios web de confianza no sanean a estos caracteres no imprimibles. Twitter filtra Esc pero no retrocede. Pastebin.com no parece filtrar nada. ¡Tampoco Stack Exchange , por lo tanto, la siguiente vulnerabilidad ( ADVERTENCIA: ¡código malicioso, NO lo copie ni lo pegue en un terminal Unix! ) que podría muy bien convertirse en algo peor y más probable que sea pegado por una víctima:

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

Editar : los intercambios sin procesar ahora se filtran mediante Stack Exchange, por lo que este PoC requiere & # escapes. /Edit

Aquí es cómo Chrome lo procesa:

Firefoxnoseengañatanfácilmente,perosiguesiendoajenoalenfoquedeJSoCSS:

Y cuando se pega en un terminal, simplemente mata todos los procesos del usuario.

¿Qué hacer?

Lo que básicamente me dice es que nunca debería, nunca, copiar nada de una página web y pegarlo en una aplicación de terminal . Pues, genial. Mi entorno de trabajo es básicamente 1 navegador web y 40 ventanas / pestañas de terminal. Copio y pego fragmentos de código todo el tiempo.

Ahora, ¿hay alguien que pueda protegerme de mis propios malos hábitos (que, honestamente, no creo que sean tan malos)? ¿Vendedores de navegador? ¿Vendedores de terminales? Los vendedores de sistemas de portapapeles? ¿Una aplicación de terceros tal vez?

    
pregunta sam hocevar 18.07.2013 - 02:08
fuente

7 respuestas

26

Tenga en cuenta que a partir de la versión 292 , xterm elimina los caracteres de control ASCII, excepto \b , \r , \t , DEL (0x7f) y \n (convierte \n a \r s como otros terminales), y puede recuperarlos con el recurso allowPasteControls . VTE (la biblioteca de emulador de terminal utilizada por gnome-terminal , terminator , xfce-terminal ...) también lo hace desde octubre de 2015

Entonces, en esos terminales, ^C , ^[ , ^D , ^Z , ^\ , ^U , ^W ya no son un problema, pero DEL, \b , \t (muy peligroso con algunas configuraciones (incluida la predeterminada) de zsh donde la terminación puede expandir las sustituciones de comandos), \r y \n siguen siendo.

xterm también tiene un par de modos de pegado que pueden ayudar aquí.

  • el modo de pegado entre corchetes habilitado con la secuencia \e[?2004h como se usa en algunos zsh o vim safe - pegar complementos .

    zsh ( zle ) desde 5.1 y bash ( readline ) desde 4.4 ahora tienen soporte para eso integrado. Mientras que está habilitado de forma predeterminada en zsh , debe habilitarlo manualmente con bind 'set enable-bracketed-paste on' en bash (o en la configuración readline en ~/.inputrc o /etc/inputrc ).

    Este envuelve la selección entre \e[200~ y \e[201~ .

    La mayoría de las otras terminales modernas, como las basadas en VTE ( gnome-terminal , xfce-terminal , terminator ...), rxvt , konsole , OS / X Terminal ahora también admiten esa.

    Sin embargo, en algunos de esos otros terminales (o versiones de los mismos) (o con allowPasteControls en xterm ), es defectuoso que \e[201~ aparezca en la selección, y se tomará como corchete de cierre.

    Esto podría solucionarse entre corchetes como \e\e[201~\e[200~201~ , pero no lo ha hecho ningún emulador de terminal todavía AFAIK (y significaría que la aplicación vería varias pastas).

    ^C / ^Z / ^\ también causaría que las señales se envíen al grupo de proceso de primer plano del terminal si ISIG no se deshabilitó en la disciplina de línea tty.

  • El modo de pegar entre comillas habilitado con la secuencia \e[?2005h (deshabilitado con \e[?2005l ).

    Este prefigura cada carácter (en realidad byte) con un carácter ^V .

    ^V es el carácter predeterminado lnext ( literal next ) de la disciplina de línea tty en modo canónico, y también es reconocido como tal por vi y otros editores y algunos editores de línea como readline o zsh 's zle.

    Ese no tiene el mismo problema que el modo entre corchetes anterior, y tiene la ventaja de que funciona para el modo canónico del terminal (como cuando haces cat > file ) y algunas otras aplicaciones. pero tiene algunos inconvenientes:

    • newline y CR terminan representándose como ^M . Eso se puede evitar con otra secuencia de escape: \e[?2006h , pero eso hace que las nuevas líneas se inserten como caracteres NUL en vim y aparezcan como ^J (a menos que hagas stty -echoctl ) en el modo canónico del terminal (aunque es solo un problema cosmético).
    • Eso no funciona bien para los caracteres de varios bytes que no se insertan correctamente en zle o vim , por ejemplo.
    • algunas aplicaciones visuales no manejan ^V como literal siguiente , por lo que es posible que aún tengas que desactivarlo selectivamente.
    • no puede usarlo en vim porque ^V 1 , por ejemplo, no inserta 1 sino ^A allí.
    • No conozco ningún otro terminal que no sea compatible con xterm , pero no he realizado una encuesta exhaustiva.
  • también le permite definir su propio modo de pegado entre corchetes a través de la configuración. Por ejemplo, con:

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

    insertaría CLIPBOARD / PRIMARY / CUT_BUFFER0 como ^[[202~<size-in-bytes>~<content> en Shift + Ctrl + Insert . Entonces, la aplicación podría interpretar eso de manera confiable (aunque todavía tendría que deshabilitar ISIG en la disciplina de línea tty).

Otro enfoque sería usar un envoltorio de pseudo-tty que inserte esos ^V solo delante de los caracteres de control. Dicha envoltura debería ser capaz de detectar caracteres de control en pastas con cierta confiabilidad porque las pulsaciones reales del teclado solo enviarían un carácter a la vez o una secuencia de caracteres que comienzan con ESC, mientras que las pastas enviarían varios a la vez .

Aún tendrías el problema de las nuevas líneas mostradas como ^J en el modo canónico terminal o ^@ en vim , pero eso podría solucionarse con cierta cooperación por parte del shell

Una prueba de concepto:

Para ser utilizado por ejemplo como:

./safe-paste bash

Para iniciar un shell bash bajo ese contenedor.

#!/usr/bin/perl

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

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

  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;

  exec(@ARGV);
  die "Cannot exec(@ARGV): $!";
}
$pty->close_slave();

$SIG{WINCH} = sub {
  $pty->slave->clone_winsize_from(\*STDIN);
};

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 =~ /.[
 XTerm*allowPasteControls: true
 XTerm.VT100.translations: #override \
   Ctrl Shift<KeyPress> Insert: \
     insert-formatted("3[202~%S~%s", CLIPBOARD,PRIMARY,CUT_BUFFER0)'
-77]/ || $buf =~ /^(?:[
./safe-paste bash
-24-7]|3.*?[~a-zA-NP-Z])./) { $buf =~ s/[
#!/usr/bin/perl

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

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

  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;

  exec(@ARGV);
  die "Cannot exec(@ARGV): $!";
}
$pty->close_slave();

$SIG{WINCH} = sub {
  $pty->slave->clone_winsize_from(\*STDIN);
};

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 =~ /.[%pre%-77]/ || $buf =~ /^(?:[%pre%-24-7]|3.*?[~a-zA-NP-Z])./) {
          $buf =~ s/[%pre%-77]/6$&/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)}
-77]/6$&/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)}

Un mejor enfoque probablemente sería usar un administrador de portapapeles donde puedes especificar el modo de pegado y eso marcaría las selecciones potencialmente peligrosas.

    
respondido por el Stéphane Chazelas 04.03.2014 - 09:05
fuente
45

Es posible que hayas adivinado esto, pero nunca uses los terminales que pegan la funcionalidad para pegar cosas en vim / emacs . Es como enviar un lote de comandos al editor, que puede hacer cualquier cosa.

Por estas razones, los editores tienen su propia funcionalidad de pegado de copia, que no se puede inyectar. Por ejemplo, en vim, debe usar el registro + para intercambiar datos con el portapapeles del sistema ( "+p para pegar).

Con respecto al shell u otras aplicaciones de terminal: se ha establecido , que no debe pegar datos no seguros en su terminal.

Hay un complemento de pegado seguro para zsh, que evita que el código se ejecute realmente cuando pegado, pero alguien ya ha explotado de todos modos .

También, una pregunta similar (sobre el pegado accidental) en apple.se. La mayoría de las soluciones también podrían funcionar para usted.

Actualización: En vim, si se usa set mouse=a , pegar con el botón central del ratón es seguro. Aún puedes pegar con shift-Insert aunque.

    
respondido por el copy 18.07.2013 - 14:02
fuente
19

Bueno, resulta que mi enfoque actual del portapapeles es bueno para mitigar esto.

Al copiar y pegar fragmentos entre pestañas, simplemente copio y pego normalmente.

Sin embargo, al copiar y pegar en un terminal / sesión de PuTTY, yo (siendo un poco reacio a editar el texto en el terminal), normalmente lo armo en Notepad ++ o Emacs (según el sistema operativo) y luego copio y pegue el texto final. en la terminal Ambos editores muestran los caracteres de control (y otros caracteres no imprimibles), por lo que es fácil notar cualquier problema de este tipo.

Lamentablemente, no puedo afirmar que use el enfoque del editor de texto intermedio por razones de seguridad, porque todavía no soy un experto en vim o en cualquier otro editor basado en terminales.

    
respondido por el Manishearth 18.07.2013 - 10:08
fuente
17

Podría afirmar que cualquier copia y pegado de fragmentos de código es un mal hábito, pero eso no es un problema. Personalmente escribo dichos elementos de código en lugar de copiarlos, pero eso es porque generalmente quiero cambiar algunas cosas en ellos, o aprender a hacer la tarea en cuestión; o tal vez solo soy un loco maniático.

Lo que podrías hacer es desinfectar automáticamente el contenido del portapapeles . Una aplicación en segundo plano puede monitorear constantemente el contenido del buffer de corte y eliminar los caracteres de control; No estoy seguro de que se pueda convencer a X11 para que envíe un evento para un cambio de búfer de corte, pero el sondeo 10 veces por segundo haría el truco. La dualidad X11 (corte de búferes frente a selecciones) hará las cosas un poco más complejas, pero creo que esto se puede hacer (y, además, creo que usted puede hacerlo).

La desinfección de contenidos puede ser complicada. Por ejemplo, supongamos que elimina todos los bytes en el rango 0..31 (los caracteres de control ASCII) excepto la nueva línea (10), el retorno de carro (13) y las tabulaciones (9). Entonces, si escribo esto (sistema Linux):

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

y luego lo pego en una instancia vim que se ejecuta en xterm (en modo UTF-8), luego mato todos mis procesos ... aunque el búfer de corte nunca contiene ningún carácter de control "no deseado" en ningún punto. La secuencia C0 9B no es válida en UTF-8, pero está lo suficientemente cerca como para que xterm intente descodificarla de todos modos, y descodifica a 0x1B , también conocido como Escape ... otras secuencias difíciles incluyen E0 80 9B . Tenga en cuenta que si bien el valor de UTF-8 nunca incluye un byte de valor C0 , puede contener bytes de valor E0 , 80 y 9B (pero no en esa secuencia). Por lo tanto, es mejor que el proceso de desinfección sea minucioso y estricto.

Una funcionalidad adicional de esta herramienta sería convertir automáticamente las secuencias CR + LF, y la única CR, en LF. Posiblemente convierta los sangrientos caracteres CP-1252 del rango 128..159 en sus equivalentes sanos y estándar. Esto no es solo un problema de seguridad; podría ser una herramienta útil en situaciones no maliciosas.

    
respondido por el Thomas Pornin 18.07.2013 - 15:10
fuente
3

Recientemente resolví este problema por mi cuenta ejecutando mi navegador web en una máquina virtual. La selección de x no está sincronizada entre la máquina virtual y el host, por lo que ya no es posible que haga clic en el botón central sin pegar cosas de la web a un terminal.

    
respondido por el gravitation 13.08.2013 - 18:12
fuente
2

Va mucho más profundo que las ediciones de línea incrustadas donde al menos vería lo que terminó sucediendo; O al menos ver que algo estaba siendo escondido. Podría publicar un fragmento de código de aspecto benigno como este:

let t_BE="\<esc>[?2004h"

Luego, intente inducir a la gente a copiarlo y pegarlo en un editor de texto específico en un emulador de terminal específico, por ejemplo, sugiriendo que es necesario mitigar un problema (tal vez incluso un problema de seguridad) que los dos tienen cuando operan juntos .

Lo que realmente puede copiar en el fragmento de código anterior contiene caracteres de control y código que puede ejecutarse invisiblemente , por lo que no está consciente de que podría haber ocurrido algo siniestro (siempre que lo pegue en el lugar adecuado). editor a través de un terminal adecuado, que diseñamos con un poco de ingeniería social).

Esto se logra usando trucos de texto oculto convencionales combinados con una secuencia de escape para convencer al terminal de que la operación de pegado está completa (descrita en otras respuestas aquí) y una serie de comandos de teclado que el editor puede ejecutar tan rápidamente que la pantalla No los muestra sucediendo. Finalmente, los comandos maliciosos se eliminan del historial interno para que no se descubran más tarde.

Para redondear esto en una respuesta; esta manifestación es sobre todo histórica. El modo de pegado entre paréntesis es ampliamente compatible, y en la mayoría de los terminales no es posible romperlo con escapes incrustados (verifique esto y actualice / informe según sea necesario). Debes configurar tu shell y tus editores y cualquier otra cosa que puedas pegar para habilitar el modo de pegado entre corchetes.

    
respondido por el sh1 10.04.2018 - 09:43
fuente
1

Lo que hago para protegerme contra esto es usar el modo de pegado entre corchetes. Como dijo Stéphane, zsh y urxvt lo soportan por defecto. Si se limita a pegar solo cuando está en un indicador zsh en urxvt, el único problema real que le queda (no veo ningún problema con ^C o ^Z causando señales en el indicador) es que el pegado podría contener la secuencia de escape para finalizar el pegado e interpretar el resto como si estuviera escrito.

Solucionar este problema pendiente es de lo que trata esta respuesta. Escribiré lo que hice para mi caso, pero esto también debería ser factible en otros terminales, si no es por extensión luego por parche.

urxvt soporta extensiones perl. Escribí esto en ~/.urxvt/ext/filter-paste :

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

lo incluyó en la lista de extensiones perl para cargar en ~/.Xresources :

URxvt.perl-ext-common: filter-paste

y volvió a cargar la configuración para nuevas ventanas de terminal con xrdb ~/.Xresources .

Con eso, urxvt elimina todas las instancias de la secuencia de escape del modo de pegado de cierre de lo que se está pegando en el terminal.

La página web, enlace , tiene 2 ejemplos de exploits para esto en este momento. Antes de escribir la extensión, zsh con urxvt sería invulnerable para el primer ejemplo, pero no para el segundo, que incluye la secuencia de escape para finalizar el modo de pegado. Después de escribirlo, ninguno funcionó, y el contenido completo de lo que estaba pegado se mostraría en el indicador, esperando un Enter antes de ejecutar (zsh no ejecutará un comando pegado con el modo de pegado entre corchetes, incluso si contiene nuevas líneas hasta que presione Enter ).

EDIT: sugerido por el comentario de sh1, probé otros emuladores de terminal para ver cuáles aún eran vulnerables.

|----------------+---------+--------|
| 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     |
| st             |   0.8.1 | failed |
| qterminal      |   0.8.0 | failed |
| putty          |    0.70 | failed |
|----------------+---------+--------|

Todas las versiones son las que se encuentran actualmente en el momento de escribir en los repositorios de paquetes de Archlinux (Archlinux es versión de lanzamiento). sh1 vinculado a un commit en PuTTY que soluciona esto, pero parece que aún no se ha lanzado en una nueva versión.

    
respondido por el JoL 19.04.2018 - 23:47
fuente

Lea otras preguntas en las etiquetas