juice-shop
234 строки · 7.0 Кб
1/*
2* Copyright (c) 2014-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
3* SPDX-License-Identifier: MIT
4*/
5
6/* jslint node: true */
7import packageJson from '../package.json'
8import fs from 'fs'
9import logger from './logger'
10import config from 'config'
11import jsSHA from 'jssha'
12import download from 'download'
13import crypto from 'crypto'
14import clarinet from 'clarinet'
15import type { Challenge } from 'data/types'
16
17import isHeroku from './is-heroku'
18import isDocker from './is-docker'
19import isWindows from './is-windows'
20export { default as isDocker } from './is-docker'
21export { default as isWindows } from './is-windows'
22// import isGitpod from 'is-gitpod') // FIXME Roll back to this when https://github.com/dword-design/is-gitpod/issues/94 is resolve
23const isGitpod = () => false
24
25const months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
26
27export const queryResultToJson = <T>(
28data: T,
29status: string = 'success'
30): { data: T, status: string } => {
31return {
32status,
33data
34}
35}
36
37export const isUrl = (url: string) => {
38return startsWith(url, 'http')
39}
40
41export const startsWith = (str: string, prefix: string) => str ? str.indexOf(prefix) === 0 : false
42
43export const endsWith = (str?: string, suffix?: string) => (str && suffix) ? str.includes(suffix, str.length - suffix.length) : false
44
45export const contains = (str: string, element: string) => str ? str.includes(element) : false // TODO Inline all usages as this function is not adding any functionality to String.includes
46
47export const containsEscaped = function (str: string, element: string) {
48return contains(str, element.replace(/"/g, '\\"'))
49}
50
51export const containsOrEscaped = function (str: string, element: string) {
52return contains(str, element) || containsEscaped(str, element)
53}
54
55export const unquote = function (str: string) {
56if (str && startsWith(str, '"') && endsWith(str, '"')) {
57return str.substring(1, str.length - 1)
58} else {
59return str
60}
61}
62
63export const trunc = function (str: string, length: number) {
64str = str.replace(/(\r\n|\n|\r)/gm, '')
65return (str.length > length) ? str.substr(0, length - 1) + '...' : str
66}
67
68export const version = (module?: string) => {
69if (module) {
70// @ts-expect-error FIXME Ignoring any type issue on purpose
71return packageJson.dependencies[module]
72} else {
73return packageJson.version
74}
75}
76
77let cachedCtfKey: string | undefined
78const getCtfKey = () => {
79if (!cachedCtfKey) {
80if (process.env.CTF_KEY !== undefined && process.env.CTF_KEY !== '') {
81cachedCtfKey = process.env.CTF_KEY
82} else {
83const data = fs.readFileSync('ctf.key', 'utf8')
84cachedCtfKey = data
85}
86}
87return cachedCtfKey
88}
89export const ctfFlag = (text: string) => {
90const shaObj = new jsSHA('SHA-1', 'TEXT') // eslint-disable-line new-cap
91shaObj.setHMACKey(getCtfKey(), 'TEXT')
92shaObj.update(text)
93return shaObj.getHMAC('HEX')
94}
95
96export const toMMMYY = (date: Date) => {
97const month = date.getMonth()
98const year = date.getFullYear()
99return months[month] + year.toString().substring(2, 4)
100}
101
102export const toISO8601 = (date: Date) => {
103let day = '' + date.getDate()
104let month = '' + (date.getMonth() + 1)
105const year = date.getFullYear()
106
107if (month.length < 2) month = '0' + month
108if (day.length < 2) day = '0' + day
109
110return [year, month, day].join('-')
111}
112
113export const extractFilename = (url: string) => {
114let file = decodeURIComponent(url.substring(url.lastIndexOf('/') + 1))
115if (contains(file, '?')) {
116file = file.substring(0, file.indexOf('?'))
117}
118return file
119}
120
121export const downloadToFile = async (url: string, dest: string) => {
122try {
123const data = await download(url)
124fs.writeFileSync(dest, data)
125} catch (err) {
126logger.warn('Failed to download ' + url + ' (' + getErrorMessage(err) + ')')
127}
128}
129
130export const jwtFrom = ({ headers }: { headers: any }) => {
131if (headers?.authorization) {
132const parts = headers.authorization.split(' ')
133if (parts.length === 2) {
134const scheme = parts[0]
135const token = parts[1]
136
137if (/^Bearer$/i.test(scheme)) {
138return token
139}
140}
141}
142return undefined
143}
144
145export const randomHexString = (length: number): string => {
146return crypto.randomBytes(Math.ceil(length / 2)).toString('hex').slice(0, length)
147}
148
149export interface ChallengeEnablementStatus {
150enabled: boolean
151disabledBecause: string | null
152}
153
154type SafetyModeSetting = 'enabled' | 'disabled' | 'auto'
155
156type isEnvironmentFunction = () => boolean
157
158export function getChallengeEnablementStatus (challenge: Challenge,
159safetyModeSetting: SafetyModeSetting = config.get<SafetyModeSetting>('challenges.safetyMode'),
160isEnvironmentFunctions: {
161isDocker: isEnvironmentFunction
162isHeroku: isEnvironmentFunction
163isWindows: isEnvironmentFunction
164isGitpod: isEnvironmentFunction
165} = { isDocker, isHeroku, isWindows, isGitpod }): ChallengeEnablementStatus {
166if (!challenge?.disabledEnv) {
167return { enabled: true, disabledBecause: null }
168}
169
170if (safetyModeSetting === 'disabled') {
171return { enabled: true, disabledBecause: null }
172}
173
174if (challenge.disabledEnv?.includes('Docker') && isEnvironmentFunctions.isDocker()) {
175return { enabled: false, disabledBecause: 'Docker' }
176}
177if (challenge.disabledEnv?.includes('Heroku') && isEnvironmentFunctions.isHeroku()) {
178return { enabled: false, disabledBecause: 'Heroku' }
179}
180if (challenge.disabledEnv?.includes('Windows') && isEnvironmentFunctions.isWindows()) {
181return { enabled: false, disabledBecause: 'Windows' }
182}
183if (challenge.disabledEnv?.includes('Gitpod') && isEnvironmentFunctions.isGitpod()) {
184return { enabled: false, disabledBecause: 'Gitpod' }
185}
186if (challenge.disabledEnv && safetyModeSetting === 'enabled') {
187return { enabled: false, disabledBecause: 'Safety Mode' }
188}
189
190return { enabled: true, disabledBecause: null }
191}
192export function isChallengeEnabled (challenge: Challenge): boolean {
193const { enabled } = getChallengeEnablementStatus(challenge)
194return enabled
195}
196
197export const parseJsonCustom = (jsonString: string) => {
198const parser = clarinet.parser()
199const result: any[] = []
200parser.onkey = parser.onopenobject = (k: any) => {
201result.push({ key: k, value: null })
202}
203parser.onvalue = (v: any) => {
204result[result.length - 1].value = v
205}
206parser.write(jsonString)
207parser.close()
208return result
209}
210
211export const toSimpleIpAddress = (ipv6: string) => {
212if (startsWith(ipv6, '::ffff:')) {
213return ipv6.substr(7)
214} else if (ipv6 === '::1') {
215return '127.0.0.1'
216} else {
217return ipv6
218}
219}
220
221export const getErrorMessage = (error: unknown) => {
222if (error instanceof Error) return error.message
223return String(error)
224}
225
226export const matchesSystemIniFile = (text: string) => {
227const match = text.match(/; for 16-bit app support/gi)
228return match !== null && match.length >= 1
229}
230
231export const matchesEtcPasswdFile = (text: string) => {
232const match = text.match(/(\w*:\w*:\d*:\d*:\w*:.*)|(Note that this file is consulted directly)/gi)
233return match !== null && match.length >= 1
234}
235