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