Security for developers/Tutorial



Welcome to the tutorial on Secure Coding and Code Review, originally given at the Berlin Hackathon 2012. The live tutorial will cover:
 * Problems we see on MediaWiki: Top Vulnerabilities (20 min overview)
 * "Spot the Vulnerability" (5 min)
 * Secure Design Principles (5-10 min)
 * Collaborative: write some secure code (15 min)
 * Security review of developers' code

Tutorial Preparation
Would you like to see some specific code reviewed, live, during the tutorial? You can suggest it via this list, below.

Introduction
The Secure coding and code review for MediaWiki Tutorial is based on MediaWiki security best practices and was originally created for the Berlin Hackathon 2012. It is designed to teach skills, to be reusable and, as all things Wikimedia, to be a living, editable document. Please contribute your thoughts on the discussion page and update this tutorial as needed. Tutorial slides are available.

Top Problems We See

 * Stuff we keep seeing
 * Cross-site Scripting (XSS)
 * Cross-site Request Forgery (CSRF)
 * Not protecting against Register Globals
 * SQL Injection

XSS

 * 1) Definition: an attacker is able to inject client-side scripting into a web page viewed by other users.
 * 2) Results in:
 * 3) Authenticated Requests
 * 4) Session Hijacking
 * 5) Click Jacking
 * 6) XSS Worms
 * 7) Internal network portscanning
 * 8) Types
 * 9) Reflected
 * 10) Stored / 2nd Order
 * 11) DOM / 3rd Order
 * 12) XSSI / "javascript hijackign"

Reflected XSS
An attacker is able to inject client-side scripting into a web page, executed by others.

2nd Order (Stored) XSS
Attacker-controlled data is stored on the website, and executable scripts are displayed to the viewer (victim)

3rd Order (Dom-based) XSS
Attacker influences existing DOM manipulations in a way that generates attacker-controlled execution of scripts

XSSI

 * A script with sensitive data is included and manipulated from another domain
 * Known as "JavaScript hijacking" Here
 * Used in 2006 to compromise gmail
 * Often used to enable a CSRF attack

 document.write("");

More about XSS

 * For the theories behind XSS, and why certain filter should be applied, read: OWASP XSS Prevention Cheat Sheet
 * Quick reference of how to escape data in different document settings: OWASP Abridged XSS Prevention Cheat Sheet

Same Origin Policy
''' To understand the cross-site aspects of xss, you will need to understand Same Origin Policy SOP '''  Lots of useful information about how browsers handle cross-domain situations  ''' SOP is NOT the same for Javascript vs. Flash vs. XHR ''' SOP is changing with CORS
 * OWASP .ppt on SOP
 * http://code.google.com/p/browsersec/wiki/Part2

Understanding

 * When an HTTP session is tracked with a cookie, the cookie is appended to every call to the cookie's originating server. This is done automatically by the browser.
 * This includes calls to a server for an image, or an iframe
 * If a user has an authenticated session established, a remote site can request remote resources from that site. The browser will request those resources with the authority of the logged in user.

// a page on funnykitties.com  ...  ...

Or more likely:

  ...  document.wikiedit.submit;

Preventing

 * Tokens given out just prior to editing, checked to authorized edit
 * In addition to authentication / authorization checks (not a replacement for)
 * Must be difficult to predict / guess (e.g., md5( $username, $timestamp ) would be bad; md5( $username, $secretKey, $timestamp ) would be ok)

Register Globals

 * If register_globals is on, then an attacker can set variables in your script

Dangers

 * Remote File Inclusion (RFI), if allow_url_fopen is also true
 * Alter code execution

Alter execution
<?php //MyScript.php if ( authenticate( $_POST['username'], $_POST['pass'] ) ) { $authenticated = true; } if ( $authenticated ) { ... }

Protections

 * Don't use globals in script paths
 * Ensure your script is called in the correct context
 * if ( !defined( 'MEDIAWIKI' ) ) die( 'Invalid entry point.' );
 * Sanitize defined globals before use
 * Define security-critical variables before use as 'false' or 'null'

Understanding

 * Poorly validated data received from the user is used as part of a database (SQL) statement
 * Often occurs when attacker-controlled values are concatinated into INSERT or WHERE clauses

