Skip to content

Instantly share code, notes, and snippets.

@joncave
Last active March 27, 2025 13:23

Revisions

  1. joncave revised this gist Apr 10, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion vulnerable.php
    Original file line number Diff line number Diff line change
    @@ -87,7 +87,7 @@ function dvp_view_all_logs() {

    echo '<h3>Settings</h3>';
    echo '<form action="admin-post.php?action=dvp_settings" method="post">';
    wp_nonce_field( 'nonce', 'dvp_settings' );
    wp_nonce_field( 'dvp_settings', 'nonce' );
    echo '<label>';
    echo '<input type="checkbox" name="option[dvp_unknown_logins]" value="1" ' . checked(1, get_option('dvp_unknown_logins'), false) . ' />';
    echo 'Should login attempts for unknown usernames be logged?</label>';
  2. joncave revised this gist Apr 9, 2013. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion plugin.php
    Original file line number Diff line number Diff line change
    @@ -2,7 +2,7 @@
    /* Plugin Name: Damn Vulnerable WordPress Plugin
    * Description: Intentionally vulnerable plugin for plugin author education
    * Version: 0.1
    * Plugin URI: http://make.wordpress.org/plugins/
    * Plugin URI: http://make.wordpress.org/plugins/2013/04/09/intentionally-vulnerable-plugin/
    * Author: Jon Cave
    * Author URI: http://joncave.co.uk
    * License: GPLv2+
  3. joncave created this gist Apr 9, 2013.
    39 changes: 39 additions & 0 deletions plugin.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,39 @@
    <?php
    /* Plugin Name: Damn Vulnerable WordPress Plugin
    * Description: Intentionally vulnerable plugin for plugin author education
    * Version: 0.1
    * Plugin URI: http://make.wordpress.org/plugins/
    * Author: Jon Cave
    * Author URI: http://joncave.co.uk
    * License: GPLv2+
    *
    * DO NOT RUN THIS PLUGIN ON AN INTERNET ACCESSIBLE SITE
    */

    function dvp_admin_safety_notice() {
    echo '<div class="error"><p><strong>WARNING:</strong> Damn Vulnerable WordPress Plugin contains
    intentional security issues and should only be run on local development machines.</p></div>';
    }
    add_action( 'all_admin_notices', 'dvp_admin_safety_notice' );

    // Safety precautions are out of the way so load the actual stuff
    if (defined('LOAD_INTENTIONAL_VULNS') && LOAD_INTENTIONAL_VULNS) {
    include( dirname(__FILE__) . '/vulnerable.php' );
    }

    function dvp_install() {
    $sql = "CREATE TABLE login_audit (
    ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    login varchar(200) NOT NULL default '',
    pass varchar(200) NOT NULL default '',
    ip varchar(20) NOT NULL default '',
    time datetime NOT NULL default '0000-00-00 00:00:00',
    PRIMARY KEY (ID)
    );";

    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql );

    update_option( 'dvp_unknown_logins', 1 );
    }
    register_activation_hook( __FILE__, 'dvp_install' );
    168 changes: 168 additions & 0 deletions vulnerable.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,168 @@
    <?php
    /**
    * Fake plugin containing intentional security vulnerabilities designed for
    * plugin author education.
    *
    * Do NOT run this plugin on an internet accessible site. Do NOT re-use code
    * from this plugin.
    *
    * This plugin attempts to track potential attackers visiting a site and display
    * audit information to the administrator.
    */

    /**
    * Log failed authentication attempts.
    *
    * @param WP_User $user
    * @param string $pass
    * @return WP_User
    */
    function dvp_check_login( $user, $pass ) {
    if ( ! wp_check_password( $pass, $user->user_pass, $user->ID ) ) {
    dvp_log_failed_login( $user, $pass );
    }

    return $user;
    }
    add_filter( 'wp_authenticate_user', 'dvp_check_login', 10, 2 );

    /**
    * Add a log record for a failed login attempt.
    *
    * @param WP_User $user
    * @param string $pass
    */
    function dvp_log_failed_login( $user, $pass ) {
    global $wpdb;

    $login = $user->user_login;
    $ip = dvp_get_ip();
    $time = current_time( 'mysql' );

    $wpdb->query( $wpdb->prepare( "INSERT INTO login_audit (login, pass, ip, time) VALUES ('$login', '$pass', '$ip', '$time')" ) );
    }

    function dvp_menu() {
    add_submenu_page( 'tools.php', 'Failed Logins', 'Failed Logins', 'manage_options', 'failed-logins', 'dvp_admin' );
    }
    add_action( 'admin_menu', 'dvp_menu' );

    // Display the failed login(s)
    function dvp_admin() {
    echo '<div class="wrap">';
    if ( ! empty( $_GET['id'] ) ) {
    dvp_view_log( $_GET['id'] );
    } else {
    dvp_view_all_logs();
    }
    echo '</div>';
    }

    // Display all failed login attempts + options form
    function dvp_view_all_logs() {
    global $wpdb;
    $logs = $wpdb->get_results( "SELECT * FROM login_audit", ARRAY_A );

    echo '<h2>Failed logins</h2>';

    if (empty($logs)) {
    echo '<p>None... yet</p>';
    } else {
    echo '<table><thead><tr><td>Username</td><td>Password</td><td>IP address</td><td>Time</td></tr></thead><tbody>';

    foreach ($logs as $log) {
    echo '<tr>';
    echo '<td>' . $log['login'] . '</td>';
    echo '<td>' . $log['pass'] . '</td>';
    echo '<td>' . $log['ip'] . '</td>';
    $url = add_query_arg( 'id', $log['ID'], menu_page_url( 'failed-logins', false ) );
    echo '<td><a href="' . $url . '">' . $log['time'] . '</a></td>';
    echo '</tr>';
    }

    echo '</tbody></table>';
    }

    echo '<hr />';

    echo '<h3>Settings</h3>';
    echo '<form action="admin-post.php?action=dvp_settings" method="post">';
    wp_nonce_field( 'nonce', 'dvp_settings' );
    echo '<label>';
    echo '<input type="checkbox" name="option[dvp_unknown_logins]" value="1" ' . checked(1, get_option('dvp_unknown_logins'), false) . ' />';
    echo 'Should login attempts for unknown usernames be logged?</label>';
    submit_button( 'Update', 'secondary' );
    echo '</form>';
    }

    // Display a single failed attempt with a form to delete the entry
    function dvp_view_log( $id ) {
    global $wpdb;

    $log = $wpdb->get_row( "SELECT * FROM login_audit WHERE ID = " . esc_sql( $id ), ARRAY_A );

    echo '<h2>Failed login #' . $id . '</h2>';

    echo '<div>';
    echo '<strong>Username:</strong> ' . $log['login'];
    echo '<br /><strong>Attempted password:</strong> ' . $log['pass'];
    echo '<br /><strong>IP address:</strong> ' . $log['ip'];
    echo '<br /><strong>Time of event:</strong> ' . $log['time'];
    echo '</div>';

    echo '<form action="admin-post.php?action=dvp_delete_log" method="post">';
    wp_nonce_field();
    echo '<input type="hidden" name="id" value="' . $id . '" />';
    echo '<input type="hidden" name="redirect" value="' . $_SERVER['PHP_SELF'] . '?page=failed-logins" />';
    submit_button( 'Delete entry', 'delete' );
    echo '</form>';
    }

    // Delete entry handler
    function dvp_delete_log() {
    check_admin_referer();

    if ( isset( $_POST['id'] ) ) {
    global $wpdb;
    $wpdb->query( "DELETE FROM login_audit WHERE ID = " . esc_sql( $_POST['id'] ) );
    }

    wp_redirect( $_REQUEST['redirect'] );
    }
    add_action( 'admin_post_dvp_delete_log', 'dvp_delete_log' );

    // Update plugin options handler
    function dvp_change_settings() {
    // CSRF defence + caps check
    if (isset($_REQUEST['nonce']) && ! wp_verify_nonce($_REQUEST['nonce'], 'dvp_settings')
    || ! current_user_can( 'manage_options' )
    ) {
    wp_safe_redirect( admin_url( 'tools.php?page=failed-logins' ) );
    }

    if ( ! isset( $_POST['option']['dvp_unknown_logins'] ) )
    $_POST['option']['dvp_unknown_logins'] = 0;

    // Update options and redirect
    foreach ( $_POST['option'] as $name => $value )
    update_option( $name, $value );
    wp_safe_redirect( admin_url( 'tools.php?page=failed-logins' ) );
    }
    add_action( 'admin_post_dvp_settings', 'dvp_change_settings' );


    /**
    * Retrieve the IP address of the current user
    *
    * @return string IP address of current user
    */
    function dvp_get_ip() {
    // True IP in case of proxies
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    return $_SERVER['HTTP_X_FORWARDED_FOR'];
    } else if (isset($_SERVER['REMOTE_ADDR'])) {
    return $_SERVER['REMOTE_ADDR'];
    }

    return '0.0.0.0';
    }