From de50f14959636aacaca0db0e4e27ac8e121fe68a Mon Sep 17 00:00:00 2001 From: shavindaL Date: Thu, 26 Sep 2024 22:45:36 +0530 Subject: [PATCH] CSRF vulnerability fix --- client/src/components/CustomerAccount.js | 11 +++++ server/controllers/csrf-controller.js | 19 ++++++++ server/package-lock.json | 57 ++++++++++++++++++++++++ server/package.json | 4 +- server/routes/SiteFeedbackRoutes.js | 3 +- server/routes/csrf-routes.js | 9 ++++ server/server.js | 9 ++++ 7 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 server/controllers/csrf-controller.js create mode 100644 server/routes/csrf-routes.js diff --git a/client/src/components/CustomerAccount.js b/client/src/components/CustomerAccount.js index 872ec84..4f909cf 100644 --- a/client/src/components/CustomerAccount.js +++ b/client/src/components/CustomerAccount.js @@ -112,10 +112,20 @@ function CustomerAccount() { console.log("Feedback "+sFeedback) const siteFeedback = { name, sFeedback, email} + const csrfResponse = await fetch('/csrf/generate', { + method: 'GET', + credentials: 'include' + }) + const csrfData = await csrfResponse.json(); + const csrfToken = csrfData.csrfToken; + console.log("csrfToken", csrfToken); + const response = await fetch('/api/site-feedbacks', { method: 'POST', + credentials: 'include', body: JSON.stringify(siteFeedback), headers: { + 'x-csrf-token': csrfToken, // Include the CSRF token in the header 'Content-Type': 'application/json' } }) @@ -128,6 +138,7 @@ function CustomerAccount() { } if (response.ok) { + alert('New feedback added'); console.log('new feedback added', json) window.location.reload(); } diff --git a/server/controllers/csrf-controller.js b/server/controllers/csrf-controller.js new file mode 100644 index 0000000..959fc8b --- /dev/null +++ b/server/controllers/csrf-controller.js @@ -0,0 +1,19 @@ +require("dotenv").config(); + +const { doubleCsrf } = require("csrf-csrf"); + +const doubleCsrfConfig = { + getSecret: () => process.env.CSRF_SECRET, // A function that optionally takes the request and returns a secret +}; + +const { generateToken, doubleCsrfProtection } = doubleCsrf(doubleCsrfConfig); + +const generateCsrfToken = (req, res) => { + const csrfToken = generateToken(req, res); + res.json({ csrfToken }); +}; + +module.exports = { + generateCsrfToken, + doubleCsrfProtection, +}; diff --git a/server/package-lock.json b/server/package-lock.json index 9c812a9..cd7fc64 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,8 +10,10 @@ "license": "ISC", "dependencies": { "bcrypt": "^5.1.1", + "cookie-parser": "^1.4.6", "cookie-session": "^2.1.0", "cors": "^2.8.5", + "csrf-csrf": "^3.0.8", "dotenv": "^16.4.5", "express": "^4.21.0", "express-rate-limit": "^7.4.0", @@ -433,6 +435,28 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "license": "MIT", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-session": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.1.0.tgz", @@ -498,6 +522,15 @@ "node": ">= 0.10" } }, + "node_modules/csrf-csrf": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csrf-csrf/-/csrf-csrf-3.0.8.tgz", + "integrity": "sha512-JdyBcND7hWgDR9GdBvvwslIGv2y2dI0bZLhAcg/YYdF1HnWFCWg+HQEkgtsiwH3Mi+qs/xRDDZC4G7tpRlrtBg==", + "license": "ISC", + "dependencies": { + "http-errors": "^2.0.0" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2720,6 +2753,22 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, + "cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + } + } + }, "cookie-session": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cookie-session/-/cookie-session-2.1.0.tgz", @@ -2774,6 +2823,14 @@ "vary": "^1" } }, + "csrf-csrf": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csrf-csrf/-/csrf-csrf-3.0.8.tgz", + "integrity": "sha512-JdyBcND7hWgDR9GdBvvwslIGv2y2dI0bZLhAcg/YYdF1HnWFCWg+HQEkgtsiwH3Mi+qs/xRDDZC4G7tpRlrtBg==", + "requires": { + "http-errors": "^2.0.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", diff --git a/server/package.json b/server/package.json index 1c13773..cc343ba 100644 --- a/server/package.json +++ b/server/package.json @@ -12,8 +12,10 @@ "license": "ISC", "dependencies": { "bcrypt": "^5.1.1", + "cookie-parser": "^1.4.6", "cookie-session": "^2.1.0", "cors": "^2.8.5", + "csrf-csrf": "^3.0.8", "dotenv": "^16.4.5", "express": "^4.21.0", "express-rate-limit": "^7.4.0", @@ -28,4 +30,4 @@ "passport": "^0.5.3", "passport-google-oauth20": "^2.0.0" } -} +} \ No newline at end of file diff --git a/server/routes/SiteFeedbackRoutes.js b/server/routes/SiteFeedbackRoutes.js index c57080e..9983c7c 100644 --- a/server/routes/SiteFeedbackRoutes.js +++ b/server/routes/SiteFeedbackRoutes.js @@ -7,6 +7,7 @@ const { updatePublicSiteFeedback, updatePrivateSiteFeedback } = require('../controllers/siteFeedbackController') +const { doubleCsrfProtection } = require('../controllers/csrf-controller') const router = express.Router() @@ -17,7 +18,7 @@ router.get('/private', getPrivateSiteFeedbacks) router.get('/public', getPublicSiteFeedbacks) // POST a new feedback -router.post('/', createSiteFeedback) +router.post('/', doubleCsrfProtection, createSiteFeedback) // DELETE a feedback router.delete('/:id', deleteSiteFeedback) diff --git a/server/routes/csrf-routes.js b/server/routes/csrf-routes.js new file mode 100644 index 0000000..2bdd986 --- /dev/null +++ b/server/routes/csrf-routes.js @@ -0,0 +1,9 @@ + +const express = require('express'); +const { generateCsrfToken } = require('../controllers/csrf-controller'); + +const router = express.Router() + +router.get("/generate", generateCsrfToken); + +module.exports = router diff --git a/server/server.js b/server/server.js index 8fd0ab8..61871ba 100644 --- a/server/server.js +++ b/server/server.js @@ -14,6 +14,9 @@ const certificate = fs.readFileSync('./security/cert.pem') const googleOAuthRoutes = require('./routes/google-oauth-routes') +/* CSRF routes */ +const csrfRoutes = require("./routes/csrf-routes"); + const userRoutes = require('./routes/userRoutes') const siteFeedbacks = require('./routes/SiteFeedbackRoutes') @@ -82,6 +85,8 @@ app.use(deserializeToken); const cookieSession = require('cookie-session'); const passport = require('passport'); +const cookieParser = require('cookie-parser'); + app.use( cookieSession({ name: "cookie-session", @@ -98,6 +103,10 @@ app.use(passport.session()); // Google oauth routes app.use('/auth', googleOAuthRoutes) +// CSRF routes and cookie-parser middleware +app.use(cookieParser()); +app.use("/csrf", csrfRoutes); + // routes app.use('/api/users', userRoutes) app.use('/api/orders', orderRoutes)