์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ›ก๏ธ OWASP ZAP ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŒ…์œผ๋กœ ๋ณด์•ˆ ํ…Œ์ŠคํŠธ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ

๐Ÿ›ก๏ธ OWASP ZAP ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŒ…์œผ๋กœ ๋ณด์•ˆ ํ…Œ์ŠคํŠธ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ

์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๋ฐฐ์šฐ๋Š” ์›น ๋ณด์•ˆ ์ž๋™ํ™”์˜ ๋ชจ๋“  ๊ฒƒ

๐ŸŽฏ ์ด ๊ธ€์—์„œ ๋ฐฐ์šธ ๋‚ด์šฉ

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณด์•ˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™ํ™”ํ•˜๊ณ  ์‹ถ์€๋ฐ ์–ด๋””์„œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์•ผ ํ• ์ง€ ๋ง‰๋ง‰ํ–ˆ๋˜ ์  ์žˆ์ง€? OWASP ZAP(Zed Attack Proxy)์€ ์„ธ๊ณ„์—์„œ ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ์˜คํ”ˆ์†Œ์Šค ์›น ๋ณด์•ˆ ์Šค์บ๋„ˆ์•ผ. ์ด ๊ธ€์—์„œ๋Š” ZAP์˜ ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŒ… ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•ด์„œ ๋ฐ˜๋ณต์ ์ธ ๋ณด์•ˆ ํ…Œ์ŠคํŠธ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์นœ๊ตฌ์—๊ฒŒ ์„ค๋ช…ํ•˜๋“ฏ ์‰ฝ๊ฒŒ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ’ช

๐Ÿ” OWASP ZAP์ด ๋ญ๊ธธ๋ž˜?

OWASP ZAP์€ Open Web Application Security Project์—์„œ ๋งŒ๋“  ๋ฌด๋ฃŒ ๋ณด์•ˆ ํ…Œ์ŠคํŒ… ๋„๊ตฌ์•ผ. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด ์›น์‚ฌ์ดํŠธ๋‚˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ์ฐพ์•„์ฃผ๋Š” ์นœ๊ตฌ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ. ๐Ÿ•ต๏ธ

์ผ๋ฐ˜์ ์œผ๋กœ ๋ณด์•ˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜๋™์œผ๋กœ ํ•˜๋ ค๋ฉด ์—„์ฒญ๋‚œ ์‹œ๊ฐ„๊ณผ ๋…ธ๋ ฅ์ด ํ•„์š”ํ•ด. ๋งค๋ฒˆ ๊ฐ™์€ ํ…Œ์ŠคํŠธ๋ฅผ ๋ฐ˜๋ณตํ•˜๋Š” ๊ฑด ์ •๋ง ์ง€๋ฃจํ•˜๊ณ  ๋น„ํšจ์œจ์ ์ด์ง€. ๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒŒ ๋ฐ”๋กœ ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŒ…์ด์•ผ!

๐Ÿ’ก ์™œ ZAP์„ ์„ ํƒํ•ด์•ผ ํ• ๊นŒ?

โœ… ์™„์ „ ๋ฌด๋ฃŒ ์˜คํ”ˆ์†Œ์Šค
โœ… ํ™œ๋ฐœํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ์ง€์›
โœ… ๋‹ค์–‘ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด ์ง€์› (Python, Java, Node.js ๋“ฑ)
โœ… CI/CD ํŒŒ์ดํ”„๋ผ์ธ ํ†ตํ•ฉ ๊ฐ€๋Šฅ
โœ… ์ดˆ๋ณด์ž๋ถ€ํ„ฐ ์ „๋ฌธ๊ฐ€๊นŒ์ง€ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ธํ„ฐํŽ˜์ด์Šค

ZAP Python Script API Call Automated Security Test ZAP Automation Workflow

๐Ÿš€ ZAP ์ž๋™ํ™”์˜ ํ•ต์‹ฌ ๊ฐœ๋…

ZAP ์ž๋™ํ™”๋ฅผ ์ดํ•ดํ•˜๋ ค๋ฉด ๋จผ์ € ๋ช‡ ๊ฐ€์ง€ ํ•ต์‹ฌ ๊ฐœ๋…์„ ์•Œ์•„์•ผ ํ•ด. ์–ด๋ ต์ง€ ์•Š์œผ๋‹ˆ๊นŒ ์ฒœ์ฒœํžˆ ๋”ฐ๋ผ์™€๋ด! ๐Ÿ˜Š

1. ZAP API - ์ž๋™ํ™”์˜ ์‹œ์ž‘์ 

ZAP API๋Š” ZAP์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์•ผ. REST API ํ˜•ํƒœ๋กœ ์ œ๊ณต๋˜๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋–ค ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์—์„œ๋“  HTTP ์š”์ฒญ๋งŒ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์œผ๋ฉด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ”‘ ZAP API์˜ ์ฃผ์š” ๊ตฌ์„ฑ ์š”์†Œ

Core API: ZAP์˜ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ์ œ์–ด (์Šค์บ” ์‹œ์ž‘/์ค‘์ง€, ๊ฒฐ๊ณผ ์กฐํšŒ ๋“ฑ)
Spider API: ์›น์‚ฌ์ดํŠธ ํฌ๋กค๋ง ๊ธฐ๋Šฅ
Active Scan API: ๋Šฅ๋™์  ๋ณด์•ˆ ์Šค์บ” ์ˆ˜ํ–‰
Passive Scan API: ์ˆ˜๋™์  ๋ณด์•ˆ ๋ถ„์„
Ajax Spider API: JavaScript ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํฌ๋กค๋ง
Authentication API: ์ธ์ฆ ๊ด€๋ จ ์„ค์ •

2. ZAP ๋ฐ๋ชฌ ๋ชจ๋“œ

์ž๋™ํ™”๋ฅผ ์œ„ํ•ด์„œ๋Š” ZAP์„ ๋ฐ๋ชฌ(๋ฐฑ๊ทธ๋ผ์šด๋“œ) ๋ชจ๋“œ๋กœ ์‹คํ–‰ํ•ด์•ผ ํ•ด. GUI ์—†์ด ๋ช…๋ น์ค„์—์„œ ์‹คํ–‰๋˜๋Š” ๋ฐฉ์‹์ด์ง€. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์„œ๋ฒ„ ํ™˜๊ฒฝ์ด๋‚˜ CI/CD ํŒŒ์ดํ”„๋ผ์ธ์—์„œ๋„ ZAP์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ–ฅ๏ธ

# ZAP ๋ฐ๋ชฌ ๋ชจ๋“œ๋กœ ์‹คํ–‰ํ•˜๊ธฐ
zap.sh -daemon -host 0.0.0.0 -port 8080 -config api.key=your-api-key-here

3. ์ปจํ…์ŠคํŠธ(Context)์™€ ์„ธ์…˜(Session)

ZAP์—์„œ ์ปจํ…์ŠคํŠธ๋Š” ํ…Œ์ŠคํŠธํ•  ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฒ”์œ„๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฑฐ์•ผ. ์–ด๋–ค URL์„ ํฌํ•จํ•˜๊ณ  ์ œ์™ธํ• ์ง€, ์ธ์ฆ์€ ์–ด๋–ป๊ฒŒ ํ• ์ง€ ๋“ฑ์„ ์„ค์ •ํ•˜์ง€. ์„ธ์…˜์€ ์ด๋Ÿฐ ์„ค์ •๋“ค์„ ์ €์žฅํ•˜๊ณ  ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜.

๐Ÿ› ๏ธ ํ™˜๊ฒฝ ์„ค์ •ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ZAP ์ž๋™ํ™” ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•ด๋ณด์ž! ๋‹จ๊ณ„๋ณ„๋กœ ์ฐจ๊ทผ์ฐจ๊ทผ ์ง„ํ–‰ํ• ๊ฒŒ. ๐Ÿ“

Step 1: ZAP ์„ค์น˜

ZAP์€ Windows, macOS, Linux ๋ชจ๋‘์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๊ณต์‹ ์›น์‚ฌ์ดํŠธ(www.zaproxy.org)์—์„œ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ์ง€.

๐ŸชŸ Windows

์„ค์น˜ ํŒŒ์ผ(.exe)์„ ๋‹ค์šด๋กœ๋“œํ•ด์„œ ์‹คํ–‰ํ•˜๋ฉด ๋! ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด์•ผ.

๐ŸŽ macOS

DMG ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•˜๊ฑฐ๋‚˜ Homebrew๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด:
brew install --cask owasp-zap

๐Ÿง Linux

ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €๋‚˜ ์ง์ ‘ ๋‹ค์šด๋กœ๋“œ:
sudo apt install zaproxy

Step 2: Python ํ™˜๊ฒฝ ๊ตฌ์„ฑ

Python์œผ๋กœ ZAP์„ ์ œ์–ดํ•˜๋Š” ๊ฒŒ ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด์•ผ. Python ZAP ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•ด๋ณด์ž!

# Python ZAP ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜
pip install python-owasp-zap-v2.4

# ์ถ”๊ฐ€๋กœ ์œ ์šฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค
pip install requests
pip install python-dotenv  # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ด€๋ฆฌ์šฉ

Step 3: API ํ‚ค ์„ค์ •

๋ณด์•ˆ์„ ์œ„ํ•ด ZAP API๋Š” API ํ‚ค๋ฅผ ์š”๊ตฌํ•ด. ZAP์„ ์ฒ˜์Œ ์‹คํ–‰ํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋Š”๋ฐ, ์ด๊ฑธ ํ™•์ธํ•˜๊ณ  ์ €์žฅํ•ด๋‘์ž.

โš ๏ธ ๋ณด์•ˆ ์ฃผ์˜์‚ฌํ•ญ

API ํ‚ค๋Š” ์ ˆ๋Œ€ ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉํ•˜์ง€ ๋งˆ! ํ™˜๊ฒฝ๋ณ€์ˆ˜๋‚˜ ๋ณ„๋„์˜ ์„ค์ • ํŒŒ์ผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒŒ ์ข‹์•„. Git์— ์˜ฌ๋ฆด ๋•Œ ์‹ค์ˆ˜๋กœ ๋…ธ์ถœ๋˜๋ฉด ํฐ์ผ๋‚˜๊ฑฐ๋“ . ๐Ÿ”

# .env ํŒŒ์ผ ์˜ˆ์‹œ
ZAP_API_KEY=your-secret-api-key-here
ZAP_HOST=localhost
ZAP_PORT=8080
TARGET_URL=https://example.com

๐Ÿ’ป ์ฒซ ๋ฒˆ์งธ ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑํ•˜๊ธฐ

๋“œ๋””์–ด ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์‹œ๊ฐ„์ด์•ผ! ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์Šค์บ” ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž. ๐ŸŽจ

๊ธฐ๋ณธ ์Šค์บ” ์Šคํฌ๋ฆฝํŠธ

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import time
import os
from zapv2 import ZAPv2
from dotenv import load_dotenv

# ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กœ๋“œ
load_dotenv()

# ZAP ์„ค์ •
ZAP_API_KEY = os.getenv('ZAP_API_KEY')
ZAP_HOST = os.getenv('ZAP_HOST', 'localhost')
ZAP_PORT = int(os.getenv('ZAP_PORT', 8080))
TARGET_URL = os.getenv('TARGET_URL')

# ZAP ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
zap = ZAPv2(
    apikey=ZAP_API_KEY,
    proxies={
        'http': f'http://{ZAP_HOST}:{ZAP_PORT}',
        'https': f'http://{ZAP_HOST}:{ZAP_PORT}'
    }
)

def main():
    print(f"๐Ÿš€ ZAP ์ž๋™ํ™” ์Šค์บ” ์‹œ์ž‘!")
    print(f"๐Ÿ“ ๋Œ€์ƒ URL: {TARGET_URL}")
    
    # 1. ์ƒˆ ์„ธ์…˜ ์‹œ์ž‘
    print("\nโœจ ์ƒˆ ์„ธ์…˜ ์ƒ์„ฑ ์ค‘...")
    zap.core.new_session()
    
    # 2. Spider ์‹คํ–‰ (์‚ฌ์ดํŠธ ํฌ๋กค๋ง)
    print("\n๐Ÿ•ท๏ธ Spider ํฌ๋กค๋ง ์‹œ์ž‘...")
    scan_id = zap.spider.scan(TARGET_URL)
    
    # Spider ์ง„ํ–‰์ƒํ™ฉ ๋ชจ๋‹ˆํ„ฐ๋ง
    while int(zap.spider.status(scan_id)) < 100:
        progress = zap.spider.status(scan_id)
        print(f"Spider ์ง„ํ–‰๋ฅ : {progress}%")
        time.sleep(2)
    
    print("โœ… Spider ํฌ๋กค๋ง ์™„๋ฃŒ!")
    
    # 3. Passive Scan ๋Œ€๊ธฐ
    print("\n๐Ÿ” Passive Scan ์ง„ํ–‰ ์ค‘...")
    while int(zap.pscan.records_to_scan) > 0:
        records = zap.pscan.records_to_scan
        print(f"๋‚จ์€ ๋ ˆ์ฝ”๋“œ: {records}")
        time.sleep(2)
    
    print("โœ… Passive Scan ์™„๋ฃŒ!")
    
    # 4. Active Scan ์‹คํ–‰
    print("\nโšก Active Scan ์‹œ์ž‘...")
    scan_id = zap.ascan.scan(TARGET_URL)
    
    # Active Scan ์ง„ํ–‰์ƒํ™ฉ ๋ชจ๋‹ˆํ„ฐ๋ง
    while int(zap.ascan.status(scan_id)) < 100:
        progress = zap.ascan.status(scan_id)
        print(f"Active Scan ์ง„ํ–‰๋ฅ : {progress}%")
        time.sleep(5)
    
    print("โœ… Active Scan ์™„๋ฃŒ!")
    
    # 5. ๊ฒฐ๊ณผ ์ˆ˜์ง‘
    print("\n๐Ÿ“Š ์Šค์บ” ๊ฒฐ๊ณผ ์ˆ˜์ง‘ ์ค‘...")
    alerts = zap.core.alerts(baseurl=TARGET_URL)
    
    # ์‹ฌ๊ฐ๋„๋ณ„ ๋ถ„๋ฅ˜
    high_alerts = [a for a in alerts if a['risk'] == 'High']
    medium_alerts = [a for a in alerts if a['risk'] == 'Medium']
    low_alerts = [a for a in alerts if a['risk'] == 'Low']
    info_alerts = [a for a in alerts if a['risk'] == 'Informational']
    
    # ๊ฒฐ๊ณผ ์ถœ๋ ฅ
    print("\n" + "="*50)
    print("๐ŸŽฏ ์Šค์บ” ๊ฒฐ๊ณผ ์š”์•ฝ")
    print("="*50)
    print(f"๐Ÿ”ด High: {len(high_alerts)}๊ฐœ")
    print(f"๐ŸŸก Medium: {len(medium_alerts)}๊ฐœ")
    print(f"๐ŸŸข Low: {len(low_alerts)}๊ฐœ")
    print(f"โ„น๏ธ  Info: {len(info_alerts)}๊ฐœ")
    print("="*50)
    
    # High ์•Œ๋ฆผ ์ƒ์„ธ ์ถœ๋ ฅ
    if high_alerts:
        print("\nโš ๏ธ ์‹ฌ๊ฐํ•œ ์ทจ์•ฝ์  ๋ฐœ๊ฒฌ!")
        for alert in high_alerts:
            print(f"\n์ œ๋ชฉ: {alert['alert']}")
            print(f"URL: {alert['url']}")
            print(f"์„ค๋ช…: {alert['description'][:100]}...")
    
    # 6. HTML ๋ฆฌํฌํŠธ ์ƒ์„ฑ
    print("\n๐Ÿ“„ HTML ๋ฆฌํฌํŠธ ์ƒ์„ฑ ์ค‘...")
    html_report = zap.core.htmlreport()
    
    report_filename = f"zap_report_{time.strftime('%Y%m%d_%H%M%S')}.html"
    with open(report_filename, 'w', encoding='utf-8') as f:
        f.write(html_report)
    
    print(f"โœ… ๋ฆฌํฌํŠธ ์ €์žฅ ์™„๋ฃŒ: {report_filename}")
    print("\n๐ŸŽ‰ ๋ชจ๋“  ์ž‘์—… ์™„๋ฃŒ!")

if __name__ == "__main__":
    main()

์ด ์Šคํฌ๋ฆฝํŠธ๋Š” ZAP ์ž๋™ํ™”์˜ ๊ธฐ๋ณธ ํ๋ฆ„์„ ๋ณด์—ฌ์ค˜. Spider๋กœ ์‚ฌ์ดํŠธ๋ฅผ ํฌ๋กค๋งํ•˜๊ณ , Passive Scan๊ณผ Active Scan์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰ํ•œ ๋‹ค์Œ, ๊ฒฐ๊ณผ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  ๋ฆฌํฌํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฑฐ์ง€! ๐ŸŽฏ

๐Ÿ’ก Pro Tip

์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ํƒ€์ž„์•„์›ƒ ์„ค์ •, ์—๋Ÿฌ ํ•ธ๋“ค๋ง, ๋กœ๊น… ๋“ฑ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ด. ์œ„ ์ฝ”๋“œ๋Š” ํ•™์Šต์šฉ ๊ธฐ๋ณธ ๋ฒ„์ „์ด๋‹ˆ๊นŒ ์‹ค์ œ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋” ๊ฒฌ๊ณ ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๋Š” ์  ์žŠ์ง€ ๋งˆ!

๐ŸŽฏ ๊ณ ๊ธ‰ ์ž๋™ํ™” ๊ธฐ๋ฒ•

๊ธฐ๋ณธ์„ ๋งˆ์Šคํ„ฐํ–ˆ๋‹ค๋ฉด ์ด์ œ ์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์„ ์•Œ์•„๋ณผ ์ฐจ๋ก€์•ผ! ์‹ค๋ฌด์—์„œ ์ •๋ง ์œ ์šฉํ•œ ํ…Œํฌ๋‹‰๋“ค์ด๋‹ˆ๊นŒ ์ง‘์ค‘ํ•ด๋ด. ๐Ÿ”ฅ

1. ์ธ์ฆ์ด ํ•„์š”ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ…Œ์ŠคํŠธ

๋Œ€๋ถ€๋ถ„์˜ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•˜์ง€? ZAP์—์„œ ์ธ์ฆ์„ ์ž๋™ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž!

def setup_authentication(zap, target_url, context_name):
    """
    ํผ ๊ธฐ๋ฐ˜ ์ธ์ฆ ์„ค์ •
    """
    print("๐Ÿ” ์ธ์ฆ ์„ค์ • ์ค‘...")
    
    # ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
    context_id = zap.context.new_context(context_name)
    
    # URL์„ ์ปจํ…์ŠคํŠธ์— ํฌํ•จ
    zap.context.include_in_context(context_name, f"{target_url}.*")
    
    # ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์„ค์ •
    login_url = f"{target_url}/login"
    zap.authentication.set_authentication_method(
        contextid=context_id,
        authmethodname='formBasedAuthentication',
        authmethodconfigparams=f'loginUrl={login_url}&loginRequestData=username={{%username%}}&password={{%password%}}'
    )
    
    # ์‚ฌ์šฉ์ž ์ถ”๊ฐ€
    user_id = zap.users.new_user(context_id, 'test_user')
    zap.users.set_authentication_credentials(
        contextid=context_id,
        userid=user_id,
        authcredentialsconfigparams='username=testuser&password=testpass123'
    )
    
    # ์‚ฌ์šฉ์ž ํ™œ์„ฑํ™”
    zap.users.set_user_enabled(context_id, user_id, True)
    
    print("โœ… ์ธ์ฆ ์„ค์ • ์™„๋ฃŒ!")
    return context_id, user_id

2. ์Šค์บ” ์ •์ฑ… ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

๋ชจ๋“  ์ทจ์•ฝ์ ์„ ๋‹ค ๊ฒ€์‚ฌํ•˜๋ฉด ์‹œ๊ฐ„์ด ๋„ˆ๋ฌด ์˜ค๋ž˜ ๊ฑธ๋ ค. ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์„ ํƒ์ ์œผ๋กœ ์Šค์บ”ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด์•ผ!

def create_custom_scan_policy(zap, policy_name):
    """
    ์ปค์Šคํ…€ ์Šค์บ” ์ •์ฑ… ์ƒ์„ฑ
    """
    print(f"โš™๏ธ ์ปค์Šคํ…€ ์Šค์บ” ์ •์ฑ… '{policy_name}' ์ƒ์„ฑ ์ค‘...")
    
    # ๋ชจ๋“  ์Šค์บ๋„ˆ ๋น„ํ™œ์„ฑํ™”
    scanners = zap.ascan.scanners()
    for scanner in scanners:
        zap.ascan.set_scanner_alert_threshold(
            id=scanner['id'],
            alertthreshold='OFF'
        )
    
    # ์ค‘์š”ํ•œ ์Šค์บ๋„ˆ๋งŒ ํ™œ์„ฑํ™”
    important_scanners = [
        '40012',  # Cross Site Scripting (Reflected)
        '40014',  # Cross Site Scripting (Persistent)
        '40018',  # SQL Injection
        '90019',  # Server Side Code Injection
        '90020',  # Remote OS Command Injection
    ]
    
    for scanner_id in important_scanners:
        zap.ascan.set_scanner_alert_threshold(
            id=scanner_id,
            alertthreshold='MEDIUM'
        )
        zap.ascan.set_scanner_attack_strength(
            id=scanner_id,
            attackstrength='HIGH'
        )
    
    print("โœ… ์Šค์บ” ์ •์ฑ… ์„ค์ • ์™„๋ฃŒ!")
    print(f"ํ™œ์„ฑํ™”๋œ ์Šค์บ๋„ˆ: {len(important_scanners)}๊ฐœ")

3. Ajax Spider ํ™œ์šฉ

์š”์ฆ˜ ์›น์‚ฌ์ดํŠธ๋Š” ๋Œ€๋ถ€๋ถ„ JavaScript๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜์ž–์•„? ์ผ๋ฐ˜ Spider๋กœ๋Š” ์ฐพ์ง€ ๋ชปํ•˜๋Š” ํŽ˜์ด์ง€๋“ค์„ Ajax Spider๋กœ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด! ๐Ÿ•ธ๏ธ

def run_ajax_spider(zap, target_url):
    """
    Ajax Spider ์‹คํ–‰ (JavaScript ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์šฉ)
    """
    print("๐ŸŒ Ajax Spider ์‹œ์ž‘...")
    
    # Ajax Spider ์„ค์ •
    zap.ajaxSpider.set_option_max_duration('10')  # ์ตœ๋Œ€ 10๋ถ„
    zap.ajaxSpider.set_option_number_of_browsers('2')  # ๋ธŒ๋ผ์šฐ์ € 2๊ฐœ ์‚ฌ์šฉ
    
    # Ajax Spider ์‹คํ–‰
    zap.ajaxSpider.scan(target_url)
    
    # ์ง„ํ–‰์ƒํ™ฉ ๋ชจ๋‹ˆํ„ฐ๋ง
    while zap.ajaxSpider.status == 'running':
        print(f"Ajax Spider ์‹คํ–‰ ์ค‘... (๋ฐœ๊ฒฌ๋œ URL: {zap.ajaxSpider.number_of_results})")
        time.sleep(5)
    
    print(f"โœ… Ajax Spider ์™„๋ฃŒ! ์ด {zap.ajaxSpider.number_of_results}๊ฐœ URL ๋ฐœ๊ฒฌ")
    
    # ๋ฐœ๊ฒฌ๋œ URL ๋ชฉ๋ก ๋ฐ˜ํ™˜
    return zap.ajaxSpider.results()

๐Ÿ”„ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ํ†ตํ•ฉ

์ง„์งœ ์ž๋™ํ™”์˜ ํž˜์€ CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ํ†ตํ•ฉํ•  ๋•Œ ๋ฐœํœ˜๋ผ! ์ฝ”๋“œ๊ฐ€ ์ปค๋ฐ‹๋  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋ณด์•ˆ ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๊ฑฐ์ง€. ๐Ÿš€

Jenkins ํ†ตํ•ฉ ์˜ˆ์‹œ

pipeline {
    agent any
    
    environment {
        ZAP_HOST = 'localhost'
        ZAP_PORT = '8080'
        TARGET_URL = 'https://staging.example.com'
    }
    
    stages {
        stage('Start ZAP') {
            steps {
                script {
                    // ZAP ๋ฐ๋ชฌ ์‹œ์ž‘
                    sh '''
                        docker run -d --name zap \
                        -p 8080:8080 \
                        -v $(pwd):/zap/wrk/:rw \
                        owasp/zap2docker-stable \
                        zap.sh -daemon -host 0.0.0.0 -port 8080 \
                        -config api.key=${ZAP_API_KEY}
                    '''
                    sleep(time: 30, unit: 'SECONDS')
                }
            }
        }
        
        stage('Run Security Scan') {
            steps {
                script {
                    sh 'python3 zap_automation.py'
                }
            }
        }
        
        stage('Generate Report') {
            steps {
                script {
                    // HTML ๋ฆฌํฌํŠธ ์•„์นด์ด๋ธŒ
                    archiveArtifacts artifacts: 'zap_report_*.html'
                    
                    // ์ทจ์•ฝ์ ์ด ๋ฐœ๊ฒฌ๋˜๋ฉด ๋นŒ๋“œ ์‹คํŒจ
                    sh '''
                        python3 check_vulnerabilities.py
                    '''
                }
            }
        }
    }
    
    post {
        always {
            // ZAP ์ปจํ…Œ์ด๋„ˆ ์ •๋ฆฌ
            sh 'docker stop zap && docker rm zap'
        }
        failure {
            // ์‹คํŒจ ์‹œ ์•Œ๋ฆผ
            emailext (
                subject: "๋ณด์•ˆ ์Šค์บ” ์‹คํŒจ: ${env.JOB_NAME}",
                body: "๋ณด์•ˆ ์ทจ์•ฝ์ ์ด ๋ฐœ๊ฒฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌํฌํŠธ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.",
                to: "security-team@example.com"
            )
        }
    }
}

GitHub Actions ํ†ตํ•ฉ ์˜ˆ์‹œ

name: Security Scan with ZAP

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  zap-scan:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v2
    
    - name: Setup Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    
    - name: Install dependencies
      run: |
        pip install python-owasp-zap-v2.4
        pip install requests
    
    - name: Run ZAP Docker
      run: |
        docker run -d --name zap \
        -p 8080:8080 \
        owasp/zap2docker-stable \
        zap.sh -daemon -host 0.0.0.0 -port 8080 \
        -config api.disablekey=true
        sleep 30
    
    - name: Run ZAP Scan
      env:
        TARGET_URL: ${{ secrets.TARGET_URL }}
      run: python3 zap_automation.py
    
    - name: Upload Report
      uses: actions/upload-artifact@v2
      with:
        name: zap-report
        path: zap_report_*.html
    
    - name: Check Vulnerabilities
      run: python3 check_vulnerabilities.py
    
    - name: Stop ZAP
      if: always()
      run: docker stop zap && docker rm zap

๐Ÿ’ก CI/CD ํ†ตํ•ฉ ํŒ

๐ŸŽฏ ๋‹จ๊ณ„์  ์ ‘๊ทผ: ์ฒ˜์Œ์—๋Š” ๊ฒฝ๊ณ ๋งŒ ํ•˜๊ณ , ์ ์ฐจ ๋นŒ๋“œ ์‹คํŒจ ์กฐ๊ฑด์„ ๊ฐ•ํ™”ํ•ด๋‚˜๊ฐ€
๐ŸŽฏ ๋ณ‘๋ ฌ ์‹คํ–‰: ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ์„ ๋™์‹œ์— ํ…Œ์ŠคํŠธํ•˜๋ฉด ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์–ด
๐ŸŽฏ ์บ์‹ฑ ํ™œ์šฉ: ZAP ์„ธ์…˜์„ ์บ์‹ฑํ•˜๋ฉด ๋ฐ˜๋ณต ์Šค์บ”์ด ๋นจ๋ผ์ ธ
๐ŸŽฏ ์•Œ๋ฆผ ์„ค์ •: Slack, Teams ๋“ฑ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ์„ ๋ฐ›๋„๋ก ์„ค์ •ํ•˜์ž

๐Ÿ“Š ๊ฒฐ๊ณผ ๋ถ„์„๊ณผ ๋ฆฌํฌํŒ…

