Package: mini-httpd
Version: 1.30-8
Severity: normal

Dear Maintainer,

mini-httpd as currently found in https://salsa.debian.org/debian/mini-httpd/-/blob/master/mini_httpd.c?ref_type=heads violates the CGI RFC (RFC-3875). The RFC specifies in "5.2 NPH Response" that an "NPH script MUST return a complete HTTP response message", and more "The server MUST ensure that the script output is sent to the client unmodified." But mini-http actually DOES modify the HTTP response from NPH CGIs. It prefixes the script output with a fixed "HTTP/1.0 200 OK" header, even if the NPH CGI wants to report a different status. This happens only if SSL is active.

The problematic code is in static void cgi_interpose_output(int rfd, int parse_headers). If parse_headers is false, the constant "HTTP/1.0 200 OK\015\012" is written before CGI output is forwarded to the HTTP client. According to the RFC, the web server must not send any output to the client except for what the CGI has send to the HTTP server.

cgi_interpose_output() is called from static void do_cgi(void) if SSL is active -- if (parse_headers || do_ssl).

do_cgi() is called from static void do_file(void) if the filename matches the configured cgi_pattern.

do_file() is called from static void handle_request(void) for any requested file, and for any file that may serve as directory index.

handle_request() is called from int main(int argc, char ** argv) in the client-handling process.

If SSL is not enabled, do_cgi() does not call cgi_interpose_output() for NPH CGIs, and so, no extra output is generated.

To fix this problem, rewrite the following lines from cgi_interpose_output() in mini_httpd.c

    if ( ! parse_headers )
        {
        /* If we're not parsing headers, write out the default status line
        ** and proceed to the echo phase.
        */
        char http_head[] = "HTTP/1.0 200 OK\015\012";
        (void) my_write( http_head, sizeof(http_head) );
        }
    else

to just

    if ( parse_headers )

This eleminates all extra output when handling output from NPH CGIs (parse_headers is false).

Script to demonstrate the problem and script output are attached.
The script needs netcat and OpenSSL. Note that in SSL mode, two HTTP status lines are returned when requesting data from the NPH CGI: "HTTP/1.0 200 OK" from mini_httpd, and "HTTP/1.0 418 I'm a teapot" from the NPH CGI. Only the second one should be returned.

