Siunam's Website

My personal website

Home About Blog Writeups Projects E-Portfolio

Java Code Analysis!?!

Overview

Background

Author: Nandan Desai

Description

BookShelf Pico, my premium online book-reading service. I believe that my website is super secure. I challenge you to prove me wrong by reading the ‘Flag’ book! Here are the credentials to get you started:

Source code can be downloaded here.

Website can be accessed here!.

Enumeration

Home page:

Let’s login with the provided credentials!

In here, we can see there are 3 books!

In this challenge, we’re provided with the source code:

┌[siunam♥earth]-(~/ctf/picoCTF-2023/Web-Exploitation/Java-Code-Analysis!?!)-[2023.03.16|14:08:34(HKT)]
└> file bookshelf-pico.zip
bookshelf-pico.zip: Zip archive data, at least v2.0 to extract, compression method=store
┌[siunam♥earth]-(~/ctf/picoCTF-2023/Web-Exploitation/Java-Code-Analysis!?!)-[2023.03.16|14:08:35(HKT)]
└> unzip bookshelf-pico.zip  
Archive:  bookshelf-pico.zip
   creating: gradle/
   creating: gradle/wrapper/
  inflating: gradle/wrapper/gradle-wrapper.jar  
  inflating: gradle/wrapper/gradle-wrapper.properties  
[...]
┌[siunam♥earth]-(~/ctf/picoCTF-2023/Web-Exploitation/Java-Code-Analysis!?!)-[2023.03.16|14:12:17(HKT)]
└> ls -lah                 
total 5.5M
drwxr-xr-x 5 siunam nam 4.0K Mar 16 14:08 .
drwxr-xr-x 3 siunam nam 4.0K Mar 16 14:05 ..
-rw-r--r-- 1 siunam nam 5.5M Mar 16 14:06 bookshelf-pico.zip
-rw-rw-r-- 1 siunam nam 1.5K Jun  7  2022 build.gradle
drwxrwxr-x 3 siunam nam 4.0K Jun  7  2022 gradle
-rwxrwxr-x 1 siunam nam 5.7K Jun  7  2022 gradlew
-rw-rw-r-- 1 siunam nam 2.7K Jun  7  2022 gradlew.bat
-rw-rw-r-- 1 siunam nam 4.5K Dec  9 16:56 README.md
-rw-rw-r-- 1 siunam nam   43 Jun  7  2022 settings.gradle
drwxrwxr-x 4 siunam nam 4.0K Jun  7  2022 src
drwxrwxr-x 4 siunam nam 4.0K Nov 27 07:03 userdata

Burp Suite HTTP history:

When we’re logged in, it’ll response us a JWT (JSON Web Token)!!

Note: It’s highlighted in green because of the “JSON Web Tokens” extension in Burp Suite.

JSON Web Tokens (JWTs) are a standardized format for sending cryptographically signed JSON data between systems. They can theoretically contain any kind of data, but are most commonly used to send information (“claims”) about users as part of authentication, session handling, and access control mechanisms.

Unlike with classic session tokens, all of the data that a server needs is stored client-side within the JWT itself. This makes JWTs a popular choice for highly distributed websites where users need to interact seamlessly with multiple back-end servers.

A JWT consists of 3 parts: a header, a payload, and a signature. These are each separated by a dot.

The header and payload parts of a JWT are just base64url-encoded JSON objects. The header contains metadata about the token itself, while the payload contains the actual “claims” about the user. For example, you can decode the payload from the token above to reveal the following claims from jwt.io:

In the header, we see the algorithm is “HS256”, which is HMAC + SHA-256.

Payload:

{
  "role": "Free",
  "iss": "bookshelf",
  "exp": 1679579451,
  "iat": 1678974651,
  "userId": 1,
  "email": "user"
}

The payload stores some information about the logged in’s user.

Now, what if we sign our own JWT??

However, we didn’t know the secret.

Let’s move on to the source code.

After fumbling around at the source code, I found the following interesting thing.

/security/SecretGenerator.java:

