org.openrewrite.java.security.FindTextDirectionChanges Maven / Gradle / Ivy
Show all versions of rewrite-java-security Show documentation
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.java.security;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.Tree;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.Comment;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.Space;
import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FindTextDirectionChanges extends Recipe {
public static final char LRE = '\u202A';
public static final char RLE = '\u202B';
public static final char LRO = '\u202D';
public static final char RLO = '\u202E';
@Override
public Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(5);
}
public static final char LRI = '\u2066';
public static final char RLI = '\u2067';
public static final char FSI = '\u2068';
public static final char PDF = '\u202C';
public static final char PDI = '\u2069';
public static final Set sneakyCodes = Stream.of(LRE, RLE, LRO, RLO, LRI, RLI, FSI, PDF, PDI)
.collect(Collectors.toSet());
public static final Map charToText = new HashMap<>();
static {
charToText.put(LRE, "LRE");
charToText.put(RLE, "RLE");
charToText.put(LRO, "LRO");
charToText.put(RLO, "RLO");
charToText.put(LRI, "LRI");
charToText.put(RLI, "RLI");
charToText.put(FSI, "FSI");
charToText.put(PDF, "PDF");
charToText.put(PDI, "PDI");
}
@Override
public String getDisplayName() {
return "Find text-direction changes";
}
@Override
public String getDescription() {
return "Finds unicode control characters which can change the direction text is displayed in. " +
"These control characters can alter how source code is presented to a human reader without affecting its interpretation by tools like compilers. " +
"So a malicious patch could pass code review while introducing vulnerabilities. " +
"Note that text direction-changing unicode control characters aren't inherently malicious. " +
"These characters can appear for legitimate reasons in code written in or dealing with right-to-left languages. " +
"See: https://trojansource.codes/";
}
@Override
public Set getTags() {
return Collections.singleton("CVE-2021-42574");
}
@Override
protected JavaIsoVisitor getVisitor() {
return new JavaIsoVisitor() {
@Override
public @Nullable J visit(@Nullable Tree tree, ExecutionContext context) {
J j = super.visit(tree, context);
Object foundCodes = getCursor().pollMessage("FOUND_SNEAKY_CODES");
if (j != null && foundCodes != null) {
//noinspection unchecked
j = j.withMarkers(j.getMarkers().searchResult("Found text-direction altering unicode control characters: " +
String.join(",", (Set) foundCodes)));
}
return j;
}
@Override
public Space visitSpace(Space s, Space.Location loc, ExecutionContext context) {
Set foundCodes = null;
if (containsSneakyCodes(s.getWhitespace())) {
foundCodes = listSneakyCodes(s.getWhitespace());
}
if (containsSneakyCodes(s.getComments(), (Comment c) -> c.printComment(getCursor()))) {
if (foundCodes == null) {
foundCodes = new HashSet<>();
}
foundCodes.addAll(listSneakyCodes(s.getComments(), (Comment c) -> c.printComment(getCursor())));
}
if (containsSneakyCodes(s.getComments(), Comment::getSuffix)) {
if (foundCodes == null) {
foundCodes = new HashSet<>();
}
foundCodes.addAll(listSneakyCodes(s.getComments(), Comment::getSuffix));
}
if (foundCodes != null) {
getCursor().putMessage("FOUND_SNEAKY_CODES", foundCodes);
}
return s;
}
@Override
public J.Literal visitLiteral(J.Literal literal, ExecutionContext context) {
J.Literal l = super.visitLiteral(literal, context);
if (l.getType() == JavaType.Primitive.String && l.getValueSource() != null && containsSneakyCodes(l.getValueSource())) {
l = l.withMarkers(l.getMarkers().searchResult("Found text-direction altering unicode control characters: " +
String.join(",", listSneakyCodes(l.getValueSource()))));
}
return l;
}
};
}
private static boolean containsSneakyCodes(String s) {
for (char c : s.toCharArray()) {
if (sneakyCodes.contains(c)) {
return true;
}
}
return false;
}
private static boolean containsSneakyCodes(Collection collection, Function conversion) {
return collection.stream()
.map(conversion)
.anyMatch(FindTextDirectionChanges::containsSneakyCodes);
}
private static Set listSneakyCodes(String s) {
Set foundCodes = new HashSet<>();
for (char c : s.toCharArray()) {
if (sneakyCodes.contains(c)) {
foundCodes.add(charToText.get(c));
}
}
return foundCodes;
}
private static Set listSneakyCodes(Collection collection, Function conversion) {
return collection.stream()
.map(conversion)
.flatMap(suffix -> listSneakyCodes(suffix).stream())
.collect(Collectors.toSet());
}
}