Skip to content

Commit

Permalink
fix: sanitize SVG uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
NGPixel committed Dec 18, 2021
1 parent 79e1538 commit 5d3e814
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 25 deletions.
14 changes: 14 additions & 0 deletions client/components/admin/admin-security.vue
Expand Up @@ -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)
Expand Down Expand Up @@ -242,6 +251,7 @@ export default {
config: {
uploadMaxFileSize: 0,
uploadMaxFiles: 0,
uploadScanSVG: true,
securityOpenRedirect: true,
securityIframe: true,
securityReferrerPolicy: true,
Expand Down Expand Up @@ -286,6 +296,7 @@ export default {
$authJwtRenewablePeriod: String
$uploadMaxFileSize: Int
$uploadMaxFiles: Int
$uploadScanSVG: Boolean
$securityOpenRedirect: Boolean
$securityIframe: Boolean
$securityReferrerPolicy: Boolean
Expand All @@ -307,6 +318,7 @@ export default {
authJwtRenewablePeriod: $authJwtRenewablePeriod,
uploadMaxFileSize: $uploadMaxFileSize,
uploadMaxFiles: $uploadMaxFiles,
uploadScanSVG: $uploadScanSVG
securityOpenRedirect: $securityOpenRedirect,
securityIframe: $securityIframe,
securityReferrerPolicy: $securityReferrerPolicy,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -388,6 +401,7 @@ export default {
authJwtRenewablePeriod
uploadMaxFileSize
uploadMaxFiles
uploadScanSVG
securityOpenRedirect
securityIframe
securityReferrerPolicy
Expand Down
1 change: 1 addition & 0 deletions server/app/data.yml
Expand Up @@ -80,6 +80,7 @@ defaults:
uploads:
maxFileSize: 5242880
maxFiles: 10
scanSVG: true
flags:
ldapdebug: false
sqllog: false
Expand Down
6 changes: 4 additions & 2 deletions server/graph/resolvers/site.js
Expand Up @@ -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
}
}
},
Expand Down Expand Up @@ -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'])
Expand Down
48 changes: 25 additions & 23 deletions server/graph/schemas/site.graphql
Expand Up @@ -54,6 +54,7 @@ type SiteMutation {
securityCSPDirectives: String
uploadMaxFileSize: Int
uploadMaxFiles: Int
uploadScanSVG: Boolean

): DefaultResponse @auth(requires: ["manage:system"])
}
Expand All @@ -63,34 +64,35 @@ 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
authLoginBgUrl: String
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
}
25 changes: 25 additions & 0 deletions server/jobs/sanitize-svg.js
@@ -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
}
}
10 changes: 10 additions & 0 deletions server/models/assets.js
Expand Up @@ -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)
Expand Down

0 comments on commit 5d3e814

Please sign in to comment.