IDE插件开发_代码审计之多项拼接型SQLi-2 – 作者:tolly

一、背景

代码审计,想必搞安全的同学再熟悉不过了,和标题中的IDE 插件有什么关系呢?莫不是标题党?
偶然挖洞闲暇时,发现了陌陌安全SRC的一个公告SAST开源,赶紧去撸一波源码,最终以看不懂为由挂机了。因此我决定从IDE插件开发学起,撸光它的码子。安全行业难能可贵的是开源精神,谢谢大厂。有请今天的主角momo-code-sec-inspector-java。大家可以先看着源码,看不懂了我们在来看看我下面的从0到1学开发插件。

上一篇介绍完IDEA插件开发的环境准备以及第一个插件开发,从本篇内容开始就对审计不同漏洞类型的代码进行分析,如下表,本篇为1001 拼接SQLi

编号 规则名称 修复建议 一键修复
1001 多项式拼接型SQL注入漏洞 T
1002 占位符拼接型SQL注入漏洞 T
1003 Mybatis注解SQL注入漏洞 T T
1004 Mybatis XML SQL注入漏洞 T T
1005 RegexDos风险 T T
1006 Jackson反序列化风险 T T
1007 Fastjson反序列化风险 T T
1008 Netty响应拆分攻击 T T
1009 固定的随机数种子风险 T T
1010 XXE漏洞 T T
1011 XStream反序列化风险 T T
1014 脆弱的消息摘要算法 T
1015 过时的加密标准 T
1016 XMLDecoder反序列化风险 T
1017 LDAP反序列化风险 T T
1018 宽泛的CORS Allowed Origin设置 T
1019 SpringSecurity关闭Debug模式 T T
1020 硬编码凭证风险 T
1021 “@RequestMapping” 方法应当为 “public” T T
1022 Spring 会话固定攻击风险 T T
1023 不安全的伪随机数生成器 T T
1024 OpenSAML2 认证绕过风险 T T
1025 IP地址硬编码 T

二、 插件代码分析

2.1 知识储备

java_sec_code

提供了java漏洞代码示例,非专业程序猿请勿模仿,这是漏洞代码,漏洞代码,漏洞代码

代码审计官方示例

提供代码审计模板,可以git clone下来以此为模板

PSI理解

PSI是IDEA 插件开发里面一个特殊命名(Program Structure Interface)

PSI负责解析文件并创建语法和语义代码模型,以支持jetbrains的所有产品语言

通俗的来讲,PSI文件就是一棵大树,那这棵大树就是.java文件,大树上有很多枝丫,这些枝丫就是PSI element,枝丫上的所有小枝丫都是element
image

PsiViewer 插件安装

这个插件便于我们理解代码中重写方法的选择,也是IDEA开发获取psi element的一个辅助工具PsiViewer

也可以在plugin中搜索安装

image

官方SDK文档
SDK文档

2.2 项目结构图

image

2.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>

2、功能类继承IDEA 相关API,例如代码审计需继承LocalInspectionTool

查看功能类

image-20210218140335655.png

进入BaseSQLi,这个类是检测SQL注入的一些前置条件,可以通过里面的方法来检测sql语句是否满足这些条件
image-20210218140424887.png

进入MomoBaseLocalInspectionTool后2个父类就是LocalInspectionTool
image-20210218140556873.png

3、功能类遍历正在浏览/编辑的java文件,利用我们重写的功能类方法匹配来发现有问题代码

4、将问题代码高亮显示,并提供问题详情和修复建议

image-20210218140700519.png

5、使用ALT + ENTER 自动修复
image-20210218140719383.png

修复后代码样例

image-20210218140804568.png

2.4 拼接SQL注入代码审计

下面是漏洞代码

// sqli vuln code
Statement statement = con.createStatement();
// 请查看示例 https://gist.github.com/retanoj/5fd369524a18ab68a4fe7ac5e0d121e8
String sql = "select * from users where username = '" + username + "'";
String sql11 = String.format("select * from users where username = '%s'", username);
String sql1 = "fgfd" ;
String sql12 = "fgfd" + "ds" + username;
logger.info(sql);
ResultSet rs = statement.executeQuery(sql);

下面是功能类

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)) {
                        // 对于 "select * from " + getTable() + " where id = %s" 的情况
                        // getTable() 被忽略了,要考虑后面 %s 的问题
                        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);
            }
        };
    }
}

MESSAGE变量

如果匹配了代码审计规则,就会代码高亮,并显示问题msg

QUICK_FIX_NAME变量

如果有修复方案,ALT + ENTER会显示修复 msg

showHelpCommentQuickFix变量
修复建议提示名称(alt + enter后的名称)和帮助提示
image.png

buildVisitor方法是用来访问PSI 文件的一个父类方法,返回PSI element,所有审计代码都在重写这个方法。

holder就是当前查看/编辑文件对象,也就是 LocalInspection类的实例,通过方法里面的rule来找到有问题的代码段后返回。
image-20210218152344246.png

PSI 元素中拼接的String(拼接的必须带变量) ,约定元素名为: PsiPolyadicExpression
image-20210218154558432.png

所以重写JavaElementVisitor的方法为:visitPolyadicExpression来寻找拼接SQLi
image-20210218154714334.png

JavaElementVisitor中的局部变量List exps是MoExpressionUtils类的实例,将拼接语句split 后分为3段,保存进 list。
image-20210218165620128.png

expStr是exps利用stream()方法将list 元素转换为 string
image-20210218172301732.png

①判断转换后的expstr是否是sql 语句,如果是sql 格式,②则再判断 exps LIST中的元素type是否为String,如果是则将String s append 进入 StringBuilder sb中,通过for循环最终将sb添加到List sql_segments中。如果segments为空则idea告警为占位符型SQLi(字符串拼接有多种方式String.format也可以拼接),否则为拼接型SQLi

image-20210220094215167.png

①判断代码 BaseSQLi.java

public abstract class BaseSQLi extends MomoBaseLocalInspectionTool {
    private static final Pattern sqlPattern =
            Pattern.compile("^\\s*(select|delete|update|insert)\\s+.*?(from|into|set)\\s+.*?where.*", Pattern.CASE_INSENSITIVE);

    /**
     * 判断指定字符串是否为SQL语句
     * @param str String
     * @return boolean
     */
    protected boolean isSql(String str) {
        return sqlPattern.matcher(str).find();
    }

②判断代码MoExpressionUtils.java先判断是否在定义的三个类型 type中
image-20210220094800389.png

使用PsiViewer来查看 exps的type类型
image-20210219185058696.png

对判断结果进行告警
image-20210219191013948.png
image-20210220095130608.png
image-20210220095113116.png

三、总结

本篇讲的是拼接型SQLI 审计讲解,陌陌安全代码中还判断了是否是占位符型的SQLi,其实是多余了,因为在重写JavaElementVisitor的 方法visitPolyadicExpression后,接收的expression 只有 PsiPolyadicExpression, 占位符SQL注入的类型是PsiMethodCallExpression。如有不对之处还请大家评论区帮忙更正,谢谢!我下一节讲占位符SQLi类型,请关注我,及时看到更新哦。
image-20210220102932847.png

来源:freebuf.com 2021-02-20 11:19:47 by: tolly

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论