※ 주의 : 보안 관련 학습은 자유이지만, 학습 내용을 사용한 경우 법적 책임은 행위자에게 있습니다. (Note: Security-related learning is allowed, but the individual is solely responsible for any legal consequences arising from the use of the acquired knowledge.)
※ 공부 기록용 포스트이며, 검수를 하고 올리지만 간혹 잘 못된 정보가 있을 수도 있습니다.
[보안 공부] 11주 차 - XSS 활용 기초 PC 화면에서 보시기 적합하도록 작성되었습니다.
수정 이력 : 2024-06-29 18:16 - VScode사용으로 코드 가독성 증가, iframe 내용 긁어오는 부분 추가.
XSS 활용 기초
마땅한 단어가 생각이 나지 않아서 제목을 XSS 활용 기초라고 붙이긴 했는데,
XSS에 대한 개념은 알고 있는 상태에서 XSS를 활용 함에 있어서 알아야 할 기초를 공부해 본다.
1. HTML에서 스크립트 부분이 아닌 태그 안에서도 XSS가능하다. (이벤트 핸들러 이용)
</body> onerror 이벤트 핸들러 테스트 화면, 이미지 태그 2개를 삽입했고, 하나는 정상적인 태극기 이미지, 하나는 에러를 유도해서 네이버 페이지로 리다이렉션을 했다. 브라우저는 이미지를 띄우려고 빙글빙글 돌다가 안되니까 엑박을 띄우고 리다이렉트 시켰다.
2. 스크립트는 해당 페이지에서만 작동하기에 다른 페이지로 리다이렉션 하면 XSS 불능.
ID와 비밀번호를 키로거로 빼내기 위해 XSS로 키로거 삽입을 한다고 가정했을 때, XSS 삽입 위치가 게시판이면 아무런 의미가 없다. 키로거를 로그인 페이지에서 삽입을 해야 한다. 현실적으로 어렵기 때문에, XSS로 다른 정보를 탈취해야 한다. (3번으로)
3. httpOnly, secure, sameSite 옵션으로 쿠키를 탈취할 수 없는 경우가 대부분이라 쿠키 탈취 대신 다른 공격을 해야 한다.
(2번에서 계속) 그럼 쿠키라도 빼내야 하는데, 개발 단계에서 기본적으로 httpOnly, secure, samesite 옵션을 붙여 놓기 때문에 XSS로 쿠키도 못 빼낸다.
중요한 쿠키들에는 해당 옵션들이 붙어있다.
해당 페이지에 있는 다른 정보라도 빼내어야 한다. 이럴 때 쓰는 method가 getElement~이다.
getElementById(id) : 지정된 ID를 가진 요소를 반환한다.
getElementsByClassName(className) : 지정된 클래스를 가진 모든 요소를 반환한다.
getElementsByTagName(tagName): 지정된 태그 이름을 가진 모든 요소를 반환한다.
getElementsByName(Name) : 지정된 name 속성을 가진 모든 요소를 반환한다.
(주로 input, select, textarea 폼에서 사용)
querySelector(selector) : 주어진 CSS 선택자와 일치하는 첫 번째 요소를 반환한다.
querySelectorAll(selector) : 주어진 CSS 선택자와 일치하는 모든 요소를 반환한다.
예시 - getElementsByName(Name) 로그인 페이지에서 '아이디'라고 적힌 placeholder값을 선택하고 콘솔에 출력해보자개발자 도구를 열어서 HTML 구성을 확인해 보고 name이 무엇으로 되어 있는지 확인한다.개발자 도구 - 콘솔에서 해당 명령어를 입력해보자.
①document.getElementsByName('id')를 입력하니 NodeList라는 객체를 반환했는데 리스트 형식이다. 그래서 [0]를 붙여서 ②명령어를 입력해 본다.
②document.getElementsByName('id')[0]를 입력하니 <inputtype="text"id="id"name="id"placeholder="아이디"title="아이디"class="input_text"maxlength="41"value=""> 라는 객체를 반환했다.
③ 도트 연산자로 DOM 요소 객체의 속성에 접근할 수 있다. document.getElementsByName('id')[0].placeholder 를 입력하여 placeholder 속성을 불러오자. '아이디'라는 문자열이 나왔다.
해당 요소를 공격자 페이지로 보내면 된다.(앞 시간에 한 부분이라서 과정은 생략)
getElement~로 일부를 긁어 올 수도 있고, document.documentElement.outerHTML;로 HTML 코드 전부를 긁어올 수도 있다. document.body.innerHTML; 로 body부분 코드를 전부 긁어올 수도 있다. (이 부분은 https://freestyle.tistory.com/46 에서 2.실행 - ②탈취할 정보의 태그 위치를 모르는경우 참고)
이번엔 다른 페이지에 가서 정보를 가져와 보자. (iframe 태그)
당연하지만 원래, 스크립트는 해당 페이지에서만 작동하기 때문에, 해당 페이지에 중요한 데이터가 표출되거나 ID, PW를 입력하지 않다면 XSS는 별 쓸모가 없다. 이를 해결하기 위한 방법이 iframe 태그이다.
iframe은 window(창문)나 액자(frame)로 생각하면 된다. 운영체제 윈도우가 왜 '윈도우'인가? 창을 자꾸 띄워서 윈도우이다(사실이다). 웹 사이트 내부에 또 액자(또는 창문)를 만들어 다른 사이트를 열 수 있다.
아래는 iframe 태그 예시이다.
<iframesrc="https://공격할 로그인 페이지"></iframe> 이렇게 설정하여, 키로거 스크립트가 작동 중인 페이지에서, iframe으로 네이버 로그인창을 띄워서 로그인을 하도록 유도한다던지, 개인정보가 나와있는 페이지로 이동시켜서 getElement~ method로 개인정보를 긁어올 수 있다.
iframe 내부의 내용을 콘솔에 출력하는 방법
실습을 위해 steal info 1,2번 문제의 게시판을 이용하였다. 스크립트 실행 취약점은 이제 다들 아시리라 생각한다.
코드 차근차근 읽어보면 중간에 뜬금 없는 것(아래 설명) 하나 빼고는 어렵지 않다. 스크립트 부분은 iframe 내부의 내용을 가져오는 것이다. 여기서는 HTML전체를 가져오는 것이다.
console.log(outerHTML);// iframe 내부 문서의 전체 HTML 출력
};
});
</script>
내가 쓴 게시글(761번 글)을 이 글에 iframe으로 띄우고 있다. 716번 게시글
var iframeDocument = iframe.contentDocument || iframe.contentWindow.document; 이 부분의 코드가 조금 특이한데, 알아보니 구버전 브라우저 호환성 때문에 이렇게 작성하는 것이라고 한다. contentWindow는 IE 5.5버전, contentDocument는 IE 8버전부터 지원. 지금은 이 정도만 알아보자 더 알아보니 필자도 잘 모르겠다.. (알려주실 분 댓글 환영)
이렇게 스크립트를 작성하고 게시글에 들어가면 길이 : 9초 / 스크립트가 실행되며 iframe안의 내용을 콘솔에 출력한다.iframe내용 출력 화면. 이제 응용해서 steal info문제를 풀면서 요청으로 던져보자
정보를 탈취하지 않는다고 하더라도 iframe태그를 만들고 크기를 0으로 설정하여 나도 모르게 어떤 페이지에 접속하도록 할 수도 있다.(조회수 올리기 등으로 악용가능)
하지만 iframe 주소를 네이버나 구글 등으로 하면 작동하지 않을 것이다. iframe 방어 방법이 몇 가지 있기 때문이다.
X-Frame-Options 헤더 : 웹 페이지가 iframe, frame, embed 등의 요소에 의해 불러와지는 것을 방지한다. 옵션 : DENY(전부 불가), SAMEORIGIN(동일 출처는 가능) 적용 예시 : Apache인 경우 Header set X-Frame-Options "DENY" 적용 예시 : Nginx인 경우 add_header X-Frame-Options "DENY";
서버 코드에서 직접 설정 예를 들어 php 작성 시 아래와 같이 설정할 수 있다. header("X-Frame-Options: DENY"); header("Content-Security-Policy: frame-ancestors 'none'");
등등..
그렇다면 그런 옵션이 적힌 헤더를 무시하면 되는 것 아닌가? → 현대의 브라우저는 보안 헤더를 무시할 수 없다고 한다. (강제인듯하다.) → 구글 확장 프로그램으로 하면 가능은 하다고 한다. (사용자측에서 설치해야 함.)
4. HTML에서 스크립트 위치가 탈취하는 값보다 위에 있어서 탈취할 값이 나오지도 않았는데 스크립트가 먼저 실행되어 버려 undefine이 발생되는 경우
탈취할 값이 웹페이지에 로드되기도 전에 스크립트가 실행되어 undefine(응? 없는데??)를 띄우는 경우, addEventListener method를 사용할 수 있다. add 이벤트 리스너는 웹 페이지에 특정 이벤트가 발생했을 때 실행하는 함수를 지정하는 것이다. 여기서 '모두 로딩이 되면' 조건을 붙이면 되는 것이다.
기본 구조 : element.addEventListener(event,handler,options);
element: 이벤트 리스너를 추가할 대상 요소.
event: 반응할 이벤트 유형(예: 'click', 'mouseover', 'keydown', 'DOMContentLoaded', 'load' 등).
handler: 이벤트가 발생했을 때 실행될 함수.
options (선택 사항): 이벤트 리스너의 동작을 제어하는 옵션.
예시 - window.addEventListener('DOMContentLoaded',function(){실행할함수}); 테스트를 위하여 인터넷 속도를 낮춘다. 개발자도구 - 네트워크 - 해당 부분에 보면 인터넷 속도 스로틀링 설정이 가능하다. 빠른 3G 정도를 추천한다. 캐시 사용 안 함도 체크해 준다. 로드가 완료되기 전에 재빨리 콘솔에 window.addEventListener('DOMContentLoaded',function(){alert('로딩완료');});를 넣어본다. 길이 : 26초 / 페이지가 완전히 로드되기 전에 alert가 뜬 것은 DOMContent는 다 받은 상태라서 그런 것 같다. 비슷한 기능으로 'load'가 있다. load는 모든 리소스(이미지, CSS 등)가 로드된 이후 발생한다.
(하지만 네이버에서 테스트해 보니 이미지가 다 뜨기 전에 alert가 떴다. 네이버 웹페이지의 표시 방법이 생각과 다른 것 같다.)
add이벤트 리스너 이외에 setTimeout(function() {실행 내용} , 딜레이); method도 사용이 가능하다.
기본 문법 - setTimeout(function,delay, [param1,param2, ...]);
예시 - setTimeout(function(){
location.href="공격자의 웹사이트";
}, 2000);
이 경우, 2000ms (2초) 뒤에 스크립트가 작동하여 공격자의 웹사이트로 이동한다.
setTimeout method도, 스크립트가 순차적으로 실행됨에 따라 딜레이만 추가될 뿐, 다른 필요한 요소가 로드되지 않는 것 아닌가 생각했는데, 알아보니 Javascript가 스크립트이니 절차지향도 맞지만, 비동기 프로그래밍과 이벤트 기반 프로그래밍을 지원하여, 지연 시간 동안 다른 요소들이 로드되는 것을 확인하였다.