It wasn't a very good result, but I participated in the SECCON Beginners CTF 2024, so I'll leave a Writeup as a memo.
ssrforfi
Check source
The folder structure after unzipping tar.gz is as follows:
$ find ./ ./ ./docker-compose.yml ./.env ./app ./app/app.py ./app/requirements.txt ./app/Dockerfile ./app/uwsgi.ini ./nginx ./nginx/Dockerfile ./nginx/nginx.conf
The contents of each file are as follows:
services: uwsgi: build: ./app env_file: - .env expose: - "7777" restart: always nginx: build: ./nginx links: - uwsgi ports: - "4989:80" environment: TZ: "Asia/Tokyo" restart: always
COPY nginx.conf /etc/nginx/nginx.conf CMD ["nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"]
user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; upstream uwsgi { server uwsgi:7777; } server { listen 80; merge_slashes off; location / { include uwsgi_params; uwsgi_pass uwsgi; } } }
FROM ubuntu:22.04 ENV DEBIAN_FRONTEND noninteractive RUN apt-get -y update --fix-missing && apt-get -y upgrade RUN apt-get -y install python3 python3-pip curl RUN mkdir /var/www WORKDIR /var/www COPY ./ ./ RUN pip3 install -r requirements.txt ENV LANG C.UTF-8 RUN chmod 755 -R /var/www RUN adduser -u 1000 ssrforlfi USER ssrforlfi CMD ["uwsgi", "--ini", "/var/www/uwsgi.ini"]
Flask == 3.0.0 uWSGI == 2.0.23
[uwsgi] wsgi-file = app.py callable = app master = true processes = 4 threads = 4 socket = :7777 chmod-socket = 666 vacuum = true die-on-term = true py-autoreload = 1
import os import re import subprocess from flask import Flask, request app = Flask(__name__) @app.route("/") def ssrforlfi(): url = request.args.get("url") if not url: return "Welcome to Website Viewer.<br> <code>?url=http://example.com/</code> " # Allow only az, ", (, ), ., /, :, ;, <, >, @, | if not re.match('^[az"()./:;<>@|]*$', url): return "Invalid URL ;(" # SSRF & LFI protection if url.startswith("http://") or url.startswith("https://"): if "localhost" in url: return "Detected SSRF ;(" elif url.startswith("file://"): path = url[7:] if os.path.exists(path) or ".." in path: return "Detected LFI ;(" else: # Block other schemes return "Invalid Scheme ;(" try: # RCE ? proc = subprocess.run( f"curl '{url}'", capture_output=True, shell=True, text=True, timeout=1, ) except subprocess.TimeoutExpired: return "Timeout ;(" if proc.returncode != 0: return "Error ;(" return proc.stdout if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=4989)
answer
What is the app doing is an app that makes a request to the URL specified by the URL parameter "?url=xxx" and returns the content as a response.
The content is very simple, but I had to break through the protections of SSRF and LFI and get the flag.
The first thing to note is the code below.
http and https are validating localhost. However, file does not validate localhost.
# SSRF & LFI protection if url.startswith("http://") or url.startswith("https://"): if "localhost" in url: return "Detected SSRF ;(" elif url.startswith("file://"): path = url[7:] if os.path.exists(path) or ".." in path: return "Detected LFI ;(" else: # Block other schemes return "Invalid Scheme ;("
The file://
scheme can access local files, so it may be possible to read environment variables and so on.
In other words, it seems that you can get the flag if possible ?url=file://localhost/[file where you can check environment variables]
This means that
the environment variables for the current process /proc/self/environ
Finally, I'll try it with ?url=file://localhost/proc/self/environ
Now you get the flag.

I thought it was something to do command injection, but I was unable to solve the problem within the time limit.
Wooorker
RequestBin