Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WordPress Plugin Bug Bounty Checker

A fully local, automated security and performance auditing tool for WordPress plugins. Provide a WordPress.org plugin URL or a .zip file — the tool downloads, extracts, scans with multiple analysis engines, and generates a comprehensive report.


Quick Start

# Scan any WordPress.org plugin by URL
python3 /var/www/bug-b/check.py --url https://wordpress.org/plugins/contact-form-7/

# Scan a local plugin zip
python3 /var/www/bug-b/check.py --zip /path/to/my-plugin.zip

Reports are saved to /var/www/bug-b/reports/{plugin-slug}_{timestamp}/


Installation

Tools are already installed. To verify or reinstall:

# Install/update all tools (PHPStan, PHPMD, Semgrep)
bash /var/www/bug-b/tools/install.sh

# Verify all tools are functional
bash /var/www/bug-b/tools/verify.sh

Pre-installed tools (global)

Tool Version Purpose
PHP 8.1 Runtime
PHPCS 3.10.2 Static analysis engine
WordPress Coding Standards latest WP security sniffs
VIP WordPress Coding Standards latest Enterprise WP sniffs
PHPCompatibility latest PHP version compatibility
VariableAnalysis latest Undefined variable detection
nikic/php-parser 5.x AST-based taint analysis
parallel-lint 1.4 PHP syntax validation
Python 3.10 Orchestration runtime

Locally installed tools (in tools/vendor/)

Tool Purpose
PHPStan Deep type and flow analysis
PHPMD Code complexity and mess detection
Semgrep Custom WordPress data-flow rules

Usage

python3 check.py --url <wordpress-org-url> [options]
python3 check.py --zip <path-to-zip>      [options]

Options

Flag Default Description
--url URL WordPress.org plugin page or download URL
--zip FILE Path to local plugin .zip file
--format html,json,md all three Output report formats
--skip SCANNERS none Comma-separated scanners to skip
--output DIR auto Custom directory for report output
--timeout N 120 Per-scanner timeout in seconds
--keep-extracted false Keep extracted plugin source after scan

Examples

# Scan by WordPress.org URL
python3 check.py --url https://wordpress.org/plugins/woocommerce/

# Scan a local zip, HTML report only
python3 check.py --zip ~/downloads/my-plugin.zip --format html

# Skip slow scanners for a quick result
python3 check.py --url https://wordpress.org/plugins/akismet/ --skip phpstan,phpmd,semgrep

# Save report to a specific directory
python3 check.py --zip plugin.zip --output /tmp/my-audit

# Increase timeout for large plugins
python3 check.py --url https://wordpress.org/plugins/elementor/ --timeout 300

# Keep extracted source for manual review
python3 check.py --zip plugin.zip --keep-extracted

Scanner Descriptions

The tool runs up to 7 scanners in parallel. Each can be individually skipped with --skip.

1. phpcs_security — WPCS Security Rules

Runs PHP_CodeSniffer with security-focused WordPress Coding Standards sniffs.

Detects:

  • SQL injection via $wpdb without prepare()
  • XSS via unescaped output (echo $var without esc_html())
  • Missing nonce verification ($_POST without check_ajax_referer())
  • Missing input sanitization ($_GET used raw)
  • Open redirects (wp_redirect() without safe URL check)
  • Missing capability checks (current_user_can() absent)
  • Dangerous PHP functions (eval, exec, system, etc.)
  • File inclusion from user input
  • Dynamic function calls

2. phpcs_performance — WPCS Performance Rules

Runs PHP_CodeSniffer with performance-focused sniffs.

Detects:

  • posts_per_page: -1 (loads all posts)
  • ORDER BY RAND() (forces full table scan)
  • Slow meta queries with REGEXP
  • Uncached remote data fetches
  • Missing HTTP request timeout
  • Scripts/styles enqueued incorrectly
  • Cache values being overridden
  • Autoloaded options abuse

3. ast_taint — AST Data Flow Analysis

Custom PHP taint tracker built on nikic/php-parser. Parses each file into an AST and tracks data flowing from user-controlled sources to dangerous sinks.

Sources tracked: $_GET, $_POST, $_REQUEST, $_COOKIE, $_FILES, get_option(), get_post_meta()

Sinks tracked: $wpdb->query(), echo, eval(), include/require, system(), exec(), unserialize(), file_get_contents(), wp_redirect()

Sanitizers recognized (remove taint): esc_html(), esc_attr(), esc_url(), $wpdb->prepare(), absint(), intval(), sanitize_text_field(), wp_verify_nonce(), and 20+ others

Detects (with data-flow proof): SQL injection, XSS, RCE, LFI, deserialization, open redirect, backdoor obfuscation patterns

4. pattern_scanner — Fast Regex Scanner

High-speed pre-scan using carefully crafted regular expressions. Catches obvious patterns before slower tools run.

Security patterns: backdoors (eval+base64/gzinflate), PHP object injection, direct SQLi, XSS echo, RCE via system functions, LFI via include, SSRF via curl, open redirect, XXE via SimpleXML, timing attacks, hardcoded credentials, variable injection via extract()

Performance patterns: unlimited post queries, ORDER BY RAND, autoloaded options, N+1 queries (get_post_meta/get_posts in loops), SELECT *, suppress_filters bypass, direct mysqli calls

5. phpstan — Static Type Analysis

Runs PHPStan at level 5 against the plugin code.

Detects: Undefined variables, incorrect function argument types, always-false conditions, dead code, unreachable statements

Skip with: --skip phpstan (can be slow on large plugins)

6. phpmd — Code Complexity Analysis

Runs PHP Mess Detector with code quality rules.

Detects: High cyclomatic complexity (>15), deep nesting, eval() expressions (security flag), goto statements, unused variables and parameters, excessive class/method length

Skip with: --skip phpmd

7. semgrep — Custom WordPress Data Flow Rules

Runs Semgrep with a custom YAML ruleset tuned specifically for WordPress plugin vulnerabilities.

Detects: Direct $wpdb->query() concatenation, unescaped superglobal echo, unserialize() on user input, REST API routes with __return_true permission callback, wp_redirect() with user URL, posts_per_page: -1, ORDER BY RAND(), arbitrary file read, command injection, open redirect, hardcoded credentials

Skip with: --skip semgrep


Output Reports

Each scan produces three report files in /var/www/bug-b/reports/{slug}_{timestamp}/:

report.html — Self-Contained HTML Report

A single portable HTML file (no CDN, no external dependencies). Open in any browser.

Contains:

  • Plugin metadata (name, version, author, active installs, WP requires)
  • Color-coded risk score badge (CRITICAL / HIGH / MEDIUM / LOW / CLEAN)
  • Bar chart of findings by severity
  • Finding cards grouped by severity with code snippets and remediation
  • Category breakdown table
  • Metadata warnings (suspicious files, outdated bundled libraries)
  • Tool coverage matrix (which scanners ran, duration, finding counts)

report.json — Machine-Readable JSON

Structured output for importing into other tools, dashboards, or CI pipelines.

{
  "meta": {
    "plugin_name": "Contact Form 7",
    "plugin_slug": "contact-form-7",
    "plugin_version": "6.1.5",
    "plugin_author": "Rock Lobster Inc.",
    "active_installs": "10000000",
    "scan_date": "2026-03-20T00:29:27Z",
    "total_php_files": 111,
    "total_lines_of_code": 25075
  },
  "summary": {
    "total_findings": 211,
    "by_severity": { "CRITICAL": 23, "HIGH": 31, "MEDIUM": 37, "LOW": 1, "INFO": 119 },
    "by_category": { "missing_capability": 33, "performance": 24, "rce": 18 },
    "risk_score": 9.4,
    "risk_label": "CRITICAL"
  },
  "findings": [
    {
      "id": "WPBB-001",
      "title": "...",
      "category": "sqli",
      "severity": "CRITICAL",
      "cvss_score": 9.8,
      "cwe": "CWE-89",
      "file": "includes/db.php",
      "line": 42,
      "code_snippet": "...",
      "remediation": "Use $wpdb->prepare()...",
      "detected_by": ["phpcs_security", "ast_taint"],
      "confidence": "HIGH"
    }
  ],
  "tool_coverage": { ... }
}

