pppd currently uses the rand48 family both for public protocol values and for authentication challenges.
magic_init() initializes the shared rand48 state from a host-dependent value, the current time and the process ID:
seed = get_host_seed() ^ t.tv_sec ^ t.tv_usec ^ getpid();
srand48(seed);
The same state is then used by both magic() and random_bytes():
u_int32_t
magic(void)
{
return (u_int32_t)mrand48();
}
void
random_bytes(unsigned char *buf, int len)
{
int i;
for (i = 0; i < len; ++i)
buf[i] = mrand48() >> 24;
}
magic() is primarily used for non-cryptographic values such as LCP Magic-Numbers. Using a conventional PRNG for an LCP Magic-Number is not by itself a security issue: the value is public and its purpose is to detect looped-back links.
random_bytes(), however, is used to generate:
There are also direct uses of drand48() for authentication data:
For example, EAP-MD5 currently fills its challenge as follows:
while (--challen >= 0)
*ptr++ = (u_char)(drand48() * 0x100);
Why the shared PRNG state may matter
The challenge itself is public, so confidentiality is not the requirement. Authentication challenges are instead expected to be unique and unpredictable in order to prevent replay and preplay attacks.
The rand48 family is a linear generator with 48 bits of state. An mrand48() result exposes 32 bits derived from that state, leaving at most 2^16 candidate states after one observed result. Additional outputs can be used to distinguish the remaining candidates and predict subsequent drand48() and mrand48() results.
pppd transmits outputs of magic() to the peer as LCP Magic-Numbers before authentication. More values may be generated during LCP negotiation, for example when lcp_nakci() or lcp_reqci() selects another Magic-Number. Because these public values and the later authentication challenges consume the same rand48 state, the LCP exchange appears to provide an active peer with information that could be used to recover the state and predict a subsequent CHAP or EAP challenge.
This also means that the usual limitation of one PPP interface per pppd process does not necessarily eliminate the concern: the relevant state exposure and challenge generation can occur on the same link, with LCP preceding authentication.
For CHAP-MD5, the response is calculated over the identifier, shared secret and challenge. RFC 1994, Section 2.3 says that each challenge should be unique and unpredictable, and specifically describes the risk of an attacker tricking a peer into responding to a predicted future challenge and later using that response to masquerade as the peer.
A possible active preplay scenario would therefore be:
- interact with pppd during LCP and observe public outputs derived from the shared
rand48 state;
- recover the generator state and predict a future CHAP identifier and challenge;
- obtain a response to those predicted values from the legitimate peer by acting as an authenticator;
- use the response when completing authentication with the real pppd instance.
I have not implemented a proof of concept and do not claim that this is exploitable in every deployment. In particular:
- pppd must be configured to authenticate its peer with one of the affected methods; a remote peer cannot normally select the local authentication policy by itself;
- an active preplay attack would require access to the legitimate peer and synchronization with the authenticator;
- CHAP-MD5, EAP-MD5 and MS-CHAPv2 are legacy methods with additional known security limitations.
Nevertheless, the use of a predictable generator for authentication challenges seems avoidable while these methods remain supported. The shared state with public LCP values also appears to make this more than a purely theoretical concern about the choice of random-number API.
Possible direction
Would it make sense to introduce a separate secure-random API backed by an operating system CSPRNG, for example:
getrandom() on Linux;
arc4random_buf() where available;
/dev/urandom as a fallback with complete error handling?
Authentication challenge generation could fail closed if secure random bytes cannot be obtained, rather than falling back to rand48.
The existing non-cryptographic PRNG could remain in use for values that do not require cryptographic unpredictability, such as LCP Magic-Numbers, packet identifiers and randomized challenge lengths. The important part would be to stop deriving authentication challenges from that state.
A possible scope would be:
- add a secure-random abstraction suitable for the platforms supported by pppd;
- make
random_bytes() use it for CHAP-MD5, MS-CHAP and MS-CHAPv2 authenticator challenges;
- replace the direct
drand48() use for EAP-MD5 challenge contents;
- replace the direct
drand48() use for the MS-CHAPv2 Peer-Challenge;
- retain the existing PRNG only for non-security-sensitive values.
I would appreciate the maintainers' opinion on whether this analysis is correct, whether the shared state with LCP Magic-Numbers is considered relevant in the supported threat model.
pppd currently uses the
rand48family both for public protocol values and for authentication challenges.magic_init()initializes the sharedrand48state from a host-dependent value, the current time and the process ID:The same state is then used by both
magic()andrandom_bytes():magic()is primarily used for non-cryptographic values such as LCP Magic-Numbers. Using a conventional PRNG for an LCP Magic-Number is not by itself a security issue: the value is public and its purpose is to detect looped-back links.random_bytes(), however, is used to generate:pppd/chap-md5.c;pppd/chap_ms.c.There are also direct uses of
drand48()for authentication data:pppd/eap.c;pppd/chap_ms.c.For example, EAP-MD5 currently fills its challenge as follows:
Why the shared PRNG state may matter
The challenge itself is public, so confidentiality is not the requirement. Authentication challenges are instead expected to be unique and unpredictable in order to prevent replay and preplay attacks.
The
rand48family is a linear generator with 48 bits of state. Anmrand48()result exposes 32 bits derived from that state, leaving at most 2^16 candidate states after one observed result. Additional outputs can be used to distinguish the remaining candidates and predict subsequentdrand48()andmrand48()results.pppd transmits outputs of
magic()to the peer as LCP Magic-Numbers before authentication. More values may be generated during LCP negotiation, for example whenlcp_nakci()orlcp_reqci()selects another Magic-Number. Because these public values and the later authentication challenges consume the samerand48state, the LCP exchange appears to provide an active peer with information that could be used to recover the state and predict a subsequent CHAP or EAP challenge.This also means that the usual limitation of one PPP interface per pppd process does not necessarily eliminate the concern: the relevant state exposure and challenge generation can occur on the same link, with LCP preceding authentication.
For CHAP-MD5, the response is calculated over the identifier, shared secret and challenge. RFC 1994, Section 2.3 says that each challenge should be unique and unpredictable, and specifically describes the risk of an attacker tricking a peer into responding to a predicted future challenge and later using that response to masquerade as the peer.
A possible active preplay scenario would therefore be:
rand48state;I have not implemented a proof of concept and do not claim that this is exploitable in every deployment. In particular:
Nevertheless, the use of a predictable generator for authentication challenges seems avoidable while these methods remain supported. The shared state with public LCP values also appears to make this more than a purely theoretical concern about the choice of random-number API.
Possible direction
Would it make sense to introduce a separate secure-random API backed by an operating system CSPRNG, for example:
getrandom()on Linux;arc4random_buf()where available;/dev/urandomas a fallback with complete error handling?Authentication challenge generation could fail closed if secure random bytes cannot be obtained, rather than falling back to
rand48.The existing non-cryptographic PRNG could remain in use for values that do not require cryptographic unpredictability, such as LCP Magic-Numbers, packet identifiers and randomized challenge lengths. The important part would be to stop deriving authentication challenges from that state.
A possible scope would be:
random_bytes()use it for CHAP-MD5, MS-CHAP and MS-CHAPv2 authenticator challenges;drand48()use for EAP-MD5 challenge contents;drand48()use for the MS-CHAPv2 Peer-Challenge;I would appreciate the maintainers' opinion on whether this analysis is correct, whether the shared state with LCP Magic-Numbers is considered relevant in the supported threat model.