Skip to content

Commit 81e101f

Browse files
northdpoleblabla1337
authored andcommitted
code examples using Javascript (#507)
* Added nodejs/expressjs examples Signed-off-by: northdpole <[email protected]> * 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 parent 92f0ba9 commit 81e101f

23 files changed

+1023
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# CSRF Tokens
2+
3+
- [General](#general)
4+
- [Example](#example)
5+
- [Considerations](#considerations)
6+
7+
## General
8+
9+
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.
10+
11+
If you support plain HTML form submissions, read on.
12+
13+
**Hint:** you can check if you support plain HTML form submissions by searching for:
14+
15+
```js
16+
const bodyParser = require('body-parser');
17+
bodyParser.urlencoded();
18+
```
19+
20+
## Example
21+
22+
The following handlebar template snippet shows the code used to place the antiCSRF token inside a html page.
23+
24+
When the page renders, the `<cu:antiCSRF/>` 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.
25+
26+
When the user presses the commandButton then CSRF token parameter is compared with the CSRF session parameter.
27+
28+
```hbs
29+
<form action="/process" method="POST">
30+
<input type="hidden" name="_csrf" value="{{csrfToken}}">
31+
...
32+
<button type="submit">Submit</button>
33+
</form>
34+
```
35+
36+
The following snippet is used to generate and check the token:
37+
38+
```js
39+
const csrf = require('csurf'); //csrf module
40+
const csrfProtection = csrf({ cookie: true }); // setup route middlewares
41+
42+
// This is required because "cookie" is true in csrfProtection
43+
app.use(cookieParser());
44+
45+
// Error handler(Optional) shows custom error message when token is missing or mismatches
46+
app.use((err, req, res, next) => {
47+
// on token validation fail, error is thrown with code 'CSRFERROR'
48+
if (err.code !== 'CSRFERROR') return next(err);
49+
res.status(403);
50+
res.send('csrf error');
51+
});
52+
53+
// We need to pass the middleware to each route
54+
app.get('/form', csrfProtection, (req, res) => {
55+
// generate and pass the csrfToken to the view
56+
res.render('send', { csrfToken: req.csrfToken() });
57+
});
58+
59+
// and check it when the request is being processed
60+
app.post('/process', parseForm, csrfProtection, (req, res) => {
61+
res.send('data is being processed');
62+
});
63+
```
64+
65+
## Considerations
66+
`csurf` doesn't protect by default requests such as `GET`, `OPTIONS`, `HEAD`.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Encoder (SQL - Parameterized Inputs)
2+
3+
- [General](#general)
4+
- [Example](#example)
5+
- [Considerations](#considerations)
6+
7+
## General
8+
TBA
9+
10+
## Example
11+
Execute prepared statement with parameterized user inputs using [`mysql` module](https://www.npmjs.com/package/mysql):
12+
```js
13+
const sqlQuery = 'SELECT * FROM accounts WHERE username=? AND password=?';
14+
15+
connection.query(sqlQuery, [username, passwordHash], (err, rows, fields) => {
16+
// handle both success and failure for query result
17+
});
18+
19+
connection.end();
20+
```
21+
22+
## Considerations
23+
TBA
24+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# File Uploading
2+
3+
- [General](#general)
4+
- [Example](#example)
5+
- [Considerations](#considerations)
6+
7+
## General
8+
TBA
9+
10+
## Example
11+
TBA
12+
13+
## Considerations
14+
TBA
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# `httpOnly` flag
2+
3+
- [General](#general)
4+
- [Example](#example)
5+
- [Considerations](#considerations)
6+
7+
## General
8+
`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.
9+
10+
## Example
11+
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:
12+
```js
13+
const session = require('express-session');
14+
15+
app.use(session({
16+
secret: 'some random and long value',
17+
key: 'sessionId',
18+
cookie: {
19+
httpOnly: true,
20+
secure: true
21+
}
22+
}));
23+
```
24+
25+
## Considerations
26+
TBA
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# Identifier-based authorization
2+
3+
- [General](#general)
4+
- [Example](#example)
5+
- [Considerations](#considerations)
6+
7+
## General
8+
Database expected is MS SQL server making use of [mssql](https://www.npmjs.com/package/mssql).
9+
`file_access` is formatted as so:
10+
| user_id | file_id |
11+
|---------|---------|
12+
| 1 | 2 |
13+
Where both ids are foreign keys and the primary key is made of a composite of the two.
14+
15+
## Example
16+
```js
17+
const express = require('express');
18+
const session = require('express-session')
19+
const FileStore = require('session-file-store')(session);
20+
const bodyParser = require('body-parser');
21+
const passport = require('passport');
22+
const LocalStrategy = require('passport-local').Strategy;
23+
const sql = require('mssql')
24+
25+
//Made up external files
26+
const validator = require('./validator'); //Handles validating logins
27+
const files = require('./files'); //Gets files from DB or store
28+
29+
30+
31+
// configure passport.js to use the local strategy
32+
passport.use(new LocalStrategy(
33+
{ usernameField: 'email' }, (email, password, done) => {
34+
const user = validator.login(email, password); //Validator returns false if invalid
35+
return done(null, user)
36+
}
37+
));
38+
39+
// tell passport how to serialize the user
40+
passport.serializeUser((user, done) => {
41+
done(null, user.id);
42+
});
43+
44+
passport.deserializeUser((id, done) => {
45+
const user = users.getUserById(id);
46+
done(null, user);
47+
});
48+
49+
// create the server
50+
const app = express();
51+
52+
// add & configure middleware
53+
app.use(bodyParser.urlencoded({ extended: false }))
54+
app.use(bodyParser.json())
55+
app.use(session({
56+
store: new FileStore(),
57+
secret: process.env.sessionKey, //always use environment variables to pass in keys.
58+
resave: false,
59+
saveUninitialized: true
60+
}))
61+
app.use(passport.initialize());
62+
app.use(passport.session());
63+
64+
//Login excluded
65+
66+
app.get('/post', passport.authenticate('local', { failureRedirect: '/login' }), //authenticates the user using session
67+
async function(req, res) {
68+
const data = req.body;
69+
let pool = await sql.connect(config)
70+
let result = await pool.request()
71+
.input('user_id', sql.Int, req.user.id) //sql.Int validates that only a integer value can be in the variable
72+
.input('file_id', sql.Int, data.id)
73+
.query('select * from file_access where user_id = @user_id and file_id = @file_id'); //variables inlined into sql query
74+
75+
if(result.recordsets.length === 1) { //If the result exists the user has access
76+
res.send(files.getFile(data.id)) //sends file
77+
} else {
78+
res.redirect('/invalidFile'); //redirects to a generic invalid file
79+
}
80+
});
81+
```
82+
83+
## Considerations
84+
TBA
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Login Functionality
2+
3+
- [General](#general)
4+
- [Example](#example)
5+
- [Considerations](#considerations)
6+
7+
## General
8+
TBA
9+
10+
## Example
11+
Using the [Passport middleware](http://www.passportjs.org/)
12+
13+
The following example assumes username/password authentication.
14+
15+
First, configure the middleware:
16+
```
17+
var auth_manager = require('passport')
18+
, LocalStrategy = require('passport-local').Strategy;
19+
20+
auth_manager.use(new LocalStrategy(
21+
function(username, password, done) {
22+
User.findOne({ username: username }, function(err, user) {
23+
if (err) { return done(err); }
24+
if (!user) {
25+
return done(null, false, { message: 'Incorrect username.' });
26+
}
27+
if (!user.validPassword(password)) {
28+
return done(null, false, { message: 'Incorrect password.' });
29+
}
30+
return done(null, user);
31+
});
32+
}
33+
));
34+
```
35+
36+
Then, register the route handling authentication can be:
37+
```
38+
app.post('/login',
39+
auth_manager.authenticate('local', { successRedirect: '/',
40+
failureRedirect: '/login'
41+
})
42+
);
43+
```
44+
45+
## Considerations
46+
TBA
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Logout Functionality
2+
3+
- [General](#general)
4+
- [Example](#example)
5+
- [Considerations](#considerations)
6+
7+
## General
8+
9+
## Example
10+
Using [Passport](http://www.passportjs.org/docs/logout/) as a middleware call logOut() or logout() on your req object.
11+
```
12+
app.get('/logout', function(req, res){
13+
req.logout();
14+
res.redirect('/');
15+
});
16+
17+
```
18+
19+
20+
## Considerations
21+
TBA
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Charsets
2+
3+
- [General](#general)
4+
- [Example](#example)
5+
- [Considerations](#considerations)
6+
7+
## General
8+
TBA
9+
10+
## Example
11+
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:
12+
```js
13+
res.charset = 'utf-8'; //utf-8 is the default encoding for json
14+
```
15+
16+
Or directly in your HTML markup:
17+
```html
18+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
19+
```
20+
21+
### Considerations
22+
TBA
23+
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Open Forwards and Redirects
2+
3+
- [General](#general)
4+
- [Example](#example)
5+
- [Considerations](#considerations)
6+
7+
## General
8+
TBA
9+
10+
## Example
11+
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:
12+
```js
13+
app.get('/offers', (req, res, next) => {
14+
const redirectTo = req.query.redirect;
15+
res.redirect(redirectTo);
16+
});
17+
```
18+
19+
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:
20+
```js
21+
const validRedirectURLs = [...]; // list of URLs permitted for redirection
22+
23+
app.get('/offers', (req, res, next) => {
24+
const redirectTo = req.query.redirect;
25+
26+
if(validRedirectURLs.includes(redirectTo)) {
27+
res.redirect(redirectTo);
28+
} else {
29+
return res.status(500).send({ error: 'Invalid redirection URL' });
30+
}
31+
});
32+
```
33+
34+
## Considerations
35+
TBA

0 commit comments

Comments
 (0)