From 44d676650fd673f68b52d43ecee00b1896984621 Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Tue, 5 Feb 2019 13:30:40 +0200 Subject: [PATCH] Implement CSRF protection Enabled for all POST endpoints, as well as for GET /rpc-browser when the "execute" query string argument is specified. --- app.js | 6 ++++++ package.json | 1 + routes/baseActionsRouter.js | 39 ++++++++++++++++++++++--------------- views/browser.pug | 1 + views/connect.pug | 2 ++ views/layout.pug | 2 ++ views/search.pug | 1 + views/terminal.pug | 3 +++ 8 files changed, 39 insertions(+), 16 deletions(-) diff --git a/app.js b/app.js index 1e401cd..56192aa 100755 --- a/app.js +++ b/app.js @@ -11,6 +11,7 @@ var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var session = require("express-session"); +var csurf = require("csurf"); var config = require("./app/config.js"); var simpleGit = require('simple-git'); var utils = require("./app/utils.js"); @@ -454,6 +455,11 @@ app.use(function(req, res, next) { next(); }); +app.use(csurf(), (req, res, next) => { + res.locals.csrfToken = req.csrfToken(); + next(); +}); + app.use('/', baseActionsRouter); /// catch 404 and forwarding to error handler diff --git a/package.json b/package.json index e1a307c..eac251e 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "body-parser": "~1.18.2", "cookie-parser": "~1.4.3", "crypto-js": "3.1.9-1", + "csurf": "^1.9.0", "debug": "~2.6.0", "decimal.js": "7.2.3", "dotenv": "^6.2.0", diff --git a/routes/baseActionsRouter.js b/routes/baseActionsRouter.js index 9efe2e8..4e741a0 100644 --- a/routes/baseActionsRouter.js +++ b/routes/baseActionsRouter.js @@ -1,4 +1,5 @@ var express = require('express'); +var csurf = require('csurf'); var router = express.Router(); var util = require('util'); var moment = require('moment'); @@ -14,6 +15,8 @@ var coins = require("./../app/coins.js"); var config = require("./../app/config.js"); var coreApi = require("./../app/api/coreApi.js"); +const forceCsrf = csurf({ ignoreMethods: [] }); + router.get("/", function(req, res) { if (req.session.host == null || req.session.host.trim() == "") { if (req.cookies['rpc-host']) { @@ -815,7 +818,7 @@ router.post("/rpc-terminal", function(req, res) { }); }); -router.get("/rpc-browser", function(req, res) { +router.get("/rpc-browser", function(req, res, next) { if (!config.demoSite) { var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; var match = config.ipWhitelistForRpcCommands.exec(ip); @@ -883,26 +886,30 @@ router.get("/rpc-browser", function(req, res) { return; } - console.log("Executing RPC '" + req.query.method + "' with params: [" + argValues + "]"); + forceCsrf(req, res, err => { + if (err) return next(err); + + console.log("Executing RPC '" + req.query.method + "' with params: [" + argValues + "]"); + + client.command([{method:req.query.method, parameters:argValues}], function(err3, result3, resHeaders3) { + console.log("RPC Response: err=" + err3 + ", result=" + result3 + ", headers=" + resHeaders3); + + if (err3) { + if (result3) { + res.locals.methodResult = {error:("" + err3), result:result3}; - client.command([{method:req.query.method, parameters:argValues}], function(err3, result3, resHeaders3) { - console.log("RPC Response: err=" + err3 + ", result=" + result3 + ", headers=" + resHeaders3); + } else { + res.locals.methodResult = {error:("" + err3)}; + } + } else if (result3) { + res.locals.methodResult = result3; - if (err3) { - if (result3) { - res.locals.methodResult = {error:("" + err3), result:result3}; - } else { - res.locals.methodResult = {error:("" + err3)}; + res.locals.methodResult = {"Error":"No response from node."}; } - } else if (result3) { - res.locals.methodResult = result3; - - } else { - res.locals.methodResult = {"Error":"No response from node."}; - } - res.render("browser"); + res.render("browser"); + }); }); } else { res.render("browser"); diff --git a/views/browser.pug b/views/browser.pug index ec14bd5..7e72ef7 100644 --- a/views/browser.pug +++ b/views/browser.pug @@ -46,6 +46,7 @@ block content hr form(method="get") + input(type="hidden", name="_csrf", value=csrfToken) input(type="hidden", name="method", value=method) div(class="h5 mb-3") Arguments diff --git a/views/connect.pug b/views/connect.pug index b6a37c7..1cd8aa5 100644 --- a/views/connect.pug +++ b/views/connect.pug @@ -5,6 +5,8 @@ block content hr form(method="post", action="/connect") + input(type="hidden", name="_csrf", value=csrfToken) + div(class="form-group") label(for="input-host") Host / IP input(id="input-host", type="text", name="host", class="form-control", placeholder="Host / IP", value=host) diff --git a/views/layout.pug b/views/layout.pug index 601688f..39e54b2 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -2,6 +2,7 @@ doctype html html(lang="en") head meta(charset="utf-8") + meta(name="csrf-token", content=csrfToken) meta(name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no") if (session.uiTheme && session.uiTheme == "dark") @@ -86,6 +87,7 @@ html(lang="en") span Dark form(method="post", action="/search", class="form-inline") + input(type="hidden", name="_csrf", value=csrfToken) div(class="input-group input-group-sm") input(type="text", class="form-control form-control-sm", name="query", placeholder="block height/hash, txid, address", value=(query), style="width: 300px;") div(class="input-group-append") diff --git a/views/search.pug b/views/search.pug index febec71..d74e024 100644 --- a/views/search.pug +++ b/views/search.pug @@ -9,6 +9,7 @@ block content div(class="mb-5") form(method="post", action="/search", class="form") + input(type="hidden", name="_csrf", value=csrfToken) div(class="input-group input-group-lg") input(type="text", class="form-control form-control-sm", name="query", placeholder="block height/hash, txid, address", value=(query), style="width: 300px;") div(class="input-group-append") diff --git a/views/terminal.pug b/views/terminal.pug index fe5c669..1299933 100644 --- a/views/terminal.pug +++ b/views/terminal.pug @@ -33,6 +33,8 @@ block content block endOfBody script. + var csrfToken = $('meta[name=csrf-token]').attr('content'); + $(document).ready(function() { $("#terminal-form").submit(function(e) { e.preventDefault(); @@ -41,6 +43,7 @@ block endOfBody var postData = {}; postData.cmd = cmd; + postData._csrf = csrfToken; $.post( "/rpc-terminal",