← Back to CVE Explorer

CVE-2026-12047

Full vulnerability record from the local CVENow database.

Description

HTML injection in pgAdmin 4's cloud deployment module. The verify_credentials, deploy, regions, and update-server endpoints under /rds/, /azure/, /google/, and the top-level /cloud/ blueprint propagated AWS / Azure / Google SDK exception text — and the related file-resolution and database-commit exception text — into the JSON response body (the info and errormsg fields) without HTML-encoding. The Cloud Wizard frontend rendered these strings through html-react-parser, so an attacker-influenced exception message embedded structural HTML directly into the wizard's DOM. The reported entry point is /rds/verify_credentials/. An authenticated pgAdmin user submits a crafted access_key whose value contains an <iframe/src=...> payload; AWS STS rejects the credential with an IncompleteSignature exception whose text quotes the access_key verbatim; the pgAdmin backend forwards that text into the JSON info field; the Cloud Wizard's FormFooterMessage parses it as HTML. The browser fetches the iframe's src from an attacker-controlled host, and JavaScript executing inside the cross-origin iframe writes to parent.location, redirecting the victim's pgAdmin tab. Because the injection renders inside pgAdmin's own interface, X-Frame-Options and Content-Security-Policy frame-ancestors do not mitigate it. Baseline impact is self-targeted (the same user who supplied the payload sees the injection); escalation against other authenticated users requires an additional cross-site request-forgery primitive capable of submitting the malformed credential request with a valid X-pgA-CSRFToken in the victim's browser context. The same unsanitised-error-into-JSON pattern was present across multiple sibling endpoints — Azure's check_cluster_name_availability, every Google endpoint that surfaces SDK errors (verification_ack, projects, regions, instance_types, database_versions, the verify_credentials path-resolution branches), the central /deploy endpoint that bubbles str(e) from deploy_on_rds / deploy_on_azure / deploy_on_google, and update_cloud_server which surfaces the str(e) from a failing db.session.commit — all of which are now covered. Fix HTML-escapes every external/SDK exception string at the endpoint sink via a new shared sanitize_external_text helper (HTML escape with control-character strip), promoted out of the psycopg3 driver into web/pgadmin/utils/text_sanitize.py. The Cloud Wizard frontend additionally renders its FormFooterMessage in plain-text mode for backend-derived strings, so the value is never parsed as HTML even if a future sink forgets the escape. This issue affects pgAdmin 4: from 6.6 before 9.16.

Severity
MEDIUM
CVSS score
4.8 (v4.0)
Vulnerability status
Analyzed
Source identifier
f86ef6dc-4d3a-42ad-8f28-e6d5547a5007
Published
2026-06-19 00:16:47.040000
Last modified
2026-10-26 11:10:00.157000
View original CVE JSON
{
  "id": "CVE-2026-12047",
  "sourceIdentifier": "f86ef6dc-4d3a-42ad-8f28-e6d5547a5007",
  "published": "2026-06-19T00:16:47.040",
  "lastModified": "2026-10-26T11:10:00.157",
  "vulnStatus": "Analyzed",
  "cveTags": [],
  "descriptions": [
    {
      "lang": "en",
      "value": "HTML injection in pgAdmin 4's cloud deployment module. The verify_credentials, deploy, regions, and update-server endpoints under /rds/, /azure/, /google/, and the top-level /cloud/ blueprint propagated AWS / Azure / Google SDK exception text — and the related file-resolution and database-commit exception text — into the JSON response body (the info and errormsg fields) without HTML-encoding. The Cloud Wizard frontend rendered these strings through html-react-parser, so an attacker-influenced exception message embedded structural HTML directly into the wizard's DOM.\n\nThe reported entry point is /rds/verify_credentials/. An authenticated pgAdmin user submits a crafted access_key whose value contains an <iframe/src=...> payload; AWS STS rejects the credential with an IncompleteSignature exception whose text quotes the access_key verbatim; the pgAdmin backend forwards that text into the JSON info field; the Cloud Wizard's FormFooterMessage parses it as HTML. The browser fetches the iframe's src from an attacker-controlled host, and JavaScript executing inside the cross-origin iframe writes to parent.location, redirecting the victim's pgAdmin tab. Because the injection renders inside pgAdmin's own interface, X-Frame-Options and Content-Security-Policy frame-ancestors do not mitigate it. Baseline impact is self-targeted (the same user who supplied the payload sees the injection); escalation against other authenticated users requires an additional cross-site request-forgery primitive capable of submitting the malformed credential request with a valid X-pgA-CSRFToken in the victim's browser context.\n\nThe same unsanitised-error-into-JSON pattern was present across multiple sibling endpoints — Azure's check_cluster_name_availability, every Google endpoint that surfaces SDK errors (verification_ack, projects, regions, instance_types, database_versions, the verify_credentials path-resolution branches), the central /deploy endpoint that bubbles str(e) from deploy_on_rds / deploy_on_azure / deploy_on_google, and update_cloud_server which surfaces the str(e) from a failing db.session.commit — all of which are now covered.\n\nFix HTML-escapes every external/SDK exception string at the endpoint sink via a new shared sanitize_external_text helper (HTML escape with control-character strip), promoted out of the psycopg3 driver into web/pgadmin/utils/text_sanitize.py. The Cloud Wizard frontend additionally renders its FormFooterMessage in plain-text mode for backend-derived strings, so the value is never parsed as HTML even if a future sink forgets the escape.\n\nThis issue affects pgAdmin 4: from 6.6 before 9.16."
    },
    {
      "lang": "es",
      "value": "Inyección HTML en el módulo de despliegue en la nube de pgAdmin 4. Los puntos finales verify_credentials, deploy, regions y update-server bajo /rds/, /azure/, /google/ y el esquema de nivel superior /cloud/ propagaron texto de excepción del SDK de AWS / Azure / Google - y el texto de excepción relacionado con la resolución de archivos y la confirmación de la base de datos - en el cuerpo de la respuesta JSON (los campos info y errormsg) sin codificación HTML. El frontend del Asistente de la Nube renderizó estas cadenas a través de html-react-parser, por lo que un mensaje de excepción influenciado por el atacante incrustó HTML estructural directamente en el DOM del asistente.\n\nEl punto de entrada reportado es /rds/verify_credentials/. Un usuario autenticado de pgAdmin envía una access_key manipulada cuyo valor contiene una carga útil [iframe_tag]; AWS STS rechaza la credencial con una excepción IncompleteSignature cuyo texto cita la access_key textualmente; el backend de pgAdmin reenvía ese texto al campo JSON info; el FormFooterMessage del Asistente de la Nube lo analiza como HTML. El navegador obtiene el src del iframe de un host controlado por el atacante, y JavaScript ejecutándose dentro del iframe de origen cruzado escribe en parent.location, redirigiendo la pestaña de pgAdmin de la víctima. Debido a que la inyección se renderiza dentro de la propia interfaz de pgAdmin, X-Frame-Options y Content-Security-Policy frame-ancestors no lo mitigan. El impacto base es auto-dirigido (el mismo usuario que proporcionó la carga útil ve la inyección); la escalada contra otros usuarios autenticados requiere una primitiva adicional de falsificación de solicitud entre sitios capaz de enviar la solicitud de credencial malformada con un X-pgA-CSRFToken válido en el contexto del navegador de la víctima.\n\nEl mismo patrón de error no saneado en JSON estaba presente en múltiples puntos finales hermanos - check_cluster_name_availability de Azure, cada punto final de Google que muestra errores del SDK (verification_ack, projects, regions, instance_types, database_versions, las ramas de resolución de ruta de verify_credentials), el punto final central /deploy que propaga str(e) de deploy_on_rds / deploy_on_azure / deploy_on_google, y update_cloud_server que muestra el str(e) de un db.session.commit fallido - todos los cuales ahora están cubiertos.\n\nSe corrige el escape HTML de cada cadena de excepción externa/del SDK en el sumidero del punto final a través de un nuevo ayudante compartido sanitize_external_text (escape HTML con eliminación de caracteres de control), promovido del controlador psycopg3 a web/pgadmin/utils/text_sanitize.py. El frontend del Asistente de la Nube adicionalmente renderiza su FormFooterMessage en modo de texto plano para cadenas derivadas del backend, por lo que el valor nunca se analiza como HTML incluso si un futuro sumidero olvida el escape.\n\nEste problema afecta a pgAdmin 4: desde 6.6 antes de 9.16."
    }
  ],
  "affected": [
    {
      "source": "f86ef6dc-4d3a-42ad-8f28-e6d5547a5007",
      "affectedData": [
        {
          "vendor": "pgadmin.org",
          "product": "pgAdmin 4",
          "defaultStatus": "unaffected",
          "modules": [
            "Cloud Deployment",
            "Cloud RDS",
            "Cloud Azure",
            "Cloud Google"
          ],
          "programFiles": [
            "https://github.com/pgadmin-org/pgadmin4/blob/master/web/pgadmin/misc/cloud/__init__.py",
            "https://github.com/pgadmin-org/pgadmin4/blob/master/web/pgadmin/misc/cloud/rds/__init__.py",
            "https://github.com/pgadmin-org/pgadmin4/blob/master/web/pgadmin/misc/cloud/azure/__init__.py",
            "https://github.com/pgadmin-org/pgadmin4/blob/master/web/pgadmin/misc/cloud/google/__init__.py",
            "https://github.com/pgadmin-org/pgadmin4/blob/master/web/pgadmin/misc/cloud/static/js/CloudWizard.jsx"
          ],
          "repo": "https://github.com/pgadmin-org/pgadmin4",
          "versions": [
            {
              "version": "6.6",
              "lessThan": "9.16",
              "versionType": "custom",
              "status": "affected"
            }
          ]
        }
      ]
    }
  ],
  "metrics": {
    "cvssMetricV40": [
      {
        "source": "f86ef6dc-4d3a-42ad-8f28-e6d5547a5007",
        "type": "Secondary",
        "cvssData": {
          "version": "4.0",
          "vectorString": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:A/VC:N/VI:L/VA:N/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",
          "baseScore": 4.8,
          "baseSeverity": "MEDIUM",
          "attackVector": "NETWORK",
          "attackComplexity": "LOW",
          "attackRequirements": "NONE",
          "privilegesRequired": "LOW",
          "userInteraction": "ACTIVE",
          "vulnConfidentialityImpact": "NONE",
          "vulnIntegrityImpact": "LOW",
          "vulnAvailabilityImpact": "NONE",
          "subConfidentialityImpact": "NONE",
          "subIntegrityImpact": "NONE",
          "subAvailabilityImpact": "NONE",
          "exploitMaturity": "NOT_DEFINED",
          "confidentialityRequirement": "NOT_DEFINED",
          "integrityRequirement": "NOT_DEFINED",
          "availabilityRequirement": "NOT_DEFINED",
          "modifiedAttackVector": "NOT_DEFINED",
          "modifiedAttackComplexity": "NOT_DEFINED",
          "modifiedAttackRequirements": "NOT_DEFINED",
          "modifiedPrivilegesRequired": "NOT_DEFINED",
          "modifiedUserInteraction": "NOT_DEFINED",
          "modifiedVulnConfidentialityImpact": "NOT_DEFINED",
          "modifiedVulnIntegrityImpact": "NOT_DEFINED",
          "modifiedVulnAvailabilityImpact": "NOT_DEFINED",
          "modifiedSubConfidentialityImpact": "NOT_DEFINED",
          "modifiedSubIntegrityImpact": "NOT_DEFINED",
          "modifiedSubAvailabilityImpact": "NOT_DEFINED",
          "Safety": "NOT_DEFINED",
          "Automatable": "NOT_DEFINED",
          "Recovery": "NOT_DEFINED",
          "valueDensity": "NOT_DEFINED",
          "vulnerabilityResponseEffort": "NOT_DEFINED",
          "providerUrgency": "NOT_DEFINED"
        }
      }
    ],
    "cvssMetricV31": [
      {
        "source": "f86ef6dc-4d3a-42ad-8f28-e6d5547a5007",
        "type": "Secondary",
        "cvssData": {
          "version": "3.1",
          "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:L/A:N",
          "baseScore": 3.5,
          "baseSeverity": "LOW",
          "attackVector": "NETWORK",
          "attackComplexity": "LOW",
          "privilegesRequired": "LOW",
          "userInteraction": "REQUIRED",
          "scope": "UNCHANGED",
          "confidentialityImpact": "NONE",
          "integrityImpact": "LOW",
          "availabilityImpact": "NONE"
        },
        "exploitabilityScore": 2.1,
        "impactScore": 1.4
      },
      {
        "source": "[email protected]",
        "type": "Primary",
        "cvssData": {
          "version": "3.1",
          "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N",
          "baseScore": 5.4,
          "baseSeverity": "MEDIUM",
          "attackVector": "NETWORK",
          "attackComplexity": "LOW",
          "privilegesRequired": "LOW",
          "userInteraction": "REQUIRED",
          "scope": "CHANGED",
          "confidentialityImpact": "LOW",
          "integrityImpact": "LOW",
          "availabilityImpact": "NONE"
        },
        "exploitabilityScore": 2.3,
        "impactScore": 2.7
      }
    ],
    "ssvcV203": [
      {
        "source": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
        "ssvcData": {
          "timestamp": "2026-06-22T19:13:58.685569Z",
          "id": "CVE-2026-12047",
          "options": [
            {
              "exploitation": "none"
            },
            {
              "automatable": "no"
            },
            {
              "technicalImpact": "partial"
            }
          ],
          "role": "CISA Coordinator",
          "version": "2.0.3"
        }
      }
    ]
  },
  "weaknesses": [
    {
      "source": "f86ef6dc-4d3a-42ad-8f28-e6d5547a5007",
      "type": "Secondary",
      "description": [
        {
          "lang": "en",
          "value": "CWE-79"
        },
        {
          "lang": "en",
          "value": "CWE-116"
        }
      ]
    },
    {
      "source": "[email protected]",
      "type": "Primary",
      "description": [
        {
          "lang": "en",
          "value": "CWE-79"
        }
      ]
    }
  ],
  "configurations": [
    {
      "nodes": [
        {
          "operator": "OR",
          "negate": false,
          "cpeMatch": [
            {
              "vulnerable": true,
              "criteria": "cpe:2.3:a:pgadmin:pgadmin_4:*:*:*:*:*:postgresql:*:*",
              "versionStartIncluding": "6.6",
              "versionEndExcluding": "9.16",
              "matchCriteriaId": "BC047297-CF61-4536-AA24-CBA6439EA155"
            }
          ]
        }
      ]
    }
  ],
  "references": [
    {
      "url": "https://github.com/pgadmin-org/pgadmin4/commit/60d149864b5fdd99675754c7996637737a24fce3",
      "source": "f86ef6dc-4d3a-42ad-8f28-e6d5547a5007",
      "tags": [
        "Patch"
      ]
    },
    {
      "url": "https://github.com/pgadmin-org/pgadmin4/issues/10069",
      "source": "f86ef6dc-4d3a-42ad-8f28-e6d5547a5007",
      "tags": [
        "Issue Tracking",
        "Patch"
      ]
    }
  ]
}