์Šค์บ”์„ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ๊ฒฐ๊ณผ๋ฅผ ์ œ๋Œ€๋กœ ๋ถ„์„ํ•˜๊ณ  ๋ฆฌํฌํŠธํ•˜๋Š” ๊ฒƒ๋„ ์—„์ฒญ ์ค‘์š”ํ•ด! ๐Ÿ“ˆ

๊ฒฐ๊ณผ ํ•„ํ„ฐ๋ง๊ณผ ๋ถ„๋ฅ˜

def analyze_scan_results(zap, target_url):
    """
    ์Šค์บ” ๊ฒฐ๊ณผ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ๋ถ„๋ฅ˜
    """
    print("๐Ÿ“Š ์Šค์บ” ๊ฒฐ๊ณผ ๋ถ„์„ ์ค‘...")
    
    # ๋ชจ๋“  ์•Œ๋ฆผ ๊ฐ€์ ธ์˜ค๊ธฐ
    alerts = zap.core.alerts(baseurl=target_url)
    
    # ์‹ฌ๊ฐ๋„๋ณ„ ๋ถ„๋ฅ˜
    severity_counts = {
        'High': 0,
        'Medium': 0,
        'Low': 0,
        'Informational': 0
    }
    
    # ์ทจ์•ฝ์  ํƒ€์ž…๋ณ„ ๋ถ„๋ฅ˜
    vulnerability_types = {}
    
    for alert in alerts:
        # ์‹ฌ๊ฐ๋„ ์นด์šดํŠธ
        severity_counts[alert['risk']] += 1
        
        # ํƒ€์ž…๋ณ„ ๋ถ„๋ฅ˜
        alert_name = alert['alert']
        if alert_name not in vulnerability_types:
            vulnerability_types[alert_name] = {
                'count': 0,
                'urls': [],
                'risk': alert['risk']
            }
        
        vulnerability_types[alert_name]['count'] += 1
        vulnerability_types[alert_name]['urls'].append(alert['url'])
    
    # ๊ฒฐ๊ณผ ์ถœ๋ ฅ
    print("\n" + "="*60)
    print("๐Ÿ“ˆ ์Šค์บ” ๊ฒฐ๊ณผ ์ƒ์„ธ ๋ถ„์„")
    print("="*60)
    
    print("\n๐ŸŽฏ ์‹ฌ๊ฐ๋„๋ณ„ ํ†ต๊ณ„:")
    for severity, count in severity_counts.items():
        emoji = {'High': '๐Ÿ”ด', 'Medium': '๐ŸŸก', 'Low': '๐ŸŸข', 'Informational': 'โ„น๏ธ'}
        print(f"{emoji[severity]} {severity}: {count}๊ฐœ")
    
    print("\n๐Ÿ” ์ทจ์•ฝ์  ํƒ€์ž…๋ณ„ ํ†ต๊ณ„:")
    sorted_vulns = sorted(
        vulnerability_types.items(),
        key=lambda x: x[1]['count'],
        reverse=True
    )
    
    for vuln_name, vuln_data in sorted_vulns[:10]:  # ์ƒ์œ„ 10๊ฐœ๋งŒ
        print(f"\nโ€ข {vuln_name}")
        print(f"  - ๋ฐœ๊ฒฌ ํšŸ์ˆ˜: {vuln_data['count']}ํšŒ")
        print(f"  - ์‹ฌ๊ฐ๋„: {vuln_data['risk']}")
        print(f"  - ์˜ํ–ฅ๋ฐ›๋Š” URL: {len(vuln_data['urls'])}๊ฐœ")
    
    return {
        'severity_counts': severity_counts,
        'vulnerability_types': vulnerability_types,
        'total_alerts': len(alerts)
    }

์ปค์Šคํ…€ ๋ฆฌํฌํŠธ ์ƒ์„ฑ

ZAP์˜ ๊ธฐ๋ณธ ๋ฆฌํฌํŠธ๋„ ์ข‹์ง€๋งŒ, ํŒ€์— ๋งž๋Š” ์ปค์Šคํ…€ ๋ฆฌํฌํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด ๋” ์œ ์šฉํ•ด!

import json
from datetime import datetime

def generate_custom_report(analysis_results, target_url, output_format='json'):
    """
    ์ปค์Šคํ…€ ๋ฆฌํฌํŠธ ์ƒ์„ฑ
    """
    report_data = {
        'scan_info': {
            'target_url': target_url,
            'scan_date': datetime.now().isoformat(),
            'total_vulnerabilities': analysis_results['total_alerts']
        },
        'severity_summary': analysis_results['severity_counts'],
        'vulnerability_details': analysis_results['vulnerability_types']
    }
    
    if output_format == 'json':
        filename = f"custom_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(report_data, f, indent=2, ensure_ascii=False)
        print(f"โœ… JSON ๋ฆฌํฌํŠธ ์ƒ์„ฑ: {filename}")
    
    elif output_format == 'markdown':
        filename = f"custom_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
        with open(filename, 'w', encoding='utf-8') as f:
            f.write(f"# ๋ณด์•ˆ ์Šค์บ” ๋ฆฌํฌํŠธ\n\n")
            f.write(f"**๋Œ€์ƒ URL:** {target_url}\n")
            f.write(f"**์Šค์บ” ์ผ์‹œ:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
            
            f.write("## ์‹ฌ๊ฐ๋„๋ณ„ ์š”์•ฝ\n\n")
            for severity, count in report_data['severity_summary'].items():
                f.write(f"- **{severity}:** {count}๊ฐœ\n")
            
            f.write("\n## ์ทจ์•ฝ์  ์ƒ์„ธ\n\n")
            for vuln_name, vuln_data in report_data['vulnerability_details'].items():
                f.write(f"### {vuln_name}\n")
                f.write(f"- ์‹ฌ๊ฐ๋„: {vuln_data['risk']}\n")
                f.write(f"- ๋ฐœ๊ฒฌ ํšŸ์ˆ˜: {vuln_data['count']}ํšŒ\n\n")
        
        print(f"โœ… Markdown ๋ฆฌํฌํŠธ ์ƒ์„ฑ: {filename}")
    
    return filename

๐ŸŽจ ์‹ค์ „ ํ™œ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ ์ƒํ™ฉ์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ณด์ž! ๐Ÿ’ผ

์‹œ๋‚˜๋ฆฌ์˜ค 1: ์ •๊ธฐ ๋ณด์•ˆ ์ ๊ฒ€ ์ž๋™ํ™”

๋งค์ฃผ ์›”์š”์ผ ์˜ค์ „ 2์‹œ์— ์ž๋™์œผ๋กœ ๋ณด์•ˆ ์Šค์บ”์„ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ด๋ฉ”์ผ๋กœ ๋ฐ›๋Š” ์‹œ์Šคํ…œ์ด์•ผ.

import schedule
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

def weekly_security_scan():
    """
    ์ฃผ๊ฐ„ ์ •๊ธฐ ๋ณด์•ˆ ์Šค์บ”
    """
    print("๐Ÿ—“๏ธ ์ฃผ๊ฐ„ ์ •๊ธฐ ๋ณด์•ˆ ์Šค์บ” ์‹œ์ž‘...")
    
    # ZAP ์Šค์บ” ์‹คํ–‰
    zap = initialize_zap()
    run_full_scan(zap, TARGET_URL)
    
    # ๊ฒฐ๊ณผ ๋ถ„์„
    results = analyze_scan_results(zap, TARGET_URL)
    
    # ๋ฆฌํฌํŠธ ์ƒ์„ฑ
    report_file = generate_custom_report(results, TARGET_URL, 'markdown')
    
    # ์ด๋ฉ”์ผ ๋ฐœ์†ก
    send_email_report(
        subject=f"์ฃผ๊ฐ„ ๋ณด์•ˆ ์Šค์บ” ๋ฆฌํฌํŠธ - {datetime.now().strftime('%Y-%m-%d')}",
        body=f"์ด๋ฒˆ ์ฃผ ๋ณด์•ˆ ์Šค์บ”์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\n\n"
             f"์ด {results['total_alerts']}๊ฐœ์˜ ์ด์Šˆ๊ฐ€ ๋ฐœ๊ฒฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\n"
             f"์ƒ์„ธ ๋‚ด์šฉ์€ ์ฒจ๋ถ€ ํŒŒ์ผ์„ ํ™•์ธํ•˜์„ธ์š”.",
        attachment=report_file
    )
    
    print("โœ… ์ฃผ๊ฐ„ ๋ณด์•ˆ ์Šค์บ” ์™„๋ฃŒ ๋ฐ ๋ฆฌํฌํŠธ ๋ฐœ์†ก!")

def send_email_report(subject, body, attachment):
    """
    ์ด๋ฉ”์ผ๋กœ ๋ฆฌํฌํŠธ ๋ฐœ์†ก
    """
    msg = MIMEMultipart()
    msg['From'] = 'security-bot@example.com'
    msg['To'] = 'security-team@example.com'
    msg['Subject'] = subject
    
    msg.attach(MIMEText(body, 'plain'))
    
    # ์ฒจ๋ถ€ํŒŒ์ผ ์ถ”๊ฐ€
    with open(attachment, 'rb') as f:
        part = MIMEBase('application', 'octet-stream')
        part.set_payload(f.read())
        encoders.encode_base64(part)
        part.add_header('Content-Disposition', f'attachment; filename={attachment}')
        msg.attach(part)
    
    # SMTP ์„œ๋ฒ„๋กœ ์ „์†ก
    server = smtplib.SMTP('smtp.example.com', 587)
    server.starttls()
    server.login('security-bot@example.com', 'password')
    server.send_message(msg)
    server.quit()

# ์Šค์ผ€์ค„ ์„ค์ •
schedule.every().monday.at("02:00").do(weekly_security_scan)

# ์Šค์ผ€์ค„๋Ÿฌ ์‹คํ–‰
while True:
    schedule.run_pending()
    time.sleep(60)

์‹œ๋‚˜๋ฆฌ์˜ค 2: API ์—”๋“œํฌ์ธํŠธ ๋ณด์•ˆ ํ…Œ์ŠคํŠธ

REST API์˜ ๊ฐ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ž๋™์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ์•ผ. API ๊ฐœ๋ฐœํ•  ๋•Œ ์ •๋ง ์œ ์šฉํ•ด! ๐Ÿ”Œ

def test_api_endpoints(zap, base_url, api_spec):
    """
    OpenAPI/Swagger ์ŠคํŽ™ ๊ธฐ๋ฐ˜ API ํ…Œ์ŠคํŠธ
    """
    print("๐Ÿ”Œ API ์—”๋“œํฌ์ธํŠธ ๋ณด์•ˆ ํ…Œ์ŠคํŠธ ์‹œ์ž‘...")
    
    # OpenAPI ์ŠคํŽ™ ์ž„ํฌํŠธ
    zap.openapi.import_url(api_spec)
    
    # ๊ฐ ์—”๋“œํฌ์ธํŠธ๋ณ„ ํ…Œ์ŠคํŠธ
    endpoints = [
        {'method': 'GET', 'path': '/api/users'},
        {'method': 'POST', 'path': '/api/users'},
        {'method': 'GET', 'path': '/api/users/{id}'},
        {'method': 'PUT', 'path': '/api/users/{id}'},
        {'method': 'DELETE', 'path': '/api/users/{id}'},
    ]
    
    results = {}
    
    for endpoint in endpoints:
        url = f"{base_url}{endpoint['path']}"
        print(f"\nํ…Œ์ŠคํŠธ ์ค‘: {endpoint['method']} {url}")
        
        # Active Scan ์‹คํ–‰
        scan_id = zap.ascan.scan(url)
        
        # ์Šค์บ” ์™„๋ฃŒ ๋Œ€๊ธฐ
        while int(zap.ascan.status(scan_id)) < 100:
            time.sleep(2)
        
        # ๊ฒฐ๊ณผ ์ˆ˜์ง‘
        alerts = zap.core.alerts(baseurl=url)
        results[f"{endpoint['method']} {endpoint['path']}"] = {
            'alert_count': len(alerts),
            'high_risk': len([a for a in alerts if a['risk'] == 'High']),
            'alerts': alerts
        }
        
        print(f"โœ… ์™„๋ฃŒ: {len(alerts)}๊ฐœ ์ด์Šˆ ๋ฐœ๊ฒฌ")
    
    # ๊ฒฐ๊ณผ ์š”์•ฝ
    print("\n" + "="*60)
    print("๐Ÿ“Š API ์—”๋“œํฌ์ธํŠธ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ")
    print("="*60)
    
    for endpoint, data in results.items():
        print(f"\n{endpoint}")
        print(f"  ์ด ์ด์Šˆ: {data['alert_count']}๊ฐœ")
        print(f"  High ์œ„ํ—˜: {data['high_risk']}๊ฐœ")
    
    return results

์‹œ๋‚˜๋ฆฌ์˜ค 3: ํšŒ๊ท€ ํ…Œ์ŠคํŠธ (Regression Testing)

์ด์ „ ์Šค์บ” ๊ฒฐ๊ณผ์™€ ๋น„๊ตํ•ด์„œ ์ƒˆ๋กœ์šด ์ทจ์•ฝ์ ์ด ์ƒ๊ฒผ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฑฐ์•ผ!

def regression_test(zap, target_url, baseline_file):
    """
    ํšŒ๊ท€ ํ…Œ์ŠคํŠธ - ์ด์ „ ๊ฒฐ๊ณผ์™€ ๋น„๊ต
    """
    print("๐Ÿ”„ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ ์‹œ์ž‘...")
    
    # ํ˜„์žฌ ์Šค์บ” ์‹คํ–‰
    current_results = run_full_scan(zap, target_url)
    current_alerts = zap.core.alerts(baseurl=target_url)
    
    # ๋ฒ ์ด์Šค๋ผ์ธ ๋กœ๋“œ
    with open(baseline_file, 'r') as f:
        baseline_alerts = json.load(f)
    
    # ์ƒˆ๋กœ์šด ์ทจ์•ฝ์  ์ฐพ๊ธฐ
    new_vulnerabilities = []
    fixed_vulnerabilities = []
    
    current_alert_ids = {alert['alert'] + alert['url'] for alert in current_alerts}
    baseline_alert_ids = {alert['alert'] + alert['url'] for alert in baseline_alerts}
    
    # ์ƒˆ๋กœ ๋ฐœ๊ฒฌ๋œ ์ทจ์•ฝ์ 
    for alert in current_alerts:
        alert_id = alert['alert'] + alert['url']
        if alert_id not in baseline_alert_ids:
            new_vulnerabilities.append(alert)
    
    # ์ˆ˜์ •๋œ ์ทจ์•ฝ์ 
    for alert in baseline_alerts:
        alert_id = alert['alert'] + alert['url']
        if alert_id not in current_alert_ids:
            fixed_vulnerabilities.append(alert)
    
    # ๊ฒฐ๊ณผ ์ถœ๋ ฅ
    print("\n" + "="*60)
    print("๐Ÿ”„ ํšŒ๊ท€ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ")
    print("="*60)
    print(f"โœจ ์ƒˆ๋กœ ๋ฐœ๊ฒฌ๋œ ์ทจ์•ฝ์ : {len(new_vulnerabilities)}๊ฐœ")
    print(f"โœ… ์ˆ˜์ •๋œ ์ทจ์•ฝ์ : {len(fixed_vulnerabilities)}๊ฐœ")
    
    if new_vulnerabilities:
        print("\nโš ๏ธ ์ƒˆ๋กœ์šด ์ทจ์•ฝ์  ์ƒ์„ธ:")
        for vuln in new_vulnerabilities:
            print(f"  โ€ข {vuln['alert']} ({vuln['risk']})")
            print(f"    URL: {vuln['url']}")
    
    if fixed_vulnerabilities:
        print("\n๐ŸŽ‰ ์ˆ˜์ •๋œ ์ทจ์•ฝ์ :")
        for vuln in fixed_vulnerabilities:
            print(f"  โ€ข {vuln['alert']}")
    
    # ํ˜„์žฌ ๊ฒฐ๊ณผ๋ฅผ ์ƒˆ ๋ฒ ์ด์Šค๋ผ์ธ์œผ๋กœ ์ €์žฅ
    with open(baseline_file, 'w') as f:
        json.dump(current_alerts, f, indent=2)
    
    return {
        'new': new_vulnerabilities,
        'fixed': fixed_vulnerabilities
    }

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

ZAP ์Šค์บ”์€ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์–ด. ํšจ์œจ์ ์œผ๋กœ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•๋“ค์„ ์•Œ์•„๋ณด์ž! โฑ๏ธ

๐ŸŽฏ ์Šค์บ” ๋ฒ”์œ„ ์ œํ•œ

ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์Šค์บ”ํ•˜๋„๋ก ์ปจํ…์ŠคํŠธ๋ฅผ ์ •ํ™•ํžˆ ์„ค์ •ํ•ด. ๋ถˆํ•„์š”ํ•œ URL์€ ์ œ์™ธํ•˜์ž!

zap.context.exclude_from_context(
    'MyContext',
    '.*logout.*'
)

โš™๏ธ ์Šค๋ ˆ๋“œ ์ˆ˜ ์กฐ์ •

์„œ๋ฒ„ ์„ฑ๋Šฅ์— ๋งž์ถฐ ๋™์‹œ ์‹คํ–‰ ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ์กฐ์ •ํ•˜๋ฉด ์†๋„๊ฐ€ ๋นจ๋ผ์ ธ!

zap.ascan.set_option_thread_per_host(5)
zap.ascan.set_option_max_scan_duration_in_mins(30)

๐Ÿ’พ ์„ธ์…˜ ์žฌ์‚ฌ์šฉ

Spider ๊ฒฐ๊ณผ๋ฅผ ์ €์žฅํ•ด๋‘๊ณ  ์žฌ์‚ฌ์šฉํ•˜๋ฉด ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์–ด!

zap.core.save_session('my_session')
# ๋‚˜์ค‘์—...
zap.core.load_session('my_session')
์ตœ์ ํ™” ๊ธฐ๋ฒ• ํšจ๊ณผ ์ ์šฉ ๋‚œ์ด๋„
์Šค์บ” ์ •์ฑ… ์ปค์Šคํ„ฐ๋งˆ์ด์ง• 30-50% ์‹œ๊ฐ„ ๋‹จ์ถ• ์‰ฌ์›€
๋ณ‘๋ ฌ ์Šค์บ” ์‹คํ–‰ 50-70% ์‹œ๊ฐ„ ๋‹จ์ถ• ์ค‘๊ฐ„
์ฆ๋ถ„ ์Šค์บ” (๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ) 60-80% ์‹œ๊ฐ„ ๋‹จ์ถ• ์–ด๋ ค์›€
์บ์‹ฑ ํ™œ์šฉ 20-40% ์‹œ๊ฐ„ ๋‹จ์ถ• ์‰ฌ์›€
์Šค์บ” ๋ฒ”์œ„ ์ตœ์ ํ™” 40-60% ์‹œ๊ฐ„ ๋‹จ์ถ• ์‰ฌ์›€

๐Ÿ› ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ

ZAP ์ž๋™ํ™”ํ•˜๋‹ค ๋ณด๋ฉด ์—ฌ๋Ÿฌ ๋ฌธ์ œ๋ฅผ ๋งŒ๋‚  ์ˆ˜ ์žˆ์–ด. ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•ด๋ดค์–ด! ๐Ÿ”ง

๋ฌธ์ œ 1: ZAP API ์—ฐ๊ฒฐ ์‹คํŒจ

์ฆ์ƒ: ConnectionError: Connection refused

์›์ธ: ZAP์ด ์‹คํ–‰๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜ ํฌํŠธ๊ฐ€ ๋‹ค๋ฆ„

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•:

# ZAP์ด ์‹คํ–‰ ์ค‘์ธ์ง€ ํ™•์ธ
ps aux | grep zap

# ํฌํŠธ ํ™•์ธ
netstat -an | grep 8080

# ZAP ์žฌ์‹œ์ž‘
zap.sh -daemon -host 0.0.0.0 -port 8080

๋ฌธ์ œ 2: ์Šค์บ”์ด ๋ฉˆ์ถค

์ฆ์ƒ: ์Šค์บ” ์ง„ํ–‰๋ฅ ์ด 99%์—์„œ ๋ฉˆ์ถค

์›์ธ: ํƒ€์ž„์•„์›ƒ ์„ค์ •์ด ๋„ˆ๋ฌด ๊ธธ๊ฑฐ๋‚˜ ๋ฌดํ•œ ๋ฃจํ”„

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•:

# ํƒ€์ž„์•„์›ƒ ์„ค์ •
zap.ascan.set_option_max_scan_duration_in_mins(60)

# ๊ฐ•์ œ ์ค‘์ง€ ํ›„ ์žฌ์‹œ์ž‘
zap.ascan.stop(scan_id)
time.sleep(5)
scan_id = zap.ascan.scan(target_url)

๋ฌธ์ œ 3: ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ

์ฆ์ƒ: OutOfMemoryError ๋˜๋Š” ์‹œ์Šคํ…œ ๋А๋ ค์ง

์›์ธ: ํฐ ์‚ฌ์ดํŠธ ์Šค์บ” ์‹œ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•:

# ZAP ์‹คํ–‰ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น
zap.sh -daemon -Xmx4g  # 4GB ํ• ๋‹น

# ๋˜๋Š” Docker ์‚ฌ์šฉ ์‹œ
docker run -m 4g owasp/zap2docker-stable

๋ฌธ์ œ 4: ์ธ์ฆ ์‹คํŒจ

์ฆ์ƒ: ๋กœ๊ทธ์ธ ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ์Šค์บ”ํ•˜์ง€ ๋ชปํ•จ

์›์ธ: ์ธ์ฆ ์„ค์ • ์˜ค๋ฅ˜ ๋˜๋Š” ์„ธ์…˜ ๋งŒ๋ฃŒ

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•:

# ์ธ์ฆ ๊ฒ€์ฆ URL ์„ค์ •
zap.authentication.set_logged_in_indicator(
    contextid=context_id,
    loggedinindicatorregex='Logout'
)

# ์„ธ์…˜ ์œ ์ง€ ํ™•์ธ
zap.httpsessions.set_active_session(
    site=target_url,
    session='session_name'
)

๐ŸŒŸ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

์‹ค๋ฌด์—์„œ ZAP ์ž๋™ํ™”๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ์šด์˜ํ•˜๊ธฐ ์œ„ํ•œ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค๋ฅผ ๊ณต์œ ํ• ๊ฒŒ! ์ด๊ฑด ์ •๋ง ์ค‘์š”ํ•œ ๋‚ด์šฉ์ด๋‹ˆ๊นŒ ๊ผญ ๊ธฐ์–ตํ•ด๋‘ฌ. ๐Ÿ“š

1๏ธโƒฃ ์ ์ง„์  ๋„์ž…

์ฒ˜์Œ๋ถ€ํ„ฐ ๋ชจ๋“  ๊ฑธ ์ž๋™ํ™”ํ•˜๋ ค๊ณ  ํ•˜์ง€ ๋งˆ. ๋จผ์ € ๊ฐ„๋‹จํ•œ ์Šค์บ”๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์ฐจ ํ™•์žฅํ•ด๋‚˜๊ฐ€๋Š” ๊ฒŒ ์ข‹์•„. ํŒ€์ด ์ต์ˆ™ํ•ด์งˆ ์‹œ๊ฐ„์„ ์ค˜์•ผ ํ•ด!

2๏ธโƒฃ ํ™˜๊ฒฝ๋ณ„ ์„ค์ • ๋ถ„๋ฆฌ

๊ฐœ๋ฐœ, ์Šคํ…Œ์ด์ง•, ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ๋งˆ๋‹ค ๋‹ค๋ฅธ ์„ค์ •์„ ์‚ฌ์šฉํ•ด. ํ”„๋กœ๋•์…˜์—์„œ๋Š” ๋” ์กฐ์‹ฌ์Šค๋Ÿฝ๊ฒŒ ์Šค์บ”ํ•ด์•ผ ํ•˜๊ฑฐ๋“ !

# config/dev.env
ZAP_ATTACK_STRENGTH=HIGH
ZAP_SCAN_POLICY=full

# config/prod.env
ZAP_ATTACK_STRENGTH=LOW
ZAP_SCAN_POLICY=passive_only

3๏ธโƒฃ ๋กœ๊น…๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง

๋ชจ๋“  ์Šค์บ” ํ™œ๋™์„ ๋กœ๊ทธ๋กœ ๋‚จ๊ธฐ๊ณ , ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋ฉด ์ฆ‰์‹œ ์•Œ๋ฆผ์„ ๋ฐ›๋„๋ก ์„ค์ •ํ•ด!

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('zap_automation.log'),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger(__name__)
logger.info("ZAP ์Šค์บ” ์‹œ์ž‘")

4๏ธโƒฃ False Positive ๊ด€๋ฆฌ

ZAP์ด ์ž˜๋ชป ํƒ์ง€ํ•˜๋Š” ๊ฒฝ์šฐ(False Positive)๊ฐ€ ์žˆ์–ด. ์ด๋Ÿฐ ๊ฑด ๋”ฐ๋กœ ๊ด€๋ฆฌํ•ด์„œ ๋‹ค์Œ ์Šค์บ”์—์„œ ์ œ์™ธํ•˜๋„๋ก ํ•ด!

# false_positives.json
{
  "excluded_alerts": [
    {
      "alert": "X-Frame-Options Header Not Set",
      "url": "https://example.com/public/*",
      "reason": "Public pages don't need frame protection"
    }
  ]
}

5๏ธโƒฃ ์ •๊ธฐ์ ์ธ ์—…๋ฐ์ดํŠธ

ZAP๊ณผ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ •๊ธฐ์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ด. ์ƒˆ๋กœ์šด ์ทจ์•ฝ์  ํŒจํ„ด์ด ๊ณ„์† ์ถ”๊ฐ€๋˜๊ฑฐ๋“ !

# ์ž๋™ ์—…๋ฐ์ดํŠธ ์Šคํฌ๋ฆฝํŠธ
def update_zap():
    print("๐Ÿ”„ ZAP ์—…๋ฐ์ดํŠธ ํ™•์ธ ์ค‘...")
    zap.autoupdate.download_latest_release()
    print("โœ… ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ!")
ZAP Automation Best Practices 1 ์ ์ง„์  ๋„์ž… 2 ํ™˜๊ฒฝ๋ณ„ ๋ถ„๋ฆฌ 3 ๋กœ๊น… ๋ชจ๋‹ˆํ„ฐ๋ง 4 FP ๊ด€๋ฆฌ 5 ์ •๊ธฐ ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต์ ์ธ ๋ณด์•ˆ ์ž๋™ํ™”๋ฅผ ์œ„ํ•œ ํ•ต์‹ฌ ์›์น™ โœ“ ์ž‘๊ฒŒ ์‹œ์ž‘ํ•ด์„œ ์ ์ง„์ ์œผ๋กœ ํ™•์žฅ โœ“ ๊ฐ ํ™˜๊ฒฝ์— ๋งž๋Š” ์ตœ์ ํ™”๋œ ์„ค์ • โœ“ ํˆฌ๋ช…ํ•œ ๋กœ๊น…๊ณผ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ โœ“ False Positive ์ฒด๊ณ„์  ๊ด€๋ฆฌ โœ“ ์ตœ์‹  ๋ณด์•ˆ ํŒจํ„ด ์ง€์† ์—…๋ฐ์ดํŠธ โœ“ ํŒ€ ์ „์ฒด์˜ ๋ณด์•ˆ ์˜์‹ ํ–ฅ์ƒ

๐ŸŽ“ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์™„์ „ํ•œ ์ž๋™ํ™” ์‹œ์Šคํ…œ ๊ตฌ์ถ•

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋ชจ๋“  ๊ฑธ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์™„์ „ํ•œ ์ž๋™ํ™” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณด์ž! ์ด๊ฑด ์ง„์งœ ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์ด์•ผ. ๐Ÿš€

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

zap-automation/
โ”œโ”€โ”€ config/
โ”‚   โ”œโ”€โ”€ dev.env
โ”‚   โ”œโ”€โ”€ staging.env
โ”‚   โ””โ”€โ”€ prod.env
โ”œโ”€โ”€ scripts/
โ”‚   โ”œโ”€โ”€ zap_scanner.py
โ”‚   โ”œโ”€โ”€ report_generator.py
โ”‚   โ””โ”€โ”€ notification.py
โ”œโ”€โ”€ policies/
โ”‚   โ”œโ”€โ”€ full_scan.policy
โ”‚   โ””โ”€โ”€ quick_scan.policy
โ”œโ”€โ”€ reports/
โ”‚   โ””โ”€โ”€ (generated reports)
โ”œโ”€โ”€ logs/
โ”‚   โ””โ”€โ”€ (log files)
โ”œโ”€โ”€ docker-compose.yml
โ”œโ”€โ”€ requirements.txt
โ””โ”€โ”€ README.md

๋ฉ”์ธ ์Šค์บ๋„ˆ ๋ชจ๋“ˆ (zap_scanner.py)

#!/usr/bin/env python3
"""
ZAP ์ž๋™ํ™” ์Šค์บ๋„ˆ - ๋ฉ”์ธ ๋ชจ๋“ˆ
"""

import os
import sys
import time
import logging
from datetime import datetime
from zapv2 import ZAPv2
from dotenv import load_dotenv
import argparse

class ZAPScanner:
    """ZAP ์Šค์บ๋„ˆ ํด๋ž˜์Šค"""
    
    def __init__(self, config_file=None):
        """์ดˆ๊ธฐํ™”"""
        if config_file:
            load_dotenv(config_file)
        else:
            load_dotenv()
        
        self.zap = ZAPv2(
            apikey=os.getenv('ZAP_API_KEY'),
            proxies={
                'http': f"http://{os.getenv('ZAP_HOST')}:{os.getenv('ZAP_PORT')}",
                'https': f"http://{os.getenv('ZAP_HOST')}:{os.getenv('ZAP_PORT')}"
            }
        )
        
        self.target_url = os.getenv('TARGET_URL')
        self.scan_policy = os.getenv('SCAN_POLICY', 'default')
        
        # ๋กœ๊น… ์„ค์ •
        self.setup_logging()
        
    def setup_logging(self):
        """๋กœ๊น… ์„ค์ •"""
        log_dir = 'logs'
        os.makedirs(log_dir, exist_ok=True)
        
        log_file = os.path.join(
            log_dir,
            f"zap_scan_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
        )
        
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler(log_file),
                logging.StreamHandler(sys.stdout)
            ]
        )
        
        self.logger = logging.getLogger(__name__)
        self.logger.info("ZAP Scanner ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
    
    def check_zap_connection(self):
        """ZAP ์—ฐ๊ฒฐ ํ™•์ธ"""
        try:
            version = self.zap.core.version
            self.logger.info(f"ZAP ์—ฐ๊ฒฐ ์„ฑ๊ณต (๋ฒ„์ „: {version})")
            return True
        except Exception as e:
            self.logger.error(f"ZAP ์—ฐ๊ฒฐ ์‹คํŒจ: {e}")
            return False
    
    def setup_context(self, context_name="AutoScanContext"):
        """์ปจํ…์ŠคํŠธ ์„ค์ •"""
        self.logger.info(f"์ปจํ…์ŠคํŠธ '{context_name}' ์„ค์ • ์ค‘...")
        
        # ์ƒˆ ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
        context_id = self.zap.context.new_context(context_name)
        
        # URL ํฌํ•จ
        self.zap.context.include_in_context(
            context_name,
            f"{self.target_url}.*"
        )
        
        # ์ œ์™ธํ•  URL ํŒจํ„ด
        exclude_patterns = [
            '.*logout.*',
            '.*signout.*',
            '.*delete.*'
        ]
        
        for pattern in exclude_patterns:
            self.zap.context.exclude_from_context(context_name, pattern)
        
        self.logger.info(f"์ปจํ…์ŠคํŠธ ์„ค์ • ์™„๋ฃŒ (ID: {context_id})")
        return context_id
    
    def run_spider(self):
        """Spider ํฌ๋กค๋ง ์‹คํ–‰"""
        self.logger.info("Spider ํฌ๋กค๋ง ์‹œ์ž‘...")
        
        scan_id = self.zap.spider.scan(self.target_url)
        
        while int(self.zap.spider.status(scan_id)) < 100:
            progress = self.zap.spider.status(scan_id)
            self.logger.info(f"Spider ์ง„ํ–‰๋ฅ : {progress}%")
            time.sleep(3)
        
        urls_found = len(self.zap.spider.results(scan_id))
        self.logger.info(f"Spider ์™„๋ฃŒ - {urls_found}๊ฐœ URL ๋ฐœ๊ฒฌ")
        
        return urls_found
    
    def run_ajax_spider(self):
        """Ajax Spider ์‹คํ–‰"""
        self.logger.info("Ajax Spider ์‹œ์ž‘...")
        
        self.zap.ajaxSpider.set_option_max_duration('5')
        self.zap.ajaxSpider.scan(self.target_url)
        
        while self.zap.ajaxSpider.status == 'running':
            results = self.zap.ajaxSpider.number_of_results
            self.logger.info(f"Ajax Spider ์‹คํ–‰ ์ค‘... ({results}๊ฐœ URL)")
            time.sleep(5)
        
        urls_found = self.zap.ajaxSpider.number_of_results
        self.logger.info(f"Ajax Spider ์™„๋ฃŒ - {urls_found}๊ฐœ URL ๋ฐœ๊ฒฌ")
        
        return urls_found
    
    def wait_passive_scan(self):
        """Passive Scan ์™„๋ฃŒ ๋Œ€๊ธฐ"""
        self.logger.info("Passive Scan ๋Œ€๊ธฐ ์ค‘...")
        
        while int(self.zap.pscan.records_to_scan) > 0:
            records = self.zap.pscan.records_to_scan
            self.logger.info(f"Passive Scan ๋‚จ์€ ๋ ˆ์ฝ”๋“œ: {records}")
            time.sleep(2)
        
        self.logger.info("Passive Scan ์™„๋ฃŒ")
    
    def run_active_scan(self):
        """Active Scan ์‹คํ–‰"""
        self.logger.info("Active Scan ์‹œ์ž‘...")
        
        scan_id = self.zap.ascan.scan(self.target_url)
        
        while int(self.zap.ascan.status(scan_id)) < 100:
            progress = self.zap.ascan.status(scan_id)
            self.logger.info(f"Active Scan ์ง„ํ–‰๋ฅ : {progress}%")
            time.sleep(10)
        
        self.logger.info("Active Scan ์™„๋ฃŒ")
        return scan_id
    
    def get_alerts(self):
        """์•Œ๋ฆผ ์ˆ˜์ง‘"""
        self.logger.info("์Šค์บ” ๊ฒฐ๊ณผ ์ˆ˜์ง‘ ์ค‘...")
        
        alerts = self.zap.core.alerts(baseurl=self.target_url)
        
        # ์‹ฌ๊ฐ๋„๋ณ„ ๋ถ„๋ฅ˜
        severity_counts = {
            'High': 0,
            'Medium': 0,
            'Low': 0,
            'Informational': 0
        }
        
        for alert in alerts:
            severity_counts[alert['risk']] += 1
        
        self.logger.info("์Šค์บ” ๊ฒฐ๊ณผ ์š”์•ฝ:")
        for severity, count in severity_counts.items():
            self.logger.info(f"  {severity}: {count}๊ฐœ")
        
        return alerts, severity_counts
    
    def generate_report(self, alerts):
        """๋ฆฌํฌํŠธ ์ƒ์„ฑ"""
        self.logger.info("๋ฆฌํฌํŠธ ์ƒ์„ฑ ์ค‘...")
        
        report_dir = 'reports'
        os.makedirs(report_dir, exist_ok=True)
        
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        
        # HTML ๋ฆฌํฌํŠธ
        html_report = self.zap.core.htmlreport()
        html_file = os.path.join(report_dir, f"zap_report_{timestamp}.html")
        with open(html_file, 'w', encoding='utf-8') as f:
            f.write(html_report)
        
        self.logger.info(f"HTML ๋ฆฌํฌํŠธ ์ƒ์„ฑ: {html_file}")
        
        # JSON ๋ฆฌํฌํŠธ
        import json
        json_file = os.path.join(report_dir, f"zap_report_{timestamp}.json")
        with open(json_file, 'w', encoding='utf-8') as f:
            json.dump(alerts, f, indent=2, ensure_ascii=False)
        
        self.logger.info(f"JSON ๋ฆฌํฌํŠธ ์ƒ์„ฑ: {json_file}")
        
        return html_file, json_file
    
    def run_full_scan(self):
        """์ „์ฒด ์Šค์บ” ์‹คํ–‰"""
        self.logger.info("="*60)
        self.logger.info("ZAP ์ „์ฒด ์Šค์บ” ์‹œ์ž‘")
        self.logger.info(f"๋Œ€์ƒ URL: {self.target_url}")
        self.logger.info("="*60)
        
        start_time = time.time()
        
        try:
            # 1. ZAP ์—ฐ๊ฒฐ ํ™•์ธ
            if not self.check_zap_connection():
                raise Exception("ZAP ์—ฐ๊ฒฐ ์‹คํŒจ")
            
            # 2. ์ƒˆ ์„ธ์…˜ ์‹œ์ž‘
            self.zap.core.new_session()
            
            # 3. ์ปจํ…์ŠคํŠธ ์„ค์ •
            context_id = self.setup_context()
            
            # 4. Spider ์‹คํ–‰
            spider_urls = self.run_spider()
            
            # 5. Ajax Spider ์‹คํ–‰ (์˜ต์…˜)
            if os.getenv('USE_AJAX_SPIDER', 'false').lower() == 'true':
                ajax_urls = self.run_ajax_spider()
            
            # 6. Passive Scan ๋Œ€๊ธฐ
            self.wait_passive_scan()
            
            # 7. Active Scan ์‹คํ–‰
            scan_id = self.run_active_scan()
            
            # 8. ๊ฒฐ๊ณผ ์ˆ˜์ง‘
            alerts, severity_counts = self.get_alerts()
            
            # 9. ๋ฆฌํฌํŠธ ์ƒ์„ฑ
            html_file, json_file = self.generate_report(alerts)
            
            # 10. ์‹คํ–‰ ์‹œ๊ฐ„ ๊ณ„์‚ฐ
            elapsed_time = time.time() - start_time
            
            self.logger.info("="*60)
            self.logger.info("์Šค์บ” ์™„๋ฃŒ!")
            self.logger.info(f"์ด ์‹คํ–‰ ์‹œ๊ฐ„: {elapsed_time:.2f}์ดˆ")
            self.logger.info(f"๋ฐœ๊ฒฌ๋œ ์ด์Šˆ: {len(alerts)}๊ฐœ")
            self.logger.info("="*60)
            
            return {
                'success': True,
                'alerts': alerts,
                'severity_counts': severity_counts,
                'reports': {
                    'html': html_file,
                    'json': json_file
                },
                'elapsed_time': elapsed_time
            }
            
        except Exception as e:
            self.logger.error(f"์Šค์บ” ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
            return {
                'success': False,
                'error': str(e)
            }

def main():
    """๋ฉ”์ธ ํ•จ์ˆ˜"""
    parser = argparse.ArgumentParser(description='ZAP ์ž๋™ํ™” ์Šค์บ๋„ˆ')
    parser.add_argument(
        '--config',
        help='์„ค์ • ํŒŒ์ผ ๊ฒฝ๋กœ',
        default=None
    )
    parser.add_argument(
        '--target',
        help='๋Œ€์ƒ URL',
        default=None
    )
    
    args = parser.parse_args()
    
    # ์Šค์บ๋„ˆ ์ดˆ๊ธฐํ™”
    scanner = ZAPScanner(config_file=args.config)
    
    # ๋Œ€์ƒ URL ์˜ค๋ฒ„๋ผ์ด๋“œ
    if args.target:
        scanner.target_url = args.target
    
    # ์Šค์บ” ์‹คํ–‰
    result = scanner.run_full_scan()
    
    # ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ์ข…๋ฃŒ ์ฝ”๋“œ ๋ฐ˜ํ™˜
    sys.exit(0 if result['success'] else 1)

if __name__ == "__main__":
    main()

Docker Compose ์„ค์ •

Docker๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ™˜๊ฒฝ ์„ค์ •์ด ํ›จ์”ฌ ์‰ฌ์›Œ์ ธ! ๐Ÿณ

version: '3.8'

services:
  zap:
    image: owasp/zap2docker-stable
    container_name: zap-daemon
    ports:
      - "8080:8080"
    command: zap.sh -daemon -host 0.0.0.0 -port 8080 -config api.disablekey=true
    volumes:
      - ./reports:/zap/wrk
    networks:
      - zap-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080"]
      interval: 30s
      timeout: 10s
      retries: 3
  
  scanner:
    build: .
    container_name: zap-scanner
    depends_on:
      - zap
    environment:
      - ZAP_HOST=zap
      - ZAP_PORT=8080
      - TARGET_URL=${TARGET_URL}
    volumes:
      - ./reports:/app/reports
      - ./logs:/app/logs
    networks:
      - zap-network
    command: python zap_scanner.py

networks:
  zap-network:
    driver: bridge

๐ŸŒ ์žฌ๋Šฅ๋„ท์—์„œ ๋ณด์•ˆ ์ „๋ฌธ๊ฐ€ ์ฐพ๊ธฐ

ZAP ์ž๋™ํ™”๋ฅผ ๋ฐฐ์šฐ๋Š” ๊ฑด ์ข‹์ง€๋งŒ, ๋•Œ๋กœ๋Š” ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์ด ํ•„์š”ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด. ์žฌ๋Šฅ๋„ท์—์„œ๋Š” ๋ณด์•ˆ ํ…Œ์ŠคํŒ…, ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŒ…, DevSecOps ๊ตฌ์ถ• ๋“ฑ ๋‹ค์–‘ํ•œ ๋ณด์•ˆ ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ฐ€์ง„ ์ „๋ฌธ๊ฐ€๋“ค์„ ๋งŒ๋‚  ์ˆ˜ ์žˆ์–ด! ๐Ÿ’ผ

ํŠนํžˆ ์ฒ˜์Œ ๋ณด์•ˆ ์ž๋™ํ™” ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ๋•Œ๋Š” ๊ฒฝํ—˜ ์žˆ๋Š” ์ „๋ฌธ๊ฐ€์˜ ์ปจ์„คํŒ…์„ ๋ฐ›์œผ๋ฉด ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๋งŽ์ด ์ค„์ผ ์ˆ˜ ์žˆ๊ฑฐ๋“ . ์žฌ๋Šฅ๋„ท์—์„œ ๋ณด์•ˆ ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ! ๐ŸŽฏ

๐Ÿ“š ์ถ”๊ฐ€ ํ•™์Šต ๋ฆฌ์†Œ์Šค

ZAP ์ž๋™ํ™”๋ฅผ ๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฆฌ์†Œ์Šค๋“ค์„ ์ฐธ๊ณ ํ•ด๋ด! ๐Ÿ“–

๐Ÿ“˜ ๊ณต์‹ ๋ฌธ์„œ

OWASP ZAP ๊ณต์‹ ๋ฌธ์„œ๋Š” ๊ฐ€์žฅ ์ •ํ™•ํ•˜๊ณ  ์ตœ์‹  ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ด.
www.zaproxy.org/docs

๐ŸŽฅ ๋น„๋””์˜ค ํŠœํ† ๋ฆฌ์–ผ

YouTube์— ZAP ๊ด€๋ จ ํŠœํ† ๋ฆฌ์–ผ์ด ๋งŽ์•„. ํŠนํžˆ OWASP ๊ณต์‹ ์ฑ„๋„์„ ์ถ”์ฒœํ•ด!

๐Ÿ’ฌ ์ปค๋ฎค๋‹ˆํ‹ฐ

ZAP User Group๊ณผ Stack Overflow์—์„œ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋“ค๊ณผ ๊ฒฝํ—˜์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ“– ๊ด€๋ จ ์„œ์ 

"Web Application Security Testing with ZAP" ๊ฐ™์€ ์ฑ…๋“ค์ด ์ฒด๊ณ„์ ์ธ ํ•™์Šต์— ๋„์›€์ด ๋ผ.

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์—ˆ๋‹ค๋ฉด ์ •๋ง ๋Œ€๋‹จํ•ด! ๐ŸŽ‰ ์šฐ๋ฆฌ๋Š” OWASP ZAP ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŒ…์˜ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•, ์‹ค์ „ ํ”„๋กœ์ ํŠธ๊นŒ์ง€ ์ •๋ง ๋งŽ์€ ๊ฑธ ๋‹ค๋ค˜์–ด.

๋ณด์•ˆ ์ž๋™ํ™”๋Š” ํ•œ ๋ฒˆ ์„ค์ •ํ•ด๋‘๋ฉด ๊ณ„์†ํ•ด์„œ ๊ฐ€์น˜๋ฅผ ์ œ๊ณตํ•˜๋Š” ํˆฌ์ž์•ผ. ์ฒ˜์Œ์—๋Š” ์„ค์ •ํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ์ง€๋งŒ, ์ผ๋‹จ ๊ตฌ์ถ•ํ•˜๊ณ  ๋‚˜๋ฉด ๋ฐ˜๋ณต์ ์ธ ๋ณด์•ˆ ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์—„์ฒญ๋‚œ ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์ง€. โฐ

๐Ÿš€ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๊ธฐ

โœจ ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์ง„์ ์œผ๋กœ ํ™•์žฅํ•˜๊ธฐ
โœจ ํŒ€์›๋“ค๊ณผ ์ง€์‹ ๊ณต์œ ํ•˜๊ณ  ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๊ธฐ
โœจ ์ตœ์‹  ๋ณด์•ˆ ํŠธ๋ Œ๋“œ ๊ณ„์† ํ•™์Šตํ•˜๊ธฐ
โœจ ์ปค๋ฎค๋‹ˆํ‹ฐ์— ๊ธฐ์—ฌํ•˜๊ณ  ๊ฒฝํ—˜ ๋‚˜๋ˆ„๊ธฐ
โœจ ์‹ค๋ฌด์— ์ ์šฉํ•˜๋ฉด์„œ ์ง€์†์ ์œผ๋กœ ๊ฐœ์„ ํ•˜๊ธฐ

๋ณด์•ˆ์€ ํ•œ ๋ฒˆ ํ•˜๊ณ  ๋๋‚˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์ง€์†์ ์œผ๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๋Š” ํ”„๋กœ์„ธ์Šค์•ผ. ZAP ์ž๋™ํ™”๋Š” ์ด๋Ÿฐ ์ง€์†์ ์ธ ๋ณด์•ˆ ๊ด€๋ฆฌ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ง€. ๐Ÿ’ช

์ด ๊ธ€์ด ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ณด์•ˆ ์ž๋™ํ™” ์—ฌ์ •์— ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์–ด. ๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๊ฑฐ๋‚˜ ๋” ์•Œ๊ณ  ์‹ถ์€ ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด ์–ธ์ œ๋“ ์ง€ ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•ด๋ด. ์šฐ๋ฆฌ ๋ชจ๋‘ ํ•จ๊ป˜ ๋” ์•ˆ์ „ํ•œ ์›น์„ ๋งŒ๋“ค์–ด๊ฐ€์ž! ๐ŸŒŸ

ํ•ดํ”ผ ํ•ดํ‚น! (๋ฌผ๋ก  ํ™”์ดํŠธํ–‡์œผ๋กœ! ๐Ÿ˜„) ๐Ÿ›ก๏ธ

Happy Secure Coding! ๐Ÿ›ก๏ธ ๐Ÿ”’ ๐Ÿš€ Keep Learning, Keep Securing!
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.