I'm currently trying to create a simple Git HTTP Server in C without an already existing Web server. Currently the only thing I do is creating a server socket and executing the git-http-backend CGI script with the Environment Variables from the Client Request. The Pull Request already works, but only for empty repositories. When I'm trying to clone a repository with content, I'm getting this Error on the client side:
fatal: protocol error: bad line length character:
Here is the communication log between client and server:
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
Just a little side note: HTTP/1.1 200 OK is added manually, the rest is from the CGI script. Also you can find my code here. First I had the theory, that the content of of the server response has a false placement of the new lines (e.g. The HEAD should be a line higher), but turns out this is not really the case. So my Question is: Is there anything I could do? Editing this response to a good format is pretty complicated in C, especially with longer responses.
First of all, please make sure you understand the security implications of handing in data controlled by an outside actor to a function like
popen
. The implementation you have right now is trivially exploitable by shell injection by adding shell special characters to the request line. Even by just using git with a specially crafted repository name your current code allows arbitrary commands to execute on the server. Try this for example:This will create a file in the working directory of the server with the string "unexpected" in it and will send back the contents of
/etc/passwd
to the client (use wireshark to see it).To avoid this, you need to make sure that you properly escape the input data so that no shell injection can happen. Ideally you would use mechanisms like
execve
that allow you to hand in the environment variables and possible command line arguments as buffers instead of producing possibly unsafe strings that are then parsed by a shell. Such a solution is of course a bit more involved as it means restructuring your program.Then you are using an unsafe way to concatenate strings.
strcat
has no way to know how large the destination buffer is, it will therefore happily overwrite the stack past the buffer given enough input. This is a classic stack overflow that can then be exploited. Use safer alternatives likestrlcat
or better yet a proper string library.Now on to your original problem:
The output you get from
git http-backend
is raw binary output, including null bytes. In the example response there would indeed be a null byte after theHEAD
separating off the supported feature list. You can see that by running your command manually and piping it to something likexxd
or dumping it to a file and looking at it with a hex editor.In the loop where you read from the pipe and then concatenate the output into your response buffer, you truncate the data because
strcat
operates on C strings that are terminated by a null byte. The rest of theHEAD
line and the null byte itself never make it to the response, breaking the git protocol.You can use
fread
to read raw data from the pipe directly into the buffer. Then you would need to copy that buffer to the response buffer with a function that doesn't stop at a null byte, likememcpy
. For this to work you also need to keep track of the bytes already read and how much space still remains in the response buffer.Alternatively, since you do not actually do any processing on the final response buffer, you could also directly send the data you read from the pipe to the client socket. This way you don't need to worry about the response buffer size and keeping track of the offset and remaining space. Here is a version that works for the initial request
git
does:The subsequent POST request then fails.