From 81e101ff625d44b96007e73f440029f33a9ac35a Mon Sep 17 00:00:00 2001 From: Spyros Date: Mon, 30 Sep 2019 09:15:02 +0200 Subject: [PATCH] code examples using Javascript (#507) * Added nodejs/expressjs examples Signed-off-by: northdpole * changes * Refactor some of the code examples into new structure * Improve nav for XSS * Finish refactoring and cleaning files * Update 10-code_example--Prepared_Statements_SQL--.md replaced ESAPI with parameterized inputs since we're not using esapi * first attempt and ID based auth example * minor fixes * closes #14 * closes issue #16 using passport * closes(?) #11 * closes #10 * Update 21-code_example--Password_forget_and_disallow_old_passwords--.md --- .../1-code_example--CSRF_Token_csurf--.md | 66 +++++++++ ...code_example--Prepared_Statements_SQL--.md | 24 ++++ .../12-code_example--File_uploading--.md | 14 ++ .../15-code_example--HttpOnly_flag--.md | 26 ++++ ...ample--Identifier_based_authorization--.md | 84 +++++++++++ .../18-code_example--Login_function--.md | 46 ++++++ .../19-code_example--Logout--.md | 21 +++ .../2-code_example--Charsets--.md | 23 +++ ..._example--Open_Forwards_and_redirects--.md | 35 +++++ ...ord_forget_and_disallow_old_passwords--.md | 115 +++++++++++++++ .../23-code_example--Sandboxing--.md | 27 ++++ ...-code_example--Secure_Session_Cookies--.md | 31 ++++ ...ple--RFD_and_file_download_injections--.md | 14 ++ .../29-code_example--Randomizer_function--.md | 32 +++++ .../3-code_example--XSS_Filtering--.md | 54 +++++++ ...ample--session_hijacking_and_fixation--.md | 50 +++++++ .../32-code_example--Timeout_Sessions--.md | 21 +++ ...egistration_SQL_truncation_prevention--.md | 132 ++++++++++++++++++ .../36-code_example--Anti_clickjacking--.md | 67 +++++++++ ...xample--X_Content_Type_Options_header--.md | 26 ++++ .../42-code_example--Hashing--.md | 48 +++++++ .../5-code_exmaple--Anti_caching_headers--.md | 29 ++++ ...mple--Directory_path_traversal_attack--.md | 38 +++++ 23 files changed, 1023 insertions(+) create mode 100755 skf/markdown/code_examples/nodejs-express/1-code_example--CSRF_Token_csurf--.md create mode 100755 skf/markdown/code_examples/nodejs-express/10-code_example--Prepared_Statements_SQL--.md create mode 100755 skf/markdown/code_examples/nodejs-express/12-code_example--File_uploading--.md create mode 100755 skf/markdown/code_examples/nodejs-express/15-code_example--HttpOnly_flag--.md create mode 100755 skf/markdown/code_examples/nodejs-express/17-code_example--Identifier_based_authorization--.md create mode 100755 skf/markdown/code_examples/nodejs-express/18-code_example--Login_function--.md create mode 100755 skf/markdown/code_examples/nodejs-express/19-code_example--Logout--.md create mode 100755 skf/markdown/code_examples/nodejs-express/2-code_example--Charsets--.md create mode 100755 skf/markdown/code_examples/nodejs-express/20-code_example--Open_Forwards_and_redirects--.md create mode 100755 skf/markdown/code_examples/nodejs-express/21-code_example--Password_forget_and_disallow_old_passwords--.md create mode 100755 skf/markdown/code_examples/nodejs-express/23-code_example--Sandboxing--.md create mode 100755 skf/markdown/code_examples/nodejs-express/24-code_example--Secure_Session_Cookies--.md create mode 100755 skf/markdown/code_examples/nodejs-express/26-code_example--RFD_and_file_download_injections--.md create mode 100755 skf/markdown/code_examples/nodejs-express/29-code_example--Randomizer_function--.md create mode 100755 skf/markdown/code_examples/nodejs-express/3-code_example--XSS_Filtering--.md create mode 100755 skf/markdown/code_examples/nodejs-express/30-code_example--session_hijacking_and_fixation--.md create mode 100755 skf/markdown/code_examples/nodejs-express/32-code_example--Timeout_Sessions--.md create mode 100755 skf/markdown/code_examples/nodejs-express/33-code_example--User_Registration_SQL_truncation_prevention--.md create mode 100755 skf/markdown/code_examples/nodejs-express/36-code_example--Anti_clickjacking--.md create mode 100755 skf/markdown/code_examples/nodejs-express/37-code_example--X_Content_Type_Options_header--.md create mode 100755 skf/markdown/code_examples/nodejs-express/42-code_example--Hashing--.md create mode 100755 skf/markdown/code_examples/nodejs-express/5-code_exmaple--Anti_caching_headers--.md create mode 100755 skf/markdown/code_examples/nodejs-express/8-code_example--Directory_path_traversal_attack--.md diff --git a/skf/markdown/code_examples/nodejs-express/1-code_example--CSRF_Token_csurf--.md b/skf/markdown/code_examples/nodejs-express/1-code_example--CSRF_Token_csurf--.md new file mode 100755 index 000000000..7b0326cbd --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/1-code_example--CSRF_Token_csurf--.md @@ -0,0 +1,66 @@ +# CSRF Tokens + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General + +If you're using JSON over REST to mutate server state and the application doesn't support plain HTML form submissions and your CORS configuration bans cross-domain requests then Express has built-in CSRF protection. + +If you support plain HTML form submissions, read on. + +**Hint:** you can check if you support plain HTML form submissions by searching for: + +```js +const bodyParser = require('body-parser'); +bodyParser.urlencoded(); +``` + +## Example + +The following handlebar template snippet shows the code used to place the antiCSRF token inside a html page. + +When the page renders, the `` is created as a viewstate encoded html input tag which then carries the antiCSRF token. While in process of rendering the page, a new token is generated and added into the existing session. + +When the user presses the commandButton then CSRF token parameter is compared with the CSRF session parameter. + +```hbs +
+ + ... + +
+``` + +The following snippet is used to generate and check the token: + +```js +const csrf = require('csurf'); //csrf module +const csrfProtection = csrf({ cookie: true }); // setup route middlewares + +// This is required because "cookie" is true in csrfProtection +app.use(cookieParser()); + +// Error handler(Optional) shows custom error message when token is missing or mismatches +app.use((err, req, res, next) => { + // on token validation fail, error is thrown with code 'CSRFERROR' + if (err.code !== 'CSRFERROR') return next(err); + res.status(403); + res.send('csrf error'); +}); + +// We need to pass the middleware to each route +app.get('/form', csrfProtection, (req, res) => { + // generate and pass the csrfToken to the view + res.render('send', { csrfToken: req.csrfToken() }); +}); + +// and check it when the request is being processed +app.post('/process', parseForm, csrfProtection, (req, res) => { + res.send('data is being processed'); +}); +``` + +## Considerations +`csurf` doesn't protect by default requests such as `GET`, `OPTIONS`, `HEAD`. diff --git a/skf/markdown/code_examples/nodejs-express/10-code_example--Prepared_Statements_SQL--.md b/skf/markdown/code_examples/nodejs-express/10-code_example--Prepared_Statements_SQL--.md new file mode 100755 index 000000000..8cbaf833e --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/10-code_example--Prepared_Statements_SQL--.md @@ -0,0 +1,24 @@ +# Encoder (SQL - Parameterized Inputs) + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +Execute prepared statement with parameterized user inputs using [`mysql` module](https://www.npmjs.com/package/mysql): +```js +const sqlQuery = 'SELECT * FROM accounts WHERE username=? AND password=?'; + +connection.query(sqlQuery, [username, passwordHash], (err, rows, fields) => { + // handle both success and failure for query result +}); + +connection.end(); +``` + +## Considerations +TBA + diff --git a/skf/markdown/code_examples/nodejs-express/12-code_example--File_uploading--.md b/skf/markdown/code_examples/nodejs-express/12-code_example--File_uploading--.md new file mode 100755 index 000000000..da776044f --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/12-code_example--File_uploading--.md @@ -0,0 +1,14 @@ +# File Uploading + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +TBA + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/15-code_example--HttpOnly_flag--.md b/skf/markdown/code_examples/nodejs-express/15-code_example--HttpOnly_flag--.md new file mode 100755 index 000000000..496b999a8 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/15-code_example--HttpOnly_flag--.md @@ -0,0 +1,26 @@ +# `httpOnly` flag + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +`httpOnly` flag can be added to the `Set-Cookie` response header in order to dissalow client-side scripts from accessing or modifying the cookie in question. This can help to mitigate most common XSS attacks by protecting the cookie data. + +## Example +When setting sessions with [`express-session` module](https://www.npmjs.com/package/express-session) you can add the `cookie` portion of the configuration as shown below in order to protect session ID cookie: +```js +const session = require('express-session'); + +app.use(session({ + secret: 'some random and long value', + key: 'sessionId', + cookie: { + httpOnly: true, + secure: true + } +})); +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/17-code_example--Identifier_based_authorization--.md b/skf/markdown/code_examples/nodejs-express/17-code_example--Identifier_based_authorization--.md new file mode 100755 index 000000000..78f8a7177 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/17-code_example--Identifier_based_authorization--.md @@ -0,0 +1,84 @@ +# Identifier-based authorization + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +Database expected is MS SQL server making use of [mssql](https://www.npmjs.com/package/mssql). +`file_access` is formatted as so: +| user_id | file_id | +|---------|---------| +| 1 | 2 | +Where both ids are foreign keys and the primary key is made of a composite of the two. + +## Example +```js +const express = require('express'); +const session = require('express-session') +const FileStore = require('session-file-store')(session); +const bodyParser = require('body-parser'); +const passport = require('passport'); +const LocalStrategy = require('passport-local').Strategy; +const sql = require('mssql') + +//Made up external files +const validator = require('./validator'); //Handles validating logins +const files = require('./files'); //Gets files from DB or store + + + +// configure passport.js to use the local strategy +passport.use(new LocalStrategy( + { usernameField: 'email' }, (email, password, done) => { + const user = validator.login(email, password); //Validator returns false if invalid + return done(null, user) + } +)); + +// tell passport how to serialize the user +passport.serializeUser((user, done) => { + done(null, user.id); +}); + +passport.deserializeUser((id, done) => { + const user = users.getUserById(id); + done(null, user); +}); + +// create the server +const app = express(); + +// add & configure middleware +app.use(bodyParser.urlencoded({ extended: false })) +app.use(bodyParser.json()) +app.use(session({ + store: new FileStore(), + secret: process.env.sessionKey, //always use environment variables to pass in keys. + resave: false, + saveUninitialized: true +})) +app.use(passport.initialize()); +app.use(passport.session()); + +//Login excluded + +app.get('/post', passport.authenticate('local', { failureRedirect: '/login' }), //authenticates the user using session + async function(req, res) { + const data = req.body; + let pool = await sql.connect(config) + let result = await pool.request() + .input('user_id', sql.Int, req.user.id) //sql.Int validates that only a integer value can be in the variable + .input('file_id', sql.Int, data.id) + .query('select * from file_access where user_id = @user_id and file_id = @file_id'); //variables inlined into sql query + + if(result.recordsets.length === 1) { //If the result exists the user has access + res.send(files.getFile(data.id)) //sends file + } else { + res.redirect('/invalidFile'); //redirects to a generic invalid file + } + }); +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/18-code_example--Login_function--.md b/skf/markdown/code_examples/nodejs-express/18-code_example--Login_function--.md new file mode 100755 index 000000000..9aea873a7 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/18-code_example--Login_function--.md @@ -0,0 +1,46 @@ +# Login Functionality + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +Using the [Passport middleware](http://www.passportjs.org/) + +The following example assumes username/password authentication. + +First, configure the middleware: +``` + var auth_manager = require('passport') + , LocalStrategy = require('passport-local').Strategy; + +auth_manager.use(new LocalStrategy( + function(username, password, done) { + User.findOne({ username: username }, function(err, user) { + if (err) { return done(err); } + if (!user) { + return done(null, false, { message: 'Incorrect username.' }); + } + if (!user.validPassword(password)) { + return done(null, false, { message: 'Incorrect password.' }); + } + return done(null, user); + }); + } +)); +``` + +Then, register the route handling authentication can be: +``` +app.post('/login', + auth_manager.authenticate('local', { successRedirect: '/', + failureRedirect: '/login' + }) +); +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/19-code_example--Logout--.md b/skf/markdown/code_examples/nodejs-express/19-code_example--Logout--.md new file mode 100755 index 000000000..09de91862 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/19-code_example--Logout--.md @@ -0,0 +1,21 @@ +# Logout Functionality + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General + +## Example +Using [Passport](http://www.passportjs.org/docs/logout/) as a middleware call logOut() or logout() on your req object. +``` +app.get('/logout', function(req, res){ + req.logout(); + res.redirect('/'); +}); + +``` + + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/2-code_example--Charsets--.md b/skf/markdown/code_examples/nodejs-express/2-code_example--Charsets--.md new file mode 100755 index 000000000..75f0dafe9 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/2-code_example--Charsets--.md @@ -0,0 +1,23 @@ +# Charsets + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +Charset header should be set on the response your server sends back to the client. For example, in the case of `text/html` this can be achieved by the following code: +```js +res.charset = 'utf-8'; //utf-8 is the default encoding for json +``` + +Or directly in your HTML markup: +```html + +``` + +### Considerations +TBA + \ No newline at end of file diff --git a/skf/markdown/code_examples/nodejs-express/20-code_example--Open_Forwards_and_redirects--.md b/skf/markdown/code_examples/nodejs-express/20-code_example--Open_Forwards_and_redirects--.md new file mode 100755 index 000000000..4ea976818 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/20-code_example--Open_Forwards_and_redirects--.md @@ -0,0 +1,35 @@ +# Open Forwards and Redirects + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +When using forwards and redirects you should make sure the URL is being explicitly declared in the code and cannot be manipulated by an attacker like in the case of `redirectTo` being dynamically set based on user input: +```js +app.get('/offers', (req, res, next) => { + const redirectTo = req.query.redirect; + res.redirect(redirectTo); +}); +``` + +Generally you should avoid getting parameters which could contain user input into the redirect by any means. If for any reason this is not feasible, then you should make a whitelist input validation for the redirect as shown below: +```js +const validRedirectURLs = [...]; // list of URLs permitted for redirection + +app.get('/offers', (req, res, next) => { + const redirectTo = req.query.redirect; + + if(validRedirectURLs.includes(redirectTo)) { + res.redirect(redirectTo); + } else { + return res.status(500).send({ error: 'Invalid redirection URL' }); + } +}); +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/21-code_example--Password_forget_and_disallow_old_passwords--.md b/skf/markdown/code_examples/nodejs-express/21-code_example--Password_forget_and_disallow_old_passwords--.md new file mode 100755 index 000000000..0386b9c58 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/21-code_example--Password_forget_and_disallow_old_passwords--.md @@ -0,0 +1,115 @@ +# Password forget and disallow of old passwords + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +Whenever you are developing a password forget function, these are the steps to follow in order to create hardened defenses. + +``` +TABLE users +| userID | userName | password | emailAddress | access | +| --- | --- | --- | --- | --- | +| 1 | Admin | securely hashed password | info@admin.com | TRUE | +| 2 | User | securely hashed password | info@user.com | FALSE | +| 3 | Guest | securely hashed password | info@guest.com | FALSE | + + +`TABLE passwordForget` +| forgotPasswordID | token | userID | active | oldPasswordHashes | +| --- | --- | --- | --- | --- | +| 1 | c3ab8ff13720e.... | 1 | Yes | <......> | +| 2 | 7dd39466b3c89.... | 1 | No | <......> | +| 3 | 83d4a3960714c.... | 3 | No | <......> | +``` + +As you can see we also store the old passwords into the password forget table. This is done in order to prevent the user from using old passwords later on in the process. + +Also use a CRON job to make sure that the generated tokens for the password reset expire after a certain amount of time like 20 minutes. + +THIS CODE NEEDS TO BE CLEANED UP + +```js +app.post('/forgot', function(req, res, next) { + async.waterfall([ + function(done) { + crypto.randomBytes(20, function(err, buf) { + var token = buf.toString('hex'); + done(err, token); + }); + }, + function(token, done) { + User.findOne({ email: req.body.email }, + function(err, user) { // get user by email + if (!user) { + req.flash('Success', 'You should receive an email with your password reset link shortly'); + return res.redirect('/forgot'); + } + user.resetPasswordToken = token; + user.resetPasswordExpires = Date.now() + PASSWORD_EXPIRY_TOKEN_DURATION; // 1 hour + user.save(function(err) { + done(err, token, user); + }); + }); + }, + function(token, user, done) { + send_reset_password_email() + } + ]) + }, + function(err) { + if (err) return next(err); + res.redirect('/forgot'); + }); + + app.get('/reset/:token', function(req, res) { + User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) { + if (!user) { + req.flash('error', 'Password reset token is invalid or has expired.'); + return res.redirect('/forgot'); + } + res.render('reset', { + user: req.user + }); + }); + }); + + app.post('/reset/:token', function(req, res) { + async.waterfall([ + function(done) { + User.findOne({ + resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() }, + function(err, user) { + if (!user) { + req.flash('error', 'Password reset token is invalid or has expired.'); + return res.redirect('back'); + } + if (req.body.password ) { + hash = password_hash(req.body.password) + user.resetPasswordToken = undefined; + user.resetPasswordExpires = undefined; + user.save(function(err) { + req.logIn(user, function(err) { + done(err, user); + }); + }); + }); + } + }); + } + function(user, done) { + send_pass_change_confirmation_email() + } + ]), + function(err) { + res.redirect('/'); + }) + }); +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/23-code_example--Sandboxing--.md b/skf/markdown/code_examples/nodejs-express/23-code_example--Sandboxing--.md new file mode 100755 index 000000000..e91d8344c --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/23-code_example--Sandboxing--.md @@ -0,0 +1,27 @@ +# Iframe Sandboxing + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +Sandboxing applies a set of restrictions to the iframes in order to tighten security. It can be declared as follows: +```html + +``` + +Desired restriction can be lifted as shown below: +```html + +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/24-code_example--Secure_Session_Cookies--.md b/skf/markdown/code_examples/nodejs-express/24-code_example--Secure_Session_Cookies--.md new file mode 100755 index 000000000..4c6e7bbe6 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/24-code_example--Secure_Session_Cookies--.md @@ -0,0 +1,31 @@ +# Secure flag for session cookies + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +In [`express-session` module](https://www.npmjs.com/package/express-session) it is possible to supply `secure` flag as a part of `cookie` configuration. + +This flag instructs the browser to never send cookies over an `HTTP` request. The cookie will only be sent over `HTTPS` even if the user manually types in a request for `HTTP`. The HTTP request itself will be sent, but the browser will not send any cookies. + +## Example +A session with secured cookies can be created in the following way: +```js +app.use(session({ + name: 'session', + keys: ['key1', 'key2'], + cokkie: { + secure: true + httpOnly: true, + domain: 'complete.subdomain.example.com', + path: 'foo/bar', + expires: expiryDate + } +})); +``` + +## Considerations +The `HTTP` request is still sent and thus could be manipulated by a man in the middle to perform convincing phishing attacks (please see Strict Transport Security for a recommended solution). + +Setting the `domain` attribute to a too permissive value, such as `example.com`, allows an attacker to launch attacks on the session IDs between different hosts and web applications belonging to the same domain, known as cross-subdomain cookies. For example, vulnerabilities in `example.com` might allow an attacker to get access to the session IDs from `secure.example.com`. diff --git a/skf/markdown/code_examples/nodejs-express/26-code_example--RFD_and_file_download_injections--.md b/skf/markdown/code_examples/nodejs-express/26-code_example--RFD_and_file_download_injections--.md new file mode 100755 index 000000000..359e24ffb --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/26-code_example--RFD_and_file_download_injections--.md @@ -0,0 +1,14 @@ +# RFD and file download injections + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +You should **not** accept random filenames for downloading from users and use path sanitizations as shown earlier with RBAC to make sure that the user owns the file being downloaded. + +## Example: +TBA + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/29-code_example--Randomizer_function--.md b/skf/markdown/code_examples/nodejs-express/29-code_example--Randomizer_function--.md new file mode 100755 index 000000000..4c687c6c0 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/29-code_example--Randomizer_function--.md @@ -0,0 +1,32 @@ +# Randomizer function + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +Please see [documentation for randomizer function](https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback). + +## Example +Asynchronous: +```js +const crypto = require('crypto'); + +crypto.randomBytes(256, (err, buf) => { + //buf holds your bytes + if (err) throw err; + + console.log(`${buf.length} bytes of random data: ${buf.toString('hex')}`); +}); +``` + +Synchronous: +```js +const crypto = require('crypto'); +const buf = crypto.randomBytes(256); + +console.log(`${buf.length} bytes of random data: ${buf.toString('hex')}`); +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/3-code_example--XSS_Filtering--.md b/skf/markdown/code_examples/nodejs-express/3-code_example--XSS_Filtering--.md new file mode 100755 index 000000000..dccb292e9 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/3-code_example--XSS_Filtering--.md @@ -0,0 +1,54 @@ +# XSS filtering + +- [General](#general) +- [Example](#example) + - [Dangerous methods in frameworks](#dangerous-methods-in-frameworks) +- [Considerations](#considerations) + +## General +TBA + +## Example: +If you're creating server - side pages you can use [`dompurify`](https://www.npmjs.com/package/dompurify) to sanitize strings as shown below: +```js +const dompurify = require('dompurify'); +... +const clean = DOMPurify.sanitize(dirty); +``` +Then you can use sanitized string as normal. + +### Dangerous methods in frameworks +If you're using a template engine or a framework, then **AVOID** using the following methods: + +#### {{ mustache }} and Handlebars +```hbs +{{{ raw html }}} +``` + +#### EJS +``` +<%- raw html %> +``` + +#### Nunjucks +``` +{% raw html %} +``` + +#### Angular +``` +
+``` + +#### React +``` +
+``` + +#### Vue.js +``` +
+``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/30-code_example--session_hijacking_and_fixation--.md b/skf/markdown/code_examples/nodejs-express/30-code_example--session_hijacking_and_fixation--.md new file mode 100755 index 000000000..2b8ab9cc0 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/30-code_example--session_hijacking_and_fixation--.md @@ -0,0 +1,50 @@ +# Session hijacking and fixation + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +First you need to implement the strict transport security header. This is done in order to prevent users from accessing your application over an unprotected connection. + +Strict transport security header can be set as shown below: +```js +response.setHeader('Strict-Transport-Security', 'max-age=31536000'); +``` + +If all present and future subdomains will be `HTTPS`: +```js +response.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubdomains;'); +``` + +We recoomend to have your domain included in the `HSTS` preload list maintained by Chrome (and used by Firefox and Safari). To achieve this you need to do the following: +```js +response.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubdomains; preload'); +``` + +The `preload` flag indicates the site owner's consent to have their domain preloaded. The preload list +enforces the browser to always present your application on HTTPS even on the first time the user hits your application. + +Then you should set the `httpOnly` flag (please see "HttpOnly" in the code examples for more details about implementation). + +Then set the flag for session timeout (please see "Timeout" in the code examples for more details about implementation). + +Then set the session `secure` flag (see "Secure flag" in the code examples for more details about implementation). + +On login we also need to add another cookie with a random value to the application in order to prevent an attacker to fixate an `JSSESSION` id on your users and hijack their sessions (This code example can be found in the "Login functionality" for more detailed information). + +Now imagine the scenario after the login of the user (see the "Login functionality" in the code examples for more details). Whenever the user is logged in, the users IP address, user agent string and session id are also stored in the database these values are used in order to verify if there are multiple users active on the same session. + +If so, we can let the user decide to terminate the session and terminate theother assigned sessions. +```js +const login = (**args**) => { + /* Passport prevents session fixation but doesn't track concurrent long lived sessions, this is custom code on top of passport + */ +} +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/32-code_example--Timeout_Sessions--.md b/skf/markdown/code_examples/nodejs-express/32-code_example--Timeout_Sessions--.md new file mode 100755 index 000000000..be39f17ac --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/32-code_example--Timeout_Sessions--.md @@ -0,0 +1,21 @@ +# Session timeout + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example: +``` +app.use(express.session({ + secret : 'your_cookie_secret', + cookie:{_expires : (10 * 60 * 1000)}, // time im ms, this is 10 minutes + }) + ); + +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/33-code_example--User_Registration_SQL_truncation_prevention--.md b/skf/markdown/code_examples/nodejs-express/33-code_example--User_Registration_SQL_truncation_prevention--.md new file mode 100755 index 000000000..dbf7c1b2e --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/33-code_example--User_Registration_SQL_truncation_prevention--.md @@ -0,0 +1,132 @@ +# User registration SQL truncation prevention + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +In order to prevent Column truncation SQL injection we have to make sure that the applications structural logic does not mismatches with the database structural logic. + +To achieve this imagine the follow example of a user model: + +``` +// models/user.js +// load the things we need +var database = require('my_favorite_database'); + + +// define the schema for our user model +var userSchema = database.Schema({ + + local : { + email : String, + password : String, + firstname : String, + lastname : String, + } +}); +``` + +Then we need to define some methods to hash passwords and check password validity + +``` +var bcrypt = require('bcrypt-nodejs'); +// to hash +userSchema.methods.generateHash = function(password) { + return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); +}; + +// to validate +userSchema.methods.validPassword = function(password) { + return bcrypt.compareSync(password, this.local.password); +}; + +// create the model for users and expose it to our app +module.exports = database.model('User', userSchema); + +``` +Then you can config the [Passport](http://www.passportjs.org/) middleware to handle your local authentication strategy + +``` +// authentication.js + +// load all the things we need +var LocalStrategy = require('passport-local').Strategy; + +// load up the user model +var User = require('models/user'); + +// expose this function to our app using module.exports +module.exports = function(passport) { + + // passport needs ability to serialize and unserialize users out of session + + // used to serialize the user for the session + passport.serializeUser(function(user, done) { + done(null, user.id); + }); + + // used to deserialize the user + passport.deserializeUser(function(id, done) { + User.findById(id, function(err, user) { + done(err, user); + }); + }); + + passport.use('local-signup', new LocalStrategy({ + usernameField : 'email', + passwordField : 'password', + passReqToCallback : true + }, + function(req, email, password, firstname, lastname done) { + process.nextTick(function() { + User.findOne({ 'local.email' : email }, function(err, user) { + if (err) + return done(err); + + if (user) { + return done(null, false, req.flash('signupMessage', 'User exists')); + } else { + var user = new User(); + user.local.email = email; + user.local.password = user.generateHash(password); + user.local.firstname = firstname; + user.local.lastname = lastname + + // save the user + newUser.save(function(err) { + if (err) + throw err; + return done(null, user); + }); + } + + }); + + }); + + })); + +}; + +``` + +Then in the app routes + +``` + // signup form processing + app.post('/register', passport.authenticate('local-signup', { + successRedirect : '/profile', // redirect to the secure profile section + failureRedirect : '/register', // redirect back to the signup page if there is an error + failureFlash : true // allow flash messages + })); + + +``` + + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/36-code_example--Anti_clickjacking--.md b/skf/markdown/code_examples/nodejs-express/36-code_example--Anti_clickjacking--.md new file mode 100755 index 000000000..705a9b0e8 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/36-code_example--Anti_clickjacking--.md @@ -0,0 +1,67 @@ +# Anti-clickjacking + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +One way to defend against clickjacking is to include a `frame-breaker` script in each page that should not be framed. The following methodology will prevent a webpage from being framed even in legacy browsers, that do not support the `X-Frame-Options-Header`. + +In the document `HEAD` element please add the following code: +1. Apply an ID to the style element itself: +```html + +``` +2. And then delete that style by its ID immediately after in the script: +```html + +``` + +The second option is to use security headers. There are two options for setting the `anti-clickjacking` headers in your application:. + +This will completely prevent your page from being displayed in an iframe: +```js +response.addHeader('X-Frame-Options', 'deny'); +``` + +This will completely prevent your page from being displayed in an iframe on other sites: +```js +response.addHeader('X-Frame-Options', 'SAMEORIGIN'); +``` + +Alternatively you can use [`helmet` module]( https://www.npmjs.com/package/helmet) which sets `X-FRAME` headers along with a host of other security headers. + +If you only want `X-FRAME-OPTIONS` please use [`frameguard`](https://github.com/helmetjs/frameguard). +```js +const frameguard = require('frameguard'); + +// Don't allow me to be in ANY frames: +app.use(frameguard({ action: 'deny' })); + +// Only let me be framed by people of the same origin: +app.use(frameguard({ action: 'sameorigin' })); +app.use(frameguard()); // defaults to sameorigin + +// Allow from a specific host: +app.use(frameguard({ + action: 'allow-from', + domain: 'http://example.com' +})); +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/37-code_example--X_Content_Type_Options_header--.md b/skf/markdown/code_examples/nodejs-express/37-code_example--X_Content_Type_Options_header--.md new file mode 100755 index 000000000..38c55c4f7 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/37-code_example--X_Content_Type_Options_header--.md @@ -0,0 +1,26 @@ +# `X-Content-Type-Options` header + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +In order to set the `X-Content-Type-Options` header you'll have to add the following code to the head of your application: +```js +res.set('X-Content-Type-Options', 'nosniff'); +``` + +Alternatively you can use [`dont-sniff-mimetype` module](https://www.npmjs.com/package/dont-sniff-mimetype): +```js +const nosniff = require('dont-sniff-mimetype'); + +app.use(nosniff()); +``` + +The same can be achieved by using [`helmet` module]( https://www.npmjs.com/package/helmet) which sets X-FRAME headers along with a host of other security headers. + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/42-code_example--Hashing--.md b/skf/markdown/code_examples/nodejs-express/42-code_example--Hashing--.md new file mode 100755 index 000000000..644ac1d30 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/42-code_example--Hashing--.md @@ -0,0 +1,48 @@ +# Hashing + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +For this you can use [`bcrypt` module](https://www.npmjs.com/package/bcrypt): +```js +const bcrypt = require('bcrypt') + +const hash = password => { + const saltRounds = 10; + return bcrypt.hash(myPlaintextPassword, saltRounds); +} +``` + +Asynchronous method: +```js +const validatePassword = (user, password) => { + bcrypt.compare(myPlaintextPassword, hash, (err, res) => { + if(res) { + // Passwords match, handle success + } else { + // Passwords don't match, handle failure + } + }); +}; +``` + +Synchronous method: +```js +const validatePassword = (user, password) => { + if(bcrypt.compareSync('somePassword', hash)) { + // Passwords match, handle success + return true; + } else { + // Passwords don't match, handle failure + return false; + } +} +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/5-code_exmaple--Anti_caching_headers--.md b/skf/markdown/code_examples/nodejs-express/5-code_exmaple--Anti_caching_headers--.md new file mode 100755 index 000000000..6f564b2e0 --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/5-code_exmaple--Anti_caching_headers--.md @@ -0,0 +1,29 @@ +# Anti-cashing headers + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +Add the following headers to your response head in order to prevent the browser from caching: + +### HTTP 1.1 +```js +resp.set('Cache-Control', 'no-cache, no-store, must-revalidate'); +``` + +### HTTP 1.0 +```js +res.set('Pragma', 'no-cache'); +``` + +### Proxies +```js +res.set('Expires', '0'); +``` + +## Considerations +TBA diff --git a/skf/markdown/code_examples/nodejs-express/8-code_example--Directory_path_traversal_attack--.md b/skf/markdown/code_examples/nodejs-express/8-code_example--Directory_path_traversal_attack--.md new file mode 100755 index 000000000..f2aeb11fa --- /dev/null +++ b/skf/markdown/code_examples/nodejs-express/8-code_example--Directory_path_traversal_attack--.md @@ -0,0 +1,38 @@ +# Directory Path traversal + +- [General](#general) +- [Example](#example) +- [Considerations](#considerations) + +## General +TBA + +## Example +First, we want to filter the filenames for expected values. For this example the filenames should consist of only alphanumeric characters, which we validate with the following regex - `/^[a-zA-Z0-9]+$/`. +```js +const isValidPath = path => { + const filenamesRegex = /^[a-zA-Z0-9]+$/; + return filenamesRegex.test(path); +}; +``` + +Then we whitelist the path to only the allowed locations using the [path](https://nodejs.org/api/path.html) library. `dirWhitelist` is an array of directory pathnames (such as `/foo/bar/baz`) the application is allowed to load resources from: +```js +const isAllowedLocation = path => + dirWhitelist.includes(path.dirname(path)); +``` + +Together the methods shown above can be used as follows: +```js +app.get('/read-file', (req, res) => { + const filePath = req.query.filename; + if(isValidPath(filePath) && isAllowedLocation(filePath)) { + // serve request + } else { + // return error + } +}); +``` + +## Considerations +TBA