Skip to content

Commit bff2f2c

Browse files
Copilotasselitx
authored andcommitted
HPCC-35595 Improve ESP session ID security
Migrate ESP session IDs from 32-bit to 128-bit secure strings, addressing session hijacking vulnerability. - Change session token type from unsigned to const char* in all security interfaces. - Update createHTTPSession() to generate 128-bit cryptographically secure random session IDs. - Implement OpenSSL RAND_bytes for secure random generation. - Fallback to previous hash-based tokens when no OpenSSL or OpenSSL failures. - Update all session validation and lookup functions to use string-based XPath queries. - Remove numeric readCookie function, use string-based cookie reading. - Update session ID documentation to show new 128-bit hex format. - Add collision detection retry loop with max 3 attempts, do not re-use sessions. Session token in SecUser distinct from sessionID- can be used to verify auth but can't be used to lookup sessionID (wasn't used that way). - Add sessionIDToToken() helper to hash 128-bit session ID to 32-bit token. - Update all setSessionToken calls to use hash conversion. - Maintains backward compatibility with existing sessionToken interfaces. - Session IDs remain 128-bit for security while tokens remain 32-bit unsigned. Maintain separate external ID that can't derive session ID from. - Add SHA256 hashing for PropSessionExternalID (when OpenSSL available). - Fallback to hashc when OpenSSL not present. - External ID is now different from internal session ID for admin security. - Add local testing script with option to adjust expectations when OpenSSL is not available. - Fix config XSLT to allow granting admins WsESPControl access. - Remove error logging when using fallback scheme. Print one-time message on startup when OpenSSL unavailable. - Replace logging of SessionID with ExternalSessionID. Signed-off-by: Terrence Asselin <terrence.asselin+copilot@lexisnexisrisk.com>
1 parent 3dcf77e commit bff2f2c

File tree

7 files changed

+1330
-87
lines changed

7 files changed

+1330
-87
lines changed

esp/bindings/http/platform/httpservice.cpp

Lines changed: 150 additions & 76 deletions
Large diffs are not rendered by default.

esp/bindings/http/platform/httpservice.hpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,27 +102,26 @@ class CEspHttpServer : implements IHttpServerService, public CInterface
102102
EspAuthState checkUserAuthPerRequest(EspAuthRequest& authReq);
103103
EspAuthState checkUserAuthPerSession(EspAuthRequest& authReq, StringBuffer& authorizationHeader);
104104
EspAuthState authNewSession(EspAuthRequest& authReq, const char* _userName, const char* _password, const char* sessionStartURL, bool unlock);
105-
EspAuthState authExistingSession(EspAuthRequest& req, unsigned sessionID);
106-
void logoutSession(EspAuthRequest& authReq, unsigned sessionID, IPropertyTree* domainSessions, bool lock);
105+
EspAuthState authExistingSession(EspAuthRequest& req, const char* sessionID);
106+
void logoutSession(EspAuthRequest& authReq, const char* sessionID, IPropertyTree* domainSessions, bool lock);
107107
void askUserLogin(EspAuthRequest& authReq, const char* msg);
108108
bool changeRedirectURL(EspAuthRequest& authReq);
109109
EspAuthState handleUserNameOnlyMode(EspAuthRequest& authReq);
110110
EspAuthState handleAuthFailed(bool sessionAuth, EspAuthRequest& authReq, bool unlock, const char* msg);
111111
EspHttpBinding* getEspHttpBinding(EspAuthRequest& req);
112112
bool isAuthRequiredForBinding(EspAuthRequest& req);
113113
void authOptionalGroups(EspAuthRequest& req);
114-
unsigned createHTTPSession(IEspContext* ctx, EspHttpBinding* authBinding, const char* userID, const char* loginURL);
114+
const char* createHTTPSession(IEspContext* ctx, EspHttpBinding* authBinding, const char* userID, const char* loginURL, StringBuffer& sessionID);
115115
void timeoutESPSessions(EspHttpBinding* authBinding, IPropertyTree* espSessions);
116116
void addCookie(const char* cookieName, const char *cookieValue, int maxAgeSec, bool httpOnly);
117117
void clearCookie(const char* cookieName);
118118
void clearSessionCookies(EspAuthRequest& authReq);
119-
unsigned readCookie(const char* cookieName);
120119
const char* readCookie(const char* cookieName, StringBuffer& cookieValue);
121120
void sendLockResponse(bool lock, bool error, const char* msg);
122121
void sendAuthorizationMsg(EspAuthRequest& authReq);
123122
void sendGetAuthTypeResponse(EspAuthRequest& authReq, const char* authType);
124123
void createGetSessionTimeoutResponse(StringBuffer& resp, ESPSerializationFormat format, IPropertyTree* sessionTree);
125-
void resetSessionTimeout(EspAuthRequest& authReq, unsigned sessionID, StringBuffer& resp, ESPSerializationFormat format, IPropertyTree* sessionTree);
124+
void resetSessionTimeout(EspAuthRequest& authReq, const char* sessionID, StringBuffer& resp, ESPSerializationFormat format, IPropertyTree* sessionTree);
126125
void sendException(EspAuthRequest& authReq, unsigned code, const char* msg);
127126
void sendInternalError(bool loggedDetails);
128127
void sendMessage(const char* msg, const char* msgType);

