DOS不是那个windows的前身,而是Denial of Service,有作过系统安全方面的小伙伴可能对这个再熟悉不过了,简单点讲,DOS就是服务型响应不过来,从而拒绝了正常的服务请求。html
今天本文不是要讲怎么发起一个DOS攻击,而是讲一下怎么在java的代码层面尽可能减小DOS的可能性。前端
为何会有DOS呢?排除恶意攻击的状况下,DOS的缘由就是资源的使用不当。通常意义上咱们所说的资源有CPU周期,内存,磁盘空间,和文件描述符等。java
若是这些资源受到了恶意使用,那么颇有可能会影响正常的系统服务响应,从而产生DOS。webpack
怎么在编码层面上,解决DOS问题呢?web
若是系统有不合理的资源使用的话,就会形成资源紧缺,从而会产生问题。咱们这里举一些不合理使用资源的例子。正则表达式
SVG (全称是 Scalable Vector Graphics) 是一个跟分辨率无关的图形格式。由于SVG是基于XML的,而且保存着大量的复杂路径信息,因此它的体积通常比较大。咱们在使用的时候要考虑。算法
同时若是使用大量的字体文件也会加剧系统的资源负担。windows
图片是一个文件,文件就可使用二进制来表示,一样的若是咱们把二进制进行base64编码就获得了图片的字符串表示。安全
若是使用过webpack进行前端项目构建的同窗应该知道,对于项目中的小图像,通常是将其编码成为字符串直接嵌套在html中的。可是对于大图片,仍是保存的原来的格式。网络
若是咱们在后台对字符串或者二进制表示的图片进行转换的时候,可能会须要几倍于原image大小的内存。
看一个imageToBase64的例子:
public String imageToBase64() { File f = new File("/tmp/abc.jpg"); try { BufferedImage bi = ImageIO.read(f); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(bi, "jpg", baos); byte[] bytes = baos.toByteArray(); return encoder.encodeBuffer(bytes).trim(); } catch (IOException e) { e.printStackTrace(); } return null; }
为了提高数据传输的效率,不少时候咱们都会使用压缩算法,好比在HTTP中。可是一个压缩过的很小的zip文件,解压以后可能会变得很是很是大。
这里给你们介绍一个很是有名的zip炸弹。
42.zip 是颇有名的zip炸弹。它的大小只有42KB,可是解压以后竟然有4.5PB之多。
怎么作的呢?
一个zip文件中又包含了16个zip文件,每个zip文件又包含了16个zip文件,这样循环5次,产生了16的5次方个文件,每一个文件的大小是4.3GB,最后致使你的硬盘爆炸了。
感兴趣的朋友能够从http://www.unforgettable.dk/4... 下载,本身尝试一下。
怎么避免zip炸弹呢?
第一种作法在解压过程当中检测解压事后的文件大小,若是超出必定的限制就结束解压。
另外一种作法,就是判断压缩文件中是否还有压缩文件,尽可能减小这种压缩套压缩的作法。
billion laughs attack是解析XML文件产生的DOS攻击。
先上代码:
<?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"> <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"> <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"> <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"> <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;"> ]> <lolz>&lol9;</lolz>
上面的代码定义了10个entities,每一个entity又包含了10个前面定义的entity,从而实现了指数级的字符串增加。最后生成了包含10亿个字符串的xml文件。
通常状况下,咱们会将xml放在内存中保存,这么多的字符串最后会耗尽咱们的内存,最终致使DOS。
咱们能够经过设置 XMLConstants.FEATURE_SECURE_PROCESSING 来防止这种攻击。
咱们知道java中hashMap是用分离链表来处理hash冲突的,若是插入了太多相同hashcode的元素,就会致使这个hashcode对应的链表变得很长,从而查询效率下降,影响程序性能。
什么是悲观回溯呢?
咱们举个例子,假如你们对正则表达式已经很熟悉了。
假如咱们使用/^(x*)y$/ 来和字符串xxxxxxy来进行匹配。
匹配以后第一个分组(也就是括号里面的匹配值)是xxxxxx。
若是咱们把正则表达式改写为 /^(x*)xy$/ 再来和字符串xxxxxxy来进行匹配。 匹配的结果就是xxxxx。
这个过程是怎么样的呢?
首先(x)会尽量的匹配更多的x,知道遇到字符y。 这时候(x)已经匹配了6个x。
接着正则表达式继续执行(x)以后的xy,发现不能匹配,这时候(x)须要从已经匹配的6个x中,吐出一个x,而后从新执行正则表达式中的xy,发现可以匹配,正则表达式结束。
这个过程就是一个回溯的过程。
若是正则表达式写的很差,那么就有可能会出现悲观回溯。
仍是上面的例子,可是此次咱们用/^(x*)y$/ 来和字符串xxxxxx来进行匹配。
按照上面的流程,咱们知道正则表达式须要进行6次回溯,最后匹配失败。
考虑一些极端的状况,可能会致使回溯一个很是大的次数,从而致使CPU占用率飙升。
咱们将java对象存进文件或者进行网络传输的时候,都须要使用到序列化和反序列化。
若是咱们在对一个java对象进行反序列化的时候,极可能就会加载恶意代码。
所以咱们须要在反序列化的时候进行住够的安全控制。
一般咱们为了调试程序或者寻找问题都会输出大量的日志,若是日志文件太大会影响到磁盘空间的使用。
同时,日志写入操做也会对同一个硬盘上的其余写入操做产生影响。因此日志输出要抓住重点。
在使用循环的时候必定要注意,不要产生无限循环的状况。
现代的java程序都会使用第三方jar包,可是第三方jar包的安全性仍是须要咱们注意的。若是某些第三方jar包中包含有恶意代码,那么会对咱们的系统形成很是严重的影响。
XPath 解析器是用来解析XML结构的工具,可是在使用XPath 解析器的时候,咱们须要注意防止注入攻击。
举个例子:
<users> <user> <name>张三</name> <username>zhangsan</username> <password>123</password> </user> <user> <name>李四</name> <username>lisi</username> <password>456</password> </user>
若是使用xpath,咱们须要这样来验证一个用户是否存在:
//users/user[username/text()='lisi'and password/text()='456']
若是用户传入username = 'lisi' 和 password = '456', 那么能够匹配成功,证实用户存在。
可是若是用户输入相似 ' or 1=1 or ''=' 的值,咱们看下xpath的解析结果:
//users/user[username/text()=''or 1=1 or ''='' and password/text()='' or 1=1 or ''='']
结果产生和SQL注入同样的结果。
一般来讲,咱们在进行文件操做,锁获取操做的的时候会申请相应的资源,在使用完这些资源事后,千万要记得释放他们。
在JDK7 以后,引入了try with表达式,咱们能够将要释放的资源放入try语句内,在程序执行完毕,资源会自动释放。
举个例子:
public R readFileBuffered( InputStreamHandler handler ) throws IOException { try (final InputStream in = Files.newInputStream(path)) { handler.handle(new BufferedInputStream(in)); } }
上面的InputStream会自动释放。
本文已收录于 http://www.flydean.com/java-security-code-line-dos/最通俗的解读,最深入的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注个人公众号:「程序那些事」,懂技术,更懂你!