{"id":15480801,"url":"https://github.com/davidsword/not-vulnerable-plugin","last_synced_at":"2026-04-18T10:05:00.826Z","repository":{"id":136189520,"uuid":"140053460","full_name":"davidsword/not-vulnerable-plugin","owner":"davidsword","description":"My take on securing the Intentionally Vulnerable Plugin (a plugin created for education).","archived":false,"fork":false,"pushed_at":"2018-07-30T06:01:58.000Z","size":33,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-01-18T10:44:53.133Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidsword.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-07-07T04:46:56.000Z","updated_at":"2018-07-30T06:01:59.000Z","dependencies_parsed_at":"2023-07-23T02:16:17.441Z","dependency_job_id":null,"html_url":"https://github.com/davidsword/not-vulnerable-plugin","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsword%2Fnot-vulnerable-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsword%2Fnot-vulnerable-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsword%2Fnot-vulnerable-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidsword%2Fnot-vulnerable-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidsword","download_url":"https://codeload.github.com/davidsword/not-vulnerable-plugin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243136238,"owners_count":20241988,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-02T05:00:30.137Z","updated_at":"2025-12-24T10:38:08.679Z","avatar_url":"https://github.com/davidsword.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Not Vulnerable Plugin\n\nThis is my take on securing the [Intentionally Vulnerable Plugin](https://make.wordpress.org/plugins/2013/04/09/intentionally-vulnerable-plugin/). For anyone unaware, the Intentionally Vulnerable Plugin is a security code challenge from WordPress that highlights and showcases common security vulnerabilities and missteps that may occur in WordPress plugins and themes.\n\nFor this challenge, I've gone through the original code, cleaned up the code, fixed all the security holes, and documented points of vulnerability.\n\n## My Changes\n\nPlease keep in mind that I've edited this plugin as a _collaborator_ as well as a _security auditor_.\n\n-   As a collaborator: I've made some structural changes intentionally to have a better codebase and keep up to WordPress standards.\n\n-   As an auditor:  I've highlighted any critical security changes (the whole point of this challenge) with inline blockdoc with emoji symbols, like so:\n\n```\n/**\n * ✅🔒 SECURE (#reference number): (explanation)\n *\n * Prevents (category of security hole).\n *\n * (elaboration, and optional demo of how-to exploit if not done)\n *\n * @link (source code link to the original vulnerable code)\n */\n(secured code)\n```\n\nAll functions have kept their `dvp_` prefix for easier reference.\n\n## Change Log\n\n### 1.0.0\n\n**SECURITY**\n\nCross-site scripting (XSS):\n-   Added: Escaped all i11n strings with `esc_html_*` (incase translator compromised).\n-   Added: Escaped all variables and output with `esc_*` (incase database, theme, or plugin compromised).\n-   Added: Escape on `add_query_arg()` output (to prevent XSS attacks with `PHP_SELF`).\n-   Added: Escaped, verified, and validated all user input.\n-   Changed: Validated user supplied IP address for storage\n\nSQL Injection:\n-   Changed: Used `$wpdb-\u003edelete()` instead of vulnerable `$wpdb-\u003equery(\"DELETE ... esc_sql())`.\n-   Changed: Used `$wpdb-\u003eprepare` properly instead of `\"SELECT ... \".esc_sql(` to properly escape values\n-   Changed: Used `-\u003einsert()` method instead to sanitize and escape variables, instead of vulnerable `$wpdb-\u003equery( $wpdb-\u003eprepare( \"INSERT.. '$var' ..\")`.\n\nCross-Site Request Forgery (CSRF):\n-   Changed: Used `wp_verify_none()` instead of `check_admin_referer()` as the later returns false and doesn't use custom nonce names which can result in coding mistakes that can lead to unintended access andor running of code.\n-   Changed: logic of `!isset($_REQUEST['_wpnonce'])` conditional which made it bypass-able.\n\nRedirects:\n-   Added: `exit` or `return` after redirects to prevent unintended code execution.\n-   Changed: Use `wp_safe_redirect()` instead of `wp_redirect()`.\n-   Changed: Used hard coded redirect instead of the doubly-exploitable `input[name=redirect]{$_SERVER['PHP_SELF']}` value.\n\nBad Coding:\n-   Changed: `dvp_change_settings()`'s' enormous security hole that made the entire `\\_options` table vulnerable to injection and alteration (looping through user-supplied array dynamically - see inline doc for full details).\n-   Changed: Uses `wp_login_failed` hook instead of `wp_authenticate_user` to process already-confirmed-failed log ins (instead of validating log in and handling the plain-text password).\n-   Removed: Plain text password handling and logging (saving a plain text password can lead to sensitive data exposure with any breach, and violates the users privacy/rights).\n\nMisconfiguration:\n-   Added: `index.php` with _\"silence is golden\"_ message to prevent directory exploit.\n-   Added: `ABSPATH` constant check to insure files not loaded directly.\n-   Added: `Requires PHP: 5.6` requirement (although it'd be nice to force 7.x on everyone!).\n\nFor full details of security changes and additions, please read inline doc in `\\*.php` pages.\n\n**CODE CHANGES \u0026 STANDARDS**\n\n-   Added: `readme.txt` for proper WordPress plugin rendering.\n-   Added: `README.md` file for Github presentation.\n-   Added: l10n support on all text strings, and `.pot` in `/languages/`.\n-   Added: Completed functionality of \"Ignore known users\" settings in logger.\n-   Added: Names on blank instances of `wp_nonce_field()` for better understanding of what's where.\n-   Added: Conditional check on `dvp_install()`'s initial `update_option` to not overwrite users setting if re-activated.\n-   Added: `admin_notices` on settings change and log deletion.\n-   Changed: Using `$wpdb-\u003eprefix` \u0026amp; `-\u003eget_charset_collate` in `dvp_install()` to properly name and character set the table.\n-   Changed: `ip` database column to `39` character length in for IPv6 support.\n-   Changed: Wrapped plugin in classes to prevent any name collisions, as this plugin may already be on reviewers setup.\n-   Changed: `vulnerable.php` split into two classes for easier code structure.\n-   Changed: All 8-character soft space tabbing to WPCS single hard tabs.\n-   Changed: Most instances of repetitive single line `echo \"{html}\";` converted to actual html.\n-   Changed: Code formatting throughout, for consistency and to meet WPCS.\n-   Removed: `LOAD_INTENTIONAL_VULNS` constant as its no longer vulnerable.\n-   Removed: `dvp_admin_safety_notice()` no need for admin notice, no longer vulnerable.\n\n### 0.1\n\n-   Jon Cave's [initial](https://make.wordpress.org/plugins/2013/04/09/intentionally-vulnerable-plugin/) [vulnerable source code](https://gist.github.com/joncave/5348689) (big shouts for starting this 🙏🏻!!!)\n\n\n## The Bonus Challenge\n\n\u003e \"with access to a subscriber level account can you find any ways of extracting the data from an option named secret_option?\"\n\n[Line 102](https://gist.github.com/joncave/5348689#file-vulnerable-php-L102):\n\n```\n$log = $wpdb-\u003eget_row( \"SELECT * FROM login_audit WHERE ID = \" . esc_sql( $id ), ARRAY_A );\n```\n\nIs the vulnerability for a subscriber level account reading the `secrete_option`.\n\n1) This function `dvp_view_log()` has no cap checks, so if the URL was known, a subscriber could access it without verification.\n\n2) The `esc_sql()` function works when wrapped in quotes. It does not properly escape code when used without quotes.\n\n3) The `esc_sql()` function contains `$id`, which is an unescaped `$_GET['id']` value.\n\nThese three factors make injection possible. An attacker could create a request similar to:\n\n```\n\u0026id=1 UNION SELECT option_id, option_name, option_value, autoload FROM wp_options\n```\n\nWhich would create the query:\n\n```\nSELECT * FROM wp_login_audit WHERE ID = 1 UNION SELECT option_id, option_name, option_value, autoload FROM wp_options\n```\n\nWhich would return all of the logs, as well as the entire `wp_options` table contents to `$log`. The `wp_options` table is basically appended to `wp_login_audit` table. The `secret_option` name and it's value would be visible within the audit log's HTML table, in plain text, as if it were a username and time value.\n\nNote This UNION method works in my case because I've removed the passwords column in `wp_login_audit` for other reasons, so the column counts match. If the column count wasn't the same, with a slightly different query the injection could still work.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidsword%2Fnot-vulnerable-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidsword%2Fnot-vulnerable-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidsword%2Fnot-vulnerable-plugin/lists"}