I found a routing issue in CodeIgniter 4 and have volunteered a fix for it. However, I started thinking today that this might not be entirely secure.

CI4 autorouting starts with the Router::autoRoute function which receives the path portion of a url. E.g., if you request https://www.example.com/subdir/nested-directory/long/path/stuff?blah+blah+blah then CI4, failing to find any matching route, will call the autoRoute function with a $uri of subdir/nested-directory/long/path/stuff.

That function uses explode to split the $uri into $segments along the slashes and then calls (a very poorly named) function, Router::validateRequest which currently looks like this:

	protected function validateRequest(array $segments): array
	{
		$segments = array_filter($segments, function ($segment) {
			return ($segment || ($segment === '0'));
		});
		$segments = array_values($segments);

		$c                 = count($segments);
		$directoryOverride = isset($this->directory);

		// Loop through our segments and return as soon as a controller
		// is found or when such a directory doesn't exist
		while ($c-- > 0)
		{
			$segmentConvert = ucfirst($this->translateURIDashes === true ? str_replace('-', '_', $segments[0]) : $segments[0]);
			$test           = APPPATH . 'Controllers/' . $this->directory . $segmentConvert;

			if (! is_file($test . '.php') && $directoryOverride === false && is_dir($test))
			{
				$this->setDirectory($segmentConvert, true);
				array_shift($segments);
				continue;
			}

			return $segments;
		}

		// This means that all segments were actually directories
		return $segments;
	}

This function should be called something like findController or something. What it does is it strips off each segment, applying ucfirst and optionally mapping dashes to underscores to make a PSR-4 compliant path segment. It iterates through the $segments array until it either a) finds matching controller file or b) the segments no longer match directory names.

This function is taking user input directly from the user-supplied uri request and probes the controller directory looking for a matching controller. We must therefor carefully construct it to prevent mischief. Malicious users should not be able to construct uris which probe for sensitive files or execute arbitrary PHP files on the server.

If anyone could tell me how to construct a custom HTTPS request (telnet? ssh? curl?) where I can send some mischief, I'd appreciate it. For example, I tried this url but the double period (parent directory) segment was evaluated at some prior stage, either by firefox or apache, and the double period path segment doesn't make its way to the validateRequest function: https://www.cidev.com/../subdir/nested-directory. I'd also like to be able to construct uris containing a backspace or delete characters or perhaps multibyte UTF8 codes. I tried using telnet to port 443 and pasted this:

GET /subdir/nested-directory HTTP/1.1 Host:www.example.com

but got a 400 response:

Your browser sent a request that this server could not understand.<br />
Reason: You're speaking plain HTTP to an SSL-enabled server port.<br />
Instead use the HTTPS scheme to access this URL, please.

The apache log file:

127.0.0.1 - - [19/Feb/2021:16:39:45 -0800] "GET / HTTP/1.0" 400 0 "-" "-"

I tried curl, too, but the parent-directory double period gets evaluated prior to the request getting sent:

curl -vk https://www.example.com/subdir/../nested-directory

For that last request, my apache log just shows:

127.0.0.1 - - [19/Feb/2021:16:37:15 -0800] "GET /nested-directory HTTP/1.1" 404 3146 "-" "curl/7.68.0"

I'd also like to know what kind of uri segments I should avoid, especially as this code will be scanning the file system. The code as written takes care to avoid empty string segments, and the setDirectory function removes any periods from any segment. Seems to me we should probably watch out for any reserved characters (i.e., chars not permitted in filenames). Should any such characters arrive in a segment, the code should probably stop scanning the file system and instead assume the segment was intended as input or parameter to be supplied to the controller that handles the request.

Any thoughts or advice would be much appreciated.

    I'm happy to report that I've got a primitive approach to sending raw GET requests to my server via HTTPS. I'm hoping to maybe roll this up into a script so I can automate it but you can connect with this CLI command on my Ubuntu workstation without installing any additional software:

    openssl s_client -crlf -connect www.example.com:443

    this opens the connection and negotiates the TLS details. I can then paste/type in these two lines in rapid succession (or the connection times out):

    GET /subdir/../nested-directory HTTP/1.1
    Host: www.cidev.com

    Each line followed by the <enter> key and then i hit <enter> one extra time for the request to be sent. Note the -crlf option in the original command. According to the man pages, that option:

    -crlf
    This option translated a line feed from the terminal into CR+LF as required by some servers.

    As I had hoped, this sneaky path is not evaluated by the openssl client and does find its way right into the the autoRoute and validateRequest functions described above. Now I just need to figure out how to create a bash or php script so I can package up my request into a single command that just returns the server response. That should allow me to automate things instead of all the copying and pasting and stuff.

      Write a Reply...