esp/platform/espcontext.hpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,24 +81,24 @@ static const char* const PropSessionLoginURL = "@loginurl";
8181
<Sessions>
8282
<Process name="myesp">
8383
<Application port="8010">
84-
<Session_3831947145 createtime="1497376914"
85-
id="3831947145"
84+
<Session_a3f5e7b9c2d4f81e0b2c4d6f8a1c3e5f createtime="1497376914"
85+
id="a3f5e7b9c2d4f81e0b2c4d6f8a1c3e5f"
8686
lastaccessed="1497377015"
8787
timeoutAt="1497477015"
8888
loginurl="/"
8989
netaddr="10.176.152.200"
9090
userid="user1"/>
91-
<Session_4106750941 createtime="1497377427"
92-
id="4106750941"
91+
<Session_b4c6d8e0a2f4b81c3d5e7f9a1c3e5f7b createtime="1497377427"
92+
id="b4c6d8e0a2f4b81c3d5e7f9a1c3e5f7b"
9393
lastaccessed="1497377427"
9494
timeoutAt="1497477427"
9595
loginurl="/"
9696
netaddr="10.176.152.200"
9797
userid="user2"/>
9898
</Application>
9999
<Application port="8002">
100-
<Session_3680948651 createtime="1497376989"
101-
id="3680948651"
100+
<Session_c5d7e9f1b3a5c92d4e6f8a0b2c4d6e8f createtime="1497376989"
101+
id="c5d7e9f1b3a5c92d4e6f8a0b2c4d6e8f"
102102
lastaccessed="1497377003"
103103
timeoutAt="1497477003"
104104
loginurl="/"

esp/platform/espp.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,9 @@ int init_main(int argc, const char* argv[])
572572
openEspLogFile(envpt.get(), procpt.get());
573573

574574
DBGLOG("Esp starting %s", hpccBuildInfo.buildTag);
575+
#ifndef _USE_OPENSSL
576+
UWARNLOG("OpenSSL not in use. ESP Session ID generation is less secure. To use OpenSSL, build ESP with OpenSSL support.");
577+
#endif
575578

576579
StringBuffer componentfilesDir;
577580
if(procpt->hasProp("@componentfilesDir"))

initfiles/componentfiles/configxml/@temp/esp_service_WsSMC.xsl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,9 @@ This is required by its binding with ESP service '<xsl:value-of select="$espServ
617617
<xsl:for-each select="../EspProcess[Authentication/@ldapServer=$ldapservername]/EspBinding">
618618
<Binding name="{@name}" service="{@service}" port="{@port}" basedn="{@resourcesBasedn}" workunitsBasedn="{@workunitsBasedn}"/>
619619
</xsl:for-each>
620+
<xsl:for-each select="../EspProcess[Authentication/@ldapServer=$ldapservername]/EspControlBinding">
621+
<Binding name="{@name}" service="{@service}" port="{@port}" basedn="{@resourcesBasedn}" workunitsBasedn="{@workunitsBasedn}"/>
622+
</xsl:for-each>
620623
</Resources>
621624
</EspService>
622625

