Files
FreshRSS/app/views/update/index.phtml
GigiandAlexandre Alapetite a6253486c0 Require POST+CSRF for self-update mutations; guard post-config step (CWE-352) (#9335)
* update: require POST + CSRF for updater mutations; guard post-config step

The self-update check/apply mutations were reachable via GET, which is never
anti-CSRF validated (the token is only checked on POST), and the privileged
post-configuration step (do_post_update()/PostUpdate hook, removal of the
staged update file) was additionally reauth-exempt and GET-reachable (CWE-352).

- checkAction and the apply branch now require POST, so the global anti-CSRF
  token check applies; the update page's Check/Apply controls are POST forms
  carrying the token.
- The post_conf finalisation is bound to a one-time session marker set by the
  successful apply right before its internal redirect, so it only runs as that
  continuation, not via a direct cross-site GET (independent of reauth config).
- Reauth is now required consistently, including for apply/post_conf (was
  exempted); the legitimate post_conf redirect still passes on the reauth done
  moments earlier by the apply request.

Status/index endpoints remain GET and read-only.

* Reduce some comments

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-26 17:02:22 +02:00

85 lines
2.5 KiB
PHTML

<?php
declare(strict_types=1);
/** @var FreshRSS_View $this */
$this->partial('aside_configure');
?>
<main class="post">
<h1><?= _t('admin.update') ?></h1>
<?php if (!empty($this->message)) { ?>
<?php
$class = 'alert-warn';
switch ($this->message['status']) {
case 'bad':
$class = 'alert-error';
break;
case 'latest':
$class = 'alert-success';
break;
default:
$class = 'alert-warn';
break;
}
?>
<p class="alert <?= $class ?>">
<?= $this->message['body'] ?>
</p>
<?php } ?>
<div class="form-group">
<label class="group-name"><?= _t('admin.update.current_version') ?></label>
<div class="group-controls">
<?= FRESHRSS_VERSION ?> (<a href="https://github.com/FreshRSS/FreshRSS/releases" target="_blank"><?= _t('admin.update.changelog') ?></a>)
</div>
</div>
<div class="form-group">
<label class="group-name"><?= _t('admin.update.releaseChannel') ?></label>
<div class="group-controls">
<?php if ($this->is_release_channel_stable) { ?>
<a href="https://github.com/FreshRSS/FreshRSS/releases/latest" target="_blank">
<?= _t('admin.update.releaseChannel.latest') ?>
</a>
<?php } else { ?>
<a href="https://github.com/FreshRSS/FreshRSS/tree/edge" target="_blank">
<?= _t('admin.update.releaseChannel.edge') ?>
</a>
<?php } ?>
</div>
</div>
<?php if (!FreshRSS_Context::systemConf()->disable_update): ?>
<div class="form-group">
<label class="group-name"><?= _t('admin.update.last') ?></label>
<div class="group-controls">
<?= $this->last_update_time ?>
</div>
</div>
<?php
if (empty($this->message) || $this->message['status'] !== 'good') {
?>
<div class="form-group form-actions">
<div class="group-controls">
<form method="post" action="<?= _url('update', 'check') ?>">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<button type="submit" class="btn btn-important"><?= _t('admin.update.check') ?></button>
</form>
</div>
</div>
<?php } ?>
<?php if ($this->update_to_apply) { ?>
<div class="form-group form-actions">
<div class="group-controls">
<form method="post" action="<?= _url('update', 'apply') ?>">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<button type="submit" class="btn btn-attention btn-state1" data-state2-id="button-update-loading"><?= _t('admin.update.apply') ?></button>
<span class="btn btn-state2" id="button-update-loading"><?= _t('admin.update.loading') ?></span>
</form>
</div>
</div>
<?php } ?>
<?php endif; ?>
</main>