fix: force download of unsafe extensions

This commit is contained in:
NGPixel 2021-12-24 20:18:12 -05:00
parent 57b56d3a5b
commit 79bdd44093
No known key found for this signature in database
GPG Key ID: 8FDA2F1757F60D63
6 changed files with 33 additions and 2 deletions

View File

@ -151,6 +151,15 @@
persistent-hint persistent-hint
hint='Should SVG uploads be scanned for vulnerabilities and stripped of any potentially unsafe content.' hint='Should SVG uploads be scanned for vulnerabilities and stripped of any potentially unsafe content.'
) )
v-divider.mt-3
v-switch(
inset
label='Force Download of Unsafe Extensions'
color='primary'
v-model='config.uploadForceDownload'
persistent-hint
hint='Should non-image files be forced as downloads when accessed directly. This prevents potential XSS attacks via unsafe file extensions uploads.'
)
v-card.mt-3.animated.fadeInUp.wait-p2s v-card.mt-3.animated.fadeInUp.wait-p2s
v-toolbar(flat, color='primary', dark, dense) v-toolbar(flat, color='primary', dark, dense)
@ -252,6 +261,7 @@ export default {
uploadMaxFileSize: 0, uploadMaxFileSize: 0,
uploadMaxFiles: 0, uploadMaxFiles: 0,
uploadScanSVG: true, uploadScanSVG: true,
uploadForceDownload: true,
securityOpenRedirect: true, securityOpenRedirect: true,
securityIframe: true, securityIframe: true,
securityReferrerPolicy: true, securityReferrerPolicy: true,
@ -297,6 +307,7 @@ export default {
$uploadMaxFileSize: Int $uploadMaxFileSize: Int
$uploadMaxFiles: Int $uploadMaxFiles: Int
$uploadScanSVG: Boolean $uploadScanSVG: Boolean
$uploadForceDownload: Boolean
$securityOpenRedirect: Boolean $securityOpenRedirect: Boolean
$securityIframe: Boolean $securityIframe: Boolean
$securityReferrerPolicy: Boolean $securityReferrerPolicy: Boolean
@ -319,6 +330,7 @@ export default {
uploadMaxFileSize: $uploadMaxFileSize, uploadMaxFileSize: $uploadMaxFileSize,
uploadMaxFiles: $uploadMaxFiles, uploadMaxFiles: $uploadMaxFiles,
uploadScanSVG: $uploadScanSVG uploadScanSVG: $uploadScanSVG
uploadForceDownload: $uploadForceDownload,
securityOpenRedirect: $securityOpenRedirect, securityOpenRedirect: $securityOpenRedirect,
securityIframe: $securityIframe, securityIframe: $securityIframe,
securityReferrerPolicy: $securityReferrerPolicy, securityReferrerPolicy: $securityReferrerPolicy,
@ -350,6 +362,7 @@ export default {
uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)), uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)),
uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)), uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)),
uploadScanSVG: _.get(this.config, 'uploadScanSVG', false), uploadScanSVG: _.get(this.config, 'uploadScanSVG', false),
uploadForceDownload: _.get(this.config, 'uploadForceDownload', false),
securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false), securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false),
securityIframe: _.get(this.config, 'securityIframe', false), securityIframe: _.get(this.config, 'securityIframe', false),
securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false), securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
@ -402,6 +415,7 @@ export default {
uploadMaxFileSize uploadMaxFileSize
uploadMaxFiles uploadMaxFiles
uploadScanSVG uploadScanSVG
uploadForceDownload
securityOpenRedirect securityOpenRedirect
securityIframe securityIframe
securityReferrerPolicy securityReferrerPolicy

View File

@ -81,6 +81,7 @@ defaults:
maxFileSize: 5242880 maxFileSize: 5242880
maxFiles: 10 maxFiles: 10
scanSVG: true scanSVG: true
forceDownload: true
flags: flags:
ldapdebug: false ldapdebug: false
sqllog: false sqllog: false

View File

@ -30,7 +30,8 @@ module.exports = {
authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal, authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal,
uploadMaxFileSize: WIKI.config.uploads.maxFileSize, uploadMaxFileSize: WIKI.config.uploads.maxFileSize,
uploadMaxFiles: WIKI.config.uploads.maxFiles, uploadMaxFiles: WIKI.config.uploads.maxFiles,
uploadScanSVG: WIKI.config.uploads.scanSVG uploadScanSVG: WIKI.config.uploads.scanSVG,
uploadForceDownload: WIKI.config.uploads.forceDownload
} }
} }
}, },
@ -99,7 +100,8 @@ module.exports = {
WIKI.config.uploads = { WIKI.config.uploads = {
maxFileSize: _.get(args, 'uploadMaxFileSize', WIKI.config.uploads.maxFileSize), maxFileSize: _.get(args, 'uploadMaxFileSize', WIKI.config.uploads.maxFileSize),
maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles), maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles),
scanSVG: _.get(args, 'uploadScanSVG', WIKI.config.uploads.scanSVG) scanSVG: _.get(args, 'uploadScanSVG', WIKI.config.uploads.scanSVG),
forceDownload: _.get(args, 'uploadForceDownload', WIKI.config.uploads.forceDownload)
} }
await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads']) await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads'])

View File

@ -55,6 +55,7 @@ type SiteMutation {
uploadMaxFileSize: Int uploadMaxFileSize: Int
uploadMaxFiles: Int uploadMaxFiles: Int
uploadScanSVG: Boolean uploadScanSVG: Boolean
uploadForceDownload: Boolean
): DefaultResponse @auth(requires: ["manage:system"]) ): DefaultResponse @auth(requires: ["manage:system"])
} }
@ -95,4 +96,5 @@ type SiteConfig {
uploadMaxFileSize: Int uploadMaxFileSize: Int
uploadMaxFiles: Int uploadMaxFiles: Int
uploadScanSVG: Boolean uploadScanSVG: Boolean
uploadForceDownload: Boolean
} }

View File

@ -1,4 +1,5 @@
const crypto = require('crypto') const crypto = require('crypto')
const path = require('path')
module.exports = { module.exports = {
/** /**
@ -6,5 +7,9 @@ module.exports = {
*/ */
generateHash(assetPath) { generateHash(assetPath) {
return crypto.createHash('sha1').update(assetPath).digest('hex') return crypto.createHash('sha1').update(assetPath).digest('hex')
},
getPathInfo(assetPath) {
return path.parse(assetPath.toLowerCase())
} }
} }

View File

@ -168,8 +168,15 @@ module.exports = class Asset extends Model {
static async getAsset(assetPath, res) { static async getAsset(assetPath, res) {
try { try {
const fileInfo = assetHelper.getPathInfo(assetPath)
const fileHash = assetHelper.generateHash(assetPath) const fileHash = assetHelper.generateHash(assetPath)
const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`) const cachePath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, `cache/${fileHash}.dat`)
// Force unsafe extensions to download
if (WIKI.config.uploads.forceDownload && !['.png', '.apng', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg'].includes(fileInfo.ext)) {
res.set('Content-disposition', 'attachment; filename=' + fileInfo.base)
}
if (await WIKI.models.assets.getAssetFromCache(assetPath, cachePath, res)) { if (await WIKI.models.assets.getAssetFromCache(assetPath, cachePath, res)) {
return return
} }