Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
bkimminich committed Jan 29, 2019
2 parents e572ac5 + 079c87a commit cbaf33f
Show file tree
Hide file tree
Showing 22 changed files with 497 additions and 77 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
FROM node:9 as installer
FROM node:10 as installer
COPY . /juice-shop-ctf
WORKDIR /juice-shop-ctf
RUN chown -R node .
USER node
ARG DEV_BUILD=false
RUN if [ ${DEV_BUILD} = true ]; then npm i && npm test && npm run e2e; else npm install --production --unsafe-perm; fi
RUN if [ ${DEV_BUILD} = true ]; then npm i && npm lint && npm test && npm run e2e; else npm install --production --unsafe-perm; fi

FROM node:9-alpine
ARG BUILD_DATE
Expand All @@ -16,7 +16,7 @@ LABEL maintainer="Bjoern Kimminich <[email protected]>" \
org.opencontainers.image.vendor="Open Web Application Security Project" \
org.opencontainers.image.documentation="http://help.owasp-juice.shop/part1/ctf.html" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.version="5.0.1" \
org.opencontainers.image.version="6.0.0" \
org.opencontainers.image.url="http://owasp-juice.shop" \
org.opencontainers.image.source="https://github.com/bkimminich/juice-shop-ctf.git" \
org.opencontainers.image.revision=$VCS_REF \
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ helps you to prepare [Capture the Flag](https://en.wikipedia.org/wiki/Capture_th
The following open source CTF frameworks are supported by
`juice-shop-ctf-cli`:

* [CTFd](https://ctfd.io/)
* [CTFd 2.x](https://github.com/CTFd/CTFd/releases/latest)
* [CTFd 1.x](https://ctfd.io/)
([1.1.x](https://github.com/CTFd/CTFd/releases/tag/1.1.4) or [1.2.x](https://github.com/CTFd/CTFd/releases/tag/1.2.0))
* [FBCTF](https://github.com/facebook/fbctf)

Expand Down Expand Up @@ -46,12 +47,12 @@ Instead of answering questions in the CLI you can also provide your
desired configuration in a file with the following format:

```yaml
ctfFramework: CTFd | FBCTF
ctfFramework: CTFd 2.x | CTFd 1.x | FBCTF
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: https://raw.githubusercontent.com/bkimminich/juice-shop/master/ctf.key # can also be actual key instead URL
countryMapping: https://raw.githubusercontent.com/bkimminich/juice-shop/master/config/fbctf.yml
countryMapping: https://raw.githubusercontent.com/bkimminich/juice-shop/master/config/fbctf.yml # ignored for CTFd
insertHints: none | free | paid
insertHintUrls: none | free | paid
insertHintUrls: none | free | paid # optional for FBCTF
```
You can then run the generator with:
Expand Down
6 changes: 5 additions & 1 deletion data/fbctfImportTemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,10 @@
"category": "Difficulty 5",
"protected": false
},
{
"category": "Difficulty 6",
"protected": false
},
{
"category": "None",
"protected": true
Expand All @@ -1657,4 +1661,4 @@
"levels": {
"levels": []
}
}
}
10 changes: 4 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ const readConfigStream = require('./lib/readConfigStream')
const fs = require('fs')
const options = require('./lib/options')

const generateCTFExport = require('./lib/generators/')

const ctfdCompatibleVersion = '1.1.x or 1.2.x'
const generateCtfExport = require('./lib/generators/')

const argv = require('yargs')
.option('config', {
Expand All @@ -28,7 +26,7 @@ const questions = [
type: 'list',
name: 'ctfFramework',
message: 'CTF framework to generate data for?',
choices: [options.ctfdFramework, options.fbctfFramework],
choices: [options.ctfd2Framework, options.ctfdFramework, options.fbctfFramework],
default: 0
},
{
Expand Down Expand Up @@ -75,7 +73,7 @@ function getConfig (argv, questions) {

const juiceShopCtfCli = async () => {
console.log()
console.log('Generate ' + 'OWASP Juice Shop'.bold + ' challenge archive for setting up ' + options.ctfdFramework.bold + ' (' + ctfdCompatibleVersion + ') or ' + options.fbctfFramework.bold + ' score server')
console.log('Generate ' + 'OWASP Juice Shop'.bold + ' challenge archive for setting up ' + options.ctfdFramework.bold + ', ' + options.ctfd2Framework.bold + ' or ' + options.fbctfFramework.bold + ' score server')

try {
const answers = await getConfig(argv, questions)
Expand All @@ -88,7 +86,7 @@ const juiceShopCtfCli = async () => {
fetchCountryMapping(answers.countryMapping)
])

await generateCTFExport(answers.ctfFramework || options.ctfdFramework, challenges, {
await generateCtfExport(answers.ctfFramework || options.ctfdFramework, challenges, {
insertHints: answers.insertHints,
insertHintUrls: answers.insertHintUrls,
ctfKey: fetchedSecretKey,
Expand Down
4 changes: 2 additions & 2 deletions lib/generators/ctfd.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const calculateHintCost = require('../calculateHintCost')
const hmacSha1 = require('../hmac')
const options = require('../options')

function createCTFdExport (challenges, { insertHints, insertHintUrls, ctfKey }) {
function createCtfdExport (challenges, { insertHints, insertHintUrls, ctfKey }) {
function insertChallenge (data, challenge) {
const score = calculateScore(challenge.difficulty)
data.challenges['results'].push(
Expand Down Expand Up @@ -84,4 +84,4 @@ function createCTFdExport (challenges, { insertHints, insertHintUrls, ctfKey })
})
}

module.exports = createCTFdExport
module.exports = createCtfdExport
87 changes: 87 additions & 0 deletions lib/generators/ctfd2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const Promise = require('bluebird')
const calculateScore = require('../calculateScore')
const calculateHintCost = require('../calculateHintCost')
const hmacSha1 = require('../hmac')
const options = require('../options')

function createCtfd2Export (challenges, { insertHints, insertHintUrls, ctfKey }) {
function insertChallenge (data, challenge) {
const score = calculateScore(challenge.difficulty)
data.challenges['results'].push(
{
'id': challenge.id,
'name': challenge.name,
'description': challenge.description.replace(/"/g, '""') + ' (Difficulty Level: ' + challenge.difficulty + ')',
'max_attempts': 0,
'value': score,
'category': challenge.category,
'type': 'standard',
'state': 'visible'
}
)
}

function insertKey ({ flagKeys }, { id, name }) {
flagKeys['results'].push(
{
'id': id,
'challenge_id': id,
'type': 'static',
'content': hmacSha1(ctfKey, name),
'data': null
}
)
}

function insertTextHint ({ hints }, challenge) {
hints['results'].push(
{
'id': challenge.id,
'type': 'standard',
'challenge_id': challenge.id,
'content': challenge.hint,
'cost': calculateHintCost(challenge, insertHints)
}
)
}

function insertHintUrl ({ hints }, challenge) {
hints['results'].push(
{
'id': 10000 + challenge.id,
'type': 'standard',
'challenge_id': challenge.id,
'content': challenge.hintUrl,
'cost': calculateHintCost(challenge, insertHintUrls)
}
)
}

return new Promise((resolve, reject) => {
try {
const data = {
challenges: { 'results': [] },
hints: { 'results': [] },
flagKeys: { 'results': [] }
}
for (const key in challenges) {
if (challenges.hasOwnProperty(key)) {
const challenge = challenges[key]
insertChallenge(data, challenge)
insertKey(data, challenge)
if (challenge.hint && insertHints !== options.noTextHints) {
insertTextHint(data, challenge)
}
if (challenge.hintUrl && insertHintUrls !== options.noHintUrls) {
insertHintUrl(data, challenge)
}
}
}
resolve(data)
} catch (error) {
reject(new Error('Failed to generate challenge data! ' + error.message))
}
})
}

module.exports = createCtfd2Export
4 changes: 2 additions & 2 deletions lib/generators/fbctf.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async function createDummyUser () {
}
}

async function createFBCTFExport (challenges, { insertHints, insertHintUrls, ctfKey, countryMapping }) {
async function createFbctfExport (challenges, { insertHints, insertHintUrls, ctfKey, countryMapping }) {
const fbctfTemplate = await loadTemplate()

fbctfTemplate.teams.teams.push(await createDummyUser())
Expand Down Expand Up @@ -89,4 +89,4 @@ async function createFBCTFExport (challenges, { insertHints, insertHintUrls, ctf
return fbctfTemplate
}

module.exports = createFBCTFExport
module.exports = createFbctfExport
32 changes: 23 additions & 9 deletions lib/generators/index.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
const writeToZipFile = require('../writeToZipFile')
const writeToJsonFile = require('../writeToJsonFile')
const writeToCtfdZip = require('../writeToCtfdZip')
const writeToCtfd2Zip = require('../writeToCtfd2Zip')
const writeToFbctfJson = require('../writeToFbctfJson')
const options = require('../options')

const createCTFdExport = require('./ctfd')
const createFBCTFExport = require('./fbctf')
const createCtfdExport = require('./ctfd')
const createCtfd2Export = require('./ctfd2')
const createFbctfExport = require('./fbctf')

async function generateCTFExport (ctfFramework, challenges, settings) {
switch (ctfFramework) {
case options.ctfd2Framework:
const ctfd2Data = await createCtfd2Export(challenges, settings)
const ctfd2File = await writeToCtfd2Zip(ctfd2Data, settings.outputLocation)

console.log('Backup archive written to ' + ctfd2File)
console.log()
console.log('You can dismiss the potential '.cyan + 'Internal Server Error'.italic + ' alert popup after import.'.cyan)
console.log('Simply restart CTFd and set up CTF name and administrator credentials again.'.cyan)
console.log()
console.log('For a step-by-step guide to import the ZIP-archive into ' + 'CTFd 2.x'.bold + ', please refer to')
console.log('https://bkimminich.gitbooks.io/pwning-owasp-juice-shop/content/part1/ctf.html#running-ctfd'.bold)
break
case options.ctfdFramework:
const ctfdData = await createCTFdExport(challenges, settings)
const ctfdFile = await writeToZipFile(ctfdData, settings.outputLocation)
const ctfdData = await createCtfdExport(challenges, settings)
const ctfdFile = await writeToCtfdZip(ctfdData, settings.outputLocation)

console.log('Backup archive written to ' + ctfdFile)
console.log()
console.log('For a step-by-step guide to import the ZIP-archive into ' + 'CTFd'.bold + ', please refer to')
console.log('For a step-by-step guide to import the ZIP-archive into ' + 'CTFd 1.x'.bold + ', please refer to')
console.log('https://bkimminich.gitbooks.io/pwning-owasp-juice-shop/content/part1/ctf.html#running-ctfd'.bold)
break
case options.fbctfFramework:
const fbctfData = await createFBCTFExport(challenges, settings)
const fbctfFile = await writeToJsonFile(fbctfData, settings.outputLocation)
const fbctfData = await createFbctfExport(challenges, settings)
const fbctfFile = await writeToFbctfJson(fbctfData, settings.outputLocation)

console.log('Full Game Export written to ' + fbctfFile)
console.log()
Expand Down
3 changes: 2 additions & 1 deletion lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
noHintUrls: 'No hint URLs',
freeHintUrls: 'Free hint URLs',
paidHintUrls: 'Paid hint URLs',
ctfdFramework: 'CTFd',
ctfdFramework: 'CTFd 1.x',
ctfd2Framework: 'CTFd 2.x',
fbctfFramework: 'FBCTF'
}
6 changes: 3 additions & 3 deletions lib/readConfigStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ const yaml = require('js-yaml')
const Joi = require('joi')
const options = require('./options')
const schema = Joi.object().keys({
ctfFramework: Joi.string().optional().valid(options.ctfdFramework, options.fbctfFramework),
ctfFramework: Joi.string().optional().valid(options.ctfd2Framework, options.ctfdFramework, options.fbctfFramework),
juiceShopUrl: [Joi.string().uri().required(), Joi.string().ip().required()],
countryMapping: Joi.string().when('ctfFramework', { is: options.fbctfFramework, then: Joi.required(), otherwise: Joi.optional() }),
ctfKey: Joi.string().required(),
insertHints: Joi.any().valid('none', 'free', 'paid').required(),
insertHintUrls: Joi.any().valid('none', 'free', 'paid').when('ctfFramework', { is: options.ctfdFramework, then: Joi.required(), otherwise: Joi.optional() })
insertHintUrls: Joi.any().valid('none', 'free', 'paid').when('ctfFramework', { is: options.fbctfFramework, then: Joi.optional(), otherwise: Joi.required() })
})

const hintsMap = { 'none': 0, 'free': 1, 'paid': 2 }
Expand All @@ -27,7 +27,7 @@ function readConfigStream (stream) {
} else {
const result = validation.value
result.insertHints = hintsMap[result.insertHints]
result.insertHintUrls = hintsMap[result.insertHintUrls]
result.insertHintUrls = result.insertHintUrls ? hintsMap[result.insertHintUrls] : 0
resolve(result)
}
})
Expand Down
24 changes: 24 additions & 0 deletions lib/writeToCtfd2Zip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const Promise = require('bluebird')
const fs = require('fs')
Promise.promisifyAll(fs)
const path = require('path')
const dateFormat = require('dateformat')
const Zip = require('node-zip')
const zip = new Zip()

function writeToCtfd3Zip ({ challenges, hints, flagKeys }, desiredFileName) {
return new Promise((resolve, reject) => {
const fileName = desiredFileName || 'OWASP_Juice_Shop.' + dateFormat(new Date(), 'yyyy-mm-dd') + '.CTFd2.zip'
zip.file('db/alembic_version.json', '{"count": 1, "results": [{"version_num": "8369118943a1"}], "meta": {}}')
zip.file('db/challenges.json', JSON.stringify(challenges))
zip.file('db/hints.json', JSON.stringify(hints))
zip.file('db/flags.json', JSON.stringify(flagKeys))
fs.writeFileAsync(fileName, zip.generate({ base64: false, compression: 'DEFLATE' }), 'binary').then(() => {
resolve(path.resolve(fileName).green)
}).catch(({ message }) => {
reject(new Error('Failed to write output to file! ' + message))
})
})
}

module.exports = writeToCtfd3Zip
4 changes: 2 additions & 2 deletions lib/writeToZipFile.js → lib/writeToCtfdZip.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const dateFormat = require('dateformat')
const Zip = require('node-zip')
const zip = new Zip()

function writeToZipFile ({ challenges, hints, flagKeys }, desiredFileName) {
function writeToCtfdZip ({ challenges, hints, flagKeys }, desiredFileName) {
return new Promise((resolve, reject) => {
const fileName = desiredFileName || 'OWASP_Juice_Shop.' + dateFormat(new Date(), 'yyyy-mm-dd') + '.CTFd.zip'
zip.file('db/challenges.json', JSON.stringify(challenges))
Expand All @@ -22,4 +22,4 @@ function writeToZipFile ({ challenges, hints, flagKeys }, desiredFileName) {
})
}

module.exports = writeToZipFile
module.exports = writeToCtfdZip
4 changes: 2 additions & 2 deletions lib/writeToJsonFile.js → lib/writeToFbctfJson.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Promise.promisifyAll(fs)
const path = require('path')
const dateFormat = require('dateformat')

async function writeToJsonFile (report, desiredFileName) {
async function writeToFbctfJson (report, desiredFileName) {
return new Promise((resolve, reject) => {
const fileName = desiredFileName || 'OWASP_Juice_Shop.' + dateFormat(new Date(), 'yyyy-mm-dd') + '.FBCTF.json'
fs.writeFileAsync(fileName, JSON.stringify(report, null, 2), { encoding: 'utf8' }).then(() => {
Expand All @@ -15,4 +15,4 @@ async function writeToJsonFile (report, desiredFileName) {
})
}

module.exports = writeToJsonFile
module.exports = writeToFbctfJson
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "juice-shop-ctf-cli",
"version": "5.0.2",
"version": "6.0.0",
"description": "Capture-the-Flag (CTF) environment setup tools for OWASP Juice Shop",
"author": "Bjoern Kimminich <[email protected]> (https://www.owasp.org/index.php/User:Bjoern_Kimminich)",
"contributors": [
Expand All @@ -21,6 +21,7 @@
"capture the flag",
"ctf",
"ctfd",
"fbctf",
"cli"
],
"preferGlobal": true,
Expand Down Expand Up @@ -53,7 +54,7 @@
"yargs": "~12.0.5",
"dateformat": "~3.0.3",
"inquirer": "~6.2.0",
"joi": "~14.2.0",
"joi": "~14.3.1",
"jssha": "~2.3.1",
"js-yaml": "~3.12.0",
"node-zip": "~1.1.1",
Expand All @@ -62,7 +63,7 @@
"request-promise": "~4.2.2"
},
"devDependencies": {
"ava": "~1.0.1",
"ava": "~1.1.0",
"chai": "~4.2.0",
"chai-as-promised": "~7.1.1",
"chai-spies": "~1.0.0",
Expand Down
Loading

0 comments on commit cbaf33f

Please sign in to comment.