CODEMOTION MILAN - SPECIAL EDITION 10 - 11 NOVEMBER 2017
Principal Application Engineer
is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.
JWT is...
An URL safe, stateless protocol for transferring claims
signature = HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), password )
header
payload
secret
SIGNATURE
+
+
=
Session token
Browser
1. POST /login
2. generate session
id:"Y4sHySEPWAjc" user:"luciano"
user:"luciano" pass:"mariobros"
3. session cookie
SID:"Y4sHySEPWAjc"
4. GET /profile
5. query
id:"Y4sHySEPWAjc"
6. record
id:"Y4sHySEPWAjc" user:"luciano"
7. (page)
<h1>hello luciano</h1>
Server
Sessions
Database
id:"Y4sHySEPWAjc" user:"luciano"
SID:"Y4sHySEPWAjc"
Browser
1. POST /login
3. JWT Token
{"sub":"luciano"}
user:"luciano" pass:"mariobros"
6. (page)
<h1>hello luciano</h1>
Server
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsdWNpYW5vIn0.V92iQaqMrBUhkgEAyRaCY7pezgH-Kls85DY8wHnFrk4
4. GET /profile
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsdWNpYW5vIn0.V92iQaqMrBUhkgEAyRaCY7pezgH-Kls85DY8wHnFrk4
5. verify
2. create
JWT
You only have to Base64Url-decode the token header and payload
and you have a readable JSON
JWT based web app
Take a valid JWT token
try to "guess" the secret and validate the token against it
if the token is validated, then you found the secret!
YOU CAN NOW CREATE AND SIGN ANY JWT TOKEN FOR THIS APPLICATION!
all the strings (of any length) that can be generated within a given alphabet
(empty string), a, b, c, 1, aa, ab, ac, a1, ba, bb, bc, b1, ca, cb, cc, c1, 1a, 1b, 1c, 11, aaa, aab, aac, aa1, aba, ...
if we sort all the possible strings over an alphabet
Alphabet = [a,b]
0 ⟶ (empty string) 1 ⟶ a 2 ⟶ b 3 ⟶ aa 4 ⟶ ab 5 ⟶ ba 6 ⟶ bb
7 ⟶ aaa
8 ⟶ aab
9 ⟶ aba
10 ⟶ abb 11 ⟶ baa 12 ⟶ bab 13 ⟶ bba 14 ⟶ bbb 15 ⟶ aaaa 16 ⟶ aaab
17 ⟶ aaba
18 ⟶ aabb
...
Router channels:
Pub/Sub channel:
{
"cursor": 0,
"clients": {}
}
{
"cursor": 3,
"clients": {
"client1": [0,2]
}
}
{
"cursor": 9,
"clients": {
"client1": [0,2],
"client2": [3,5],
"client3": [6,8]
}
}
{
"cursor": 12,
"clients": {
"client1": [0,2],
"client2": [9,11],
"client3": [6,8]
}
}
let cursor = 0
const clients = new Map()
const assignNextBatch = client => {
const from = cursor
const to = cursor + batchSize - 1
const batch = [from, to]
cursor = cursor + batchSize
client.currentBatch = batch
client.currentBatchStartedAt = new Date()
return batch
}
const addClient = channel => {
const id = channel.toString('hex')
const client = {id, channel, joinedAt: new Date()}
assignNextBatch(client)
clients.set(id, client)
return client
}
Server
JWT Cracker
Server
JWT Cracker
Client
1. JOIN
2. START
{token, alphabet, firstBatch}
3. NEXT
4. BATCH
{nextBatch}
5. SUCCESS
{secret}
const router = (channel, rawMessage) => {
const msg = JSON.parse(rawMessage.toString())
switch (msg.type) {
case 'join': {
const client = addClient(channel)
const response = {
type: 'start',
id: client.id,
batch: client.currentBatch,
alphabet,
token
}
batchSocket.send([channel, JSON.stringify(response)])
break
}
case 'next': {
const batch = assignNextBatch(clients.get(channel.toString('hex')))
batchSocket.send([channel, JSON.stringify({type: 'batch', batch})])
break
}
case 'success': {
const pwd = msg.password
// publish exit signal and closes the app
signalSocket.send(['exit', JSON.stringify({password: pwd, client: channel.toString('hex')})], 0, () => {
batchSocket.close()
signalSocket.close()
exit(0)
})
break
}
}
}
Server
let id, variations, token
const dealer = rawMessage => {
const msg = JSON.parse(rawMessage.toString())
const start = msg => {
id = msg.id
variations = generator(msg.alphabet)
token = msg.token
}
const batch = msg => {
processBatch(token, variations, msg.batch, (pwd, index) => {
if (typeof pwd === 'undefined') {
// request next batch
batchSocket.send(JSON.stringify({type: 'next'}))
} else {
// propagate success
batchSocket.send(JSON.stringify({type: 'success', password: pwd, index}))
exit(0)
}
})
}
switch (msg.type) {
case 'start':
start(msg)
batch(msg)
break
case 'batch':
batch(msg)
break
}
}
Client
Given chunk [3,6] over alphabet "ab"
[3,6] ⇒
3 ⟶ aa 4 ⟶ ab 5 ⟶ ba 6 ⟶ bb
⇠ check if one of the strings is the secret that validates the current token
const jwt = require('jsonwebtoken')
const generator = require('indexed-string-variation').generator;
const variations = generator('someAlphabet')
const processChunk = (token, from, to) => {
let pwd
for (let i = from; i < to; i++) {
pwd = variations(i)
jwt.verify(token, pwd, {
ignoreExpiration: true,
ignoreNotBefore: true
})
// finished, password found
return ({found: i})
}
// finished, password not found
return null
}
Client
Or, even better
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmFjayI6Im1lIiwiaWYiOiJ5b3UgY2FuIn0.tI8zO0gj6WBgaVoKNeHwCKOxOlr3Jo7OqKHwMgrqJJE
If you can, tweet the secret to @loige
I have a prize for the first one!