Java: Move some taint tests.

This commit is contained in:
Anders Schack-Mulligen
2020-02-03 11:55:13 +01:00
parent d995d5a4a0
commit 2b1723dd88
18 changed files with 618 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
class Test {
public static void ioutils() {
InputStream inp = new FileInputStream("test"); // user input
InputStream buf = IOUtils.buffer(inp);
List<String> lines = IOUtils.readLines(inp, "UTF-8");
byte[] bytes = IOUtils.readFully(inp, 1000);
InputStream buf2 = IOUtils.toBufferedInputStream(inp);
Reader bufread = IOUtils.toBufferedReader(new InputStreamReader(inp));
byte[] bytes2 = IOUtils.toByteArray(inp, 1000);
char[] chars = IOUtils.toCharArray(inp, "UTF-8");
String s = IOUtils.toString(inp, "UTF-8");
InputStream is = IOUtils.toInputStream(s, "UTF-8");
StringWriter writer = new StringWriter();
writer.toString(); // not tainted
IOUtils.copy(inp, writer, "UTF-8");
writer.toString(); // tainted
writer = new StringWriter();
writer.toString(); // not tainted
IOUtils.copyLarge(bufread, writer);
writer.toString(); // tainted
byte x;
byte[] tgt = new byte[100];
x = tgt[0]; // not tainted
IOUtils.read(inp, tgt);
x = tgt[0]; // tainted
tgt = new byte[100];
x = tgt[0]; // not tainted
IOUtils.readFully(inp, tgt);
x = tgt[0]; // tainted
writer = new StringWriter();
writer.toString(); // not tainted
IOUtils.write(chars, writer);
writer.toString(); // tainted
writer = new StringWriter();
writer.toString(); // not tainted
IOUtils.writeChunked(chars, writer);
writer.toString(); // tainted
writer = new StringWriter();
writer.toString(); // not tainted
IOUtils.writeLines(lines, "\n", writer);
writer.toString(); // tainted
writer = new StringWriter();
writer.toString(); // not tainted
IOUtils.writeLines(new ArrayList<String>(), s, writer);
writer.toString(); // tainted
}
}

View File

@@ -0,0 +1,54 @@
| Test.java:12:21:12:47 | new FileInputStream(...) |
| Test.java:14:21:14:39 | buffer(...) |
| Test.java:14:36:14:38 | inp |
| Test.java:15:24:15:54 | readLines(...) |
| Test.java:15:42:15:44 | inp |
| Test.java:16:18:16:45 | readFully(...) |
| Test.java:16:36:16:38 | inp |
| Test.java:17:22:17:55 | toBufferedInputStream(...) |
| Test.java:17:52:17:54 | inp |
| Test.java:18:20:18:71 | toBufferedReader(...) |
| Test.java:18:45:18:70 | new InputStreamReader(...) |
| Test.java:18:67:18:69 | inp |
| Test.java:19:19:19:48 | toByteArray(...) |
| Test.java:19:39:19:41 | inp |
| Test.java:20:18:20:50 | toCharArray(...) |
| Test.java:20:38:20:40 | inp |
| Test.java:21:14:21:43 | toString(...) |
| Test.java:21:31:21:33 | inp |
| Test.java:22:20:22:52 | toInputStream(...) |
| Test.java:22:42:22:42 | s |
| Test.java:26:16:26:18 | inp |
| Test.java:26:21:26:26 | writer |
| Test.java:27:3:27:8 | writer |
| Test.java:27:3:27:19 | toString(...) |
| Test.java:31:21:31:27 | bufread |
| Test.java:31:30:31:35 | writer |
| Test.java:32:3:32:8 | writer |
| Test.java:32:3:32:19 | toString(...) |
| Test.java:37:16:37:18 | inp |
| Test.java:37:21:37:23 | tgt |
| Test.java:38:3:38:12 | ...=... |
| Test.java:38:7:38:9 | tgt |
| Test.java:38:7:38:12 | ...[...] |
| Test.java:42:21:42:23 | inp |
| Test.java:42:26:42:28 | tgt |
| Test.java:43:3:43:12 | ...=... |
| Test.java:43:7:43:9 | tgt |
| Test.java:43:7:43:12 | ...[...] |
| Test.java:47:17:47:21 | chars |
| Test.java:47:24:47:29 | writer |
| Test.java:48:3:48:8 | writer |
| Test.java:48:3:48:19 | toString(...) |
| Test.java:52:24:52:28 | chars |
| Test.java:52:31:52:36 | writer |
| Test.java:53:3:53:8 | writer |
| Test.java:53:3:53:19 | toString(...) |
| Test.java:57:22:57:26 | lines |
| Test.java:57:35:57:40 | writer |
| Test.java:58:3:58:8 | writer |
| Test.java:58:3:58:19 | toString(...) |
| Test.java:62:47:62:47 | s |
| Test.java:62:50:62:55 | writer |
| Test.java:63:3:63:8 | writer |
| Test.java:63:3:63:19 | toString(...) |

