XSS(Cross-Site Scripting) 취약점 - WordPress 404 to 301 플러그인

by Toff

   개요
이번 포스팅에서는 404 to 301 플러그인을 사용해 Persistent XSS(Cross-site Scripting) 취약점 대해서 알아보자. XSS 취약점에 대한 설명은 다음 포스팅을 참고하도록 하자.
> http://dntsecurity.blogspot.kr/2017/01/xsscross-site-scripting.html

해당 취약점은 요청 헤더(HTTP Header)값을 조작할 때 발생한다. 해당 플러그인을 사용해본 사용자라면 어느 정도 쉽게 취약점을 찾을 수 있으나, 플러그인을 사용해보지 못한 사용자는 쉽게 찾을 수 없을 것이다. 실제로 모의 해킹을 진행하기 앞서 모의 해킹 하고자 하는 웹 애플리케이션의 기능을 살펴보는 과정은 굉장히 중요하다. 자세한 설명은 결과 분석에서 살펴보도록 하자.

   환경 구성
환경 구성은 다음 그림 1-1 과 같다.
그림 1-1 환경 구성

404 to 301 플러그인 설치 주소는 다음과 같다.
- 취약점 발생 버전 : https://downloads.wordpress.org/plugin/404-to-301.2.3.0.zip
- 취약점 패치 버전 : https://downloads.wordpress.org/plugin/404-to-301.2.3.1.zip


   실습 순서
아래의 포스팅 순서대로 실습을 진행해도 되지만 더욱 빠른 이해를 위해 아래의 영상으로 실습해도 된다. 해당 영상은 "실습 과정"만을 영상으로 만들었기에 포스팅과 같이 본다면 더욱 편하다.

먼저 그림 1-2 와 같이 404 to 301 플러그인을 활성화 시킨다.
그림 1-2 404 to 301 플러그인 활성화

OWASP-ZAP을 실행시켜 프록시 설정을 한 후, 초록색 버튼을 클릭해 트랩을 잡고 그림 1-3 과 같이 메인페이지에서 준재하지 않는 임의의 페이지를 삽입한다.
그림 1-3 OWASP-ZAP 트랩 기능 사용

그림 1-4 와 같이 요청 헤더 값이 출력된다. 해당 취약점은 User-Agent 값에서 발생한다. 그림 1-5 와 같이 해당 값을 스크립트 문으로 수정하고 상단에 있는 ▶ 버튼을 클릭해 값을 전송한다.
그림 1-4 요청 헤더 값

그림 1-5 요청 헤더 - User-Agent 값 변경

그림 1-6 과 같이 피해자 PC에서 404 to 301 탭의 Error Logs 를 클릭하면 스크립트가 동작한다.
그림 1-6 Persistent XSS 취약점 확인


   결과 분석
해당 취약점의 발생 원인을 파악하려면 앞서 개요에서 말했듯이 플러그인의 기능을 알 필요가 있다. 404 to 301 플러그인은 이용자가 존재하지 않는 페이지(404 error)를 접속했을 때 자동으로 다른 페이지로 리다이렉트 시켜주는 기능을 한다. 또한 이용자가 잘못된 페이지에 접근한다면 해당 정보에 대한 로그가 자동으로 저장되며 그림 1-6 왼쪽 하단에 보이는 404 Error Logs 에서 확인할 수 있다. 해당 로그에는 날짜, 접속 페이지, User-Agent 값 등을 저장한다. 이를 이용해 공격자는 잘못된 페이지 접근 시에 실습에서와 같이 User-Agent 값을 악의적인 스크립트 문으로 조작해 데이터를 전송한다. 악의적인 값이 데이터베이스 저장되면서 관리자가 Error Logs 페이지에 접속할때마다 Persistent XSS 취약점이 발생하는 것이다.

실제로 데이터베이스에 저장된 값을 보면 그림 1-7 과 같다. 악의적으로 동작할 수 있는 <script> 구문이 그대로 저장되면서 취약점이 발생하는 것이다.
그림 1-7 취약점 발생 시 데이터베이스 확인

