从工具的代码角度学安全:Sublist3r子域名枚举神器源码阅读

媒介

Sublist3r是一款简单易用的子域名罗列利器。然则本日我不盘算解说它的应用教程,而是深入一层,从它的代码中窥测道理。大概有人可能很不理解,为什么不好好的应用对象而偏偏去追究它的代码实现细节。

从我小我角度来说,我感觉:

第一个层面,一位优秀的安然从业者,至少在读/写代码层面不能成为短柄。假如一味的只应用对象,不懂得道理,那很轻易成为大年夜家口中的“脚本小子(script kiddie)”。

第二个层面,这是我小我推重的的进修措施,从自己的进修蹊径中,我爱好遵照3个W的进修原则,即(What->Why->What):

第一个What表示在初学阶段,必要学会哪些器械醒目些什么,比如知道了Sublist3r对象可以用来罗列子域名。

第二个Why则表示在中级阶段,去懂得这个器械的实现道理,以及为什么要这么实现,比如经由过程对象的代码学会它的底层细节,与此同时还能富厚常识体系。

第三个What表示在高档阶段,知道要干什么工作必要什么对象,不在细究技巧细节,而是经由过程前期的进修,形成自己的常识收集,能精准的根据当前环境定位到详细的对象。

Sublist3r对象先容

从Sublist3r的GitHub主页(https://github.com/aboul3la/Sublist3r)上,我们可以懂得到这是一款基于python开拓应用OSINT技巧的子域名罗列对象。赞助渗透测试者和bug捕获者网络目标域名的子域名。

什么是OSINT

OSINT是英文名“Open-source intelligence”的缩写,中文名称叫“公开滥觞情报”。从”民众,”可见的信息中,查找所需目标的信息。说的普通易懂一点,便是我想知道某某器械的具体信息,我在公开的信息里查找、检索,找到有关这个器械的任何内容都提掏出来,着末将这些内容汇总,获得较为具体的信息。再说的普通易懂一点便是:查资料。再再普通一点便是:找百度。终纵目标信息的准确度、具体水匀称依附于查找源。以是Sublist3r的道理便是这样,然则Sublist3r的查找源很多,不仅仅是在个别搜索引擎上查找。搜索源包括有百度、Yahoo、Google、Bing、Ask、Netcraft等等除此之外应用经由过程查找SSL证书、DNS、暴力罗列等这些手段去查找子域名。

简单应用

可以经由过程git clone https://github.com/aboul3la/Sublist3r.git安装Sublist3r。然后应用sudo pip install -r requirements.txt安装依附库。着末经由过程一个简单的敕令查找子域名,这里以freebuf为例子。python3 sublist3r.py -d freebuf.com网站内容越多查询光阴越慢,可以加一个-v来实时显示找到的子域名。

代码细节

代码目录

在Linux终端下,应用tree能打印当前目录等布局树:

.

├── LICENSE

├── README.md

├── requirements.txt

├── subbrute

│├── init.py

│├── names.txt

│├── resolvers.txt

│├── subbrute.py

└── sublist3r.py

进口函数

sublist3r的的主要代码实现在sublist3r.py文件中。可以应用你爱好的代码编辑器,打开这个文件。为了便于涉猎,我们把所有的代码块(def、class)合上。你大年夜概会看到如下的场景:

import re

import sys

#……省略多少引用

def banner():

def paeser_error(errmsg):

#……省略多少函数

class enumratorBase(object):

class enumratorBaseThreaded(multiprocessing.Process, enumratorBase):

class GoogleEnum(enumratorBaseThreaded):

class YahooEnum(enumratorBaseThreaded):

class AskEnum(enumratorBaseThreaded):

class BingEnum(enumratorBaseThreaded):

class BaiduEnum(enumratorBaseThreaded):

#……省略多少 XXEnum 的类

def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, engines):

#…省略main内容

if name == “main”:

args = parse_args()

domain = args.domain

threads = args.threads

savefile = args.output

ports = args.ports

enable_bruteforce = args.bruteforce

verbose = args.verbose

engines = args.engines

if verbose or verbose is None:

verbose = True

banner()

res = main(domain, threads, savefile, ports, silent=False, verbose=verbose, enable_bruteforce=enable_bruteforce, engines=engines)

假如不是应用包引用,而是直接在节制台打开,会履行if name == “main”:下的内容。这里我们可以大年夜概的懂得到这里的功能,主如果用于处置惩罚用户在节制台输入的参数,并将其作为参数去调用main函数。我们可以简单的懂得一下这些参数,domain是用户输入的主域名,比如freebuf.com;savefile是用于指定查找停止后子域名数据的保存目;verbose便是我们前面说的-v参数,经由过程这个参数来节制是否实时输出信息;engines是查找引擎(搜索源),假如为空则表示查找整个的引擎,假如用户指定了某个引擎(比如baidu),则只应用指定的引擎(比如baidu)进行查找。

main函数

一下是main这个函数的代码,我省略了很多的细节和差错处置惩罚部分的代码。

def main(domain, threads, savefile, ports, silent, verbose, enable_bruteforce, engines):

search_list = set()

if is_windows:

subdomains_queue = list()

else:

[1] [2] [3] [4]下一页

subdomains_queue = multiprocessing.Manager().list()

# Validate domain

domain_check = re.compile(“^(http|https)?[a-zA-Z0-9]+([-.]{1}[a-zA-Z0-9]+).[a-zA-Z]{2,}$”)

#…省略域名验证代码

parsed_domain = urlparse.urlparse(domain)

supported_engines = {‘baidu’: BaiduEnum,

‘yahoo’: YahooEnum,

‘google’: GoogleEnum,

‘bing’: BingEnum,

‘ask’: AskEnum,

‘netcraft’: NetcraftEnum,

‘dnsdumpster’: DNSdumpster,

‘virustotal’: Virustotal,

‘threatcrowd’: ThreatCrowd,

‘ssl’: CrtSearch,

‘passivedns’: PassiveDNS

}

chosenEnums = []

if engines is None:

chosenEnums = [

BaiduEnum, YahooEnum, GoogleEnum, BingEnum, AskEnum,

NetcraftEnum, DNSdumpster, Virustotal, ThreatCrowd,

CrtSearch, PassiveDNS

]

else:

engines = engines.split(‘,’)

#…省略自定义引擎处置惩罚

# Start the engines enumeration

enums = [enum(domain, [], q=subdomains_queue, silent=silent, verbose=verbose) for enum in chosenEnums]

for enum in enums:

enum.start()

for enum in enums:

enum.join()

subdomains = set(subdomains_queue)

for subdomain in subdomains:

search_list.add(subdomain)

subdomains = search_list

if subdomains:

subdomains = sorted(subdomains, key=subdomain_sorting_key)

if savefile:

write_file(savefile, subdomains)

#…..省略

return subdomains

首先,代码开首根据本地的操作系统类型去声明变量subdomains_queue,这个用于保存找到的子域名。接着你可以看到domain_check,这个用于验证域名是否合法。字典变量supported_engines内即为本日的重头戏——查找引擎。冒号前面的字符串比如‘baidu’表示引擎的名字,冒号后面的BaiduEnum表示对应的类class BaiduEnum(enumratorBaseThreaded)。

{

‘baidu’: BaiduEnum,

‘yahoo’: YahooEnum,

‘google’: GoogleEnum,

‘bing’: BingEnum,

‘ask’: AskEnum,

‘netcraft’: NetcraftEnum,

‘dnsdumpster’: DNSdumpster,

‘virustotal’: Virustotal,

‘threatcrowd’: ThreatCrowd,

‘ssl’: CrtSearch,

‘passivedns’: PassiveDNS

}

每一个引擎便是一个写好的类,我们把类作为数据保存到列表enums里面。终极我们经由过程for轮回来依次调用每一个类。而这每一个类都对应了该网站的子域名查询规则。就像BaiduEnum这个类,它里面就写好了若何在百度上查找关键词,以及若何筛选数据等等内容。

上一页[1] [2] [3] [4]下一页

类BaiduEnum

由于文章篇幅有限,我们就不一个一个类讲以前,就从BaiduEnum这个类为例子,其他的类处置惩罚措施也差不多。BaiduEnum这个类承袭与enumratorBaseThreaded类,而enumratorBaseThreaded类又承袭于multiprocessing.Process类以及enumratorBase类。这2个类是所有引擎的基类。它们把引擎的基础功能提掏出来,比如收集哀求、http头数据等等。假如不是很懂得这个观点可以去查找一下面向工具编程中的承袭观点。我们来看看BaiduEnum的构造函数:

def init(self, domain, subdomains=None, q=None, silent=False, verbose=True):

subdomains = subdomains or []

base_url = ‘https://www.baidu.com/s?pn={page_no}&wd={query}&oq={query}’

self.engine_name = “Baidu”

self.MAX_DOMAINS = 2

self.MAX_PAGES = 760

enumratorBaseThreaded.init(self, base_url, self.engine_name, domain, subdomains, q=q, silent=silent, verbose=verbose)

self.querydomain = self.domain

self.q = q

return

重点在于base_url,url中的{page_no}以及{query}便是必要终极调换的目标,query表示查询参数,page_no表示页数,大概很多人会以为这个页数是我们普通意义上的1表示第一页,2表示第二页。但假如你去仔细看代码,你会发明在默认环境下这个page_no是每一次加10。这是由于百度和谷歌对付page_no的处置惩罚并不因此页数为根基,而因此搜索条数为根基。每一页显示10条搜索内容,那么下一页便是源根基上加上10。这样说可能会有点抽象,说的简单点,page_no为0的时刻,搜索引擎默认显示的是1-10笔记录,当page_no为10的时刻,搜索引擎显示的是第11-20笔记录,以此类推。这2个参数会在后续代码中构造出来并调换。比如{query}在后续的代码中它则会根据查找出来的子域名实时调换。

def generate_query(self):

if self.subdomains and self.querydomain != self.domain:

found = ‘ -site:’.join(self.querydomain)

query = “site:{domain} -site:www.{domain} -site:{found} “.format(domain=self.domain, found=found)

else:

query = “site:{domain} -site:www.{domain}”.format(domain=self.domain)

return query

要理解这个,我们要先懂得一下一个搜索的小技术。假如我盼望在搜索引擎上查找关于域名的常识,那我可以搜索域名。然则忽然我感觉这样搜索很不精准,我想把所有百度知道的内容扫除掉落,那我就可以搜索:域名 -百度知道。再进一步,我想把百度贴吧的数据也扫除掉落,那么我的搜索词应该是这样的:域名 -百度知道 -贴吧。着末我发明,只有知乎上的信息对拍照符我的要求,那我就可以直接指定查询前提:域名 site:zhihu.com来查询。这个技术也用到了{query}身上。假设我们想探求freebuf.com的子域名,那么我们最开始的初始值应该是这样的:site:freebuf.com,接着我们发明有一个子域名www.freebuf.com,那么我们的查询前提就变成了:site:freebuf.com -site:www.freebuf.com。再接着,我们发清楚明了一个新的子域名job.freebuf.com,那我们的查询前提就变成了site:freebuf.com -site:www.freebuf.com -site:job.freebuf.com以此类推,赓续的限定查询前提直到找不到为止。好了,我们已经知道若何构建url链接去搜索,那么它sublist3r又是若何经由过程搜索结果获取域名的呢?在类BaiduEnum的extract_domains措施里就实现了url地址的解析,如下所示。

def extract_domains(self, resp):

links = list()

found_newdomain = False

subdomain_list = []

link_regx = re.compile(‘?class=”c-showurl”.?>(.?)’)

try:

links = link_regx.findall(resp)

for link in links:

link = re.sub(‘|>|, ”, link)

if not link.startswith(‘http’):

link = “http://” + link

subdomain = urlparse.urlparse(link).netloc

if subdomain.endswith(self.domain):

subdomain_list.append(subdomain)

if subdomain not in self.subdomains and subdomain != self.domain:

上一页[1] [2] [3] [4]下一页

foundnewdomain = True

if self.verbose:

self.print(“%s%s: %s%s” % (R, self.engine_name, W, subdomain))

self.subdomains.append(subdomain.strip())

except Exception:

pass

if not found_newdomain and subdomain_list:

self.querydomain = self.findsubs(subdomain_list)

return links

另外的代码都是为高低文办事的,我们就来看看几个关键代码就好,比如re.compile(‘?class=”c-showurl”.?>(.?)’)这是一个正则表达式。找到所有class名称为‘c-showurl’的a标签。我们可以在百度上随便搜索一点器械,然后右键源代码,看到搜索记录的html代码。我这里的一个例子是这样的:

a target=\”_blank\” href=\”http://www.baidu.com/link?url=Z6IgCu5XyBlPrQ5dB9aEMfc_kRh9NhHpI1LcsEe3xR4tfVp_VaDNr3kRUPzi88eGvokctArtiUoNh1ANE5BZM_\” class=\”c-showurl\” style=\”text-decoration:none;\”>www.freebuf.com/articl… /a>

没错,这个a标签的class是c-showurl,且我们可以看到在a标签包孕下,有一个若隐若现的网址,虽然被暗藏了后面一半,我们照样可以看出它的子域名。第二个正则表达式re.sub(‘|>|的目的是把找到的文本的细枝末节砍掉落,留下我们必要的那一段域名。就这样我们我们经由过程正则表达式找到了搜索记录里的子域名,着末颠末去重,拿到终极的子域名列表。代码的其他细节我就没有很具体的描述,我盼望这里只是一个涉猎向导,更多代码的细节因为篇幅有限就不在一行一行具体描述。由于大年夜部分的代码写出来是为了履行高低文而写的,并不是为了道理而写。我们只要知道代码的履行道理,以及代码的目的即可。

暴力罗列子域名

暴力罗列也是很普遍的一种措施。所谓的暴力罗列真的很暴力,它是经由过程一个一个子域名试以前,看看目标子域名是否存在。暴力罗列域名的代码没有写在sublist3r.py代码中,而是自力包装了一个库,称之为subbrute,代码在/subbrute/subbrute.py文件中。它会根据你今朝的参数去设置设置设备摆设摆设是否激活暴力罗列。这一段代码写在main函数中,前面的main函数为了简洁,我把暴力罗列那一部分的代码去掉落了。假如要看完备版的可以去看官方的代码。下面是经由过程sublist3r.py中main函数调用subbrute的代码片段。

if enable_bruteforce:

if not silent:

print(G + “[-] Starting bruteforce module now using subbrute..” + W)

record_type = False

path_to_file = os.path.dirname(os.path.realpath(file))

subs = os.path.join(path_to_file, ‘subbrute’, ‘names.txt’)

resolvers = os.path.join(path_to_file, ‘subbrute’, ‘resolvers.txt’)

process_count = threads

output = False

json_output = False

bruteforce_list = subbrute.print_target(parsed_domain.netloc, record_type, subs, resolvers, process_count, output, json_output, search_list, verbose)

subdomains = search_list.union(bruteforce_list)

你可以看到,先经由过程enable_bruteforce变量判断是否要激活(默认是激活状态)暴力罗列。然后读入‘names.txt’和‘resolvers.txt’文件,终极,传给包subbrute,获得罗列后的子域名列表。并存储到bruteforce_list内。subbrute的代码大年夜家可以自己去浏览一下。然则这里可以简单的解说一下它的道理。着实相对来说也是对照简单的。暴力罗列虽然暴力,然则并不傻。不会真的从a开始不停试到zzzzz……它是采纳了把可能会应用到子域名都写出来,然后再一个一个试以前。前面代码中引入的‘names.txt’便是可能用到的子域名。不可大年夜家可以打开看看,里面包孕了大年夜部分我们日常生活中会用到的子域名组合,比如’email,www’等等。至于‘resolvers.txt’文件着实便是开放的DNS查询地址,类似于8.8.8.8这种DNS办事的聚拢。

应用ssl证书罗列子域名

现在带有的ssl证书的越来越多,假如目标站点是全站https,一样平常就只有一个证书,然后,各个子域名都包孕在证书里面。我们可以经由过程对ssl证书的解析去获取子域名数据。比如下面的这个例子:

你可以看到,这个证书揭橥给了旗下的这么多域名。然则在sublist3r中没有采纳自己下载ssl证书解析的形式,而是直接经由过程一个在线办事https://crt.sh/ 来查询,相对来说会对照方便,然则暗藏了ssl子域名罗列的技巧细节,对付想经由过程代码懂得实际道理的小伙伴来说不太友好。别的还有一个经由过程DNS罗列子域名也是经由过程在线的办事https://dnsdumpster.com来达到目的。至于dnsdumpster的底层细节我预测可能是经由过程一些我们熟知的域传送破绽,dns反查,暴力罗列等措施去实现。

总结

sublist3r代码量不多,很得当初学者进修。从写文章的角度来说,其实是难以一行一行代码展开讲。这也是我第一次写代码涉猎的文章,没有什么履历,假如大年夜家有什么建议迎接大年夜家理性的提出来。

上一页[1] [2] [3] [4]

赞(0) 打赏
分享到: 更多 (0)
免责申明:本站所有资料均来自于网络,版权归原创者所有!本站不提供任何保证,不保证真实性,并不承担任何法律责任

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

阿里云优惠网 更专业 更优惠

阿里云优惠券阿里云大礼包