소프트웨어 개발보안 가이드 분석(2021) : XML 삽입

검증되지 않은 외부 입력 값이 XQuery 또는 XPath 쿼리문을 생성하는 문자열로 사용되어 공격자가 쿼리문의 구조로 임의로 변경하고

임의의 쿼리를 실행하여 허가되지 않은 데이터를 열람하거나 인증절차를 우회할 수 있는 보안약점

소프트웨어 개발보안가이드(2021), 한국인터넷진흥원

XML 삽입의 경우 2021년 소프트웨어 개발보안 가이드가 통합되며 기존에 있었던 XPath 삽입과 XQuery 삽입이 합쳐져 새로운 항목으로 추가되었습니다.

데이터베이스와 연동된 웹 애플리케이션에서 XPath 및 XQuery 질의문에 대한 필터링이 제대로 이루어지지 않으면 공격자는 입력이 가능한 폼에 조작된 XPath 혹은 XQuery 질의문을 삽입하여 인증 우회를 통해 인가받지 않고 데이터를 열람할 수 있는 보안약점입니다.

XQuery 수행 과정
  1. 공격자는 유효성 검증을 하지 않는 입력 가능한 폼에 XQuery 질의문을 입력합니다.(name:a' or 'a' = 'a)
  2. 서버는 질의문을 처리하지만 변조된 질의문은 항상 참(True)의 결괏값을 반환합니다.( food[name =' a' or 'a'='a '] 형태를 이룸)
  3. 결국 그림과 같이 모든 price 정보가 공격자에게 노출됩니다.

취약한 코드 예시

  1      String userInput = getUserInput();
  2      String xquery = "for $item in //bookstore/book where $item/author = '" + userInput + "' return $item/title";
    }

  3  private static String getUserInput() {
  4      Scanner scanner = new Scanner(System.in);
  5      System.out.print("작가 이름을 입력하세요: ");
  6      return scanner.nextLine();
    }
  • 1행 : getUserInput 메서드를 호출하여 입력 가능한 폼으로부터 작가 이름을 입력받습니다.
  • 2행 : 입력값을 검증하지 않고 입력된 작가 이름에 해당하는 책 제목을 조회하는 쿼리입니다.
  • //bookstore/book 경로에 있는 모든 책 요소를 검색하여 item/title을 통해 작가 이름과 일치하는 책의 제목을 반환합니다.
  • 입력 가능 폼에 입력받은 값은 userInput 변수에 유효성 검증 없이 저장됩니다. 만일 이곳에 ' or 1=1 or ''=' 와 같은 입력이 들어가게 된다면 XQuery는 항상 참이 되기 때문에 모든 책 정보를 반환할 것입니다.

안전한 코드 작성 가이드

1. 이스케이프 처리

1 String userInput = getUserInput();
2 String escapedUserInput = escapeXml(userInput);

3 String xquery = String.format("for $item in //bookstore/book where $item/title = '%s' return $item", escapedUserInput);
}

4
private static String escapeXml(String input) {
  return input.replaceAll("&", "&")
              .replaceAll("<", "&lt;")
              .replaceAll(">", "&gt;")
              .replaceAll("'", "&apos;")
              .replaceAll("\"", "&quot;");
    }
  • 4행 : escapeXml 메서드를 통해 특수문자들을 모두 HTML Entity로 이스케이프 처리해 줍니다.
  • 결과적으로 입력값을 이스케이프 처리한 후에 XQuery에 포함시켜 조작된 XQuery가 들어와도 영향을 받지 않게 됩니다.

2. 바인딩 변수

    1       String userInput = getUserInput();

    2       String xquery = "for $item in //bookstore/book where $item/title = $title return $item";
            
    3       XQDataSource dataSource = new org.exist.xqj.ExistXQDataSource();
            // eXist DB에 연결에 필요한 속성을 설정하고 
            // getConnection 메서드를 호출하여 XQConnection 객체를 생성합니다.
    4       XQConnection conn = dataSource.getConnection();

    5       XQPreparedExpression expr = conn.prepareExpression(xquery);
    6       expr.bindString(new QName("title"), userInput, null);

    7       XQResultSequence result = expr.executeQuery();
  • 2행 : XQuery이 다음과 같이 정의되어 책의 제목이 userInput 값과 일치하는 책을 찾아 반환합니다. 이때 $title을 이용하여 사용자 입력을 나타내는 바인딩 변수로 사용합니다.
  • 3~4행 : eXist DB에 연결에 필요한 설정을 수행합니다.
  • 5행 : XQConnection 객체를 사용하여 XQuery를 파싱 하여 컴파일 후 XQPreparedExpression 객체로 생성합니다.
  • 6행 : 사용자 입력을 XQuery에 바인딩 합니다. 바인딩 변수 &title에 입력값이 전달됩니다.
  • 7행 : 미리 컴파일된 XQuery를 실행하여 그 결과로 XML 문서에서 선택된 정보를 담은 객체(title)을 가져와 출력됩니다.
  • 결국 입력 가능한 폼에 XQuery에 바인딩 하여 외부에서 삽입된 코드를 실행하지 않으므로 XQuery Injection 공격에 대응할 수 있습니다. 이 방법은 주로 SQL에서 PreparedStatement와 유사한 방법으로 사용되지만, XQuery나 XPath와 같은 다른 쿼리 언어에서도 적용할 수 있습니다.

  • 이전 소프트웨어 개발보안 가이드 분석(2021) : 부적절한 XML 외부개체 참조
  • 다음 소프트웨어 개발보안 가이드 분석(2021) : LDAP 삽입