Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions data/datacreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
13 changes: 10 additions & 3 deletions lib/codingChallenges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential file inclusion attack via reading file - medium severity
If an attacker can control the input leading into the ReadFile function, they might be able to read sensitive files and launch further attacks with that information.

Show fix

Remediation: Ignore this issue only after you've verified or sanitized the input going into this function. This issue is only relevant in the backend, not in the frontend!

Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

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 {
Expand Down
7 changes: 6 additions & 1 deletion routes/dataErasure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ router.post('/', async (req: Request<Record<string, unknown>, Record<string, unk

res.clearCookie('token')
if (req.body.layout) {
const filePath: string = path.resolve(req.body.layout).toLowerCase()
const userInput = req.body.layout
if (userInput.includes('..') || path.isAbsolute(userInput)) {
next(new Error('Invalid input'))
return
}
const filePath: string = path.resolve(userInput).toLowerCase()
const isForbiddenFile: boolean = (filePath.includes('ftp') || filePath.includes('ctf.key') || filePath.includes('encryptionkeys'))
if (!isForbiddenFile) {
res.render('dataErasureResult', {
Expand Down
16 changes: 12 additions & 4 deletions routes/fileUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ function handleZipFileUpload ({ file }: Request, res: Response, next: NextFuncti
if (((file?.buffer) != null) && !utils.disableOnContainerEnv()) {
const buffer = file.buffer
const filename = file.originalname.toLowerCase()
const tempFile = path.join(os.tmpdir(), filename)
const tmpBase = path.resolve(os.tmpdir())
const tempFile = path.resolve(tmpBase, filename)
const tempFileRelative = path.relative(tmpBase, tempFile)
if (tempFileRelative.startsWith('..') || path.isAbsolute(tempFileRelative)) {
res.status(400).end()
return
}
fs.open(tempFile, 'w', function (err, fd) {
if (err != null) { next(err) }
fs.write(fd, buffer, 0, buffer.length, null, function (err) {
Expand All @@ -36,10 +42,12 @@ function handleZipFileUpload ({ file }: Request, res: Response, next: NextFuncti
.pipe(unzipper.Parse())
.on('entry', function (entry: any) {
const fileName = entry.path
const absolutePath = path.resolve('uploads/complaints/' + fileName)
const base = path.resolve('uploads/complaints/')
const absolutePath = path.resolve(base, fileName)
const relativePath = path.relative(base, absolutePath)
challengeUtils.solveIf(challenges.fileWriteChallenge, () => { 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()
}
Expand Down
10 changes: 9 additions & 1 deletion routes/keyServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!'))
Expand Down
10 changes: 9 additions & 1 deletion routes/logfileServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!'))
Expand Down
10 changes: 9 additions & 1 deletion routes/quarantineServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!'))
Expand Down
8 changes: 6 additions & 2 deletions routes/vulnCodeFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -76,8 +77,11 @@ export const checkCorrectFix = () => async (req: Request<Record<string, unknown>
})
} 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'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe yaml load can lead to remote code execution - medium severity
js-yaml has the ability to construct an arbitrary JS object. This is dangerous if you receive a YAML document from an untrusted source.

Show fix

Remediation: Ignore this issue only if you will always load YAML documents from trusted origins. If possible, use JSON.

Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

const selectedFixInfo = codingChallengeInfos?.fixes.find(({ id }: { id: number }) => id === selectedFix + 1)
if (selectedFixInfo?.explanation) explanation = res.__(selectedFixInfo.explanation)
}
Expand Down
7 changes: 5 additions & 2 deletions routes/vulnCodeSnippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,11 @@ exports.checkVulnLines = () => async (req: Request<Record<string, unknown>, 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'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe yaml load can lead to remote code execution - medium severity
js-yaml has the ability to construct an arbitrary JS object. This is dangerous if you receive a YAML document from an untrusted source.

Show fix

Remediation: Ignore this issue only if you will always load YAML documents from trusted origins. If possible, use JSON.

Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

if (codingChallengeInfos?.hints) {
if (accuracy.getFindItAttempts(key) > codingChallengeInfos.hints.length) {
if (vulnLines.length === 1) {
Expand Down
16 changes: 14 additions & 2 deletions rsn/rsnUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential file inclusion attack via reading file - medium severity
If an attacker can control the input leading into the ReadFile function, they might be able to read sensitive files and launch further attacks with that information.

Show fix

Remediation: Ignore this issue only after you've verified or sanitized the input going into this function. This issue is only relevant in the backend, not in the frontend!

Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

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))
Expand Down