我目前正在尝试在C语言中创建一个简单的Git HTTP Server,而不使用现有的Web服务器。当前,我唯一要做的就是创建服务器套接字,并使用来自客户端请求的环境变量执行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脚本。您也可以找到我的代码here。 首先,我有一个理论,即服务器响应的内容在新行中放置了错误的位置(例如HEAD应该在更高的行),但是事实并非如此。所以我的问题是:我能做些什么?在C中,以良好的格式编辑此响应非常复杂,尤其是响应较长时。
答案 0 :(得分:0)
首先,请确保您了解将外部参与者控制的数据移交给popen
之类的函数的安全隐患。通过将shell特殊字符添加到请求行,shell注入可以轻松利用您现在拥有的实现。即使仅将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请求失败。