PT-2026-49058 · Go · Github.Com/Pilinux/Gorest
Published
2026-06-12
·
Updated
2026-06-12
·
CVE-2026-48154
CVSS v3.1
5.9
Medium
| Vector | AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H |
Vulnerability: CWE-362 — Concurrent Map Access Race Condition in InMemorySecret2FA
CWE: CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization)
Affected Component
github.com/pilinux/gorest— Go REST API boilerplate- InMemorySecret2FA — in-memory 2FA secret store
Vulnerability Locations
| File | Line | Role |
|---|---|---|
database/model/twoFA.go | 43 | Global map[uint64]Secret2FA — bare map, no sync.RWMutex |
handler/login.go | 139 | Map write during user login |
handler/twoFA.go | 205 | Map write during 2FA setup |
handler/twoFA.go | 272 | Map write during 2FA activation |
handler/twoFA.go | 575 | Map write during 2FA verification |
handler/twoFA.go | 189 | Map read during 2FA operations |
handler/twoFA.go | 245 | Map read during 2FA operations |
handler/twoFA.go | 491 | Map read during 2FA operations |
service/common.go | 79 | Map delete |
Data Flow
Multiple HTTP goroutines (concurrent requests)
│
├── handler/login.go:139 ─► map write ──┐
├── handler/twoFA.go:205 ─► map write ──┼── InMemorySecret2FA (bare map)
├── handler/twoFA.go:189 ─► map read ───┤ ▲ NO sync.RWMutex
├── handler/twoFA.go:245 ─► map read ───┤ │
├── handler/twoFA.go:491 ─► map read ───┤ │
└── service/common.go:79 ─► map delete ─┘ │
│
Go runtime detects concurrent map │
read+write or write+write │
│ │
▼ │
fatal error: concurrent map read and map write │
fatal error: concurrent map writes │
│ │
▼ │
Process crash (DoS) ──────────────────────┘
Description
The
InMemorySecret2FA in database/model/twoFA.go was defined as a package-level map[uint64]Secret2FA — a bare Go map with no synchronization primitive. Multiple HTTP handlers in handler/login.go and handler/twoFA.go read from and wrote to this map concurrently. Go's runtime detects unsynchronized concurrent map access and throws an unrecoverable fatal error, which crashes the entire process.This is a CWE-362 race condition: the shared resource (the map) is accessed concurrently without proper synchronization, and the failure mode is a hard process crash (denial of service).
Trigger Conditions
- Two users with 2FA enabled logging in simultaneously — concurrent map writes
- One user logging in (map write) while another performs 2FA verification (map read)
- Any concurrent combination of the 9 affected handler locations
Proof of Concept
# Simulate two concurrent logins with 2FA enabled
for i in 1 2; do
curl -X POST http://target:8080/api/v1/login -H "Content-Type: application/json" -d "{"email":"user${i}@example.com","password":"testpass"}" &
done
wait
# Go runtime output:
# fatal error: concurrent map writes
# goroutine 34 [running]:
# runtime.throw({0x...})
# runtime/map.go:...
Impact
- Availability (High): Hard process crash via Go runtime fatal error. No recovery possible — the process exits. An attacker can repeat the concurrent requests to crash the service on demand.
- Confidentiality (None): The crash itself does not leak data.
- Integrity (None): No data corruption (Go prevents it by crashing).
Fix (PR #391)
Introduced
Secret2FAStore struct with sync.RWMutex protection:// BEFORE: database/model/twoFA.go — bare map, no protection
var InMemorySecret2FA map[uint64]Secret2FA
// AFTER: Wrapped with sync.RWMutex
type Secret2FAStore struct {
mu sync.RWMutex
data map[uint64]Secret2FA
}
func (s *Secret2FAStore) Get(key uint64) (Secret2FA, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
v, ok := s.data[key]
return cloneSecret2FA(v), ok
}
func (s *Secret2FAStore) Set(key uint64, value Secret2FA) {
s.mu.Lock()
defer s.mu.Unlock()
s.data[key] = cloneSecret2FA(value)
}
func (s *Secret2FAStore) Delete(key uint64) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.data, key)
}
// cloneSecret2FA returns a deep copy of a Secret2FA.
// This prevents external code from mutating the store's data
// through shared slice backing arrays.
func cloneSecret2FA(v Secret2FA) Secret2FA {
out := Secret2FA{Image: v.Image}
if v.PassHash != nil {
out.PassHash = append([]byte(nil), v.PassHash...)
}
if v.KeySalt != nil {
out.KeySalt = append([]byte(nil), v.KeySalt...)
}
if v.Secret != nil {
out.Secret = append([]byte(nil), v.Secret...)
}
return out
}
All 9 handler call sites updated from direct map access to store method calls.
Not Vulnerable (verified during audit)
- JWT: RSA keys from files, appleboy/gin-jwt middleware — correct
- Password hashing: Argon2 via pilinux/argon2 — correct
- SQL queries: GORM parameterized — correct
- CORS: validates wildcard+credentials combination at config load — correct
Patched Versions
All versions after PR #391 merge.
Resources
Credit
Reported by @saaa99999999 via manual security audit.
Fix
Race Condition
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Github.Com/Pilinux/Gorest