课程表

网络安全课程

工具箱
速查手册

验证用户输入(二)

当前位置:免费教程 » 其他 » 网络安全
注意:本页面内容为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格式。如:

  1. 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文件:

  1. 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注入攻击,将在以后的数据库安全章节中详细介绍。


三、验证用户输入

从之前的内容我们可以看出,防止用户输入攻击的关键在于,对用户输入内容进行充分有效的验证。在验证时应遵循以下原则:

  1. 执行紧缩的验证,只“放过”那些明确是合法的用户输入,而对一切不明的输入都进行拒绝。
  2. 考虑周全,对服务器、数据库需要什么样的数据一清二楚。充分验证用户输入数据的长度、数据类型、值的范围、是否含有敏感字符等,每种编程语言语法不一样,但验证的东西却都是一样的。
  3. 敏感数据,应该从数据库中读取,而不信赖用户的输入。

在很多情况下,我们需要用户输入特定的格式的数据,例如手机号就必须要求用户输入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代码:

  1. void function(char *str) {
  2. char buffer[16];
  3. strcpy(buffer,str);
  4. }

例子中的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指向任何地址。

那么,如何防范缓冲区溢出问题呢?可以从以下方面入手:

  1. 进行完全而周密的用户输入检测,长度、类型不对,应该有响应机制。
  2. 数组定义时就定义好长度,使用时时刻注意不要超过。
  3. 分配或申请内存时,也定义好长度,使用时,不能超过。
  4. 对于不确定的数据缓冲,定义一个量超过时丢弃一部分不需要或可丢弃的。
  5. 在程序指针失效前进行完整性检查。
  6. 用grep来搜索容易产生漏洞的库的调用,比如对strcpy和sprintf的调用。
  7. 利用一些高级查错工具,如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 低危险 确保缓冲区大小与它所说的一样大。

如果我们是程序的使用者,比如,我的服务器上必须安装一个软件,那么,有哪些策略可以防止缓冲区溢出带来的危害呢?

  1. 关闭端口或服务。不必要的端口和服务会给黑客带来更便利的通道。
  2. 及时安装软件(包括操作系统)厂商的补丁,漏洞一公布,大的厂商就会及时提供补丁。
  3. 在防火墙上过滤特殊的流量,但需要注意的是,这一招无法阻止内部人员的溢出攻击。
  4. 如果你有缓冲区溢出相关的知识,可以自己检查程序的漏洞。
  5. 以必需的最小权限运行软件(这一点之前我们已多次强调)。
注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue  发布时间:2017/12/26 20:53:43
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号