SiamCafe.net Blog
Cybersecurity

WordPress WooCommerce Shift Left Security — รักษาความปลอดภัย E-commerce ตั้งแต่ขั้นตอนพัฒนา

wordpress woocommerce shift left security
WordPress WooCommerce Shift Left Security | SiamCafe Blog
2025-09-15· อ. บอม — SiamCafe.net· 1,392 คำ

Shift Left Security คืออะไรและทำไมต้องใช้กับ WooCommerce

Shift Left Security เป็นแนวคิดที่ย้ายการทดสอบความปลอดภัยมาไว้ในขั้นตอนแรกๆของ software development lifecycle แทนที่จะรอตรวจสอบตอนก่อน deploy หรือหลัง deploy ซึ่งแก้ไขได้ยากและค่าใช้จ่ายสูง Shift Left ทำให้ตรวจพบช่องโหว่ตั้งแต่ตอนเขียนโค้ดหรือตอน commit

WooCommerce เป็น e-commerce platform ที่สร้างบน WordPress มีข้อมูลสำคัญเช่น ข้อมูลลูกค้า ที่อยู่จัดส่ง ข้อมูลการชำระเงิน ประวัติการสั่งซื้อ ทำให้เป็นเป้าหมายของ hackers การใช้ Shift Left Security ช่วยลดความเสี่ยงตั้งแต่ขั้นตอนการพัฒนา

ช่องโหว่ที่พบบ่อยใน WordPress/WooCommerce ได้แก่ SQL Injection ผ่าน custom queries ที่ไม่ใช้ prepared statements, Cross-Site Scripting (XSS) จากการไม่ sanitize input/output, Cross-Site Request Forgery (CSRF) จากการไม่ใช้ nonce verification, Insecure Direct Object Reference (IDOR) ที่เข้าถึงข้อมูล order ของคนอื่นได้ และ File Upload Vulnerability จากการไม่ตรวจสอบ file type

Shift Left Security Pipeline สำหรับ WooCommerce ประกอบด้วย Pre-commit hooks ที่ตรวจสอบโค้ดก่อน commit, SAST ที่วิเคราะห์ source code หา vulnerabilities, Dependency scanning ที่ตรวจสอบ plugins/libraries ที่มีช่องโหว่, DAST ที่ทดสอบ running application และ Continuous monitoring หลัง deploy

ตั้งค่า Security Scanning ใน CI/CD Pipeline

สร้าง CI/CD Pipeline ที่รวม security scanning ทุกขั้นตอน

# .github/workflows/woocommerce-security.yml
name: WooCommerce Security Pipeline

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

jobs:
  sast-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
          tools: composer
      
      - name: Install dependencies
        run: composer install --no-interaction
      
      # PHP Security Scanner
      - name: Run PHPStan Security
        run: |
          composer require --dev phpstan/phpstan
          vendor/bin/phpstan analyse wp-content/plugins/my-plugin/ \
            --level=6 --error-format=json > phpstan-results.json
      
      # PHPCS Security Sniffs
      - name: Run PHPCS Security Audit
        run: |
          composer require --dev pheromone/phpcs-security-audit
          vendor/bin/phpcs --standard=Security \
            wp-content/plugins/my-plugin/ \
            --report=json > phpcs-security.json
      
      # Semgrep SAST
      - name: Run Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: >-
            p/php
            p/wordpress
            p/sql-injection
            p/xss
          generateSarif: true
      
      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: semgrep.sarif

  dependency-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      # Composer Audit
      - name: Composer Security Audit
        run: composer audit --format=json > composer-audit.json
      
      # WPScan for plugin vulnerabilities
      - name: WPScan Plugin Check
        run: |
          docker run --rm wpscanteam/wpscan \
            --url } \
            --enumerate vp, vt \
            --api-token } \
            --format json > wpscan-results.json
      
      # npm audit for JS dependencies
      - name: NPM Security Audit
        run: |
          cd wp-content/themes/my-theme
          npm audit --json > npm-audit.json
      
      - name: Check Critical Vulnerabilities
        run: |
          python3 scripts/check_vulns.py \
            --composer composer-audit.json \
            --npm npm-audit.json \
            --fail-on critical, high

  dast-scan:
    needs: [sast-scan, dependency-scan]
    runs-on: ubuntu-latest
    steps:
      - name: OWASP ZAP Scan
        uses: zaproxy/action-full-scan@v0.10.0
        with:
          target: }
          rules_file_name: 'zap-rules.tsv'
          cmd_options: '-a -j'
      
      - name: Upload ZAP Report
        uses: actions/upload-artifact@v4
        with:
          name: zap-report
          path: report_html.html

