0

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

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

4

1 回答 1

0

首先,请确保您了解将由外部参与者控制的数据交给类似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. 为此,您还需要跟踪已读取的字节以及响应缓冲区中还剩余多少空间。

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

于 2018-12-26T14:55:50.643 回答