|
本篇文章根据作者多年来对数百万行代码执行安全分析,得出了在应用层日志方面的脆弱现状。文章讨论了在应用程序的安全方面,日志常常被忽略,并证明了应用程序通过实时的安全检查可以获得许多好处。文章提出了一个可操作的执行思路,并提供了相关的风险和成本分析。
应用程序安全的推动力
开发人员与安全人员开始重视安全编程的实践,实际上,IT厂商也正在利用这个趋势来向用户保证应用程序与基础设施的安全性。然而不幸的是,许多人认为访问控制和加密差不多囊括了应用程序安全的全部领域。
实际上,一些其它的领域也包含在应用程序安全中。OWASP Guide是识别这些领域的一个很好的出发点。工业界的开发小组对OWASP-identified领域有着不同的认识。例如,大部分开发者都已经认识到他们不能开发自己的加密算法,然而很少人意识到用伪随机数产生器(例如java.util.Random)产生Session IDs所带来的问题。另外一个被很多人忽视的特别领域就是日志与监控。
检测应用层的攻击
在一个典型的企业环境下,几乎很少应用程序内置了检测并通知潜在恶意行为的功能,相反,这些软件依靠基础设备,如防火墙,来执行这个功能。但是,不可避免地会存在一些攻击,它们可能绕过设备的保护机制:例如,来自内部网络的拒绝服务攻击,或者绕过客户端的验证并被企业网络认为合法流量的攻击。当然阻止其中的一部分攻击是网络层的入侵检测系统和入侵阻止系统的任务,但是随着网站服务的出现、内部网络和网络边界合作访问的发展,单独的IDS/IPS系统可能不足以检测出这些攻击。
当然,为每个应用程序定置一个入侵检测引擎,这是不切实际的。实际上,这种配置入侵检测引擎的方法与应用程序安全的原则相矛盾:“要安全地编程,而不是另外再编写一个安全工具”。一种更可行的办法是:使用一个日志文件分析引擎来实时地读取日志文件,运用智能的规则来查找攻击模式。这类工具已经存在,如Sawmill和Network Intelligence,它们并不是为某个特定的应用程序日志文件分析而设计,通过配置,它们能满足多个应用程序的需求。本文并不深入讨论这些工具;本文的重点是如何使应用程序符合这些工具的要求。大多数应用程序缺乏的是用标准的格式和协调的方法来记录日志,使得分析引擎能够从日志文件中提取有用信息。
实例一:应用程序中的认证日志
许多应用程序,包括那些符合CMM Level 5组织的应用程序,都在使用并不协调的日志模式。下面的例子展示了一个Java程序通过Log4J来记录非法验证请求的过程。需要注意的是,在C#.Net领域,这个例子必须采用Log4net而不是Log4J。本文假设已经在使用一些日志工具,如何配置和部署这样的工具在此并不作讨论。
if (!request.password.equals(result.password))
{ //user supplied wrong p/w
log.debug("Invalid access attempt for " + request.usernane + "
with password " + request.password);
...
同一个应用程序在用户认证模块可能采用下面这个不同的接口:
if (myRequest.getPassword() != data.getPassword()) {
log.info("Login failed");
...
除了没有对密码进行加密这个明显的安全漏洞之外,上面的编码方法还有一些其它的问题:
两个例子采用不同的日志等级(“debug”和“info”)来记录失败的认证尝试。这就意味着每个程序员必须单独决定一个日志事件的敏感度。当一个程序员认为这是一个调试事件时,另一个程序员可能认为它稍微有些重要于是把它确定为第二级(“log”)日志,这就意味着这些事件将写到不同日志文件中。对失败认证尝试的相关分析将会变得很复杂甚至不可能。
两个例子使用了不同的语法。这将再次归因于每个程序员在记录日志时的个人习惯。许多程序员把低级日志当作调试信息的扩展,并不遵循相关的标准。上面的例子仅仅展示了两个程序员输出的不同;在实际的应用程序开发中,可能有几十个、几百个甚至几千个程序员都采用他们各自的语法,这将导致分析这些信息会是一个可怕的事情。而且这也无法保证如果日志发生改变,在软件新版本中这些分析将仍然有意义。
Log4J、Log4Net以及大部分其它类似的工具将会把调用者传过来的参数当成日志准确地记录。如果应用程序使用某个日志分析工具,它期望一个特殊的语法(例如,“AppName, LogMessage, TimeStamp, Date”),那么开发者将被强迫使用这个语法来记录日志事件。如果使用一个新的分析工具或者老工具的语法发生了变化,那么开发者必须更新应用程序中每个相关的调用函数。
这些事件是与安全相关的,但是并不能体现出区别,它们将与调试信息(例如,未初始化变量)、业务逻辑错误(例如,有一个表格域没有填写)和系统错误(例如,数据库服务器死机)一起写到日志文件中。
实例二:应用程序的SQL注入日志
让我们来看另一个实例:一个输入验证程序发现一个字符串可能用于SQL注入攻击。注意,这个例子中使用“黑名单”或者“已知危险字符”的检验方法,这种方法虽然不是一个完美的输入验证方法,但是经常使用:
if (!request.desc.indexOf(‘;’) != 0)
{ //possible SQL Injection character
log.fine("Possible SQL injection character ';' in request.desc
value of " + request.desc);
同样,攻击的严重程度是由程序员来判断的。这里没有一个标准的信息让自动分析引擎决定是否存在输入验证攻击。单一的恶意字符可能是无意的,但是如果多个这样事件可能就要引起注意了。由于没有办法把这些事件实时地关联起来,操作人员就无法适当地对此事件作出反应(例如,锁定恶意的IP地址)。
在这个例子中,必须更加协调地记录日志。试想,如果一个攻击者试图从不同的输入域执行SQL注入攻击,这些域常常会映射到数据访问对象层(Data Access Object Layer)的不同实例(例如,CustomerDAO.java、OrderDAO.java等等)。如果这些实例是由不同的程序员开发的,那么记录日志的语法很可能不同。下面是常规日志文件的可能输出:
1st event:
Warning: SQL Injection detected: "Some description' OR 1=1 --;"
...
2nd event:
Possibility of malicious input, found character "-" in input field.
如果是这样,则攻击者可以手动或者自动地对输入域进行Fuzzing攻击,而安全操作人员可能无法发现应用程序正受到威胁。分析上面的输出,如果不知道每个程序员使用的语法的话,一个分析工具怎样才能把上面的两个事件关联起来,预告可能的SQL注入攻击呢?
采用集中处理器解决问题
解决这些问题的一个方法是:根据应用程序逻辑设计一个集中的日志处理器。创建一个静态类,我们把它命名为“AppLog”以便讨论。AppLog实际上是当前应用程序与日志分析工具之间的适配器。AppLog至少提供四个公共的调用方法:
·AppLog.logDebugMessage(logMessage, logPriority, callingObject)
·AppLog.logSecurityMessage(logMessage, logPriority, callingObject)
·AppLog.logBusinessLogicError(logMessage, logPriority, callingObject)
·AppLog.logSystemMessage(logMessage, logPriority, callingObject)
这个方法强制编程人员选择一种日志类型,而不是简单地选择一个优先级,同时logPriority参数为他们保留了选择优先级的功能。CallingObject参数允许AppLog自动地扩展调用实例的类型。此外,AppLog可以在实际写入日志文件或者使用Log4J和Log4Net之前为某个特定的日志分析工具加入语法参数;可以为每个分析工具建立新的AppLog子类。除了AppLog和其子类的执行,程序员不必关心外部分析工具的具体语法。
但是仅仅这样并不能解决上述所有问题。两个开发人员仍然可以把不同的消息传递给logSecurityMessage()函数。为了解决此问题,程序员必须在AppLog加入针对每个安全事件的特殊处理函数,如logInvalidAccessAttempt(user_id, timestamp, callingObject)和logSQLInjectionAttempt(user_id, timestamp, maliciousChar, inputString, callingObject)。这些函数将构建不同的消息然后调用logSecurity函数。最终记录的事件可能是:
"code:312,app:MyApp,event:Invalid_auth_attempt,user:admin,time:3713252,
calling-obj:com.mycompany.package.MyClass"
这种语法对特定的日志分析工具来说是有意义的,如果分析工具发生了改变,那么我们只要简单地改变AppLog或者它的子类的函数,而不是整个应用程序的日志处理函数。由于开发人员不必去揣测用哪个英文短语来描述非法的访问尝试(例如,“Login failed”),不同模块的开发人员在记录非法访问尝试时会使用相同的语法。
让我们再回顾一下SQL注入的例子,集中处理函数两次调用logSQLInjectionAttempt函数,产生的日志文件可能是:
1st event:
"code:312,app:MyApp,event:sql_injection_attempt,user:myuser1,mal-char:"-",input-string:"
Some description' OR 1=1 --;
",time:371245,calling-
obj:com.mycompany.DAO.CustomerDAO"
2nd event:
"code:312,app:MyApp,event:sql_injection_attempt,user:myuser1,mal-char:"-",input-
string:"An order name' OR 1=1
--;",time:371253,calling-
obj:com.mycompany.DAO.OrderDAO"
管理员可以配置日志分析工具,使它们能解析出同一个用户在一个很短的时间内有两次或者更多次SQL注入尝试,这通过协调的日志格式很容易就能做到。当然,如果你的应用程序遭到这种常规方式的注入尝试,也许在两个SQL注入尝试之后就给安全操作人员发出警告,可能会使安全操作人员对分析感到厌烦。在这种情况下,管理员可以简单地配置工具,在足够的注入尝试次数(如100或者1000)之后再给安全操作人员发出警告。
记录日志是基础设施的责任?
一些人可能会认为,这个方法用来解决应用层日志的问题是错误的。他们主张,这些功能必须在基础设施的某个地方实现,例如,LDAP目录或者Web应用程序防火墙。但是,在基础设施层记录每一个安全日志是不可能的,通过程序开发人员处理安全问题有很大的益处。此外,如果没有应用程序逻辑,有一些安全事件根本无法检测到,例如,在注册页面有些用户输入了错误的验证码值。因此,开发人员应该与底层的基础设施一起共同识别与安全相关的事件。
低成本的投资
除了日志分析引擎的成本外,建立这样一个日志处理系统是一个相当实惠的实践。执行这个方法只要制定一套标准,然后建立一个简单的适配器依附于这个标准,并把这个标准告诉开发人员。即使在短期内可能不会使用日志分析工具,如果在应用程序设计时增加这项功能,那么在最终使用日志分析工具时将可以很方便地运用这项功能。还有,标准化工作使得日志易于读取和理解。大部分企业级开发人员承认在发现问题需要处理时,他们依靠应用程序的日志,但是对这些日志进行分类整理是一件非常困难、非常耗时的工作,因为这些日志数量很大而且语法非常随意。采
【责任编辑 王凡】
|