Here’s a good way to protect against cross-site scripting attacks and SQL injection attacks. This will help catch mistakes where you (well actually your teammate, since you’re perfect) forgot to call “h” in a <%= %> block, or accidentally passed a SQL statement to the database without escaping the values:
Sprinkle unclosed HTML tags and apostrophes all over your fixture data and test code.
Then use assert_select liberally, which will barf on the console if it sees unclosed HTML tags–even if you were selecting some other part of the document.
I Like Stuff that’s Safe
Here is what a posts.yml file might look like:
test_post: id: 1 subject: <script> attack! detail: "sql injection: '; drop table posts;"
(If you use an apostrophe in YAML you have to quote the whole string.)
So assert_select has this handy side-effect I mentioned where it tells you about your malformed HTML. Since Rails tests don’t actually run in a browser, you need some other way to know that you’ve forgotten to escape data. Unclosed HTML tags in your fixtures, yeah, that’s the ticket.
And remember, you don’t need to call assert_select on the element that contains the bad data. Just call assert_select on anything and it will parse the output to make sure it’s well-formed.
def test_show
post = posts(:test_post)
get :show, post.id
assert_select "body"
end
The idea is that by sprinkling XSS attacks through your fixtures and using assert_select whenever you’re testing other stuff, the XSS attacks will become apparent.
If you do need to assert that the output is correct, you can call CGI::escapeHTML:
def test_show
post = posts(:test_post)
get :show, post.id
assert_select "span", :count => 1,
:text => CGI::escapeHTML(post.detail)
end
I can’t haz SQL injection attacks
I admit that putting SQL injection attacks in the fixtures is a bit contrived and may not help. A better way to catch SQL injection attacks is to pass apostrophes into the app from your test code, so go ahead and sprinkle your test code with beauties like this:
def test_update
post :update, posts(:test_post).id,
:detail => "sql injection: '; drop table posts;"
end
The secret to making this work is:
- apostrophe
- semicolon
- SQL statement
- another semicolon
You want to use a SQL statement that will cause a test to fail. It would be coolio if there were some way to make the current test succeed and subsequent tests fail, but I’m not sure I know a way to do that consistently. But at least if you use a “drop table” statement, you’re going to cause subsequent tests to fail (if there are any subsequent tests that use that table) because a schema change does not happen in a transaction. So even if you’re using transactional fixtures, the next test will fail anyway cuz the dang table is gone.
February 24, 2009 at 7:24 pm |
If you put several layers of protection in, you’re less likely to suffer from XSS attacks. Use the whitelist plugin, and use Erubis with the default escape text. There’s also a couple of plugins that validate models for HTML.
February 25, 2009 at 4:55 pm |
@Will: You no longer need to use the WhiteList plugin as it’s functionality has been integrated into Rails’ own sanitize method. The days of avoiding sanitize are over if you are on Rails 2.2+
March 9, 2009 at 6:26 pm |
“schema change does not happen in a transaction”
True except when dealing with postgresql which supports DDL transactions
March 9, 2009 at 6:42 pm |
True anon, but my point was in this case it’s *good* to not have schema changes run in a transaction. And I’m sure it’s easy enough in postgresql to have your schema changes not run in a transaction.