Notey

For the purpose of simplicity I will list here only important functions and files.

Init.db
-- Table structure for table `users`
--

DROP TABLE IF EXISTS `users`;
CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL UNIQUE,
  `password` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=66 ;

CREATE TABLE IF NOT EXISTS `notes` (
  `note_id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `secret` varchar(255) NOT NULL,
  `note` varchar(255) NOT NULL,
  PRIMARY KEY (`note_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=66 ;
index.js
const express = require('express');
const bodyParser = require('body-parser');
const crypto=require('crypto');
var session = require('express-session');
const db = require('./database');
const middleware = require('./middlewares');

const app = express();


app.use(bodyParser.urlencoded({
extended: true
}))

app.use(session({
    secret: crypto.randomBytes(32).toString("hex"),
    resave: true,
    saveUninitialized: true
}));

View endpoint

Get note by ID endpoint

Insert flag as note in the admin account

So this application basically has these functionalities:

  • Register an account

  • Login to account

  • Profile view

  • Add note

  • View note

Also, when starting the program it automatically calls insertAdminNoteOnce, which will insert the flag as a note with a random 32 hex secret in the admin account.

To view a note you need to provide 2 GET parameters: note_id & note_secret.

So how can we view the flag? Clearly, we can't bruteforce a 32 random bytes secret.

Analyzing

We can easily get the note id, by looking at the table creation: AUTO_INCREMENT=66 which means that the ID starts from 66.

For the note secret, looking at the first few lines of index.js we can see an interesting line:

What does extended mean? From expressjs docsarrow-up-right:

The “extended” syntax allows for rich objects and arrays to be encoded into the URL-encoded format, allowing for a JSON-like experience with URL-encoded.

This allows nested query parameters. For example, if we pass our GET query like:

It would be interpreted as:

The viewNote route will then call the getNoteById function, which constructs a SELECT query with our inputs:

When you pass a JavaScript object to SQL (the mysql library), it treats the key as a column and the value as the value of that column. In simpler terms our query is going to be like this:

Backticks (`) in MySQL encapsulate identifiers like table and column names. So if c was an existing column, it will compare all the values in it with the secret column which would result in true if at least one value appeared in both columns (c and secret) and then it will compare it with the value 'd'.

So if we passed this GET query:

It would be interpreted in SQL as:

so secret = secret = 1 will evaluate to true = 1; which is always true in MySQL.

Exploiting

Because of jailing we need to write a simple and fast script using a requests session:

And we get the flag:

Last updated