View File

@@ -0,0 +1,15 @@
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
class Conf extends TaintTracking::Configuration {
Conf() { this = "qltest:dataflow:ioutils" }
override predicate isSource(DataFlow::Node source) { source instanceof UserInput }
override predicate isSink(DataFlow::Node sink) { any() }
}
from UserInput u, DataFlow::Node e, Conf config
where config.hasFlow(u, e) and e.getEnclosingCallable().hasName("ioutils")
select e

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/apache-commons-io-2.6

View File

@@ -0,0 +1,159 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.StringTokenizer;
public class B {
public static String[] taint() { return new String[] { "tainted" }; }
public static void sink(Object o) { }
public static void maintest() {
String[] args = taint();
// tainted - access to main args
String[] aaaargs = args;
sink(aaaargs);
// tainted - access to tainted array
String s = args[0];
sink(s);
// tainted - concatenation of tainted string
String concat = "Look at me " + s + ", I'm tainted!";
sink(concat);
// tainted - parenthesised
String pars = (concat);
sink(pars);
// tainted method argument, implies tainted return value
String method = tainty(pars);
sink(method);
// tainted - complex
String complex = ("Look at me " + args[0]) + ", I'm tainted!";
sink(complex);
// tainted - data preserving constructors
String constructed = new String(complex);
sink(constructed);
// tainted - unsafe escape
String badEscape = constructed.replaceAll("(<script>)", "");
sink(badEscape);
// tainted - tokenized string
String token = new StringTokenizer(badEscape).nextToken();
sink(token);
// not tainted
String safe = notTainty(complex);
sink(safe);
String shouldBeFine = taintyOtherArg(safe, complex);
sink(shouldBeFine);
// non-whitelisted constructors don't pass taint
StringWrapper herring = new StringWrapper(complex);
sink(herring);
// tainted equality check with constant
boolean cond = "foo" == s;
sink(cond);
// tainted logic with tainted operand
boolean logic = cond && safe();
sink(logic);
// tainted condition
sink(concat.endsWith("I'm tainted"));
// tainted
logic = safe() || cond;
sink(logic);
// tainted, use of equals
logic = badEscape.equals("constant");
sink(logic);
// not tainted
boolean okay = s == shouldBeFine;
sink(okay);
// methods on string that pass on taint
String trimmed = s.trim();
sink(trimmed);
String[] split = s.split(" ");
sink(split);
String lower = s.toLowerCase();
sink(lower);
String upper = s.toUpperCase();
sink(upper);
byte[] bytes = s.getBytes("UTF-8");
sink(bytes);
String toString = s.toString();
sink(toString);
String subs = s.substring(1, 10);
sink(subs);
String repl = "some constant".replace(" ", s);
sink(repl);
String replAll = "some constant".replaceAll(" ", s);
sink(replAll);
String replFirst = "some constant".replaceFirst(" ", s);
sink(replFirst);
ByteArrayOutputStream baos = null;
ObjectOutput oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try
{
// serialization of tainted string
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(s);
byte[] serializedData = baos.toByteArray(); // tainted
sink(serializedData);
// serialization of fixed string
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject("not tainted");
byte[] serializedData2 = baos.toByteArray(); // *not* tainted
sink(serializedData2);
// de-serialization of tainted string
bais = new ByteArrayInputStream(serializedData);
ois = new ObjectInputStream(bais);
String deserializedData = (String)ois.readObject(); // tainted
sink(deserializedData);
} catch (IOException e) {
// ignored in test code
} catch (ClassNotFoundException e) {
// ignored in test code
}
// tainted array initializers
String[] taintedArray = new String[] { s };
sink(taintedArray);
String[][] taintedArray2 = new String[][] { { s } };
sink(taintedArray2);
String[][][] taintedArray3 = new String[][][] { { { s } } };
sink(taintedArray3);
return;
}
public static String tainty(String arg) {
// tainted return value
return arg;
}
public static String taintyOtherArg(String safe, String tainted) {
return safe;
}
public static String notTainty(String arg) {
return "foo";
}
public static class StringWrapper {
public String wrapped;
public StringWrapper(String s) {
this.wrapped = s;
}
}
public static boolean safe() {
return true;
}
}

