Skip to content

Commit

Permalink
v1.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
pptx704 committed Apr 15, 2024
1 parent b88a61a commit 58ffa62
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 55 deletions.
61 changes: 45 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,26 @@ Current features (v1.0.1)-
- User-friendly output
- Resolving A records (IPv4)

![](https://i.postimg.cc/596nWXrv/image.png)
![](https://i.postimg.cc/J0SBWgG8/image.png)

![](https://i.postimg.cc/wBHy3RkC/image.png)

- Virtual hostname enumeration
- Reverse DNS lookup
- Subdomains are accepted as input

![](https://i.postimg.cc/gcyMzCDq/image.png)
![](https://i.postimg.cc/rsRKgJgP/image.png)

![](https://i.postimg.cc/wx7GpGvs/image.png)

- Detects wildcard subdomains (for bruteforcing)
- Basic TCP port scanning
- Subdomains are accepted as input

![](https://i.postimg.cc/2SZYS5Sh/image.png)

- Export results to JSON file

![](https://i.postimg.cc/Vk31BmS4/image.png)
![](https://i.postimg.cc/9fv3ZYRM/image.png)

A few features are work in progress. See [Planned features](#planned-features) for more details.

Expand All @@ -61,7 +69,7 @@ Or, you can just download the binary from the [release page](https://github.com/
# Usage

```
./domainim <domain> [--ports=<ports> | -p] [--dns=<dns> | -d:<dns>] [--wordlist=<filename> | -l:<filename>] [--throttle=<int> | -t:<int>]
./domainim <domain> [--ports=<ports> | -p:<ports>] [--wordlist=<filename> | l:<filename> [--rps=<int> | -r:<int>]] [--dns=<dns> | -d:<dns>] [--out=<filename> | -o:<filename>]
```
- `<domain>` is the domain to be enumerated. It can be a subdomain as well.
- `-- ports | -p` is a string speicification of the ports to be scanned. It can be one of the following-
Expand All @@ -76,29 +84,50 @@ Or, you can just download the binary from the [release page](https://github.com/
- `a.b.c.d` - Use DNS server at `a.b.c.d` on port 53
- `a.b.c.d#n` - Use DNS server at `a.b.c.d` on port `e`
- `--wordlist | -l` - Path to the wordlist file. This is used for bruteforcing subdomains. If the file is invalid, bruteforcing will be skipped. You can get a wordlist from [SecLists](https://github.com/danielmiessler/SecLists/tree/master/Discovery/DNS). A wordlist is also provided in the [release page](https://github.com/pptx704/domainim/releases).
- `--throttle | -t` - This is the time (in ms) where 1024 requests will be spread out. i.e. for value `1000`, 1024 requests will be made in 1s, each having different delay before processing. The lesser the faster, bruteforcing will be. Set this to a higher value if you are getting rate limited. Default value is `1000`.
- `--rps | -r` - Number of requests to be made per second during bruteforce. The default value is `1024 req/s`. It is to be noted that, DNS queries are made in batches and next batch is made only after the previous one is completed. Since quries can be rate limited, increasing the value does not always guarantee faster results.
- `--out | -o` - Path to the output file. The output will be saved in JSON format. The filename must end with `.json`.

**Examples**
- `./domainim nmap.org --ports=all`
- `./domainim google.com --ports=none --dns=8.8.8.8#53`
- `./domainim pptx704.com --ports=t100`
- `./domainim pptx704.com --ports=t100 --wordlist=wordlist.txt --rps=1500`
- `./domainim pptx704.com --ports=t100 --wordlist=wordlist.txt --outfile=results.json`
- `./domainim mysite.com --ports=t50,5432,7000-9000 --dns=1.1.1.1`

The help menu can be accessed using `./domainim --help` or `./domainim -h`.
```
Usage:
domainim <domain> [--ports=<ports> | -p:<ports>] [--wordlist=<filename> | l:<filename>] [--dns=<dns> | -d:<dns>] [--throttle=<int> | -t:<int>]
domainim <domain> [--ports=<ports> | -p:<ports>] [--wordlist=<filename> | l:<filename> [--rps=<int> | -r:<int>]] [--dns=<dns> | -d:<dns>] [--out=<filename> | -o:<filename>]
domainim (-h | --help)
Options:
-h, --help Show this screen.
-p, --ports Ports to scan. [default: `none`]
Can be `all`, `none`, `t<n>`, single value, range value, combination
-l, --wordlist Wordlist for subdomain bruteforcing. Bruteforcing is skipped for invalid file.
-d, --dns IP and Port for DNS Resolver. Should be a valid IPv4 with an optional port [default: system default]
-t, --throttle Time (in ms) needed per 1024 DNS query [default: 1000]
-r, --rps DNS queries to be made per second [default: 1024 req/s]
-o, --out JSON file where the output will be saved. Filename must end with `.json`
Examples:
domainim domainim.com -p:t500 -l:wordlist.txt --dns:1.1.1.1#53
domainim sub.domainim.com --ports=all --dns:8.8.8.8 -t:1500
domainim domainim.com -p:t500 -l:wordlist.txt --dns:1.1.1.1#53 --out=results.json
domainim sub.domainim.com --ports=all --dns:8.8.8.8 -t:1500 -o:results.json
```

The JSON schema for the results is as follows-

```json
[
{
"subdomain": string,
"data": [
"ipv4": string,
"vhosts": [string],
"reverse_dns": string,
"ports": [int]
]
}
]
```

# Contributing
Expand All @@ -108,12 +137,12 @@ Contributions are welcome. Feel free to open a pull request or an issue.
- [x] TCP port scanning
- [ ] UDP port scanning support
- [ ] Resolve AAAA records (IPv6)
- [ ] Force bruteforcing (even if wildcard subdomain is found)
- [x] Custom DNS server
- [x] Add more engines for subdomain enumeration (bruteforcing added)
- [ ] File output (probably CSV or JSON)
- [x] Add bruteforcing subdomains using a wordlist
- [ ] Force bruteforcing (even if wildcard subdomain is found)
- [ ] Add more engines for subdomain enumeration
- [x] File output (JSON)
- [ ] Multiple domain enumeration
- [ ] Local network scanning
- [ ] Dir and File busting

## Others
Expand Down Expand Up @@ -144,7 +173,7 @@ But previously while testing, I found cases where not all IPs are shared by same

![](https://i.postimg.cc/q7PjB8NW/image.png)

DNS server might have some sort of rate limiting. That's why I added random delays (between 0-300ms) for IPv4 resolving per query. This is to not make the DNS server get all the queries at once but rather in a more natural way. For bruteforcing method, the value is between 0-1000ms by default but that can be changed using `--throttle | -t` flag.
DNS server might have some sort of rate limiting. That's why I added random delays (between 0-300ms) for IPv4 resolving per query. This is to not make the DNS server get all the queries at once but rather in a more natural way. For bruteforcing method, the value is between 0-1000ms by default but that can be changed using `--rps | -t` flag.

One particular limitation that is bugging me is that the DNS resolver would not return all the IPs for a domain. So it is necessary to make multiple queries to get all (or most) of the IPs. But then again, it is not possible to know how many IPs are there for a domain. I still have to come up with a solution for this. Also, `nim-ndns` doesn't support CNAME records. So, if a domain has a CNAME record, it will not be resolved. I am waiting for a response from the author for this.

Expand Down
55 changes: 42 additions & 13 deletions src/domainim.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import std/[parseopt, os, strformat, terminal, strutils]
import processors, helpers, results

let
usage = fmt"Usage ./{getAppFilename().extractFilename} <domain> [--ports=<ports> | -p:ports] [--wordlist=<filename>] [--dns=<ip>[#port]] [--throttle=<int>]"
usage = fmt"Invalid argument(s). Use ./{getAppFilename().extractFilename} --help for usage."
banner = """
▓█████▄ ▒█████ ███▄ ▄███▓ ▄▄▄ ██▓ ███▄ █ ██▓ ███▄ ▄███▓
Expand All @@ -18,22 +18,37 @@ let
"""

proc startChecking(domain: string, portStr: string, dnsStr: string, sbList: string, throttle: int) =
var ports: seq[int]
proc startChecking(domain: string, portStr: string, dnsStr: string, sbList: string, rps: int, filename: string) =
var
outfile: File
writeable: bool = true
ports: seq[int]
try:
ports = processPortString(portStr)
except:
echo "Invalid port specification. Example of proper form: 't10,5432,53,100-150'"

echo banner
styledEcho "Provided domain: ", styleUnderscore, domain
let subdomains = processSubdomains(domain, dnsStr, sbList, throttle)

let subdomains = processSubdomains(domain, dnsStr, sbList, rps)
if len(subdomains) == 0:
printMsg(error, fmt"[!] No subdomains found for the {domain}")
return
var iptable = processVHostNames(subdomains)

printPorts(portStr)
var iptable = processVHostNames(subdomains)
iptable = processOpenPorts(iptable, ports)
printResults(subdomains, iptable)

if not filename.isEmptyOrWhitespace and not outfile.open(filename, fmWrite):
printOutfile(false, filename)
writeable = false

if not filename.isEmptyOrWhitespace and writeable:
printOutfile(true, filename)
saveResults(subdomains, iptable, outfile)
printUpdate(success, fmt"[+] Results saved to {filename}")
else:
printResults(subdomains, iptable)


proc main =
Expand All @@ -43,7 +58,8 @@ proc main =
domain: string
dns = ""
sbList = ""
throttle: int = 1000
rps: int = 1000
outfile = ""
if paramCount() == 0:
echo usage
return
Expand All @@ -64,12 +80,18 @@ proc main =
dns = p.val
of "wordlist":
sbList = p.val
of "throttle":
of "rps":
try:
throttle = p.val.parseInt
rps = p.val.parseInt
except:
echo usage
return
of "out":
if p.val.endsWith(".json"):
outfile = p.val
else:
echo fmt"Invalid output filename {p.val}. File must be a json file."
return
of "help":
printHelp()
return
Expand All @@ -84,19 +106,26 @@ proc main =
dns = p.val
of "l":
sbList = p.val
of "t":
of "r":
try:
throttle = p.val.parseInt
rps = p.val.parseInt
except:
echo usage
return
of "o":
if p.val.endsWith(".json"):
outfile = p.val
else:
echo fmt"Invalid output filename {p.val}. File must be a json file."
return
of "h":
printHelp()
return
else:
echo usage
return
startChecking(domain, ports, dns, sbList, throttle)

startChecking(domain, ports, dns, sbList, rps, outfile)

when isMainModule:
main()
16 changes: 11 additions & 5 deletions src/helpers.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Module helper
import std/[terminal, strutils, os]
import std/[terminal, strutils, os, strformat]

type MsgType* = enum
info, success, error, neutral
Expand Down Expand Up @@ -47,11 +47,16 @@ proc printPorts*(portStr: string) =
elif portStr == "":
printMsg(neutral, "[*] Port specification not provided. Port scanning will be skipped.")

proc printOutfile*(status: bool, filename: string) =
if status:
printMsg(info, fmt"[*] Writing results to {filename}.")
else:
printMsg(error, fmt"[!] Could not open {filename}. Results will be printed on console.")

proc printHelp*() =
echo """
Usage:
$1 <domain> [--ports=<ports> | -p:<ports>] [--wordlist=<filename> | l:<filename>] [--dns=<dns> | -d:<dns>] [--throttle=<int> | -t:<int>]
$1 <domain> [--ports=<ports> | -p:<ports>] [--wordlist=<filename> | l:<filename> [--rps=<int> | -r:<int>]] [--dns=<dns> | -d:<dns>] [--out=<filename> | -o:<filename>]
$1 (-h | --help)
Options:
Expand All @@ -60,9 +65,10 @@ Options:
Can be `all`, `none`, `t<n>`, single value, range value, combination
-l, --wordlist Wordlist for subdomain bruteforcing. Bruteforcing is skipped for invalid file.
-d, --dns IP and Port for DNS Resolver. Should be a valid IPv4 with an optional port [default: system default]
-t, --throttle Time (in ms) needed per 1024 DNS query [default: 1000]
-r, --rps DNS queries to be made per second [default: 1024 req/s]
-o, --out JSON file where the output will be saved. Filename must end with `.json`
Examples:
$1 domainim.com -p:t500 -l:wordlist.txt --dns:1.1.1.1#53
$1 sub.domainim.com --ports=all --dns:8.8.8.8 -t:1500
$1 domainim.com -p:t500 -l:wordlist.txt --dns:1.1.1.1#53 --out=results.json
$1 sub.domainim.com --ports=all --dns:8.8.8.8 -t:1500 -o:results.json
""" % getAppFilename().extractFilename
2 changes: 1 addition & 1 deletion src/modules/scannerutils.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import std/[osproc,strutils,sequtils,algorithm]
import std/[osproc, strutils, sequtils, algorithm]
import regex

type
Expand Down
13 changes: 6 additions & 7 deletions src/modules/sf/crtsh.nim
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
# Module crtsh

import std/[httpclient, strformat, net, json]
import regex
import std/[httpclient, strformat, net, json, strutils]
import utils

const
crtUrl = "https://crt.sh/"
userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"
rowRegEx = re2"<TR>\s*<TD\s+style=\x22text-align:center\x22>[\s\S]*?</TR>"
subdomainRegEx = re2"<TD>(.*?)</TD>"

let
headers = {
Expand All @@ -19,7 +16,7 @@ let
}


client: HttpClient = newHttpClient(userAgent, timeout=20000) # 20s timeout
client: HttpClient = newHttpClient(userAgent, timeout=45000) # 45s timeout

client.headers = newHttpHeaders(headers)

Expand All @@ -28,7 +25,7 @@ proc makeRequest(url: string): Response =
let paramUrl = fmt"{crtUrl}?Identity={url}&output=json"#&exclude=expired"
result = client.get(paramUrl)
except TimeoutError:
raise newException(WebpageParseError, "crt.sh is not responding as expected")
raise newException(WebpageParseError, "crt.sh is request timeout")


proc getARecords(response: Response, target: string): seq[string] =
Expand All @@ -51,7 +48,9 @@ proc getARecords(response: Response, target: string): seq[string] =
# traverse the json and get the name_value
for entry in data:
if entry.hasKey("name_value"):
result.add(entry["name_value"].str)
var names: string = entry["name_value"].str
for name in names.split("\n"):
result.add(name)

result = cleanAll(result, target) # crtsh sometimes provide unnecessary urls. cleaning is needed here
if len(result) == 0:
Expand Down
19 changes: 12 additions & 7 deletions src/modules/sf/dnsdumpster.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ let
"Content-Type": "application/x-www-form-urlencoded"
}

client: HttpClient = newHttpClient(userAgent, timeout=20000) # 20s timeout
client: HttpClient = newHttpClient(userAgent, timeout=45000) # 45s timeout

client.headers = newHttpHeaders(headers)

Expand Down Expand Up @@ -53,12 +53,16 @@ proc makeRequest(reqMethod: string, url: string): Response =
if reqMethod == "GET":
result = client.get(ddUrl)
else:
var resp: Response
var
resp: Response
status: int
try:
resp = makeRequest("GET", ddUrl)
setCookie(resp)
except KeyError, TimeoutError:
raise newException(WebpageParseError, "dnsdumpster.com is not responding as expected")
except KeyError:
raise newException(WebpageParseError, "Could not fetch cookie from dnsdumpster.com")
except TimeoutError:
raise newException(WebpageParseError, "dnsdumpster.com request timeout")

var data = MultipartData()
data["csrfmiddlewaretoken"] = getCSRFToken(resp)
Expand All @@ -67,9 +71,10 @@ proc makeRequest(reqMethod: string, url: string): Response =
try:
result = client.post(ddUrl, multipart = data)
except TimeoutError:
raise newException(WebpageParseError, "dnsdumpster.com is not responding as expected")
if parseInt(result.status.split()[0]) >= 400:
raise newException(WebpageParseError, "dnsdumpster.com is not responding as expected")
raise newException(WebpageParseError, "dnsdumpster.com is request timeout")
status = parseInt(result.status.split()[0])
if status >= 400:
raise newException(WebpageParseError, "dnsdumpster.com responded with status code " & $status)

proc getHostTable(str: string): string =
try:
Expand Down
4 changes: 2 additions & 2 deletions src/modules/sf/utils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "../iputils"
type
Subdomain* = object
url*: string
isAlive*: bool
isAlive*: bool # consists 1 or more ip4 addresses
ipv4*: seq[string]

WebpageParseError* = object of CatchableError
Expand Down Expand Up @@ -67,7 +67,7 @@ proc createDnsClient*(dnsStr: string): DnsClient =
else:
result = initDnsClient(dnsStr)

proc resolveDomain*(subdomain: string, client: DnsClient, throttle: int = 300): Future[(string, seq[string])] {.async.} =
proc resolveDomain*(subdomain: string, client: DnsClient, throttle: int = 1000): Future[(string, seq[string])] {.async.} =
await sleepAsync(rand(throttle))
let allIpv4 = await asyncResolveIpv4(client, subdomain, 1000)
var validIPv4s: seq[string] = @[]
Expand Down
Loading

0 comments on commit 58ffa62

Please sign in to comment.