취약점이 발생한 코드는 다음 표 1-1 이며, 404 to 301/404-to-301.2.3.0/404-to-301/admin/class-404-to-301-logs.php 에 정의 되어 있다. $ua_data 값이 User-Agent 값이며 값을 받아와 출력할때까지 악의적으로 동작할 수 있는 스크립트 문을 필터링 하는 구문이 존재하지 않는다.
    public function column_ua($item) {  // 취약점 발생 버전

        // Apply filter - i4t3_log_list_ref_column
        $ua_data = apply_filters( 'i4t3_log_list_ua_column', $this->get_empty_text( $item['ua'], $item['ua'] ) );

        return $ua_data;
    }
표 1-1 class-404-to-301-logs.php ( 취약점 발생 버전 )

   대응 방안
XSS 취약점의 대응 방안은 악의적으로 동작할 수 있는 스크립트 문을 필터링 하는 것과 URL, 각종 Form 의 길이 제한을 하는 방법이 있다. 해당 플러그인에서는 전자로 대응 방안을 세웠다. 후자의 대응 방안은 제목, email, ID 값 등은 불필요하게 길 필요가 없을 뿐더러 악의적인 스크립트를 삽입해 동작시키려면 적어도 10글자 이상이 필요하다. 그렇기에 글자 제한을 함으로써 어느 정도 취약점을 보완할수는 있지만 이는 완벽한 대응 방안이 아니다. 전자와 후자를 같이 대응 방안으로 세운다면 보다 완벽할 것이다. 이제 해당 플러그인에서 세운 대응 방안을 살펴보도록 하자.

해당 플러그인은 취약점을 보완한 패치 버전이 존재하기에 패치 이전의 코드 (표 1-1)와 패치 버전의 코드를 비교해보도록 하자. 패치 버전의 코드는 표 1-2 와 같다. 패치 버전에서 세 번째 줄의 코드가 추가된 것을 알 수 있다.
    public function column_ua($item) {  // 취약점 패치 버전

        $ua = sanitize_text_field( $item['ua'] );

        // Apply filter - i4t3_log_list_ref_column
        $ua_data = apply_filters( 'i4t3_log_list_ua_column', $this->get_empty_text( $ua, $ua ) );

        return $ua_data;
    }
표 1-2 class-404-to-301-logs.php ( 취약점 패치 버전 )

sanitize_text_field 함수는 표 1-3 과 같다. 워드프레스 함수이며 /wordpress/wp-includes/formatting.php 에 정의되어 있다. 정상적인utf8 값인지를 판별하고, 특수문자를 필터링시켜 모든 태그를 제거한다. 또한 줄 개행 및 공백 문자(탭 포함) 등을 제거해주는 함수이다. 주로 값이 데이터베이스에 저장되기 직전에 사용되는 함수이다. 해당 플러그인에서는 해당 함수를 사용해 악의적으로 동작할 수 있는 스크립트를 필터링 했다.
function sanitize_text_field( $str ) {
        $filtered = wp_check_invalid_utf8( $str );

        if ( strpos($filtered, '<') !== false ) {
                $filtered = wp_pre_kses_less_than( $filtered );
                // This will strip extra whitespace for us.
                $filtered = wp_strip_all_tags( $filtered, true );
        } else {
                $filtered = trim( preg_replace('/[\r\n\t ]+/', ' ', $filtered) );
        }

        $found = false;
        while ( preg_match('/%[a-f0-9]{2}/i', $filtered, $match) ) {
                $filtered = str_replace($match[0], '', $filtered);
                $found = true;
        }

        if ( $found ) {
                // Strip out the whitespace that may now exist after removing the octets.
                $filtered = trim( preg_replace('/ +/', ' ', $filtered) );
        }

        /**
         * Filters a sanitized text field string.
         *
         * @since 2.9.0
         *
         * @param string $filtered The sanitized string.
         * @param string $str      The string prior to being sanitized.
         */
        return apply_filters( 'sanitize_text_field', $filtered, $str );
}
표 1-3 sanitize_text-field 함수

취약점을 패치한 2.3.1 버전에서 위의 실습과 똑같이 실습했을 때 데이터베이스를 확인하면 그림 1-8과 같다. 모든 태그가 그대로 저장된 그림 1-7 과 달리 특수문자 값이 &quot; &lt; &gt; 등으로 필터링 된것을 알 수 있다.
그림 1-8 취약점 패치 후 데이터베이스 확인

취약점이 발생했던 Error Logs 페이지를 접속하면 값은 정상적으로 출력되지만 취약점은 발생하지 않는다.


그림 1-9 취약점이 발생했던 Error Logs 페이지