report.md — Markdown Report

Clean Markdown suitable for pasting into GitHub issues, HackerOne reports, Notion, or Confluence.

Contains: Same structure as HTML — severity tables, per-finding details with file paths, code snippets, CWE/CVSS, remediation, and tool coverage.

raw/ — Raw Scanner Output

The raw/ subdirectory contains the unprocessed JSON from each scanner for debugging or custom processing.


Security Checks Reference

Vulnerability CVSS Scanners
SQL Injection 9.8 phpcs_security, ast_taint, pattern_scanner, semgrep
Remote Code Execution 10.0 phpcs_security, ast_taint, pattern_scanner, semgrep
PHP Object Injection (unserialize) 9.8 ast_taint, pattern_scanner, semgrep
Backdoor / Obfuscated Code 10.0 ast_taint, pattern_scanner
Cross-Site Scripting (XSS) 6.1 phpcs_security, ast_taint, pattern_scanner, semgrep
CSRF / Missing Nonce 8.8 phpcs_security, semgrep
Missing Capability Check 8.1 phpcs_security, semgrep
Local File Inclusion 7.5 phpcs_security, ast_taint, pattern_scanner
SSRF 7.5 pattern_scanner, semgrep
Open Redirect 6.1 phpcs_security, ast_taint, semgrep
Arbitrary File Upload 7.2 pattern_scanner
XXE 7.5 pattern_scanner
Hardcoded Credentials pattern_scanner, semgrep
Timing Attack (strcmp) pattern_scanner

Performance Checks Reference

Issue Scanners
posts_per_page: -1 (unbounded queries) phpcs_performance, pattern_scanner, semgrep
ORDER BY RAND() phpcs_performance, semgrep
N+1 queries (DB calls inside loops) pattern_scanner
Autoloaded options (autoload=yes) phpcs_performance, semgrep
Missing transient/cache for expensive queries pattern_scanner
SELECT * queries pattern_scanner
Uncached remote HTTP fetches phpcs_performance
suppress_filters: true (disables caching) pattern_scanner
Enqueue scripts outside proper hook phpcs_performance
Direct mysqli_query() bypassing $wpdb pattern_scanner
Slow cron intervals phpcs_performance

Configuration

config/scanner-config.json

Central configuration. Key sections:

{
  "timeout_seconds": 120,
  "max_workers": 4,
  "severity_map": {
    "sqli": "CRITICAL",
    "xss": "HIGH",
    "performance": "INFO"
  },
  "cvss_map": {
    "sqli": 9.8,
    "rce": 10.0,
    "xss": 6.1
  }
}

To change scanner binary paths, edit the *_binary keys. To adjust how PHPCS sniff codes map to vulnerability categories, edit wpcs_severity_map.

config/phpcs-security.xml

Controls which WPCS/VIP sniffs run. Add or remove <rule ref="..."/> entries to tune findings.

config/phpcs-performance.xml

Same structure for performance sniffs.

config/semgrep-wordpress.yaml

Custom Semgrep rules in YAML. Add new rules following the existing pattern:

rules:
  - id: my-new-rule
    pattern: dangerous_function($_POST[$KEY])
    message: "Description of the issue"
    severity: ERROR
    languages: [php]

config/phpmd-rules.xml

PHPMD ruleset. Adjust complexity thresholds under <properties>.


Project Structure

