通過(guò)Inspection機(jī)制,對(duì)靜態(tài)代碼安全審查
一、前言
真能鬧,怕喇喇蛄,還不種稻子了?
喇喇蛄,是東北的一種害蟲(chóng),經(jīng)常在種水稻的季節(jié),在池埂子上盜洞,導(dǎo)致稻田里的水悄悄的流沒(méi)了,影響稻苗發(fā)育。
后來(lái)發(fā)現(xiàn)原來(lái)寫(xiě)代碼,也能碰見(jiàn)“蝲蝲蛄”,無(wú)論你寫(xiě)的是什么功能、哪種技術(shù)、作何目的,蝲蝲蛄總能給盜幾個(gè)洞出來(lái)。“你這已經(jīng)有其他的某某了你怎么還造輪子”、“你這方案不行程序員不要浪費(fèi)時(shí)間”、“也沒(méi)看出來(lái)你這有啥優(yōu)勢(shì)和價(jià)值呀怎么給業(yè)務(wù)賦能”,這種話聽(tīng)上去“賊”有道理,吹的叮當(dāng)?shù)模屗プ鲇帜芨愕南〉哪业摹?/p>
所以,遠(yuǎn)離蝲蝲蛄,做你想做的、搞你想搞的、學(xué)你想學(xué)的,知識(shí)是不斷沉淀的積累、方案是積累后的創(chuàng)造。
二、需求目的
怎么辦,都有標(biāo)準(zhǔn)的研發(fā)規(guī)范,但還是沒(méi)法控制住到具體的每個(gè)研發(fā)下,給寫(xiě)出什么代碼了。
有時(shí)候標(biāo)準(zhǔn)只是文檔,看和執(zhí)行的這個(gè)過(guò)程中就會(huì)一定的轉(zhuǎn)行失效性,你可能會(huì)想加手段;評(píng)審、扣錢、罰績(jī)效、檢討等等,但這樣可能還只是增加過(guò)程成本,最終效果也不會(huì)太好。不太可能一個(gè)寫(xiě)代碼還得配一個(gè)保姆,所以就像 p3c、pmd-idea,這樣的插件出來(lái)了,幫助程序員把代碼寫(xiě)好,治理掉一些不合標(biāo)準(zhǔn)的問(wèn)題代碼。
那么,你好奇這個(gè)事是怎么干的嗎,怎么你就在 IDEA 寫(xiě)代碼,它就能給你檢測(cè)出來(lái),告訴你有問(wèn)題,并提醒你修改以及有些還可以一鍵幫助你修改呢?那如果你想再增加點(diǎn)你們公司個(gè)性的要求的時(shí)候,怎么擴(kuò)展呢?本章節(jié)我們就使用 IDEA 插件開(kāi)發(fā)能力,把這個(gè)事辦嘍
三、案例開(kāi)發(fā)
1. 工程結(jié)構(gòu)
- guide-idea-plugin-pmd
- ├── .gradle
- └── src
- ├── main
- │ └── java
- │ └── cn.bugstack.guide.idea.plugin
- │ ├── rule
- │ │ ├── FastJsonAutoType.java
- │ │ ├── HardcodedIp.java
- │ │ └── ReplacePseudorandomGenerator.java
- │ └── utils
- │ └── InspectionBundle.java
- ├── resources
- │ ├── inspectionDescriptions
- │ │ ├── FastJsonAutoType.html
- │ │ ├── HardcodedIp.html
- │ │ └── ReplacePseudorandomGenerator.html
- │ └── META-INF
- │ └── plugin.xml
- ├── build.gradle
- └── gradle.properties
在此 IDEA 插件工程中,主要分為3塊區(qū)域:
- rule:規(guī)則配置區(qū)域,以繼承 IDEA 原生 Inspection 檢查類,擴(kuò)展自身需要掃描的代碼片段,進(jìn)行警告、注釋、修復(fù)。
- inspectionDescriptions:是對(duì)應(yīng)的警告注釋,編寫(xiě)到 html 中,最終展示到 IDEA 下對(duì)應(yīng)的問(wèn)題代碼片段上。
- plugin.xml:中需要配置 localInspection 也就是配置你自定義的代碼檢測(cè)實(shí)現(xiàn)類。
2. 偽隨機(jī)數(shù)檢測(cè)
目的:把代碼中的 new Random 不安全偽隨機(jī)數(shù)警告并提供修復(fù),處理為 new SecureRandom
RandomRule
- PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
- typeElement.replace(factory.createTypeElementFromText("SecureRandom", null));
- PsiNewExpression secureNewExp = (PsiNewExpression) factory.createExpressionFromText("new SecureRandom()", null);
- newExp.replace(secureNewExp);
- 通過(guò)繼承 AbstractBaseJavaLocalInspectionTool Override buildVisitor 方法,擴(kuò)展檢測(cè)代碼。當(dāng)你寫(xiě)了這段方法后,IDEA 會(huì)把一行行的代碼都通過(guò)這個(gè)方法傳進(jìn)來(lái)
- 在 visitNewExpression 方法中擴(kuò)展自身的檢測(cè)處理,遇到了哪種代碼片段,要提供什么樣的提醒以及提醒的級(jí)別,最后是提供一個(gè) Fix 修復(fù)能力,這個(gè)修復(fù)能力就在替換這段代碼片段,通過(guò)還可以操作引入新包的動(dòng)作 import xxx
3. FastJson檢測(cè)
目的:com.alibaba:fastjson 在開(kāi)啟 AutoTypeSupport 時(shí),存在反序列化風(fēng)險(xiǎn)。如果程序中有 ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 代碼直接提醒刪除處理。
- public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
- return new JavaElementVisitor() {
- @Override
- public void visitMethodCallExpression(PsiMethodCallExpression expression) {
- if (hasFullQualifiedName(expression, "com.alibaba.fastjson.parser.ParserConfig", "setAutoTypeSupport")) {
- PsiExpression[] args = expression.getArgumentList().getExpressions();
- if (args.length == 1 &&
- args[0] instanceof PsiLiteralExpression &&
- Boolean.TRUE.equals(((PsiLiteralExpression) args[0]).getValue())
- ) {
- holder.registerProblem(
- expression,
- "FastJson unserialization risk",
- ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
- new DeleteElementQuickFix(expression, "!Fix: remove setAutoTypeSupport")
- );
- }
- }
- }
- };
- }
整個(gè)對(duì)代碼檢測(cè)的操作基本都是類似的,這個(gè)無(wú)非也是檢測(cè)出代碼庫(kù),并進(jìn)行刪除的提醒處理 DeleteElementQuickFix
4. 提醒模板
- <html>
- <body>
- <b>小傅哥-提醒:</b> 不安全的偽隨機(jī)數(shù)生成器 <br>
- <br>
- <p>java.util.Random 依賴一個(gè)可被預(yù)測(cè)的偽隨機(jī)數(shù)生成器。</p>
- <br>
- <p style="font-size: 10px;color: #629460;">最佳實(shí)踐:</p>
- <p style="font-size: 10px;">使用java.security.SecureRandom</p>
- </body>
- </html>
提醒模板需要編寫(xiě) html 格式的內(nèi)容,這個(gè)內(nèi)容會(huì)被展示到錯(cuò)誤代碼的詳情里。后面我們做測(cè)試的可以查看
5. 檢測(cè)配置
- <extensions defaultExtensionNs="com.intellij">
- <localInspection
- language="JAVA" groupPath="Java"
- groupName="X-PMD" enabledByDefault="true" level="ERROR"
- bundle="InspectionBundle" key="replace.pseudorandom.generator.name"
- implementationClass="cn.bugstack.guide.idea.plugin.rule.RandomRule"
- />
- <localInspection
- language="JAVA" groupPath="Java"
- groupName="X-PMD" enabledByDefault="true" level="ERROR"
- bundle="InspectionBundle" key="fastjson.auto.type.name"
- implementationClass="cn.bugstack.guide.idea.plugin.rule.FastJsonRule"
- />
- <localInspection
- language="JAVA" groupPath="Java"
- groupName="X-PMD" enabledByDefault="true" level="WARNING"
- bundle="InspectionBundle" key="hardcoded.ip.name"
- implementationClass="cn.bugstack.guide.idea.plugin.rule.IPRule"
- />
- </extensions>
在 plugin.xml 中配置我們自己開(kāi)發(fā)好的代碼靜態(tài)檢測(cè)對(duì)象,這樣你的檢測(cè)類就生效了。
四、測(cè)試驗(yàn)證
啟動(dòng)插件
如果你下載代碼后,沒(méi)有 Plugin 可以自己配置一下,在 Tasks 中配置 :runIde
錯(cuò)誤提醒
錯(cuò)誤詳情
當(dāng)你點(diǎn)擊 Fix,那么接下來(lái)就可以進(jìn)行自動(dòng)替換代碼并修復(fù)了,就是把 Random random = new Random() 替換為 SecureRandom random = new SecureRandom();
其他2個(gè)也可以在獲取代碼后進(jìn)行測(cè)試驗(yàn)證,一個(gè)是IP,另外一個(gè)是使用 ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 的錯(cuò)誤提醒。
五、總結(jié)
- 本章節(jié)我們學(xué)習(xí)了如何使用 IDEA 原生 Inspection 檢查機(jī)制,擴(kuò)展我們自己需要添加的代碼檢測(cè)邏輯,以及使用 LocalQuickFix 的實(shí)現(xiàn)類,做代碼的替換和引入響應(yīng)包的操作。
- 另外對(duì)于代碼檢測(cè),還有一個(gè)更加標(biāo)準(zhǔn)的工具叫 PMD 它是一款采用 BSD 協(xié)議的代碼檢查工具,你可以擴(kuò)展實(shí)現(xiàn)為自己的標(biāo)準(zhǔn)和規(guī)范以及完善個(gè)性的提醒和修復(fù)操作。
- 像 p3c 就是一款靜態(tài)代碼檢測(cè)工具,用的人也非常多,不過(guò)它的插件開(kāi)發(fā)不是基于 Java 實(shí)現(xiàn)的,代碼開(kāi)發(fā)上也并沒(méi)有一些注釋。所以非常建議閱讀 pmd-idea,這款代碼寫(xiě)的非常好,抽象充足、結(jié)構(gòu)清晰、內(nèi)容完整:https://github.com/ybroeker/pmd-idea