Skip to content

Instantly share code, notes, and snippets.

@WangYihang
Last active December 19, 2024 02:20
Show Gist options
  • Save WangYihang/e7d36b744557e4673d2157499f6c6b5e to your computer and use it in GitHub Desktop.
Save WangYihang/e7d36b744557e4673d2157499f6c6b5e to your computer and use it in GitHub Desktop.
port forwarding via python socket
#!/usr/bin/env python3
# Tcp Port Forwarding (Reverse Proxy)
# Author : WangYihang <wangyihanger@gmail.com>
'''
+-----------------------------+ +---------------------------------------------+ +--------------------------------+
| My Laptop (Alice) | | Intermediary Server (Bob) | | Internal Server (Carol) |
+-----------------------------+ +----------------------+----------------------+ +--------------------------------+
| $ ssh -p 1022 carol@1.2.3.4 |<------->| IF 1: 1.2.3.4 | IF 2: 192.168.1.1 |<------->| IF 1: 192.168.1.2 |
| carol@1.2.3.4's password: | +----------------------+----------------------+ +--------------------------------+
| carol@hostname:~$ whoami | | $ python pf.py --listen-host 1.2.3.4 \ | | 192.168.1.2:22(OpenSSH Server) |
| carol | | --listen-port 1022 \ | +--------------------------------+
+-----------------------------+ | --connect-host 192.168.1.2 \ |
| --connect-port 22 |
+---------------------------------------------+
'''
import socket
import threading
import argparse
import logging
format = '%(asctime)s - %(filename)s:%(lineno)d - %(levelname)s: %(message)s'
logging.basicConfig(level=logging.INFO, format=format)
def handle(buffer, direction, src_address, src_port, dst_address, dst_port):
'''
intercept the data flows between local port and the target port
'''
if direction:
logging.debug(f"{src_address, src_port} -> {dst_address, dst_port} {len(buffer)} bytes")
else:
logging.debug(f"{src_address, src_port} <- {dst_address, dst_port} {len(buffer)} bytes")
return buffer
def transfer(src, dst, direction):
src_address, src_port = src.getsockname()
dst_address, dst_port = dst.getsockname()
while True:
try:
buffer = src.recv(4096)
if len(buffer) == 0:
break
dst.send(handle(buffer, direction, src_address, src_port, dst_address, dst_port))
except Exception as e:
logging.error(repr(e))
break
logging.warning(f"Closing connect {src_address, src_port}! ")
src.close()
logging.warning(f"Closing connect {dst_address, dst_port}! ")
dst.close()
def server(local_host, local_port, remote_host, remote_port):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((local_host, local_port))
server_socket.listen(0x40)
logging.info(f"Server started {local_host, local_port}")
logging.info(f"Connect to {local_host, local_port} to get the content of {remote_host, remote_port}")
while True:
src_socket, src_address = server_socket.accept()
logging.info(f"[Establishing] {src_address} -> {local_host, local_port} -> ? -> {remote_host, remote_port}")
try:
dst_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
dst_socket.connect((remote_host, remote_port))
logging.info(f"[OK] {src_address} -> {local_host, local_port} -> {dst_socket.getsockname()} -> {remote_host, remote_port}")
s = threading.Thread(target=transfer, args=(dst_socket, src_socket, False))
r = threading.Thread(target=transfer, args=(src_socket, dst_socket, True))
s.start()
r.start()
except Exception as e:
logging.error(repr(e))
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--listen-host", help="the host to listen", required=True)
parser.add_argument("--listen-port", type=int, help="the port to bind", required=True)
parser.add_argument("--connect-host", help="the target host to connect", required=True)
parser.add_argument("--connect-port", type=int, help="the target port to connect", required=True)
args = parser.parse_args()
server(args.listen_host, args.listen_port,
args.connect_host, args.connect_port)
if __name__ == "__main__":
main()
@rhuangHack
Copy link

rhuangHack commented Mar 8, 2018

since this program repeatedly close to socket in transfer function, so the easy way to fix it is just delete or comment line 37 to line 39. have fun with it

@KINGSABRI
Copy link

Thanks
If you're looking for ruby implementation for port forwarding (ruby-port-forward)

@zhengxiangyue
Copy link

👍

@prateek-mgh
Copy link

if am using this as a tunnel connection between two, how can i print the input queries to this local address and port in the logs?

@WangYihang
Copy link
Author

@prateek-mgh, thank you for asking, would please elaborate 'input queries', did you mean income connection?

@prateek-mgh
Copy link

yes i was trying to log the income connections with the queries.
Am able to do now but looking forward for your input how would you do that

@prateek-mgh
Copy link

Also i noticed that though the script shows closing connection, it actually doesn't drop the connection, i guess this can depend upon target client to which it is forwarding to

@thiwanga
Copy link

can i use this as a traffic bot?

@amnkh
Copy link

amnkh commented May 26, 2020

Thanks for the code @WangYihang .
I feel F-strings are more readable, so I changed your string formatting (% operator) to F-strings.
But it makes the code incompatible with Python releases before 3.6
For anyone interested:
https://gist.github.com/amnkh/879caa4a82c513503d368f1d56643f11

@Lakr233
Copy link

Lakr233 commented Nov 1, 2020

THANKS FOR YOUR HELP!!
I got a flag from my ctf using your code! Amazing work!

@rohan-a99
Copy link

can i use this for remote debugging where my application will be running on a non-static ip and the debugger will connect from a static ip?

@ZhanQuan123
Copy link

Hello, I am getting an error like this:

