본문 바로가기

드림핵

[드림핵 워게임] file-csp-1

<문제>

 

<풀이>

문제 사이트에 접속하면 처음 보이는 페이지이다.

TEST YOUR CSP!라는 문구가 나오는데,

 

CSP란

Content Security Policy의 약자로 XSS 공격을 막기 위해

지정한 규칙을 만족하는 스크립트 또는 CSS 만 허용하는 정책이다.

 

/test 페이지로 들어가보면 CSP 입력창이 뜬다.

 

<h1>Test your CSP</h1><br/>
<form method="GET" action="/live">
  <div class="row">
    <div class="col-md-6 form-group">
      <label for="CSP">Your own CSP</label>
      <input type="text" class="form-control" id="CSP" placeholder="script-src 'unsafe-inline'" name="csp" required>
    </div>
  </div>

test.html의 코드를 살펴보면

입력폼을 전송하면 /live로 넘어가는 것을 확인할 수 있다.

 

@APP.route('/live', methods=['GET'])
def live_csp():
    csp = request.args.get('csp', '')
    resp = make_response(render_template('csp.html'))
    resp.headers.set('Content-Security-Policy', csp)
    return resp

/live에서는 입력한 csp 값을 Contetn-Security-Policy 헤더로 설정한다.

그리고 csp.html을 렌더링한다.

 

	<head>
	<!-- block me -->
	<script>
		function a() { return 'a'; }
		document.write('a: block me!<br>');
	</script>
	<!-- block me -->
	 <script nonce="i_am_super_random">
		function b() { return 'b'; }
		document.write('b: block me!<br>');
	</script>
	<!-- allow me -->
	<script
  src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
  integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="
  crossorigin="anonymous"></script>
	<!-- allow me -->
	 <script nonce="i_am_super_random">
		function c() { return 'c'; }
		document.write('c: allow me!<br>');
		try { $(document); document.write('jquery: allow me!<br>'); } catch (e) {  }
	</script>
	</head>

csp.html 에는 a(), b(), c() 3개의 함수와 jquery 소스가 스크립트로 존재하는데,

주석을 보면 a(), b()는 차단해야 하고, c()와 jquery는 허용해야 하는 것 같다.

 

/verify로 들어가보면 또 입력폼이 뜨는데,

여기에 입력한 값을 처리하는 코드를 살펴보면

 

    if request.method == 'POST':
        csp = request.form.get('csp')
        try:
            options = webdriver.ChromeOptions()
            for _ in ['headless', 'window-size=1920x1080', 'disable-gpu', 'no-sandbox', 'disable-dev-shm-usage']:
                options.add_argument(_)
            driver = webdriver.Chrome('/chromedriver', options=options)
            driver.implicitly_wait(3)
            driver.set_page_load_timeout(3)
            driver.get(f'http://localhost:8000/live?csp={quote(csp)}')
            try:
                a = driver.execute_script('return a()');
            except:
                a = 'error'
            try:
                b = driver.execute_script('return b()');
            except:
                b = 'error'
            try:
                c = driver.execute_script('return c()');
            except Exception as e:
                c = 'error'
                c = e
            try:
                d = driver.execute_script('return $(document)');
            except:
                d = 'error'

            if a == 'error' and b == 'error' and c == 'c' and d != 'error':
                return FLAG

            return f'Try again!, {a}, {b}, {c}, {d}'
        except Exception as e:
            return f'An error occured!, {e}'

/test 에서 확인한 대로 a(), b()함수 호출은 실패해서 error를 발생시키고, c()와 jquery는 return 값을 받는 데 성공하면

FLAG가 출력된다.

 

일단 placeholder에 써있는 대로 script-src 'unsafe-inline'을 입력해 봤다.

 

a(),b(),c() 함수가 모두 정상적으로 호출되었다.

unsafe-inline 옵션은 모든 인라인 스크립트를 허용하기 때문이다.

 

b()와 c() 함수에 적용되어있는 nonce 속성을 적용해

nonce옵션을 지정해봤다.

 

nonce 요소가 없는 a()는 차단되었고

b()와 c()만 허용되었다.

 

문제를 풀기 위해서는 b()를 차단하고 c()를 허용해야 하는데

nonce 로는 b()와 c()에 서로 다른 필터를 적용해줄 수 없으므로 다른 방법을 찾아야 한다.

 

검색을 하다가 해시값으로 필터링을 할 수 있는 방법을 알게 되었다.

script-src 'none'으로 지정해주었다.

옵션을 none으로 설정하면 모든 스트립트가 차단된다.

 

요청을 보낸 뒤, 응답된 페이지에서 개발자도구의 Console 탭을 열어보면 에러 메시지를 확인할 수 있다.

그리고 차단된 스크립트의 해시값을 확인할 수 있다.

 

맨 위에 뜬 해시값을 옵션으로 지정해보았다.

포맷은

script-src 'sha256-해시값'

이다.

 

a() 만 허용되었다.

 

script-src 'sha256-l1OSKODPRVBa1/91J7WfPisrJ6WCxCRnKFzXaOkpsY4='

로 지정하면 c()만 허용되는 것을 확인할 수 있다.

 

jquery도 허용하기 위해 옵션을 추가해준다.

 

script-src 'sha256-l1OSKODPRVBa1/91J7WfPisrJ6WCxCRnKFzXaOkpsY4=' https://code.jquery.com

로 CSP를 지정해주었다.

 

https://code.jquery.com 부분은 csp.html 15~18행에서 가져온 것이다.

 

c()와 jquery만 허용된 것을 확인할 수 있다.

 

동일한 CSP를 /verify에 입력한다.

 

FLAG가 출력되었다.