戳上面的蓝字关注我吧!
01
前言
上篇关于《SpringInspector源码分析文章》中提到过“污点传播过程中清洗函数怎么定义?能否通过自然语言处理提取其特征来自动化检测?”,近段时间才得空能接着上次的想法继续思考,由于SpringInspector检测工具对规则的依赖性比较强,因此检测污点分析中的过滤函数也是精进工具必不可少的一环。
常用的程序代码检测技术主要分为两类,即:
属性计数(Attribute Counting,AC):针对程序代码的各种统计属性进行处理,从程序代码中提取多个软件度量特征,然后再用这些特征比较程序从而得到程序相似度。该方法也是目前为止软件科学度量方法中最早的典型属性计数法。
基于结构度量(Structural Metrics,SM):该方法加入了程序内部结构信息的比较。
而目前的匹配技术来实现相似度计算的方法有:
点阵图(dotplot)
余弦相似度
Levenshtein距离法
最长公共子序列(Longest Common Subsequence,LCS)
求最长公共子串RKR-GST法
而对此代码相似度的检测方案也有两个思路:
权重比较法,获取代码中的关键词来比较权重。
解析AST,并使用深度优先遍历DFT来生成文本向量,之后再对文本向量进行相似度分析。
02
**关键词权重比较法
**
关键词权重比较方法是传统意义上的相似度比较方法,是通过捕获源代码中是否存在某些关键词,然后更具优先程度来计算余弦相似度的一种方式。
其中正则匹配阶段有使用JavaParase的方法读取其方法的属性,进一步准确的提取相关特征。
删除注释的方式可以通过正则匹配,过滤掉单行和多行注释
`String code = n.getBody().get().toString().replaceAll("(?<!:)\\/\\/.*", "").replaceAll("\\/\\*(\\s|.)*?\\*\\/", "");``System.out.println(code);`
有了过滤完的代码段之后,就只需要对其提取特征并判断
`float isName = (n.getName().toString().toLowerCase().contains("xss"))? 1 : 0; //方法名称中是否有Xss关键词``Parameter p = n.getParameters().get(0);``float isParam = (p.getTypeAsString().contains("String"))? 1 : 0; //参数中是否有String类型`` ``String code = n.getBody().get().toString().replaceAll("(?<!:)\\/\\/.*", "").replaceAll("\\/\\*(\\s|.)*?\\*\\/", ""); //过滤方法体中的注释`` ``Pattern pat = Pattern.compile("([a-zA-Z0-9_.&%]+)+"); //实例化Pattern对象``Matcher mat = pat.matcher(code); //实例化Matcher对象``float isReplace = 0;``float isScript = 0;``float isOnEvent = 0;``float isUnEscapeHtml4 = 0;``while(mat.find()) {` `String str = mat.group().toLowerCase();` `if(str.contains("replace")) { //方法体是否有调用Replace方法` `isReplace = 1;` `}` `if(str.contains("script")) { //常见Script标签` `isScript = 1;` `}` `if(str.equals("onmouseover") || str.equals("onclick") || str.equals("onunload") || str.equals("onerror")) { //常见的事件` `isOnEvent = 1;` `}` `if(str.contains("StringEscapeUtils") || str.contains("unescapeHtml4")) { //HTML实体化方法` `isUnEscapeHtml4 = 1;` `}``}``flag = new float[]{(float) isName, isParam, isReplace, isScript, isOnEvent, isUnEscapeHtml4};`
上述代码片段展示了是如何提取相关方法特征的,最后通过余弦相似度算法计算
`package javaTest;`` ``import java.io.FileInputStream;``import java.io.FileNotFoundException;``import java.util.ArrayList;``import java.util.Arrays;``import java.util.List;``import java.util.regex.MatchResult;``import java.util.regex.Matcher;``import java.util.regex.Pattern;``import java.util.stream.Collectors;`` ``import com.github.javaparser.StaticJavaParser;``import com.github.javaparser.ast.CompilationUnit;``import com.github.javaparser.ast.body.MethodDeclaration;``import com.github.javaparser.ast.body.Parameter;``import com.github.javaparser.ast.visitor.VoidVisitorAdapter;`` `` ``public class SimilarityMain {`` ` `public static ArrayList<Float> vb = new ArrayList() {{` `add(1);` `add(1);` `add(1);` `add(1);` `add(1);` `add(1);` `}};` `public static float flag[] = {(float) 0, 0, 0, 0, 0, 0};` `public static ArrayList<String> vitem = new ArrayList() {{` `add("方法名称中有Xss关键词");` `add("参数中有String类型");` `add("有调用Replace方法");` `add("常见Script标签");` `add("常见的事件");` `add("HTML实体化方法");` `}};`` ` `public static double similarity(ArrayList va, ArrayList vb) {` `if (va.size() > vb.size()) {` `int temp = va.size() - vb.size();` `for (int i = 0; i < temp; i++) {` `vb.add(0);` `}` `} else if (va.size() < vb.size()) {` `int temp = vb.size() - va.size();` `for (int i = 0; i < temp; i++) {` `va.add(0);` `}` `}`` ` `int size = va.size();` `double simVal = 0;`` `` ` `double num = 0;` `double den = 1;` `double powa_sum = 0;` `double powb_sum = 0;` `for (int i = 0; i < size; i++) {` `double a = Double.parseDouble(va.get(i).toString());` `double b = Double.parseDouble(vb.get(i).toString());`` ` `num = num + a * b;` `powa_sum = powa_sum + (double) Math.pow(a, 2);` `powb_sum = powb_sum + (double) Math.pow(b, 2);` `}` `double sqrta = (double) Math.sqrt(powa_sum);` `double sqrtb = (double) Math.sqrt(powb_sum);` `den = sqrta * sqrtb;`` ` `simVal = num / den;`` ` `return simVal;` `}`` ` `public static void generateArrayList(String file) throws FileNotFoundException {` `FileInputStream in = new FileInputStream(file);`` ` `CompilationUnit cu = StaticJavaParser.parse(in);` `new MethodVisitor().visit(cu, null);` `}`` ` `/**` `* Simple visitor implementation for visiting MethodDeclaration nodes.` `*/` `private static class MethodVisitor extends VoidVisitorAdapter {` `@Override` `public void visit(MethodDeclaration n, Object arg) {` `float isName = (n.getName().toString().toLowerCase().contains("xss"))? 1 : 0; //方法名称中是否有Xss关键词` `Parameter p = n.getParameters().get(0);` `float isParam = (p.getTypeAsString().contains("String"))? 1 : 0; //参数中是否有String类型`` ` `String code = n.getBody().get().toString().replaceAll("(?<!:)\\/\\/.*", "").replaceAll("\\/\\*(\\s|.)*?\\*\\/", ""); //过滤方法体中的注释`` ` `Pattern pat = Pattern.compile("([a-zA-Z0-9_.&%]+)+"); //实例化Pattern对象` `Matcher mat = pat.matcher(code); //实例化Matcher对象` `float isReplace = 0;` `float isScript = 0;` `float isOnEvent = 0;` `float isUnEscapeHtml4 = 0;` `while(mat.find()) {` `String str = mat.group().toLowerCase();` `if(str.contains("replace")) { //方法体是否有调用Replace方法` `isReplace = 1;` `}` `if(str.contains("script")) { //常见Script标签` `isScript = 1;` `}` `if(str.equals("onmouseover") || str.equals("onclick") || str.equals("onunload") || str.equals("onerror")) { //常见的事件` `isOnEvent = 1;` `}` `if(str.contains("StringEscapeUtils") || str.contains("unescapeHtml4")) { //HTML实体化方法` `isUnEscapeHtml4 = 1;` `}` `}` `flag = new float[]{(float) isName, isParam, isReplace, isScript, isOnEvent, isUnEscapeHtml4};` `ArrayList<Float> va = new ArrayList();` `for (int i = 0; i < flag.length; i++)` `{` `va.add(new Float(flag[i]));` `}`` ` `SimilarityMain sim = new SimilarityMain();` `double simVal = sim.similarity(va, vb);` `System.out.println("Method Name:" + n.getName() + ", The sim value is:" + simVal);` `for(float a : flag) {` `System.out.print(a+" ");` `}` `System.out.println();` `}` `}`` ` `public static void main(String[] args) throws FileNotFoundException {` `System.out.print("特征列表");` `System.out.println(vitem);` `generateArrayList("C:\\Users\\DELL\\eclipse-workspace\\javaTest\\src\\javaTest\\Hello.java"); //0.81`` ` `}`` ``}`
03
基于AST的代码相似度检测
首先对Java代码进行预处理,减少冗余信息。然后对预处理后的代码进行词法分析、语义分析等步骤,最终生成AST语法树。之后再对生成好的AST转换成字符串序列,再对字符串序列进行相似度计算,检测代码是否相似。
AST解析工具我这里用的是ANTLR4的库类,使用专门的词法分析和语法分析来解析Java源代码。
Antlr (ANother Tool for Language Recognition) 是一个强大的跨语言语法解析器,可以用来读取、处理、执行或翻译结构化文本或二进制文件。它被广泛用来构建语言,工具和框架。Antlr可以从语法上来生成一个可以构建和遍历解析树的解析器。
添加如下依赖
`<dependency>` `<groupId>org.antlr</groupId>` `<artifactId>antlr4-runtime</artifactId>` `<version>4.9.3</version>``</dependency>`
我这里的开发环境IDE是用的eclipse,因此还需要安装一个ANTLR的工具antlr4ide-eclipse-release[3]
安装之后就可以通过*.g4的文件规则生成对应的ANTLR4的词法分析和语法分析类了
插件正常安装之后,可以对*.g4的文件直接生成对应的类了。这里我给出Java8语法下的代码分析规则:
Java8Lexer(词法分析):https://github.com/antlr/grammars-v4/blob/master/java/java8/Java8Lexer.g4
Java8Parser(语法分析):https://github.com/antlr/grammars-v4/blob/master/java/java8/Java8Parser.g4
生成好了之后,可以用ANTLR4生成Java代码的字符串序列,方便之后进行相似度分析。
`public static String getAntlr4String(String path) throws IOException {` `FileInputStream inputStream = new FileInputStream(path); //获取java源代码文件的路径` `Lexer lexer = new Java8Lexer(CharStreams.fromStream(inputStream)); //利用自定义的词法分析器` `TokenStream tokenStream = new CommonTokenStream(lexer); //获取词法分析器生成的Token` `Java8Parser parser = new Java8Parser(tokenStream); //语法解析器` `ParserRuleContext t = parser.compilationUnit();` `return t.toStringTree(parser); //最后讲生成的AST转换为字符串序列组,方便后续进行相似度计算``}`
在这之后该方法会返回字符串序列
这里我用到文本相似度分析框架:https://github.com/shibing624/similarity
采用的是similarity框架的段落文本相似度分析计算
`public static void main(String[] args) throws ExitException {` `System.out.println("Antlr4 Examples");` `try {` `String text1 = getAntlr4String("C:\\Users\\DELL\\eclipse-workspace\\javaTest\\src\\javaTest\\Hello.java");` `String text2 = getAntlr4String("C:\\Users\\DELL\\eclipse-workspace\\javaTest\\src\\javaTest\\Hello2.java");` `// System.out.println(text1);` `// System.out.println(text2);` `TextSimilarity cosSimilarity = new CosineSimilarity();` `double score1 = cosSimilarity.getSimilarity(text1, text2);` `System.out.println("cos相似度分值:" + score1);`` ` `TextSimilarity editSimilarity = new EditDistanceSimilarity();` `double score2 = editSimilarity.getSimilarity(text1, text2);` `System.out.println("edit相似度分值:" + score2);`` ` `} catch (IOException e) {` `e.printStackTrace();` `}``}`
这里用常见的XSS修复代码案例来做个测试:
上述两个Xss过滤代码是我从GitHub上检索到的公开源码,对其进行相似度查询:
首先是Cos余弦相似度分值:
然后是Edit编辑距离相似度分值:
可以看到两个分值一个在0.99,一个在0.49。相差还是蛮大的,而且Edit编辑距离算法用到递归的特性,所以运行时间要比余弦相似度更久,
经过几次比对之后发现,就算用两种不相干的方法片段,其Cos余弦相似度还是会一样,因此在这里就只能选择Edit编辑距离算法。当然读者也可以在这里替换其他准确度更高的相似度比较算法。
最后,不管是上述哪种方法,都有一定的阈值需要经过多次测试才能确保检出的稳定性。
04
Reference
[1].https://www.cnblogs.com/clonen/p/9083359.html
[2].https://github.com/antlr/antlr4
[3].https://github.com/antlr4ide/antlr4ide-eclipse-release
[4].https://github.com/shibing624/similarity
[6].https://blog.csdn.net/lockhou/article/details/113883940
[7].https://blog.csdn.net/qq\_37857921/article/details/103489583
[8].https://github.com/lcygithub/CosineSimilar/blob/master/similarity.py
[9].张丽萍,刘东升,李彦臣,钟美.一种基于AST的代码抄袭检测方法[J].计算机应用研究,2011,28(12):4616-4620.
[10].朱波,郑虹,孙琳琳,杨友星.基于AST的程序代码相似性度量研究[J].吉林大学学报(信息科学版),2015,33(01):99-104.
[11].史志成,周宇.代码特征自动提取方法[J].计算机科学与探索,2021,15(03):456-467.
[12].李彦臣. 基于后缀语法树的代码抄袭检测研究[D].内蒙古师范大学,2011.