forked from juice-shop/juice-shop
-
Notifications
You must be signed in to change notification settings - Fork 0
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
PavelLinearB
wants to merge
2
commits into
master
Choose a base branch
from
add-utils-insecurity
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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