2.5.4 Web安全防范

  无论是一个简单的博客,还是大型的社交网站,Web安全都应该放在首位。Web安全问题涉及广泛,在这里介绍其中常见的几种攻击 (attack)和其他常见的漏洞(vulnerability。
  对于Web程序的安全问题,一个首要的原则是:永远不要相信你的用户。大部分Web安全问题都是因为没有对用户输入的内容进行“消毒”造成的。


1.注入攻击


  在OWASP(Open Web Application Security Project,开放式Web程序安全项目)发布的最危险的Web程序安全风险Top 10中,无论是最新的2017年的排名,2013年的排名还是最早的2010年,注入攻击 (Injection)都位列第一。注入攻击包括系统命令(OS Command)注入、SQL(Structured Query Language,结构化查询语言)注入(SQL Injection)、NoSQL注入、ORM(Object Relational Mapper,对象关系
映射)注入等。我们这里重点介绍的是SQL注入。

(1)攻击原理
  在编写SQL语句时,如果直接将用户传入的数据作为参数使用字符串拼接的方式插入到SQL查询中,那么攻击者可以通过注入其他语句来 执行攻击操作,这些攻击操作包括可以通过SQL语句做的任何事:获取敏感数据、修改数据、删除数据库表……

(2)攻击示例
  假设我们的程序是一个学生信息查询程序,其中的某个视图函数接收用户输入的密码,返回根据密码查询对应的数据。我们的数据库由一 个db对象表示,SQL语句通过execute()方法执行:

@app.route('/students') 
def bobby_table():    
    password = request.args.get('password')    
    cur = db.execute("SELECT * FROM students WHERE password='%s';" % password)
    results = cur.fetchall()       
    return results

注:在实际应用中,敏感数据需要通过表单提交的POST请求接收,这 里为了便于演示,我们通过查询参数接收。

  我们通过查询字符串获取用户输入的查询参数,并且不经过任何处理就使用字符串格式化的方法拼接到SQL语句中。在这种情况下,如果 攻击者输入的password参数值为“'or 1=1--”, 那么最终视图函数中 被执行的SQL语句将变为:

SELECT * FROM students WHERE password='' or 1=1 --;'

这时会把students表中的所有记录全部查询并返回,也就意味着所有的记录都被攻击者窃取了。更可怕的是,如果攻击者将password参数 的值设为“';drop table users;--”,那么查询语句就会变成:

SELECT * FROM students WHERE password=''; drop table students; --;

执行这个语句会把students表中的所有记录全部删除掉。(“--”用来注释后面的语句)

(3)主要防范方法
1)使用ORM可以一定程度上避免SQL注入问题。
2)验证输入类型。比如某个视图函数接收整型id来查询,那么就 在URL规则中限制URL变量为整型。
3)参数化查询。在构造SQL语句时避免使用拼接字符串或字符串
格式化(使用百分号或format()方法)的方式来构建SQL语句。而要使用各类接口库提供的参数化查询方法。

db.execute('SELECT * FROM students WHERE password=?, password)

4)转义特殊字符,比如引号、分号和横线等。


2 XSS攻击


XSS(Cross-Site Scripting,跨站脚本)攻击

(1)攻击原理
  XSS是注入攻击的一种,攻击者通过将代码注入被攻击者的网站 中,用户一旦访问网页便会执行被注入的恶意脚本。XSS攻击主要分为 反射型XSS攻击(Reflected XSS Attack)和存储型XSS攻击(Stored XSS Attack)两类。

  反射型XSS又称为非持久型XSS(Non-Persistent XSS)。当某个站点存在XSS漏洞时,这种攻击会通过URL注入攻击脚本,只有当用户访问这个URL时才会执行攻击脚本。我们在本章前面介绍查询字符串和 cookie时引入的示例就包含反射型XSS漏洞,如下所示:

@app.route('/hello') 
def hello():    
    name = request.args.get('name')   
    response = '<h1>Hello, %s!</h1>' % name

  这个视图函数接收用户通过查询字符串传入的数据,未做任何处理 就把它直接插入到返回的响应主体中,返回给客户端。如果某个用户输入了一段JavaScript代码作为查询参数name的值,如下所示:

name=<script>alert('Bingo!');</script>

  当客户端接收到响应后,浏览器解析这行代码就会打开一个弹窗,你觉得一个小弹窗不会造成什么危害?那你就完全错了,能够执行 alert()函数就意味着通过这种方式可以执行任意JavaScript代码。即攻击者通过JavaScript几乎能够做任何事情:窃取用户的cookie和其他敏感 数据,重定向到钓鱼网站,发送其他请求,执行诸如转账、发布广告信 息、在社交网站关注某个用户等。即使不插入JavaScript代码,通过HTML和CSS(CSS注入)也可以 影响页面正常的输出,篡改页面样式,插入图片等。

  存储型XSS也被称为持久型XSS(persistent XSS),这种类型的 XSS攻击更常见,危害也更大。它和反射型XSS类似,不过会把攻击代码储存到数据库中,任何用户访问包含攻击代码的页面都会被殃及。比如,某个网站通过表单接收用户的留言,如果服务器接收数据后未经处 理就存储到数据库中,那么用户可以在留言中插入任意JavaScript代码。 比如,攻击者在留言中加入一行重定向代码:

<script>window.location.href="http://attacker.com";</script>

其他任意用户一旦访问留言板页面,就会执行其中的JavaScript脚本。就会被重定向到攻击者写入的站点。
(3)主要防范措施
a.HTML转义
  防范XSS攻击最主要的方法是对用户输入的内容进行HTML转义, 转义后可以确保用户输入的内容在浏览器中作为文本显示,而不是作为代码解析。这里的转义和Python中的概念相同,即消除代码执行时的歧义,也就是把变量标记的内容标记为文本,而不是HTML代码。具体来说,这会把变量中与HTML相关的符号转换为安全字符,以避免变量中包含影响页面输出的HTML标签或恶意的JavaScript代码。
比如,我们可以使用Jinja2提供的escape()函数对用户传入的数据 进行转义:

from jinja2 import escape
@app.route('/hello')
def hello():    
     name = request.args.get('name')    
     response = '<h1>Hello, %s!</h1>' % escape(name)

b.验证用户输入
  XSS攻击可以在任何用户可定制内容的地方进行,例如图片引用、自定义链接。仅仅转义HTML中的特殊字符并不能完全规避XSS攻击,因为在某些HTML属性中,使用普通的字符也可以插入JavaScript代码。 除了转义用户输入外,我们还需要对用户的输入数据进行类型验证。在 所有接收用户输入的地方做好验证工作。
以某个程序的用户资料页面为例,我们来演示一下转义无法完全避免的XSS攻击。。程序允许用户输入个人资料中的个人网站地址,通过下面的方式显示在资料页面中:

<a href="{{ url }}">Website</a>

其中{{url}}部分表示会被替换为用户输入的url变量值。如果不对 URL进行验证,那么用户就可以写入JavaScript代码,比如“javascript: alert('Bingo!');”。因为这个值并不包含会被转义的<和>。最终页面 上的链接代码会变为:

<a href="javascript:alert('Bingo!');">Website</a>

类似的,{{url}}部分表示会被替换为用户输入的url变量值。如果不对输入的URL进行验证,那么用户可以将url设为“123"onerror="alert('Bingo!')”,最终的<img>标签就会变为:

<img src="123" onerror="alert('Bingo!')">

在这里因为src中传入了一个错误的URL,浏览器便会执行onerror属 性中设置的JavaScript代码。
所以需要对用户输入进行验证


3.CSRF攻击


CSRF(Cross Site Request Forgery,跨站请求伪造)是一种近年来 才逐渐被大众了解的网络攻击方式,又被称为One-Click Attack或Session Riding。

(1)攻击原理
  CSRF攻击的大致方式如下:某用户登录了A网站,认证信息保存在 cookie中。当用户访问攻击者创建的B网站时,攻击者通过在B网站发送 一个伪造的请求提交到A网站服务器上,让A网站服务器误以为请求来自于自己的网站,于是执行相应的操作,该用户的信息便遭到了篡改。总结起来就是,攻击者利用用户在浏览器中保存的认证信息,向对应的站点发送伪造请求。在前面学习cookie时,我们介绍过用户认证通过保存在cookie中的数据实现。在发送请求时,只要浏览器中保存了对应的 cookie,服务器端就会认为用户已经处于登录状态,而攻击者正是利用了这一机制。

(2)攻击示例
  假设我们网站是一个社交网站简称网站A;攻击者的网站可以是任意类型的网站,简称网站B。在我们的网站中,删除账户的操作通过GET请求执行,由使用下面的delete_account视图处理:

@app.route('/account/delete') 
def delete_account():   
     if not current_user.authenticated:        
         abort(401)    
     current_user.delete()    
     return 'Deleted!'

当用户登录后,只要访问http://A.com/account/delete就会删 账户。那么在攻击者的网站上,只需要创建一个显示图片的img标签, 其中的src属性加入删除账户的URL:

<img src="http://A.com/account/delete">

当用户访问B网站时,浏览器在解析网页时会自动向img标签的src 属性中的地址发起请求。此时你在A网站的登录信息保存在cookie中, 因此,仅仅是访问B网站的页面就会让你的账户被删除掉。

  当然,现实中很少有网站会使用GET请求来执行包含数据更改的敏感操作,这里只是一个示例。现在,假设我们吸取了教训,改用POST 请求提交删除账户的请求。尽管如此,攻击者只需要在B网站中内嵌一 个隐藏表单,然后设置在页面加载后执行提交表单的JavaScript函数,攻 击仍然会在用户访问B网站时发起。

(3)主要防范措施
a.正确使用HTTP方法
  防范CSRF的基础就是正确使用HTTP方法。在前面我们介绍过 HTTP中的常用方法。在普通的Web程序中,一般只会使用到GET和 POST方法。而且,目前在HTML中仅支持GET和POST方法(借助 AJAX则可以使用其他方法)。在使用HTTP方法时,通常应该遵循下面 的原则:

-GET方法属于安全方法,不会改变资源状态,仅用于获取资源, 因此又被称为幂等方法(idempotent method)。页面中所有可以通过链接发起的请求都属于GET请求。

-POST方法用于创建、修改和删除资源。在HTML中使用form标签 创建表单并设置提交方法为POST,在提交时会创建POST请求。

b.CSRF令牌校验

当处理非GET请求时,要想避免CSRF攻击,关键在于判断请求是否来自自己的网站。在前面我们曾经介绍过使用HTTP referer获取请求来源,理论上说,通过referer可以判断源站点从而避免CSRF攻击,但因 为referer很容易被修改和伪造,所以不能作为主要的防御措施。
除了在表单中加入验证码外,一般的做法是通过在客户端页面中加入伪随机数来防御CSRF攻击,这个伪随机数通常被称为CSRF令牌 (token)。

在HTML中,POST方法的请求通过表单创建。我们把在服务器端创建的伪随机数(CSRF令牌)添加到表单中的隐藏字段里和session变量(即签名cookie)中,当用户提交表单时,这个令牌会和表单数据一起提交。在服务器端处理POST请求时,我们会对表单中的令牌值进 验证,如果表单中的令牌值和session中的令牌值相同,那么就说明请求发自自己的网站。因为CSRF令牌在用户向包含表单的页面发起GET请求时创建,并且在一定时间内过期,一般情况下攻击者无法获取到这个令牌值,所以我们可以有效地区分出请求的来源是否安全。


除了这几个攻击方式外,我们还有很多安全问题要注意。比如文件上传漏洞、敏感数据存储、用户认证(authentication)与权限管理等。
这些内容我们将在后面的陆续介绍。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Web 安全的对于 Web 从业人员来说是一个非常重要的课题 , 所以在这里总结一下 Web 相关的安全攻防知识,...
    Yishto阅读 40,255评论 3 33
  • 第六部分 原文 范氏有子曰子华,善养私名,举国服之。有宠于晋君,不仕而居三卿之右。目所偏视,晋国爵之;口所偏肥,晋...
    君远近阅读 402评论 0 0
  • 有些人对见面交谈不太有信心,一直很疑惑,说见面该说什么呢? 1.初次见面说话重要吗? 可以说重要也不重要。说它重要...
    曦水阅读 557评论 0 3
  • 刻意练习是非常专业的练习形式,所以严格意义上来说,它是有门槛要求的。第一,所训练的领域是合理发展的行业,有一整套成...
    杏子Pin_cha阅读 904评论 0 0
  • 微信6.3.16版更新,终于可以“置顶公众号”啦! 30个媒体平台 1. 泛媒体平台 1. 微信公众平台 http...
    李咧咧阅读 401评论 0 0