Compare commits

...

27 Commits

Author SHA1 Message Date
Barış Soner Uşaklı
12cba2d12c backport fix for cswsh 2023-07-19 18:17:40 -04:00
Barış Soner Uşaklı
bb997a78cc fix: backport prototype vuln. fixes 2023-07-19 17:52:42 -04:00
Barış Soner Uşaklı
354c9c2cc1 only allow valid types for doExport
backport of
2023-07-19 17:51:13 -04:00
Barış Soner Uşaklı
e9ec1969b5 Revert "fix: #11756, fix unique visitor stats in acp table"
This reverts commit b450689705.
2023-07-19 17:47:34 -04:00
Julian Lam
88c9d9d152 fix: improper neutralization of user input in image wrapping code 2023-07-19 17:46:37 -04:00
Barış Soner Uşaklı
b450689705 fix: #11756, fix unique visitor stats in acp table 2023-06-26 09:57:36 -04:00
Barış Soner Uşaklı
81e3c1ba48 fix: get rid of math.random in generateUUID 2022-05-26 12:25:49 -04:00
Misty Release Bot
e0080d9005 chore: incrementing version number - v1.19.7
(cherry picked from commit 0c4850e287)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-04-28 13:40:05 +00:00
Misty Release Bot
addd701de2 Merge commit '0d9179f7a1423af1934099a6e4bc6d6990bee9c1' into v1.19.x 2022-04-28 13:39:59 +00:00
Misty Release Bot
24ba3e84cb chore: incrementing version number - v1.19.6
(cherry picked from commit 283a0072a8)
Signed-off-by: Misty Release Bot <deploy@nodebb.org>
2022-04-13 21:25:10 +00:00
Misty Release Bot
70a0135209 Merge commit '5316029f91308f225de65444e59f1fe846c07525' into v1.19.x 2022-04-13 21:25:05 +00:00
Misty (Bot)
a3ae8c48ce chore: incrementing version number - v1.19.5
(cherry picked from commit 48d6eb4f14)
Signed-off-by: Misty (Bot) <deploy@nodebb.org>
2022-03-16 17:05:48 -04:00
Misty (Bot)
e5ca0232de Merge commit '3935a86b839005fdf3054e6f5a953092a617ddeb' into v1.19.x 2022-03-16 17:05:45 -04:00
Misty (Bot)
8d5ef17248 chore: incrementing version number - v1.19.4
(cherry picked from commit 67282057e7)
Signed-off-by: Misty (Bot) <deploy@nodebb.org>
2022-03-09 15:51:43 -05:00
Misty (Bot)
40ce9af189 Merge commit 'df46ab4874fe698d25cf26a0641aa832dad9379d' into v1.19.x 2022-03-09 15:51:28 -05:00
Barış Soner Uşaklı
e4bd4f3107 feat: backport filter:posts.getUserInfoForPosts 2022-03-09 15:07:36 -05:00
Renovate Bot
4a87b3225c fix(deps): update dependency nodebb-plugin-markdown to v9.0.10 2022-03-07 13:22:31 -05:00
renovate[bot]
673fcfb052 fix(deps): update dependency nodebb-plugin-mentions to v3.0.6 (#10328)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-02-23 15:37:18 -05:00
Julian Lam
3f13a69298 Re-introduce lodash into src/package-install.js (#10315)
* test: add failing test for if package.json is non-existant, fix tests' beforeEach method

* Revert "fix: #10289, remove lodash dependency in src/cli/package-install.js"

This reverts commit 81fa2e22bc.

* fix: regression caused by 94b79ce402

`./nodebb setup` was no longer able to be called without arguments or env vars

* fix: .updatePackageFile() throwing if no package.json

* fix: removing unneeded code in src/cli/index.js that seemed to be used to handle cases where package.json was missing (initial install)

... However, as .updatePackageFile() now handled cases where there is no package.json, it should be ok to remove this code

* fix: handle missing package.json or node_modules/
2022-02-18 10:13:11 -05:00
Julian Lam
b60174f51e fix: regression caused by 94b79ce402
`./nodebb setup` was no longer able to be called without arguments or env vars
2022-02-18 10:12:59 -05:00
Misty (Bot)
7388f111b7 chore: incrementing version number - v1.19.3 2022-02-16 19:20:39 +00:00
Misty (Bot)
4bd559deba Merge commit 'e9e48a756fad301e8a6729d3e74852a644228724' into v1.19.x 2022-02-16 19:20:36 +00:00
Misty (Bot)
ded19254ac chore: incrementing version number - v1.19.2 2022-02-09 21:28:32 +00:00
Misty (Bot)
5c89557155 Merge commit '8e52abe8bed8706d2f75dce4f118490e48c6fab8' into v1.19.x 2022-02-09 21:28:11 +00:00
Misty (Bot)
04ce24e661 chore: incrementing version number - v1.19.1 2022-01-21 18:20:49 +00:00
Misty (Bot)
a24a108a66 Merge commit 'd098e26f82096188a8ef910561c5ebc7a784a399' into v1.19.x 2022-01-21 18:18:46 +00:00
Misty (Bot)
aa77758afd chore: incrementing version number - v1.19.0 2022-01-13 18:51:21 +00:00
13 changed files with 104 additions and 41 deletions

View File

@@ -2,7 +2,7 @@
"name": "nodebb",
"license": "GPL-3.0",
"description": "NodeBB Forum",
"version": "1.19.6",
"version": "1.19.7",
"homepage": "http://www.nodebb.org",
"repository": {
"type": "git",
@@ -54,7 +54,7 @@
"cookie-parser": "1.4.6",
"cron": "1.8.2",
"cropperjs": "1.5.12",
"csurf": "1.11.0",
"csrf-sync": "4.0.1",
"daemon": "1.1.0",
"diff": "5.0.0",
"express": "4.18.0",

View File

@@ -3,28 +3,26 @@
define('forum/topic/images', [], function () {
const Images = {};
const suffixRegex = /-resized(\.[\w]+)?$/;
Images.wrapImagesInLinks = function (posts) {
posts.find('[component="post/content"] img:not(.emoji)').each(function () {
const $this = $(this);
let src = $this.attr('src') || '';
const alt = $this.attr('alt') || '';
const suffixRegex = /-resized(\.[\w]+)?$/;
if (src === 'about:blank') {
return;
}
if (utils.isRelativeUrl(src) && suffixRegex.test(src)) {
src = src.replace(suffixRegex, '$1');
}
const srcExt = src.split('.').slice(1).pop();
const altFilename = alt.split('/').pop();
const altExt = altFilename.split('.').slice(1).pop();
if (!$this.parent().is('a')) {
if (utils.isRelativeUrl(src) && suffixRegex.test(src)) {
src = src.replace(suffixRegex, '$1');
}
const alt = $this.attr('alt') || '';
const srcExt = src.split('.').slice(1).pop();
const altFilename = alt.split('/').pop();
const altExt = altFilename.split('.').slice(1).pop();
$this.wrap('<a href="' + src + '" ' +
(!srcExt && altExt ? ' download="' + altFilename + '" ' : '') +
(!srcExt && altExt ? ' download="' + utils.escapeHTML(altFilename) + '" ' : '') +
' target="_blank" rel="noopener">');
}
});

View File

@@ -12,6 +12,9 @@ socket = window.socket;
reconnectionDelay: config.reconnectionDelay,
transports: config.socketioTransports,
path: config.relative_path + '/socket.io',
query: {
_csrf: config.csrf_token,
},
};
socket = io(config.websocketAddress, ioParams);

View File

@@ -290,13 +290,11 @@
const utils = {
generateUUID: function () {
/* eslint-disable no-bitwise */
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : ((r & 0x3) | 0x8);
return v.toString(16);
});
/* eslint-enable no-bitwise */
// from https://github.com/tracker1/node-uuid4/blob/master/browser.js
const temp_url = URL.createObjectURL(new Blob());
const uuid = temp_url.toString();
URL.revokeObjectURL(temp_url);
return uuid.split(/[:\/]/g).pop().toLowerCase(); // remove prefixes
},
// https://github.com/substack/node-ent/blob/master/index.js
decodeHTMLEntities: function (html) {

View File

@@ -9,6 +9,7 @@ const categories = require('../categories');
const plugins = require('../plugins');
const translator = require('../translator');
const languages = require('../languages');
const { generateToken } = require('../middleware/csrf');
const apiController = module.exports;
@@ -64,7 +65,7 @@ apiController.loadConfig = async function (req) {
'cache-buster': meta.config['cache-buster'] || '',
topicPostSort: meta.config.topicPostSort || 'oldest_to_newest',
categoryTopicSort: meta.config.categoryTopicSort || 'newest_to_oldest',
csrf_token: req.uid >= 0 && req.csrfToken && req.csrfToken(),
csrf_token: req.uid >= 0 ? generateToken(req) : false,
searchEnabled: plugins.hooks.hasListeners('filter:search.query'),
searchDefaultInQuick: meta.config.searchDefaultInQuick || 'titles',
bootswatchSkin: meta.config.bootswatchSkin || '',

28
src/middleware/csrf.js Normal file
View File

@@ -0,0 +1,28 @@
'use strict';
const { csrfSync } = require('csrf-sync');
const {
generateToken,
csrfSynchronisedProtection,
isRequestValid,
} = csrfSync({
getTokenFromRequest: (req) => {
if (req.headers['x-csrf-token']) {
return req.headers['x-csrf-token'];
} else if (req.body && req.body.csrf_token) {
return req.body.csrf_token;
} else if (req.body && req.body._csrf) {
return req.body._csrf;
} else if (req.query && req.query._csrf) {
return req.query._csrf;
}
},
size: 64,
});
module.exports = {
generateToken,
csrfSynchronisedProtection,
isRequestValid,
};

View File

@@ -2,13 +2,14 @@
const async = require('async');
const path = require('path');
const csrf = require('csurf');
const validator = require('validator');
const nconf = require('nconf');
const toobusy = require('toobusy-js');
const LRU = require('lru-cache');
const util = require('util');
const { csrfSynchronisedProtection } = require('./csrf');
const plugins = require('../plugins');
const meta = require('../meta');
const user = require('../user');
@@ -34,7 +35,7 @@ middleware.regexes = {
timestampedUpload: /^\d+-.+$/,
};
const csrfMiddleware = csrf();
const csrfMiddleware = csrfSynchronisedProtection;
middleware.applyCSRF = function (req, res, next) {
if (req.uid >= 0) {

View File

@@ -10,6 +10,7 @@ const meta = require('../meta');
const controllers = require('../controllers');
const helpers = require('../controllers/helpers');
const plugins = require('../plugins');
const { generateToken } = require('../middleware/csrf');
let loginStrategies = [];
@@ -94,7 +95,7 @@ Auth.reloadRoutes = async function (params) {
};
if (strategy.checkState !== false) {
req.session.ssoState = req.csrfToken && req.csrfToken();
req.session.ssoState = generateToken(req, true);
opts.state = req.session.ssoState;
}

View File

@@ -13,7 +13,7 @@ const logger = require('../logger');
const plugins = require('../plugins');
const ratelimit = require('../middleware/ratelimit');
const Namespaces = {};
const Namespaces = Object.create(null);
const Sockets = module.exports;
@@ -34,13 +34,25 @@ Sockets.init = async function (server) {
}
}
io.use(authorize);
io.on('connection', onConnection);
const opts = {
transports: nconf.get('socket.io:transports') || ['polling', 'websocket'],
cookie: false,
allowRequest: (req, callback) => {
authorize(req, (err) => {
if (err) {
return callback(err);
}
const csrf = require('../middleware/csrf');
const isValid = csrf.isRequestValid({
session: req.session || {},
query: req._query,
headers: req.headers,
});
callback(null, isValid);
});
},
};
/*
* Restrict socket.io listener to cookie domain. If none is set, infer based on url.
@@ -62,7 +74,11 @@ Sockets.init = async function (server) {
};
function onConnection(socket) {
socket.ip = (socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress || '').split(',')[0];
socket.uid = socket.request.uid;
socket.ip = (
socket.request.headers['x-forwarded-for'] ||
socket.request.connection.remoteAddress || ''
).split(',')[0];
socket.request.ip = socket.ip;
logger.io_one(socket, socket.uid);
@@ -123,7 +139,7 @@ async function onMessage(socket, payload) {
const parts = eventName.toString().split('.');
const namespace = parts[0];
const methodToCall = parts.reduce((prev, cur) => {
if (prev !== null && prev[cur]) {
if (prev !== null && prev[cur] && (!prev.hasOwnProperty || prev.hasOwnProperty(cur))) {
return prev[cur];
}
return null;
@@ -225,9 +241,7 @@ async function validateSession(socket, errorMsg) {
const cookieParserAsync = util.promisify((req, callback) => cookieParser(req, {}, err => callback(err)));
async function authorize(socket, callback) {
const { request } = socket;
async function authorize(request, callback) {
if (!request) {
return callback(new Error('[[error:not-authorized]]'));
}
@@ -240,15 +254,13 @@ async function authorize(socket, callback) {
});
const sessionData = await getSessionAsync(sessionId);
request.session = sessionData;
let uid = 0;
if (sessionData && sessionData.passport && sessionData.passport.user) {
request.session = sessionData;
socket.uid = parseInt(sessionData.passport.user, 10);
} else {
socket.uid = 0;
uid = parseInt(sessionData.passport.user, 10);
}
request.uid = socket.uid;
callback();
request.uid = uid;
callback(null, uid);
}
Sockets.in = function (room) {

View File

@@ -64,6 +64,10 @@ module.exports = function (SocketUser) {
};
async function doExport(socket, data, type) {
const validTypes = ['profile', 'posts', 'uploads'];
if (!validTypes.includes(type)) {
throw new Error('[[error:invalid-data]]');
}
if (!socket.uid) {
throw new Error('[[error:invalid-uid]]');
}

View File

@@ -1,3 +1,17 @@
'use strict';
const crypto = require('crypto');
module.exports = require('../public/src/utils');
module.exports.generateUUID = function () {
// from https://github.com/tracker1/node-uuid4/blob/master/index.js
let rnd = crypto.randomBytes(16);
/* eslint-disable no-bitwise */
rnd[6] = (rnd[6] & 0x0f) | 0x40;
rnd[8] = (rnd[8] & 0x3f) | 0x80;
/* eslint-enable no-bitwise */
rnd = rnd.toString('hex').match(/(.{8})(.{4})(.{4})(.{4})(.{12})/);
rnd.shift();
return rnd.join('-');
};

View File

@@ -95,7 +95,7 @@ helpers.logoutUser = function (jar, callback) {
});
};
helpers.connectSocketIO = function (res, callback) {
helpers.connectSocketIO = function (res, csrf_token, callback) {
const io = require('socket.io-client');
let cookies = res.headers['set-cookie'];
cookies = cookies.filter(c => /express.sid=[^;]+;/.test(c));
@@ -106,6 +106,9 @@ helpers.connectSocketIO = function (res, callback) {
Origin: nconf.get('url'),
Cookie: cookie,
},
query: {
_csrf: csrf_token,
},
});
socket.on('connect', () => {

View File

@@ -74,7 +74,7 @@ describe('socket.io', () => {
}, (err, res) => {
assert.ifError(err);
helpers.connectSocketIO(res, (err, _io) => {
helpers.connectSocketIO(res, body.csrf_token, (err, _io) => {
io = _io;
assert.ifError(err);