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.
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.)
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:
- 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.