Ошибка HTTP Git 'фатальная: ошибка протокола: неверный символ длины строки: '

В настоящее время я пытаюсь создать простой Git HTTP-сервер в C без уже существующего веб-сервера. В настоящее время единственное, что я делаю, - это создание сокета сервера и выполнение CGI-скрипта git-http-backend с переменными среды из запроса клиента. Запрос на извлечение уже работает, но только для пустых репозиториев. Когда я пытаюсь клонировать репозиторий с контентом, я получаю эту ошибку на стороне клиента:

fatal: protocol error: bad line length character: 

Вот журнал связи между клиентом и сервером:

C: GET /test.git/info/refs?service=git-upload-pack HTTP/1.1
C: Host: localhost:9000
C: User-Agent: git/2.20.1
C: Accept: */*
C: Accept-Encoding: deflate, gzip
C: Accept-Language: en-US, *;q=0.9
C: Pragma: no-cache
C:

S: HTTP/1.1 200 OK
S: Expires: Fri, 01 Jan 1980 00:00:00 GMT
S: Pragma: no-cache
S: Cache-Control: no-cache, max-age=0, must-revalidate
S: Content-Type: application/x-git-upload-pack-advertisement
S: 
S: 001e# service=git-upload-pack
S: 000000fadd3fba560f4afe000e70464ac3a7a9991ad13eb0
S: HEAD003fdd3fba560f4afe000e70464ac3a7a9991ad13eb0 refs/heads/master
S: 0000

Небольшое примечание: HTTP/1.1 200 OK добавляется вручную, остальное - из скрипта CGI. Также вы можете найти мой код здесь. Сначала у меня была теория, что содержимое ответа сервера ложно размещает новые строки (например, заголовок должен быть на строку выше), но оказывается, что это не совсем так. Итак, мой вопрос: могу ли я что-нибудь сделать? Редактирование этого ответа в хорошем формате довольно сложно в C, особенно с более длинными ответами.

1 ответ

Решение

Прежде всего, пожалуйста, убедитесь, что вы понимаете последствия для безопасности передачи данных, контролируемых внешним субъектом, для такой функции, как popen, Реализация, которую вы имеете сейчас, тривиальна для использования путем внедрения оболочки, добавляя специальные символы оболочки в строку запроса. Даже используя просто git со специально созданным именем хранилища, ваш текущий код позволяет выполнять произвольные команды на сервере. Попробуйте это например:

git clone "$(echo -e 'http://localhost:9000/;echo\tunexpected\t>helloworld;cat\t/etc/passwd;exit;.git')"

Это создаст файл в рабочем каталоге сервера со строкой "неожиданно" в нем и отправит обратно содержимое /etc/passwd клиенту (используйте wireshark, чтобы увидеть его).

Чтобы избежать этого, вам нужно убедиться, что вы правильно экранировали входные данные, чтобы не было возможности внедрения оболочки. В идеале вы должны использовать такие механизмы, как execve которые позволяют передавать переменные окружения и возможные аргументы командной строки в качестве буферов, вместо того, чтобы создавать, возможно, небезопасные строки, которые затем анализируются оболочкой. Такое решение, конечно, немного сложнее, так как оно означает реструктуризацию вашей программы.

Тогда вы используете небезопасный способ объединения строк. strcat не имеет возможности узнать, насколько велик целевой буфер, поэтому он с радостью перезапишет стек после буфера при достаточном вводе. Это классическое переполнение стека, которое затем можно использовать. Используйте более безопасные альтернативы, такие как strlcat или, еще лучше, правильная библиотека строк.

Теперь перейдем к исходной проблеме:

Выход вы получаете от git http-backend является необработанным двоичным выводом, включая нулевые байты. В ответе в качестве примера действительно будет нулевой байт после HEAD выделение списка поддерживаемых функций. Вы можете увидеть это, запустив команду вручную и передав ее xxd или выгрузить его в файл и посмотреть на него с помощью шестнадцатеричного редактора.

В цикле, в котором вы читаете из канала, а затем объединяете выходные данные в буфер ответов, вы усекаете данные, потому что strcat работает со строками C, которые заканчиваются нулевым байтом. Остаток от HEAD строка и сам нулевой байт никогда не доходят до ответа, нарушая протокол git.

Ты можешь использовать fread читать необработанные данные из канала прямо в буфер. Затем вам нужно будет скопировать этот буфер в буфер ответа с помощью функции, которая не останавливается на нулевом байте, например memcpy, Чтобы это работало, вам также нужно отслеживать уже прочитанные байты и сколько места остается в буфере ответов.

В качестве альтернативы, поскольку вы фактически не выполняете никакой обработки в буфере окончательного ответа, вы также можете напрямую отправлять данные, прочитанные из канала, в клиентский сокет. Таким образом, вам не нужно беспокоиться о размере буфера ответов и отслеживании смещения и оставшегося пространства. Вот версия, которая работает для первоначального запроса git делает:

        char response[10000] = "HTTP/1.1 200 OK\r\n";
        send(client_socket, response, strlen(response), 0);
        while (!feof(g)) {
            size_t bytes_read = fread(response, 1, sizeof(response), g);
            if (bytes_read == 0)
                break;

            send(client_socket, response, bytes_read, 0);
        }

Последующий запрос POST не выполняется.

Другие вопросы по тегам