I'm trying to add content security policy to my app. So I'm using helmet csp. But when I add it and check it in browser / terminal, I see that content security policy is not getting set. Not able to figure out why?
I have a prod.js module like this
const helmet = require('helmet');
const compression = require('compression');
const crypto = require("crypto");
module.exports = function (app) {
app.use(helmet())
app.use((req, res, next) => {
res.locals.cspNonce = crypto.randomBytes(16).toString("hex");
next();
});
app.use((req, res, next) => {
csp({
useDefaults: true,
directives: {
scriptSrc: [ "'self', js.stripe.com', 'https://checkout.stripe.com',
'js.stripe.com', 'https://billing.stripe.com'"],
'https://www.googletagmanager.com',
'*.googletagmanager.com',( request, response ) => `'nonce-${res.locals.cspNonce}'` ],
styleSrc: ["'unsafe-inline'"],
connectSrc:[" * 'self' https://checkout.stripe.com https://billing.stripe.com"],
frameSrc: [" 'self https://checkout.stripe.com https://billing.stripe.com https://js.stripe.com "],
imgSrc: [" 'self' blob: https://api.wcompany.com/ data:"],
},
})(req, res, next);
});
app.use(compression());
};
and then I have index.js file like this :
const winston = require('winston');
const express = require('express');
const https = require('https');
const path = require('path');
const fs = require('fs');
const app = express();
require('./startup/routes')(app);
require('./startup/db')();
require('./startup/config')();
require('./startup/validation')();
require('./startup/prod')(app);
const port = process.env.PORT || 3000;
But I can't see content policy header in the browser. Should I explicitly add res.headers to see CSP in browser.
Also on terminal I checked curl http://localhost:3000 --include
Access-Control-Allow-Origin: *
Content-Security-Policy: default-src 'none'
X-DNS-Prefetch-Control: off
Expect-CT: max-age=0
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: no-referrer
X-XSS-Protection: 0
Content-Type: text/html; charset=utf-8
Content-Length: 139
Vary: Accept-Encoding
Date: Fri, 25 Feb 2022 15:57:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>
P.S: I'm quite new to programming. So appreciate any help.
You are not loading helmet into your express application. You need to add something like this:
app.use(helmet())
Related
I'm currently following along https://youtu.be/Ud5xKCYQTjM but in TypeScript, my GET request works
GET http://localhost:48080/users
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-l9Fw4VUO7kr8CvBlt4zaMCqXZ0w"
Date: Thu, 28 Jul 2022 13:14:52 GMT
Connection: close
[]
while my POST request fails
POST http://localhost:48080/users Content-Type: application/json
{
"name": "Kyle", "password": "password"
}
HTTP/1.1 404 Not Found
X-Powered-By: Express
Vary: Origin
Access-Control-Allow-Credentials: true
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 180
Date: Thu, 28 Jul 2022 13:15:09 GMT
Connection: close
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /users%20Content-Type:%20application/json</pre>
</body>
</html>
I am unsure why it returns a 404 not found when the route exists? I've tried removing 'async' in my server.ts file in regards to the post request, as at the start of the guide, he did not use 'async' but it did not work. Could CORS be an issue? but I'm sending requests within the same origin.
server.ts
import express from "express";
import cors from "cors";
import { Request } from "express-serve-static-core";
import { ParsedQs } from "qs";
// const bcrypt = require('bcrypt')
/* Set up server app */
const app = express();
// const port: number = +(process.env.PORT || 48080);
const port: number = 48080;
const users: { name: string; password: string; }[] = []
app.get('/users', (req, res) => {
res.json(users)
})
app.post('/users', async (req, res) => {
try {
const user = { name: req.body.name, password: req.body.password }
users.push(user)
res.status(201).send()
} catch {
res.status(500).send()
}
})
app.post('/users/login', async (req, res) => {
const user = users.find(user => user.name === req.body.name)
if (user == null) {
return res.status(400).send('Cannot find user')
}
try {
if(userAuthenticate(req, user)) {
res.send('Success')
} else {
res.send('Not Allowed')
}
} catch {
res.status(500).send()
}
})
/* Listen for incoming connections */
app.listen(port, () => {
console.log(`API listening on port ${port}.`);
});
/* Enable CORS */
app.use(cors({
origin: ["http://localhost:3000", "http://localhost:48080"],
credentials: true,
optionsSuccessStatus: 200
}));
function userAuthenticate(req: Request<{}, any, any, ParsedQs, Record<string, any>>, user: { name: string; password: string; }) {
return req.body.password == user.password;
}
Look at the error message:
Cannot POST /users%20Content-Type:%20application/json
Now look at your command line:
POST http://localhost:48080/users Content-Type: application/json
The tool you are using to make the HTTP request is treating Content-Type: application/json as part of the path and not as a request header.
You need to read the documentation for your POST command line tool and figure out how to make it an HTTP header.
I'm using the node.js module CSURF, which is configured to use cookies via cookie-parser.
For demo purposes, I'm just echoing the ANTI-CSRF token to the screen on a /form GET request. Here's the request and response via VS Code Rest Client plugin:
GET http://localhost:9000/form HTTP/1.1
User-Agent: vscode-restclient
accept-encoding: gzip, deflate
cookie: sid=s%3AYdAxaIHCvv38D6vd3VOi085SOzqkuZpN.eloHBwtgNm4yXQia3FtgR6puNj48kNZVbxlWtBZhSk0; _csrf=xdfFevA7j1qcGRo5BvB7JDQ2
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Security-Policy: default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
X-DNS-Prefetch-Control: off
Expect-CT: max-age=0
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: no-referrer
X-XSS-Protection: 0
Content-Type: application/json; charset=utf-8
Content-Length: 52
ETag: W/"34-4PDt3TpquKFR5AlQtYw1wqZJRD4"
Date: Wed, 03 Nov 2021 02:47:01 GMT
Connection: close
{
"csrfToken": "HhEOYbdx-lhbaEmFT_Udx-CyyZFvuXG2u3lI"
}
You can see the _csrf value in the cookie -- xdfFevA7j1qcGRo5BvB7JDQ2
Interestingly, this doesn't match the token output to screen -- HhEOYbdx-lhbaEmFT_Udx-CyyZFvuXG2u3lI
So I presume it's a cryptographic match, or a salt was added to the _csrf value to generate unique ANTI-CSRF tokens every time.
...which is fine, b/c CSURF works when I issue a POST request using HhEOYbdx-lhbaEmFT_Udx-CyyZFvuXG2u3lI.
The question/confusion comes into play when I issue a new GET request to the /form endpoint. The _csrf value (xdfFevA7j1qcGRo5BvB7JDQ2) doesn't change, only the ANTI-CSRF token that was output to the screen.
So it appears the ANTI-CSRF token changes on every request, but the cookie value doesn't. Is this correct behavior? It doesn't seem like it b/c I'd be able to always use any ANTI-CSRF token to bypass the check.
Here's the full code from CSURF URL https://www.npmjs.com/package/csurf:
var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
// setup route middlewares
var csrfProtection = csrf({ cookie: true })
var parseForm = bodyParser.urlencoded({ extended: false })
// create express app
var app = express()
// parse cookies
// we need this because "cookie" is true in csrfProtection
app.use(cookieParser())
app.get('/form', csrfProtection, function (req, res) {
// changed original code to display token to screen instead of render it within a form; this is for dev purposes only
res.json({ csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function (req, res) {
res.send('data is being processed')
})
Per OWASP (see this URL: https://security.stackexchange.com/questions/209993/csrf-token-unique-per-user-session-why), ANTI-CSRF token pairs should be changed on new sessions. So once I logged out (deleted my cookie), a new ANTI-CSRF token pair was created.
I wonder if I could change this time every time.
As i stated in the title, I tried setting headers in node.js, but some of them just would not stick/would get overwritten.
Here is content of my server.js file:
const next = require("next");
const http = require("http");
const url = require("url");
const path = require("path");
const port = process.env.PORT || 3000;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
http
.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("X-Frame-Options", "DENY");
res.setHeader("X-XSS-Protection", "1; mode=block");
res.setHeader(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload"
);
res.setHeader("Cache-Control", "public, max-age=31557600");
handle(req, res, parsedUrl);
})
.listen(port, () => {
console.log(`listening on PORT ${port}`);
});
});
Every header except Cache-control and x-powered-by (its not in code but i tried) is getting set.
Weird thing is that when i log response, my headers are loged out:
'x-content-type-options': [ 'X-Content-Type-Options', 'nosniff' ],
'x-frame-options': [ 'X-Frame-Options', 'DENY' ],
'x-xss-protection': [ 'X-XSS-Protection', '1; mode=block' ],
'strict-transport-security': [
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
],
'cache-control': [ 'Cache-Control', 'public, max-age=31557600' ]
There are the headers that i have in my browser:
HTTP/1.1 200 OK
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Cache-Control: no-store, must-revalidate
X-Powered-By: Next.js
ETag: "1373f-5S13UfVtDhxl5s8GCDKvO1iq/oY"
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Content-Encoding: gzip
Date: Thu, 06 Feb 2020 17:13:04 GMT
Connection: keep-alive
Transfer-Encoding: chunked
Any idea whats happening here? Some default setting overwriting my custom?
What you are experiencing is specific to Next.js framework.
Regarding 'X-Powered-By', this is expected, as per the Next.js docs
By default Next.js will add x-powered-by to the request headers. To opt-out of it, open next.config.js and disable the poweredByHeader config:
Regarding 'Cache-Control', it looks like it is overwritten by Next.js in development mode as you can see in the source code
sendHTML(req: IncomingMessage, res: ServerResponse, html: string) {
// In dev, we should not cache pages for any reason.
res.setHeader('Cache-Control', 'no-store, must-revalidate')
return super.sendHTML(req, res, html)
}
I have a AngularJS and NodeJS (API) application.
I had already enabled CORS on my NodeJS using CORS() nodejs library.
I included the required headers to enable CORS too.
I am having CORS issue only when I access the website from my company computer. It Works fine from my personal computers. Can anyone please guide me on what I am doing wrong. Any help or suggestion please.
Chrome Console Logs:
-------------------- Headers ---------------------
General:
Request URL:www.example.com:81/api/getdata
Request Method:GET
Status Code:503 Service Unavailable
Remote Address:111.111.11.111:81
Response Headers:
Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection:close
Content-Length:973
Content-Type:text/html; charset=UTF-8
Expires:Thu, 01 Jan 1970 00:00:00 GMT
P3P:CP="CAO PSA OUR"
Pragma:no-cache
Request Headers:
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Host:www.example.com:81
Origin:http://www.example.com
Referer:http://www.example.com/
User-Agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
-------------------- Response: --------------------
<html>
<head>
<title>Web Page Blocked</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
<meta name="viewport" content="initial-scale=1.0">
</head>
<body bgcolor="#e7e8e9">
<div id="content">
<h1>Web Page Blocked</h1>
<p>Access to the web page you were trying to visit has been blocked in accordance with company policy. Please contact your
system administrator if you believe this is in error.</p>
<p><b>User:</b> </p>
<p><b>URL:</b> www.example.com:81/api/getdata </p>
<p><b>Category:</b> unknown </p>
</div>
</body>
</html>
Browser Console Error:
XMLHttpRequest cannot load http://www.example.com:81/api/getdata. No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://www.example.com' is therefore not allowed
access. The response had HTTP status code 503.
------ EDIT -----
Here is the cors() I am passing.
app.options('*',cors());
app.use(function (req, res, next){
res.header("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With,Content-type, Accept");
res.header("Access-Control-Allow-Credentials", true);
next();
});
It's either a problem with your company firewall or a CORS problem.
Try to accept every origin in CORS module :
var express = require('express');
var cors = require('cors')
var app = express();
var corsOptions = {
origin: '*',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
};
app.get('/products/:id', cors(corsOptions), function(req, res, next){
res.json({msg: 'This is CORS-enabled for every origin.'});
});
app.listen(80, function(){
console.log('CORS-enabled web server listening on port 80');
});
Now you should receive response headers like this :
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json
If it still does not work, then port 81 must be blocked.
Another possibility is that the proxy of your company removes or blocks some headers which could explain why Access-Control-Allow-Origin: www.example.com is not present in your response headers
I am working with Visual Studio's Tools for Apache Cordova.
When I build the app with Ripple, all is well. But when I build it to my android device, the app refuses to connect to my external API.
This is the error in the JavaScript Console log:
Refused to connect to 'http://XXX.herokuapp.com/api/posts/0/5' because it violates the following Content Security Policy directive: "default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'".
Note that 'connect-src' was not explicitly set, so 'default-src' is
used as a fallback.
And:
Error: Failed to execute 'open' on 'XMLHttpRequest': Refused to
connect to 'http:// XXX. herokuapp. com/api/posts/0/5'
My API is built with Node.js and express. There is Access-Control-Allow-Headers in my server.js, but it still doesn't work on my device.
Server.js:
//'use strict';
var express = require('express'); // call express
var app = express(); // define our app using express
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var router = express.Router();
var fs = require('fs');
var path = require('path');
app.use(bodyParser.json()); // parse application/json
app.use(bodyParser.json({ type: 'application/vnd.api+json' })); // parse application/vnd.api+json as json
app.use(bodyParser.urlencoded({ extended: true })); // parse application/x-www-form-urlencoded
app.use(methodOverride('X-HTTP-Method-Override')); // override with the X-HTTP-Method-Override header in the request. simulate DELETE/PUT
app.use(express.static(__dirname + '/www'));
// middleware to use for all requests
app.use(function (req, res, next) {
console.log('in middleware');
res.setHeader('Access-Control-Allow-Origin', '*');//allowing ripple's localhost get access to node's localhost(5432).
console.log(req.header);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers',"X-Requested-With,Content-Type");
//res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
require('./app/routes')(app); // pass our application into our routes -- must
app.use('/api', router);//put this line beofre passing app to routes.js for it to take effect.
var port = process.env.PORT || 8080;
app.listen(port, function() {
console.log("Listening on " + port);
});
exports = module.exports = app; // expose app
I have also tried adding a meta tag to my index.html file, but with no success.
<meta http-equiv="Content-Security-Policy" content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:8080 http://XXX.herokuapp.com">
Any ideas what might be the problem?
From the Error Message. You are calling Ajax Request in your JS. But you only added http://XXX.herokuapp.com after script-src, which only allows loading the script content. To allow the Ajax request, http://XXX.herokuapp.com needs to be added after connect-srclike this:
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; connect-src 'self' http://XXX.herokuapp.com; style-src 'self' 'unsafe-inline'; media-src *">
Alternatively, you can add the URL after default-src, which sets a default policy for allowing everything(loading script/CSS contents,Ajax Request and so on). So the meta Tag should be like this:
<meta http-equiv="Content-Security-Policy" content="default-src 'self' http://XXX.herokuapp.com data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
For detailed information about Content Security Policy you can refer to Content Security Policy Reference.