SAST-短小精悍的Benchmark

引言

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

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

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

一、流敏感

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

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

  • 示例
 

Java

复制代码

// 方法一 @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):对控制流分支敏感

 

Java

复制代码

// 方法一 @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时可能会有如下代码场景:

 

Java

复制代码

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.容器域敏感

  • 代码片段一
 

Java

复制代码

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

  • 代码片段二
 

Java

复制代码

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样例举例,就不再写重复的代码了,测试代码如下:

  • 共用方法
 

Java

复制代码

private static String getName(int x, HttpServletRequest req) { // 这里也是需要 path-sensitive 路径敏感 能力的 if (x > 0) { return req.getParameter("name"); } else { return "zhangsan"; } }

  • 代码片段一:
 

Java

复制代码

String username = getName(1, req); String query = "SELECT * FROM my_table where name = " + username; // 4. 执行查询 statement.executeQuery(query); // sink

  • 代码片段二
 

Java

复制代码

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,你爱了吗!

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值