- Description: The
docker-compose.yml
file sets a default, weak password "yourpass" for the MySQL root user if theMYSQL_PASSWORD
environment variable is not explicitly set during deployment. An attacker could potentially gain unauthorized access to the MySQL database if this default password is used in a production environment. - Impact: An attacker gaining access to the MySQL database could read, modify, or delete sensitive data, including user credentials, subscription information, and payment details. This can lead to a complete compromise of the application's backend and data integrity.
- Vulnerability rank: High
- Currently implemented mitigations: None in the provided files. The
docker-compose.yml
uses environment variables, which is a standard practice for configuration, but it relies on the user to override the default password. - Missing mitigations:
- The default password in
docker-compose.yml
should be removed or set to a strong, randomly generated value. - Documentation should explicitly warn users about the security risk of using default passwords and instruct them to set a strong
MYSQL_PASSWORD
environment variable before deploying the application. - The application could include a startup check to ensure that the MySQL root password has been changed from the default and refuse to start if it hasn't.
- The default password in
- Preconditions:
- The application is deployed using
docker-compose.yml
without setting a strongMYSQL_PASSWORD
environment variable. - The MySQL port (default 3306) is exposed to the attacker's network, either directly or indirectly through application vulnerabilities.
- The application is deployed using
- Source code analysis:
- File:
/code/docker-compose.yml
mysql: image: mysql:8.2 container_name: mysql restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD:-yourpass} MYSQL_DATABASE: sspanel
- The line
MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD:-yourpass}
sets the MySQL root password. The:-yourpass
part means that if the environment variableMYSQL_PASSWORD
is not set, it will default to "yourpass".
- File:
- Security test case:
- Deploy the application using
docker-compose up
without setting theMYSQL_PASSWORD
environment variable. - Attempt to connect to the MySQL database from outside the Docker container using the root user and password "yourpass", targeting the exposed MySQL port (if any, or within the Docker network).
- If the connection is successful, it confirms the vulnerability. For example, using
mysql -h <docker-host-ip> -P <exposed-mysql-port> -u root -p
.
- Deploy the application using
- Description: The API authentication mechanism uses a simple token-based approach where the API key (
settings.TOKEN
) is expected to be passed as a query parameter namedtoken
in GET requests. This method is vulnerable because API keys in query parameters can be easily exposed in server logs, browser history, and network traffic. An attacker could intercept or discover this API key and reuse it to gain unauthorized access to API endpoints. This issue is present in the@api_authorized
decorator used for API authentication and also when generating API endpoints in models likeProxyNode
andRelayNode
, where the token is directly embedded in the URL. - Impact: If the API key is compromised, an attacker can bypass authentication and access all API endpoints protected by the
@api_authorized
decorator or directly access endpoints using the generated URLs. Based onopenapi.yaml
and code analysis, these endpoints include functionalities to manage proxy nodes, relay nodes, user information, and retrieve configurations, potentially allowing attackers to manipulate the service, access user data, or perform administrative actions. - Vulnerability rank: High
- Currently implemented mitigations: The project implements a basic API key check using the
@api_authorized
decorator inapps/utils.py
andOpenAPIStaffAuthentication
inapps/openapi/utils.py
. - Missing mitigations:
- API key should not be passed in query parameters, including in dynamically generated URLs.
- Implement a more secure method for API key authentication for both
@api_authorized
decorator and OpenAPI, such as using:- API keys in request headers (e.g.,
Authorization: Bearer <API_KEY>
orX-API-KEY
). The OpenAPI already usesX-API-KEY
, but@api_authorized
decorator still uses query parameter. - OAuth 2.0 or JWT for more robust authentication and authorization.
- HTTPS should be enforced to encrypt network traffic and protect API keys during transmission.
- API keys in request headers (e.g.,
- Preconditions:
- API endpoints are protected using the
@api_authorized
decorator or accessed via URLs containing the API key. - The application is deployed with API endpoints accessible over the network.
- An attacker is able to observe network traffic, access server logs, or browser history where the API key might be exposed in URLs.
- API endpoints are protected using the
- Source code analysis:
- File:
/code/apps/utils.py
def api_authorized(view_func): @wraps(view_func) def wrapper(request, *args, **kwargs): token = request.GET.get("token", "") if token != settings.TOKEN: return JsonResponse({"msg": "auth error"}) return view_func(request, *args, **kwargs) return wrapper
- The
api_authorized
decorator retrieves the API key fromrequest.GET.get("token", "")
, indicating that the API key is expected in the query parametertoken
. - File:
/code/apps/api/views.py
class ProxyConfigsView(View): # ... @method_decorator(api_authorized) def get(self, request, node_id): # ... @method_decorator(api_authorized) def post(self, request, node_id): # ...
- The
@method_decorator(api_authorized)
is used to protect API views likeProxyConfigsView
, enforcing the API key check. - File:
/code/apps/proxy/models.py
&/code/apps/relay/models.py
class ProxyNode(BaseNodeModel, SequenceMixin): # ... @property def api_endpoint(self): params = {"token": settings.TOKEN} return f"{settings.SITE_HOST}/api/proxy_configs/{self.id}/?{urlencode(params)}" class RelayNode(BaseNodeModel): # ... @property def api_endpoint(self): params = {"token": settings.TOKEN} return ( f"{settings.SITE_HOST}/api/ehco_relay_config/{self.id}/?{urlencode(params)}" )
- The
api_endpoint
property inProxyNode
andRelayNode
models generates URLs that include the API key in thetoken
query parameter.
- File:
- Security test case:
- Identify an API endpoint protected by
@api_authorized
, for example,/api/proxy_configs/<node_id>/
. - Access the API endpoint without providing the
token
query parameter. Verify that access is denied with a JSON response{"msg": "auth error"}
. - Access the API endpoint with the correct
token
query parameter (assuming you have obtained or guessed thesettings.TOKEN
value). Verify that access is granted and you receive the expected API response. - Monitor network traffic (e.g., using a proxy like Burp Suite or browser developer tools) when accessing the API with the
token
in the query parameter. Observe that the API key is visible in the URL within the network request. - Check server logs and browser history to confirm that the API key is also logged in these locations if requests with the API key are made.
- Access the
api_endpoint
URLs generated byProxyNode
orRelayNode
models (e.g., by inspecting the HTML source in the admin panel if these URLs are used there). Verify that the API key is present in the URL.
- Identify an API endpoint protected by
- Description: The
settings_value
template tag inehcofilter.py
allows rendering Django settings values directly in templates. If a template using this tag is rendered and accessible to users, and if thename
argument to the tag corresponds to a sensitive setting (likeSECRET_KEY
, database credentials, API keys, etc.), then an attacker could potentially view these sensitive values. - Impact: Exposure of sensitive settings can lead to severe security breaches. For example, exposing
SECRET_KEY
can allow session hijacking, signing arbitrary data, and other attacks. Exposing database credentials or API keys can lead to unauthorized access to backend systems and data. - Vulnerability rank: High
- Currently implemented mitigations: None in the provided code.
- Missing mitigations:
- Avoid using the
settings_value
template tag in templates accessible to users, especially with settings names that could be sensitive. - If the tag is necessary, restrict its usage to admin-only templates and ensure that only non-sensitive settings are accessed through it.
- Review all templates to identify usages of
settings_value
and assess the risk of information disclosure.
- Avoid using the
- Preconditions:
- A template using the
settings_value
tag is rendered and accessible to an attacker (either anonymously or through user account access). - The
name
argument passed tosettings_value
in the template corresponds to a sensitive Django setting.
- A template using the
- Source code analysis:
- File:
/code/apps/sspanel/templatetags/ehcofilter.py
@register.simple_tag def settings_value(name): return getattr(settings, name, "")
- The
settings_value
tag directly retrieves and returns the value of the Django setting specified by thename
argument. There is no sanitization or access control in this tag. If a template uses{% settings_value 'SECRET_KEY' %}
, it will output theSECRET_KEY
value in the rendered HTML.
- File:
- Security test case:
- Identify a template in the application that is accessible to users (e.g., a user profile page, a public information page, etc.).
- Modify this template (if possible in a test environment, or by patching the template file) to include the following template tag:
{% load ehcofilter %}{% settings_value 'SECRET_KEY' %}
. - Access the modified template through the application in a browser.
- Inspect the HTML source of the page. If the
SECRET_KEY
(or any other sensitive setting) is visible in the HTML, the vulnerability is confirmed. - Alternatively, try to guess other potentially sensitive setting names (e.g.,
DEBUG
,DATABASE_PASSWORD
,API_TOKEN
, etc.) and test if they are also disclosed.
- Description: The project defines a custom function (e.g. in
/code/apps/utils.py
viaget_random_string
or similar functions used by the User model’s default value for passwords/invite codes) to create tokens for sensitive purposes. This function obtains randomness from Python’s built‑inrandom
module, re‑seeding it on every call using predictable values (such as the current state, time, and a hardcoded string). An attacker who can approximate the time window may be able to reproduce the seed and guess the generated tokens. - Impact: An attacker may predict or reproduce sensitive tokens (such as proxy passwords or invite codes), granting unauthorized access or allowing further compromise of subscription‑based resources.
- Vulnerability Rank: High (potentially Critical if used for critical credentials)
- Currently Implemented Mitigations: The project currently uses the insecure Python
random
module for token generation without incorporating additional entropy or cryptographically secure alternatives. - Missing Mitigations:
- Use a cryptographically secure random source (for example, Python’s
secrets
module oros.urandom()
). - Avoid re‑seeding on every call and adopt a secure mechanism that does not expose predictable state.
- Use a cryptographically secure random source (for example, Python’s
- Preconditions: Token generation is triggered during user account creation or when generating sensitive unique codes, and the attacker can estimate the generation time.
- Source Code Analysis:
- In the User migration file (e.g.
/code/apps/sspanel/migrations/0001_squashed_0055_auto_20200726_0847.py
), the User model’s password field uses a default ofapps.utils.get_short_random_string
(later renamed toproxy_password
in a subsequent migration). - The token generation logic re‑seeds the RNG with a hash of the current state, time, and a hardcoded secret, resulting in largely predictable outputs.
- In the User migration file (e.g.
- Security Test Case:
- Trigger the generation of new tokens by creating several user accounts (or actions that generate invite codes/proxy passwords).
- Record the approximate generation time and the produced tokens.
- Replicate the seeding and token generation logic in a controlled script using the same predictable inputs.
- Verify that the tokens can be re‑generated and used to access sensitive endpoints.
- Description: The endpoint for updating a user’s proxy configuration (in the
UserSettingsView
in/code/apps/api/views.py
) is decorated with@csrf_exempt
. Although the view requires that the user is authenticated vialogin_required
, the removal of Django’s CSRF protection allows an attacker to force a logged‑in user’s browser into submitting an unwanted POST request that updates their proxy settings. - Impact: An attacker may alter a user’s proxy configuration (for example, by changing the proxy password to an attacker‑controlled value), leading to account hijacking or misrouted network traffic.
- Vulnerability Rank: High
- Currently Implemented Mitigations: The view enforces that a user must be logged in; however, the explicit use of
@csrf_exempt
disables a key defense against CSRF. - Missing Mitigations:
- Remove the
@csrf_exempt
decorator or implement robust CSRF token verification on state‑changing endpoints. - Enforce that only genuine requests from the user’s browser (with a valid CSRF token) are permitted.
- Remove the
- Preconditions: The victim must be logged in using their browser, and the attacker must cause the victim’s browser to send a forged POST (via a hidden form or XHR) to the vulnerable endpoint.
- Source Code Analysis:
- In
/code/apps/api/views.py
, theUserSettingsView
is defined with adispatch
method decorated by@csrf_exempt
and apost
method that updates proxy configuration based solely on POST data. - The lack of a CSRF token check means that a third party can trigger unwanted changes when the user is authenticated.
- In
- Security Test Case:
- Log in to the application in a web browser as an authenticated user.
- Host a malicious HTML page that automatically submits a form to
/api/user/settings/
with altered proxy configuration data. - Visit the malicious page while still logged into the application.
- Verify that the proxy configuration has been changed as specified by the forged request.
- Description: The caching layer implemented in the file
/code/apps/extensions/cachext.py
uses Python’spickle
for serializing and deserializing objects when interacting with Redis. Specifically, theRedisClient.get()
method retrieves data from Redis and directly passes it topickle.loads()
without any validation or integrity checking. If an attacker can inject or manipulate data stored in Redis—perhaps by exploiting a misconfigured Redis instance or an SSRF that allows arbitrary writes—the malicious pickle payload could trigger arbitrary code execution upon deserialization. - Impact: Exploiting this vulnerability can lead to arbitrary code execution on the host server. An attacker who successfully deserializes a crafted payload may execute arbitrary Python code, compromise the application’s integrity and confidentiality, and potentially pivot to further compromise internal systems.
- Vulnerability Rank: Critical
- Currently Implemented Mitigations: There are no safeguards in the caching layer to authenticate or verify the integrity of the data retrieved from Redis. The use of
pickle.loads()
occurs directly, relying solely on Redis for storing serialized data. - Missing Mitigations:
- Use a safe serialization format such as JSON for caching data instead of pickle (which allows arbitrary code execution).
- Restrict network access to the Redis instance (using firewalls, proper configuration, and authentication) so that attackers cannot inject data.
- Optionally, sign cached data and verify the signature before deserialization.
- Preconditions: An attacker must be able to inject or modify cached data in the Redis instance. This could happen if Redis is misconfigured (e.g., exposed to the public internet without authentication) or if an SSRF or other vulnerability elsewhere in the application allows writing arbitrary data into the cache.
- Source Code Analysis:
- In
/code/apps/extensions/cachext.py
, theRedisClient
class defines theget()
method that retrieves data withself._client.get(key)
. - When a value is returned, it is immediately passed to
pickle.loads(v)
without any validation of the payload. - This unsafeguarded deserialization process means that any malicious payload stored in Redis would be executed when the application reads that key.
- In
- Security Test Case:
- Set up a test environment where the Redis server is intentionally misconfigured to allow external writes (or simulate an attacker’s injection into the Redis cache).
- Using a controlled tool/script, write a malicious pickle payload to a cache key that the application is expected to read (for example, one generated by the
make_default_key
function used by the caching decorator). - Invoke an application function that triggers a cache lookup for that key (e.g., call a cached function via its API endpoint).
- Observe that upon deserialization via
pickle.loads()
, the malicious payload is executed, confirming the presence of insecure deserialization.
- Description: In the file
/code/configs/default/sites.py
, the configuration setsALLOWED_HOSTS = ["*"]
. This wildcard setting causes Django to accept requests with any Host header. An attacker can exploit this misconfiguration to inject malicious host headers, potentially influencing how the application constructs absolute URLs, processes session cookies, or applies security policies. - Impact: An attacker may leverage host header injection to perform cache poisoning, facilitate password reset poisoning attacks, or bypass certain security controls that rely on host names, thereby undermining the integrity of application responses.
- Vulnerability Rank: High
- Currently Implemented Mitigations: The application’s configuration does not further validate or restrict incoming Host header values beyond the wildcard setting in
ALLOWED_HOSTS
. - Missing Mitigations:
- Restrict the
ALLOWED_HOSTS
setting to include only trusted domain names or IP addresses instead of using a wildcard. - Employ reverse proxy or firewall rules that enforce valid Host header values before requests reach the application.
- Restrict the
- Preconditions: The application is directly accessible to the public internet and the underlying network infrastructure does not enforce host header restrictions.
- Source Code Analysis:
- In
/code/configs/default/sites.py
, the lineALLOWED_HOSTS = ["*"]
is present. - This means that any Host header (including those set by an attacker) will be accepted by Django without additional checks.
- Maliciously crafted requests with attacker-controlled Host headers can therefore influence URL/email generation and other host-dependent functionality.
- In
- Security Test Case:
- Deploy the application using the current configuration.
- Craft an HTTP request to the server with a malicious
Host
header (e.g.,Host: malicious.com
). - Monitor the response to verify that the application uses the supplied Host header when generating links or handling sessions.
- Assess whether the manipulated Host header can facilitate further attacks (for example, by checking for unexpected redirects or modifications in session cookie behavior).
- Description: The configuration in
/code/configs/default/sites.py
sets theSECRET_KEY
using the environment variableSECRET_KEY
with a fallback default value of"aasdasdas"
. If this default value is deployed in a production environment, it severely undermines Django’s cryptographic signing mechanisms. An attacker who knows or can guess this key may forge session cookies, CSRF tokens, and other signed data. - Impact: With a predictable or default secret key, an attacker could compromise session integrity, hijack user accounts, and bypass numerous security measures that depend on cryptographic signatures, leading to a full compromise of user authentication and data integrity.
- Vulnerability Rank: Critical
- Currently Implemented Mitigations: The project does not enforce the mandatory provision of a strong, unique secret key in production environments; it falls back to an insecure default if the
SECRET_KEY
environment variable is not set. - Missing Mitigations:
- Enforce the use of a strong, unique, and unpredictable secret key in production, potentially aborting startup if one is not provided via environment variables.
- Remove insecure fallback defaults from the code.
- Preconditions: The application is deployed in a production-like environment without properly overriding the default
SECRET_KEY
via environment variables. - Source Code Analysis:
- In
/code/configs/default/sites.py
, the lineSECRET_KEY = os.getenv("SECRET_KEY", "aasdasdas")
sets the secret key. - If the
SECRET_KEY
environment variable is absent, the default insecure value ("aasdasdas") is used. - This predictable key compromises all cryptographic signatures, impacting session cookies, CSRF tokens, and other security-critical operations.
- In
- Security Test Case:
- Deploy the application in an environment without setting the
SECRET_KEY
environment variable. - Confirm that the application falls back to using the default insecure key.
- Attempt to forge a session cookie or tamper with any signed data (e.g., CSRF token) using the known default key.
- Verify that the application accepts the forged or tampered data, demonstrating a breach of cryptographic integrity.
- Deploy the application in an environment without setting the
-
Description:
- An attacker obtains a valid API key.
- The attacker sends a GET request to
/api/proxy_configs/{node_id}/
endpoint, replacing{node_id}
with the ID of a proxy node. - The server authenticates the request using the API key.
- The server retrieves the proxy node configuration based on the provided
node_id
. - The server returns the proxy node configurations in JSON format without verifying if the API key is authorized to access the configuration of this specific node.
- The attacker can iterate through different
node_id
values to retrieve configurations for various proxy nodes.
-
Impact:
- Exposure of sensitive proxy node configuration details such as server addresses, ports, encryption methods, and passwords if included in configurations.
- Attackers can use this information to directly target proxy servers, potentially bypassing application-level security controls, launching denial-of-service attacks against proxy nodes, or attempting to intercept user traffic.
-
Vulnerability Rank: High
-
Currently implemented mitigations:
- API key authentication is implemented using the
@api_authorized
decorator inapps/api/views.py
, which verifies the presence and validity of an API key in the request. This is a basic authentication measure.
- API key authentication is implemented using the
-
Missing mitigations:
- Missing authorization checks to verify if the authenticated API key has the necessary permissions to access the configuration of the requested proxy node (
node_id
). - Role-based access control (RBAC) or Access Control Lists (ACLs) are not implemented to manage API key permissions and restrict access to specific resources.
- Missing authorization checks to verify if the authenticated API key has the necessary permissions to access the configuration of the requested proxy node (
-
Preconditions:
- A publicly accessible instance of the django-sspanel application must be deployed.
- An attacker must possess a valid API key. This could be obtained through legitimate means (if API keys are intended for general use without authorization controls), or through compromising an administrative account or exploiting another vulnerability to retrieve an API key.
-
Source code analysis:
# File: /code/apps/api/views.py from django.views import View from django.http import JsonResponse, HttpResponseBadRequest from django.utils.decorators import method_decorator from apps.utils import api_authorized from apps.proxy import models as m class ProxyConfigsView(View): @method_decorator(api_authorized) def get(self, request, node_id): node = m.ProxyNode.get_or_none(node_id) # [POINT OF VULNERABILITY] - Retrieves node by ID without authorization check return ( JsonResponse(node.get_proxy_configs()) if node else HttpResponseBadRequest() )
- The
ProxyConfigsView
in/code/apps/api/views.py
is protected by the@api_authorized
decorator, which provides authentication by checking the API key. However, after successful authentication, the code directly retrieves theProxyNode
based on thenode_id
path parameter usingm.ProxyNode.get_or_none(node_id)
without any further authorization checks. This means any valid API key, regardless of its intended scope or permissions, can be used to access the configurations of anyProxyNode
by simply altering thenode_id
in the request. There is no mechanism to ensure that the API key is authorized to access the configuration of the specificProxyNode
being requested.
- The
-
Security test case:
- Setup: Deploy a publicly accessible instance of django-sspanel with at least two proxy nodes, for example, Node A (ID: 1) and Node B (ID: 2). Obtain a valid API key. Assume for this test that any valid API key grants access after authentication, which reflects the lack of authorization in the code.
- Step 1: Request Configuration for Node A: As an attacker, send a GET request to the endpoint for retrieving proxy configurations for Node A. Use the obtained API key in the
token
parameter and setnode_id
to 1.GET /api/proxy_configs/1/?token=<YOUR_API_KEY> HTTP/1.1 Host: <YOUR_DJANGO_SSPANEL_INSTANCE>
- Step 2: Observe Response for Node A: Examine the response from the server. It should return a JSON response containing the proxy configurations for Node A, indicating successful access.
- Step 3: Request Configuration for Node B: Now, send a similar GET request, but this time, change the
node_id
to 2 to target Node B, while using the same API key.GET /api/proxy_configs/2/?token=<YOUR_API_KEY> HTTP/1.1 Host: <YOUR_DJANGO_SSPANEL_INSTANCE>
- Step 4: Observe Response for Node B: Examine the response for this second request. It should also return a JSON response containing the proxy configurations for Node B, even though there was no specific authorization granted for this API key to access Node B's configuration.
- Step 5: Verify Vulnerability: If both requests are successful and return the configurations for different proxy nodes using the same API key, it confirms the Insecure Direct Object Reference vulnerability. This demonstrates that the API key, once authenticated, can access configurations of arbitrary proxy nodes without proper authorization checks.