SHOW:
|
|
- or go back to the newest paste.
1 | #!/usr/bin/env bash | |
2 | # @author: Danilo Basanta | |
3 | # @author-linkedin: https://www.linkedin.com/in/danilobasanta/ | |
4 | # @author-github: https://github.com/dabasanta | |
5 | ||
6 | # GLOBAL VARS - DO NOT MODIFY | |
7 | export DOMAIN="None" | |
8 | export EXTENDED_CHECKS=false | |
9 | export zone_transfer="No" | |
10 | export dns_servers_count=0 | |
11 | export subdomains_count=0 | |
12 | export webservers_count=0 | |
13 | # Modify the DNS_BRUTE_THREADS variable to set the number of threads to use in the custom-dictionary attack. | |
14 | # By default, the script will use 15% of the number of records in the dictionary. | |
15 | export DNS_BRUTE_THREADS=0 | |
16 | ||
17 | # CRTL+C handler | |
18 | function scape() { | |
19 | clean | |
20 | } | |
21 | trap scape INT | |
22 | ||
23 | # Creating the tmp directory | |
24 | tmpdir="/tmp/dnsexplorer" | |
25 | mkdir -p $tmpdir | |
26 | mkdir -p $tmpdir/whatweb | |
27 | mkdir -p $tmpdir/wafw00f | |
28 | tput civis | |
29 | ||
30 | end="\e[0m" | |
31 | info="\e[36m[+]" | |
32 | cyan="\e[36m" | |
33 | output_color="\e[0m\e[36m" | |
34 | error="\e[1m\e[91m[!]" | |
35 | question="\e[93m" | |
36 | yellow="\e[1m\e[93m" | |
37 | green="\e[92m" | |
38 | ok="\e[1m\e[92m" | |
39 | resalted_output="\e[1;37m" | |
40 | ||
41 | # Clean -exit- function | |
42 | clean(){ | |
43 | echo -e "\n\n" | |
44 | rm -rf $tmpdir | |
45 | echo -e "${end}Happy hunting." | |
46 | tput cnorm | |
47 | exit 0 | |
48 | } | |
49 | ||
50 | # Custom dicctionary attack function | |
51 | dictionaryAttackCustom() { #5 | |
52 | dicc_outfile="$tmpdir/dicctionary.results.txt" | |
53 | : > "$dicc_outfile" | |
54 | check=0 | |
55 | while [ "$check" -eq 0 ]; do | |
56 | echo -e "$question" | |
57 | read -rp "Ingrese la ruta del archivo de diccionario> " dfile | |
58 | echo -e "$end" | |
59 | ||
60 | if [ ! -f "$dfile" ] || [[ $(file "$dfile" | awk '{print $2}') != @(ASCII|Unicode) ]]; then | |
61 | echo -e "$error El archivo $dfile no existe o no es un archivo de texto ASCII/Unicode." | |
62 | else | |
63 | check=1 | |
64 | fi | |
65 | done | |
66 | ||
67 | lon_dicc=$(wc -l < "$dfile") | |
68 | tput civis | |
69 | ||
70 | threads=$(echo "scale=0; ($lon_dicc * 0.15 + 0.5)/1" | bc) | |
71 | [ "$threads" -lt 1 ] && threads=1 | |
72 | [ "$threads" -gt 40 ] && threads=40 | |
73 | [ "$DNS_BRUTE_THREADS" -ne 0 ] && threads="$DNS_BRUTE_THREADS" | |
74 | echo -e " ${question}This file has $lon_dicc records, $threads parallel processes will be used to speed up the attack, press any key to start\n" | |
75 | read -n 1 -s -r -p "" | |
76 | ||
77 | grep -Eva '[^a-zA-Z0-9\-_]' < "$dfile" | xargs -P $threads -I {} sh -c ' | |
78 | GREEN="\033[32m" | |
79 | resalted_output="\e[1;37m" | |
80 | RESET="\033[0m" | |
81 | lon_dicc="$4" | |
82 | if host "$1.$2" 2>/dev/null | head -1 | grep -q "has address"; then | |
83 | echo "$1.$2" >> "$3" | |
84 | printf " [+] Found: ${resalted_output}%s.%s${RESET}\n" "$1" "$2" | |
85 | fi | |
86 | ||
87 | echo "." >> /tmp/dnsexplorer/tracker.txt | |
88 | len=$(wc -l < /tmp/dnsexplorer/tracker.txt) | |
89 | dicc=$() | |
90 | percent=$(echo "scale=2; ($len / $lon_dicc) * 100" | bc) | |
91 | printf "${GREEN}[%.0f%%] Reading file...${RESET}\r" $percent | |
92 | ' _ {} "$DOMAIN" "$dicc_outfile" "$lon_dicc" | |
93 | ||
94 | total=$(wc -l < $dicc_outfile) | |
95 | echo "" | |
96 | echo -e "${info} $total Subdomains found.${end}" | |
97 | echo "" | |
98 | crtSH "dicattack" | |
99 | } | |
100 | ||
101 | # Dictionary attack function | |
102 | dictionaryAttack(){ #5 | |
103 | tput civis | |
104 | bitquark="$tmpdir/bit.txt" | |
105 | dicc_outfile="$tmpdir/dicctionary.results.txt" | |
106 | echo "" > $dicc_outfile | |
107 | echo -e "\n${info}Using SECLISTS: bitquark-subdomains-top100000.txt${end}\n" | |
108 | curl -s https://raw.githubusercontent.com/danielmiessler/SecLists/master/Discovery/DNS/bitquark-subdomains-top100000.txt -o $bitquark | |
109 | l_bitq=$(cat $bitquark | wc -l) | |
110 | touch $tmpdir/tracker.txt | |
111 | if [ $l_bitq -gt 999 ];then | |
112 | grep -v '^ *#' < "$bitquark" | xargs -P 40 -I {} sh -c ' | |
113 | GREEN="\033[32m" | |
114 | resalted_output="\e[1;37m" | |
115 | RESET="\033[0m" | |
116 | ||
117 | if host "$1.$2" | head -1 | grep -q "has address"; then | |
118 | echo "$1.$2" >> "$3" | |
119 | printf " [+] Found: ${resalted_output}%s.%s${RESET}\n" "$1" "$2" | |
120 | fi | |
121 | ||
122 | echo "." >> /tmp/dnsexplorer/tracker.txt | |
123 | len=$(wc -l < /tmp/dnsexplorer/tracker.txt) | |
124 | percent=$(echo "scale=2; ($len / 100000) * 100" | bc) | |
125 | printf "${GREEN}[%.0f%%] Reading file...${RESET}\r" $percent | |
126 | ' _ {} "$DOMAIN" "$dicc_outfile" | |
127 | total=$(wc -l < $dicc_outfile) | |
128 | echo "" | |
129 | echo -e "${info} $total Subdomains found.${end}" | |
130 | echo "" | |
131 | crtSH "dicattack" | |
132 | else | |
133 | echo -e "$error Could not download dictionary from seclists url.$end" | |
134 | dictionaryAttackCustom | |
135 | fi | |
136 | } | |
137 | ||
138 | # DNS Brute Force handler function - Trigger the dictorinary attack | |
139 | bruteForceDNS(){ | |
140 | echo -e " | |
141 | ██████╗ ██╗ ██████╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗ █████╗ ██████╗ ██╗ ██╗ | |
142 | ██╔══██╗██║██╔════╝██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔══██╗██╔══██╗╚██╗ ██╔╝ | |
143 | ██║ ██║██║██║ ██║ ██║ ██║██║ ██║██╔██╗ ██║███████║██████╔╝ ╚████╔╝ | |
144 | ██║ ██║██║██║ ██║ ██║ ██║██║ ██║██║╚██╗██║██╔══██║██╔══██╗ ╚██╔╝ | |
145 | ██████╔╝██║╚██████╗╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║██║ ██║██║ ██║ ██║ | |
146 | ╚═════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ | |
147 | ||
148 | █████╗ ████████╗████████╗ █████╗ ██████╗██╗ ██╗ | |
149 | ██╔══██╗╚══██╔══╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝ | |
150 | ███████║ ██║ ██║ ███████║██║ █████╔╝ | |
151 | ██╔══██║ ██║ ██║ ██╔══██║██║ ██╔═██╗ | |
152 | ██║ ██║ ██║ ██║ ██║ ██║╚██████╗██║ ██╗ | |
153 | ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ | |
154 | ||
155 | \n\t\t$output_color Fuzzing subdomains of $1[++]$end\n | |
156 | ${question}Do yo want to use a custom dictionary? [c=custom/d=default]$end | |
157 | $info Default: Provides a dictionary with the top 1000 of the most commonly used subdomains. | |
158 | $info Custom: Use your own custom dictionary.$question\n" | |
159 | ||
160 | while true; do | |
161 | read -rp "[d/c]> " dc | |
162 | case $dc in | |
163 | [Dd]* ) dictionaryAttack; break;; | |
164 | [Cc]* ) dictionaryAttackCustom; break;; | |
165 | * ) echo -e "$error Please answer$green D$end \e[1m\e[91mor$end$green\e[1m C$end\e[1m\e[91m.$end\n";; | |
166 | esac | |
167 | done | |
168 | } | |
169 | ||
170 | # Check if the subdomain has webserver | |
171 | check_web_server() { #8 | |
172 | local end="\e[0m" | |
173 | local cyan="\e[1m\e[36m" | |
174 | local ok="\e[1m\e[92m" | |
175 | local resalted_output="\e[1;37m" | |
176 | local -r webservers_outfile=$(mktemp /tmp/dnsexplorer/XXXX.webservers.txt) | |
177 | ||
178 | local response=$(curl -m 3 --head --silent --output /dev/null --write-out '%{http_code}' "http://${1}") | |
179 | if ((response >= 100 && response <= 599)); then | |
180 | [ "$protocol" == "https" ] && secure=" secure" | |
181 | echo -e "${end}The domain ${resalted_output}$1${end} has a web server. [${cyan}HTTP${end}:$ok$response$end]" | |
182 | echo "http://$1" >> "$webservers_outfile" | |
183 | fi | |
184 | ||
185 | local response=$(curl -m 3 --head --silent --output /dev/null --write-out '%{http_code}' "https://${1}") | |
186 | if ((response >= 100 && response <= 599)); then | |
187 | [ "$protocol" == "https" ] && secure=" secure" | |
188 | echo -e "${end}The domain $resalted_output$1$end has a secure web server. [${cyan}HTTPS${end}:$ok$response$end]" | |
189 | echo "https://$1" >> "$webservers_outfile" | |
190 | fi | |
191 | } | |
192 | export -f check_web_server | |
193 | ||
194 | # Abuse of crt.sh website - Internet connection required. | |
195 | crtSH(){ #7 | |
196 | declare -r call_source="$1" | |
197 | ||
198 | # Make sure the function is called from another function | |
199 | if [ -z "$call_source" ]; then | |
200 | echo -e "$error The function 'crtSH' must be called from another function.$end" | |
201 | return 1 | |
202 | fi | |
203 | ||
204 | crtshoutput="$tmpdir/crtsh-output.txt" # crt.sh RAW output | |
205 | crtsh_parsed_output="$tmpdir/crt.sh.reg" # crt.sh parsed output | |
206 | subdomain_file="$output/$DOMAIN.subdomains.txt" # Subdomains results without wildcard | |
207 | subdomain_wildcard_file="$output/$DOMAIN.wildcard.txt" # Subdomains results with wildcard | |
208 | final_outputfile="$output/$DOMAIN-all.txt" # Final output subdomain file | |
209 | webservers_outfile="$output/$DOMAIN.webservers" # Final output subdomain file | |
210 | SANs_tmp_file=$(mktemp $tmpdir/XXX.SAN.tmp) # Final output SAN certificates file | |
211 | new_SANs="$tmpdir/found_new_SAN.tmp" | |
212 | ||
213 | if [ "$call_source" == "dicattack" ];then | |
214 | previus_result="$tmpdir/dicctionary.results.txt" # Dictionary attack results | |
215 | fi | |
216 | if [ "$call_source" == "zonetransfer" ];then | |
217 | previus_result="$output/$DOMAIN.zoneTransfer.txt" # Dictionary attack results | |
218 | #zonetransfer_tmp_data=$(cat "$previus_result"| grep -E "([a-zA-Z0-9.-]+)\.$DOMAIN.*" | awk '{print $1}' | sort -u | grep -v '_' | sed 's/\.$//') | |
219 | cat "$previus_result" | grep -E "([a-zA-Z0-9.-]+)\.$DOMAIN.*" | awk '{print $1}' | sort -u | grep -v '_' | sed 's/\.$//' > $tmpdir/zonetransfer.results.txt | |
220 | zonetransfer_tmp_data="$tmpdir/zonetransfer.results.txt" | |
221 | fi | |
222 | if [ "$call_source" == "None" ];then | |
223 | previus_result="None" # Dictionary attack results | |
224 | fi | |
225 | ||
226 | echo "" > $subdomain_file && echo "" > $subdomain_wildcard_file && echo "" > $SANs_tmp_file && echo "" > $new_SANs | |
227 | echo -e "\n$info Finding subdomains - abusing Certificate Transparency Logs using https://crt.sh/\n$end" | |
228 | ||
229 | max_retries=3 | |
230 | retry_count=0 | |
231 | crtsh_susscess=1 | |
232 | ||
233 | while [ $retry_count -lt $max_retries ]; do | |
234 | # Make sure we have a valid response | |
235 | crtsh_response=$(curl -s -w "%{size_download} %{http_code}" "https://crt.sh/?q=${DOMAIN}&output=json" -o $crtshoutput) | |
236 | crtsh_response_size=$(echo $crtsh_response | awk '{print $1}') | |
237 | crtsh_response_code=$(echo $crtsh_response | awk '{print $2}') | |
238 | ||
239 | if [ "$crtsh_response_size" -gt 2 ] && [ "$crtsh_response_code" -eq 200 ]; then | |
240 | real_response_size=$(wc -c < $crtshoutput) | |
241 | ||
242 | if [ "$real_response_size" -gt 2 ]; then | |
243 | ||
244 | ### INTEGRAR VALIDACION DE SI EL ATAQUE DE DICCIONARIO SE LLEVO A CABO | |
245 | ||
246 | ||
247 | grep -o '"common_name":"[^"]*' $crtshoutput | awk -F ':"' '{ print $2 }' | sort -u > $crtsh_parsed_output | |
248 | size_crtsh_output=$(wc -l < $crtsh_parsed_output) | |
249 | ||
250 | # Filter and sort subdomains only once | |
251 | sorted_subdomains=$(sort -u $crtsh_parsed_output) | |
252 | ||
253 | # Check if there are wildcard subdomains | |
254 | if echo "$sorted_subdomains" | grep -q '^\*\.'; then | |
255 | # Calculate the size of the output list - with wildcard | |
256 | crtsh_parsed_output_wildcard_size=$(echo "$sorted_subdomains" | grep '^\*\.' | wc -l) | |
257 | ||
258 | # Calculate the size of the output list - without wildcard | |
259 | crtsh_parsed_output_no_wildcard_size=$(echo "$sorted_subdomains" | grep -v '^\*\.' | wc -l) | |
260 | ||
261 | # Save subdomains without wildcard | |
262 | echo "$sorted_subdomains" | sed 's/^\*\.//g' > $subdomain_file | |
263 | ||
264 | # Save subdomains with wildcard | |
265 | echo "$sorted_subdomains" | grep '^\*\.' > $subdomain_wildcard_file | |
266 | ||
267 | # Print results | |
268 | echo -e "$ok[$resalted_output$crtsh_parsed_output_no_wildcard_size$ok] subdomains found" | |
269 | echo -e "$ok[$resalted_output$crtsh_parsed_output_wildcard_size$ok] wildcard subdomains found" | |
270 | echo -e "$ok[$resalted_output$size_crtsh_output$ok] total subdomains" | |
271 | crtsh_susscess=0 | |
272 | else | |
273 | # Save subdomains without wildcard | |
274 | echo "$sorted_subdomains" > $subdomain_file | |
275 | ||
276 | # Print results | |
277 | echo -e "$info CRTsh results\n[$size_crtsh_output] total subdomains found" | |
278 | crtsh_susscess=0 | |
279 | fi | |
280 | ||
281 | if [ "$call_source" == "zonetransfer" ]; then | |
282 | cat "$zonetransfer_tmp_data" "$subdomain_wildcard_file" "$subdomain_file" | sed '/^$/d' | sed 's/*\.//g' | sort -u > "$final_outputfile" | |
283 | elif [ "$call_source" == "dicattack" ]; then | |
284 | cat "$previus_result" "$subdomain_wildcard_file" "$subdomain_file" | sed '/^$/d' | sed 's/*\.//g' | sort -u > "$final_outputfile" | |
285 | else | |
286 | cat "$subdomain_wildcard_file" "$subdomain_file" | sed '/^$/d' | sed 's/*\.//g' | sort -u > "$final_outputfile" | |
287 | fi | |
288 | ||
289 | # Calculate the number of threads to use | |
290 | count_domains=$(wc -l < "$final_outputfile") | |
291 | threads=$(echo "scale=0; ($count_domains * 0.15 + 0.5)/1" | bc) | |
292 | [ "$threads" -lt 1 ] && threads=1 | |
293 | [ "$threads" -gt 25 ] && threads=25 | |
294 | ||
295 | # Print info | |
296 | echo -e "$info Loaded $count_domains targets...$end\n\n" | |
297 | ||
298 | # Execute the check_web_server function in parallel | |
299 | sort -u "$final_outputfile" | parallel -j "$threads" check_web_server | |
300 | ||
301 | break | |
302 | else | |
303 | echo -e "$error Unable to connect to CTR.sh$end" | |
304 | retry_count=$((retry_count + 1)) | |
305 | fi | |
306 | else | |
307 | echo -e "$error Unable to connect to CTR.sh$end" | |
308 | retry_count=$((retry_count + 1)) | |
309 | fi | |
310 | done | |
311 | ||
312 | # if we have reached the max number of retries | |
313 | if [ "$max_retries" -eq "$retry_count" ]; then | |
314 | ||
315 | # if the previus_result variable is empty and the crtsh was not success | |
316 | if [[ "$call_source" == "None" && "$crtsh_susscess" -eq 1 ]]; then | |
317 | ||
318 | # No previus result and crtsh was not success, try to enumerate the main domain | |
319 | echo -e "$error It was not possible to find subdomains using any conventional enumeration method. Running the extra mile......$end" | |
320 | check_web_server "$DOMAIN" | |
321 | cat /tmp/dnsexplorer/*.webservers.txt > "$webservers_outfile" | |
322 | webservers_count=$(wc -l < $webservers_outfile) | |
323 | ||
324 | # Check if there are webservers | |
325 | if [ "$webservers_count" -gt 0 ]; then | |
326 | echo -e "$ok[$resalted_output$webservers_count$ok] webservers found" | |
327 | protocol=$(grep "https://" "$webservers_outfile" | head -1 | awk -F'://' '{print $1}') | |
328 | ||
329 | # Check if there are https webservers | |
330 | if [ "$protocol" == "https" ]; then | |
331 | echo -e "$info Checking for SANs in the certificate$end" | |
332 | echo "" > "$SANs_tmp_file" | |
333 | grep "https://" "$webservers_outfile" | xargs -I {} -P 1 -n 1 bash -c 'checkCertificateSubjectsAlternativeNames "$1" "$2" "$3"' _ {} 443 "$SANs_tmp_file" | |
334 | ||
335 | # Check if there are http webservers | |
336 | elif [ "$protocol" == "http" ]; then | |
337 | if $EXTENDED_CHECKS;then | |
338 | local -r webEnumOutputCSV="$output/$DOMAIN.webenum.csv" | |
339 | echo "URL,HTTPServer,IP,PoweredBy,X-Powered-By,Country,WAF" > "$webEnumOutputCSV" | |
340 | ||
341 | count_webservers=$(wc -l < "$webservers_outfile") | |
342 | threads_webenum=$(echo "scale=0; ($count_webservers * 0.15 + 0.5)/1" | bc) | |
343 | [ "$threads_webenum" -lt 1 ] && threads=1 | |
344 | [ "$threads_webenum" -gt 25 ] && threads=25 | |
345 | ||
346 | sort -u "$webservers_outfile" | parallel -j "$threads_webenum" webEnum | |
347 | cat $tmpdir/whatweb/*.csv >> "$webEnumOutputCSV" | |
348 | ||
349 | printResults 0.05 | |
350 | # FIN | |
351 | fi | |
352 | fi | |
353 | else | |
354 | echo -e "$error No webservers found$end" | |
355 | printResults 0.05 | |
356 | clean | |
357 | # FIN | |
358 | fi | |
359 | else | |
360 | ||
361 | # Check if the previus result is not empty | |
362 | if [ "$call_source" == "zonetransfer" ];then | |
363 | echo "$zonetransfer_tmp_data" > "$final_outputfile" | |
364 | count_domains=$(wc -l < "$final_outputfile") | |
365 | threads=$(echo "scale=0; ($count_domains * 0.15 + 0.5)/1" | bc) | |
366 | [ "$threads" -lt 1 ] && threads=1 | |
367 | [ "$threads" -gt 25 ] && threads=25 | |
368 | echo -e "$info Loaded $count_domains targets...$end\n\n" | |
369 | sort -u "$final_outputfile" | parallel -j "$threads" check_web_server | |
370 | ||
371 | # Check if the previus result is not empty | |
372 | elif [ "$call_source" == "dicattack" ];then | |
373 | cp "$previus_result" "$final_outputfile" | |
374 | count_domains=$(wc -l < "$final_outputfile") | |
375 | threads=$(echo "scale=0; ($count_domains * 0.15 + 0.5)/1" | bc) | |
376 | [ "$threads" -lt 1 ] && threads=1 | |
377 | [ "$threads" -gt 25 ] && threads=25 | |
378 | echo -e "$info Loaded $count_domains targets...$end\n\n" | |
379 | sort -u "$final_outputfile" | parallel -j "$threads" check_web_server | |
380 | fi | |
381 | fi | |
382 | fi | |
383 | ||
384 | # Even if crt.sh has no results, the script takes the data from the source and performs the web server check, even if dicct attacks was not successful. | |
385 | ||
386 | # Merge all the webservers files | |
387 | cat /tmp/dnsexplorer/*.webservers.txt | sort -u > "$webservers_outfile" | |
388 | https_count=$(grep "https://" "$webservers_outfile" | wc -l) | |
389 | threads_enumSANs=$(echo "scale=0; ($https_count * 0.15 + 0.5)/1" | bc) | |
390 | [ "$threads_enumSANs" -lt 1 ] && threads=1 | |
391 | [ "$threads_enumSANs" -gt 25 ] && threads=25 | |
392 | ||
393 | # Check if there are SANs | |
394 | grep "https://" "$webservers_outfile" | xargs -I {} -P "$threads_enumSANs" -n 1 bash -c 'checkCertificateSubjectsAlternativeNames "$1" "$2" "$3"' _ {} 443 "$SANs_tmp_file" | |
395 | ||
396 | # Discover new SANs | |
397 | sort -u $SANs_tmp_file | sed 's/^\*\.//g' | grep -E '.*\.'$domain'\\b' > $new_SANs | |
398 | new_subdomains=$(mktemp $tmpdir/XXX.new_subdomains.tmp) | |
399 | curated_previus_webservers=$(mktemp $tmpdir/XXX.curated_previus_webservers.tmp) | |
400 | cat "$webservers_outfile" | sed 's/http\:\/\///g' | sed 's/https\:\/\///g' | sort -u > $curated_previus_webservers | |
401 | grep -Fxv -f $curated_previus_webservers $new_SANs > $new_subdomains | |
402 | count_newsubdomains=$(wc -l < $new_subdomains) | |
403 | ||
404 | # Print results | |
405 | if [ "$count_newsubdomains" -gt 0 ]; then | |
406 | echo -e "\n${info}${count_newsubdomains} New subdomains was found${end}" | |
407 | cat $new_subdomains | |
408 | cat $new_subdomains >> $final_outputfile | |
409 | else | |
410 | echo -e "\n${cyan}No new subdomains found" | |
411 | fi | |
412 | ||
413 | webservers_count=$(wc -l < $webservers_outfile) | |
414 | subdomains_count=$(wc -l < $final_outputfile) | |
415 | ||
416 | # ExtendedChecks | |
417 | if $EXTENDED_CHECKS;then | |
418 | local -r webEnumOutputCSV="$output/$DOMAIN.webenum.csv" | |
419 | echo "URL,HTTPServer,IP,PoweredBy,X-Powered-By,Country,WAF" > "$webEnumOutputCSV" | |
420 | ||
421 | # Calculate the number of threads to use and launch the webEnum function in parallel | |
422 | count_webservers=$(wc -l < "$webservers_outfile") | |
423 | threads_webenum=$(echo "scale=0; ($count_webservers * 0.15 + 0.5)/1" | bc) | |
424 | [ "$threads_webenum" -lt 1 ] && threads=1 | |
425 | [ "$threads_webenum" -gt 25 ] && threads=25 | |
426 | echo "" # Just a blank line :) | |
427 | sort -u "$webservers_outfile" | parallel -j "$threads_webenum" webEnum | |
428 | ||
429 | # Merge all the CSV files | |
430 | cat $tmpdir/whatweb/*.csv >> "$webEnumOutputCSV" | |
431 | fi | |
432 | ||
433 | printResults 0.05 | |
434 | } | |
435 | ||
436 | # Print results function | |
437 | printResults() { | |
438 | delay=$1 | |
439 | ||
440 | DNSExplorerResults="Domain:${DOMAIN} | |
441 | DNS Servers: ${dns_servers_count} | |
442 | Zone Transfer: ${zone_transfer} | |
443 | Subdomains: ${subdomains_count} | |
444 | Webservers: ${webservers_count}" | |
445 | echo -e "\e[92;1m" | |
446 | for i in $(seq 0 $((${#DNSExplorerResults} - 1))); do | |
447 | echo -ne "${DNSExplorerResults:$i:1}" | |
448 | sleep "$delay" | |
449 | done | |
450 | echo -e "\e[0m" # New line | |
451 | } | |
452 | ||
453 | # Extended checks capabilities | |
454 | webEnum() { #9 | |
455 | info="\e[36m[+]" | |
456 | end="\e[0m" | |
457 | if [ -z "$1" ]; then | |
458 | return 1 | |
459 | fi | |
460 | ||
461 | if [[ $1 != http://* && $1 != https://* ]]; then | |
462 | return 1 | |
463 | fi | |
464 | ||
465 | local -r wafwoof_txt_tmp_output=$(mktemp /tmp/dnsexplorer/wafw00f/XXX.wafw00f) | |
466 | local -r output_txt_webenum_file=$(mktemp /tmp/dnsexplorer/whatweb/XXX.csv) | |
467 | ||
468 | # Check if the site has a WAF | |
469 | wafw00f "$1" -f json -o "$wafwoof_txt_tmp_output" > /dev/null 2>&1 | |
470 | firewall_detected=$(cat "$wafwoof_txt_tmp_output" | grep -oE '"firewall": "[^"]+"' | awk -F': ' '{gsub(/[",]/, "", $2); print $2}') | |
471 | [ -z "$firewall_detected" ] && firewall_detected="None" | |
472 | ||
473 | # Check webserver technologies | |
474 | local -r ww_result=$(whatweb --no-errors --open-timeout=7 --read-timeout=15 --colour=never "$1" 2>/dev/null) | |
475 | ||
476 | echo -e "${info} Testing webserver ${end}$1" | |
477 | ||
478 | # Get the webserver technologies | |
479 | httpserver=$(echo "$ww_result" | grep -o 'HTTPServer\[[^]]*\]' | cut -d'[' -f2 | cut -d']' -f1) | |
480 | [ -z "$httpserver" ] && httpserver="None" | |
481 | ip=$(echo "$ww_result" | grep -o 'IP\[[^]]*\]' | cut -d'[' -f2 | cut -d']' -f1 | head -1) | |
482 | [ -z "$ip" ] && ip="None" | |
483 | poweredby=$(echo "$ww_result" | grep -o 'PoweredBy\[[^]]*\]' | cut -d'[' -f2 | cut -d']' -f1) | |
484 | [ -z "$country" ] && country="None" | |
485 | xpoweredby=$(echo "$ww_result" | grep -o 'X-Powered-By\[[^]]*\]' | cut -d'[' -f2 | cut -d']' -f1) | |
486 | [ -z "$poweredby" ] && poweredby="None" | |
487 | country=$(echo "$ww_result" | grep -o 'Country\[[^]]*\]' | cut -d'[' -f2 | cut -d']' -f1) | |
488 | [ -z "$xpoweredby" ] && xpoweredby="None" | |
489 | ||
490 | # Save results | |
491 | echo "\"$1\",\"$httpserver\",\"$ip\",\"$poweredby\",\"$xpoweredby\",\"$country\",\"$firewall_detected\"" > "$output_txt_webenum_file" | |
492 | } | |
493 | export -f webEnum | |
494 | ||
495 | # Funcion for checking if the site has Subject Alternative Names | |
496 | checkCertificateSubjectsAlternativeNames() { #6 | |
497 | port=$2 | |
498 | declare output_file="$3" | |
499 | ||
500 | if [ -z "$1" ]; then | |
501 | return 1 | |
502 | fi | |
503 | ||
504 | if [ -z "$port" ]; then | |
505 | port=443 | |
506 | fi | |
507 | ||
508 | if [ -z "$output_file" ]; then | |
509 | return 1 | |
510 | fi | |
511 | ||
512 | server=$(echo "$1" | sed 's/http:\/\///' | sed 's/https:\/\///' | sed 's/\/$//') | |
513 | ||
514 | # Make sure opnessl can connect to the site | |
515 | connected=$(echo -n | openssl s_client -connect "$server:$port" 2>/dev/null | head -1 | awk -F "(" '{print $1}') | |
516 | ||
517 | # Check if the site has a webserver | |
518 | if [[ "$connected" == "CONNECTED" ]];then | |
519 | ||
520 | # Check if the site has DNS names | |
521 | DNS=$(echo -n | openssl s_client -connect "$server:$port" 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -text 2> /dev/null | sed 's/\ //'|grep -i "DNS:" | awk -F ":" '{print $1}') | |
522 | ||
523 | # Make sure the site has DNS names | |
524 | if [[ "$DNS" == "DNS" ]];then | |
525 | ||
526 | # Get the number of Subject Alternative Names | |
527 | len_subjects=$(echo -n | openssl s_client -connect "$server:$port" 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -text 2>/dev/null | grep "DNS:" 2>/dev/null | tr ',' '\n' | sed 's/\ //' | wc -l) | |
528 | ||
529 | # Check if the site has almost one Subject Alternative Names | |
530 | if [ $len_subjects -ge 1 ];then | |
531 | ||
532 | # Get the Subject Alternative Names | |
533 | SANs=$(echo -n | openssl s_client -connect "$server:$port" 2>/dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -text | grep "DNS:"| tr ',' '\n' | sed 's/\ //' | sed 's/\s//g' | sed 's/DNS://g') | |
534 | ||
535 | # return the Subject Alternative Names | |
536 | echo "$SANs" >> "$output_file" | |
537 | else | |
538 | return 1 | |
539 | fi | |
540 | else | |
541 | return 1 | |
542 | fi | |
543 | else | |
544 | return 1 | |
545 | fi | |
546 | } | |
547 | export -f checkCertificateSubjectsAlternativeNames | |
548 | ||
549 | # Initial recon | |
550 | initHostRecon(){ #3 | |
551 | echo -e "\n${info} A records for ${resalted_output}${DOMAIN}${end}${cyan} domain${end}" | |
552 | # A Records | |
553 | host "$DOMAIN" | grep 'has address' | awk '{print $4}' | |
554 | ||
555 | echo -e "\n${info} AAA records for ${resalted_output}${DOMAIN}${end}${cyan} domain${end}" | |
556 | # AAA Records | |
557 | if host "$DOMAIN" | grep 'IPv6' >/dev/null 2>&1;then | |
558 | host "$DOMAIN" | grep 'IPv6'| awk '{print $5}' | |
559 | else | |
560 | echo -e "$question Hosts $DOMAIN has not IPv6 address" | |
561 | fi | |
562 | ||
563 | echo -e "\n${info} MX records for ${resalted_output}${DOMAIN}${end}${cyan} domain${end}" | |
564 | # MAIL Records | |
565 | if host -t MX "$DOMAIN" | grep 'mail' >/dev/null 2>&1;then | |
566 | host "$DOMAIN" | grep 'mail' | awk '{print $6,$7}' | |
567 | else | |
568 | echo -e "$question Hosts $DOMAIN has not mail server records\n" | |
569 | fi | |
570 | ||
571 | echo -e "\n${info} CNAME records for ${resalted_output}${DOMAIN}${end}${cyan} domain${end}" | |
572 | # CNAME Records | |
573 | if host -t CNAME "$DOMAIN" | grep 'alias' >/dev/null 2>&1;then | |
574 | host -t CNAME "$DOMAIN" | awk '{print $1,$4,$6}' | |
575 | else | |
576 | echo -e "$question Hosts $DOMAIN has not alias records" | |
577 | fi | |
578 | ||
579 | echo -e "\n${info} TXT records for ${resalted_output}${DOMAIN}${end}${cyan} domain${end}" | |
580 | ` # TXT Records` | |
581 | if host -t txt "$DOMAIN" | grep 'descriptive' >/dev/null 2>&1;then | |
582 | host -t txt "$DOMAIN" | grep 'descriptive' | |
583 | else | |
584 | echo -e "$question Hosts $DOMAIN has not description records\n" | |
585 | fi | |
586 | } | |
587 | ||
588 | # Zone transfer attack function | |
589 | doZoneTransfer(){ #4 | |
590 | success=1 | |
591 | if host -t NS "$DOMAIN" | grep 'name server' >/dev/null 2>&1;then | |
592 | echo -e "\n${info} Enumerating DNS Servers..." | |
593 | host -t NS "$DOMAIN" | cut -d " " -f 4 > $tmpdir/NameServers.txt | |
594 | ||
595 | ns=$(wc -l $tmpdir/NameServers.txt | awk '{print $1}') | |
596 | ||
597 | if [ $ns -ge 1 ];then | |
598 | echo -e " ${green}[${ns}] DNS Servers was found, trying ZoneTransfer on these servers${end}" | |
599 | dns_servers_count=$ns | |
600 | ||
601 | # Verify if the DNS servers accept zone transfer | |
602 | while IFS= read -r nameserver;do | |
603 | host -t axfr "$DOMAIN" "$nameserver" | grep -E 'Received ([0-9]+) bytes from [0-9\.]+#[0-9]+ in ([0-9]+) ms' >/dev/null 2>&1 | |
604 | ||
605 | if [ $? -eq 0 ];then | |
606 | axfr_tmp_file="$tmpdir/axfr.tmp" && echo "" > "$axfr_tmp_file" | |
607 | axfr_parsed_file="$tmpdir/parsed_axfr.tmp" && echo "" > "$axfr_tmp_file" | |
608 | host -t axfr "$DOMAIN" "$nameserver" > "$axfr_tmp_file" | |
609 | declare -a record_types=("A" "AAA" "AXFR" "CNAME" "MX" "NS" "SOA" "SRV" "TXT") | |
610 | total_records=0 | |
611 | success=0 | |
612 | ||
613 | # SHow blinking message | |
614 | for i in {1..3}; do | |
615 | echo -ne "${ok}\e[5;7mNameServer ${nameserver} accept ZoneTransfer\e[0m" | |
616 | sleep 0.5 | |
617 | echo -ne "\r\e[K" | |
618 | sleep 0.5 | |
619 | done | |
620 | ||
621 | for record_type in "${record_types[@]}"; do | |
622 | # Extract the current records of the current type | |
623 | current_records=$(cat "$axfr_tmp_file" | grep "IN[[:space:]]\+$record_type" | sort -u | awk '{print $1 "\t" $NF}' | column -t -s $'\t') | |
624 | ||
625 | # Verify if there are records of the current type | |
626 | if [ -n "$current_records" ]; then | |
627 | count_current_records=$(cat "$axfr_tmp_file" | grep "IN[[:space:]]\+$record_type" | sort -u | wc -l) | |
628 | echo -e "${info} ${count_current_records} '$record_type' records found:${end}\n$current_records\n" | |
629 | echo -e "$current_records" >> "$axfr_parsed_file" | |
630 | total_records=$((total_records + count_current_records)) | |
631 | fi | |
632 | done | |
633 | echo -e "${ok}[${total_records}] Records found in $nameserver$end\nPlease take note of the other DNS servers, they may do zone transfers as well.${end}" | |
634 | break | |
635 | else | |
636 | echo -e " $error NameServer $nameserver does not accept zone transfer$end" | |
637 | fi | |
638 | done < <(grep -v '^ *#' < $tmpdir/NameServers.txt) | |
639 | else | |
640 | echo -e "$error No DNS servers found for $DOMAIN$end" | |
641 | fi | |
642 | ||
643 | # Check if the zone transfer was successful | |
644 | if [ $success -eq 0 ];then | |
645 | echo -e "\n$ok DNS zone transfer was possible, no bruteforce attacks on the subdomains are required. $end\n" | |
646 | ||
647 | cp "$axfr_parsed_file" $output/$DOMAIN.zoneTransfer.txt | |
648 | zone_transfer="Yes" | |
649 | crtSH "zonetransfer" | |
650 | clean | |
651 | ||
652 | # If the zonetransfer was not successful, then call to bruteforce | |
653 | else | |
654 | echo -e "\n$error DNS zone transfer was not possible, DNS servers are not accept it" | |
655 | ||
656 | while true; do | |
657 | echo "" | |
658 | tput cnorm | |
659 | echo -e "$question" | |
660 | read -rp "Do you want to brute force subdomains? [Y/n]> " yn | |
661 | echo -e "$end" | |
662 | ||
663 | case $yn in | |
664 | [Yy]* ) bruteForceDNS; clean; break;; | |
665 | [Nn]* ) crtSH "None"; clean;; | |
666 | * ) echo -e "$error Please answer yes or no.$end\n";; | |
667 | esac | |
668 | done | |
669 | fi | |
670 | fi | |
671 | } | |
672 | ||
673 | # Init recon with 'host' command | |
674 | basicRecon(){ #2 | |
675 | initHostRecon | |
676 | doZoneTransfer | |
677 | } | |
678 | ||
679 | # Check dependencies: curl, host, parallel. | |
680 | checkDependencies() { | |
681 | declare -A dependencies=( | |
682 | ["host"]="bind-utils/dnsutils" | |
683 | ["curl"]="curl" | |
684 | ["parallel"]="Parallel" | |
685 | ["bc"]="BC" | |
686 | ) | |
687 | for cmd in "${!dependencies[@]}"; do | |
688 | if ! command -v "$cmd" &> /dev/null; then | |
689 | echo -e "$error '$cmd' command is not available, please install the ${dependencies[$1]} package. $end" | |
690 | clean | |
691 | fi | |
692 | done | |
693 | } | |
694 | ||
695 | # Check opt dependencies: wafw00f, whatweb. | |
696 | optDependencies() { | |
697 | declare -A dependencies=( | |
698 | ["whatweb"]="whatweb" | |
699 | ["wafw00f"]="wafw00f" | |
700 | ) | |
701 | for cmd in "${!dependencies[@]}"; do | |
702 | if ! command -v "$cmd" &> /dev/null; then | |
703 | echo -e "$error '$cmd' command is not available, please install the ${dependencies[$1]} package for extended checks. $end" | |
704 | clean | |
705 | fi | |
706 | done | |
707 | } | |
708 | ||
709 | banner(){ | |
710 | echo -e "\e[91m | |
711 | ▓█████▄ ███▄ █ ██████ ▓█████ ▒██ ██▒ ██▓███ ██▓ ▒█████ ██▀███ ▓█████ ██▀███ | |
712 | ▒██▀ ██▌ ██ ▀█ █ ▒██ ▒ ▓█ ▀ ▒▒ █ █ ▒░▓██░ ██▒▓██▒ ▒██▒ ██▒▓██ ▒ ██▒▓█ ▀ ▓██ ▒ ██▒ | |
713 | ░██ █▌▓██ ▀█ ██▒░ ▓██▄ ▒███ ░░ █ ░▓██░ ██▓▒▒██░ ▒██░ ██▒▓██ ░▄█ ▒▒███ ▓██ ░▄█ ▒ | |
714 | ░▓█▄ ▌▓██▒ ▐▌██▒ ▒ ██▒▒▓█ ▄ ░ █ █ ▒ ▒██▄█▓▒ ▒▒██░ ▒██ ██░▒██▀▀█▄ ▒▓█ ▄ ▒██▀▀█▄ | |
715 | ░▒████▓ ▒██░ ▓██░▒██████▒▒░▒████▒▒██▒ ▒██▒▒██▒ ░ ░░██████▒░ ████▓▒░░██▓ ▒██▒░▒████▒░██▓ ▒██▒ | |
716 | ▒▒▓ ▒ ░ ▒░ ▒ ▒ ▒ ▒▓▒ ▒ ░░░ ▒░ ░▒▒ ░ ░▓ ░▒▓▒░ ░ ░░ ▒░▓ ░░ ▒░▒░▒░ ░ ▒▓ ░▒▓░░░ ▒░ ░░ ▒▓ ░▒▓░ | |
717 | ░ ▒ ▒ ░ ░░ ░ ▒░░ ░▒ ░ ░ ░ ░ ░░░ ░▒ ░░▒ ░ ░ ░ ▒ ░ ░ ▒ ▒░ ░▒ ░ ▒░ ░ ░ ░ ░▒ ░ ▒░ | |
718 | ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░░ ░ ░ ░ ░ ░ ▒ ░░ ░ ░ ░░ ░ | |
719 | ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ | |
720 | v:2.0 ░ $end By: Danilo Basanta (https://github.com/dabasanta/) ░ (https://www.linkedin.com/in/danilobasanta/)\n\n | |
721 | ||
722 | ||
723 | \033[3mThe author does not promote malicious actions or the use of the script for illegal operations. Remember to always obtain prior permission from the target company's system administrators before performing any malicious actions.\033[0m\n\n" | |
724 | } | |
725 | ||
726 | help(){ # Simply help function | |
727 | echo -e "\e[91m | |
728 | @@@ @@@ @@@@@@@@ @@@ @@@@@@@ | |
729 | @@@ @@@ @@@@@@@@ @@@ @@@@@@@@ | |
730 | @@! @@@ @@! @@! @@! @@@ | |
731 | !@! @!@ !@! !@! !@! @!@ | |
732 | @!@!@!@! @!!!:! @!! @!@@!@! | |
733 | !!!@!!!! !!!!!: !!! !!@!!! | |
734 | !!: !!! !!: !!: !!: | |
735 | :!: !:! :!: :!: :!: | |
736 | :: ::: :: :::: :: :::: :: | |
737 | : : : : :: :: : :: : : : | |
738 | v:2.0 ░ By: Danilo Basanta (https://github.com/dabasanta/) ░ (https://www.linkedin.com/in/danilobasanta/)\n\n${end} | |
739 | " | |
740 | ||
741 | options=$(cat <<- EOM | |
742 | ${resalted_output}Usage:${end} ${green}\e[3m./DNSExplorer.sh <domain>${end} | |
743 | ||
744 | ${resalted_output}Extended:${end} ${green}\e[3m./DNSExplorer.sh <domain> --extended${end} | |
745 | ||
746 | ${resalted_output}Help:${end} ${green}\e[3m-h, --help Display this help and exit${end} | |
747 | ||
748 | EOM | |
749 | ) | |
750 | echo -e "$options" | |
751 | tput cnorm | |
752 | } | |
753 | ||
754 | main(){ #1 | |
755 | export output="$DOMAIN.out" | |
756 | mkdir -p $output | |
757 | ||
758 | banner | |
759 | checkDependencies | |
760 | if ping -c 1 "$DOMAIN" > /dev/null 2>&1;then | |
761 | if host "$DOMAIN" > /dev/null 2>&1;then | |
762 | basicRecon "$DOMAIN" | |
763 | else | |
764 | echo -e "$error No route to host, please verify your DNS server or internet connection$end" | |
765 | clean | |
766 | fi | |
767 | else | |
768 | echo -e "${question}PING was not success, does server ignoring ICMP packets?$end" | |
769 | if host "$DOMAIN" > /dev/null 2>&1;then | |
770 | echo -e "${info}Running checks anyway$end\n" | |
771 | basicRecon "$DOMAIN" | |
772 | else | |
773 | echo -e "$error No route to host, please verify your DNS server or internet connection$end" | |
774 | clean | |
775 | fi | |
776 | fi | |
777 | } | |
778 | ||
779 | # Init flow | |
780 | if [ "$1" = "-h" ] || [ "$1" = "help" ] || [ "$1" = "--help" ] || [ "$2" = "-h" ] || [ "$2" = "--help" ] || [ "$2" = "help" ]; then | |
781 | help | |
782 | elif [ $# -eq 2 ]; then | |
783 | ||
784 | # Check if the second parameter is --extended | |
785 | if [ "$2" = "--extended" ]; then | |
786 | DOMAIN=$1 | |
787 | EXTENDED_CHECKS=true | |
788 | optDependencies | |
789 | main "$DOMAIN" | |
790 | else | |
791 | echo -e "$error Parameter '$2' is not recognized $end" | |
792 | help | |
793 | tput cnorm | |
794 | exit 1 | |
795 | fi | |
796 | elif [ $# -eq 0 ]; then | |
797 | help | |
798 | tput cnorm | |
799 | exit 1 | |
800 | else | |
801 | DOMAIN=$1 | |
802 | main "$DOMAIN" | |
803 | fi |