事件起因:一个之前使用Nodejs开发的网站。网站上有一个页面具有允许用户上传图片或粘贴图片链接的功能。服务器读取用户上传的图片信息或请求用户填写的图片链接获取图片信息。如果用户使用上传功能,前端可以在输入控件上限制上传文件的类型,然后在后端进行验证,以确保获取图片文件的合法性(或安全性)。如果是用户填写的图片链接,安全隐患比较大。如果用户填写的链接不是普通的http、https、ftp链接,而是可以在服务器上执行的curl之类的命令,如果服务器拿到用户的链接,不处理直接请求,它可能给公司造成不可估量的损失(例如:内网服务器信息泄露,更严重的是内网服务器被攻击)。事件处理方法:公司有专门的安全小组,专门负责网络安全的工作。发生这件事时,我首先收到服务器短信告警,网上出现大量FATAL日志。这时,我立即登上服务器进行调查,随后在内部聊天工具上被公司安全团队的同事通知,该网站受到了ssrf(服务器端请求伪造)攻击。接口第一次下线,评估安全方案,接口再次上线。那么在服务器端防止ssrf攻击的具体方法是什么,请往下看。服务端方案ssrf的具体原理是在服务端伪造请求,向服务端请求资源。我在上面也提到了案例场景。我有一个功能,会请求外网图片链接获取图片信息。用户填写的图片链接很可能是非法请求。当我的服务器拿到这个链接的时候,并没有像预期的那样向外网请求资源,而是向内网服务请求资源。这样我的服务器就被攻击了。那么什么可能导致ssrf漏洞:web服务有请求外部网络资源的功能。请求的url参数是可以控制服务器的外部用户。服务器在请求外网资源之前,不检查域名和IP(是否请求内网资源)只是通过正则匹配来判断IP段(域名也可能指向内网IP,如果IP在十进制,没有问题,但是IP也有十六进制,十进制,二进制)上面说了其实可以知道一些解决办法:1.验证用户填写的链接的协议头,在获取的时候验证用户填写的链接。检查是否为合法的http、https、ftp协议头。当然这个校验要在前端和后端都校验,以减轻服务器的压力。如果不是有效的请求协议头,则直接拒绝,并返回请求资源失败的错误信息。(伪代码如下)constallowReqprotocol=['http','https','ftp',];constreqProtocol=request.protocol;//强制检查用户输入的图片链接的协议头是否为网站允许的协议头if(allowReqprotocol.indexOf(reqProtocol)<0){yog.log.warning(imgUrl);res.json(errorMsg.imgTypeError());return;}2.检查用户请求的IP是否指向内网ip前面已经介绍过,仅仅通过正则化是没有办法完全验证是否是合法IP段的。其实还有一种方法可以将我们的IP段转成int来进行判断。正好npm上有个包ip-to-int可以满足我们的需求。我们可以结合两者来判断请求链接的IP是否为内网IP。constdns=require('dns');consturl=require('url');constipToInt=require('ip-to-int');constparseUrl=url.parse(rqUrlString);consthostname=parseUrl.hostname;//使用Nodejs的dns模块根据域名解析域名对应的ip地址dns.lookup(hostname,(err,address,family)=>{//将我的内网IP段添加到180.xxx.xxx.xxx180.255.255.255constipRegx=/^180\./;constipArr=[ipToInt(180.0.0.0).toInt(),ipToInt(180.255.255.255).toInt()];if(ipRegx.test(address)||ipRang(address)){//拒绝请求}else{//继续请求}});//判断一个ip是否在数组之间functionipRang(ip,array){//ip在给定的范围内ip段返回真;//ip不在给定的ip段之间returnfalse;}3、最后的检查经过上面的检查,基本可以保证,如果有请求外部Url,服务端执行后会指向该请求它也是一种外部网络资源。然后当资源到达服务器时,为了保证资源的安全。我们对请求的内容进行最终检查。比如我只想请求图片资源,有限几种格式。例如:png、jpg、jpeg。constallowImgContentType=['image/jpeg','image/png','image/gif'];if(allowImgContentType.indexOf(response.headers['content-type'])<0){//返回的内容是非法资源}4.如果允许请求内网资源,以上方法如果请求基本都会被拒绝用于内网资源。但是如果有请求内网的需求呢。如果内网的资源是对外开发的,那肯定是具体的机器。这些机器也必须被OP专门隔离,没有敏感的公司信息资源。那么当我们的请求到达公司内网时,如何保证请求总是请求特定的IP呢?其实我们只需要在header中将请求链接的host和请求机器的ip绑定即可。//通过dns模块解析host对应的ip,这里是address//并通过url模块解析端口路径和querystring重新组装请求的urlconstbindURLString=`${parseUrl.protocol}//${address}:${reqUrlPort}${pathName}?${queryString}`;//然后在请求发出时设置header,下面是我使用request模块发送请求时设置的参数constoptions={'url':bindURLString,'encoding':'binary','rejectUnauthorized':false,'headers':{'Host':bindDnsObj.hostname//这里是绑定请求的host}};这是强制请求请求特定机器。不会要求其他机器。
