GHSA-89V5-38XR-9M4J

Vulnerability from github – Published: 2026-03-27 15:47 – Updated: 2026-03-27 15:47
VLAI
Summary
Postiz has Multiple SSRF Vectors - Webhooks, RSS Feed, URL Loader
Details

Summary

Postiz has multiple SSRF vulnerabilities where user-provided URLs are fetched server-side without any IP validation or SSRF protection.

Vulnerable Code

1. Webhook Send Endpoint (Most Critical)

apps/backend/src/api/routes/webhooks.controller.ts lines 58-70:

async sendWebhook(@Body() body: any, @Query('url') url: string) {
  try {
    await fetch(url, {  // No URL validation
      method: 'POST',
      body: JSON.stringify(body),
      headers: { 'Content-Type': 'application/json' },
    });
  } catch (err) { }
  return { send: true };
}

Accepts arbitrary URL via query parameter and fetches directly.

2. Stored Webhook Delivery

apps/orchestrator/src/activities/post.activity.ts lines 256-281:

async sendWebhooks(postId: string, orgId: string, integrationId: string) {
  const webhooks = await this._webhookService.getWebhooks(orgId);
  return Promise.all(
    webhooks.map(async (webhook) => {
      await fetch(webhook.url, {  // Stored URL, no validation
        method: 'POST',
        body: JSON.stringify(post),
      });
    })
  );
}

3. RSS/XML Feed Parser

libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts line 135:

async loadXML(url: string) {
  const { items } = await parser.parseURL(url);  // No URL validation
}

4. HTML Content Loader

libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts line 185:

async loadUrl(url: string) {
  const loadDom = new JSDOM(await (await fetch(url)).text());  // No validation
}

Missing Protections

  • No request-filtering-agent or SSRF library
  • No private IP range filtering
  • No cloud metadata endpoint blocking
  • No DNS rebinding protection
  • URL validation only via @IsUrl() decorator (format only, no IP check)

Attack Scenarios

  1. POST /webhooks/send?url=http://169.254.169.254/latest/meta-data/ → AWS metadata theft
  2. POST /autopost/send?url=http://127.0.0.1:6379 → Internal Redis access
  3. Create webhook with http://10.0.0.1:8080/admin → Internal service access on post publish

Impact

  • Cloud metadata theft: AWS/GCP/Azure credentials
  • Internal network scanning: Full access to private IP ranges
  • Multiple entry points: Webhooks, RSS feeds, URL loader all vulnerable
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "postiz"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "2.0.12"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-27T15:47:57Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\nPostiz has multiple SSRF vulnerabilities where user-provided URLs are fetched server-side without any IP validation or SSRF protection.\n\n## Vulnerable Code\n\n### 1. Webhook Send Endpoint (Most Critical)\n\n**`apps/backend/src/api/routes/webhooks.controller.ts` lines 58-70:**\n```typescript\nasync sendWebhook(@Body() body: any, @Query(\u0027url\u0027) url: string) {\n  try {\n    await fetch(url, {  // No URL validation\n      method: \u0027POST\u0027,\n      body: JSON.stringify(body),\n      headers: { \u0027Content-Type\u0027: \u0027application/json\u0027 },\n    });\n  } catch (err) { }\n  return { send: true };\n}\n```\n\nAccepts arbitrary URL via query parameter and fetches directly.\n\n### 2. Stored Webhook Delivery\n\n**`apps/orchestrator/src/activities/post.activity.ts` lines 256-281:**\n```typescript\nasync sendWebhooks(postId: string, orgId: string, integrationId: string) {\n  const webhooks = await this._webhookService.getWebhooks(orgId);\n  return Promise.all(\n    webhooks.map(async (webhook) =\u003e {\n      await fetch(webhook.url, {  // Stored URL, no validation\n        method: \u0027POST\u0027,\n        body: JSON.stringify(post),\n      });\n    })\n  );\n}\n```\n\n### 3. RSS/XML Feed Parser\n\n**`libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts` line 135:**\n```typescript\nasync loadXML(url: string) {\n  const { items } = await parser.parseURL(url);  // No URL validation\n}\n```\n\n### 4. HTML Content Loader\n\n**`libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts` line 185:**\n```typescript\nasync loadUrl(url: string) {\n  const loadDom = new JSDOM(await (await fetch(url)).text());  // No validation\n}\n```\n\n## Missing Protections\n\n- No `request-filtering-agent` or SSRF library\n- No private IP range filtering\n- No cloud metadata endpoint blocking\n- No DNS rebinding protection\n- URL validation only via `@IsUrl()` decorator (format only, no IP check)\n\n## Attack Scenarios\n\n1. `POST /webhooks/send?url=http://169.254.169.254/latest/meta-data/` \u2192 AWS metadata theft\n2. `POST /autopost/send?url=http://127.0.0.1:6379` \u2192 Internal Redis access\n3. Create webhook with `http://10.0.0.1:8080/admin` \u2192 Internal service access on post publish\n\n## Impact\n\n- **Cloud metadata theft**: AWS/GCP/Azure credentials\n- **Internal network scanning**: Full access to private IP ranges\n- **Multiple entry points**: Webhooks, RSS feeds, URL loader all vulnerable",
  "id": "GHSA-89v5-38xr-9m4j",
  "modified": "2026-03-27T15:47:57Z",
  "published": "2026-03-27T15:47:57Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/gitroomhq/postiz-app/security/advisories/GHSA-89v5-38xr-9m4j"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitroomhq/postiz-app/commit/0ad89ccd26b1c387c4f3f3544b18c20d33586466"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitroomhq/postiz-app/commit/be5d871896e97cb1f5a2c9241f156b6a1e1debe8"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/gitroomhq/postiz-app"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitroomhq/postiz-app/releases/tag/v2.21.2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:H/SI:L/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Postiz has Multiple SSRF Vectors - Webhooks, RSS Feed, URL Loader"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…