드림핵 lv.1 - File Vulnerability Advanced for linux
드림핵 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”에 환경 변수들이 저장된다는 것을 배울 수 있었다.