joomla-framework / session

Joomla Framework Session Package

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Session incorrectly invalidates when REMOTE_ADDR changes, which happens behind proxies.

PhilETaylor opened this issue · comments

Reposting from joomla/joomla-cms#35244 (comment) for visibility, and because the root issue is actually from this repo.


So, here is the root issue

What is happening is, I login, and as loing as I continue to browse quickly moving through pages, my session is being handled by one of the cloudflare ray nodes (evidenced by the Response header: cf-ray: 68b7a2854e62cdb3-CDG)

If I go for a coffee, or just wait a few mins and then try to navigate to a new page then my computer connects to a different Cloudflare ray node (as evidenced by the response header cf-ray: 68b7b6ad1b0f39ab-CDG)

As CloudFlare is nothing other than a reverse proxy (ok, with a few bells and whistles), CloudFlare requests as seen by Joomla look like any other proxied requests, and the $_SERVER['REMOTE_ADDR'] is the CloudFlare Proxy Server address and NOT the IP address of my computer (or more correctly, my public IP address). My real IP is pushed into several other headers ("2a02:c28:FACE:FACE:FACE:FACE:FACE:2e2e" in the examples below is my real IP)

Here is an example of the request headers sent to a Joomla site by Cloudflare (Ive manipulated slightly to prevent you seeing my real ipv6 etc)

  "Host" => "example.com"
  "X-Real-IP" => "2a02:c28:FACE:FACE:FACE:FACE:FACE:2e2e"
  "X-Remote-IP" => "108.162.229.27"
  "Accept-Encoding" => "gzip"
  "cf-ipcountry" => "JE"
  "X-Forwarded-For" => "2a02:c28:FACE:FACE:FACE:FACE:FACE:2e2e"
  "cf-ray" => "68b7bd27094a3317-CDG"
  "x-forwarded-proto" => "https"
  "cf-visitor" => "{"scheme":"https"}"
  "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
  "User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15"
  "Accept-Language" => "en-GB,en;q=0.9"
  "Cookie" => "0e271dc10c723185f1bb6c8ab5fca21b=4e15f8f87709624da62afc4a352afc8d"
  "cf-connecting-ip" => "2a02:c28:FACE:FACE:FACE:FACE:FACE:2e2e"
  "cdn-loop" => "cloudflare"
  "Content-Length" => "0"

This is converted by PHP to a $_SERVER global array like this (again slightly modified by me)

array:47 [▼
  "PATH" => "/usr/local/bin:/usr/bin:/bin"
  "TEMP" => "/tmp"
  "TMP" => "/tmp"
  "TMPDIR" => "/tmp"
  "PWD" => "/"
  "HTTP_ACCEPT" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
  "HTTP_ACCEPT_ENCODING" => "gzip"
  "HTTP_ACCEPT_LANGUAGE" => "en-GB,en;q=0.9"
  "CONTENT_LENGTH" => "0"
  "HTTP_COOKIE" => "0e271dc10c723185f1bb6c8ab5fca21b=4e15f8f87709624da62afc4a352afc8d"
  "HTTP_HOST" => "example.com"
  "HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15"
  "HTTP_X_FORWARDED_FOR" => "2a02:c28:FACE:FACE:FACE:FACE:FACE:2e2e"
  "HTTP_X_REAL_IP" => "2a02:c28:FACE:FACE:FACE:FACE:FACE:2e2e"
  "HTTP_X_REMOTE_IP" => "108.162.229.27"
  "HTTP_CF_IPCOUNTRY" => "JE"
  "HTTP_CF_RAY" => "68b7bd27094a3317-CDG"
  "HTTP_X_FORWARDED_PROTO" => "https"
  "HTTP_CF_VISITOR" => "{"scheme":"https"}"
  "HTTP_CF_CONNECTING_IP" => "2a02:c28:FACE:FACE:FACE:FACE:FACE:2e2e"
  "HTTP_CDN_LOOP" => "cloudflare"
  "UNIQUE_ID" => "YTiafCZInhA1Ej6fdUzPsgAABco"
  "SCRIPT_URL" => "/headers.php"
  "SCRIPT_URI" => "http://example.com/headers.php"
  "SERVER_SIGNATURE" => ""
  "SERVER_SOFTWARE" => "Apache"
  "SERVER_NAME" => "example.com"
  "SERVER_ADDR" => "88.88.88.88"
  "SERVER_PORT" => "80"
  "REMOTE_ADDR" => "108.162.229.27"
  "DOCUMENT_ROOT" => "/home/ttfyouvb/example.com"
  "REQUEST_SCHEME" => "http"
  "CONTEXT_PREFIX" => ""
  "CONTEXT_DOCUMENT_ROOT" => "/home/ttfyouvb/example.com"
  "SERVER_ADMIN" => "phil@phil-taylor.com"
  "SCRIPT_FILENAME" => "/home/ttfyouvb/example.com/headers.php"
  "REMOTE_PORT" => "41582"
  "SERVER_PROTOCOL" => "HTTP/1.1"
  "REQUEST_METHOD" => "GET"
  "QUERY_STRING" => ""
  "REQUEST_URI" => "/headers.php"
  "SCRIPT_NAME" => "/headers.php"
  "PHP_SELF" => "/headers.php"
  "REQUEST_TIME_FLOAT" => 1631099516.0671
  "REQUEST_TIME" => 1631099516
  "argv" => []
  "argc" => 0
]

