-
-
Save WPprodigy/e8c5815791c284f2fa1dd0d058acc7e4 to your computer and use it in GitHub Desktop.
An intentionally vulnerable plugin developed for WordPress plugin author education.http://make.wordpress.org/plugins/2013/04/09/intentionally-vulnerable-plugin/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Admin View: All logs | |
*/ | |
if ( ! defined( 'ABSPATH' ) ) { | |
exit; // Exit if accessed directly. | |
} | |
?> | |
<table> | |
<thead> | |
<tr> | |
<td><?php esc_html_e( 'Username', 'damn-vulnerable-wordpress-plugin' ) ?></td> | |
<td><?php esc_html_e( 'Password', 'damn-vulnerable-wordpress-plugin' ) ?></td> | |
<td><?php esc_html_e( 'IP Address', 'damn-vulnerable-wordpress-plugin' ) ?></td> | |
<td><?php esc_html_e( 'Time', 'damn-vulnerable-wordpress-plugin' ) ?></td> | |
</tr> | |
</thead> | |
<tbody> | |
<?php foreach ( $logs as $log ) : ?> | |
<tr> | |
<td><?php echo esc_html( $log['login'] ) ?></td> | |
<td><?php echo esc_html( $log['pass'] ) ?></td> | |
<td><?php echo esc_html( $log['ip'] ) ?></td> | |
<td> | |
<a href="<?php echo esc_url( add_query_arg( 'id', absint( $log['ID'] ), menu_page_url( 'failed-logins', false ) ) ); ?>"> | |
<?php echo date( "{$date_format} H:i", strtotime( esc_html( $log['time'] ) ) ); ?> | |
</a> | |
</td> | |
</tr> | |
<?php endforeach; ?> | |
</tbody> | |
</table> | |
<hr /> | |
<h3><?php esc_html_e( 'Settings', 'damn-vulnerable-wordpress-plugin' ) ?></h3> | |
<form action="<?php echo esc_url( add_query_arg( 'action', 'dvp_settings', admin_url( 'admin-post.php' ) ) ); ?>" method="post"> | |
<label> | |
<input type="checkbox" name="option[dvp_unknown_logins]" value="1" <?php checked( esc_attr( get_option( 'dvp_unknown_logins', '1' ) ) ) ?> /> | |
<?php esc_html_e( 'Should login attempts for unknown usernames be logged?', 'damn-vulnerable-wordpress-plugin' ) ?> | |
</label> | |
<?php wp_nonce_field( 'dvp_settings' ) ?> | |
<p class="submit"> | |
<input type="submit" name="submit" id="submit" class="button" value="<?php esc_html_e( 'Save Changes', 'damn-vulnerable-wordpress-plugin' ) ?>" /> | |
</p> | |
</form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Admin View: A single log | |
*/ | |
if ( ! defined( 'ABSPATH' ) ) { | |
exit; // Exit if accessed directly. | |
} | |
?> | |
<div> | |
<strong><?php esc_html_e( 'Username:', 'damn-vulnerable-wordpress-plugin' ) ?></strong> <?php echo esc_html( $log['login'] ) ?> | |
<br /><strong><?php esc_html_e( 'Attempted password:', 'damn-vulnerable-wordpress-plugin' ) ?></strong> <?php echo esc_html( $log['pass'] ) ?> | |
<br /><strong><?php esc_html_e( 'IP Address:', 'damn-vulnerable-wordpress-plugin' ) ?></strong> <?php echo esc_html( $log['ip'] ) ?> | |
<br /><strong><?php esc_html_e( 'Time of event:', 'damn-vulnerable-wordpress-plugin' ) ?></strong> <?php echo date( "{$date_format} H:i", strtotime( esc_html( $log['time'] ) ) ); ?> | |
</div> | |
<form action="<?php echo esc_url( add_query_arg( 'action', 'dvp_delete_log', admin_url( 'admin-post.php' ) ) ); ?>" method="post"> | |
<input type="hidden" name="id" value="<?php echo absint( $id ) ?>" /> | |
<input type="hidden" name="redirect" value="<?php echo esc_url( menu_page_url( 'failed-logins', false ) ) ?>" /> | |
<?php wp_nonce_field( 'dvp_delete_log' ) ?> | |
<p class="submit"> | |
<input type="submit" name="submit" id="submit" class="button" value="<?php esc_html_e( 'Delete Entry', 'damn-vulnerable-wordpress-plugin' ) ?>" /> | |
</p> | |
</form> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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/2013/04/09/intentionally-vulnerable-plugin/ | |
* Author: Jon Cave | |
* Author URI: http://joncave.co.uk | |
* Text Domain: damn-vulnerable-wordpress-plugin | |
* License: GPLv2+ | |
* | |
* DO NOT RUN THIS PLUGIN ON AN INTERNET ACCESSIBLE SITE | |
*/ | |
if ( ! defined( 'ABSPATH' ) ) { | |
exit; // Exit if accessed directly. | |
} | |
/** | |
* Display a notice in the admin to prevent use of this plugin on a real site. | |
*/ | |
function dvp_admin_safety_notice() { | |
echo '<div class="error"><p>' . wp_kses( __( '<strong>WARNING:</strong> Damn Vulnerable WordPress Plugin contains intentional security issues and should only be run on local development machines.', 'damn-vulnerable-wordpress-plugin' ), array( 'strong' => array() ) ) . '</p></div>'; | |
} | |
add_action( 'all_admin_notices', 'dvp_admin_safety_notice' ); | |
// Safety precautions are out of the way so load the actual stuff. | |
// In order for the plugin to continue working, you must define this constant. | |
if ( defined( 'LOAD_INTENTIONAL_VULNS' ) && LOAD_INTENTIONAL_VULNS ) { | |
include( dirname( __FILE__ ) . '/vulnerable.php' ); | |
} | |
/** | |
* Install the database table where we will store logs. | |
*/ | |
function dvp_install() { | |
if ( get_option( 'dvp_installed', 0 ) ) { | |
return; | |
} | |
global $wpdb; | |
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); | |
$collate = ''; | |
if ( $wpdb->has_cap( 'collation' ) ) { | |
$collate = $wpdb->get_charset_collate(); | |
} | |
$sql = " | |
CREATE TABLE {$wpdb->prefix}dvp_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) | |
) $collate; | |
"; | |
dbDelta( $sql ); | |
update_option( 'dvp_installed', 1 ); | |
} | |
register_activation_hook( __FILE__, 'dvp_install' ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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. | |
*/ | |
if ( ! defined( 'ABSPATH' ) ) { | |
exit; // Exit if accessed directly. | |
} | |
/** | |
* Log failed authentication attempts when the username was invalid. | |
* The wp_authenticate_user hook happens to late to do this. | |
* | |
* @param string $user Username entered during login. | |
* @param string $pass Password entered during login. | |
*/ | |
function dvp_check_username( $user, $pass ) { | |
if ( ! empty( $user ) && ! username_exists( $user ) && get_option( 'dvp_unknown_logins', 1 ) ) { | |
dvp_log_failed_login( $user, $pass ); | |
} | |
} | |
add_action( 'wp_authenticate', 'dvp_check_username', 10, 2 ); | |
/** | |
* Log failed authentication attempts when the username was valid but password was not. | |
* | |
* @param WP_User|WP_Error $user User object, else error object if a previous callback failed. | |
* @param string $pass The password that was entered during login. | |
* @return WP_User|WP_Error | |
*/ | |
function dvp_check_password( $user, $pass ) { | |
if ( ! is_wp_error( $user ) && ! wp_check_password( $pass, $user->user_pass, $user->ID ) ) { | |
dvp_log_failed_login( $user, $pass ); | |
} | |
return $user; | |
} | |
add_filter( 'wp_authenticate_user', 'dvp_check_password', 10, 2 ); | |
/** | |
* Add a log record for a failed login attempt. | |
* | |
* @param WP_User|string $user | |
* @param string $pass | |
*/ | |
function dvp_log_failed_login( $user, $pass ) { | |
global $wpdb; | |
if ( $user instanceof WP_User ) { | |
$user_login = sanitize_text_field( $user->user_login ); | |
} else { | |
$user_login = sanitize_text_field( $user ); | |
} | |
$password = sanitize_text_field( $pass ); | |
$ip_address = sanitize_text_field( dvp_get_ip() ); | |
$wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->prefix}dvp_login_audit ( login, pass, ip, time ) VALUES ( %s, %s, %s, %s )", array( $user_login, $password, $ip_address, current_time( 'mysql' ) ) ) ); | |
} | |
/** | |
* Add a WP Admin submenu page found at Tools > Failed Logins. | |
*/ | |
function dvp_menu() { | |
add_submenu_page( 'tools.php', esc_html__( 'Failed Logins', 'damn-vulnerable-wordpress-plugin' ), esc_html__( 'Failed Logins', 'damn-vulnerable-wordpress-plugin' ), 'manage_options', 'failed-logins', 'dvp_admin' ); | |
} | |
add_action( 'admin_menu', 'dvp_menu' ); | |
/** | |
* Display the failed login(s) in the admin. Routes to either show one specific failed attempt, or all of them. | |
*/ | |
function dvp_admin() { | |
?> | |
<div class="wrap"> | |
<?php | |
if ( isset( $_GET['id'] ) && ! empty( absint( $_GET['id'] ) ) ) { | |
dvp_view_log( absint( $_GET['id'] ) ); | |
} else { | |
dvp_view_all_logs(); | |
} | |
?> | |
</div> | |
<?php | |
} | |
/** | |
* Display all failed login attempts + options form. | |
* | |
* TODO: Need to paginate results for better performance and scalability. | |
*/ | |
function dvp_view_all_logs() { | |
global $wpdb; | |
$logs = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}dvp_login_audit", ARRAY_A ); | |
echo '<h2>' . esc_html__( 'Failed Logins', 'damn-vulnerable-wordpress-plugin' ) . '</h2>'; | |
if ( empty( $logs ) ) { | |
echo '<p>' . esc_html__( 'None… yet', 'damn-vulnerable-wordpress-plugin' ) . '</p>'; | |
} else { | |
$date_format = get_option( 'date_format' ); | |
include_once( dirname( __FILE__ ) . '/views/html-admin-all-logs.php' ); | |
} | |
} | |
/** | |
* Display a single failed attempt with a form to delete the entry. | |
*/ | |
function dvp_view_log( $id ) { | |
global $wpdb; | |
$log = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}dvp_login_audit WHERE ID = %d", absint( $id ) ), ARRAY_A ); | |
echo '<h2>' . sprintf( esc_html__( 'Failed login #%d', 'damn-vulnerable-wordpress-plugin' ), absint( $id ) ) . '</h2>'; | |
if ( empty( $log ) ) { | |
echo '<p>' . esc_html__( 'No entry found for this ID.', 'damn-vulnerable-wordpress-plugin' ) . '</p>'; | |
} else { | |
$date_format = get_option( 'date_format' ); | |
include_once( dirname( __FILE__ ) . '/views/html-admin-single-log.php' ); | |
} | |
} | |
/** | |
* Delete an entry. | |
*/ | |
function dvp_delete_log() { | |
check_admin_referer( 'dvp_delete_log' ); | |
if ( isset( $_POST['id'] ) && ! empty( absint( $_POST['id'] ) ) ) { | |
global $wpdb; | |
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}dvp_login_audit WHERE ID = %d", absint( $_POST['id'] ) ) ); | |
} | |
if ( ! empty( $_POST['redirect'] ) ) { | |
wp_safe_redirect( wp_sanitize_redirect( $_POST['redirect'] ) ); | |
} | |
} | |
add_action( 'admin_post_dvp_delete_log', 'dvp_delete_log' ); | |
/** | |
* Update plugin options. | |
*/ | |
function dvp_change_settings() { | |
// CSRF defence + caps check | |
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'dvp_settings' ) || ! current_user_can( 'manage_options' ) ) { | |
wp_safe_redirect( wp_sanitize_redirect( admin_url( 'tools.php?page=failed-logins' ) ) ); | |
exit; | |
} | |
if ( ! isset( $_POST['option']['dvp_unknown_logins'] ) ) { | |
$_POST['option']['dvp_unknown_logins'] = 0; | |
} | |
// Update options and redirect. | |
// Not ideal to do this for just one setting, but leaving for the challenge. | |
foreach ( $_POST['option'] as $name => $value ) { | |
// Would list all valid fields here, and check against an array if there were more. | |
if ( 'dvp_unknown_logins' !== $name ) { | |
continue; | |
} | |
update_option( $name, sanitize_text_field( $value ) ); | |
} | |
wp_safe_redirect( wp_sanitize_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() { | |
if ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) { | |
return wp_unslash( $_SERVER['HTTP_X_REAL_IP'] ); | |
} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { | |
// Proxy servers can send through this header like this: X-Forwarded-For: client1, proxy1, proxy2. | |
// Make sure we always only send through the first IP in the list which should always be the client IP. | |
return (string) rest_is_ip_address( trim( current( preg_split( '/[,:]/', wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) ) ); | |
} elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) { | |
return wp_unslash( $_SERVER['REMOTE_ADDR'] ); | |
} | |
return '0.0.0.0'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Notable security fixes:
$wpdb->prepare()
by passing in the second argument for the substitute variables.$wpdb->prepare()
in other places where arguments needed to be substituted in.wp_safe_redirect()
to ensure the user stays on-site.wp_safe_redirect()
to prevent further code execution.wp_sanitize_redirect()
before redirecting.Some Extras:
unknown_logins
logging feature.Things TODO:
wp_authenticate_user
filter logic into one function withwp_authenticate
hook.