Browse Source

fix: force download of unsafe extensions

pull/4824/head
NGPixel 2 years ago
parent
commit
79bdd44093
No known key found for this signature in database GPG Key ID: 8FDA2F1757F60D63
6 changed files with 33 additions and 2 deletions
  1. 14
      client/components/admin/admin-security.vue
  2. 1
      server/app/data.yml
  3. 6
      server/graph/resolvers/site.js
  4. 2
      server/graph/schemas/site.graphql
  5. 5
      server/helpers/asset.js
  6. 7
      server/models/assets.js

14
client/components/admin/admin-security.vue

@ -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)
@ -252,6 +261,7 @@ export default {
uploadMaxFileSize: 0,
uploadMaxFiles: 0,
uploadScanSVG: true,
uploadForceDownload: true,
securityOpenRedirect: true,
securityIframe: true,
securityReferrerPolicy: true,
@ -297,6 +307,7 @@ export default {
$uploadMaxFileSize: Int
$uploadMaxFiles: Int
$uploadScanSVG: Boolean
$uploadForceDownload: Boolean
$securityOpenRedirect: Boolean
$securityIframe: Boolean
$securityReferrerPolicy: Boolean
@ -319,6 +330,7 @@ export default {
uploadMaxFileSize: $uploadMaxFileSize,
uploadMaxFiles: $uploadMaxFiles,
uploadScanSVG: $uploadScanSVG
uploadForceDownload: $uploadForceDownload,
securityOpenRedirect: $securityOpenRedirect,
securityIframe: $securityIframe,
securityReferrerPolicy: $securityReferrerPolicy,
@ -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),
@ -402,6 +415,7 @@ export default {
uploadMaxFileSize
uploadMaxFiles
uploadScanSVG
uploadForceDownload
securityOpenRedirect
securityIframe
securityReferrerPolicy

1
server/app/data.yml

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

6
server/graph/resolvers/site.js

@ -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
}
}
},
@ -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'])

2
server/graph/schemas/site.graphql

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

5
server/helpers/asset.js

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

7
server/models/assets.js

@ -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
}

Loading…
Cancel
Save