jsp 如何动态给图片赋值_文件上传漏洞:getshell的最好方式,我们如何防御?

1000000){"],[20,"\n","24:\"gtdQ\"|36:79"],[20," throw new Exception(\"图片太大,请重新上传!\");"],[20,"\n","24:\"fzaA\"|36:79"],[20,"}"],[20,"\n","24:\"gtdQ\"|36:79"],[20,"这样,我们既防止了攻击者非法上传攻击脚本,还保证了服务器带宽和磁盘资源不被非法占用。","27:\"13\""],[20,"\n","24:\"xOpD\""],[20,"\n","24:\"LHZe\""],[20,"上述以图片为例讲解了如果防御或修复文件上传漏洞,在实际场景中,可能还有其他的一些文件上传,比如excel、word、txt等文件,这些文件的的处理方法同图片上传方法一致,均按照文件后缀、格式、内容、大小等多维度来判断,就不会导致文件上传漏洞。","27:\"13\""],[20,"\n","24:\"DWqn\""],[20,"\n","24:\"l2i0\""],[20,"这里,我需要着重提一个应用场景,比如在 CMS 系统中,有这样一个功能:后端可以动态新增 jsp 页面,这时避免不了 jsp 动态页面的上传,我们既要允许客户端上传 jsp 文件,又要防止非法的 jsp 文件被上传。这时,就需要对 jsp 文件内容进行判断,校验其是否存在特征代码。","27:\"13\""],[20,"\n","24:\"L8XV\""],[20,"\n","24:\"TyV3\""],[20,"攻击脚本的代码,一般都会通过内置函数调用服务器的相关命令,以达到控制服务器的目的,如 java 语言可以通过 ","27:\"13\""],[20,"Runtime.getRuntime().exec(cmd) 来执行命令。那么,我们就可以校验 jsp 文件是否包含这类关键词,如下列代码:","0:\"%23404040\"|27:\"13\"|31:2"],[20,"\n","24:\"xYG9\""],[20,"StringBuilder content = StringBuilder();"],[20,"\n","24:\"FlCb\"|36:79"],[20,"BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(),\"UTF-8\"));"],[20,"\n","24:\"bCNI\"|36:79"],[20,"String line = null;"],[20,"\n","24:\"U78b\"|36:79"],[20,"while(null != (line = reader.readLine())){"],[20,"\n","24:\"09eg\"|36:79"],[20," content.append(line);"],[20,"\n","24:\"eErb\"|36:79"],[20,"}"],[20,"\n","24:\"09eg\"|36:79"],[20,"reader.close();"],[20,"\n","24:\"4T4t\"|36:79"],[20,"if(content.toString().contains(\"exec\") || content.toString().contains(\"Runtime\")){"],[20,"\n","24:\"CbKG\"|36:79"],[20," throw new Exception(\"当前文件不合法!\");"],[20,"\n","24:\"Lfz6\"|36:79"],[20,"}"],[20,"\n","24:\"CbKG\"|36:79"],[20,"所谓上有政策,下有对策,有经验的攻击者不会明目张胆的编写带有明显特征码的攻击脚本,这就是我们常说的","27:\"13\""],[20,"免杀脚本。","27:\"13\"|8:1"],[20,"免杀脚本,通过后端代码不容易被查出来,针对这种情况,我的建议是:后端尽可能不要提供直接上传 jsp 等动态页面的入口,如果不可避免,将其上传到另外的可以执行 jsp 文件的服务器上,使其与主服务器独立开来,防止主服务器被攻击,同时再备份一份 jsp 文件。","27:\"13\""]]">

我相信,你在开发Web应用时,后端一定会提供文件的上传功能,比如前端页面肯定有图片的展示,后端必定会提供图片的上传入口。但是,你在做文件上传功能时,是否考虑过它的安全性问题呢?

请看下面的代码:

