앵하니의 더 나은 보안
HTTP 보안헤더 본문
들어가기전에
웹 서비스에서 XSS를 공략할 때, Response에 입력한 값이 그대로 노출되는데 브라우저에서는 동작하지 않았던 적이 있는가? 필자는 그런적이 있는데, 그땐 졸업한지 얼마안됐던 때라 왜 안되지라고만 생각하고 딱히 정답은 못찾았었다. 그렇게 우여곡절을 겪다가 대뜸 응답 헤더에 나와있는 x-xss-protection이라는 이상한 애를 찾고는, 아 응답 헤더에 보안 관련 설정 값을 넣을 수 있구나, 추후에 정리해야겠다 생각했지만 공부만 하고 따로 정리한적은 없어서 조금 여유있는 시간을 활용해 HTTP 보안헤더에 대해 정리해보려 한다.
보안헤더는 크게 5가지가 있는데 이 중에 XSS/CSRF 관련 보안헤더만 4개정도를 차지한다.
각각 어떻게 생겼고, 어떤 역할을 하는지 그리고 어떤 원리로 공격을 방어하는지 한번 알아보자.
X-XSS-Protection
얘는 사실상 이제 실효성이 없는 헤더다. 기존에는 크롬에서든, IE든 엣지든 지원을 했었는데, 특정 버전 이상에서는 더 이상 지원하지 않는다. 그도 그럴게, 그냥 X-XSS-Protection이 설정됐든 안됐든 XSS에 노출되지않게 브라우저가 잘 보호해주면 되기때문에(항상 X-XSS-Protection이 1로 설정됐다고 간주하면 되기때문에) 서버의 세팅 값에 의존할 필요가 없다. 서버 사이드에서 굳이굳이 설정할 순 있겠지만, 그냥 수고스러움이 늘어나는거고 실효성은 없다 보면되겠다. 브라우저 차원에서 이미 설정 돼 있는 꼴이니까.
그래도 옵션 값에 대해 알아보자면, HTTP 응답 헤더의 X-XSS-Protection 설정 값에 따른 의미는 아래와 같다.
X-XSS-Protection: 0
X-XSS-Protection을 설정하지 않음
X-XSS-Protection: 1
X-XSS-Protection을 설정
X-XSS-Protection: 1; mode=block
X-XSS-Protection을 설정하고, 의심되는 부분이 발견되는 즉시 페이지 렌더링 중지
X-XSS-Protection: 1; report=<reporting-uri>
X-XSS-Protection을 설정하고, 관련 문제 발생 시 <reporting-uri>에 관련 패킷 전송
X-XSS-Protection 필터링 원리는 브라우저마다 다르다.
과거 IE에서 X-XSS-Protection 관련 필터링에서 exploit이 발생해 X-XSS-Protection을 우회할 수 있었던 X-XSS-Nightmare라는 취약점이 존재했는데, 이 부분은 글의 뒷부분에서 다루겠다.
Content-Security-Policy(CSP)
CSP 헤더는 설정된 사이트에 어떤 스크립트/자원을 로드할지 화이트리스트 방식으로 제한한다.
그래서 이 보안 헤더는 XSS/Click Jacking을 방지하는데에 쓰인다.
CORS랑 성격이 엄청 유사한데, CORS 설정은 서버의 자원을 특정 외부에 공유하겠다고 지정하는것이고, CSP는 특정 외부의 자원을 본 사이트에 로드할 수 있도록 허용하겠다는 것이니 유사하면서도 서로 상반되는 개념이라고 이해하면 되겠다.
그리고 CORS에 비해서 CSP는 속성 값이 디테일하게 많다. 모든 속성의 의미를 달달 외워 알아놓을 필요는 없지만, 아 이런게 있구나 수준으로 이해하고, 다음에 CSP를 만난다면 본 소개글을 참고하자.
CSP 형태
CSP 헤더는 크게 3가지 형태로 나뉜다.
Content-Security-Policy : W3C에서 지정한 표준헤더, 대부분 이걸 사용
X-Content-Security-Policy : Firefox/IE 구형 브라우저에서 사용되는 헤더
X-WebKit-CSP : Chrome 기반의 구형 브라우저에서 사용되는 헤더
하지만 이 중에 주로 사용되는 형태는 Content-Security-Policy고, 다른 둘은 보기 힘드니 저런 형태는 똑같이 CSP를 뜻하는구나 하고 알고 넘어가면 될 듯 하다.
위 3가지 형태 중 Content-Security-Policy가 설정됐다고 가정하면, 다음과 같은 형식으로 응답 헤더에 표기된다.
Content-Security-Policy: script-src 'self' https://apis.google.com; default-src https: 'unsafe-inline';
한 눈에 뭐 어떤걸 어떻게 허용하겠다는건지 이해하기 어렵지만, CSP가 directive와 value로 그리고 각 단위의 구분은 세미콜론(;)으로 이루어진다는 것만 알면 이해하기 쉽다.
CSP 구성
directive
directive는 사이트에서 자원을 허락하는 명령어 또는 지시사항으로, directive만 사용할 수도, directive와 value를 같이 사용할 수도 있다. CSP가 존재하는 경우 해당 CSP를 보고 이해할 수준이면 되니, 어떤 directive에서 value가 필요한지까지는 알 필요는 없고 이것도 마찬가지로 이런게 있구나 하고 넘어간 뒤에 나중에 필요할때 검색해 찾아보면 되겠다.
directive
|
설명 |
base-uri
|
페이지의 <base> 요소에 나타날 수 있는 URL을 제한 |
block-all-mixed-content
|
혼합 콘텐츠 리소스 요청을 차단 |
child-src
|
작업자와 삽입된 프레임 콘텐츠에 대한 URL을 나열 예: child-src https://youtube.com을 사용하면 다른 출처가 아니라 YouTube에서 가져온 동영상 삽입 가능 지원 중단된 frame-src 지시문 대체제로 해당 지시문 사용 권고 |
connect-src
|
(XHR, WebSockets 및 EventSource를 통해) 연결할 수 있는 출처를 제한 |
default-src
|
value에 기재된 값에 의해 child-src, connect-src, font-src, frame-src, img-src, manifest-src, media-src, object-src, prefetch-src, script-src, script-src-elem, script-src-attr, style-src, style-src-elem, style-src-attr, worker-src 값이 설정 단 위 directive 중 CSP로 동시에 명시된 directive가 있을 경우 해당 directive에 영향을 미치지 않음 |
font-src
|
웹 글꼴을 제공할 수 있는 출처를 지정 |
form-action
|
<form> 태그에서의 제출을 위해 유효한 엔드포인트를 나열 |
frame-ancestors
|
현재 페이지를 삽입할 수 있는 소스를 지정 이 지시문은 <frame>, <iframe>, <embed> 및 <applet> 태그에 적용되며, <meta> 태그에서 사용할 수 없고 HTML 이외의 리소스에만 적용 |
frame-src
|
지원 중단 frame-src 대신 child-src 사용 권고 |
img-src
|
이미지를 로드할 수 있는 출처를 정의 |
manifest-src
|
허용되는 웹 애플리케이션 매니페스트 파일의 출처를 명시적으로 지정 |
media-src
|
동영상과 오디오를 제공하도록 허용되는 출처를 제한 |
<meta> element support
|
특정 요소 유형 (예: <iframe>, <audio>, <video>)에 대한 출처을 정의 |
object-src
|
플래시와 기타 플러그인에 대한 제어를 허용 |
plugin-types
|
지원 중단 |
prefetch-src
|
지원 중단 |
referrer
|
지원 중단 |
report-sample
|
브라우저가 정책 위반을 발생시킨 구체적인 요소나 스크립트 위치를 포함하여 정책 위반 보고서를 생성 |
report-to
|
CSP 위반 사항을 비동기적으로 보고하는데 사용 |
report-uri
|
콘텐츠 보안 정책 위반 시 브라우저가 보고서를 보낼 URL을 지정 |
require-trusted-types-for
|
Trusted Types를 사용하여 DOM 조작에 대한 제한을 설정 |
script-src
|
웹 페이지에서 스크립트를 어떤 출처에서 로드할 수 있는지 제어 |
External scripts with hash
|
웹 페이지에서 외부 스크립트를 로드할 때 스크립트의 해시 값을 포함하여 스크립트 출처를 검증 |
inline-speculation-rules source expression
|
JavaScript의 인라인 실행 및 컴파일을 제어하는 규칙을 설정 |
Source expression allowing WebAssembly execution
|
웹 어셈블리(WebAssembly) 실행을 허용하는 출처 표현식을 설정 |
script-src-attr
|
웹 페이지에서 동적으로 생성된 스크립트의 출처를 제어 |
script-src-elem
|
웹 페이지 내에서 동적으로 생성된 스크립트 요소의 출처를 제어 |
strict-dynamic
|
동적으로 로드되는 스크립트를 허용하면서도 CSP 정책을 강화하는 역할 |
style-src
|
script-src에서 스타일시트에 해당 |
style-src-attr
|
웹 페이지에서 동적으로 생성된 스타일 속성 (style 속성)의 출처를 제어 |
style-src-elem
|
웹 페이지 내에서 동적으로 생성된 스타일 요소의 출처를 제어 |
trusted-types
|
Trusted Types를 활성화하여 웹 애플리케이션에서 안전한 DOM 조작을 강제 |
unsafe-hashes
|
해시가 매칭되지 않을 때 브라우저가 스크립트를 차단하지 않고, 대신 보안 보고서를 생성 |
upgrade-insecure-requests
|
사용자 에이전트에 URL 구성표를 다시 작성하여 HTTP를 HTTPS로 변경하도록 지시 |
worker-src
|
웹 워커(worker) 스크립트의 출처를 제어 |
value
'none' : 어떠한 리소스도 허용하지 않는다. none은 다른 값과 같이 사용할 수 없다.
'self' : 현재 origin의 리소스만 허용한다. 하위 도메인은 허용하지 않는다.
'unsafe-inline' : 스크립트나 스타일을 인라인으로 사용하는 것을 허용한다.
'unsafe-eval' : eval, setImmediate와 같은 api를 사용하는 것을 허용한다.
<Host> : 직접 허용하는 호스트를 명시한다.
http://example.com , *.example.com, https://example.com/b.html과 같은 값들이 될 수 있다.
<Scheme:> : 허용하는 프로토콜을 명시할 수 있다. http:, https:, data: 와 같은 값들이 될 수 있다.
CSP 해석
그럼 이제 위 내용을 토대로 아까 설정됐던 CSP를 해석해보자.
Content-Security-Policy: script-src 'self' https://apis.google.com; default-src https: 'unsafe-inline';
directive로 설정된 script-src에 의해, script 로드는 본 사이트 도메인과 https://apis.google.com으로 제한되며, 그 다음 directive는 세미콜론(;)에 의해 구분돼 default-src라는걸 알 수 있다.
그리고 default-src에 의해 child-src, connect-src, font-src, frame-src, img-src, manifest-src, media-src, object-src, prefetch-src, script-src, script-src-elem, script-src-attr, style-src, style-src-elem, style-src-attr, worker-src 값이 https:와 ‘unsafe-inline’으로 설정되지만, script-src는 별도로 명시된 상태이므로 default-src의 영향을 받지 않고 그대로 ‘self’와 https://apis.google.com에서만 허용된다.
그래서 CSP를 설정하면 XSS/ClickJacking이 왜 방지 된다는건데?
CSP가 아래와 같이 설정된 사이트 cspsetting.com이 있다고 가정해보자
content-security-policy : script-src 'self'; img-src 'self'; child-src 'none';
그럼 cspsetting.com에서는, <script src=http://hack_script.domain/hack_script.js></script> 라는 형태의 악성 스크립트를 삽입할 수 없게되고, 마찬가지로 <img src=x onerror=alert('test')/> 같은 형태의 악성 스크립트 또한 활용할 수 없게 된다. src가 ‘self’로 설정됐고, script의 src=hack_script.domain과 img의 src=x는 self에 해당하지 않기때문이다.
그러니까 XSS를 절대적으로 방어해주는 강력한 보안헤더라기 보다는, 일부 유형을 완화해준다 정도의 수준이라고 이해하면 되겠다.
마찬가지로 child-src가 none으로 설정됨에 따라 어디에서도 cspsetting.com을 frame 형식으로 호출할 수 없어서 frame을 활용한 clickjacking 또한 불가하다.
X-Frame-Options
외부 사이트에서 X-Frame-Options가 설정된 사이트에 frame으로 호출할 수 있는지 없는지를 설정한다.
X-Frame-Options은 아래 셋 중 하나의 형태로 사용된다.
- X-Frame-Options: deny
어떠한 사이트에서도 frame 안에 X-Frame-Options가 설정된 사이트를 호출할 수 없다. - X-Frame-Options: sameorigin
같은 origin에 한해서만 frame 안에 X-Frame-Options가 설정된 사이트를 호출할 수 있다. - X-Frame-Options: allow-from https://example.com/
allow-from에 기재된 사이트에 한해서만 X-Frame-Options가 설정된 사이트를 호출할 수 있다.
자 그럼 이 X-Frame-Options와 Content Security Policy의 child-src는 뭐가 다른걸까?
X-Content-Type-Options
MIME type(content-type)을 고정시키는 용도로 사용하는 보안헤더다.
MIME type을 고정시키는 이유는 MIME sniffing(content sniffing)을 방지하기 위함이다.
이 MIME sniffing이라는건 웹 서버에 접근할 때 발생하는 HTTP Response의 내용에 따라, 브라우저에 의해 MIME Type(content-type)이 임의로 수정될 수 있는데, 이를 의도적으로 악용하는 것을 의미한다. 악의적인 의도를 가진 해커가 악성 스크립트가 담긴 PNG를 업로드 한 후, 해당 파일을 HTML로 해석할 수 있도록 MIME sniffing을 수행(image/x-png > text/html)하게 되면, MIME sniffing을 통해 XSS가 발생할 수 있다.
그래서 아래와 같이 X-Content-Type-Options를 설정함으로써 MIME Type(content-type)을 웹 서버에서 지정한대로 고정해 MIME sniffing을 방지한다.
X-Content-Type-Options : nosniff
Strict-Transport-Security(HSTS)
Strict-Transport-Security 설정은 http 통신을 https로 강제하기 위한 보안헤더다.
이는 부분적으로 https를 적용한 서비스에서 SSL Strip의 발생 위험을 방지하기 위한 보안헤더로,
http 통신을 시도하면 https 통신으로 강제시키는 역할을 해 최초 통신부터 https통신을 수행함으로써 중간자가 SSLStrip을 시도하지 못하게 한다.
이 HSTS는 아래와 같이 표시되며 각 뜻은 다음과 같다.
- Strict-Transport-Security: max-age=<expire-time>
HSTS 설정 유효기간을 초 단위의 <expire-time>만큼 설정한다. - Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
HSTS 설정 유효기간을 초 단위의 <expire-time>만큼 설정하고 서브도메인 또한 적용 - Strict-Transport-Security: max-age=<expire-time>; includeSubDomains; preload
HSTS 설정 유효기간을 초 단위의 <expire-time>만큼 설정하고 서브도메인 또한 적용하며, 브라우저에 사전 설치된 preload HSTS 목록에 추가되도록 브라우저 社측에 요청
HTTP 보안헤더 Bypass
CRLF Injection
CRLF Injection이 발생하면, 존재하는 모든 보안헤더를 깡그리 무력화 시킬 수 있다.
응답 헤더 변조
HTTP 응답 코드가 변조되면, 그러니까 200OK가 아니라 300, 400, 500번대 에러가 발생하면 보안 헤더 설정이 일부 벗겨지는 경우가 있다. 에러가 발생하면 이전에 전달했던 파라미터가 무용지물이 되니 이 상황에서의 exploit이 힘들 수 있지만, 그래도 파라미터 값을 에러페이지에서도 노출 시키는 경우 그리고 동시에 에러 발생 시 보안헤더가 벗겨지는 경우에 exploit이 가능할 수 있다.
X-XSS-Protection Bypass
기존에 X-XSS-Protection을 무력화하는 X-XSS-Nightmare라는 취약점이 있었는데, IE에만 존재했던 취약점이였고, 현재 IE는 브라우저 계열에서 퇴출되다시피 했으므로 생략하겠다.
정 궁금하다면,
X-XSS-Nightmare: 1; mode=attack XSS Attacks Exploiting XSS Filter를 참고하자
원리가 재밌긴해서 한번쯤 보는것도 좋다.
Content Security Policy Bypass
허용된 사이트에 악성 스크립트를 직접 밀어넣거나, 허용되는 사이트를 추가해주는 방식으로 우회를 진행할 수 있다.
참고
https://blog.naver.com/ucert/221244965677
https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/X-XSS-Protection
https://webhack.dynu.net/?idx=20161119.001
https://webstone.tistory.com/98
https://velog.io/@shroad1802/Content-Security-Policy-CSP
https://sdy-study.tistory.com/63
https://developer.mozilla.org/ko/docs/Web/HTTP/CSP
https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/X-Frame-Options
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
'보안 기술 > WEB' 카테고리의 다른 글
TLS 1.3 (0) | 2023.11.05 |
---|---|
restAPI 환경에서 XSS는 왜 동작하지 않는걸까? (0) | 2023.11.05 |
HTTPS(SSL/TLS) 세션 성립 과정 (0) | 2023.10.08 |
SSRF (0) | 2023.10.01 |
Session storage JWT token 탈취 시도 (0) | 2023.06.09 |