Login fails silently on multiple PHPSESSID cookies
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:
- Get two
PHPSESSIDcookies in the jar. How? That's another issue to investigate :) - Go to
https://10.0.2.1/orhttps://[fd41:bb86:195d:2241:1614:4bff:feb3:5680]/ - Enter login & password
[Press enter]POSTis redirected back to /index.php- ✔️
audit 9712 - [meta sequenceId="105190"] user root authenticated successfully for WebGui [using OPNsense\Auth\Services\WebGui + OPNsense\Auth\Local] - ✔️
audit 9712 - [meta sequenceId="105191"] /index.php: Successful login for user 'root' from: 10.91.66.130 - 💥
/index.phpshows login form once again, as if nothing happened at all
Describe alternatives you considered
- Private browsing works just fine.
- Cleaning
/var/lib/php/sessionsdoes not help.
Screenshots
POST / sets one cookie on top of existing ones:
GET /index.php overrides another cookie to avoid session fixation, but the browser still sends two of them.
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.
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.
I also think that replacing
PHPSESSIDwith something likeSID-${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.