@PostMapping("upload")
public String upload(@RequestParam("file")MultipartFile file,HttpServletRequest request)throws Exception{
String filename = file.getOriginalFilename();
//上传文件到服务器
String url = uploadService.upload(filename);
return url;
}

上述代码存在非常严重的安全漏洞,可以看到,该代码没有对文件做任何的限制,只要传入的是文件流,它就能接收它,并且将其上传到服务器。

假设你的服务器运行的是 Tomcat 容器,那么它能执行后缀为 .jsp 的文件,攻击者就可以上传 .jsp 文件,并且文件内容为可执行的 java 代码。当 .jsp 文件被上传上去后,攻击就可以利用菜刀、蚁剑等工具连接该 .jsp 文件,连接成功后,攻击者就可以控制你的服务器,俗称 getshell。如图所示:

       8e21b500bb4a149aa470585c323cc0f6.png      

我用蚁剑成功连接上目标服务器,并且可以对服务器上的文件做删除修改操作,如果你的服务器还存在漏洞(如缓冲区溢出漏洞)的话,攻击者还可以提权,以获取更高的操作权限,可以说如果你的应用存在文件上传漏洞的话,威胁是相当大的。

那么,我们该如何防御呢?

通过前面的演示,要修复上述文件上传漏洞,最关键的就是不能让攻击上传带有木马特性代码的可执行文件。

我们假设该上传功能,只允许上传图片格式的文件。那么,第一步就是要判断文件类型,判断文件类型的方式主要有两种:

  1. 根据HTTP请求头的“Content-Type”来判断。

  2. 截断文件名,判断文件后缀名。

