IDE插件开发_代码审计之多项拼接型SQLi-2
字数 1542 2025-08-15 21:33:52
IDE插件开发之多项拼接型SQL注入代码审计
一、背景与概述
本文基于陌陌安全开源的momo-code-sec-inspector-java项目,介绍如何开发IDE插件来检测Java代码中的多项拼接型SQL注入漏洞(SQLi)。
漏洞检测规则列表
该项目包含多种安全规则的检测能力,以下是部分规则列表:
| 编号 | 规则名称 | 修复建议 | 一键修复 |
|---|---|---|---|
| 1001 | 多项式拼接型SQL注入漏洞 | T | |
| 1002 | 占位符拼接型SQL注入漏洞 | T | |
| 1003 | Mybatis注解SQL注入漏洞 | T | T |
| 1004 | Mybatis XML SQL注入漏洞 | T | T |
| ... | ... | ... | ... |
二、开发准备
2.1 知识储备
- Java漏洞代码示例:参考
java_sec_code项目提供的漏洞代码示例 - 代码审计模板:可从官方示例中获取模板
- PSI理解:
- PSI(Program Structure Interface)是IDEA插件开发中的核心概念
- PSI负责解析文件并创建语法和语义代码模型
- 类比:PSI文件是一棵树(.java文件),树枝是PSI element
2.2 工具准备
- PsiViewer插件:辅助理解代码中重写方法的选择,可视化PSI element
- 官方SDK文档:JetBrains官方提供的插件开发文档
三、项目结构与审计流程
3.1 项目结构
插件通过plugin.xml配置文件定义功能类及相关描述:
<extensions defaultExtensionNs="com.intellij">
<localInspection
language="JAVA"
groupPath="Java"
groupName="MomoSec"
enabledByDefault="true"
level="ERROR"
bundle="com.immomo.momosec.bundle.InspectionBundle"
key="polyadic.expression.sqli.name"
implementationClass="com.immomo.momosec.lang.java.rule.momosecurity.PolyadicExpressionSQLi" />
</extensions>
3.2 代码审计流程
- 从
plugin.xml读取功能类及描述 - 功能类继承IDEA相关API(如
LocalInspectionTool) - 遍历正在浏览/编辑的Java文件,匹配问题代码
- 高亮显示问题代码,提供详情和修复建议
- 支持使用
ALT + ENTER自动修复
四、多项拼接型SQL注入检测实现
4.1 漏洞代码示例
Statement statement = con.createStatement();
String sql = "select * from users where username = '" + username + "'";
String sql11 = String.format("select * from users where username = '%s'", username);
String sql12 = "fgfd" + "ds" + username;
ResultSet rs = statement.executeQuery(sql);
4.2 检测类实现
public class PolyadicExpressionSQLi extends BaseSQLi {
public static final String MESSAGE = InspectionBundle.message("polyadic.expression.sqli.msg");
private static final String QUICK_FIX_NAME = InspectionBundle.message("polyadic.expression.sqli.fix");
private final ShowHelpCommentQuickFix showHelpCommentQuickFix =
new ShowHelpCommentQuickFix(QUICK_FIX_NAME, SQL_INJECTION_HELP_COMMENT);
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitPolyadicExpression(PsiPolyadicExpression expression) {
List<PsiExpression> exps = MoExpressionUtils.deconPolyadicExpression(expression);
if (exps.isEmpty()) {
return;
}
String expStr = exps.stream()
.map(item -> MoExpressionUtils.getText(item, true))
.collect(Collectors.joining());
if (isSql(expStr)) {
List<String> sql_segments = new ArrayList<>();
StringBuilder sb = new StringBuilder();
boolean hasVar = false;
for (PsiExpression exp : exps) {
if (isSqliCareExpression(exp)) {
String s = MoExpressionUtils.getLiteralInnerText(exp);
if (s == null) {
if (!sb.toString().isEmpty()) {
sql_segments.add(sb.toString());
sb.delete(0, sb.length());
}
if (!MoExpressionUtils.isText(exp)) {
hasVar = true;
}
} else {
sb.append(s);
}
} else {
sb.append(末段要 drop 掉,不用查);
}
}
if (sql_segments.isEmpty() || Boolean.FALSE.equals(hasVar)
|| !hasEvalAdditive(sql_segments)) {
if (hasPlaceholderProblem(expStr) && !ignoreMethodName(expression)) {
holder.registerProblem(expression,
PlaceholderStringSQLi.MESSAGE,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
showHelpCommentQuickFix);
}
return;
}
if (!ignoreMethodName(expression)) {
holder.registerProblem(expression,
MESSAGE,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
showHelpCommentQuickFix);
}
}
}
};
}
private boolean hasPlaceholderProblem(String content) {
return placeholderPattern.matcher(content).find()
&& isSql(content)
&& hasEvalAdditive(content, placeholderPattern);
}
}
4.3 关键点解析
-
变量说明:
MESSAGE:匹配规则后显示的问题信息QUICK_FIX_NAME:修复方案显示名称showHelpCommentQuickFix:修复建议提示
-
核心方法:
buildVisitor:访问PSI文件的父类方法,返回PSI elementholder:当前查看/编辑文件对象(LocalInspection类的实例)
-
PSI元素处理:
- 拼接的String元素类型为
PsiPolyadicExpression - 重写
visitPolyadicExpression方法来寻找拼接SQLi
- 拼接的String元素类型为
-
检测逻辑:
- 使用
MoExpressionUtils.deconPolyadicExpression将拼接语句split为多段 - 判断是否为SQL语句(
isSql方法) - 检查拼接部分是否包含变量
- 根据检查结果注册问题(
holder.registerProblem)
- 使用
4.4 SQL语句识别
在BaseSQLi.java中定义SQL识别模式:
private static final Pattern sqlPattern =
Pattern.compile("^\\s*(select|delete|update|insert)\\s+.*?(from|into|set)\\s+.*?where.*",
Pattern.CASE_INSENSITIVE);
protected boolean isSql(String str) {
return sqlPattern.matcher(str).find();
}
五、总结
- 本文介绍了多项拼接型SQL注入的检测方法,核心是识别
PsiPolyadicExpression类型的元素 - 占位符型SQL注入的类型是
PsiMethodCallExpression,与多项拼接型不同 - 通过PSI解析和模式匹配,可以有效识别代码中的不安全SQL拼接
- 插件提供了问题高亮、描述信息和快速修复建议
后续可以扩展实现其他类型漏洞的检测,如占位符型SQL注入、MyBatis相关注入等。