网络安全编程——开发一个TCP代理Python实现(二)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


本篇灵感

也是好久没有编代码了,今天也是重新找下手感;

具体的代码在网络安全编程------开发一个TCP代理Python实现已经说过,这里不再赘述;

今天在回顾代码的时候,发现有两个模块并没有用到(如果想在代理转发数据包之前,修改一下 回复 / 请求 的数据包,可以在这里进行修改)

代码展示

这里我稍微优化了一下代码,用argparse 模块来代替之前的sys.argv[1:] 功能(--help 帮助文档):

完整代码如下:

python 复制代码
import socket
import threading
import argparse
import sys

# 修正:使用 ''.join 而不是 '.'.join,否则会多出很多不必要的点
Hex_filter = ''.join(chr(i) if 32 <= i <= 126 else '.' for i in range(256))

def hexdump(src, step=16, show=True):
    if isinstance(src, bytes):
        src = src.decode(errors='replace') # 加上 errors='replace' 防止乱码报错

    result = []
    for i in range(0, len(src), step):
        word = str(src[i:i+step])
        printable = word.translate(Hex_filter)

        # 修正:十六进制格式化是 02x(2位补0),而不是 0x2
        hexa = ' '.join([f'{ord(c):02x}' for c in word]) # 修正:这里用空格连接比较好看

        hexwidth = step * 3
        # 修正:十六进制格式化是 04x,不是 0x4
        result.append(f'{i:04x} {hexa:<{hexwidth}} {printable}')

        if show:
            for line in result:
                print(line)
        else:
            return result

def receive_from(connections):
    buffer = b''
    connections.settimeout(5)
    try:
        while True:
            data = connections.recv(4096)
            if not data:
                break
            buffer += data
    except Exception as e:
        pass
    return buffer

def request_handler(buffer):
    return buffer

def response_handler(buffer):
    return buffer

def proxy_handler(client_socket, remote_host, remote_port, receive_first):
    remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 修正:connect 需要传入元组,所以是双层括号
    remote_socket.connect((remote_host, remote_port))

    if receive_first:
        # 修正:这里传入的是 remote_socket,而不是 receive_first 布尔值
        remote_buffer = receive_from(remote_socket)
        if len(remote_buffer):
            hexdump(remote_buffer)
            remote_buffer = response_handler(remote_buffer)
            print(f'[<==] Sending {len(remote_buffer)} bytes data to local')
            # 修正:直接发送 bytes 即可,不需要 decode
            client_socket.send(remote_buffer)

    while True:
        # 从本地读 -> 发往远程
        local_buffer = receive_from(client_socket)
        if len(local_buffer):
            print(f"[==>] Received {len(local_buffer)} bytes data from local")
            hexdump(local_buffer)
            local_buffer = request_handler(local_buffer)
            remote_socket.send(local_buffer)
            print(f"[*] Successful send data to Remote.")

        # 从远程读 -> 发往本地
        # 修正:这里你之前漏掉了重新读取远程数据的代码!
        remote_buffer = receive_from(remote_socket)
        if len(remote_buffer):
            print(f"[<==] Received {len(remote_buffer)} bytes data from remote")
            hexdump(remote_buffer)
            remote_buffer = response_handler(remote_buffer)
            client_socket.send(remote_buffer)
            print(f"[*] Successful send data to Local")
            # 修正:删除了这里无条件的 break

        # 如果双方都没有数据了,断开连接
        if not len(remote_buffer) and not len(local_buffer):
            client_socket.close()
            remote_socket.close()
            print(f"[*] No data send to anywhere, Connection closing.")
            break

def server_loop(local_host, local_port, remote_host, remote_port, receive_first):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        # 修正:bind 需要元组格式
        server.bind((local_host, local_port))
    except Exception as e:
        print(f"[!!] Failed to listen to {local_host}:{local_port}. Error is {e}")
        sys.exit()

    print(f"[**] Successful listen to {local_host}:{local_port}.")
    server.listen(5)

    while True:
        client_socket, addr = server.accept()
        print(f"[*] Received incoming connection from {addr[0]}:{addr[1]}")

        # 修正:target=proxy_handler 不能带括号,否则会在主线程立刻执行报错
        proxy_thread = threading.Thread(
            target=proxy_handler,
            args=(client_socket, remote_host, remote_port, receive_first)
        )
        proxy_thread.start()

def main():
    parser = argparse.ArgumentParser(description='TCP proxy tools v1.1 by W1nner丶')
    parser.add_argument("localhost", help='local IP addr')
    parser.add_argument("localport", type=int, help='local Port to listen.')
    parser.add_argument("remotehost", help='remote server IP addr')
    parser.add_argument("remoteport", type=int, help='remote server Port to listen.')
    parser.add_argument("-r", "--receive", action="store_true",
                        help="Receive data from remote first (for handshake protocols)")
    args = parser.parse_args()

    server_loop(args.localhost, args.localport, args.remotehost, args.remoteport, args.receive)

# 修正:顶格写,不能放在 main 函数里面!
if __name__ == "__main__":
    main()

# 错误
# 多余的括号:target=proxy_handler(),加了括号意味着"立即执行并把返回值当做目标",正确写法是不带括号 target=proxy_handler。
#
# 连接元组错误:remote_socket.connect(remote_host,remote_port),连接必须传入一个元组,应该是双层括号 .connect((remote_host, remote_port))。
#
# 格式化写反了:在 hexdump 中,十六进制补零的写法是 02x 和 04x,你写成了 0x2 会报错。
#
# 循环里忘记接收数据:在 while True 循环里"从远程读"的部分,你直接用了 if len(remote_buffer):,却忘记写 remote_buffer = receive_from(remote_socket)去重新接收数据了!
#
# 异常的 break:在发往本地数据后,你加了一个没有任何条件的 break,这会导致只通信一次代理就直接强行关闭了。

这里我们首先看一下效果: -h 帮助文档

效果演示

这里我们在本地用PHPstudy + proxy代码来进行演示:

(1)首先在 D:\phpstudy_pro\WWW 目录下创建一个前端网页(用于POST提交数据)

代码如下:

php 复制代码
<!DOCTYPE html>
<html>
<head><title>BHP TCP proxy tools</title></head>
<body>
    <h2>Login system</h2>
    <form method="POST" action="">
        user: <input type="text" name="username"><br>
        pass: <input type="password" name="password"><br>
        <input type="submit" value="login">
    </form>

    <?php
    if ($_SERVER['REQUEST_METHOD'] == 'POST') {
        echo "<hr>服务端收到请求:<br>";
        echo "用户: " . $_POST['username'] . "<br>";
        echo "密码: " . $_POST['password'];
    }
    ?>
</body>
</html>

(2)随后输入网址 http://127.0.0.1:8082/1.php 查看一下效果:

正常代理拦截数据(嗅探明文密码)

(1)这里我们输入命令,正常代理一下网页:

bash 复制代码
python proxy.py 127.0.0.1 9999 127.0.0.1 8082

效果如下:

(2)打开你的 Edge 浏览器,访问你在 WWW 目录下的 1.php,但是要走 9999 代理端口:http://127.0.0.1:9999/1.php

输入网址后,命令行成功返回了结果;

(3)然后提交一下数据:admin / admin

成功获取到了user=admin&pass=admin 的明文数据

页面也返回了效果:

已经成功让流量经过了 Python 代理,屏幕上的十六进制数据(Hexdump)就是最好的证明。

篡改请求体

假设你想恶作剧:无论用户在浏览器里输入什么账号,发给服务器的都强行变成 hacker

(1)打开你的 proxy.py,找到里面的 request_handler 函数,修改成这样:

bash 复制代码
def request_handler(buffer):
    # 检查发送给服务器的数据中是否包含用户输入的 'admin'
    if b"admin" in buffer:
        # 将字节流中的 admin 替换成 hacker
        buffer = buffer.replace(b"admin", b"hacker")
        print("[***] 警告:已将登录账号从 admin 篡改为 hacker!")
    return buffer

修改后重新运行脚本。在网页里输入 admin 登录,页面打印出来的接收结果将变成 hacker因为流量在半路被你写的代码"劫持并修改"了。

这里我输入的是 admin / 123456

而页面数据同样也被篡改:

篡改响应体(网页挂马/篡改)

不仅能改发出去的数据,还能改收回来的数据。你想把网页的标题 Login system 偷偷换掉吗?

(1)修改代码中的 response_handler

bash 复制代码
def response_handler(buffer):
    # 'Login system' 长度是 12
    # 'Hacked_Leco!' 长度也是 12
    if b"Login system" in buffer:
        buffer = buffer.replace(b"Login system", b"Hacked_Leco!")
        print("[***] 警告:已等长篡改服务器返回的网页内容!")
    return buffer

步骤同上,先监听然后提交表单:

提交数据如下:

随后进行拦截修改:

而网页标题也确实发生了改变

但是网页源码并没有变化:

总结

这两个 handler 函数就是网络安全中"中间人攻击 (MITM)"的核心原理缩影。

所以代码还是挺有趣的;

期待下次再见;

相关推荐
弹简特2 小时前
【JavaSE-网络部分06】TCP 纯高性能优化机制:延迟应答・捎带应答【传输层】
网络·tcp/ip·性能优化·捎带应答·延迟应答
Ulyanov2 小时前
卡尔曼滤波技术博客系列:第四篇:多目标跟踪:数据关联与航迹管理
python·目标跟踪·系统仿真·雷达电子战·仿真引擎
Three~stone2 小时前
MATLAB vs Python 两者区别和安装教程
开发语言·python·matlab
soragui3 小时前
【Python】第 1 章:Python 解释器原理
开发语言·python
Ulyanov3 小时前
卡尔曼滤波技术博客系列:第三篇 雷达目标跟踪:运动模型与坐标转换
python·目标跟踪·系统仿真·雷达电子战
MOYIXIAOWEIWEI3 小时前
VMware-centos7更改静态ip
网络·网络协议·tcp/ip
nimadan123 小时前
生成剧本杀软件2025推荐,创新剧情设计工具引领潮流
人工智能·python
极光代码工作室3 小时前
基于深度学习的智能垃圾分类系统
python·深度学习·神经网络·机器学习·ai
MediaTea3 小时前
Pandas 操作指南(二):数据选取与条件筛选
人工智能·python·机器学习·数据挖掘·pandas