Php_fastcgi phishing redirection

1. Caddy version (caddy version):

v2.4.6 h1:HGkGICFGvyrodcqOOclHKfvJC0qTU7vny/7FhYp9hNw=

2. How I run Caddy:

Caddy is started with supervisor

a. System environment:

Docker / Alpine

b. Command:

/usr/local/bin/caddy run --config /etc/caddy/Caddyfile --adapter caddyfile

c. Service/unit/compose file:

Paste full file contents here.
Make sure backticks stay on their own lines,
and the post looks nice in the preview pane.

d. My complete Caddyfile or JSON config:

:80, :{$PORT} {
    root * /app/{$PUBLIC_DIR}
    
    php_fastcgi 127.0.0.1:9000

    file_server

    log {
        output file /var/log/access.log
    }

    push
    encode gzip
}

3. The problem Iā€™m having:

When accessing this URL, php_fastcgi redirects to example.com, when trying to add the trailing slash

http://mysite.com/%5cexample.com/%2f%2e%2e

I added this to fix it:

redir "/\*" / 308

4. Error messages and/or full log output:

5. What I already tried:

redir "/\*" / 308

fixes the issue for me but the default php_fastcgi has a security hole

6. Links to relevant resources:

Iā€™m not able to reproduce it. My (simplified) config:

{
	debug
}
localhost {
	php_fastcgi 127.0.0.1:9000
	file_server
	log
}

Logs:

