juice-shop-ctf-cli
Version:
Capture-the-Flag (CTF) environment setup tools for OWASP Juice Shop
223 lines (192 loc) • 8.74 kB
JavaScript
/*
* Copyright (c) 2016-2025 Bjoern Kimminich & the OWASP Juice Shop contributors.
* SPDX-License-Identifier: MIT
*/
const chai = require('chai')
chai.use(require('chai-as-promised'))
const expect = chai.expect
const inquirer = require('inquirer-test')
const run = inquirer
const ENTER = inquirer.ENTER
const DOWN = inquirer.DOWN
const fs = require('fs')
const path = require('path')
const dateFormat = require('dateformat')
const outputFile = 'OWASP_Juice_Shop.' + dateFormat(new Date(), 'yyyy-mm-dd') + '.CTFd.csv'
const desiredCtfdOutputFile = './output.zip'
const desiredFbctfOutputFile = './output.json'
const desiredRtbOutputFile = './output.xml'
const configFile = 'config.yml'
const util = require('util')
const execFile = util.promisify(require('child_process').execFile)
const TIMEOUT = 45000
const juiceShopCtfCli = [path.join(__dirname, '../../bin/juice-shop-ctf.js')]
function cleanup () {
if (fs.existsSync(outputFile)) {
fs.unlinkSync(outputFile)
}
if (fs.existsSync(configFile)) {
fs.unlinkSync(configFile)
}
if (fs.existsSync(desiredCtfdOutputFile)) {
fs.unlinkSync(desiredCtfdOutputFile)
}
if (fs.existsSync(desiredFbctfOutputFile)) {
fs.unlinkSync(desiredFbctfOutputFile)
}
if (fs.existsSync(desiredRtbOutputFile)) {
fs.unlinkSync(desiredRtbOutputFile)
}
}
describe('juice-shop-ctf', () => {
beforeEach(cleanup)
after(cleanup)
it('should accept defaults for all input questions', function () {
this.timeout(TIMEOUT)
return expect(run(juiceShopCtfCli, [ENTER, ENTER, ENTER, ENTER, ENTER, ENTER], 2000)).to
.eventually.match(/Backup archive written to /i).and
.eventually.match(/Insert a text hint along with each challenge\? No text hints/i).and
.eventually.match(/Insert a hint URL along with each challenge\? No hint URLs/i).and
.eventually.match(/Insert a code snippet as hint for each challenge\? No hint snippets/i)
})
it('should insert free hints when chosen', function () {
this.timeout(TIMEOUT)
return expect(run(juiceShopCtfCli, [ENTER, ENTER, ENTER, DOWN, ENTER, ENTER, ENTER], 2000)).to
.eventually.match(/Insert a text hint along with each challenge\? Free text hints/i)
})
it('should insert paid hints when chosen', function () {
this.timeout(TIMEOUT)
return expect(run(juiceShopCtfCli, [ENTER, ENTER, ENTER, DOWN, DOWN, ENTER, ENTER, ENTER], 2000)).to
.eventually.match(/Insert a text hint along with each challenge\? Paid text hints/i)
})
it('should insert free hint URLs when chosen', function () {
this.timeout(TIMEOUT)
return expect(run(juiceShopCtfCli, [ENTER, ENTER, ENTER, ENTER, DOWN, ENTER, ENTER], 2000)).to
.eventually.match(/Insert a hint URL along with each challenge\? Free hint URLs/i)
})
it('should insert paid hint URLs when chosen', function () {
this.timeout(TIMEOUT)
return expect(run(juiceShopCtfCli, [ENTER, ENTER, ENTER, ENTER, DOWN, DOWN, ENTER, ENTER], 2000)).to
.eventually.match(/Insert a hint URL along with each challenge\? Paid hint URLs/i)
})
it('should fail on invalid Juice Shop URL', function () {
this.timeout(TIMEOUT)
return expect(run(juiceShopCtfCli, [ENTER, 'localhorst', ENTER, ENTER, ENTER, ENTER, ENTER], 2000)).to
.eventually.match(/Failed to fetch challenges from API!/i)
})
it('should fail on invalid ctf.key URL', function () {
this.timeout(TIMEOUT)
return expect(run(juiceShopCtfCli, [ENTER, ENTER, 'httpx://invalid/ctf-key', ENTER, ENTER, ENTER, ENTER], 2000)).to
.eventually.match(/Failed to fetch secret key from URL!/i)
})
it('should generate a FBCTF export when choosen', function () {
this.timeout(TIMEOUT)
return expect(run(juiceShopCtfCli, [DOWN, ENTER, ENTER, ENTER, ENTER, ENTER, ENTER], 2000)).to
.eventually.match(/CTF framework to generate data for\? FBCTF/i)
})
it('should generate a RootTheBox export when choosen', function () {
this.timeout(TIMEOUT)
return expect(run(juiceShopCtfCli, [DOWN, DOWN, ENTER, ENTER, ENTER, ENTER, ENTER, ENTER], 1500)).to
.eventually.match(/CTF framework to generate data for\? RootTheBox/i)
})
it('should accept a config file', function () {
fs.writeFileSync(configFile, `
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: https://raw.githubusercontent.com/bkimminich/juice-shop/master/ctf.key
insertHints: paid
insertHintUrls: paid
insertHintSnippets: paid`)
this.timeout(TIMEOUT)
return expect(execFile('node', [juiceShopCtfCli[0], '--config', configFile]).then(obj => obj.stdout)).to
.eventually.match(/Backup archive written to /i)
})
it('should be able to ignore SslWarnings', function () {
fs.writeFileSync(configFile, `
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: https://raw.githubusercontent.com/bkimminich/juice-shop/master/ctf.key
insertHints: paid
insertHintUrls: paid
insertHintSnippets: paid`)
this.timeout(TIMEOUT)
return expect(execFile('node', [juiceShopCtfCli[0], '--config', configFile, '--ignoreSslWarnings']).then(obj => obj.stdout)).to
.eventually.match(/Backup archive written to /i)
})
it('should fail when the config file cannot be parsed', function () {
fs.writeFileSync(configFile, `
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: https://raw.githubusercontent.com/bkimminich/juice-shop/master/ctf.key
insertHints`)
this.timeout(TIMEOUT)
return expect(execFile('node', [juiceShopCtfCli[0], '--config', configFile]).then(obj => obj.stdout)).to
.eventually.match(/can not read /i)
})
it('should fail when the config file contains invalid values', function () {
fs.writeFileSync(configFile, `
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: https://raw.githubusercontent.com/bkimminich/juice-shop/master/ctf.key
insertHints: paid
insertHintUrls: invalidValue
insertHintSnippets: paid`)
this.timeout(TIMEOUT)
return expect(execFile('node', [juiceShopCtfCli[0], '--config', configFile]).then(obj => obj.stdout)).to
.eventually.match(/"insertHintUrls" must be one of /i)
})
it('should write the output file to the specified location', function () {
fs.writeFileSync(configFile, `
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: https://raw.githubusercontent.com/bkimminich/juice-shop/master/ctf.key
insertHints: paid
insertHintUrls: paid
insertHintSnippets: paid`)
this.timeout(TIMEOUT)
return expect(execFile('node', [juiceShopCtfCli[0], '--config', configFile, '--output', desiredCtfdOutputFile])
.then(() => fs.existsSync(desiredCtfdOutputFile))).to
.eventually.equal(true)
})
it('should be possible to create a CTFd export with a config file', function () {
fs.writeFileSync(configFile, `
ctfFramework: CTFd
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: https://raw.githubusercontent.com/bkimminich/juice-shop/master/ctf.key
insertHints: paid
insertHintUrls: paid
insertHintSnippets: paid`)
this.timeout(TIMEOUT)
return expect(execFile('node', [juiceShopCtfCli[0], '--config', configFile, '--output', desiredCtfdOutputFile])
.then(() => fs.existsSync(desiredCtfdOutputFile))).to
.eventually.equal(true)
})
it('should be possible to create a FBCTF export with a config file', function () {
fs.writeFileSync(configFile, `
ctfFramework: FBCTF
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: https://raw.githubusercontent.com/bkimminich/juice-shop/master/ctf.key
countryMapping: https://raw.githubusercontent.com/bkimminich/juice-shop/master/config/fbctf.yml
insertHints: paid
insertHintUrls: paid
insertHintSnippets: paid`)
this.timeout(TIMEOUT)
return expect(execFile('node', [juiceShopCtfCli[0], '--config', configFile, '--output', desiredFbctfOutputFile])
.then(() => fs.existsSync(desiredFbctfOutputFile))).to
.eventually.equal(true)
})
it('should be possible to create a RootTheBox export with a config file', function () {
fs.writeFileSync(configFile, `
ctfFramework: RootTheBox
juiceShopUrl: https://juice-shop.herokuapp.com
ctfKey: https://raw.githubusercontent.com/bkimminich/juice-shop/master/ctf.key
insertHints: paid
insertHintUrls: paid
insertHintSnippets: paid`)
this.timeout(TIMEOUT)
return expect(execFile('node', [juiceShopCtfCli[0], '--config', configFile, '--output', desiredRtbOutputFile])
.then(() => fs.existsSync(desiredRtbOutputFile))).to
.eventually.equal(true)
})
it('should fail when output file cannot be written', function () {
this.timeout(TIMEOUT)
fs.openSync(outputFile, 'w', 0)
return expect(run(juiceShopCtfCli, [ENTER, ENTER, ENTER, ENTER, ENTER, ENTER], 2000)).to
.eventually.match(/Failed to write output to file!/i)
})
})