diff --git a/auth/pdcpauth.py b/auth/pdcpauth.py new file mode 100644 index 0000000..48a1fd2 --- /dev/null +++ b/auth/pdcpauth.py @@ -0,0 +1,12 @@ +# pdcpauth.py + +def check_and_validate_credentials(tool_name): + """ + Simulates checking and validating credentials for a tool. + Args: + tool_name (str): The name of the tool to authenticate. + """ + print(f"Validating credentials for {tool_name}...") + # Replace this with actual logic for authenticating a user + # For now, it just returns True to simulate a successful validation. + return True diff --git a/banner.py b/banner.py new file mode 100644 index 0000000..f6406be --- /dev/null +++ b/banner.py @@ -0,0 +1,40 @@ +import logging +from updateutils import get_update_tool_callback +from auth.pdcpauth import check_and_validate_credentials +from colorama import Fore, Style, init + +# Initialize colorama +init(autoreset=True) + +# Define the banner with magenta color and green for the version +banner = f''' +{Fore.MAGENTA} + __________________ + ____ ____/_ __/ ____/ ____/ + / __ \/ __ \/ / / __/ / / +/ /_/ / /_/ / / / /___/ /___ +\____/ .___/_/ /_____/\____/ + /_/ {Fore.GREEN}ver 1.0.0{Style.RESET_ALL} + +{Style.RESET_ALL} +''' + +# Show the banner to the user +def show_banner(): + print(f"{banner}") + print("\t\toptec.asfaad.com\n") + +# Update nuclei binary/tool to the latest version +def nuclei_tool_update_callback(): + show_banner() + get_update_tool_callback('nuclei', '1.0.0')() + +# Authenticate with PDCP +def auth_with_pdcp(): + show_banner() + check_and_validate_credentials('nuclei') + +# Example usage +if __name__ == "__main__": + auth_with_pdcp() + nuclei_tool_update_callback() diff --git a/engine/matchers.py b/engine/matchers.py index 081fb40..4ad164f 100644 --- a/engine/matchers.py +++ b/engine/matchers.py @@ -31,6 +31,7 @@ def header_search(response, matcher): headers = dict(response.headers) stuff_to_match = matcher['name'] condition_type = matcher['condition'] # Either Present or Contains + if condition_type == 'present': if stuff_to_match in headers: return True @@ -46,25 +47,46 @@ def header_search(response, matcher): return False else: return False - + def regex_match(response: Response, matcher: Dict[str, Any]) -> bool: """ Checks if the response body matches a given regex pattern. """ - pattern = matcher.get("pattern") - if not pattern: + patterns = matcher.get("pattern", []) + condition = matcher.get("condition", "or").lower() + + if not patterns or not isinstance(patterns, list): return False - return bool(re.search(pattern, response.text)) + + pool_of_response_text = response.text + if condition == "and": + return all(bool(re.search(pattern, pool_of_response_text)) for pattern in patterns) + elif condition == "or": + return any(bool(re.search(pattern, pool_of_response_text)) for pattern in patterns) def word_match(response: Response, matcher: Dict[str, Any]) -> bool: + """ + Checks if the response body contains the specified words. """ - Checks if th respose body contains the word - """ - word_to_search = matcher.get("word") - if not word_to_search: + words_to_search = matcher.get("words", []) + condition = matcher.get("condition", "or").lower() + + # Validate that `words_to_search` contains strings + words_to_search = [str(word) for word in words_to_search if isinstance(word, (str, int, float))] + + # Return False if no valid words are available + if not words_to_search: return False + pool_of_response_text = response.text - return bool(pool_of_response_text.find(word_to_search) > 0) # returns false if not found, ie, returns -1 + + # Check based on the specified condition + if condition == "and": + return all(word in pool_of_response_text for word in words_to_search) + elif condition == "or": + return any(word in pool_of_response_text for word in words_to_search) + else: + return False # Default case if an unknown condition is provided def status_match(response: Response, matcher: Dict[str, Any]) -> bool: @@ -75,4 +97,3 @@ def status_match(response: Response, matcher: Dict[str, Any]) -> bool: return response.status_code in statuses - diff --git a/engine/scanner.py b/engine/scanner.py index c00de0a..ff7194c 100644 --- a/engine/scanner.py +++ b/engine/scanner.py @@ -1,5 +1,7 @@ import requests # Alternative to a browser in real lyf from typing import Dict, Any, List +import dns.resolver +from urllib.parse import urlparse from .matchers import run_matchers @@ -14,22 +16,40 @@ def __init__(self, templates: List[Dict[str, Any]]): self.logged_urls = set() # Set to track already logged URLs for warnings def scan(self, target_url: str): - """ - Scan a single target URL with all loaded templates. - """ + #Scan a single target URL with all loaded templates. + results = [] + for template in self.templates: template_id = template.get("id", "unknown-id") template_info = template.get("info", {}) - template_requests = template.get("http", []) + for key, value in template.items(): + if key in {"id", "info"}: + continue + elif key == "http": + template_requests = template.get("http", []) + for request_config in template_requests: + result = self._process_http_request(template_id, template_info, request_config, target_url) + if result: + results.append(result) + elif key == "ssl": + template_requests = template.get("ssl", []) + for request_config in template_requests: + result = self._process_ssl_request(template_id, template_info, request_config, target_url) + if result: + results.append(result) + elif key == "dns": + template_requests = template.get("dns", []) + for request_config in template_requests: + result = self._process_dns_request(template_id, template_info, request_config, target_url) + if result: + results.append(result) - for request_config in template_requests: - result = self._process_request(template_id, template_info, request_config, target_url) - if result: - results.append(result) return results - def _process_request(self, template_id, template_info, request_config, target_url): + + + def _process_http_request(self, template_id, template_info, request_config, target_url): """ Process a single request configuration from a template. """ @@ -37,7 +57,7 @@ def _process_request(self, template_id, template_info, request_config, target_ur paths = request_config.get("path", []) matchers_config = request_config.get("matchers", []) for path in paths: - url = path.replace("{{BaseURL}}", target_url).strip("/") # Remove extra slashes + url = path.replace("{{BaseURL}}", target_url).strip("/") # Remove extra slashes # TODO : Check for slashes and/or double slashes and then remove the same #noqa try: # Make the request based on method @@ -50,7 +70,7 @@ def _process_request(self, template_id, template_info, request_config, target_ur matched_result = run_matchers(response, matchers_config) # Combine results: Header issues override matchers - if matched_result: + if matched_result: # For clarity Lets keep if true it means there is a vulnerability and false means all good return { "template_id": template_id, "name": template_info.get("name"), @@ -77,3 +97,121 @@ def _process_request(self, template_id, template_info, request_config, target_ur return None + + def _process_dns_request(self, template_id, template_info, request_config, target_url): + """ + Process DNS-related requests from a template. + """ + # Extract domain from target_url + parsed_url = urlparse(target_url) + url = parsed_url.netloc # Use netloc to get the domain (e.g., 'example.com') + + query_type = request_config.get("type", "A").upper() # Default to A record + query_url = request_config.get("query", "").replace("{{BaseDomain}}", url) + matchers_config = request_config.get("matchers", []) + + try: + # Perform DNS query + answers = dns.resolver.resolve(query_url, query_type) + response_data = [answer.to_text() for answer in answers] + + # Inline evaluation of matchers + matched_result = True + for matcher in matchers_config: + matcher_type = matcher.get("matcher_type") + if matcher_type == "value": + expected_value = matcher.get("value", "") + if expected_value not in response_data: + matched_result = False + break + elif matcher_type == "exists": + if not response_data: # Fail if there are no DNS results + matched_result = False + break + + return { + "template_id": template_id, + "name": template_info.get("name"), + "author": template_info.get("author"), + "severity": template_info.get("severity"), + "url": query_url, + "query_type": query_type, + "response_data": response_data, + "matched": matched_result, + } + + except dns.resolver.NoAnswer: + # Handle NoAnswer gracefully, similar to NXDOMAIN + return { + "template_id": template_id, + "name": template_info.get("name"), + "author": template_info.get("author"), + "severity": template_info.get("severity"), + "url": query_url, + "query_type": query_type, + "response_data": [], + "matched": False, + } + except dns.resolver.NXDOMAIN: + print(f"Domain {query_url} does not exist.") + return { + "template_id": template_id, + "name": template_info.get("name"), + "author": template_info.get("author"), + "severity": template_info.get("severity"), + "url": query_url, + "query_type": query_type, + "response_data": [], + "matched": False, + } + except Exception as e: + print(f"[ERROR] DNS query failed for {query_url}: {e}") + return { + "template_id": template_id, + "name": template_info.get("name"), + "author": template_info.get("author"), + "severity": template_info.get("severity"), + "url": query_url, + "query_type": query_type, + "response_data": [], + "matched": False, + } + def _process_ssl_request(self, template_id, template_info, request_config, target_url): + paths = request_config.get("path", []) # TODO : Strip this to just domain name in case any path exists + matchers_config = request_config.get("matchers", []) + for path in paths: + url = path.replace("{{BaseURL}}", target_url).strip("/") + try: + response = requests.get(url, verify=True) + if response.url.startswith("https://"): + return { + "template_id": template_id, + "name": template_info.get("name"), + "author": template_info.get("author"), + "severity": template_info.get("severity"), + "url": url, + "status_code": response.status_code, + "matched": "True", # As a postive result + } + else: + return { + "template_id": template_id, + "name": template_info.get("name"), + "author": template_info.get("author"), + "severity": template_info.get("severity"), + "url": url, + "status_code": response.status_code, + "matched": "False", # As a negetive result + } + except requests.exceptions.SSLError: + return { + "template_id": template_id, + "name": template_info.get("name"), + "author": template_info.get("author"), + "severity": template_info.get("severity"), + "url": url, + "status_code": response.status_code, + "matched": "False", # As a negetive result + } + except requests.exceptions.RequestException as e: + print("The request was errored out ", str(e)) diff --git a/engine/template_parser.py b/engine/template_parser.py index 6634742..4b483d4 100644 --- a/engine/template_parser.py +++ b/engine/template_parser.py @@ -5,22 +5,25 @@ def load_templates_from_directory(directory: str) -> List[Dict[str, Any]]: """ - Loads all YAML files from the specified directory and returns - them as a list of parsed dictionaries. + Loads all YAML files from the specified directory and its subdirectories + and returns them as a list of parsed dictionaries. - The directory needs to be till yaml or else nothing at all - incase of no directory is passed, load all yaml - TODO: Handled by another function - Who will take care of this ? + If no directory is passed, it defaults to the current directory. """ templates = [] - for filename in os.listdir(directory): - if filename.endswith(".yaml") or filename.endswith(".yml"): - filepath = os.path.join(directory, filename) - with open(filepath, "r", encoding="utf-8") as f: - try: - data = yaml.safe_load(f) # Using safe load here, I dont want deserialization attack happening here ! - if data: - templates.append(data) # TODO : Handle else case YAML errors may need a blank - except yaml.YAMLError as e: - print(f"[ERROR] Failed to parse YAML file {filename}: {e}") + for root, _, files in os.walk(directory): # Use os.walk for recursive traversal + for filename in files: + if filename.endswith(".yaml") or filename.endswith(".yml"): + filepath = os.path.join(root, filename) + with open(filepath, "r", encoding="utf-8") as f: + try: + data = yaml.safe_load(f) # Safe load to avoid deserialization attacks + if data: + templates.append(data) + except yaml.YAMLError as e: + print(f"[ERROR] Failed to parse YAML file {filename}: {e}") return templates + + + + diff --git a/engine/utils.py b/engine/utils.py index eab47ec..d75eee3 100644 --- a/engine/utils.py +++ b/engine/utils.py @@ -1,11 +1,33 @@ -import json from typing import List, Dict, Any +import datetime - -def print_results(results: List[Dict[str, Any]]): +def process_scan_results(results: List[Dict[str, Any]], target_url: str) -> Dict[str, Any]: """ - Print or store the results of the scanning in a desired format. - Here, we do a simple JSON dump to console. + Process scan results to prepare data for reporting. + + Args: + results (List[Dict[str, Any]]): List of scan result dictionaries. + target_url (str): The URL that was scanned. + + Returns: + Dict[str, Any]: Processed data for the report. """ + vulnerabilities = [] for res in results: - print(json.dumps(res, indent=2)) + if res.get("matched"): + vulnerabilities.append({ + "type": res.get("name", "Unknown"), + "severity": res.get("severity", "Unknown").capitalize(), + "url": res.get("url", "Unknown"), + "description": res.get("description", "No description available."), + "recommendation": res.get("recommendation", "No recommendation provided.") + }) + + report_data = { + "website": target_url, + "date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "total_vulnerabilities": len(vulnerabilities), + "vulnerabilities": vulnerabilities, + } + + return report_data diff --git a/jinja.py b/jinja.py new file mode 100644 index 0000000..abb6f53 --- /dev/null +++ b/jinja.py @@ -0,0 +1,144 @@ +import time +from typing import Dict, Any +from jinja2 import Template +import re +from weasyprint import HTML +import google.generativeai as genai + +# Set up the API key +genai.configure(api_key="apikey") + + +def fetch_description_and_recommendation(vuln_name: str, severity: str) -> Dict[str, str]: + """Fetch vulnerability description and recommendation using Google's Gemini AI.""" + try: + # Structured prompt for Gemini AI + prompt = ( + f"Explain the cybersecurity issue called '{vuln_name}' with a severity level of '{severity}'. " + f"Provide the following in bullet points, without using asterisks or paragraph formatting:\n\n" + f"1. A detailed description.\n" + f"2. A recommended solution.\n\n" + f"Format your response as:\n" + f"Description:\n- \n" + f"Recommendation:\n- " + ) + + + + # Generate response using Google's AI + model = genai.GenerativeModel("gemini-2.0-flash") + response = model.generate_content(prompt) + + # Validate response structure + if response and response.candidates: + ai_response = response.candidates[0].content.parts[0].text.strip() if response.candidates[0].content.parts else "" + + if not ai_response: + raise ValueError("AI response is empty or incomplete.") + + # Extract description and recommendation + description_match = re.search(r"Description:\s*(.*?)\s*(Recommendation:|$)", ai_response, re.DOTALL) + recommendation_match = re.search(r"Recommendation:\s*(.*?)$", ai_response, re.DOTALL) + + description = description_match.group(1).strip() if description_match else "No description available." + recommendation = recommendation_match.group(1).strip() if recommendation_match else "No recommendation available." + + return {"description": description, "recommendation": recommendation} + + else: + raise ValueError("Google AI response is empty or flagged.") + + except Exception as e: + print(f"Error fetching data from Google AI: {e}") + return { + "description": "Error fetching description.", + "recommendation": "Error fetching recommendation." + } + + + +# Function to generate HTML report +def generate_html_report(data: Dict[str, Any], template_path: str = "scan_report.html"): + + + template = Template(""" + + + + + + Website Scan Report + + +

