-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdomainips.sh
More file actions
executable file
·311 lines (260 loc) · 8.77 KB
/
domainips.sh
File metadata and controls
executable file
·311 lines (260 loc) · 8.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#!/usr/bin/env bash
set -euo pipefail
# domainips.sh - Enumerate all DNS records and IP addresses for a domain
# Usage: ./domainips.sh -d <domain> [-o <output-file>] [-w <wordlist>]
# Compatible with Bash 3.2+ (macOS default)
readonly VERSION="1.0.0"
readonly CT_LOG_URL="https://crt.sh"
readonly TMPDIR_BASE="${TMPDIR:-/tmp}"
readonly WORK_DIR=$(mktemp -d "${TMPDIR_BASE}/domainips.XXXXXX")
trap 'rm -rf "$WORK_DIR"' EXIT
readonly SUBS_FILE="${WORK_DIR}/subdomains.txt"
readonly RESULTS_FILE="${WORK_DIR}/results.csv"
touch "$SUBS_FILE" "$RESULTS_FILE"
DEFAULT_SUBDOMAINS="www mail ftp smtp pop imap webmail ns ns1 ns2 ns3 dns dns1 dns2
mx mx1 mx2 api app dev staging prod test beta alpha demo
admin portal vpn remote gateway proxy cdn assets static media
blog shop store support help docs doc wiki forum community
status monitor grafana prometheus kibana elastic jenkins ci cd
git gitlab github bitbucket jira confluence slack
db database mysql postgres redis mongo elasticsearch
backup bak old new legacy archive
auth login sso oauth id identity
search analytics tracking metrics
intranet internal ext external
cloud aws azure gcp s3
autodiscover autoconfig cpanel whm plesk
_dmarc _domainkey"
usage() {
cat <<EOF
domainips v${VERSION} - DNS Record & IP Enumeration Tool
Usage: $(basename "$0") -d <domain> [options]
Options:
-d <domain> Target domain (required)
-o <file> Output results to CSV file
-w <wordlist> Custom subdomain wordlist (one per line)
-s Skip Certificate Transparency lookup
-q Quiet mode (less verbose output)
-h Show this help
Examples:
$(basename "$0") -d example.com
$(basename "$0") -d example.com -o results.csv
$(basename "$0") -d example.com -w subdomains.txt
EOF
exit 0
}
log() {
[[ "${QUIET}" == "true" ]] && return
echo -e "\033[1;34m[*]\033[0m $1" >&2
}
warn() {
echo -e "\033[1;33m[!]\033[0m $1" >&2
}
error() {
echo -e "\033[1;31m[-]\033[0m $1" >&2
exit 1
}
success() {
echo -e "\033[1;32m[+]\033[0m $1" >&2
}
# Add a subdomain to the discovered list (deduplicates via sort -u later)
add_subdomain() {
local sub
sub=$(echo "$1" | tr '[:upper:]' '[:lower:]' | sed 's/\.$//')
# skip wildcards and empty
[[ -z "$sub" ]] && return
echo "$sub" | grep -q '\*' && return
echo "$sub" >> "$SUBS_FILE"
}
# Attempt DNS zone transfer
try_zone_transfer() {
local domain="$1"
log "Attempting zone transfer (AXFR) for ${domain}..."
local ns_servers
ns_servers=$(dig +short +time=3 +tries=1 NS "$domain" 2>/dev/null || true)
if [[ -z "$ns_servers" ]]; then
warn "No NS records found for zone transfer"
return
fi
for ns in $ns_servers; do
local axfr_result
axfr_result=$(dig @"$ns" "$domain" AXFR +noall +answer +time=5 +tries=1 2>/dev/null || true)
if [[ -n "$axfr_result" ]]; then
success "Zone transfer successful from ${ns}!"
echo "$axfr_result" | awk '{print $1}' | while IFS= read -r name; do
add_subdomain "$name"
done
fi
done
}
# Query Certificate Transparency logs via crt.sh
query_ct_logs() {
local domain="$1"
log "Querying Certificate Transparency logs (crt.sh)..."
local ct_result
ct_result=$(curl -s --max-time 30 \
"${CT_LOG_URL}/?q=%25.${domain}&output=json" 2>/dev/null || true)
if [[ -z "$ct_result" || "$ct_result" == "null" ]]; then
warn "No CT log results or crt.sh unreachable"
return
fi
local names
names=$(echo "$ct_result" | jq -r '.[].name_value' 2>/dev/null | sort -u || true)
local count=0
while IFS= read -r name; do
[[ -z "$name" ]] && continue
# crt.sh can return newline-separated names in one field
while IFS= read -r sub; do
case "$sub" in
*".${domain}"|"${domain}")
add_subdomain "$sub"
count=$((count + 1))
;;
esac
done <<< "$name"
done <<< "$names"
success "Found ${count} entries from CT logs"
}
# Brute-force common subdomains via DNS resolution
bruteforce_subdomains() {
local domain="$1"
local wordlist="$2"
local total
total=$(echo "$wordlist" | wc -w | tr -d ' ')
log "Checking ${total} common subdomain names..."
local count=0
for sub in $wordlist; do
local fqdn
# If entry is already a FQDN for this domain, use it directly
case "$sub" in
*".${domain}"|"${domain}") fqdn="$sub" ;;
*) fqdn="${sub}.${domain}" ;;
esac
local result
result=$(dig +short +time=2 +tries=1 A "$fqdn" 2>/dev/null | grep -v '^;;' 2>/dev/null | head -1 || true)
if [[ -n "$result" ]]; then
add_subdomain "$fqdn"
count=$((count + 1))
fi
done
success "Found ${count} subdomains via DNS brute-force"
}
# Run dig and filter error lines
safe_dig() {
dig +short +time=2 +tries=1 "$@" 2>/dev/null | grep -v '^;;' || true
}
# Resolve a single domain and print all record types
resolve_domain() {
local domain="$1"
local type val
for type in A AAAA CNAME MX TXT NS; do
while IFS= read -r val; do
[[ -n "$val" ]] && echo "${domain},${type},${val}"
done < <(safe_dig "$type" "$domain")
done
}
# --- Main ---
DOMAIN=""
OUTPUT_FILE=""
CUSTOM_WORDLIST=""
SKIP_CT=false
QUIET=false
while getopts "d:o:w:sqh" opt; do
case "$opt" in
d) DOMAIN="$OPTARG" ;;
o) OUTPUT_FILE="$OPTARG" ;;
w) CUSTOM_WORDLIST="$OPTARG" ;;
s) SKIP_CT=true ;;
q) QUIET=true ;;
h) usage ;;
*) usage ;;
esac
done
[[ -z "$DOMAIN" ]] && error "Domain is required. Use -d <domain>"
# Validate domain format
if ! echo "$DOMAIN" | grep -qE '^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$'; then
error "Invalid domain format: ${DOMAIN}"
fi
log "Starting DNS enumeration for: ${DOMAIN}"
log "========================================="
# Always include the base domain
add_subdomain "$DOMAIN"
# When a wordlist is provided, skip discovery phases (subdiscover.sh already did this)
if [[ -z "$CUSTOM_WORDLIST" ]]; then
# Phase 1: Zone Transfer
try_zone_transfer "$DOMAIN"
# Phase 2: Certificate Transparency
if [[ "$SKIP_CT" == "false" ]]; then
query_ct_logs "$DOMAIN"
fi
fi
# Phase 3: Subdomain discovery
if [[ -n "$CUSTOM_WORDLIST" ]]; then
if [[ ! -f "$CUSTOM_WORDLIST" ]]; then
error "Wordlist file not found: ${CUSTOM_WORDLIST}"
fi
# Check if wordlist contains FQDNs (from subdiscover.sh) or plain prefixes
FIRST_ENTRY=$(head -1 "$CUSTOM_WORDLIST")
case "$FIRST_ENTRY" in
*".${DOMAIN}"|"${DOMAIN}")
# FQDNs: add directly, skip redundant brute-force check
log "Importing $(wc -l < "$CUSTOM_WORDLIST" | tr -d ' ') domains from wordlist..."
cat "$CUSTOM_WORDLIST" >> "$SUBS_FILE"
;;
*)
# Plain prefixes: brute-force check
WORDS=$(cat "$CUSTOM_WORDLIST" | tr '\n' ' ')
bruteforce_subdomains "$DOMAIN" "$WORDS"
;;
esac
else
bruteforce_subdomains "$DOMAIN" "$DEFAULT_SUBDOMAINS"
fi
# Deduplicate subdomains
sort -u "$SUBS_FILE" -o "$SUBS_FILE"
TOTAL_SUBS=$(wc -l < "$SUBS_FILE" | tr -d ' ')
# Phase 4: Resolve all found subdomains
log "========================================="
log "Resolving DNS records for ${TOTAL_SUBS} unique domains..."
RESOLVED=0
CURRENT=0
while IFS= read -r sub; do
[[ -z "$sub" ]] && continue
CURRENT=$((CURRENT + 1))
[[ "${QUIET}" != "true" ]] && printf "\r\033[1;34m[*]\033[0m Resolving %d/%d: %s\033[K" "$CURRENT" "$TOTAL_SUBS" "$sub" >&2
result=$(resolve_domain "$sub")
if [[ -n "$result" ]]; then
echo "$result" >> "$RESULTS_FILE"
RESOLVED=$((RESOLVED + 1))
fi
done < "$SUBS_FILE"
[[ "${QUIET}" != "true" ]] && printf "\r\033[K" >&2
# Sort results
sort -t',' -k1,1 -k2,2 "$RESULTS_FILE" -o "$RESULTS_FILE"
# Output results
echo ""
echo "==========================================="
echo " DNS Enumeration Results for: ${DOMAIN}"
echo "==========================================="
echo ""
echo "Subdomains discovered: ${TOTAL_SUBS}"
echo "Subdomains with records: ${RESOLVED}"
echo ""
# Print table
printf "%-50s %-8s %s\n" "DOMAIN" "TYPE" "VALUE"
printf "%-50s %-8s %s\n" "$(printf '%0.s-' {1..50})" "--------" "$(printf '%0.s-' {1..40})"
while IFS=',' read -r domain type value; do
[[ -z "$domain" ]] && continue
printf "%-50s %-8s %s\n" "$domain" "$type" "$value"
done < "$RESULTS_FILE"
# CSV output
if [[ -n "$OUTPUT_FILE" ]]; then
{
echo "Domain,Record Type,Value"
cat "$RESULTS_FILE"
} > "$OUTPUT_FILE"
echo ""
success "Results saved to: ${OUTPUT_FILE}"
fi
echo ""
log "Done."