Skip to content

Commit 2f2bce5

Browse files
committed
add secure flag remediation mapping
1 parent e0ecc18 commit 2f2bce5

File tree

9 files changed

+464
-3
lines changed

9 files changed

+464
-3
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public static List<Class<? extends CodeChanger>> asList() {
9090
SemgrepOverlyPermissiveFilePermissionsCodemod.class,
9191
SimplifyRestControllerAnnotationsCodemod.class,
9292
SubstituteReplaceAllCodemod.class,
93+
SonarCookieMissingSecureFlagCodemod.class,
9394
SonarJNDIInjectionCodemod.class,
9495
SonarObjectDeserializationCodemod.class,
9596
SonarRemoveUnthrowableExceptionCodemod.class,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.codemodder.codemods.sonar;
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.RuleHotspot;
8+
import io.codemodder.providers.sonar.SonarRemediatingJavaParserChanger;
9+
import io.codemodder.remediation.GenericRemediationMetadata;
10+
import io.codemodder.remediation.Remediator;
11+
import io.codemodder.remediation.missingsecureflag.MissingSecureFlagRemediator;
12+
import io.codemodder.sonar.model.Hotspot;
13+
import io.codemodder.sonar.model.SonarFinding;
14+
import java.util.List;
15+
import java.util.Objects;
16+
import java.util.Optional;
17+
import javax.inject.Inject;
18+
19+
@Codemod(
20+
id = "sonar:java/cookie-missing-secure-flag-2092",
21+
reviewGuidance = ReviewGuidance.MERGE_AFTER_CURSORY_REVIEW,
22+
importance = Importance.HIGH,
23+
executionPriority = CodemodExecutionPriority.HIGH)
24+
public final class SonarCookieMissingSecureFlagCodemod extends SonarRemediatingJavaParserChanger {
25+
26+
private final Remediator<Hotspot> remediationStrategy;
27+
private final RuleHotspot issues;
28+
29+
@Inject
30+
public SonarCookieMissingSecureFlagCodemod(
31+
@ProvidedSonarScan(ruleId = "java:S2092") final RuleHotspot hotspots) {
32+
super(GenericRemediationMetadata.MISSING_SECURE_FLAG.reporter(), hotspots);
33+
this.issues = Objects.requireNonNull(hotspots);
34+
this.remediationStrategy = new MissingSecureFlagRemediator<>();
35+
}
36+
37+
@Override
38+
public DetectorRule detectorRule() {
39+
return new DetectorRule(
40+
"java:S2092",
41+
"Make sure creating this cookie without the \"secure\" flag is safe here.",
42+
"https://rules.sonarsource.com/java/type/Security%20Hotspot/RSPEC-2092/");
43+
}
44+
45+
@Override
46+
public CodemodFileScanningResult visit(
47+
final CodemodInvocationContext context, final CompilationUnit cu) {
48+
List<Hotspot> issuesForFile = issues.getResultsByPath(context.path());
49+
return remediationStrategy.remediateAll(
50+
cu,
51+
context.path().toString(),
52+
detectorRule(),
53+
issuesForFile,
54+
SonarFinding::getKey,
55+
i -> i.getTextRange() != null ? i.getTextRange().getStartLine() : i.getLine(),
56+
i ->
57+
i.getTextRange() != null
58+
? Optional.of(i.getTextRange().getEndLine())
59+
: Optional.empty(),
60+
i -> Optional.empty());
61+
}
62+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package io.codemodder.codemods.sonar;
2+
3+
import io.codemodder.testutils.CodemodTestMixin;
4+
import io.codemodder.testutils.Metadata;
5+
import org.junit.jupiter.api.Nested;
6+
7+
final class SonarCookieMissingSecureFlagCodemodTest {
8+
9+
@Nested
10+
@Metadata(
11+
codemodType = SonarCookieMissingSecureFlagCodemod.class,
12+
testResourceDir = "sonar-missing-secure-flag-2092",
13+
renameTestFile =
14+
"src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookieAssignment.java",
15+
expectingFixesAtLines = {76},
16+
doRetransformTest = false,
17+
dependencies = {})
18+
final class SpoofCookieAssignmentTest implements CodemodTestMixin {}
19+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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 - 2021 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.spoofcookie;
24+
25+
import java.util.Map;
26+
import javax.servlet.http.Cookie;
27+
import javax.servlet.http.HttpServletResponse;
28+
import org.apache.commons.lang3.StringUtils;
29+
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
30+
import org.owasp.webgoat.container.assignments.AttackResult;
31+
import org.owasp.webgoat.lessons.spoofcookie.encoders.EncDec;
32+
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
33+
import org.springframework.web.bind.annotation.CookieValue;
34+
import org.springframework.web.bind.annotation.ExceptionHandler;
35+
import org.springframework.web.bind.annotation.GetMapping;
36+
import org.springframework.web.bind.annotation.PostMapping;
37+
import org.springframework.web.bind.annotation.RequestParam;
38+
import org.springframework.web.bind.annotation.ResponseBody;
39+
import org.springframework.web.bind.annotation.RestController;
40+
41+
/***
42+
*
43+
* @author Angel Olle Blazquez
44+
*
45+
*/
46+
47+
@RestController
48+
public class SpoofCookieAssignment extends AssignmentEndpoint {
49+
50+
private static final String COOKIE_NAME = "spoof_auth";
51+
private static final String COOKIE_INFO =
52+
"Cookie details for user %s:<br />" + COOKIE_NAME + "=%s";
53+
private static final String ATTACK_USERNAME = "tom";
54+
55+
private static final Map<String, String> users =
56+
Map.of("webgoat", "webgoat", "admin", "admin", ATTACK_USERNAME, "apasswordfortom");
57+
58+
@PostMapping(path = "/SpoofCookie/login")
59+
@ResponseBody
60+
@ExceptionHandler(UnsatisfiedServletRequestParameterException.class)
61+
public AttackResult login(
62+
@RequestParam String username,
63+
@RequestParam String password,
64+
@CookieValue(value = COOKIE_NAME, required = false) String cookieValue,
65+
HttpServletResponse response) {
66+
67+
if (StringUtils.isEmpty(cookieValue)) {
68+
return credentialsLoginFlow(username, password, response);
69+
} else {
70+
return cookieLoginFlow(cookieValue);
71+
}
72+
}
73+
74+
@GetMapping(path = "/SpoofCookie/cleanup")
75+
public void cleanup(HttpServletResponse response) {
76+
Cookie cookie = new Cookie(COOKIE_NAME, "");
77+
cookie.setSecure(true);
78+
cookie.setMaxAge(0);
79+
response.addCookie(cookie);
80+
}
81+
82+
private AttackResult credentialsLoginFlow(
83+
String username, String password, HttpServletResponse response) {
84+
String lowerCasedUsername = username.toLowerCase();
85+
if (ATTACK_USERNAME.equals(lowerCasedUsername)
86+
&& users.get(lowerCasedUsername).equals(password)) {
87+
return informationMessage(this).feedback("spoofcookie.cheating").build();
88+
}
89+
90+
String authPassword = users.getOrDefault(lowerCasedUsername, "");
91+
if (!authPassword.isBlank() && authPassword.equals(password)) {
92+
String newCookieValue = EncDec.encode(lowerCasedUsername);
93+
Cookie newCookie = new Cookie(COOKIE_NAME, newCookieValue);
94+
newCookie.setPath("/WebGoat");
95+
newCookie.setSecure(true);
96+
response.addCookie(newCookie);
97+
return informationMessage(this)
98+
.feedback("spoofcookie.login")
99+
.output(String.format(COOKIE_INFO, lowerCasedUsername, newCookie.getValue()))
100+
.build();
101+
}
102+
103+
return informationMessage(this).feedback("spoofcookie.wrong-login").build();
104+
}
105+
106+
private AttackResult cookieLoginFlow(String cookieValue) {
107+
String cookieUsername;
108+
try {
109+
cookieUsername = EncDec.decode(cookieValue).toLowerCase();
110+
} catch (Exception e) {
111+
// for providing some instructive guidance, we won't return 4xx error here
112+
return failed(this).output(e.getMessage()).build();
113+
}
114+
if (users.containsKey(cookieUsername)) {
115+
if (cookieUsername.equals(ATTACK_USERNAME)) {
116+
return success(this).build();
117+
}
118+
return failed(this)
119+
.feedback("spoofcookie.cookie-login")
120+
.output(String.format(COOKIE_INFO, cookieUsername, cookieValue))
121+
.build();
122+
}
123+
124+
return failed(this).feedback("spoofcookie.wrong-cookie").build();
125+
}
126+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 - 2021 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.spoofcookie;
24+
25+
import java.util.Map;
26+
import javax.servlet.http.Cookie;
27+
import javax.servlet.http.HttpServletResponse;
28+
import org.apache.commons.lang3.StringUtils;
29+
import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
30+
import org.owasp.webgoat.container.assignments.AttackResult;
31+
import org.owasp.webgoat.lessons.spoofcookie.encoders.EncDec;
32+
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
33+
import org.springframework.web.bind.annotation.CookieValue;
34+
import org.springframework.web.bind.annotation.ExceptionHandler;
35+
import org.springframework.web.bind.annotation.GetMapping;
36+
import org.springframework.web.bind.annotation.PostMapping;
37+
import org.springframework.web.bind.annotation.RequestParam;
38+
import org.springframework.web.bind.annotation.ResponseBody;
39+
import org.springframework.web.bind.annotation.RestController;
40+
41+
/***
42+
*
43+
* @author Angel Olle Blazquez
44+
*
45+
*/
46+
47+
@RestController
48+
public class SpoofCookieAssignment extends AssignmentEndpoint {
49+
50+
private static final String COOKIE_NAME = "spoof_auth";
51+
private static final String COOKIE_INFO =
52+
"Cookie details for user %s:<br />" + COOKIE_NAME + "=%s";
53+
private static final String ATTACK_USERNAME = "tom";
54+
55+
private static final Map<String, String> users =
56+
Map.of("webgoat", "webgoat", "admin", "admin", ATTACK_USERNAME, "apasswordfortom");
57+
58+
@PostMapping(path = "/SpoofCookie/login")
59+
@ResponseBody
60+
@ExceptionHandler(UnsatisfiedServletRequestParameterException.class)
61+
public AttackResult login(
62+
@RequestParam String username,
63+
@RequestParam String password,
64+
@CookieValue(value = COOKIE_NAME, required = false) String cookieValue,
65+
HttpServletResponse response) {
66+
67+
if (StringUtils.isEmpty(cookieValue)) {
68+
return credentialsLoginFlow(username, password, response);
69+
} else {
70+
return cookieLoginFlow(cookieValue);
71+
}
72+
}
73+
74+
@GetMapping(path = "/SpoofCookie/cleanup")
75+
public void cleanup(HttpServletResponse response) {
76+
Cookie cookie = new Cookie(COOKIE_NAME, "");
77+
cookie.setMaxAge(0);
78+
response.addCookie(cookie);
79+
}
80+
81+
private AttackResult credentialsLoginFlow(
82+
String username, String password, HttpServletResponse response) {
83+
String lowerCasedUsername = username.toLowerCase();
84+
if (ATTACK_USERNAME.equals(lowerCasedUsername)
85+
&& users.get(lowerCasedUsername).equals(password)) {
86+
return informationMessage(this).feedback("spoofcookie.cheating").build();
87+
}
88+
89+
String authPassword = users.getOrDefault(lowerCasedUsername, "");
90+
if (!authPassword.isBlank() && authPassword.equals(password)) {
91+
String newCookieValue = EncDec.encode(lowerCasedUsername);
92+
Cookie newCookie = new Cookie(COOKIE_NAME, newCookieValue);
93+
newCookie.setPath("/WebGoat");
94+
newCookie.setSecure(true);
95+
response.addCookie(newCookie);
96+
return informationMessage(this)
97+
.feedback("spoofcookie.login")
98+
.output(String.format(COOKIE_INFO, lowerCasedUsername, newCookie.getValue()))
99+
.build();
100+
}
101+
102+
return informationMessage(this).feedback("spoofcookie.wrong-login").build();
103+
}
104+
105+
private AttackResult cookieLoginFlow(String cookieValue) {
106+
String cookieUsername;
107+
try {
108+
cookieUsername = EncDec.decode(cookieValue).toLowerCase();
109+
} catch (Exception e) {
110+
// for providing some instructive guidance, we won't return 4xx error here
111+
return failed(this).output(e.getMessage()).build();
112+
}
113+
if (users.containsKey(cookieUsername)) {
114+
if (cookieUsername.equals(ATTACK_USERNAME)) {
115+
return success(this).build();
116+
}
117+
return failed(this)
118+
.feedback("spoofcookie.cookie-login")
119+
.output(String.format(COOKIE_INFO, cookieUsername, cookieValue))
120+
.build();
121+
}
122+
123+
return failed(this).feedback("spoofcookie.wrong-cookie").build();
124+
}
125+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"paging": {
3+
"pageIndex": 1,
4+
"pageSize": 100,
5+
"total": 1
6+
},
7+
"hotspots": [
8+
{
9+
"key": "AZPB23bawGhA7VQ2Ui-U",
10+
"component": "nahsra_WebGoatSonarDemo:src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookieAssignment.java",
11+
"project": "nahsra_WebGoatSonarDemo",
12+
"securityCategory": "insecure-conf",
13+
"vulnerabilityProbability": "LOW",
14+
"status": "TO_REVIEW",
15+
"line": 76,
16+
"message": "Make sure creating this cookie without the \"secure\" flag is safe here.",
17+
"assignee": "AYu2RswFLuhbfWU895e4",
18+
"author": "[email protected]",
19+
"creationDate": "2024-12-13T22:06:37+0100",
20+
"updateDate": "2024-12-13T22:09:25+0100",
21+
"textRange": {
22+
"startLine": 76,
23+
"endLine": 76,
24+
"startOffset": 24,
25+
"endOffset": 30
26+
},
27+
"flows": [],
28+
"ruleKey": "java:S2092"
29+
}
30+
],
31+
"components": [
32+
{
33+
"organization": "nahsra",
34+
"key": "nahsra_WebGoatSonarDemo:src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookieAssignment.java",
35+
"qualifier": "FIL",
36+
"name": "SpoofCookieAssignment.java",
37+
"longName": "src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookieAssignment.java",
38+
"path": "src/main/java/org/owasp/webgoat/lessons/spoofcookie/SpoofCookieAssignment.java"
39+
},
40+
{
41+
"organization": "nahsra",
42+
"key": "nahsra_WebGoatSonarDemo",
43+
"qualifier": "TRK",
44+
"name": "WebGoatSonarDemo",
45+
"longName": "WebGoatSonarDemo"
46+
}
47+
]
48+
}

0 commit comments

Comments
 (0)