Static Application Security Testing (SAST) สำหรับ WordPress

ตั้งค่า SAST tools สำหรับตรวจสอบโค้ด WordPress

# === Pre-commit Hook สำหรับ Security ===
# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: php-security-check
        name: PHP Security Check
        entry: bash -c 'vendor/bin/phpcs --standard=Security "$@"'
        language: system
        files: '\.php$'
      
      - id: no-eval
        name: Block eval() usage
        entry: 'eval\s*\('
        language: pygrep
        files: '\.php$'
      
      - id: no-exec
        name: Block exec/shell_exec
        entry: '(exec|shell_exec|system|passthru|popen)\s*\('
        language: pygrep
        files: '\.php$'
      
      - id: no-raw-sql
        name: Block raw SQL queries
        entry: '\$wpdb->(query|get_results|get_row|get_var)\s*\(\s*["\$]'
        language: pygrep
        files: '\.php$'

# === Semgrep Custom Rules สำหรับ WordPress ===
# .semgrep/wordpress-security.yml
rules:
  - id: wp-sql-injection
    patterns:
      - pattern: $wpdb->query($QUERY)
      - pattern-not: $wpdb->query($wpdb->prepare(...))
    message: "SQL query without prepare() — SQL Injection risk"
    severity: ERROR
    languages: [php]
  
  - id: wp-xss-echo
    patterns:
      - pattern: echo $_GET[...];
      - pattern: echo $_POST[...];
      - pattern: echo $_REQUEST[...];
    message: "Direct echo of user input — XSS risk. Use esc_html()"
    severity: ERROR
    languages: [php]
  
  - id: wp-missing-nonce
    patterns:
      - pattern: |
          function $FUNC() {
            ...
            update_option(...);
            ...
          }
      - pattern-not: |
          function $FUNC() {
            ...
            wp_verify_nonce(...);
            ...
          }
    message: "State-changing function without nonce verification — CSRF risk"
    severity: WARNING
    languages: [php]
  
  - id: wp-unsafe-redirect
    patterns:
      - pattern: wp_redirect($_GET[...])
      - pattern: wp_redirect($_POST[...])
    message: "Redirect using user input — Open Redirect risk. Use wp_safe_redirect()"
    severity: ERROR
    languages: [php]

# === PHPStan Security Extension ===
# phpstan.neon
includes:
  - vendor/phpstan/phpstan-strict-rules/rules.neon

parameters:
  level: 6
  paths:
    - wp-content/plugins/my-woocommerce-plugin
  ignoreErrors: []
  checkMissingIterableValueType: false

Dependency Scanning และ Vulnerability Management

ตรวจสอบ plugins, themes และ dependencies ที่มีช่องโหว่

#!/usr/bin/env python3
# wp_vuln_scanner.py — WordPress Vulnerability Scanner
import json
import subprocess
import requests
from datetime import datetime

