c语言 - Git HTTP 错误 '致命: 协议错误: 错误的行长字符: '



我目前正在尝试在没有现有Web服务器的情况下用C创建一个简单的Git HTTP服务器。目前,我所做的唯一一件事就是创建一个服务器套接字,并使用客户端请求中的环境变量执行githttp后端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.1200OK是手动添加的,其余部分来自CGI脚本。你也可以在这里找到我的代码。首先,我有一个理论,即服务器响应的内容错误地放置了新行(例如,HEAD应该高一行),但事实并非如此。所以我的问题是:我能做些什么吗?在C中,将此响应编辑为良好的格式是相当复杂的,尤其是对于较长的响应。

首先,请确保您了解将由外部参与者控制的数据交给popen等函数的安全含义。通过在请求行中添加shell特殊字符,shell注入可以很容易地利用您现在拥有的实现。即使只使用git和特制的存储库名称,您当前的代码也允许在服务器上执行任意命令。举个例子:

git clone "$(echo -e 'http://localhost:9000/;echotunexpectedt>helloworld;catt/etc/passwd;exit;.git')"

这将在服务器的工作目录中创建一个文件,其中包含字符串"expected",并将/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 OKrn";
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请求失败。

相关内容

  • 没有找到相关文章

最新更新