Details
-
Bug
-
Resolution: Fixed
-
Major
-
None
-
None
-
None
-
Observium Pro, Ubuntu 20.04, Nginx 1.18, PHP 7.4-FPM
Description
Summary
When attempting to revoke an API token on the /api_tokens/ page, a CSRF validation error occurs 100% of the time. The token is never revoked. The root cause is a missing requesttoken hidden field in the revoke form.
Symptoms
The following errors appear when clicking the Revoke button:
WARNING. Possible CSRF attack with EMPTY request token.
|
Invalid request token. Please try again.
|
The error occurs 100% of the time regardless of browser, session, or user account.
Root Cause
The revoke form in html/pages/api_tokens.inc.php generates a POST form for each token but is missing the requesttoken hidden field that Observium's CSRF protection requires.
The Broken Code (around line ~175):
$actions = '<form method="post" style="display: inline;" '; |
$actions .= 'onsubmit="return confirm(\'Are you sure you want to revoke this token?\')">'; |
$actions .= '<input type="hidden" name="action" value="revoke">'; |
$actions .= '<input type="hidden" name="token_id" value="' . $token['token_id'] . '">'; |
// requesttoken field is MISSING here
|
$actions .= '<button type="submit" class="btn btn-xs btn-danger">'; |
$actions .= '<i class="fa fa-ban"></i> Revoke'; |
$actions .= '</button>'; |
$actions .= '</form>'; |
What Happens:
- User clicks "Revoke" button
- # Form POSTs to /api_tokens/ with only action=revoke&token_id=X
- # request_token_valid($vars) in api_tokens.inc.php checks for $vars['requesttoken']
- # Field is empty/missing → CSRF check fails → error is shown
Evidence from HAR capture:
POST https://observium.xxx.com/api_tokens/
|
Body: action=revoke&token_id=2
|
# requesttoken field is completely absent
|
#
|
How Observium's CSRF System Works:
- On login, authenticate.inc.php generates a token: bin2hex(random_bytes(32))
- * Token is stored in $_SESSION['requesttoken']
- * Token is registered as a meta tag: register_html_meta('csrf-token', $_SESSION['requesttoken'])
- * observium.js reads: var csrfToken = $('meta[name="csrf-token"]').attr('content')
- * JS injects it into AJAX requests automatically
- * For regular HTML forms, the token must be manually added as a hidden field
- * api_tokens.inc.php uses a regular HTML form but never adds the hidden field
Fix
Add the missing requesttoken hidden field to the revoke form:
$actions .= '<input type="hidden" name="action" value="revoke">'; |
$actions .= '<input type="hidden" name="token_id" value="' . $token['token_id'] . '">'; |
$actions .= '<input type="hidden" name="requesttoken" value="' . $_SESSION['requesttoken'] . '">'; // ADDED |
Other Pages That Handle This Correctly
For reference, other Observium pages correctly include the requesttoken in their forms:
- html/includes/print/search.inc.php — adds requesttoken to search forms
- * html/pages/user_edit.inc.php — passes requesttoken through form actions
- * html/pages/preferences.inc.php — validates requesttoken on all POST actions
api_tokens.inc.php follows the same CSRF validation pattern at the top of the file but the form builder omits the field. This appears to be an oversight in the initial implementation of the API tokens feature.
Maintenance Note
This fix will be overwritten by Observium updates. After any update, verify with:
grep "requesttoken" /opt/observium/html/pages/api_tokens.inc.php |