Dangers

 * Authentication Bypass
 * SELECT * FROM users WHERE username='$username' AND password='$pass';
 * If $pass is set to "' OR 1='1"?
 * Data corruption
 * DROP TABLE, UPDATE data (esp. user tables)
 * In the worst case, complete system compromose
 * xp_cmdshell on SQL Server
 * SELECT INTO OUTFILE on MySql
 * lots of other bad stuff...

Preventing

 * Use MediaWiki builtin db classes and pass variables by key=>value for CRUD
 * select
 * selectRow
 * insert
 * insertSelect
 * update
 * delete
 * deleteJoin
 * buildLike
 * If you really have to, use database::addQuotes to escape a single value

Top Vulnerabilites in Web Apps OWASP top 10

 * A1: Injection
 * A2: Cross-Site Scripting (XSS)
 * A3: Broken Authentication and Session Management
 * A4: Insecure Direct Object References
 * A5: Cross-Site Request Forgery (CSRF)
 * A6: Security Misconfiguration
 * A7: Insecure Cryptographic Storage
 * A8: Failure to Restrict URL Access
 * A9: Insufficient Transport Layer Protection
 * A10: Unvalidated Redirects and Forwards

XSS 1
getVal('text', '') ); $action = htmlspecialchars( $wgRequest->getVal('action', '') );

print "$theText \n";

XSS 2
Search: var pos = document.URL.indexOf("query=")+6; var qry = ''; if ( pos > 5 ) { qry = decodeURIComponent ( document.URL.substring( pos, document.URL.length ) ); }	document.write('');

SQL Injection
/** * Authentication bypass is possible. How? */ $username = $_POST['user']; $password = $_POST['pass']; $token = $_POST['csrf']; $safeUsername = mysql_real_escape_string( $username ); $safePassword = mysql_real_escape_string( $password ); $safeUsername = substr($safeUsername, 0, 20); $safePassword = substr($safeUsername, 0, 20);

if ( isValidToken( $token, __METHOD__ ) ) { $query = "SELECT * FROM User WHERE `user`='$safeUsername' AND `pass`='$safePassord' LIMIT 1"; $result = $db->query($query); if ( $result->num_rows == 1 ) { return "Success"; } else { return "Invalid Username or Password"; } } else { return "Invalid format for Username, Password, or Token"; }

CSRF
prepare("DELETE FROM `articles` WHERE `id` = ? LIMIT 1"); $stmt->bind_param('i', $formArticleId ); $stmt->execute; } else { $errorMsg = "Error with your token, please try again."; }	} else { $errorMsg = "You do not have permissions to delete this article"; } }

if ( $errorMsg <> '' ) { print " $errorMsg \n"; }

$ts = time; $stmt = $db->prepare("SELECT `id`, `title` FROM `articles` WHERE `owner` = ?"); $stmt->bind_param('s', $user ); $stmt->execute; $stmt->bind_result($articleId, $articleTitle);

while ($stmt->fetch) { print ' '; print ''. htmlspecialchars( $articleTitle ). ' ';	print ' '; print ''; print ''; print ''; print ''; print '<input type="submit" name="action" value="delete"/>'; print " \n"; }

More Bad Code...
<?php /** * Spot: * - A SQL Injection attack * - At least two XSS attacks * - A RFI attack */ $lang = isset($_REQUEST['lang'])?$_REQUEST['lang']:'en'; include_once( $lang.'/translations.php' ); //define $translations array of phrases if ( isset( $_POST['action'] && $_POST['action'] == 'login' ) {       // Handle Authentication Submit        $username = $_POST['user'];        $username = $_POST['pass'];        $db = getDB;        $result = mysql_query( "SELECT * FROM users WHERE username='$username' AND password=MD5('$username') LIMIT 1" );        if (mysql_num_rows($result) == 1 ) {                setcookie( 'myApp', "User:${_POST['user']}", 0 );                header( "Location: ${_POST['nextUrl']}" );        } else {                header( "Location: ${_SERVER['PHP_SELF']}?errorMsg=${translations['bad_login']}" ) ); } } else { //Otehrwise Show the Form print " \n \n \n \n"; print "       ${translations['login']}! \n"; if ( isset( $_GET['errorMsg'] ) ) { print "       <p class='error'>Error: ${_GET['errorMsg']} \n"; }       print "        <form action=\"${_SERVER['PHP_SELF']}\" method=\"POST\">\n"; print "               <input type='text' name='user' />\n"; print "               <input type='password' name='pass' />\n"; print "               <input type='hidden' name='action' value='login' />\n"; print "               <input type='hidden' name='nextUrl' value='secretPage.php' />\n"; print "               <input type='hidden' name='lang' value='$lang' />\n"; print "               <input type='submit' />\n"; print "       \n"; print " \n"; print " \n"; }

