Clickjacking in Rocket with Cockroachdb
Clickjacking in Rocket with Cockroachdb — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI security issue where an attacker tricks a user into interacting with a hidden or disguised element inside an invisible or transparent iframe. In a Rocket web application that uses Cockroachdb as the backend database, the risk emerges from a mismatch between rendered UI controls and server-side authorization checks, not from Cockroachdb itself. Cockroachdb, as a distributed SQL database, stores user permissions, session tokens, and record-level access policies; it does not enforce UI protections. When a Rocket route renders a form or button without considering whether the requesting user is allowed to perform the action, and the frontend embeds that page in an iframe, an attacker can overlay invisible controls or hijack mouse events.
Consider a Rocket handler that displays a "Delete Account" button based solely on route access, without verifying that the requesting origin is intended for that UI. If this page is loaded inside an attacker-controlled page via an iframe, a user logged into your Rocket + Cockroachdb application might unknowingly trigger actions. The Cockroachdb database may correctly enforce row-level security when queried, but if the Rocket endpoint does not validate the Origin or Referer header, or lacks anti-CSRF tokens, the UI becomes a vector. The database returns success, but the context of the request is malicious. This is an integration risk: Rocket’s templating or handler logic must align with the permissions model stored in Cockroachdb, and the frontend must prevent embedding via X-Frame-Options or Content-Security-Policy frame-ancestors.
An attacker does not need SQL injection; they exploit the UI surface. For example, a page fetched from https://app.example.com/settings might include a form that issues a DELETE to DELETE /account. If that page is framed, an adversary can position a transparent button over a legitimate action. Because Cockroachdb may show the user as authenticated and possessing the right records, the request succeeds. The vulnerability is not in Cockroachdb’s SQL engine but in how Rocket serves content and how the browser renders frames. Proper defenses require both server-side context validation and strict framing rules.
Cockroachdb-Specific Remediation in Rocket — concrete code fixes
Remediation focuses on ensuring that every request that changes state is verified for origin, includes anti-CSRF tokens, and that UI rendering respects the same permissions stored in Cockroachdb. Below are concrete examples using the rocket and cockroachdb ecosystem in Rust.
1. Enforce SameSite and Secure Cookies
Set cookie attributes to prevent cross-origin transmission of session identifiers.
use rocket::http::Cookie;
use rocket::request::{self, FromRequest};
use rocket::{Build, Rocket};
#[rocket::main]
async fn main() {
let rocket = rocket::build()
.manage(/* DB pool */)
.configure(rocket::Config {
cookies: rocket::Config::cookies().set_same_site(Some(rocket::http::SameSite::Strict)),
..Default::default()
});
// launch...
}
2. Add Anti-CSRF Token to Forms and Validate in Handlers
Generate a per-session token, store it server-side (in Cockroachdb session table), and require it for state-changing requests.
use rocket::form::Form;
use rocket::http::Status;
use rocket::response::Redirect;
use rocket::State;
use uuid::Uuid;
struct CsrfToken(String);
#[rocket::async_trait]
impl<'r> rocket::request::FromRequest<'r> for CsrfToken {
type Error = ();
async fn from_request(request: &'r rocket::Request<'_>) -> request::Outcome {
let session_id = request.cookies().get_private("session_id").map(|c| c.value())?;
// Here you would query Cockroachdb to validate the token for this session
// SELECT token FROM sessions WHERE id = $1
Outcome::Success(CsrfToken(session_id.to_string()))
}
}
#[derive(FromForm)]
struct DeleteForm {
csrf_token: String,
}
#[post("/account/delete", data = &
3. Set Content-Security-Policy Frame Ancestors
Prevent the page from being embedded in iframes.
use rocket::response::Response;
use rocket::{Request, ResponseBuilder};
fn csp_middleware(req: &Request, mut response: Response) {
response.set_header(rocket::http::Header::new(
"Content-Security-Policy",
"frame-ancestors 'self' https://trusted.example.com",
));
}
#[rocket::catch(404)]
fn not_found() -> &'static str {
"Not found"
}
#[rocket::main]
async fn main() {
let rocket = rocket::build()
.attach(rocket::fairing::AdHoc::on_response("CSP", csp_middleware));
// launch...
}
4. Validate Origin Header for State-Changing Requests
Ensure requests originate from your own domain when modifying data.
use rocket::http::Header;
#[post("/api/action", rank = 1)]
async fn api_action(origin: &Header<"Origin">) -> Result {
let allowed = ["https://app.example.com"];
if !allowed.contains(&origin.as_str()) {
return Err(Status::Forbidden);
}
// Proceed with Cockroachdb write
Ok("OK".into())
}
5. Use Per-Request Permissions Checks Against Cockroachdb
Even if a page is rendered, verify on each action that the user has the right to perform it.
use sqlx::PgPool;
async fn can_user_delete(pool: &PgPool, user_id: i32, target_id: i32) -> bool {
let record = sqlx::query!(
"SELECT user_id FROM sensitive_data WHERE id = $1",
target_id
)
.fetch_optional(pool)
.await
.unwrap_or(None);
record.map_or(false, |r| r.user_id == user_id)
}
// In a Rocket handler:
// if can_user_delete(&pool, current_user.id, target_id).await {
// // delete
// }