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 ใหม่
