【SDL实践指南】Foritify结构化规则定义

基本介绍

结构分析器匹配源代码中的任意程序结构,它的设计目的不是为了发现由执行流或数据流引起的问题,相反它通过识别某些代码模式来检测问题

规则定义

结构树介绍

结构分析器在程序源代码的一个模型上运行,该模型称为结构树,结构树由一组节点组成,这些节点表示程序构造,例如:类、函数、字段、代码块、语句和表达式,结构树中的节点可以有一个父节点和多个子节点,例如:表示字段的节点是表示声明该字段的类的节点的子节点,同样表示表达式的节点是表示表达式所在语句的节点的子节点

以下示例显示了一个小型Java程序的简化结构树的构造,每个示例包括程序源代码、结构树图和说明,这些示例包括用于说明目的的结构树图,为了简单起见这些图排除了一些数据库属性,随着示例程序变得更加复杂

Example 1:程序仅包含具有单个成员字段的类

    class C {   private int f; }

    在结构树中字段与具有fields属性的类相关,该属性列出了类的所有字段

    【SDL实践指南】Foritify结构化规则定义

    Example 2:向类添加一个空函数

    class C {   private int f;    void func() {   }}

    结构树包含函数及其主体块的节点:

    【SDL实践指南】Foritify结构化规则定义

    专门匹配此代码中字段的查询如下所示,其中查询包含对类和字段节点的名称属性的约束,如果重命名类或字段,它将不再与代码匹配

    Field field: field.name == \\\"f\\\" and field.enclosingClass is[Class class: class.name == \\\"C\\\"]

    Example 3:向函数添加一个局部变量声明

    class C {   private int f;    void func() {      int x;   }

    主体块现在具有声明变量的语句的子节点

    Example 4:对字段值执行算术运算并将结果分配给一个局部变量

    class C {   private int f;    void func() {      int x;      x = f + 1;   }}

    结构树现在包含一个赋值语句,它将两个表达式关联起来。左侧表达式(lhs)表示要分配的位置,而右侧表达式(rhs)是要分配的值,赋值右侧的表达式进一步分解为对两个组件的运算(加法):字段和整数,访问字段和变量的表达式包括连接到相应声明的属性

    以下查询匹配程序中的任何赋值,其中写入的位置是一个局部变量并且值的表达式包括一个字段的读取,该字段与函数出现的类属于同一类,这与前面的示例代码相匹配,与示例2中的查询不同它不包含对名称的约束,它的通用性足以匹配程序其他部分中的类似代码模式

    AssignmentStatement a: a.lhs is [VariableAccess:] and a.rhs contains[FieldAccess fa: fa.field.enclosingClass == a.enclosingFunction.enclosingClass]
    规则XML表示

    下面的示例显示了一个结构规则,该规则匹配名为hashcode的函数的所有实例

      <StructuralRule formatVersion=\\\"22.1\\\" language=\\\"java\\\">  <RuleID>5707596F-F163-7D69-35F6-B18C9FEFDB1B</RuleID>  <VulnCategory>Confusing Method Name</VulnCategory>  <DefaultSeverity>2.0</DefaultSeverity>  <Description/>  <Predicate><![CDATA[  Function: name is \\\"hashcode\\\"  ]]></Predicate></StructuralRule>

      Predicate:此元素指定一个或多个结构查询,如果程序构造与任何<Predicate>元素中包含的查询匹配,则Structural Analyzer会报告该程序构造存在漏洞,将<Predicate>元素的内容封装在CDATA部分中以避免需要转义查询中的任何XML特殊字符

      自定义扫描规则
      Leftover Debug

      此场景强调了结构分析器检测调试代码所需的规则,突出显示的问题主要是——遗留的调试代码调试代码可能会暴露已部署应用程序中的意外功能,该场景强调了以下分析和规则概念

      • Function construct objects

      • Slot construct objects

      • Startswith operator

      • Structural rule

      下面的应用程序包含开发人员调用以调试敏感数据检索的方法(调用debugTransactions()方法来检查事务的内容)

      public static List getTransactions(String acctno) throws Exception {  ...  // TODO: remove this before deploying to production  debugTransactions(transactions);  return transactions;}

      以下代码显示了应用程序如何调试事务:

      public static void debugTransactions(List transactions) throws Exception {  Logger debugLogger = Logger.getLogger(TransactionService.class.getName());  debugLogger.setLevel(Level.FINEST);  FileHandler fh = new FileHandler(\\\"debug.log\\\");  fh.setLevel(Level.FINEST);  debugLogger.addHandler(fh);
      for (int index=0; index < transactions.size(); index++) { Transaction proposedTransaction = (Transaction)transactions.get(index);
      debugLogger.finest(\\\"Request transaction statement: \\\" + proposedTransaction.getId()+\\\": \\\" + proposedTransaction.getAcctno() + \\\"; \\\" + proposedTransaction.getAmount() + \\\"; \\\" + proposedTransaction.getDate() + \\\"; \\\" + proposedTransaction.getDescription()); }}

      debugTransactions方法将敏感数据记录在未加密的日志文件中,如果应用程序在生产环境中执行此方法则敏感数据将写入未加密文件,这增加了向第三方意外披露敏感数据的风险,对于漏洞扫描规则这里有一个通用的方法签名用于标识应用程序中的每个调试方法,源代码中的代码说明了每个调试方法的名称都以单词debug开头,此外该方法接受java.util.List类型的一个参数,以下示例中的结构规则标识与此调试签名匹配的所有方法:

      <StructuralRule formatVersion=\\\"22.1\\\" language=\\\"java\\\">  <RuleID>8206ED21-9FB0-44AC-9058-6FCDA601E699</RuleID>  <VulnCategory>J2EE Bad Practices</VulnCategory>  <VulnSubcategory>Leftover Debug Code</VulnSubcategory>  <DefaultSeverity>2.0</DefaultSeverity>  <Description/>  <Predicate><![CDATA[   Function: name startsWith \\\"debug\\\" and     parameterTypes.length == 1 and    parameterTypes[0].name == \\\"java.util.List\\\"  ]]></Predicate></StructuralRule>
      Empty Catch Block

      此场景显示了Structural Analyzer检测空捕获块漏洞的规则,该场景演示了攻击者如何利用空捕获块漏洞,然后显示Structural Analyzer如何使用结构规则来识别此类漏洞,该场景强调了以下分析和规则概念

      • Catch block construct object 

      • Structural rule

      以下代码构建应用程序在后续数据库操作中使用的Hibernate会话,ConnectionFactory类构造函数包含可能引发软件异常的代码

      private ConnectionFactory() {try {  String pFile = System.getProperty(\\\"ConnectionFactory.pfile\\\");  if (pFile != null) {    java.util.Properties props = new java.util.Properties();    props.load( new java.io.FileInputStream(pFile) );    }}catch (Exception e) {  //TODO: fill in this code}...

      要识别代码示例中显示的空catch块,Structural Analyzer应检查每个CatchBlock构造对象的空属性,此布尔属性表示对应的catch块不包含任何代码,以下规则标识空捕获块

      <StructuralRule formatVersion=\\\"22.1\\\" language=\\\"java\\\">  <RuleID>D693090B-3F8C-48BD-BCDE-C6DCA2266710</RuleID>  <VulnCategory>Poor Error Handling</VulnCategory>  <VulnSubcategory>Empty Catch Block</VulnSubcategory>  <DefaultSeverity>2.0</DefaultSeverity>  <Description/>  <Predicate><![CDATA[    CatchBlock: empty   ]]></Predicate></StructuralRule>
      Poor Logging Practice

      此场景演示了一个规则,该规则使StructuralAnalyzer能够识别未声明为静态和最终的日志记录对象,该场景演示了糟糕的日志记录实践,然后它说明了Structural Analyzer使用规则来识别这类问题的方式,该场景强调了以下分析和规则概念:

      • Class construct objects 

      • Contains operator 

      • Field construct objects 

      • Not operator 

      • Structural rule

      好的编程实践是在特定类的所有实例之间共享一个记录器对象并在整个应用程序期间使用同一个记录器,以下示例中应用程序ConnectionClass类的方式有误

      public class ConnectionFactory {  private static Logger log =     Logger.getLogger(ConnectionFactory.class.getName());  private static ConnectionFactory instance = null;

      以下规则报告声明为字段但不同时使用静态关键字和final关键字的java.util.loging.Logger对象的任何实例,Structural Analyzer会检查每个field构造对象的静态和最终属性,如果任一值为假,则字段满足规则的第一组条件,Field构造对象满足第一个条件后,规则检查Field对象的声明类型,该字段必须是java.util.loging.Logger对象的实例或从该类继承的扩展,如果Field构造对象同时满足这两个条件,Structural Analyzer会将字段声明报告为问题

      <StructuralRule formatVersion=\\\"22.1\\\" language=\\\"java\\\">  <RuleID>B95EB686-8EBC-498F-B332-55E31F9DFB8A</RuleID>  <VulnCategory>Poor Logging Practice</VulnCategory>  <DefaultSeverity>2.0</DefaultSeverity>  <Description/>  <Predicate>    Field f: not (static and final) and type.definition.supers contains    [Class: name == \\\"java.util.logging.Logger  </Predicate></StructuralRule>
      Password in Comments

      此场景演示了一个规则,该规则使Structural Analyzer能够检测注释中的密码,这包括密码在注释中的显示方式以及攻击者如何利用此漏洞,然后该场景显示Structural Analyzer如何使用规则来识别此类漏洞,该场景强调了以下分析和规则概念:

      • Comment construct object 

      • Java regular expressions 

      • Structural rule

      如果应用程序的源代码包含生产数据库的身份验证凭据则任何有权访问开发环境及其源代码的人都可以访问生产环境中的数据,以下代码显示ProfileService类中的硬编码凭据

      public class ProfileService {  // NOTE: sample profiles can be reproduced through internal server  // host: db1.riches.com; username: service, password: passw0rd1!{

      以下结构规则标识注释块、内联注释或JavaDoc中包含单词密码的文本

      <StructuralRule formatVersion=\\\"22.1\\\" language=\\\"java\\\">  <RuleID>C938AE93-EA38-403b-ABDA-3F01BEFA7933</RuleID>  <VulnCategory>Password Management</VulnCategory>  <DefaultSeverity>2.0</DefaultSeverity>  <Description/>  <Predicate><![CDATA[    Comment c: (c.doc or c.inline or c.block)        and c.text matches \\\"(?i).*password.*\\\"  ]]></Predicate></StructuralRule>

      上面的规则检查应用程序中每个注释构造对象的doc、inline和block属性,如果这些属性之一为真则注释满足以下条件:它必须是块、内联或JavaDoc注释,然后该规则检查对象文本的文本属性以查看属性值的值是否与Java正则表达式\\”(?i).*password.*\\”匹配,该表达式匹配其值中任何位置包含密码的文本,无论大小写如何

      Dangerous Function Calls

      此场景强调了结构分析器检测危险函数调用漏洞所需的规则,此场景突出显示了——危险方法切勿使用不安全的功能,该场景强调了以下分析和规则概念

      • FunctionCall construct object 

      • Structural rule

      下面的应用程序中存在跨站点脚本漏洞,其中应用程序从用户接收消息并将内容写入数据库而未做任何过滤验证处理,下面的代码示例显示了一种方法,该方法用于在应用程序将消息写入磁盘之前过滤消息中的任何恶意字符:

      private static Message validateMessage(Message incomingMessage) throws Exception {  // Validate sender  String incomingSender = incomingMessage.getSender();  if ((incomingSender == null) || (incomingSender.length() == 0))    throw new Exception(\\\"invalid sender in message\\\");
      // Validate subject String incomingSubject = incomingMessage.getSubject(); if (incomingSubject == null) throw new Exception(\\\"invalid subject in message\\\");
      // Validate severity String incomingSeverity = incomingMessage.getSeverity(); if ((incomingSeverity == null) || (incomingSeverity.length() == 0)) throw new Exception(\\\"invalid sender in message\\\");
      // Validate body String incomingBody = incomingMessage.getBody(); if (incomingBody == null) throw new Exception(\\\"invalid sender in message\\\"); return incomingMessage;}

      以下结构规则将标识应用程序调用MessageService.validateMessage()方法的所有实例

      <StructuralRule formatVersion=\\\"22.1\\\" language=\\\"java\\\">  <RuleID>95C67A96-5AF7-402E-B451-6CEFF4EB8973</RuleID>  <VulnKingdom>API Abuse</VulnKingdom>  <VulnCategory>Dangerous Method</VulnCategory>  <DefaultSeverity>2.0</DefaultSeverity>  <Description/>  <Predicate><![CDATA[    FunctionCall call: call.function.name == \\\"validateMessage\\\" and    call.function.enclosingClass.name ==       \\\"com.fortify.samples.riches.model.MessageService\\\"  ]]></Predicate></StructuralRule>
      Overly Broad Catch Blocks

      此场景演示了overly broad catch如何导致安全问题,同时该场景提供了与构造分析器一起工作的规则示例,以查找由过宽的捕获块导致的漏洞,此场景突出显示了以下漏洞:

      • Poor error handling-broad catch:Catch块处理大量异常,可能会捕获不同的问题或此时不应在程序中处理的问题

      该场景强调了以下分析和规则概念:

      • CatchBlock construct object 

      • Contains operator 

      • Exception construct object 

      • Not operator 

      • ThrowStatement construct object 

      • Structural rule

      以下代码显示了一个overly broad catch Blocks的示例:

      public static void addMessage(Message message) {  Session session = null;  try {    session = ConnectionFactory.getInstance().getSession();    Transaction tx = session.beginTransaction();    session.save(message);    tx.commit();    session.flush();    session.close();    }  catch(Exception e) {    // Treat all exceptions the same here  }}

      理想情况下单独的catch块单独处理特定或相关的安全异常,程序应单独处理这些安全异常以创建跟踪错误和检测安全漏洞所需的审计,并非每一个过于宽泛的捕获块都代表一个问题,例如:下面的代码捕获所有异常并将其抛出调用堆栈

      public static boolean isAdmin(int roleid) throws Exception {  boolean auth = false;  Connection conn = ConnFactory.getInstance().getConnection();  ResultSet rs = null;  try {    Statement statement =  conn.createStatement();    rs = statement.executeQuery(\\\"SELECT rolename FROM auth WHERE       roleid = \\\" + roleid);    rs.next();
      if (rs !=null && rs.getString(\\\"rolename\\\").equals(\\\"admin\\\")) auth = true; conn.close(); } catch(Exception e) { throw e; } return auth;}

      以下代码显示了一个适当宽泛的catch块的示例,该块在所有异常退出程序之前立即捕获它们:

      public static void main(String args[]) {try {  BannerAdServer obj = new BannerAdServer();  BannerAdSource stub =     (BannerAdSource)UnicastRemoteObject.exportObject(obj, 0);
      // Bind the remote object\\\'s stub in the registry Registry registry = LocateRegistry.getRegistry(); registry.bind(\\\"BannerAdSource\\\" stub);}catch (Exception e) { // Process any exceptions that are not handled anywhere else}

      规则需要报告main()方法中未定义的所有过于宽泛的catch块,并且不要将异常抛出到调用堆栈中,以下规则报告满足这些要求的捕获块:

      <StructuralRule formatVersion=\\\"22.1\\\" language=\\\"java\\\">  <RuleID>C9ECD6EC-DAA1-41BE-9715-033F74CE664F</RuleID>  <VulnCategory>Poor Error Handling</VulnCategory>  <VulnSubcategory>Overly Broad Catch</VulnSubcategory>  <DefaultSeverity>2.0</DefaultSeverity>  <Description/>  <Predicate><![CDATA[    CatchBlock: exception.type.name == \\\"java.lang.Exception\\\" and    not contains [ThrowStatement: ] and    not (enclosingFunction.name == \\\"main\\\")  ]]></Predicate></StructuralRule>

      文末小结

      本篇文章就结构化分析器以及结构化规则进行了简单介绍,同时对Foritify中结构化规则的定义进行了说明~

      原创文章,作者:七芒星实验室,如若转载,请注明出处:https://www.sudun.com/ask/34159.html

      (0)
      七芒星实验室's avatar七芒星实验室
      上一篇 2024年4月4日 下午9:32
      下一篇 2024年4月4日 下午9:34

      相关推荐

      发表回复

      您的邮箱地址不会被公开。 必填项已用 * 标注