-
-
Notifications
You must be signed in to change notification settings - Fork 136
Expand file tree
/
Copy pathindex.ts
More file actions
143 lines (129 loc) · 4.65 KB
/
index.ts
File metadata and controls
143 lines (129 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
* Copyright (c) 2016-2025 Bjoern Kimminich & the OWASP Juice Shop contributors.
* SPDX-License-Identifier: MIT
*/
import colors from 'colors' // no assignment necessary as this module extends the String prototype
import inquirer from 'inquirer'
import fetchSecretKey from './lib/fetchSecretKey'
import fetchChallenges from './lib/fetchChallenges'
import fetchHints from './lib/fetchHints'
import fetchCountryMapping from './lib/fetchCountryMapping'
import readConfigStream from './lib/readConfigStream'
import { options as juiceShopOptions } from './lib/options'
import * as fs from 'fs'
import generateCtfExport from './lib/generators/'
import yargs from 'yargs'
interface Argv {
config?: string
output?: string
ignoreSslWarnings?: boolean
}
const argv = yargs
.option('config', {
alias: 'c',
describe: 'provide a configuration file',
type: 'string'
})
.option('output', {
alias: 'o',
describe: 'change the output file',
type: 'string'
})
.option('ignoreSslWarnings', {
alias: 'i',
describe: 'ignore tls certificate warnings',
type: 'boolean'
})
.help()
.argv as Argv
const DEFAULT_JUICE_SHOP_URL = 'http://localhost:3000/'
const questions = [
{
type: 'list',
name: 'ctfFramework',
message: 'CTF framework to generate data for?',
choices: [juiceShopOptions.ctfdFramework, juiceShopOptions.fbctfFramework, juiceShopOptions.rtbFramework],
default: 0
},
{
type: 'input',
name: 'juiceShopUrl',
message: 'Juice Shop URL to retrieve challenges?',
default: DEFAULT_JUICE_SHOP_URL
},
{
type: 'input',
name: 'ctfKey',
message: 'URL to ctf.key file <or> secret key <or> (CTFd only) comma-separated list of secret keys?',
default: 'https://raw.githubusercontent.com/juice-shop/juice-shop/master/ctf.key'
},
{
type: 'input',
name: 'countryMapping',
message: 'URL to country-mapping.yml file?',
default: 'https://raw.githubusercontent.com/juice-shop/juice-shop/master/config/fbctf.yml',
when: ({ ctfFramework }: { ctfFramework: string }) => ctfFramework === juiceShopOptions.fbctfFramework
},
{
type: 'list',
name: 'insertHints',
message: 'Insert a list of hints along with each challenge?',
choices: [juiceShopOptions.noHints, juiceShopOptions.freeHints, juiceShopOptions.paidHints],
default: 0
}
]
interface ConfigAnswers {
ctfFramework: string
juiceShopUrl: string
ctfKey: string
countryMapping?: string
insertHints: typeof juiceShopOptions.freeHints | typeof juiceShopOptions.paidHints | typeof juiceShopOptions.noHints
}
async function getConfig (
argv: Argv,
questions: Array<Record<string, any>>
): Promise<ConfigAnswers> {
if (argv.config != null && argv.config !== '') {
return await readConfigStream(fs.createReadStream(argv.config)).then((config: any) => ({
ctfFramework: config.ctfFramework ?? juiceShopOptions.ctfdFramework,
juiceShopUrl: config.juiceShopUrl,
ctfKey: config.ctfKey,
countryMapping: config.countryMapping,
insertHints: config.insertHints
}))
}
return await inquirer.prompt(questions)
}
export default async function juiceShopCtfCli (): Promise<void> {
console.log()
console.log(`Generate ${colors.bold('OWASP Juice Shop')} challenge archive for setting up ${colors.bold(juiceShopOptions.ctfdFramework)}, ${colors.bold(juiceShopOptions.fbctfFramework)}, or ${colors.bold(juiceShopOptions.rtbFramework)} score server`)
try {
const answers = await getConfig(argv, questions)
console.log()
// Only fetch hints if user wants them
const shouldFetchHints = answers.insertHints !== juiceShopOptions.noHints
// Prepare fetch operations
const fetchOperations = [
fetchSecretKey(answers.ctfKey, argv.ignoreSslWarnings ?? false),
fetchChallenges(answers.juiceShopUrl, argv.ignoreSslWarnings ?? false),
fetchHints(answers.juiceShopUrl, argv.ignoreSslWarnings ?? false, !shouldFetchHints),
fetchCountryMapping(answers.countryMapping !== undefined && answers.countryMapping !== '' ? answers.countryMapping : '', argv.ignoreSslWarnings ?? false)
] as const
const [fetchedSecretKey, challenges, hints, countryMapping] = await Promise.all(fetchOperations)
await generateCtfExport(
answers.ctfFramework ?? juiceShopOptions.ctfdFramework,
challenges,
hints,
{
juiceShopUrl: answers.juiceShopUrl,
insertHints: answers.insertHints,
ctfKey: fetchedSecretKey ?? '',
countryMapping,
outputLocation: argv.output ?? ''
}
)
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
console.log('Failed to write output to file!'.red, message.red)
}
}