diff --git a/.github/workflows/penetration-tests.yml b/.github/workflows/penetration-tests.yml index 9ac0e4f2d8..49f50a5323 100644 --- a/.github/workflows/penetration-tests.yml +++ b/.github/workflows/penetration-tests.yml @@ -12,6 +12,8 @@ jobs: matrix: group: - 'test/front_login/contact.test.ts' + - 'test/front_login/cart/cart.test.ts' + - 'test/front_login/cart/cart_delete.test.ts' steps: - name: Checkout @@ -27,6 +29,9 @@ jobs: git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" git config --global user.email "$(git --no-pager log --format=format:'%ae' -n 1)" git am zap/patches/0001-CSRF-OWASP-ZAP.patch + - name: Apply patch to cart_delete + if: matrix.group == 'test/front_login/cart/cart_delete.test.ts' + run: git am zap/patches/0009-cart_delete.patch - name: Setup environment run: echo "COMPOSE_FILE=docker-compose.yml:docker-compose.pgsql.yml:docker-compose.dev.yml:docker-compose.owaspzap.yml:docker-compose.owaspzap.daemon.yml" >> $GITHUB_ENV diff --git a/docker-compose.owaspzap.yml b/docker-compose.owaspzap.yml index dac730765a..0817b4763a 100644 --- a/docker-compose.owaspzap.yml +++ b/docker-compose.owaspzap.yml @@ -6,6 +6,7 @@ services: # Use Zap Proxy HTTP_URL: https://ec-cube/ HTTPS_URL: https://ec-cube/ + USE_FILENAME_DIR_INDEX: 1 zap: build: context: ./zap diff --git a/e2e-tests/test/front_login/cart/cart.test.ts b/e2e-tests/test/front_login/cart/cart.test.ts new file mode 100644 index 0000000000..ad5000b950 --- /dev/null +++ b/e2e-tests/test/front_login/cart/cart.test.ts @@ -0,0 +1,155 @@ +import { test, expect, chromium, Page } from '@playwright/test'; +import PlaywrightConfig from '../../../../playwright.config'; +import { ZapClient, Mode, ContextType, Risk, HttpMessage } from '../../../utils/ZapClient'; +import { intervalRepeater } from '../../../utils/Progress'; +const zapClient = new ZapClient(); + +const url = `${PlaywrightConfig.use.baseURL}/cart/index.php`; + +test.describe.serial('カートページのテストをします', () => { + let page: Page; + test.beforeAll(async () => { + await zapClient.setMode(Mode.Protect); + await zapClient.newSession('/zap/wrk/sessions/front_login_cart', true); + await zapClient.importContext(ContextType.FrontLogin); + + if (!await zapClient.isForcedUserModeEnabled()) { + await zapClient.setForcedUserModeEnabled(); + expect(await zapClient.isForcedUserModeEnabled()).toBeTruthy(); + } + const browser = await chromium.launch(); + page = await browser.newPage(); + await page.goto(url); + }); + + const detailURL = `${PlaywrightConfig.use.baseURL}/products/detail.php?product_id=1`; + test('商品詳細ページを表示します', async () => { + await page.goto(detailURL); + await expect(page.locator('#detailrightbloc > h2')).toContainText('アイスクリーム'); + }); + + test('商品をカートに入れます', async () => { + await page.selectOption('select[name=classcategory_id1]', { label: '抹茶' }); + await page.selectOption('select[name=classcategory_id2]', { label: 'S' }); + await page.fill('input[name=quantity]', '2'); + await page.click('[alt=カゴに入れる]'); + }); + + test('カートの内容を確認します', async () => { + await expect(page.locator('h2.title')).toContainText('現在のカゴの中'); + await expect(page.locator('table[summary=商品情報] >> tr >> nth=1')).toContainText('アイスクリーム'); + }); + + test.describe('テストを実行します[GET] @attack', () => { + let scanId: number; + test('アクティブスキャンを実行します', async () => { + scanId = await zapClient.activeScanAsUser(url, 2, 110, false, null, 'GET'); + await intervalRepeater(async () => await zapClient.getActiveScanStatus(scanId), 5000, page); + }); + + test('結果を確認します', async () => { + await zapClient.getAlerts(url, 0, 1, Risk.High) + .then(alerts => expect(alerts).toEqual([])); + }); + }); + + test('カートの数量を加算します', async () => { + await page.reload(); + const quantity = parseInt(await page.locator('table[summary=商品情報] >> tr >> nth=1 >> td >> nth=4').textContent()); + await page.click('table[summary=商品情報] >> tr >> nth=1 >> td >> nth=4 >> [alt="+"]'); + await expect(page.locator('table[summary=商品情報] >> tr >> nth=1 >> td >> nth=4')).toContainText(String(quantity + 1)); + }); + + test.describe('数量加算のテストを実行します[POST] @attack', () => { + + let message: HttpMessage; + let requestBody: string; + test('履歴を取得します', async () => { + const result = await zapClient.getMessages(url, await zapClient.getNumberOfMessages(url) - 1, 1); + message = result.pop(); + expect(message.requestBody).toContain('mode=up'); + }); + test('transactionid を取得し直します', async () => { + await page.goto(url); + const transactionid = await page.locator('input[name=transactionid]').first().inputValue(); + requestBody = message.requestBody.replace(/transactionid=[a-z0-9]+/, `transactionid=${transactionid}`); + }); + + let scanId: number; + test('アクティブスキャンを実行します', async () => { + expect(requestBody).toContain('mode=up'); + scanId = await zapClient.activeScanAsUser(url, 2, 110, false, null, 'POST', requestBody); + await intervalRepeater(async () => await zapClient.getActiveScanStatus(scanId), 5000, page); + }); + + test('結果を確認します', async () => { + await zapClient.getAlerts(url, 0, 1, Risk.High) + .then(alerts => expect(alerts).toEqual([])); + }); + }); + + test('カートの数量を減算します', async () => { + await page.reload(); + const quantity = parseInt(await page.locator('table[summary=商品情報] >> tr >> nth=1 >> td >> nth=4').textContent()); + await page.click('table[summary=商品情報] >> tr >> nth=1 >> td >> nth=4 >> [alt="-"]'); + await expect(page.locator('table[summary=商品情報] >> tr >> nth=1 >> td >> nth=4')).toContainText(String(quantity - 1)); + }); + + test.describe('数量減算のテストを実行します[POST] @attack', () => { + + let message: HttpMessage; + let requestBody: string; + test('履歴を取得します', async () => { + const result = await zapClient.getMessages(url, await zapClient.getNumberOfMessages(url) - 1, 1); + message = result.pop(); + }); + test('transactionid を取得し直します', async () => { + await page.goto(url); + const transactionid = await page.locator('input[name=transactionid]').first().inputValue(); + requestBody = message.requestBody.replace(/transactionid=[a-z0-9]+/, `transactionid=${transactionid}`); + }); + let manuallyMessage: HttpMessage; + test('数量減算の requestBody に書き換えて手動送信します', async () => { + requestBody = requestBody.replace(/mode=down/, 'mode=down&mode_down=dummy'); + await zapClient.sendRequest(message.requestHeader + requestBody); + manuallyMessage = await zapClient.getLastMessage(url); + }); + let scanId: number; + test('アクティブスキャンを実行します', async () => { + expect(manuallyMessage.requestBody).toContain('mode=down'); + scanId = await zapClient.activeScanAsUser(url, 2, 110, false, null, 'POST', requestBody); + await intervalRepeater(async () => await zapClient.getActiveScanStatus(scanId), 5000, page); + }); + + test('結果を確認します', async () => { + await zapClient.getAlerts(url, 0, 1, Risk.High) + .then(alerts => expect(alerts).toEqual([])); + }); + }); + + test('購入手続きへ進みます', async () => { + await page.goto(url); + await page.click('input[name=confirm][alt=購入手続きへ]'); + await expect(page.locator('h2.title')).toContainText('お届け先の指定'); + }); + + test.describe('購入手続きへ進むテストを実行します[POST] @attack', () => { + let message: HttpMessage; + test('履歴を取得します', async () => { + message = await zapClient.getLastMessage(url); + expect(message.requestHeader).toContain(`POST ${url}`); + expect(message.responseHeader).toContain('HTTP/1.1 302 Found'); + }); + + let scanId: number; + test('アクティブスキャンを実行します', async () => { + scanId = await zapClient.activeScanAsUser(url, 2, 110, false, null, 'POST', message.requestBody); + await intervalRepeater(async () => await zapClient.getActiveScanStatus(scanId), 5000, page); + }); + + test('結果を確認します', async () => { + await zapClient.getAlerts(url, 0, 1, Risk.High) + .then(alerts => expect(alerts).toEqual([])); + }); + }); +}); diff --git a/e2e-tests/test/front_login/cart/cart_delete.test.ts b/e2e-tests/test/front_login/cart/cart_delete.test.ts new file mode 100644 index 0000000000..924359dc50 --- /dev/null +++ b/e2e-tests/test/front_login/cart/cart_delete.test.ts @@ -0,0 +1,70 @@ +import { test, expect, chromium, Page } from '@playwright/test'; +import PlaywrightConfig from '../../../../playwright.config'; +import { ZapClient, Mode, ContextType, Risk, HttpMessage } from '../../../utils/ZapClient'; +import { intervalRepeater } from '../../../utils/Progress'; +const zapClient = new ZapClient(); + +const url = `${PlaywrightConfig.use.baseURL}/cart/index.php`; + +// zap/patches/0009-cart_delete.patch を適用する必要があります +test.describe.serial('カートページのテストをします', () => { + let page: Page; + test.beforeAll(async () => { + await zapClient.setMode(Mode.Protect); + await zapClient.newSession('/zap/wrk/sessions/front_login_contact', true); + await zapClient.importContext(ContextType.FrontLogin); + + if (!await zapClient.isForcedUserModeEnabled()) { + await zapClient.setForcedUserModeEnabled(); + expect(await zapClient.isForcedUserModeEnabled()).toBeTruthy(); + } + const browser = await chromium.launch(); + page = await browser.newPage(); + await page.goto(url); + }); + + const detailURL = `${PlaywrightConfig.use.baseURL}/products/detail.php?product_id=1`; + test('商品詳細ページを表示します', async () => { + await page.goto(detailURL); + await expect(page.locator('#detailrightbloc > h2')).toContainText('アイスクリーム'); + }); + + test('商品をカートに入れます', async () => { + await page.selectOption('select[name=classcategory_id1]', { label: '抹茶' }); + await page.selectOption('select[name=classcategory_id2]', { label: 'S' }); + await page.fill('input[name=quantity]', '2'); + await page.click('[alt=カゴに入れる]'); + }); + + test('カートの内容を確認します', async () => { + await expect(page.locator('h2.title')).toContainText('現在のカゴの中'); + await expect(page.locator('table[summary=商品情報] >> tr >> nth=1')).toContainText('アイスクリーム'); + }); + + test('カートを削除します', async () => { + page.on('dialog', dialog => dialog.accept()); + await page.reload(); + await page.click('table[summary=商品情報] >> tr >> nth=1 >> td >> nth=0 >> text=削除'); + }); + + test.describe('カート削除のテストを実行します[POST] @attack', () => { + + let message: HttpMessage; + test('履歴を取得します', async () => { + const result = await zapClient.getMessages(url, await zapClient.getNumberOfMessages(url) - 1, 1); + message = result.pop(); + }); + + let scanId: number; + test('アクティブスキャンを実行します', async () => { + expect(message.requestBody).toContain('mode=delete'); + scanId = await zapClient.activeScanAsUser(url, 2, 110, false, null, 'POST', message.requestBody); + await intervalRepeater(async () => await zapClient.getActiveScanStatus(scanId), 5000, page); + }); + + test('結果を確認します', async () => { + await zapClient.getAlerts(url, 0, 1, Risk.High) + .then(alerts => expect(alerts).toEqual([])); + }); + }); +}); diff --git a/html/define.php b/html/define.php index adfc28e72f..46c57a6edc 100644 --- a/html/define.php +++ b/html/define.php @@ -11,7 +11,7 @@ * true: 使用する, false: 使用しない, null: 自動(Symfony, IIS は true、それ以外は false) * ※ IIS は、POST 時にファイル名を使用しないと不具合が発生する。(http://support.microsoft.com/kb/247536/ja) */ -define('USE_FILENAME_DIR_INDEX', null); +define('USE_FILENAME_DIR_INDEX', getenv('USE_FILENAME_DIR_INDEX') ? (bool) getenv('USE_FILENAME_DIR_INDEX') : null); $autoload = HTML_REALDIR . HTML2DATA_DIR . 'vendor/autoload.php'; if (!file_exists($autoload) && !is_readable($autoload)) { diff --git a/zap/patches/0009-cart_delete.patch b/zap/patches/0009-cart_delete.patch new file mode 100644 index 0000000000..62782501d2 --- /dev/null +++ b/zap/patches/0009-cart_delete.patch @@ -0,0 +1,30 @@ +From 274562f19542ac7548c75a65677d0e46af89a655 Mon Sep 17 00:00:00 2001 +From: Kentaro Ohkouchi +Date: Fri, 18 Feb 2022 15:56:54 +0900 +Subject: [PATCH] =?UTF-8?q?=E3=82=AB=E3=83=BC=E3=83=88=E5=89=8A=E9=99=A4?= + =?UTF-8?q?=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E3=81=99?= + =?UTF-8?q?=E3=82=8B=E3=83=91=E3=83=83=E3=83=81?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + data/class/SC_CartSession.php | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/data/class/SC_CartSession.php b/data/class/SC_CartSession.php +index add3a0555..b022d932f 100644 +--- a/data/class/SC_CartSession.php ++++ b/data/class/SC_CartSession.php +@@ -518,7 +518,7 @@ class SC_CartSession + $max = $this->getMax($productTypeId); + for ($i = 0; $i <= $max; $i++) { + if ($this->cartSession[$productTypeId][$i]['cart_no'] == $cart_no) { +- unset($this->cartSession[$productTypeId][$i]); ++ // unset($this->cartSession[$productTypeId][$i]); + } + } + } +-- +2.34.1 +