前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于IDEA的自动化代码审计插件开发初探

基于IDEA的自动化代码审计插件开发初探

作者头像
tnt阿信
发布2021-04-26 12:28:16
1.5K0
发布2021-04-26 12:28:16
举报

想写这篇文章好久了,重度拖延症患者就是拖拖拖...

然后本文主要是给大家介绍一下怎么实现一个IDEA静态代码检测插件,现在都在讲安全左移嘛,我觉得静态代码检测插件就是一个安全左移很好的落地,于是就想着学习一下

我自己在写这个插件的起步阶段其实遇到了很多问题,这些问题肯定也会是每一个想要写插件的人都会遇到的,所以,我就简单记录下我的开发过程供来者参考

开发环境搭建

首先得确保你的Plugin DevKit插件成功安装并启用了,一般情况下idea都自带了这个插件,你只需要手动启用一下就行,启用过后记得重启idea

然后创建工程,我这里使用gradle创建一个IntelliJ platform plugin的工程,如果你没有启用devkit插件的话,这里应该是找不到intellij platform plugin这个选项的

通过gradle创建的工程,build.gradle文件中会有这么一项

gradle会根据这里的version字段去下载对应版本的intellij依赖包,这一阶段需要花费大量的时间(还有可能一直下载不下来,可以尝试挂代理),可能是因为源在国外吧

gradle项目构建好过后,项目结构大概如下

这里有个比较关键的文件plugin.xml,这是插件的配置文件,很重要,里面有如下常见字段

<idea-plugin>
  <!-- 插件相关信息, 会展示在IDEA插件的描述中 -->
  
  <!-- 插件唯一id, 遵循使用包名的原则 -->
  <id>com.test.sast</id>
  <!-- 插件名称 -->
  <name>AxinSAST</name>
  <!-- 插件版本 -->
  <version>1.0</version>
  <!-- 开发者信息 -->
  <vendor email="wuhu@gmail.com" url="http://wuhu.top">$Company|$Name</vendor>
  <!-- 插件的描述 -->
  <description>my plugin description</description>
  <!-- 插件版本变更信息 -->
  <change-notes>Initial release of the plugin.</change-notes>
  <!-- 如果该插件还依赖了其他插件,则配置对对应的插件id -->
  <depends>com.intellij.modules.all</depends>
  <!-- 插件兼容IDEA的最大和最小build号,不配置则不做限制 -->
  <idea-version since-build="94.539" until-build="192"/>
  <!-- Actions: 如添加一个文件右击菜单按钮 -->
  <actions>
    <action id="FinderAction" class="com.test.finder.FinderAction" text="FileFinder" description="FileFinder">
      <add-to-group group-id="ProjectViewPopupMenu" anchor="first"/>
    </action>
  </actions>
  <!-- 插件定义的扩展点,以供其他插件扩展该插件,类似Java的抽象类的功能 -->
  <extensionPoints>
    ...
  </extensionPoints>
  <!-- 声明该插件对IDEA core或其他插件的扩展 -->
  <extensions xmlns="com.intellij">
    ...
  </extensions>
</idea-plugin>

通常来说,按照上面的操作应该就可以成功建立一个插件工程了,比较容易卡住的就是下载idea依赖那一块儿

当我们写好代码过后,可以使用gradle的runIde这个task测试自己的插件,这个task首次执行时会去下载一个jbr(jetbrains runtime)压缩包,然后利用这个runtime来启动我们的插件

runIde这个task会在一个沙箱环境里运行我们编写的插件,不会影响我们当前正在使用的idea环境,也就是会新起一个测试用的idea,然后我们的插件会在这个测试idea上跑起来

为了能够对idea插件有一个更加清晰的认识,我们来写一个小demo,这个demo也是网上最常见的一个demo

插件开发初体验

我们使用devkit 新建一个action

然后填写好对应的信息:

上述操作完成后,会在我们的项目目录下生成一个对应TestAction类文件,我们重写下该类的ationPerformed方法:

import com.intellij.notification.Notification;
import com.intellij.notification.NotificationDisplayType;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.ui.MessageType;

