332 lines
13 KiB
Plaintext
332 lines
13 KiB
Plaintext
[.analysis]
|
|
== ASVS Analysis
|
|
|
|
For the analysis section, the project will be evaluated under the scope of the V3 (Session Management) chapter of the OWASP ASVS, using version v4.0.3. This will include an assessment of the session management mechanisms implemented, as well as any vulnerabilities identified and possible mitigations.
|
|
|
|
=== V3 Session Management
|
|
|
|
==== Fundamental Session Management Security
|
|
|
|
[cols="^1,10,^1,^1", options="header", source]
|
|
|===
|
|
| Requirement | Description | Applicable | Implemented
|
|
|
|
| 3.1.1
|
|
| Verify the application never reveals session tokens in URL parameters.
|
|
| ✔
|
|
| ✔
|
|
|
|
|===
|
|
|
|
===== 3.1.1
|
|
|
|
The current implementation meets the requirement, as the session tokens are not exposed in the URL parameters but instead are sent in the Authorization header.
|
|
|
|
This way, instead of parsing the URL for the token, the server can directly access the token from the header, which is a more secure method of handling session tokens. This is done with the following line of code (including the necessary error handling):
|
|
|
|
[source,python]
|
|
----
|
|
token = request.headers.get("Authorization")
|
|
if not session_token:
|
|
return jsonify({"error": "No session token"}), 400
|
|
----
|
|
|
|
This piece of code is present in all endpoints that require a session token, ensuring that the token is always sent in the header and never in the URL.
|
|
|
|
==== Session Binding
|
|
|
|
[cols="^1,10,^1,^1", options="header", source]
|
|
|===
|
|
| Requirement | Description | Applicable | Implemented
|
|
|
|
| 3.2.1
|
|
| Verify the application generates a new session token on user authentication.
|
|
| ✔
|
|
| ✔
|
|
|
|
| 3.2.2
|
|
| Verify that session tokens possess at least 64 bits of entropy.
|
|
| ✔
|
|
| ✔
|
|
|
|
| 3.2.3
|
|
| Verify the application only stores session tokens in the browser using secure methods such as appropriately secured cookies (see section 3.4) or HTML 5 session storage.
|
|
| ✗
|
|
| ✗
|
|
|
|
| 3.2.4
|
|
| Verify that session tokens are generated using approved cryptographic algorithms.
|
|
| ✔
|
|
| ✔
|
|
|===
|
|
|
|
[[reqs_3_2_x]]
|
|
===== 3.2.1, 3.2.2, 3.2.4
|
|
|
|
The application generates a new session token on session creation when a user logs in.
|
|
|
|
This token is generated using the `secrets.token_hex(128)`
|
|
function, which generates a 256-character hexadecimal string, providing more than 64 bits of entropy. This function has been certified as secure by OWASP in their https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#secure-random-number-generation[cheat sheet series] footnote:[https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#secure-random-number-generation].
|
|
|
|
This generation is implemented in the code as follows:
|
|
|
|
[source,python]
|
|
----
|
|
def create_session(user: User, org: Organization) -> Session:
|
|
session = Session(
|
|
user_id=user.id,
|
|
org_id=org.id,
|
|
token=secrets.token_hex(128), # 256-character hexadecimal string
|
|
roles=[],
|
|
challenge=secrets.token_hex(128),
|
|
verified=False
|
|
)
|
|
db.add(session)
|
|
db.commit()
|
|
db.refresh(session)
|
|
return session
|
|
----
|
|
|
|
===== 3.2.3
|
|
|
|
This requirement is not applicable to the current implementation, as there is no browser involved with the application and therefore not used to store any session tokens.
|
|
|
|
==== Session Termination
|
|
|
|
[cols="^1,10,^1,^1", options="header", source]
|
|
|===
|
|
| Requirement | Description | Applicable | Implemented
|
|
|
|
| 3.3.1
|
|
| Verify that logout and expiration invalidate the session token, such that the back button or a downstream relying party does not resume an authenticated session, including across relying parties.
|
|
| ✔
|
|
| ✔
|
|
|
|
| 3.3.2
|
|
| If authenticators permit users to remain logged in, verify that re-authentication occurs periodically both when actively used or after an idle period.
|
|
| ✔
|
|
| ✗
|
|
|
|
| 3.3.3
|
|
| Verify that the application gives the option to terminate all other active sessions after a successful password change (including change via password reset/recovery), and that this is effective across the application, federated login (if present), and any relying parties.
|
|
| ✗
|
|
| ✗
|
|
|
|
| 3.3.4
|
|
| Verify that users are able to view and (having re-entered login credentials) log out of any or all currently active sessions and devices.
|
|
| ✔
|
|
| ✗
|
|
|===
|
|
|
|
===== 3.3.1
|
|
|
|
Upon logout, or any session deletion, the session data is completely removed from the database also deleting the session token, thus invalidating the session.
|
|
|
|
This is implemented in the code as follows, and accessed through the `POST /user/logout` endpoint:
|
|
|
|
[source,python]
|
|
----
|
|
def delete_session(session: Session) -> None:
|
|
db.delete(session)
|
|
db.commit()
|
|
----
|
|
|
|
===== 3.3.2
|
|
|
|
The application does not currently implement re-authentication after a period of inactivity.
|
|
|
|
This could be implemented by storing, for each session, the last time it was used, and checking this timestamp against the current time when a request is made. If the time difference exceeds the threshold defined in ASVS (12 hours or 15 minutes of inactivity, with 2FA), the user would be required to re-authenticate. This could be implemented in the code as follows:
|
|
|
|
[source,python]
|
|
----
|
|
def check_session_timeout(session: Session) -> bool:
|
|
return (datetime.now() - session.last_used).total_seconds() > SESSION_TIMEOUT
|
|
----
|
|
|
|
===== 3.3.3
|
|
|
|
This requirement is not applicable to the current implementation, as the application doesn't use password for logins, and therefore doesn't have a password change mechanism and thus not having the mechanism to terminate all other active sessions after a successful password change.
|
|
|
|
===== 3.3.4
|
|
|
|
Currently, there isn't a mechanism to view and log out of active sessions and devices.
|
|
|
|
This could be implemented by storing the device information in the session data and enabling an endpoint for the user to view all active sessions and devices, and then revoke access to them. This endpoint could be implemented as follows:
|
|
|
|
[source,python]
|
|
----
|
|
@user_bp.route("/sessions", methods=["GET"])
|
|
def list_sessions():
|
|
session_token = request.headers.get("Authorization")
|
|
if not session_token:
|
|
return jsonify({"error": "No session token"}), 400
|
|
|
|
try:
|
|
session = SessionService.validate_session(session_token)
|
|
except SessionException as e:
|
|
return jsonify({"error": e.message}), e.code
|
|
|
|
user = UserService.get_user(session.user_id)
|
|
if not user:
|
|
return jsonify({"error": "User not found"}), 404
|
|
|
|
sessions = SessionService.get_user_sessions(user)
|
|
return jsonify({"sessions": sessions}), 200
|
|
----
|
|
|
|
This would return a list of all active sessions for the user, and then the user could choose to revoke access to any of them, using the session id (not the token) through the following endpoint:
|
|
|
|
[source,python]
|
|
----
|
|
@user_bp.route("/logout/<session_id>", methods=["POST"])
|
|
def user_logout_session(session_id):
|
|
session_token = request.headers.get("Authorization")
|
|
if not session_token:
|
|
return jsonify({"error": "No session token"}), 400
|
|
|
|
current_session = SessionService.get_session(session_token)
|
|
if not current_session:
|
|
return jsonify({"error": "Not authenticated"}), 401
|
|
|
|
session = SessionService.get_session_by_id(session_id)
|
|
if not session:
|
|
return jsonify({"error": "Session not found"}), 404
|
|
|
|
if session.user_id != current_session.user_id:
|
|
return jsonify({"error": "Unauthorized"}), 403
|
|
|
|
SessionService.delete_session(session)
|
|
return jsonify({"message": f"Logged out from session with id {session_id}"}), 200
|
|
----
|
|
|
|
|
|
==== Cookie-based Session Management
|
|
|
|
[cols="^1,10,^1,^1", options="header", source]
|
|
|===
|
|
| Requirement | Description | Applicable | Implemented
|
|
|
|
| 3.4.1
|
|
| Verify that cookie-based session tokens have the 'Secure' attribute set.
|
|
| ✗
|
|
| ✗
|
|
|
|
| 3.4.2
|
|
| Verify that cookie-based session tokens have the 'HttpOnly' attribute set.
|
|
| ✗
|
|
| ✗
|
|
|
|
| 3.4.3
|
|
| Verify that cookie-based session tokens utilize the 'SameSite' attribute to limit exposure to cross-site request forgery attacks.
|
|
| ✗
|
|
| ✗
|
|
|
|
| 3.4.4
|
|
| Verify that cookie-based session tokens use the "__Host-" prefix so cookies are only sent to the host that initially set the cookie.
|
|
| ✗
|
|
| ✗
|
|
|
|
| 3.4.5
|
|
| Verify that if the application is published under a domain name with other applications that set or use session cookies that might disclose the session cookies, set the path attribute in cookie-based session tokens using the most precise path possible.
|
|
| ✗
|
|
| ✗
|
|
|===
|
|
|
|
===== 3.4.1, 3.4.2, 3.4.3, 3.4.4, 3.4.5
|
|
|
|
None of the requirements are applicable to the current implementation, as the application does not use cookies to store any data or to manage sessions.
|
|
|
|
==== Token-based Session Management
|
|
|
|
[cols="^1,10,^1,^1", options="header", source]
|
|
|===
|
|
| Requirement | Description | Applicable | Implemented
|
|
|
|
| 3.5.1
|
|
| Verify the application allows users to revoke OAuth tokens that form trust relationships with linked applications.
|
|
| ✗
|
|
| ✗
|
|
|
|
| 3.5.2
|
|
| Verify the application uses session tokens rather than static API secrets and keys, except with legacy implementations.
|
|
| ✔
|
|
| ✔
|
|
|
|
| 3.5.3
|
|
| Verify that stateless session tokens use digital signatures, encryption, and other countermeasures to protect against tampering, enveloping, replay, null cipher, and key substitution attacks.
|
|
| ✔
|
|
| ✗
|
|
|===
|
|
|
|
===== 3.5.1
|
|
|
|
This requirement is not applicable to the current implementation, as there is no OAuth service implemented in the application.
|
|
|
|
===== 3.5.2
|
|
|
|
The application uses that exact implementation, using session tokens instead of static API secrets and keys to manage sessions as mentioned in the link:#reqs_3_2_x[section 3.2] of the ASVS.
|
|
|
|
The server generates a session token upon login, using the function `secrets.token_hex(128)`, and sends it to the client. The client then sends the token in the Authorization header in every request that requires authentication.
|
|
|
|
===== 3.5.3
|
|
|
|
Since this topic involves a few different types of attacks, it's best to address them individually:
|
|
|
|
====== Digital Signatures and Replay
|
|
These attacks could be tackled with the use of JWT (JSON Web Tokens). They support a `exp` field, that indicate the expiry date of said packet (protects against Replay attacks). Expiry time could be adjusted dynamically according to client-server ping.
|
|
|
|
JWT also supports signatures, so with a given secret key, the server can verify the integrity of the token. The signature is calculated using the header and payload of the token (with the content of the token being base64 encoded).
|
|
|
|
====== Tampering
|
|
With the support of signatures, the server can verify the integrity of the token, and if it was tampered with, the signature would not match the content of the token.
|
|
|
|
Another added layer of security could be the use of a counter, that is incremented with each request, where the server verifies if the counter is correct, and vice versa.
|
|
|
|
====== Null Cipher
|
|
The cipher python package used in the project, `cryptohazmat`, uses AES-CFB, that avoids null ciphers. Of course, any failure within the package (or any other package) due to an update or a bug could eventually lead to a null cipher attack.
|
|
|
|
====== Key Substitution
|
|
Since it is being used a Diffie-Hellman key exchange, the key is never sent over the network, the server and client end up with the same key, so there is no key to be substituted. It is also not possible to substitute a key, that would require re-authentication.
|
|
|
|
====== Encryption
|
|
The current implementation does not encrypt the session token, which could be a vulnerability. This could be implemented by encrypting the session token with a symmetric key (and use a strong algorithm, like AES-256), and then decrypting it on the server side. This would protect the token from being read by an attacker, even if they manage to intercept it.
|
|
|
|
|
|
==== Federated Re-authentication
|
|
|
|
[cols="^1,10,^1,^1", options="header", source]
|
|
|===
|
|
| Requirement | Description | Applicable | Implemented
|
|
|
|
| 3.6.1
|
|
| Verify that Relying Parties (RPs) specify the maximum authentication time to Credential Service Providers (CSPs) and that CSPs re-authenticate the user if they haven't used a session within that period.
|
|
| ✗
|
|
| ✗
|
|
|
|
| 3.6.2
|
|
| Verify that Credential Service Providers (CSPs) inform Relying Parties (RPs) of the last authentication event, to allow RPs to determine if they need to re-authenticate the user.
|
|
| ✗
|
|
| ✗
|
|
|===
|
|
|
|
===== 3.6.1, 3.6.2
|
|
|
|
These requirements are not applicable to the current implementation, as the application does not have a federated authentication system.
|
|
|
|
==== Defenses Against Session Management Exploits
|
|
|
|
[cols="^1,10,^1,^1", options="header", source]
|
|
|===
|
|
| Requirement | Description | Applicable | Implemented
|
|
|
|
| 3.7.1
|
|
| Verify the application ensures a full, valid login session or requires re-authentication or secondary verification before allowing any sensitive transactions or account modifications.
|
|
| ✔
|
|
| ✗
|
|
|===
|
|
|
|
===== 3.7.1
|
|
|
|
Currently, the application does not require re-authentication or secondary verification before allowing sensitive transactions or account modifications, it just checks if the user is authenticated and has the required permissions.
|
|
|
|
This could be implemented by adding a challenge signature to the request using the rsa key pair, which would be verified by the server before allowing the transaction. This is the same mechanism already used for the login endpoint. To implement this, every endpoint that involves sensitive transactions or account modifications would need to be updated to include the challenge generation and signature verification. |