diff --git a/README.md b/README.md
@@ -152,6 +152,30 @@ on ProjectDiscovery (https://cloud.projectdiscovery.io/) and retrieve an API key
Then you should set this key in your .bashrc (or equivalent) to VULNX_API_KEY.
+#### Credential Leak Checking (Leaker)
+
+The `osint` subcommand can check for credential leaks using the integrated
+[leaker](https://github.com/vflame6/leaker) library.
+
+Some leaker sources (like LeakCheck) require API keys. Create a provider config file:
+
+```yaml
+# ~/.config/leaker/provider-config.yml
+leakcheck: [your-api-key-here]
+```
+
+Set the config path via environment variable:
+
+```bash
+export LEAKER_PROVIDER_CONFIG=~/.config/leaker/provider-config.yml
+```
+
+Usage:
+
+```bash
+./yellow osint -d target.com --emails /path/to/emails.txt
+```
+
#### Running fingerprinting
If you don't want to scan the whole website, but just run the fingerprint and retrieve the CVEs,
diff --git a/cli/parser.go b/cli/parser.go
@@ -157,6 +157,7 @@ func GetParser(opts *StandardOptions) *cobra.Command {
osintOpts.SetProxy(opts.Proxy)
osintOpts.SetDryRun(opts.RunDry)
osintOpts.SetRateLimit(opts.RateLimit)
+ osintOpts.SetEmailsFile(opts.EmailsFilePath)
// if used create subcommand, put the results in scans
if helper.Exists(opts.OutName + "/scans/") {
@@ -215,6 +216,7 @@ func GetParser(opts *StandardOptions) *cobra.Command {
osintCmd.Flags().BoolVarP(&opts.RunDry, "dry", "", defaults.RunDry, "Run a dry-run (test mode)")
osintCmd.Flags().Int32VarP(&opts.RateLimit, "rate-limit", "r", defaults.RateLimit, "Requests rate-limit (used for the tools supporting it. Other will prompt a warning msg)")
osintCmd.Flags().StringVarP(&opts.TargetFilePath, "file", "f", defaults.TargetFilePath, "File containing list of targets (should be a list of IP Addresses or domains)")
+ osintCmd.Flags().StringVarP(&opts.EmailsFilePath, "emails", "e", defaults.EmailsFilePath, "File containing list of emails for credential leak checking")
rootCmd.AddCommand(scanCmd)
scanCmd.Flags().StringVarP(&opts.OutName, "dir-name", "d", defaults.OutName, "Outfile directory name (if no target-file is specified, will also be target domain)")
diff --git a/cli/types.go b/cli/types.go
@@ -27,6 +27,9 @@ type StandardOptions struct {
// Disable Gobuster run (we dont need it sometimes yk)
NoGobuster bool
+
+ // File containing list of emails for credential leak checking
+ EmailsFilePath string
}
func GetDefaultOptions() StandardOptions {
@@ -39,6 +42,7 @@ func GetDefaultOptions() StandardOptions {
WordlistPath: "/usr/share/wordlists/seclists/Discovery/Web-Content/common.txt",
TargetFilePath: "",
NoGobuster: false,
+ EmailsFilePath: "",
}
return opts
diff --git a/go.mod b/go.mod
@@ -1,6 +1,6 @@
module evil.djnn.sh/djnn/yellow
-go 1.25
+go 1.25.5
require (
github.com/OJ/gobuster/v3 v3.8.2
@@ -14,6 +14,7 @@ require (
github.com/shadowscatcher/shodan v1.0.8
github.com/spf13/cobra v1.10.2
github.com/spiral-sec/assetfinder v0.2.0
+ github.com/vflame6/leaker v1.0.5
)
require (
diff --git a/go.sum b/go.sum
@@ -453,6 +453,8 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
+github.com/vflame6/leaker v1.0.5 h1:Ybs3wAkyj3G+1OlUSKpmLRubxcomMn77MmapuGF9iJs=
+github.com/vflame6/leaker v1.0.5/go.mod h1:uGvnC7PqZvNDBH1NqV14lN6Zit62UtZ/qYZjQ4uji4U=
github.com/vulncheck-oss/go-exploit v1.53.2 h1:zdoUyUXeZcPtkjOWzXc++Jq6w7V42wDT6hY/gz798U8=
github.com/vulncheck-oss/go-exploit v1.53.2/go.mod h1:7lgRF0lNlXCr1QZ4+bnQAMgUbtS64XIw6iN/tZKF/yw=
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
diff --git a/osint/leaker.go b/osint/leaker.go
@@ -0,0 +1,90 @@
+package osint
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "time"
+
+ "github.com/vflame6/leaker/runner"
+)
+
+type Leaker struct {
+ outfile string
+ emailsFile string
+ proxy string
+ providerConfig string
+}
+
+func (l *Leaker) Info(target string) {
+ fmt.Printf("[+] Running leaker for credential leak checking (target: %s)\n", target)
+ if l.emailsFile != "" {
+ fmt.Printf(" Emails file: %s\n", l.emailsFile)
+ }
+ if l.providerConfig != "" {
+ fmt.Printf(" Provider config: %s\n", l.providerConfig)
+ }
+}
+
+func (l *Leaker) Configure(c any) {
+ cfg := c.(map[string]any)
+ l.outfile = cfg["outfile"].(string)
+ l.emailsFile = cfg["emailsFile"].(string)
+ l.proxy = cfg["proxy"].(string)
+ l.providerConfig = os.Getenv("LEAKER_PROVIDER_CONFIG")
+}
+
+func (l *Leaker) ShouldRun() bool {
+ if l.emailsFile == "" {
+ fmt.Println("[!] Leaker: no emails file provided, skipping")
+ return false
+ }
+
+ if _, err := os.Stat(l.emailsFile); os.IsNotExist(err) {
+ fmt.Printf("[!] Leaker: emails file %s does not exist, skipping\n", l.emailsFile)
+ return false
+ }
+
+ return true
+}
+
+func (l *Leaker) Run(target string) {
+ emailsReader, err := os.Open(l.emailsFile)
+ if err != nil {
+ fmt.Printf("[!] Leaker: failed to open emails file: %v\n", err)
+ return
+ }
+ defer emailsReader.Close()
+
+ outFile, err := os.Create(l.outfile)
+ if err != nil {
+ fmt.Printf("[!] Leaker: failed to create output file: %v\n", err)
+ return
+ }
+ defer outFile.Close()
+
+ opts := &runner.Options{
+ Timeout: 60 * time.Second,
+ OutputFile: l.outfile,
+ ProviderConfig: l.providerConfig,
+ Quiet: true,
+ }
+
+ if l.proxy != "" {
+ opts.Proxy = l.proxy
+ }
+
+ r, err := runner.NewRunner(opts)
+ if err != nil {
+ fmt.Printf("[!] Leaker: failed to create runner: %v\n", err)
+ return
+ }
+
+ err = r.EnumerateMultipleEmails(emailsReader, []io.Writer{outFile})
+ if err != nil {
+ fmt.Printf("[!] Leaker: enumeration failed: %v\n", err)
+ return
+ }
+
+ fmt.Printf("[OSINT %s] Leaker results are stored in %s\n", target, l.outfile)
+}
diff --git a/osint/run.go b/osint/run.go
@@ -12,11 +12,12 @@ import (
)
type OsintOpts struct {
- domain string
- scanPath string
- proxy string
- dryRun bool
- rateLimit int32
+ domain string
+ scanPath string
+ proxy string
+ dryRun bool
+ rateLimit int32
+ emailsFile string
}
func (opts *OsintOpts) SetRateLimit(data int32) {
@@ -39,6 +40,10 @@ func (opts *OsintOpts) SetDryRun(data bool) {
opts.dryRun = data
}
+func (opts *OsintOpts) SetEmailsFile(data string) {
+ opts.emailsFile = data
+}
+
func (opts OsintOpts) RunCleanup() {
println("[+] cleaning up...")
@@ -140,6 +145,25 @@ func (opts OsintOpts) runDnsx() {
}
}
+func (opts OsintOpts) runLeaker() {
+ leaker := Leaker{}
+ leakerCfg := make(map[string]any)
+
+ leakerOutfile := fmt.Sprintf("%s/leaks.txt", opts.scanPath)
+ leakerCfg["outfile"] = leakerOutfile
+ leakerCfg["emailsFile"] = opts.emailsFile
+ leakerCfg["proxy"] = opts.proxy
+
+ leaker.Configure(leakerCfg)
+ leaker.Info(opts.domain)
+
+ if opts.dryRun || !leaker.ShouldRun() {
+ return
+ }
+
+ leaker.Run(opts.domain)
+}
+
func (opts OsintOpts) Run() {
fmt.Printf("\n[OSINT] domain: %s\n\n", opts.domain)
@@ -147,6 +171,7 @@ func (opts OsintOpts) Run() {
opts.runSubfinder()
opts.runAssetfinder()
opts.runDnsx()
+ opts.runLeaker()
asfFilepath := fmt.Sprintf("%s/assetfinder.txt", opts.scanPath)
dnsxFilepath := fmt.Sprintf("%s/dnsx.json", opts.scanPath)
| | | | | | |