Recording on version control from the octocat animal shelter aka the hackathon of life aka the conference room that looks a lot like a bar aka the xss justice league aka the no-sql-no-cry school aka the pull-request-of-destiny school aka the ask-me-about-blocks-procs-and-lambdas school aka the Flatiron School in purgatory
Now that I have some idea how to control my tables and views my next big task is to make sure no one else gets any ideas about how to control my tables and views. Security is for lovers. I love my databases and I love my users and I don’t want any sql injection or cross-site scripting getting between us.
The SQL Hamburgler
Sql injection is the hostile practice of using sql fragments in form inputs to manipulate the database queries that will happen when processing that form. In Pleasantville form inputs supply only values to sql statements. In Crooklyn inputs are fragments of sql statements that will be processed as part of the database query. The corrupt database queries will perform actions that suit your attacker’s interests more than yours. Sql injection attacks usually have one of two objectives:
- Bypassing Authorization
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
- Unauthorized Reading
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Defense Against the Dark Arts
The counter-spell to SQL injection is to handle the database query plan and the values separately. As of Rails 3.1 ActiveRecord enables prepared statements in database queries. A prepared statement looks like:
SELECT "people".* FROM "people" WHERE "people"."id" = ? LIMIT 1 [["id", 1]]
For higher-level commands like :new and :find ActiveRecord takes care of the formatting. For lower-level commands like :where and :select you have to do it yourself. Both of these result in prepared statements:
Person.where("user_name = ? AND password = ?", user_name, password).first
Person.new(name: "Spider Jerusalem")
Before prepared statements ActiveRecord took four steps to execute a database query:
- Parse SQL statement
- Come up with query plan
- Execute query plan
- Return results
Without prepared statements the query plan depends on parsing the SQL statement, user input and all. This way SQL fragments in values can affect the query plan.
With prepared statements the database comes up with a query plan and caches it before we send any values. We then send the values along with a token for the cached sequel statement. With the statement already cached(meaning after the first query) the database only needs to perform 2 steps:
- Execute the query plan
- Return results
This way the values have no affect on the query plan. If an attacker inputs some SQL craziness ActiveRecord will only wind up searching the corresponding column for a row containing that SQL craziness. No harm done.
Efficiency side-note: on SQlite3 and Postgres caching prepared statements means dramatically more efficient database queries. The effect is greater with more complex queries because the query planning step takes longer. On mySQL, however, prepared statements are actually less efficient for simple queries because there is no separate step for query planning in these simple cases. mySQL does still follow the general trend for more complex queries.
Mass-assignment
Mass-assignment is a convenient way of processing all the params of a form at once. The vulnerability is that params you don’t want to assign can be sent to your controller. For example, with the http request:
PUT http://massassignment.com/users/1?user[can_edit_anything]=true
We don’t want to assign can_edit_anything without deliberation. By listing attributes under attr-accessible in the model we tell ActiveRecord which attributes it can mass-assign. These should be the benign attributes. Attributes like can_edit_anything have no place on that list.
class User < ActiveRecord::Base
attr_accessible :firstname, :lastname
end
We can also customize the list for different roles:
class User < ActiveRecord::Base
attr_accessible :firstname, :lastname
attr_accessible :can_edit_anything, :as => :admin
end
The attributes that are listed as accessible will still be protected from meddling as long as your controller is doing it’s job. The controller should be checking that you are logged in as the current user before changing the current user’s attributes. So you can change your name as many times as you want but no one else can.
Cross-Site Scripting Attacks
Cross-site Scripting While SQL injection is a server-side attack, Cross-site Scripting is a client-side attack. In this case, attackers are taking advantage of their input being rendered with the DOM to change the functionality of the page. Cross-site Scripting attacks usually have one of these objectives:
- Stealing the cookie to hijack the session
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
- Redirecting to a fake website
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
- Defacement
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Installing malicious software through security holes in the web browser.
See “Samy”: http://en.wikipedia.org/wiki/Samy_(computer_worm)
Defense against the dark arts:
To defend against cookie-stealers we can include httpOnly in the HTTP response header. httpOnly is a flag that can be set on a cookie. This flag makes it so the cookie can only be sent in http requests and it cannot be accessed by client-side script. This defense is effective against ordinary javascript injection but is still vunerable to asynchronous scripts.
Popularity side-note: In 2011 this Report found that just over half the top 50 sites do not use httpOnly and as a result are vulnerable to cookie-stealing xss attacks. As of version 2.3.2 Rails was the only open source framework to set the HTTP-only flag by default. Minswan.
The best defense against xss is old-fashioned santizing of user input. Whitelisting is better than blacklisting. With a blacklist, if you are only filtering the input once then an attacker might anticipate your filter by burying their malicious code in the second layer of their input. For example,
Cross-Site Request Forgeries
Cross-site Request Forgeries are like Cross-site Scripting attacks in that they are client-side attacks. In this case, however, the attacker exploits the possibility of the user being logged in on another site. If they are logged in the attack sends a malicious http request to that site.
1 2 3 4 5 6 7 8 9 10 |
|
Defense against the dark arts:
Being careful about seperating GET and POST helps with the most basic attacks. Rails just about forces good behavior on this front so no GET request should be executing business logic. There is, however, still the case of when an attacker tricks a user into submitting a form with dangerous hidden values.
The Synchronizer Token Pattern_Prevention_Cheat_Sheet) calls for embedding a random, unique token in all forms for a given session. The controller action can then check the token to verify that the HTTP request came from an authorized form.
Conclusion
Prepared statements, attr-accessible, httpOnly, and restful controller actions are very simple ways of protecting yourself. The Synchronizer Token Pattern takes a little more work but sounds straight-forward. Not having experience with security, I imagine most of the time spent locking down a site goes into sanitizing user input with whitelists and regExp. If you want to survive you can’t trust anyone with a keyboard or a touch-screen or a magic mouse or a wiimote.