fix exploit; no longer use extended body parser mode and remove unneeded array prefix from array body fields, since we use different lib to parse body now. also upgrade express and dont allow body for modlog actions to be entered into modlog, replace with non user controlled text

merge-requests/208/head
fatchan 5 years ago
parent 14cd1c34d0
commit d9559c76e6
  1. 4
      db/posts.js
  2. 0
      gulp/res/img/bumplocked.png
  3. 0
      gulp/res/img/bumplocked.svg
  4. 5
      helpers/paramconverter.js
  5. 54
      models/forms/actionhandler.js
  6. 0
      models/forms/bumplockposts.js
  7. 4
      models/forms/makepost.js
  8. 80
      package-lock.json
  9. 5
      package.json
  10. 6
      server.js
  11. 34
      views/includes/actionfooter.pug
  12. 4
      views/includes/postmods.pug
  13. 2
      views/mixins/ban.pug
  14. 2
      views/mixins/newspost.pug
  15. 4
      views/mixins/post.pug
  16. 2
      views/mixins/report.pug
  17. 2
      views/pages/globalmanageaccounts.pug
  18. 2
      views/pages/managebanners.pug

@ -300,8 +300,8 @@ module.exports = {
'replyfiles': data.files.length
}
}
//if post email is not sage, and thread not saged, set bump date
if (data.email !== 'sage' && !thread.saged) {
//if post email is not sage, and thread not bumplocked, set bump date
if (data.email !== 'sage' && !thread.bumplocked) {
query['$set'] = {
'bumped': Date.now()
}

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 200 B

After

Width:  |  Height:  |  Size: 200 B

@ -1,13 +1,14 @@
'use strict';
const { ObjectId } = require(__dirname+'/../db/db.js')
, allowedArrays = new Set(['checkednews', 'checkedposts', 'globalcheckedposts', 'checkedreports', 'checkedbans', 'checkedbanners', 'checkedaccounts']) //only these can be arrays, since express bodyparser will output arrays
, allowedArrays = new Set(['checkednews', 'checkedposts', 'globalcheckedposts',
'checkedreports', 'checkedbans', 'checkedbanners', 'checkedaccounts']) //only these should be arrays, since express bodyparser can output arrays
, trimFields = ['tags', 'uri', 'moderators', 'filters', 'announcement', 'description', 'message',
'name', 'subject', 'email', 'password', 'default_name', 'report_reason', 'ban_reason', 'log_message'] //trim if we dont want filed with whitespace
, numberFields = ['filter_mode', 'captcha_mode', 'tph_trigger', 'tph_trigger_action', 'reply_limit',
'max_files', 'thread_limit', 'thread', 'min_thread_message_length', 'min_reply_message_length', 'auth_level'] //convert these to numbers before they hit our routes
, banDurationRegex = /^(?<year>[\d]+y)?(?<month>[\d]+m)?(?<week>[\d]+w)?(?<day>[\d]+d)?(?<hour>[\d]+h)?$/
, msTime = require(__dirname+'/mstime.js')
, msTime = require(__dirname+'/mstime.js');
module.exports = (req, res, next) => {

@ -6,7 +6,7 @@ const { Posts, Boards, Modlogs } = require(__dirname+'/../../db/')
, deletePosts = require(__dirname+'/deletepost.js')
, spoilerPosts = require(__dirname+'/spoilerpost.js')
, stickyPosts = require(__dirname+'/stickyposts.js')
, sagePosts = require(__dirname+'/sageposts.js')
, bumplockPosts = require(__dirname+'/bumplockposts.js')
, lockPosts = require(__dirname+'/lockposts.js')
, cyclePosts = require(__dirname+'/cycleposts.js')
, deletePostsFiles = require(__dirname+'/deletepostsfiles.js')
@ -82,11 +82,15 @@ module.exports = async (req, res, next) => {
// if getting global banned, board ban doesnt matter
if (req.body.ban || req.body.global_ban || req.body.report_ban || req.body.global_report_ban) {
const { message, action, query } = await banPoster(req, res, next);
if (req.body.ban || req.body.global_ban) {
modlogActions.push(req.body.ban || req.body.global_ban);
if (req.body.ban) {
modlogActions.push('Ban');
} else if (req.body.global_ban) {
modlogActions.push('Global Ban');
}
if (req.body.report_ban || req.body.global_report_ban) {
modlogActions.push(req.body.report_ban || req.body.global_report_ban);
if (req.body.report_ban) {
modlogActions.push('Ban reporter');
} else if (req.body.global_report_ban) {
modlogActions.push('Global ban reporter');
}
if (action) {
combinedQuery[action] = { ...combinedQuery[action], ...query}
@ -114,7 +118,13 @@ module.exports = async (req, res, next) => {
const { action, message } = await deletePosts(res.locals.posts, req.body.delete_ip_global ? null : req.params.board);
messages.push(message);
if (action) {
modlogActions.push(req.body.delete || req.body.delete_ip_board || req.body.delete_ip_global);
if (req.body.delete) {
modlogActions.push('Delete');
} else if (req.body.delete_ip_board) {
modlogActions.push('Delete by IP');
} else if (req.body.delete_ip_global) {
modlogActions.push('Global delete by IP');
}
aggregateNeeded = true;
}
} else {
@ -122,7 +132,11 @@ module.exports = async (req, res, next) => {
if (req.body.unlink_file || req.body.delete_file) {
const { message, action, query } = await deletePostsFiles(res.locals.posts, req.body.unlink_file);
if (action) {
modlogActions.push(req.body.unlink_file || req.body.delete_file);
if (req.body.unlink_file) {
modlogActions.push('Unlink files');
} else if () {
modlogActions.push('Delete files');
}
aggregateNeeded = true;
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
@ -130,16 +144,16 @@ module.exports = async (req, res, next) => {
} else if (req.body.spoiler) {
const { message, action, query } = spoilerPosts(res.locals.posts);
if (action) {
modlogActions.push(req.body.spoiler);
modlogActions.push('Spoiler files');
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
}
//lock, sticky, sage, cyclic
if (req.body.sage) {
const { message, action, query } = sagePosts(res.locals.posts);
//lock, sticky, bumplock, cyclic
if (req.body.bumplock) {
const { message, action, query } = bumplockPosts(res.locals.posts);
if (action) {
modlogActions.push(req.body.sage);
modlogActions.push('Bumplock');
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
@ -147,7 +161,7 @@ module.exports = async (req, res, next) => {
if (req.body.lock) {
const { message, action, query } = lockPosts(res.locals.posts);
if (action) {
modlogActions.push(req.body.lock);
modlogActions.push('Lock');
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
@ -155,7 +169,7 @@ module.exports = async (req, res, next) => {
if (req.body.sticky) {
const { message, action, query } = stickyPosts(res.locals.posts);
if (action) {
modlogActions.push(req.body.sticky);
modlogActions.push('Sticky');
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
@ -163,7 +177,7 @@ module.exports = async (req, res, next) => {
if (req.body.cyclic) {
const { message, action, query } = cyclePosts(res.locals.posts);
if (action) {
modlogActions.push(req.body.cyclic);
modlogActions.push('Cycle');
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
@ -172,14 +186,18 @@ module.exports = async (req, res, next) => {
if (req.body.report || req.body.global_report) {
const { message, action, query } = reportPosts(req, res);
if (action) {
//no modlog for making reports
//no modlog entry for making reports
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
} else if (req.body.dismiss || req.body.global_dismiss) {
const { message, action, query } = dismissReports(req, res);
if (action) {
modlogActions.push(req.body.dismiss || req.body.global_dismiss);
if (req.body.dismiss) {
modlogActions.push('Dismiss reports');
} else if (req.body.global_dismiss) {
modlogActions.push('Dismiss global reports');
}
combinedQuery[action] = { ...combinedQuery[action], ...query}
}
messages.push(message);
@ -390,7 +408,7 @@ module.exports = async (req, res, next) => {
'endpage': threadPageOldest,
}
});
} else if (req.body.lock || req.body.sage || req.body.cyclic || req.body.unlink_file) {
} else if (req.body.lock || req.body.bumplock || req.body.cyclic || req.body.unlink_file) {
buildQueue.push({
'task': 'buildBoardMultiple',
'options': {

@ -347,7 +347,7 @@ module.exports = async (req, res, next) => {
//NOTE: these are numbers because we XOR them for toggling in action handler
'sticky': Mongo.NumberInt(0),
'locked': Mongo.NumberInt(0),
'saged': Mongo.NumberInt(0),
'bumplocked': Mongo.NumberInt(0),
'cyclic': Mongo.NumberInt(0),
'salt': salt
});
@ -421,7 +421,7 @@ module.exports = async (req, res, next) => {
} else if (data.thread) {
//refersh pages
const threadPage = await Posts.getThreadPage(req.params.board, thread);
if (data.email === 'sage' || thread.sage) {
if (data.email === 'sage' || thread.bumplocked) {
//refresh the page that the thread is on
buildQueue.push({
'task': 'buildBoard',

80
package-lock.json generated

@ -965,26 +965,31 @@
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"bull": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/bull/-/bull-3.10.0.tgz",
"integrity": "sha512-LbQsc7c+eYd7IaJD7tS373yKLYttjTfoPZ+9xYYlPM5+gutAjofSTsESOGGyaxyX2lE1dkg+eWhUK5kAPl5Zow==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/bull/-/bull-3.11.0.tgz",
"integrity": "sha512-QQOn63RkL6CfnmZcacPVg1EF42SwQcYxNSn9OGlM5S2JW+Gah/dwCcXxZQ3h2nYnhsNfBsherJ7EpLzIsi2kSQ==",
"requires": {
"cron-parser": "^2.7.3",
"cron-parser": "^2.13.0",
"debuglog": "^1.0.0",
"get-port": "^5.0.0",
"ioredis": "^4.5.1",
"lodash": "^4.17.11",
"ioredis": "^4.14.1",
"lodash": "^4.17.15",
"p-timeout": "^3.1.0",
"promise.prototype.finally": "^3.1.0",
"semver": "^6.1.1",
"promise.prototype.finally": "^3.1.1",
"semver": "^6.3.0",
"util.promisify": "^1.0.0",
"uuid": "^3.2.1"
"uuid": "^3.3.3"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
},
"uuid": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
}
}
},
@ -1902,16 +1907,20 @@
}
},
"es-abstract": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
"integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==",
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz",
"integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==",
"requires": {
"es-to-primitive": "^1.2.0",
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.0",
"is-callable": "^1.1.4",
"is-regex": "^1.0.4",
"object-keys": "^1.0.12"
"object-inspect": "^1.6.0",
"object-keys": "^1.1.1",
"string.prototype.trimleft": "^2.0.0",
"string.prototype.trimright": "^2.0.0"
}
},
"es-to-primitive": {
@ -3695,9 +3704,9 @@
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
},
"ioredis": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.14.0.tgz",
"integrity": "sha512-vGzyW9QTdGMjaAPUhMj48Z31mIO5qJLzkbsE5dg+orNi7L5Ph035htmkBZNDTDdDk7kp7e9UJUr+alhRuaWp8g==",
"version": "4.14.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-4.14.1.tgz",
"integrity": "sha512-94W+X//GHM+1GJvDk6JPc+8qlM7Dul+9K+lg3/aHixPN7ZGkW6qlvX0DG6At9hWtH2v3B32myfZqWoANUJYGJA==",
"requires": {
"cluster-key-slot": "^1.1.0",
"debug": "^4.1.1",
@ -3722,14 +3731,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
"requires": {
"redis-errors": "^1.0.0"
}
}
}
},
@ -4756,6 +4757,11 @@
}
}
},
"object-inspect": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
"integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ=="
},
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@ -5690,6 +5696,14 @@
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
},
"redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
"requires": {
"redis-errors": "^1.0.0"
}
},
"redlock": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/redlock/-/redlock-4.0.0.tgz",
@ -6357,6 +6371,24 @@
"strip-ansi": "^3.0.0"
}
},
"string.prototype.trimleft": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
"integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
}
},
"string.prototype.trimright": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
"integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
}
},
"string_decoder": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz",

@ -6,8 +6,7 @@
"dependencies": {
"@tohru/gm": "github:fatchan/gm",
"bcrypt": "^3.0.6",
"body-parser": "^1.19.0",
"bull": "^3.10.0",
"bull": "^3.11.0",
"cache-pug-templates": "^2.0.1",
"connect-redis": "^4.0.2",
"cookie-parser": "^1.4.4",
@ -26,7 +25,7 @@
"gulp-less": "^4.0.1",
"gulp-pug": "^4.0.1",
"gulp-uglify-es": "^1.0.4",
"ioredis": "^4.14.0",
"ioredis": "^4.14.1",
"mongodb": "^3.3.2",
"node-fetch": "^2.6.0",
"path": "^0.12.7",

@ -9,7 +9,6 @@ const express = require('express')
, redisStore = require('connect-redis')(session)
, path = require('path')
, app = express()
, bodyParser = require('body-parser')
, cookieParser = require('cookie-parser')
, configs = require(__dirname+'/configs/main.json')
, ipHash = require(__dirname+'/helpers/iphash.js')
@ -32,9 +31,8 @@ const express = require('express')
// disable useless express header
app.disable('x-powered-by');
// parse forms (is json required?)
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
// parse forms
app.use(express.urlencoded({extended: true}));
// parse cookies
app.use(cookieParser());

@ -3,46 +3,46 @@ details.toggle-label
.actions
h4.no-m-p Actions:
label
input.post-check(type='checkbox', name='delete' value='Delete post')
input.post-check(type='checkbox', name='delete' value='1')
| Delete Posts
label
input.post-check(type='checkbox', name='unlink_file' value='Unlink files')
input.post-check(type='checkbox', name='unlink_file' value='1')
| Unlink Files
label
input.post-check(type='checkbox', name='spoiler' value='Spoiler files')
input.post-check(type='checkbox', name='spoiler' value='1')
| Spoiler Files
label
input#password(type='text', name='password', placeholder='post password' autocomplete='off')
label
input.post-check(type='checkbox', name='report' value='Report')
input.post-check(type='checkbox', name='report' value='1')
| Report
label
input.post-check(type='checkbox', name='global_report' value='Report global')
input.post-check(type='checkbox', name='global_report' value='1')
| Global Report
label
input#report(type='text', name='report_reason', placeholder='report reason' autocomplete='off')
details.actions
summary(style='font-weight: bold') Staff Actions:
label
input.post-check(type='checkbox', name='delete_ip_board' value='Delete posts by IP')
input.post-check(type='checkbox', name='delete_ip_board' value='1')
| Delete from IP on board
label
input.post-check(type='checkbox', name='delete_ip_global' value='Delete posts by IP global')
input.post-check(type='checkbox', name='delete_ip_global' value='1')
| Delete from IP globally
label
input.post-check(type='checkbox', name='delete_file' value='Delete files')
input.post-check(type='checkbox', name='delete_file' value='1')
| Delete Files
label
input.post-check(type='checkbox', name='ban' value='Ban')
input.post-check(type='checkbox', name='ban' value='1')
| Ban Poster
label
input.post-check(type='checkbox', name='global_ban' value='Ban global')
input.post-check(type='checkbox', name='global_ban' value='1')
| Global Ban Poster
label
input.post-check(type='checkbox', name='no_appeal' value='Non-appealable')
input.post-check(type='checkbox', name='no_appeal' value='1')
| Non-appealable Ban
label
input.post-check(type='checkbox', name='preserve_post' value='Show post in ban')
input.post-check(type='checkbox', name='preserve_post' value='1')
| Show Post In Ban
label
input(type='text', name='ban_reason', placeholder='ban reason' autocomplete='off')
@ -51,16 +51,16 @@ details.toggle-label
label
input(type='text', name='log_message', placeholder='modlog message' autocomplete='off')
label
input.post-check(type='checkbox', name='sticky' value='Sticky')
input.post-check(type='checkbox', name='sticky' value='1')
| Toggle Sticky
label
input.post-check(type='checkbox', name='lock' value='Lock')
input.post-check(type='checkbox', name='lock' value='1')
| Toggle Lock
label
input.post-check(type='checkbox', name='sage' value='Sage')
| Toggle Sage
input.post-check(type='checkbox', name='bumplock' value='1')
| Toggle Bumplock
label
input.post-check(type='checkbox', name='cyclic' value='Cycle')
input.post-check(type='checkbox', name='cyclic' value='1')
| Toggle Cycle
.actions
h4.no-m-p Captcha:

@ -1,8 +1,8 @@
if post.sticky
img(src='/img/sticky.png' height='12' width='12' title='Sticky')
|
if post.saged
img(src='/img/saged.png' height='12' width='12' title='Bumplocked')
if post.bumplocked
img(src='/img/bumplocked.png' height='12' width='12' title='Bumplocked')
|
if post.locked
img(src='/img/locked.png' height='12' width='12' title='Locked')

@ -3,7 +3,7 @@ mixin ban(ban, banpage)
tr
td
if !banpage || (ban.appeal == null && ban.allowAppeal === true)
input.post-check(type='checkbox', name='checkedbans[]' value=ban._id)
input.post-check(type='checkbox', name='checkedbans' value=ban._id)
td
if ban.board
a(href=`/${ban.board}/`) /#{ban.board}/

@ -5,7 +5,7 @@ mixin newspost(post, globalmanage=false)
tr
th
if globalmanage === true
input.left.post-check(type='checkbox', name='checkednews[]' value=post._id)
input.left.post-check(type='checkbox', name='checkednews' value=post._id)
a.left(href=`#${post._id}`) #{post.title}
p.right.no-m-p #{post.date.toLocaleString(undefined, {hour12:false})}
tr

@ -6,9 +6,9 @@ mixin post(post, truncate, manage=false, globalmanage=false, ban=false)
header.post-info
label
if globalmanage
input.post-check(type='checkbox', name='globalcheckedposts[]' value=post._id)
input.post-check(type='checkbox', name='globalcheckedposts' value=post._id)
else if !ban
input.post-check(type='checkbox', name='checkedposts[]' value=post.postId)
input.post-check(type='checkbox', name='checkedposts' value=post.postId)
|
if !post.thread
include ../includes/postmods.pug

@ -1,6 +1,6 @@
mixin report(r)
.reports.post-container
input.post-check(type='checkbox', name='checkedreports[]' value=r.id)
input.post-check(type='checkbox', name='checkedreports' value=r.id)
|
span Date: #{r.date.toLocaleString()}
|

@ -22,7 +22,7 @@ block content
th Auth Level
for account in accounts
tr
td: input(type='checkbox', name='checkedaccounts[]' value=account._id)
td: input(type='checkbox', name='checkedaccounts' value=account._id)
td #{account._id}
td #{account.authLevel}
section.row

@ -28,6 +28,6 @@ block content
input(type='hidden' name='_csrf' value=csrf)
each banner in board.banners
label.banner-check
input(type='checkbox' name='checkedbanners[]' value=banner)
input(type='checkbox' name='checkedbanners' value=banner)
img.board-banner(src=`/banner/${board._id}/${banner}` width='300' height='100')
input(type='submit', value='delete')

Loading…
Cancel
Save