Skip to content

PostgreSQL 防止服务器欺骗学习笔记

概述

在企业数据库环境中,服务器欺骗是一个严重的安全威胁。当 PostgreSQL 服务器因维护、故障或其他原因关闭时,恶意用户可能会利用这个窗口期启动伪造的数据库服务器,试图窃取客户端的认证凭据和敏感查询。

服务器欺骗威胁分析

攻击原理

威胁场景

真实业务风险在生产环境中,以下场景容易遭受服务器欺骗攻击:

  1. 数据库维护窗口期:DBA 执行版本升级或硬件维护
  2. 服务器故障期间:主服务器宕机,客户端重连时
  3. 开发测试环境:多个开发者共享同一台服务器
  4. 云环境迁移:服务器迁移过程中的连接切换 :::

防护策略详解

1. Local 连接防护

1.1 Unix 域套接字目录权限控制

解决问题: 防止恶意用户在默认套接字目录创建伪造套接字文件

配置示例:

bash
# 1. 创建专用的安全套接字目录
sudo mkdir -p /var/run/postgresql
sudo chown postgres:postgres /var/run/postgresql
sudo chmod 755 /var/run/postgresql

# 2. 配置 postgresql.conf
echo "unix_socket_directories = '/var/run/postgresql'" >> /etc/postgresql/15/main/postgresql.conf

# 3. 重启PostgreSQL服务
sudo systemctl restart postgresql

分析过程:

  • unix_socket_directories 指定套接字文件位置
  • 通过目录权限控制,只允许postgres用户创建套接字文件
  • 恶意用户无法在此目录创建伪造套接字

验证配置效果:

bash
# 检查套接字文件位置和权限
ls -la /var/run/postgresql/
# 输出示例:
# srwxrwxrwx 1 postgres postgres 0 Jan 15 10:30 .s.PGSQL.5432
# -rw------- 1 postgres postgres 5 Jan 15 10:30 .s.PGSQL.5432.lock

1.2 符号链接防护

解决问题: 防止应用程序仍然引用 /tmp 目录中的套接字文件

bash
# 创建指向安全位置的符号链接
sudo ln -sf /var/run/postgresql/.s.PGSQL.5432 /tmp/.s.PGSQL.5432

# 修改系统清理脚本,防止删除符号链接
echo '#!/bin/bash
# 清理临时文件,但保留PostgreSQL套接字符号链接
find /tmp -type f -atime +7 -not -name ".s.PGSQL.*" -delete
' > /etc/cron.daily/cleanup-tmp

1.3 requirepeer 连接验证

解决问题: 客户端验证服务器进程的实际所有者

客户端连接配置:

python
import psycopg2

# Python客户端使用 requirepeer 验证
conn_params = {
    'host': '/var/run/postgresql',  # Unix套接字路径
    'database': 'myapp',
    'user': 'app_user',
    'password': 'secure_password',
    'options': '-c search_path=public',
    # 要求连接的服务器进程所有者必须是 postgres
    'requirepeer': 'postgres'
}

try:
    conn = psycopg2.connect(**conn_params)
    print("✅ 连接验证成功,服务器进程所有者正确")
except psycopg2.OperationalError as e:
    print(f"❌ 连接失败,可能的服务器欺骗: {e}")

2. TCP 连接防护

2.1 SSL 证书验证

解决问题: 通过加密和证书验证确保连接到合法服务器

服务器端 SSL 配置:

bash
# 1. 生成服务器证书(生产环境应使用CA签发)
openssl req -new -x509 -days 365 -nodes -text \
    -out /etc/postgresql/15/main/server.crt \
    -keyout /etc/postgresql/15/main/server.key \
    -subj "/CN=postgresql.company.com"

# 2. 设置证书文件权限
sudo chown postgres:postgres /etc/postgresql/15/main/server.crt
sudo chown postgres:postgres /etc/postgresql/15/main/server.key
sudo chmod 600 /etc/postgresql/15/main/server.key
sudo chmod 644 /etc/postgresql/15/main/server.crt

postgresql.conf SSL 配置:

ini
# SSL 连接配置
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_ca_file = 'ca.crt'  # 如果使用客户端证书验证

# 强制SSL连接
ssl_min_protocol_version = 'TLSv1.2'
ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL'
ssl_prefer_server_ciphers = on

pg_hba.conf 配置:

bash
# 只允许SSL连接,拒绝非加密连接
# TYPE  DATABASE    USER        ADDRESS         METHOD
hostssl all         all         0.0.0.0/0       scram-sha-256
host    all         all         127.0.0.1/32    reject  # 拒绝本地非SSL连接

2.2 客户端 SSL 验证配置

完整的 Java 客户端示例:

java
import java.sql.*;
import java.util.Properties;

public class SecurePostgreSQLConnection {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://postgresql.company.com:5432/myapp";

        Properties props = new Properties();
        props.setProperty("user", "app_user");
        props.setProperty("password", "secure_password");

        // SSL 安全连接配置
        props.setProperty("ssl", "true");
        props.setProperty("sslmode", "verify-full");  // 验证服务器证书和主机名
        props.setProperty("sslcert", "/path/to/client.crt");  // 客户端证书
        props.setProperty("sslkey", "/path/to/client.key");   // 客户端私钥
        props.setProperty("sslrootcert", "/path/to/ca.crt");  // CA根证书

        try (Connection conn = DriverManager.getConnection(url, props)) {
            System.out.println("✅ 安全连接建立成功");

            // 验证连接安全性
            DatabaseMetaData metaData = conn.getMetaData();
            System.out.println("数据库版本: " + metaData.getDatabaseProductVersion());

        } catch (SQLException e) {
            System.err.println("❌ 连接失败: " + e.getMessage());
            // 可能是服务器欺骗或证书验证失败
        }
    }
}

2.3 系统 CA 池使用

解决问题: 在生产环境中使用受信任的证书颁发机构

python
import psycopg2
import ssl

def create_secure_connection():
    """使用系统CA池创建安全连接"""

    conn_params = {
        'host': 'prod-postgresql.company.com',
        'port': 5432,
        'database': 'production_db',
        'user': 'app_user',
        'password': 'secure_password',
        'sslmode': 'verify-full',  # 强制完整验证
        'sslrootcert': 'system',   # 使用系统CA池
    }

    try:
        # 建立连接
        conn = psycopg2.connect(**conn_params)

        # 验证SSL连接状态
        with conn.cursor() as cur:
            cur.execute("SELECT ssl_is_used(), ssl_version(), ssl_cipher();")
            ssl_info = cur.fetchone()

            print(f"SSL使用状态: {ssl_info[0]}")
            print(f"SSL版本: {ssl_info[1]}")
            print(f"加密套件: {ssl_info[2]}")

        return conn

    except psycopg2.OperationalError as e:
        print(f"❌ SSL连接失败: {e}")
        raise

# 使用示例
if __name__ == "__main__":
    conn = create_secure_connection()
    # 执行业务逻辑...
    conn.close()

3. 特定认证方法的防护

3.1 SCRAM-SHA-256 与 Channel Binding

解决问题: 防止 SCRAM 认证过程中的中间人攻击

python
import psycopg2

def scram_secure_connection():
    """使用SCRAM-SHA-256和通道绑定的安全连接"""

    conn_params = {
        'host': 'postgresql.company.com',
        'database': 'secure_app',
        'user': 'secure_user',
        'password': 'complex_password_123!',

        # SSL 必需配置
        'sslmode': 'verify-full',
        'sslrootcert': '/etc/ssl/certs/company-ca.crt',

        # SCRAM 和通道绑定配置
        'channel_binding': 'require',  # 要求通道绑定
    }

    try:
        conn = psycopg2.connect(**conn_params)

        # 验证认证方法
        with conn.cursor() as cur:
            cur.execute("""
                SELECT
                    usename,
                    application_name,
                    client_addr,
                    backend_start,
                    ssl,
                    ssl_version
                FROM pg_stat_ssl pss
                JOIN pg_stat_activity psa ON pss.pid = psa.pid
                WHERE psa.usename = current_user;
            """)

            connection_info = cur.fetchone()
            print("认证连接信息:")
            print(f"  用户: {connection_info[0]}")
            print(f"  SSL状态: {connection_info[4]}")
            print(f"  SSL版本: {connection_info[5]}")

        return conn

    except Exception as e:
        print(f"❌ SCRAM认证失败: {e}")
        raise

# pg_hba.conf 相应配置
"""
# TYPE  DATABASE    USER           ADDRESS         METHOD
hostssl secure_app  secure_user    0.0.0.0/0       scram-sha-256
"""

3.2 GSSAPI 认证防护

解决问题: 企业环境中使用 Kerberos 认证防止欺骗

服务器端配置:

bash
# postgresql.conf GSSAPI 配置
krb_server_keyfile = '/etc/postgresql/15/main/krb5.keytab'
krb_caseins_users = on

# pg_hba.conf 配置
echo "hostgssenc all all 0.0.0.0/0 gss include_realm=0" >> /etc/postgresql/15/main/pg_hba.conf

客户端 GSSAPI 连接:

java
import java.sql.*;
import java.util.Properties;

public class KerberosPostgreSQLConnection {

    public static Connection createGSSAPIConnection() throws SQLException {
        // 设置Kerberos系统属性
        System.setProperty("java.security.krb5.conf", "/etc/krb5.conf");
        System.setProperty("java.security.auth.login.config", "/path/to/jaas.conf");

        String url = "jdbc:postgresql://postgresql.company.com:5432/enterprise_db";
        Properties props = new Properties();

        // GSSAPI 认证配置
        props.setProperty("user", "[email protected]");
        props.setProperty("gsslib", "gssapi");           // 使用GSSAPI
        props.setProperty("gssencmode", "require");      // 强制GSSAPI加密
        props.setProperty("jaasApplicationName", "PostgreSQLClient");

        try {
            Connection conn = DriverManager.getConnection(url, props);
            System.out.println("✅ GSSAPI认证成功");
            return conn;

        } catch (SQLException e) {
            System.err.println("❌ GSSAPI认证失败: " + e.getMessage());
            throw e;
        }
    }
}

防护效果对比

防护方法防护等级适用场景实施复杂度性能影响
Unix 套接字权限本地连接
requirepeer本地连接
SSL 证书验证远程连接轻微
系统 CA 池很高生产环境轻微
SCRAM+通道绑定认证安全轻微
GSSAPI 加密很高企业环境中等

攻击检测和响应

检测伪造服务器的 SQL 查询

sql
-- 检查当前连接的SSL状态
SELECT
    pid,
    usename,
    application_name,
    client_addr,
    backend_start,
    ssl,
    ssl_version,
    ssl_cipher
FROM pg_stat_ssl pss
JOIN pg_stat_activity psa ON pss.pid = psa.pid
WHERE ssl = true;

-- 检查异常连接模式
SELECT
    client_addr,
    COUNT(*) as connection_count,
    MIN(backend_start) as first_connection,
    MAX(backend_start) as last_connection
FROM pg_stat_activity
WHERE backend_start > NOW() - INTERVAL '1 hour'
GROUP BY client_addr
HAVING COUNT(*) > 50  -- 检测异常高频连接
ORDER BY connection_count DESC;

-- 监控认证失败
SELECT
    log_time,
    user_name,
    database_name,
    remote_host,
    message
FROM pg_log
WHERE message LIKE '%authentication failed%'
AND log_time > NOW() - INTERVAL '24 hours'
ORDER BY log_time DESC;

总结

防止 PostgreSQL 服务器欺骗需要多层次的安全策略:

  1. 网络层防护: 使用 SSL/TLS 加密和证书验证
  2. 认证层防护: 采用强认证方法如 SCRAM-SHA-256 和 GSSAPI
  3. 系统层防护: 合理配置文件权限和套接字目录
  4. 应用层防护: 客户端进行严格的服务器验证
  5. 监控层防护: 实施持续监控和异常检测

在生产环境中,建议同时使用多种防护方法,构建深度防御体系。单一防护方法可能存在被绕过的风险,只有多层防护才能确保数据库连接的绝对安全。

通过实施这些安全措施,企业可以有效防范服务器欺骗攻击,保护数据库连接的完整性和机密性。