Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

readme.org

Joi.js Input Validation (Part 2)

Joi.js Input Validation (Part 2)

Scope

  1. Exercise: Try to bypass a Joi.js schema
  2. 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?

Ex: Joi Bypass (Hint 1 Answer)

  • It could override sensitive fields in the database
    • Ex: isAdmin: false (set by backend logic) could be overridden with isAdmin: true
    • Type of Mass Assignment vulnerability

Ex: Joi Bypass (Hint 2)

  • In the json that’s fed into Joi.validate(), find where isAdmin: 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: 'foo@example.com',
  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.)

The validation error is: ValidationError: "isAdmin" is not allowed

Ex: Joi Bypass (Hint 3)

Ex: Joi Bypass (Hint 3 Answer)

roles: Joi.object()
  .keys()

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: 'foo@example.com',
  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

Additional Resources