This is a sample Premium report
Real scan of a demo theme. Every Premium feature is unlocked — line numbers, fix guides, secure code replacements.
Scan Summary
storefront-child-scan.zipUser-supplied data from $_GET['product_id'] is concatenated directly into an SQL query without sanitization. An attacker can manipulate this to dump your entire database, bypass authentication, or delete records.
Code Snippet
45: function get_product_reviews() {
46: global $wpdb;
47: $id = $_GET['product_id'];
48: $q = "SELECT * FROM wp_reviews WHERE product_id = $id";
49: return $wpdb->get_results($q);
50: }🔧 How to Fix
Always use $wpdb->prepare() with placeholders: $q = $wpdb->prepare( "SELECT * FROM wp_reviews WHERE product_id = %d", intval($_GET['product_id']) ); return $wpdb->get_results($q);
🛡 Secure Replacement
function get_product_reviews() { global $wpdb; $id = absint($_GET['product_id']); if ( ! $id ) return array(); return $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM wp_reviews WHERE product_id = %d', $id ) ); }
eval() executes arbitrary PHP passed via $_POST['template']. Any visitor can send a POST request with PHP code and execute it on your server — read files, create backdoors, or delete your entire site.
Code Snippet
32: function process_template($tpl) {
33: $user_input = $_POST['template'];
34: eval('?>' . $user_input);
35: return ob_get_clean();
36: }🔧 How to Fix
Never eval() user input. Use a safe template engine like Twig, or restrict templates to a whitelist of trusted, pre-approved strings stored server-side. If dynamic content is needed, use sprintf() with a fixed template and sanitized variables.
🛡 Secure Replacement
// Whitelist-based template selection function process_template($tpl_name) { $allowed = array('welcome', 'receipt', 'reset'); $key = sanitize_key($tpl_name); if (!in_array($key, $allowed)) { return ''; } return file_get_contents( plugin_dir_path(__FILE__) . 'templates/' . $key . '.html' ); }
No file type, extension, or size validation before moving the uploaded file. An attacker can upload a PHP web shell disguised as an image, then visit the URL to execute arbitrary code on the server.
Code Snippet
87: if (isset($_FILES['attachment'])) {
88: $dest = WP_CONTENT_DIR . '/uploads/' . $_FILES['attachment']['name'];
89: move_uploaded_file($_FILES['attachment']['tmp_name'], $dest);
90: echo 'File uploaded: ' . $dest;
91: }🔧 How to Fix
Validate MIME type using wp_check_filetype(), enforce an allowlist of safe extensions, rename the file to a random name, and use wp_handle_upload() which applies WordPress\'s built-in checks.
🛡 Secure Replacement
if ( isset($_FILES['attachment']) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; $allowed = array('jpg','jpeg','png','gif','pdf'); $check = wp_check_filetype($_FILES['attachment']['name']); if ( !in_array($check['ext'], $allowed) ) { wp_die('File type not permitted.'); } $result = wp_handle_upload($_FILES['attachment'], array('test_form' => false)); if ( isset($result['error']) ) wp_die($result['error']); }
The search term from $_GET['s'] is echoed directly into the HTML without escaping. An attacker can craft a URL with JavaScript in the query string, send it to a victim, and steal their session cookie or perform actions on their behalf.
Code Snippet
21: <h2>Search results for:</h2> 22: <p class="search-term"> 23: <?php echo $_GET['s']; ?> 24: </p>
🔧 How to Fix
Always escape output with esc_html() in WordPress. Never echo raw user input into HTML: <?php echo esc_html( get_search_query() ); ?>
🛡 Secure Replacement
<h2>Search results for:</h2> <p class="search-term"> <?php echo esc_html( get_search_query() ); ?> </p>
$_POST['tab'] is included as a file path without validation. An attacker can supply a path like ../../../../wp-config.php to read any file on the server, including your database credentials.
Code Snippet
154: function load_settings_tab() {
155: $tab = $_POST['tab'] ?? 'general';
156: include( plugin_dir_path(__FILE__) . $tab . '.php' );
157: }🔧 How to Fix
Use a strict whitelist. Never build file paths from user input: $allowed_tabs = array('general','email','advanced'); $tab = in_array($_POST['tab'], $allowed_tabs) ? $_POST['tab'] : 'general'; include plugin_dir_path(__FILE__) . $tab . '.php';
🛡 Secure Replacement
function load_settings_tab() { $allowed = array('general', 'email', 'advanced'); $tab = sanitize_key( $_POST['tab'] ?? 'general' ); if ( !in_array($tab, $allowed) ) { $tab = 'general'; } include plugin_dir_path( __FILE__ ) . $tab . '.php'; }
Database credentials are hardcoded in a PHP file inside the webroot. If this file is ever exposed (e.g. via a misconfigured server or another vulnerability), an attacker gets direct database access.
Code Snippet
10: // Direct DB connection for legacy stats
11: $legacy_db = new mysqli(
12: 'localhost', 'root', 'Sup3rS3cr3t!', 'legacy_stats'
13: );
14: if ($legacy_db->connect_error) die('DB error');🔧 How to Fix
Move credentials to wp-config.php as constants and reference them, or use environment variables. Never hardcode passwords in theme or plugin files.
🛡 Secure Replacement
// In wp-config.php: // define('LEGACY_DB_PASS', getenv('LEGACY_DB_PASS')); // In helpers.php: $legacy_db = new mysqli( DB_HOST, 'legacy_user', defined('LEGACY_DB_PASS') ? LEGACY_DB_PASS : '', 'legacy_stats' ); if ($legacy_db->connect_error) { error_log('Legacy DB: ' . $legacy_db->connect_error); }
Displaying PHP errors on a live site reveals file paths, function names, and internal logic to anyone who triggers an error. Attackers actively probe for these disclosures to map your server before an attack.
Code Snippet
6: <?php
7: // Temporary debug — remove before launch
8: error_reporting(E_ALL);
9: ini_set('display_errors', 1);
10: get_header();🔧 How to Fix
Remove error_reporting() and ini_set('display_errors') from all theme/plugin files. In production, errors should be logged to a file, never displayed: define('WP_DEBUG', false); define('WP_DEBUG_LOG', true); // logs to /wp-content/debug.log
🛡 Secure Replacement
<?php // Remove all error_reporting/display_errors calls. // In wp-config.php set: // define('WP_DEBUG', false); // define('WP_DEBUG_LOG', true); get_header();