class WPVulnScanner:
    def __init__(self, wp_path, wpscan_token=None):
        self.wp_path = wp_path
        self.wpscan_token = wpscan_token
        self.results = {"plugins": [], "themes": [], "core": None}
    
    def scan_composer_deps(self):
        """Scan composer.json for known vulnerabilities"""
        result = subprocess.run(
            ["composer", "audit", "--format=json"],
            capture_output=True, text=True, cwd=self.wp_path
        )
        
        try:
            audit = json.loads(result.stdout)
            vulns = []
            for advisory in audit.get("advisories", {}).values():
                for item in advisory:
                    vulns.append({
                        "package": item.get("packageName"),
                        "title": item.get("title"),
                        "cve": item.get("cve"),
                        "severity": self._classify_severity(item),
                        "fixed_in": item.get("reportedAt"),
                    })
            return vulns
        except json.JSONDecodeError:
            return []
    
    def scan_plugins(self):
        """List plugins and check versions"""
        result = subprocess.run(
            ["wp", "plugin", "list", "--format=json", f"--path={self.wp_path}"],
            capture_output=True, text=True
        )
        
        try:
            plugins = json.loads(result.stdout)
            for plugin in plugins:
                status = "OK"
                if plugin.get("update") == "available":
                    status = "UPDATE_AVAILABLE"
                
                self.results["plugins"].append({
                    "name": plugin["name"],
                    "version": plugin["version"],
                    "status": plugin["status"],
                    "update_status": status,
                })
            return self.results["plugins"]
        except json.JSONDecodeError:
            return []
    
    def check_wpscan_api(self, plugin_slug, version):
        """Check WPScan API for known vulnerabilities"""
        if not self.wpscan_token:
            return []
        
        headers = {"Authorization": f"Token token={self.wpscan_token}"}
        url = f"https://wpscan.com/api/v3/plugins/{plugin_slug}"
        
        resp = requests.get(url, headers=headers)
        if resp.status_code != 200:
            return []
        
        data = resp.json()
        vulns = []
        for vuln in data.get(plugin_slug, {}).get("vulnerabilities", []):
            fixed = vuln.get("fixed_in")
            if fixed and self._version_compare(version, fixed) >= 0:
                continue
            vulns.append({
                "title": vuln.get("title"),
                "vuln_type": vuln.get("vuln_type"),
                "fixed_in": fixed,
                "references": vuln.get("references", {}).get("cve", []),
            })
        return vulns
    
    def generate_report(self):
        report = {
            "scan_date": datetime.now().isoformat(),
            "plugins": self.results["plugins"],
            "total_plugins": len(self.results["plugins"]),
            "outdated": len([p for p in self.results["plugins"] if p["update_status"] != "OK"]),
        }
        
        with open("security-report.json", "w") as f:
            json.dump(report, f, indent=2)
        
        print(f"Scan complete: {report['total_plugins']} plugins, "
              f"{report['outdated']} need updates")
        return report
    
    @staticmethod
    def _version_compare(v1, v2):
        parts1 = [int(x) for x in v1.split(".")]
        parts2 = [int(x) for x in v2.split(".")]
        for a, b in zip(parts1, parts2):
            if a != b:
                return a - b
        return len(parts1) - len(parts2)
    
    @staticmethod
    def _classify_severity(item):
        title = item.get("title", "").lower()
        if "critical" in title or "rce" in title:
            return "CRITICAL"
        if "sql injection" in title or "authentication bypass" in title:
            return "HIGH"
        return "MEDIUM"

scanner = WPVulnScanner("/var/www/html/wordpress")
scanner.scan_plugins()
scanner.generate_report()

Hardening WooCommerce ด้วย Security Headers และ WAF

ตั้งค่า security headers และ Web Application Firewall

# === Nginx Security Headers สำหรับ WooCommerce ===
# /etc/nginx/conf.d/security-headers.conf

# Content Security Policy
add_header Content-Security-Policy "
    default-src 'self';
    script-src 'self' 'unsafe-inline' 'unsafe-eval'
        https://js.stripe.com
        https://www.google.com/recaptcha/
        https://www.gstatic.com/recaptcha/;
    style-src 'self' 'unsafe-inline'
        https://fonts.googleapis.com;
    img-src 'self' data: https:;
    font-src 'self' https://fonts.gstatic.com;
    frame-src https://js.stripe.com
        https://www.google.com/recaptcha/;
    connect-src 'self' https://api.stripe.com;
" always;

# Other Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# === WordPress Specific Nginx Rules ===
# Block access to sensitive files
location ~* /(\.|wp-config\.php|readme\.html|license\.txt) {
    deny all;
}

# Block PHP execution in uploads
location ~* /wp-content/uploads/.*\.php$ {
    deny all;
}

