포스트

드림핵 lv.1 - File Vulnerability Advanced for linux

드림핵 File Vulnerability Advanced for linux 웹해킹 워게임 풀이

드림핵 lv.1 - File Vulnerability Advanced for linux

https://dreamhack.io/wargame/challenges/417/

문제 설명

Exercise: File Vulnerability Advanced for Linux에서 실습하는 문제입니다.


문제 풀이

코드 분석

1
2
3
4
5
6
7
8
9
10
[Code Structure]
app/
	- files/
		- test.txt
	- main.py
	- nginx.conf
	- requirements.txt
nginx/
	- default.conf
Dockerfile

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import os, subprocess
from functools import wraps
from flask import Flask, request

app = Flask(__name__)
API_KEY = os.environ.get('API_KEY', None)

def key_required(view):
    @wraps(view)
    def wrapped_view(**kwargs):
        apikey = request.args.get('API_KEY', None)
        if API_KEY and apikey:
            if apikey == API_KEY:
                return view(**kwargs)
        return 'Access Denied !'
    return wrapped_view


@app.route('/', methods=['GET'])
def index():
    return 'API Index'


@app.route('/file', methods=['GET'])
def file():
    path = request.args.get('path', None)
    if path:
        data = open('./files/' + path).read()
        return data
    return 'Error !'


@app.route('/admin', methods=['GET'])
@key_required
def admin():
    cmd = request.args.get('cmd', None)
    if cmd:
        result = subprocess.getoutput(cmd)
        return result
    else:
        return 'Error !'


if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

웹사이트에 접속해보니 “API Index”라는 문자만 출력이 되고 아무 정보도 얻을 수가 없어서 우선 코드를 살펴보자.

/file

라우트 “/”는 살펴볼 것도 없고 “/file” 부분을 보면 “path” 값을 url로부터 받아와서 "./files/" + path에 해당하는 파일의 내용을 화면에 출력해주는 페이지라는 것을 확인할 수 있다.
“/file?path=test.txt” 테스트용으로 해당 페이지에 접속하면 “test”가 출력된다.

/admin

“/admin” 페이지에서는 “cmd” 값을 url에서 가져온 후에 subprocess.getoutput(cmd)로 넘겨주어 결과를 출력해준다.
문서를 확인해보면 getoutput() 함수는 shell 명령어를 인자로 받아서 해당 결과를 반환하는 단순한 코드이다.

하지만 해당 페이지에 들어갈 때 @key_required 함수 때문에 접근 권한이 필요하다.

key_required()

1
2
3
4
5
6
7
8
9
10
11
API_KEY = os.environ.get('API_KEY', None)

def key_required(view):
    @wraps(view)
    def wrapped_view(**kwargs):
        apikey = request.args.get('API_KEY', None)
        if API_KEY and apikey:
            if apikey == API_KEY:
                return view(**kwargs)
        return 'Access Denied !'
    return wrapped_view

접근 권한을 확인하는 함수를 살펴보면 url로부터 “API_KEY” 값을 받아와서 해당 값이 os.environ.get(“API_KEY”) 값과 동일하면 통과이다. environ 함수는 해당 서버 컴퓨터의 환경 변수를 딕셔너리 포멧으로 반환한다.

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM tiangolo/uwsgi-nginx-flask:python3.10

# ENV
ENV port 80
ENV API_KEY API_KEY

# SET USER
RUN useradd -d /home/user/ -m -s /bin/bash user

# SET packages
RUN apt-get update -y
RUN apt-get install -y python3-pip

# SET challenges
ADD ./app /app
WORKDIR /app
RUN pip install -r requirements.txt
ADD ./nginx/ /etc/nginx/conf.d/
EXPOSE $port

Dockerfile을 보면 ENV API_KEY API_KEY설정을 이용해서 환경변수 중 하나로 API_KEY를 특정 API_KEY로 초기화 해주는 부분이 보인다.

최종 풀이

취약점 분석

/file 페이지

“/file” 페이지에서 파일 경로 필터링을 하고있지 않기 때문에 File Traversal 취약점이 발생한다.
이 점을 이용하여 Dockerfile을 살펴보고 API 키를 알아낼 수 있을 것 같다.

/admin 페이지

“/admin” 페이지에서는 shell 명령어를 받고 결과를 화면에 출력해주는데 이때 작성할 수 있는 명령어에 제한이 전혀 걸려있지 않다.
“/file”에서 API 값을 얻어내어 “/admin”에 접속하고 cmd 명령어를 통해 서버를 돌아다니면서 플래그 값을 찾으면 될 것 같다.

적용

API_KEY 구하기

”../../Dockerfile”에 접근하려고 했는데 아무리 해봐도 500 에러만 발생하고 정작 파일은 읽을 수가 없었다.
곰곰히 생각해보니 Docker은 일종의 가상 머신 개념이라서 DockerFile은 웹 서버 컴퓨터가 아니라 웹 서버 컴퓨터를 가상으로 돌리고 있는 진짜 컴퓨터에 저장돼있다는 것을 간과했다.

그럼 다른 파일에서 API 키를 얻어내야 한다.
리눅스에서 프로세스의 환경 변수를 담은 “../../proc/self/environ”에 접근하면 API_KEY를 얻어낼 수 있다.

다른 방법

드림핵 강의에서 API_KEY를 알아낼 때 시도해볼만한 다른 루트도 소개해주었다.
API 값이 URL을 통해서 입력되기 때문에 nginx 서버의 로그 파일인 /var/log/nginx/nginx_access.log에서 얻어낼 수도 있을 것이다.
혹은 서버 사용자가 shell을 통해서 유의미한 정보를 남겼을지도 모르니 .bash_history를 확인해볼 것을 추천해주었다.

flag 파일 찾기

“/admin” 페이지에 cmd 값을 “ls ..”로 설정하고 접속해보면 flag 파일이 버젓이 보인다.
파일 위치를 알아냈으니 “../flag”를 입력해주면 flag 파일의 실행 결과가 화면에 출력되는 것을 알 수 있다.

배운 것

os.environ.get()의 기능과 “/proc/PID/environ”에 환경 변수들이 저장된다는 것을 배울 수 있었다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.