我们先来说第一种方式,“Content-Type”记录的就是当前请求内容的类型,它有固定的值,如果是图片类型一般以“image/**”来表示,其中“**”为图片格式,如:image/png、image/jpeg等。

那么,判断文件类型为图片格式就可以使用下列代码:

String contentType = request.getHeader("Content-Type");
if(!"image/png".equals(contentType) || !"image/jpeg".equals(contentType) || !"image/gif".equals(contentType)){
throw new Exception("图片格式不正确!");
}

上述代码的安全性不够,还会存在问题,因为“Content-Type”是客户端传给服务端的,攻击者可以捕获HTTP请求,手动修改“Content-Type”的值为image/jpeg,但是文件后缀依然是 .jsp,也可以骗过服务端上传到服务器。

因此,一般采用第二种方式更为保险。其代码如下:

String filename = file.getOriginalFilename();
String suffix = filename.substring(file.lastIndexOf('.'));
if(!".jpg".equals(suffix) || !".jpeg".equals(suffix)|| !"png".equals(suffix) || !"gif".equals(suffix)){
throw new Exception("图片格式不正确!");
}

仅判断文件后缀,只是提高了攻击的门槛,但是对于有经验的攻击者来说,同样有方法可以绕过,攻击者可以采用截断的方式来绕过,例如将攻击脚本命名为:shell.jsp%00.jpg,在这个命名中多了一个“%00”字符,该字符属于截断字符,当上传到服务器后,服务器发现“%00”字符后,它会截断后面的内容,从而上传到服务器后,文件名变成了 shell.jsp。

因此,我们还应修改上述代码,使之变得不可绕过。有一个比较好的方法就是强制改变上传到服务器的文件名,并且后面带上截取的文件后缀名。

String newFilename = UUID.randomUUID().toString() + suffix;
//上传文件到服务器
String url = uploadService.upload(newFilename);
return url;

上述代码大大地提高了文件上传的安全性,但也不是绝对的安全,它存在一个问题:如果上传后的文件是放到可执行脚本的容器下面,则攻击者可以将攻击脚本隐藏到图片中,使图片可以骗过容器变成可执行文件,并实施攻击。

要彻底修复文件上传漏洞,你可以采取下面两个方法:

  1. 有条件的话,你可以将文件上传到专门的文件服务器,该文件服务器只存放文件,不启动任何容器,这样一来,访问文件服务器的任何文件,它都会按照普通文本/二进制文件来处理。

  2. 如果只能放到 Tomcat 等容器下,需要判断图片内容,不同的图片,它的二进制流内容是有严格规定的,如果图片的二进制按照这种严格的规定输入的话,则 Tomcat 只能按照图片方式来处理,不会执行里面的任何攻击脚本。

上述的第2种方法涉及到对图片二进制的理解,我们不需要深入研究,只要知道图片二进制的文件头是如何定义的就行了。

用 WinHex 分别打开 JPG/JPEG、PNG、GIF格式的图片,如图所示:

       5bcd13620145d9066dd50a7807ff1276.png      

       328861b32aef8bb3e9641345775ecbbc.png      

       f76a25c11435f5bbd5efac3ff14d6767.png      

文件头都是在二进制的最开始定义的,JPG/JPEG 的文件头标识为:FFD8FFE0,PNG 的文件头标识为:89504E47,GIF 的文件头标识为:47494638。因此,在判断文件类型时,只需要读取文件流并转成字节数组,判断所取得的字节数组前几位是否为图片的文件头即可。示例代码如下:

InputStream inputStream  = file.getInputStream();
int len = -1;
StringBuilder builder = new StringBuilder(4);
for(int i = 0;i < 4 && -1 != (len = inputStream.read());i++){
builder.append(Integer.toHexString(len));
}
if(!"ffd8ffe0".equals(builder.toString()) || !"89504e47".equals(builder.toString()) || !"47494638".equals(builder.toString())){
throw new Exception("文件类型必须是JPG、PNG或GIF");
}
inputStream.close();

至此,我们可以完全防止攻击者上传攻击脚本。但是,上述代码并不完美,虽然可以防止攻击者上传攻击脚本,但是我们并没有限制文件大小,如果攻击者上传足够大的文件,比如100G,会导致服务器带宽始终被攻击者占用,如果攻击者采取分布式上传策略,很可能导致DDoS(分布式拒绝服务)攻击或者磁盘被攻击者上传的文件占满。因此,我们还应对文件大小做限制,如:

int size = file.getSize();
if(size > 1000000){
throw new Exception("图片太大,请重新上传!");
}

这样,我们既防止了攻击者非法上传攻击脚本,还保证了服务器带宽和磁盘资源不被非法占用。

上述以图片为例讲解了如果防御或修复文件上传漏洞,在实际场景中,可能还有其他的一些文件上传,比如excel、word、txt等文件,这些文件的的处理方法同图片上传方法一致,均按照文件后缀、格式、内容、大小等多维度来判断,就不会导致文件上传漏洞。

这里,我需要着重提一个应用场景,比如在 CMS 系统中,有这样一个功能:后端可以动态新增 jsp 页面,这时避免不了 jsp 动态页面的上传,我们既要允许客户端上传 jsp 文件,又要防止非法的 jsp 文件被上传。这时,就需要对 jsp 文件内容进行判断,校验其是否存在特征代码。

攻击脚本的代码,一般都会通过内置函数调用服务器的相关命令,以达到控制服务器的目的,如 java 语言可以通过 Runtime.getRuntime().exec(cmd) 来执行命令。那么,我们就可以校验 jsp 文件是否包含这类关键词,如下列代码:

StringBuilder content = StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(),"UTF-8"));
String line = null;
while(null != (line = reader.readLine())){
content.append(line);
}
reader.close();
if(content.toString().contains("exec") || content.toString().contains("Runtime")){
throw new Exception("当前文件不合法!");
}

所谓上有政策,下有对策,有经验的攻击者不会明目张胆的编写带有明显特征码的攻击脚本,这就是我们常说的免杀脚本。免杀脚本,通过后端代码不容易被查出来,针对这种情况,我的建议是:后端尽可能不要提供直接上传 jsp 等动态页面的入口,如果不可避免,将其上传到另外的可以执行 jsp 文件的服务器上,使其与主服务器独立开来,防止主服务器被攻击,同时再备份一份 jsp 文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值