juice-shop
111 строк · 4.6 Кб
1/*
2* Copyright (c) 2014-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
3* SPDX-License-Identifier: MIT
4*/
5
6import os from 'os'7import fs = require('fs')8import challengeUtils = require('../lib/challengeUtils')9import { type NextFunction, type Request, type Response } from 'express'10import path from 'path'11import * as utils from '../lib/utils'12import { challenges } from '../data/datacache'13
14const libxml = require('libxmljs')15const vm = require('vm')16const unzipper = require('unzipper')17
18function ensureFileIsPassed ({ file }: Request, res: Response, next: NextFunction) {19if (file != null) {20next()21}22}
23
24function handleZipFileUpload ({ file }: Request, res: Response, next: NextFunction) {25if (utils.endsWith(file?.originalname.toLowerCase(), '.zip')) {26if (((file?.buffer) != null) && utils.isChallengeEnabled(challenges.fileWriteChallenge)) {27const buffer = file.buffer28const filename = file.originalname.toLowerCase()29const tempFile = path.join(os.tmpdir(), filename)30fs.open(tempFile, 'w', function (err, fd) {31if (err != null) { next(err) }32fs.write(fd, buffer, 0, buffer.length, null, function (err) {33if (err != null) { next(err) }34fs.close(fd, function () {35fs.createReadStream(tempFile)36.pipe(unzipper.Parse())37.on('entry', function (entry: any) {38const fileName = entry.path39const absolutePath = path.resolve('uploads/complaints/' + fileName)40challengeUtils.solveIf(challenges.fileWriteChallenge, () => { return absolutePath === path.resolve('ftp/legal.md') })41if (absolutePath.includes(path.resolve('.'))) {42entry.pipe(fs.createWriteStream('uploads/complaints/' + fileName).on('error', function (err) { next(err) }))43} else {44entry.autodrain()45}46}).on('error', function (err: unknown) { next(err) })47})48})49})50}51res.status(204).end()52} else {53next()54}55}
56
57function checkUploadSize ({ file }: Request, res: Response, next: NextFunction) {58if (file != null) {59challengeUtils.solveIf(challenges.uploadSizeChallenge, () => { return file?.size > 100000 })60}61next()62}
63
64function checkFileType ({ file }: Request, res: Response, next: NextFunction) {65const fileType = file?.originalname.substr(file.originalname.lastIndexOf('.') + 1).toLowerCase()66challengeUtils.solveIf(challenges.uploadTypeChallenge, () => {67return !(fileType === 'pdf' || fileType === 'xml' || fileType === 'zip')68})69next()70}
71
72function handleXmlUpload ({ file }: Request, res: Response, next: NextFunction) {73if (utils.endsWith(file?.originalname.toLowerCase(), '.xml')) {74challengeUtils.solveIf(challenges.deprecatedInterfaceChallenge, () => { return true })75if (((file?.buffer) != null) && utils.isChallengeEnabled(challenges.deprecatedInterfaceChallenge)) { // XXE attacks in Docker/Heroku containers regularly cause "segfault" crashes76const data = file.buffer.toString()77try {78const sandbox = { libxml, data }79vm.createContext(sandbox)80const xmlDoc = vm.runInContext('libxml.parseXml(data, { noblanks: true, noent: true, nocdata: true })', sandbox, { timeout: 2000 })81const xmlString = xmlDoc.toString(false)82challengeUtils.solveIf(challenges.xxeFileDisclosureChallenge, () => { return (utils.matchesEtcPasswdFile(xmlString) || utils.matchesSystemIniFile(xmlString)) })83res.status(410)84next(new Error('B2B customer complaints via file upload have been deprecated for security reasons: ' + utils.trunc(xmlString, 400) + ' (' + file.originalname + ')'))85} catch (err: any) { // TODO: Remove any86if (utils.contains(err.message, 'Script execution timed out')) {87if (challengeUtils.notSolved(challenges.xxeDosChallenge)) {88challengeUtils.solve(challenges.xxeDosChallenge)89}90res.status(503)91next(new Error('Sorry, we are temporarily not available! Please try again later.'))92} else {93res.status(410)94next(new Error('B2B customer complaints via file upload have been deprecated for security reasons: ' + err.message + ' (' + file.originalname + ')'))95}96}97} else {98res.status(410)99next(new Error('B2B customer complaints via file upload have been deprecated for security reasons (' + file?.originalname + ')'))100}101}102res.status(204).end()103}
104
105module.exports = {106ensureFileIsPassed,107handleZipFileUpload,108checkUploadSize,109checkFileType,110handleXmlUpload
111}
112