Skip to content

Instantly share code, notes, and snippets.

@juliaamosova
Forked from joncave/plugin.php
Last active April 1, 2018 01:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save juliaamosova/32608cf13cbdd6bd396a883c260c2c1b to your computer and use it in GitHub Desktop.
Save juliaamosova/32608cf13cbdd6bd396a883c260c2c1b 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/
<?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
* 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' );
<?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' );
// JULIA'S COMMENT: SQL Injection vulnerability:
// Login details should not be saved in plain text, including failed attempts.
// Instead, we should be using the second argument of $wpdb->prepare to pass the actuall variables
// and use placeholders like %d and $s (printf format strings) to pass integers and strings (implement value binding method).
$wpdb->query( $wpdb->prepare( "INSERT INTO login_audit (login, pass, ip, time) VALUES (%s, %s, %s, %s)", array ($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">';
// JULIA'S COMMENT: Making sure that the evalue is set, not null and not empty here.
// Also making sure that the value is of the type we need so using
// `absint` - convert a value to non-negative integer.
if ( isset( $_GET['id'] ) && ! empty( absint( $_GET['id'] ) ) ) {
dvp_view_log( absint( $_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>';
// JULIA'S COMMENT: using `esc_html` and `esc_url` to sanitize the output to prevent an XSS attack.
echo '<td>' . esc_html( $log['login'] ) . '</td>';
echo '<td>' . esc_html( $log['pass'] ) . '</td>';
echo '<td>' . esc_html( $log['ip'] ) . '</td>';
$url = add_query_arg( 'id', $log['ID'], menu_page_url( 'failed-logins', false ) );
echo '<td><a href="' . esc_url( $url ) . '">' . esc_html( $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( '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>';
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;
// JULIA'S COMMENT: SQL Injection vulnerability: same explanation as on line 42.
$log = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM login_audit WHERE ID = %d', array($id) ) );
echo '<h2>Failed login #' . $id . '</h2>';
echo '<div>';
// JULIA'S COMMENT: using `esc_html` to sanitize the output to prevent an XSS attack.
echo '<strong>Username:</strong> ' . esc_html( $log['login'] );
echo '<br /><strong>Attempted password:</strong> ' . esc_html( $log['pass'] );
echo '<br /><strong>IP address:</strong> ' . esc_html( $log['ip'] );
echo '<br /><strong>Time of event:</strong> ' . esc_html( $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();
// JULIA'S COMMENT: Making sure that the evalue is set, not null and not empty here.
// Also making sure that the value is of the type we need so using
// `absint` - convert a value to non-negative integer.
if ( isset( $_POST['id'] ) && ! empty( absint( $_POST['id'] ) ) ) {
global $wpdb;
// JULIA'S COMMENT: SQL Injection vulnerability: same explanation as on line 42.
$wpdb->query( $wpdb->prepare( 'SELECT * FROM login_audit WHERE ID = %d', array($_POST['id']) ) );
}
// JULIA'S COMMENT: see comment below on line 152 regarding why we shouldn't use $_REQUEST.
wp_redirect( $_POST['redirect'] );
}
add_action( 'admin_post_dvp_delete_log', 'dvp_delete_log' );
// Update plugin options handler
function dvp_change_settings() {
// CSRF defence + caps check
// JULIA'S COMMENT: $_REQUEST should never be used because it is hard to track where the data is coming
// from (was it POST, or GET, or a cookie?), which makes reviewing the code more difficult.
// Additionally, it makes it easy to introduce sneaky and hard to find bugs, as any of the
// aforementioned locations can supply the data, which is hard to predict. Much better to be
// explicit and use either $_POST or $_GET instead.
if (isset($_POST['nonce']) && ! wp_verify_nonce($_POST['nonce'], 'dvp_settings')
|| ! current_user_can( 'manage_options' )
) {
// JULIA'S COMMENT: Using `wp_sanitize_redirect` - sanitizes a URL for use in a redirect.
// Removes invalid URL characters in the URL parameter.
wp_safe_redirect( wp_sanitize_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 );
// JULIA'S COMMENT: Using `wp_sanitize_redirect` - sanitizes a URL for use in a redirect.
// Removes invalid URL characters in the URL parameter.
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() {
// 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';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment