security: fix LDAP injection vulnerability in username parameter

- Add strict username validation to prevent LDAP injection attacks
- Only allow alphanumeric characters, underscores, and hyphens in usernames
- Implement length limits and format validation for usernames
- Replace direct string interpolation with validated input in LDAP filters
- Update all logging to use sanitized username consistently
- Fix ESLint warnings for code style compliance

This prevents injection attacks like: *)(|(uid=admin that could bypass
authentication or allow user enumeration through malicious LDAP filters.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Feng Yue
2025-08-15 18:57:42 +08:00
parent 71ce1e33b7
commit 7a0acbdfdc

View File

@@ -65,7 +65,7 @@ class LdapService {
// Method 1: Direct dn property
if (ldapEntry.dn) {
dn = ldapEntry.dn
;({ dn } = ldapEntry)
}
// Method 2: objectName property (common in some LDAP implementations)
else if (ldapEntry.objectName) {
@@ -85,7 +85,7 @@ class LdapService {
if (dn.toString && typeof dn.toString === 'function') {
dn = dn.toString()
} else if (dn.dn && typeof dn.dn === 'string') {
dn = dn.dn
;({ dn } = dn)
}
}
@@ -253,9 +253,9 @@ class LdapService {
logger.debug('🔗 LDAP search referral:', referral.uris)
})
res.on('error', (err) => {
logger.error('❌ LDAP search result error:', err)
reject(err)
res.on('error', (error) => {
logger.error('❌ LDAP search result error:', error)
reject(error)
})
res.on('end', (result) => {
@@ -368,15 +368,41 @@ class LdapService {
}
}
// 🔍 验证和清理用户名
validateAndSanitizeUsername(username) {
if (!username || typeof username !== 'string' || username.trim() === '') {
throw new Error('Username is required and must be a non-empty string')
}
const trimmedUsername = username.trim()
// 用户名只能包含字母、数字、下划线和连字符
const usernameRegex = /^[a-zA-Z0-9_-]+$/
if (!usernameRegex.test(trimmedUsername)) {
throw new Error('Username can only contain letters, numbers, underscores, and hyphens')
}
// 长度限制 (防止过长的输入)
if (trimmedUsername.length > 64) {
throw new Error('Username cannot exceed 64 characters')
}
// 不能以连字符开头或结尾
if (trimmedUsername.startsWith('-') || trimmedUsername.endsWith('-')) {
throw new Error('Username cannot start or end with a hyphen')
}
return trimmedUsername
}
// 🔐 主要的登录验证方法
async authenticateUserCredentials(username, password) {
if (!this.config.enabled) {
throw new Error('LDAP authentication is not enabled')
}
if (!username || typeof username !== 'string' || username.trim() === '') {
throw new Error('Username is required and must be a non-empty string')
}
// 验证和清理用户名 (防止LDAP注入)
const sanitizedUsername = this.validateAndSanitizeUsername(username)
if (!password || typeof password !== 'string' || password.trim() === '') {
throw new Error('Password is required and must be a non-empty string')
@@ -408,10 +434,10 @@ class LdapService {
// 1. 使用管理员凭据绑定
await this.bindClient(client)
// 2. 搜索用户
const ldapEntry = await this.searchUser(client, username)
// 2. 搜索用户 (使用已验证的用户名)
const ldapEntry = await this.searchUser(client, sanitizedUsername)
if (!ldapEntry) {
logger.info(`🚫 User not found in LDAP: ${username}`)
logger.info(`🚫 User not found in LDAP: ${sanitizedUsername}`)
return { success: false, message: 'Invalid username or password' }
}
@@ -433,7 +459,7 @@ class LdapService {
// 验证用户DN
if (!userDN) {
logger.error(`❌ Invalid or missing DN for user: ${username}`, {
logger.error(`❌ Invalid or missing DN for user: ${sanitizedUsername}`, {
ldapEntryDn: ldapEntry.dn,
ldapEntryObjectName: ldapEntry.objectName,
ldapEntryType: typeof ldapEntry,
@@ -445,19 +471,21 @@ class LdapService {
// 4. 验证用户密码
const isPasswordValid = await this.authenticateUser(userDN, password)
if (!isPasswordValid) {
logger.info(`🚫 Invalid password for user: ${username}`)
logger.info(`🚫 Invalid password for user: ${sanitizedUsername}`)
return { success: false, message: 'Invalid username or password' }
}
// 5. 提取用户信息
const userInfo = this.extractUserInfo(ldapEntry, username)
const userInfo = this.extractUserInfo(ldapEntry, sanitizedUsername)
// 6. 创建或更新本地用户
const user = await userService.createOrUpdateUser(userInfo)
// 7. 检查用户是否被禁用
if (!user.isActive) {
logger.security(`🔒 Disabled user LDAP login attempt: ${username} from LDAP authentication`)
logger.security(
`🔒 Disabled user LDAP login attempt: ${sanitizedUsername} from LDAP authentication`
)
return {
success: false,
message: 'Your account has been disabled. Please contact administrator.'
@@ -470,7 +498,7 @@ class LdapService {
// 9. 创建用户会话
const sessionToken = await userService.createUserSession(user.id)
logger.info(`✅ LDAP authentication successful for user: ${username}`)
logger.info(`✅ LDAP authentication successful for user: ${sanitizedUsername}`)
return {
success: true,