martes, 6 de septiembre de 2011

Cómo inyectar un commit maliciosos en un repositorio de Git (o no)

Creo que recuerdan la noticia de hace unos días sobre la violación de seguridad que sucedió en los servidores de kernel.org. Pues bien, para los que quedaron con la duda sobre si afectaría o no a los sistemas que sincronizan con dichos repositorios les va este trabajo traído del blog de los mantenedores de Git, que finalmente es el sistema que asegura que fenómenos como este no lleguen mas allá de lo debido. Conste que no es el único mecanismo de seguridad involucrado en la tarea, todo el código del núcleo está protegido por funciones hash que generan números de 160 bits y ya saben como funcionan las susodichas de modo que si modifican una sola linea de código su hash resultante por ende resultará modificado. Consideren que Git comprueba los hashes regularmente, así que cualquier cambio sería notado casi de inmediato (ojo, nos referimos a los cambios en el código del núcleo propiamente dicho, el ataque consiguió ganar derechos de "root" que es otra cosa distinta y que de hecho tardaron mas días de lo deseable en descubrir). Para terminar los hashes se calculan partiendo de los hashes de todos los archivos contenidos en un árbol determinado y de todos los estados anteriores del mismo, no hay manera de cambiar un solo archivo de cualquier release o de cualquier otra versión anterior sin que salte a la vista (Git es además un sistema de control distribuido en oposición a sistemas como CVS o Subversión que son centralizados, eso cuenta y mucho). 
En fin, ahora supongamos que si, que conseguimos cambiarlo ¿que sucedería?, ¿a donde lograrían llegar las modificaciones luego de eso?. Pues aquí les va como ejercicio y para su tranquilidad... aquí no ha pasado nada :-P  

Cómo inyectar un commit maliciosos en un repositorio de Git (o no)
Suponga que por un momento ganó acceso de escritura a los repositorios públicos de otras personas en un punto de distribución de gran tamaño, como kernel.org. ¿Qué daño podría causar a sus proyectos si quisiera?
Ud podría crear un commit malicioso en la parte superior de la punta de la rama "master" del repositorio linux.git de Linus Torvalds. Nadie le impide que se haga pasar por Linus:

$ GIT_AUTHOR_NAME = "Linus Torvalds" \
  GIT_COMMITTER_NAME = "Linus Torvalds" \
  GIT_AUTHOR_EMAIL torvalds@linux-foundation.org = \
  GIT_COMMITTER_EMAIL torvalds@linux-foundation.org = \
  git commit-s

Su Inglés puede ser suficientemente bueno para engañar a los lectores a creer que el mensaje de registro puede haber venido de Linus. Tal vez pudo haber hecho esto sobre el 12 de agosto, cuando la punta de la verdadera rama "master" de Linus (M) es modificada y X es el commit malicioso que ha creado en la parte superior de la misma. La historia resultante puede tener este aspecto:

- M
   \
    X 

Si una víctima inocente copia regularmente desde el repositorio de Linus, puede ejecutar un "git-pullantes de que su commit malicioso sea descubierto en una auditoría de seguridad. Y es posible que ya haya basado sus productos derivados ​​en esta versión maliciosa del kernel.

¿Es esto un gran "Oops"? Vamos a ver qué pasa con esta víctima inocente más adelante.

Cuando Linus intenta cargar su trabajo actualizado, sin embargo, el historial en su equipo de desarrollo (que no es el punto de distribución en el que se las arregló para agregar su commit malicioso) no tiene el commit X. En términos de Git, el historial que usted alteró y el historial de Linux ahora divergen:

- M --- o --- o --- o --- o --- o --- o --- o --- L
   \
    X

Donde M es la punta de la rama "master" original en el repositorio público, X es el commit malicioso que ud creó y actualizó la rama "master" para apuntar a el, y L es la punta del historial que Linus está a punto de subir. Decimos "L no se adelanta rápidamente a X" (fast forward en el original), ya que X no es parte de L (el tiempo fluye de izquierda a derecha).
Lo que sucede ahora es que el "git-push" que Linus ejecuta para subir a su repositorio público nota que la actualización de la rama "master" en el repositorio público con la punta de su historial no concuerda con la X que ud ha creado (no se da cuenta de que el commit de que está a punto de perderse es un código malicioso, ni cuenta de que no fue hecho por Linus, pero no tiene que notarlo tampoco para que funcione esta protección), y se niega a hacerlo. Linus definitivamente notará que algo raro está pasando, porque él tiene que hacer algo que nunca suele hacer para impulsar su cambio como su próximo paso.
Si se tratara de un ajuste de repositorio compartido, Linus puede pensar "Ah, alguien se me adelantó", a continuación, ejecuta "git pull" para "fusionar" el trabajo de otras personas que comparten el mismo repositorio público (es decir usted) a su árbol y crear un commit fusionado Y, a continuación, inserta el resultado una vez más:

