UNPKG

juice-shop-ctf-cli

Version:

Capture-the-Flag (CTF) environment setup tools for OWASP Juice Shop

91 lines (85 loc) 3.79 kB
/* * Copyright (c) 2016-2025 Bjoern Kimminich & the OWASP Juice Shop contributors. * SPDX-License-Identifier: MIT */ const Promise = require('bluebird') const calculateScore = require('../calculateScore') const calculateHintCost = require('../calculateHintCost') const hmacSha1 = require('../hmac') const options = require('../options') function createCtfdExport (challenges, { insertHints, insertHintUrls, insertHintSnippets, ctfKey, vulnSnippets }) { function insertChallengeHints (challenge) { const hints = [] if (challenge.hint && insertHints !== options.noTextHints) { hints.push(challenge.hint.replaceAll('"', '""').replaceAll(',', '٬')) } if (challenge.hintUrl && insertHintUrls !== options.noHintUrls) { hints.push(challenge.hintUrl) } if (vulnSnippets[challenge.key] && insertHintSnippets !== options.noHintSnippets) { hints.push('<pre><code>' + vulnSnippets[challenge.key].replaceAll('"', '""').replaceAll(',', '٬') + '</code></pre>') } return hints } function insertChallengeHintCosts (challenge) { const hintCosts = [] if (challenge.hint && insertHints !== options.noTextHints) { hintCosts.push(calculateHintCost(challenge, insertHints)) } if (challenge.hintUrl && insertHintUrls !== options.noHintUrls) { hintCosts.push(calculateHintCost(challenge, insertHintUrls)) } if (vulnSnippets[challenge.key] && insertHintSnippets !== options.noHintSnippets) { hintCosts.push(calculateHintCost(challenge, insertHintSnippets)) } return hintCosts } // In the flags section of the returned data we iterate through the result of string splitting by comma, and compute the hash of the single flag key + challenge name. // Format expected is: challenge3,challenge description,category3,100,dynamic,visible,0,"flag1,flag2,flag3","tag1,tag2,tag3","hint1,hint2,hint3","{""initial"":100, ""minimum"":10, ""decay"":10}" // If we provide a single key with no commas, we do not incapsulate the output in a "" pair. return new Promise((resolve, reject) => { try { const data = [] for (const key in challenges) { if (Object.prototype.hasOwnProperty.call(challenges, key)) { const challenge = challenges[key] const row = { name: challenge.name, description: `"${challenge.description.replaceAll('"', '""')} (Difficulty Level: ${challenge.difficulty})"`, category: challenge.category, value: calculateScore(challenge.difficulty), type: 'standard', state: 'visible', max_attempts: 0, flags: ctfKey.split(',').length === 1 ? hmacSha1(ctfKey, challenge.name) : `"${ctfKey.split(',').map(key => `${hmacSha1(key, challenge.name)}`).join(',')}"`, tags: challenge.tags ? `"${challenge.tags}"` : '', hints_raw: insertChallengeHints(challenge), hint_cost: insertChallengeHintCosts(challenge), type_data: '' } const hints = [] if (row.hints_raw.length !== 0) { for (let index = 0; index < row.hints_raw.length; index++) { const hint = { content: row.hints_raw[index], cost: row.hint_cost[index] } hints.push(hint) } const hintsObject = JSON.stringify(hints).replace(/"/g, '""') row.hints = `"${hintsObject}"` } else { row.hints = '' } delete row.hints_raw delete row.hint_cost data.push(row) } } resolve(data) } catch (error) { reject(new Error('Failed to generate challenge data! ' + error.message)) } }) } module.exports = createCtfdExport