服务端常见漏洞及防范建议

1,745 阅读6分钟

Demo代码使用Python+Django做示范,且变量命名比较简单和语义化,对Python不熟的同学应该也能大致理解~

SQL注入

这种漏洞通常的成因是:后端对于http请求参数(一般来自用户输入)没有做校验或转义编码,直接插入sql模版语句,然后在DB中执行。攻击者可以借此设计特别的请求参数从而改写最终执行的sql,从而获取到非授权的数据库信息,甚至直接修改数据库

Bad case ❌ :

一个典型的业务场景:根据用户搜索关键字来查找数据。

@rest_view
def get_data(request):
    search_word = request.GET.get('search_word')
    sql = 'select * from data_tbl where name like \'%{}%\''.format(search_word)
    data = list(DataTbl.objects.raw(sql))
    return data

上面的case中若攻击者将传参search_word设计为'; delete from important_tbl where xxx=yyy; --

那么实际DB中执行的sql如下(导致攻击者对数据库做了删除操作)

select * from data_tbl where name like '%'; delete from important_tbl where xxx=yyy; --%'

Good case ✅ :

使用ORM(可以理解为一个数据库操作框架,使用JS、Python等语言调用框架api,框架会自动转成相应sql去操作数据库)封装好的api进行DB操作,业界成熟的ORM都会在内部做好防sql注入的对策

@rest_view
def get_data(request):
    search_word = request.GET.get('search_word')
    
    # DataTbl.objects.filter就是一个ORM api,相当于执行 select * from data_tbl where name like '%xxx%'
    data = list(DataTbl.objects.filter(name__contains=search_word))
    return data

Good case ✅ :

Django推荐的raw 参数使用方法

有时需要的DB操作比较复杂,ORM的api难以支持或者写起来异常复杂,可以考虑使用其raw方法直接执行sql,但是对于需要传入参数的情况,需要严格遵守ORM的使用规范来防止sql注入

@rest_view
def get_data(request):
    search_word = request.GET.get('search_word')
    sql = 'select * from data_tbl where name like %(search_word)s'
    
    # 这里ORM会自动对参数做转义,同样能防止sql注入
    data = list(DataTbl.objects.raw(sql, dict(
        search_word=('%' + search_word + '%')
    )))
    return data

RCE

RCE(Remote Code Execution),即远程代码执行,指的是请求参数会被后端当作或插入进代码或命令执行,使得攻击者通过一个HTTP请求就能在服务器执行任意代码/命令。

比如有些特殊业务场景需要用户在前端填写代码并提交,然后后端在某种情况下会在函数中执行这些代码,若此函数并非与外界完全隔离的沙箱,且对代码没有做好完善的前置校验,则会产生RCE漏洞

Bad case ❌ :

@rest_view
@axios_post
def get_data(request, post_data):
    code = post_data.get('code')
    data = eval(code) # dangerous!!!
    return data

Good case ✅ :

使用安全沙箱执行代码/命令

@rest_view
@axios_post
def get_data(request, post_data):
    code = post_data.get('code')
    data = safe_sandbox.exec(code)
    return data

Good case ✅ :

提前对代码进行静态安全检查,比如禁止使用进程/系统相关的包,或者

@rest_view
@axios_post
def get_data(request, post_data):
    code = post_data.get('code')
    legal = safe_check(code)
    if not legal:
        raise Exception('illegal code!')
    else:
        data = sandbox.exec(code)
        return data

SSRF

SSRF,Server-Side Request Forgery,服务端请求伪造,即服务端提供了代替用户访问其它服务资源的功能且未对此做边界控制。存在该漏洞的服务器会被攻击者当作跳板去访问原本通过正常途径访问不到的服务资源

常见的“替用户访问其它服务”的业务场景:

  • 从指定URL地址获取网页文本内容 (网站翻译)

  • 对用户提供的远端图片添加水印 (在线编辑器、加水印功能)

  • 下载/上传用户提供的文件 (下载用户提供的图片保存到云端服务器)

  • 判断网站是否存活,状态码等 (广告平台获取广告主网站信息)

  • 获取网站的title等功能 (分享网站URL时,输出网站标题)

Bad case ❌ :

一个最简单直接的例子:前端传什么url,服务端就替其访问什么url。假如该服务面向外网,那么外网的攻击者就可以借助该服务访问到未做严格鉴权的内网资源

import requests

@rest_view
@axios_post
def get_data(request, post_data):
    url = post_data.get('url')
    req = post_data.get('req')
    headers = post_data.get('headers')
    r = requests.post(url, json=req, headers=headers)
    data = r.json()
    return data

Good case ✅ :

尽可能不要设计“替用户访问其它服务”的功能,假如必须要有这样的功能,那么就需要对下游目标服务做严格限制。

import requests

@rest_view
@axios_post
def get_data(request, post_data):
    url = post_data.get('url')
    req = post_data.get('req')
    headers = post_data.get('headers')
    
    if is_illegal(url):
        raise Exception('illagal url!')
    r = requests.post(url, json=req, headers=headers)
    data = r.json()
    return data

静态文件上传XSS

Bad case ❌ :

举个🌰:攻击者上传一份html文件到服务端,服务端将其直接保存到公司CDN或对象存储服务,并返回文件****url(访问则可获取文件)。可能导致的危害:

  1. 用户访问这个url,包含恶意内容的html文件被解析渲染,呈现黄色暴力内容,导致公司被查水表

  2. 某些平台由于特殊的业务逻辑,访问其线上地址会返回静态文件,那么假如攻击者诱导用户访问某个地址刚好返回恶意的html,就可以达成XSS攻击的效果

Good case ✅ :

从上述例子中可以看出,要达成这种攻击需要同时满足某些条件(已加粗标示),反言之只要阻断某项条件就可以防止该攻击造成危害:

  1. 阻断源头:上传内容由服务端生成,而非用户。即上传内容用户不可控

  2. 谨慎保存:对上传文件类型做严格校验&限定,防止特殊文件被上传;或者将文本文件无脑保存为 text/plain类型,这样即使访问html文件,也不会被浏览器当作html解析

  3. 不返回url,也不返回文件:如果前端拿不到url和文件,攻击自然不会发生

服务端模版注入XSS

Bad case ❌ :

对于采用SSR(服务端渲染)技术的Web应用来说,HTML文件是由服务端基于模版动态生成的,这个过程中经常需要将某些变量拼接进HTML文本内容,如果变量未经过滤/转义就拼进去,很可能会注入恶意代码从而造成XSS

Good case ✅ :

正确使用业界通用的模版引擎,按照官方文档规范正确拼接变量(一般都会自动做过滤/转义),而非自己手动拼接

日志泄露隐私信息

Bad case ❌ :

在服务日志中有意或无意地将用户cookie、access_key等隐私信息也打印了出来,导致有查询日志权限的人员有可能获取职权外的大量隐私数据

Good case ✅ :

打印服务日志时做到精细化(只打印必要信息),尽量别打印不可预测的内容(不可预测,意味着可能包含隐私信息)。如果是历史遗留的该类漏洞,对于业务代码一般直接删除即可,对于引用的第三方代码则需要沟通&升级