From 12777eb5c155e7961fb0300b0bd2bdaa4bcb7350 Mon Sep 17 00:00:00 2001 From: "aikido-autofix[bot]" <119856028+aikido-autofix[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 23:14:33 +0000 Subject: [PATCH] fix(security): autofix NoSQL injection attack possible --- routes/likeProductReviews.ts | 122 ++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 44 deletions(-) diff --git a/routes/likeProductReviews.ts b/routes/likeProductReviews.ts index 00bcb2f40f0..f484f7a0e86 100644 --- a/routes/likeProductReviews.ts +++ b/routes/likeProductReviews.ts @@ -11,55 +11,89 @@ const challenges = require('../data/datacache').challenges const db = require('../data/mongodb') const security = require('../lib/insecurity') +const TIMING_ATTACK_DELAY_MS = 150 +const TIMING_ATTACK_THRESHOLD = 2 + +/** + * Delays execution for timing attack challenge + */ +const delay = async (ms: number): Promise => { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +/** + * Creates a safe query filter to prevent NoSQL injection + */ +const createSafeIdFilter = (id: string) => ({ _id: { $eq: id } }) + module.exports = function productReviews () { - return (req: Request, res: Response, next: NextFunction) => { - const id = req.body.id + return async (req: Request, res: Response, next: NextFunction): Promise => { + const { id } = req.body const user = security.authenticatedUsers.from(req) - db.reviews.findOne({ _id: id }).then((review: Review) => { + + // Validate required parameters + if (!id || !user?.data?.email) { + res.status(400).json({ error: 'Invalid request parameters' }) + return + } + + try { + const safeFilter = createSafeIdFilter(id) + + // Use $eq operator to prevent NoSQL injection + const review = await db.reviews.findOne(safeFilter) + if (!review) { res.status(404).json({ error: 'Not found' }) - } else { - const likedBy = review.likedBy - if (!likedBy.includes(user.data.email)) { - db.reviews.update( - { _id: id }, - { $inc: { likesCount: 1 } } - ).then( - () => { - // Artificial wait for timing attack challenge - setTimeout(function () { - db.reviews.findOne({ _id: id }).then((review: Review) => { - const likedBy = review.likedBy - likedBy.push(user.data.email) - let count = 0 - for (let i = 0; i < likedBy.length; i++) { - if (likedBy[i] === user.data.email) { - count++ - } - } - challengeUtils.solveIf(challenges.timingAttackChallenge, () => { return count > 2 }) - db.reviews.update( - { _id: id }, - { $set: { likedBy } } - ).then( - (result: any) => { - res.json(result) - }, (err: unknown) => { - res.status(500).json(err) - }) - }, () => { - res.status(400).json({ error: 'Wrong Params' }) - }) - }, 150) - }, (err: unknown) => { - res.status(500).json(err) - }) - } else { - res.status(403).json({ error: 'Not allowed' }) - } + return + } + + const { likedBy } = review + const userEmail = user.data.email + + // Check if user already liked this review + if (likedBy.includes(userEmail)) { + res.status(403).json({ error: 'Not allowed' }) + return + } + + // Increment like count + await db.reviews.update( + safeFilter, + { $inc: { likesCount: 1 } } + ) + + // Artificial wait for timing attack challenge + await delay(TIMING_ATTACK_DELAY_MS) + + // Fetch updated review + const updatedReview = await db.reviews.findOne(safeFilter) + + if (!updatedReview) { + res.status(400).json({ error: 'Wrong Params' }) + return } - }, () => { + + // Add user email to likedBy array + const updatedLikedBy = [...updatedReview.likedBy, userEmail] + + // Count how many times user has liked (for challenge detection) + const userLikeCount = updatedLikedBy.filter((email: string) => email === userEmail).length + + challengeUtils.solveIf( + challenges.timingAttackChallenge, + () => userLikeCount > TIMING_ATTACK_THRESHOLD + ) + + // Update likedBy array + const result = await db.reviews.update( + safeFilter, + { $set: { likedBy: updatedLikedBy } } + ) + + res.json(result) + } catch (err: unknown) { res.status(400).json({ error: 'Wrong Params' }) - }) + } } }