简述污点分析及SonarQube中污点分析配置

maijun 发表于 2021/04/23 15:36:06 2021/04/23
【摘要】 污点分析技术是一种常用的软件分析技术,广泛应用在软件静态代码分析中,本文将简单介绍污点分析的部分基本概念,及如何在SoanrQube中配置污点分析规则的信息。

污点分析技术是一种常用的软件分析技术,广泛应用在软件静态代码分析中,本文将简单介绍污点分析的部分基本概念,及如何在SoanrQube中配置污点分析规则的信息。

1. 污点分析的关键概念

1.1 污点分析及传播类型

污点分析,即通过跟踪污点数据的传播,来识别程序中存在的问题的一种方法。很多典型的问题,例如注入类、敏感信息泄露等问题(规整点儿来讲,注入类问题破坏数据的完整性,名信息泄露破坏数据的保密性),都可以基于污点分析实现。下面举一个简单的例子:

public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    // some code
    response.setContentType("text/html;charset=UTF-8");

    String param = "";
    if (request.getHeader("BenchmarkTest00008") != null) {
        param = request.getHeader("BenchmarkTest00008");                   // 污点数据入口,从 HTTP Request 获取到的数据
    }

    // URL Decode the header value since req.getHeader() doesn't. Unlike req.getParameter().
    param = java.net.URLDecoder.decode(param, "UTF-8");                  // 污点传播,decode数据

    String sql = "{call " + param + "}";                                                    // 污点传播,污点数据拼接到 sql 中

    try {
        java.sql.Connection connection =
                org.owasp.benchmark.helpers.DatabaseHelper.getSqlConnection();
        java.sql.CallableStatement statement = connection.prepareCall(sql);                // 构造 Statement
        java.sql.ResultSet rs = statement.executeQuery();                        // 漏洞爆发点,执行SQL语句
        org.owasp.benchmark.helpers.DatabaseHelper.printResults(rs, sql, response);

    } catch (java.sql.SQLException e) {
        if (org.owasp.benchmark.helpers.DatabaseHelper.hideSQLErrors) {
            response.getWriter().println("Error processing request.");
            return;
        } else throw new ServletException(e);
    }
}

可以参考上面的注释,上面从http request中,获取到了一个数据,并且数据没有净化处理,直接拼接到了sql语句中,因此可能导致一个SQL注入的问题。

污点传播主要有两种类型:

(1) 显式污点传播

显示污点传播,即污点传播仅仅在数据依赖上面传递,如上面的例子,污点传播的各个数据之间,都有非常明确的依赖关系。这种类型的污点传播,一般都可以进行很好的分析。

(2) 隐式污点传播

隐式污点传播,即污点数据在传播时,不仅仅通过数据依赖产生关系,而且通过控制依赖传递。

请查看下面的函数定义:

public static String getStr(String str) {
    if (str == null || str.trim().length() == 0) {
        return "";
    }

    StringBuffer sb = new StringBuffer();
    char[] chars = str.toCharArray();
    for (char c : chars) {
        int j = 0;
        for (int i = 0; i < c; i ++) {
            j ++;
        }
        sb.append((char)j);
    }
    return sb.toString();
}

对上面的方法进行分析,我们可以知道该方法除了在入参为空或者为null的情况下,返回空字符串以外,都是返回和原来入参相同的值,但是,返回值并不是直接由入参直接赋值得到的。因此,上面入参和返回值之间,没有直接或者间接的数据依赖(显式依赖关系),但是,如果入参是污染数据,则污点标记也应该通过隐式污点传播传递给返回值。

1.2 污点传播关键概念

在进行污点分析中,工具实现污点分析引擎,需要在引擎上面设置一些关键节点的定义,下面简单介绍和污点分析强相关的部分概念定义:

(1) 污点源(source)

即污点数据的入口。一般来说,外部数据均为不可信数据,外部数据包括但不仅限于:配置文件、数据库、网络、文件、命令行传参等。只要是从外部获取数据的接口,全部可以认为是污点源,例如第1个例子,从 http request 的 header 中获取的数据。

(2) 污点传播点(passthrough)

即污点数据传入到该点之后,污点数据是怎么传播下去的。例如上面 String sql = "{call " + param + "}"; 语句,param 是污点数据,经过一个 字符串拼接 的操作,将 污点信息 传递给了后面整个表达式,然后通过一个 赋值操作,将 污点信息 传递给了 变量sql。

我们在定义这些点的时候,可能需要关注的一些信息:

a) 如果该点是函数操作,则需要关注哪个参数为污点数据时会有问题;如果有污点数据传入,哪些结果会被污染,是返回值,还是其他入参?

b) 如果是基本操作,则需要关心污点数据的类型,并不是所有类型的污点数据在特定操作下,都可以把污点传递下去。

(3) 爆发点(污点坑,sink)

即污点数据到达该点之后,将会存在问题的点,例如第1个例子中的 statement.executeQuery()。在该点执行后,将发生注入或者敏感信息泄露或者其他让咱们觉得不舒服的地方。

(4) 净化点(sanitizer)

如果在该点,错误地认为污点可以向下传递,则可以设置该点为净化点,当污点数据通过该点之后,可以认为该污点数据不再是污点数据。