View File

@@ -0,0 +1,38 @@
public class MethodFlow {
public static String taint() { return "tainted"; }
public static void sink(String s) { }
public void test() {
String tainted = taint();
sink(tainted);
String tainted2 = notNull(taint());
sink(tainted2);
String tainted3 = wrapNotNull(taint());
sink(tainted3);
String safe = notNull("a constant");
sink(safe);
String diffString = returnDiffString(taint());
sink(diffString);
}
public <T> T notNull(T x) {
if (x == null) {
throw new NullPointerException();
}
return x;
}
public <T> T wrapNotNull(T x) {
T res = notNull(x);
sink("Logged: " + res);
return res;
}
public String returnDiffString(String x) {
sink("Received: " + x);
return "OK";
}
}

View File

@@ -0,0 +1,66 @@
public class StringBuilderTests {
public static String taint() { return "tainted"; }
public static void sink(String s) { }
static void stringBuilderBad() {
StringBuilder sb = new StringBuilder();
sb.append("from preferences select locale where user='");
sb.append(taint());
sb.append("'");
sink(sb.toString());
}
static void stringBuilderOkay() {
StringBuilder sb = new StringBuilder();
sb.append("from preferences select locale where user='");
sb.append("fred");
sb.append("'");
sink(sb.toString());
}
static void stringBufferBad() {
StringBuffer sb = new StringBuffer();
sb.append("from preferences select locale where user='");
sb.append(taint());
sb.append("'");
sink(sb.toString());
}
static void stringBuilderNoVarBad() {
sink(new StringBuilder()
.append("from preferences select locale where user='")
.append(taint())
.append("'").toString()
);
}
static void stringBuilderConstructorBad() {
StringBuilder sb = new StringBuilder(taint());
sb.append("from preferences select locale where user='");
sb.append("fred");
sb.append("'");
sink(sb.toString());
}
static void stringBuilderMultipleAppendsBad() {
StringBuilder sb = new StringBuilder();
sb.append("from preferences select locale where user='").append(taint());
sb.append("'");
sink(sb.toString());
}
static void stringBuilderReplaceBad() {
StringBuilder sb = new StringBuilder();
sb.append("from preferences select locale where user='placeholder'");
sb.replace(45, 57, taint());
sink(sb.toString());
}
static void stringBuilderInsertBad() {
StringBuilder sb = new StringBuilder();
sb.append("from preferences select locale where user=''");
sb.insert(45, taint());
sink(sb.toString());
}
}

View File

@@ -0,0 +1,26 @@
public class Varargs {
public String taint() { return "tainted"; }
public void sink(String s) { }
public void sources() {
f1(taint());
f2(taint(), taint());
f3(new String[] { taint(), "" });
}
public void f1(String... ss) {
String s = ss[0];
sink(s);
}
public void f2(String... ss) {
String s = ss[0];
sink(s);
}
public void f3(String... ss) {
String s = ss[0];
sink(s);
}
}

View File

