yegor256/0rsk

`GET` endpoints for state-changing operations `/projects/delete`, `/ranked/delete`, `/responses/detach` are exposed to CSRF attacks

Open

#268 opened on May 11, 2026

View on GitHub
 (3 comments) (0 reactions) (0 assignees)Ruby (6 forks)github user discovery
buggood-titlehelp wanted

Repository metrics

Stars
 (24 stars)
PR merge metrics
 (PR metrics pending)

Description

What happens

Several state-changing endpoints in 0rsk.rb are routed as get rather than post, with no CSRF token, no Origin/Referer validation, and no idempotency guard:

get '/projects/delete' do
  projects.get(params[:id].to_i).delete
  redirect '/projects'
end

get '/ranked/delete' do
  # destructive — deletes a ranked triple
end

get '/responses/detach' do
  # destructive — detaches a plan from a response
end

Authentication is via the glogin cookie set in front/front_login.rb:31. Because Sinatra's default Rack::Protection::HttpOrigin only inspects POST/PUT/DELETE requests, a get to a destructive route is not covered. An attacker controlling evil.com can therefore trigger any of these deletions by embedding a single <img src="https://www.0rsk.com/projects/delete?id=42"> on a page a logged-in user opens. The browser attaches the glogin cookie, and the destructive side effect executes silently.

This is a CSRF vulnerability on the GET-based state-changing endpoints. It is the same authorization-binding defect family as the accepted IDOR #263 — the cookie identifies the user but the request shape carries no proof that the user actually intended the action.

What should happen

Two changes — either alone closes the hole, both together is best:

  1. Convert each destructive route from get to post (or delete) so it is covered by Rack::Protection::HttpOrigin and by any future CSRF-token middleware. Update the corresponding view links to submit a one-button <form method="post"> instead of an <a>.

  2. Enable Rack::Protection::AuthenticityToken (requires enable :sessions plus a token embedded in every state-changing form), so every state-changing request — regardless of HTTP verb — carries a same-site secret that an attacker on evil.com cannot produce.

Result: an <img> tag on a hostile page can no longer trigger account-state changes against a logged-in 0rsk user, and the cookie no longer doubles as standing authorization for every URL the browser can be tricked into fetching.

Contributor guide