Skip to content

Instantly share code, notes, and snippets.

@zacscott
Last active August 29, 2015 14:04
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 zacscott/0b052fc6f5fd42d33a02 to your computer and use it in GitHub Desktop.
Save zacscott/0b052fc6f5fd42d33a02 to your computer and use it in GitHub Desktop.
Checks HTTP requests for common attacks/exploits (XSS, XSRF and SQL injection).
<?php
/**
Plugin Name: Secman
Plugin URI: https://gist.github.com/zscott92/0b052fc6f5fd42d33a02
Description: Checks HTTP requests for common attacks/exploits (XSS, XSRF and SQL injection). Just activate and forget - no configuration!
Version: 1.0
License: MIT License
Author: Zachary Scott
Author URI: http://www.zacscott.net
*/
namespace zacscott;
/** Checks if the referer domain from the given header is correct. */
function checkRefferer( $header )
{
if ( isset( $_SERVER[ $header ] ) ) {
$header = $_SERVER[ $header ];
$domain = $_SERVER[ 'SERVER_NAME' ];
$pos = strpos( $header, $domain );
return ( $pos === 7 || $pos === 8 ); // i.e. after http:// or https://
} else {
return true; // can't determine - no header
}
}
/**
* Detects cross-site request forgeries for form submission.
* Reports a `possibleHack()` and `die`s immediately as there are few
* false positives. However there may be false negatives (including
* intentional ones!).
*
* Both the `HTTP_ORIGIN` and `HTTP_REFERER` headers are checked if present.
*/
function detectXSRF()
{
if ( $_SERVER[ 'REQUEST_METHOD' ] == 'POST' ) { // can't check GET request as may be legit
if ( !checkRefferer( 'HTTP_ORIGIN' ) ) // as of 2014 only implemented by chrome :-(
$header = 'HTTP_ORIGIN';
if ( !checkRefferer( 'HTTP_REFERER' ) ) // all browsers
$header = 'HTTP_REFERER';
// check and report if not match
if ( isset( $header ) ) {
possibleHack( "XSRF HTTP refferer check failed on header $header." );
die(
'A potential cross-site request forgery has been detected. '.
'For security purposes you cannot interact with this website.'
);
}
}
}
/** Attempts to detect XSS and SQL injection attacks in the given string. */
function checkUserInput( $name, $input )
{
if ( $input != null ) {
// detect attacks
detectXss( $name, $input );
detectSqlInjection( $name, $input );
}
}
/** Attempts to detect a xss attack, from user input. */
function detectXss( $name, $input )
{
assert( $name != null );
assert( $input != null );
// check common xss keywords & tags
$d = preg_match(
'{<\s*script|<\s*img|<\s*a|<\s*embed|<\s*image|src\s*=|<!--|javascript:}i',
$input
);
// report if detected
if ( $d )
futileHack( "XSS attack detected in input <$name>: \n$input" );
return $d;
}
/** Attempts to detect a SQL injection attack, from user input. */
function detectSqlInjection( $name, $input )
{
assert( $name != null );
assert( $input != null );
// common indicators
$d = preg_match(
'{^\s*\'\s*;|--\s*$|#\s*$|/\*\s*$}',
$input
);
// explicit keywords
$d |= preg_match(
'{^\s*[\'"]\s*(union|select|insert|drop|delete)}i',
$input
);
// report if detected
if ( $d )
futileHack( "SQL injection detected in input <$name>: \n$input" );
return $d;
}
/** Returns the clients ip address. */
function clientIp()
{
return $_SERVER[ 'REMOTE_ADDR' ];
}
/** Builds a stack trace starting from outside the Security object. */
function stacktrace()
{
$s = '';
$first = true;
$backtrace = debug_backtrace();
foreach ( $backtrace as $e ) {
// skip if empty entry
if ( !isset( $e[ 'file' ] ) || !isset( $e[ 'line' ] ) )
continue;
// caller info
$file = $e[ 'file' ];
$line = $e[ 'line' ];
$func = $e[ 'function' ];
$class = isset( $e[ 'class' ] ) ? $e[ 'class' ] : '';
$type = isset( $e[ 'type' ] ) ? $e[ 'type' ] : '';
if ( $func != 'net\\zeddev\\' ) // skip call to stackTrace()
$s .= "\t$class$type$func() ($file:$line) \n";
}
return $s;
}
/** Builds the log message for a security alert to the webmaster. */
function logmsg( $msg )
{
$logmsg = "$msg\n";
$logmsg .= "\n";
$ip = clientIp();
$ts = date( 'Y-m-d H:i:s', time() );
$logmsg .= "$title \n";
$logmsg .= "Time: $ts\n";
$logmsg .= "IP $ip \n";
// add user info
if ( is_user_logged_in() ) {
$user = wp_get_current_user();
$logmsg .= 'User - '. $user->first_name .' '. $user->last_name;
$logmsg .= ' ('. $user->ID .")\n";
}
$logmsg .= "\n";
$logmsg .=
"Stacktrace:\n". stacktrace() .
"\$_SERVER[] = \n". print_r( $_SERVER, true ) .
"\$_GET[] = \n". print_r( $_GET, true ) .
"\$_POST[] = \n". print_r( $_POST, true ).
"\n\n";
return $logmsg;
}
/** Reports a security vulnerability and/or hack attempt. */
function report( $msg )
{
$email = get_option( 'admin_email' );
error_log( $msg, 0 ); // log to 'log_error'
error_log( logmsg( $msg ), 1, $email ); // send webmaster an email
}
/**
* Quietly reports a futile hack against the system. Indicates a possible
* hack attempt which should not cause any damage but may may indicate an
* unscrupulous individual looking for a vulnerability in the system.
*
* @param $msg The message to report. Must not be `null`.
*/
function futileHack( $msg = '' )
{
assert( $msg != null );
$ip = clientIp();
report(
"FUTILE HACK DETECTED - $msg\n".
"NOTE: It is a distinct possibility that this is a false positive,".
" however it may indicate an unscrupulous individual looking for a".
" vulnerability."
);
}
/**
* Quietly reports a possible hack in the system. The cause might be a false
* positive however it still indicates a security vulneratibility which
* should have been detected before the call to this method. Therefore this
* method should be called to report the `last-line-of-defence` checks.
*
* @param $msg The message to report. Must not be `null`.
*/
function possibleHack( $msg = '' )
{
assert( $msg != null );
$ip = clientIp();
report(
"POSSIBLE HACK DETECTED - $msg\n".
"NOTE: It is a distinct possibility that this is a false positive,".
" however it still should not occur and therefore warrants further".
" investigation."
);
}
// run the auto check sequence once all plugins are loaded
add_action( 'plugins_loaded', function() {
if ( !empty($_GET) || !empty( $_POST ) ) { // skip if not postback
// check user/request input (for get)
foreach ( $_GET as $k => $v )
checkUserInput($k, $v);
// same again for post
foreach ( $_POST as $k => $v )
checkUserInput($k, $v);
// check for cross-site request forgeries
detectXSRF();
}
} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment