VYPR
Moderate severityNVD Advisory· Published Feb 26, 2026· Updated Feb 26, 2026

Fleet: Device lock PIN can be predicted if lock time is known

CVE-2026-23999

Description

Fleet is open source device management software. In versions prior to 4.80.1, Fleet generated device lock and wipe PINs using a predictable algorithm based solely on the current Unix timestamp. Because no secret key or additional entropy was used, the resulting PIN could potentially be derived if the approximate time the device was locked is known. Fleet’s device lock and wipe commands generate a 6-digit PIN that is displayed to administrators for unlocking a device. In affected versions, this PIN was deterministically derived from the current timestamp. An attacker with physical possession of a locked device and knowledge of the approximate time the lock command was issued could theoretically predict the correct PIN within a limited search window. However, successful exploitation is constrained by multiple factors: Physical access to the device is required, the approximate lock time must be known, the operating system enforces rate limiting on PIN entry attempts, attempts would need to be spread over, and device wipe operations would typically complete before sufficient attempts could be made. As a result, this issue does not allow remote exploitation, fleet-wide compromise, or bypass of Fleet authentication controls. Version 4.80.1 contains a patch. No known workarounds are available.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/fleetdm/fleet/v4Go
< 4.80.14.80.1

Affected products

1

Patches

1
05ca0693621e

Add missing unit test (#38439)

https://github.com/fleetdm/fleetJordan MontgomeryJan 16, 2026via ghsa
3 files changed · +26 12
  • server/mdm/apple/commander.go+8 2 modified
    @@ -116,7 +116,10 @@ func (svc *MDMAppleCommander) DeviceLock(ctx context.Context, host *fleet.Host,
     	}
     
     	// No pending lock, create a new one
    -	unlockPIN = GenerateRandomPin(6)
    +	unlockPIN, err = GenerateRandomPin(6)
    +	if err != nil {
    +		return "", ctxerr.Wrap(ctx, err, "generating random PIN for DeviceLock")
    +	}
     	raw := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
     <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
     <plist version="1.0">
    @@ -236,7 +239,10 @@ func (svc *MDMAppleCommander) DisableLostMode(ctx context.Context, host *fleet.H
     }
     
     func (svc *MDMAppleCommander) EraseDevice(ctx context.Context, host *fleet.Host, uuid string) error {
    -	pin := GenerateRandomPin(6)
    +	pin, err := GenerateRandomPin(6)
    +	if err != nil {
    +		return ctxerr.Wrap(ctx, err, "generating random PIN for EraseDevice")
    +	}
     	raw := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
     <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
     <plist version="1.0">
    
  • server/mdm/apple/util.go+10 10 modified
    @@ -5,14 +5,12 @@ import (
     	"crypto/rsa"
     	"crypto/sha256"
     	"crypto/x509"
    -	"encoding/binary"
     	"encoding/pem"
     	"fmt"
     	"math"
     	"net/url"
     	"path"
     	"strings"
    -	"time"
     
     	"github.com/fleetdm/fleet/v4/server/fleet"
     	"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/mdm"
    @@ -36,14 +34,16 @@ func EncodeCertRequestPEM(cert *x509.CertificateRequest) []byte {
     	return pem.EncodeToMemory(pemBlock)
     }
     
    -// GenerateRandomPin generates a `lenght`-digit PIN number that takes into
    -// account the current time as described in rfc4226 (for one time passwords)
    +// GenerateRandomPin generates a `length`-digit random PIN number
     //
    -// The implementation details have been mostly taken from https://github.com/pquerna/otp
    -func GenerateRandomPin(length int) string {
    -	counter := uint64(time.Now().Unix()) //nolint:gosec // dismiss G115
    -	buf := make([]byte, 8)
    -	binary.BigEndian.PutUint64(buf, counter)
    +// The implementation details for converting the randomness to a PIN
    +// have been mostly taken from https://github.com/pquerna/otp
    +func GenerateRandomPin(length int) (string, error) {
    +	buf := make([]byte, 16)
    +	_, err := rand.Read(buf)
    +	if err != nil {
    +		return "", err
    +	}
     	m := sha256.New()
     	m.Write(buf)
     	sum := m.Sum(nil)
    @@ -54,7 +54,7 @@ func GenerateRandomPin(length int) string {
     		(int(sum[offset+3]) & 0xff))
     	v := int32(value % int64(math.Pow10(length))) //nolint:gosec // dismiss G115
     	f := fmt.Sprintf("%%0%dd", length)
    -	return fmt.Sprintf(f, v)
    +	return fmt.Sprintf(f, v), nil
     }
     
     // FmtErrorChain formats Command error message for macOS MDM v1
    
  • server/mdm/apple/util_test.go+8 0 modified
    @@ -36,3 +36,11 @@ func TestMDMAppleEnrollURL(t *testing.T) {
     		require.Equal(t, tt.expectedURL, enrollURL)
     	}
     }
    +
    +func TestGenerateRandomPin(t *testing.T) {
    +	for i := 1; i <= 100; i++ {
    +		pin, err := GenerateRandomPin(i)
    +		require.NoError(t, err)
    +		require.Len(t, pin, i)
    +	}
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.