본문으로 바로가기

1. 개요

워드프레스 'StageShow' 5.0.8 이하 버전의 플러그인에서 오픈 리디렉트 취약점이 발견되었다. 해당 취약점에 대한 정보는 wpvulndb.com/vulnerabilities/8073에 올라와 있으며, PoC 코드를 제공하고 있다.

그림 1. StageShow 플러그인 취약점

2. 취약한 플러그인 설치하기

이번에 취약한 플러그인 설치하는 방법은 기존에 설치하는 방법과 다른 방법을 사용해본다. 우선 취약한 플러그인을 찾아야 하는데, 찾는 방법은 다음과 같다.

  • 첫째, 워드프레스 홈페이지(wordpress.org)의 플러그인 카테고리에 접속한다.
  • 둘째, 다운로드 받고자 하는 플러그인을 검색한다.
  • 셋째, 검색된 플러그인을 클릭한다.

그림 2. 플러그인 다운로드 1

  • 넷째, 다운로드 버튼에서 마우스 우클릭하여 링크 주소를 복사한다.

그림 3. 플러그인 다운로드 2

  • 다섯째, 붙여넣기 한 링크에서 버전정보를 수정하여 다운로드 실행한다.

그림 4. 플러그인 다운로드 3

이제 다운로드 받은 플러그인을 설치한다. 이번에 설치하는 방법은 zip 형태로 압축 된 파일만 가능하다. 이렇게 설치하는 권장하는 방법이기에 안정적인 설치가 가능하다.

그림 5. 플러그인 업로드 1

그림 6. 플러그인 업로드 2

그림 7. 플러그인 설치 완료

3. 오픈 리디렉트(Open Redirect)

3.1. 개요

오픈 리디렉트는 다른 말로 "검증되지 않은 리디렉션" 또는 "공개된 재전송"으로 부른다. 이 취약점은 사용자의 입력 값 등 외부 입력에 의해 다른 사이트로 리디렉트되는 보안 취약점이다. 오픈 리디렉트를 컨번트 리디렉트(Covert Redirect) 로 부르기도 하는데, 컨버트 리디렉트는 보안 이슈에서 리디렉션이 발생하는 모든 상황(XSS 포함)을 지칭한다.

공격 노출 위치는 쿼리스트링이나 form 데이터 등에서 특정 URL로 재전송을 수행하도록 개발이 된 (또는 포함된) 웹 애플리케이션에서 주로 발생한다.

그림 8. 피싱 페이지

URL을 자세히 보면 returnUrl=[사이트] 가 공격자가 교묘하게 변경해놓은 사이트이다. 오리지널 사이트는 nerddinner.com 이고 공격자가 만든 사이트는 nerddiner.com (n이 없음)이다. 여기서 오픈 리디렉트가 발생하면 공격자가 설정한 페이지로 페이지가 전환된다. 전환된 페이지는 다음 그림과 같이 공격자가 실제 사이트와 동일하게 구성한 피싱 사이트이다.

그림 9. 피싱 사이트

이렇게 사용자가 피싱 사이트에 계정과 비밀번호를 입력하게 되면 계정 도용이 발생한다. 다음 소스코드는 ASP.NET MVC 2에서 제공되는 AccountController.csLogOn 액션 코드이다.

public ActionResult LogOn(LogOnModel model, string returnUrl) 
{ 
    if (ModelState.IsValid) 
    { 
        if (MembershipService.ValidateUser(model.UserName, model.Password)) 
        { 
            FormsService.SignIn(model.UserName, model.RememberMe); 
            if (!String.IsNullOrEmpty(returnUrl)) 
            // returnUrl 이 Null이나 Empty가 아니면 return!!
            // 검증 방식이 너무 단순하다.
            { 
                return Redirect(returnUrl); 
            } 
            else 
            { 
                return RedirectToAction("Index", "Home"); 
            } 
        } 
        else 
        { 
            ModelState.AddModelError("", "The user name or password provided is incorrect."); 
        } 
    } 
     
return View(model); 
}

눈치 챘겠지만, 오픈 리디렉트는 공격 방법이 피싱에 한정되어 있다. 좀 더 자세한 내용을 보기 위해 CWE-601 항목을 살펴보면 다음과 같다. 특히 사람들이 이러한 공격에 당할 수 밖에 없는 이유로, 공격자가 설정하는 위치가 사용자들이 일반적으로 인식하는 도메인의 위치가 아니기에 도메인을 신뢰할 수 있는 사이트로 유지하고 리디렉션 시킬 수 있기 때문이다.

그림 10. CWE-601

그리고 2013 OWASP Top10에서 A10으로 위험한 취약점으로 설명하고 있다.

그림 11. OWASP Top10 A10

4. StageShow Open Redirect Vulnerability

4.1. 개념 증명 코드

이번 워드프레스 플러그인에서 발생한 취약점은 앞서 설명한 오픈 리디렉트 취약점이다. PoC 코드는 다음과 같다.

http://192.168.0.138/wp-content/plugins/stageshow/stageshow_redirect.php?url=http://hakawati.co.kr

