ProxyAsAService
3 minutos de lectura
Se nos proporciona un sitio web que redirige a /r/catvideos
:
También se nos proporciona el código fuente de la aplicación web, que está hecha con Flask (Python).
Análisis del código fuente
Hay algunas rutas para un proxy y rutas para debug (application/index.py
):
from flask import Flask, jsonify
from application.blueprints.routes import proxy_api, debug
app = Flask(__name__)
app.config.from_object('application.config.Config')
app.register_blueprint(proxy_api, url_prefix='/')
app.register_blueprint(debug, url_prefix='/debug')
@app.errorhandler(404)
def not_found(error):
return jsonify({'error': 'Not Found'}), 404
@app.errorhandler(403)
def forbidden(error):
return jsonify({'error': 'Not Allowed'}), 403
@app.errorhandler(400)
def bad_request(error):
return jsonify({'error': 'Bad Request'}), 400
Estas son las rutas (application/blueprints/routes.py
):
from flask import Blueprint, request, Response, jsonify, redirect, url_for
from application.util import is_from_localhost, proxy_req
import random, os
SITE_NAME = 'reddit.com'
proxy_api = Blueprint('proxy_api', __name__)
debug = Blueprint('debug', __name__)
@proxy_api.route('/', methods=['GET', 'POST'])
def proxy():
url = request.args.get('url')
if not url:
cat_meme_subreddits = [
'/r/cats/',
'/r/catpictures',
'/r/catvideos/'
]
random_subreddit = random.choice(cat_meme_subreddits)
return redirect(url_for('.proxy', url=random_subreddit))
target_url = f'http://{SITE_NAME}{url}'
response, headers = proxy_req(target_url)
return Response(response.content, response.status_code, headers.items())
@debug.route('/environment', methods=['GET'])
@is_from_localhost
def debug_environment():
environment_info = {
'Environment variables': dict(os.environ),
'Request headers': dict(request.headers)
}
return jsonify(environment_info)
Como se puede ver, la ruta raíz (/
) coge una subreddit aleatoria y redirige si no existe parámetro url
. Si no, añade el parámetro url
a la HTTP Request URI:
target_url = f'http://{SITE_NAME}{url}'
La función proxy_req
aparece en application/util.py
:
from flask import request, abort
import functools, requests
RESTRICTED_URLS = ['localhost', '127.', '192.168.', '10.', '172.']
def is_safe_url(url):
for restricted_url in RESTRICTED_URLS:
if restricted_url in url:
return False
return True
def is_from_localhost(func):
@functools.wraps(func)
def check_ip(*args, **kwargs):
if request.remote_addr != '127.0.0.1':
return abort(403)
return func(*args, **kwargs)
return check_ip
def proxy_req(url):
method = request.method
headers = {
key: value for key, value in request.headers if key.lower() in ['x-csrf-token', 'cookie', 'referer']
}
data = request.get_data()
response = requests.request(
method,
url,
headers=headers,
data=data,
verify=False
)
if not is_safe_url(url) or not is_safe_url(response.url):
return abort(403)
return response, headers
Esta función verifica que no estamos tratando de acceder a sitios web locales, entre otras cosas. Sin embargo, la verificación no es exhaustiva porque todavía podemos pasar otras representaciones de una dirección IP (decimal, hexadecimal…).
Está claro que necesitamos acceder a localhost
para poder llamar a /debug
y leer variables de entorno:
@debug.route('/environment', methods=['GET'])
@is_from_localhost
def debug_environment():
environment_info = {
'Environment variables': dict(os.environ),
'Request headers': dict(request.headers)
}
return jsonify(environment_info)
En el DockerFile
, se configura la flag como una variable de entorno:
# Place flag in environ
ENV FLAG=HTB{f4k3_fl4g_f0r_t3st1ng}
Sin embargo, la función is_from_localhost
siempre fallará porque estamos fuera del servidor y request.remote_addr
siempre será diferente de 127.0.0.1
.
HTTP Request URI
La idea de este desafío es engañar del servidor de alguna manera para redirigir a 127.0.0.1
. No podemos simplemente agregar esto a url
porque no estamos realizando la solicitud de localhost
.
El problema aquí es que url
se concatena con SITE_NAME
(reddit.com
) sin barras (/
). Como resultado, podemos agregar un signo especial @
. Con esto, podremos especificar el host para realizar la petición. Esto sucede porque la HTTP Request URI tiene este formato (más información en Wikipedia):
http[s]://[username[:password]@]domain-or-ip[:port]
Por lo tanto, podemos poner algo como @127.0.0.1:1337/debug/environment
, y la HTTP Request URI será:
http://reddit.com@127.0.0.1:1337/debug/environment
El servidor web pensará que reddit.com
es el nombre de usuario, que es inútil ya que la autenticación básica HTTP no se usa en esta aplicación web. Entonces, la petición irá a 127.0.0.1:1337/debug/environment
desde localhost
, por lo que nos saltaremos la función is_from_localhost
.
Por otro lado, necesitamos usar otro valor para 127.0.0.1
, por ejemplo, 0x7f000001
(formato hexadecimal).
Flag
Ahora, si ponemos url=@0x7f000001:1337/debug/environment
, veremos la flag:
HTB{fl4gs_4s_4_S3rv1c3}