增加准则:在重定向前对输入数据进行验证

wuhaoyang
Guo XIn 11 months ago
parent 0dabc96154
commit 89eb2d44af
  1. 168
      sonar-keyware-plugins-java/src/main/java/com/keyware/sonar/java/rules/checkers/RedirectUrlChecker.java
  2. 9
      sonar-keyware-plugins-java/src/main/resources/org/sonar/l10n/java/rules/java/RedirectUrlChecker.html
  3. 13
      sonar-keyware-plugins-java/src/main/resources/org/sonar/l10n/java/rules/java/RedirectUrlChecker.json
  4. 36
      sonar-keyware-plugins-java/src/test/files/RedirectUrlChecker.java
  5. 5
      sonar-keyware-plugins-java/src/test/java/com/keyware/sonar/java/rules/checkers/ABCVarNameCheckerTest.java
  6. 23
      sonar-keyware-plugins-java/src/test/java/com/keyware/sonar/java/rules/checkers/RedirectUrlCheckerTest.java

@ -0,0 +1,168 @@
package com.keyware.sonar.java.rules.checkers;
import org.sonar.check.Rule;
import org.sonar.java.ast.visitors.SubscriptionVisitor;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.*;
import java.util.Arrays;
import java.util.List;
/**
* 在重定向前对输入数据进行验证确保仅重定向至允许的URL或者在重定向至未知站点时向用户发出明确警告
*
* @author GuoXin
* @date 2024/1/9
*/
@Rule(key = "RedirectUrlChecker")
public class RedirectUrlChecker extends SubscriptionVisitor {
@Override
public List<Tree.Kind> nodesToVisit() {
var nodeType = new Tree.Kind[]{Tree.Kind.METHOD};
return Arrays.asList(nodeType);
}
@Override
public void visitNode(Tree tree) {
MethodTree methodTree = (MethodTree) tree;
BlockTree block = methodTree.block();
// 方法的参数列表
List<? extends VariableTree> parameters = methodTree.parameters();
if (block != null && !parameters.isEmpty() && isHttpRequestHandlerMethod(methodTree)) {
// 判断方法的返回节点的类型为RedirectView 或 String 类型
if ("RedirectView".equals(methodTree.returnType().toString())) {
// 传递上下文,和方法的参数列表
new RedirectViewCheckVisitor(this, parameters).check(block);
} else if ("String".equals(methodTree.returnType().toString())) {
checkByStringType(block, parameters);
}
}
}
private void checkByStringType(BlockTree block, List<? extends VariableTree> methodParameters) {
for (StatementTree statementTree : block.body()){
if(statementTree.kind() == Tree.Kind.RETURN_STATEMENT){
ReturnStatementTree rs = (ReturnStatementTree) statementTree;
ExpressionTree exprTree = rs.expression();
if(exprTree != null && !exprTree.is(Tree.Kind.STRING_LITERAL)){
if(exprTree instanceof BinaryExpressionTree){
BinaryExpressionTree bExprTree = (BinaryExpressionTree) exprTree;
if(bExprTree.is(Tree.Kind.PLUS) && bExprTree.leftOperand().is(Tree.Kind.STRING_LITERAL) && bExprTree.rightOperand().is(Tree.Kind.IDENTIFIER)){
var identifierTree = (IdentifierTree) bExprTree.rightOperand();
String argName = identifierTree.name();
if (methodParameters.stream().anyMatch(parameter -> parameter.simpleName().name().equals(argName))) {
// 说明该变量是方法传递进来的,抛出问题
context.reportIssue(this, identifierTree, "在重定向前对输入数据进行验证");
}
}
} else if (exprTree.is(Tree.Kind.IDENTIFIER)) {
var identifierTree = (IdentifierTree) exprTree;
String argName = identifierTree.name();
if (methodParameters.stream().anyMatch(parameter -> parameter.simpleName().name().equals(argName))) {
// 说明该变量是方法传递进来的,抛出问题
context.reportIssue(this, identifierTree, "在重定向前对输入数据进行验证");
}
}
}
}
}
}
/**
* 判断方法是否为public方法
*
* @param methodTree 方法树
* @return true: ; false:
*/
private boolean isHttpRequestHandlerMethod(MethodTree methodTree) {
var isPublic = false;
var hasMappingAnnotation = false;
for (ModifierTree modifier : methodTree.modifiers()) {
// 判断是否为公共方法
if (!isPublic && modifier instanceof ModifierKeywordTree) {
if (((ModifierKeywordTree) modifier).modifier() == Modifier.PUBLIC) {
isPublic = true;
}
}
// 判断是否包含Mapping注解
if (!hasMappingAnnotation && modifier instanceof AnnotationTree) {
AnnotationTree annotationTree = (AnnotationTree) modifier;
if (annotationTree.annotationType() instanceof IdentifierTree) {
IdentifierTree identifierTree = (IdentifierTree) annotationTree.annotationType();
String name = identifierTree.name();
if (name.endsWith("Mapping")) {
hasMappingAnnotation = true;
}
}
}
if (isPublic && hasMappingAnnotation) {
return true;
}
}
return false;
}
static class RedirectViewCheckVisitor extends SubscriptionVisitor {
private final RedirectUrlChecker checker;
// 方法的参数列表
private final List<? extends VariableTree> methodParameters;
public RedirectViewCheckVisitor(RedirectUrlChecker checker, List<? extends VariableTree> parameters) {
this.checker = checker;
this.methodParameters = parameters;
}
public void check(Tree block) {
this.scanTree(block);
}
@Override
public List<Tree.Kind> nodesToVisit() {
// 订阅new class和 函数调用的节点
var nodeType = new Tree.Kind[]{Tree.Kind.NEW_CLASS, Tree.Kind.METHOD_INVOCATION};
return Arrays.asList(nodeType);
}
@Override
public void visitNode(Tree tree) {
if (tree.is(Tree.Kind.NEW_CLASS)) {
NewClassTree classTree = (NewClassTree) tree;
// 判断是否为RedirectView,如果是,则判断是否有参数,如果有参数,则判断参数的类型是否由方法传递进来的
String name = classTree.identifier().toString();
if ("RedirectView".equals(name)) {
if (classTree.arguments().size() > 0) {
// 获取第一个参数语法树节点
ExpressionTree argNode = classTree.arguments().get(0);
checkArgs(argNode, tree);
}
}
} else {
MethodInvocationTree invocationTree = (MethodInvocationTree) tree;
ExpressionTree expressionTree = invocationTree.methodSelect();
if (expressionTree instanceof MemberSelectExpressionTree) {
MemberSelectExpressionTree member = (MemberSelectExpressionTree) expressionTree;
if (member.expression().symbolType().is("RedirectView")
&& "setUrl".equals(member.identifier().name())) {
ExpressionTree argNode = invocationTree.arguments().get(0);
checkArgs(argNode, tree);
}
}
}
}
private void checkArgs(ExpressionTree argNode, Tree tree) {
// 判断该语法树节点是否为IdentifierTree,如果是,则说明语法树节点为变量,然后判断该变量是否是包含在方法的参数列表中
if (argNode instanceof IdentifierTree) {
IdentifierTree identifierTree = (IdentifierTree) argNode;
String argName = identifierTree.name();
if (methodParameters.stream().anyMatch(parameter -> parameter.simpleName().name().equals(argName))) {
// 说明该变量是方法传递进来的,抛出问题
checker.context.reportIssue(checker, tree, "在重定向前对输入数据进行验证");
}
}
}
}
}

