fix: sanitize SVG uploads

This commit is contained in:
NGPixel 2021-12-17 21:41:23 -05:00
parent 79e153815f
commit 5d3e81496f
No known key found for this signature in database
GPG Key ID: 8FDA2F1757F60D63
6 changed files with 79 additions and 25 deletions

View File

@ -142,6 +142,15 @@
:suffix='$t(`admin:security.maxUploadBatchSuffix`)'
style='max-width: 450px;'
)
v-divider.mt-3
v-switch(
inset
label='Scan and Sanitize SVG Uploads'
color='primary'
v-model='config.uploadScanSVG'
persistent-hint
hint='Should SVG uploads be scanned for vulnerabilities and stripped of any potentially unsafe content.'
)
v-card.mt-3.animated.fadeInUp.wait-p2s
v-toolbar(flat, color='primary', dark, dense)
@ -242,6 +251,7 @@ export default {
config: {
uploadMaxFileSize: 0,
uploadMaxFiles: 0,
uploadScanSVG: true,
securityOpenRedirect: true,
securityIframe: true,
securityReferrerPolicy: true,
@ -286,6 +296,7 @@ export default {
$authJwtRenewablePeriod: String
$uploadMaxFileSize: Int
$uploadMaxFiles: Int
$uploadScanSVG: Boolean
$securityOpenRedirect: Boolean
$securityIframe: Boolean
$securityReferrerPolicy: Boolean
@ -307,6 +318,7 @@ export default {
authJwtRenewablePeriod: $authJwtRenewablePeriod,
uploadMaxFileSize: $uploadMaxFileSize,
uploadMaxFiles: $uploadMaxFiles,
uploadScanSVG: $uploadScanSVG
securityOpenRedirect: $securityOpenRedirect,
securityIframe: $securityIframe,
securityReferrerPolicy: $securityReferrerPolicy,
@ -337,6 +349,7 @@ export default {
authJwtRenewablePeriod: _.get(this.config, 'authJwtRenewablePeriod', ''),
uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)),
uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)),
uploadScanSVG: _.get(this.config, 'uploadScanSVG', false),
securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false),
securityIframe: _.get(this.config, 'securityIframe', false),
securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
@ -388,6 +401,7 @@ export default {
authJwtRenewablePeriod
uploadMaxFileSize
uploadMaxFiles
uploadScanSVG
securityOpenRedirect
securityIframe
securityReferrerPolicy

View File

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

View File

@ -29,7 +29,8 @@ module.exports = {
authJwtExpiration: WIKI.config.auth.tokenExpiration,
authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal,
uploadMaxFileSize: WIKI.config.uploads.maxFileSize,
uploadMaxFiles: WIKI.config.uploads.maxFiles
uploadMaxFiles: WIKI.config.uploads.maxFiles,
uploadScanSVG: WIKI.config.uploads.scanSVG
}
}
},
@ -97,7 +98,8 @@ module.exports = {
WIKI.config.uploads = {
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)
}
await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads'])

View File

@ -54,6 +54,7 @@ type SiteMutation {
securityCSPDirectives: String
uploadMaxFileSize: Int
uploadMaxFiles: Int
uploadScanSVG: Boolean
): DefaultResponse @auth(requires: ["manage:system"])
}
@ -63,15 +64,15 @@ type SiteMutation {
# -----------------------------------------------
type SiteConfig {
host: String!
title: String!
description: String!
robots: [String]!
analyticsService: String!
analyticsId: String!
company: String!
contentLicense: String!
logoUrl: String!
host: String
title: String
description: String
robots: [String]
analyticsService: String
analyticsId: String
company: String
contentLicense: String
logoUrl: String
authAutoLogin: Boolean
authEnforce2FA: Boolean
authHideLocal: Boolean
@ -79,18 +80,19 @@ type SiteConfig {
authJwtAudience: String
authJwtExpiration: String
authJwtRenewablePeriod: String
featurePageRatings: Boolean!
featurePageComments: Boolean!
featurePersonalWikis: Boolean!
securityOpenRedirect: Boolean!
securityIframe: Boolean!
securityReferrerPolicy: Boolean!
securityTrustProxy: Boolean!
securitySRI: Boolean!
securityHSTS: Boolean!
securityHSTSDuration: Int!
securityCSP: Boolean!
securityCSPDirectives: String!
uploadMaxFileSize: Int!
uploadMaxFiles: Int!
featurePageRatings: Boolean
featurePageComments: Boolean
featurePersonalWikis: Boolean
securityOpenRedirect: Boolean
securityIframe: Boolean
securityReferrerPolicy: Boolean
securityTrustProxy: Boolean
securitySRI: Boolean
securityHSTS: Boolean
securityHSTSDuration: Int
securityCSP: Boolean
securityCSPDirectives: String
uploadMaxFileSize: Int
uploadMaxFiles: Int
uploadScanSVG: Boolean
}

View File

@ -0,0 +1,25 @@
const fs = require('fs-extra')
const { JSDOM } = require('jsdom')
const createDOMPurify = require('dompurify')
/* global WIKI */
module.exports = async (svgPath) => {
WIKI.logger.info(`Sanitizing SVG file upload...`)
try {
let svgContents = await fs.readFile(svgPath, 'utf8')
const window = new JSDOM('').window
const DOMPurify = createDOMPurify(window)
svgContents = DOMPurify.sanitize(svgContents)
await fs.writeFile(svgPath, svgContents)
WIKI.logger.info(`Sanitized SVG file upload: [ COMPLETED ]`)
} catch (err) {
WIKI.logger.error(`Failed to sanitize SVG file upload: [ FAILED ]`)
WIKI.logger.error(err.message)
throw err
}
}

View File

@ -99,6 +99,16 @@ module.exports = class Asset extends Model {
folderId: opts.folderId
}
// Sanitize SVG contents
if (WIKI.config.uploads.scanSVG && opts.mimetype === 'image/svg+xml') {
const svgSanitizeJob = await WIKI.scheduler.registerJob({
name: 'sanitize-svg',
immediate: true,
worker: true
}, opts.path)
await svgSanitizeJob.finished
}
// Save asset data
try {
const fileBuffer = await fs.readFile(opts.path)