【SECCON Beginners CTF 2024】WEB Writeup

  • URLをコピーしました!

あまり良い結果ではなかったですが、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

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

情報セキュリティを勉強するために始めたブログです。
新人のため、広い心を持って見ていただけると嬉しく思います。
楽しくプログラミングを勉強するために、「Teech Lab.」もありますので、ソフトウェア開発にも興味があればぜひ覗いて見てください!

目次