用户
搜索
  • TA的每日心情
    无聊
    2021-3-16 19:57
  • 签到天数: 8 天

    连续签到: 1 天

    [LV.3]经常看看I

    i春秋作家

    Rank: 7Rank: 7Rank: 7

    4

    主题

    16

    帖子

    279

    魔法币
    收听
    0
    粉丝
    1
    注册时间
    2019-10-8

    i春秋签约作者

    发表于 2021-12-2 15:40:57 22006
    本帖最后由 fatmo 于 2021-12-2 23:44 编辑

    本文原创作者fatmo,本文属i春秋原创奖励计划,未经许可禁止转载。
    [i]本文参考了github项目:https://github.com/timwhitez/Frog-checkCDN[/i]

    往期回顾:
    SRC信息收集学习与自动化(一):开发基础知识巩固

    0x01 CDN简介

    CDN,全称Content Delivery Network,即内容分发网络。是一种新型的网络构建方式。

    大部分CDN用于为用户访问服务加速,降低DDOS攻击的影响。CDN服务器会对服务本身做缓存,当用户访问服务时,会自动寻找距离最近(网络意义)的CDN服务器,从该服务器中请求数据,从而实现访问加速的功能,缓解了网络压力。

    本文代码已上传github:https://github.com/fatmo666/InfoScripts/blob/master/CDNCheck.py

    相关配置文件已上传github:https://github.com/fatmo666/InfoScripts/tree/master/Config/CDN

    0x02 探测和绕过CDN的意义

    在进行渗透测试时,我们往往需要获得目标主机的ip地址。以域名'www.xxx.com'为例,若该目标未部署CDN我们直接ping www.xxx.com,即可获得网站的真实ip;若该目标部署了CDN,则我们ping www.xxx.com,只能得到CDN服务器的ip地址,而非目标主机的地址。

    0x03 探测目标CDN的方法

    1.多地ping/超级ping

    最常见的方法是使用多地ping(又称超级ping),使用位于各地不同的服务器去ping目标主机,若发现处于不同地区的主机ping出的ip地址不同,则表示目标主机使用了cdn。

    我们可以在这个网站进行多地Ping:https://ping.chinaz.com/。如图,在输入框内输入目标域名,再点击Ping检测即可。

    CDN01.png

    01 我们测试域名chinaz.com,发现结果指向同一个ip地址,表明未使用CDN

    CDN02.png

    02 我们再测试taobao.com,出现了不同的ip地址,表明使用了CDN

    CDN03.png

    此方法若编程自动化实现,要么需要自己准备多地的主机,要么需要爬取别人的接口,可能涉及违规,因此此方法不编程自动化实现。

    2.根据http头信息判断

    注意观察存在CDN目标的网站,其HTTP返回包头中有没有存在CDN特征的信息,也可积累出一个通过http头判断目标CDN的文件。

    收集的http头信息如下:

    headers = [
        'via',
        'x-via',
        'by-360wzb',
        'by-anquanbao',
        'cc_cache',
        'cdn cache server',
        'cf-ray',
        'chinacache',
        'verycdn',
        'webcache',
        'x-cacheable',
        'x-fastly',
        'yunjiasu',
        'x-cdn-provider'
    ]

    01 以网站zhuanlan.zhihu.com为例,我们先多地ping,出现多个ip地址,可判断存在CDN

    CDN04.png

    2.抓流量看看其HTTP返回包,发现特征信息。之后,我们就可以判断HTTP返回包是否有'x-cdn-procider'来判断网站是否存在CDN:

    CDN05.png

    编程实现方法较简单,只需要发出HTTP请求,检查其返回包中的是否有我们字典中的key即可:

        async def checkHeader(self, domain):
            """
            检查HTTP请求头
            :param domain:需请求的域名
            :return:共两个返回值:
            1. 检查结果,True/False
            2. 若为True,命中的那个key
            """
            sem = asyncio.Semaphore(1024)
            try:
                async with aiohttp.ClientSession(connector=aiohttp.TCPConnector()) as session:
                    async with sem:
                        async with session.get(domain, timeout=20, headers=self.headers) as req:
                            await asyncio.sleep(1)
                            # 遍历字典
                            for header in headers:
                                if header in dict(req.headers).keys():
                                    return True, header
                            req.close()
            except CancelledError:
                pass
            except ConnectionResetError:
                pass
            except Exception as e:
                self.logger.error('[-]CDNCheck-Check header: Resolve {} fail'.format(domain))
    
            return False, ""

    3.根据cname记录探测CDN

    域名的cname记录也会暴露CDN信息,我们以网站www.taobao.com为例,查询一下此域名的cname记录,很明显其使用了淘宝自己的CDN:

    CDN06.png

    所以我们可以收集一些CDN的特征,形成自己的探测字典(篇幅太长只取部分,文末github有完整字典):

    cnames = {
        'tbcache.com': u'taobao',  # 应该是淘宝自己的。。。。
        'tcdn.qq.com': u'tcdn.qq.com',  # 应该是腾讯的。。。
        '00cdn.com': u'XYcdn',  # 星域cdn
        '21cvcdn.com': u'21Vianet',  # 世纪互联
        '21okglb.cn': u'21Vianet',  # 世纪互联
        '21speedcdn.com': u'21Vianet',  # 世纪互联
        '21vianet.com.cn': u'21Vianet',  # 世纪互联
        '21vokglb.cn': u'21Vianet',  # 世纪互联
        '360wzb.com': u'360',  # 360网站卫士
        '51cdn.com': u'ChinaCache',  # 网宿科技
        'acadn.com': u'Dnion',  # 帝联科技
        'aicdn.com': u'UPYUN',  # 又拍云
        'akadns.net': u'Akamai',  # Akamai
        'akamai-staging.net': u'Akamai',  # Akamai
        'akamai.com': u'Akamai',  # Akamai
        'akamai.net': u'Akamai',  # Akamai
        'akamaitech.net': u'Akamai',  # 易通锐进
        'akamaized.net': u'Akamai',  # Akamai
        'alicloudlayer.com': u'ALiyun',  # 阿里云
        'alikunlun.com': u'ALiyun',  # 阿里云
        'aliyun-inc.com': u'ALiyun',  # 阿里云
        'aliyuncs.com': u'ALiyun',  # 阿里云
        'amazonaws.com': u'Amazon Cloudfront',  # 亚马逊
        'anankecdn.com.br': u'Ananke',  # Ananke
        ……

    编写代码时,我们需要循环进行cname解析,知道解析结束为止:

    先写一个异步cname解析的方法:

        async def getCNAME(self, domain, resolver):
            """
            异步cname解析
            :param domain:要解析的域名
            :param resolver:异步DNS解析对象
            :return:解析结果
            """
            try:
                answer = await resolver.query(domain, 'CNAME')
            except:
                return None
            cname = answer.cname
            return cname

    然后写一个循环解析的方法,调用我们的getCNAME()方法:

        async def getCNAMES(self, domain, resolver):
            """
            根据DNS,CNAME解析结果判断,循环解析cname并推入列表
            :param domain:须解析的域名
            :param resolver:异步DNS解析对象
            :return:解析出的别名
            """
            cnames = []
            cname = await self.getCNAME(domain, resolver)
            if cname is not None:
                cnames.append(cname)
            while (cname != None):
                cname = await self.getCNAME(cname, resolver)
                if cname is not None:
                    cnames.append(cname)
            return cnames

    我们在调用方法时,需要先判断传入的是IP地址还是域名,然后在取getCNAMES()方法返回的别名列表,与我们的字典一一匹配:

            if not re.search(r'\d+\.\d+\.\d+\.\d+', domain):
                cnameList = await self.getCNAMES(domain, resolver)
                match = False
                result = None
                for i in cnameList:
                    match, result = self.matched(i, cnames)
                    if match == True:
                        break
                if match == True:
                    self.queryResult[domain]['isCdn'] = "1"
                    self.queryResult[domain]['cname'] = result
        def matched(self, obj, list):
            # print(obj)
            for i in list:
                if i in obj:
                    return True, list[i]
            return False, None

    4.根据IP段探测CDN

    通过收集CDN服务器所在的IP段,根据IP段判断是否为CDN服务器。

    收集的IP段如下(列表太长只取开头,文末github有完整列表):

    segments = [
            '223.99.255.0/24',
        '71.152.0.0/17',
        '219.153.73.0/24',
        '125.39.46.0/24',
        '190.93.240.0/20',
        '14.0.113.0/24',
        '14.0.47.0/24',
        '113.20.148.0/22',
        '103.75.201.0/24',
        '1.32.239.0/24',
        '101.79.239.0/24',
        '52.46.0.0/18',
        '125.88.189.0/24',
        '150.138.248.0/24',
        '180.153.235.0/24',
        '205.251.252.0/23',

    代码较为简单,先异步进行DNS的A类型解析出IP地址列表(ip地址可能有多个),然后判断IP地址是否在这些代码段中:

                #尝试获取IP地址
            if not re.search(r'\d+\.\d+\.\d+\.\d+', domain):
                isCDNByAddr, ipList = await self.getIP(domain, resolver)
            else:
                ipList = list(domain)
            if ipList is None:
                return
        def checkSegment(self, ipList):
            """
            根据Segment判断CDN
            :param ipList:
            :return:
            """
            try:
                for ip in ipList:
                    for segment in segments:
                        if ipaddress.ip_address(ip) in ipaddress.ip_network(segment):
                            return True, segment
                return False, None
            except:
                return False, None

    5.根据IP的ASN信息判断

    同样收集CDN服务器所在的ASN号进行判断

    收集的ASN号如下:

    ASNS = [
        '10576', '10762', '11748', '131099', '132601', '133496', '134409', '135295', '136764', '137187', '13777', '13890',
        '14103', '14520', '17132', '199251', '200013', '200325', '200856', '201263', '202294', '203075', '203139', '204248',
        '204286', '204545', '206227', '206734', '206848', '206986', '207158', '208559', '209403', '21030', '21257', '23327',
        '23393', '23637', '23794', '24997', '26492', '268843', '28709', '29264', '30282', '30637', '328126', '36408',
        '38107', '397192', '40366', '43303', '44907', '46071', '46177', '47542', '49287', '49689', '51286', '55082',
        '55254', '56636', '57363', '58127', '59730', '59776', '60068', '60626', '60922', '61107', '61159', '62026', '62229',
        '63062', '64232', '8868', '9053', '55770', '49846', '49249', '48163', '45700', '43639', '39836', '393560', '393234',
        '36183', '35994', '35993', '35204', '34850', '34164', '33905', '32787', '31377', '31110', '31109', '31108', '31107',
        '30675', '24319', '23903', '23455', '23454', '22207', '21399', '21357', '21342', '20940', '20189', '18717', '18680',
        '17334', '16702', '16625', '12222', '209101', '201585', '135429', '395747', '394536', '209242', '203898', '202623',
        '14789', '133877', '13335', '132892', '21859', '6185', '47823', '30148'
    ]

    代码较为简单,先异步进行DNS的A类型解析出IP地址列表(ip地址可能有多个),然后判断IP地址的ASN是否在这些ASN中:

            #尝试获取IP地址
            if not re.search(r'\d+\.\d+\.\d+\.\d+', domain):
                isCDNByAddr, ipList = await self.getIP(domain, resolver)
            else:
                ipList = list(domain)
            if ipList is None:
                return
        def checkASN(self, ipList):
            """
            根据ASN判断
            :param ipList:
            :return:
            """
            try:
                for ip in ipList:
                    with geoip2.database.Reader('./Config/GeoLite2-ASN.mmdb') as reader:
                        response = reader.asn(ip)
                        for i in ASNS:
                            if response.autonomous_system_number == int(i):
                                return True
            except:
                return False
            return False

    6.根据DNS,A类型解析判断

    我们直接进行DNS的A类型解析,会发现,如果网站部署了CDN,极可能会解析出多个IP地址,表示我们ping主机中最近的几个CDN服务器:

    CDN07.png

    代码直接针对域名进行DNS解析,然后看返回IP的数量即可:

    #尝试获取IP地址
    if not re.search(r'\d+\.\d+\.\d+\.\d+', domain):
        isCDNByAddr, ipList = await self.getIP(domain, resolver)
    else:
        ipList = list(domain)
    if ipList is None:
        return
    
    if isCDNByAddr != 0:
        self.queryResult[domain]['isCdn'] = "1"
        self.queryResult[domain]['ipAddr'] = isCDNByAddr
        async def getIP(self, domain, resolver):
            """
            尝试获得域名的IP地址
            :param domain:
            :return:
            """
    
            try:
                answer = await resolver.query(domain, 'A')
                ipList = []
                for ip in answer:
                    ipList.append(ip.host)
                if len(answer) > 1:
                    return len(answer), ipList
                else:
                    return 0, ipList
            except:
                return 0, None

    0x04 脚本完善

    最后我们需要补充以下结果写入方法:

        def writeResult(self):
            """
            保存结果
            :return:
            """
            with open('./CheckResult/' + self.fileName + "/" + 'isCDN' + '.txt', 'a') as fpIs:
                with open('./CheckResult/' + self.fileName + "/" + 'isNotCDN' + '.txt', 'a') as fpIsNot:
                    for domain in self.domains:
                        if self.queryResult[domain]['isCdn'] == '1':
                            fpIs.write(domain + "\n")
                        else:
                            fpIsNot.write(domain + "\n")
    
                        with open('./result/' + domain + "/" + 'cdnInfo' + '.json', 'w') as fpResult:
                            json.dump(self.queryResult[domain], fpResult, indent=2)

    将所有检测出部署CDN的目标存入/CheckResult/isCDN.txt

    将所有检测出没有部署CDN的目标存入/CheckResult/isNotCDN.txt

    见每个域名的详细检测结果存入/result/www.xxx.com/cdninfo.json

    给出两个cdninfo.json的例子:

    {
      "isCdn": "0"
    }
    {
      "isCdn": "1",
      "ipAddr": 2,
      "header": "x-via",
      "cname": "taobao"
    }

    在完善协程启动方法:

        def startQuery(self):
            try:
                tasks = []
                newLoop = asyncio.new_event_loop()
                asyncio.set_event_loop(newLoop)
                loop = asyncio.get_event_loop()
                resolver = aiodns.DNSResolver(loop=loop)
    
                for domain in self.domains:
                    if os.path.exists(os.getcwd() + '/result/' + domain + '/') is False:
                        os.mkdir(os.getcwd() + '/result/' + domain + '/')
    
                    tasks.append(asyncio.ensure_future(self.checkCDN(domain, resolver)))
    
                loop.run_until_complete(asyncio.wait(tasks))
            except KeyboardInterrupt:
                self.logger.info('[+]Break From Queue.')
            except CancelledError:
                pass
    
            self.writeResult()

    在任务跑完或意外终止时,开始写入结果。

    到此未知,CDN检测脚本大功告成!

    下一篇文章将尝试绕过CDN获得目标真实ip。[/i][i]
    完整代码见github:

    https://github.com/fatmo666/InfoScripts/blob/master/BaseObject.py

    https://github.com/fatmo666/InfoScripts/blob/master/CDNCheck.py

    https://github.com/fatmo666/InfoScripts/tree/master/Config/CDN




    学习了
    使用道具 举报 回复
    感谢楼主分享,和上篇文章的建议一样,建议不使用相对路径,例如你的'./'
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册