(OpenSSL error output is redirected to /dev/null in s_client mode because s_client does not really like the self-signed certificate, but that's not relevant here.)

Best regards
Alexander Foken

-- System Information:
Debian Release: trixie/sid
  APT prefers unstable
  APT policy: (500, 'unstable')
Architecture: amd64 (x86_64)

Kernel: Linux 6.6.13-amd64 (SMP w/2 CPU threads; PREEMPT)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE=en_US:en
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages mini-httpd depends on:
ii  init-system-helpers  1.66
ii  libc6                2.37-14
ii  libcrypt1            1:4.4.36-4
ii  libssl3              3.1.4-2

Versions of packages mini-httpd recommends:
ii  apache2-utils  2.4.58-1+b1

mini-httpd suggests no packages.

-- no debconf information
alex@debian-sid:~$ ./test-ssl-nph-cgi.sh
Generating SSL certificate:
===========================

..+...+....+.....+.+...............+...+..+++++++++++++++++++++++++++++++++++++++*..+.+.....+....+.....+......+...............+.+...+.....+....+.....+.+..+.......+.....+.+....................+....+...........+.+.....+......+...+.+...+........+++++++++++++++++++++++++++++++++++++++*..+.....+.+...+...+...+......+......+.................+.+......+........+......+.+.........+.....+....+..+..........+.........+.....+.......+..+......+.......+...............+.....+..........+..+......+.+...+........+............+...+....+...........+...+................+...+..+...............+.+............+............+...+........+.........+.............+..+....+.........+...+.................+.+...........++++++
...+++++++++++++++++++++++++++++++++++++++*.........+............+++++++++++++++++++++++++++++++++++++++*.+............+..........+........+......+.......+..............+.+............+.....+......+............+...+.+.........+.....+......+.+..+..........+..+.............+......+...........+...+.......+........................+......+.....+....+.................+............+.......+..............++++++
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:de
State or Province Name (full name) [Some-State]:bla
Locality Name (eg, city) []:bla
Organization Name (eg, company) [Internet Widgits Pty Ltd]:bla
Organizational Unit Name (eg, section) []:bla
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:root@localhost

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Certificate request self-signature ok
subject=C = de, ST = bla, L = bla, O = bla, OU = bla, CN = localhost, 
emailAddress = root@localhost

Generating nph-test.cgi:
========================

Generating index.html:
======================

Running mini-httpd in non-SSL mode:
===================================


Requesting index.html:
=====================

GET / HTTP/1.0

HTTP/1.0 200 Ok
Server: mini_httpd/1.30 26Oct2018
Date: Sun, 25 Feb 2024 17:42:43 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 67
Last-Modified: Sun, 25 Feb 2024 17:42:42 GMT
Connection: close

<!DOCTYPE html>
<html>
<title>Hello</title>
<h1>Hello</h1>
</html>

Requesting nph-test.cgi:
========================

GET /nph-test.cgi HTTP/1.0

HTTP/1.0 418 I'm a teapot
Content-Type: text/plain

I'm a teapot

Stopping mini-httpd
===================

/usr/sbin/mini_httpd: exiting due to signal 15
waitpid: PID 1601 has exited, skipping

Running mini-httpd in SSL mode:
===============================


Requesting index.html:
=====================

GET / HTTP/1.0

HTTP/1.0 200 Ok
Server: mini_httpd/1.30 26Oct2018
Date: Sun, 25 Feb 2024 17:42:44 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 67
Last-Modified: Sun, 25 Feb 2024 17:42:42 GMT
Connection: close

<!DOCTYPE html>
<html>
<title>Hello</title>
<h1>Hello</h1>
</html>

Requesting nph-test.cgi:
========================

GET /nph-test.cgi HTTP/1.0

HTTP/1.0 200 OK
HTTP/1.0 418 I'm a teapot
Content-Type: text/plain

I'm a teapot

Stopping mini-httpd
===================

/usr/sbin/mini_httpd: exiting due to signal 15
alex@debian-sid:~$
#!/bin/bash
mkdir -p /tmp/web
cd /tmp/web
if [ ! -f server.pem ] ; then
        echo "Generating SSL certificate:"
        echo "==========================="
        echo ""
        openssl req -nodes -new > cert.csr
        openssl x509 -in cert.csr -out cert.pem -req -signkey privkey.pem -days 
1000
        cat privkey.pem cert.pem > server.pem
        echo ""
fi
echo "Generating nph-test.cgi:"
echo "========================"
echo ""
cat > nph-test.cgi <<'__magic__'
#!/bin/bash
echo -en "HTTP/1.0 418 I'm a teapot\r\n"
echo -en "Content-Type: text/plain\r\n"
echo -en "\r\n"
echo -en "I'm a teapot\r\n"
__magic__
chmod 755 nph-test.cgi
echo "Generating index.html:"
echo "======================"
echo ""
cat > index.html << '__magic__'
<!DOCTYPE html>
<html>
<title>Hello</title>
<h1>Hello</h1>
</html>
__magic__
echo "Running mini-httpd in non-SSL mode:"
echo "==================================="
echo ""
/usr/sbin/mini_httpd -h 127.0.0.1 -p 8080 -c '**.cgi' -i mini_httpd.pid
sleep 1
echo ""
echo "Requesting index.html:"
echo "====================="
echo ""
echo -en "GET / HTTP/1.0\r\n\r\n" | tee /dev/stderr | nc 127.0.0.1 8080
echo ""
echo "Requesting nph-test.cgi:"
echo "========================"
echo ""
echo -en "GET /nph-test.cgi HTTP/1.0\r\n\r\n" | tee /dev/stderr | nc 127.0.0.1 
8080
echo ""
echo "Stopping mini-httpd"
echo "==================="
echo ""
PID="$(cat mini_httpd.pid)"
kill -TERM "$PID"
waitpid -e $PID
echo ""
echo "Running mini-httpd in SSL mode:"
echo "==============================="
echo ""
/usr/sbin/mini_httpd -h 127.0.0.1 -p 8443 -S -E server.pem -c '**.cgi' -i 
mini_httpd.pid
sleep 1
echo ""
echo "Requesting index.html:"
echo "====================="
echo ""
echo -en "GET / HTTP/1.0\r\n\r\n" | tee /dev/stderr |  openssl s_client 
-connect 127.0.0.1:8443 -quiet 2> /dev/null
echo ""
echo "Requesting nph-test.cgi:"
echo "========================"
echo ""
echo -en "GET /nph-test.cgi HTTP/1.0\r\n\r\n" | tee /dev/stderr | openssl 
s_client -connect 127.0.0.1:8443 -quiet 2> /dev/null
echo ""
echo "Stopping mini-httpd"
echo "==================="
echo ""
PID="$(cat mini_httpd.pid)"
kill -TERM "$PID"
waitpid -e $PID

Reply via email to