This document consolidates identified vulnerabilities, removing duplicates and providing a comprehensive description for each.
-
Description:
- The
ulid.Make()
function in theulid
library is designed to generate ULIDs using the current time and random entropy. By default, it utilizesdefaultEntropy
as the entropy source. defaultEntropy
is initialized to usemath/rand.Rand
, a pseudo-random number generator that is not cryptographically secure, seeded with the current Unix nano timestamp.- Consequently, ULIDs generated using
ulid.Make()
by default are predictable, especially if an attacker can observe a sequence of generated ULIDs or estimate the time of generation. - Similarly, the command-line tool
ulid
provides a-q
or--quick
flag. When used, this flag also employsmath/rand
for entropy generation, leading to the creation of predictable ULIDs in the command-line tool as well. - An attacker who can observe or infer the approximate time at which ULIDs are generated might narrow down the seed value for
math/rand
. With knowledge (or estimation) of the seed, the attacker can reproduce the pseudo-random stream and predict future ULIDs. - This predictability issue affects both the library's default
Make()
function and the command-line tool when the-q
flag is used.
- The
-
Impact:
- If ULIDs generated by
ulid.Make()
orulid -q
are used as security-sensitive identifiers (e.g., session IDs, API keys, CSRF tokens, or in URL parameters), this vulnerability can have a high impact. - Predictable ULIDs can enable attackers to bypass security measures, gain unauthorized access, perform session hijacking, or execute CSRF attacks by predicting or enumerating valid identifiers.
- The vulnerability rank is high because predictable identifiers in security contexts can directly lead to breaches of confidentiality and integrity.
- If ULIDs generated by
-
Vulnerability Rank: high
-
Currently Implemented Mitigations:
- Documentation: The README.md file explicitly warns that
math/rand.Rand
is not cryptographically secure and recommends usingcrypto/rand
for security-sensitive use cases. It also notes thatMake()
uses a "pseudo-random" source of entropy. - Command Line Help: The command-line tool help text for the
-q
flag states "when generating, use non-crypto-grade entropy", indicating that the quick mode is not intended for secure applications.
- Documentation: The README.md file explicitly warns that
-
Missing Mitigations:
- Default Entropy Source: The default entropy source for
ulid.Make()
should be changed to a cryptographically secure random number generator, such ascrypto/rand.Reader
. - Command Line Tool Default: The command line tool
ulid
should usecrypto/rand.Reader
as the default entropy source when generating ULIDs without the-q
flag to promote secure-by-default behavior. - Security Warning: Add a prominent security warning in the documentation, specifically highlighting the risks of using
ulid.Make()
andulid -q
in security-sensitive contexts due to the use of pseudo-random entropy. Recommend usingulid.New
withcrypto/rand.Reader
for secure applications. - Runtime Check (Optional): Consider adding an optional runtime check or build flag that would enforce the use of
crypto/rand
in production environments or for security-sensitive operations, although this might add complexity for users.
- Default Entropy Source: The default entropy source for
-
Preconditions:
- The application or system must be using the
ulid.Make()
function or theulid -q
command-line tool to generate ULIDs. - These generated ULIDs are employed as security-sensitive identifiers where unpredictability is a requirement for maintaining security (e.g., authentication tokens, access control identifiers).
- An attacker needs to be able to observe or intercept a sufficient number of generated ULIDs, or have knowledge about the system's time, to potentially analyze and predict future values if
math/rand
is used.
- The application or system must be using the
-
Source Code Analysis:
-
/code/ulid.go
:var defaultEntropy = func() io.Reader { rng := rand.New(rand.NewSource(time.Now().UnixNano())) // math/rand is used here return &LockedMonotonicReader{MonotonicReader: Monotonic(rng, 0)} }() func Make() (id ULID) { return MustNew(Now(), defaultEntropy) // defaultEntropy is used by Make() }
The
Make()
function, intended for general use, defaults todefaultEntropy
, which utilizesmath/rand
. This makes the generated entropy source predictable. -
/code/cmd/ulid/main.go
:func generate(quick, zero bool) { entropy := cryptorand.Reader // Default entropy is crypto/rand if quick { seed := time.Now().UnixNano() source := mathrand.NewSource(seed) entropy = mathrand.New(source) // math/rand is used when -q is used } // ... id, err := ulid.New(ulid.Timestamp(time.Now()), entropy) // ... }
In the
generate
function of the command-line tool, the default entropy source iscrypto/rand.Reader
, which is secure. However, when the-q
flag is used, it switches tomath/rand
, making the generated ULIDs predictable.
-
-
Security Test Case:
- Deploy a service or application that uses
ulid.Make()
to generate identifiers in a security-sensitive context (e.g., session IDs). - Collect a sequence of ULIDs generated by this service, along with timestamps of their generation if possible.
- Alternatively, use the command line tool with
-q
flag:Collect several ULIDs generated usingulid -q ulid -q ulid -q
ulid -q
. - Analyze the entropy portion of the collected ULIDs. Observe if there are discernible patterns or a lack of expected randomness, especially when compared to ULIDs generated without the
-q
flag or usingcrypto/rand
directly. - Attempt to predict future ULIDs. Based on the observed patterns or by trying to estimate the seed used for
math/rand
(if timestamps are available), attempt to predict subsequently generated ULIDs. If successful, this confirms the predictability of ULIDs generated usingmath/rand
and thus the vulnerability. - For command line
-q
test case, generate a few ULIDs and observe the entropy part for repetition or lack of randomness compared to defaultulid
command output.
- Deploy a service or application that uses
-
Description:
- The command-line tool
ulid
supports a-z
(or--zero
) flag intended to “fix entropy to all-zeroes.” - When this flag is used, the
generate()
function incmd/ulid/main.go
sets the entropy source tozeroReader{}
. - The
zeroReader
'sRead
method fills every byte of the provided slice with zero. - As a result, when the
-z
flag is used, the ULID's entropy component becomes entirely predictable (all bytes are zero), and only the timestamp portion varies.
- The command-line tool
-
Impact:
- If ULIDs generated with zero entropy (using the
-z
flag) are inadvertently or intentionally used as unique identifiers in a security-sensitive application (e.g., in URL parameters, session IDs, or API access tokens), they become trivially predictable. - An attacker aware that the
-z
flag is in use can easily enumerate ULID values (since the entropy portion is fixed) and potentially attempt unauthorized actions such as resource enumeration or impersonation. - This vulnerability has a high rank because it directly leads to predictable identifiers in security contexts, severely compromising confidentiality and integrity.
- If ULIDs generated with zero entropy (using the
-
Vulnerability Rank: high
-
Currently Implemented Mitigations:
- Explicit Option: The
-z
flag is provided explicitly as a command-line option with accompanying help text, suggesting it is intended for testing or demonstration purposes. - No Runtime Warning: There is no additional guard or runtime warning when the
-z
flag is used.
- Explicit Option: The
-
Missing Mitigations:
- Usage Prevention/Warning: The tool does not prevent or warn against using the
-z
flag in production or any environment where ULID unpredictability is critical for security. - Confirmation Prompt: A safer implementation might prompt for confirmation or refuse to operate in security-sensitive contexts when an all-zero entropy source is selected, to prevent accidental misuse in production.
- Usage Prevention/Warning: The tool does not prevent or warn against using the
-
Preconditions:
- An operator uses the command-line tool with the
-z
flag (either inadvertently or due to misconfiguration) in a production or publicly accessible service environment. - ULIDs generated with all-zero entropy are employed for purposes beyond mere testing, specifically as security-sensitive identifiers (e.g., authentication tokens or resource identifiers).
- An operator uses the command-line tool with the
-
Source Code Analysis:
/code/cmd/ulid/main.go
:func generate(quick, zero bool) { // ... if zero { entropy = zeroReader{} } // ... id, err := ulid.New(ulid.Timestamp(time.Now()), entropy) // ... }
/code/cmd/ulid/main.go
:Thetype zeroReader struct{} func (zeroReader) Read(p []byte) (int, error) { for i := range p { p[i] = 0 } return len(p), nil }
generate()
function checks for thezero
flag. If set, it useszeroReader
as the entropy source.zeroReader
'sRead
method always fills the buffer with zeros. This means every generated ULID with-z
will have its lower 10 bytes (entropy) fixed to 0, leaving only the time-based 6 bytes variable.
-
Security Test Case:
- Run the ULID command-line tool with the
-z
flag:ulid -z
- Observe the generated ULID output. Note that the last 16 characters (encoding the 80-bit entropy) correspond to a fixed value (all zeroes). For example:
01AN4Z07BY77EZK1CQ0XDGZY0Z
. The entropy part77EZK1CQ0XDGZY0Z
when decoded from base32 will be all zeros. - Generate multiple ULIDs in quick succession using the
-z
flag:Confirm that only the timestamp portion changes while the entropy portion remains unchanged (all zeroes).ulid -z ulid -z ulid -z
- Demonstrate Predictability: Explain that knowing the timestamp is sufficient to almost completely determine the ULID, as the entropy is fixed. In a real-world scenario, if an attacker knows that
-z
flag is used and can estimate the time of ULID generation, they can effectively predict the entire identifier, confirming the vulnerability.
- Run the ULID command-line tool with the