- M --- o --- o --- o --- o --- o --- o --- o --- L
   \ \
    >>> x + y

Al final, su malicioso commit X podría terminar en el historial que resulta de esta manera, siempre y cuando se ejecute la fusión, y si el (es decir Linus)  no inspecciona la fusión Y.
Sin embargo, Linus (o cualquier otra persona del núcleo con repositorios publicados en general en kernel.org) no trabaja usando un repositorio compartido con otras personas, para empezar. El repositorio de kernel.org es su repositorio de publicación y sólo suyo, por lo que no puede escabullir su commit malicioso en su historial a través de este medio. 
Linus puede optar por ser descuidado y forzar su fusión, sin molestarse en investigar por qué no avanza rápidamente (en la vida real, esto no va a suceder, pero por el bien de ejercicio mental, imaginemos que él eligió ser descuidado y vamos a ver qué pasa). Esto eliminará su commit malicioso de su repositorio público. Si lo hiciera, el repositorio quedaría como sigue:

- M --- o --- o --- o --- o --- o --- o --- o --- L

Su commit malicioso X no tendría ningún efecto en la gente que se descargan del repositorio público de Linus luego de que esto sucede, pero ¿qué pasa con la víctima inocente que descargó X antes de que Linus forzara la fusión? ¿Estarían contaminados con su commit malicioso y no se darían cuenta nunca?

Recuerde, que en lo que a ellos respecta, el historial de Linus que descargaron antes, el cual mantiene en su rama de seguimiento remota era X, y entonces se actualizaría a L, lo cual no sucedería rapidamente. Su "git pull" (en realidad se trata de "git-fetch" que se invoca como parte de "pull") se dará cuenta e informará:

De git: / / git.kernel.org / ... / Torvalds / linux.git /
+ 9d901d9 ... ad4d968 master -> origin / master (forced update)

Notó el término "forced update"? La víctima inocente puede notar que la rama lateral X ya no es parte del historial de Linus.
Un consejo de seguridad que se ofrece aquí es el siguiente. Si usted sabe que su upstream (en este ejemplo, Linus) nunca rebobina su historia, usted puede configurar su archivo de configuración de git (ábralo con su $EDITOR favorito, es un simple archivo de texto y está diseñado para ser editable a mano ) y quite el signo '+' de la linea de "fetch". Encuentre una línea que se parezca a esto:

[Remote "origin"]
        fetch = +refs/heads/*:refs/remote/origin/*

Y edítelo para que se vea así:

[Remote "origin"]
        fetch = refs/heads/*:refs/remote/origin/*

Esto hará que su "git pull" (de nuevo, en realidad es "git fetch" que se invoca desde el comando) falle cuando el upstream rebobine su historial, de esta manera. Verás que el comando fallará cuando intente descargar desde el de Linus:

From git://git.kernel.org/.../Torvalds/linux.git/
! [rejected] master -> origin/master (non-fast-forward)

Podríamos querer volver a revisar la configuración predeterminada que "git clone" deja en su nuevo repositorio para hacer más difícil que el upstream rebobine sus ramas eliminando el '+' (que significa "no permitir el avance rápido), pero que tendría que ser discutido en la lista de correo Git (git@vger.kernel.org), no en este post. Hay una razón por la que configuramos por defecto el insistir en los avances rápidos.
Por cierto, no tiene un ápice de diferencia en la historia de arriba si usted reescribe el commit que conduce a M (es decir, el viejo truco de la rama "master" en el historial de Linus) usando "rebase" o "commit --amend ". La única diferencia es que ese cambio moverá el punto de bifurcación (fork) de las historias que divergen de M (en la historia anterior) más atrás, a un commit diferente que es mas viejo que M en la cadena de ascendencia. El historial que Linus trata de forzar al repositorio público L no avanza rápidamente al commit que ud coloca en la punta de la rama "master" que contiene la versión maliciosa, y esto es lo único que importa. 


(*) El término "commit" viene de "committer" es decir, individuos que tienen los derechos, e implícitamente la capacidad, de hacer modificaciones al código fuente de una parte de un software de código abierto. De modo que pueden entenderlo como "modificación" cuando menos aproximadamente, tiene de hecho otras implicaciones que no vienen a cuento. Regresar.