2021/12/29 20:14:47.856	DEBUG	http.handlers.file_server	sanitized path join	{"site_root": ".", "request_path": "//example.com//..", "result": "."}
2021/12/29 20:14:47.856	DEBUG	http.handlers.file_server	no index file in directory	{"path": ".", "index_filenames": ["index.html", "index.txt"]}
2021/12/29 20:14:47.856	ERROR	http.log.access	handled request	{"request": {"remote_addr": "127.0.0.1:57927", "proto": "HTTP/2.0", "method": "GET", "host": "localhost", "uri": "//example.com/%2f..", "headers": {"Sec-Fetch-User": ["?1"], "Sec-Gpc": ["1"], "User-Agent": ["Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"], "Accept-Language": ["en-US,en;q=0.5"], "Accept-Encoding": ["gzip, deflate, br"], "Dnt": ["1"], "Upgrade-Insecure-Requests": ["1"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"], "Sec-Fetch-Dest": ["document"], "Sec-Fetch-Mode": ["navigate"], "Sec-Fetch-Site": ["none"], "Te": ["trailers"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "proto_mutual": true, "server_name": "localhost"}}, "common_log": "127.0.0.1 - - [29/Dec/2021:23:14:47 +0300] \"GET //example.com/%2f.. HTTP/2.0\" 404 0", "user_id": "", "duration": 0.00019171, "size": 0, "status": 404, "resp_headers": {"Server": ["Caddy"]}}

As you can see in the first log line, caddy sanitizes the path and reduces it to ., which is the root, which eventually serves 404. Am I missing something? Can you investigate if your PHP app is issuing any redirects?

@Mohammed90 the request never makes it to my PHP app, I added a var_dump() in my index.php file and I never see it. I also donā€™t see anything in the logs of the PHP app.

hereā€™s the Caddyfile, I actually removed the headers, but I think it might be part of the problem:

{
   debug
}
:80, :{$PORT} {
    root * /app/{$PUBLIC_DIR}

    php_fastcgi 127.0.0.1:9000
    file_server

    header / {
        # Enable cross-site filter (XSS) and tell browser to block detected attacks
        X-XSS-Protection "1; mode=block"
        # Prevent some browsers from MIME-sniffing a response away from the declared Content-Type
        X-Content-Type-Options "nosniff"
        # Disallow the site to be rendered within a frame (clickjacking protection)
        X-Frame-Options "DENY"
    }

    log {
        output file /var/log/access.log
    }

    push
    encode gzip
}

Logs:

{"level":"info","ts":1640809716.4354498,"logger":"http.log.access.log0","msg":"handled request","request":{"remote_addr":"172.22.0.7:44506","proto":"HTTP/1.1","method":"GET","host":"mysite.com","uri":"/%5cexample.com/%2f%2e%2e","headers":{"User-Agent":["curl/7.77.0"],"Accept":["*/*"],"X-Forwarded-For":["172.22.0.1"],"X-Forwarded-Proto":["http"],"Accept-Encoding":["gzip"]}},"common_log":"172.22.0.7 - - [29/Dec/2021:20:28:36 +0000] \"GET /%5cexample.com/%2f%2e%2e HTTP/1.1\" 308 0","user_id":"","duration":0.000210542,"size":0,"status":308,"resp_headers":{"Content-Type":[],"Server":["Caddy"],"Location":["/\\example.com//../"]}}

Can you please reduce it to a minimally reproducible example?

  • Remove bits from the config as long as it still works (I highly doubt push, header, encode are needed)
  • Remove the environment variables and hard-code it to something.
  • Use curl -v to make the requests (not a browser) so itā€™s easier to reproduce.
  • Remove files from the configured root location that arenā€™t necessary (Iā€™m guessing only index.php needs to be there).

hereā€™s the full config, with the curl -v request:

/etc/caddy # curl -v "http://localhost/%5cexample.com/%2f%2e%2e"
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /%5cexample.com/%2f%2e%2e HTTP/1.1
> Host: localhost
> User-Agent: curl/7.80.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 308 Permanent Redirect
< Location: /\example.com//../
< Server: Caddy
< Date: Thu, 30 Dec 2021 03:28:50 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact
/etc/caddy # cat Caddyfile
{
   debug
}
:80, :9001 {
    root * /app/public

    php_fastcgi 127.0.0.1:9000
    file_server

    log {
        output file /var/log/access.log
    }
}
/etc/caddy # ls -la /app/public/
total 16
drwxr-xr-x    9 root     root           288 Dec 29 17:50 .
drwxr-xr-x   46 root     root          1472 Dec 29 15:15 ..
drwxrwxrwx    3 root     root            96 Dec 29 15:09 bundles
-rw-r--r--    1 root     root          1150 Nov 22  2019 favicon.ico
drwxr-xr-x   10 root     root           320 Sep 30 16:39 images
-rw-r--r--    1 root     root           710 Dec 29 17:50 index.php
-rw-r--r--    1 root     root           106 Nov 22  2019 robots.txt

Alright, I can reproduce. Thanks for that.

So the problem is with the first chunk of config that comes with php_fastcgi, see the expanded form:

In particular, itā€™s this part:

	# Add trailing slash for directory requests
	@canonicalPath {
		file {path}/index.php
		not path */
	}
	redir @canonicalPath {path}/ 308

Replacing php_fastcgi + file_server in the config with just the above exhibits the reported behaviour.

I donā€™t have a suggested fix though. Iā€™m not sure what should happen instead.

What would you suggest?

As @Mohammed90 mentioned, it would be best for Caddy to sanitize the path before php_fastcgi

Right now the redirection is for a path with no trailing slash but it should also be for path that donā€™t start with /\

I have a fix but it feels more like a hack :slight_smile: , before php_fastcgi

redir "/\*" / 308

Hmm. Maybe we need a {http.request.sanitized_path} placeholder which is the path, but cleaned.

Then we could do:

redir @canonicalPath {http.request.sanitized_path}/ 308

Do you think this would fix it?

it seems to fix it :slight_smile: and the rest of the app (Symfony backend) works perfectly

/etc/caddy # curl -v "http://localhost/%5cexample.com/%2f%2e%2e"
*   Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /%5cexample.com/%2f%2e%2e HTTP/1.1
> Host: localhost
> User-Agent: curl/7.80.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 308 Permanent Redirect
< Location: /
< Server: Caddy
< Date: Thu, 30 Dec 2021 04:16:33 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact
/etc/caddy # cat Caddyfile
{
   debug
}
:80, :{$PORT} {
    root * /app/{$PUBLIC_DIR}

    route {
	# Add trailing slash for directory requests
	@canonicalPath {
		file {path}/index.php
		not path */
	}
	redir @canonicalPath {http.request.sanitized_path}/ 308

	# If the requested file does not exist, try index files
	@indexFiles file {
		try_files {path} {path}/index.php index.php
		split_path .php
	}
	rewrite @indexFiles {http.matchers.file.relative}

	# Proxy PHP files to the FastCGI responder
	@phpFiles path *.php
	reverse_proxy @phpFiles 127.0.0.1:9000 {
		transport fastcgi {
			split .php
		}
	}
    }

    file_server

    log {
        output file /var/log/access.log
    }
}

To be clear, this does not exist in Caddy right now. So thatā€™s a false-negative.

oh lol, ok that would be great if it did then :slight_smile:

I dug a bit deeper, I think there was actually a problem with the path matcher (i.e. the part at not path */) which would cause it to still match that kind of request.

I opened a PR with a proposed fix:

If you could make a build of Caddy from that branch (you can easily do so by running xcaddy build fix-match-path-clean, see the instructions on Build from source ā€” Caddy Documentation)

@francislavoie thanks so much for the quick fix, this looks great to me.

1 Like

If you wouldnā€™t mind, can you think of other kinds of URLs that might be problematic here?

@francislavoie my team found the problem with php_fastcgi using this list:

This topic was automatically closed after 30 days. New replies are no longer allowed.