RCE
(远程命令/代码执行)
介绍
英文名就是直接翻译过来的,造成问题的原因就在于,当设计者提供用户使用一定范围内的系统命令的时候没有做完全的限制,使其可以意外地执行其他意想不到的命令,可能会控制服务器。
有一些程序的函数可以让我们执行系统命令,有的可以让我们执行相应的其他内置函数等。
这里可以列举一些,但包括不限于,并且有一些我自己也不太清楚。(php
语言为例)
php
system("ls");
exec("ls",$result);//需要手动输出结果,可以使用var_dump();
shell_exec("ls");//同上,需要手动
passthru("ls");
popen("ls","r");//在PHP中,popen() 函数用于打开一个进程并与其建立一个双向的管道,通常用于执行一个命令并读取命令的输出,r的意思是这个通道只读。之后我们需要使用到fread()、fgets()之类函数进行读取数据,从读取方式也可以看出来函数返回值传递给我们的是一个文件指针。然后记得读取之后一定要关闭流哦。
echo `ls`//听说如果是反引号,里面内容也会被解析成OS命令。
这个写法和echo $(ls)等价,都可以。所以要注意用在处理信任输入的时候。
还有一些执行程序片段的函数:
php
eval("phpinfo();");//eval() 是 PHP 中的一个语言结构,用于执行 PHP 代码字符串。因为它具有执行任意代码的能力,所以非常强大,但与此同时也是非常危险的。错误或恶意地使用 eval() 可能导致严重的安全隐患。
//如果eval里面的代码没有返回值,那它也就没有返回值,返回NULL。
//正因为它不是一个函数,所以不能作为函数名动态执行代码,也就是不可以让一个变量等于这个字符串然后在后面加两个括号那么使用。然后,它必须要字符串内有分号。
php
assert();//这个里面如果是字符串的话会被当成php代码执行,这个我们之前提到过。它的正确用法是在某处设置断言,主要用于调试,在7.0之后版本如果字符串是一个表达式的话会抛出解析错误。如果想要不报错的话可以使用匿名函数,但这样破坏了它的设计目的,对于网安来说无太大所谓倒是。
//表达式是计算并返回单个值的代码片段。它不像语句那样执行命令或指令,而只是评估并产生一个值。因此,你不能在单一的表达式中包含多个独立的语句。
assert(function() { phpinfo(); return true; });
然后就是call_user_func('','');
、call_user_func_array('',array('',''));(后面是一个数组,多少都可以,我这里写了两个)
、array_map('',array())
、pnctl_exec('/bin/ls',array('-al','.'))(这是一个进程控制函数,可以执行指定程序,前面那个需要使用全路径。使用pcntl_exec函数时,当前的PHP进程会被替换为新的程序。也就是说,pcntl_exec后面的代码将不会执行,除非pcntl_exec调用失败。)
。
看一下的介绍:
array_filter()
是 PHP 中的一个函数,用于过滤数组元素。它使用回调函数来确定是否保留数组的某个元素。如果回调函数返回true
,该元素会被包含在返回的数组中;如果回调函数返回false
,该元素则被排除。
那我们就可以利用这个机制,<?php array_filter( array("phpinfo();"), 'assert'); ?>
这样写,就可以让其执行assert回调函数,说实话想出来的人真是聪明。
看到这里不知道大家有没有疑问,我是有疑问的,为什么这里我可以用函数名的字符串来指代那个函数,而我自己写的时候却要去掉呢?我也不了解,于是我问了chatGPT
,以下是一个参考,不代表百分百正确。
在 PHP 中,当你想将一个函数名作为字符串传递给其他函数时(例如作为回调),你可以使用函数名的字符串形式。但是,PHP 也允许直接传递函数的引用,而不需要用字符串形式。
还有一个就是:
php
<?php
$a = preg_replace("/(.+)/e", "phpinfo();", "999");
?>
在php5.5
版本以前,我们可以使用/e
修饰符来让替换的那个字符串当作php代码执行,然后最后一个字符串是要匹配的字符串,新版本以后这个修饰符已经被弃用。以后可以考虑仔细学习一下正则表达式。
接下来我们来说一说Linux的命令,讲一下它的一些命令行输入不同符号的功能。
其实也没有什么特殊的地方,学习过一些虚拟机之类的应该也都用过大多数,而且编程当中某些符号也和这个相类似,我只说比较不同的。
bash
sleep 10 & #这就意味着让前面的命令在后台执行,进行挂起。
echo woshishabi > yeah.txt
cat < yeah.txt
#上面那两个分别是:重定向符号 > 表示左命令结果重定向输出结果到右侧;< 表示的是输入重定向的意思,就是把<后面的文件作为输入
读取文件相关命令介绍
使用的最多的那就是cat
,后面添加要读取的文件就可以了,可以是当前目录,如果也可以写绝对路径之类。
还有一个就是它倒过来写:tac
,文件就是从最后一行开始倒着显示。
还有很多命令,在此不过多解释,可以自己私下尝试或者之后遇到会详细解释:
bash
less
more
head #指定显示文件的前十行
tail
sort #默认情况下,以字母序进行文本排序
nl #将文件添加行号后写入标准输出
uniq #用来检测和删除文本文件当中重复出现的行列。
xxd #转换二进制文件为十六进制文件
rev #翻转读文件
寻找相关特征文件的命令
find <目录> -name <文件名>
、grep -r "<文件内容特征>" ./
(第二个命令是在当前目录和子目录查找引号内相关内容的文件,-r是递归查找的意思)。
绕过
有很多函数可以使用,这里给大家说明一下,scandir()
函数返回值是一个数组,我们要特定值的时候就需要用[]
来选择了。
如果是做题的话,可能会对空格进行一个匹配,如果存在空格就会不通过。这个时候就需要对空格进行改写:
- %09(制表符)
- %0c(换页符)
- /**/注释符
- $IFS$9
- %0d(回车符)
- ${IFS}
- < 重定向符号
- %0a(换行符)
;
- 目录分隔符有时候我们需要使用到,就是这个:
/
,那如果系统没有限制其他的话,通过shell当中参数扩展表达式:${VAR:START:LENGTH}
: 从$VAR
中提取子字符串,开始于位置START
,长度为LENGTH
。这里我们可以写:{PATH:0:1}或者{HOME:0:1}这之类的来代替/
,因为PATH这些变量的开头都是/usr/local/bin:/usr/bin:/bin这种格式,那么第一个就是/
了。
关键字绕过
在shell当中\
会被忽略,也就是cat和ca\t没有区别。flag和fla\g没有区别。
然后如果允许的话,*
和?
可以代替你不知道的文件名内容,比如/bin/cat /flag 我们可以写成 /???/??? /????。虽然这么些有的地方是不认得,但是如果承认,就会把所有符合条件的东西给查出来,根据你的命令输出或者处理。
还有取反绕过:有的时候它对你的传入数据进行十分严格的检测,忽略大小写的检测所有带有敏感信息的字母,这个时候我们可以通过观察它到底把我们的传入数据干什么了,如果当作代码一部分执行了,那么根据程序语言的特性。我们对其进行二次取反的话就会称为原本要传入的信息,根据具体被当成什么代码部分执行进行下一步操作。可能是把传入的信息构造成一个函数来执行,无论如何要注意的是,对常见字母对应的二进制取反会出现一个很大的甚至没有正常字母对应的二进制,这个无法通过复制它来传输,这就需要把它进行url编码来解决这个问题。
提供一个例子:
php
<?php
echo urlencode(~"system");
echo urlencode(~"ls");
//(~%8C%86%8C%8B%9A%92)(~%93%8C) === > (system)(ls)
命令盲注
如果它是属于那种没有回显的形式,那就要试试看命令盲注了。但是这一般都需要大量的尝试才能获取稍微一点点的信息。我们通常使用脚本语言来编写,进行自动执行而不手动执行。
在实战当中,管理人员通过查看当前服务器状态还有日志可以很清楚的看到我们正在做什么,这也就很危险。
脚本代码我目前还不太会写,这其中不像sql
那样,还需要了解shell
脚本语言的规则和表达式才能很好的测试出来。还需要当前用户拥有执行相关命令的权限。
具体代码我就不写出来了,各位可以在网络上找到,一定的,或者自己写。
还有一个做题时候发现的点,就是要知道,当自己的输入被当作命令执行的时候,就相当于我们在写程序一样,利用上源码(如果有)提供的那些变量,然后还有被**``**包裹的内容会被当作系统命令执行。
在shell当中
'<变量>
的形式会将那个变量字符转换成ASCII码数值。