长亭百川云 - 文章详情

SAST-短小精悍的Benchmark

CodeAnalyzer Ultra

73

2024-07-13

引言

相信大家对DevSecOps这个概念已经不陌生了,其是一种软件开发理念,强调在DevOps的过程中融入安全性,即将安全实践融入到开发、测试和部署的全生命周期中。DevSecOps的目标是在快速迭代的软件交付过程中,通过自动化工具和持续监控的方式,确保安全措施的有效实施,从而降低安全风险,实现安全左移,提高软件质量和可靠性。

静态代码分析工具自然就成为了开发流程中不可或缺的一部分。熟悉SAST工具的朋友们可能都知道,其最头疼的问题就是漏报/误报问题,SAST工具扫描出来的结果还需要具备安全+开发能力的人员去审计。因为在用户看来,可能很多问题都是误报,这样就增加了SAST工具的使用成本。若SAST工具的分析能力足够强大,将会很大程度上降低其维护成本。

影响SAST工具核心分析能力的是敏感性分析,包括流敏感路径敏感域敏感上下文敏感等。接下来,我们结合具体的代码示例来看这几个敏感性概念。

一、流敏感

流敏感(Flow Sensitive):对语句的执行顺序敏感

也就是说,引擎是可以感知语句的执行顺序的,若执行顺序改变影响到了最终的分析结果,引擎也是可以感知的。

  • 示例
`// 方法一   @Override   protected void doGet(HttpServletRequest req, HttpServletResponse res) {       String username;       username = "SASTing"; // 赋值在username被污染前执行       // 用户请求中的数据认为是有风险的,所以被认为是污染源       username = req.getParameter("name"); // tainted source       // 污染源被拼接到sql语句中       String sql = "select * from users where username = " + username; // add username to sql       // 模拟sql语句执行,被污染的sql被执行了,所以是被认为是爆发点       executeSql(sql); // sink   }      // 方法二   @Override   protected void doGet(HttpServletRequest req, HttpServletResponse res) {       String username;       username = req.getParameter("name");       username = "SASTing"; // 赋值在username被污染后执行       String sql = "select * from users where username = " + username;       executeSql(sql);   }   `

对比以上两个方法,显然方法一是有SQL注入风险的,方法二是没有SQL注入风险的,因为在方法二中,usernamesql拼接前被重新赋值为"SASTing"

若SAST工具支持流敏感分析,那么其是可以感知语句间的执行顺序的,也就是说,可以感知username被污染和被重新赋值的执行顺序,所以在方法二中不会产生误报;相反,不支持流敏感分析的就会有误报产生。

流不敏感的分析可能会这么处理:

针对变量username,只要方法内有一条语句对其进行了污染(即使在污染后对username做了重新赋值),那么在当前方法内,username就被认为是污点数据, 从而造成误报。

二、路径敏感

路径敏感(Path Sensitive):对控制流分支敏感

`// 方法一   @Override   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {       // bad       String input;       int x = 3;       // path-sensitive       if (x > 0) {           input = req.getParameter("input1"); // source       } else {           input = "safe input";       }       resp.getWriter().write(input);  // sink   }      // 方法二   @Override   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {       // good       String input;       int x = 3;       // path-sensitive       if (x < 0) {           input = req.getParameter("input1");       } else {           input = "safe input";       }       resp.getWriter().write(input);   }   `

上述两个方法,数据流是这样的:

  • source:从用户请求中获取参数"input1"(tainted data)

  • sink:tainted data 传递给resp.getWriter().write()方法,被直接返回给前端页面

  • 因此会有反射型XSS风险

但是,很明显方法二是没有风险的,因为方法二中的if条件永远为false,所以input永远为"safe input"字符串,所以不存在风险。

若分析引擎可以识别方法二中的分支条件,不会造成误报,那么大概率可以判断是路径敏感(path-sensitive)的。

路径不敏感的分析可能会这么处理:

if 块和 else 块内的数据都流向数据读取处,然后对数据做类似这样的merge处理:merge(unsafeData, safeData) = unsafeData,从而造成误报。

路径敏感一般的实现方式有:常量传播抽象解释符号执行、**SSA(静态单赋值)**等。

三、域敏感

域敏感(Field Sensitive):对类字段/容器域敏感

1.类成员敏感

在使用JDBC+mysql时可能会有如下代码场景:

`static final String JDBC_URL = "jdbc:mysql://localhost:3306/mydatabase";   static final String USERNAME = "username";   static final String PASSWORD = "password";      @Data   private static class User {       private String name;       private String gender;   }      // 方法一   private void test_bad(HttpServletRequest req) throws SQLException, ClassNotFoundException {       Connection connection = null;          // 1. 加载驱动程序       Class.forName("com.mysql.cj.jdbc.Driver");          // 2. 建立数据库连接       connection = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);          // 3. 创建Statement对象       Statement statement = connection.createStatement();          String username = req.getParameter("name");  // tainted source       User user = new User();       user.setName(username);          // 4. 执行查询       String query = "SELECT * FROM my_table where name = " + user.getName(); // field-sensitive -> user.getName() tainted       ResultSet resultSet = statement.executeQuery(query); // sink       // 6. 关闭资源       connection.close();   }      // 方法二   private void test_good(HttpServletRequest req) throws ClassNotFoundException, SQLException {       Connection connection = null;          // 1. 加载驱动程序       Class.forName("com.mysql.cj.jdbc.Driver");          // 2. 建立数据库连接       connection = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);          // 3. 创建Statement对象       Statement statement = connection.createStatement();          String username = req.getParameter("name");  // tainted source       User user = new User();       user.setName(username);          // 4. 执行查询       String query = "SELECT * FROM my_table where name = " + user.getGender(); // field-sensitive -> user.getGender() not tainted       ResultSet resultSet = statement.executeQuery(query); // not sink       // 6. 关闭资源       connection.close();   }   `

可以看到,以上方法一和方法二的主要区别在于:

方法一user对象的name成员被污染,然后将user.getName()拼接到sql语句

方法二user对象的name成员被污染,然后将user.getGender()拼接到sql语句

显然,方法二是没有sql注入风险的

若分析引擎可以很好地识别被污染对象的具体成员,方法二的情况不会发生误报,那么其大概率是域敏感的。

非域敏感的分析可能会这么处理:

一个对象的某一个成员被污染了,就把该对象整体当作是一个被污染的对象,后续获取该对象的所有成员都认为是被污染的数据,从而造成误报。

2.容器元素敏感

  • 代码片段一
`List<String> usernames = new LinkedList<>();   String username = req.getParameter("name");  // tainted source   usernames.add("zhangsan");   usernames.add(username); // passthrough      String query = "SELECT * FROM my_table where name = " + usernames.get(1); // usernames.get(1) is tainted   ResultSet resultSet = statement.executeQuery(query); // sink   `
  • 代码片段二
`List<String> usernames = new LinkedList<>();   String username = req.getParameter("name");  // tainted source   usernames.add("zhangsan");   usernames.add(username); // passthrough      String query = "SELECT * FROM my_table where name = " + usernames.get(0); // usernames.get(0) is not tainted   ResultSet resultSet = statement.executeQuery(query); // not sink   `

可以看到,两个代码片段的区别:

被污染的username被加到了usernames的第二个位置

  • 代码片段一将usernames.get(1)拼接到sql中,然后执行

  • 代码片段二将usernames.get(0)拼接到sql中,然后执行

显然,片段二是不会造成SQL注入风险的。

若分析引擎能够很好地支持这种容器相关的污点数据流分析,分析片段二时不会误报,那么其大概率是域敏感的。

非域敏感的分析可能会这么处理:

一个容器中的某一个元素被污染了,就把该容器整体当作是一个被污染的容器,后续从容器内获取的所有元素都是被污染的对象,从而造成误报。

四、上下文敏感

上下文敏感(Context Sensitive):对方法调用的上下文敏感

还是拿JDBC样例举例,就不再写重复的代码了,测试代码如下:

  • 共用方法
`private static String getName(int x, HttpServletRequest req) {       // 这里也是需要 path-sensitive 路径敏感 能力的       if (x > 0) {           return req.getParameter("name");       }       else {           return "zhangsan";       }   }   `
  • 代码片段一:
`String username = getName(1, req);   String query = "SELECT * FROM my_table where name = " + username; // 4. 执行查询   statement.executeQuery(query); // sink   `
  • 代码片段二
`String username = getName(-1, req);   String query = "SELECT * FROM my_table where name = " + username; // 4. 执行查询   statement.executeQuery(query); // sink   `

观察上述代码可以发现:

  • 片段一获取的username是被污染的数据

  • 片段二获取的username是安全的数据

二者获取的最终数据是否被污染是由传入方法getName(int x, HttpServletRequest req)的参数x决定的,也就是由getName()方法调用的上下文决定的。

若分析引擎可以很好地识别方法调用的上下文,那么其大概率是上下文敏感的。

上下文不敏感的分析可能这么处理:

在遇到方法调用时,不考虑方法调用的上下文信息,上述示例中可能将getName()方法的返回值通过某种规则认为其返回值永远是tainted data或者是safe data,从而造成误报或漏报。

总结

若你想要测试一款SAST分析引擎的分析能力,直接拿本文的测试样例去跑,根据测试结果就能大概评估引擎的分析能力了,这么短小精悍的Benchmark,你爱了吗!

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2