Website Scan Report

+

Website: {{ website }}

+

Scan Date: {{ date }}

+

Summary

+

Total vulnerabilities found: {{ total_vulnerabilities }}

+

This report presents the results of a comprehensive penetration test conducted on {{website}} internal network and publicly accessible web applications. The objective of this engagement was to assess the security posture of the infrastructure and applications by identifying vulnerabilities, evaluating their potential impact, and providing recommendations for remediation.

+

Scope of testing

+

This penetration test focused on evaluating the security of specific components within the network and web application environment. The defined scope included: + +1. Network Penetration Testing +Targeted Services: + +DNS (Domain Name System): + +DNS zone transfer testing + +Cache poisoning checks + +DNS enumeration and misconfiguration analysis + +SSL/TLS (Secure Sockets Layer / Transport Layer Security): + +Certificate validation + +SSL/TLS version and cipher suite analysis + +Configuration flaws (e.g., support for deprecated protocols, weak ciphers, lack of forward secrecy) + +Exclusions: No other network services, hosts, or internal infrastructure were included in the test scope. + +2. Web Application Penetration Testing +Full assessment of the specified web application(s) for vulnerabilities based on the OWASP Top 10 and other common security flaws.

+

Vulnerability Details

+ + + + + + + + + + + + + {% for vuln in vulnerabilities %} + + + + + + + + + {% endfor %} + +
VulnerabilityResultSeverityAffected URLDescriptionRecommendation
{{ vuln.name }}{{ vuln.matched }}{{ vuln.severity }}{{ vuln.url }}{{ vuln.description }}{{ vuln.recommendation }}
+ + + """) + + html_report = template.render(data) + with open(template_path, "w") as file: + file.write(html_report) + +# Function to convert HTML report to PDF +def convert_html_to_pdf(html_path: str, pdf_path: str = "scan_report.pdf"): + try: + HTML(html_path).write_pdf(pdf_path) + print(f"PDF report generated: {pdf_path}") + except Exception as e: + print(f"Error converting to PDF: {e}") diff --git a/main.py b/main.py index 0d43d0b..228902f 100644 --- a/main.py +++ b/main.py @@ -1,28 +1,119 @@ +import argparse +import os import sys +import datetime +from colorama import Fore, Style, init # Import colorama +sys.path.append(os.path.dirname(os.path.abspath(__file__))) +import yaml +import html from engine.template_parser import load_templates_from_directory from engine.scanner import Scanner -from engine.utils import print_results - +from banner import show_banner +from jinja import generate_html_report, convert_html_to_pdf,fetch_description_and_recommendation # Import the generate_html_report and pdf function def main(): - # TODO, setup CLI Interface https://www.geeksforgeeks.org/command-line-option-and-argument-parsing-using-argparse-in-python/ - if len(sys.argv) < 2: - print(f"Usage: python {sys.argv[0]} []") - sys.exit(1) + # Initialize colorama + init(autoreset=True) + + # Show banner at the start + show_banner() + + parser = argparse.ArgumentParser( + description="A tool for scanning a target URL using templates." + ) + + parser.add_argument( + "target_url", + help="The target URL to scan.", + ) + parser.add_argument( + "-t", "--templates", + default="templates", + help=( + "Path to a specific template file or directory containing YAML templates. " + "Default: templates" + ), + ) + parser.add_argument( + "-tag", + nargs="+", + help="Filter templates by tags. Provide one or more tags to filter.", + ) + + # Parse the arguments + args = parser.parse_args() + target_url = args.target_url + templates_path = args.templates + selected_tags = args.tag + # Updated templates path logic + templates_path = args.templates or os.getcwd() + + # Check and load YAML templates + if os.path.isfile(templates_path) and templates_path.endswith(".yaml"): + print(f"Loading specific template: {templates_path}") + with open(templates_path, "r") as file: + try: + templates = [yaml.safe_load(file)] # Parse the YAML into a dictionary + except yaml.YAMLError as e: + print(f"Error parsing YAML file: {e}") + exit(1) - target_url = sys.argv[1] - templates_dir = sys.argv[2] if len(sys.argv) > 2 else "templates/http" + elif os.path.isdir(templates_path): + print(f"Loading all YAML files from directory: {templates_path}") + templates = load_templates_from_directory(templates_path) + + else: + print(f"Invalid path: {templates_path}. Ensure it points to a .yaml file or a directory.") + exit(1) - # Load the templates - templates = load_templates_from_directory(templates_dir) - # Create the scanner instance scanner = Scanner(templates) # Run the scan + print(f"Scanning target URL: {target_url}") + results = scanner.scan(target_url) + if isinstance(results, dict): # In case only one YAML file is present + results = [results] + + # Prepare the results for printing and saving + scan_results = [] + for idx, result in enumerate(results): + # Colorize "True" or "False" + if str(result["matched"]) == "True": + status_color = f"{Fore.GREEN}True{Style.RESET_ALL}" + else: + status_color = f"{Fore.RED}False{Style.RESET_ALL}" + + # Print the result with colored "True" or "False" + print( + f"[{idx + 1}]{result['name']}: " + f"[{status_color}]" + f"[{result['severity'].capitalize()}]" + ) + + # Store the result in the scan_results list for idx, result in enumerate(results): - print(f"Result #{idx + 1}, result for {result['name']}: {result['matched']}") + chatgpt_data = fetch_description_and_recommendation(result['name'], result['severity']) + scan_results.append({ + "name": html.escape(result['name']), + "matched": result['matched'], + "severity": html.escape(result['severity']), + "url": html.escape(result.get('url', 'N/A')), + "description": html.escape(chatgpt_data["description"]), + "recommendation": html.escape(chatgpt_data["recommendation"]) + }) + + # Structure data for the report + data = { + "website": target_url, + "date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "total_vulnerabilities": len(scan_results), + "vulnerabilities": scan_results + } + # Generate HTML report with the scan_results + generate_html_report(data, "scan_report.html") + convert_html_to_pdf("scan_report.html", "scan_report.pdf") if __name__ == "__main__": main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..8fd8d67 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" diff --git a/requirments.txt b/requirments.txt index c5c6ca4..ad26fcf 100644 Binary files a/requirments.txt and b/requirments.txt differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ff91648 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup + +setup( + name="optec", + version="1.0.0", + description="A tool for scanning a target URL using YAML templates.", + author="X", + url="https://github.com/mynameisfathima/PenetrationTesting/tree/check", + py_modules=["main"], # Assuming your file is named main.py + entry_points={ + "console_scripts": [ + "optec=main:main", # Maps the `optech` command to the `main()` function in `main.py` + ], + }, + install_requires=["pyyaml"], # Add other dependencies as needed +) diff --git a/templates/dns/dmarc-detection.yaml b/templates/dns/dmarc-detection.yaml new file mode 100644 index 0000000..b82c362 --- /dev/null +++ b/templates/dns/dmarc-detection.yaml @@ -0,0 +1,19 @@ +id: dmarc-detection + +info: + name: DMARC Record Detection + severity: info + classification: + cwe-id: CWE-200 + metadata: + max-request: 1 + tags: dns,dmarc,email,security,spf + +dns: + - name: "_dmarc.{{BaseURL}}" + type: TXT + + matchers: + - type: word + words: + - "v=DMARC1" diff --git a/templates/dns/dns-record-check.yaml b/templates/dns/dns-record-check.yaml new file mode 100644 index 0000000..9523357 --- /dev/null +++ b/templates/dns/dns-record-check.yaml @@ -0,0 +1,19 @@ +id: dkim-detection + +info: + name: DKIM Record Detection + severity: info + classification: + cwe-id: CWE-200 + metadata: + max-request: 1 + tags: dns,dkim,email,security + +dns: + - name: "default._domainkey.{{BaseURL}}" + type: TXT + + matchers: + - type: word + words: + - "v=DKIM1" diff --git a/templates/dns/ec2-detection.yaml b/templates/dns/ec2-detection.yaml new file mode 100644 index 0000000..a62f050 --- /dev/null +++ b/templates/dns/ec2-detection.yaml @@ -0,0 +1,22 @@ +id: ec2-detection + +info: + name: AWS EC2 Detection + author: melbadry9 + severity: info + classification: + cwe-id: CWE-200 + metadata: + max-request: 1 + tags: dns,ec2,aws + +dns: + - name: "{{BaseURL}}" + type: CNAME + + extractors: + - type: regex + regex: + - "ec2-[-\\d]+\\.compute[-\\d]*\\.amazonaws\\.com" + - "ec2-[-\\d]+\\.[\\w\\d\\-]+\\.compute[-\\d]*\\.amazonaws\\.com" +# digest: 490a0046304402200db5f928a66a67c8574159c33c8864f0db076d31b7c6ab658cd792accad1d65702201ccf0864bea4e34bab0d1aff2c6a1ee6ce984c5b87d009e3c0ad918d415ef0d9:922c64590222798bb761d5b6d8e72950 diff --git a/templates/dns/nameserver-fingerprint.yaml b/templates/dns/nameserver-fingerprint.yaml new file mode 100644 index 0000000..166500a --- /dev/null +++ b/templates/dns/nameserver-fingerprint.yaml @@ -0,0 +1,27 @@ +id: nameserver-fingerprint + +info: + name: NS Record Detection + author: pdteam + severity: info + classification: + cwe-id: CWE-200 + metadata: + max-request: 1 + tags: dns,ns + +dns: + - name: "{{BaseURL}}" + type: NS + matchers: + - type: regex + part: answer + regex: + - "IN\tNS\\t(.+)$" + + extractors: + - type: regex + group: 1 + regex: + - "IN\tNS\t(.+)" +# digest: 490a0046304402204c8549e5c2a45732f218c404c2f4d49ed740bb91f27dab259f710952fc089496022058f7dc0d477f9b990d5fac516e5aba8552b222b9b4a1dd17491a4011ffa4ad34:922c64590222798bb761d5b6d8e72950 \ No newline at end of file diff --git a/templates/dns/spoofable-spf-records-ptr.yaml b/templates/dns/spoofable-spf-records-ptr.yaml new file mode 100644 index 0000000..97e32eb --- /dev/null +++ b/templates/dns/spoofable-spf-records-ptr.yaml @@ -0,0 +1,22 @@ +id: spoofable-spf-records-ptr + +info: + name: Spoofable SPF Records with PTR Mechanism + author: binaryfigments + severity: info + classification: + cwe-id: CWE-200 + metadata: + max-request: 1 + tags: dns,spf + +dns: + - name: "{{BaseURL}}" + type: TXT + matchers: + - type: word + words: + - "v=spf1" + - " ptr " + condition: and +# digest: 490a0046304402205db2464b5a702857d30775b25f61f312a8fb1e6586cef7fd3e226b1e68202d9d02203ce1ee037b0e8c4be474aa4730a60fe546824f5f8e5a942f2f0d2ee2165bfd11:922c64590222798bb761d5b6d8e72950 \ No newline at end of file diff --git a/templates/dns/txt-fingerprint.yaml b/templates/dns/txt-fingerprint.yaml new file mode 100644 index 0000000..7294df5 --- /dev/null +++ b/templates/dns/txt-fingerprint.yaml @@ -0,0 +1,27 @@ +id: txt-fingerprint + +info: + name: DNS TXT Record Detected + author: pdteam + severity: info + classification: + cwe-id: CWE-200 + metadata: + max-request: 1 + tags: dns,txt + +dns: + - name: "{{BaseURL}}" + type: TXT + matchers: + - type: regex + part: answer + regex: + - "IN\tTXT\\t(.+)$" + + extractors: + - type: regex + group: 1 + regex: + - "IN\tTXT\t(.+)" +# digest: 4b0a00483046022100d9ea414760699f0839a5d1807d059209634442c29976b88728fb98ad68b09a67022100f4c11f94fc53c6ec41a3754a908d54c67b96f34b730f32d26557adacb1692a9b:922c64590222798bb761d5b6d8e72950 \ No newline at end of file diff --git a/templates/dns/txt-service-detect.yaml b/templates/dns/txt-service-detect.yaml new file mode 100644 index 0000000..f5e310d --- /dev/null +++ b/templates/dns/txt-service-detect.yaml @@ -0,0 +1,217 @@ +id: txt-service-detect + +info: + name: DNS TXT Service - Detect + author: rxerium + severity: info + metadata: + verified: true + max-request: 1 + tags: dns,txt + +dns: + - name: "{{BaseURL}}" + type: TXT + + matchers-condition: or + matchers: + - type: word + name: "keybase" + words: + - "keybase-site-verification" + + - type: word + name: "proton-mail" + words: + - "protonmail-verification" + + - type: word + name: "webex" + words: + - "webexdomainverification" + + - type: word + name: "apple" + words: + - "apple-domain-verification" + + - type: word + name: "facebook" + words: + - "facebook-domain-verification" + + - type: word + name: "autodesk" + words: + - "autodesk-domain-verification" + + - type: word + name: "stripe" + words: + - "stripe-verification" + + - type: word + name: "atlassian" + words: + - "atlassian-domain-verification" + + - type: word + name: "adobe-sign" + words: + - "adobe-sign-verification" + + - type: word + name: "zoho" + words: + - "zoho-verification" + + - type: word + name: "have-i-been-pwned" + words: + - "have-i-been-pwned-verification" + + - type: word + name: "knowbe4" + words: + - "knowbe4-site-verification" + + - type: word + name: "jamf" + words: + - "jamf-site-verification" + + - type: word + name: "parallels" + words: + - "parallels-domain-verification" + + - type: word + name: "dropbox" + words: + - "dropbox-domain-verification" + + - type: word + name: "vmware-cloud" + words: + - "vmware-cloud-verification" + + - type: word + name: "canva" + words: + - "canva-site-verification" + + - type: word + name: "mongodb" + words: + - "mongodb-site-verification" + + - type: word + name: "slack" + words: + - "slack-domain-verification" + + - type: word + name: "teamViewer" + words: + - "teamviewer-sso-verification" + + - type: word + name: "bugcrowd" + words: + - "bugcrowd-verification" + + - type: word + name: "cisco" + words: + - "cisco-site-verification" + + - type: word + name: "palo-alto-networks" + words: + - "paloaltonetworks-site-verification" + + - type: word + name: "twilio" + words: + - "twilio-domain-verification" + + - type: word + name: "dell-technologies" + words: + - "dell-technologies-domain-verification" + + - type: word + name: "1password" + words: + - "1password-site-verification" + + - type: word + name: "duo" + words: + - "duo_sso_verification" + + - type: word + name: "sophos" + words: + - "sophos-domain-verification" + + - type: word + name: "pinterest" + words: + - "pinterest-site-verification" + + - type: word + name: "citrix" + words: + - "citrix-verification-code" + + - type: word + name: "zapier" + words: + - "zapier-domain-verification-challenge" + + - type: word + name: "uber" + words: + - "uber-domain-verification" + + - type: word + name: "zoom" + words: + - "zoom-domain-verification" + + - type: word + name: "lastpass" + words: + - "lastpass-verification-code" + + - type: word + name: "google-workspace" + words: + - "google-site-verification" + + - type: word + name: "flexera" + words: + - "flexera-domain-verification" + + - type: word + name: "yandex" + words: + - "yandex-verification" + + - type: word + name: "calendly" + words: + - "calendly-site-verification" + + - type: word + name: "docusign" + words: + - "docusign" + + - type: word + name: "whimsical" + words: + - "whimsical" +# digest: 4a0a0047304502207f3cfebafdde23640010064ce8dc4fa58a20de6e4a65a43c34cc8f99c992a055022100b8625de624e659dfec53c13473213e9a95620b0cd42914dab9d4a3b1f7832320:922c64590222798bb761d5b6d8e72950 \ No newline at end of file diff --git a/templates/dns/worksites-detection.yaml b/templates/dns/worksites-detection.yaml new file mode 100644 index 0000000..70b087b --- /dev/null +++ b/templates/dns/worksites-detection.yaml @@ -0,0 +1,20 @@ +id: detect-worksites + +info: + name: Worksites.net Service Detection + author: melbadry9 + severity: info + classification: + cwe-id: CWE-200 + metadata: + max-request: 1 + tags: dns,service + +dns: + - name: "{{BaseURL}}" + type: A + matchers: + - type: word + words: + - "69.164.223.206" +# digest: 4a0a0047304502206f1d69edc61a00493a4e9228339de95b5c081cc3f997bdc28f784c9719de70b1022100f2b09e254a96166ed72130fc3ad13401d5e75cb6c2146ae8d8ad6aa8d18438c2:922c64590222798bb761d5b6d8e72950 \ No newline at end of file diff --git a/templates/http/Broken-authentication.yaml b/templates/http/Broken-authentication.yaml new file mode 100644 index 0000000..c5373a9 --- /dev/null +++ b/templates/http/Broken-authentication.yaml @@ -0,0 +1,25 @@ +id: broken-authentication-test +info: + name: Broken Authentication Vulnerability Test + description: Test for broken authentication vulnerabilities in login forms. + severity: Critical + author: Project + tags: + - authentication + - security + +http: + - method: POST + path: + - "{{BaseURL}}/login" + body: + - "username=admin&password=admin' OR '1'='1" + headers: + Content-Type: application/x-www-form-urlencoded + User-Agent: Auth-Scanner + matchers: + - type: word + words: + - login failed + - incorrect username or password + condition: or diff --git a/templates/http/broken-access-control.yaml b/templates/http/broken-access-control.yaml new file mode 100644 index 0000000..abe4b26 --- /dev/null +++ b/templates/http/broken-access-control.yaml @@ -0,0 +1,23 @@ +id: broken-access-control-test +info: + name: Broken Access Control Vulnerability Test for WordPress Websites + description: Test for broken access control to see if a wordpress website have exposed wp-admin directory + severity: Critical + author: Project + tags: + - access + - control + - security + +http: + - method: GET + path: + - "{{BaseURL}}/wp-admin" + headers: + User-Agent: Access-Control-Tester + matchers: + - type: word + words: + - "Username" + - "Password" + condition: or diff --git a/templates/http/insufficient-logging-monitoring.yaml b/templates/http/insufficient-logging-monitoring.yaml new file mode 100644 index 0000000..5e811fa --- /dev/null +++ b/templates/http/insufficient-logging-monitoring.yaml @@ -0,0 +1,23 @@ +id: insufficient-logging-monitoring-test +info: + name: Insufficient Logging & Monitoring Vulnerability Test + description: Test for missing or insufficient logging and monitoring. + severity: High + author: Project + tags: + - logging + - monitoring + - security + +http: + - method: GET + path: + - "{{BaseURL}}/admin/logs" + headers: + User-Agent: Logging-Monitoring-Scanner + matchers: + - type: word + words: + - "error" + - "log" + condition: or diff --git a/templates/http/known-vulnerable-components.yaml b/templates/http/known-vulnerable-components.yaml new file mode 100644 index 0000000..b16cbba --- /dev/null +++ b/templates/http/known-vulnerable-components.yaml @@ -0,0 +1,22 @@ +id: known-vulnerable-components-test +info: + name: Components with Known Vulnerabilities Test + description: Test for vulnerable components by checking versions. + severity: High + author: Project + tags: + - components + - vulnerabilities + +http: + - method: GET + path: + - "{{BaseURL}}/version" + headers: + User-Agent: Vulnerability-Scanner + matchers: + - type: word + words: + - "CVE" + - "version" + condition: or diff --git a/templates/http/ping_google.yaml b/templates/http/ping_google.yaml index 3a11ba0..6d3ff40 100644 --- a/templates/http/ping_google.yaml +++ b/templates/http/ping_google.yaml @@ -1,7 +1,7 @@ id: check-route-root info: - name: Script to check if HTTP Page of Google at 8.8.8.8 is available + name: Script to check if HTTP Page is available author: DanBrown47 severity: none description: Script will send a HTTP request to 8.8.8.8 to see if network is available @@ -15,4 +15,4 @@ http: max-redirects: 1 - response: - - status: 200 \ No newline at end of file + - status: 200 diff --git a/templates/http/security-misconfiguration.yaml b/templates/http/security-misconfiguration.yaml new file mode 100644 index 0000000..a4db83c --- /dev/null +++ b/templates/http/security-misconfiguration.yaml @@ -0,0 +1,21 @@ +id: security-misconfiguration-test +info: + name: Security Misconfiguration Vulnerability Test + description: Test for misconfiguration issues, such as access to sensitive files. + severity: High + author: Project + tags: + - misconfiguration + - security +http: + - method: GET + path: + - "{{BaseURL}}/.env" + headers: + User-Agent: Misconfiguration-Scanner + matchers: + - type: word + words: + - "database_url" + - "secret_key" + condition: or diff --git a/templates/http/sensitive-data-exposure.yaml b/templates/http/sensitive-data-exposure.yaml new file mode 100644 index 0000000..e906e3d --- /dev/null +++ b/templates/http/sensitive-data-exposure.yaml @@ -0,0 +1,24 @@ +id: sensitive-data-exposure-test +info: + name: Sensitive Data Exposure Vulnerability Test + description: Test for sensitive data exposure in profile pages. + severity: Critical + author: Project + tags: + - data + - exposure + - privacy + +http: + - method: GET + path: + - "{{BaseURL}}/profile" + headers: + User-Agent: Sensitive-Data-Exposure-Scanner + matchers: + - type: word + words: + - "password=" + - "token=" + - "session=" + condition: or diff --git a/templates/http/sql-injection.yaml b/templates/http/sql-injection.yaml new file mode 100644 index 0000000..cad299a --- /dev/null +++ b/templates/http/sql-injection.yaml @@ -0,0 +1,39 @@ +id: sql-vulnerability-test +info: + name: SQL Injection Vulnerability + description: Extensive test for SQL injection vulnerabilities in query parameters and form fields. + severity: Critical + author: Project + tags: + - sql + - injection + - critical + - database + - web + +http: + - method: GET + path: + - "{{BaseURL}}?id=1' OR '1'='1" + - "{{BaseURL}}?id=1'--" + - "{{BaseURL}}?id=1'/*" + - "{{BaseURL}}?id=1'; DROP TABLE users;--" + - "{{BaseURL}}?id=1' UNION SELECT NULL, NULL--" + - "{{BaseURL}}?id=1' AND SLEEP(5)--" + - "{{BaseURL}}?id=1' OR 'a'='a" + - "{{BaseURL}}?id=-1' UNION SELECT 1, @@version--" + - "{{BaseURL}}?id=1' AND '1'='2" + headers: + User-Agent: SQL-Injection-Scanner + matchers: + - type: word + words: + - SQL syntax error + - MySQL + - syntax error + - unclosed quotation mark + - Warning: mysql_fetch + - Unknown column + - database error + condition: or + diff --git a/templates/http/xss-vulnerability.yaml b/templates/http/xss-vulnerability.yaml new file mode 100644 index 0000000..6bce17e --- /dev/null +++ b/templates/http/xss-vulnerability.yaml @@ -0,0 +1,22 @@ +id: xss-vulnerability-test +info: + name: Cross-Site Scripting (XSS) Vulnerability Test + description: Test for reflected or stored XSS vulnerabilities in inputs. + severity: High + author: Project + tags: + - xss + - security + +http: + - method: GET + path: + - "{{BaseURL}}?search=" + headers: + User-Agent: XSS-Scanner + matchers: + - type: word + words: + - "