# Block xmlrpc.php (unless needed)
location = /xmlrpc.php {
    deny all;
    return 403;
}

# Rate limiting for login
limit_req_zone $binary_remote_addr zone=wplogin:10m rate=3r/m;
location = /wp-login.php {
    limit_req zone=wplogin burst=2 nodelay;
    include fastcgi_params;
    fastcgi_pass php-fpm;
}

# Rate limiting for WooCommerce checkout
limit_req_zone $binary_remote_addr zone=wccheckout:10m rate=10r/m;
location ~* /checkout/ {
    limit_req zone=wccheckout burst=5 nodelay;
    try_files $uri $uri/ /index.php?$args;
}

# === wp-config.php Security Settings ===
# เพิ่มใน wp-config.php:
#
# // Disable file editing
# define('DISALLOW_FILE_EDIT', true);
#
# // Force SSL admin
# define('FORCE_SSL_ADMIN', true);
#
# // Limit post revisions
# define('WP_POST_REVISIONS', 5);
#
# // Security keys (regenerate at https://api.wordpress.org/secret-key/1.1/salt/)
# define('AUTH_KEY', 'unique-phrase-here');
# define('SECURE_AUTH_KEY', 'unique-phrase-here');
#
# // Block external HTTP requests (whitelist needed domains)
# define('WP_HTTP_BLOCK_EXTERNAL', true);
# define('WP_ACCESSIBLE_HOSTS', 'api.wordpress.org, downloads.wordpress.org');

Automated Security Testing ด้วย WPScan และ Custom Scripts

สร้าง automated security testing workflow

#!/bin/bash
# wp_security_test.sh — Automated WordPress Security Testing
set -euo pipefail

SITE_URL=""
REPORT_DIR="./security-reports/$(date +%Y%m%d)"
mkdir -p "$REPORT_DIR"

echo "=== WordPress Security Test: $SITE_URL ==="
echo "Report directory: $REPORT_DIR"

# 1. WPScan — Plugin and Theme Vulnerability Scan
echo "[1/5] Running WPScan..."
docker run --rm -v "$REPORT_DIR:/output" wpscanteam/wpscan \
    --url "$SITE_URL" \
    --enumerate vp, vt, u1-10 \
    --plugins-detection aggressive \
    --api-token "$WPSCAN_TOKEN" \
    --format json \
    --output /output/wpscan.json 2>/dev/null

# 2. Nikto — Web Server Scanner
echo "[2/5] Running Nikto..."
docker run --rm -v "$REPORT_DIR:/output" secfigo/nikto \
    -h "$SITE_URL" \
    -Format json \
    -output /output/nikto.json 2>/dev/null

# 3. SSL/TLS Check
echo "[3/5] Checking SSL/TLS..."
docker run --rm drwetter/testssl.sh \
    --jsonfile /dev/stdout "$SITE_URL" > "$REPORT_DIR/ssl-test.json" 2>/dev/null

# 4. Security Headers Check
echo "[4/5] Checking Security Headers..."
curl -sI "$SITE_URL" | tee "$REPORT_DIR/headers.txt" | while read -r line; do
    header=$(echo "$line" | tr '[:upper:]' '[:lower:]')
    case "$header" in
        *content-security-policy*) echo "  [OK] CSP header found" ;;
        *x-frame-options*) echo "  [OK] X-Frame-Options found" ;;
        *x-content-type-options*) echo "  [OK] X-Content-Type-Options found" ;;
        *strict-transport-security*) echo "  [OK] HSTS found" ;;
    esac
done

# Check missing headers
for hdr in "content-security-policy" "x-frame-options" "x-content-type-options" "strict-transport-security"; do
    if ! grep -qi "$hdr" "$REPORT_DIR/headers.txt"; then
        echo "  [WARN] Missing: $hdr"
    fi
done

# 5. Custom WordPress Checks
echo "[5/5] Running custom checks..."
python3 - "$SITE_URL" "$REPORT_DIR" << 'PYEOF'
import sys, requests, json

site = sys.argv[1]
report_dir = sys.argv[2]
findings = []

