From 6eb53b80038edf4768a798736621a5fc3674f0b0 Mon Sep 17 00:00:00 2001 From: caffix Date: Thu, 17 Oct 2024 21:12:41 -0400 Subject: [PATCH] initial commit --- cmd/oam_assoc/main.go | 199 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 cmd/oam_assoc/main.go diff --git a/cmd/oam_assoc/main.go b/cmd/oam_assoc/main.go new file mode 100644 index 000000000..66c2d019d --- /dev/null +++ b/cmd/oam_assoc/main.go @@ -0,0 +1,199 @@ +// Copyright © by Jeff Foley 2017-2024. 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 + +// oam_track: Analyze collected OAM data to identify newly discovered assets +// +// +----------------------------------------------------------------------------+ +// | ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ OWASP Amass ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ | +// +----------------------------------------------------------------------------+ +// | .+++:. : .+++. | +// | +W@@@@@@8 &+W@# o8W8: +W@@@@@@#. oW@@@W#+ | +// | &@#+ .o@##. .@@@o@W.o@@o :@@#&W8o .@#: .:oW+ .@#+++&#& | +// | +@& &@& #@8 +@W@&8@+ :@W. +@8 +@: .@8 | +// | 8@ @@ 8@o 8@8 WW .@W W@+ .@W. o@#: | +// | WW &@o &@: o@+ o@+ #@. 8@o +W@#+. +W@8: | +// | #@ :@W &@+ &@+ @8 :@o o@o oW@@W+ oW@8 | +// | o@+ @@& &@+ &@+ #@ &@. .W@W .+#@& o@W. | +// | WW +@W@8. &@+ :& o@+ #@ :@W&@& &@: .. :@o | +// | :@W: o@# +Wo &@+ :W: +@W&o++o@W. &@& 8@#o+&@W. #@: o@+ | +// | :W@@WWWW@@8 + :&W@@@@& &W .o#@@W&. :W@WWW@@& | +// | +o&&&&+. +oooo. | +// +----------------------------------------------------------------------------+ +package main + +import ( + "bytes" + "flag" + "io" + "os" + "path" + "time" + + "github.com/caffix/stringset" + "github.com/fatih/color" + "github.com/owasp-amass/amass/v4/config" + "github.com/owasp-amass/amass/v4/utils" + "github.com/owasp-amass/amass/v4/utils/afmt" + assetdb "github.com/owasp-amass/asset-db" + oam "github.com/owasp-amass/open-asset-model" + "github.com/owasp-amass/open-asset-model/domain" +) + +const ( + timeFormat = "01/02 15:04:05 2006 MST" + usageMsg = "[options] [-since '" + timeFormat + "'] " + "-d domain" +) + +type trackArgs struct { + Domains *stringset.Set + Since string + Options struct { + NoColor bool + Silent bool + } + Filepaths struct { + ConfigFile string + Directory string + Domains string + } +} + +func main() { + var args trackArgs + var help1, help2 bool + trackCommand := flag.NewFlagSet("track", flag.ContinueOnError) + + args.Domains = stringset.New() + defer args.Domains.Close() + + trackBuf := new(bytes.Buffer) + trackCommand.SetOutput(trackBuf) + + trackCommand.BoolVar(&help1, "h", false, "Show the program usage message") + trackCommand.BoolVar(&help2, "help", false, "Show the program usage message") + trackCommand.Var(args.Domains, "d", "Domain names separated by commas (can be used multiple times)") + trackCommand.StringVar(&args.Since, "since", "", "Exclude all assets discovered before (format: "+timeFormat+")") + trackCommand.BoolVar(&args.Options.NoColor, "nocolor", false, "Disable colorized output") + trackCommand.BoolVar(&args.Options.Silent, "silent", false, "Disable all output during execution") + trackCommand.StringVar(&args.Filepaths.ConfigFile, "config", "", "Path to the YAML configuration file") + trackCommand.StringVar(&args.Filepaths.Directory, "dir", "", "Path to the directory containing the graph database") + trackCommand.StringVar(&args.Filepaths.Domains, "df", "", "Path to a file providing registered domain names") + + var usage = func() { + afmt.G.Fprintf(color.Error, "Usage: %s %s\n\n", path.Base(os.Args[0]), usageMsg) + trackCommand.PrintDefaults() + afmt.G.Fprintln(color.Error, trackBuf.String()) + } + + if len(os.Args) < 2 { + usage() + return + } + if err := trackCommand.Parse(os.Args[1:]); err != nil { + afmt.R.Fprintf(color.Error, "%v\n", err) + os.Exit(1) + } + if help1 || help2 { + usage() + return + } + if args.Options.NoColor { + color.NoColor = true + } + if args.Options.Silent { + color.Output = io.Discard + color.Error = io.Discard + } + if args.Filepaths.Domains != "" { + list, err := config.GetListFromFile(args.Filepaths.Domains) + if err != nil { + afmt.R.Fprintf(color.Error, "Failed to parse the domain names file: %v\n", err) + os.Exit(1) + } + args.Domains.InsertMany(list...) + } + if args.Domains.Len() == 0 { + afmt.R.Fprintln(color.Error, "No root domain names were provided") + os.Exit(1) + } + + var err error + var start time.Time + if args.Since != "" { + start, err = time.Parse(timeFormat, args.Since) + if err != nil { + afmt.R.Fprintf(color.Error, "%s is not in the correct format: %s\n", args.Since, timeFormat) + os.Exit(1) + } + } + + cfg := config.NewConfig() + // Check if a configuration file was provided, and if so, load the settings + if err := config.AcquireConfig(args.Filepaths.Directory, args.Filepaths.ConfigFile, cfg); err == nil { + if args.Filepaths.Directory == "" { + args.Filepaths.Directory = cfg.Dir + } + if args.Domains.Len() == 0 { + args.Domains.InsertMany(cfg.Domains()...) + } + } else if args.Filepaths.ConfigFile != "" { + afmt.R.Fprintf(color.Error, "Failed to load the configuration file: %v\n", err) + os.Exit(1) + } + // Connect with the graph database containing the enumeration data + db := utils.OpenGraphDatabase(cfg) + if db == nil { + afmt.R.Fprintln(color.Error, "Failed to connect with the database") + os.Exit(1) + } + + for _, name := range getNewNames(args.Domains.Slice(), start, db) { + afmt.G.Fprintln(color.Output, name) + } +} + +func getNewNames(domains []string, since time.Time, db *assetdb.AssetDB) []string { + if len(domains) == 0 { + return []string{} + } + + var fqdns []oam.Asset + for _, d := range domains { + fqdns = append(fqdns, &domain.FQDN{Name: d}) + } + + if !since.IsZero() { + since = since.UTC() + } + + assets, err := db.FindByScope(fqdns, since) + if err != nil { + return []string{} + } + + if since.IsZero() { + var latest time.Time + + for _, a := range assets { + if _, ok := a.Asset.(*domain.FQDN); ok && a.LastSeen.After(latest) { + latest = a.LastSeen + } + } + + since = latest.Truncate(24 * time.Hour) + } + + res := stringset.New() + defer res.Close() + + for _, a := range assets { + if n, ok := a.Asset.(*domain.FQDN); ok && !res.Has(n.Name) && + (a.CreatedAt.Equal(since) || a.CreatedAt.After(since)) && + (a.LastSeen.Equal(since) || a.LastSeen.After(since)) { + res.Insert(n.Name) + } + } + + return res.Slice() +}