core icon indicating copy to clipboard operation
core copied to clipboard

Login fails silently on multiple PHPSESSID cookies

Open darkk opened this issue 1 month ago • 2 comments

Important notices

  • [x] I have read the contributing guide lines at https://github.com/opnsense/core/blob/master/CONTRIBUTING.md
  • [x] I am convinced that my issue is new after having checked both open and closed issues at https://github.com/opnsense/core/issues?q=is%3Aissue

Describe the bug

If the browser sends two PHPSESSID cookies (coming from two different scopes), OPNsense is doomed as session can't be initialized properly. The login fails silently.

I've observed that several times. One box was opnsense: 24.7.12_4 -> 25.1, another one was updated more than once. The browser I used today is a reasonably fresh Firefox 145.0.

To Reproduce

Steps to reproduce the behavior:

  1. Get two PHPSESSID cookies in the jar. How? That's another issue to investigate :)
  2. Go to https://10.0.2.1/ or https://[fd41:bb86:195d:2241:1614:4bff:feb3:5680]/
  3. Enter login & password
  4. [Press enter]
  5. POST is redirected back to /index.php
  6. ✔️ audit 9712 - [meta sequenceId="105190"] user root authenticated successfully for WebGui [using OPNsense\Auth\Services\WebGui + OPNsense\Auth\Local]
  7. ✔️ audit 9712 - [meta sequenceId="105191"] /index.php: Successful login for user 'root' from: 10.91.66.130
  8. 💥 /index.php shows login form once again, as if nothing happened at all

Describe alternatives you considered

  • Private browsing works just fine.
  • Cleaning /var/lib/php/sessions does not help.

Screenshots

POST / sets one cookie on top of existing ones:

Image

GET /index.php overrides another cookie to avoid session fixation, but the browser still sends two of them.

Image

Additional context

I don't know what exactly triggers two PHPSESSID cookies to be stored by Firefox. That's another bug worth investigating, however my friend reported similar issue with their OPNsense box for road-warrior-to-office VPNs, so it's probably not just my machine 😆

Here are two samples from my cookie jar at $HOME/snap/firefox/common/.mozilla/firefox/*/sessionstore-backups/recovery.jsonlz4:

>>> pprint(keyed[('10.0.2.1', 'PHPSESSID')])
[{'host': '10.0.2.1',
  'httponly': True,
  'isPartitioned': True,
  'name': 'PHPSESSID',
  'originAttributes': {'firstPartyDomain': '',
                       'geckoViewSessionContextId': '',
                       'partitionKey': '(https,10.0.2.1)',
                       'privateBrowsingId': 0,
                       'userContextId': 0},
  'path': '/',
  'sameSite': 0,
  'schemeMap': 2,
  'secure': True,
  'value': '98f5c2560301e96be907155747cd115c'},
 {'host': '10.0.2.1',
  'httponly': True,
  'name': 'PHPSESSID',
  'originAttributes': {'firstPartyDomain': '',
                       'geckoViewSessionContextId': '',
                       'partitionKey': '',
                       'privateBrowsingId': 0,
                       'userContextId': 0},
  'path': '/',
  'sameSite': 256,
  'schemeMap': 2,
  'secure': True,
  'value': '98f5c2560301e96be907155747cd115c'}]

>>> pprint(keyed[('[fd41:bb86:195d:2241:1614:4bff:feb3:5680]', 'PHPSESSID')])
[{'host': '[fd41:bb86:195d:2241:1614:4bff:feb3:5680]',
  'httponly': True,
  'isPartitioned': True,
  'name': 'PHPSESSID',
  'originAttributes': {'firstPartyDomain': '',
                       'geckoViewSessionContextId': '',
                       'partitionKey': '(https,[fd41:bb86:195d:2241:1614:4bff:feb3:5680])',
                       'privateBrowsingId': 0,
                       'userContextId': 0},
  'path': '/',
  'sameSite': 0,
  'schemeMap': 2,
  'secure': True,
  'value': '30b8fff244be6972733a45e81070455a'},
 {'host': '[fd41:bb86:195d:2241:1614:4bff:feb3:5680]',
  'httponly': True,
  'name': 'PHPSESSID',
  'originAttributes': {'firstPartyDomain': '',
                       'geckoViewSessionContextId': '',
                       'partitionKey': '',
                       'privateBrowsingId': 0,
                       'userContextId': 0},
  'path': '/',
  'sameSite': 1,
  'schemeMap': 2,
  'secure': True,
  'value': 'ce435bd33c3e625f4d6fa72f1ec56c7b'}]

Environment

OPNsense at 10.0.2.1 was recently updated from 25.1 to 25.7.x. OPNsense at [fd41:bb86:195d:2241:1614:4bff:feb3:5680] was updated from 24.7 to 25.1 long long time ago. Probably, the problem was not noticed before due to Private Browsing being used to configure that box.

darkk avatar Nov 17 '25 18:11 darkk

I also think that replacing PHPSESSID with something like SID-$(md5 -s "$(opnsense-version)|$(cat /etc/machine-id)" | python3 -c 'import sys, binascii, base64; print(base64.b64encode(binascii.unhexlify(sys.stdin.read().strip()), altchars=b"==").decode("ascii").replace("=", "")[:7])') may prevent multiple PHPSESSID cookies from appearing at least in some cases. Both code version and node become parts of the cookie "scope".

It also solves some loosely-related desire to make PHPSESSID configurable: #8365, #8366, #8515. However, that's a different story.

darkk avatar Nov 17 '25 18:11 darkk

I also think that replacing PHPSESSID with something like SID-${machine-id}

It might also be an inferior idea from the security perspective: https://github.com/opnsense/core/pull/8515#issuecomment-3547330784 — it'll enable unsuspecting users shooting themselves in the feet over http://localhost:8080/.

Still, PHPSESSID cookie is contaminated in some upgrade paths.

Replacing PHPSESSID with OPNSIDv1 and bumping version number on every cookie-related code change gives a better UX for users upgrading their routers.

However, using something like OPNSIDv1 also improves UX for cookie stealers as the name becomes unique for a specific software (OPNsense) while PHPSESSID is pretty generic.

That's why I still think that a warning on multiple PHPSESSID instances is a reasonably "balanced" solution for the issue.

darkk avatar Nov 18 '25 12:11 darkk