Skip to content

Commit 6b7e74c

Browse files
authored
New Sonar Codemod for Object Deserialization (#451)
\close #work
1 parent 93d1c90 commit 6b7e74c

File tree

8 files changed

+433
-5
lines changed

8 files changed

+433
-5
lines changed

core-codemods/src/main/java/io/codemodder/codemods/DefaultCodemods.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,12 @@ public static List<Class<? extends CodeChanger>> asList() {
7575
SimplifyRestControllerAnnotationsCodemod.class,
7676
SubstituteReplaceAllCodemod.class,
7777
SonarJNDIInjectionCodemod.class,
78+
SonarObjectDeserializationCodemod.class,
7879
SonarRemoveUnthrowableExceptionCodemod.class,
79-
SonarXXECodemod.class,
8080
SonarSQLInjectionCodemod.class,
81-
SonarUnsafeReflectionRemediationCodemod.class,
8281
SonarSSRFCodemod.class,
82+
SonarUnsafeReflectionRemediationCodemod.class,
83+
SonarXXECodemod.class,
8384
SQLParameterizerCodemod.class,
8485
SSRFCodemod.class,
8586
StackTraceExposureCodemod.class,
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package io.codemodder.codemods;
2+
3+
import com.github.javaparser.ast.CompilationUnit;
4+
import io.codemodder.*;
5+
import io.codemodder.codetf.DetectorRule;
6+
import io.codemodder.providers.sonar.ProvidedSonarScan;
7+
import io.codemodder.providers.sonar.RuleIssue;
8+
import io.codemodder.providers.sonar.SonarRemediatingJavaParserChanger;
9+
import io.codemodder.remediation.GenericRemediationMetadata;
10+
import io.codemodder.remediation.javadeserialization.JavaDeserializationRemediator;
11+
import io.codemodder.sonar.model.Issue;
12+
import io.codemodder.sonar.model.SonarFinding;
13+
import java.util.List;
14+
import java.util.Objects;
15+
import javax.inject.Inject;
16+
17+
/** Fixes Object Deserialization issues found by sonar rule javasecurity:S5135. */
18+
@Codemod(
19+
id = "sonar:java/object-deserialization-s5135",
20+
reviewGuidance = ReviewGuidance.MERGE_WITHOUT_REVIEW,
21+
executionPriority = CodemodExecutionPriority.HIGH,
22+
importance = Importance.HIGH)
23+
public final class SonarObjectDeserializationCodemod extends SonarRemediatingJavaParserChanger {
24+
25+
private final JavaDeserializationRemediator remediator;
26+
private final RuleIssue issues;
27+
28+
@Inject
29+
public SonarObjectDeserializationCodemod(
30+
@ProvidedSonarScan(ruleId = "javasecurity:S5135") final RuleIssue issues) {
31+
super(GenericRemediationMetadata.DESERIALIZATION.reporter(), issues);
32+
this.issues = Objects.requireNonNull(issues);
33+
this.remediator = JavaDeserializationRemediator.DEFAULT;
34+
}
35+
36+
@Override
37+
public DetectorRule detectorRule() {
38+
return new DetectorRule(
39+
"javasecurity:S5135",
40+
"Deserialization should not be vulnerable to injection attacks",
41+
"https://rules.sonarsource.com/java/RSPEC-5135/");
42+
}
43+
44+
@Override
45+
public CodemodFileScanningResult visit(
46+
final CodemodInvocationContext context, final CompilationUnit cu) {
47+
List<Issue> issuesForFile = issues.getResultsByPath(context.path());
48+
return remediator.remediateAll(
49+
cu,
50+
context.path().toString(),
51+
detectorRule(),
52+
issuesForFile,
53+
SonarFinding::getKey,
54+
i -> i.getTextRange() != null ? i.getTextRange().getStartLine() : i.getLine(),
55+
i -> i.getTextRange() != null ? i.getTextRange().getEndLine() : null,
56+
i -> i.getTextRange() != null ? i.getTextRange().getStartOffset() : null);
57+
}
58+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.codemodder.codemods;
2+
3+
import io.codemodder.DependencyGAV;
4+
import io.codemodder.testutils.CodemodTestMixin;
5+
import io.codemodder.testutils.Metadata;
6+
7+
@Metadata(
8+
codemodType = SonarObjectDeserializationCodemod.class,
9+
testResourceDir = "sonar-object-deserialization-s5135",
10+
renameTestFile =
11+
"src/main/java/org/owasp/webgoat/lessons/deserialization/InsecureDeserializationTask.java",
12+
expectingFixesAtLines = {60},
13+
dependencies = DependencyGAV.JAVA_SECURITY_TOOLKIT_GAV)
14+
final class SonarObjectDeserializationCodemodTest implements CodemodTestMixin {}

core-codemods/src/test/java/io/codemodder/codemods/SonarSSRFCodemodTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ final class SonarSSRFCodemodTest {
1515
"src/main/java/org/owasp/webgoat/lessons/passwordreset/ResetLinkAssignmentForgotPassword.java",
1616
expectingFixesAtLines = {104},
1717
dependencies = DependencyGAV.JAVA_SECURITY_TOOLKIT_GAV)
18-
class RestTemplateTest implements CodemodTestMixin {}
18+
final class RestTemplateTest implements CodemodTestMixin {}
1919
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
3+
*
4+
* Copyright (c) 2002 - 2019 Bruce Mayhew
5+
*
6+
* This program is free software; you can redistribute it and/or modify it under the terms of the
7+
* GNU General Public License as published by the Free Software Foundation; either version 2 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
11+
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License along with this program; if
15+
* not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
16+
* 02111-1307, USA.
17+
*
18+
* Getting Source ==============
19+
*
20+
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
21+
*/
22+
23+
package org.owasp.webgoat.lessons.deserialization;
24+
25+
import static io.github.pixee.security.ObjectInputFilters.createSafeObjectInputStream;
26+
import java.io.ByteArrayInputStream;
27+
import java.io.IOException;
28+
import java.io.InvalidClassException;
29+
import java.io.ObjectInputStream;
30+
import java.util.Base64;
31+
import org.dummy.insecure.framework.VulnerableTaskHolder;
32+
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
33+
import org.owasp.webgoat.container.assignments.AssignmentHints;
34+
import org.owasp.webgoat.container.assignments.AttackResult;
35+
import org.springframework.web.bind.annotation.PostMapping;
36+
import org.springframework.web.bind.annotation.RequestParam;
37+
import org.springframework.web.bind.annotation.ResponseBody;
38+
import org.springframework.web.bind.annotation.RestController;
39+
40+
@RestController
41+
@AssignmentHints({
42+
"insecure-deserialization.hints.1",
43+
"insecure-deserialization.hints.2",
44+
"insecure-deserialization.hints.3"
45+
})
46+
public class InsecureDeserializationTask extends AssignmentEndpoint {
47+
48+
@PostMapping("/InsecureDeserialization/task")
49+
@ResponseBody
50+
public AttackResult completed(@RequestParam String token) throws IOException {
51+
String b64token;
52+
long before;
53+
long after;
54+
int delay;
55+
56+
b64token = token.replace('-', '+').replace('_', '/');
57+
58+
try (ObjectInputStream ois =
59+
createSafeObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)))) {
60+
before = System.currentTimeMillis();
61+
Object o = ois.readObject();
62+
if (!(o instanceof VulnerableTaskHolder)) {
63+
if (o instanceof String) {
64+
return failed(this).feedback("insecure-deserialization.stringobject").build();
65+
}
66+
return failed(this).feedback("insecure-deserialization.wrongobject").build();
67+
}
68+
after = System.currentTimeMillis();
69+
} catch (InvalidClassException e) {
70+
return failed(this).feedback("insecure-deserialization.invalidversion").build();
71+
} catch (IllegalArgumentException e) {
72+
return failed(this).feedback("insecure-deserialization.expired").build();
73+
} catch (Exception e) {
74+
return failed(this).feedback("insecure-deserialization.invalidversion").build();
75+
}
76+
77+
delay = (int) (after - before);
78+
if (delay > 7000) {
79+
return failed(this).build();
80+
}
81+
if (delay < 3000) {
82+
return failed(this).build();
83+
}
84+
return success(this).build();
85+
}
86+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
3+
*
4+
* Copyright (c) 2002 - 2019 Bruce Mayhew
5+
*
6+
* This program is free software; you can redistribute it and/or modify it under the terms of the
7+
* GNU General Public License as published by the Free Software Foundation; either version 2 of the
8+
* License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
11+
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License along with this program; if
15+
* not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
16+
* 02111-1307, USA.
17+
*
18+
* Getting Source ==============
19+
*
20+
* Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
21+
*/
22+
23+
package org.owasp.webgoat.lessons.deserialization;
24+
25+
import java.io.ByteArrayInputStream;
26+
import java.io.IOException;
27+
import java.io.InvalidClassException;
28+
import java.io.ObjectInputStream;
29+
import java.util.Base64;
30+
import org.dummy.insecure.framework.VulnerableTaskHolder;
31+
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
32+
import org.owasp.webgoat.container.assignments.AssignmentHints;
33+
import org.owasp.webgoat.container.assignments.AttackResult;
34+
import org.springframework.web.bind.annotation.PostMapping;
35+
import org.springframework.web.bind.annotation.RequestParam;
36+
import org.springframework.web.bind.annotation.ResponseBody;
37+
import org.springframework.web.bind.annotation.RestController;
38+
39+
@RestController
40+
@AssignmentHints({
41+
"insecure-deserialization.hints.1",
42+
"insecure-deserialization.hints.2",
43+
"insecure-deserialization.hints.3"
44+
})
45+
public class InsecureDeserializationTask extends AssignmentEndpoint {
46+
47+
@PostMapping("/InsecureDeserialization/task")
48+
@ResponseBody
49+
public AttackResult completed(@RequestParam String token) throws IOException {
50+
String b64token;
51+
long before;
52+
long after;
53+
int delay;
54+
55+
b64token = token.replace('-', '+').replace('_', '/');
56+
57+
try (ObjectInputStream ois =
58+
new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)))) {
59+
before = System.currentTimeMillis();
60+
Object o = ois.readObject();
61+
if (!(o instanceof VulnerableTaskHolder)) {
62+
if (o instanceof String) {
63+
return failed(this).feedback("insecure-deserialization.stringobject").build();
64+
}
65+
return failed(this).feedback("insecure-deserialization.wrongobject").build();
66+
}
67+
after = System.currentTimeMillis();
68+
} catch (InvalidClassException e) {
69+
return failed(this).feedback("insecure-deserialization.invalidversion").build();
70+
} catch (IllegalArgumentException e) {
71+
return failed(this).feedback("insecure-deserialization.expired").build();
72+
} catch (Exception e) {
73+
return failed(this).feedback("insecure-deserialization.invalidversion").build();
74+
}
75+
76+
delay = (int) (after - before);
77+
if (delay > 7000) {
78+
return failed(this).build();
79+
}
80+
if (delay < 3000) {
81+
return failed(this).build();
82+
}
83+
return success(this).build();
84+
}
85+
}

0 commit comments

Comments
 (0)