mirror of
https://github.com/owasp-amass/amass.git
synced 2026-10-12 02:20:44 +00:00
433 lines
11 KiB
Go
433 lines
11 KiB
Go
// Copyright © by Jeff Foley 2017-2023. All rights reserved.
|
|
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package scripting
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
|
|
"github.com/owasp-amass/amass/v3/config"
|
|
"github.com/owasp-amass/amass/v3/net/dns"
|
|
"github.com/owasp-amass/amass/v3/requests"
|
|
"github.com/owasp-amass/amass/v3/systems"
|
|
"github.com/caffix/service"
|
|
luaurl "github.com/cjoudrey/gluaurl"
|
|
lua "github.com/yuin/gopher-lua"
|
|
luajson "layeh.com/gopher-json"
|
|
)
|
|
|
|
// Script callback functions
|
|
type callbacks struct {
|
|
Start lua.LValue
|
|
Stop lua.LValue
|
|
Check lua.LValue
|
|
Vertical lua.LValue
|
|
Horizontal lua.LValue
|
|
Address lua.LValue
|
|
Asn lua.LValue
|
|
Resolved lua.LValue
|
|
Subdomain lua.LValue
|
|
}
|
|
|
|
// Script is the Service that handles access to the Script data source.
|
|
type Script struct {
|
|
service.BaseService
|
|
start chan struct{}
|
|
startRet chan error
|
|
stop chan struct{}
|
|
SourceType string
|
|
sys systems.System
|
|
luaState *lua.LState
|
|
cbs *callbacks
|
|
subre *regexp.Regexp
|
|
seconds int
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
// NewScript returns the object initialized, but not yet started.
|
|
func NewScript(script string, sys systems.System) *Script {
|
|
re, err := regexp.Compile(dns.AnySubdomainRegexString())
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
s := &Script{
|
|
start: make(chan struct{}, 1),
|
|
startRet: make(chan error, 1),
|
|
stop: make(chan struct{}, 1),
|
|
sys: sys,
|
|
subre: re,
|
|
}
|
|
s.ctx, s.cancel = context.WithCancel(context.Background())
|
|
L := s.newLuaState(sys.Config())
|
|
|
|
// Load the script
|
|
if err := L.DoString(script); err != nil {
|
|
sys.Config().Log.Printf("Script: Failed to load the %s script: %v", script, err)
|
|
return nil
|
|
}
|
|
// Pull the script name from the script
|
|
name, err := s.scriptName()
|
|
if err != nil {
|
|
sys.Config().Log.Printf("Script: Failed to obtain the %s script name: %v", script, err)
|
|
return nil
|
|
}
|
|
// Pull the script type from the script
|
|
s.SourceType, err = s.scriptType()
|
|
if err != nil {
|
|
sys.Config().Log.Printf("Script: Failed to obtain the %s script type: %v", script, err)
|
|
return nil
|
|
}
|
|
|
|
s.BaseService = *service.NewBaseService(s, name)
|
|
s.assignCallbacks()
|
|
go s.requests()
|
|
return s
|
|
}
|
|
|
|
// Setup the Lua state with desired constraints and access to necessary functionality.
|
|
func (s *Script) newLuaState(cfg *config.Config) *lua.LState {
|
|
L := lua.NewState(lua.Options{
|
|
CallStackSize: 120,
|
|
MinimizeStackMemory: true,
|
|
RegistrySize: 32,
|
|
RegistryMaxSize: 1024 * 100,
|
|
RegistryGrowStep: 32,
|
|
})
|
|
s.luaState = L
|
|
|
|
registerSocketType(L)
|
|
L.PreloadModule("url", luaurl.Loader)
|
|
L.PreloadModule("json", luajson.Loader)
|
|
L.SetGlobal("config", L.NewFunction(s.config))
|
|
L.SetGlobal("datasrc_config", L.NewFunction(s.dataSourceConfig))
|
|
L.SetGlobal("brute_wordlist", L.NewFunction(s.bruteWordlist))
|
|
L.SetGlobal("alt_wordlist", L.NewFunction(s.altWordlist))
|
|
L.SetGlobal("log", L.NewFunction(s.log))
|
|
L.SetGlobal("find", L.NewFunction(s.find))
|
|
L.SetGlobal("submatch", L.NewFunction(s.submatch))
|
|
L.SetGlobal("mtime", L.NewFunction(s.modDateTime))
|
|
L.SetGlobal("new_name", L.NewFunction(s.newName))
|
|
L.SetGlobal("send_names", L.NewFunction(s.sendNames))
|
|
L.SetGlobal("send_dns_records", L.NewFunction(s.sendDNSRecords))
|
|
L.SetGlobal("new_addr", L.NewFunction(s.newAddr))
|
|
L.SetGlobal("new_asn", L.NewFunction(s.newASN))
|
|
L.SetGlobal("associated", L.NewFunction(s.associated))
|
|
L.SetGlobal("in_scope", L.NewFunction(s.inScope))
|
|
L.SetGlobal("request", L.NewFunction(s.request))
|
|
L.SetGlobal("scrape", L.NewFunction(s.scrape))
|
|
L.SetGlobal("crawl", L.NewFunction(s.crawl))
|
|
L.SetGlobal("resolve", L.NewFunction(s.resolve))
|
|
L.SetGlobal("reverse_sweep", L.NewFunction(s.reverseSweep))
|
|
L.SetGlobal("zone_walk", L.NewFunction(s.zoneWalk))
|
|
L.SetGlobal("zone_transfer", L.NewFunction(s.wrapZoneTransfer))
|
|
L.SetGlobal("output_dir", L.NewFunction(s.outputdir))
|
|
L.SetGlobal("set_rate_limit", L.NewFunction(s.setRateLimit))
|
|
L.SetGlobal("check_rate_limit", L.NewFunction(s.checkRateLimit))
|
|
L.SetGlobal("subdomain_regex", lua.LString(dns.AnySubdomainRegexString()))
|
|
return L
|
|
}
|
|
|
|
// Save references to the script functions that serve as callbacks for Amass events.
|
|
func (s *Script) assignCallbacks() {
|
|
L := s.luaState
|
|
|
|
s.cbs = &callbacks{
|
|
Start: L.GetGlobal("start"),
|
|
Stop: L.GetGlobal("stop"),
|
|
Check: L.GetGlobal("check"),
|
|
Vertical: L.GetGlobal("vertical"),
|
|
Horizontal: L.GetGlobal("horizontal"),
|
|
Address: L.GetGlobal("address"),
|
|
Asn: L.GetGlobal("asn"),
|
|
Resolved: L.GetGlobal("resolved"),
|
|
Subdomain: L.GetGlobal("subdomain"),
|
|
}
|
|
}
|
|
|
|
// Acquires the script name of the script by accessing the global variable.
|
|
func (s *Script) scriptName() (string, error) {
|
|
lv := s.luaState.GetGlobal("name")
|
|
|
|
if lv.Type() == lua.LTNil {
|
|
return "", errors.New("Script does not contain the 'name' global")
|
|
}
|
|
if str, ok := lv.(lua.LString); ok {
|
|
return string(str), nil
|
|
}
|
|
return "", errors.New("the script global 'name' is not a string")
|
|
}
|
|
|
|
// Acquires the script type of the script by accessing the global variable.
|
|
func (s *Script) scriptType() (string, error) {
|
|
lv := s.luaState.GetGlobal("type")
|
|
|
|
if lv.Type() == lua.LTNil {
|
|
return "", errors.New("Script does not contain the 'type' global")
|
|
}
|
|
if str, ok := lv.(lua.LString); ok {
|
|
return string(str), nil
|
|
}
|
|
return "", errors.New("the script global 'type' is not a string")
|
|
}
|
|
|
|
// Description implements the Service interface.
|
|
func (s *Script) Description() string {
|
|
return s.SourceType
|
|
}
|
|
|
|
// OnStart implements the Service interface.
|
|
func (s *Script) OnStart() error {
|
|
s.start <- struct{}{}
|
|
return <-s.startRet
|
|
}
|
|
|
|
// OnStop implements the Service interface.
|
|
func (s *Script) OnStop() error {
|
|
s.stop <- struct{}{}
|
|
return nil
|
|
}
|
|
|
|
func (s *Script) requests() {
|
|
for {
|
|
select {
|
|
case <-s.Done():
|
|
return
|
|
case <-s.ctx.Done():
|
|
return
|
|
case <-s.start:
|
|
s.startScript()
|
|
case <-s.stop:
|
|
s.stopScript()
|
|
case in := <-s.Input():
|
|
s.dispatch(in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Script) startScript() {
|
|
if L := s.luaState; s.cbs.Start.Type() != lua.LTNil {
|
|
err := L.CallByParam(lua.P{
|
|
Fn: s.cbs.Start,
|
|
NRet: 0,
|
|
Protect: true,
|
|
})
|
|
if err != nil {
|
|
s.sys.Config().Log.Printf("%s: start callback: %v", s.String(), err)
|
|
s.startRet <- err
|
|
return
|
|
}
|
|
}
|
|
|
|
if s.seconds > 0 {
|
|
s.SetRateLimit(1)
|
|
}
|
|
|
|
s.startRet <- s.checkConfig()
|
|
}
|
|
|
|
func (s *Script) checkConfig() error {
|
|
L := s.luaState
|
|
|
|
if s.cbs.Check.Type() == lua.LTNil {
|
|
return nil
|
|
}
|
|
|
|
err := L.CallByParam(lua.P{
|
|
Fn: s.cbs.Check,
|
|
NRet: 1,
|
|
Protect: true,
|
|
})
|
|
if err != nil {
|
|
estr := fmt.Sprintf("%s: check callback: %v", s.String(), err)
|
|
|
|
s.sys.Config().Log.Print(estr)
|
|
return errors.New(estr)
|
|
}
|
|
|
|
ret := L.Get(-1)
|
|
L.Pop(1)
|
|
|
|
passed, ok := ret.(lua.LBool)
|
|
if ok && bool(passed) {
|
|
return nil
|
|
}
|
|
|
|
estr := fmt.Sprintf("%s: check callback failed for the configuration", s.String())
|
|
s.sys.Config().Log.Print(estr)
|
|
return errors.New(estr)
|
|
}
|
|
|
|
func (s *Script) stopScript() {
|
|
s.cancel()
|
|
|
|
if L := s.luaState; s.cbs.Stop.Type() != lua.LTNil {
|
|
err := L.CallByParam(lua.P{
|
|
Fn: s.cbs.Stop,
|
|
NRet: 0,
|
|
Protect: true,
|
|
})
|
|
if err != nil {
|
|
err = fmt.Errorf("%s: stop callback: %v", s.String(), err)
|
|
s.sys.Config().Log.Print(err.Error())
|
|
}
|
|
}
|
|
|
|
s.luaState.Close()
|
|
s.luaState = nil
|
|
}
|
|
|
|
func (s *Script) dispatch(in interface{}) {
|
|
switch req := in.(type) {
|
|
case *requests.DNSRequest:
|
|
if s.cbs.Vertical.Type() != lua.LTNil && req != nil && req.Domain != "" {
|
|
s.CheckRateLimit()
|
|
s.dnsRequest(s.ctx, req)
|
|
}
|
|
case *requests.ResolvedRequest:
|
|
if s.cbs.Resolved.Type() != lua.LTNil && req != nil && req.Name != "" && len(req.Records) > 0 {
|
|
s.CheckRateLimit()
|
|
s.resolvedRequest(s.ctx, req)
|
|
}
|
|
case *requests.SubdomainRequest:
|
|
if s.cbs.Subdomain.Type() != lua.LTNil && req != nil && req.Name != "" {
|
|
s.CheckRateLimit()
|
|
s.subdomainRequest(s.ctx, req)
|
|
}
|
|
case *requests.AddrRequest:
|
|
if s.cbs.Address.Type() != lua.LTNil && req != nil && req.Address != "" {
|
|
s.CheckRateLimit()
|
|
s.addrRequest(s.ctx, req)
|
|
}
|
|
case *requests.ASNRequest:
|
|
if s.cbs.Asn.Type() != lua.LTNil && req != nil && (req.Address != "" || req.ASN != 0) {
|
|
s.CheckRateLimit()
|
|
s.asnRequest(s.ctx, req)
|
|
}
|
|
case *requests.WhoisRequest:
|
|
if s.cbs.Horizontal.Type() != lua.LTNil {
|
|
s.CheckRateLimit()
|
|
s.whoisRequest(s.ctx, req)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Script) dnsRequest(ctx context.Context, req *requests.DNSRequest) {
|
|
L := s.luaState
|
|
|
|
if contextExpired(ctx) {
|
|
return
|
|
}
|
|
|
|
s.sys.Config().Log.Printf("Querying %s for %s subdomains", s.String(), req.Domain)
|
|
|
|
err := L.CallByParam(lua.P{
|
|
Fn: s.cbs.Vertical,
|
|
NRet: 0,
|
|
Protect: true,
|
|
}, s.contextToUserData(ctx), lua.LString(req.Domain))
|
|
if err != nil {
|
|
s.sys.Config().Log.Printf("%s: vertical callback: %v", s.String(), err)
|
|
}
|
|
}
|
|
|
|
func (s *Script) resolvedRequest(ctx context.Context, req *requests.ResolvedRequest) {
|
|
L := s.luaState
|
|
|
|
if contextExpired(ctx) {
|
|
return
|
|
}
|
|
|
|
records := L.NewTable()
|
|
for _, rec := range req.Records {
|
|
tb := L.NewTable()
|
|
|
|
tb.RawSetString("rrname", lua.LString(rec.Name))
|
|
tb.RawSetString("rrtype", lua.LNumber(rec.Type))
|
|
tb.RawSetString("rrdata", lua.LString(rec.Data))
|
|
records.Append(tb)
|
|
}
|
|
|
|
err := L.CallByParam(lua.P{
|
|
Fn: s.cbs.Resolved,
|
|
NRet: 0,
|
|
Protect: true,
|
|
}, s.contextToUserData(ctx), lua.LString(req.Name), lua.LString(req.Domain), records)
|
|
if err != nil {
|
|
s.sys.Config().Log.Printf("%s: resolved callback: %v", s.String(), err)
|
|
}
|
|
}
|
|
|
|
func (s *Script) subdomainRequest(ctx context.Context, req *requests.SubdomainRequest) {
|
|
L := s.luaState
|
|
|
|
if contextExpired(ctx) {
|
|
return
|
|
}
|
|
|
|
err := L.CallByParam(lua.P{
|
|
Fn: s.cbs.Subdomain,
|
|
NRet: 0,
|
|
Protect: true,
|
|
}, s.contextToUserData(ctx), lua.LString(req.Name), lua.LString(req.Domain), lua.LNumber(req.Times))
|
|
if err != nil {
|
|
s.sys.Config().Log.Printf("%s: subdomain callback: %v", s.String(), err)
|
|
}
|
|
}
|
|
|
|
func (s *Script) addrRequest(ctx context.Context, req *requests.AddrRequest) {
|
|
L := s.luaState
|
|
|
|
if contextExpired(ctx) {
|
|
return
|
|
}
|
|
|
|
err := L.CallByParam(lua.P{
|
|
Fn: s.cbs.Address,
|
|
NRet: 0,
|
|
Protect: true,
|
|
}, s.contextToUserData(ctx), lua.LString(req.Address))
|
|
if err != nil {
|
|
s.sys.Config().Log.Printf("%s: address callback: %v", s.String(), err)
|
|
}
|
|
}
|
|
|
|
func (s *Script) asnRequest(ctx context.Context, req *requests.ASNRequest) {
|
|
L := s.luaState
|
|
|
|
if contextExpired(ctx) {
|
|
return
|
|
}
|
|
|
|
err := L.CallByParam(lua.P{
|
|
Fn: s.cbs.Asn,
|
|
NRet: 0,
|
|
Protect: true,
|
|
}, s.contextToUserData(ctx), lua.LString(req.Address), lua.LNumber(req.ASN))
|
|
if err != nil {
|
|
s.sys.Config().Log.Printf("%s: asn callback: %v", s.String(), err)
|
|
}
|
|
}
|
|
|
|
func (s *Script) whoisRequest(ctx context.Context, req *requests.WhoisRequest) {
|
|
L := s.luaState
|
|
|
|
if contextExpired(ctx) {
|
|
return
|
|
}
|
|
|
|
err := L.CallByParam(lua.P{
|
|
Fn: s.cbs.Horizontal,
|
|
NRet: 0,
|
|
Protect: true,
|
|
}, s.contextToUserData(ctx), lua.LString(req.Domain))
|
|
if err != nil {
|
|
s.sys.Config().Log.Printf("%s: horizontal callback: %v", s.String(), err)
|
|
}
|
|
}
|