Git HTTP error 'fatal: protocol error: bad line length character: '

Git HTTP error 'fatal: protocol error: bad line length character: '

我目前正在尝试在 C 语言中创建一个简单的 Git HTTP 服务器,而无需现有的 Web 服务器。目前我唯一要做的就是创建一个服务器套接字并使用来自客户端请求的环境变量执行 git-http-backend CGI 脚本。 Pull Request 已经有效,但仅适用于空存储库。当我尝试克隆包含内容的存储库时,我在客户端收到此错误:

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 脚本。你也可以找到我的代码here。 首先我有一个理论,即服务器响应的内容错误地放置了新行(例如,HEAD 应该高出一行),但事实并非如此。所以我的问题是:有什么我可以做的吗?在 C 中将此响应编辑为良好的格式非常复杂,尤其是较长的响应。

首先,请确保您了解将由外部参与者控制的数据提交给 popen 这样的函数的安全隐患。通过向请求行添加 shell 特殊字符,可以通过 shell 注入轻松利用您现在的实现。即使只是使用 git 和特制的存储库名称,您当前的代码也允许在服务器上执行任意命令。例如试试这个:

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

这将在服务器的工作目录中创建一个文件,其中包含字符串 "unexpected",并将 /etc/passwd 的内容发送回客户端(使用 wireshark 查看)。

为避免这种情况,您需要确保正确转义输入数据,这样就不会发生 shell 注入。理想情况下,您会使用像 execve 这样的机制,允许您将环境变量和可能的命令行参数作为缓冲区提交,而不是生成可能不安全的字符串,然后由 shell 解析。这样的解决方案当然有点复杂,因为它意味着重构你的程序。

那么你正在使用一种不安全的方式来连接字符串。 strcat 无法知道目标缓冲区有多大,因此如果输入足够,它会很乐意覆盖超过缓冲区的堆栈。这是一个典型的堆栈溢出,可以被利用。使用更安全的替代方案,例如 strlcat 或更好的适当字符串库。

现在回到你原来的问题:

您从 git http-backend 获得的输出是原始二进制输出,包括空字节。在示例响应中,在 HEAD 分隔支持的功能列表之后确实会有一个空字节。您可以通过 运行 手动查看您的命令并将其通过管道传输到 xxd 或将其转储到文件并使用十六进制编辑器查看它。

在从管道读取然后将输出连接到响应缓冲区的循环中,您截断了数据,因为 strcat 对以空字节终止的 C 字符串进行操作。 HEAD 行的其余部分和空字节本身永远不会进入响应,从而破坏了 git 协议。

您可以使用fread将原始数据从管道直接读入缓冲区。然后,您需要使用不在空字节处停止的函数(如 memcpy)将该缓冲区复制到响应缓冲区。为此,您还需要跟踪已读取的字节数以及响应缓冲区中仍有多少 space。

或者,由于您实际上不对最终响应缓冲区做任何处理,您也可以直接将从管道读取的数据发送到客户端套接字。这样你就不需要担心响应缓冲区大小和跟踪偏移量和剩余 space。这是一个适用于初始请求 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 请求失败。