Prepare for the finest magic products out there. However, please be aware that we've implemented a specialized protective spell within our web application to guard against any black magic aimed at our web shop.🔮🎩
Source
entrypoint.sh
#!/bin/shDB_PATH="/opt/www/app/nothreshold.db"sqlite3"$DB_PATH"<<EOFCREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, password TEXT NOT NULL);INSERT INTO users (username, password) VALUES ('admin', '$(head /dev/urandom |tr-dc A-Za-z0-9 |head-c32)');.quitEOFuwsgi--ini/opt/www/app/uwsgi.ini&haproxy-f/etc/haproxy/haproxy.cfgtail-f/dev/null
conf/uwsgi.ini
conf/haproxy.cfg
challenge/__init__.py
challenge/blueprints/dashboard.py
challenge/blueprints/login.py
challenge/blueprints/verify2fa.py
Solution
No-Threshold.png
So first of all we can't do anything in the Shop, because we need to Login. To login we need to bypass the 403 set by HAProxy.
The login is denied to any request coming from outside, but only to /auth/login, meaning we can tamper with the URL such as //auth/login and it will not get blocked and we are able to bypass the proxy rule.
No-Threshold-1.png
Because login does raw SQL queries we can just do simplest SQLi and bypass it, but the web redirects us to url without // as prefix so catch the request via burp and modify the path:
No-Threshold-2.png
Now we need to get 2FA Code somehow
No-Threshold-3.png
The code is set by uwsgi API, it's 4 digits long (because login creates it: set_2fa_code(4))
The server didn't like async code... so brute with non async 😭
global
daemon
maxconn 256
defaults
mode http
option forwardfor
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend haproxy
bind 0.0.0.0:1337
default_backend backend
# Parse the X-Forwarded-For header value if it exists. If it doesn't exist, add the client's IP address to the X-Forwarded-For header.
http-request add-header X-Forwarded-For %[src] if !{ req.hdr(X-Forwarded-For) -m found }
# Apply rate limit on the /auth/verify-2fa route.
acl is_auth_verify_2fa path_beg,url_dec /auth/verify-2fa
# Checks for valid IPv4 address in X-Forwarded-For header and denies request if malformed IPv4 is found. (Application accepts IP addresses in the range from 0.0.0.0 to 255.255.255.255.)
acl valid_ipv4 req.hdr(X-Forwarded-For) -m reg ^([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])$
http-request deny deny_status 400 if is_auth_verify_2fa !valid_ipv4
# Crate a stick-table to track the number of requests from a single IP address. (1min expire)
stick-table type ip size 100k expire 60s store http_req_rate(60s)
# Deny users that make more than 20 requests in a small timeframe.
http-request track-sc0 hdr(X-Forwarded-For) if is_auth_verify_2fa
http-request deny deny_status 429 if is_auth_verify_2fa { sc_http_req_rate(0) gt 20 }
# External users should be blocked from accessing routes under maintenance.
http-request deny if { path_beg /auth/login }
backend backend
balance roundrobin
server s1 0.0.0.0:8888 maxconn 32 check
from app.blueprints.verify2fa import *
from app.blueprints.dashboard import *
from app.blueprints.login import *
from app.blueprints.index import *
from app.config import Config
from flask import Flask
app = Flask(__name__)
app.config["SECRET_KEY"] = Config.SECRET_KEY
app.register_blueprint(index_bp)
app.register_blueprint(dashboard_bp)
app.register_blueprint(login_bp, url_prefix="/auth")
app.register_blueprint(verify2fa_bp, url_prefix="/auth")