あまり良い結果ではなかったですが、SECCON Beginners CTF 2024に出場しましたので、備忘録としてWriteup残しておこうと思います。
ssrforlfi
ソース確認
tar.gzを解凍したフォルダ構成は下記になります。
$ find ./
./
./docker-compose.yml
./.env
./app
./app/app.py
./app/requirements.txt
./app/Dockerfile
./app/uwsgi.ini
./nginx
./nginx/Dockerfile
./nginx/nginx.conf
それぞれのファイルの記載内容は、下記になっています。
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 a-z, ", (, ), ., /, :, ;, <, >, @, |
if not re.match('^[a-z"()./:;<>@|]*$', 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)
解答
何をしているアプリかというと、「?url=xxxx」というurlパラメータで指定されたURLにリクエストをして、その内容をレスポンスとして返却しているアプリです。
内容としては非常にシンプルですが、SSRFやらLFIやらの保護を突破しつつ、フラグを取る必要がありました。
まず着目しないといけないのが、下記のコードです。
httpやhttpsは、localhostの検証をしています。が、fileは、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 ;("
file://
スキームは、ローカルファイルにアクセス可能なため、環境変数などを読み取れる可能性があります。
すなわち、?url=file://localhost/[環境変数を確認できるファイル]
とできればフラグを取得できそうだということです。
現在のプロセスの環境変数は、/proc/self/environ
で確認できるということなので。
最終的には、’?url=file://localhost/proc/self/environ
で試してみます。
これでフラグが取得できました。
コマンドインジェクションをするものだと思っていたばかりに、制限時間内に解くことができませんでした。
Wooorker
RequestBin