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