# Check debug mode
resp = requests.get(f"{site}/wp-content/debug.log", timeout=10)
if resp.status_code == 200:
    findings.append({"severity": "HIGH", "issue": "debug.log is publicly accessible"})

# Check user enumeration
resp = requests.get(f"{site}/wp-json/wp/v2/users", timeout=10)
if resp.status_code == 200:
    users = resp.json()
    findings.append({"severity": "MEDIUM", "issue": f"User enumeration: {len(users)} users exposed via REST API"})

# Check xmlrpc
resp = requests.post(f"{site}/xmlrpc.php", data="system.listMethods", timeout=10)
if resp.status_code == 200 and "methodResponse" in resp.text:
    findings.append({"severity": "MEDIUM", "issue": "XML-RPC is enabled"})

# Check directory listing
for path in ["/wp-content/uploads/", "/wp-content/plugins/", "/wp-includes/"]:
    resp = requests.get(f"{site}{path}", timeout=10)
    if resp.status_code == 200 and "Index of" in resp.text:
        findings.append({"severity": "LOW", "issue": f"Directory listing enabled: {path}"})

with open(f"{report_dir}/custom-checks.json", "w") as f:
    json.dump(findings, f, indent=2)

print(f"\n  Found {len(findings)} issues")
for f in findings:
    print(f"  [{f['severity']}] {f['issue']}")
PYEOF

echo ""
echo "=== Security Test Complete ==="
echo "Reports saved to: $REPORT_DIR"

FAQ คำถามที่พบบ่อย

Q: Shift Left Security เพิ่มเวลาในการ develop มากไหม?

A: ในช่วงแรกอาจเพิ่มเวลา 10-15% เพราะต้อง setup tools และ fix findings แต่ในระยะยาวจะประหยัดเวลามากเพราะพบ bugs เร็วขึ้น ค่าใช้จ่ายในการแก้ bug ที่พบในขั้น development ต่ำกว่าที่พบใน production ถึง 30 เท่า pre-commit hooks ทำงานไม่กี่วินาทีต่อ commit

Q: WooCommerce plugins ที่ต้องระวังเรื่อง security มีอะไรบ้าง?

A: ต้องระวัง plugins ที่ไม่ได้อัปเดตนานกว่า 6 เดือน plugins จาก developers ที่ไม่น่าเชื่อถือ plugins ที่มี less than 1000 active installations และ nulled/pirated plugins ที่อาจมี backdoor ฝังอยู่ ควรใช้เฉพาะ plugins จาก WordPress.org หรือ vendors ที่เชื่อถือได้ และอัปเดตทันทีที่มี security patch

Q: ต้อง PCI DSS compliance สำหรับ WooCommerce ไหม?

A: ถ้าใช้ payment gateway แบบ hosted (เช่น Stripe, PayPal) ที่ redirect ลูกค้าไปชำระเงินที่ gateway โดยตรง WooCommerce site จะอยู่ในระดับ PCI DSS Level 4 SAQ A ซึ่งข้อกำหนดน้อยที่สุด แต่ถ้ารับ credit card data โดยตรงบน site ต้อง comply กับ PCI DSS Level 1-3 ซึ่งซับซ้อนมาก แนะนำใช้ hosted payment forms เสมอ

Q: ควร scan security บ่อยแค่ไหน?

A: SAST ควร run ทุก commit ผ่าน pre-commit hooks และ CI/CD dependency scanning ควร run ทุกวันหรือทุก commit DAST scanning ควร run อย่างน้อยสัปดาห์ละครั้ง full penetration test ควรทำอย่างน้อยปีละครั้งหรือเมื่อมี major changes และ WPScan ควร run ทุกวันเพื่อตรวจสอบ plugin vulnerabilities ใหม่

📖 บทความที่เกี่ยวข้อง

WordPress WooCommerce Cloud Migration Strategyอ่านบทความ → MySQL InnoDB Tuning Shift Left Securityอ่านบทความ → WordPress WooCommerce Load Testing Strategyอ่านบทความ → PagerDuty Incident Shift Left Securityอ่านบทความ → Text Generation WebUI Shift Left Securityอ่านบทความ →

📚 ดูบทความทั้งหมด →