testing/esp/sessionid/README.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Overview
2+
3+
These are tests for the ESP session ID moving to a 128-bit cryptographically secure format.
4+
5+
# Implementation
6+
7+
## Prerequisites and Assumptions
8+
9+
The tests require a running ESP with security enabled, so they are expected to be run manually. These assumptions about the environment are set as variables in the test script but can be overridden on the CLI:
10+
11+
1. HTTP default, but can support HTTPS
12+
2. Target ESP host is 127.0.0.1 (port 8010)
13+
3. Regular user ID is `hpcc_user`
14+
4. Admin user ID is `hpcc_admin`
15+
16+
The user passwords are expected to be set as environmental values but can be specified on the CLI:
17+
18+
1. Regular user password in `$HPCC_TEST_USER_PW`
19+
2. Admin user password in `$HPCC_TEST_ADMIN_PW`
20+
21+
The ESP must have WSESPControl authorized for the Admin user, and that user must belong to a group of the same name as the LDAP Admin group.
22+
23+
### Python Dependencies
24+
25+
Install required Python packages:
26+
27+
```bash
28+
pip install requests
29+
```
30+
31+
## Running Tests
32+
33+
### Basic Usage
34+
35+
Set environment variables and run all tests:
36+
37+
```bash
38+
export HPCC_TEST_USER_PW=your_user_password
39+
export HPCC_TEST_ADMIN_PW=your_admin_password
40+
python3 sessionid_test.py
41+
```
42+
43+
### Advanced Options
44+
45+
```bash
46+
# Custom host and port
47+
python3 sessionid_test.py --host 192.168.1.100 --port 8010
48+
49+
# Use HTTPS
50+
python3 sessionid_test.py --protocol https
51+
52+
# Custom credentials
53+
python3 sessionid_test.py --user myuser --user-pw mypass --admin myadmin --admin-pw adminpass
54+
55+
# Run specific test
56+
python3 sessionid_test.py -t test_new_format_validation
57+
58+
# Verbose output
59+
python3 sessionid_test.py -v
60+
61+
# Show help
62+
python3 sessionid_test.py --help
63+
```
64+
65+
## Tests
66+
67+
The test suite (`sessionid_test.py`) implements the following automated tests using ESP APIs. Tests pull data from response fields, headers and cookies as needed. All tests run as regular user unless otherwise specified.
68+
69+
### Test 1: New Format Validation
70+
- Login via `/esp/login`
71+
- Extract `ESPSessionID` cookie
72+
- Validate session ID is 32-character hex string (128-bit)
73+
- Make authenticated request to `/WsSMC/Activity`
74+
- Verify successful authentication with new session ID format
75+
76+
### Test 2: Incorrect Format/Unknown Session ID Rejection
77+
- Craft multiple malformed session IDs:
78+
- Empty string
79+
- Too short
80+
- Too long (33+ characters)
81+
- Non-hex characters
82+
- Wrong format (with dashes, etc.)
83+
- Attempt authenticated requests with each invalid session ID
84+
- Confirm expected failure (HTTP 401/403 or redirect to login)
85+
86+
### Test 3: ws_espcontrol Session Timeout
87+
1. Login as regular user
88+
2. Verify session is active with authenticated request
89+
3. Login as admin
90+
4. Use `/WSESPControl/SessionQuery` to find user's external session ID
91+
5. Call `/WSESPControl/SetSessionTimeout` with `TimeoutMinutes=1`
92+
6. Wait 90 seconds (timeout + ESP cleanup cycle buffer)
93+
7. Confirm session has timed out (request fails or redirects)
94+
95+
### Test 4: Logout Session Invalidation
96+
1. Login as user
97+
2. Extract session ID and verify it works
98+
3. Call `/esp/logout`
99+
4. Attempt authenticated request with old session cookie
100+
5. Confirm session is invalid (HTTP 401/403 or redirect)
101+
102+
### Test 5: Concurrent Sessions Load Test
103+
- Use `ThreadPoolExecutor` to create multiple concurrent login sessions
104+
- All threads login as same user simultaneously
105+
- Capture session IDs from all successful logins
106+
- Verify each session is active by making authenticated request to `/WsSMC/Activity`
107+
- Confirm ESP allows multiple concurrent sessions per user
108+
- Confirm all captured session IDs are functional
109+
110+
### Test 6: Session ID Uniqueness
111+
1. Login as admin for session queries
112+
2. Perform many sequential logins as regular user (configurable count, default 1000)
113+
3. For each iteration:
114+
- Login and capture session ID from cookie
115+
- Use `/WSESPControl/SessionQuery` to retrieve external session ID
116+
- Store both session ID and external ID
117+
- Logout to clean up session
118+
4. Verify all session IDs are unique (no duplicates in set)
119+
5. Verify all external IDs are unique (no duplicates in set)
120+
6. Verify external IDs differ from their corresponding session IDs
121+
7. Report statistics: unique counts, duplicate counts, failed logins/queries
122+
123+
### Test 7: Active Session Collision Detection
124+
1. Login as admin for session queries
125+
2. Create many concurrent active sessions (configurable count, default 1000)
126+
3. Keep all `ESPSession` objects alive (no logout)
127+
4. Verify all session IDs collected are unique
128+
5. Use `/WSESPControl/SessionQuery` to retrieve all external session IDs
129+
6. Extract all external IDs from admin API response
130+
7. Verify count of external IDs matches count of active sessions
131+
8. Verify all external IDs are unique
132+
9. Verify external IDs differ from their session IDs
133+
10. Confirm collision detection works under realistic concurrent load

0 commit comments

Comments
 (0)