2024年的一个深夜,我被一通紧急电话吵醒。公司的核心业务系统突然无法访问,客户投诉电话已经打爆了客服热线。我火速登录服务器排查,发现问题出在一个看似简单的操作上——某位同事为了"方便",在部署时执行了chmod -R 777 /var/www/html。
这个命令在当时看起来解决了文件访问的问题,但却为攻击者打开了一扇大门。黑客通过上传的恶意脚本获得了执行权限,进而拿下了整台服务器。这次事故让公司损失了近百万,也让我深刻认识到:Linux权限管理绝不是一个可以"随便搞搞"的话题。
从那以后,我花了大量时间深入研究Linux权限体系,从最基础的rwx到ACL、SELinux,再到现代容器环境下的权限控制。今天,我把这些年积累的经验整理成这篇文章,希望能帮助更多运维同学避开那些我曾经踩过的坑。
在聊具体技术之前,我想先说说为什么我们需要认真对待权限管理。
安全是底线,不是选项。 根据2024年的安全报告统计,超过60%的Linux服务器入侵事件与权限配置不当有关。很多时候,攻击者不需要利用什么高深的0day漏洞,仅仅是利用管理员留下的权限漏洞就能得手。
权限管理是多人协作的基础。 在现代DevOps环境中,一台服务器可能同时运行着多个应用,由不同的团队负责维护。如果没有清晰的权限边界,要么是互相干扰,要么是出了问题找不到责任人。
合规审计的硬性要求。 如果你的公司需要通过ISO 27001、等保三级或者SOC 2认证,权限管理是必查项目。审计师会仔细检查你的服务器上是否存在777权限的文件、是否有不必要的SUID程序、是否启用了SELinux等。
Linux的权限体系并不是一成不变的,它经历了几个重要的发展阶段:
第一代:传统Unix权限(1970s)这是最基础的owner/group/others模型,通过rwx三个权限位控制访问。简单、高效,但灵活性不足。
第二代:特殊权限位(1980s)引入SUID、SGID、Sticky Bit,解决了一些特殊场景的需求,但也带来了新的安全风险。
第三代:ACL访问控制列表(1990s-2000s)POSIX ACL的出现让权限管理更加精细化,可以针对单个用户或组设置权限,不再受限于传统的三元组模型。
第四代:强制访问控制(2000s-至今)SELinux、AppArmor等MAC机制的出现,将权限控制提升到了一个新的高度。即使是root用户,在MAC策略的约束下也不能为所欲为。
第五代:容器与云原生时代(2015-至今)随着Docker、Kubernetes的普及,namespace、cgroup、seccomp等技术与传统权限系统结合,形成了更加复杂但也更加安全的权限控制体系。
这篇文章的内容适用于以下场景:
环境要求:
# 操作系统版本(推荐)RHEL/Rocky Linux/AlmaLinux 9.xUbuntu 24.04 LTSDebian 12# 内核版本Linux Kernel 6.1+(支持最新的安全特性)# 必要的软件包acl # ACL支持attr # 扩展属性支持policycoreutils-python-utils # SELinux工具(RHEL系)apparmor-utils # AppArmor工具(Ubuntu/Debian)auditd # 审计守护进程在动手配置之前,我们需要先把基础概念搞清楚。我见过太多人急于求成,对着网上的命令一顿复制粘贴,结果把系统搞得一团糟。
在Linux中,一切皆文件。每个文件都有三组权限:
每组权限包含三个基本操作:
对于文件和目录,这些权限的含义略有不同:
权限 | 对文件的含义 | 对目录的含义------|------------------------|---------------------------r | 查看文件内容 | 列出目录中的文件名w | 修改文件内容 | 在目录中创建、删除、重命名文件x | 执行文件(脚本/程序) | 进入目录(cd)这里有个容易混淆的地方:目录的r权限只能让你看到文件名列表,如果没有x权限,你无法读取文件的详细信息(如大小、权限、修改时间),也无法访问目录中的文件内容。
我来演示一下:
# 创建测试目录和文件mkdir /tmp/perm_testecho"secret content" > /tmp/perm_test/secret.txt# 只给r权限,不给x权限chmod 744 /tmp/perm_testchmod 744 /tmp/perm_test/secret.txt# 切换到普通用户测试su - testuser# 可以看到文件名,但无法看到详细信息ls /tmp/perm_test# 输出:secret.txtls -l /tmp/perm_test# 输出:ls: cannot access '/tmp/perm_test/secret.txt': Permission deniedcat /tmp/perm_test/secret.txt# 输出:cat: /tmp/perm_test/secret.txt: Permission denied很多新手对权限的数字表示法感到困惑,其实换算规则很简单:
r = 4w = 2x = 1将需要的权限数字相加:rwx = 4 + 2 + 1 = 7rw- = 4 + 2 + 0 = 6r-x = 4 + 0 + 1 = 5r-- = 4 + 0 + 0 = 4所以常见的权限组合:
755 = rwxr-xr-x # 所有者可读写执行,其他人可读和执行644 = rw-r--r-- # 所有者可读写,其他人只读700 = rwx------ # 只有所有者可访问600 = rw------- # 只有所有者可读写我个人有个记忆口诀:**"7是全开,6是读写,5是读执行,4是只读,0是全关"**。记住这几个数字的含义,其他的都是组合。
chmod是最常用的权限管理命令,支持两种模式:
符号模式:
# 基本语法chmod [ugoa][+-=][rwx] 文件名# u: user(所有者) g: group(所属组) o: others(其他人) a: all(所有人)# +: 添加权限 -: 移除权限 =: 设置权限# 示例chmod u+x script.sh # 给所有者添加执行权限chmod g-w config.ini # 移除所属组的写权限chmod o=r public.html # 设置其他人只有读权限chmod a+r readme.txt # 所有人添加读权限chmod u+x,g=rx,o= app.sh # 组合操作数字模式:
# 基本语法chmod [权限数字] 文件名# 示例chmod 755 script.sh # rwxr-xr-xchmod 644 config.ini # rw-r--r--chmod 600 id_rsa # rw-------chmod 700 .ssh # rwx------递归操作:
# 递归修改目录及其所有内容chmod -R 755 /var/www/html# 但这种做法通常是错误的!# 因为目录需要x权限才能进入,但普通文件通常不需要执行权限# 正确的做法是分别处理目录和文件# 只修改目录权限find /var/www/html -type d -exec chmod 755 {} \;# 只修改文件权限find /var/www/html -type f -exec chmod 644 {} \;这是一个非常重要的实践经验。我见过很多人直接chmod -R 755,结果把所有文件都变成了可执行的,这在安全上是不可接受的。
# 基本语法chown [用户]:[组] 文件名# 示例chown nginx:nginx /var/www/html # 修改所有者和所属组chown nginx /var/www/html # 只修改所有者chown :www-data /var/www/html # 只修改所属组chown -R nginx:nginx /var/www/html # 递归修改# 使用UID和GIDchown 1000:1000 /data/app # 在容器环境中常用# 保持引用chown --reference=/etc/nginx/nginx.conf /etc/nginx/conf.d/ # 复制参考文件的所有者# 基本语法chgrp [组] 文件名# 示例chgrp developers /opt/projectchgrp -R www-data /var/www/html# 实际工作中,chown命令可以同时修改用户和组,所以chgrp用得相对较少除了基本的rwx权限,Linux还有三个特殊权限位:SUID、SGID和Sticky Bit。这些权限位功能强大,但如果使用不当,也会带来严重的安全风险。
原理: 当一个可执行文件设置了SUID位后,任何用户执行该文件时,都会临时获得文件所有者的权限。
典型应用:/usr/bin/passwd命令就设置了SUID。普通用户修改密码时,需要写入/etc/shadow文件,但这个文件只有root能写。通过SUID,普通用户执行passwd时临时获得root权限,完成密码修改。
# 查看passwd的权限ls -l /usr/bin/passwd# -rwsr-xr-x. 1 root root 32648 Aug 10 2021 /usr/bin/passwd# 注意所有者权限位中的's',这就是SUID的标志# 设置SUIDchmod u+s /path/to/executablechmod 4755 /path/to/executable # 4表示SUID# 移除SUIDchmod u-s /path/to/executablechmod 0755 /path/to/executable安全警告: SUID是一个非常危险的权限。如果一个设置了SUID的程序存在漏洞,攻击者可能借此获得root权限。我的建议是:
find / -perm -4000 -type f 2>/dev/nullSGID根据应用对象不同,有两种效果:
对可执行文件: 执行时临时获得文件所属组的权限。
对目录: 在该目录中创建的新文件会继承目录的所属组,而不是创建者的主组。
# 查看SGID标志(所属组权限位中的's')ls -ld /usr/bin/write# -rwxr-sr-x. 1 root tty 19544 Aug 10 2021 /usr/bin/write# 设置SGIDchmod g+s /path/to/dirchmod 2755 /path/to/dir # 2表示SGID# SGID目录的实际应用:团队共享目录mkdir /opt/projectgroupadd developerschgrp developers /opt/projectchmod 2775 /opt/project# 现在,任何人在/opt/project中创建的文件,所属组都会是developers我在很多公司都用SGID来设置团队共享目录。它解决了一个常见问题:不同用户创建的文件默认属于各自的主组,其他组员可能无法访问。设置SGID后,所有文件统一属于项目组。
原理: 对于设置了Sticky Bit的目录,即使用户对目录有写权限,也只能删除自己创建的文件,不能删除其他用户的文件。
典型应用:/tmp目录就设置了Sticky Bit。所有用户都能在/tmp中创建文件,但不能删除别人的文件。
# 查看/tmp的权限ls -ld /tmp# drwxrwxrwt. 18 root root 4096 Jan 7 10:00 /tmp# 注意最后的't',这就是Sticky Bit的标志# 设置Sticky Bitchmod +t /path/to/dirchmod 1777 /path/to/dir # 1表示Sticky Bit# 移除Sticky Bitchmod -t /path/to/dir配置完权限后,一定要验证效果。我习惯用以下方法:
# 1. 使用stat命令查看详细权限信息stat /var/www/html# 输出会包含Access、Uid、Gid等详细信息# 2. 使用namei命令检查路径上的所有权限namei -l /var/www/html/index.php# 这会显示路径上每一级目录的权限,帮你找到权限链条中的问题# 3. 使用sudo -u切换用户测试sudo -u nginx cat /var/www/html/config.phpsudo -u www-data ls -la /var/www/html# 4. 使用getfacl查看ACL(如果使用了ACL)getfacl /var/www/html这是我在工作中最常见的场景。一个典型的Web应用需要考虑:运行用户、上传目录、日志目录、配置文件等不同位置的权限。
#!/bin/bash# web_permission_setup.sh# Web应用权限配置脚本# 定义变量WEB_ROOT="/var/www/myapp"WEB_USER="nginx"WEB_GROUP="www-data"UPLOAD_DIR="${WEB_ROOT}/uploads"LOG_DIR="/var/log/myapp"CONFIG_DIR="${WEB_ROOT}/config"# 1. 创建目录结构mkdir -p ${WEB_ROOT}/{public,config,uploads,storage}mkdir -p ${LOG_DIR}# 2. 设置基本所有权chown -R ${WEB_USER}:${WEB_GROUP}${WEB_ROOT}chown -R ${WEB_USER}:${WEB_GROUP}${LOG_DIR}# 3. 设置目录权限# 目录需要执行权限才能进入find ${WEB_ROOT} -type d -exec chmod 755 {} \;# 4. 设置文件权限# 普通文件不需要执行权限find ${WEB_ROOT} -type f -exec chmod 644 {} \;# 5. 配置文件加强保护# 配置文件只允许所有者读写chmod 600 ${CONFIG_DIR}/*.phpchmod 600 ${CONFIG_DIR}/.env# 6. 上传目录特殊处理# 禁止执行任何脚本chmod 755 ${UPLOAD_DIR}# 在nginx配置中也要禁用PHP执行(后面会讲)# 7. 日志目录chmod 750 ${LOG_DIR}chmod 640 ${LOG_DIR}/*.log 2>/dev/null# 8. 可执行脚本(如果有)chmod 750 ${WEB_ROOT}/bin/*.sh 2>/dev/null# 9. 使用SGID确保新文件属于正确的组chmod g+s ${UPLOAD_DIR}chmod g+s ${LOG_DIR}echo"权限配置完成!"# 验证echo"=== 验证权限设置 ==="ls -la ${WEB_ROOT}ls -la ${CONFIG_DIR}ls -la ${UPLOAD_DIR}配合Nginx配置,禁止上传目录执行脚本:
# /etc/nginx/conf.d/myapp.confserver { listen 80; server_name myapp.example.com; root /var/www/myapp/public; # 禁止uploads目录执行PHP location ^~ /uploads/ { location ~ \.php$ { deny all; return 403; } } # 禁止访问隐藏文件和配置文件 location ~ /\. { deny all; return 404; } location ~ /config/ { deny all; return 404; }}数据库文件的权限配置需要特别谨慎,因为这里存储着最敏感的数据。
#!/bin/bash# mysql_permission_setup.sh# MySQL数据库权限配置脚本# MySQL数据目录MYSQL_DATA="/var/lib/mysql"MYSQL_LOG="/var/log/mysql"MYSQL_CONFIG="/etc/mysql"MYSQL_USER="mysql"MYSQL_GROUP="mysql"# 1. 数据目录 - 只有mysql用户可以访问chown -R ${MYSQL_USER}:${MYSQL_GROUP}${MYSQL_DATA}chmod 750 ${MYSQL_DATA}find ${MYSQL_DATA} -type d -exec chmod 750 {} \;find ${MYSQL_DATA} -type f -exec chmod 640 {} \;# 2. 日志目录chown -R ${MYSQL_USER}:${MYSQL_GROUP}${MYSQL_LOG}chmod 750 ${MYSQL_LOG}chmod 640 ${MYSQL_LOG}/*.log 2>/dev/null# 3. 配置文件# my.cnf应该是root拥有,mysql可读chown root:${MYSQL_GROUP}${MYSQL_CONFIG}/my.cnfchmod 640 ${MYSQL_CONFIG}/my.cnf# conf.d目录下的配置文件chown root:${MYSQL_GROUP}${MYSQL_CONFIG}/conf.d/*chmod 640 ${MYSQL_CONFIG}/conf.d/*# 4. Socket文件目录mkdir -p /var/run/mysqldchown ${MYSQL_USER}:${MYSQL_GROUP} /var/run/mysqldchmod 755 /var/run/mysqld# 5. 备份目录 - 更严格的权限BACKUP_DIR="/backup/mysql"mkdir -p ${BACKUP_DIR}chown root:${MYSQL_GROUP}${BACKUP_DIR}chmod 750 ${BACKUP_DIR}echo"MySQL权限配置完成!"SSH对密钥文件的权限要求非常严格。权限配置不正确,SSH会直接拒绝使用这些密钥。
#!/bin/bash# ssh_permission_setup.sh# SSH密钥权限配置脚本SSH_DIR="$HOME/.ssh"# 1. .ssh目录chmod 700 ${SSH_DIR}# 2. 私钥文件 - 只有所有者可读chmod 600 ${SSH_DIR}/id_rsachmod 600 ${SSH_DIR}/id_ed25519chmod 600 ${SSH_DIR}/id_ecdsa# 处理所有私钥find ${SSH_DIR} -name "id_*" ! -name "*.pub" -exec chmod 600 {} \;# 3. 公钥文件chmod 644 ${SSH_DIR}/*.pub# 4. authorized_keys - 非常重要chmod 600 ${SSH_DIR}/authorized_keys# 5. known_hostschmod 644 ${SSH_DIR}/known_hosts# 6. config文件chmod 600 ${SSH_DIR}/config# 对于服务器端的SSH配置# /etc/ssh/sshd_configsudo chmod 600 /etc/ssh/sshd_configsudo chmod 600 /etc/ssh/ssh_host_*_keysudo chmod 644 /etc/ssh/ssh_host_*_key.pubecho"SSH权限配置完成!"# 验证echo"=== 验证SSH权限 ==="ls -la ${SSH_DIR}当传统的owner/group/others模型无法满足需求时,就需要使用ACL了。
场景示例: 一个项目目录需要让开发组有读写权限,运维组有只读权限,而某个特定的外包人员需要只读特定子目录。
#!/bin/bash# acl_setup.sh# ACL权限配置示例PROJECT_DIR="/opt/project"DEV_GROUP="developers"OPS_GROUP="operations"CONTRACTOR="john_contractor"# 1. 首先确保文件系统支持ACL# 检查挂载选项mount | grep " / " | grep acl# 如果没有,需要重新挂载或修改/etc/fstab# 2. 设置基本权限chown -R root:${DEV_GROUP}${PROJECT_DIR}chmod 750 ${PROJECT_DIR}# 3. 为开发组设置默认ACL# -d: 设置默认ACL,对新建文件生效# -m: 修改ACLsetfacl -R -m g:${DEV_GROUP}:rwx ${PROJECT_DIR}setfacl -R -d -m g:${DEV_GROUP}:rwx ${PROJECT_DIR}# 4. 为运维组设置只读权限setfacl -R -m g:${OPS_GROUP}:rx ${PROJECT_DIR}setfacl -R -d -m g:${OPS_GROUP}:rx ${PROJECT_DIR}# 5. 为外包人员设置特定目录的权限setfacl -m u:${CONTRACTOR}:rx ${PROJECT_DIR}/docssetfacl -R -m u:${CONTRACTOR}:rx ${PROJECT_DIR}/docs# 6. 查看ACL设置echo"=== ACL配置结果 ==="getfacl ${PROJECT_DIR}# 7. 备份ACL配置(很重要!)getfacl -R ${PROJECT_DIR} > /backup/project_acl_backup.txt# 8. 恢复ACL配置# setfacl --restore=/backup/project_acl_backup.txtACL常用命令速查:
# 查看文件的ACLgetfacl /path/to/file# 设置用户ACLsetfacl -m u:username:rwx /path/to/file# 设置组ACLsetfacl -m g:groupname:rx /path/to/file# 设置默认ACL(对目录中新建的文件生效)setfacl -d -m u:username:rwx /path/to/dir# 递归设置ACLsetfacl -R -m u:username:rwx /path/to/dir# 移除特定用户的ACLsetfacl -x u:username /path/to/file# 移除所有ACLsetfacl -b /path/to/file# 复制ACL到另一个文件getfacl file1 | setfacl --set-file=- file2SELinux是红帽系Linux发行版的默认强制访问控制系统。很多管理员因为它"太麻烦"而直接禁用,这是非常不明智的做法。
#!/bin/bash# selinux_web_setup.sh# SELinux配置示例 - Web应用场景# 1. 检查SELinux状态getenforce# Enforcing: 强制模式,违反策略会被阻止# Permissive: 宽容模式,违反策略只记录不阻止# Disabled: 禁用# 2. 查看当前SELinux策略sestatus# 3. 设置Web目录的SELinux上下文# httpd_sys_content_t: 允许httpd读取# httpd_sys_rw_content_t: 允许httpd读写WEB_ROOT="/var/www/myapp"# 设置静态内容为只读semanage fcontext -a -t httpd_sys_content_t "${WEB_ROOT}(/.*)?"# 设置uploads目录为可写semanage fcontext -a -t httpd_sys_rw_content_t "${WEB_ROOT}/uploads(/.*)?"# 应用上下文restorecon -Rv ${WEB_ROOT}# 4. 查看文件的SELinux上下文ls -Z ${WEB_ROOT}# 5. 临时修改上下文(重启或restorecon后会失效)chcon -t httpd_sys_rw_content_t ${WEB_ROOT}/temp# 6. 常见的布尔值设置# 允许httpd连接网络(如调用外部API)setsebool -P httpd_can_network_connect on# 允许httpd连接数据库setsebool -P httpd_can_network_connect_db on# 允许httpd发送邮件setsebool -P httpd_can_sendmail on# 允许httpd执行CGI脚本setsebool -P httpd_enable_cgi on# 7. 查看所有httpd相关的布尔值getsebool -a | grep httpd# 8. 排查SELinux拒绝问题# 查看审计日志ausearch -m avc -ts recent# 使用audit2why分析原因ausearch -m avc -ts recent | audit2why# 生成允许规则(谨慎使用!)ausearch -m avc -ts recent | audit2allow -M myapp_policysemodule -i myapp_policy.ppecho"SELinux配置完成!"SELinux常用上下文类型:
httpd_sys_content_t - Web服务器只读内容httpd_sys_rw_content_t - Web服务器可读写内容httpd_sys_script_exec_t - Web服务器可执行脚本mysqld_db_t - MySQL数据库文件postgresql_db_t - PostgreSQL数据库文件ssh_home_t - SSH配置文件user_home_t - 用户主目录内容tmp_t - 临时文件var_log_t - 日志文件Ubuntu和Debian系统默认使用AppArmor。相比SELinux,AppArmor的配置更加直观。
#!/bin/bash# apparmor_setup.sh# AppArmor配置示例# 1. 检查AppArmor状态aa-status# 2. 查看加载的配置文件ls /etc/apparmor.d/# 3. 创建自定义配置文件cat > /etc/apparmor.d/usr.local.bin.myapp << 'EOF'#include <tunables/global>/usr/local/bin/myapp {#include <abstractions/base>#include <abstractions/nameservice># 允许读取配置文件 /etc/myapp/** r, /etc/myapp/config.ini r,# 允许读写数据目录 /var/lib/myapp/** rw, /var/lib/myapp/ r,# 允许读写日志 /var/log/myapp/** rw, /var/log/myapp/ r,# 允许执行自身 /usr/local/bin/myapp mr,# 允许网络访问 network inet stream, network inet dgram,# 拒绝所有其他访问 deny /home/** rw, deny /root/** rw, deny /etc/passwd w, deny /etc/shadow rw,}EOF# 4. 检查配置文件语法apparmor_parser -p /etc/apparmor.d/usr.local.bin.myapp# 5. 加载配置文件apparmor_parser -r /etc/apparmor.d/usr.local.bin.myapp# 6. 设置为强制模式aa-enforce /usr/local/bin/myapp# 7. 临时设置为宽容模式(用于调试)aa-complain /usr/local/bin/myapp# 8. 查看AppArmor日志journalctl -xe | grep apparmordmesg | grep apparmor在2025年,容器已经成为应用部署的主流方式。容器环境的权限控制有其特殊性。
# docker-compose.yml 安全配置示例version:'3.8'services:webapp:image:myapp:latestuser:"1000:1000"# 不使用root用户运行# 只读文件系统read_only:true# 挂载必要的可写目录volumes:-./config:/app/config:ro# 配置文件只读-webapp_data:/app/data:rw# 数据目录可写-webapp_logs:/var/log/app:rw# 日志目录可写# tmpfs用于临时文件tmpfs:-/tmp:size=100M,mode=1777-/run:size=10M,mode=755# 安全选项security_opt:-no-new-privileges:true# 禁止提权-seccomp:default# 使用默认的seccomp配置# 移除不必要的能力cap_drop:-ALL# 只添加必要的能力cap_add:-NET_BIND_SERVICE# 如果需要绑定低端口# AppArmor配置(如果使用)# security_opt:# - apparmor:docker-defaultvolumes:webapp_data:webapp_logs:Kubernetes Pod安全配置:
# pod-security.yamlapiVersion:v1kind:Podmetadata:name:secure-appspec:securityContext:runAsNonRoot:truerunAsUser:1000runAsGroup:1000fsGroup:1000seccompProfile:type:RuntimeDefaultcontainers:-name:appimage:myapp:latestsecurityContext:allowPrivilegeEscalation:falsereadOnlyRootFilesystem:truecapabilities:drop:-ALLvolumeMounts:-name:tmpmountPath:/tmp-name:datamountPath:/app/data-name:configmountPath:/app/configreadOnly:truevolumes:-name:tmpemptyDir:medium:MemorysizeLimit:100Mi-name:datapersistentVolumeClaim:claimName:app-data-pvc-name:configconfigMap:name:app-configdefaultMode:0400# 只读权限经过多年的实践,我总结出以下权限设计原则:
这是安全领域最重要的原则之一:只授予完成任务所必需的最小权限。
# 错误示范 - 图方便chmod 777 /var/www/htmlchmod -R 777 /opt/app# 正确做法 - 精确控制chmod 755 /var/www/htmlchmod 644 /var/www/html/*.phpchmod 600 /var/www/html/config.php不同的任务应该由不同的用户/组来执行,避免权限过度集中。
# 创建专用用户和组groupadd webappuseradd -r -g webapp -s /sbin/nologin webappgroupadd dbbackupuseradd -r -g dbbackup -s /sbin/nologin dbbackup# Web应用使用webapp用户运行# 数据库备份使用dbbackup用户执行不要依赖单一的安全措施,应该建立多层防御。
# 第一层:文件系统权限chmod 600 /etc/myapp/secrets.conf# 第二层:SELinux/AppArmorsemanage fcontext -a -t myapp_secret_t "/etc/myapp/secrets.conf"# 第三层:使用加密存储敏感信息# 不要明文存储密码,使用vault或加密配置# 第四层:审计监控auditctl -w /etc/myapp/secrets.conf -p rwa -k secrets_access#!/bin/bash# audit_suid_sgid.sh# 审计SUID/SGID程序echo"=== SUID程序 ==="find / -perm -4000 -type f 2>/dev/null | whileread file; do ls -l "$file" rpm -qf "$file" 2>/dev/null || dpkg -S "$file" 2>/dev/null || echo"未知来源"echo"---"doneecho"=== SGID程序 ==="find / -perm -2000 -type f 2>/dev/null | whileread file; do ls -l "$file" rpm -qf "$file" 2>/dev/null || dpkg -S "$file" 2>/dev/null || echo"未知来源"echo"---"done# 移除不必要的SUID# chmod u-s /usr/bin/unnecessary_suid_program#!/bin/bash# find_world_writable.sh# 查找全局可写文件和目录echo"=== 全局可写目录(不含Sticky Bit)==="find / -type d -perm -0002 ! -perm -1000 2>/dev/nullecho"=== 全局可写文件 ==="find / -type f -perm -0002 2>/dev/nullecho"=== 无主文件 ==="find / -nouser -o -nogroup 2>/dev/null# /etc/profile 或 /etc/bashrcumask 027 # 新建文件默认权限640,目录750# 对于安全要求更高的环境umask 077 # 新建文件默认权限600,目录700# 在systemd服务中设置# /etc/systemd/system/myapp.service[Service]UMask=0027这是最常见也是最危险的错误。我曾经遇到过一个客户,他们的生产服务器上有超过10万个777权限的文件。
错误做法:
# "反正能用就行"chmod 777 /var/www/htmlchmod -R 777 /opt/app正确做法:
# 分析实际需求# Web服务器运行用户是nginxchown -R nginx:nginx /var/www/htmlfind /var/www/html -type d -exec chmod 755 {} \;find /var/www/html -type f -exec chmod 644 {} \;# 需要写入的目录单独处理chmod 775 /var/www/html/uploads很多人只关注目标文件的权限,忽略了路径上每一级目录的权限。
# 文件权限正确chmod 644 /opt/app/data/config/secrets.json# 但是目录权限错了chmod 777 /opt/app/data # 任何人都能进入并创建文件# 正确做法:检查整个路径namei -l /opt/app/data/config/secrets.json使用cp、scp等命令复制文件时,可能会改变文件的权限和所有者。
# 错误:复制后不检查权限cp /root/app.conf /etc/myapp/# 正确:复制时保留权限,或者复制后重新设置cp -p /root/app.conf /etc/myapp/ # -p保留权限# 或者cp /root/app.conf /etc/myapp/chown root:myapp /etc/myapp/app.confchmod 640 /etc/myapp/app.conf很多人遇到SELinux拒绝就直接禁用,这是非常不明智的。
# 错误做法setenforce 0# 在/etc/selinux/config中设置SELINUX=disabled# 正确做法# 1. 查看SELinux拒绝原因ausearch -m avc -ts recent | audit2why# 2. 根据原因采取措施# 如果是上下文问题restorecon -Rv /path/to/file# 如果需要开启布尔值setsebool -P httpd_can_network_connect on# 3. 只有在确认安全的情况下才生成自定义策略权限检查会带来一定的性能开销,虽然通常可以忽略,但在高性能场景下需要注意:
# ACL会比传统权限检查慢一些# 如果不需要ACL的灵活性,使用传统权限更高效# SELinux在enforce模式下会进行额外的检查# 确保策略配置正确,避免不必要的deny日志# 对于高频访问的文件,权限设计要简单直接# 避免复杂的ACL规则链权限问题的排查首先要看日志:
# 1. 系统日志journalctl -xetail -f /var/log/messagestail -f /var/log/syslog# 2. 应用日志tail -f /var/log/nginx/error.logtail -f /var/log/apache2/error.logtail -f /var/log/php-fpm/error.log# 3. SELinux审计日志ausearch -m avc -ts todayausearch -m avc -c httpd # 查找httpd相关的拒绝# 4. AppArmor日志journalctl -xe | grep apparmordmesg | grep apparmor# 5. 使用audit2why分析SELinux拒绝原因ausearch -m avc -ts today | audit2why当遇到"Permission denied"错误时,我通常按以下流程排查:
#!/bin/bash# permission_troubleshoot.sh# 权限问题排查脚本TARGET=$1# 要检查的文件或目录if [ -z "$TARGET" ]; thenecho"用法: $0 <文件或目录>"exit 1fiecho"=== 第一步:检查文件是否存在 ==="ls -la "$TARGET" 2>&1echo""echo"=== 第二步:检查路径上所有目录的权限 ==="namei -l "$TARGET"echo""echo"=== 第三步:检查ACL ==="getfacl "$TARGET" 2>&1echo""echo"=== 第四步:检查SELinux上下文 ==="ls -Z "$TARGET" 2>&1echo""echo"=== 第五步:检查SELinux状态 ==="getenforceecho""echo"=== 第六步:最近的SELinux拒绝 ==="ausearch -m avc -ts recent 2>&1 | head -50echo""echo"=== 第七步:检查AppArmor状态 ==="aa-status 2>&1 | head -20echo""echo"=== 第八步:检查文件系统挂载选项 ==="# 检查是否有noexec、nosuid等选项df "$TARGET" | tail -1 | awk '{print $1}' | xargs -I {} mount | grep {}echo""echo"=== 第九步:检查文件属性 ==="lsattr "$TARGET" 2>&1auditd是Linux系统审计的核心工具,可以监控文件访问和权限变更。
#!/bin/bash# audit_setup.sh# 配置auditd监控权限相关事件# 1. 安装auditdyum install -y audit audit-libs # RHEL系apt install -y auditd audispd-plugins # Debian系# 2. 启动auditd服务systemctl enable auditdsystemctl start auditd# 3. 配置审计规则cat >> /etc/audit/rules.d/permissions.rules << 'EOF'# 监控权限变更命令-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -k perm_change-a always,exit -F arch=b64 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -k owner_change-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -k attr_change# 监控敏感文件访问-w /etc/passwd -p wa -k passwd_changes-w /etc/shadow -p wa -k shadow_changes-w /etc/sudoers -p wa -k sudoers_changes-w /etc/ssh/sshd_config -p wa -k sshd_config# 监控SUID/SGID变更-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F perm=04000 -k suid_change-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F perm=02000 -k sgid_change# 监控特定目录-w /var/www/html -p wa -k web_content-w /etc/myapp/ -p wa -k app_configEOF# 4. 重新加载规则augenrules --loadauditctl -l# 5. 查看审计日志ausearch -k perm_change -ts todayausearch -k passwd_changes -ts today# 6. 生成审计报告aureport --summaryaureport --file --start today结合Prometheus和AlertManager实现权限异常告警:
# prometheus/rules/permission_alerts.ymlgroups:-name:permission_alertsrules:-alert:SuidFileCreatedexpr:increase(audit_suid_changes_total[5m])>0for:0mlabels:severity:criticalannotations:summary:"SUID文件被创建或修改"description:"在主机 {{ $labels.instance }} 上检测到SUID文件变更"-alert:SensitiveFileAccessedexpr:increase(audit_sensitive_file_access_total[5m])>5for:0mlabels:severity:warningannotations:summary:"敏感文件被频繁访问"description:"文件 {{ $labels.file }} 在5分钟内被访问超过5次"-alert:PermissionChangeSpikeexpr:increase(audit_permission_changes_total[10m])>100for:5mlabels:severity:warningannotations:summary:"权限变更操作异常增多"description:"10分钟内权限变更操作超过100次,可能存在异常"使用自定义脚本导出审计指标:
#!/bin/bash# audit_exporter.sh# 导出审计指标供Prometheus采集METRICS_FILE="/var/lib/node_exporter/audit_metrics.prom"# SUID变更计数suid_changes=$(ausearch -k suid_change -ts today 2>/dev/null | grep -c "type=SYSCALL")echo"audit_suid_changes_total $suid_changes" > $METRICS_FILE# 权限变更计数perm_changes=$(ausearch -k perm_change -ts today 2>/dev/null | grep -c "type=SYSCALL")echo"audit_permission_changes_total $perm_changes" >> $METRICS_FILE# 敏感文件访问计数sensitive_access=$(ausearch -k passwd_changes -k shadow_changes -k sudoers_changes -ts today 2>/dev/null | grep -c "type=SYSCALL")echo"audit_sensitive_file_access_total $sensitive_access" >> $METRICS_FILE#!/bin/bash# realtime_permission_monitor.sh# 实时监控关键目录的权限变化WATCH_DIRS="/etc /var/www /opt/app"BASELINE_FILE="/var/lib/permission_baseline.txt"ALERT_EMAIL="security@example.com"# 生成基线generate_baseline() {echo"生成权限基线..."for dir in$WATCH_DIRS; do find $dir -type f -printf"%p %m %u %g\n" 2>/dev/null find $dir -type d -printf"%p %m %u %g\n" 2>/dev/nulldone | sort > $BASELINE_FILEecho"基线已保存到 $BASELINE_FILE"}# 检查变化check_changes() { CURRENT_FILE=$(mktemp)for dir in$WATCH_DIRS; do find $dir -type f -printf"%p %m %u %g\n" 2>/dev/null find $dir -type d -printf"%p %m %u %g\n" 2>/dev/nulldone | sort > $CURRENT_FILE DIFF=$(diff $BASELINE_FILE$CURRENT_FILE)if [ -n "$DIFF" ]; thenecho"检测到权限变化:"echo"$DIFF"# 发送告警echo"权限变化告警: $DIFF" | mail -s "权限变化告警"$ALERT_EMAILfi rm -f $CURRENT_FILE}case"$1"in baseline) generate_baseline ;; check) check_changes ;; *)echo"用法: $0 {baseline|check}"exit 1 ;;esac经过这篇文章的学习,我们掌握了Linux权限体系的完整知识:
基础权限:
特殊权限:
ACL高级权限:
强制访问控制:
容器环境:
监控审计:
回顾文章开头提到的事故,以及我这些年遇到的其他案例,有几点深刻教训:
如果你想在权限管理方面继续深入,可以关注以下方向:
# === 基础权限命令 ===chmod 755 file # 设置权限为rwxr-xr-xchmod u+x file # 给所有者添加执行权限chmod g-w file # 移除组的写权限chmod o=r file # 设置其他人只有读权限chmod -R 755 dir # 递归设置目录权限chown user:group file # 修改所有者和组chown -R user:group dir # 递归修改所有者chgrp group file # 修改所属组# === 特殊权限命令 ===chmod u+s file # 设置SUIDchmod g+s dir # 设置SGIDchmod +t dir # 设置Sticky Bitchmod 4755 file # SUID + 755chmod 2755 dir # SGID + 755chmod 1777 dir # Sticky Bit + 777# === ACL命令 ===getfacl file # 查看ACLsetfacl -m u:user:rwx file # 设置用户ACLsetfacl -m g:group:rx file # 设置组ACLsetfacl -d -m u:user:rwx dir # 设置默认ACLsetfacl -R -m u:user:rwx dir # 递归设置ACLsetfacl -x u:user file # 移除用户ACLsetfacl -b file # 移除所有ACLgetfacl -R dir > backup.acl # 备份ACLsetfacl --restore=backup.acl # 恢复ACL# === SELinux命令 ===getenforce # 查看SELinux状态setenforce 1 # 设置为强制模式setenforce 0 # 设置为宽容模式sestatus # 查看详细状态ls -Z file # 查看文件安全上下文chcon -t httpd_sys_content_t file # 临时修改上下文restorecon -Rv dir # 恢复默认上下文semanage fcontext -l # 列出上下文映射semanage fcontext -a -t type"path(/.*)?"# 添加上下文映射getsebool -a # 列出所有布尔值setsebool -P bool on # 设置布尔值ausearch -m avc -ts today # 查看AVC拒绝audit2why < /var/log/audit/audit.log # 分析拒绝原因audit2allow -M policy < avc.log # 生成允许策略# === AppArmor命令 ===aa-status # 查看状态aa-enforce /path/to/profile # 设置强制模式aa-complain /path/to/profile # 设置宽容模式aa-disable /path/to/profile # 禁用配置文件apparmor_parser -r /etc/apparmor.d/profile # 重新加载配置# === 审计命令 ===auditctl -l # 列出审计规则auditctl -w /path -p rwxa -k key # 添加监控规则ausearch -k key -ts today # 搜索审计日志aureport --summary # 生成审计报告augenrules --load # 加载规则文件# === 查找命令 ===find / -perm -4000 -type f # 查找SUID文件find / -perm -2000 -type f # 查找SGID文件find / -perm -0002 -type f # 查找全局可写文件find / -nouser -o -nogroup # 查找无主文件# === Web应用 ===/var/www/html 755 nginx:nginx # Web根目录/var/www/html/*.php 644 nginx:nginx # PHP文件/var/www/html/config 750 nginx:nginx # 配置目录/var/www/html/uploads 755 nginx:nginx # 上传目录(SGID)/var/www/html/.env 600 nginx:nginx # 环境配置# === SSH ===~/.ssh 700 user:user # SSH目录~/.ssh/id_rsa 600 user:user # 私钥~/.ssh/id_rsa.pub 644 user:user # 公钥~/.ssh/authorized_keys 600 user:user # 授权密钥~/.ssh/config 600 user:user # SSH配置/etc/ssh/sshd_config 600 root:root # SSHD配置# === 数据库 ===/var/lib/mysql 750 mysql:mysql # MySQL数据目录/etc/mysql/my.cnf 640 root:mysql # MySQL配置/var/log/mysql 750 mysql:mysql # MySQL日志# === 系统关键文件 ===/etc/passwd 644 root:root # 用户信息/etc/shadow 000 root:root # 密码哈希/etc/sudoers 440 root:root # sudo配置/etc/crontab 644 root:root # 计划任务# === 日志文件 ===/var/log 755 root:root # 日志目录/var/log/messages 640 root:root # 系统日志/var/log/secure 600 root:root # 安全日志类型 说明------------------------------------------------------------httpd_sys_content_t Web服务器只读内容httpd_sys_rw_content_t Web服务器可读写内容httpd_sys_script_exec_t Web服务器可执行脚本httpd_log_t Web服务器日志mysqld_db_t MySQL数据库文件mysqld_log_t MySQL日志postgresql_db_t PostgreSQL数据库文件ssh_home_t SSH配置文件sshd_key_t SSH主机密钥user_home_t 用户主目录内容admin_home_t 管理员主目录tmp_t 临时文件var_log_t 日志文件etc_t 配置文件bin_t 可执行文件lib_t 库文件container_file_t 容器文件(Podman/Docker)svirt_sandbox_file_t 容器沙箱文件ls -lnamei -l | ||
ausearch -m avc | ||
filechmod +x | ||
lsoffuser | ||
mountremount |
“写在最后
权限管理看似简单,实则博大精深。从最基础的rwx到复杂的SELinux策略,每一层都有其存在的意义。作为运维工程师,我们的职责不仅是让系统"能用",更要让它"安全"。
希望这篇文章能帮助你建立起完整的权限知识体系。如果你在实践中遇到问题,欢迎交流讨论。记住,在安全面前,没有"图方便"这回事。
祝工作顺利,系统安全!
对Linux学习感兴趣的同学,这里给大家分享一份大佬总结的超详细《Linux学习笔记》,足足有1456页!
并且图文并茂,代码清晰,每一章下面都有更具体详细的内容,十分适合Linux运维学习参考!


领取方式:
现在这本Linux学习笔记
前30名同学可以免费领取
扫码备注:
1456页Linux学习笔记
即可领取~
