Skip to content

Commit

Permalink
fix: force download of unsafe extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
NGPixel committed Dec 25, 2021
1 parent 57b56d3 commit 79bdd44
Show file tree
Hide file tree
Showing 6 changed files with 33 additions and 2 deletions.
14 changes: 14 additions & 0 deletions client/components/admin/admin-security.vue
Expand Up @@ -151,6 +151,15 @@
persistent-hint
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-toolbar(flat, color='primary', dark, dense)
Expand Down Expand Up @@ -252,6 +261,7 @@ export default {
uploadMaxFileSize: 0,
uploadMaxFiles: 0,
uploadScanSVG: true,
uploadForceDownload: true,
securityOpenRedirect: true,
securityIframe: true,
securityReferrerPolicy: true,
Expand Down Expand Up @@ -297,6 +307,7 @@ export default {
$uploadMaxFileSize: Int
$uploadMaxFiles: Int
$uploadScanSVG: Boolean
$uploadForceDownload: Boolean
$securityOpenRedirect: Boolean
$securityIframe: Boolean
$securityReferrerPolicy: Boolean
Expand All @@ -319,6 +330,7 @@ export default {
uploadMaxFileSize: $uploadMaxFileSize,
uploadMaxFiles: $uploadMaxFiles,
uploadScanSVG: $uploadScanSVG
uploadForceDownload: $uploadForceDownload,
securityOpenRedirect: $securityOpenRedirect,
securityIframe: $securityIframe,
securityReferrerPolicy: $securityReferrerPolicy,
Expand Down Expand Up @@ -350,6 +362,7 @@ export default {
uploadMaxFileSize: _.toSafeInteger(_.get(this.config, 'uploadMaxFileSize', 0)),
uploadMaxFiles: _.toSafeInteger(_.get(this.config, 'uploadMaxFiles', 0)),
uploadScanSVG: _.get(this.config, 'uploadScanSVG', false),
uploadForceDownload: _.get(this.config, 'uploadForceDownload', false),
securityOpenRedirect: _.get(this.config, 'securityOpenRedirect', false),
securityIframe: _.get(this.config, 'securityIframe', false),
securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
Expand Down Expand Up @@ -402,6 +415,7 @@ export default {
uploadMaxFileSize
uploadMaxFiles
uploadScanSVG
uploadForceDownload
securityOpenRedirect
securityIframe
securityReferrerPolicy
Expand Down
1 change: 1 addition & 0 deletions server/app/data.yml
Expand Up @@ -81,6 +81,7 @@ defaults:
maxFileSize: 5242880
maxFiles: 10
scanSVG: true
forceDownload: true
flags:
ldapdebug: false
sqllog: false
Expand Down
6 changes: 4 additions & 2 deletions server/graph/resolvers/site.js
Expand Up @@ -30,7 +30,8 @@ module.exports = {
authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal,
uploadMaxFileSize: WIKI.config.uploads.maxFileSize,
uploadMaxFiles: WIKI.config.uploads.maxFiles,
uploadScanSVG: WIKI.config.uploads.scanSVG
uploadScanSVG: WIKI.config.uploads.scanSVG,
uploadForceDownload: WIKI.config.uploads.forceDownload
}
}
},
Expand Down Expand Up @@ -99,7 +100,8 @@ module.exports = {
WIKI.config.uploads = {
maxFileSize: _.get(args, 'uploadMaxFileSize', WIKI.config.uploads.maxFileSize),
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'])
Expand Down
2 changes: 2 additions & 0 deletions server/graph/schemas/site.graphql
Expand Up @@ -55,6 +55,7 @@ type SiteMutation {
uploadMaxFileSize: Int
uploadMaxFiles: Int
uploadScanSVG: Boolean
uploadForceDownload: Boolean

): DefaultResponse @auth(requires: ["manage:system"])
}
Expand Down Expand Up @@ -95,4 +96,5 @@ type SiteConfig {
uploadMaxFileSize: Int
uploadMaxFiles: Int
uploadScanSVG: Boolean
uploadForceDownload: Boolean
}
5 changes: 5 additions & 0 deletions server/helpers/asset.js
@@ -1,10 +1,15 @@
const crypto = require('crypto')
const path = require('path')

module.exports = {
/**
* Generate unique hash from page
*/
generateHash(assetPath) {
return crypto.createHash('sha1').update(assetPath).digest('hex')
},

getPathInfo(assetPath) {
return path.parse(assetPath.toLowerCase())
}
}
7 changes: 7 additions & 0 deletions server/models/assets.js
Expand Up @@ -168,8 +168,15 @@ module.exports = class Asset extends Model {

static async getAsset(assetPath, res) {
try {
const fileInfo = assetHelper.getPathInfo(assetPath)
const fileHash = assetHelper.generateHash(assetPath)
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)) {
return
}
Expand Down

0 comments on commit 79bdd44

Please sign in to comment.