/var/www/bug-b/
├── check.py                      # Main entry point
├── config/
│   ├── phpcs-security.xml        # PHPCS security ruleset
│   ├── phpcs-performance.xml     # PHPCS performance ruleset
│   ├── phpmd-rules.xml           # PHPMD ruleset
│   ├── phpstan.neon              # PHPStan config
│   ├── semgrep-wordpress.yaml    # Custom Semgrep rules
│   └── scanner-config.json       # Master config (paths, severity maps, CVSS)
├── scanners/
│   ├── ast_scanner.php           # AST taint tracker (nikic/php-parser)
│   ├── ast_runner.py             # Python wrapper for ast_scanner.php
│   ├── pattern_scanner.py        # Fast regex scanner
│   ├── phpcs_runner.py           # PHPCS executor and parser
│   ├── phpstan_runner.py         # PHPStan executor and parser
│   ├── phpmd_runner.py           # PHPMD executor and parser
│   ├── semgrep_runner.py         # Semgrep executor and parser
│   └── metadata_analyzer.py      # Plugin structure/header/library analysis
├── reporters/
│   ├── report_builder.py         # Deduplicates and aggregates findings
│   ├── html_reporter.py          # Generates self-contained HTML
│   ├── json_reporter.py          # Generates JSON
│   └── markdown_reporter.py      # Generates Markdown
├── tools/
│   ├── install.sh                # Installs PHPStan, PHPMD, Semgrep
│   ├── verify.sh                 # Checks all tools are functional
│   └── vendor/                   # Local Composer packages (PHPStan, PHPMD)
├── workspace/
│   ├── downloads/                # Cached downloaded .zip files
│   ├── extracted/                # Extracted plugin source (auto-cleaned)
│   └── results/                  # Raw scanner JSON (auto-cleaned)
└── reports/                      # All generated reports (permanent)
    └── {slug}_{timestamp}/
        ├── report.html
        ├── report.json
        ├── report.md
        └── raw/                  # Raw per-tool JSON output

How Deduplication Works

When multiple scanners flag the same issue, they are merged into a single finding:

  • Dedup key: file_path + line_number + vulnerability_category
  • Confidence upgrade: If 2 or more tools agree on the same finding → confidence becomes HIGH
  • Severity upgrade: If tools disagree, the most severe rating wins
  • Attribution: The detected_by array lists all tools that found it

This eliminates noise from overlapping tools while preserving cross-tool validation signals.


Severity and CVSS Reference

Severity CVSS Range Examples
CRITICAL 9.0–10.0 SQLi, RCE, deserialization, backdoor
HIGH 7.0–8.9 XSS, CSRF, missing nonce, LFI
MEDIUM 4.0–6.9 Open redirect, missing capability, unsafe upload
LOW 0.1–3.9 Hardcoded credentials, deprecated functions
INFO 0.0 Performance issues, code quality, suggestions

Troubleshooting

PHPCS returns no results

# Test PHPCS manually
/home/krisha/.config/composer/vendor/bin/phpcs \
  --standard=/var/www/bug-b/config/phpcs-security.xml \
  --report=json -s /path/to/plugin/

Semgrep not found

# Reinstall
pip3 install --user semgrep
# It installs to:
/home/krisha/.local/bin/semgrep --version

PHPStan / PHPMD not found

bash /var/www/bug-b/tools/install.sh

Scan times out on a large plugin

# Increase timeout and skip slower scanners
python3 check.py --url <url> --timeout 300 --skip phpstan

Too many false positives Edit config/phpcs-security.xml and comment out noisy sniff rules with <!-- <rule ref="..."/> -->.

Plugin zip is password protected or corrupt The tool will report "Invalid zip file" and exit. Obtain a valid unprotected zip.


CI/CD and Pre-Commit Integration

New flags for automation

Flag Description
--dir PATH Scan an already-extracted directory (no download/zip needed — for CI and pre-commit)
--fail-on LEVEL Exit with code 1 if findings at or above this level exist. Choices: critical high medium low none (default: none)

Git Pre-Commit Hook (local)

Blocks commits that introduce CRITICAL vulnerabilities. Fast — skips PHPStan/PHPMD, typically runs in under 10 seconds.

Install once into your plugin repo:

bash /var/www/bug-b/tools/setup-hooks.sh /path/to/your-plugin-repo

That copies ci/pre-commit-hook.sh to .git/hooks/pre-commit and makes it executable.

What happens on each git commit:

┌─────────────────────────────────────────────┐
│  WordPress Plugin Security Check (pre-commit) │
└─────────────────────────────────────────────┘
  Staged PHP files: 3
  Scanning: /home/user/my-plugin