设置净化点的一个很重要的原因,是很多静态代码分析引擎,在遇到不认识的函数时,对该函数的默认处理是不改变污点信息,这样讲由此导致误报或者漏报。合理设置净化点,可以有效减少误报,而且因为清理掉了污点标记,可以减少数据分析的量,从而提高分析效率。

一般净化点可能有:自定义的数据校验函数、加密库函数、针对注入类问题数据的数据处理函数等。

(5) 污点标记(taint flag)

即该污点数据可能导致的问题的一个标记,例如注入类问题,也有SQL注入、LDAP注入,JSON注入等各种类型,一个污点数据,不见得就会引起所有的问题类型,因此,在部分分析场景里面,可以针对特定的污点,设置污点标记,例如通过某个方法获取的数据,只会引起SQL注入的问题,不会引起敏感信息泄露的问题等。

2. SonarQube中安全规则配置

SonarQube说句实话,能力很差,不过还是能用一下,下面主要梳理一下SonarQube安全配置设置,主要以 Java 为例,支持为下面的规则设置source, sink,passthrough和sanitizer:

2.1 MethodId获取

在 SonarQube中,只支持配置函数类型的相关污点传播节点。传播时,需要设置函数的MethodId,Java语言的MethodId是从字节码中获取的。

我们来尝试获取 System.getenv() 函数的MethodId:

(1) 先写一段程序,包含需要的函数调用

class Test {
    public static void main(String[] args) {
        System.out.println(System.getenv().get("abc"));
    }
}

(2) 先执行 javac,再执行 javap,如下:

$ javac Test.java
$ javap -c Test.class

javap执行的输出如下:

Compiled from "Test.java"
class Test {
  Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: invokestatic  #3                  // Method java/lang/System.getenv:()Ljava/util/Map;
       6: ldc           #4                  // String abc
       8: invokeinterface #5,  2            // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
      13: checkcast     #6                  // class java/lang/String
      16: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      19: return
}

从这个 javap 的输出,可以分成清晰的看到,System.getenv() 注释有:java/lang/System.getenv:()Ljava/util/Map

(3) 然后执行如下操作:

a) 将 package 中的 路径分隔符(/) 替换为英文字符的小数点(.);

b) 移除里面的所有的冒号;

c) 将类名和函数名之间的小数点替换为 #.

最终产生的结果就是:java.lang.System#getenv()Ljava/util/Map

上面介绍了 java 语言的 MethodId 的获取方法,如果要获取 PHP,C#,Python等语言的 MethodId,可以自行参考:https://docs.sonarqube.org/latest/analysis/security_configuration/

2.2 安全配置文件

针对 Java 语言,安全配置的一个例子:

{
  "S3649": {
    "sources": [
      {
        "methodId": "my.package.ServerRequest#getQuery()Ljava/lang/String;"
      }
    ],
    "sanitizers": [
      {
        "methodId": "my.package.StringUtils#stringReplace(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
      }
    ],
    "passthroughs": [
      {
        "methodId": "my.package.RawUrl#<init>(Ljava/lang/String;)V",
        "isWhitelist": true,
        "args": [
          1
        ]
      }
    ],
    "sinks": [
      {
        "methodId": "my.package.MySql#query(Ljava/lang/String;)V",
        "args": [
          1
        ]
      },
      {
        "methodId": "my.package.SqlStatement#execute",
        "isMethodPrefix": true,
        "args": [
          0,
          1
        ]
      },
      {
        "methodId": "my.package.SqlStatement#run(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
        "interval": {
          "fromIndex": 1
        }
      }
    ]
  },
  "S5131": {
    "sources": [
      {
        "methodId": "my.package.ServerRequest#getQueryString()Ljava/lang/String;"
      }
    ],
    "sinks": [
      {
        "methodId": "my.package.Server#write(",
        "isMethodPrefix": true,
        "interval": {
          "fromIndex": 1
        }
      }
    ]
  }
}

对上面的部分参数进行说明:

(1) methodId:上一节已经有介绍;

(2) args:表示在函数调用时,第几个参数可以接收污点数据,函数调用的下标从1开始,方法调用的下标从0开始,并且下标0表示当前对象(例如java中的 this);

(3) interval:由fromIndex整数组成的对象,其中每个整数表示方法签名中检测受污染值的位置;

(4) isMethodPrefix:如果设置了,则遇到函数会尝试匹配全部能匹配到 prefix 为 MethodId 的方法调用;

(5) isShallow:在自定义规则中不需要使用;

(6) isWhitelist:对污点值是否继续传播跟踪。

完整格式可以参考:https://github.com/SonarSource/sonarqube/blob/master/server/sonar-webserver-webapi/src/main/resources/json-schemas/security.json

2.3 配置到SonarQube服务

可以配置到工程级别或者全局级别,页面导航如下:

  • Project level – Project Settings > General Settings > SAST Engine > JAVA/PHP/C#/Python custom configuration
  • Global level – Administration > General Settings > SAST Engine > JAVA/PHP/C#/Python custom configuration

3. 参考资料

https://docs.sonarqube.org/latest/analysis/security_configuration/

https://github.com/SonarSource/sonarqube/blob/master/server/sonar-webserver-webapi/src/main/resources/json-schemas/security.json

https://github.com/OWASP/Benchmark

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。