We've recently participated in Faust 2020 as part of MoreBushSmokedWhackers team and took the first place. We were first to solve the IPPS service (which was also the first blood of the game as a whole). This blog post covers the service itself, the vulnerabilities and the exploitation details.
The legend of the service is parcel status tracking, like UPS or something. The service provides the following functionality:
- User registration/login
- Storing of user settings, which are "delivery address" and "credit card number" (the latter contains the flag)
- Posting publicly accessible feedback reviews
- Parcel tracking itself
The scoreboard bot registers a user, logins using these credentials, stores a flag into the credit card number field, and posts a feedback review. As the feedback is publicly accessible, the latter action provides a way to get usernames of scoreboard users, which would not be possible otherwise.
The parcel tracking feature, somewhat counterintuitively, does not contain any bugs and does not have any uses (at least none we're aware of).
IPPS is written in Go. The sources were provided by the organizers, which makes it a real pleasure to solve the challenge.
There are 3 different ways to communicate with the service.
The first way to communicate with the service is its web interface. It is a classical web application which provides access to all features of the service.
The web interface uses a session cookie mechanism to store information about current user (e.g. has the user logged in, and her username in case she has).
The service provides a set of REST endpoints. These endpoints allow logging in and requesting credit card information for a user. This part supposed to have the same authorization mechanism as the web interface, see more in the description of bug #1.
IPPS also has GRPC service running on a separate port. It provides a way to retrieve a user's credit card information as well.
GRPC uses an authorization mechanism that is different from the one used by other APIs. A user can request a JWT for a username/password pair, and then use it to retrieve credit card info. The token is signed using an RSA key pair, and the public key is also available via another GRPC endpoint.
The service contained a set of four different vulnerabilities, which are covered below.
Each vulnerability provides a way to get the credit card number of a user without knowing her password.
Retrieving user names
The service does not provide a way to list all users. However, scoreboard users post a publicly accessible feedback review. To exploit either of the vulnerabilities, teams must scrap the feedback page and extract user names.
Bug #1: IDOR in the JSON API
First and the simplest bug is that JSON API does not perform any access checks before giving out users' information. The endpoint
/api/user/<username> prints user credit card no matter user authorized or not. The exploitation is pretty trivial.
To mitigate the vulnerability, a team should enable
loginChecker middleware for the JSON APIs. It is already implemented in a separate file, but never used in the original source.
Bugs #2 and #3: hardcoded keys
The service has two values that are supposed to be kept in secret: the session cookie signing key, and the private part of the JWT signing key pair.
Both values were hardcoded in the original service distribution, which meant that other teams' installations use the same secrets. It allowed session cookie forgery, as well as JWT forgery for the GRPC interface. The exploitation of the vulnerability is easy if you write the exploit in Go because you can reuse the code from the challenge to make up both values.
To mitigate these vulnerabilities a team should regenerate JWT signing RSA key pair and change session cookie secret to any random value.
Bug #4: Invalid JWT validation in the GRPC service
The last vulnerability was also the hardest to spot. It is present in the JWT verification code in the GRPC service.
As mentioned below, the GRPC part of the service has a unique authorization mechanism. First, a user requests a JWT by providing credentials. The server validates the credentials, and, if they are correct, generates and signs a token that contains username information. Second, the user sends this token to retrieve her credit card number.
The token is signed using an RSA key pair, which corresponds to the
RS256 JWT algorithm. A well-known attack on JWT authorization is to change the algorithm to "none" or invalid value in hopes that signature verification will be skipped entirely.
This service does not allow "none" algorithm; however, it supports "HS256" algorithm, which is based on symmetrical cryptography — the signing key is the same as the verification key.
The bug resides in this HS256 support. After carefully reading the source code, it can be noted that the
key argument of the JWT verification function is always the public part of the RSA key pair. It is correct for the
RS256 algorithm but makes no sense for
HS256 variant: the public part is, well, public, and anyone knowing
HS256 validation key can forge the token.
To exploit the vulnerability, an attacker can retrieve RSA public key via GRPC (the service provides such endpoint) and then use it to sign a forged JWT via HS256 algorithm. It can be done using any JWT library, but we used the same code the challenge uses to be sure everything will go smoothly.
In order to mitigate this one, teams could disable HS256 algorithm altogether. The login process via JWT does not provide a way to generate HS256 signed token, so there's no way for such a token to be legit.
While the vulnerabilities were not very hard to find and exploit, it was enjoyable and entertaining to solve this challenge. The FAUST team does a great job organizing the CTF for the fifth time, and we're looking forward to winning it again next year.