Skip to content

Commit

Permalink
add utils and insecurity
Browse files Browse the repository at this point in the history
  • Loading branch information
PavelLinearB committed Sep 23, 2024
1 parent bc5632d commit bdbe6c0
Show file tree
Hide file tree
Showing 2 changed files with 435 additions and 0 deletions.
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-----'

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

0 comments on commit bdbe6c0

Please sign in to comment.