通過Inspection機制,對靜態(tài)代碼安全審查
一、前言
真能鬧,怕喇喇蛄,還不種稻子了?
喇喇蛄,是東北的一種害蟲,經(jīng)常在種水稻的季節(jié),在池埂子上盜洞,導(dǎo)致稻田里的水悄悄的流沒了,影響稻苗發(fā)育。
后來發(fā)現(xiàn)原來寫代碼,也能碰見“蝲蝲蛄”,無論你寫的是什么功能、哪種技術(shù)、作何目的,蝲蝲蛄總能給盜幾個洞出來。“你這已經(jīng)有其他的某某了你怎么還造輪子”、“你這方案不行程序員不要浪費時間”、“也沒看出來你這有啥優(yōu)勢和價值呀怎么給業(yè)務(wù)賦能”,這種話聽上去“賊”有道理,吹的叮當?shù)模屗プ鲇帜芨愕南〉哪业摹?/p>
所以,遠離蝲蝲蛄,做你想做的、搞你想搞的、學你想學的,知識是不斷沉淀的積累、方案是積累后的創(chuàng)造。
二、需求目的
怎么辦,都有標準的研發(fā)規(guī)范,但還是沒法控制住到具體的每個研發(fā)下,給寫出什么代碼了。
有時候標準只是文檔,看和執(zhí)行的這個過程中就會一定的轉(zhuǎn)行失效性,你可能會想加手段;評審、扣錢、罰績效、檢討等等,但這樣可能還只是增加過程成本,最終效果也不會太好。不太可能一個寫代碼還得配一個保姆,所以就像 p3c、pmd-idea,這樣的插件出來了,幫助程序員把代碼寫好,治理掉一些不合標準的問題代碼。
那么,你好奇這個事是怎么干的嗎,怎么你就在 IDEA 寫代碼,它就能給你檢測出來,告訴你有問題,并提醒你修改以及有些還可以一鍵幫助你修改呢?那如果你想再增加點你們公司個性的要求的時候,怎么擴展呢?本章節(jié)我們就使用 IDEA 插件開發(fā)能力,把這個事辦嘍
三、案例開發(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 檢查類,擴展自身需要掃描的代碼片段,進行警告、注釋、修復(fù)。
- inspectionDescriptions:是對應(yīng)的警告注釋,編寫到 html 中,最終展示到 IDEA 下對應(yīng)的問題代碼片段上。
- plugin.xml:中需要配置 localInspection 也就是配置你自定義的代碼檢測實現(xiàn)類。
2. 偽隨機數(shù)檢測
目的:把代碼中的 new Random 不安全偽隨機數(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);
- 通過繼承 AbstractBaseJavaLocalInspectionTool Override buildVisitor 方法,擴展檢測代碼。當你寫了這段方法后,IDEA 會把一行行的代碼都通過這個方法傳進來
- 在 visitNewExpression 方法中擴展自身的檢測處理,遇到了哪種代碼片段,要提供什么樣的提醒以及提醒的級別,最后是提供一個 Fix 修復(fù)能力,這個修復(fù)能力就在替換這段代碼片段,通過還可以操作引入新包的動作 import xxx
3. FastJson檢測
目的:com.alibaba:fastjson 在開啟 AutoTypeSupport 時,存在反序列化風險。如果程序中有 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")
- );
- }
- }
- }
- };
- }
整個對代碼檢測的操作基本都是類似的,這個無非也是檢測出代碼庫,并進行刪除的提醒處理 DeleteElementQuickFix
4. 提醒模板
- <html>
- <body>
- <b>小傅哥-提醒:</b> 不安全的偽隨機數(shù)生成器 <br>
- <br>
- <p>java.util.Random 依賴一個可被預(yù)測的偽隨機數(shù)生成器。</p>
- <br>
- <p style="font-size: 10px;color: #629460;">最佳實踐:</p>
- <p style="font-size: 10px;">使用java.security.SecureRandom</p>
- </body>
- </html>
提醒模板需要編寫 html 格式的內(nèi)容,這個內(nèi)容會被展示到錯誤代碼的詳情里。后面我們做測試的可以查看
5. 檢測配置
- <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 中配置我們自己開發(fā)好的代碼靜態(tài)檢測對象,這樣你的檢測類就生效了。
四、測試驗證
啟動插件
如果你下載代碼后,沒有 Plugin 可以自己配置一下,在 Tasks 中配置 :runIde
錯誤提醒
錯誤詳情
當你點擊 Fix,那么接下來就可以進行自動替換代碼并修復(fù)了,就是把 Random random = new Random() 替換為 SecureRandom random = new SecureRandom();
其他2個也可以在獲取代碼后進行測試驗證,一個是IP,另外一個是使用 ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 的錯誤提醒。
五、總結(jié)
- 本章節(jié)我們學習了如何使用 IDEA 原生 Inspection 檢查機制,擴展我們自己需要添加的代碼檢測邏輯,以及使用 LocalQuickFix 的實現(xiàn)類,做代碼的替換和引入響應(yīng)包的操作。
- 另外對于代碼檢測,還有一個更加標準的工具叫 PMD 它是一款采用 BSD 協(xié)議的代碼檢查工具,你可以擴展實現(xiàn)為自己的標準和規(guī)范以及完善個性的提醒和修復(fù)操作。
- 像 p3c 就是一款靜態(tài)代碼檢測工具,用的人也非常多,不過它的插件開發(fā)不是基于 Java 實現(xiàn)的,代碼開發(fā)上也并沒有一些注釋。所以非常建議閱讀 pmd-idea,這款代碼寫的非常好,抽象充足、結(jié)構(gòu)清晰、內(nèi)容完整:https://github.com/ybroeker/pmd-idea