[Phase 1] ...
[Phase 4] Running security scanners...
  [1/4] phpcs_security:   ✓ 12 findings
  [2/4] phpcs_performance: ✓ 3 findings
  [3/4] ast_taint:         ✓ 2 findings
  [4/4] pattern_scanner:   ✓ 1 findings

╔══════════════════════════════════════════════════╗
║  COMMIT BLOCKED — CRITICAL security issues found ║
╚══════════════════════════════════════════════════╝

  Full report: .git/security-report/report.html
  Quick view:  cat .git/security-report/report.md

  To bypass (NOT recommended): git commit --no-verify

Configure the block threshold — edit .git/hooks/pre-commit:

FAIL_ON="critical"   # or: high | medium | none
SKIP_SCANNERS="phpstan,phpmd"   # keep fast for pre-commit

Uninstall:

rm /path/to/your-plugin/.git/hooks/pre-commit

GitHub Actions

Copy ci/github-actions.yml into your plugin repo as .github/workflows/security-check.yml.

Triggers on every push to main/master/develop and on all pull requests.

What you get:

  • Build fails if CRITICAL or HIGH findings exist (--fail-on high)
  • Full HTML + JSON + Markdown report uploaded as a downloadable artifact
  • Report content posted as a comment on the Pull Request
  • Job summary shows the Markdown report inline in the Actions tab

Setup:

# In your plugin repo
mkdir -p .github/workflows
cp /var/www/bug-b/ci/github-actions.yml .github/workflows/security-check.yml

Then edit line 43 — replace the git clone URL with the actual location of this checker:

- name: Clone WP Bug Bounty Checker
  run: git clone https://github.com/YOUR_ORG/bug-b.git /tmp/bug-b

Change failure threshold on line 56:

--fail-on high       # fail on HIGH or CRITICAL
--fail-on critical   # fail only on CRITICAL
--fail-on none       # never fail the build (report only)

GitLab CI/CD

Copy ci/gitlab-ci.yml to your plugin repo as .gitlab-ci.yml (or merge into an existing one).

cp /var/www/bug-b/ci/gitlab-ci.yml /path/to/your-plugin/.gitlab-ci.yml

Edit the git clone line to point to the checker's real location:

- git clone https://github.com/YOUR_ORG/bug-b.git $CHECKER_DIR

Key variables (set in GitLab CI/CD settings or edit the file):

variables:
  FAIL_ON: "high"       # critical | high | medium | low | none
  CHECKER_DIR: "/tmp/bug-b"
  REPORT_DIR: "$CI_PROJECT_DIR/security-report"

The report is stored as a GitLab artifact (downloadable from the pipeline page for 30 days).


CI/CD Behaviour Summary

Scenario Exit Code Commit/Build
No PHP files changed 0 ✓ Allowed
Findings below threshold 0 ✓ Allowed
Findings at/above threshold 1 ✗ Blocked
Scanner error (tool missing) 0 ✓ Allowed (graceful)

Recommended thresholds:

Environment --fail-on Rationale
Pre-commit hook critical Fast feedback, don't block developers on every warning
PR / Merge Request high Catch serious issues before code review
Main branch push critical Final gate before deployment

Adding Custom Rules

Add a Semgrep rule

Edit config/semgrep-wordpress.yaml:

- id: my-custom-rule
  pattern: some_dangerous_function($_REQUEST[$KEY])
  message: "Custom: dangerous function with user input"
  severity: ERROR
  languages: [php]
  metadata:
    category: security
    cwe: CWE-XX

Add a regex pattern

Edit scanners/pattern_scanner.py, add to SECURITY_PATTERNS or PERFORMANCE_PATTERNS:

{
    "id": "my_pattern",
    "regex": r"dangerous_call\s*\(\s*\$_(GET|POST)",
    "severity": "HIGH",
    "category": "rce",
    "title": "Dangerous call with user input",
    "description": "...",
    "cwe": "CWE-78",
    "confidence": "HIGH",
},

Add a PHPCS sniff mapping

Edit config/scanner-config.jsonwpcs_severity_map:

"MyPlugin.Security.MySniff": "sqli"

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages