黑客是如何对子域进行接管的?快去检查下你的DNS

推荐视频

我黑了这张照片……-FancyPig's blog
我黑了这张照片……-FancyPig's blog
FancyPig的头像-FancyPig's blog汉堡会员4个月前
5374311

视频讲解

很多个人、公司在弃用Github、微软Azure之后,都没有删除DNS解析,导致他人注册了相同的Github ID、使用原先的Azure dns地址,从而可以接管个人、公司真实域名,上传任意页面……

图文讲解

通常,在个人、公司删除项目时,会有下面的操作,在移除项目后,同时删除Github账户

图片[1]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

但是很多个人、公司都会忘记一个事情,那就是清理dns解析记录。如果没有清理,这会造成什么样的后果呢?

我们可以尝试创建一个相同名称的账户(目前Github做了90天的限制,如果一个账户被删除,只能90天之后才能创建相同名称的账户;微软Azure等其他存储并未进行限制,故可以在删除账户后立刻接管。)

图片[2]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

那我们这里就假设过了90天,我们创建了相同用户名的账户(因为时间原因,这里下面我们就拿hackwellgit3进行演示了)

我们先创建github账户

图片[3]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

新建仓库

图片[4]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

创建一个CNAME文件夹,并设置我们要解析的域名(我们这里是黑客身份,因为之前的个人、公司没有删除对github.io的cname解析,所以我们创建之后就可以接管他们的域名了)

图片[5]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

并在custom domain里设置我们要使用的域名,可以看到现在已经在检查DNS了

图片[6]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

DNS检查已经成功了

图片[7]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

我们现在可以创建一个html文件,然后使用这个域名访问,看下效果咯!

可以看到这里使用的是对方的域名,但实际上访问的是我们的内容,并且通常情况下,对方是不会察觉到这一点的

图片[8]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

同样道理我们也可以对微软Azure进行接管,这里不再赘述,大家可以去看原视频详细操作。

到这里你可能会问,作为渗透测试人员,我们如何快速发现可以接管的域名呢?

如何发现子域名(Amass)

我们可以使用docker快速部署

docker build -t amass https://github.com/OWASP/Amass.git

然后使用/wordlists/all.txt进行子域名探测

docker run -v OUTPUT_DIR_PATH:/.config/amass/ amass enum -brute -w /wordlists/all.txt -d example.com

通过子域名探测,我们可以找到域名的相关资产

图片[9]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

子域名接管探测

核心代码takeover.py

#!/usr/bin/env python3
# takeover - subdomain takeover finder
# coded by M'hamed (@m4ll0k) Outaadi

import os
import json
import requests
import urllib.parse
import concurrent.futures as thread
import urllib3
import getopt
import sys
import re


r = '\033[1;31m'
g = '\033[1;32m'
y = '\033[1;33m'
b = '\033[1;34m'
r_ = '\033[0;31m'
g_ = '\033[0;32m'
y_ = '\033[0;33m'
b_ = '\033[0;34m'
e = '\033[0m'

global _output
_output = []
global k_
k_ = {
    'domain': None,
    'threads': 1,
    'd_list': None,
    'proxy': None,
    'output': None,
    'timeout': None,
    'process': False,
    'user_agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.36 Safari/537.36',
    'verbose': False,
    'dict_len': 0
}

# index/lenght * 100


def PERCENT(x, y): return float(x)/float(y) * 100


services = {
    'AWS/S3': {'error': r'The specified bucket does not exist'},
    'BitBucket': {'error': r'Repository not found'},
    'Github': {'error': r'There isn\\\'t a Github Pages site here\.'},
    'Shopify': {'error': r'Sorry\, this shop is currently unavailable\.'},
    'Fastly': {'error': r'Fastly error\: unknown domain\:'},

    'Ghost': {'error': r'The thing you were looking for is no longer here\, or never was'},
    'Heroku': {'error': r'no-such-app.html|<title>no such app</title>|herokucdn.com/error-pages/no-such-app.html'},
    'Pantheon': {'error': r'The gods are wise, but do not know of the site which you seek.'},
    'Tumbler': {'error': r'Whatever you were looking for doesn\\\'t currently exist at this address.'},
    'Wordpress': {'error': r'Do you want to register'},

    'TeamWork': {'error': r'Oops - We didn\'t find your site.'},
    'Helpjuice': {'error': r'We could not find what you\'re looking for.'},
    'Helpscout': {'error': r'No settings were found for this company:'},
    'Cargo': {'error': r'<title>404 — File not found</title>'},
    'Uservoice': {'error': r'This UserVoice subdomain is currently available!'},
    'Surge': {'error': r'project not found'},
    'Intercom': {'error': r'This page is reserved for artistic dogs\.|Uh oh\. That page doesn\'t exist</h1>'},

    'Webflow': {'error': r'<p class=\"description\">The page you are looking for doesn\'t exist or has been moved.</p>'},
    'Kajabi': {'error': r'<h1>The page you were looking for doesn\'t exist.</h1>'},
    'Thinkific': {'error': r'You may have mistyped the address or the page may have moved.'},
    'Tave': {'error': r'<h1>Error 404: Page Not Found</h1>'},

    'Wishpond': {'error': r'<h1>https://www.wishpond.com/404?campaign=true'},
    'Aftership': {'error': r'Oops.</h2><p class=\"text-muted text-tight\">The page you\'re looking for doesn\'t exist.'},
    'Aha': {'error': r'There is no portal here \.\.\. sending you back to Aha!'},
    'Tictail': {'error': r'to target URL: <a href=\"https://tictail.com|Start selling on Tictail.'},
    'Brightcove': {'error': r'<p class=\"bc-gallery-error-code\">Error Code: 404</p>'},
    'Bigcartel': {'error': r'<h1>Oops! We couldn’t find that page.</h1>'},
    'ActiveCampaign': {'error': r'alt=\"LIGHTTPD - fly light.\"'},

    'Campaignmonitor': {'error': r'Double check the URL or <a href=\"mailto:help@createsend.com'},
    'Acquia': {'error': r'The site you are looking for could not be found.|If you are an Acquia Cloud customer and expect to see your site at this address'},
    'Proposify': {'error': r'If you need immediate assistance, please contact <a href=\"mailto:support@proposify.biz'},
    'Simplebooklet': {'error': r'We can\'t find this <a href=\"https://simplebooklet.com'},
    'GetResponse': {'error': r'With GetResponse Landing Pages, lead generation has never been easier'},
    'Vend': {'error': r'Looks like you\'ve traveled too far into cyberspace.'},
    'Jetbrains': {'error': r'is not a registered InCloud YouTrack.'},

    'Smartling': {'error': r'Domain is not configured'},
    'Pingdom': {'error': r'pingdom'},
    'Tilda': {'error': r'Domain has been assigned'},
    'Surveygizmo': {'error': r'data-html-name'},
    'Mashery': {'error': r'Unrecognized domain <strong>'},
    'Divio': {'error': r'Application not responding'},
    'feedpress': {'error': r'The feed has not been found.'},
    'readme': {'error': r'Project doesnt exist... yet!'},
    'statuspage': {'error': r'You are being <a href=\'https>'},
    'zendesk': {'error': r'Help Center Closed'},
    'worksites.net': {'error': r'Hello! Sorry, but the webs>'}
}


def plus(string):
    print('{0}[ + ]{1} {2}'.format(g, e, string))


def warn(string, exit=not 1):
    print('{0}[ ! ]{1} {2}'.format(r, e, string))
    if exit:
        sys.exit()


def info(string):
    print('{0}[ i ]{1} {2}'.format(y, e, string))


def _info():
    return '{0}[ i ]{1} '.format(y, e)


def err(string):
    print(r'  |= [REGEX]: {0}{1}{2}'.format(y_, string, e))


def request(domain, proxy, timeout, user_agent):
    url = checkurl(domain)
    timeout = timeout
    proxies = {
        'http': proxy,
        'https': proxy
    }
    redirect = True
    headers = {
        'User-Agent': user_agent
    }
    try:
        req = requests.packages.urllib3.disable_warnings(
            urllib3.exceptions.InsecureRequestWarning
        )
        req = requests.get(
            url=url,
            headers=headers,
            verify=False,
            allow_redirects=redirect,
            timeout=int(timeout) if timeout != None else None,
            proxies=proxies
        )
        return req.status_code, req.content
    except Exception as err:
        if k_.get('d_list'):
            print("")
            warn('Failed to establish a new connection for: %s' % (domain), 1)
        else:
            warn('Failed to establish a new connection for: %s' % (domain), 1)


def find(status, content, ok):
    for service in services:
        for values in services[service].items():
            if re.findall(str(values[1]), str(content), re.I) and int(status) in range(201 if ok is False else 200, 599):
                return str(service), str(values[1])


def banner():
    print("\n   /~\\")
    print("  C oo   ---------------")
    print(" _( ^)  |T|A|K|E|O|V|E|R|")
    print("/   ~\\  ----------------")
    print("#> by M'hamed (@m4ll0k) Outaadi")
    print("#> http://github.com/m4ll0k")
    print("-"*40)


def help(_exit_=False):
    banner()
    print("Usage: %s [OPTION]\n" % sys.argv[0])
    print("\t-d\tSet domain URL (e.g: www.test.com)")
    print("\t-t\tSet threads, default 1")
    print("\t-l\tScan multiple targets in a text file")
    print("\t-p\tUse a proxy to connect the target URL")
    print("\t-o\tUse this settings for save a file, args=json or text")
    print("\t-T\tSet a request timeout,default value is 20 seconds")
    print("\t-k\tProcess 200 http code, cause more false positive")
    print("\t-u\tSet custom user agent (e.g: takeover-bot)")
    print("\t-v\tVerbose, print more info\n")
    if _exit_:
        sys.exit()


def checkpath(path):
    if os.path.exists(path):
        return path
    elif os.path.isdir(path):
        warn('"%s" is directory!', 1)
    elif os.path.exists(path) is False:
        warn('"%s" not exists!' % path, 1)
    else:
        warn('Error in: "%s"' % path, 1)


def readfile(path):
    info('Read wordlist.. "%s"' % path)
    return [x.strip() for x in open(checkpath(path), 'r')]


def checkurl(url):
    o = urllib.parse.urlsplit(url)
    if o.scheme not in ['http', 'https', '']:
        warn('Scheme "%s" not supported!' % o.scheme, 1)
    if o.netloc == '':
        return 'http://' + o.path
    elif o.netloc:
        return o.scheme + '://' + o.netloc
    else:
        return 'http://' + o.netloc


def print_(string):
    sys.stdout.write('\033[1K')
    sys.stdout.write('\033[0G')
    sys.stdout.write(string)
    sys.stdout.flush()


def runner(k):
    threadpool = thread.ThreadPoolExecutor(max_workers=k.get('threads'))
    if k.get('verbose'):
        info('Set %s threads..' % k.get('threads'))
    futures = (threadpool.submit(requester, domain, k.get("proxy"), k.get("timeout"), k.get("user_agent"),
                                 k.get("output"), k.get('process'), k.get('verbose')) for domain in k.get("domains"))
    for i, results in enumerate(thread.as_completed(futures)):
        if k.get('verbose') and k.get('d_list'):
            str_ = "{i}{b:.2f}% Domain: {d}".format(
                i=_info(),
                b=PERCENT(int(i),
                          int(k.get('dict_len'))), d=k.get('domains')[i]
            )
            print_(str_)
        else:
            info('Domain: {}'.format(k.get('domains')[i]))
        pass


def requester(domain, proxy, timeout, user_agent, output, ok, v):
    code, html = request(domain, proxy, timeout, user_agent)
    service, error = find(code, html, ok)
    if service and error:
        if output:
            _output.append((domain, service, error))
            if v and not k_.get('d_list'):
                plus('%s service found! Potential domain takeover found! - %s' %
                     (service, domain))
            elif v and k_.get('d_list'):
                print("")
                plus('%s service found! Potential domain takeover found! - %s' %
                     (service, domain))
        else:
            if k_.get('d_list'):
                print("")
                plus('%s service found! Potential domain takeover found! - %s' %
                     (service, domain))
            elif not k_.get('d_list'):
                plus('%s service found! Potential domain takeover found! - %s' %
                     (service, domain))
            if v:
                err(error)


def savejson(path, content, v):
    if v and not k_.get('d_list'):
        info('Writing file..')
    elif v and k_.get('d_list'):
        print("")
        info("Writing file..")
    a = {}
    b = {"domains": {}}
    for i in content:
        a.update({i[0]: {'service': i[1], 'error': i[2]}})
    b['domains'] = a
    with open(path, 'w+') as outjsonfile:
        json.dump(b, outjsonfile, indent=4)
        outjsonfile.close()
    info('Saved at '+path+'..')


def savetxt(path, content, v):
    if v and not k_.get('d_list'):
        info('Writing file..')
    elif v and k_.get('d_list'):
        print("")
        info("Writing file..")
    br = '-'*40
    bf = '='*40
    out = ''+br+'\n'
    for i in content:
        out += 'Domain\t: %s\n' % i[0]
        out += 'Service\t: %s\n' % i[1]
        out += 'Error\t: %s\n' % i[2]
        out += ''+bf+'\n'
    out += ''+br+'\n'
    with open(path, 'w+') as outtxtfile:
        outtxtfile.write(out)
        outtxtfile.close()
    info('Saved at '+path+'..')


def main():
    # --
    if len(sys.argv) < 2:
        help(1)
    try:
        opts, args = getopt.getopt(sys.argv[1:],
                                   'd:l:p:o:t:T::u:kv',
                                   ['d=', 'l=', 'p=', 'v', 'o=', 't=', 'T=', 'u=', 'k'])
    except Exception as e:
        warn(e, 1)
    for o, a in opts:
        if o == '-d':
            k_['domain'] = a
        if o == '-t':
            k_['threads'] = int(a)
        if o == '-l':
            k_['d_list'] = a
        if o == '-p':
            k_['proxy'] = a
        if o == '-o':
            k_['output'] = a
        if o == '-T':
            k_['timeout'] = int(a)
        if o == '-k':
            k_['process'] = True
        if o == '-u':
            k_['user_agent'] = a
        if o == '-v':
            k_['verbose'] = True

    if k_.get("domain") or k_.get("d_list"):
        banner()
        domains = []
        if k_.get('verbose'):
            info('Starting..')

        if k_.get("d_list"):
            domains.extend(readfile(k_.get("d_list")))
        else:
            domains.append(k_.get("domain"))
        k_['domains'] = domains
        k_['dict_len'] = len(domains)
        runner(k_)
        if k_.get("output"):
            if '.txt' in k_.get('output'):
                savetxt(k_.get('output'), _output, k_.get('verbose'))
            elif '.json' in k_.get('output'):
                savejson(k_.get('output'), _output, k_.get('verbose'))
            else:
                warn('Output Error: %s extension not supported, only .txt or .json' % k_.get(
                    'output').split('.')[1], 1)
    elif k_.get('domain') is None and k_.get('d_list') is None:
        help(1)


if __name__ == '__main__':
    try:
        main()
    except (KeyboardInterrupt) as e:
        sys.exit(0)

首先我们将我们要扫描的域名存在targets.txt中

git1.hackwell.industries.com
code.hackwell.industries.com
git.hackwell.industries.com

然后我们可以输入相应的命令

python3 takeover.py -l targets.txt -v

可以看到存在可以接管的github服务

图片[10]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

我们为了验证相关域名是否可以接管,可以使用dig命令查看解析情况

图片[11]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

可以看到确实存在CNAME解析到github.io

故如果hackwellgit2账户如果被删除,但是解析没有清理掉,我们就可以注册并使用这个用户名,最终接管左侧的code.hackwellindustries.com真实域名

图片[12]-黑客是如何对子域进行接管的?快去检查下你的DNS-FancyPig's blog

© 版权声明
THE END
喜欢就支持一下吧
点赞15赞赏 分享
评论 共5条

请登录后发表评论