roaris / ctf-log

0 stars 0 forks source link

WaniCTF 2024 : sh #60

Open roaris opened 4 months ago

roaris commented 4 months ago

https://github.com/wani-hackase/wanictf2024-writeup/tree/main/mis/sh

本番で解けなかった問題

roaris commented 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 ]]を成立させられなくなる

roaris commented 4 months ago

シェルスクリプトの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
roaris commented 4 months ago

次に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 errexitset -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 -uset -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と同じである

roaris commented 4 months ago

によると、

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"
roaris commented 4 months ago

今回の問題では、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}
roaris commented 4 months ago

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は出ていない この問題においては脆弱性となったが、本来は望ましい設定なので、出てこないのは当然だろう