@ -0,0 +1,9 @@
<h2>在重定向前对输入数据进行验证</h2>
<p>在重定向前对输入数据进行验证,确保仅重定向至允许的URL。或者在重定向至未知站点时向用户发出明确警告。</p>
<pre>
</pre>
<h2>合规解决方案</h2>
<pre>
</pre>

@ -0,0 +1,13 @@
{
"title": "在重定向前对输入数据进行验证",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"28suo"
],
"defaultSeverity": "Minor"
}

@ -0,0 +1,36 @@
@Controller
public class MyController {
@GetMapping("/old-url")
public RedirectView redirectOldUrl(String url) { // Compliant,因为重定向的路径不是由方法传递进来的
RedirectView redirectView = new RedirectView();
redirectView.setUrl(url);// Noncompliant {{在重定向前对输入数据进行验证}}
return redirectView;
}
@GetMapping("/old-url2")
public String redirectOldUrl2() { // Compliant,因为重定向的路径不是由方法传递进来的
// 302临时重定向到新的URL
return "redirect:/new-url";
}
@GetMapping("/old-url3")
public RedirectView redirectOldUrl3(String url) {
RedirectView redirectView = new RedirectView(url); // Noncompliant {{在重定向前对输入数据进行验证}}
redirectView.setUrl(url); // Noncompliant {{在重定向前对输入数据进行验证}}
return redirectView;
}
@GetMapping("/old-url4")
public String redirectOldUrl4(String url) {
// 302临时重定向到新的URL
return "redirect:" + url; // Noncompliant {{在重定向前对输入数据进行验证}}
}
@GetMapping("/old-url5")
public String redirectOldUrl4(String url) {
// 302临时重定向到新的URL
return url; // Noncompliant {{在重定向前对输入数据进行验证}}
}
}

@ -24,9 +24,8 @@ public class ABCVarNameCheckerTest {
ABCVarNameChecker rule = new ABCVarNameChecker(); ABCVarNameChecker rule = new ABCVarNameChecker();
// Verifies that the check will raise the adequate issues with the expected message. // 验证检查是否会按预期检测到样例代码中预定的问题
// In the test file, lines which should raise an issue have been commented out // 在测试样例文件中,问题所在行必须使用单行注释声明,如: "// Noncompliant {{EXPECTED_MESSAGE}}"
// by using the following syntax: "// Noncompliant {{EXPECTED_MESSAGE}}"
CheckVerifier.newVerifier() CheckVerifier.newVerifier()
.onFile("src/test/files/ABCVarNameRule.java") .onFile("src/test/files/ABCVarNameRule.java")
.withCheck(rule) .withCheck(rule)

@ -0,0 +1,23 @@
package com.keyware.sonar.java.rules.checkers;
import com.keyware.sonar.java.utils.FilesUtils;
import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;
/**
* TODO RedirectUrlCheckerTest
*
* @author GuoXin
* @date 2024/1/9
*/
public class RedirectUrlCheckerTest {
@Test
public void test(){
CheckVerifier.newVerifier()
.onFile("src/test/files/RedirectUrlChecker.java")
.withCheck(new RedirectUrlChecker())
.withClassPath(FilesUtils.getClassPath("target/test-jars"))
.verifyIssues();
}
}
Loading…
Cancel
Save