4.2. 소스코드 분석

문제가 발생하는 stageshow_redirect.php 파일의 소스코드이다.

<?php
if(!isset($_SESSION))
{
    session_start();
}
 
$_SESSION['REDIRECTED_GET'] = serialize($_GET);
$url = urldecode($_GET['url']);
 
Redirect($url, true);
 
function Redirect($url, $permanent = false)
{
    header('Location: ' . $url, true, $permanent ? 301 : 302);
    die;
}
 
?>

소스코드를 분석해보면 사용자의 입력을 받아 저장하는 변수 url에 대한 값을 검증 없이 301또는 302 리디렉션을 발생시킨다. 이 문제를 해결하려면 조건문을 이용하여 url에 대한 검증 작업을 해야 한다.

4.3. 패치

StageShow 5.0.8 에서 발생한 이번 문제는 5.0.9 버전으로 패치하면서 해결됐다.

그림 12. StageShow 5.0.9 패치 로그

이번에는 패치 비교를 meld를 이용하지 않고, 오픈 소스 개발자들이 패치 할 때 남기는 패치 로그를 확인해본다. 보통 패치로그는 패키지를 설치하는 시스템에 남아 있거나 레파지토리에서 요청하여 받아 볼 수 있다. 하지만 워드프레스의 경우 웹 애플리케이션에서 직접 확인 가능하다.

  • 첫째, 워드프레스 홈페이지에서 플러그인 다운로드 받는 페이지로 방문한다.
  • 둘째, 해당 페이지에서 개발자(Devlopers) 카테고리를 선택한다.
  • 셋째, 로드된 항목에서 "Browse in Trac"을 클릭한다. (TracBrowser 라 부른다)

그림 13. Browse in Trac

  • 넷째, 패치한 5.0.9 버전 검색한다.

그림 14. 패치한 5.0.9 버전 검색

  • 다섯째, 검색 결과에서 릴리즈 노트를 선택한다.

그림 15. 릴리즈 버전 선택

패치 노트를 보면, 8개의 수정된 파일과 1개의 삭제된 파일이 보여진다. 문제가 발생하는 stageshow_redirect.php 파일은 삭제되어 시큐어코딩 관련된 부분은 확인하지 못했다.

그림 16. 패치 로그

5. 대응방안

5.1. 시큐어코딩

7 페이지에서 언급한 취약한 소스코드가 어떤 형태로 검증을 하고 대응하는지 확인해본다.

다음 소스코드는 IsLocalUrl 함수를 통해 url 변수에 저장된 웹 주소를 검증한다. 이 클래스에서 핵심은 IsUrlLocalToHost 메소드이다.

public bool IsLocalUrl(string url) { 
    return System.Web.WebPages.RequestExtensions.IsUrlLocalToHost( 
        RequestContext.HttpContext.Request, url); 
}

IsUrlLocalToHost 메소드는 실제 입력 받은 데이터에서 유효성을 검사하는 로직이 들어가 있다. 몇 가지 분석하면 absoluteUri는 URI의 모든 속성을 가져온다. 두 번째 조건문에서 메인 도메인과 url 파라미터에 입력된 도메인을 비교하여 같으면 리턴하고 다르면 다음 조건으로 넘어간다. 만약 위 조건이 다르면, 시작 문자열이 httphttps가 아니고 로컬 호스트인 경우 로컬호스트 주소를 상대주소로 변경하여 반환한다.

public static bool IsUrlLocalToHost(this HttpRequestBase request, string url) { 
    if (url.IsEmpty())  
    { 
        return false; 
    } 

    Uri absoluteUri; 
    if (Uri.TryCreate(url, UriKind.Absolute, out absoluteUri)) { 
        return String.Equals(request.Url.Host, absoluteUri.Host, 
                    StringComparison.OrdinalIgnoreCase); 
    } 
    else { 
        bool isLocal = !url.StartsWith("http:", StringComparison.OrdinalIgnoreCase) 
            && !url.StartsWith("https:", StringComparison.OrdinalIgnoreCase) 
            && Uri.IsWellFormedUriString(url, UriKind.Relative); 
        return isLocal; 
    } 
}

이제 기존의 LogOn 클래스에서 returnUrl을 검증하기 위해 만들어놓은 IsLocalUrl 메소드를 다음과 같이 사용한다.

public ActionResult LogOn(LogOnModel model, string returnUrl) 
{ 
    if (ModelState.IsValid) 
    { 
        if (MembershipService.ValidateUser(model.UserName, model.Password)) 
        { 
            FormsService.SignIn(model.UserName, model.RememberMe); 
            if (IsLocalUrl(returnUrl)) 
{ 
                return Redirect(returnUrl); 
            } 
            else 
            { 
                return RedirectToAction("Index", "Home"); 
            } 
        } 
        else 
        { 
            ModelState.AddModelError("", "The user name or password provided is incorrect."); 
        } 
    } 
     
return View(model); 
}

6. 참조 사이트



댓글을 달아 주세요

티스토리 툴바