怎么实现一个IDEA静态代码检测插件的起步阶段?
发表时间:2023-09-12 16:01:19
文章来源:炫佑科技
浏览次数:161
菏泽炫佑科技
怎么实现一个IDEA静态代码检测插件的起步阶段?
这篇文章我想写很久了,但是拖延症严重的人就是拖延……
那么本文主要介绍如何实现一个IDEA静态代码检测插件。 现在每个人都在谈论安全左移。 我觉得静态代码检测插件是安全左移的一个很好的实现,所以我想学习一下。
我在写这个插件的初期其实遇到了很多问题。 这些问题肯定是每个想写插件的人都会遇到的。 因此,我简单记录一下我的开发过程,以供参考。
开发环境设置
首先,确保您的插件已成功安装并启用。 一般idea都会自带这个插件。 您只需手动启用它即可。 启用idea后记得重启一下。
然后创建一个项目。 我在这里使用创建项目。 如果您不启用该插件,则不应在此处找到此选项。
通过创建的项目,build中会有这么一项。 文件
会根据这里的字段下载对应版本的依赖包。 这个阶段需要花费很多时间(可能一直下载不完,可以尝试连接代理),可能是因为源在国外。
项目搭建完成后,项目结构大致如下
这里有一个关键文件.xml。 这是插件的配置文件。 这是非常重要的。 它包含以下公共字段。
<idea-plugin>
<id>com.test.sastid>
<name>AxinSASTname>
<version>1.0version>
<vendor email="wuhu@gmail.com" url="http://wuhu.top">$Company|$Namevendor>
<description>my plugin descriptiondescription>
<change-notes>Initial release of the plugin.change-notes>
<depends>com.intellij.modules.alldepends>
<idea-version since-build="94.539" until-build="192"/>
<actions>
<action id="FinderAction" class="com.test.finder.FinderAction" text="FileFinder" description="FileFinder">
<add-to-group group-id="ProjectViewPopupMenu" anchor="first"/>
action>
actions>
<extensionPoints>
...
extensionPoints>
<extensions xmlns="com.intellij">
...
extensions>
idea-plugin>
一般来说,按照以上操作应该就可以成功创建插件项目了。 更容易陷入困境的是下载想法依赖项。
编写代码后,我们可以使用此任务来测试我们的插件。 当这个任务**次执行时,它会下载一个jbr()压缩包,然后用它来启动我们的插件。
该任务会在沙盒环境中运行我们编写的插件,不会影响我们当前使用的idea环境。 也就是说,将创建一个新的测试想法,然后我们的插件将在这个测试想法上运行。
为了更清楚的了解idea插件,我们来写一个小demo。 这个demo也是网上*常见的demo。
初体验插件开发
我们使用以下命令创建一个新的
然后填写相应的信息:
上述操作完成后,会在我们的项目目录下生成对应的class文件。 我们重写这个类的方法:
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);
}
}
完成上述操作后,我们可以查看.xml文件的变化
你会发现这里多了一个。 这实际上帮助我们自动注册,所以现在你应该能够理解它的用途了。 它帮助我们封装了一些方法,让我们开发插件更加方便。
如果我们刚才没有使用它来创建的话,我们需要手动在.xml文件中注册它,然后在相应的类中实现相应的方法
好了,现在我们的小demo就真正完成了。 到目前为止,您不需要了解上面代码的使用。 你只需要按照我的步骤来就可以了。 至于上面的代码能实现什么,还有什么比亲眼所见更好的呢?
接下来就是验证插件的效果了。 直接执行任务就可以了。
运行这个会创建一个新的想法,然后我们的插件会自动安装在这个想法上。 我们的插件会在工具菜单下注册一个项目,然后点击该项目会在idea的右下角弹出一条消息“测试通知”。
然后我们就可以在idea中看到我们的插件已经成功安装并启用了。
上图是我们的测试插件。 红框中的显示副本可以在.xml文件中配置。
说白了,写插件就涉及到调用各种API。 想要写好插件,就需要了解SDK提供的各种方法和接口。
我们编写静态代码审计插件的难点在于“应该使用哪个接口来进行代码审计”以及“SDK提供了哪些工具或方法来方便我们完成代码审计?” 只要克服了这两个问题自动化软件开发,那么从AST中查找代码中可能存在的问题就变得一样了。
在idea中进行静态代码审计
首先解决**个问题,代码审计应该使用SDK的哪个接口?
虽然文档写得不好,但好在他上面放了一些代码示例,而且模块和模块(尤其是这个模块)给了我们提示。 通过这两个模块,我知道需要自动化代码检查所使用的函数被调用,那么在此基础上我们可以进一步查询使用文档
既然写到这里了,为了让屏幕前的大家更好的理解它的使用,我简单给大家讲解一下这个模块的一些知识点。 这个简单的demo的主要功能是检测java代码。 比较引用类型时是否存在任何错误,例如错误地使用 != 或 == 而不是类型的 .() 方法?
如果发现错误的使用方式,插件会高亮对应的代码怎么实现一个IDEA静态代码检测插件的起步阶段?,并提供一键修复功能。 另外,这个demo还向我们展示了如何编写测试用例。
这正是我们SAST插件想要做的事情——发现漏洞,突出显示易受攻击的代码,并在可能的情况下提供一键修复功能
当然,除此之外,我们也希望将发现的漏洞代码及时报告给SOC,以便后续人工确认。
首先,我们必须用java来做。 我们需要在build.中配置java。
配置好之后,我们就可以实现一个我们自己的类了。 这个类需要被继承。 那么我们实现的类的总体结构如下:
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
}
}
}
}
*重要的是方法。 上面的代码中有n个方法。 这个方法有什么用呢?
当插件运行时,该方法会检查代码。 你可能会问那是什么?
in 实际上代表了代码中的二项式,例如:
"select * from table where id=" + id
1+2
1-2
上面的表达式被封装在idea中。 代码中的每个二项式都将作为参数传递到 n 方法中。 然后你就可以用这个方法来处理这些二项式,比如判断这个二项式是否是潜在的。 SQL注入语句(如上面**个二项式)
除了n方法之外,还提供了很多访问方法。 这些方法都是为了方便我们的代码。 例如可以访问所有方法调用语句以及所有类似于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语句都是二项式表达式,所以我们可以用n来得到。 拿到之后,如何判断是一条SQL语句呢?毕竟这两个二项式表达式都被封装在这个对象中了。 如何通过这个对象判断是否是SQL语句是一个需要解决的问题。
这里我们借用一个插件“”,这个插件可以查看当前源代码文件的AST树,见图
上图左边是我们的源代码,右边是插件窗口。 当我们将光标停留在源代码中的某个位置时,窗口将显示我们在 AST 树中的位置。 反之,当我们在窗口中选择ast树中的某处时,对应的源代码也会被高亮显示。
这个插件可以让我们对解析后的AST树有更清晰的认识。
现在,让我们回到*初的问题。 拿到之后我们如何判断是否是一条SQL语句呢?
实际上
如果你仔细看的话,从上图中可以看到,窗口的下半部分显示了当前对象的所有属性及其值。
您可以看到前两个属性是(左操作数)和(右操作数)。 另外,还有一个属性,上面的截图中没有捕捉到。 该属性指示当前二项式运算符是什么。
常见的运算符有+、-等。
所以,现在如何确定二项式是否是 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);
}
好吧,看来这就够了。 如果我想更详细的话,我就直接贴出代码,然后逐句注释掉。
那么下面是运行我写的demo插件的效果:
在写了一些通过 AST 进行代码审计的 demo 后,我发现 AST 能做的事情非常有限,而 QL 是自动化代码审计更正确的方法。