Skip to content

Commit 53bdf15

Browse files
author
Rafael Brinhosa
committed
fix: Generate only one PoC per domain and improve screenshot naming
- Modified apidetectorv2.py to generate only one PoC per domain/subdomain - Improved screenshot filename generation in pocgenerator.py - Updated HTML template to handle the new screenshot naming format - Added domain information to screenshot filenames for better organization
1 parent 9dfc7d3 commit 53bdf15

File tree

3 files changed

+72
-9
lines changed

3 files changed

+72
-9
lines changed

apidetectorv2.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"""
2626

2727
# Function to test a single endpoint
28-
def test_endpoint(url, error_content, verbose, user_agent):
28+
def test_endpoint(url, error_content, verbose, user_agent, poc_already_generated=False):
2929
headers = {'User-Agent': user_agent}
3030
try:
3131
response = requests.get(url, headers=headers, timeout=30, allow_redirects=False)
@@ -37,18 +37,27 @@ def test_endpoint(url, error_content, verbose, user_agent):
3737
swagger_patterns = ['/swagger-ui', '/api-docs', '/openapi', '/swagger.', '/swagger-resources']
3838
is_swagger = any(pattern in url for pattern in swagger_patterns)
3939

40-
if is_swagger:
40+
# Only generate PoC if we haven't already generated one for this domain
41+
# and this is a Swagger/OpenAPI endpoint
42+
if is_swagger and not poc_already_generated:
4143
print(f"Calling PoC generator for {url}")
4244
current_dir = os.path.dirname(os.path.abspath(__file__))
4345
pocgenerator_path = os.path.join(current_dir, 'pocgenerator.py')
4446

47+
# Extract the domain from the URL for use in the filename
48+
from urllib.parse import urlparse
49+
parsed_url = urlparse(url)
50+
domain = parsed_url.netloc
51+
4552
# Add configUrl parameter for Swagger UI endpoints
4653
endpoint_url = url
4754
if '/swagger-ui' in url:
4855
endpoint_url += "?configUrl=https://raw.githubusercontent.com/brinhosa/payloads/master/testswagger.json"
4956

5057
# Pass the SCREENSHOT_PATH environment variable if it exists
5158
env = os.environ.copy()
59+
# Add domain information to help with filename generation
60+
env['DOMAIN'] = domain
5261
subprocess.run(['python3', pocgenerator_path, endpoint_url], env=env)
5362
return url
5463
except requests.RequestException as e:
@@ -96,12 +105,19 @@ def test_subdomain_endpoints(subdomain, common_endpoints, mixed_mode, verbose, u
96105
except requests.RequestException:
97106
pass
98107

108+
# Flag to track if we've already generated a PoC for this domain
109+
poc_generated = False
110+
99111
for protocol in protocols:
100112
for endpoint in common_endpoints:
101113
url = f"{protocol}{subdomain}{endpoint}"
102-
result = test_endpoint(url, error_content, verbose, user_agent)
114+
result = test_endpoint(url, error_content, verbose, user_agent, poc_generated)
103115
if result:
104116
valid_urls.append(result)
117+
# If this is a Swagger/OpenAPI endpoint, mark that we've generated a PoC
118+
swagger_patterns = ['/swagger-ui', '/api-docs', '/openapi', '/swagger.', '/swagger-resources']
119+
if any(pattern in url for pattern in swagger_patterns):
120+
poc_generated = True
105121
if verbose:
106122
print(f"Found: {url}")
107123
return valid_urls

pocgenerator.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,35 @@
1616
nest_asyncio.apply()
1717

1818
def generate_output_filename(url):
19-
# Remove the http or https part
20-
url = re.sub(r'^https?:\/\/', '', url)
21-
# Replace non-alphanumeric characters with underscores
22-
filename = re.sub(r'[^a-zA-Z0-9]', '_', url)
23-
return f"{filename[:60]}.png"
19+
# Get domain from environment if available
20+
domain = os.environ.get('DOMAIN', '')
21+
22+
# Extract endpoint path from URL
23+
url_path = url.split('://')[-1] # Remove protocol
24+
if '/' in url_path:
25+
# Split at first slash after domain
26+
parts = url_path.split('/', 1)
27+
if len(parts) > 1:
28+
endpoint = parts[1]
29+
else:
30+
endpoint = ''
31+
else:
32+
endpoint = ''
33+
34+
# Use domain and endpoint for filename
35+
if domain:
36+
# Create a clean domain name
37+
clean_domain = re.sub(r'[^a-zA-Z0-9]', '_', domain)
38+
# Create a clean endpoint name
39+
clean_endpoint = re.sub(r'[^a-zA-Z0-9]', '_', endpoint)
40+
# Combine them with a meaningful separator
41+
filename = f"{clean_domain}_{clean_endpoint[:40]}".strip('_')
42+
else:
43+
# Fallback to old method if domain not provided
44+
url_no_protocol = re.sub(r'^https?:\/\/', '', url)
45+
filename = re.sub(r'[^a-zA-Z0-9]', '_', url_no_protocol)[:60]
46+
47+
return f"{filename}.png"
2448

2549
async def generate_poc_screenshot(url, output_file):
2650
try:

templates/index.html

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,30 @@ <h2 class="text-xl font-semibold">Discovered Endpoints</h2>
316316
<div class="border-t border-gray-200 pt-3">
317317
<h4 class="text-sm font-semibold text-secondary-700 mb-2">Proof of Concept:</h4>
318318
<div class="relative">
319-
<img :src="'/screenshots/' + result.replace(/https?:///g, '').replace(/[^a-zA-Z0-9]/g, '_') + '.png'"
319+
<!-- Extract domain and endpoint for screenshot filename -->
320+
<img x-init="
321+
let url = result;
322+
let domain = '';
323+
let endpoint = '';
324+
325+
// Extract domain
326+
if (url.includes('://')) {
327+
let urlNoProtocol = url.split('://')[1];
328+
if (urlNoProtocol.includes('/')) {
329+
domain = urlNoProtocol.split('/')[0];
330+
endpoint = urlNoProtocol.substring(urlNoProtocol.indexOf('/'));
331+
} else {
332+
domain = urlNoProtocol;
333+
}
334+
}
335+
336+
// Clean domain and endpoint for filename
337+
let cleanDomain = domain.replace(/[^a-zA-Z0-9]/g, '_');
338+
let cleanEndpoint = endpoint.replace(/[^a-zA-Z0-9]/g, '_');
339+
340+
// Set the src attribute
341+
$el.src = '/screenshots/' + cleanDomain + '_' + cleanEndpoint.substring(0, 40) + '.png';
342+
"
320343
class="w-full h-auto rounded-lg border border-gray-300"
321344
alt="PoC Screenshot"
322345
@error="$el.classList.add('hidden'); $el.nextElementSibling.classList.remove('hidden')"

0 commit comments

Comments
 (0)