## Challenge description:
The site [log-me-in.web.ctfcompetition.com](https://log-me-in.web.ctfcompetition.com/) had a navbar with menu-items: home, about, profile, flag and login.
![image](https://user-images.githubusercontent.com/24471300/91640429-374e3b80-ea3d-11ea-9503-882102acdd56.png)
Clicking on the flag button sent us to `/flag` route which popped up an error `You must be logged in to access that`. The `/login` route had a simple login form:
![image](https://user-images.githubusercontent.com/24471300/91652179-47553200-eab4-11ea-979e-f9bfb7ae8a80.png)
We were also provided with the express-js source code for the app:
```javascriptconst 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) =>a {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"})  }});});```
## Quest
 The `const targetUser = "michelle"` in the source hinted we were to somehow bypass the login to be authenticated as `michelle`. My first guess was sql injection but there was a prepared query used:
```javascriptapp.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"})  }});});```
I decided to play around with the post parameters anyways. A normal request with `username=michelle&password=michelle&csrf=` simply gave an error `Invalid username or password`. Since this app was also written in express-js, I thought of trying out the trick from "pasteurize" challenge here as well. I repeated the request with `username=michelle&password=michelle&password=extra&csrf=` which gave back a new error `Unknown error: Error: ER_PARSE_ERROR: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' 'extra' order by id' at line 1`. This confirmed similar behavior.
I then tried requesting with `username=michelle&password[0]=&csrf=` but it again gave the same old error `Invalid username or password` back. I repeated with `username=michelle&password[r]=&csrf=` and got back `Unknown error: Error: ER_BAD_FIELD_ERROR: Unknown column 'r' in 'where clause'`. Bingo! I was then able to login by simply replacing `r` with `id`(or any existing column). The password parameter `password[id]=` was being parsed as an object(something like `{id:null}`) which somehow bypassed the password check in the prepared query.
## Solution
I simply sent a post request via burp repeater to `/login` route with the body `username=michelle&password[id]=&csrf=`. This gave a 302/redirect response(unlike other requests which gave a 200/no-redirect). The response also (re)set the `session` cookie `Set-Cookie: session=eyJjc3JmIjoiYjU3OWM2MWYtNjhmMi00YTIzLWFkYzgtNzAxODE3YTU1YWIzIiwidXNlcm5hbWUiOiJtaWNoZWxsZSIsImZsYWciOiJDVEZ7YS1wcmVtaXVtLWVmZm9ydC1kZXNlcnZlcy1hLXByZW1pdW0tZmxhZ30ifQ==; path=/; httponly`. I was thus able to extract the flag from the base-64-decoded cookie: `CTF{a-premium-effort-deserves-a-premium-flag}`.