LabHub
学习 学习路径 课程

Shell 脚本

扛得住恶意文件名的脚本

在 LabHub 中继续学习

目标

编写能够承受带空格、换行和以连字符开头的文件名,以及选项和注入字符串的 脚本。把每条规则的错误写法与修正写法并排放置进行验证。

环境

/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" 라는 이름을 인자로 직접 넘겨 지워지는지

步骤

  1. 创建四种带有攻击性的文件名。
  2. 对同一个值分别加引号和不加引号,统计并记录参数数量
  3. 分别按行和使用 -print0 统计同一个目录的项目数。 两个数字会不同。
  4. 编写 clean.sh。接收一个文件名参数并删除该文件。文件不存在时, 必须以非 0 退出码结束。
  5. 编写 wrap.sh。对每个收到的参数逐行输出 ARG:<값>
  6. 编写 opts.sh。接收 -v-o <값>,并输出剩余参数数量。 输出形式为 verbose=0 out= rest=0
  7. 编写 search.sh。接收模式和文件并进行搜索。不要使用 eval
  8. 总结五条规则及其依据。

提示

第 4 步如果使用 find 取出文件名,前面会带上 ./,从而掩盖问题。 所以评分器会把文件名直接作为参数传入。实际环境中也是这种形式最危险—— 例如通过 for f in *,或直接传递其他程序给出的名称时。

创建带有攻击性的文件名

创建四种带有攻击性的文件名。

分别创建含空格、含换行、以连字符开头的名称,以及一个普通名称。使用 ls -b 查看时,特殊字符会以转义形式显示。

一个引号就会改变参数数量

对同一个值分别加引号和不加引号,统计并记录参数数量

set -- $fset -- "$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 就只会成为没有依据的惯例。