Course Content
Joi.js Input Validation (Part 2)
Scope
- Exercise: Try to bypass a Joi.js schema
- Understand how to approach input validation (useful for any input validation library)
Ex: Joi Bypass (Hint 1)
- How can the schema be bypassed?
- Hint 1
- JSON input (with keys/values) is directly assigned into keys/values in the database
- What could this potentially override?
- JSON input (with keys/values) is directly assigned into keys/values in the database
- Hint 1
Ex: Joi Bypass (Hint 1 Answer)
- It could override sensitive fields in the database
- Ex:
isAdmin: false
(set by backend logic) could be overridden withisAdmin: true
- Type of Mass Assignment vulnerability
- Ex:
Ex: Joi Bypass (Hint 2)
- In the json that's fed into
Joi.validate()
, find whereisAdmin: true
could be injected
Ex: Joi Bypass (Hint 2 Answer)
- Add
isAdmin: true
to root level
const Joi = require('joi');
const schema = Joi.object()
.keys({
// Requires a given string value
username: Joi.string()
.alphanum()
.min(3)
.max(30)
.required(),
// Define password complexity requirements through regex (consider more complex regex)
password: Joi.string()
.regex(/^[a-zA-Z0-9]{3,30}$/)
.required(),
// Force passwords to match
password_confirmation: Joi.any()
.equal(Joi.ref('password'))
.required(),
// Accept different Joi types. Optional, unconstrained string or number
access_token: [Joi.string(), Joi.number()],
// Required birthyear to be an int between range
birthyear: Joi.number()
.integer()
.min(1900)
.max(2013)
.required(),
// Validate email adress from example.com (remember spoofing considerations)
email: Joi.string()
.email()
.regex(/example\.com$/),
marketing_opt_out: Joi.boolean(),
csrf_token: Joi.string()
.guid({
version: 'uuidv4',
})
.required(),
sex: Joi.string()
.equal(['M', 'F', 'MALE', 'FEMALE', 'DECLINE'])
.required(),
time: Joi.date()
.timestamp('javascript'),
roles: Joi.object()
.keys(),
})
// email must be accompanied by marketing_opt_out
.with('email', 'marketing_opt_out');
const result = Joi.validate({
username: 'Ronald',
password: 'ZZZ',
password_confirmation: 'ZZZ',
birthyear: 2010,
email: '[email protected]',
marketing_opt_out: true,
csrf_token: '6d4d8c14-ef12-45d9-ab3c-5dddf941fb76',
sex: 'F',
time: 1534942475121,
roles: {},
isAdmin: true,
}, schema);
// If result.error === null, payload is valid
console.log(`The validation error is: ${result.error}`);
Ex: Joi Bypass (Hint 2 Answer CONT.)
Ex: Joi Bypass (Hint 3)
- Where else could
isAdmin: true
be inserted? - Review https://github.com/hapijs/joi/blob/master/API.md#objectkeysschema
- How could this confuse a developer?
Ex: Joi Bypass (Hint 3 Answer)
- No keys are whitelisted
- Appears that only an empty object would pass validation
- Backend leverages this object to populate ACLs
- Appears that only an empty object would pass validation
- https://github.com/hapijs/joi/blob/master/API.md#objectkeysschema
object.keys([schema])
- If schema is
{}
no keys are allowed. If schema isnull
orundefined
, any key is allowed
- If schema is
Ex: Joi Bypass (Answer)
const Joi = require('joi');
const schema = Joi.object()
.keys({
// Requires a given string value
username: Joi.string()
.alphanum()
.min(3)
.max(30)
.required(),
// Define password complexity requirements through regex (consider more complex regex)
password: Joi.string()
.regex(/^[a-zA-Z0-9]{3,30}$/)
.required(),
// Force passwords to match
password_confirmation: Joi.any()
.equal(Joi.ref('password'))
.required(),
// Accept different Joi types. Optional, unconstrained string or number
access_token: [Joi.string(), Joi.number()],
// Required birthyear to be an int between range
birthyear: Joi.number()
.integer()
.min(1900)
.max(2013)
.required(),
// Validate email adress from example.com (remember spoofing considerations)
email: Joi.string()
.email()
.regex(/example\.com$/),
marketing_opt_out: Joi.boolean(),
csrf_token: Joi.string()
.guid({
version: 'uuidv4',
})
.required(),
sex: Joi.string()
.equal(['M', 'F', 'MALE', 'FEMALE', 'DECLINE'])
.required(),
time: Joi.date()
.timestamp('javascript'),
roles: Joi.object()
.keys(),
})
// email must be accompanied by marketing_opt_out
.with('email', 'marketing_opt_out');
const result = Joi.validate({
username: 'Ronald',
password: 'ZZZ',
password_confirmation: 'ZZZ',
// access_token: 1234,
birthyear: 2010,
email: '[email protected]',
marketing_opt_out: true,
csrf_token: '6d4d8c14-ef12-45d9-ab3c-5dddf941fb76',
sex: 'F',
time: 1534942475121,
roles: {
isAdmin: true,
},
}, schema);
// If result.error === null, payload is valid
console.log(`The validation error is: ${result.error}`);
Ex: Joi Bypass (Answer) CONT
The validation error is: null
Takeaways
- Be very careful with library defaults when leveraging input validation
- Easy for hackers to look for edge cases in validation
0 comments