注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue 发布时间:2017/12/26 20:53:43
如果我们在编写程序的过程中,默认信任用户的输入,那么,程序将会变的极易受攻击。不管这种攻击是有意还是无意的(绝大多数情况下都是有意的)。我们在之前的章节中已经大致介绍了为什么需要进行用户输入。
一、用户输入简介
在一个Web应用中,用户输入有5种形式:统一资源定位器(Uniform Resource Locator,URL,一般俗称网址),GET数据,POST数据,Cookies,HTTP标头。这5中方式中任意一种都有可能变成攻击的入口。下面先简要介绍一下5种输入方式。
1、URL
URL是用户请求单独资源的方式,从最基本的html页面,到带有GET参数的请求,都可以作为URL格式。如:
- http://www.example.com/example.aspx?id=123&keyword=abc
上面的URL带了2个参数,分别为id和keyword,值分别为123,keyword为abc。
2、GET数据
GET数据包含用户传递给web应用所需的参数和值,它们都包含在URL中。如上个例子中的值和数据,它们可以由普通的html页面或任意动态页面的表单发起,向服务器传递数据。
3、POST数据
POST数据包含客户传递给web应用用来满足post请求的参数和值,不同于GET方式,这些请求和数据是看不见的,不会包含在URL中,它们可以由普通的html页面或任意动态页面的表单发起。
4、Cookie
Cookie用于在客户端计算机中存储数据,Cookie和URL相关联,使用一个带Cookie的HTTP标头,服务器可以要求浏览器为一个URL存储一个数据到cookie中。如果客户端本地已经存在一个cookie,则每当请求此URL时,客户端浏览器都会将此cookie作为HTTP请求的一部分。另外,也可以通过用户浏览器,通过JS脚本等方式,存储、读取Cookie。
5、HTTP标头
客户端浏览器提供了大量的HTTP请求标头,用来向服务器端描述客户端环境,这些标头包括:授权(请求者[即客户端]的身份验证证书)、来源(请求者的电子邮件地址)、引用(获取当前URL资源的地址)、用户代理(请求者浏览器和本地计算机的环境信息)。
用户输入可能引发Web应用程序产生恶意的数据,例如执行恶意脚本或者访问不该访问的资源。任何一个小小的漏洞,都可能被无限放大,被用来不停的进行攻击。
因此,我们必须把所有的用户输入都当做潜在的攻击来源,无论何时接收用户数据,都必须明确一个很窄的有效输入范围,然后通过程序来确保仅接收那个范围的值。这里,我们必须采用“有罪推定”,即只“放过”那些明确是合法的用户输入,而对一切不明的输入都进行拒绝。
我们如何做到这样的验证呢?一般来说,我们通过2道“代码门”,来进行验证。第一扇门,即客户端验证。客户端验证是指服务器把初步的JS验证代码,通过服务器发送给客户端,让客户端的浏览器自己执行验证。不过,黑客可以轻松绕过这一点,但并不是说这么做毫无意义,因为这样加大了黑客的工作量。第二扇门,即服务器端验证,通过服务器端的程序,验证用户的输入,这个验证程序不受黑客控制,因此,是最主要的方式。一般情况下,我们必须两道门都加以使用,所有的用户输入必须既通过客户端验证,也能通过服务器端验证,才能进行最终的响应、入库等处理。
二、用户输入攻击的类型
那么,常见的攻击方式有哪些呢?常见的预防手段又有哪些?
1、URL格式攻击
URL通常由这样几个部分组成:通信协议(如http、https、ftp、thouder等)+ :// + DNS域名/IP地址 + 目录 + 文件名 + 参数。这种形式可能会变成一种缺陷,例如,如果允许URL以“../"这样的方式打开父目录,攻击者可以通过以下地址访问到本不应该访问到的c:\system32\cmd.exe文件:
- http://www.exaple.com/abc/showcontent.aspx?filename=../../winnt/system32/cmd.exe
这里假设IIS用的是系统默认的目录(即C:/inetpub/wwwroot)。上面这个URL如果没有被限制,它会访问cmd.exe,这样攻击者便在自己的电脑上有了一个控制服务器的命令行程序,并发送参数和命令给这个命令行。
要如何应对这种危险呢?最好的办法,就是禁止Web服务器程序(如IIS)使用父路径,而是要以绝对路径来引用web页。
此外,对于接受参数的页面来说,需要对URL传递来的参数进行充分的验证(后文会详细叙述)。
2、Cookie攻击
cookie一般分为两种,一种是保存在本地的持久性cookie,以文件的形式保存。第二种,是会话cookie,只在会话期间保留在客户端内存中,会话cookie在会话结束后被删除。
所有的cookie都能被攻击者以某种方式编辑,如果攻击者更改自己的cookie,或者通过木马病毒等控制他人电脑读取、修改cookie,再访问服务器,如果服务器不加验证,将会导致用户信息泄露、源代码暴露、经济损失等严重的安全问题。因此,千万不要在cookie中存储敏感数据。
例如,如果你的站点依赖cookie保存某个商品的价格,在用户带着这些cookie访问你的服务器,如果你的程序没有对cookie进行判断就将其接收为商品价格,那么结果会是灾难性的:用户可以任意更改本地cookie中的价格,达到任意他想达到的折扣。
又比如,你的站点依赖cookie保存用户名和密码,而一旦黑客控制了某个“肉鸡”的电脑,便获取了其cookie内容,并将其作为凭据登录你的程序,导致这位用户账号“被盗”。
或者,在你没有对cookie进行验证的情况下,攻击者修改了cookie,导致了程序的错误,一段代码被暴露出来了,更不幸的可能是,你的数据库连接账号、密码、地址就在旁边。
最好的办法是,在服务器中存储一个查询键,将键名存储到cookie中,用户持这个cookie信息去查询,如果这个cookie遭到恶意更改,在服务器中就查不到相关信息,就失效了。
也可以在发送cookie前,对cookie内容进行加密,如存储密码。这种情况下,依然不要使用单个md5加密之类的弱强度加密方法,而要综合使用一套独有的加密方法。
3、HTTP标头攻击
HTTP标头的值可以从客户端发送给服务器,也可以从服务器发送到客户端。客户端发送的标头信息不可信,因为恶意攻击者可以随意篡改这些信息。如果,服务器端将客户端发送的标头信息,作为敏感决策的依据,将会带来重大安全隐患。例如,依赖HTTP标头作为web应用程序产生的流量通信费凭据,恶意的攻击者会篡改标头信息,来始终访问该web应用程序,而表面上看没有损耗任何流量。
4、表单数据攻击
前文提到,我们通过2道“代码门”,来进行验证,第一道是客户端验证。而可怕的是,只要运用了表单数据攻击,这第一道门将完全失效!
这绝非危言耸听,攻击者如何做到这一点?我们知道,web应用程序会把表单控件构建的任务交给客户端浏览器,我们执行的客户端验证,也只是将验证程序发送给客户端。但是,攻击者如果将这些源码,保存为本地html文件,但是其表单提交的对象地址还是服务器的地址。那么,它就可以轻松篡改本地验证程序,并将其直接提交给服务器。如果服务器没有对提交的数据进行合法性验证,灾难便会发生。
举个例子,当我们将一个商品价格字段,设置为一个只能输入数字的表单控件,攻击者通过本地表单控件的更改,输入了一些带有特殊字符,或者不符合要求的数据。服务器如果不加验证,最轻的会导致程序错误代码泄露,严重的,会带来巨大的经济损失。
防止表单数据攻击,可以从下面几个方面入手:第一,服务器端对用户输入的数据进行充分的验证。第二,对传递数据的页面地址进行限制,使程序只接受本服务器上的页面传递的数据。第三,将表单的值,只显示给用户,处理数据时,却使用服务器或数据库中存储的数据。且要慎用隐藏类的表单控件,它们没有丝毫安全防范价值,反而带来一丝安全的错觉。
5、注入式脚本攻击
在web应用程序中,往往需要针对用户输入的不同数据,产生不同的应对脚本,而这其中就包含了危险。恶意的攻击者,会将攻击脚本作为普通数据,传递给服务器,如果服务器不加验证,将会导致安全性问题。
常见的注入式脚本攻击是跨站脚本攻击(XSS),攻击者并不攻击服务器本身,而是用来攻击其他用户。攻击者使用一个包含脚本的地址(这些地址往往被重编码而难以辨认危险性),发送给其他不知情的用户访问,不知情的用户访问该地址后,因为服务器没有加以验证便将脚本文本、以服务器响应的方式发送给不知情的用户,恶意的脚本得以执行,这些脚本可以读取cookie、运行其他攻击者想要运行的脚本、调用本地组件。
6、跨站点脚本攻击
跨站点脚本攻击,英文名为Cross-Site Scripting,由于首字母与样式表缩写CSS重名,故用X代替C,简称为XSS。关于跨站点脚本攻击的知识,我们将在下一章节中详细介绍。
7、SQL注入攻击
其基本的原理是,利用输入将SQL语法作为数据传递给服务器,从而执行攻击者想要的操作。关于SQL注入攻击,将在以后的数据库安全章节中详细介绍。
三、验证用户输入
从之前的内容我们可以看出,防止用户输入攻击的关键在于,对用户输入内容进行充分有效的验证。在验证时应遵循以下原则:
- 执行紧缩的验证,只“放过”那些明确是合法的用户输入,而对一切不明的输入都进行拒绝。
- 考虑周全,对服务器、数据库需要什么样的数据一清二楚。充分验证用户输入数据的长度、数据类型、值的范围、是否含有敏感字符等,每种编程语言语法不一样,但验证的东西却都是一样的。
- 敏感数据,应该从数据库中读取,而不信赖用户的输入。
在很多情况下,我们需要用户输入特定的格式的数据,例如手机号就必须要求用户输入11位、以1开头的数字,身份证号就要求用户输入15或18位的数字,最后一位可以是字母“X”。还有一些更复杂的要求,如果我们通过代码进行验证,将会显得十分冗长而低效。而正则表达式就可以高效、简洁的完成验证功能。
正则表达式简而言之,是一种严密、简洁的语法,它用来表示特定的字符串模式,只有特定类型的字符串才会匹配一个正则表达式。关于正则表达式的知识,请访问本站的正则表达式教程。此外,本站还提供在线验证正则表达式的工具,它位于工具箱中。
不管是客户端,还是服务器端,都可以利用正则表达式对用户输入进行合法性验证,因为JS和绝大多数编程语言(Java、C#、Python、php等)都支持正则表达式。
四、隐藏敏感信息
验证用户输入当然是关键的防范攻击的方法,但是我们不应该只满足于一种应对方法。应对黑客的招数应该越多越好,因为也许黑客就被挡在其中一扇门外。
这里假设,我们的验证手段出了问题,程序出现了错误,我们服务器的执行代码暴露在攻击者面前,这些代码看似无关紧要,但实际上很可能被黑客利用。这里我们想象一个很坏的情况——我们的数据库连接的相关信息出现在出错代码部分周围。我们应该将用户与web应用程序之间的交互程度,控制在最低的“必须”级别线上,使攻击者很难了解web程序的执行细节。
1、不要暴露私有信息
典型的反面例子就是,在用户登录界面,如果用户输入了正确的用户名,但密码输错了,这时我们去提示用户“密码错误”。这虽然小小的改善了用户体验,但实际上是十分愚蠢的行为。这相当于告诉那些满怀恶意的黑客,这个用户名是正确的,数据库中存在,你现在只需要尝试不同的密码就可以了!省去了他们多少倍的精力(用户名+密码组合是乘法的组合倍数,而不是加法)。正确的办法应该是提示“用户名或密码错误”,试想一个黑客为了一个不存在的用户名尝试了诸多密码的情形,其难度要比知道用户名,暴力破解密码高多少!这里顺便说一句,防范暴力破解密码有很多有效的办法,最常用的是增加验证码,以及限制试错次数。
另外一个社会工程学防范是,不要在用户登录后,显示其完整的“用户名”,取而代之,我们可以显示他们的“昵称”。
2、少暴露注释和代码
不要在发布版本中包含注释之类的东西,特别是服务器端代码。这些注释将大大有利于你维护程序,同时还大大有利于黑客理解你的程序。在使用.net技术时,也不要往生产环境部署源文件(.cs和.vb)。使用面向对象编程语言编写服务器程序时,尽量对程序进行封装处理,这样,就算某个地点出错,黑客看到的也只是某个私有变量的名字,而看不到其值。
3、向用户反馈信息时不要暴露出错细节
一般情况下,我们都会根据用户不同的输入,反馈不同的信息给用户。因此,我们应该尽可能的考虑到用户输入错误的情形,这样就能最大限度避免意外情况。在我们考虑到错误信息后,反馈给用户的信息里,也不要含有错误的详细技术信息,如出错代码、错误的字符串、堆栈调用情况、系统配置信息等,因为这将方便黑客取得服务器的信息。
在发送给用户一份一般性错误时,我们可以同时使用日志功能,记录详细的错误信息。日志可以存储为操作系统的日志、本地日志文件、数据库等。同时必须确保这些日志文件,不能被黑客访问(一般通过服务器配置来设置),或者阅后即焚,我们自己知道错误信息后,即可将日志删除。
4、配置信息存储在安全的地方
一些重要的文件,例如数据库连接字符串、用户名和密码等敏感信息,可以存储在服务器的安全文件中,或者存储在操作系统注册表中,而不要将其存储在脚本或源代码中。
五、缓冲区溢出
简单的说,缓冲区溢出就是程序的缓冲区被写入了超长的数据导致的错误。长久以来,缓冲区溢出一直是困扰开发者的安全问题,它自从20世纪60年代就已经被确认,但直到今天仍然在持续发生。1988年,世界上第一个缓冲区攻击蠕虫病毒由罗伯特·莫里斯(Robert T. Morris)编写出来,并迅速爆发,导致当时的互联网几乎处于停顿状态,各个网站不得不断开服务器来遏制危害。利用缓冲区溢出攻击,可以执行某些指令,执行某些非法操作。
缓冲区溢为什么会屡屡出现?简单来说,糟糕的编码习惯是问题的根源。类似C/C++这种接近底层的语言,为程序员犯错提供了“绝好的”机会。而相对“高级”一点的语言,如Java、C#、Perl等,都会做数组越界的运行时检查。以Java为例,Java会实现内存自动管理,纯Java不会出现这个问题。说到这里,你也许以为不使用C/C++就可以了,但因为底层系统调用的本地接口基本都是C/C++编写的,而且C/C++因为接近底层,速度极快,所以往往不可避免的在程序中会调用C/C++编写的程序,因此还是存在相关风险。
下面我们来看一个例子,来说明缓冲区溢出为什么会出现。某个程序,为某个用户输入设置了一个大小为4位的缓冲区(0-3号格子),而程序的总缓冲区大小有16位,程序的缓冲区就像一个个格子,每个格子中存放不同的东西,有的是命令,有的是数据。如果用户输入了一个长度为16位的值,程序没有加以验证,就会导致缓冲区溢出:

以下是这个例子的C代码:
- void function(char *str) {
- char buffer[16];
- strcpy(buffer,str);
- }
例子中的strcpy()函数直接把str中的内容复制到buffer中。这样只要str的长度大于16,就会造成buffer的溢出,使程序运行出错。类似strcpy的标准函数还有strcat(),sprintf(),scanf(),gets(),sscanf(),fscanf(),vfscanf(),vsprintf(),vscanf(),vsscanf(),streadd(),strecpy(),strtrns()等,它们都有可能导致缓冲区溢出。
缓冲区溢出攻击,就是专门通过往缓冲区写超出其长度的数据,导致缓冲区溢出,破坏程序的堆栈,造成程序崩溃或转而执行其它指令,以达到攻击的目的。造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。
缓冲区溢出攻击中,最为危险的是堆栈溢出攻击,因为攻击者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。
堆栈溢出攻击者会预先在缓冲区放置危险代码,一般攻击者会有2种方法:第一,将代码指令作为用户普通输入的字符串提交给程序。第二,利用现有程序中已有的函数,如C语言中libc库中代码执行函数“exec (arg)”,改变它们的参数,使之执行攻击想要执行的程序。攻击者会使程序溢出一个缓冲区,扰乱程序的正常执行,然后用暴力的方法改写相邻的程序空间,跳过了系统的检查。在C中,每当一个函数调用,这个函数会在堆栈中留下一个记录,包含函数的返回地址。当攻击者发动攻击使得缓冲区溢出后,这个函数的返回地址也被改变了,它指向了一些攻击者预先设置的地址。代码植入和缓冲区溢出可以在一次动作内完成,也可以分不同批次完成。
此外,攻击者还会采用缓冲区溢出的办法,改变原本的函数指针,当某一个时刻程序再次调用这个指针时,攻击者就得逞了。
C语言中,还存在简单的检验/恢复系统,称为setjmp/longjmp。如果攻击者能够造成缓冲区溢出,就能象改变函数指针一样,使得longjmp指向任何地址。
那么,如何防范缓冲区溢出问题呢?可以从以下方面入手:
- 进行完全而周密的用户输入检测,长度、类型不对,应该有响应机制。
- 数组定义时就定义好长度,使用时时刻注意不要超过。
- 分配或申请内存时,也定义好长度,使用时,不能超过。
- 对于不确定的数据缓冲,定义一个量超过时丢弃一部分不需要或可丢弃的。
- 在程序指针失效前进行完整性检查。
- 用grep来搜索容易产生漏洞的库的调用,比如对strcpy和sprintf的调用。
- 利用一些高级查错工具,如fault injection等进行查错。这些工具会故意随机地产生一些缓冲区溢出来寻找漏洞。此外,还有一些静态分析工具用于侦测缓冲区溢出的存在。虽然这些工具帮助程序员开发更安全的程序,但是由于C语言的特点,这些工具不可能找出所有的缓冲区溢出漏洞,所以,不要掉以轻心。
下表更详细的说明了C中,那些危险函数的应对策略:
函数 | 严重性 | 解决方案 |
---|---|---|
gets | 最危险 | 使用 fgets(buf, size, stdin)。这几乎总是一个大问题! |
strcpy | 很危险 | 改为使用 strncpy。 |
strcat | 很危险 | 改为使用 strncat。 |
sprintf | 很危险 | 改为使用 snprintf,或者使用精度说明符。 |
scanf | 很危险 | 使用精度说明符,或自己进行解析。 |
sscanf | 很危险 | 使用精度说明符,或自己进行解析。 |
fscanf | 很危险 | 使用精度说明符,或自己进行解析。 |
vfscanf | 很危险 | 使用精度说明符,或自己进行解析。 |
vsprintf | 很危险 | 改为使用 vsnprintf,或者使用精度说明符。 |
vscanf | 很危险 | 使用精度说明符,或自己进行解析。 |
vsscanf | 很危险 | 使用精度说明符,或自己进行解析。 |
streadd | 很危险 | 确保分配的目的地参数大小是源参数大小的四倍。 |
strecpy | 很危险 | 确保分配的目的地参数大小是源参数大小的四倍。 |
strtrns | 危险 | 手工检查来查看目的地大小是否至少与源字符串相等。 |
realpath | 很危险(或稍小,取决于实现) | 分配缓冲区大小为 MAXPATHLEN。同样,手工检查参数以确保输入参数不超过 MAXPATHLEN。 |
syslog | 很危险(或稍小,取决于实现) | 在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。 |
getopt | 很危险(或稍小,取决于实现) | 在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。 |
getopt_long | 很危险(或稍小,取决于实现) | 在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。 |
getpass | 很危险(或稍小,取决于实现) | 在将字符串输入传递给该函数之前,将所有字符串输入截成合理的大小。 |
getchar | 中等危险 | 如果在循环中使用该函数,确保检查缓冲区边界。 |
fgetc | 中等危险 | 如果在循环中使用该函数,确保检查缓冲区边界。 |
getc | 中等危险 | 如果在循环中使用该函数,确保检查缓冲区边界。 |
read | 中等危险 | 如果在循环中使用该函数,确保检查缓冲区边界。 |
bcopy | 低危险 | 确保缓冲区大小与它所说的一样大。 |
fgets | 低危险 | 确保缓冲区大小与它所说的一样大。 |
memcpy | 低危险 | 确保缓冲区大小与它所说的一样大。 |
snprintf | 低危险 | 确保缓冲区大小与它所说的一样大。 |
strccpy | 低危险 | 确保缓冲区大小与它所说的一样大。 |
strcadd | 低危险 | 确保缓冲区大小与它所说的一样大。 |
strncpy | 低危险 | 确保缓冲区大小与它所说的一样大。 |
vsnprintf | 低危险 | 确保缓冲区大小与它所说的一样大。 |
如果我们是程序的使用者,比如,我的服务器上必须安装一个软件,那么,有哪些策略可以防止缓冲区溢出带来的危害呢?
- 关闭端口或服务。不必要的端口和服务会给黑客带来更便利的通道。
- 及时安装软件(包括操作系统)厂商的补丁,漏洞一公布,大的厂商就会及时提供补丁。
- 在防火墙上过滤特殊的流量,但需要注意的是,这一招无法阻止内部人员的溢出攻击。
- 如果你有缓冲区溢出相关的知识,可以自己检查程序的漏洞。
- 以必需的最小权限运行软件(这一点之前我们已多次强调)。
注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue 发布时间:2017/12/26 20:53:43