Contents

🌐 Java Code Analysis!?!

A detailed write-up of the Web challenge 'Java Code Analysis!?!' from PicoCTF - 2023

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/challenge_presentation.png
Challenge Presentation

📊 Challenge Overview

Category Details Additional Info
🏆 Event PicoGym Event Link
🔰 Category Web 🌐
💎 Points Out of 500 total
⭐ Difficulty 🟡 Medium Personal Rating: 3/10
👤 Author Nandan Desai Profile
🎮 Solves (At the time of flag submission) 4.594 solve rate
📅 Date 11-02-2025 PicoGym
🦾 Solved By mH4ck3r0n3 Team:

📝 Challenge Information

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:

  • Username: “user”
  • Password: “user”

🎯 Challenge Files & Infrastructure

Provided Files

Files:

🔍 Initial Analysis

First Steps

Initially, the website appears as follows:

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/site_presentation.png
Site Presentation

Trying to log in with the credentials provided by the challenge username=user & password=user, I was able to access the following page:

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/homepage.png
Homepage

The description also suggests that to obtain the flag, we need to read the book Flag, which, as we can see, is locked and can only be accessed by an Admin account. This led me to think that there might be some sort of authorization, like a cookie or token, that needs to be forged in order to access the book with privileged access. Going through the attached files, as suggested by the title of the challenge, it’s related to Java. Below, I will report only the interesting things I found. The first thing that caught my eye was the directory src/main/java/io/github/nandandesai/pico/security. Upon opening it, I discovered that JWT is being used:

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/security.png
Security

And as we can see, the fields are userId, email, role, where I imagine role will determine the access level to the books. In fact, as expected, in the file src/main/java/io/github/nandandesai/pico/security/models/UserAuthority.java, I found the following lines that suggest the role is associated with the permissions:

1
2
    private Integer userId;
    private String authority; //role

In the file JwtService.java, there is a reference to how the JWT token is formatted and decoded. In the class constructor, the SECRET_KEY is set, which is used to encrypt the token:

1
2
3
4
5
private final String SECRET_KEY;
@Autowired
    public JwtService(SecretGenerator secretGenerator){
        this.SECRET_KEY = secretGenerator.getServerSecret();
    }

It uses the secretGenerator object with the function getServerSecret(). Let’s take a look at how it’s implemented by opening the file SecretGenerator.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@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;
        }
    }
}

It retrieves the SECRET_KEY from the file server_secret.txt, but if it doesn’t exist, it generates it as follows: String newSecret = generateRandomString(32). If we look closely, the function generateRandomString() takes an int as an argument but always returns return "1234";. So, if the server_secret.txt file is not present on the server, the key will be 1234 (which we can exploit to forge a token and verify it with the secret key). Later, I found an interesting comment in the file models/Role.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
@Entity
@Table(name = "roles")
public class Role {
    @Id
    @Column
    private String name;  
    @Column
    private Integer value; //higher the value, more the privilege. By this logic, admin is supposed to
    // have the highest value
}

As we can see, the higher the value of the role, the greater the privileges, so I thought I would need to assign the highest value to gain administrator privileges. Later, in the file configs/BookShelfConfig.java, I found the initialization of the DB with the users:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 Role FreeRole = roleRepository.findById("Free").get();
 Role PremiumRole = roleRepository.findById("Premium").get();
 Role AdminRole = roleRepository.findById("Admin").get();
 /*
* Initialize admin and a user
* */
User freeUser = new User();
freeUser.setProfilePicName("default-avatar.png")
  	.setRole(FreeRole)
  	.setLastLogin(LocalDateTime.now())
  	.setFullName("User")
  	.setEmail("user")
  	.setPassword(passwordEncoder.encode("user"));
userRepository.save(freeUser);

User admin = new User();
admin.setProfilePicName("default-avatar.png")
  	.setRole(AdminRole)
  	.setLastLogin(LocalDateTime.now())
  	.setFullName("Admin")
  	.setEmail("admin")
  	.setPassword(passwordEncoder.encode("\<redacted>"));
userRepository.save(admin);

As we can see, the user admin has email=admin and role=Admin, and also id=2, as it is the second user created in the database. Now that we have all this information, we just need to take the JWT of the user and use it to change the account to admin in order to access the Flag book. I retrieved the token through the Network section of the ChromeDevTools:

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/token.png
Token

As we can see, in the request header section, the authorization contains Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiRnJlZSIsImlzcyI6ImJvb2tzaGVsZiIsImV4cCI6MTczOTg5NDI2OCwiaWF0IjoxNzM5Mjg5NDY4LCJ1c2VySWQiOjEsImVtYWlsIjoidXNlciJ9.lE6bz9SmbWdhuazvtYgSvm03kBRp7kTpTHXESXp4HaY. These are usually also stored in LocalStorage. In fact, when I tried going to the Application section, I found the token there as well:

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/application.png
Application

The first thing I did was analyze it with https://jwt.io:

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/jwtio.png
Jwt.io

But I am shown the same token payload seen in the image above. The additional information I get is the algorithm:

1
2
3
4
{
 "typ": "JWT",
 "alg": "HS256"
}

It uses HS256. Now that we have everything we need, let’s move on to the exploitation phase.

🔬 Vulnerability Analysis

Potential Vulnerabilities

  • JWT Weak Secret

🎯 Solution Path

Exploitation Steps

Initial setup

After understanding the vulnerability and how to exploit it (by trying to forge an admin token with the secret key), we can proceed to the next step.

Exploitation

From all that information, it’s very easy to forge a valid JWT with the admin user. In fact, all we need to do is directly modify the fields in the decoded section of jwt.io.

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/forged_token.png
Forged Jwt

Now, all that’s left is to insert the token payload and the forged token into the local storage, refresh the page (press F5), and access the Flag book.

Flag capture

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/manual_flag.png
Manual Flag

🛠️ Exploitation Process

Approach

The automatic exploit was not possible for me to write because it didn’t allow me to access the page with the PDF, otherwise, it could have been extracted with pyPDF2 to get the flag. However, I used curl to extract the PDF with the generated token:

1
curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiQWRtaW4iLCJpc3MiOiJib29rc2hlbGYiLCJleHAiOjE3Mzk4OTcyNzksImlhdCI6MTczOTI5MjQ3OSwidXNlcklkIjoyLCJlbWFpbCI6ImFkbWluIn0.HtqfQ48AgTGT-NFVixqfFEEAnrlGnwwmpxe1RKwmIog" http://saturn.picoctf.net:58554/base/books/pdf/5 -o flag.pdf

🚩 Flag Capture

Flag

Proof of Execution

/images/PicoGym/PicoCTF-2023/JavaCodeAnalysis/automated_flag.png
Automated Flag
Screenshot of successful exploitation

🔧 Tools Used

Tool Purpose
jwt.io JWT Forging
ChromeDevTools Web Testing

💡 Key Learnings

Time Optimization

  • In the case of JWT, immediately check if there is any key leak in the attached files, and then the structure to obtain admin access.

Skills Improved

  • Binary Exploitation
  • Reverse Engineering
  • Web Exploitation
  • Cryptography
  • Forensics
  • OSINT
  • Miscellaneous

📚 References & Resources

Learning Resources


📊 Final Statistics

Metric Value Notes
Time to Solve 00:35 From start to flag
Global Ranking (At the time of flag submission) Challenge ranking
Points Earned Team contribution

Created: 11-02-2025 • Last Modified: 11-02-2025 *Author: mH4ck3r0n3 • Team: *