Skip to content

Commit b0ba058

Browse files
committed
Add models for Apache Commons Lang and Text's Str[ing]Substitutor
1 parent f749c31 commit b0ba058

File tree

5 files changed

+687
-0
lines changed

5 files changed

+687
-0
lines changed

java/ql/src/semmle/code/java/frameworks/apache/Lang.qll

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,43 @@ private class ApacheStrLookupTaintGetter extends TaintPreservingCallable {
306306

307307
override predicate returnsTaintFrom(int arg) { arg = -1 }
308308
}
309+
310+
private class ApacheStrSubstitutor extends RefType {
311+
ApacheStrSubstitutor() {
312+
this.hasQualifiedName("org.apache.commons.lang3.text", "StrSubstitutor") or
313+
this.hasQualifiedName("org.apache.commons.text", "StringSubstitutor")
314+
}
315+
}
316+
317+
/**
318+
* A callable declared on Apache Commons `StrSubstitutor` that returns taint.
319+
*/
320+
private class ApacheStrSubstitutorTaintGetter extends TaintPreservingCallable {
321+
ApacheStrSubstitutorTaintGetter() {
322+
this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrSubstitutor and
323+
(
324+
this instanceof Constructor or
325+
this.getName() = "replace"
326+
)
327+
}
328+
329+
override predicate returnsTaintFrom(int arg) {
330+
arg in [0, -1]
331+
or
332+
this.isStatic() and arg = 1
333+
}
334+
}
335+
336+
/**
337+
* A callable declared on Apache Commons `StrSubstitutor` that transfers taint.
338+
*/
339+
private class ApacheStrSubstitutorTaintTransfer extends TaintPreservingCallable {
340+
ApacheStrSubstitutorTaintTransfer() {
341+
this.getSourceDeclaration().getDeclaringType() instanceof ApacheStrSubstitutor and
342+
this.getName() in ["replaceIn", "setVariableResolver"]
343+
}
344+
345+
override predicate transfersTaint(int src, int sink) {
346+
if this.getName() = "replaceIn" then (src = -1 and sink = 0) else (src = 0 and sink = -1)
347+
}
348+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import org.apache.commons.lang3.text.StrSubstitutor;
2+
import org.apache.commons.lang3.text.StrLookup;
3+
import org.apache.commons.lang3.text.StrMatcher;
4+
import org.apache.commons.lang3.text.StrBuilder;
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
import java.util.Properties;
8+
9+
class StrSubstitutorTest {
10+
String taint() { return "tainted"; }
11+
12+
void sink(Object o) {}
13+
14+
void test() throws Exception {
15+
Map<String, String> taintedMap = new HashMap<String, String>();
16+
taintedMap.put("key", taint());
17+
StrLookup<String> taintedLookup = StrLookup.mapLookup(taintedMap);
18+
19+
// Test constructors:
20+
StrSubstitutor ss1 = new StrSubstitutor(); ss1.setVariableResolver(taintedLookup); sink(ss1.replace("input")); // $hasTaintFlow=y
21+
StrSubstitutor ss2 = new StrSubstitutor(taintedMap); sink(ss2.replace("input")); // $hasTaintFlow=y
22+
StrSubstitutor ss3 = new StrSubstitutor(taintedMap, "{", "}"); sink(ss3.replace("input")); // $hasTaintFlow=y
23+
StrSubstitutor ss4 = new StrSubstitutor(taintedMap, "{", "}", ' '); sink(ss4.replace("input")); // $hasTaintFlow=y
24+
StrSubstitutor ss5 = new StrSubstitutor(taintedMap, "{", "}", ' ', ","); sink(ss5.replace("input")); // $hasTaintFlow=y
25+
StrSubstitutor ss6 = new StrSubstitutor(taintedLookup); sink(ss6.replace("input")); // $hasTaintFlow=y
26+
StrSubstitutor ss7 = new StrSubstitutor(taintedLookup, "{", "}", ' '); sink(ss7.replace("input")); // $hasTaintFlow=y
27+
StrSubstitutor ss8 = new StrSubstitutor(taintedLookup, "{", "}", ' ', ","); sink(ss8.replace("input")); // $hasTaintFlow=y
28+
StrSubstitutor ss9 = new StrSubstitutor(taintedLookup, (StrMatcher)null, null, ' '); sink(ss9.replace("input")); // $hasTaintFlow=y
29+
StrSubstitutor ss10 = new StrSubstitutor(taintedLookup, (StrMatcher)null, null, ' ', null); sink(ss10.replace("input")); // $hasTaintFlow=y
30+
31+
// Test replace overloads (tainted substitution map):
32+
StrSubstitutor taintedSubst = ss2;
33+
sink(taintedSubst.replace((Object)"input")); // $hasTaintFlow=y
34+
sink(taintedSubst.replace("input")); // $hasTaintFlow=y
35+
sink(taintedSubst.replace("input", 0, 0)); // $hasTaintFlow=y
36+
sink(taintedSubst.replace("input".toCharArray())); // $hasTaintFlow=y
37+
sink(taintedSubst.replace("input".toCharArray(), 0, 0)); // $hasTaintFlow=y
38+
sink(taintedSubst.replace((CharSequence)"input")); // $hasTaintFlow=y
39+
sink(taintedSubst.replace((CharSequence)"input", 0, 0)); // $hasTaintFlow=y
40+
sink(taintedSubst.replace(new StrBuilder("input"))); // $hasTaintFlow=y
41+
sink(taintedSubst.replace(new StrBuilder("input"), 0, 0)); // $hasTaintFlow=y
42+
sink(taintedSubst.replace(new StringBuilder("input"))); // $hasTaintFlow=y
43+
sink(taintedSubst.replace(new StringBuilder("input"), 0, 0)); // $hasTaintFlow=y
44+
sink(taintedSubst.replace(new StringBuffer("input"))); // $hasTaintFlow=y
45+
sink(taintedSubst.replace(new StringBuffer("input"), 0, 0)); // $hasTaintFlow=y
46+
47+
// Test replace overloads (tainted input):
48+
StrSubstitutor untaintedSubst = ss1;
49+
sink(untaintedSubst.replace((Object)taint())); // $hasTaintFlow=y
50+
sink(untaintedSubst.replace(taint())); // $hasTaintFlow=y
51+
sink(untaintedSubst.replace(taint(), 0, 0)); // $hasTaintFlow=y
52+
sink(untaintedSubst.replace(taint().toCharArray())); // $hasTaintFlow=y
53+
sink(untaintedSubst.replace(taint().toCharArray(), 0, 0)); // $hasTaintFlow=y
54+
sink(untaintedSubst.replace((CharSequence)taint())); // $hasTaintFlow=y
55+
sink(untaintedSubst.replace((CharSequence)taint(), 0, 0)); // $hasTaintFlow=y
56+
sink(untaintedSubst.replace(new StrBuilder(taint()))); // $hasTaintFlow=y
57+
sink(untaintedSubst.replace(new StrBuilder(taint()), 0, 0)); // $hasTaintFlow=y
58+
sink(untaintedSubst.replace(new StringBuilder(taint()))); // $hasTaintFlow=y
59+
sink(untaintedSubst.replace(new StringBuilder(taint()), 0, 0)); // $hasTaintFlow=y
60+
sink(untaintedSubst.replace(new StringBuffer(taint()))); // $hasTaintFlow=y
61+
sink(untaintedSubst.replace(new StringBuffer(taint()), 0, 0)); // $hasTaintFlow=y
62+
63+
// Test static replace methods:
64+
sink(StrSubstitutor.replace(taint(), new HashMap<String, String>())); // $hasTaintFlow=y
65+
sink(StrSubstitutor.replace(taint(), new HashMap<String, String>(), "{", "}")); // $hasTaintFlow=y
66+
sink(StrSubstitutor.replace("input", taintedMap)); // $hasTaintFlow=y
67+
sink(StrSubstitutor.replace("input", taintedMap, "{", "}")); // $hasTaintFlow=y
68+
Properties taintedProps = new Properties();
69+
taintedProps.put("key", taint());
70+
sink(StrSubstitutor.replace(taint(), new Properties())); // $hasTaintFlow=y
71+
sink(StrSubstitutor.replace("input", taintedProps)); // $hasTaintFlow=y
72+
73+
// Test replaceIn methods:
74+
StrBuilder strBuilder1 = new StrBuilder(); taintedSubst.replaceIn(strBuilder1); sink(strBuilder1.toString()); // $hasTaintFlow=y
75+
StrBuilder strBuilder2 = new StrBuilder(); taintedSubst.replaceIn(strBuilder2, 0, 0); sink(strBuilder2.toString()); // $hasTaintFlow=y
76+
StringBuilder stringBuilder1 = new StringBuilder(); taintedSubst.replaceIn(stringBuilder1); sink(stringBuilder1.toString()); // $hasTaintFlow=y
77+
StringBuilder stringBuilder2 = new StringBuilder(); taintedSubst.replaceIn(stringBuilder2, 0, 0); sink(stringBuilder2.toString()); // $hasTaintFlow=y
78+
StringBuffer stringBuffer1 = new StringBuffer(); taintedSubst.replaceIn(stringBuffer1); sink(stringBuffer1.toString()); // $hasTaintFlow=y
79+
StringBuffer stringBuffer2 = new StringBuffer(); taintedSubst.replaceIn(stringBuffer2, 0, 0); sink(stringBuffer2.toString()); // $hasTaintFlow=y
80+
}
81+
82+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import org.apache.commons.text.StringSubstitutor;
2+
import org.apache.commons.text.lookup.StringLookup;
3+
import org.apache.commons.text.lookup.StringLookupFactory;
4+
import org.apache.commons.text.matcher.StringMatcher;
5+
import org.apache.commons.text.TextStringBuilder;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import java.util.Properties;
9+
10+
class StringSubstitutorTextTest {
11+
String taint() { return "tainted"; }
12+
13+
void sink(Object o) {}
14+
15+
void test() throws Exception {
16+
Map<String, String> taintedMap = new HashMap<String, String>();
17+
taintedMap.put("key", taint());
18+
StringLookup taintedLookup = StringLookupFactory.INSTANCE.mapStringLookup(taintedMap);
19+
20+
// Test constructors:
21+
StringSubstitutor ss1 = new StringSubstitutor(); ss1.setVariableResolver(taintedLookup); sink(ss1.replace("input")); // $hasTaintFlow=y
22+
StringSubstitutor ss2 = new StringSubstitutor(taintedMap); sink(ss2.replace("input")); // $hasTaintFlow=y
23+
StringSubstitutor ss3 = new StringSubstitutor(taintedMap, "{", "}"); sink(ss3.replace("input")); // $hasTaintFlow=y
24+
StringSubstitutor ss4 = new StringSubstitutor(taintedMap, "{", "}", ' '); sink(ss4.replace("input")); // $hasTaintFlow=y
25+
StringSubstitutor ss5 = new StringSubstitutor(taintedMap, "{", "}", ' ', ","); sink(ss5.replace("input")); // $hasTaintFlow=y
26+
StringSubstitutor ss6 = new StringSubstitutor(taintedLookup); sink(ss6.replace("input")); // $hasTaintFlow=y
27+
StringSubstitutor ss7 = new StringSubstitutor(taintedLookup, "{", "}", ' '); sink(ss7.replace("input")); // $hasTaintFlow=y
28+
StringSubstitutor ss8 = new StringSubstitutor(taintedLookup, "{", "}", ' ', ","); sink(ss8.replace("input")); // $hasTaintFlow=y
29+
StringSubstitutor ss9 = new StringSubstitutor(taintedLookup, (StringMatcher)null, null, ' '); sink(ss9.replace("input")); // $hasTaintFlow=y
30+
StringSubstitutor ss10 = new StringSubstitutor(taintedLookup, (StringMatcher)null, null, ' ', null); sink(ss10.replace("input")); // $hasTaintFlow=y
31+
32+
// Test replace overloads (tainted substitution map):
33+
StringSubstitutor taintedSubst = ss2;
34+
sink(taintedSubst.replace((Object)"input")); // $hasTaintFlow=y
35+
sink(taintedSubst.replace("input")); // $hasTaintFlow=y
36+
sink(taintedSubst.replace("input", 0, 0)); // $hasTaintFlow=y
37+
sink(taintedSubst.replace("input".toCharArray())); // $hasTaintFlow=y
38+
sink(taintedSubst.replace("input".toCharArray(), 0, 0)); // $hasTaintFlow=y
39+
sink(taintedSubst.replace((CharSequence)"input")); // $hasTaintFlow=y
40+
sink(taintedSubst.replace((CharSequence)"input", 0, 0)); // $hasTaintFlow=y
41+
sink(taintedSubst.replace(new TextStringBuilder("input"))); // $hasTaintFlow=y
42+
sink(taintedSubst.replace(new TextStringBuilder("input"), 0, 0)); // $hasTaintFlow=y
43+
sink(taintedSubst.replace(new StringBuilder("input"))); // $hasTaintFlow=y
44+
sink(taintedSubst.replace(new StringBuilder("input"), 0, 0)); // $hasTaintFlow=y
45+
sink(taintedSubst.replace(new StringBuffer("input"))); // $hasTaintFlow=y
46+
sink(taintedSubst.replace(new StringBuffer("input"), 0, 0)); // $hasTaintFlow=y
47+
48+
// Test replace overloads (tainted input):
49+
StringSubstitutor untaintedSubst = ss1;
50+
sink(untaintedSubst.replace((Object)taint())); // $hasTaintFlow=y
51+
sink(untaintedSubst.replace(taint())); // $hasTaintFlow=y
52+
sink(untaintedSubst.replace(taint(), 0, 0)); // $hasTaintFlow=y
53+
sink(untaintedSubst.replace(taint().toCharArray())); // $hasTaintFlow=y
54+
sink(untaintedSubst.replace(taint().toCharArray(), 0, 0)); // $hasTaintFlow=y
55+
sink(untaintedSubst.replace((CharSequence)taint())); // $hasTaintFlow=y
56+
sink(untaintedSubst.replace((CharSequence)taint(), 0, 0)); // $hasTaintFlow=y
57+
sink(untaintedSubst.replace(new TextStringBuilder(taint()))); // $hasTaintFlow=y
58+
sink(untaintedSubst.replace(new TextStringBuilder(taint()), 0, 0)); // $hasTaintFlow=y
59+
sink(untaintedSubst.replace(new StringBuilder(taint()))); // $hasTaintFlow=y
60+
sink(untaintedSubst.replace(new StringBuilder(taint()), 0, 0)); // $hasTaintFlow=y
61+
sink(untaintedSubst.replace(new StringBuffer(taint()))); // $hasTaintFlow=y
62+
sink(untaintedSubst.replace(new StringBuffer(taint()), 0, 0)); // $hasTaintFlow=y
63+
64+
// Test static replace methods:
65+
sink(StringSubstitutor.replace(taint(), new HashMap<String, String>())); // $hasTaintFlow=y
66+
sink(StringSubstitutor.replace(taint(), new HashMap<String, String>(), "{", "}")); // $hasTaintFlow=y
67+
sink(StringSubstitutor.replace("input", taintedMap)); // $hasTaintFlow=y
68+
sink(StringSubstitutor.replace("input", taintedMap, "{", "}")); // $hasTaintFlow=y
69+
Properties taintedProps = new Properties();
70+
taintedProps.put("key", taint());
71+
sink(StringSubstitutor.replace(taint(), new Properties())); // $hasTaintFlow=y
72+
sink(StringSubstitutor.replace("input", taintedProps)); // $hasTaintFlow=y
73+
74+
// Test replaceIn methods:
75+
TextStringBuilder strBuilder1 = new TextStringBuilder(); taintedSubst.replaceIn(strBuilder1); sink(strBuilder1.toString()); // $hasTaintFlow=y
76+
TextStringBuilder strBuilder2 = new TextStringBuilder(); taintedSubst.replaceIn(strBuilder2, 0, 0); sink(strBuilder2.toString()); // $hasTaintFlow=y
77+
StringBuilder stringBuilder1 = new StringBuilder(); taintedSubst.replaceIn(stringBuilder1); sink(stringBuilder1.toString()); // $hasTaintFlow=y
78+
StringBuilder stringBuilder2 = new StringBuilder(); taintedSubst.replaceIn(stringBuilder2, 0, 0); sink(stringBuilder2.toString()); // $hasTaintFlow=y
79+
StringBuffer stringBuffer1 = new StringBuffer(); taintedSubst.replaceIn(stringBuffer1); sink(stringBuffer1.toString()); // $hasTaintFlow=y
80+
StringBuffer stringBuffer2 = new StringBuffer(); taintedSubst.replaceIn(stringBuffer2, 0, 0); sink(stringBuffer2.toString()); // $hasTaintFlow=y
81+
}
82+
83+
}

0 commit comments

Comments
 (0)