¿Cuál es la diferencia entre `curl | sh` y `sh -c“ $ (curl) ”`?

23

Un método de instalación fácil para Docker (por ejemplo) es este:

curl -sSL https://get.docker.com/ | sh

Sin embargo, también he visto algunos que se ven así (usando el ejemplo de Docker):

sh -c "$(curl -sSL https://get.docker.com/)"

Parecen funcionalmente iguales, pero ¿hay alguna razón para usar uno sobre el otro? ¿O es solo una preferencia / cosa estética?

(Cabe destacar que tenga mucho cuidado al ejecutar secuencias de comandos de orígenes desconocidos).

Sarke
fuente

Respuestas:

39

Hay una diferencia práctica.

curl -sSL https://get.docker.com/ | shcomienza curly, shal mismo tiempo, conecta la salida de curlcon la entrada de sh. curlse llevará a cabo con la descarga (aproximadamente) tan rápido como shpueda ejecutar el script. El servidor puede detectar las irregularidades en el tiempo e inyectar código malicioso que no es visible cuando simplemente descarga el recurso en un archivo o búfer o cuando lo ve en un navegador.

En sh -c "$(curl -sSL https://get.docker.com/)", curlse ejecuta estrictamente antes de que shse ejecute. Todo el contenido del recurso se descarga y se pasa a su shell antes de que shse inicie. Su shell solo comienza shcuando curlha salido y le pasa el texto del recurso. El servidor no puede detectar la shllamada; solo se inicia después de que finaliza la conexión. Es similar a descargar el script en un archivo primero.

(Esto puede no ser relevante en el caso de la ventana acoplable, pero puede ser un problema en general y resalta una diferencia práctica entre los dos comandos).

Jonas Schäfer
fuente
2
Gracias, esta parece ser la diferencia más importante entre los dos.
Sarke
¿Podría citar un recurso que respalde este reclamo, por favor? Estaría muy interesado en saber cómo el servidor puede detectar la llamada 'sh'.
Alfred Armstrong el
1
@AlfredArmstrong Uhm, puse un enlace en la vulnerable to server-side detectionfrase. Lleva a una publicación de blog que explica con gran detalle cómo lo logran. TL; DR: suspenda su script y observe el retraso en la recepción en el servidor.
Jonas Schäfer el
1
@JonasWielicki gracias, el enlace no era muy claro, no es tu culpa, creo que hasta el CSS de SE. La gente es astutamente brillante, ¿no? :)
Alfred Armstrong el
1
Todo lo cual me lleva a preguntarme si alguien ha intentado hacer ese truco en realidad sin ser atrapado de inmediato. No es que probablemente importe mucho, ya que probablemente podría hacer que el script haga algo malicioso incluso sin tales trucos, y cualquiera que no lo lea en su totalidad sería vulnerable.
ilkkachu
11

Creo que son prácticamente idénticos. Sin embargo, hay casos raros en los que son diferentes.

$(cmd)se sustituye con los resultados de cmd. Si la longitud de ese comando de resultado excede el valor máximo de longitud de argumento devuelto por getconf ARG_MAX, truncará el resultado, lo que puede dar lugar a resultados impredecibles.

La opción de tubería no tiene esta limitación. Cada línea de salida del curlcomando se ejecutará a bashmedida que llegue desde la tubería.

Pero ARG_MAX generalmente está en el rango de 256,000 caracteres. Para una instalación de docker, estaría seguro de usar cualquiera de los métodos. :-)

Greg Tarsa
fuente
Interesante, entonces hay una diferencia. Gracias
Sarke
1
En caso de duda, utilice el método de tubería. No especifiqué una preferencia en mi respuesta, pero preferiría el método de tubería para este tipo de uso porque nunca se sabe cuántos datos ingresan a través de la tubería.
Greg Tarsa
2
"truncará el resultado": el shell debe emitir un mensaje de error para eso, no truncar silenciosamente. Al realizar la prueba, incluso recibo un error del shell que se encuentra muy por debajo ARG_MAX, bash limita un argumento individual a 131072 bytes en mi sistema, cuando getconf ARG_MAXimprime 2097152. Pero de cualquier manera, error o truncamiento, no funcionaría.
hvd
Pero las implementaciones antiguas de Bourne Shell tenían límites mucho más bajos. En 4.2BSD, el límite era 10240 caracteres, y en sistemas anteriores era aún más bajo. Por supuesto, eso fue hace 30 años, por lo que es poco probable que encuentre límites tan bajos hoy. Si no recuerdo mal, algunos de estos primeros proyectiles se truncaron en silencio.
AndyB
El límite de 128 kB para un solo argumento es una cosa de Linux, no se trata de Bash.
ilkkachu
8

En curl -sSL https://get.docker.com/ | sh:

  • Ambos comandos, curly sh, comenzarán al mismo tiempo, en subcapas respectivas

  • El STDOUT de se curlpasará como el STDIN a sh(esto es lo que hace la tubería |)

Mientras que en sh -c "$(curl -sSL https://get.docker.com/)":

  • La sustitución del comando $(), se ejecutará primero, es decir curl, se ejecutará primero en una subshell

  • La sustitución del comando $(), será reemplazada por el STDOUT decurl

  • sh -c (shell no interactivo, sin inicio de sesión) ejecutará STDOUT desde curl

heemayl
fuente
1
Entonces, ¿hay alguna diferencia real?
Sarke
@Sarke Sí, teóricamente como mencioné, pero prácticamente apenas se nota. (Habría un efecto visible si deja la sustitución del comando sin comillas).
heemayl
1
@Sarke, si los scripts no se descargan por completo, no necesariamente lo notarás con las tuberías. El proceso al que se canaliza puede ignorar esa señal.
Janus Troelsen
@JanusTroelsen, ¿qué quieres decir con no descargar completamente? ¿Por qué sucedería eso excepto un error del servidor en cuyo caso no habrá nada canalizado a sh?
Hasufell
O interrupción de la conexión ... hay muchas maneras en que una transferencia puede fallar
Janus Troelsen
0

Una diferencia entre los dos (tomados de otras respuestas en la web) es que si no descarga el script completo a la vez, podría cortar la mitad del script en un punto desconocido y cambiar el significado del comando para que sea ejecutado. Entonces parece que primero se descarga todo el archivo y luego evaluarlo sería mejor.

Lance Pollard
fuente