I have this forgot password handler in my Express app. I send an email with a crypto.randomBytes(20).toString("hex") token, that I use later to verify the request. It is working perfectly.
However, I have seen that people are hashing this token before sending it and storing in the data base, and I don't see why, cause it is already a random string.
const forgotPassword = async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
if (!user) {
throw Error("incorrect email");
}
const resetPasswordToken = crypto.randomBytes(20).toString("hex");
user.resetPasswordToken = resetPasswordToken;
user.resetPasswordTokenExpire = Date.now() + 10 * (60 * 1000);
await user.save();
const message = `
<h1>You have requested a password reset</h1>
<p>Here is your token : </p>
<p>${resetPasswordToken}</p>
`;
try {
await sendEmail({
to: user.email,
subject: "Password reset request",
text: message,
});
res.status(200).json({ message: "Email sent" });
} catch (err) {
user.resetPasswordToken = undefined;
user.resetPasswordTokenExpire = undefined;
res.status(500).json({ message: "Email could not be sent" });
}
} catch (error) {
console.log(error);
const errorDetails = handleErrors(error);
res.status(400).json(errorDetails);
}
};
If you hash the token and only save the hash into the database, you can make sure that admins and other people who are able to access the database cannot use the token to reset a password for a different user.
It's basically the same reason why you hash (and salt and pepper) passwords, because you don't want that the original string can be recreated whenever somebody has access to the table.
Related
I am trying to implement email confirmation on my project. I sign a JWT which is sent in an email using nodemailer. After the link is clicked the JWT is sent to the server for processing. I am able to find out if the JWT matches the one generated by the server. However I am now struggling with updating the database to store that the user has confirmed the email.
Controller
confirmEmail = async (req, res, next) => {
this.checkValidation(req)
const { email } = req.body
const param = req.params.token
const user = await userModel.findOne({ email })
if(!user)
{
throw new HttpException(401, 'User not found')
}
if(user.confired)
{
throw new HttpException(401, 'User already confirmed')
}
if(!user.confirmed)
{
const confirmJWT = jwt.verify(param, process.env.SECRET_JWT)
if(!confirmJWT)
{
throw new HttpException(200, 'Token failed')
}
const result = await userModel.emailConfirmed(email)
throw new HttpException(401, 'User not cofirmed')
}
}
Model
emailConfirmed = async({email}) => {
const sql = `UPDATE ${this.tableName} SET confirmed = true WHERE email = ?`
const result = await query(sql, email)
return result
}
[Error] Error: Incorrect arguments to mysqld_stmt_execute
this is the error I get when attempting to change the db.
I'm trying to beat this CTF: a website under construction that has a simple login page where you can login or register a new user.
It uses node express and a SQLite DB.
Analyzing the source code I found this query:
getUser(username){
return new Promise((res, rej) => {
db.get(`SELECT * FROM users WHERE username = '${username}'`, (err, data) => {
if (err) return rej(err);
res(data);
});
});
},
This function gets called after a Middleware checked session cookies with jsonwebtoken to retrieve the username when you GET '/'.
This is where it's called:
router.get('/', AuthMiddleware, async (req, res, next) => {
try{
let user = await DBHelper.getUser(req.data.username);
if (user === undefined) {
return res.send(`user ${req.data.username} doesn't exist in our database.`);
}
return res.render('index.html', { user });
}catch (err){
return next(err);
}
});
AuthMiddleware:
module.exports = async (req, res, next) => {
try{
if (req.cookies.session === undefined) return res.redirect('/auth');
let data = await JWTHelper.decode(req.cookies.session);
req.data = {
username: data.username
}
next();
} catch(e) {
console.log(e);
return res.status(500).send('Internal server error');
}
}
Since that appears to be the only query formatted that way (the others all use ?) and in general the only evident vulnerability, I suspect the flag is stored somewhere in the database.
As the only way to get that function called is to have an active session i registered a user with 'malicious' sql code as the username. At first I tried to close the quote and attach an OR WHERE to get all the users:
SELECT * FROM users WHERE username = '${username}' +
bob' OR WHERE username IS NOT NULL =
SELECT * FROM users WHERE username = 'bob' OR WHERE username IS NOT NULL
This should at least throw an error as WHERE username = 'bob' OR WHERE username IS NOT NULL should return a collection with all the users in the database while it's rendered on the webpage as
Welcome {{ user.username }}
This site is under development.
Please come back later.
I was expecting at least "no username property on array" or something like that. Instead it always return the full username I gave him
Welcome bob' OR WHERE username IS NOT NULL
This site is under development.
Please come back later.
Am I missing something? Is there a way to escape eventual quotes that might be added during the cookie reading?
EDIT:
Here is the function that gets called when you attempt a login
/auth route:
router.post('/auth', async (req, res) => {
const { username, password } = req.body;
if((username !== undefined && username.trim().length === 0)
|| (password !== undefined && password.trim().length === 0)){
return res.redirect('/auth');
}
if(req.body.register !== undefined){
let canRegister = await DBHelper.checkUser(username);
if(!canRegister){
return res.redirect('/auth?error=Username already exists');
}
DBHelper.createUser(username, password);
return res.redirect('/auth?error=Registered successfully&type=success');
}
// login user
let canLogin = await DBHelper.attemptLogin(username, password);
if(!canLogin){
return res.redirect('/auth?error=Invalid username or password');
}
let token = await JWTHelper.sign({ // Maybe something can be done with this function?
username: username.replace(/'/g, "\'\'").replace(/"/g, "\"\"")
})
res.cookie('session', token, { maxAge: 900000 });
return res.redirect('/');
});
attemptLogin():
attemptLogin(username, password){
return new Promise((res, rej) => {
db.get(`SELECT * FROM users WHERE username = ? AND password = ?`, username, password, (err, data) => {
if (err) return rej();
res(data !== undefined);
});
});
}
EDIT 2.0:
I just noticed the part where it stores the session cookie:
let token = await JWTHelper.sign({
username: username.replace(/'/g, "\'\'").replace(/"/g, "\"\"")
})
It apparently replaces all ' with \'\'. I can solve half of that by escaping the quote so that it becomes \\'\'. This allows me to close the username='' statement, but I still need to find a way to invalidate the second \'.
I'm making a forgot password functionality in my express app suing node mailer package which sends a new password to the email of the user and then updating the database with the new password but the problem is that the database is not updating with the new password i have reviewed my code many times but unable to figure out the problem.
`app.post('/forgot', (req, res) => {
const { email } = req.body;
let newPassword = generatePassword();
db.select('*').from('login')
.where('email', '=', email)
.returning('*')
.then(user => {
if (user.length > 0) {
let mailOptions = {
from: 'haroonrmit#gmail.com',
to: `${email}`,
subject: 'Password reset',
html: `<p>Your new password is ${newPassword}</p>`
};
transporter.sendMail(mailOptions);
db('login')
.where({email: user[0].email})
.update({
password: newPassword
})
res.json(user[0]);
} else {
res.status(404).json('email not found');
}
})
});`
You are not calling .then for the second query, so it is built but never ran.
Also your http returns a value before it knows if update was executed with success.
I have a login in my Angular 2 app, and I have been converting it from using a fake backend (which works) to connect to our mongoDB-based API instead.
This is the login function I am using in the authentication service:
login(username: string, password: string) {
const u = encodeURIComponent(username);
const p = encodeURIComponent(password);
this._url = `https://api.somesite.com/v0/staff/login/${u}/${p}?apikey=somekey`;
console.log(this._url);
return this.http.post(this._url, JSON.stringify({ username: username, password: password }))
.map((response: Response) => {
// login successful if there's a jwt token in the response
const user = response.json();
if (user && user.token) {
// store user details and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
}
});
}
In my login component I am subscribing like this:
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(
data => {
this.router.navigate(['/']);
console.log('User logged in as: ' + this.model.username);
},
error => {
this.alertService.error(error);
this.loading = false;
});
this.authenticationService.username = this.model.username;
}
When I try this, and log to the console "this_url", I get what I would expect. For instance, if the user typed in "billsmith" for username, and "parisnow" for password, I see this in the console for "this_url":
https://api.somesite.com/v0/staff/login/billsmith/parisnow?apikey=somekey
Furthermore, I can type that url directly into the browser address window and see data (when the username and password correctly correspond to actual records in our database). So it's accessing the correct info in that sense.
But in the console I get a "404" error for that generated url. It also doesn't "do anything". In other words, it doesn't correctly redirect to the main component as it did with the fakeBackend-enabled login. And the only thing that's different now is the url that I am calling (because I'm connecting to our actual API now, as opposed to a fake backend provider).
FYI, the url when using the fake backend looked like this:
return this.http.post('/api/authenticate', JSON.stringify({ username: username, password: password}))
What am I missing here?
By the way, this is how things look on the server side re: our mongoDB:
exports.byLogin = function(req, res, next) {
let ioOnly = false, username, password;
if (_.isUndefined(req.params)){
ioOnly=true;
username = req.username;
password = req.password;
}
else {
username = req.params.username;
password = req.params.password;
}
staff.findOne({username: username, password: password}, function(err, doc) {
if (err) { if (!ioOnly) { return next(err) } else { return res(err)}}
else if(doc) ((!ioOnly) ? res.send(doc) : res(doc));
else ((!ioOnly) ? res.sendStatus(204) : res(doc));
});
};
I have a MongoDB/Webpack/NodeJS Express set up in my ReactJS + Redux project.
I am making API calls from action creators in redux, and reach the API server and get a successful status back, yet the data never gets saved and the database never gets created even checking with in terminal mongo -> dbs and it doesn't show practicedb database which I named it as.
What could be the issue? Am I missing something?
Any guidance or insight would be greatly appreciated. Thank you
This is my set up for API:
import axios from 'axios';
import { browserHistory } from 'react-router';
import cookie from 'react-cookie';
import { AUTH_USER, AUTH_ERROR } from './types';
const API_URL = 'http://localhost:3000/api';
export function errorHandler(dispatch, error, type) {
let errorMessage = (error.data.error) ? error.data.error : error.data;
// NOT AUTHENTICATED ERROR
if(error.status === 401) {
errorMessage = 'You are not authorized to do this.';
}
dispatch({
type: type,
payload: errorMessage
});
}
export function registerUser({ email }) {
return function(dispatch) {
axios.post(`${API_URL}/auth/register`, { email })
.then(response => {
console.log('THIS IS TESTING PURPOSE')
console.log(response)
dispatch({ type: AUTH_USER });
})
.catch((error) => {
errorHandler(dispatch, error.response, AUTH_ERROR)
});
}
}
And my API controller is set up as such:
"use strict";
const User = require('../models/user')
exports.register = function(req, res, next) {
const email = req.body.email;
console.log('ERROR 1')
if(!email) {
return res.status(422).send({ error: 'You must enter an email address.'})
console.log('ERROR 1')
}
User.findOne({ email: email }, function(err, existingUser) {
if(err) { return next(err); }
console.log('ERROR 2')
if(existingUser) {
return res.status(422).send({ error: 'That email address is already in use.'})
}
console.log('ERROR 3')
let user = new User({
email: email,
})
console.log('ERROR 4')
user.save(function(err, user) {
if(err) { return next(err); }
console.log('ERROR 5')
res.status(201).json({
user: user,
})
})
})
console.log('ERROR 6')
}
Configuration for the API:
module.exports = {
'database': 'mongodb://localhost/practicedb',
'port': process.env.PORT || 3000,
'secret': 'dogcat',
}
The project so far just has an input text field, where it accepts an email address. If the email has already been registered, the API should return the error That email address is already in use. and it does.
So I tried console logging to see what the problem is, and the first time I submit the POST request, it logs the following (the terminal showing API console logs):
And if I try to submit the same email again, it throws me the API error that the email is already in use with 422 error, yet the data do not get saved and database (practicedb) never get created:
Also, what is the OPTIONS request that shows up in terminal? I only made an attempt to POST. Lastly, is OPTIONS why the ERROR log in API server is not logging in chronological order?
EDIT
You're using the wrong Mongo shell command: db will only show you the current database (test), but if you want to see a list of all databases, you should use show dbs.
If you want to switch databases:
use practicedb
And, if you want to see the collections in the current database:
show collections