diff --git a/data/datacreator.ts b/data/datacreator.ts index ddf03e6a798..6cc8b80d376 100644 --- a/data/datacreator.ts +++ b/data/datacreator.ts @@ -38,10 +38,15 @@ const entities = new Entities() const readFile = util.promisify(fs.readFile) function loadStaticData (file: string) { - const filePath = path.resolve('./data/static/' + file + '.yml') - return readFile(filePath, 'utf8') + const base = path.resolve('./data/static/') + const target = path.resolve(base, file + '.yml') + const relative = path.relative(base, target) + if (relative.startsWith('..') || path.isAbsolute(relative)) { + throw new Error('Invalid file path') + } + return readFile(target, 'utf8') .then(safeLoad) - .catch(() => logger.error('Could not open file: "' + filePath + '"')) + .catch(() => logger.error('Could not open file: "' + target + '"')) } module.exports = async () => { diff --git a/lib/codingChallenges.ts b/lib/codingChallenges.ts index 7650cc28999..6b6d5ac5c80 100644 --- a/lib/codingChallenges.ts +++ b/lib/codingChallenges.ts @@ -20,9 +20,16 @@ export const findFilesWithCodeChallenges = async (paths: readonly string[]): Pro for (const currPath of paths) { if ((await fs.lstat(currPath)).isDirectory()) { const files = await fs.readdir(currPath) - const moreMatches = await findFilesWithCodeChallenges( - files.map(file => path.resolve(currPath, file)) - ) + const base = path.resolve(currPath) + const resolvedFiles = files.map(file => { + const target = path.resolve(base, file) + const relative = path.relative(base, target) + if (relative.startsWith('..') || path.isAbsolute(relative)) { + throw new Error('Invalid file path') + } + return target + }) + const moreMatches = await findFilesWithCodeChallenges(resolvedFiles) matches.push(...moreMatches) } else { try { diff --git a/routes/dataErasure.ts b/routes/dataErasure.ts index 39ec58b1124..4869ad9a704 100644 --- a/routes/dataErasure.ts +++ b/routes/dataErasure.ts @@ -66,7 +66,12 @@ router.post('/', async (req: Request, Record { return absolutePath === path.resolve('ftp/legal.md') }) - if (absolutePath.includes(path.resolve('.'))) { - entry.pipe(fs.createWriteStream('uploads/complaints/' + fileName).on('error', function (err) { next(err) })) + if (!relativePath.startsWith('..') && !path.isAbsolute(relativePath) && absolutePath.includes(path.resolve('.'))) { + entry.pipe(fs.createWriteStream(absolutePath).on('error', function (err) { next(err) })) } else { entry.autodrain() } diff --git a/routes/keyServer.ts b/routes/keyServer.ts index de05aff5564..fe70df8e494 100644 --- a/routes/keyServer.ts +++ b/routes/keyServer.ts @@ -11,7 +11,15 @@ module.exports = function serveKeyFiles () { const file = params.file if (!file.includes('/')) { - res.sendFile(path.resolve('encryptionkeys/', file)) + const base = path.resolve('encryptionkeys/') + const target = path.resolve(base, file) + const relative = path.relative(base, target) + if (relative.startsWith('..') || path.isAbsolute(relative)) { + res.status(403) + next(new Error('Invalid file path!')) + } else { + res.sendFile(target) + } } else { res.status(403) next(new Error('File names cannot contain forward slashes!')) diff --git a/routes/logfileServer.ts b/routes/logfileServer.ts index dbe4011a40e..4a8bcd53798 100644 --- a/routes/logfileServer.ts +++ b/routes/logfileServer.ts @@ -11,7 +11,15 @@ module.exports = function serveLogFiles () { const file = params.file if (!file.includes('/')) { - res.sendFile(path.resolve('logs/', file)) + const base = path.resolve('logs/') + const target = path.resolve(base, file) + const relative = path.relative(base, target) + if (relative.startsWith('..') || path.isAbsolute(relative)) { + res.status(403) + next(new Error('Invalid file path!')) + } else { + res.sendFile(target) + } } else { res.status(403) next(new Error('File names cannot contain forward slashes!')) diff --git a/routes/quarantineServer.ts b/routes/quarantineServer.ts index 7dada55cdb2..a403cd8114b 100644 --- a/routes/quarantineServer.ts +++ b/routes/quarantineServer.ts @@ -11,7 +11,15 @@ module.exports = function serveQuarantineFiles () { const file = params.file if (!file.includes('/')) { - res.sendFile(path.resolve('ftp/quarantine/', file)) + const base = path.resolve('ftp/quarantine/') + const target = path.resolve(base, file) + const relative = path.relative(base, target) + if (relative.startsWith('..') || path.isAbsolute(relative)) { + res.status(403) + next(new Error('Invalid file path!')) + } else { + res.sendFile(target) + } } else { res.status(403) next(new Error('File names cannot contain forward slashes!')) diff --git a/routes/vulnCodeFixes.ts b/routes/vulnCodeFixes.ts index 4255b1b7b47..5ca946c759c 100644 --- a/routes/vulnCodeFixes.ts +++ b/routes/vulnCodeFixes.ts @@ -4,6 +4,7 @@ import * as accuracy from '../lib/accuracy' const challengeUtils = require('../lib/challengeUtils') const fs = require('fs') const yaml = require('js-yaml') +const path = require('path') const FixesDir = 'data/static/codefixes' @@ -76,8 +77,11 @@ export const checkCorrectFix = () => async (req: Request }) } else { let explanation - if (fs.existsSync('./data/static/codefixes/' + key + '.info.yml')) { - const codingChallengeInfos = yaml.load(fs.readFileSync('./data/static/codefixes/' + key + '.info.yml', 'utf8')) + const base = path.resolve('./data/static/codefixes') + const target = path.resolve(base, key + '.info.yml') + const relative = path.relative(base, target) + if (!relative.startsWith('..') && !path.isAbsolute(relative) && fs.existsSync(target)) { + const codingChallengeInfos = yaml.load(fs.readFileSync(target, 'utf8')) const selectedFixInfo = codingChallengeInfos?.fixes.find(({ id }: { id: number }) => id === selectedFix + 1) if (selectedFixInfo?.explanation) explanation = res.__(selectedFixInfo.explanation) } diff --git a/routes/vulnCodeSnippet.ts b/routes/vulnCodeSnippet.ts index 604594b5cfd..1ddfbd565ea 100644 --- a/routes/vulnCodeSnippet.ts +++ b/routes/vulnCodeSnippet.ts @@ -90,8 +90,11 @@ exports.checkVulnLines = () => async (req: Request, Reco const selectedLines: number[] = req.body.selectedLines const verdict = getVerdict(vulnLines, neutralLines, selectedLines) let hint - if (fs.existsSync('./data/static/codefixes/' + key + '.info.yml')) { - const codingChallengeInfos = yaml.load(fs.readFileSync('./data/static/codefixes/' + key + '.info.yml', 'utf8')) + const base = path.resolve('./data/static/codefixes') + const target = path.resolve(base, key + '.info.yml') + const relative = path.relative(base, target) + if (!relative.startsWith('..') && !path.isAbsolute(relative) && fs.existsSync(target)) { + const codingChallengeInfos = yaml.load(fs.readFileSync(target, 'utf8')) if (codingChallengeInfos?.hints) { if (accuracy.getFindItAttempts(key) > codingChallengeInfos.hints.length) { if (vulnLines.length === 1) { diff --git a/rsn/rsnUtil.ts b/rsn/rsnUtil.ts index e701e9d7a1b..406819f8d6e 100644 --- a/rsn/rsnUtil.ts +++ b/rsn/rsnUtil.ts @@ -45,7 +45,13 @@ const checkDiffs = async (keys: string[]) => { .then(snippet => { if (snippet == null) return process.stdout.write(val + ': ') - const fileData = fs.readFileSync(fixesPath + '/' + val).toString() + const base = path.resolve(fixesPath) + const target = path.resolve(base, val) + const relative = path.relative(base, target) + if (relative.startsWith('..') || path.isAbsolute(relative)) { + throw new Error('Invalid file path') + } + const fileData = fs.readFileSync(target).toString() const diff = Diff.diffLines(filterString(fileData), filterString(snippet.snippet)) let line = 0 for (const part of diff) { @@ -100,7 +106,13 @@ const checkDiffs = async (keys: string[]) => { } async function seePatch (file: string) { - const fileData = fs.readFileSync(fixesPath + '/' + file).toString() + const resolvedBase = path.resolve(fixesPath) + const resolvedTarget = path.resolve(resolvedBase, file) + const relativePath = path.relative(resolvedBase, resolvedTarget) + if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) { + throw new Error('Invalid file path') + } + const fileData = fs.readFileSync(resolvedTarget).toString() const snippet = await retrieveCodeSnippet(file.split('_')[0]) if (snippet == null) return const patch = Diff.structuredPatch(file, file, filterString(snippet.snippet), filterString(fileData))