이 도구를 이용하여 허용받지 않은 서비스 대상으로 해킹을 시도하는 행위는 범죄 행위 입니다. 해킹을 시도할 때에 발생하는 법적인 책임은 그것을 행한 사용자에게 있다는 것을 명심하시기 바랍니다.
A1 - XML/XPath Injection (Login Form)
XML은 데이터를 트리 구조의 노드로 표현하며 사용자 정의로 데이터를 분류한다. 아래의 예시에서 최상위 노드는 'movies'이고 하위 노드는 'action'입니다. action의 하위 노드에는 'id'와 'name'이 있습니다. 이렇게 XML로 사용자는 다양한 데이터를 편의에 맞게 분류할 수 있습니다.
Xpath는 일종의 쿼리로, XML 데이터베이스 내용을 선택하고 조작하기 위하여 사용합니다. XML은 트리 구조로 정의되어 있어서 아래의 예시처럼 '/' 문자를 사용하여 최상의 노드부터 질의할 곳을 지정합니다. 이외에도 Xpath에 사용하는 명령어는 다양합니다.
XML과 Xpath 인젝션은 XML 구조에 악의적인 행위를 일으키는 내용을 삽입하거나 Xpath를 조작하여 XML의 내용을 노추하는 취약점 입니다.
xmli_1.php 페이지는 superhero 그룹의 사용자로 로그인하는 기능을 제공하며, superhero 그룹의 사용자 계정 정보는 XML 데이터베이스에 저장됩니다.
인젝션이 가능한지 알아보기 위하여 아이디 입력란에 작은따옴표를 입력해 보도록 하겠습니다.
작은따옴표를 입력하면 아래와 같이 상단에 경고 표시와 함께 오류 구문을 확인할수 있으며, 오류메시지에는 XML과 관련된 경고를 표시하고, 확인해 본 결과 'xpath' 관련 오류라는 것을 알수 있습니다.
XML은 노드들로 이루어져 있고 Xpath로 데이터를 호출합니다. Xpath에서 사용하는 명령어는 아래와 같습니다.
xmli_1.php 페이지는 로그인 페이지로 아이디와 비밀번호를 AND 연산으로 호출한다고 추측할 수 있으며, AND 연산은 OR 연산보다 우선하기 때문에 항상 OR 연산과 함께 참이 되는 쿼리를 입력하면 AND 연산 결과에 상관없이 항상 결과는 참이 됩니다.
' or 1=1 or '
아이디 입력란에 조작한 쿼리를 입력하고 비밀번호 입력란에 아무 문자를 입력후 Login 버튼을 누르게 되면 결과 값에 'neo'라는 사용자로 로그인 된것을 확인할 수 있습니다.
'neo'라는 사용자 외에도 heroes 테이블에 저장된 사용자로도 로그인할 수 있습니다. heroes에 있는 사용자 중 'alice'로 접속할 때는 다음 쿼리를 사용합니다.
alice' or 'alice'='alice
XML 인젝션으로 'neo'를 제외한 나머지 사용자에 접속할 때는 OR 연산자에 '1=1' 대신 문자를 입력하여 결과가 참이 되는 쿼리를 만듭니다.
XML 데이터베이스에 저장되어 있으므로 superhero 그룹 사용자의 계정 정보는 노드 구조로 이루어져 있습니다. 따라서 Xpath를 사용하여 데이터베이스의 구조를 파악합니다.
'xmli_1.php' 페이지는 로그인 페이지이므로 로그인 성공일 때와 로그인 실패일 때의 응답만 제공합니다. 따라서 쿼리 입력에 따른 참과 거짓 결과를 통하여 데이터베이스를 추측하는 Blind SQL 인젝션을 시도합니다.
우선 데이터베이스의 구조를 파악하기 위하여 노드의 개수를 파악하는 'count'함수를 사용하도록 하겠습니다. count 함수의 인자로는 부모 노드의 모든 자식 노드를 조회하는 Xpath를 입력합니다. 즉, 현재 노드를 포함하여 부모 노드의 자식 노드가 총 몇개인지 파악합니다.
그 다음 OR 연산자로 항상 결과가 거짓이 되는 쿼리를 연결한 후 AND 연산자로 정상 쿼리와 연결한 앞의 쿼리가 거짓이면 두 쿼리 모드 거짓이 되므로 쿼리 입력 결과로 로그인 실패 메시지를 출력하고 앞의 쿼리가 참이 되면 로그인에 성공합니다.
그러나 현재 페이지에서는 로그인 검증에 필요한 아이디와 비밀번호를 동일한 쿼리에서 입력받기 때문에 OR 연산자와 항상 참인 쿼리를 연결하여도 Blind SQL 인젝션이 가능합니다. OR 연산자에 입력한 쿼리는 비밀번호를 입력받는 AND 연산자와 연결됩니다. 그 이유는 비밀번호 변수 값이 항상 거짓이 되기 때문입니다.
neo' and count(../child::*)=1 or 'a'='b
neo' and count(../child::*)=2 or 'a'='b
neo' and count(../child::*)=3 or 'a'='b
neo' and count(../child::*)=4 or 'a'='b
neo' and count(../child::*)=5 or 'a'='b
neo' and count(../child::*)=6 or 'a'='b
1부터 입력한 결과 부모 노드의 자식 노드는 총 6개인것을 확인 하였습니다. 이처럼 count 함수와 Xpath를 적절하게 사용하면 노드 구조를 파악할 수 있습니다.
이번에는 부모 노드 명을 확인하기 위한 쿼리를 입력해 보도록 하겠습니다. 'name'은 인자에 입력된 노드명을 출력하는 함수이고, 'string-length'는 인자로 받은 문자열의 길이를 반환하는 함수 입니다. 먼저 부모 노드 명의 길이를 추측합니다. 1부터 값을 입력해 보시기 바랍니다.
neo' and string-length(name(parent::*))=6 or 'a'='b
다음은 부모 노드의 이름을 추측하기 위한 쿼리를 입력해 보도록 하겠습니다. name 함수를 사용하여 substring 함수에 부모 노드 명을 인자로 입력합니다.
substring 함수를 사용
첫 번째 인자에 문자열을 입력
두 번째 인자에서 문자열을 잘라낼 시작위치를 지정
세 번째 인자에서 시작 위치부터 몇 개의 문자를 반활할지 입력
다음의 쿼리로 부모 노드의 첫 번째 문자를 추측합니다. a부터 임력할 결과 h를 입력하면 로그인에 성공 합니다. 즉 부모 노드 명은 'hOOOOO' 입니다.
neo' and substring(name(parent::*),1,1)='h' or 'a'='b
다음은 부모 노드의 자식 노드 명을 추측하기 위한 쿼리를 입력해 보도록 하겠습니다. 'position' 함수는 MySQL에서 limit 연산자와 같은 기능을 합니다. 1부터 입력하여 결과값을 확인한 후 자식 노드 명의 길이를 확인해 보시기 바랍니다.
neo' and string-length(name(../child::*[position()=1]))=4 or 'a'='b
다음 쿼리는 첫 번째 자식 노드 명을 추측하기 위하여 substring 함수를 사용합니다. a부터 입력한 결과 h를 입력하면 로그인에 성공 합니다. 따라서 첫 번째 자식 노드는 'hOOO'이라는 것을 확인 할수 있습니다.
neo' and substring(name(../child::*[position()=1]),1,1)='h' or 'a'='b
부모 노드의 자식 노드는 총 6개이므로 position 함수에 입력한 숫자를 변경하며 노드 명을 확인 합니다. 계속 노드 명을 추측하다 보면 모든 자식 노드 명이 같은 것을 알수 있고, XML 데이터베이스이므로 사용자 정의로 같은 속성을 지닌 노드는 노드 명이 동일 하다는 것을 알수 있습니다. 따라서 현재 노드까지의 데이터베이스 구조는 다음과 같습니다.
또 다른 방법으로 현재 노드를 통하여 노드 명을 알아 냅니다. 현재 노드 명의 글자 수를 찾기 위하여 노드의 이름을 호출하는 name 함수와 string-length 함수를 사용합니다. '.'은 Xpath에서 현재 노드를 의미
neo' and string-length(name(.))=4 or 'a'='b
현재 노드 명을 찾기 위하여 name 함수와 substring 함수를 사용합니다. 다음 쿼리로 현재 노드명의 첫 번째 문자를 추측합니다.
neo' and substring(name(.),1,1)='h' or 'a'='b
노드의 그다음 문자를 확인하려면 substring 함수에서 두 번째 인자를 2로 변경한 후 추측합니다. 현재 노드의 자식 노드 개수를 찾기 위하여 count 함수를 사용하는데, hero 노드가 6개 이므로 '[]'로 첫 번째 자식 노드를 지정합니다. 첫 번째 hero 노드의 자식 노드는 총 6개이고, 전체 노드 개수를 조회하려면 '[]'를 제거한 쿼리를 입력합니다.
neo' and count(/heroes/hero[1]/child::*)=6 or 'a'='b
자식 노드 명의 글자 수를 찾기 위하여 자식 노드 중 위치를 지정하는 position 함수와 문자열의 길이를 출력하는 string-length 함수를 사용합니다. Xpath 쿼리인 '//'를 사용하여 부모 노드 명을 몰라도 자식 노드 명을 알아 낼수 있습니다. 1부터 입력한 결과 2를 입력하면 로그인에 성공하며, 첫 번째 자식 노드의 문자열 길이는 2라는 것을 알수 있습니다.
neo' and string-length(name(//hero[1]/child::*[position()=1]))=2 or 'a'='b
첫 번째 자식 노드 명을 알아내기 위하여 이번에는 substring함수를 사용합니다. a부터 입력한 결과 i를 입력하면 로그인에 성공합니다. 즉, 첫 번째 자식 노드 명은 'iO'라는 것을 알수 있습니다. substring 함수의 두 번째 인자 값을 2로 변경하여 자식 노드의 두번째 문자도 확인해 보시기 바랍니다.
neo' and substring(name(//hero[1]/child::*[position()=1]),1,1)='i' or 'a'='b
첫 번째 자식 노드 값의 길이가 몇인지 알아내기 위하여 문자열을 반환하는 string과 문자열의 길이를 반환하는 string-length 함수를 사용하도록 하겠습니다. 1부터 입력한 결과 바로 로그인에 성공하는 것을 확인할수 있습니다. 'id'라는 자식 노드 명과 노드에 입력된 문자열의 길이가 1이라는 점에서 순서 번호를 뜻한다고 추측할수 있습니다.
neo' and string-length(string(//hero[1]/id))=1 or 'a'='b
길이를 알았으니 이번에는 첫 번째 자식 노드의 값을 알아내기 위하여 string 함수와 substring 함수를 사용합니다. 1부터 입력한 결과 바로 로그인에 성공하는 것을 알수 있습니다. 즉, 첫 번째 자식노드 'id'에서 순서 번호 1이 저장 됩니다.
neo' and substring(string(//hero[1]/id),1,1)=1 or 'a'='b
현재까지 SQL 인젝션으로 알아낸 데이터베이스 정보는 아래와 같습니다.
다음 자식 노드의 정보를 알아내려면 position 함수에 대입한 값을 증가 시켜줍니다.
neo' and string-length(name(//hero[1]/child::*[position()=2]))=5 or 'a'='b
계속해서 그다음 노드 정보를 알아내려면 '[]' 안의 숫자를 변경합니다. 아니면 계속 'position'의 함수의 값을 증가시켜주어도 확인이 가능합니다.
neo' and substring(string(//hero[2]/id),1,1)=2 or 'a'='b
난이도 상에서는 작은따옴표를 사용하여도 XML 오류 메시지를 확인할수 없습니다. xmli_1.php 페이지의 코드를 확인하면 'xmli_check_1' 함수를 사용하여 입력 데이터를 우회 합니다.
xmli_check_1 함수는 functions_external.php에 정의되어 있고 'str_replace' 함수를 호출합니다. str_replace 함수는 인젝션에 사용되는 문자를 공백으로 대체 합니다.
'Code::Security > 비박스(BWAPP)' 카테고리의 다른 글
비박스(bWAPP) A2 - Broken Auth & Session Mgmt (인증 및 세션 관리 취약점) (0) | 2022.06.13 |
---|---|
비박스(bWAPP) XML/XPath Injection (Search) (0) | 2021.08.12 |
비박스(bWAPP) SQL Injection - Blind (WS/SOAP) (0) | 2021.06.22 |
비박스(bWAPP) SQL Injection - Blind - Time-Based (0) | 2021.06.17 |
비박스(bWAPP) SQL Injection Blind - Boolean-Based (0) | 2021.04.15 |