mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 09:30:21 +00:00
fix(html): escape provider identity fields in report header (#12424)
Co-authored-by: pedrooot <pedromarting3@gmail.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pedrooot <56402503+pedrooot@users.noreply.github.com>
This commit is contained in:
co-authored by
pedrooot
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
pedrooot
parent
de64df11b9
commit
02df22ca19
@@ -0,0 +1 @@
|
||||
HTML report header now HTML-escapes every provider identity field across all 23 providers, closing a stored XSS in the header block (Secur0, CWE-79) that was left unaddressed by the earlier finding-row fix in #12221
|
||||
@@ -463,6 +463,11 @@ class HTML(Output):
|
||||
audited_regions = "All Regions"
|
||||
else:
|
||||
audited_regions = ", ".join(provider.identity.audited_regions)
|
||||
account = escape(str(provider.identity.account))
|
||||
profile = escape(str(profile))
|
||||
audited_regions = escape(str(audited_regions))
|
||||
user_id = escape(str(provider.identity.user_id))
|
||||
identity_arn = escape(str(provider.identity.identity_arn))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -471,7 +476,7 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>AWS Account:</b> {provider.identity.account}
|
||||
<b>AWS Account:</b> {account}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>AWS-CLI Profile:</b> {profile}
|
||||
@@ -489,10 +494,10 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>User Id:</b> {provider.identity.user_id}
|
||||
<b>User Id:</b> {user_id}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Caller Identity ARN:</b> {provider.identity.identity_arn}
|
||||
<b>Caller Identity ARN:</b> {identity_arn}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -530,6 +535,11 @@ class HTML(Output):
|
||||
)
|
||||
else:
|
||||
html_identity = provider.identity.identity_id
|
||||
tenant_ids = escape(" ".join(provider.identity.tenant_ids))
|
||||
tenant_domain = escape(str(provider.identity.tenant_domain))
|
||||
subscriptions = escape(" ".join(printed_subscriptions))
|
||||
identity_type = escape(str(provider.identity.identity_type))
|
||||
html_identity = escape(str(html_identity))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -538,13 +548,13 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Azure Tenant IDs:</b> {" ".join(provider.identity.tenant_ids)}
|
||||
<b>Azure Tenant IDs:</b> {tenant_ids}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Azure Tenant Domain:</b> {provider.identity.tenant_domain}
|
||||
<b>Azure Tenant Domain:</b> {tenant_domain}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Azure Subscriptions:</b> {" ".join(printed_subscriptions)}
|
||||
<b>Azure Subscriptions:</b> {subscriptions}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -556,7 +566,7 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Azure Identity Type:</b> {provider.identity.identity_type}
|
||||
<b>Azure Identity Type:</b> {identity_type}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Azure Identity ID:</b> {html_identity}
|
||||
@@ -591,6 +601,8 @@ class HTML(Output):
|
||||
)
|
||||
except AttributeError:
|
||||
profile = "default"
|
||||
project_ids = escape(", ".join(provider.project_ids))
|
||||
profile = escape(str(profile))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -599,7 +611,7 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>GCP Project IDs:</b> {", ".join(provider.project_ids)}
|
||||
<b>GCP Project IDs:</b> {project_ids}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -634,6 +646,8 @@ class HTML(Output):
|
||||
str: the HTML assessment summary
|
||||
"""
|
||||
try:
|
||||
cluster = escape(str(provider.identity.cluster))
|
||||
context = escape(str(provider.identity.context))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -643,7 +657,7 @@ class HTML(Output):
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Kubernetes Cluster:</b> {provider.identity.cluster}
|
||||
<b>Kubernetes Cluster:</b> {cluster}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -656,7 +670,7 @@ class HTML(Output):
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Kubernetes Context:</b> {provider.identity.context}
|
||||
<b>Kubernetes Context:</b> {context}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -679,11 +693,13 @@ class HTML(Output):
|
||||
str: the HTML assessment summary
|
||||
"""
|
||||
try:
|
||||
auth_method = escape(str(provider.auth_method))
|
||||
if hasattr(provider.identity, "account_name"):
|
||||
# GithubIdentityInfo (Personal Access Token, OAuth)
|
||||
account_name = escape(str(provider.identity.account_name))
|
||||
account_info_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub account:</b> {provider.identity.account_name}
|
||||
<b>GitHub account:</b> {account_name}
|
||||
</li>
|
||||
"""
|
||||
# Add email if available
|
||||
@@ -691,23 +707,27 @@ class HTML(Output):
|
||||
hasattr(provider.identity, "account_email")
|
||||
and provider.identity.account_email
|
||||
):
|
||||
account_email = escape(str(provider.identity.account_email))
|
||||
account_info_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub account email:</b> {provider.identity.account_email}
|
||||
<b>GitHub account email:</b> {account_email}
|
||||
</li>"""
|
||||
elif hasattr(provider.identity, "app_id"):
|
||||
# GithubAppIdentityInfo (GitHub App)
|
||||
# Assessment items: App Name and Installations
|
||||
app_name = escape(str(provider.identity.app_name))
|
||||
account_info_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub App Name:</b> {provider.identity.app_name}
|
||||
<b>GitHub App Name:</b> {app_name}
|
||||
</li>"""
|
||||
# Add installations if available
|
||||
if (
|
||||
hasattr(provider.identity, "installations")
|
||||
and provider.identity.installations
|
||||
):
|
||||
installations_display = ", ".join(provider.identity.installations)
|
||||
installations_display = escape(
|
||||
", ".join(provider.identity.installations)
|
||||
)
|
||||
account_info_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Installations:</b> {installations_display}
|
||||
@@ -719,26 +739,27 @@ class HTML(Output):
|
||||
</li>"""
|
||||
|
||||
# Credentials items: Authentication method and App ID
|
||||
app_id = escape(str(provider.identity.app_id))
|
||||
credentials_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub authentication method:</b> {provider.auth_method}
|
||||
<b>GitHub authentication method:</b> {auth_method}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>GitHub App ID:</b> {provider.identity.app_id}
|
||||
<b>GitHub App ID:</b> {app_id}
|
||||
</li>"""
|
||||
else:
|
||||
# Fallback for other identity types
|
||||
account_info_items = ""
|
||||
credentials_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub authentication method:</b> {provider.auth_method}
|
||||
<b>GitHub authentication method:</b> {auth_method}
|
||||
</li>"""
|
||||
|
||||
# For PAT/OAuth, use default credentials structure
|
||||
if hasattr(provider.identity, "account_name"):
|
||||
credentials_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>GitHub authentication method:</b> {provider.auth_method}
|
||||
<b>GitHub authentication method:</b> {auth_method}
|
||||
</li>"""
|
||||
|
||||
return f"""
|
||||
@@ -779,6 +800,18 @@ class HTML(Output):
|
||||
str: the HTML assessment summary
|
||||
"""
|
||||
try:
|
||||
tenant_domain = escape(str(provider.identity.tenant_domain))
|
||||
identity_type = escape(str(provider.identity.identity_type))
|
||||
identity_id = escape(str(provider.identity.identity_id))
|
||||
user_item = ""
|
||||
if (
|
||||
hasattr(provider.identity, "user")
|
||||
and provider.identity.user is not None
|
||||
):
|
||||
user = escape(str(provider.identity.user))
|
||||
user_item = f"""<li class="list-group-item">
|
||||
<b>M365 User:</b> {user}
|
||||
</li>"""
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -787,9 +820,7 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>M365 Tenant Domain:</b> {
|
||||
provider.identity.tenant_domain
|
||||
}
|
||||
<b>M365 Tenant Domain:</b> {tenant_domain}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -801,19 +832,12 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>M365 Identity Type:</b> {provider.identity.identity_type}
|
||||
<b>M365 Identity Type:</b> {identity_type}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>M365 Identity ID:</b> {provider.identity.identity_id}
|
||||
<b>M365 Identity ID:</b> {identity_id}
|
||||
</li>
|
||||
{
|
||||
f'''<li class="list-group-item">
|
||||
<b>M365 User:</b> {provider.identity.user}
|
||||
</li>'''
|
||||
if hasattr(provider.identity, "user")
|
||||
and provider.identity.user is not None
|
||||
else ""
|
||||
}
|
||||
{user_item}
|
||||
</ul>
|
||||
</div>
|
||||
</div>"""
|
||||
@@ -834,6 +858,9 @@ class HTML(Output):
|
||||
str: the HTML assessment summary
|
||||
"""
|
||||
try:
|
||||
tenant_domain = escape(str(provider.identity.tenant_domain))
|
||||
identity_type = escape(str(provider.identity.identity_type))
|
||||
identity_id = escape(str(provider.identity.identity_id))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -842,7 +869,7 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>NHN Tenant Domain:</b> {provider.identity.tenant_domain}
|
||||
<b>NHN Tenant Domain:</b> {tenant_domain}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -854,10 +881,10 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>NHN Identity Type:</b> {provider.identity.identity_type}
|
||||
<b>NHN Identity Type:</b> {identity_type}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>NHN Identity ID:</b> {provider.identity.identity_id}
|
||||
<b>NHN Identity ID:</b> {identity_id}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -880,6 +907,7 @@ class HTML(Output):
|
||||
str: the HTML assessment summary
|
||||
"""
|
||||
try:
|
||||
organization_name = escape(str(provider.identity.organization_name))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -889,7 +917,7 @@ class HTML(Output):
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>MongoDB Atlas organization:</b> {provider.identity.organization_name}
|
||||
<b>MongoDB Atlas organization:</b> {organization_name}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -925,6 +953,13 @@ class HTML(Output):
|
||||
str: the HTML assessment summary
|
||||
"""
|
||||
try:
|
||||
if provider.scan_repository_url:
|
||||
target_info = "<b>IAC repository URL:</b> " + str(
|
||||
escape(str(provider.scan_repository_url))
|
||||
)
|
||||
else:
|
||||
target_info = "<b>IAC path:</b> " + str(escape(str(provider.scan_path)))
|
||||
auth_method = escape(str(provider.auth_method))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -934,7 +969,7 @@ class HTML(Output):
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
{"<b>IAC repository URL:</b> " + provider.scan_repository_url if provider.scan_repository_url else "<b>IAC path:</b> " + provider.scan_path}
|
||||
{target_info}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -947,7 +982,7 @@ class HTML(Output):
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>IAC authentication method:</b> {provider.auth_method}
|
||||
<b>IAC authentication method:</b> {auth_method}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -971,10 +1006,13 @@ class HTML(Output):
|
||||
"""
|
||||
try:
|
||||
if provider.registry:
|
||||
target_info = f"<b>Registry URL:</b> {provider.registry}"
|
||||
registry = escape(str(provider.registry))
|
||||
target_info = f"<b>Registry URL:</b> {registry}"
|
||||
else:
|
||||
target_info = f'<b>Images:</b> {", ".join(provider.images)}'
|
||||
images = escape(", ".join(provider.images))
|
||||
target_info = f"<b>Images:</b> {images}"
|
||||
|
||||
auth_method = escape(str(provider.auth_method))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -997,7 +1035,7 @@ class HTML(Output):
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Image authentication method:</b> {provider.auth_method}
|
||||
<b>Image authentication method:</b> {auth_method}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1020,6 +1058,14 @@ class HTML(Output):
|
||||
str: HTML assessment summary for the LLM provider
|
||||
"""
|
||||
try:
|
||||
model = escape(str(provider.model))
|
||||
plugins = escape(", ".join(provider.plugins))
|
||||
max_concurrency = escape(str(provider.max_concurrency))
|
||||
config_file = escape(
|
||||
str(provider.config_path)
|
||||
if provider.config_path
|
||||
else "Using promptfoo defaults"
|
||||
)
|
||||
return f"""
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
@@ -1031,16 +1077,16 @@ class HTML(Output):
|
||||
<ul class="list-group
|
||||
list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Target LLM:</b> {provider.model}
|
||||
<b>Target LLM:</b> {model}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Plugins:</b> {", ".join(provider.plugins)}
|
||||
<b>Plugins:</b> {plugins}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Max concurrency:</b> {provider.max_concurrency}
|
||||
<b>Max concurrency:</b> {max_concurrency}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Config file:</b> {provider.config_path if provider.config_path else "Using promptfoo defaults"}
|
||||
<b>Config file:</b> {config_file}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1069,6 +1115,10 @@ class HTML(Output):
|
||||
tenancy_name = getattr(provider.identity, "tenancy_name", "unknown")
|
||||
tenancy_id = getattr(provider.identity, "tenancy_id", "unknown")
|
||||
|
||||
tenancy = escape(
|
||||
str(tenancy_name if tenancy_name != "unknown" else tenancy_id)
|
||||
)
|
||||
profile = escape(str(profile))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -1077,7 +1127,7 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>OracleCloud Tenancy:</b> {tenancy_name if tenancy_name != "unknown" else tenancy_id}
|
||||
<b>OracleCloud Tenancy:</b> {tenancy}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1116,10 +1166,11 @@ class HTML(Output):
|
||||
project_name = getattr(provider.identity, "project_name", "")
|
||||
audited_regions = getattr(provider.identity, "audited_regions", set())
|
||||
|
||||
project_id = escape(str(project_id))
|
||||
project_name_item = (
|
||||
f"""
|
||||
<li class="list-group-item">
|
||||
<b>Project Name:</b> {project_name}
|
||||
<b>Project Name:</b> {escape(str(project_name))}
|
||||
</li>"""
|
||||
if project_name
|
||||
else ""
|
||||
@@ -1128,7 +1179,7 @@ class HTML(Output):
|
||||
regions_item = (
|
||||
f"""
|
||||
<li class="list-group-item">
|
||||
<b>Regions:</b> {", ".join(sorted(audited_regions))}
|
||||
<b>Regions:</b> {escape(", ".join(sorted(audited_regions)))}
|
||||
</li>"""
|
||||
if audited_regions
|
||||
else ""
|
||||
@@ -1182,7 +1233,7 @@ class HTML(Output):
|
||||
# Build assessment summary items (only non-None values)
|
||||
assessment_items = ""
|
||||
if provider.accounts:
|
||||
accounts = ", ".join([acc.id for acc in provider.accounts])
|
||||
accounts = escape(", ".join([str(acc.id) for acc in provider.accounts]))
|
||||
assessment_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Accounts:</b> {accounts}
|
||||
@@ -1208,6 +1259,7 @@ class HTML(Output):
|
||||
provider.session, "api_email", None
|
||||
)
|
||||
if email:
|
||||
email = escape(str(email))
|
||||
credentials_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Email:</b> {email}
|
||||
@@ -1261,11 +1313,15 @@ class HTML(Output):
|
||||
account_name_item = (
|
||||
f"""
|
||||
<li class="list-group-item">
|
||||
<b>Account Name:</b> {account_name}
|
||||
<b>Account Name:</b> {escape(str(account_name))}
|
||||
</li>"""
|
||||
if account_name
|
||||
else ""
|
||||
)
|
||||
account_id = escape(str(account_id))
|
||||
audited_regions = escape(str(audited_regions))
|
||||
user_name = escape(str(user_name))
|
||||
identity_arn = escape(str(identity_arn))
|
||||
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
@@ -1326,7 +1382,7 @@ class HTML(Output):
|
||||
project_name_item = (
|
||||
f"""
|
||||
<li class="list-group-item">
|
||||
<b>Project Name:</b> {project_name}
|
||||
<b>Project Name:</b> {escape(str(project_name))}
|
||||
</li>"""
|
||||
if project_name
|
||||
else ""
|
||||
@@ -1335,11 +1391,14 @@ class HTML(Output):
|
||||
user_id_item = (
|
||||
f"""
|
||||
<li class="list-group-item">
|
||||
<b>User ID:</b> {user_id}
|
||||
<b>User ID:</b> {escape(str(user_id))}
|
||||
</li>"""
|
||||
if user_id
|
||||
else ""
|
||||
)
|
||||
project_id = escape(str(project_id))
|
||||
region_name = escape(str(region_name))
|
||||
username = escape(str(username))
|
||||
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
@@ -1389,6 +1448,9 @@ class HTML(Output):
|
||||
str: HTML assessment summary for the Google Workspace provider
|
||||
"""
|
||||
try:
|
||||
domain = escape(str(provider.identity.domain))
|
||||
customer_id = escape(str(provider.identity.customer_id))
|
||||
delegated_user = escape(str(provider.identity.delegated_user))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -1397,10 +1459,10 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Domain:</b> {provider.identity.domain}
|
||||
<b>Domain:</b> {domain}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Customer ID:</b> {provider.identity.customer_id}
|
||||
<b>Customer ID:</b> {customer_id}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1412,7 +1474,7 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Delegated User:</b> {provider.identity.delegated_user}
|
||||
<b>Delegated User:</b> {delegated_user}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Authentication Method:</b> Service Account with Domain-Wide Delegation
|
||||
@@ -1482,9 +1544,11 @@ class HTML(Output):
|
||||
|
||||
team = getattr(provider.identity, "team", None)
|
||||
if team:
|
||||
team_name = escape(str(team.name))
|
||||
team_id = escape(str(team.id))
|
||||
assessment_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Team:</b> {team.name} ({team.id})
|
||||
<b>Team:</b> {team_name} ({team_id})
|
||||
</li>"""
|
||||
|
||||
credentials_items = """
|
||||
@@ -1494,6 +1558,7 @@ class HTML(Output):
|
||||
|
||||
email = getattr(provider.identity, "email", None)
|
||||
if email:
|
||||
email = escape(str(email))
|
||||
credentials_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Email:</b> {email}
|
||||
@@ -1501,6 +1566,7 @@ class HTML(Output):
|
||||
|
||||
username = getattr(provider.identity, "username", None)
|
||||
if username:
|
||||
username = escape(str(username))
|
||||
credentials_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Username:</b> {username}
|
||||
@@ -1543,17 +1609,20 @@ class HTML(Output):
|
||||
str: HTML assessment summary for the Okta provider
|
||||
"""
|
||||
try:
|
||||
org_domain = escape(str(provider.identity.org_domain))
|
||||
auth_method = escape(str(provider.auth_method))
|
||||
client_id = escape(str(provider.identity.client_id))
|
||||
assessment_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>Okta Domain:</b> {provider.identity.org_domain}
|
||||
<b>Okta Domain:</b> {org_domain}
|
||||
</li>"""
|
||||
|
||||
credentials_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>Authentication:</b> {provider.auth_method}
|
||||
<b>Authentication:</b> {auth_method}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Client ID:</b> {provider.identity.client_id}
|
||||
<b>Client ID:</b> {client_id}
|
||||
</li>"""
|
||||
|
||||
return f"""
|
||||
@@ -1593,9 +1662,10 @@ class HTML(Output):
|
||||
str: HTML assessment summary for the Scaleway provider
|
||||
"""
|
||||
try:
|
||||
organization_id = escape(str(provider.identity.organization_id))
|
||||
assessment_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>Organization ID:</b> {provider.identity.organization_id}
|
||||
<b>Organization ID:</b> {organization_id}
|
||||
</li>"""
|
||||
|
||||
credentials_items = """
|
||||
@@ -1605,6 +1675,7 @@ class HTML(Output):
|
||||
|
||||
access_key = getattr(provider.session, "access_key", None)
|
||||
if access_key:
|
||||
access_key = escape(str(access_key))
|
||||
credentials_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Access Key:</b> {access_key}
|
||||
@@ -1615,6 +1686,8 @@ class HTML(Output):
|
||||
bearer_id = getattr(provider.identity, "bearer_id", None)
|
||||
if bearer_type:
|
||||
bearer_label = bearer_email or bearer_id or "-"
|
||||
bearer_type = escape(str(bearer_type))
|
||||
bearer_label = escape(str(bearer_label))
|
||||
credentials_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Bearer:</b> {bearer_type} ({bearer_label})
|
||||
@@ -1622,6 +1695,7 @@ class HTML(Output):
|
||||
|
||||
region = getattr(provider.session, "default_region", None)
|
||||
if region:
|
||||
region = escape(str(region))
|
||||
credentials_items += f"""
|
||||
<li class="list-group-item">
|
||||
<b>Default Region:</b> {region}
|
||||
@@ -1668,6 +1742,9 @@ class HTML(Output):
|
||||
email = getattr(provider.identity, "email", None) or "-"
|
||||
account_id = getattr(provider.identity, "account_id", None) or "-"
|
||||
|
||||
username = escape(str(username))
|
||||
email = escape(str(email))
|
||||
account_id = escape(str(account_id))
|
||||
assessment_items = f"""
|
||||
<li class="list-group-item">
|
||||
<b>Account ID:</b> {account_id}
|
||||
@@ -1732,6 +1809,14 @@ class HTML(Output):
|
||||
audited_regions = "All Regions"
|
||||
else:
|
||||
audited_regions = ", ".join(provider.identity.regions)
|
||||
account_id = escape(str(provider.identity.account_id))
|
||||
account_name = escape(str(provider.identity.account_name))
|
||||
profile = escape(str(profile))
|
||||
audited_regions = escape(str(audited_regions))
|
||||
domain_id = escape(str(provider.identity.domain_id))
|
||||
user_id = escape(str(provider.identity.user_id))
|
||||
user_name = escape(str(provider.identity.user_name))
|
||||
identity_type = escape(str(provider.identity.identity_type))
|
||||
return f"""
|
||||
<div class="col-md-2">
|
||||
<div class="card">
|
||||
@@ -1740,10 +1825,10 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Account ID:</b> {provider.identity.account_id}
|
||||
<b>Account ID:</b> {account_id}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Account Name:</b> {provider.identity.account_name}
|
||||
<b>Account Name:</b> {account_name}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Profile:</b> {profile}
|
||||
@@ -1761,16 +1846,16 @@ class HTML(Output):
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<b>Domain ID:</b> {provider.identity.domain_id}
|
||||
<b>Domain ID:</b> {domain_id}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>User ID:</b> {provider.identity.user_id}
|
||||
<b>User ID:</b> {user_id}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>User Name:</b> {provider.identity.user_name}
|
||||
<b>User Name:</b> {user_name}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<b>Identity Type:</b> {provider.identity.identity_type}
|
||||
<b>Identity Type:</b> {identity_type}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import re
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
from mock import MagicMock, patch
|
||||
|
||||
from prowler.config.config import prowler_version, timestamp
|
||||
@@ -676,6 +678,192 @@ html_footer = """
|
||||
"""
|
||||
|
||||
|
||||
def _setup_aws_xss(provider, payload):
|
||||
provider.identity.account = payload
|
||||
provider.identity.profile = payload
|
||||
provider.identity.audited_regions = [payload]
|
||||
provider.identity.user_id = payload
|
||||
provider.identity.identity_arn = payload
|
||||
|
||||
|
||||
def _setup_azure_xss(provider, payload):
|
||||
provider.identity.tenant_ids = [payload]
|
||||
provider.identity.tenant_domain = payload
|
||||
provider.identity.subscriptions = {payload: payload}
|
||||
provider.identity.identity_type = payload
|
||||
provider.identity.identity_id = payload
|
||||
|
||||
|
||||
def _setup_gcp_xss(provider, payload):
|
||||
provider.project_ids = [payload]
|
||||
provider.session._service_account_email = payload
|
||||
|
||||
|
||||
def _setup_kubernetes_xss(provider, payload):
|
||||
provider.identity.cluster = payload
|
||||
provider.identity.context = payload
|
||||
|
||||
|
||||
def _setup_github_xss(provider, payload):
|
||||
provider.identity = MagicMock(spec=["account_name", "account_email"])
|
||||
provider.identity.account_name = payload
|
||||
provider.identity.account_email = payload
|
||||
provider.auth_method = payload
|
||||
|
||||
|
||||
def _setup_m365_xss(provider, payload):
|
||||
provider.identity.tenant_domain = payload
|
||||
provider.identity.identity_type = payload
|
||||
provider.identity.identity_id = payload
|
||||
provider.identity.user = payload
|
||||
|
||||
|
||||
def _setup_nhn_xss(provider, payload):
|
||||
provider.identity.tenant_domain = payload
|
||||
provider.identity.identity_type = payload
|
||||
provider.identity.identity_id = payload
|
||||
|
||||
|
||||
def _setup_mongodbatlas_xss(provider, payload):
|
||||
provider.identity.organization_name = payload
|
||||
|
||||
|
||||
def _setup_iac_xss(provider, payload):
|
||||
provider.scan_repository_url = payload
|
||||
provider.scan_path = None
|
||||
provider.auth_method = payload
|
||||
|
||||
|
||||
def _setup_image_xss(provider, payload):
|
||||
provider.registry = payload
|
||||
provider.images = [payload]
|
||||
provider.auth_method = payload
|
||||
|
||||
|
||||
def _setup_llm_xss(provider, payload):
|
||||
provider.model = payload
|
||||
provider.plugins = [payload]
|
||||
provider.max_concurrency = payload
|
||||
provider.config_path = payload
|
||||
|
||||
|
||||
def _setup_oraclecloud_xss(provider, payload):
|
||||
provider.session.profile = payload
|
||||
provider.identity.tenancy_name = payload
|
||||
provider.identity.tenancy_id = payload
|
||||
|
||||
|
||||
def _setup_stackit_xss(provider, payload):
|
||||
provider.identity.project_id = payload
|
||||
provider.identity.project_name = payload
|
||||
provider.identity.audited_regions = {payload}
|
||||
|
||||
|
||||
def _setup_cloudflare_xss(provider, payload):
|
||||
account = MagicMock()
|
||||
account.id = payload
|
||||
provider.accounts = [account]
|
||||
provider.session.api_token = "token"
|
||||
provider.session.api_key = None
|
||||
provider.session.api_email = None
|
||||
provider.identity.email = payload
|
||||
|
||||
|
||||
def _setup_alibabacloud_xss(provider, payload):
|
||||
provider.identity.account_id = payload
|
||||
provider.identity.account_name = payload
|
||||
provider.identity.audited_regions = payload
|
||||
provider.identity.identity_arn = payload
|
||||
provider.identity.user_name = payload
|
||||
|
||||
|
||||
def _setup_openstack_xss(provider, payload):
|
||||
provider.identity.project_id = payload
|
||||
provider.identity.project_name = payload
|
||||
provider.identity.region_name = payload
|
||||
provider.identity.username = payload
|
||||
provider.identity.user_id = payload
|
||||
|
||||
|
||||
def _setup_googleworkspace_xss(provider, payload):
|
||||
provider.identity.domain = payload
|
||||
provider.identity.customer_id = payload
|
||||
provider.identity.delegated_user = payload
|
||||
|
||||
|
||||
def _setup_e2enetworks_xss(provider, payload):
|
||||
provider.identity.project_id = payload
|
||||
provider.identity.locations = [payload]
|
||||
|
||||
|
||||
def _setup_vercel_xss(provider, payload):
|
||||
team = MagicMock()
|
||||
team.name = payload
|
||||
team.id = payload
|
||||
provider.identity.team = team
|
||||
provider.identity.email = payload
|
||||
provider.identity.username = payload
|
||||
|
||||
|
||||
def _setup_okta_xss(provider, payload):
|
||||
provider.identity.org_domain = payload
|
||||
provider.auth_method = payload
|
||||
provider.identity.client_id = payload
|
||||
|
||||
|
||||
def _setup_scaleway_xss(provider, payload):
|
||||
provider.identity.organization_id = payload
|
||||
provider.session.access_key = payload
|
||||
provider.identity.bearer_type = payload
|
||||
provider.identity.bearer_email = payload
|
||||
provider.identity.bearer_id = payload
|
||||
provider.session.default_region = payload
|
||||
|
||||
|
||||
def _setup_linode_xss(provider, payload):
|
||||
provider.identity.username = payload
|
||||
provider.identity.email = payload
|
||||
provider.identity.account_id = payload
|
||||
|
||||
|
||||
def _setup_huaweicloud_xss(provider, payload):
|
||||
provider.identity.account_id = payload
|
||||
provider.identity.account_name = payload
|
||||
provider.identity.profile = payload
|
||||
provider.identity.regions = {payload}
|
||||
provider.identity.domain_id = payload
|
||||
provider.identity.user_id = payload
|
||||
provider.identity.user_name = payload
|
||||
provider.identity.identity_type = payload
|
||||
|
||||
|
||||
PROVIDER_XSS_SETUPS = [
|
||||
("aws", _setup_aws_xss),
|
||||
("azure", _setup_azure_xss),
|
||||
("gcp", _setup_gcp_xss),
|
||||
("kubernetes", _setup_kubernetes_xss),
|
||||
("github", _setup_github_xss),
|
||||
("m365", _setup_m365_xss),
|
||||
("nhn", _setup_nhn_xss),
|
||||
("mongodbatlas", _setup_mongodbatlas_xss),
|
||||
("iac", _setup_iac_xss),
|
||||
("image", _setup_image_xss),
|
||||
("llm", _setup_llm_xss),
|
||||
("oraclecloud", _setup_oraclecloud_xss),
|
||||
("stackit", _setup_stackit_xss),
|
||||
("cloudflare", _setup_cloudflare_xss),
|
||||
("alibabacloud", _setup_alibabacloud_xss),
|
||||
("openstack", _setup_openstack_xss),
|
||||
("googleworkspace", _setup_googleworkspace_xss),
|
||||
("e2enetworks", _setup_e2enetworks_xss),
|
||||
("vercel", _setup_vercel_xss),
|
||||
("okta", _setup_okta_xss),
|
||||
("scaleway", _setup_scaleway_xss),
|
||||
("linode", _setup_linode_xss),
|
||||
("huaweicloud", _setup_huaweicloud_xss),
|
||||
]
|
||||
|
||||
|
||||
class TestHTML:
|
||||
def test_transform_fail_finding(self):
|
||||
findings = [
|
||||
@@ -1100,6 +1288,103 @@ class TestHTML:
|
||||
assert "<script>alert(1)</script>" not in summary
|
||||
assert "Delhi"><script>alert(1)</script>" in summary
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"provider_type,setup_fn",
|
||||
PROVIDER_XSS_SETUPS,
|
||||
ids=[t for t, _ in PROVIDER_XSS_SETUPS],
|
||||
)
|
||||
def test_get_assessment_summary_escapes_provider_identity(
|
||||
self, provider_type, setup_fn
|
||||
):
|
||||
"""Every provider header must HTML-escape tenant-controlled identity fields."""
|
||||
payload = "<script>alert(1)</script>"
|
||||
findings = [generate_finding_output()]
|
||||
output = HTML(findings)
|
||||
|
||||
provider = MagicMock()
|
||||
provider.type = provider_type
|
||||
setup_fn(provider, payload)
|
||||
|
||||
summary = output.get_assessment_summary(provider)
|
||||
|
||||
assert payload not in summary
|
||||
assert "<script>alert(1)</script>" in summary
|
||||
|
||||
def test_provider_xss_setups_covers_every_assessment_summary_method(self):
|
||||
"""Adding a new get_<provider>_assessment_summary without a PROVIDER_XSS_SETUPS
|
||||
entry must fail this test, so the escape guarantee cannot silently regress."""
|
||||
pattern = re.compile(r"^get_(.+)_assessment_summary$")
|
||||
discovered = set()
|
||||
for name in dir(HTML):
|
||||
match = pattern.match(name)
|
||||
if match:
|
||||
discovered.add(match.group(1))
|
||||
|
||||
covered = {ptype for ptype, _ in PROVIDER_XSS_SETUPS}
|
||||
|
||||
missing = discovered - covered
|
||||
extra = covered - discovered
|
||||
assert (
|
||||
not missing
|
||||
), f"providers without XSS coverage in PROVIDER_XSS_SETUPS: {sorted(missing)}"
|
||||
assert (
|
||||
not extra
|
||||
), f"PROVIDER_XSS_SETUPS entries with no matching HTML method: {sorted(extra)}"
|
||||
|
||||
def test_github_app_get_assessment_summary_escapes_app_identity(self):
|
||||
"""The GitHub App branch (elif hasattr app_id) must escape app_name/app_id/installations,
|
||||
which the PAT setup does not exercise."""
|
||||
payload = "<script>alert(1)</script>"
|
||||
findings = [generate_finding_output()]
|
||||
output = HTML(findings)
|
||||
|
||||
provider = MagicMock()
|
||||
provider.type = "github"
|
||||
provider.identity = MagicMock(spec=["app_id", "app_name", "installations"])
|
||||
provider.identity.app_id = payload
|
||||
provider.identity.app_name = payload
|
||||
provider.identity.installations = [payload]
|
||||
provider.auth_method = payload
|
||||
|
||||
summary = output.get_assessment_summary(provider)
|
||||
|
||||
assert payload not in summary
|
||||
assert "<script>alert(1)</script>" in summary
|
||||
|
||||
def test_iac_get_assessment_summary_escapes_scan_path(self):
|
||||
"""The IAC scan_path branch (else of `if scan_repository_url`) must escape it."""
|
||||
payload = "<script>alert(1)</script>"
|
||||
findings = [generate_finding_output()]
|
||||
output = HTML(findings)
|
||||
|
||||
provider = MagicMock()
|
||||
provider.type = "iac"
|
||||
provider.scan_repository_url = None
|
||||
provider.scan_path = payload
|
||||
provider.auth_method = payload
|
||||
|
||||
summary = output.get_assessment_summary(provider)
|
||||
|
||||
assert payload not in summary
|
||||
assert "<script>alert(1)</script>" in summary
|
||||
|
||||
def test_image_get_assessment_summary_escapes_images_list(self):
|
||||
"""The Image `else` branch (no registry, images list) must escape each image."""
|
||||
payload = "<script>alert(1)</script>"
|
||||
findings = [generate_finding_output()]
|
||||
output = HTML(findings)
|
||||
|
||||
provider = MagicMock()
|
||||
provider.type = "image"
|
||||
provider.registry = None
|
||||
provider.images = [payload]
|
||||
provider.auth_method = payload
|
||||
|
||||
summary = output.get_assessment_summary(provider)
|
||||
|
||||
assert payload not in summary
|
||||
assert "<script>alert(1)</script>" in summary
|
||||
|
||||
def test_process_markdown_bold_text(self):
|
||||
"""Test that **text** is converted to <strong>text</strong>"""
|
||||
test_text = "This is **bold text** and this is **also bold**"
|
||||
|
||||
Reference in New Issue
Block a user