[...]
@Service
class SecretGenerator {
    private Logger logger = LoggerFactory.getLogger(SecretGenerator.class);
    private static final String SERVER_SECRET_FILENAME = "server_secret.txt";

    @Autowired
    private UserDataPaths userDataPaths;

    private String generateRandomString(int len) {
        // not so random
        return "1234";
    }

    String getServerSecret() {
        try {
            String secret = new String(FileOperation.readFile(userDataPaths.getCurrentJarPath(), SERVER_SECRET_FILENAME), Charset.defaultCharset());
            logger.info("Server secret successfully read from the filesystem. Using the same for this runtime.");
            return secret;
        }catch (IOException e){
            logger.info(SERVER_SECRET_FILENAME+" file doesn't exists or something went wrong in reading that file. Generating a new secret for the server.");
            String newSecret = generateRandomString(32);
            try {
                FileOperation.writeFile(userDataPaths.getCurrentJarPath(), SERVER_SECRET_FILENAME, newSecret.getBytes());
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            logger.info("Newly generated secret is now written to the filesystem for persistence.");
            return newSecret;
        }
    }
}

In function generateRandomString(), we see that it’s returning a string 1234!

Then, in /security/JwtService.java, we see how the JWT was created:

[...]
public class JwtService {

    private final String SECRET_KEY;

    private static final String CLAIM_KEY_USER_ID = "userId";
    private static final String CLAIM_KEY_EMAIL = "email";
    private static final String CLAIM_KEY_ROLE = "role";
    private static final String ISSUER = "bookshelf";

    @Autowired
    public JwtService(SecretGenerator secretGenerator){
        this.SECRET_KEY = secretGenerator.getServerSecret();
    }

    public String createToken(Integer userId, String email, String role){
        Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);

        Calendar expiration = Calendar.getInstance();
        expiration.add(Calendar.DATE, 7); //expires after 7 days

        return JWT.create()
                .withIssuer(ISSUER)
                .withIssuedAt(new Date())
                .withExpiresAt(expiration.getTime())
                .withClaim(CLAIM_KEY_USER_ID, userId)
                .withClaim(CLAIM_KEY_EMAIL, email)
                .withClaim(CLAIM_KEY_ROLE, role)
                .sign(algorithm);
    }
[...]

In here, class JwtService’s method createToken() is using the SECRET_KEY, which is 1234, to sign the JWT!!!

That being said, we can sign our own evil JWT!!

But before we do that, let’s read 1 more code.

/security/ReauthenticationFilter.java:

[...]
if (jwtUserInfo != null && SecurityContextHolder.getContext().getAuthentication() == null) {
        ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        grantedAuthorities.add(new UserAuthority(jwtUserInfo.getUserId(), jwtUserInfo.getRole()));
        // I trust the user input here :) They'll never be evil, or will they?
        UserSecurityDetails userSecurityDetails = new UserSecurityDetails(jwtUserInfo.getEmail(), "", grantedAuthorities);
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                userSecurityDetails, token, userSecurityDetails.getAuthorities());
        usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
    }
[...]

In here, we see the JWT doesn’t check the userId and role is evil or not!!

Also, I found that there are 2 keys in the localStorage:

Hmm… What if I modify the role key to Admin??

Wait… I’m admin now???

Let’s go to the “Admin Dashboard”:

Hmm… “Failed to load the users.”

If we go to the Burp Suite HTTP history, we see this request:

“Access is denied”…

With that said, we can’t just modify the token-payload key to escalate our privilege to admin.

Now, let’s modify our payload’s role claim, and sign it via secret 1234!

Then, change the value in auth-token key in localStorage, and refresh the page:

Boom! We can view all users!!!

In the /base/users’s response, we see admin’s id:

[...]
{
    "id":2,
    "email":"admin",
    "fullName":"Admin",
    "lastLogin":"2023-03-16T14:08:35.545662851",
    "role":"Admin"
}
[...]

Hmm… Let’s change our id (1) to 2!

Now, can we view the “Flag” book?

We can! And we found the flag!

Conclusion

What we’ve learned:

  1. Privilege Escalation Via Weak JWT Secret