Vulnerabilites: Extra Credit:
 * RFI in lang parameter
 * sqli in auth
 * $lang xss in form
 * errMsg reflective xss
 * $translations['login'] reflective xss (with register globals on)
 * insecure cookie for authentication
 * nextUrl header injection
 * $translations['bad_login'] header injection (with register globals on)
 * No salt for md5 hash of passwords
 * PHP_SELF reflective xss

Simplicity (Demonstrable Security)
The larger and more complex your code, the more likely that it will contain a vulnerability. Keeping your code clean, simple, and easy to understand will reduce the number of places your code can be attacked, as well as making it easy for others to spot flaws or potential attacks before attackers do. Clearly document any security assumptions that your code makes.

Secure by Default
This principle states that the most strict security posture for your feature / code should be the default as soon as it goes into production. If an administrator needs to lower the security for their users, they should be able to do so, but it would be an intentional action on their part to increase their risk. If you're not sure what the most secure option is, ask someone!

Secure the Weakest Link
Successful attackers will nearly always attack the weakest point in a system, instead of the most well protected ones. For example, an attacker will not spend time circumventing the MediaWiki parser functions to inject a xss if they are able to make a change to Common.js, or socially engineer an admin to give them their password. Keep the whole system in mind as you design.

Least Privileges
In general, any features you develop should be able to run with "Just enough authority to get the job done." For example, if most of your users will only need to see public information, but a few would benefit from some privileged data, segment based on a role instead of giving everyone advances privileges or showing all of the data to everyone.

Media Wiki Secure Coding Checklist

 * Do not use eval, create_function
 * Regex'es
 * Don't use with /e
 * Escape user-controled strings that get used in a regex with preg_quote
 * Use MW's HTMLForm class, or include/check $wgUser->editToken to prevent CSRF
 * Filter / Validate your inputs
 * intval, getInt, etc.
 * Use a whitelist of expected values when possible
 * Defend against register-global variable injections
 * Use Html and Xml helper classes to write out text
 * Use Sanitizer::checkCss for any css from users
 * Use database wrappers to communicate with DB
 * Clearly comment unexpected / odd parts of your code

Collaborative Development of Code
<?php /** * SpecialPageExample.php * * A special page that searches MyData * * Starting with this structure, create a page that: * - Assuming a database table full of useful info myData *   - CREATE table `myData` (`id` INT, `name` varchar(80), `body` TEXT); * - Present a search box to users * - When a search is received, search the name and body fields and display: *   - The name of the data *   - The first 200 characters of data from the body * - For the sake of example, ignore Roan's talk about DB performance... */ class SpecialSearchExample extends SpecialPage { public function __construct { parent::__construct( 'MySearchExample' ); }       public function execute( $par ) { /**                * Show a search box at the top * Something like: *                 * <input type="text" name="search" value="$oldSearchVal" /> *                 *                  */                /**                  * Show search results, if there was a search term */       } }
 * Starting with a skeleton of a SpecialPage
 * Create a Special Page that allows searching, and showing results
 * Assume a database of text data
 * CREATE table `myData` (`id` INT, `name` varchar(80), `body` TEXT);
 * Presents a search box to users of your content.
 * When a search is received, search the database for a match in the `name` or `body` fields, display the search term, and a list of article names (which link to the article), and the beginning of the body to the users.

Hands-on Code Review

 * Suggested Code for Live Review (if it's long, please post somewhere and link to it.)