Skip to content

M3str3/CVE-2026-2005

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

CVE-2026-2005: pgcrypto session key heap overflow

Heap buffer overflow in PostgreSQL's pgcrypto extension when processing a malicious "Public-Key Encrypted Session Key" (OpenPGP tag 1) packet. An attacker who can supply ciphertext to pgp_pub_decrypt() can trigger a copy of an oversized session key into a fixed 32-byte buffer, corrupting heap memory.


Affected versions

Fixed in PostgreSQL 18.2, 17.8, 16.12, 15.16, 14.21 (patch date: 2026-02-12). All earlier versions of these branches (and equivalent pre-18.2/17.8/16.12/15.16/14.21) are affected when the pgcrypto extension is used with pgp_pub_decrypt().


Vulnerability in brief

In contrib/pgcrypto/pgp-pubdec.c, the function pgp_parse_pubenc_sesskey() handles tag 1 packets. After RSA-decrypting the payload, it interprets the plaintext as: 1 byte (symmetric algorithm) + N bytes (session key) + 2 bytes (checksum). It sets sess_key_len = msglen - 3 and copies that many bytes into ctx->sess_key with memcpy(). The buffer ctx->sess_key has size PGP_MAX_KEY (32 bytes). There is no check that sess_key_len <= 32, so an attacker who controls the ciphertext can cause a heap buffer overflow.


Vulnerable code (PostgreSQL 17.7)

From contrib/pgcrypto/pgp-pubdec.c, function pgp_parse_pubenc_sesskey():

	/*
	 * extract message
	 */
	msg = check_eme_pkcs1_v15(m->data, m->bytes);
	if (msg == NULL)
	{
		px_debug("check_eme_pkcs1_v15 failed");
		res = PXE_PGP_WRONG_KEY;
		goto out;
	}
	msglen = m->bytes - (msg - m->data);

	res = control_cksum(msg, msglen);
	if (res < 0)
		goto out;

	/*
	 * got sesskey
	 */
	ctx->cipher_algo = *msg;
	ctx->sess_key_len = msglen - 3;
	memcpy(ctx->sess_key, msg + 1, ctx->sess_key_len);

The length msglen - 3 is used directly as the copy size; if it is greater than 32, memcpy writes past the end of sess_key.


Patched code (PostgreSQL 17.8 / 18.2+)

The fix adds a bounds check before the copy and returns a dedicated error when the session key is too long:

	res = control_cksum(msg, msglen);
	if (res < 0)
		goto out;

	sess_key_len = msglen - 3;
	if (sess_key_len > PGP_MAX_KEY)
	{
		px_debug("incorrect session key length=%u", sess_key_len);
		res = PXE_PGP_KEY_TOO_BIG;
		goto out;
	}

	/*
	 * got sesskey
	 */
	ctx->cipher_algo = *msg;
	ctx->sess_key_len = sess_key_len;
	memcpy(ctx->sess_key, msg + 1, ctx->sess_key_len);

The same logic appears in 18.2 and other patched branches.


Buffer definition (pgp.h)

The destination buffer and its size are defined in contrib/pgcrypto/pgp.h:

#define PGP_MAX_KEY    (256/8)
	/*
	 * read or generated data
	 */
	uint8		sess_key[PGP_MAX_KEY];
	unsigned	sess_key_len;

So sess_key is 32 bytes; any copy of more than 32 bytes overwrites sess_key_len and whatever lies after the struct on the heap.


How the PoC works

This repository contains a Python script that:

  1. Generates an RSA PGP key pair (4096 or 8192 bits, via --rsa).
  2. Builds a malicious secret message: 1 byte algorithm + N bytes "session key" + 2 bytes checksum, with N chosen to fill the RSA block (498 for RSA 4096, 1010 for RSA 8192).
  3. Pads it with PKCS#1 v1.5 and encrypts it with the public key.
  4. Wraps the ciphertext in a valid PGP tag 1 packet (Public-Key Encrypted Session Key) and appends a minimal tag 9 (Symmetrically Encrypted Data) so the parser continues.
  5. Calls pgp_pub_decrypt(packet, secret_key) against a PostgreSQL server. On a vulnerable build, the server decrypts, sets sess_key_len to N, and performs memcpy(ctx->sess_key, msg+1, N) — writing N bytes into a 32-byte buffer and corrupting the heap.

Optional payload and offset options let you control the bytes written and sweep offsets to probe for crashes.


Usage

Requirements: Python 3, psycopg2-binary, pgpy, cryptography.

pip install psycopg2-binary pgpy cryptography

Run against local PostgreSQL (default RSA 4096):

python3 PoC.py

Use RSA 8192 for a larger overflow (1010 bytes instead of 498):

python3 PoC.py --rsa 8192

Custom payload and offset (e.g. to sweep for crashes):

python3 PoC.py --payload-hex "deadbeef" --offset 100
python3 PoC.py --payload-file payload.bin --offset 0
# Sweep offsets: for i in $(seq 0 490); do python3 PoC.py --payload-hex "..." --offset $i; done

Only print SQL (no pgpy; you must supply the matching secret key when running the SQL):

python3 PoC.py --sql-only

Connection options: --host, -p / --port, -u / --user, -d / --db, -w / --password.


Diffs

The diffs/ folder contains the upstream patch for this vulnerability (PostgreSQL 17.7 → 17.8). See diffs/pgp-pubdec.c.patch and diffs/README.txt.


Disclaimer

This PoC is for authorized security research and testing only. Do not use it against systems you are not permitted to test. The authors are not responsible for misuse.

About

PoC for CVE-2026-2005 — heap buffer overflow in PostgreSQL pgcrypto (pgp_pub_decrypt). Oversized PGP session key bypasses bounds check in pgp-pubdec.c. Affects < 17.8 / 16.12 / 15.16 / 14.21 / 18.2.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages