Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add utils and insecurity #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions lib/insecurity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* Copyright (c) 2014-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
* SPDX-License-Identifier: MIT
*/

import fs from 'fs'
import crypto from 'crypto'
import { type Request, type Response, type NextFunction } from 'express'
import { type UserModel } from 'models/user'
import expressJwt from 'express-jwt'
import jwt from 'jsonwebtoken'
import jws from 'jws'
import sanitizeHtmlLib from 'sanitize-html'
import sanitizeFilenameLib from 'sanitize-filename'
import * as utils from './utils'

/* jslint node: true */
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-expect-error FIXME no typescript definitions for z85 :(
import * as z85 from 'z85'

export const publicKey = fs ? fs.readFileSync('encryptionkeys/jwt.pub', 'utf8') : 'placeholder-public-key'
const privateKey = '-----BEGIN RSA PRIVATE KEY-----\r\nMIICXAIBAAKBgQDNwqLEe9wgTXCbC7+RPdDbBbeqjdbs4kOPOIGzqLpXvJXlxxW8iMz0EaM4BKUqYsIa+ndv3NAn2RxCd5ubVdJJcX43zO6Ko0TFEZx/65gY3BE0O6syCEmUP4qbSd6exou/F+WTISzbQ5FBVPVmhnYhG/kpwt/cIxK5iUn5hm+4tQIDAQABAoGBAI+8xiPoOrA+KMnG/T4jJsG6TsHQcDHvJi7o1IKC/hnIXha0atTX5AUkRRce95qSfvKFweXdJXSQ0JMGJyfuXgU6dI0TcseFRfewXAa/ssxAC+iUVR6KUMh1PE2wXLitfeI6JLvVtrBYswm2I7CtY0q8n5AGimHWVXJPLfGV7m0BAkEA+fqFt2LXbLtyg6wZyxMA/cnmt5Nt3U2dAu77MzFJvibANUNHE4HPLZxjGNXN+a6m0K6TD4kDdh5HfUYLWWRBYQJBANK3carmulBwqzcDBjsJ0YrIONBpCAsXxk8idXb8jL9aNIg15Wumm2enqqObahDHB5jnGOLmbasizvSVqypfM9UCQCQl8xIqy+YgURXzXCN+kwUgHinrutZms87Jyi+D8Br8NY0+Nlf+zHvXAomD2W5CsEK7C+8SLBr3k/TsnRWHJuECQHFE9RA2OP8WoaLPuGCyFXaxzICThSRZYluVnWkZtxsBhW2W8z1b8PvWUE7kMy7TnkzeJS2LSnaNHoyxi7IaPQUCQCwWU4U+v4lD7uYBw00Ga/xt+7+UqFPlPVdz1yyr4q24Zxaw0LgmuEvgU5dycq8N7JxjTubX0MIRR+G9fmDBBl8=\r\n-----END RSA PRIVATE KEY-----'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security control: Secret Detection

Type: Private-Key

Description: Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.

Severity: HIGH


Jit Bot commands and options (e.g., ignore issue)

You can trigger Jit actions by commenting on this PR review:

  • #jit_ignore_fp Ignore and mark this specific single instance of finding as “False Positive”
  • #jit_ignore_accept Ignore and mark this specific single instance of finding as “Accept Risk”
  • #jit_ignore_type_in_file Ignore any finding of type "private-key" in lib/insecurity.ts; future occurrences will also be ignored.
  • #jit_undo_ignore Undo ignore command


interface ResponseWithUser {
status: string
data: UserModel
iat: number
exp: number
bid: number
}

interface IAuthenticatedUsers {
tokenMap: Record<string, ResponseWithUser>
idMap: Record<string, string>
put: (token: string, user: ResponseWithUser) => void
get: (token: string) => ResponseWithUser | undefined
tokenOf: (user: UserModel) => string | undefined
from: (req: Request) => ResponseWithUser | undefined
updateFrom: (req: Request, user: ResponseWithUser) => any
}

export const hash = (data: string) => crypto.createHash('md5').update(data).digest('hex')
export const hmac = (data: string) => crypto.createHmac('sha256', 'pa4qacea4VK9t9nGv7yZtwmj').update(data).digest('hex')

export const cutOffPoisonNullByte = (str: string) => {
const nullByte = '%00'
if (utils.contains(str, nullByte)) {
return str.substring(0, str.indexOf(nullByte))
}
return str
}

export const isAuthorized = () => expressJwt(({ secret: publicKey }) as any)
export const denyAll = () => expressJwt({ secret: '' + Math.random() } as any)
export const authorize = (user = {}) => jwt.sign(user, privateKey, { expiresIn: '6h', algorithm: 'RS256' })
export const verify = (token: string) => token ? (jws.verify as ((token: string, secret: string) => boolean))(token, publicKey) : false
export const decode = (token: string) => { return jws.decode(token)?.payload }

export const sanitizeHtml = (html: string) => sanitizeHtmlLib(html)
export const sanitizeLegacy = (input = '') => input.replace(/<(?:\w+)\W+?[\w]/gi, '')
export const sanitizeFilename = (filename: string) => sanitizeFilenameLib(filename)
export const sanitizeSecure = (html: string): string => {
const sanitized = sanitizeHtml(html)
if (sanitized === html) {
return html
} else {
return sanitizeSecure(sanitized)
}
}

export const authenticatedUsers: IAuthenticatedUsers = {
tokenMap: {},
idMap: {},
put: function (token: string, user: ResponseWithUser) {
this.tokenMap[token] = user
this.idMap[user.data.id] = token
},
get: function (token: string) {
return token ? this.tokenMap[utils.unquote(token)] : undefined
},
tokenOf: function (user: UserModel) {
return user ? this.idMap[user.id] : undefined
},
from: function (req: Request) {
const token = utils.jwtFrom(req)
return token ? this.get(token) : undefined
},
updateFrom: function (req: Request, user: ResponseWithUser) {
const token = utils.jwtFrom(req)
this.put(token, user)
}
}

export const userEmailFrom = ({ headers }: any) => {
return headers ? headers['x-user-email'] : undefined
}

export const generateCoupon = (discount: number, date = new Date()) => {
const coupon = utils.toMMMYY(date) + '-' + discount
return z85.encode(coupon)
}

export const discountFromCoupon = (coupon: string) => {
if (coupon) {
const decoded = z85.decode(coupon)
if (decoded && (hasValidFormat(decoded.toString()) != null)) {
const parts = decoded.toString().split('-')
const validity = parts[0]
if (utils.toMMMYY(new Date()) === validity) {
const discount = parts[1]
return parseInt(discount)
}
}
}
return undefined
}

function hasValidFormat (coupon: string) {
return coupon.match(/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)[0-9]{2}-[0-9]{2}/)
}

// vuln-code-snippet start redirectCryptoCurrencyChallenge redirectChallenge
export const redirectAllowlist = new Set([
'https://github.com/juice-shop/juice-shop',
'https://blockchain.info/address/1AbKfgvw9psQ41NbLi8kufDQTezwG8DRZm', // vuln-code-snippet vuln-line redirectCryptoCurrencyChallenge
'https://explorer.dash.org/address/Xr556RzuwX6hg5EGpkybbv5RanJoZN17kW', // vuln-code-snippet vuln-line redirectCryptoCurrencyChallenge
'https://etherscan.io/address/0x0f933ab9fcaaa782d0279c300d73750e1311eae6', // vuln-code-snippet vuln-line redirectCryptoCurrencyChallenge
'http://shop.spreadshirt.com/juiceshop',
'http://shop.spreadshirt.de/juiceshop',
'https://www.stickeryou.com/products/owasp-juice-shop/794',
'http://leanpub.com/juice-shop'
])

export const isRedirectAllowed = (url: string) => {
let allowed = false
for (const allowedUrl of redirectAllowlist) {
allowed = allowed || url.includes(allowedUrl) // vuln-code-snippet vuln-line redirectChallenge
}
return allowed
}
// vuln-code-snippet end redirectCryptoCurrencyChallenge redirectChallenge

export const roles = {
customer: 'customer',
deluxe: 'deluxe',
accounting: 'accounting',
admin: 'admin'
}

export const deluxeToken = (email: string) => {
const hmac = crypto.createHmac('sha256', privateKey)
return hmac.update(email + roles.deluxe).digest('hex')
}

export const isAccounting = () => {
return (req: Request, res: Response, next: NextFunction) => {
const decodedToken = verify(utils.jwtFrom(req)) && decode(utils.jwtFrom(req))
if (decodedToken?.data?.role === roles.accounting) {
next()
} else {
res.status(403).json({ error: 'Malicious activity detected' })
}
}
}

export const isDeluxe = (req: Request) => {
const decodedToken = verify(utils.jwtFrom(req)) && decode(utils.jwtFrom(req))
return decodedToken?.data?.role === roles.deluxe && decodedToken?.data?.deluxeToken && decodedToken?.data?.deluxeToken === deluxeToken(decodedToken?.data?.email)
}

export const isCustomer = (req: Request) => {
const decodedToken = verify(utils.jwtFrom(req)) && decode(utils.jwtFrom(req))
return decodedToken?.data?.role === roles.customer
}

export const appendUserId = () => {
return (req: Request, res: Response, next: NextFunction) => {
try {
req.body.UserId = authenticatedUsers.tokenMap[utils.jwtFrom(req)].data.id
next()
} catch (error: any) {
res.status(401).json({ status: 'error', message: error })
}
}
}

export const updateAuthenticatedUsers = () => (req: Request, res: Response, next: NextFunction) => {
const token = req.cookies.token || utils.jwtFrom(req)
if (token) {
jwt.verify(token, publicKey, (err: Error | null, decoded: any) => {
if (err === null) {
if (authenticatedUsers.get(token) === undefined) {
authenticatedUsers.put(token, decoded)
res.cookie('token', token)
}
}
})
}
next()
}
Loading