@@ -1,2 +1,47 @@
| A.java:10:19:10:25 | taint(...) | A.java:15:10:15:11 | b2 |
| A.java:20:19:20:25 | taint(...) | A.java:25:10:25:11 | b2 |
| B.java:15:21:15:27 | taint(...) | B.java:18:10:18:16 | aaaargs |
| B.java:15:21:15:27 | taint(...) | B.java:21:10:21:10 | s |
| B.java:15:21:15:27 | taint(...) | B.java:24:10:24:15 | concat |
| B.java:15:21:15:27 | taint(...) | B.java:27:10:27:13 | pars |
| B.java:15:21:15:27 | taint(...) | B.java:30:10:30:15 | method |
| B.java:15:21:15:27 | taint(...) | B.java:33:10:33:16 | complex |
| B.java:15:21:15:27 | taint(...) | B.java:36:10:36:20 | constructed |
| B.java:15:21:15:27 | taint(...) | B.java:39:10:39:18 | badEscape |
| B.java:15:21:15:27 | taint(...) | B.java:42:10:42:14 | token |
| B.java:15:21:15:27 | taint(...) | B.java:55:10:55:13 | cond |
| B.java:15:21:15:27 | taint(...) | B.java:58:10:58:14 | logic |
| B.java:15:21:15:27 | taint(...) | B.java:60:10:60:39 | endsWith(...) |
| B.java:15:21:15:27 | taint(...) | B.java:63:10:63:14 | logic |
| B.java:15:21:15:27 | taint(...) | B.java:66:10:66:14 | logic |
| B.java:15:21:15:27 | taint(...) | B.java:74:10:74:16 | trimmed |
| B.java:15:21:15:27 | taint(...) | B.java:76:10:76:14 | split |
| B.java:15:21:15:27 | taint(...) | B.java:78:10:78:14 | lower |
| B.java:15:21:15:27 | taint(...) | B.java:80:10:80:14 | upper |
| B.java:15:21:15:27 | taint(...) | B.java:82:10:82:14 | bytes |
| B.java:15:21:15:27 | taint(...) | B.java:84:10:84:17 | toString |
| B.java:15:21:15:27 | taint(...) | B.java:86:10:86:13 | subs |
| B.java:15:21:15:27 | taint(...) | B.java:88:10:88:13 | repl |
| B.java:15:21:15:27 | taint(...) | B.java:90:10:90:16 | replAll |
| B.java:15:21:15:27 | taint(...) | B.java:92:10:92:18 | replFirst |
| B.java:15:21:15:27 | taint(...) | B.java:105:12:105:25 | serializedData |
| B.java:15:21:15:27 | taint(...) | B.java:117:12:117:27 | deserializedData |
| B.java:15:21:15:27 | taint(...) | B.java:126:10:126:21 | taintedArray |
| B.java:15:21:15:27 | taint(...) | B.java:128:10:128:22 | taintedArray2 |
| B.java:15:21:15:27 | taint(...) | B.java:130:10:130:22 | taintedArray3 |
| MethodFlow.java:7:22:7:28 | taint(...) | MethodFlow.java:8:10:8:16 | tainted |
| MethodFlow.java:9:31:9:37 | taint(...) | MethodFlow.java:10:10:10:17 | tainted2 |
| MethodFlow.java:11:35:11:41 | taint(...) | MethodFlow.java:12:10:12:17 | tainted3 |
| MethodFlow.java:11:35:11:41 | taint(...) | MethodFlow.java:30:10:30:25 | ... + ... |
| MethodFlow.java:17:42:17:48 | taint(...) | MethodFlow.java:35:10:35:25 | ... + ... |
| StringBuilderTests.java:9:15:9:21 | taint(...) | StringBuilderTests.java:11:10:11:22 | toString(...) |
| StringBuilderTests.java:25:15:25:21 | taint(...) | StringBuilderTests.java:27:10:27:22 | toString(...) |
| StringBuilderTests.java:33:15:33:21 | taint(...) | StringBuilderTests.java:31:10:34:29 | toString(...) |
| StringBuilderTests.java:39:42:39:48 | taint(...) | StringBuilderTests.java:43:10:43:22 | toString(...) |
| StringBuilderTests.java:48:69:48:75 | taint(...) | StringBuilderTests.java:50:10:50:22 | toString(...) |
| StringBuilderTests.java:56:24:56:30 | taint(...) | StringBuilderTests.java:57:10:57:22 | toString(...) |
| StringBuilderTests.java:63:19:63:25 | taint(...) | StringBuilderTests.java:64:10:64:22 | toString(...) |
| Varargs.java:7:8:7:14 | taint(...) | Varargs.java:14:10:14:10 | s |
| Varargs.java:8:8:8:14 | taint(...) | Varargs.java:19:10:19:10 | s |
| Varargs.java:8:17:8:23 | taint(...) | Varargs.java:19:10:19:10 | s |
| Varargs.java:9:23:9:29 | taint(...) | Varargs.java:24:10:24:10 | s |

View File

@@ -0,0 +1,49 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class A {
public static void main(String[] args) {
String[] a = args; // user input
String s = args[0]; // user input
}
public static void userInput() throws SQLException, IOException, MalformedURLException {
System.getenv("test"); // user input
class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.getParameter("test"); // remote user input
req.getHeader("test"); // remote user input
req.getQueryString(); // remote user input
req.getCookies()[0].getValue(); // remote user input
}
}
new Properties().getProperty("test"); // user input
System.getProperty("test"); // user input
new Object() {
public void test(ResultSet rs) throws SQLException {
rs.getString(0); // user input
}
};
new URL("test").openConnection().getInputStream(); // remote user input
new Socket("test", 1234).getInputStream(); // remote user input
InetAddress.getByName("test").getHostName(); // remote user input
System.in.read(); // user input
new FileInputStream("test").read(); // user input
}
}

View File

@@ -0,0 +1,8 @@
package security.library.dataflow;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface RmiFlow extends Remote {
String listDirectory(String path);
}

View File

