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 9827698bed10356994e8e9d54ef06e050a0e4102
parent 7dd106c5e6276621c83019f262571c4dadcf359c
Author: djnn <[email protected]>
Date: Wed, 22 Oct 2025 23:35:04 +0200

feat(meta): adding run-web-recon script

Diffstat:
MChangelog.md | 1+
Rquick-run.sh -> inventory/quick-run.sh | 0
Ainventory/run-web-recon | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

3 files changed, 214 insertions(+), 0 deletions(-)

diff --git a/Changelog.md b/Changelog.md
@@ -3,6 +3,7 @@
 - feat(scan): saving wappalyzer results to file ~ @djnnvx
 - feat(scan): adding cvemap module ~ @djnnvx
 - fix(scan): adding --no-dirbusting ~ @djnnvx
+- feat(meta): adding run-web-recon script ~ @djnnvx
 
 
 ## v0.0.3
diff --git a/quick-run.sh b/inventory/quick-run.sh
diff --git a/inventory/run-web-recon b/inventory/run-web-recon
@@ -0,0 +1,213 @@
+#!/bin/env python
+
+"""
+    This script runs a various set of commands in a "smart" way to run recon.
+    It has a big set of dependencies, but should work against a standard kali
+    install.
+"""
+
+import argparse
+import os
+import subprocess
+import shutil
+import shlex
+from typing import Optional, Dict, Any
+
+class ShellCommandRunner:
+    """
+    A class for safely running shell commands and checking for program existence.
+
+    It uses the modern subprocess.run() API for execution and provides structured
+    results for easy parsing and decision-making in Python.
+    """
+
+    def __init__(self):
+        """
+        Initializes the ShellCommandRunner.
+        """
+
+    def _get_program_name(self, command: str) -> str:
+        """
+        Internal helper to reliably extract the program name from a command string.
+        Uses shlex.split to handle quoted arguments correctly.
+        """
+        try:
+            parts = shlex.split(command)
+            return parts[0]
+        except ValueError:
+            return command.split()[0] if command.strip() else ""
+        except IndexError:
+            return ""
+
+    def is_callable(self, command: str) -> bool:
+        """
+        Checks if the main program specified in the command string is executable
+        and can be found in the system's PATH environment variable.
+
+        Args:
+            command: The full command string (e.g., 'ls -la /tmp').
+
+        Returns:
+            True if the program is found and executable, False otherwise.
+        """
+        program_name = self._get_program_name(command)
+        if not program_name:
+            return False
+
+        return shutil.which(program_name) is not None
+
+    def execute_command(self, command: str, timeout: int = 15) -> Dict[str, Any]:
+        """
+        Safely executes a shell command and captures its output and status.
+
+        Execution is performed without using shell=True for security.
+
+        Args:
+            command: The full command string (e.g., 'grep "error" logfile.txt').
+            timeout: Maximum time (in seconds) to wait for the command to finish.
+
+        Returns:
+            A dictionary containing:
+            - 'success': bool (True if execution succeeded and exit code was 0)
+            - 'exit_code': int (The return code of the command)
+            - 'stdout': str (Standard output)
+            - 'stderr': str (Standard error)
+            - 'error': Optional[str] (Python exception description if one occurred)
+        """
+        # Default failure result
+        result = {
+            'success': False,
+            'exit_code': -1,
+            'stdout': '',
+            'stderr': '',
+            'error': None
+        }
+
+        program_name = self._get_program_name(command)
+
+        if not self.is_callable(command):
+            result['error'] = f"Program '{program_name}' not found or is not callable on the system PATH."
+            return result
+
+        try:
+            args = shlex.split(command)
+
+            proc = subprocess.run(
+                args,
+                capture_output=True,
+                text=True,
+                check=False,
+                timeout=timeout
+            )
+
+            result['exit_code'] = proc.returncode
+            result['stdout'] = proc.stdout.strip()
+            result['stderr'] = proc.stderr.strip()
+            result['success'] = proc.returncode == 0
+
+        except subprocess.TimeoutExpired:
+            result['error'] = f"Command timed out after {timeout} seconds."
+        except OSError as e:
+            result['error'] = f"OS Error during execution: {e}"
+        except Exception as e:
+            result['error'] = f"An unexpected error occurred: {e}"
+
+        return result
+
+def shexec(command):
+
+    runner = ShellCommandRunner()
+    res = runner.execute_command(command)
+
+    if not res['success']:
+        print(res['stderr'])
+        os.exit(1)
+
+    print(f'>>> {command} <<<')
+    print(res['stdout'] + '\n--------------------------------------------------------------------------------------------\n\n')
+    return res['stdout']
+
+
+if __name__ == '__main__':
+
+    # --- 1. Load arg-parser
+    parser = argparse.ArgumentParser(description="run reconaissance quickly & integrate mutliple tools (nmap, yellow, ...).")
+    parser.add_argument(
+        '--domain',
+        type=str,
+        required=True,
+        help='The primary domain to process (e.g., example.com)'
+    )
+
+    parser.add_argument(
+        '--wordlist',
+        type=str,
+        required=False,
+        help='main wordlist to use'
+    )
+
+    parser.add_argument(
+        '--proxy',
+        type=str,
+        required=False,
+        help='proxy to use if any'
+    )
+
+    parser.add_argument(
+        '--rate-limit',
+        dest='rate_limit',
+        type=int,
+        default=1000,
+        help='The maximum number of requests per unit of time (integer).'
+    )
+
+    parser.add_argument(
+        '--force-insecure',
+        action='store_true',
+        help='Force HTTP instead of HTTPS'
+    )
+
+    parser.add_argument(
+        '--verbose',
+        action='store_true',
+        help='Enable verbose output for debugging and detailed logging.'
+    )
+
+    args = parser.parse_args()
+    runner = ShellCommandRunner()
+
+    # --- 2. Check dependencies
+    deps = ['yellow', 'nmap'] # TODO: , 'nxc', 'ffuf', 'nikto', 'sqlmap']
+    for d in deps:
+        if not runner.is_callable(d):
+            print(f'[!] missing dependency: {d}')
+            os.exit(1)
+
+
+    ratelimit_opt = ''
+    if args.rate_limit:
+        ratelimit_opt = f'--rate-limit {ratelimit}'
+
+    insecure_opt = ''
+    if args.force_insecure:
+        insecure_opt = f'--insecure'
+
+    ratelimit_opt = ''
+    if args.proxy:
+        proxy_opt = f'--proxy {proxy}'
+
+    # --- 3. run yellow
+    if args.verbose:
+        print('[+] running yellow (osint & scan)')
+
+    shexec(f'yellow -d {parser.domain}')
+    shexec(f'yellow osint -d {parser.domain} {proxy_opt} {ratelimit_opt} ')
+    shexec(f'yellow scan -d {parser.domain} {proxy_opt} {ratelimit_opt} {insecure_opt} --file {parser.domain}/scans/domains.txt')
+
+    os.chdir(parser.domain)
+
+    # --- 3. nmap all IPs quickly
+    nmap_scan = shexec('nmap -sS -T4 -iL scans/domains.txt -oN scans/global-nmap')
+
+
+