安全与兼容处理
# 第 12 章 安全与兼容处理
# 目录介绍
# 12.1 安全加固
# 12.1.1 命令注入防护——引号与参数化
命令注入是 Shell 脚本最常见的漏洞——当用户输入被拼接到命令字符串中,它可能执行任意代码:
#!/bin/bash
# ===== 攻击演示 =====
# 用户输入:alice; rm -rf /
read -p "输入用户名: " username
# eval "echo Hello $username" # ❌ 危险!用户输入 "alice; rm -rf /" 会执行 rm
# ===== 防护原则:永远不要拼接命令字符串 =====
# ❌ 危险:变量直接拼接到命令中
grep "$pattern" file.txt
ssh "$host" "ls $path"
# ✅ 安全:参数保持独立,不加额外引号嵌套
grep "$1" file.txt # $1 作为 grep 的一个参数,安全
ssh "$host" ls "$path" # 参数独立传递
# ===== Shell 特殊字符的威力 =====
# 以下字符在命令拼接中都很危险:
# ; | & $ ` ( ) < > " ' # \ ! ~ { } [ ]
# ===== 防护措施 1:严格过滤输入 =====
sanitize_input() {
local input="$1"
# 移除危险字符(具体策略取决于业务)
echo "$input" | tr -d ';&|`$(){}[]<>!#~'
}
# ===== 防护措施 2:用 -- 停止选项解析 =====
# ❌ 用户输入 -rf 会被当作选项
rm "$user_input"
# ✅ -- 之后的内容不再被解析为选项
rm -- "$user_input"
grep -- "$pattern" file.txt
# ===== 防护措施 3:白名单验证 =====
validate_username() {
local name="$1"
if [[ ! "$name" =~ ^[a-zA-Z_][a-zA-Z0-9_-]{2,31}$ ]]; then
echo "无效的用户名: $name" >&2
return 1
fi
}
# ===== 防护措施 4:用安全替代 eval =====
# ❌ eval
eval "echo $user_input" # 任何内容直接作为代码执行
# ✅ 用 printf 代替
printf '%s\n' "$user_input" # 安全
# ===== 实战:安全执行外部命令 =====
safe_exec() {
local cmd="$1"
# 只允许执行白名单中的命令
case "$cmd" in
backup|clean|restart)
"/opt/scripts/${cmd}.sh"
;;
*)
echo "不允许的命令: $cmd" >&2
return 1
;;
esac
}
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
62
63
64
65
66
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
62
63
64
65
66
# 12.1.2 路径注入与 PATH 安全
#!/bin/bash
# ===== PATH 注入演示 =====
# 如果脚本的 PATH 包含 "." 或用户可写的目录:
# 有人在 /tmp 下放了一个名叫 "ls" 的恶意脚本
# 脚本里写 cd /tmp && ls → 运行了假 ls!
# 它会先搜索 PATH 中的 /tmp(如果在那),执行恶意 ls
# ===== 防护:脚本开头显式设置 PATH =====
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# 1. 不包含 "."(当前目录)
# 2. 不包含用户可写的目录
# 3. 使用绝对路径
# ===== 变量引用 IFS 安全 =====
# IFS 控制分词——恶意 IFS 可让 "ls" 变成 "l" 和 "s"
IFS=$' \t\n' # 开头重置为安全值
# ===== 路径验证 =====
validate_path() {
local path="$1"
# 检测路径遍历攻击(../)
if [[ "$path" =~ \.\./ ]]; then
echo "禁止路径遍历" >&2
return 1
fi
# 限制在允许的目录下
local real_path=$(realpath "$path" 2>/dev/null || readlink -f "$path")
if [[ "$real_path" != /opt/data/* ]]; then
echo "路径超出允许范围" >&2
return 1
fi
}
# ===== 安全地查找命令 =====
# ❌ 依赖 PATH
ls /tmp
# ✅ 用完整路径
/bin/ls /tmp
# 或先找到绝对路径:
CMD=$(command -v ls)
"$CMD" /tmp
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
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
# 12.1.3 临时文件安全——mktemp 才是正解
#!/bin/bash
# ===== mktemp —— 安全创建临时文件/目录 =====
# 自动生成唯一文件名,防止竞态条件(race condition)
# 临时文件
tmpfile=$(mktemp) # /tmp/tmp.XXXXXX
tmpfile=$(mktemp /tmp/myscript.XXXXXX) # 指定前缀和路径
# 临时目录
tmpdir=$(mktemp -d) # /tmp/tmp.XXXXXX 目录
# ===== ❌ 不安全的做法 =====
# 可预测的文件名容易被攻击者利用(Symlink 攻击)
tmpfile="/tmp/my_script_$$.tmp" # $$ = PID——可预测!
# 攻击者可以先创建 /tmp/my_script_12345.tmp 的软链接,指向 /etc/shadow
# ===== 安全模板 =====
safe_temp_usage() {
local tmpdir=""
local tmpfile=""
# 创建临时目录
tmpdir=$(mktemp -d)
# 自动清理——无论脚本如何退出
trap 'rm -rf "$tmpdir"' EXIT
tmpfile="$tmpdir/data.txt"
# 安全创建(设置严格权限)
# mktemp 默认创建 600 权限(只有 owner 可读写)
install -m 600 /dev/null "$tmpfile"
# 使用临时文件...
echo "sensitive data" > "$tmpfile"
# trap 会在退出时自动删除 $tmpdir
}
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
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
# 12.1.4 敏感信息保护——密码/密钥/Token
#!/bin/bash
# ===== 原则:不在命令行中暴露密码 =====
# ❌ 密码在 ps 输出中可见
mysql -u root -p"my_password" mydb # ps aux 能看到!
# ✅ 方式 1:配置文件(推荐)
cat > ~/.my.cnf << 'EOF' && chmod 600 ~/.my.cnf
[client]
user=root
password=my_password
EOF
mysql --defaults-file=~/.my.cnf mydb
# ✅ 方式 2:环境变量
export MYSQL_PWD="my_password"
mysql -u root mydb # 命令行不写密码
# ⚠️ 注意:环境变量在 /proc/<pid>/environ 中可能被同用户看到
# ✅ 方式 3:从 stdin 管道
echo "my_password" | mysql -u root -p mydb
# 或
mysql -u root -p < <(echo "my_password")
# ✅ 方式 4:从加密文件读取(生产推荐)
# 先用 gpg 加密密码,脚本中解密后使用
PASSWORD=$(gpg --decrypt /secure/db_pass.gpg 2>/dev/null)
# ===== API Token 保护 =====
# ❌ 硬编码
TOKEN="sk-abc123def456"
# ✅ 环境变量
TOKEN="${API_TOKEN:-}" # 从环境变量读取
if [[ -z "$TOKEN" ]]; then
die "请设置环境变量 API_TOKEN"
fi
# ===== 脚本自身不包含密钥 =====
# ✅ 从 Vault / AWS Secrets Manager / HashiCorp Vault 获取
secret=$(vault kv get -field=value secret/myapp/db 2>/dev/null)
# ===== 清理敏感信息 =====
# 脚本退出时从内存中清除
cleanup() {
unset PASSWORD TOKEN API_KEY # unset 变量
# 可选:shred 包含密码的临时文件
shred -u "$PASSWORD_FILE" 2>/dev/null
}
trap cleanup EXIT
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
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
# 12.1.5 权限最小化——umask / chmod / 降权
#!/bin/bash
# ===== 设置安全的 umask =====
# umask 决定新建文件的默认权限
# 777 - umask = 实际权限
umask 027 # 新文件: 750 (owner=rwx group=rx other=---)
# 推荐服务器脚本: umask 027 或 umask 077
# ===== 文件创建后显式设权限 =====
# 原则:能用 600 不用 644
touch /etc/myapp/config.conf
chmod 600 /etc/myapp/config.conf # 只有 owner 可读写
chown root:root /etc/myapp/config.conf
# ===== 安全检查 =====
check_insecure_permissions() {
echo "检查不安全权限..."
# 检查 .ssh 目录和 authorized_keys
find /home -maxdepth 2 -name ".ssh" -not -perm 700 -exec ls -ld {} \;
find /home -maxdepth 3 -name "authorized_keys" -not -perm 600 -exec ls -l {} \;
# 检查其他用户可写的脚本目录
find /usr/local/bin /opt -type f -perm /o+w
}
# ===== 降权运行:脚本不要全程 root =====
# ❌ 整个脚本 root 运行
setup_web_app() {
apt install nginx
mkdir -p /var/www/html
# ... 全部操作 ..
}
# ✅ 管理员部分 root,应用部分降权
setup_web_app() {
# ---- root 部分 ----
apt install nginx
adduser --system www-data
# ---- 降权到 www-data 执行 ----
sudo -u www-data bash << 'APP'
mkdir -p /var/www/html
cp /tmp/deploy/* /var/www/html/
APP
}
# 或需要 root 临时提权:sudo 只授权必要命令
# 在 sudoers 中:
# appuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
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
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
# 12.1.6 输入校验——不可信原则
#!/bin/bash
# ===== 所有外部输入都不可信 =====
# IP 地址校验
validate_ip() {
local ip="$1"
local pattern='^([0-9]{1,3}\.){3}[0-9]{1,3}$'
if [[ ! "$ip" =~ $pattern ]]; then
die "无效的 IP 地址: $ip"
fi
# 每个字节还须 <= 255
IFS='.' read -ra octets <<< "$ip"
for oct in "${octets[@]}"; do
if (( oct > 255 )); then
die "无效的 IP 地址: $ip"
fi
done
}
# 端口号校验
validate_port() {
local port="$1"
if [[ ! "$port" =~ ^[0-9]+$ ]] || (( port < 1 || port > 65535 )); then
die "无效的端口号: $port"
fi
}
# 文件名校验(白名单)
validate_filename() {
local name="$1"
# 只允许字母、数字、下划线、连字符、点
if [[ ! "$name" =~ ^[a-zA-Z0-9._-]+$ ]]; then
die "无效的文件名: $name"
fi
}
# ===== 组合校验:多层防御 =====
# 1. 类型检查(数字/字符串)
# 2. 范围检查(长度/大小)
# 3. 格式检查(正则/白名单)
# 4. 业务规则检查
validate_email() {
local email="$1"
# 1. 长度
[[ ${#email} -le 254 ]] || return 1
# 2. 格式
[[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] || return 1
# 3. 域名部分
local domain="${email#*@}"
host "$domain" > /dev/null 2>&1 || return 1
return 0
}
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
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
# 12.1.7 Shell 安全最佳实践清单
#!/bin/bash
# ===== 每条脚本应遵循的安全检查清单 =====
# ☐ 1. 开头设置
set -euo pipefail
IFS=$' \t\n'
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
# ☐ 2. 不硬编码密码/密钥
# 使用环境变量 ~/.my.cnf 或 Vault
# ☐ 3. 使用 mktemp 创建临时文件
# 配合 trap 自动清理
# ☐ 4. 所有外部输入加校验
# 包括 $1/$2/用户输入/文件内容/环境变量
# ☐ 5. 变量引用加双引号
# 除非明确需要分词
# ☐ 6. 用 -- 结束选项解析
# rm -- "$file" / grep -- "$pattern"
# ☐ 7. 限制文件权限
# umask 027 / chmod 600 关键文件
# ☐ 8. 最小权限原则
# 不需要 root 时降权执行
# ☐ 9. 不执行拼接的命令字符串
# 禁止 eval / 禁止动态拼接 SQL/命令
# ☐ 10. 运行 shellcheck
# shellcheck script.sh
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
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
# 12.2 兼容处理
# 12.2.1 bash vs sh——你用的是什么 Shell
#!/bin/bash
# ===== Shebang 决定用哪个解释器 =====
#!/bin/sh # POSIX Shell(可能是 dash/ash/bash--posix)
#!/bin/bash # Bash(本系列教程使用)
#!/usr/bin/env bash # 从 PATH 中找 bash(更可移植)
# ===== 检查当前 Shell =====
echo "$SHELL" # 默认 Shell(用户登录用的)
echo "$BASH_VERSION" # bash 版本(sh 下为空)
ps -p $$ # 当前进程名
# ===== bash 独有特性——在 sh 下运行会报错 =====
# 1. 数组
arr=("a" "b" "c") # sh 不支持
# 2. [[ ]] 条件测试
[[ -f "$file" && -x "$file" ]] # sh 只能用 [ ]
# 3. 进程替换
diff <(sort a.txt) <(sort b.txt) # sh 不支持
# 4. here-string
tr 'a-z' 'A-Z' <<< "hello" # sh 不支持
# 5. {start..end} 范围展开
echo {1..10} # sh 需要 $(seq 1 10)
# 6. 函数局部变量
local var="hello" # sh 不支持 local
# ===== 检测脚本是否在 bash 下运行 =====
if [[ -z "$BASH_VERSION" ]]; then
echo "错误: 此脚本需要 bash 运行" >&2
exit 1
fi
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
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
# 12.2.2 macOS vs Linux——BSD vs GNU 差异大全
这是跨平台 Shell 脚本最让人头疼的问题——同一命令在 macOS 和 Linux 上行为不同:
#!/bin/bash
# ===== 差异 1: sed =====
# macOS (BSD sed)
sed -i '' 's/old/new/g' file.txt # -i 必须带参数(可以是空)
# Linux (GNU sed)
sed -i 's/old/new/g' file.txt # -i 不需要参数
# ✅ 兼容写法
if [[ "$(uname)" == "Darwin" ]]; then
sed -i '' 's/old/new/g' file.txt
else
sed -i 's/old/new/g' file.txt
fi
# ===== 差异 2: 命令选项 =====
# grep -P (Perl 正则)
# macOS:默认不支持 -P → 需要 brew install grep (ggrep)
# Linux:支持 -P
# stat 查看文件信息
# macOS: stat -f "%z %N" file.txt # -f 指定格式
# Linux: stat -c "%s %n" file.txt # -c 指定格式
# date 计算日期
# macOS: date -v -1d +%Y%m%d # -v 调整
# Linux: date -d "yesterday" +%Y%m%d # -d 解析
# ===== 差异 3: 内置工具路径 =====
# /bin/bash 位置
# macOS: /bin/bash (是 bash 3.x)
# Linux: /bin/bash (通常是 bash 4.x/5.x)
# ✅ 用 env 查找
#!/usr/bin/env bash
# ===== 兼容检测函数 =====
is_macos() { [[ "$(uname)" == "Darwin" ]]; }
is_linux() { [[ "$(uname)" == "Linux" ]]; }
# macOS 缺少 GNU 工具时自动安装提示
require_gnu_tool() {
local tool="$1"
local brew_pkg="${2:-$tool}"
if is_macos; then
if ! command -v "$tool" &>/dev/null; then
echo "macOS 缺少 $tool,请安装: brew install $brew_pkg"
return 1
fi
fi
}
# ===== 通用跨平台函数 =====
# 获取文件大小
get_file_size() {
local file="$1"
if is_macos; then
stat -f%z "$file"
else
stat -c%s "$file"
fi
}
# 获取昨天日期
get_yesterday() {
if is_macos; then
date -v-1d +%Y-%m-%d
else
date -d "yesterday" +%Y-%m-%d
fi
}
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
62
63
64
65
66
67
68
69
70
71
72
73
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
62
63
64
65
66
67
68
69
70
71
72
73
# 12.2.3 POSIX 兼容写法——跨平台脚本
如果脚本需要在 sh/dash/ash/bash 等不同 Shell 上运行,遵循 POSIX 标准:
#!/bin/sh
# ===== POSIX 替代方案对照表 =====
# Bash 写法 POSIX 写法
# [[ ]] [ ]
# == (in [[ ]]) =
# function name() {} name() {}
# local var (不用 local,或用子 shell)
# &> /dev/null >/dev/null 2>&1
# source file . file
# ((i++)) i=$((i+1))
# ${arr[@]} (POSIX 无数组,用位置参数)
# {1..10} $(seq 1 10)
# <(cmd) 用临时文件
# ===== 示例:POSIX 兼容的字符串处理 =====
# 拿到文件扩展名
ext=${filename##*.} # POSIX 和 Bash 都支持
# 拿到文件名(去扩展名)
base=${filename%.*} # POSIX 和 Bash 都支持
# ===== 注意:POSIX 也支持以下特性 =====
# 1. $(command) 命令替换(非反引号)
# 2. ${var:-default} 默认值
# 3. ${var#pattern} 模式匹配
# 4. $((expr)) 算术运算
# 5. case 语句
# 6. here-document (cat << EOF)
# ===== POSIX 兼容的日志函数(不用 local)=====
_log() {
_level="$1"; shift
echo "[$(date +%H:%M:%S)] $_level: $*"
}
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
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
# 12.2.4 版本检测与条件加载
#!/bin/bash
# ===== 检测 bash 版本 =====
check_bash_version() {
local required_major="${1:-4}"
if (( BASH_VERSINFO[0] < required_major )); then
echo "需要 bash >= ${required_major}.x,当前: $BASH_VERSION" >&2
exit 1
fi
}
# check_bash_version 4
# ===== 检测命令是否存在 =====
require_cmd() {
for cmd in "$@"; do
if ! command -v "$cmd" &>/dev/null; then
echo "错误: 缺少命令 $cmd,请安装后重试" >&2
exit 1
fi
done
}
# require_cmd jq curl docker
# ===== 检查命令支持特定选项 =====
check_cmd_option() {
local cmd="$1" opt="$2"
case "$cmd" in
grep)
grep "$opt" /dev/null 2>/dev/null
;;
sed)
sed "$opt" /dev/null 2>/dev/null
;;
esac
}
# ===== 按操作系统加载不同实现 =====
if is_macos; then
OPEN_CMD="open"
PKG_MANAGER="brew"
elif is_linux; then
OPEN_CMD="xdg-open"
if command -v apt &>/dev/null; then
PKG_MANAGER="apt"
elif command -v yum &>/dev/null; then
PKG_MANAGER="yum"
fi
fi
# 安装依赖的跨平台函数
install_dep() {
local pkg="$1"
if [[ "$PKG_MANAGER" == "brew" ]]; then
brew install "$pkg"
elif [[ "$PKG_MANAGER" == "apt" ]]; then
sudo apt-get install -y "$pkg"
elif [[ "$PKG_MANAGER" == "yum" ]]; then
sudo yum install -y "$pkg"
fi
}
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
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
# 12.2.5 大小写敏感与换行符陷阱
#!/bin/bash
# ===== 换行符问题 (CRLF vs LF) =====
# Windows 文件用 CRLF (\r\n),Linux/macOS 用 LF (\n)
# 在 Linux 上运行 CRLF 脚本会报:
# /bin/bash^M: bad interpreter: No such file or directory
# 检测:
file script.sh | grep "CRLF"
cat -A script.sh | grep '\^M'
# 修复:
sed -i 's/\r$//' script.sh # Linux
# 或:
dos2unix script.sh # 需安装
# ===== 文件名大小写 =====
# macOS 文件系统默认大小写不敏感(但保留大小写)
# Linux 文件系统大小写敏感
# ReadMe.md 和 README.md 在 macOS 是同一个文件,Linux 是两个文件
# 安全做法:统一用小写
mv ReadMe.md readme.md
mv MyScript.sh myscript.sh
# ===== 路径分隔符 =====
# 不要硬编码 / 为路径分隔符
# ❌ DIR="/opt/myapp/logs"
# ✅ 考虑 Cygwin/WSL 时用变量
SEP="/" # Unix 统一使用 /
# ===== 检查脚本运行环境 =====
cat > /tmp/env_check.sh << 'SCRIPT'
#!/bin/bash
echo "===== 运行环境诊断 ====="
echo "OS: $(uname -s)"
echo "Arch: $(uname -m)"
echo "Shell: $BASH_VERSION"
echo "Bash路径: $(command -v bash)"
echo "PWD: $PWD"
echo "PATH: $PATH"
echo "Home: $HOME"
SCRIPT
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
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
# 12.3 思考题
命令注入审计:review 以下代码片段中的安全风险并修复:
user=$1 ssh "$user@host" "cd /home/$user && ls -la" rm -rf /tmp/$user/*1
2
3跨平台备份脚本:写一个兼容 macOS 和 Linux 的目录备份脚本,要求正确使用
stat/sed/date,不依赖任何平台特定选项。敏感信息保护:设计一个方案,让 Shell 脚本在执行过程中使用临时 API Token,脚本结束后确保 Token 在内存和磁盘上都不可恢复(提示:trap + unset + shred)。
PATH 安全验证:写一个
check_script_security.sh,检查脚本中的以下安全隐患:- 硬编码的密码
- 使用
eval - 变量的命令拼接
/tmp 固定路径临时文件
- 未引号的变量引用
POSIX 改造:将一个使用了
[[ ]]、数组、local、&>的 bash 脚本改写成 POSIX sh 兼容版本,并验证在dash下能正常运行。
上次更新: 2026/06/17, 12:47:39