[+] Detect connection from [127.0.0.1:43122]
[+] Trying to connect the REMOTE server [127.0.0.1:45825]
[+] Tunnel connected! Tranfering data...
[+] 127.0.0.1:8000 >>> 127.0.0.1:38794 [912]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [17]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [336]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [1024]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [1024]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [1024]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [1024]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [1024]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [1024]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [1024]
[+] 127.0.0.1:8000 <<< 127.0.0.1:38794 [918]
[-] No data received! Breaking...
[+] Closing connecions! [127.0.0.1:38794]
[+] Closing connecions! [127.0.0.1:8000]
[-] No data received! Breaking...
[+] Closing connecions! [127.0.0.1:8000]
Exception in thread Thread-34:
Traceback (most recent call last):
  File "/usr/lib64/python2.7/threading.py", line 801, in __bootstrap_inner
    self.run()
  File "/usr/lib64/python2.7/threading.py", line 754, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/home/..../port_forward.py", line 30, in transfer
    src.shutdown(socket.SHUT_RDWR)
  File "/usr/lib64/python2.7/socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
  File "/usr/lib64/python2.7/socket.py", line 174, in _dummy
    raise error(EBADF, 'Bad file descriptor')
error: [Errno 9] Bad file descriptor

Do you know how to fix it?

my guy leaked his own ip

@shubham2110
Copy link

shubham2110 commented Sep 3, 2021

This has been most used script by SYS-Admins so far.
Change print command to print function to get it working with python3.
print "[-] No data received! Breaking..." will become print("[-] No data received! Breaking...")

@sudocpMATHdotPY
Copy link

Sorry I have little experience with sockets, what would I put for L_HOST L_PORT R_HOST R_PORT?

@WangYihang
Copy link
Author

@sudocpMATHdotPY
After running python port-forwarding.py 0.0.0.0 1022 192.168.1.1 22, the script will listen on 0.0.0.0:1022.

Suppose the script is running on some machine that has a public IP address (1.2.3.4), then if you try to connect 1.2.3.4:1022 as if you communicating with 192.168.1.1:22.

@shubham2110
Copy link

@WangYihang Please change print into function so that it can also work with python3 version.

@WangYihang
Copy link
Author

@shubham2110 thanks for your advice, done.

@sudocpMATHdotPY
Copy link

@WangYihang
which ip address is public and which is private?

@WangYihang
Copy link
Author

WangYihang commented Jan 28, 2022

This diagram should be able to make it easy to understand. @sudocpMATHdotPY

image

@kristow31
Copy link

hello.
Tried to use mssql to port forwarding the connection. The connection is in progress. The program communicates with the base, there is no call to the "transfer" function. Why?

I wanted to see queries to the database, and see the response from the database. Tell me how can this be done?

@WangYihang
Copy link
Author

WangYihang commented Aug 4, 2022

I wanted to see queries to the database, and see the response from the database. Tell me how can this be done?

@kristow31

Modify function handle to meet your requirements.

def handle(buffer, direction, src_address, src_port, dst_address, dst_port):
    '''
    intercept the data flows between local port and the target port
    '''
    if direction:
        logging.info(f"{src_address, src_port} -> {dst_address, dst_port} {len(buffer)} bytes")
        logging.info(buffer)
    else:
        logging.info(f"{src_address, src_port} <- {dst_address, dst_port} {len(buffer)} bytes")
        logging.info(buffer)
    return buffer

As the following image shows.

image

@WangYihang
Copy link
Author

WangYihang commented Aug 4, 2022

@kristow31

Or you can use the Python package hexdump to view the hexadecimal data, just like the following image shows.

pip install hexdump
21a22
> import hexdump
33c34,35
<         logging.debug(f"{src_address, src_port} -> {dst_address, dst_port} {len(buffer)} bytes")
---
>         logging.info(f"{src_address, src_port} -> {dst_address, dst_port} {len(buffer)} bytes")
>         hexdump.hexdump(buffer)
35c37,38
<         logging.debug(f"{src_address, src_port} <- {dst_address, dst_port} {len(buffer)} bytes")
---
>         logging.info(f"{src_address, src_port} <- {dst_address, dst_port} {len(buffer)} bytes")
>         hexdump.hexdump(buffer)
91d93
<

image

@WangYihang
Copy link
Author

WangYihang commented Aug 4, 2022

The program communicates with the base, there is no call to the "transfer" function. Why?

@kristow31

See line 70-71, the target function of threading.Thread is function transfer.

In addition, please refer to the document of the Python threading package.

target is the callable object to be invoked by the run() method. Defaults to None, meaning nothing is called.

@medram
Copy link

medram commented Sep 16, 2022

Hello everyone,
I really like port forwarding and what you can do with it.
Here is my Python port forwarding version that I've made recently, inspired by @wangyihan script :D

I hope you like it.

By the way, it support HTTP Custom & HTTP injection (for fake Websocket upgrade reply).

@iPurya
Copy link

iPurya commented Oct 4, 2022

what about udp ?

@AnemusDev
Copy link

usage: 213.py [-h] --listen-host LISTEN_HOST --listen-port LISTEN_PORT
--connect-host CONNECT_HOST --connect-port CONNECT_PORT
213.py: error: the following arguments are required: --listen-host, --listen-port, --connect-host, --connect-port
Help, how to fix that?

@ruanhao
Copy link

ruanhao commented Sep 9, 2023

line 45, if len(buffer) == 0, there would be endless loop

@WangYihang
Copy link
Author

@ruanhao thanks for pointing out this bug, fixed.

@ldelaprade
Copy link

Why do we need --listen-host parameter ? 127.0.0.1 won't always do ?

@ruanhao
Copy link

ruanhao commented Sep 28, 2023

Why do we need --listen-host parameter ? 127.0.0.1 won't always do ?

there is need for 0.0.0.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment