CTF/LINE CTF 2022

[Study] LINE CTF 2022 Memo Drive Writeup

Vardy 2022. 3. 28. 07:47

주어진 페이지에 접근해보자.

 

아래와 같이 메모를 작성하고 읽을 수 있는 기능이 구현되어 있다.

 

주어진 소스코드를 분석해보자. 

 

우선 Dockerfile에 플래그 위치가 나타나있다. ${MEMO}/memo/flag 를 읽어 플래그를 획득 할 수 있다는 점을 기억하자.

FROM python:3.9.0

LABEL maintainer "t0rchwo0d_LINE"

ENV SALT="ONLY_FOR_LOCAL_TEST"
ENV MEMO /usr/local/opt/memo-drive
RUN mkdir -p "${MEMO}"

RUN apt-get -qq update && \
  apt-get -qq -y upgrade && \
  apt-get -qq -y install htop net-tools vim

COPY ./memo-drive "${MEMO}"

COPY start.sh "${MEMO}/start.sh"
COPY flag "${MEMO}/memo/flag"

RUN pip install -r "${MEMO}/requirements.txt"

RUN chmod -R 705 "${MEMO}"
RUN chmod 707 "${MEMO}/memo/"
RUN chmod 704 "${MEMO}/memo/flag"

RUN groupadd -g 1000 memo
RUN useradd -g memo -s /bin/bash memo

USER memo
EXPOSE 11000
WORKDIR "${MEMO}"
ENTRYPOINT ["./start.sh"]

 

처음에는 memo를 기록할 때 memo의 내용에 특정 구문을 입력하여 작성한 메모를 읽을 시 플래그 값이 노출되도록 하는 방식일 것이라고 생각했었는데 해당 시나리오로는 취약한 포인트를 찾지 못했다.

 

따라서 위 Dockerfile 에서 얻은 정보를 기반으로 직접 /memo/flag를 읽는 방식을 시도해보았다. 핵심은 메모를 읽는 로직일 것이라고 생각하여 view 메소드를 집중적으로 분석해보았다.

 

우선 일반적으로 작성한 메모를 읽을 때 아래와 같이 호출된다.

http://34.146.195.115/view?d46636bd5fec072a9d8aa2280f78ea9c=0_20220327220246

여기서 d46636bd5fec072a9d8aa2280f78ea9c 는

clientId = getClientID(request.client.host)

의 결과 값으로 사용자 IP와 salt를 더한 값에 md5를 한 값이다.

0_20220327220246 는

filename = str(idx) + '_' + datetime.datetime.now().strftime('%Y%m%d%H%M%S')

로직을 통해 만들어진 실제 메모 파일의 이름이다.

 

이를 참고하여 소스를 분석해보자.

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]):
        ##### ............................................................................ (1)
            raise
        
        filename = request.query_params[clientId]
        path = './memo/' + "".join(request.query_params.keys()) + '/' + filename ##### ... (2)
        
        f = open(path, 'r')
        contents = f.readlines()
        f.close()
        
        context['filename'] = filename
        context['contents'] = contents
    
    except:
        pass
    
    return templates.TemplateResponse('/view/view.html', context)

(1) 을 분석해보면, url query에 &과 . 이 들어갈 수 없고, 특히 filename에는 unquote를 통해 url 디코딩 후에도 . 가 포함될 수 없다.

 따라서 

http://34.146.195.115/view?d46636bd5fec072a9d8aa2280f78ea9c=../flag

와 같은 구문은 사용 할 수 없다.

 

(2)의 파일 경로를 정의하는 부분을 분석해보자.

우리가 작성한 메모의 실제 경로는 /memo/d46636bd5fec072a9d8aa2280f78ea9c/0_20220327220246 인 듯 하다.

하지만 특이한점이 있는데,

/memo/ 하위 디렉토리를 지정 할 때 request.query_params.keys() 로 여러 파라미터의 key들이 더해진 값이 되도록 한다.

따라서, 비록 & 가 제한되어 있지만 request.query_params[clientId] 외에 파라미터를 추가 할 수 있고 그 값이 /.. 라면

/memo/d46636bd5fec072a9d8aa2280f78ea9c/../flag 처럼 플래그를 호출 할 수 있을 것이다.

 

혹시 URL Query String에서 & 외에 구분자를 지정 할 수 있는 방법이 있을까해서 공부해보았다.

https://en.wikipedia.org/wiki/Query_string

 

Query string - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Part of a URL that assigns values to specified parameters A query string is a part of a uniform resource locator (URL) that assigns values to specified parameters. A query string commo

en.wikipedia.org

위 레퍼런스에 의하면 & 대신 ; 를 사용 할 수 있다는 내용이 있었다.

 

* 22.04.02 수정 ----> 위 내용에서 ; 사용은 특이케이스에 대한 내용이고 아래 레퍼런스가 더 정확한 듯 하다. 

https://bugs.python.org/issue42967

 

Issue 42967: [CVE-2021-23336] urllib.parse.parse_qsl(): Web cache poisoning - `; ` as a query args separator - Python tracker

Issue42967 Created on 2021-01-19 15:06 by AdamGold, last changed 2021-11-08 16:47 by vstinner. This issue is now closed. URL Status Linked Edit PR 24271 closed kj, 2021-01-20 15:20 PR 24297 merged AdamGold, 2021-01-22 12:34 PR 24528 merged orsenthil, 2021-

bugs.python.org

 

 

이를 활용하여 request.url.query에서의 필터링을

& 대신 ; 사용하고 "." 사용 제한을 URL 인코딩으로 우회하여

 

최종적으로 아래 payload로 플래그를 획득 할 수 있었다.

http://34.146.195.115/view?d46636bd5fec072a9d8aa2280f78ea9c=flag;/%2e%2e

--> 
http://34.146.195.115/view?d46636bd5fec072a9d8aa2280f78ea9c=flag&/%2e%2e
//request.query_params.keys() = ['d46636bd5fec072a9d8aa2280f78ea9c', '/..']
-->
memo/d46636bd5fec072a9d8aa2280f78ea9c/../flag

 

FLAG = LINECTF{The_old_bug_on_urllib_parse_qsl_fixed}

 

----------------------------------------------------------------------------------------------------------------------

또 다른 풀이로, Host 헤더에 #을 추가하여 request.url.query의 필터링을 우회 할 수 있다고 한다.

반응형