1. ๋ฌธ์
https://dreamhack.io/wargame/challenges/274
2. ํด๊ฒฐ ๊ณผ์
ํฌ๋กค๋งํ URL์ ์ ๋ ฅํ๋ ํ๋ฉด์ด ๋ณด์ธ๋ค.
#app.py
from re import split
import socket
import requests
import ipaddress
from urllib.parse import urlparse
from flask import Flask, request, render_template
app = Flask(__name__)
app.flag = '__FLAG__'
def lookup(url):
try:
return socket.gethostbyname(url)
except:
return False
def check_global(ip):
try:
return (ipaddress.ip_address(ip)).is_global
except:
return False
def check_get(url):
ip = lookup(urlparse(url).netloc.split(':')[0])
if ip == False or ip =='0.0.0.0':
return "Not a valid URL."
res=requests.get(url)
if check_global(ip) == False:
return "Can you access my admin page~?"
for i in res.text.split('>'):
if 'referer' in i:
ref_host = urlparse(res.headers.get('refer')).netloc.split(':')[0]
if ref_host == 'localhost':
return False
if ref_host == '127.0.0.1':
return False
res=requests.get(url)
return res.text
@app.route('/admin')
def admin_page():
if request.remote_addr != '127.0.0.1':
return "This is local page!"
return app.flag
@app.route('/validation')
def validation():
url = request.args.get('url', '')
ip = lookup(urlparse(url).netloc.split(':')[0])
res = check_get(url)
return render_template('validation.html', url=url, ip=ip, res=res)
@app.route('/')
def index():
return render_template('index.html')
if __name__=='__main__':
app.run(host='0.0.0.0', port=3333)
admin์ ์ ๊ทผํ๋ฉด ํ๋๊ทธ๋ฅผ ๋ฆฌํดํด์ค๋ค.
@app.route('/admin')
def admin_page():
if request.remote_addr != '127.0.0.1':
return "This is local page!"
return app.flag
ํฌํธ๋ ์ฝ๋์ ๊ฐ์ฅ ๋ฐ์์ ํ์ธํ ์ ์๋ค.
3333์ด๋ค.
if __name__=='__main__':
app.run(host='0.0.0.0', port=3333)
์ ๋ ฅ๊ฐ(url)์ ๋ํ ํํฐ๋ง์ด ์กด์ฌํ๋ค.
- ip๊ฐ false๊ฐ์ด ๋์ค๊ฑฐ๋ 0.0.0.0(์๋ฏธ์๋ ๊ฐ)์ด๋ฉด ํํฐ๋ง์ ๊ฑธ๋ฆผ
- false
- check_global() -> Can you access my admin page~? ๋ฆฌํด
- referer ๊ฐ์ด localhost / 127.0.0.1 ์ผ ๋ false ๋ฆฌํด
def check_get(url):
ip = lookup(urlparse(url).netloc.split(':')[0])
if ip == False or ip =='0.0.0.0':
return "Not a valid URL."
res=requests.get(url)
if check_global(ip) == False:
return "Can you access my admin page~?"
for i in res.text.split('>'):
if 'referer' in i:
ref_host = urlparse(res.headers.get('refer')).netloc.split(':')[0]
if ref_host == 'localhost':
return False
if ref_host == '127.0.0.1':
return False
res=requests.get(url)
return res.text
- IP ์ฃผ์ ์ ํจ์ฑ ๊ฒ์ฌ:
- ์กฐํ๋ IP ์ฃผ์๊ฐ False์ด๊ฑฐ๋ 0.0.0.0์ผ ๊ฒฝ์ฐ, ์ ํจํ์ง ์์ URL๋ก ๊ฐ์ฃผํ๊ณ "Not a valid URL." ๋ฉ์์ง๋ฅผ ๋ฐํ
- ์ธ๋ถ IP ํ์ธ:
- ๊ณต์ธ ip๊ฐ ์๋ ๊ฒฝ์ฐ (์ฌ์ค IP์ธ ๊ฒฝ์ฐ) - "Can you access my admin page?" ์ถ๋ ฅ
- ๊ณต์ธ IP์ผ ์ referer ip๊ฐ ๋ก์ปฌ ์ฃผ์์ธ์ง ํ์ธํ๋ค.
- HTTP ํค๋ ๋ฐ Referer ๊ฒ์ฌ:
- ์๋ต ํค๋์์ 'refer' ๊ฐ์ ์ถ์ถํ๊ณ ์ด๋ฅผ ๋ค์ ํ์ฑํ์ฌ ํธ์คํธ ์ด๋ฆ์ ์ป๋๋ค.
- ํธ์คํธ ์ด๋ฆ์ด 'localhost' ๋๋ '127.0.0.1'์ธ ๊ฒฝ์ฐ, False๋ฅผ ๋ฐํ
์ฆ, ์ผ๋ฐ์ ์ธ SSRF ์ทจ์ฝ์ ์ ๊ณต๊ฒฉํ๋ ๊ฒ์ผ๋ก๋ ํ๋๊ทธ๋ฅผ ๊ฐ์ง ์ ์๋ค. (๋ด๋ถ ๋คํธ์ํฌ ํํฐ๋ง)
ํ ์คํธ ์ฐจ์์์ http://dreamhack.io๋ฅผ ๋ฃ์ด๋ณด๋ฉด, ํฌ๋กค๋ง์ ํด์ค๋ค.
http://127.0.0.1:3333/admin ๋ฅผ ์ ๋ ฅํด๋ณด๋ฉด "Can you access my admin page~?"๋ผ๋ ๋ฌธ์์ด์ด ์ถ๋ ฅ๋๋ค.
์ฆ, ํ๋๊ทธ๊ฐ์ ํ์ธํ ์ ์์๋ค.
URL ๋จ์ถ (https://tinyurl.com/app ์ฌ์ฉ): ๋ฆฌ๋ค์ด๋ ํธ ํ์ฉ
+)
ToCToU
์ด ์ฝ๋๋ lookup๋ฉ์๋๋ฅผ ํธ์ถํ ํ, ๋ง์ ์ฐ์ฐ์ ํ ๋ค์ admin ํ์ด์ง๋ก get ์์ฒญ์ ๋ ๋ฆฌ๊ณ ์๋ค.
๋ฐ๋ผ์ ์ค์ ๋ก ip๋ฅผ ํ์ธํ๋ ๋ก์ง์ธ lookup๋ฉ์๋์ admin ํ์ด์ง์ request.remote_addr ์ฌ์ด์ ์ฐ์ฐ์ผ๋ก ์ธํ race condition์ด ๋ฐ์ํ๋ค. ์ด๋ฌํ ์ทจ์ฝ์ ์ Time of check to time of use(ToCToU) ์ทจ์ฝ์ ์ด๋ผ๊ณ ํ๋ค.
def lookup(url):
try:
return socket.gethostbyname(url)
except:
return False
์ด ์ทจ์ฝ์ ์ ํธ๋ฆฌ๊ฑฐํ๊ธฐ ์ํด์๋ DNS rebinding ๊ณต๊ฒฉ์ ํด์ผํ๋ค.
DNS rebinding ๊ณต๊ฒฉ์ด๋, ๋๋ฉ์ธ ์ด๋ฆ์ ํ์ธํ๋ ๋ก์ง์ ์ฐํํ๋ ๋ฐฉ๋ฒ์ด๋ค.
lookup์ ํตํด ๋๋ฉ์ธ์ ๋ํ ip๋ฅผ ํ์ธํ ๋์๋ check_global(ip)๋ฅผ ์ฐํํ ์ ์๋๋ก ๋๋ฉ์ธ์ ๋งค์นญ๋๋ ip๊ฐ global ip ์ฌ์ผํ๊ณ , ์ดํ admin ํ์ด์ง์์ request.remote_addr์ ํตํด ip๋ฅผ ๊ฒ์ฌํ ๋์๋ ๋๋ฉ์ธ์ ๋งค์นญ๋๋ ip๊ฐ local ip์ธ 127.0.0.1์ด์ฌ์ผ ํ๋ค.
- lookup - global ip
- request.remote_addr - local ip (127.0.0.1)
๊ทธ๋ ๋ค๋ฉด ๊ฐ์ ๋๋ฉ์ธ์ 2๊ฐ์ ip๊ฐ ๋ฒ๊ฐ์๊ฐ๋ฉฐ ๋งค์นญ๋๋๋ก ํ๋ค๋ฉด, flag๋ฅผ ์ป์ ์ ์์ ๊ฒ ์ ๋๋ค.
๊ณต๊ฒฉ์ ์ํํ๊ธฐ ์ํด์๋ ํ๋์ ๋๋ฉ์ธ์ 2๊ฐ์ IP๋ฅผ ๋งคํํด๋๊ณ TTL๊ฐ์ ๋น ๋ฅด๊ฒํ์ฌ request๊ฐ ์ ์ก๋๊ธฐ ์ ์ global IP์ธ ์ํ๋ก ๊ฒ์ฆ๋ก์ง์ ์ฐํํ๊ณ , ๋ค์ local IP๋ก ๋ณํ๋์ด์ผ ํ๋ค.
์ด๋ฅผ ์ํด ํ๋์ ๋๋ฉ์ธ์ 2๊ฐ์ IP๋ฅผ ๊ฑธ์ด๋๊ณ TTL์ 30์ ๋๋ก ๋๋ฉด 30์ด์ ํ๋ฒ์ฉ ๋๋ค์ผ๋ก 2๊ฐ์ IP์ค ํ๋๋ฅผ ๋๋ฉ์ธ์ ๋ฐ์ธ๋ฉํฉ๋๋ค.
๋ง์ฝ ๋๋ฉ์ธ์ด ์๋ ๊ฒฝ์ฐ์๋ DNS rebinding tool์ ์ฌ์ฉํ์ฌ 2๊ฐ์ง ip๋ฅผ ํ๋์ ๋๋ฉ์ธ์ ๋ฐ์ธ๋ฉํ๊ณ , ํด๋น ๋๋ฉ์ธ์ผ๋ก admin ํ์ด์ง์ ์ ์ํ์ฌ ํ์ด๋ฐ์ ๋ง์ถฐ์ฃผ๋ฉด flag๋ฅผ ์ป์ ์ ์๋ค.
http://7f000001.6565a4b0.rbndr.us:3333/admin