Linux重定向符绕过
基本概念
关于重新定向符详细解释可参考该篇文章:Linux重定向
简单来说,重新定向符的作用为:
分为覆盖写入以及追加写入两种写入方式,分别用两种符号表示:
<
,<<
尖的头头代表输出/输入的对象,判断输出输入流,另一侧,符号的”嘴部”后的数据,
我们可以判断该数据/指令是用于输出指令还是用于对一个对象进行输入的准备材料。
通过这样的判断,我们可以来区分该重定向是输入还是输出。具体实例可以看我放的博客传送门。
关于错误重定向:
典型代码有:1
22>&1
2>>&1从上到下分别是覆写和追加标准错误输出到标准输出,这里都是在对输出流进行操作。
关于为什么有时不写分号:
1
ls -a >/dev/null 2>&1
我们需要明确,在这行代码中,
2>&1
也是命令的一部分,和前面的ls -a
属于一部分,所以中间不需要分号分割。
通俗介绍重定向符
通俗解释
在 Linux 等系统中,每个进程默认会有三个数据流:标准输入(stdin,文件描述符为 0)、标准输出(stdout,文件描述符为 1)和标准错误输出(stderr,文件描述符为 2)。正常情况下,标准输入一般是键盘,标准输出和标准错误输出默认是终端屏幕。重定向符的作用就是改变这些数据流的目标位置。目标位置!
所以对于命令:1
ls -a >/dev/null 1>&2
这里
ls
本身的输出终端是屏幕,由于>/dev/null
的重新定向,其输出位置变为了/dev/null
,&2
的输出位置默认为终端屏幕,将输出位置已经变为/dev/null
的ls -a
命令的标准输出流定向到前端,进行输出,ls -a
输出还是空白,另外,标准错误输出流还是正常输出于前端。
关于&
符
&
在重定向符中有两种核心作用:
(1) 引用文件描述符(File Descriptor)
语法:
文件描述符>&目标
作用:将某个文件描述符的输出重定向到另一个文件描述符。即引用0,1,2。
1
2command 1>&2 # 将标准输出(1)重定向到标准错误(2)
command 2>&1 # 将标准错误(2)重定向到标准输出(1)1>
等价于>
(标准输出),2>
是标准错误。&
在此表示“引用”文件描述符,而非文件。
(2) 合并重定向符(&>
或 >&
)
语法:
&>file
或>&file
作用:将标准输出(
1
)和标准错误(2
)同时重定向到文件。1
2command &> output.log # 标准输出和错误都写入 output.log
command >& output.log # 同上(两种写法等效)
基础介绍小结
在 Linux 的 Shell 命令中,重定向符本质上是为同一个命令服务的,它们可以自由组合且无需分号,通过灵活操作文件描述符(
0
、1
、2
分别对应标准输入、标准输出、标准错误),实现数据流的精准控制。比如:
分号;
仅用于分隔多个独立命令,与重新定向符无关:1
2
3
4# 分号分隔两个命令,每个命令独立使用重定向
command1 > out1.log; command2 2> err2.log
# 分号前后是独立作用域,重定向符互不影响对于同一个
command
,我们可以使用多个重新定向符和文件描述符来进行I/O流的控制。
以web42为例
如下图:
该题中有源码:
1
>/dev/null 2>&1
这段代码的含义就是将指令写入
/dev/null
路径之下,并且将标准错误输出流重定向到标准输出流。
相关原理
“空设备符”
在 Linux 中,/dev/null 是一个特殊的设备文件,常被称为 “空设备” 。具有以下特性:
- 写入丢弃:任何写入到 /dev/null 的数据都会被立即丢弃,不会进行存储,就如同数据进入了黑洞。比如执行命令
echo "test" > /dev/null
,“test” 这个字符串不会被保存到任何地方 。 - 读取为空:尝试从 /dev/null 读取数据时,会立即返回文件结束符(EOF) ,意味着无法从中读取到实际数据。
意义何在?
- 首先我们需要明确:
比如我们使用这样的命令:ls /some/dictionary/ > /dev/null
,虽然该命令输出的结果被丢弃在空设备符中,但是这个操作是确切被落实了的,只是它的结果没有被显示在前端罢了。 - 其作用:
- 屏蔽无关输出:当你只关心命令是否成功执行,而不关心其正常输出内容时,可以将输出重定向到
/dev/null
来屏蔽大量无关的信息,使终端输出更加简洁,便于查看其他重要信息。例如,在一些脚本中,可能会执行多个命令,有些命令的输出只是中间过程信息,对最终结果没有影响,将这些输出重定向到/dev/null
可以避免它们干扰视线。 - 防止输出影响后续操作:如果后续的命令或操作依赖于当前命令的返回状态而不是输出内容,那么将当前命令的输出重定向到
/dev/null
可以确保不会因为输出内容而产生意外的影响。比如,在一个自动化脚本中,需要先执行一个可能会产生大量输出的命令来初始化一些环境设置,然后再执行其他关键命令,为了防止初始化命令的输出干扰后续命令的执行或解析,就可以将其输出重定向到/dev/null
。 - 安全和隐私考虑:在某些情况下,命令的输出可能包含敏感信息,将其重定向到
/dev/null
可以避免这些信息在终端上显示出来,从而提高安全性和保护隐私。
- 屏蔽无关输出:当你只关心命令是否成功执行,而不关心其正常输出内容时,可以将输出重定向到
关于重定向于标准输出流
- 这里代码写作:
>/dev/null 2>&1
,在>/dev/null
这段代码中明确把还未写上的输出流指令重新定向到输入流,即/dev/null
,所以可以明确标准输出就是被定义到了/dev/null
,那么在后面的/dev/null
就是该题中默认的输出流。 - 所以该题代码目的是将标准错误输出的数据流重新定向到标准输出流,即
/dev/null
,”空设备符”。
绕过思路
在源代码中有以下语句:
1
system($c." >/dev/null 2>&1")
该语句将
GET
请求的值与重新定向语句进行拼接,上文已经解析了该段重新定向命令的作用,即将重新定向的命令输入到空设备符中,所以,我们需要注意,不可以直接传入指令值,需要用一个多余的指令将后方的重新定向指令给绕过。我们传入payload:
?/c=ls;ls
,第一个ls
被成功执行,而第二个ls
则用于执行后方重新定向指令,绕过后方重新定向指令。疑问:为什么不可以采用
/?c="ls;ls"
的payload。
在url
中传入/?c=ls;ls
,传入程序中可以被php自动处理解析为字符串。
PHP 自动处理参数值为字符串 :$_GET['c']
的值已经是字符串类型,无需额外引号包裹1
$c = $_GET['c']; // 若 URL 为 ?c=ls;ls,则 $c = "ls;ls"
在php代码中使用双引号,即如原码:
1
system($c . " >/dev/null 2>&1");
这里使用双引号只是为了界定字符串边界,若
$c='ls;ls'
,则传入system
函数进行拼接后为"ls;ls >/dev/null 2>&1"
,那么我们就可以执行第一个ls
。并且避免重新定向指令的执行。我们可以获得回显:
继续传入payload:
/?c=cat flag.php;ls
获得flag。
有关参数拼接问题
以web43为例
源码如下:
1
2
3
4
5
6
7
8
9
10
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}这里很明显,原题将
;
,即分号绕过了,我们需要一个替代分号的符号:||
但是个人初见该题选择使用如下两种
payload
:?c=ls 2
?c=ls>text.txt 2
想实现在
system
函数拼接后呈现如下效果:ls 2>/dev/null 2>&1
其实并不行,输入这样的paylaod会出现这样的拼接效果:ls 2 >/dev/null 2>&1
,该命令意味着2
为ls
的参数罢了,不会和后面的重定向符进行拼接。
这就会导致,ls
出来的值会被直接输入到空容器中,最后也无法输出任何值到终端。