Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
445df7e
loads single file
mynameisfathima Jan 3, 2025
61c9451
sql-injection
mynameisfathima Jan 3, 2025
5bf41fc
Updated for sql
mynameisfathima Jan 3, 2025
0c2728c
Update sql-injection
mynameisfathima Jan 4, 2025
f5e0731
Update ping_google.yaml
mynameisfathima Jan 4, 2025
ce9b476
Created yaml
mynameisfathima Jan 4, 2025
e2caa74
creates yaml
mynameisfathima Jan 4, 2025
e254e73
yaml
mynameisfathima Jan 4, 2025
bb83abc
yaml
mynameisfathima Jan 4, 2025
ae02657
result changed
mynameisfathima Jan 4, 2025
de1dbaa
setup
mynameisfathima Jan 4, 2025
735f15c
Add files via upload
imadhiii Jan 4, 2025
cb73af4
banner.py
imadhiii Jan 4, 2025
52da0d3
Update main.py
imadhiii Jan 4, 2025
eb82e55
Update banner.py
imadhiii Jan 4, 2025
8eb2e4d
Update scanner.py
imadhiii Jan 4, 2025
aec98c6
Update template_parser.py
imadhiii Jan 4, 2025
0001c57
Create temp
imadhiii Jan 4, 2025
b9abb6f
Add files via upload
imadhiii Jan 4, 2025
cf49bb6
Delete auth/temp
mynameisfathima Jan 4, 2025
839dab9
Update main.py
imadhiii Jan 4, 2025
98834fc
Add files via upload
imadhiii Jan 4, 2025
7709419
Update main.py
imadhiii Jan 4, 2025
d2db169
Update scanner.py
mynameisfathima Jan 6, 2025
140cd4e
template_parser
mynameisfathima Jan 6, 2025
8d120e4
main
mynameisfathima Jan 6, 2025
f38ee51
pyproject.toml
mynameisfathima Jan 6, 2025
50c294b
detect-dangling-cname
mynameisfathima Jan 6, 2025
bb23ac6
exposed-zookeeper
mynameisfathima Jan 6, 2025
07d5032
detect-ssl-issuer
mynameisfathima Jan 6, 2025
883d7f8
detect-ssl-issuer
mynameisfathima Jan 6, 2025
1c66fb5
Fixed broken access control, with tests
DanBrown47 Jan 7, 2025
84d1297
Add SSL Checking test
DanBrown47 Jan 7, 2025
6a2c689
jinja
mynameisfathima Jan 18, 2025
0ebb2b1
color added
mynameisfathima Jan 18, 2025
0027667
changed
mynameisfathima Jan 19, 2025
c9a8f96
sends data to jinja
mynameisfathima Jan 19, 2025
750f59b
Update scanner.py
mynameisfathima Jan 19, 2025
d00f825
Create scannerssl.py
mynameisfathima Jan 19, 2025
233680a
changed
mynameisfathima Jan 22, 2025
7b08d44
Delete templates/network/exposed-zookeeper.yaml
mynameisfathima Jan 22, 2025
e586615
xss problrm solved
mynameisfathima Jan 22, 2025
0bfc180
added weasyprint
mynameisfathima Jan 24, 2025
ea24193
Update main
mynameisfathima Jan 24, 2025
fa56905
Create dns-record-check
mynameisfathima Jan 31, 2025
2674985
Delete templates/dns/detect-dangling-cname
mynameisfathima Jan 31, 2025
b33fb9a
Create ec2-detection
mynameisfathima Jan 31, 2025
5f33f6d
dns
mynameisfathima Jan 31, 2025
c92176e
Delete templates/http/insecure-deserialization.yaml
mynameisfathima Jan 31, 2025
3cb6d47
Update banner
mynameisfathima Jan 31, 2025
e16f627
Update template_parser
mynameisfathima Jan 31, 2025
f74dddd
Delete engine/scannerssl.py
mynameisfathima Jan 31, 2025
7668d82
Update scanner.py
mynameisfathima Jan 31, 2025
52ffcde
Update utils.py
mynameisfathima Jan 31, 2025
abc0ff1
main.py
mynameisfathima Jan 31, 2025
a0cbfb3
Update jinja.py
mynameisfathima Jan 31, 2025
ab185b8
Update setup.py
mynameisfathima Jan 31, 2025
d088e3c
Update jinja.py
mynameisfathima Jan 31, 2025
6cbab0e
Create expired-ssl.yaml
mynameisfathima Mar 23, 2025
44e6087
Create self-signed-ssl.yaml
mynameisfathima Mar 23, 2025
0b0c087
Update jinja.py
mynameisfathima Apr 6, 2025
65612cd
Update main.py
mynameisfathima Apr 6, 2025
f48fd33
Update requirments.txt
mynameisfathima Apr 9, 2025
e4cfff3
dmarc added
mynameisfathima Apr 10, 2025
c2d3a4c
DKIM added
mynameisfathima Apr 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions auth/pdcpauth.py
Original file line number Diff line number Diff line change
@@ -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
40 changes: 40 additions & 0 deletions banner.py
Original file line number Diff line number Diff line change
@@ -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()
41 changes: 31 additions & 10 deletions engine/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -75,4 +97,3 @@ def status_match(response: Response, matcher: Dict[str, Any]) -> bool:
return response.status_code in statuses



160 changes: 149 additions & 11 deletions engine/scanner.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -14,30 +16,48 @@ 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.
"""
method = request_config.get("method", "GET").upper()
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
Expand All @@ -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"),
Expand All @@ -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))
33 changes: 18 additions & 15 deletions engine/template_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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




Loading