yellow.git


yellow

~ opinionated web scanning pipeline for pentesters in a hurry
git clone https://github.com/djnnvx/yellow.git
log | files | refs | archive | README | LICENSE

commit ee5620ddf6196c356c9df109fd825f07158fa2be
parent 46393e84d281cd9b7d17c8650cbd6c86b50c33b6
Author: djnn <[email protected]>
Date: Sun, 8 Mar 2026 20:31:12 +0900

fix(git): conflicts

Diffstat:
MREADME.md | 34++++++++++++++++++++++++++++++++++
Mcli/parser.go | 2++
Mcli/types.go | 4++++
Mgo.mod | 3++-
Mgo.sum | 2++
Aosint/leaker.go | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mosint/run.go | 35++++++++++++++++++++++++++++++-----

7 files changed, 164 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
@@ -135,6 +135,16 @@ cat *.gnmap | grep -i "open/tcp" | cut -d " " -f2 | sort -u > djnn.sh/scans/web-
 ./yellow scan -d djnn.sh/scans/infra --file djnn.sh/scans/web-targets.txt
 ```
 
+#### Running port scans:
+
+You can run a TCP port scan with service fingerprinting as part of the scan command:
+
+```bash
+./yellow scan -d djnn.sh --port-scan
+# or with custom ports
+./yellow scan -d djnn.sh --port-scan --ports "22,80,443,8080-8090"
+```
+
 #### Filter inactive web domains from a list of domains:
 
 The `osint` subcommand is nice, but as it retrieves historical domains, it means there are
@@ -152,6 +162,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
@@ -159,6 +159,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/") {
@@ -217,6 +218,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
@@ -33,6 +33,9 @@ type StandardOptions struct {
 
 	// Ports to scan (comma-separated or ranges, e.g. "22,80,443,8000-9000")
 	Ports string
+
+	// File containing list of emails for credential leak checking
+	EmailsFilePath string
 }
 
 func GetDefaultOptions() StandardOptions {
@@ -47,6 +50,7 @@ func GetDefaultOptions() StandardOptions {
 		NoGobuster:      false,
 		PortScan:        false,
 		Ports:           "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3306,3389,5900,8080,8443,8888",
+		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
@@ -15,6 +15,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
@@ -496,6 +496,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)