-
Notifications
You must be signed in to change notification settings - Fork 101
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
326 additions
and
0 deletions.
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,5 @@ | ||
version: "3" | ||
|
||
services: | ||
zap: | ||
command: bash -c "zap.sh -daemon -addonupdate -addoninstall help_ja_JP -addoninstall wappalyzer -addoninstall sequence -addonuninstall hud -configfile /zap/wrk/options.properties -certpubdump /zap/wrk/owasp_zap_root_ca.cer -host 0.0.0.0 -port 8090 -config api.addrs.addr.name=.* -config api.addrs.addr.regex=true" |
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,135 @@ | ||
import { Builder, By, until } from 'selenium-webdriver' | ||
import { ZapClient, Mode, ContextType, Risk } from '../../utils/ZapClient'; | ||
import { intervalRepeater } from '../../utils/Progress'; | ||
import { SeleniumCapabilities } from '../../utils/SeleniumCapabilities'; | ||
const zapClient = new ZapClient('http://127.0.0.1:8090'); | ||
|
||
jest.setTimeout(6000000); | ||
|
||
const inputNames = [ | ||
'name01', 'name02', 'kana01', 'kana02', 'zip01', 'zip02', 'addr01', 'addr02', | ||
'tel01', 'tel02', 'tel03' | ||
] as const; | ||
type InputName = { | ||
[key in typeof inputNames[number]]?: string | ||
}; | ||
|
||
const baseURL = 'https://ec-cube'; | ||
const url = baseURL + '/contact/'; | ||
|
||
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(); | ||
} | ||
}); | ||
|
||
describe.skip('お問い合わせページを表示する', () => { | ||
test('[E2E] お問い合わせページを表示し、ログイン状態を確認する', async () => { | ||
const driver = await new Builder() | ||
.withCapabilities(SeleniumCapabilities) | ||
.build(); | ||
try { | ||
expect.assertions(15); | ||
await driver.get(url); | ||
await driver.wait( | ||
until.elementLocated(By.css('h2.title')), 10000) | ||
.getText().then(title => expect(title).toBe('お問い合わせ(入力ページ)')); | ||
|
||
inputNames.forEach( | ||
async (name) => await driver.findElement(By.name(name)).getAttribute('value') | ||
.then(value => expect(value).toEqual(expect.anything())) | ||
); | ||
await driver.findElement(By.name('email')).getAttribute('value').then(value => expect(value).toBe('[email protected]')); | ||
await driver.findElement(By.name('email02')).getAttribute('value').then(value => expect(value).toBe('[email protected]')); | ||
|
||
} finally { | ||
driver && await driver.quit(); | ||
} | ||
}); | ||
|
||
describe('[ATTACK] お問い合わせページの表示をスキャンする', () => { | ||
test('GET でお問い合わせページをスキャンする', async () => { | ||
const scanId = await zapClient.activeScanAsUser(url, 2, 110, false, null, 'GET'); | ||
|
||
await intervalRepeater(async () => await zapClient.getActiveScanStatus(scanId), 5000); | ||
|
||
const alerts = await zapClient.getAlerts(url, 0, 1, Risk.High); | ||
alerts.forEach(alert => { | ||
throw new Error(alert.name); | ||
}); | ||
expect(alerts).toHaveLength(0); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('お問い合わせ確認ページを表示する', () => { | ||
test('[E2E] お問い合わせページに入力し、確認画面に進む', async () => { | ||
const driver = await new Builder() | ||
.withCapabilities(SeleniumCapabilities) | ||
.build(); | ||
try { | ||
await driver.get(url); | ||
await driver.wait( | ||
until.elementLocated(By.css('h2.title')), 10000) | ||
.getText().then(title => expect(title).toBe('お問い合わせ(入力ページ)')); | ||
|
||
// 入力値を代入しておく | ||
let inputField: InputName = {}; | ||
inputNames.forEach( | ||
async (name) => await driver.findElement(By.name(name)).getAttribute('value') | ||
.then(value => inputField[name] = value) | ||
); | ||
|
||
await driver.findElement(By.name('contents')).sendKeys('お問い合わせ内容入力'); | ||
await driver.findElement(By.name('confirm')).click(); | ||
|
||
await driver.wait( | ||
until.elementLocated(By.css('h2.title')), 10000) | ||
.getText().then(title => expect(title).toBe('お問い合わせ(確認ページ)')); | ||
|
||
// hidden に入力されているかどうか | ||
inputNames.forEach( | ||
async (name) => await driver.findElement(By.name(name)).getAttribute('value') | ||
.then(value => expect(value).toBe(inputField[name])) | ||
); | ||
// 確認画面に表示されているかどうか | ||
await driver.findElement(By.xpath('//*[@id="form1"]/table/tbody/tr[1]/td')).getText() | ||
.then(value => expect(value).toBe(`${inputField.name01} ${inputField.name02}`)); | ||
await driver.findElement(By.xpath('//*[@id="form1"]/table/tbody/tr[2]/td')).getText() | ||
.then(value => expect(value).toBe(`${inputField.kana01} ${inputField.kana02}`)); | ||
await driver.findElement(By.xpath('//*[@id="form1"]/table/tbody/tr[3]/td')).getText() | ||
.then(value => expect(value).toBe(`〒${inputField.zip01}-${inputField.zip02}`)); | ||
await driver.findElement(By.xpath('//*[@id="form1"]/table/tbody/tr[4]/td')).getText() | ||
.then(value => expect(value).toContain(`${inputField.addr01}${inputField.addr02}`)); | ||
await driver.findElement(By.xpath('//*[@id="form1"]/table/tbody/tr[5]/td')).getText() | ||
.then(value => expect(value).toBe(`${inputField.tel01}-${inputField.tel02}-${inputField.tel03}`)); | ||
await driver.findElement(By.xpath('//*[@id="form1"]/table/tbody/tr[6]/td')).getText() | ||
.then(value => expect(value).toBe('[email protected]')); | ||
await driver.findElement(By.xpath('//*[@id="form1"]/table/tbody/tr[7]/td')).getText() | ||
.then(value => expect(value).toBe('お問い合わせ内容入力')); | ||
} finally { | ||
driver && await driver.quit(); | ||
} | ||
}); | ||
|
||
describe('[ATTACK] お問い合わせ(確認ページ)をスキャンする', () => { | ||
test('POST でお問い合わせ(確認ページ)をスキャンする', async () => { | ||
|
||
const message = await zapClient.getLastMessage(url); | ||
const scanId = await zapClient.activeScanAsUser(url, 2, 110, false, null, 'GET', message.requestBody); // XXX なぜか method=POST にすると url_not_found のエラーになる. GET にしていても POST でスキャンされる | ||
|
||
await intervalRepeater(async () => await zapClient.getActiveScanStatus(scanId), 5000); | ||
|
||
const alerts = await zapClient.getAlerts(url, 0, 1, Risk.High); | ||
alerts.forEach(alert => { | ||
throw new Error(alert.name); | ||
}); | ||
expect(alerts).toHaveLength(0); | ||
}); | ||
}); | ||
}); |
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,10 @@ | ||
const sleep = (msec: number) => new Promise(resolve => setTimeout(resolve, msec)); | ||
export const intervalRepeater = async (callback: any, interval: number) => { | ||
let progress = await callback(); | ||
|
||
while (progress < 100) { | ||
progress = await callback(); | ||
console.log(`Active Scan progress : ${progress}%`); | ||
await sleep(interval); | ||
} | ||
} |
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,18 @@ | ||
import { Capabilities, ProxyConfig } from 'selenium-webdriver' | ||
const proxy : ProxyConfig = { | ||
proxyType: 'manual', | ||
httpProxy: 'localhost:8090', | ||
sslProxy: 'localhost:8090' | ||
}; | ||
|
||
export const SeleniumCapabilities = Capabilities.chrome(); | ||
SeleniumCapabilities.set('chromeOptions', { | ||
args: [ | ||
'--headless', | ||
'--disable-gpu', | ||
'--window-size=1024,768' | ||
], | ||
w3c: false | ||
}) | ||
.setAcceptInsecureCerts(true) | ||
.setProxy(proxy); |
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,157 @@ | ||
const ClientApi = require('zaproxy'); | ||
export const Mode = { | ||
Safe: 'safe', | ||
Protect: 'protect', | ||
Standard: 'standard', | ||
// Attack: 'attack' denger!! | ||
} as const; | ||
type Mode = typeof Mode[keyof typeof Mode]; | ||
|
||
export const ContextType = { | ||
FrontLogin: 'front_login.context', | ||
FrontGuest: 'front_guest.context', | ||
Admin: 'admin.context' | ||
} as const; | ||
type ContextType = typeof ContextType[keyof typeof ContextType]; | ||
|
||
export const Risk = { | ||
Informational: 0, | ||
Low: 1, | ||
Medium: 2, | ||
High: 3 | ||
} as const; | ||
type Risk = typeof Risk[keyof typeof Risk]; | ||
|
||
// see https://github.com/zaproxy/zaproxy/blob/main/zap/src/main/java/org/zaproxy/zap/extension/api/ApiResponseConversionUtils.java#L80-L122 | ||
export type HttpMessage = { | ||
id: string, | ||
type: string, | ||
timestamp: string, | ||
rtt: string, | ||
cookieParams: string, | ||
note: string, | ||
requestHeader: string, | ||
requestBody: string, | ||
responseHeader: string, | ||
responseBody: string, | ||
tags: string[] | ||
}; | ||
|
||
// see https://github.com/zaproxy/zaproxy/blob/main/zap/src/main/java/org/parosproxy/paros/core/scanner/Alert.java#L198 | ||
export type Alert = { | ||
alertId?: string, | ||
pluginId?: string | ||
name: string, | ||
risk: string, | ||
confidence?: string, | ||
description?: string, | ||
uri?: string, | ||
param?: string, | ||
attack?: string, | ||
otherInfo?: string, | ||
solution?: string, | ||
reference?: string, | ||
evidence?: string, | ||
cweId?: string, | ||
wascId?: string, | ||
message?: any, | ||
sourceHistoryId?: string, | ||
historyRef?: any; | ||
method?: string, | ||
postData?: string, | ||
msgUri?: string | ||
source?: string, | ||
alertRef?: string | ||
}; | ||
|
||
export class ZapClientError extends Error { | ||
constructor(message?: string) { | ||
super(message); | ||
}; | ||
} | ||
|
||
export class ZapClient { | ||
|
||
private apiKey: string | null; | ||
private proxy: string; | ||
private readonly zaproxy; | ||
|
||
constructor(proxy: string, apiKey?: string | null) { | ||
this.proxy = proxy; | ||
this.apiKey = apiKey != undefined ? apiKey : null; | ||
this.zaproxy = new ClientApi({ | ||
apiKey: this.apiKey, | ||
proxy: this.proxy | ||
}); | ||
} | ||
|
||
public async setMode(mode: Mode): Promise<void> { | ||
await this.zaproxy.core.setMode(mode); | ||
} | ||
|
||
public async newSession(name: string, override: boolean): Promise<void> { | ||
await this.zaproxy.core.newSession(name, override); | ||
} | ||
|
||
public async importContext(contextType: ContextType): Promise<void> { | ||
await this.zaproxy.context.importContext('/zap/wrk/' + contextType); | ||
} | ||
|
||
public async isForcedUserModeEnabled(): Promise<boolean> { | ||
const result = await this.zaproxy.forcedUser.isForcedUserModeEnabled(); | ||
return JSON.parse(result.forcedModeEnabled); | ||
} | ||
|
||
public async setForcedUserModeEnabled(bool?: boolean): Promise<void> { | ||
await this.zaproxy.forcedUser.setForcedUserModeEnabled(bool ?? true); | ||
} | ||
|
||
public async sendRequest(request: string, followRedirects?: boolean): Promise<HttpMessage> { | ||
const result = await this.zaproxy.core.sendRequest(request, followRedirects ?? false); | ||
return result.sendRequest; | ||
} | ||
|
||
public async getNumberOfMessages(url: string): Promise<number> { | ||
const result = await this.zaproxy.core.numberOfMessages(url); | ||
return JSON.parse(result.numberOfMessages); | ||
} | ||
|
||
public async getMessages(url: string, start?: number, count?: number): Promise<HttpMessage[]> { | ||
const result = await this.zaproxy.core.messages(url, start, count); | ||
return result.messages; | ||
} | ||
|
||
public async getLastMessage(url: string): Promise<HttpMessage> { | ||
const result = await this.getMessages(url, await this.getNumberOfMessages(url), 10); | ||
const message = result.pop(); | ||
if (message === undefined) { | ||
throw new ZapClientError('Invalid response'); | ||
} | ||
|
||
return message; | ||
} | ||
|
||
public async activeScanAsUser(url: string, contextId: number, userId: number, recurse?: boolean, scanPolicyName?: string | null, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', postData?: string | null): Promise<number> { | ||
const result = await this.zaproxy.ascan.scanAsUser(url, contextId, userId, recurse ?? false, scanPolicyName ?? null, method ?? 'GET', postData ?? null); | ||
return result.scan; | ||
} | ||
|
||
public async activeScan(url: string, recurse?: boolean, inScopeOnly?: boolean, scanPolicyName?: string | null, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', postData?: string | null, contextId?: number | null): Promise<number> { | ||
const result = await this.zaproxy.ascan.scan(url, recurse ?? false, inScopeOnly ?? true, scanPolicyName ?? null, method ?? 'GET', postData ?? null, contextId ?? null) | ||
return result.scan; | ||
} | ||
|
||
public async getActiveScanStatus(scanId: number): Promise<number> { | ||
const result = await this.zaproxy.ascan.status(scanId); | ||
return result.status; | ||
} | ||
|
||
public async snapshotSession(): Promise<void> { | ||
await this.zaproxy.core.snapshotSession(); | ||
} | ||
|
||
public async getAlerts(url: string, start?: number, count?:number, riskid?: Risk): Promise<Alert[]> { | ||
const result = await this.zaproxy.core.alerts(url, start, count, riskid); | ||
return result.alerts; | ||
} | ||
} |
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