Course Content

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: '[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)

Ex: Joi Bypass (Hint 3 Answer)

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

Additional Resources

Complete and Continue  
Discussion

0 comments