Feature #16582
openFreeRADIUS: Add configurable TOTP anti-replay protection (RFC 6238)
0%
Description
- Feature Request: TOTP Anti-Replay Protection with GUI Option for FreeRADIUS
- Summary
Add configurable anti-replay protection to the Google Authenticator OTP implementation in the FreeRADIUS3 package. This includes:
1. A new checkbox in Services > FreeRADIUS > Settings to enable/disable anti-replay
2. Modified `googleauth.py` script that respects this setting
3. Full backward compatibility (disabled by default)
- Problem Statement
The current `googleauth.py` implementation accepts TOTP codes within a ±30 second window but does not track whether a code has already been used. This allows replay attacks where an attacker who intercepts a valid code can reuse it within the validity window.
RFC 6238 Section 5.2 states:
"The verifier MUST NOT accept the second attempt of the OTP after the successful validation has been issued for the first OTP, which ensures one-time only use of an OTP."
- Current Behavior
```
User authenticates with PIN 1234 + OTP 567890 → SUCCESS
Same credentials replayed within 30 seconds → SUCCESS (VULNERABILITY)
```
- Expected Behavior (when enabled)
```
User authenticates with PIN 1234 + OTP 567890 → SUCCESS
Same credentials replayed within 30 seconds → REJECT (token already used)
```
- Proposed Solution
- 1. GUI Modification (freeradiussettings.xml)
Add a new checkbox field in the OTP settings section:
```xml
<field>
<fielddescr>Enable OTP Anti-Replay Protection</fielddescr>
<fieldname>varotpantireplay</fieldname>
<description>
<![CDATA[
Enable anti-replay protection for OTP authentication.<br/>
<span class="text-info">When enabled, each OTP code can only be used once
within its validity window (90 seconds).</span><br/>
<span class="text-warning">Provides RFC 6238 compliance and prevents
replay attacks.</span><br/>
<span class="text-info">(Default: unchecked)</span>
]]>
</description>
<type>checkbox</type>
<default_value></default_value>
</field>
```
- 2. Backend Modification (freeradius.inc)
Pass the setting as a 5th parameter to the googleauth.py script:
Current:
```php
program = "{$varFREERADIUS_SCRIPTS}/googleauth.py %{request:User-Name} %{reply:MOTP-Init-Secret} %{reply:MOTP-PIN} %{request:User-Password}"
```
Modified:
```php
$varotpantireplay = ($settingsconfig['varotpantireplay'] == 'on') ? '1' : '0';
// ...
program = "{$varFREERADIUS_SCRIPTS}/googleauth.py %{request:User-Name} %{reply:MOTP-Init-Secret} %{reply:MOTP-PIN} %{request:User-Password} {$varotpantireplay}"
```
- 3. Script Modification (googleauth.py)
Modified script that:
- Accepts optional 5th parameter for anti-replay toggle
- Maintains full backward compatibility (disabled if parameter missing)
- Uses file-based token cache in `/var/run/freeradius/`
- Implements automatic cleanup of expired entries
Key additions:
```python
def is_token_used(username, token_code):
"""Check if token was already used."""
# Returns True if found in cache and not expired
def mark_token_used(username, token_code):
"""Record token with timestamp."""
# Atomic write with file locking
def authenticate(username, secretkey, pin, password, antireplay_enabled=False):
# ... existing validation ...
if antireplay_enabled and is_token_used(username, otp_attempt):
syslog.syslog(syslog.LOG_WARNING, "REPLAY ATTACK DETECTED...")
return False
# ... TOTP validation ...
if valid and antireplay_enabled:
mark_token_used(username, otp_attempt)
return valid
```
- Security Impact
- Risk Level: Medium (elevated to High in compliance-sensitive environments)
- Attack Vector: Man-in-the-middle interception
- Mitigation: TLS encryption in OpenVPN reduces but doesn't eliminate risk
- Compliance: Required for PCI-DSS, ISO 27001, SOC2 environments
- Competitor Comparison
| Solution | Anti-Replay | GUI Option |
| ---------- | ------------- | ------------ |
| pfSense FreeRADIUS (current) | ❌ | N/A |
| pfSense FreeRADIUS (proposed) | *✅* | *✅* |
| OPNsense FreeRADIUS | ❌ | N/A |
| FortiAuthenticator | ✅ | ✅ |
| PrivacyIDEA | ✅ | ✅ |
| Google Authenticator PAM | ✅ | Config file |
- Backward Compatibility
✅ Full backward compatibility guaranteed:
- Default: disabled (unchecked)
- Existing installations unaffected
- Script accepts missing 5th parameter gracefully
- No configuration migration required
- Testing Procedure
- 1. Enable anti-replay in GUI
- Services > FreeRADIUS > Settings > Enable OTP Anti-Replay Protection
- 2. Restart FreeRADIUS
/usr/local/etc/rc.d/radiusd restart
- 3. Test first authentication (should succeed)
otp=$(oathtool --totp -b $SECRET)
radtest user ${PIN}${otp} 127.0.0.1 1812 $SHARED_SECRET - Expected: Access-Accept
- 4. Test replay (should fail)
radtest user ${PIN}${otp} 127.0.0.1 1812 $SHARED_SECRET - Expected: Access-Reject
- 5. Verify log message
grep "REPLAY ATTACK DETECTED" /var/log/messages
```
- Files Modified
| File | Change |
| ------ | -------- |
| `/usr/local/pkg/freeradiussettings.xml` | Add checkbox field |
| `/usr/local/pkg/freeradius.inc` | Pass parameter to script |
| `/usr/local/etc/raddb/scripts/googleauth.py` | Implement anti-replay logic |
- Implementation Notes
- Token Storage
- Location: `/var/run/freeradius/totp_used_tokens`
- Format: `username:token:timestamp` (one per line)
- Cleared on reboot (RAM-backed)
- Auto-cleanup of expired entries
- Performance
- Negligible impact (~1ms per authentication)
- File size self-limiting via cleanup
- File locking prevents race conditions
- Edge Cases Handled
- Missing parameter: defaults to disabled
- Missing cache directory: auto-created
- File I/O errors: fail-open (authentication proceeds)
- Concurrent authentications: file locking
- References
- [RFC 6238 - TOTP Algorithm](https://datatracker.ietf.org/doc/html/rfc6238) - Section 5.2
- [NIST SP 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html) - Digital Identity Guidelines
- [FortiAuthenticator Anti-Replay](https://docs.fortinet.com/document/fortiauthenticator/6.4.0/administration-guide/996353)
- Attachments
Complete implementation files are attached:
1. `patch-freeradiussettings.xml` - GUI field definition
2. `patch-freeradius.inc.php` - PHP backend modification guide
3. `googleauth-configurable.py` - Complete modified script
Environment:
- pfSense version: 2.7.x / 2.8.x / 24.x
- Package: freeradius3
- Tested on: pfSense 2.8.1
Submitter:
This enhancement addresses a known security gap while maintaining full backward compatibility. The GUI toggle allows administrators to enable RFC 6238 compliance when required by their security policies without affecting existing deployments.
Files
Updated by Loic FONTAINE 3 days ago
Updated by Loic FONTAINE 3 days ago
- File freeradiussettings.xml.patch freeradiussettings.xml.patch added
- File freeradius.inc.patch freeradius.inc.patch added
Bad upload for diff files : true patch inside