扛得住恶意文件名的脚本
目标
编写能够承受带空格、换行和以连字符开头的文件名,以及选项和注入字符串的 脚本。把每条规则的错误写法与修正写法并排放置进行验证。
环境
在 /root/args 下工作。材料需要自行创建。
mkdir -p /root/args/data && cd /root/args/data
: > "my report.tmp"
: > "$(printf 'two
lines').txt"
: > "-rf"
: > "normal.txt"
ls -b
第三个文件是重点:**完全可以创建名为 -rf 的文件。**第二个文件还说明,
文件名中甚至可以包含换行——文件名中唯一不能出现的字符只有 NUL。
需要创建的文件
/root/args/split.txt 따옴표 유무에 따른 인자 수
/root/args/nul.txt 줄 단위와 NUL 단위로 센 개수
/root/args/clean.sh 파일 하나를 이름이 무엇이든 안전하게 지운다
/root/args/wrap.sh 받은 인자를 쪼개지 않고 그대로 넘긴다
/root/args/opts.sh getopts 로 옵션을 판다
/root/args/search.sh 입력을 값으로만 쓴다
/root/args/report.md 다섯 규칙 정리
评分方式
第 3~7 步中,评分器会使用带有攻击性的输入直接调用你的脚本。
wrap.sh "a b" "c" "d e" 를 넘겨 3개로 도착하는지
opts.sh -v -o 값 / 붙여 쓴 -vo 값 / 값 빠진 -o / 모르는 -Z
search.sh "alpha; touch /tmp/…" 를 넣고 그 파일이 생기는지
clean.sh "-rf" 라는 이름을 인자로 직접 넘겨 지워지는지
步骤
- 创建四种带有攻击性的文件名。
- 对同一个值分别加引号和不加引号,统计并记录参数数量。
- 分别按行和使用
-print0统计同一个目录的项目数。 两个数字会不同。 - 编写
clean.sh。接收一个文件名参数并删除该文件。文件不存在时, 必须以非 0 退出码结束。 - 编写
wrap.sh。对每个收到的参数逐行输出ARG:<값>。 - 编写
opts.sh。接收-v、-o <값>,并输出剩余参数数量。 输出形式为verbose=0 out= rest=0。 - 编写
search.sh。接收模式和文件并进行搜索。不要使用eval。 - 总结五条规则及其依据。
提示
第 4 步如果使用 find 取出文件名,前面会带上 ./,从而掩盖问题。
所以评分器会把文件名直接作为参数传入。实际环境中也是这种形式最危险——
例如通过 for f in *,或直接传递其他程序给出的名称时。
创建带有攻击性的文件名
创建四种带有攻击性的文件名。
分别创建含空格、含换行、以连字符开头的名称,以及一个普通名称。使用 ls -b 查看时,特殊字符会以转义形式显示。
一个引号就会改变参数数量
对同一个值分别加引号和不加引号,统计并记录参数数量。
在 set -- $f 和 set -- "$f" 后查看 $#,就能得到数字。还要用一行说明原因——shell 会在扩展之后进行分词。
按行统计会出错,用 NUL 统计才准确
分别按行和使用 -print0 统计同一个目录的项目数。
两个数字会不同。
包含换行的文件名按行读取时会被算成两个。find -print0 使用 NUL 分隔——它是文件名中唯一不能出现的字符,因此很安全。
名称以连字符开头时
编写 clean.sh。接收一个文件名参数并删除该文件。文件不存在时,
必须以非 0 退出码结束。
使用 rm -f "$f" 时,如果名称是 -rf,它会被当作选项读取。-- 表示‘从这里开始都是参数’。文件不存在时必须以非 0 退出码结束,因为 rm -f 即使找不到文件也会成功。
原样转发收到的参数
编写 wrap.sh。对每个收到的参数逐行输出 ARG:<값>。
"$@" 会分别保留每个参数,$* 则会把它们合并成一个。不带引号的 $@ 也会再次分词。评分器会传入 "a b" 与 "d e",检查其中的空格是否保留。
不要自行编写选项解析器
编写 opts.sh。接收 -v、-o <값>,并输出剩余参数数量。
输出形式为 verbose=0 out= rest=0。
手写的解析器遇到 -vo 값 这种连写形式时会失效。getopts ":vo:" 开头的冒号会启用静默错误模式,让你自行处理 : 与 \? 分支。最后如果漏掉 shift $((OPTIND - 1)),剩余参数数量就会出错。
输入是值,不是代码
编写 search.sh。接收模式和文件并进行搜索。不要使用 eval。
eval 会把字符串当作 shell 代码执行。评分器会把 alpha; touch <파일> 作为模式传入,检查该文件是否被创建。只阻止注入却让正常搜索失效,同样算失败。
总结五条规则
总结五条规则及其依据。
针对每条规则,用一行写明‘它防止什么’。尤其不能漏掉使用 NUL 的原因——它是唯一不能出现在文件名中的字符——否则 -print0 就只会成为没有依据的惯例。