diff --git a/.brightsec/tests/get-rest-chatbot-status.test.ts b/.brightsec/tests/get-rest-chatbot-status.test.ts new file mode 100644 index 00000000..688c5b00 --- /dev/null +++ b/.brightsec/tests/get-rest-chatbot-status.test.ts @@ -0,0 +1,36 @@ +import { test, before, after } from 'node:test'; +import { SecRunner } from '@sectester/runner'; +import { AttackParamLocation, HttpMethod } from '@sectester/scan'; + +const timeout = 40 * 60 * 1000; +const baseUrl = process.env.BRIGHT_TARGET_URL!; + +let runner!: SecRunner; + +before(async () => { + runner = new SecRunner({ + hostname: process.env.BRIGHT_HOSTNAME!, + projectId: process.env.BRIGHT_PROJECT_ID! + }); + + await runner.init(); +}); + +after(() => runner.clear()); + +test('GET /rest/chatbot/status', { signal: AbortSignal.timeout(timeout) }, async () => { + await runner + .createScan({ + tests: ['jwt'], + attackParamLocations: [AttackParamLocation.HEADER], + starMetadata: { databases: ['SQLite'] } + }) + .setFailFast(false) + .timeout(timeout) + .run({ + method: HttpMethod.GET, + url: `${baseUrl}/rest/chatbot/status`, + headers: { 'X-Recruiting': 'We are hiring! Visit our careers page for more information.' }, + auth: process.env.BRIGHT_AUTH_ID + }); +}); diff --git a/.brightsec/tests/post-api-addresss.test.ts b/.brightsec/tests/post-api-addresss.test.ts new file mode 100644 index 00000000..f5f299c5 --- /dev/null +++ b/.brightsec/tests/post-api-addresss.test.ts @@ -0,0 +1,46 @@ +import { test, before, after } from 'node:test'; +import { SecRunner } from '@sectester/runner'; +import { AttackParamLocation, HttpMethod } from '@sectester/scan'; + +const timeout = 40 * 60 * 1000; +const baseUrl = process.env.BRIGHT_TARGET_URL!; + +let runner!: SecRunner; + +before(async () => { + runner = new SecRunner({ + hostname: process.env.BRIGHT_HOSTNAME!, + projectId: process.env.BRIGHT_PROJECT_ID! + }); + + await runner.init(); +}); + +after(() => runner.clear()); + +test('POST /api/addresss', { signal: AbortSignal.timeout(timeout) }, async () => { + await runner + .createScan({ + tests: ['id_enumeration'], + attackParamLocations: [AttackParamLocation.BODY], + starMetadata: { databases: ['SQLite'] } + }) + .setFailFast(false) + .timeout(timeout) + .run({ + method: HttpMethod.POST, + url: `${baseUrl}/api/Addresss`, + body: { + UserId: 1, + fullName: 'John Doe', + mobileNum: 1234567890, + zipCode: '12345', + streetAddress: '123 Main St', + city: 'Metropolis', + state: 'NY', + country: 'USA' + }, + headers: { 'Content-Type': 'application/json' }, + auth: process.env.BRIGHT_AUTH_ID + }); +}); diff --git a/.brightsec/tests/post-rest-products-reviews.test.ts b/.brightsec/tests/post-rest-products-reviews.test.ts new file mode 100644 index 00000000..5dba53ce --- /dev/null +++ b/.brightsec/tests/post-rest-products-reviews.test.ts @@ -0,0 +1,39 @@ +import { test, before, after } from 'node:test'; +import { SecRunner } from '@sectester/runner'; +import { AttackParamLocation, HttpMethod } from '@sectester/scan'; + +const timeout = 40 * 60 * 1000; +const baseUrl = process.env.BRIGHT_TARGET_URL!; + +let runner!: SecRunner; + +before(async () => { + runner = new SecRunner({ + hostname: process.env.BRIGHT_HOSTNAME!, + projectId: process.env.BRIGHT_PROJECT_ID! + }); + + await runner.init(); +}); + +after(() => runner.clear()); + +test('POST /rest/products/reviews', { signal: AbortSignal.timeout(timeout) }, async () => { + await runner + .createScan({ + tests: ['nosql'], + attackParamLocations: [AttackParamLocation.BODY], + starMetadata: { databases: ['SQLite'] } + }) + .setFailFast(false) + .timeout(timeout) + .run({ + method: HttpMethod.POST, + url: `${baseUrl}/rest/products/reviews`, + body: { + id: '507f1f77bcf86cd799439011' + }, + headers: { 'Content-Type': 'application/json' }, + auth: process.env.BRIGHT_AUTH_ID + }); +}); diff --git a/.github/workflows/bright.yml b/.github/workflows/bright.yml new file mode 100644 index 00000000..682dc266 --- /dev/null +++ b/.github/workflows/bright.yml @@ -0,0 +1,44 @@ +name: Bright + +on: + pull_request: + branches: + - '**' + +permissions: + checks: write + contents: read + id-token: write + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js 22.x + uses: actions/setup-node@v4 + with: + node-version: 22.x + + - name: Install application dependencies + run: npm install + + - name: Start application + run: | + npm start & + until nc -zv 127.0.0.1 3000; do sleep 1; done + + - name: Install SecTesterJS dependencies + run: npm i --save=false --prefix .brightsec @sectester/core @sectester/repeater @sectester/scan @sectester/runner @sectester/reporter + + - name: Run security tests + env: + BRIGHT_HOSTNAME: ${{ vars.BRIGHT_HOSTNAME }} + BRIGHT_PROJECT_ID: ${{ vars.BRIGHT_PROJECT_ID }} + BRIGHT_AUTH_ID: ${{ vars.BRIGHT_AUTH_ID }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRIGHT_TOKEN: ${{ secrets.BRIGHT_TOKEN }} + BRIGHT_TARGET_URL: http://127.0.0.1:3000 + run: node --experimental-transform-types --experimental-strip-types --experimental-detect-module --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --disable-warning=ExperimentalWarning --test-force-exit --test-concurrency=4 --test .brightsec/tests/*.test.ts \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8ed5bfa..f09ccb66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,22 +1,25 @@ name: "CI/CD Pipeline" on: - push: - branches-ignore: - - l10n_develop - - gh-pages - paths-ignore: - - '*.md' - - 'LICENSE' - - 'monitoring/grafana-dashboard.json' - - 'screenshots/**' - tags-ignore: - - '*' - pull_request: - paths-ignore: - - '*.md' - - 'LICENSE' - - 'data/static/i18n/*.json' - - 'frontend/src/assets/i18n/*.json' + workflow_dispatch: +# on: +# push: +# branches-ignore: +# - l10n_develop +# - gh-pages +# paths-ignore: +# - '*.md' +# - 'LICENSE' +# - 'monitoring/grafana-dashboard.json' +# - 'screenshots/**' +# tags-ignore: +# - '*' +# pull_request: +# paths-ignore: +# - '*.md' +# - 'LICENSE' +# - 'data/static/i18n/*.json' +# - 'frontend/src/assets/i18n/*.json' + env: NODE_DEFAULT_VERSION: 22 NODE_OPTIONS: "--max_old_space_size=4096" @@ -40,18 +43,8 @@ jobs: run: npm run lint - name: "Lint customization configs" run: > - npm run lint:config -- -f ./config/7ms.yml && - npm run lint:config -- -f ./config/addo.yml && - npm run lint:config -- -f ./config/bodgeit.yml && - npm run lint:config -- -f ./config/ctf.yml && - npm run lint:config -- -f ./config/default.yml && - npm run lint:config -- -f ./config/fbctf.yml && - npm run lint:config -- -f ./config/juicebox.yml && - npm run lint:config -- -f ./config/mozilla.yml && - npm run lint:config -- -f ./config/oss.yml && - npm run lint:config -- -f ./config/quiet.yml && - npm run lint:config -- -f ./config/tutorial.yml && - npm run lint:config -- -f ./config/unsafe.yml + npm run lint:config -- -f ./config/7ms.yml && npm run lint:config -- -f ./config/addo.yml && npm run lint:config -- -f ./config/bodgeit.yml && npm run lint:config -- -f ./config/ctf.yml && npm run lint:config -- -f ./config/default.yml && npm run lint:config -- -f ./config/fbctf.yml && npm run lint:config -- -f ./config/juicebox.yml && npm run lint:config -- -f ./config/mozilla.yml && npm run lint:config -- -f ./config/oss.yml && npm run lint:config -- -f ./config/quiet.yml && npm run lint:config -- -f ./config/tutorial.yml && npm run lint:config -- -f ./config/unsafe.yml + coding-challenge-rsn: runs-on: windows-latest steps: @@ -184,17 +177,8 @@ jobs: timeout_minutes: 30 max_attempts: 3 command: > - NODE_ENV=7ms npm run test:server && - NODE_ENV=addo npm run test:server && - NODE_ENV=bodgeit npm run test:server && - NODE_ENV=ctf npm run test:server && - NODE_ENV=fbctf npm run test:server && - NODE_ENV=juicebox npm run test:server && - NODE_ENV=mozilla npm run test:server && - NODE_ENV=oss npm run test:server && - NODE_ENV=quiet npm run test:server && - NODE_ENV=tutorial npm run test:server && - NODE_ENV=unsafe npm run test:server + NODE_ENV=7ms npm run test:server && NODE_ENV=addo npm run test:server && NODE_ENV=bodgeit npm run test:server && NODE_ENV=ctf npm run test:server && NODE_ENV=fbctf npm run test:server && NODE_ENV=juicebox npm run test:server && NODE_ENV=mozilla npm run test:server && NODE_ENV=oss npm run test:server && NODE_ENV=quiet npm run test:server && NODE_ENV=tutorial npm run test:server && NODE_ENV=unsafe npm run test:server + e2e: runs-on: ${{ matrix.os }} strategy: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index fe353eb0..df6c22ca 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,8 +1,9 @@ name: "CodeQL Scan" - on: - push: - pull_request: + workflow_dispatch: +# on: +# push: +# pull_request: jobs: analyze: @@ -15,19 +16,19 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript-typescript' ] + language: ['javascript-typescript'] steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - queries: security-extended - config: | - paths-ignore: - - 'data/static/codefixes' - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: security-extended + config: | + paths-ignore: + - 'data/static/codefixes' + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/composite/configure-bright-credentials/action.yaml b/.github/workflows/composite/configure-bright-credentials/action.yaml new file mode 100644 index 00000000..84983846 --- /dev/null +++ b/.github/workflows/composite/configure-bright-credentials/action.yaml @@ -0,0 +1,53 @@ +name: 'Configure BrightSec credentials' + +inputs: + BRIGHT_HOSTNAME: + description: 'Hostname for the BrightSec environment' + required: true + BRIGHT_PROJECT_ID: + description: 'Project ID for BrightSec' + required: true + BRIGHT_TOKEN: + description: 'Pre-configured token' + required: false + +runs: + using: 'composite' + steps: + - id: configure_env_from_input + name: 'Set existing token in env' + shell: bash + if: ${{ inputs.BRIGHT_TOKEN != '' }} + env: + BRIGHT_TOKEN: ${{ inputs.BRIGHT_TOKEN }} + run: | + echo "BRIGHT_TOKEN=${BRIGHT_TOKEN}" >> $GITHUB_ENV + + - id: configure_bright_credentials_through_oidc + name: 'Exchange OIDC credentials for Bright token' + shell: bash + if: ${{ inputs.BRIGHT_TOKEN == '' }} + env: + BRIGHT_HOSTNAME: ${{ inputs.BRIGHT_HOSTNAME }} + BRIGHT_PROJECT_ID: ${{ inputs.BRIGHT_PROJECT_ID }} + run: | + # Retrieve OIDC token from GitHub + OIDC_TOKEN=$(curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + "${ACTIONS_ID_TOKEN_REQUEST_URL}" | jq -r '.value') + + # Post the token to BrightSec + RESPONSE=$(curl -s -X POST "https://${BRIGHT_HOSTNAME}/api/v1/projects/${BRIGHT_PROJECT_ID}/api-keys/oidc" \ + -H "Content-Type: application/json" \ + -d "{\"token\": \"${OIDC_TOKEN}\"}") + + if ! echo "$RESPONSE" | jq -e . > /dev/null 2>&1; then + echo "Error: $RESPONSE" 1>&2 + exit 1 + fi + + # Extract the pureKey + PURE_KEY=$(echo "$RESPONSE" | jq -r '.pureKey') + + # Mask and store in environment + echo "::add-mask::$PURE_KEY" + echo "BRIGHT_TOKEN=$PURE_KEY" >> $GITHUB_ENV diff --git a/.github/workflows/lint-fixer.yml b/.github/workflows/lint-fixer.yml index 907f841e..73a62044 100644 --- a/.github/workflows/lint-fixer.yml +++ b/.github/workflows/lint-fixer.yml @@ -1,29 +1,30 @@ name: "Let me lint:fix that for you" - -on: [push] +on: + workflow_dispatch: +# on: [push] jobs: LMLFTFY: runs-on: ubuntu-latest steps: - - name: "Check out Git repository" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - - name: "Use Node.js 22" - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af #v4.1.0 - with: - node-version: 22 - - name: "Install application" - run: | - npm install --ignore-scripts - cd frontend - npm install --ignore-scripts --legacy-peer-deps - - name: "Fix everything which can be fixed" - run: 'npm run lint:fix' - - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 #v5.0.1 - with: - commit_message: "Auto-fix linting issues" - branch: ${{ github.head_ref }} - commit_options: '--signoff' - commit_user_name: JuiceShopBot - commit_user_email: 61591748+JuiceShopBot@users.noreply.github.com - commit_author: JuiceShopBot <61591748+JuiceShopBot@users.noreply.github.com> + - name: "Check out Git repository" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + - name: "Use Node.js 22" + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af #v4.1.0 + with: + node-version: 22 + - name: "Install application" + run: | + npm install --ignore-scripts + cd frontend + npm install --ignore-scripts --legacy-peer-deps + - name: "Fix everything which can be fixed" + run: 'npm run lint:fix' + - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 #v5.0.1 + with: + commit_message: "Auto-fix linting issues" + branch: ${{ github.head_ref }} + commit_options: '--signoff' + commit_user_name: JuiceShopBot + commit_user_email: 61591748+JuiceShopBot@users.noreply.github.com + commit_author: JuiceShopBot <61591748+JuiceShopBot@users.noreply.github.com> diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index f999d22e..d2dea74a 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -1,8 +1,9 @@ name: Automatic Rebase - on: - issue_comment: - types: [created] + workflow_dispatch: +# on: +# issue_comment: +# types: [created] jobs: rebase: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27674eec..f4e8ed4b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,11 @@ name: "Release Pipeline" on: - push: - tags: - - v* + workflow_dispatch: +# on: +# push: +# tags: +# - v* + env: CYCLONEDX_NPM_VERSION: '^2.0.0||^3.0.0' jobs: diff --git a/.github/workflows/update-challenges-www.yml b/.github/workflows/update-challenges-www.yml index caaa7405..53223acf 100644 --- a/.github/workflows/update-challenges-www.yml +++ b/.github/workflows/update-challenges-www.yml @@ -1,34 +1,34 @@ name: "Update challenges on owasp-juice.shop" - on: - push: - branches: [ master ] - paths: - - 'data/static/challenges.yml' + workflow_dispatch: +# on: +# push: +# branches: [master] +# paths: +# - 'data/static/challenges.yml' jobs: UpdateChallengesOnWebsite: if: github.repository == 'juice-shop/juice-shop' runs-on: ubuntu-latest steps: - - name: Check out Git repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - with: - token: ${{ secrets.BOT_TOKEN }} - repository: OWASP/www-project-juice-shop - ref: master - - name: Update challenges.yml - run: | - cd _data/ - rm challenges.yml - wget https://raw.githubusercontent.com/juice-shop/juice-shop/master/data/static/challenges.yml - - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 #v5.0.1 - with: - commit_message: "Auto-update challenges.yml from ${{ github.sha }}" - branch: master - commit_options: '--signoff' - - # Optional commit user and author settings - commit_user_name: JuiceShopBot - commit_user_email: 61591748+JuiceShopBot@users.noreply.github.com - commit_author: JuiceShopBot <61591748+JuiceShopBot@users.noreply.github.com> + - name: Check out Git repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + with: + token: ${{ secrets.BOT_TOKEN }} + repository: OWASP/www-project-juice-shop + ref: master + - name: Update challenges.yml + run: | + cd _data/ + rm challenges.yml + wget https://raw.githubusercontent.com/juice-shop/juice-shop/master/data/static/challenges.yml + - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 #v5.0.1 + with: + commit_message: "Auto-update challenges.yml from ${{ github.sha }}" + branch: master + commit_options: '--signoff' + # Optional commit user and author settings + commit_user_name: JuiceShopBot + commit_user_email: 61591748+JuiceShopBot@users.noreply.github.com + commit_author: JuiceShopBot <61591748+JuiceShopBot@users.noreply.github.com> diff --git a/.github/workflows/update-news-www.yml b/.github/workflows/update-news-www.yml index 26757465..3c04b17d 100644 --- a/.github/workflows/update-news-www.yml +++ b/.github/workflows/update-news-www.yml @@ -1,29 +1,29 @@ name: "Update news on owasp-juice.shop" - on: - release: - types: [ published ] + workflow_dispatch: +# on: +# release: +# types: [published] jobs: UpdateNewsOnWebsite: runs-on: ubuntu-latest steps: - - name: Check out Git repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - with: - token: ${{ secrets.BOT_TOKEN }} - repository: OWASP/www-project-juice-shop - branch: master - - name: Update tab_news.md - run: | - sed -i 's//\n* ${{ github.event.release.published_at }}: juice-shop [`${{ github.event.release.tag_name }}`](https:\/\/github.com\/juice-shop\/juice-shop\/releases\/tag\/${{ github.event.release.tag_name }})/' tab_news.md - - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 #v5.0.1 - with: - commit_message: "Add juice-shop ${{ github.event.release.tag_name }} release notes to tab_news.md" - branch: master - commit_options: '--signoff' - - # Optional commit user and author settings - commit_user_name: JuiceShopBot - commit_user_email: 61591748+JuiceShopBot@users.noreply.github.com - commit_author: JuiceShopBot <61591748+JuiceShopBot@users.noreply.github.com> + - name: Check out Git repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + with: + token: ${{ secrets.BOT_TOKEN }} + repository: OWASP/www-project-juice-shop + branch: master + - name: Update tab_news.md + run: | + sed -i 's//\n* ${{ github.event.release.published_at }}: juice-shop [`${{ github.event.release.tag_name }}`](https:\/\/github.com\/juice-shop\/juice-shop\/releases\/tag\/${{ github.event.release.tag_name }})/' tab_news.md + - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 #v5.0.1 + with: + commit_message: "Add juice-shop ${{ github.event.release.tag_name }} release notes to tab_news.md" + branch: master + commit_options: '--signoff' + # Optional commit user and author settings + commit_user_name: JuiceShopBot + commit_user_email: 61591748+JuiceShopBot@users.noreply.github.com + commit_author: JuiceShopBot <61591748+JuiceShopBot@users.noreply.github.com> diff --git a/lib/insecurity.ts b/lib/insecurity.ts index 08ee8ad0..0778cbe1 100644 --- a/lib/insecurity.ts +++ b/lib/insecurity.ts @@ -54,7 +54,7 @@ export const cutOffPoisonNullByte = (str: string) => { 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 verify = (token: string) => token ? jwt.verify(token, publicKey, { algorithms: ['RS256'] }) : false export const decode = (token: string) => { return jws.decode(token)?.payload } export const sanitizeHtml = (html: string) => sanitizeHtmlLib(html) @@ -188,7 +188,7 @@ export const appendUserId = () => { 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) => { + jwt.verify(token, publicKey, { algorithms: ['RS256'] }, (err: Error | null, decoded: any) => { if (err === null) { if (authenticatedUsers.get(token) === undefined) { authenticatedUsers.put(token, decoded) @@ -198,4 +198,4 @@ export const updateAuthenticatedUsers = () => (req: Request, res: Response, next }) } next() -} +} \ No newline at end of file diff --git a/routes/address.ts b/routes/address.ts index 9d552a60..117b1ecf 100644 --- a/routes/address.ts +++ b/routes/address.ts @@ -8,6 +8,9 @@ import { AddressModel } from '../models/address' export function getAddress () { return async (req: Request, res: Response) => { + if (!req.body.UserId) { + return res.status(401).json({ status: 'error', data: 'Unauthorized access.' }) + } const addresses = await AddressModel.findAll({ where: { UserId: req.body.UserId } }) res.status(200).json({ status: 'success', data: addresses }) } @@ -15,22 +18,41 @@ export function getAddress () { export function getAddressById () { return async (req: Request, res: Response) => { + if (!req.body.UserId) { + return res.status(401).json({ status: 'error', data: 'Unauthorized access.' }) + } const address = await AddressModel.findOne({ where: { id: req.params.id, UserId: req.body.UserId } }) if (address != null) { res.status(200).json({ status: 'success', data: address }) } else { - res.status(400).json({ status: 'error', data: 'Malicious activity detected.' }) + res.status(404).json({ status: 'error', data: 'Address not found.' }) } } } export function delAddressById () { return async (req: Request, res: Response) => { + if (!req.body.UserId) { + return res.status(401).json({ status: 'error', data: 'Unauthorized access.' }) + } const address = await AddressModel.destroy({ where: { id: req.params.id, UserId: req.body.UserId } }) if (address) { res.status(200).json({ status: 'success', data: 'Address deleted successfully.' }) } else { - res.status(400).json({ status: 'error', data: 'Malicious activity detected.' }) + res.status(404).json({ status: 'error', data: 'Address not found.' }) } } } + +export function createAddress () { + return async (req: Request, res: Response) => { + if (!req.body.UserId) { + return res.status(401).json({ status: 'error', data: 'Unauthorized access.' }) + } + if (req.body.UserId !== req.user.id) { // Ensure the UserId in the request matches the authenticated user's ID + return res.status(403).json({ status: 'error', data: 'Forbidden: User ID mismatch.' }) + } + const newAddress = await AddressModel.create({ ...req.body, UserId: req.body.UserId }) + res.status(201).json({ status: 'success', data: newAddress }) + } +} \ No newline at end of file diff --git a/routes/chatbot.ts b/routes/chatbot.ts index ff12ea36..f34c5bb5 100644 --- a/routes/chatbot.ts +++ b/routes/chatbot.ts @@ -236,7 +236,7 @@ export function process () { async function getUserFromJwt (token: string): Promise { return await new Promise((resolve) => { - jwt.verify(token, security.publicKey, (err: VerifyErrors | null, decoded: JwtPayload | string | undefined) => { + jwt.verify(token, security.publicKey, { algorithms: ['RS256'] }, (err: VerifyErrors | null, decoded: JwtPayload | string | undefined) => { if (err !== null || !decoded || isString(decoded)) { resolve(null) } else { @@ -244,4 +244,4 @@ async function getUserFromJwt (token: string): Promise { } }) }) -} +} \ No newline at end of file diff --git a/routes/createProductReviews.ts b/routes/createProductReviews.ts index b02ed549..d9da224a 100644 --- a/routes/createProductReviews.ts +++ b/routes/createProductReviews.ts @@ -20,10 +20,18 @@ export function createProductReviews () { ) try { + const productId = req.params.id + if (typeof productId !== 'string' || !/^[a-f\d]{24}$/i.test(productId)) { + return res.status(400).json({ error: 'Invalid product ID format' }) + } + // Validate and sanitize input to prevent NoSQL injection + const message = utils.sanitizeInput(req.body.message) + const author = utils.sanitizeInput(req.body.author) + await reviewsCollection.insert({ - product: req.params.id, - message: req.body.message, - author: req.body.author, + product: productId, + message: message, + author: author, likesCount: 0, likedBy: [] }) @@ -32,4 +40,4 @@ export function createProductReviews () { return res.status(500).json(utils.getErrorMessage(err)) } } -} +} \ No newline at end of file diff --git a/routes/search.ts b/routes/search.ts index e89922d1..dcc9c156 100644 --- a/routes/search.ts +++ b/routes/search.ts @@ -20,7 +20,10 @@ export function searchProducts () { return (req: Request, res: Response, next: NextFunction) => { let criteria: any = req.query.q === 'undefined' ? '' : req.query.q ?? '' criteria = (criteria.length <= 200) ? criteria : criteria.substring(0, 200) - models.sequelize.query(`SELECT * FROM Products WHERE ((name LIKE '%${criteria}%' OR description LIKE '%${criteria}%') AND deletedAt IS NULL) ORDER BY name`) // vuln-code-snippet vuln-line unionSqlInjectionChallenge dbSchemaChallenge + models.sequelize.query('SELECT * FROM Products WHERE ((name LIKE :criteria OR description LIKE :criteria) AND deletedAt IS NULL) ORDER BY name', { + replacements: { criteria: `%${criteria}%` }, + type: models.sequelize.QueryTypes.SELECT + }) .then(([products]: any) => { const dataString = JSON.stringify(products) if (challengeUtils.notSolved(challenges.unionSqlInjectionChallenge)) { // vuln-code-snippet hide-start @@ -71,4 +74,4 @@ export function searchProducts () { }) } } -// vuln-code-snippet end unionSqlInjectionChallenge dbSchemaChallenge +// vuln-code-snippet end unionSqlInjectionChallenge dbSchemaChallenge \ No newline at end of file diff --git a/routes/showProductReviews.ts b/routes/showProductReviews.ts index edb075a6..e2992508 100644 --- a/routes/showProductReviews.ts +++ b/routes/showProductReviews.ts @@ -30,10 +30,15 @@ export function showProductReviews () { // Truncate id to avoid unintentional RCE const id = !utils.isChallengeEnabled(challenges.noSqlCommandChallenge) ? Number(req.params.id) : utils.trunc(req.params.id, 40) + // Validate id format + if (typeof id !== 'number' || isNaN(id)) { + return res.status(400).json({ error: 'Invalid product ID format' }) + } + // Measure how long the query takes, to check if there was a nosql dos attack const t0 = new Date().getTime() - db.reviewsCollection.find({ $where: 'this.product == ' + id }).then((reviews: Review[]) => { + db.reviewsCollection.find({ product: id }).then((reviews: Review[]) => { const t1 = new Date().getTime() challengeUtils.solveIf(challenges.noSqlCommandChallenge, () => { return (t1 - t0) > 2000 }) const user = security.authenticatedUsers.from(req) @@ -47,4 +52,4 @@ export function showProductReviews () { res.status(400).json({ error: 'Wrong Params' }) }) } -} +} \ No newline at end of file diff --git a/routes/updateProductReviews.ts b/routes/updateProductReviews.ts index 1c5bfd28..dda022be 100644 --- a/routes/updateProductReviews.ts +++ b/routes/updateProductReviews.ts @@ -14,18 +14,26 @@ import * as db from '../data/mongodb' export function updateProductReviews () { return (req: Request, res: Response, next: NextFunction) => { const user = security.authenticatedUsers.from(req) // vuln-code-snippet vuln-line forgedReviewChallenge - db.reviewsCollection.update( // vuln-code-snippet neutral-line forgedReviewChallenge - { _id: req.body.id }, // vuln-code-snippet vuln-line noSqlReviewsChallenge forgedReviewChallenge - { $set: { message: req.body.message } }, - { multi: true } // vuln-code-snippet vuln-line noSqlReviewsChallenge + const reviewId = req.body.id + if (typeof reviewId !== 'string' || !/^[a-f\d]{24}$/i.test(reviewId)) { + return res.status(400).json({ error: 'Invalid review ID format' }) + } + const message = req.body.message + if (typeof message !== 'string' || message.trim() === '') { + return res.status(400).json({ error: 'Invalid message format' }) + } + db.reviewsCollection.update( + { _id: reviewId }, + { $set: { message: message } }, + { multi: true } ).then( (result: { modified: number, original: Array<{ author: any }> }) => { - challengeUtils.solveIf(challenges.noSqlReviewsChallenge, () => { return result.modified > 1 }) // vuln-code-snippet hide-line - challengeUtils.solveIf(challenges.forgedReviewChallenge, () => { return user?.data && result.original[0] && result.original[0].author !== user.data.email && result.modified === 1 }) // vuln-code-snippet hide-line + challengeUtils.solveIf(challenges.noSqlReviewsChallenge, () => { return result.modified > 1 }) + challengeUtils.solveIf(challenges.forgedReviewChallenge, () => { return user?.data && result.original[0] && result.original[0].author !== user.data.email && result.modified === 1 }) res.json(result) }, (err: unknown) => { res.status(500).json(err) }) } } -// vuln-code-snippet end noSqlReviewsChallenge forgedReviewChallenge +// vuln-code-snippet end noSqlReviewsChallenge forgedReviewChallenge \ No newline at end of file diff --git a/routes/verify.ts b/routes/verify.ts index 63388680..f224b17a 100644 --- a/routes/verify.ts +++ b/routes/verify.ts @@ -114,7 +114,7 @@ function jwtChallenge (challenge: Challenge, req: Request, algorithm: string, em return } - jwt.verify(token, security.publicKey, (err: jwt.VerifyErrors | null) => { + jwt.verify(token, security.publicKey, { algorithms: ['RS256'] }, (err: jwt.VerifyErrors | null) => { if (err === null) { challengeUtils.solveIf(challenge, () => { return hasAlgorithm(token, algorithm) && hasEmail(decoded as { data: { email: string } }, email) @@ -437,4 +437,4 @@ function dangerousIngredients () { .map((keyword) => { return { [Op.like]: `%${keyword}%` } }) -} +} \ No newline at end of file