As you can see $_SERVER['REMOTE_ADDR'] is a ipv4 (even though Im personally connecting to CloudFlare with ipv6), and has a value of 108.162.229.27 which is the CloudFlare ray proxy address Joomla is seeing.

Now, the problem is that if I come back in 10 mins (or even 60 seconds after being idle) this REMOTE_ADDR value will change.

However, Joomla CMS layers use the joomla-framework/utilities package to detect the correct IP - and if you have correct set the "Behind Loadbalancer" configuration in Joomla Global Configuration, then that package will return the value of HTTP_X_FORWARDED_FOR and not the value for REMOTE_ADDR - which is the correct process (I know, because I recently fixed a security issue with this feature).

HOWEVER, Joomla 4 CMS layer, also uses the joomla-framework/session package to handle session management, and within that package there is a hardcoded call to $remoteAddr = $this->input->server->getString('REMOTE_ADDR', ''); this is the root of all the evil, because, as I have pointed out above, once CloudFlare uses a different ray proxy, the REMOTE_ADDR will change... and because the joomla-framework/session package @2.0-dev is so strict (for security reasons) and is unaware of the Joomla CMS Layer allowing "Behind Loadbalancer" configuration (more specifically allowing IP Overrides, the use of HTTP_X_FORWARDED_FOR instead of using REMOTE_ADDR), it then goes on to check if the current REMOTE_ADDR is the same as the IP address that started the session (earlier in the day) and because they dont match, BANG!!!! it throws an exception (which is silently caught and replaced with a FALSE), which in turn starts a new session and you are logged out....

Therefore the fix for this lies deep in the joomla-framework/session package, which needs to become aware of the Joomla CMS layer's configuration to allow IP Overides (the use of HTTP_X_FORWARDED_FOR (and others) when behind a proxy and not just blindly use REMOTE_ADDR)...

here:

$remoteAddr = $this->input->server->getString('REMOTE_ADDR', '');

And breath....

Now on to taking questions:

The session for my site is via database and in less than 1 minute, if I try to save something or if I don't navigate, I get logged out.

Because your IP Address in REMOTE_ADDR has changed, as you are now using a different CloudFlare Proxy server. Not your choice. And because joomla-framework/session is hard coded (incorrectly) to use the REMOTE_ADDR when checking for session security.

Why J3 works like a charm while J4 has a lot of issues with the sessions?

Because Joomla 4 uses a more secure session management solution. And its not "a lot of issues" its one issue with one cause and only when used behind a proxy such as CloudFlare. Lets keep this in context.

This now probably needs @wilsonge and @nibra to decide what the correct fix to apply is.

The solution is probably

  • Make joomla-framework/utilities a dependancy of joomla-framework/session so that the detection of the IP can be reused
  • Make joomla-framework/session aware of the CMS "Behind Loadbalancer" setting (which when set, already reconfigured joomla-framework/utilities to tell it to allow the IP override.