public class TestAction extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
        // 这里的testid需要和你刚刚填写的action id保持一致
        NotificationGroup notificationGroup = new NotificationGroup("testid", NotificationDisplayType.BALLOON, false);
        /**
         * content :  通知内容
         * type  :通知的类型,warning,info,error
         */
        Notification notification = notificationGroup.createNotification("测试通知", MessageType.INFO);
        Notifications.Bus.notify(notification);
    }
}

完成了上面的操作,我们可以看下plugin.xml文件有什么变化

你会发现这里多了个action,这个action其实是devkit帮我们自动注册的,所以,到这里你也应该能够理解devkit有啥用了吧,它帮我们封装了一些方法,让我们更加便利的进行插件开发

如果我们没有使用devkit来创建刚刚那么一个action的话,我们就需要手动在plugin.xml文件里注册action,然后在对应的类实现对应的方法

ok,到此其实我们的一个小demo就算是完成了,到目前为止你都不需要去理解上面的代码有啥用,你只需要按照我的步骤走一遍就行,至于上面的代码能实现什么效果,有什么是比亲眼见证更好的呢?

接下来就是验证插件的效果了,直接执行intellij的runIde任务就好

运行这个会新起一个idea,然后我们的插件会自动安装到这个idea上,我们这个插件会在tools菜单下注册一个item,然后点击该item会在idea右下角弹出消息「测试通知」

然后我们到idea的plugins里也可以看到,我们的插件的确是成功安装并启用了的

上图中的就是我们的测试插件,红框中的展示文案都是可以在plugin.xml文件中进行配置的

插件的编写说白了还是调用各种api,想要写好一个插件,就需要清楚intellij sdk提供的各种方法以及接口的使用

而我们要编写一个静态代码审计插件的难点也就在「到底该使用哪个接口来进行代码检查」以及「SDK都提供了哪些工具或方法来方便我们完成代码审计」,只要克服了这两个问题,从AST里去找代码中可能存在的问题也就变得千篇一律了

在idea中进行静态代码审计

首先解决第一个问题,用SDK的哪个接口来进行代码审计

虽然intellij的文档写的很烂,但是还好他在github上放了一些代码示例,其中inspection_basics模块以及comparing_references_inspection模块(尤其是这个模块)给了我们提示,通过这两个模块我知道了在intellij platform上进行自动化代码检查需要用到的功能被称作inspection,那么在这个基础上我们就可以进一步查询inspection的使用文档了

既然写到这里了,那为了让屏幕前的大家更好地了解inspection的使用,我干脆来简单的给大家讲解下comparing_references_inspection这个模块里的一些知识点吧,这个简单的demo主要的功能就是检测java代码中有没有错误地进行引用类型的比较,比如对于String类型,错误的使用!=或者==而不是.equals()方法

如果发现有错误的用法插件会将对应的代码高亮,并且提供一键修复功能,除此之外这个demo里还给我们展示了怎么编写测试用例

这简直就是我们的SAST插件想要做的事情——发现漏洞对漏洞代码进行高亮,如果可以,提供一键修复功能

当然,除此之外,我们还希望发现漏洞代码及时上报到soc方便后续人工确认

首先,我们要对java做inspection,需要在build.gradle中的intellij配置好java的plugin

这个配好过后,我们就可以实现一个自己的inspection类啦,这个类需要继承AbstractBaseJavaLocalInspectionTool,然后我们自己实现的inspection类大体架构如下:

public class ComparingReferencesInspection extends AbstractBaseJavaLocalInspectionTool{
 public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly){
   return new JavaElementVisitor() {
    public void visitBinaryExpression(PsiBinaryExpression expression) {
     doSomething...
     if(condition){
      holder.registerProblem(expression, "问题描述字符串")
     }
    }
    public void visitXXXXX(){
     xxxxx
    }
   }
 } 
}

其中最重要的方法就是visitXXX方法,上面代码中举了visitBinaryExpression这么个方法,这个方法有什么用呢?

当插件跑起来过后,这个方法就会检查代码中的BinaryExpression,你可能会问BinaryExpression是啥?

intellij platform中的BinaryExpression其实代表的就是代码中的二项式,例如:

"select * from table where id=" + id
1+2
1-2

idea中用BinaryExpression封装了上面这种表达式,代码中的每一个二项式都会作为参数传入visitBinaryExpression方法,然后你可以在这个方法里处理这些二项式,比如判断这个二项式是不是潜在的sql注入语句(例如上面第一条二项式那样)

除了visitBinaryExpression方法,intellij还提供了很多种visit方法,这些方法都是为了方便我们进行代码inspection的,例如visitMethodCallExpression是可以访问到所有的方法调用语句,visitNewExpression可以访问到所有的类似new Xxx()语句

有了上面的基础知识,我们尝试着想一想怎么实现一个简单的检测sql注入的插件,我们不考虑特别复杂的sql注入,我们就考虑怎么检测sql语句拼接就行,例如下面这些句子

public test(String id){
 String id2 = '2';
 String sql1 = "select * from table where id="+id;
 String sql2 = "select * from table where id=" + id2;
}

上面两个sql语句都是二项表达式,所以我们可以用visitBinaryExpression拿到他们,拿到他们过后,我们怎么确定他是一个sql语句呢?毕竟这两个二项表达式是被封装到binaryExpression这个对象里的,怎么通过这个对象判断他是一个sql语句这是一个需要我们解决的问题

这里我们借用一个插件「PsiViewer」,这个插件可以查看当前源码文件的AST树,看图

上图左侧是我们的源代码,右侧是psiviewer插件窗口,当我们在把光标停留在源码某处时,psiviewer窗口会对应展示我们正处在AST树的哪个位置,反过来,当我们在psiviewer窗口中选中ast树某处时,对应的源码也会高亮出来

这个插件可以让我们对intellij platform解析出来的AST树有更加清晰的认知

现在,让我们回到最初的问题,在拿到了BinaryExpression后,我们要怎么判断它到底是不是一个sql语句呢?

其实

如果你观察仔细,你可以从上图中发现psiviewer窗口的下半部分展示了当前PsiBinaryExpression对象所有的属性以及其值

可以看到前两个属性分别是LOperand(左操作数)以及ROperand(右操作数),除此之外还有个属性是operationTokenType,上面截图没有截到,这个属性指明了当前二项式的操作符是啥

常见的操作符有+-

所以,到目前为止怎么判断一个二项式是不是一个sql语句应该很明显了吧:

先判断当前二项式的操作符是不是加号,如果是加号,拿到左操作数以及右操作数,解析出他们的值,然后把他们的值拼接起来,最后用正则判断是不是一个sql语句

当然,这只是很理想的一个状况,如果你要检测sql注入,还需要考虑很多种情况,举一部分?:

public test(String id){
 String id2 = '2';
 String sql1 = "select * from table where id="
 String sql2 = sql1 + id;
 String sql1 += id;
 String sql3 = "select * from table where id=" + id2;
 String sql4 = "select * from" + "table" + id2;
 String sql5 = "select * from table where id=" + getId();
 String sql6 = "select * from table where id=%s";
 sql6.format(id);
}

ok,写到这里好像就已经差不多了,要是再细节一点,就是把代码直接贴出来,然后一句句注释了

然后下面是我写的demo插件运行的效果:

我自己在写了一些通过AST进行代码审计的demo后,发现AST能做的事情是很有限的,QL才是更正确的自动化代码审计姿势

代码已上传github:https://github.com/Maskhe/AxinSAST,点击阅读原文可跳转

还在开发中,代码很烂,希望大家别喷我,ball ball 大家?

我又来要赞?,如果你觉得本文写的不错就点个赞,有帮助就点个在看,如果你分享到朋友圈了,什么都别说了,好兄弟!你懂我!各位股东们的一键三连是我持续真诚写作的最大动力✌️

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-04-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个安全研究员 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开发环境搭建
  • 插件开发初体验
  • 在idea中进行静态代码审计
相关产品与服务
代码审计
代码审计(Code Audit,CA)提供通过自动化分析工具和人工审查的组合审计方式,对程序源代码逐条进行检查、分析,发现其中的错误信息、安全隐患和规范性缺陷问题,以及由这些问题引发的安全漏洞,提供代码修订措施和建议。支持脚本类语言源码以及有内存控制类源码。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档