Frustratingly enough, something fishy is going on with an input you're manually checking but you can't quite put your finger on it.
There must be something going on within the flow its value follows throughout the client-side but its complete behavior eludes you due to high complexity. Break-points need to be set and notes to be kept.
Well, fret no more! Codename SCNR to the rescue with its client-side data-flow tracing!
What I'm talking about is this:
- You configure a taint for the JS environment -- i.e. a value that SCNR needs to track.
- You operate the browser via a simple API -- Watir or Selenium.
- You retrieve the flow trace and you're golden! Your job is done! Off to exploitation you go armed with all the information you could possibly need to make it happen. :)
Let's see this in action.
Suppose you have a web application like this:
# Run with flag: | |
# -o 0.0.0.0 | |
# | |
require 'sinatra' | |
get '/' do | |
<<EOHTML | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script> | |
<body> | |
<form onsubmit="handleSubmit()"> | |
<input name="my-input"/> | |
</form> | |
</body> | |
<script> | |
function handleSubmit() { | |
var input_value = $("input[name=my-input]").val(); | |
writeToBody( prepareInput( input_value ) ); | |
return false; | |
} | |
function prepareInput( input ) { | |
return "test-".concat( input ); | |
} | |
function writeToBody( data ) { | |
$("body").html( data ); | |
} | |
</script> | |
EOHTML | |
end |
ruby tmp/articles/taint_tracer/server.rb -o 0.0.0.0
You now have to create a simple script like so:
require 'json' | |
# Get a SCNR Browser. | |
browser = Browser.new | |
# Set the taint we're interested in tracing. | |
browser.javascript.taint = 'blah' | |
# Get a Watir interface, it's much easier to control the browser like this. | |
watir = browser.watir | |
# You can also use Selenium instead of Watir if you prefer. | |
# selenium = browser.selenium | |
# Visit the page you want to examine more closely. | |
watir.goto "http://#{Socket.gethostname}:4567" | |
# Interact in which ever way you want -- the trace will be running. | |
watir.text_field( name: 'my-input' ).set 'blah' | |
watir.forms.first.submit | |
# Get the flow our taint followed through the application. | |
data_flow = browser.to_page.dom.to_rpc_data['data_flow_sinks'] | |
# A couple of nice ways to print-out the data-flow trace info. | |
# ap data_flow | |
puts JSON.pretty_generate( data_flow ) |
./bin/scnr_script tmp/articles/taint_tracer/script.rb
To get this result:
[ | |
{ | |
"function": { | |
"arguments": [ | |
"blah" | |
], | |
"name": "prepareInput", | |
"source": "function prepareInput( input ) { \n return \"test-\".concat( input );\n }" | |
}, | |
"object": "Window", | |
"taint": "blah", | |
"tainted_argument_index": 0, | |
"tainted_value": "blah", | |
"trace": [ | |
{ | |
"function": { | |
"arguments": [ | |
], | |
"name": "handleSubmit", | |
"source": "function handleSubmit() {\n var input_value = $(\"input[name=my-input]\").val();\n writeToBody( prepareInput( input_value ) );\n return false;\n }" | |
}, | |
"line": 13, | |
"url": "http://xps:4567/" | |
}, | |
{ | |
"function": { | |
"arguments": [ | |
{ | |
"currentTarget": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"eventPhase": "2", | |
"srcElement": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"target": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"type": "submit" | |
} | |
], | |
"name": "onsubmit", | |
"source": "function onsubmit(event) {\nhandleSubmit()\n}" | |
}, | |
"line": 3, | |
"url": "http://xps:4567/" | |
}, | |
{ | |
"function": { | |
"name": "eval", | |
"source": "async function(){var e = arguments[0].ownerDocument.createEvent('Event');e.initEvent('submit', true, true);if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }}" | |
}, | |
"line": null, | |
"url": "" | |
} | |
] | |
}, | |
{ | |
"function": { | |
"arguments": [ | |
"blah" | |
], | |
"name": "concat", | |
"source": "function concat() { [native code] }" | |
}, | |
"object": "String", | |
"taint": "blah", | |
"tainted_argument_index": 0, | |
"tainted_value": "blah", | |
"trace": [ | |
{ | |
"function": { | |
"arguments": [ | |
"blah" | |
], | |
"name": "prepareInput", | |
"source": "function prepareInput( input ) { \n return \"test-\".concat( input );\n }" | |
}, | |
"line": 18, | |
"url": "http://xps:4567/" | |
}, | |
{ | |
"function": { | |
"arguments": [ | |
], | |
"name": "handleSubmit", | |
"source": "function handleSubmit() {\n var input_value = $(\"input[name=my-input]\").val();\n writeToBody( prepareInput( input_value ) );\n return false;\n }" | |
}, | |
"line": 13, | |
"url": "http://xps:4567/" | |
}, | |
{ | |
"function": { | |
"arguments": [ | |
{ | |
"currentTarget": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"eventPhase": "2", | |
"srcElement": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"target": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"type": "submit" | |
} | |
], | |
"name": "onsubmit", | |
"source": "function onsubmit(event) {\nhandleSubmit()\n}" | |
}, | |
"line": 3, | |
"url": "http://xps:4567/" | |
} | |
] | |
}, | |
{ | |
"function": { | |
"arguments": [ | |
"test-blah" | |
], | |
"name": "writeToBody", | |
"source": "function writeToBody( data ) {\n $(\"body\").html( data );\n }" | |
}, | |
"object": "Window", | |
"taint": "blah", | |
"tainted_argument_index": 0, | |
"tainted_value": "test-blah", | |
"trace": [ | |
{ | |
"function": { | |
"arguments": [ | |
], | |
"name": "handleSubmit", | |
"source": "function handleSubmit() {\n var input_value = $(\"input[name=my-input]\").val();\n writeToBody( prepareInput( input_value ) );\n return false;\n }" | |
}, | |
"line": 13, | |
"url": "http://xps:4567/" | |
}, | |
{ | |
"function": { | |
"arguments": [ | |
{ | |
"currentTarget": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"eventPhase": "2", | |
"srcElement": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"target": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"type": "submit" | |
} | |
], | |
"name": "onsubmit", | |
"source": "function onsubmit(event) {\nhandleSubmit()\n}" | |
}, | |
"line": 3, | |
"url": "http://xps:4567/" | |
}, | |
{ | |
"function": { | |
"name": "eval", | |
"source": "async function(){var e = arguments[0].ownerDocument.createEvent('Event');e.initEvent('submit', true, true);if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }}" | |
}, | |
"line": null, | |
"url": "" | |
} | |
] | |
}, | |
{ | |
"function": { | |
"arguments": [ | |
"test-blah" | |
], | |
"name": "html", | |
"source": "function(e){return B(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if(\"string\"==typeof e&&!ke.test(e)&&!ge[(de.exec(e)||[\"\",\"\"])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(S.cleanData(ye(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)}" | |
}, | |
"object": "jQuery", | |
"taint": "blah", | |
"tainted_argument_index": 0, | |
"tainted_value": "test-blah", | |
"trace": [ | |
{ | |
"function": { | |
"arguments": [ | |
"test-blah" | |
], | |
"name": "writeToBody", | |
"source": "function writeToBody( data ) {\n $(\"body\").html( data );\n }" | |
}, | |
"line": 22, | |
"url": "http://xps:4567/" | |
}, | |
{ | |
"function": { | |
"arguments": [ | |
], | |
"name": "handleSubmit", | |
"source": "function handleSubmit() {\n var input_value = $(\"input[name=my-input]\").val();\n writeToBody( prepareInput( input_value ) );\n return false;\n }" | |
}, | |
"line": 13, | |
"url": "http://xps:4567/" | |
}, | |
{ | |
"function": { | |
"arguments": [ | |
{ | |
"currentTarget": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"eventPhase": "2", | |
"srcElement": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"target": "<form onsubmit=\"handleSubmit()\">\n <input name=\"my-input\">\n</form>", | |
"type": "submit" | |
} | |
], | |
"name": "onsubmit", | |
"source": "function onsubmit(event) {\nhandleSubmit()\n}" | |
}, | |
"line": 3, | |
"url": "http://xps:4567/" | |
} | |
] | |
} | |
] |