@@ -0,0 +1,15 @@
package security.library.dataflow;
public class RmiFlowImpl implements RmiFlow {
public String listDirectory(String path) {
String command = "ls " + path;
Runtime.getRuntime().exec(command);
return "pretend there are some results here";
}
public String notRemotable(String path) {
String command = "ls " + path;
Runtime.getRuntime().exec(command);
return "pretend there are some results here";
}
}

View File

@@ -0,0 +1,10 @@
| A.java:17:27:17:39 | args | A.java:17:27:17:39 | args |
| A.java:17:27:17:39 | args | A.java:18:18:18:21 | args |
| A.java:17:27:17:39 | args | A.java:19:16:19:19 | args |
| A.java:17:27:17:39 | args | A.java:19:16:19:22 | ...[...] |
| A.java:23:5:23:25 | getenv(...) | A.java:23:5:23:25 | getenv(...) |
| A.java:34:5:34:40 | getProperty(...) | A.java:34:5:34:40 | getProperty(...) |
| A.java:35:5:35:30 | getProperty(...) | A.java:35:5:35:30 | getProperty(...) |
| A.java:38:9:38:23 | getString(...) | A.java:38:9:38:23 | getString(...) |
| A.java:45:5:45:13 | System.in | A.java:45:5:45:13 | System.in |
| A.java:46:5:46:31 | new FileInputStream(...) | A.java:46:5:46:31 | new FileInputStream(...) |

View File

@@ -0,0 +1,18 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
class Conf extends TaintTracking::Configuration {
Conf() { this = "remote taint conf" }
override predicate isSource(DataFlow::Node n) {
n instanceof UserInput and
not n instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node n) { any() }
}
from DataFlow::Node src, DataFlow::Node sink, Conf conf
where conf.hasFlow(src, sink)
select src, sink

View File

@@ -0,0 +1 @@
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4

View File

@@ -0,0 +1,11 @@
| A.java:28:9:28:32 | getParameter(...) | A.java:28:9:28:32 | getParameter(...) |
| A.java:29:9:29:29 | getHeader(...) | A.java:29:9:29:29 | getHeader(...) |
| A.java:30:9:30:28 | getQueryString(...) | A.java:30:9:30:28 | getQueryString(...) |
| A.java:31:9:31:38 | getValue(...) | A.java:31:9:31:38 | getValue(...) |
| A.java:41:5:41:53 | getInputStream(...) | A.java:41:5:41:53 | getInputStream(...) |
| A.java:42:5:42:45 | getInputStream(...) | A.java:42:5:42:45 | getInputStream(...) |
| A.java:43:5:43:47 | getHostName(...) | A.java:43:5:43:47 | getHostName(...) |
| RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:4:30:4:40 | path |
| RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:5:20:5:31 | ... + ... |
| RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:5:28:5:31 | path |
| RmiFlowImpl.java:4:30:4:40 | path | RmiFlowImpl.java:6:29:6:35 | command |

View File

@@ -0,0 +1,15 @@
import java
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources
class Conf extends TaintTracking::Configuration {
Conf() { this = "remote taint conf" }
override predicate isSource(DataFlow::Node n) { n instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node n) { any() }
}
from DataFlow::Node src, DataFlow::Node sink, Conf conf
where conf.hasFlow(src, sink)
select src, sink

View File

@@ -0,0 +1,22 @@
package org.apache.commons.io;
import java.io.*;
import java.util.*;
public class IOUtils {
public static BufferedInputStream buffer(InputStream inputStream) { return null; }
public static void copy(InputStream input, Writer output, String inputEncoding) throws IOException { }
public static long copyLarge(Reader input, Writer output) throws IOException { return 42; }
public static int read(InputStream input, byte[] buffer) throws IOException { return 42; }
public static void readFully(InputStream input, byte[] buffer) throws IOException { }
public static byte[] readFully(InputStream input, int length) throws IOException { return null; }
public static List<String> readLines(InputStream input, String encoding) throws IOException { return null; }
public static InputStream toBufferedInputStream(InputStream input) throws IOException { return null; }
public static BufferedReader toBufferedReader(Reader reader) { return null; }
public static byte[] toByteArray(InputStream input, int size) throws IOException { return null; }
public static char[] toCharArray(InputStream is, String encoding) throws IOException { return null; }
public static InputStream toInputStream(String input, String encoding) throws IOException { return null; }
public static String toString(InputStream input, String encoding) throws IOException { return null; }
public static void write(char[] data, Writer output) throws IOException { }
public static void writeChunked(char[] data, Writer output) throws IOException { }
public static void writeLines(Collection<?> lines, String lineEnding, Writer writer) throws IOException { }
}