| Titre | FIT2CLOUD DataEase (open-source BI platform; vulnerable classes in the in-repo `sdk/common` auth filter and `core/core-backend` static-resource server, bundled into the community distribution) audited at released build 2.10.26, commit `0a9eaf6af890bbc91eb |
|---|
| Description | Anonymous file upload and readback through the authentication filter's suffix whitelist in DataEase
Vendor: FIT2CLOUD
Affected versions: audited at released build 2.10.26, commit `0a9eaf6af890bbc91ebcd71a8a8013f917846245` (runtime: production `CoreApplication.jar` v2.10.26); the `endsWithAny` suffix whitelist in `WhitelistUtils` and the shared write/read directories are as shipped at this tag
Vulnerability class: Missing authentication for a write endpoint / unrestricted upload of a file type the application itself serves (CWE-434, with the authentication gap CWE-306)
Preconditions: Anonymous — no account, token, cookie or share link is required for either the write or the read; every request in the proof was sent without credentials and returned HTTP 200. Deployment preconditions: a default standalone instance (server mode, no servlet context path, default `dataease.path.static-resource`; the server-mode data root `/opt/dataease2.0/data` is hardcoded by `StaticResourceConstants.getHomeData()` with no configuration override). SVG content is still screened by the 2.10.20 content blacklist — the finding is the missing authentication layer, not a content-screening bypass.
On a default standalone DataEase instance, any unauthenticated caller can upload files to the static-resource endpoint and fetch them back from the DataEase origin as `image/svg+xml`, because the only authentication filter skips token validation for any request URI that merely ends with the text `svg`.
Data flow: multipart part `file` at `POST /de2api/staticResource/upload/{fileId ending in svg}` → `TokenFilter` match=true, no token check (`TokenFilter.java:57,66-68`) → `StaticResourceServer` writes `static-resource/{fileId}.{ext}` under `WORK_DIR` (`StaticResourceServer.java:58-74`) → `GET /de2api/static-resource/{fileId}.svg` served anonymously with `Content-Type: image/svg+xml`.
### Affected interfaces
| Interface |
|---|
| POST /de2api/staticResource/upload/x.svg — multipart part `file`; anonymous when the URI ends with lowercase `svg` |
| GET /de2api/static-resource/x.svg.svg — anonymous readback of the stored file, served as `image/svg+xml` |
Primary route detail:
```
POST /de2api/staticResource/upload/f1ok.svg HTTP/1.1
Content-Type: multipart/form-data; part `file` (any image content; SVG is content-screened by the 2.10.20 blacklist)
```
Anonymous. No cookie, token, `Authorization` header or link credential is required:
1. `WhitelistUtils.java:59-60` strips the `/de2api` prefix, then `:69` `endsWithAny(requestURI, ".gif",".ico","js",".css","svg","png","jpg","js.map",".otf",".ttf",".woff2")` — the extension entries without a leading dot match any URI whose last characters are that suffix, whatever the route.
2. `TokenFilter` (`FilterConfig`, `/*` order 0, the only auth gate) computes `match = WhitelistUtils.match(...)` (`TokenFilter.java:57`) and on `match=true` calls `filterChain.doFilter` and returns with no token validation (`:66-68`).
3. Spring 6 `PathPatternParser` (verified against the bundled spring-web 6.2.14) captures `x.svg` fully into `{fileId}` — suffix truncation is gone — so the upload handler is reached while the URI satisfies the suffix exemption.
### Root cause
1. The write lands in the served directory: `StaticResourceServer.java:58-74` stores the part as `static-resource/{fileId}.{ext}` under `WORK_DIR` (default `/opt/dataease2.0/data/static-resource`); the write-path guards (`validateUploadFilename`, `writeFileIfAbsent` `CREATE_NEW`) check the path only — no authentication or caller identity anywhere on the path.
2. The read handler (`DeMvcConfig.java:26-29`) serves that same directory, the read prefix is also suffix-whitelisted (`WhitelistUtils.java:72`), and the bundled spring-web mime.types maps `.svg` to `image/svg+xml`.
3. Serving headers do not compensate: `X-Content-Type-Options: nosniff` is present, but the CSP from `HtmlResourceFilter.java:40-41` is `default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; frame-ancestors *`, which does not prevent `javascript:` navigation inside SVG content.
Missing guard: the suffix whitelist is designed for static asset reads; applying it by raw suffix text to
all URIs — including write APIs whose final path segment is attacker-chosen — turns an asset-reading
convenience into an authentication bypass for POST endpoints. The SVG content blacklist added in
2.10.20 screens what authenticated users upload; at 2.10.26 the same endpoint no longer requires any
authentication in a default standalone deployment.
Baseline: released production `CoreApplication.jar` (v2.10.26, commit `0a9eaf6af890bbc91ebcd71a8a8013f917846245`), self-hosted on `127.0.0.1:18763` under the shipped default data layout. No account was created and no token, cookie or link credential was used anywhere.
**1. Anonymous upload (benign 103-byte SVG):**
```sh
$ curl -i -X POST 'http://127.0.0.1:18763/de2api/staticResource/upload/f1ok.svg' \
-F '[email protected];type=image/svg+xml'
HTTP/1.1 200
{"code":0,"msg":null,"data":null}
```
**2. Stored on disk:** `static-resource/f1ok.svg.svg` (103 bytes) under the application's data root; on-disk content equals the uploaded bytes byte for byte.
**3. Anonymous readback, served as SVG from the application origin:**
```sh
$ curl -i 'http://127.0.0.1:18763/de2api/static-resource/f1ok.svg.svg'
HTTP/1.1 200
Content-Type: image/svg+xml
Content-Security-Policy: default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; frame-ancestors *
X-Content-Type-Options: nosniff
# body byte-identical to the uploaded file
```
**4. Negative controls — the gate works everywhere except the suffix-matching shape:**
| Control | Request | Result |
|---|---|---|
| Non-whitelisted suffix | `POST /de2api/staticResource/upload/f1neg.txt` (same multipart) | `401` + `DE-GATEWAY-FLAG: token is empty for uri {.../f1neg.txt}` |
| Protected API without token | `POST /de2api/staticResource/findResourceAsBase64` | `401` + `token is empty` |
| Case-sensitivity | `POST /de2api/staticResource/upload/f1negUp.SVG` | `401` + `token is empty` (`endsWith` is case-sensitive; the payload suffix must be lowercase) |
All testing was performed against a self-hosted instance on the reporter's own host. No third-party system, production instance or customer data was involved.
- **Vendor contact**: [email protected] (FIT2CLOUD security contact email)
- **Status**: vendor notified via email 2026-10-09; 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. |
|---|
| La source | ⚠️ https://github.com/dataease/dataease |
|---|
| Utilisateur | lucasg2g (UID 84737) |
|---|
| Soumission | 15/10/2026 07:34 (il y a 23 jours) |
|---|
| Modérer | 06/11/2026 19:27 (23 days later) |
|---|
| Statut | Accepté |
|---|
| Entrée VulDB | 432070 [DataEase jusqu’à 2.10.27 Authentication Filter WhitelistUtils.java WhitelistUtils.endsWithAny file élévation de privilèges] |
|---|
| Points | 20 |
|---|