Unsafe Filedownload
概述
文件下载功能在很多web系统上都会出现,一般我们当点击下载链接,便会向后台发送一个下载请求,一般这个请求会包含一个需要下载的文件名称,后台在收到请求后会开始执行下载代码,将该文件名对应的文件response给浏览器,从而完成下载。如果后台在收到请求的文件名后,将其直接拼进下载文件的路径中而不对其进行安全判断的话,则可能会引发不安全的文件下载漏洞。此时如果 攻击者提交的不是一个程序预期的的文件名,而是一个精心构造的路径(比如../../../etc/passwd),则很有可能会直接将该指定的文件下载下来。从而导致后台敏感信息(密码文件、源代码等)被下载。
所以,在设计文件下载功能时,如果下载的目标文件是由前端传进来的,则一定要对传进来的文件进行安全考虑。切记:所有与前端交互的数据都是不安全的,不能掉以轻心!
Unsafe Filedownload
../../../../../../../../../../../../../../etc/passwd //linux
../../../../../../../../../../../../../../Windows/win.ini //windows
抓包修改filename参数的值,即可实现文件下载。
源码
// $file_name="cookie.jpg";
$file_path="download/{$_GET['filename']}";
...
$fp=fopen($file_path,"rb");
$file_size=filesize($file_path);
...
//循环读取文件流,然后返回到浏览器feof确认是否到EOF
while(!feof($fp) && $file_count<$file_size){
$file_con=fread($fp,$buffer);
$file_count+=$buffer;
echo $file_con;
在读取文件路劲时并没有做任何的限制,这就导致可以利用../返回上级目录,从而去下载敏感文件。
Unsafe Fileupload
概述
文件上传功能在web应用系统很常见,比如很多网站注册的时候需要上传头像、上传附件等等。当用户点击上传按钮后,后台会对上传的文件进行判断
比如是否是指定的类型、后缀名、大小等等,然后将其按照设计的格式进行重命名后存储在指定的目录。
如果说后台对上传的文件没有进行任何的安全判断或者判断条件不够严谨,则攻击着可能会上传一些恶意的文件,比如一句话木马,从而导致后台服务器被webshell。所以,在设计文件上传功能时,一定要对传进来的文件进行严格的安全考虑。比如:
- 验证文件类型、后缀名、大小;
- 验证文件的上传方式;
- 对文件进行一定复杂的重命名;
- 不要暴露文件上传后的路径;
- 等等…
client check
删除前端检测函数再上传 checkFileExt(this.value)
创建一个php文件。
成功上传并执行命令。
前端jsp源码
<script>
function checkFileExt(filename)
{
var flag = false; //状态
var arr = ["jpg","png","gif"];
//取出上传文件的扩展名
var index = filename.lastIndexOf(".");
var ext = filename.substr(index+1);
//比较
for(var i=0;i<arr.length;i++)
{
if(ext == arr[i])
{
flag = true; //一旦找到合适的,立即退出循环
break;
}
}
//条件判断
if(!flag)
{
alert("上传的文件不符合要求,请重新选择!");
location.reload(true);
}
}
</script>
MIME type
修改 Content-Type。
Content-Type: image/png
成功上传。
源码
$mime=array('image/jpg','image/jpeg','image/png');//指定MIME类型,这里只是对MIME类型做了判断。
$save_path='uploads';//指定在当前目录建立一个目录
$upload=upload_sick('uploadfile',$mime,$save_path);//调用函数
if($upload['return']){
$html.="<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['new_path']}</p>";
getimagesize
getimagesize()函数会通过读取文件头部的几个字符串(即文件头), 来判断是否为正常图片的头部。
在文件头部添加GIF89a再上传。
源码
$type=array('jpg','jpeg','png');//指定类型
$mime=array('image/jpg','image/jpeg','image/png');
$save_path='uploads'.date('/Y/m/d/');//根据当天日期生成一个文件夹
$upload=upload('uploadfile','512000',$type,$mime,$save_path);//调用函数
if($upload['return']){
$html.="<p class='notice'>文件上传成功</p><p class='notice'>文件保存的路径为:{$upload['save_path']}</p>";
...
function upload($key,$size,$type=array(),$mime=array(),$save_path){
$arr_errors=array(
1=>'上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',
2=>'上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',
3=>'文件只有部分被上传',
4=>'没有文件被上传',
6=>'找不到临时文件夹',
7=>'文件写入失败'
);
Over Permission
概述
如果使用A用户的权限去操作B用户的数据,A的权限小于B的权限,如果能够成功操作,则称之为越权操作。越权漏洞形成的原因是后台使用了 不合理的权限校验规则导致的。
一般越权漏洞容易出现在权限页面(需要登录的页面)增、删、改、查的的地方,当用户对权限页面内的信息进行这些操作时,后台需要对当前用户的权限进行校验,看其是否具备操作的权限,从而给出响应,而如果校验的规则过于简单则容易出现越权漏洞。
因此,在在权限管理中应该遵守:
- 使用最小权限原则对用户进行赋权;
- 使用合理(严格)的权限校验规则;
- 使用后台登录态作为条件进行权限判断,别动不动就瞎用前端传进来的条件;
水平越权
登陆后抓包修改 username 查看不同用户的信息。
源码
//查询数据时没有使用session来校验,而是使用的传进来的值,权限校验出现问题,这里应该跟登录态关系进行绑定
$username=escape($link, $_GET['username']);
$query="select * from member where username='$username'";
$result=execute($link, $query);
if(mysqli_num_rows($result)==1){
$data=mysqli_fetch_assoc($result);
$uname=$data['username'];
$sex=$data['sex'];
$phonenum=$data['phonenum'];
$add=$data['address'];
$email=$data['email'];
垂直越权
pikachu 用户访问用户添加界面 op2_admin_edit.php 直接添加用户
直接添加新用户。
源码
// 判断是否登录,没有登录不能访问
//这里只是验证了登录状态,并没有验证级别,所以存在越权问题。
if(!check_op2_login($link)){
header("location:op2_login.php");
exit();
}
if(isset($_POST['submit'])){
if($_POST['username']!=null && $_POST['password']!=null){//用户名密码必填
$getdata=escape($link, $_POST);//转义
$query="insert into member(username,pw,sex,phonenum,email,address) values('{$getdata['username']}',md5('{$getdata['password']}'),'{$getdata['sex']}','{$getdata['phonenum']}','{$getdata['email']}','{$getdata['address']}')";
$result=execute($link, $query);
if(mysqli_affected_rows($link)==1){//判断是否插入
header("location:op2_admin.php");
../../
概述
在web功能设计中,很多时候我们会要将需要访问的文件定义成变量,从而让前端的功能便的更加灵活。当用户发起一个前端的请求时,便会将请求的这个文件的值(比如文件名称)传递到后台,后台再执行其对应的文件。在这个过程中,如果后台没有对前端传进来的值进行严格的安全考虑,则攻击者可能会通过“../”这样的手段让后台打开或者执行一些其他的文件。从而导致后台服务器上其他目录的文件结果被遍历出来,形成目录遍历漏洞
目录遍历
../../../../../../../../../etc/passwd
源码
if(isset($_GET['title'])){
$filename=$_GET['title'];
//这里直接把传进来的内容进行了require(),造成问题
require "soup/$filename";
敏感信息泄露
概述
由于后台人员的疏忽或者不当的设计,导致不应该被前端用户看到的数据被轻易的访问到。
IcanseeyourABC
前端源码存在测试账户信息。
使用该账号成功登录。
PHP反序列化
概述
**序列化serialize()**:序列化说通俗点就是把一个对象变成可以传输的字符串;
**反序列化unserialize()**:就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用;
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题。
常见的几个魔法函数:
- __construct() 当一个对象创建时被调用
- __destruct() 当一个对象销毁时被调用
- __toString() 当一个对象被当作一个字符串使用
- __sleep() 在对象在被序列化之前运行
- __wakeup 将在序列化之后立即被调用
PHP反序列化漏洞
构造序列化
<?php
class S{
public $test="<script>alert(1)</script>";
}
$s=new S(); //创建一个对象
echo serialize($s); //把这个对象进行序列化
执行php得到序列化字符串。
反序列化执行js。
O:1:"S":1:{s:4:"test";s:25:"<script>alert(1)</script>";}
我们输入的序列化字符串经过处理被输出到当前html页面中。
XXE
概述
XXE -“xml external entity injection”,既”xml外部实体注入漏洞”。概括一下就是”攻击者通过向服务器注入指定的xml实体内容,从而让服务器按照指定的配置进行执行,导致问题”也就是说服务端接收和解析了来自用户端的xml数据,而又没有做严格的安全控制,从而导致xml外部实体注入
现在很多语言里面对应的解析xml的函数默认是禁止解析外部实体内容的,从而也就直接避免了这个漏洞
以PHP为例,在PHP里面解析xml用的是libxml,其在≥2.9.0的版本中,默认是禁止解析xml外部实体内容的。
XXE漏洞
源码
$data = @simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOENT);
if($data){
$html.="<pre>{$data}</pre>";
...
//简单的看一下simplexml_load_string函数
/**
* Interprets a string of XML into an object
* @link https://php.net/manual/en/function.simplexml-load-string.php
* @param string $data <p>
* A well-formed XML string
* </p>
* @param string|null $class_name [optional] <p>
* You may use this optional parameter so that
* <b>simplexml_load_string</b> will return an object of
* the specified class. That class should extend the
* SimpleXMLElement class.
* </p>
* @param int $options [optional] <p>
* Since PHP 5.1.0 and Libxml 2.6.0, you may also use the
* <i>options</i> parameter to specify additional Libxml parameters.
* </p>
* @param string $namespace_or_prefix [optional] <p>
* Namespace prefix or URI.
* </p>
* @param bool $is_prefix [optional] <p>
* <b>TRUE</b> if <i>ns</i> is a prefix, <b>FALSE</b> if it's a URI;
* defaults to <b>FALSE</b>.
* </p>
* @return SimpleXMLElement|false an object of class SimpleXMLElement with
* properties containing the data held within the xml document, or <b>FALSE</b> on failure.
*/
function simplexml_load_string (string $data, ?string $class_name = "SimpleXMLElement", int $options = 0, string $namespace_or_prefix = "", bool $is_prefix = false): SimpleXMLElement|false {}
//大致意思就是处理xml格式的文件数据
构造伪协议读取文件。
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]>
<x>&xxe;</x>
URL重定向
概述
不安全的url跳转问题可能发生在一切执行了url地址跳转的地方。如果后端采用了前端传进来的(可能是用户传参,或者之前预埋在前端页面的url地址)参数作为了跳转的目的地,而又没有做判断的话就可能发生”跳错对象”的问题。
url跳转比较直接的危害是:钓鱼,既攻击者使用漏洞方的域名(比如一个比较出名的公司域名往往会让用户放心的点击)做掩盖,而最终跳转的确实钓鱼网站。
不安全的URL跳转
点击第四行句子发现url变化
修改url值为http://www.baidu.com会进行跳转
源码
$url = $_GET['url'];
//对输入的url没有做外部跳转的过滤或者是黑白名单的限制
if($url == 'i'){
$html.="<p>好的,希望你能坚持做你自己!</p>";
}else {
//跳转到用户指定的url
header("location:{$url}");
SSRF
概述
SSRF(Server-Side Request Forgery:服务器端请求伪造)
其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制。
导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据
数据流:攻击者—–>服务器—->目标地址。
根据后台使用的函数的不同,对应的影响和利用方法又有不一样。
PHP中下面函数的使用不当会导致SSRF:
file_get_contents()
fsockopen()
curl_exec()
如果一定要通过后台服务器远程去对用户指定(“或者预埋在前端的请求”)的地址进行资源请求,则请做好目标地址的过滤。
SSRF(curl)
可以利用FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE以及LDAP等协议。
file:///C:/windows/win.ini
源码
//接收前端URL没问题,但是要做好过滤,如果不做过滤,就会导致SSRF
$URL = $_GET['url'];
$CH = curl_init($URL);
curl_setopt($CH, CURLOPT_HEADER, FALSE);
curl_setopt($CH, CURLOPT_SSL_VERIFYPEER, FALSE);
$RES = curl_exec($CH);
curl_close($CH) ;
//ssrf的问是:前端传进来的url被后台使用curl_exec()进行了请求,然后将请求的结果又返回给了前端。
//除了http/https外,curl还支持一些其他的协议curl --version 可以查看其支持的协议,telnet
//curl支持很多协议,有FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE以及LDAP
SSRF(file_get_content)
./ssrf_info/info1.php
file_get_contents() 函数把整个文件读入一个字符串中。
如果pikachu这台服务器所处的内网还有另一台不想被访问的服务器,那么可以使用该漏洞进行请求另一台服务器的资源,这里就随便读取本地的一个文件。
源码
//读取PHP文件的源码:php://filter/read=convert.base64-encode/resource=xxx
//内网请求:http://x.x.x.x/xx.index
if(isset($_GET['file']) && $_GET['file'] !=null){
$filename = $_GET['file'];
$str = file_get_contents($filename);
echo $str;
微信公众号二维码
扫一扫关注CatalyzeSec公众号
我们一起来从零开始学习网络安全