OWASP Amass - The Amass Scripting Engine Manual
The Amass Scripting Engine allows users to provide their own data source implementations. Amass Data Source (file extension .ads
) scripts are executed in an embedded Lua programming environment, similar to the Nmap Scripting Engine. The scripts can provide findings to the Amass cyclic enumeration process using web scraping and crawling, REST APIs, TLS certificates, brute forcing, DNS name permutation, executing local programs, reading files and databases, etc.
This document will show the format of an Amass data source script, the callback functions that are triggered during enumerations, and the custom functions made available in the environment. These callbacks and custom functions allows scripts to receive requests from Amass and return discoveries to be shared with the architecture. Users can leverage the Lua Programming Language and the Lua Standard Library documentation to take full advantage of the Amass Scripting Engine.
The default Amass data source scripts can be found in resources/scripts, and are separated by the various script types. In order to execute your own script, put the .ads
file under a directory named scripts
that exists in the Amass output directory. Amass will find the script in that directory and use it during each enumeration. Your data source scripts can also be provided to Amass using the -scripts
flag on the command-line.
The Amass Scripting Engine also makes two Lua modules available to users: gluaurl for URL parsing/building and gopher-json for simple JSON encoding/decoding. These modules are made available by default and can be used by scripts via require("url")
and require("json")
, respectively.
Amass data source scripts contain the name
field, type
field, and at least one callback function to receive Amass events. These fields can be defined just as you would any other Lua global variables. The callback functions must use the predetermined names shown in the subsection below. Their names must be lowercase as shown.
The name
field provides a unique identifier for the data source that will be shared throughout the enumeration. All script names are compared in lowercase and must be unique. The name should be short and not have spaces in order to provide output consistency. The following command will show already used names:
amass enum --list
The type
field provides the category of the data source implemented by the script. The following types are valid:
Valid Value | Category |
---|---|
"dns" | DNS Queries |
"axfr" | DNS Zone Transfers |
"scrape" | Web Scraping |
"crawl" | Web Crawling |
"api" | Various APIs |
"cert" | TLS Certificates |
"archive" | Web Archives |
"brute" | Brute Forcing |
"alt" | Name Alterations |
"guess" | Name Guessing |
"rir" | Regional Internet Registry |
"ext" | External Program / Data Source |
The subdomain_regex
string is a global variable that contains a regular expression pattern that will match subdomain names.
If the name
field for a script has a matching entry in the Amass configuration file for API authentication information, then a Lua table will be made global in the script. The api
table fields are shown below. Only the fields set in the configuration file will be set in the api
table for the script.
Field Name | Data Type |
---|---|
username | string |
password | string |
key | string |
secret | string |
ttl | number |
Amass will execute the start
function (if the script defines it) once, at the beginning of the enumeration process and before any other callbacks are executed. Most data source implementations use this callback as the place to set the rate limit (more about this later) for the script.
function start()
set_rate_limit(1)
end
Amass will execute the stop
function (if the script defines it) once, at the end of the enumeration process and after all other callbacks are executed.
function stop()
-- Cleanup code, etc.
end
Amass executes the vertical
callback function when attempting to perform vertical domain name correlation. The function is provided the domain name of interest and the script sends back subdomain names it is able to discover.
function vertical(ctx, domain)
-- Send back discovered subdomain names
new_name(ctx, name)
end
Field Name | Data Type |
---|---|
ctx | UserData |
domain | string |
The ctx
parameter is a reference to the context of the caller, which is necessary for many of the custom calls shown below.
Amass executes the horizontal
callback function when attempting to perform horizontal domain name correlation. The function is provided the domain name of interest and the script sends back associated domain names it is able to discover.
function horizontal(ctx, domain)
-- Send back an associated domain name
associated(ctx, domain, assoc)
end
Field Name | Data Type |
---|---|
ctx | UserData |
domain | string |
The ctx
parameter is a reference to the context of the caller, which is necessary for many of the custom calls shown below.
Amass executes the resolved
callback function after successfully resolving name
via DNS query during the enumeration. The callback is executed for each DNS name validated this way.
function resolved(ctx, name, domain, records)
crawl(ctx, "https://" .. name, 0)
end
Field Name | Data Type |
---|---|
ctx | UserData |
name | string |
domain | string |
records | table |
The records
parameter is a table of tables that each contain the following fields:
Field Name | Data Type |
---|---|
rrname | string |
rrtype | number |
rrdata | string |
Amass executes the subdomain
callback function after successfully resolving name
via DNS query and checking that it is a proper subdomain name. A proper subdomain name must have more labels than the root domain name and be resolved with a hostname label. For example, if example.com
is the root domain name, and www.depta.example.com
is successfully resolved, then the proper subdomain name depta.example.com
will be provided to the subdomain
callback function. The times
parameter shares how many hostnames have been discovered within this proper subdomain name.
function subdomain(ctx, name, domain, times)
if times == 1 then
crawl(ctx, "https://" .. name, 0)
end
end
Field Name | Data Type |
---|---|
ctx | UserData |
name | string |
domain | string |
times | number |
Amass executes the address
callback function when attempting to discover additional subdomain names and IP addresses that are within scope. The function is provided an IP address that is within scope and the script sends back related findings.
function address(ctx, addr)
-- Send back a related subdomain name
new_name(ctx, name)
-- Send back a related IP address
new_addr(ctx, ipaddr, domain)
end
Field Name | Data Type |
---|---|
ctx | UserData |
addr | string |
Amass executes the asn
callback function when attempting to obtain autonomous system (AS) information from the provided IP address and/or AS number. The function is provided an IP address and/or AS number that is within scope and the script sends back the information for the associated AS using the new_asn
(more about this below) function.
function asn(ctx, addr, asn)
-- Send back the related AS information
new_asn(ctx, {
['addr']=addr,
['asn']=tonumber(asn),
['desc']=desc,
prefix=cidr,
cc="US",
registry="ARIN",
netblocks={cidr},
})
end
Field Name | Data Type |
---|---|
ctx | UserData |
addr | string |
asn | number |
A script can obtain the configuration of the current enumeration process by calling the config
function.
function vertical(ctx, domain)
local cfg = config(ctx)
print(cfg.mode)
end
Field Name | Data Type |
---|---|
ctx | UserData |
The config
function returns a rather large table containing values explained in the User Guide and shown below.
Field Name | Data Type |
---|---|
mode | string |
event_id | string |
max_dns_queries | number |
dns_record_types | table |
resolvers | table |
provided_names | table |
scope | table |
brute_forcing | table |
alterations | table |
Most of the tables are simply arrays of strings, but the scope
, brute_forcing
and alterations
tables deserve additional explanation.
The scope
table has the following fields:
Field Name | Data Type |
---|---|
domains | table |
blacklist | table |
addresses | table |
cidrs | table |
asns | table |
ports | table |
The brute_forcing
table has the following fields:
Field Name | Data Type |
---|---|
active | bool |
recursive | bool |
min_for_recursive | number |
The alterations
table has the following fields:
Field Name | Data Type |
---|---|
active | bool |
flip_words | bool |
flip_numbers | bool |
add_words | bool |
add_numbers | bool |
edit_distance | number |
A script can obtain the wordlist used for brute forcing by the current enumeration process via the brute_wordlist
function. The return value is an array of strings.
function vertical(ctx, domain)
local wordlist = brute_wordlist(ctx)
for i, word in pairs(wordlist) do
print(word)
end
end
Field Name | Data Type |
---|---|
ctx | UserData |
A script can obtain the wordlist used for name alterations by the current enumeration process via the alt_wordlist
function. The return value is an array of strings.
function vertical(ctx, domain)
local wordlist = alt_wordlist(ctx)
for i, word in pairs(wordlist) do
print(word)
end
end
Field Name | Data Type |
---|---|
ctx | UserData |
A script can contribute to the enumeration log file by sending a message through the log
function.
function sendmsg(ctx, msg)
log(ctx, msg)
end
Field Name | Data Type |
---|---|
ctx | UserData |
msg | string |
A script can request the file modification time associated with the provided path through the mtime
function. A return value of zero indicates the file could not be accessed or does not exist.
function file_unix_mtime(path)
return mtime(path)
end
Field Name | Data Type |
---|---|
path | string |
A script can request the filepath to the Amass output directory by executing the output_dir
function. The returned path can be relative.
function get_bin(ctx)
local path = output_dir(ctx)
return path .. "/bin"
end
Field Name | Data Type |
---|---|
ctx | UserData |
A script can check if a subdomain name is in scope of the current enumeration process by executing the in_scope
function. The function returns true
if the name is in scope and false
otherwise.
function get_names(ctx, sub)
if in_scope(ctx, sub) then
crawl(ctx, "https://" .. sub, 0)
end
end
Field Name | Data Type |
---|---|
ctx | UserData |
fqdn | string |
A script can set the number of seconds to wait between each execution of a callback function by using the set_rate_limit
function.
function start()
set_rate_limit(2)
end
Field Name | Data Type |
---|---|
seconds | number |
A script can check if the rate limit bucket has been exceeded, and if so, will block for the appropriate amount of time by executing the check_rate_limit
function.
function vertical(ctx, domain)
-- Obtain several subdomain names
for i, n in pairs(subs) do
check_rate_limit()
crawl(ctx, "https://" .. n, 0)
end
end
The find
function performs simple regular expression pattern matching. The function accepts a string containing content to be searched and a regular expression pattern as defined by the Go standard library. The find
function returns a Lua table containing all the matches found in the provided string.
function vertical(ctx, domain)
local url = "https://" .. domain
local page, err = request({['url']=url})
if (err ~= nil and err ~= "") then
return
end
local matches = find(page, subdomainre)
if (matches == nil or #matches == 0) then
return
end
for i, sub in pairs(matches) do
new_name(ctx, sub)
end
end
Field Name | Data Type |
---|---|
content | string |
pattern | string |
The submatch
function performs simple regular expression pattern matching that supports submatches. The function accepts a string containing content to be searched and a regular expression pattern as defined by the Go standard library. The submatch
function returns a Lua table of tables, each containing the leftmost match found in the provided string and the submatches. The matches are in the expected order of the 1-based array (table) returned by the function.
function vertical(ctx, domain)
local url = "https://" .. domain
local page, err = request({['url']=url})
if (err ~= nil and err ~= "") then
return
end
-- Create the pattern that contains submatches
local matches = submatch(page, pattern)
if (matches == nil or #matches == 0) then
return
end
local first = matches[1]
-- Send the first submatch
if (first ~= nil and #first >=2 and first[2] ~= "") then
new_name(ctx, first[2])
end
end
Field Name | Data Type |
---|---|
content | string |
pattern | string |
The request
function performs HTTP(s) client requests for Amass data source scripts. The function returns the page content and an error value. The function accepts an options table that can include the fields shown below. The request
function will not execute faster than a rate limit identified by the set_rate_limit
function.
function vertical(ctx, domain)
local url = "https://" .. domain
local resp, err = request(ctx, {
method="POST",
data=body,
['url']=url,
headers={['Content-Type']="application/json"},
id=api["key"],
pass=api["secret"],
})
if (err ~= nil and err ~= "") then
return
end
-- Utilize the content provided in the response
end
Field Name | Data Type |
---|---|
ctx | UserData |
params | table |
The params
table has the following fields:
Field Name | Data Type |
---|---|
method | string |
data | string |
url | string |
headers | table |
id | string |
pass | string |
The scrape
function performs HTTP(s) client requests for Amass data source scripts. The body of the response is automatically checked for subdomain names that are in scope of the enumeration process. The function returns a boolean value indicating the success of the client request, and it also returns false
if no subdomain names were found in the body. The function accepts an options table that can include the fields shown below. The scrape
function will not execute faster than a rate limit identified by the set_rate_limit
function.
function vertical(ctx, domain)
local url = "https://" .. domain
local ok = scrape(ctx, {
['url']=url,
headers={['Accept']="text/*, text/html, text/html;level=1, */*"},
id=api["username"],
pass=api["password"],
})
end
Field Name | Data Type |
---|---|
method | string |
data | string |
url | string |
headers | table |
id | string |
pass | string |
The crawl
function performs HTTP(s) web crawling/spidering for Amass data source scripts. The body of the responses are automatically checked for subdomain names that are in scope of the enumeration process. The crawler will not follow more than max
links unless the provided value is 0
.
function vertical(ctx, domain)
local url = "https://" .. domain
crawl(ctx, url, 50)
end
Field Name | Data Type |
---|---|
ctx | UserData |
url | string |
max | number |
The new_name
function allows Amass data source scripts to submit a discovered FQDN. The fqdn
parameter is automatically checked against the enumeration scope.
function vertical(ctx, domain)
-- Discover subdomain names
new_name(ctx, fqdn)
end
Field Name | Data Type |
---|---|
ctx | UserData |
fqdn | string |
The send_names
function allows Amass data source scripts to submit content
to be checked for subdomain names that are in scope of the current enumeration process.
function vertical(ctx, domain)
-- Discover content containing subdomain names
send_names(ctx, content)
end
Field Name | Data Type |
---|---|
ctx | UserData |
content | string |
The associated
function allows Amass data source scripts to submit a discovered domain name that is associated with the domain name provided by the current enumeration process.
function horizontal(ctx, domain)
-- Discover domain names associated with the provided domain parameter
associated(ctx, domain, assoc)
end
Field Name | Data Type |
---|---|
ctx | UserData |
domain | string |
assoc | string |
The new_addr
function allows Amass data source scripts to submit a discovered IP address. The fqdn
parameter is automatically checked against the enumeration scope.
function vertical(ctx, domain)
-- Discover subdomain names and associated IP addresses
new_addr(ctx, addr, fqdn)
end
Field Name | Data Type |
---|---|
ctx | UserData |
addr | string |
fqdn | string |
The new_asn
function allows Amass data source scripts to submit discovered autonomous system information related to the provided addr
or asn
parameters. The function accepts a table of return values that is defined below.
function asn(ctx, addr, asn)
-- Send back the related AS information
new_asn(ctx, {
['addr']=addr,
['asn']=tonumber(asn),
['prefix']=cidr,
['cc']="US",
['registry']="ARIN",
['desc']=desc,
['netblocks']={cidr},
})
end
Field Name | Data Type |
---|---|
addr | string |
asn | number |
prefix | string |
cc | string |
registry | string |
desc | string |
netblocks | table |
The resolve
function allows Amass data source scripts to perform a DNS query of resource records for the provided name
and type
.
function subdomain(ctx, name, domain, times)
if times ~= 1 then
return
end
local records, err = resolve(ctx, name, "NS", false)
if (err ~= nil and err ~= "") then
return
end
for _, record in pairs(records) do
new_name(ctx, record.rrname)
end
end
Field Name | Data Type |
---|---|
ctx | UserData |
name | string |
type | string |
detection | bool (opt) |
The resolve
function returns a Lua table of tables, each containing a DNS resource record name, type, and data. The field names are shown below:
Field Name | Data Type |
---|---|
rrname | string |
rrtype | number |
rrdata | string |
The socket module provides Amass data source scripts with access to basic socket communication functionality.
The connect
function allows Amass data source scripts to establish a TCP connection to the provided host
and port
. The connect
function returns a connection
data type on success and an error string on failure. The returned connection data type needs to be closed to release resources.
function vertical(ctx, domain)
local conn, err = socket.connect(ctx, "owasp.org", 80, "tcp")
if (err ~= nil and err ~= "") then
log(ctx, err)
return
end
conn:close()
end
Field Name | Data Type |
---|---|
ctx | UserData |
host | string |
port | number |
protocol | string |
The connection
data type represents a TCP connection and provides methods for reading data, writing data, and closing the connection.
The recv
method reads at least num
bytes from the receiver connection data type. The method returns a string of the read bytes on success and an error string on failure.
function vertical(ctx, domain)
local conn, err = socket.connect(ctx, "owasp.org", 80, "tcp")
if (err ~= nil and err ~= "") then
log(ctx, err)
return
end
local data
data, err = conn:recv(32)
if (err ~= nil and err ~= "") then
log(ctx, err)
conn:close()
return
end
use_data(data)
conn:close()
end
Field Name | Data Type |
---|---|
num | number |
The recv_all
method reads all bytes from the receiver connection until the EOF. The method returns a string of the read bytes on success and an error string on failure.
function vertical(ctx, domain)
local conn, err = socket.connect(ctx, "owasp.org", 80, "tcp")
if (err ~= nil and err ~= "") then
log(ctx, err)
return
end
local data
data, err = conn:recv_all()
if (err ~= nil and err ~= "") then
log(ctx, err)
conn:close()
return
end
use_data(data)
conn:close()
end
The send
method sends the provides data
to the receiver connection. The method returns the number of bytes sent on success and an error string on failure.
function asn(ctx, addr, asn)
local conn, err = socket.connect(ctx, "whois.owasp.org", 43, "tcp")
if (err ~= nil and err ~= "") then
log(ctx, err)
return
end
local n
n, err = conn:send("prefix " .. tostring(asn) .. "\n")
if (err ~= nil and err ~= "") then
log(ctx, err)
conn:close()
return
end
read_netblocks(conn)
conn:close()
end
Field Name | Data Type |
---|---|
data | string |
The close
method releases resources used and closes the receiver network connection.
function asn(ctx, addr, asn)
local conn, err = socket.connect(ctx, "whois.owasp.org", 43, "tcp")
if (err ~= nil and err ~= "") then
log(ctx, err)
return
end
conn:close()
end