Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add async function csrfProtection() #1302

Closed
wants to merge 12 commits into from
1 change: 1 addition & 0 deletions lib/defaults/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"lagCheckInterval": 500
},
"helmet": {},
"csrfProtection": false,
"bodyParser": {
"urlEncoded": {
"extended": true
Expand Down
3 changes: 3 additions & 0 deletions lib/scripts/configAuditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ function configAudit (appDir) {
case 'helmet':
checkTypes(userParam, key, ['object'])
break
case 'csrfProtection':
checkTypes(userParam, key, ['boolean'])
break
case 'bodyParser': {
checkTypes(userParam, key, ['object'])
const bodyParserParam = userParam || {}
Expand Down
29 changes: 28 additions & 1 deletion lib/setExpressConfigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require('@colors/colors')
const morgan = require('morgan') // express logger
const express = require('express')
const helmet = require('helmet')
const session = require('express-session')

module.exports = function (app) {
const logger = app.get('logger')
Expand Down Expand Up @@ -44,7 +45,11 @@ module.exports = function (app) {

// set morgan
app.set('morgan', morgan)

app.use(session({
secret: 'secret key',
resave: false,
saveUninitialized: false
}))
// set helmet middleware
if (params.mode !== 'development') {
let contentSecurityPolicy = params.helmet.contentSecurityPolicy
Expand Down Expand Up @@ -89,5 +94,27 @@ module.exports = function (app) {
logger.warn('No view engine specified. viewEngine has been disabled.')
}

// set the csrf token
if (params.csrfProtection !== false) {
csrfProtection(app)
}

return app
}
async function csrfProtection (app) {
const { csrf } = await import('malibu')

const csrfProtection = csrf({ middleware: 'session' })
app.use(csrfProtection)
app.use((req, res, next) => {
app.get(req.url, csrfProtection, (req, res) => {
res.sendFile(req.url, { csrfToken: req.csrfToken() })
})

app.post(req.url, csrfProtection, (req, res) => {
res.sendFile(req.url)
})

next()
})
}
3 changes: 3 additions & 0 deletions lib/sourceParams.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ module.exports = (params, app, appSchema) => {
helmet: {
default: defaults.helmet
},
csrfProtection: {
default: defaults.csrfProtection
},
bodyParser: {
urlEncoded: {
default: defaults.bodyParser.urlEncoded
Expand Down
98 changes: 98 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@
"execa": "7.2.0",
"express": "4.18.2",
"express-html-validator": "0.2.1",
"express-session": "1.17.3",
"formidable": "2.1.2",
"fs-extra": "11.1.1",
"helmet": "7.0.0",
"html-minifier": "4.0.0",
"klaw": "4.1.0",
"klaw-sync": "6.0.0",
"malibu": "^1.0.6",
"method-override": "3.0.0",
"morgan": "1.10.0",
"node-forge": "1.3.1",
Expand Down
31 changes: 31 additions & 0 deletions test/csrfAttack/csrfExpress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const express = require('express')
const path = require('path')
const app = express()
const https = require('https')
const http = require('http')
const fs = require('fs')

try {
const httpsOptions = {
key: fs.readFileSync('./certs/key.pem'),
cert: fs.readFileSync('./certs/cert.pem')
}

app.set('port', 3001)
app.enable('trust proxy')

https.createServer(httpsOptions, app).listen(app.get('port'), function () {
console.log('Express HTTPS server listening on port ' + app.get('port'))
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, '/'))
})
})
app.get('/form', (req, res) => {
res.sendFile(path.join(__dirname, '/form'))
})
app.post('/', function (req, res) {
res.sendFile(path.join(__dirname, '/'))
})
} catch (e) {
console.log(e)
}
18 changes: 18 additions & 0 deletions test/csrfAttack/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Some HTML that will not validate</title>
</head>
<body>
<form method="POST" action="https://localhost:34711/form">
<h1>Congratulations. You just won a bonus of 1 million dollars!!!</h1>
<input type="hidden" name="uname" value="IamHere" />
<input type="hidden" name="psw" value="123456" />
<input type="submit" name="submitAttack" id="submitAttack" value="Click here to claim your bonus"/>
</form>

</body>
</html>
108 changes: 108 additions & 0 deletions test/csrfToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/* eslint-env mocha */

const assert = require('assert')
const appCleaner = require('./util/cleanupTestApp')
const { fork } = require('child_process')
const fs = require('fs-extra')
const generateTestApp = require('./util/generateTestApp')
const path = require('path')
const request = require('supertest')
const getcsrfAttack = require('./../test/util/csrfAttack')

describe('form pages', function () {
const appDir = path.join(__dirname, 'app/errorPages')
// options to pass into test app generator
const options = { rooseveltPath: '../../../roosevelt', method: 'startServer', stopServer: true }

beforeEach(function (done) {
// copy the mvc directory into the test app directory for each test
fs.copySync(path.join(__dirname, './util/mvc'), path.join(appDir, 'mvc'))
done()
})

afterEach(function (done) {
// clean up the test app directory after each test
appCleaner(appDir, (err) => {
if (err) {
throw err
} else {
done()
}
})
})

it('should render the form test page', function (done) {
// generate the test app
generateTestApp({
appDir,
makeBuildArtifacts: true,
viewEngine: [
'html: teddy'
],
onServerStart: '(app) => {process.send(app.get("params"))}'
}, options)

// fork and run app.js as a child process
const testApp = fork(path.join(appDir, 'app.js'), { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] })

// when the app starts and sends a message back to the parent try and request the test page
testApp.on('message', () => {
request('http://localhost:43711')
.get('/form')
.expect(200, (err, res) => {
if (err) {
assert.fail(err)
testApp.send('stop')
done()
}
// test that the values rendered on the page are correct
const test1 = res.text.includes('CSRF Test')
const test2 = res.text.includes('Login Form')
const test3 = res.text.includes('User Name:')
const test4 = res.text.includes('Password:')
assert.strictEqual(test1, true)
assert.strictEqual(test2, true)
assert.strictEqual(test3, true)
assert.strictEqual(test4, true)
testApp.send('stop')
})

// when the child process exits, finish the test
testApp.on('exit', () => {
done()
})
})
})

it.only('should render', function (done) {
// generate the test app
// generateTestApp({
// appDir,
// makeBuildArtifacts: true,
// viewEngine: [
// 'html: teddy'
// ],
// onServerStart: '(app) => {process.send(app.get("params"))}'
// }, options)

// fork and run app.js as a child process
getcsrfAttack.csrfAttack()
const attackApp = fork('./csrfAttack/csrfExpress.js', { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] })
console.log(attackApp)
attackApp.on('message', () => {
request('http://localhost:3001')
.post('form')
.expect(200, (err, res) => {
if (err) {
assert.fail(err)
// attackApp.send('stop')
done()
}
const test1 = res.text.includes('Congratulations. You just won a bonus of 1 million dollars!!')
assert.strictEqual(test1, true)
// attackApp.send('stop')
})
})
done()
})
})
Loading
Loading