diff --git a/csharp/ql/lib/ext/System.IO.model.yml b/csharp/ql/lib/ext/System.IO.model.yml index 49b0818ef5f..8222f51d624 100644 --- a/csharp/ql/lib/ext/System.IO.model.yml +++ b/csharp/ql/lib/ext/System.IO.model.yml @@ -4,6 +4,23 @@ extensions: extensible: sourceModel data: - ["System.IO", "FileStream", False, "FileStream", "", "", "Argument[this]", "file", "manual"] + - ["System.IO", "FileStream", False, "FileStream", "", "", "Argument[this]", "file-write", "manual"] + - ["System.IO", "StreamWriter", False, "StreamWriter", "(System.String)", "", "Argument[this]", "file-write", "manual"] + - ["System.IO", "StreamWriter", False, "StreamWriter", "(System.String,System.Boolean)", "", "Argument[this]", "file-write", "manual"] + - ["System.IO", "StreamWriter", False, "StreamWriter", "(System.String,System.Boolean,System.Text.Encoding)", "", "Argument[this]", "file-write", "manual"] + - ["System.IO", "StreamWriter", False, "StreamWriter", "(System.String,System.Boolean,System.Text.Encoding,System.Int32)", "", "Argument[this]", "file-write", "manual"] + - ["System.IO", "StreamWriter", False, "StreamWriter", "(System.String,System.Text.Encoding,System.IO.FileStreamOptions)", "", "Argument[this]", "file-write", "manual"] + - ["System.IO", "StreamWriter", False, "StreamWriter", "(System.String,System.IO.FileStreamOptions)", "", "Argument[this]", "file-write", "manual"] + - ["System.IO", "File", False, "Open", "", "", "ReturnValue", "file-write", "manual"] + - ["System.IO", "File", False, "OpenWrite", "", "", "ReturnValue", "file-write", "manual"] + - ["System.IO", "File", False, "Create", "", "", "ReturnValue", "file-write", "manual"] + - ["System.IO", "File", False, "CreateText", "", "", "ReturnValue", "file-write", "manual"] + - ["System.IO", "File", False, "AppendText", "", "", "ReturnValue", "file-write", "manual"] + - ["System.IO", "FileInfo", False, "Open", "", "", "ReturnValue", "file-write", "manual"] + - ["System.IO", "FileInfo", False, "OpenWrite", "", "", "ReturnValue", "file-write", "manual"] + - ["System.IO", "FileInfo", False, "Create", "", "", "ReturnValue", "file-write", "manual"] + - ["System.IO", "FileInfo", False, "CreateText", "", "", "ReturnValue", "file-write", "manual"] + - ["System.IO", "FileInfo", False, "AppendText", "", "", "ReturnValue", "file-write", "manual"] - addsTo: pack: codeql/csharp-all extensible: summaryModel diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll index 50784e7d1d9..89140d44af0 100644 --- a/csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll +++ b/csharp/ql/lib/semmle/code/csharp/dataflow/ExternalFlow.qll @@ -215,7 +215,7 @@ module ModelValidation { ) or exists(string kind | sourceModel(_, _, _, _, _, _, _, kind, _) | - not kind = ["local", "remote", "file"] and + not kind = ["local", "remote", "file", "file-write"] and result = "Invalid kind \"" + kind + "\" in source model." ) } diff --git a/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll b/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll index 30736bdabf6..26b75f06269 100644 --- a/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll +++ b/csharp/ql/lib/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll @@ -6,6 +6,7 @@ import csharp private import Remote private import semmle.code.csharp.commons.Loggers private import semmle.code.csharp.frameworks.system.Web +private import semmle.code.csharp.frameworks.system.IO private import semmle.code.csharp.dataflow.ExternalFlow /** @@ -63,3 +64,56 @@ class CookieStorageSink extends ExternalLocationSink, RemoteFlowSink { ) } } + +private predicate isFileWriteCall(Expr stream, Expr data) { + exists(MethodCall mc, Method m | mc.getTarget() = m.getAnOverrider*() | + m.hasQualifiedName("System.IO", "Stream", ["Write", "WriteAsync"]) and + stream = mc.getQualifier() and + data = mc.getArgument(0) + or + m.hasQualifiedName("System.IO", "TextWriter", + ["Write", "WriteAsync", "WriteLine", "WriteLineAsync"]) and + stream = mc.getQualifier() and + data = mc.getArgument(0) + or + m.hasQualifiedName("System.Xml.Linq", "XDocument", ["Save", "SaveAsync"]) and + data = mc.getQualifier() and + stream = mc.getArgument(0) + ) +} + +/** A configuration for tracking flow from calls that open a file in write mode to methods that write to that file, excluding encrypted streams. */ +private module LocalFileOutputStreamConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node src) { sourceNode(src, "file-write") } + + predicate isSink(DataFlow::Node sink) { isFileWriteCall(sink.asExpr(), _) } + + predicate isBarrier(DataFlow::Node node) { + node.asExpr() + .(ObjectCreation) + .getObjectType() + .hasQualifiedName("System.Security.Cryptography", "CryptoStream") + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(ObjectCreation oc | + node2.asExpr() = oc and + node1.asExpr() = oc.getArgument(0) and + oc.getObjectType() instanceof SystemIOStreamWriterClass + ) + } +} + +private module LocalFileOutputStreamFlow = DataFlow::Global; + +/** + * A write to the local filesystem. + */ +class LocalFileOutputSink extends ExternalLocationSink { + LocalFileOutputSink() { + exists(DataFlow::Node streamSink | + LocalFileOutputStreamFlow::flowTo(streamSink) and + isFileWriteCall(streamSink.asExpr(), this.asExpr()) + ) + } +} diff --git a/csharp/ql/src/change-notes/2023-04-05-external-location-sinks.md b/csharp/ql/src/change-notes/2023-04-05-external-location-sinks.md new file mode 100644 index 00000000000..e306794f657 --- /dev/null +++ b/csharp/ql/src/change-notes/2023-04-05-external-location-sinks.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Additional sinks modelling writes to unencrypted local files have been added to `ExternalLocationSink`, used by the `cs/cleartext-storage` and `cs/exposure-of-sensitive-information` queries. \ No newline at end of file diff --git a/csharp/ql/test/query-tests/Security Features/CWE-312/CleartextStorage.cs b/csharp/ql/test/query-tests/Security Features/CWE-312/CleartextStorage.cs index e8dd58c0d4f..74a5568b668 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-312/CleartextStorage.cs +++ b/csharp/ql/test/query-tests/Security Features/CWE-312/CleartextStorage.cs @@ -2,6 +2,8 @@ using System.Text; using System.Web; using System.Web.Security; using System.Windows.Forms; +using System.IO; +using System.Security.Cryptography; public class ClearTextStorageHandler : IHttpHandler { @@ -24,6 +26,22 @@ public class ClearTextStorageHandler : IHttpHandler logger.Warn(GetPassword()); // GOOD: Logging encrypted sensitive data logger.Warn(Encode(GetPassword(), "Password")); + + // BAD: Storing sensitive data in local file + using (var writeStream = File.Open("passwords.txt", FileMode.Create)) + { + var writer = new StreamWriter(writeStream); + writer.Write(GetPassword()); + writer.Close(); + } + + // GOOD: Storing encrypted sensitive data + using (var writeStream = File.Open("passwords.txt", FileMode.Create)) + { + var writer = new StreamWriter(new CryptoStream(writeStream, GetEncryptor(), CryptoStreamMode.Write)); + writer.Write(GetPassword()); + writer.Close(); + } } public string Encode(string value, string type) @@ -31,6 +49,10 @@ public class ClearTextStorageHandler : IHttpHandler return Encoding.UTF8.GetString(MachineKey.Protect(Encoding.UTF8.GetBytes(value), type)); } + public ICryptoTransform GetEncryptor(){ + return null; + } + public string GetPassword() { return "password"; diff --git a/csharp/ql/test/query-tests/Security Features/CWE-312/CleartextStorage.expected b/csharp/ql/test/query-tests/Security Features/CWE-312/CleartextStorage.expected index d8a6cbf5534..74b546faa65 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-312/CleartextStorage.expected +++ b/csharp/ql/test/query-tests/Security Features/CWE-312/CleartextStorage.expected @@ -1,20 +1,22 @@ edges nodes -| CleartextStorage.cs:13:50:13:59 | access to field accountKey | semmle.label | access to field accountKey | -| CleartextStorage.cs:14:62:14:74 | call to method GetPassword | semmle.label | call to method GetPassword | -| CleartextStorage.cs:15:69:15:81 | call to method GetPassword | semmle.label | call to method GetPassword | -| CleartextStorage.cs:16:50:16:63 | call to method GetAccountID | semmle.label | call to method GetAccountID | -| CleartextStorage.cs:24:21:24:33 | call to method GetPassword | semmle.label | call to method GetPassword | -| CleartextStorage.cs:72:21:72:33 | access to property Text | semmle.label | access to property Text | -| CleartextStorage.cs:73:21:73:29 | access to property Text | semmle.label | access to property Text | -| CleartextStorage.cs:74:21:74:29 | access to property Text | semmle.label | access to property Text | +| CleartextStorage.cs:15:50:15:59 | access to field accountKey | semmle.label | access to field accountKey | +| CleartextStorage.cs:16:62:16:74 | call to method GetPassword | semmle.label | call to method GetPassword | +| CleartextStorage.cs:17:69:17:81 | call to method GetPassword | semmle.label | call to method GetPassword | +| CleartextStorage.cs:18:50:18:63 | call to method GetAccountID | semmle.label | call to method GetAccountID | +| CleartextStorage.cs:26:21:26:33 | call to method GetPassword | semmle.label | call to method GetPassword | +| CleartextStorage.cs:34:26:34:38 | call to method GetPassword | semmle.label | call to method GetPassword | +| CleartextStorage.cs:94:21:94:33 | access to property Text | semmle.label | access to property Text | +| CleartextStorage.cs:95:21:95:29 | access to property Text | semmle.label | access to property Text | +| CleartextStorage.cs:96:21:96:29 | access to property Text | semmle.label | access to property Text | subpaths #select -| CleartextStorage.cs:13:50:13:59 | access to field accountKey | CleartextStorage.cs:13:50:13:59 | access to field accountKey | CleartextStorage.cs:13:50:13:59 | access to field accountKey | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:13:50:13:59 | access to field accountKey | access to field accountKey | -| CleartextStorage.cs:14:62:14:74 | call to method GetPassword | CleartextStorage.cs:14:62:14:74 | call to method GetPassword | CleartextStorage.cs:14:62:14:74 | call to method GetPassword | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:14:62:14:74 | call to method GetPassword | call to method GetPassword | -| CleartextStorage.cs:15:69:15:81 | call to method GetPassword | CleartextStorage.cs:15:69:15:81 | call to method GetPassword | CleartextStorage.cs:15:69:15:81 | call to method GetPassword | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:15:69:15:81 | call to method GetPassword | call to method GetPassword | -| CleartextStorage.cs:16:50:16:63 | call to method GetAccountID | CleartextStorage.cs:16:50:16:63 | call to method GetAccountID | CleartextStorage.cs:16:50:16:63 | call to method GetAccountID | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:16:50:16:63 | call to method GetAccountID | call to method GetAccountID | -| CleartextStorage.cs:24:21:24:33 | call to method GetPassword | CleartextStorage.cs:24:21:24:33 | call to method GetPassword | CleartextStorage.cs:24:21:24:33 | call to method GetPassword | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:24:21:24:33 | call to method GetPassword | call to method GetPassword | -| CleartextStorage.cs:72:21:72:33 | access to property Text | CleartextStorage.cs:72:21:72:33 | access to property Text | CleartextStorage.cs:72:21:72:33 | access to property Text | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:72:21:72:33 | access to property Text | access to property Text | -| CleartextStorage.cs:73:21:73:29 | access to property Text | CleartextStorage.cs:73:21:73:29 | access to property Text | CleartextStorage.cs:73:21:73:29 | access to property Text | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:73:21:73:29 | access to property Text | access to property Text | -| CleartextStorage.cs:74:21:74:29 | access to property Text | CleartextStorage.cs:74:21:74:29 | access to property Text | CleartextStorage.cs:74:21:74:29 | access to property Text | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:74:21:74:29 | access to property Text | access to property Text | +| CleartextStorage.cs:15:50:15:59 | access to field accountKey | CleartextStorage.cs:15:50:15:59 | access to field accountKey | CleartextStorage.cs:15:50:15:59 | access to field accountKey | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:15:50:15:59 | access to field accountKey | access to field accountKey | +| CleartextStorage.cs:16:62:16:74 | call to method GetPassword | CleartextStorage.cs:16:62:16:74 | call to method GetPassword | CleartextStorage.cs:16:62:16:74 | call to method GetPassword | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:16:62:16:74 | call to method GetPassword | call to method GetPassword | +| CleartextStorage.cs:17:69:17:81 | call to method GetPassword | CleartextStorage.cs:17:69:17:81 | call to method GetPassword | CleartextStorage.cs:17:69:17:81 | call to method GetPassword | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:17:69:17:81 | call to method GetPassword | call to method GetPassword | +| CleartextStorage.cs:18:50:18:63 | call to method GetAccountID | CleartextStorage.cs:18:50:18:63 | call to method GetAccountID | CleartextStorage.cs:18:50:18:63 | call to method GetAccountID | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:18:50:18:63 | call to method GetAccountID | call to method GetAccountID | +| CleartextStorage.cs:26:21:26:33 | call to method GetPassword | CleartextStorage.cs:26:21:26:33 | call to method GetPassword | CleartextStorage.cs:26:21:26:33 | call to method GetPassword | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:26:21:26:33 | call to method GetPassword | call to method GetPassword | +| CleartextStorage.cs:34:26:34:38 | call to method GetPassword | CleartextStorage.cs:34:26:34:38 | call to method GetPassword | CleartextStorage.cs:34:26:34:38 | call to method GetPassword | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:34:26:34:38 | call to method GetPassword | call to method GetPassword | +| CleartextStorage.cs:94:21:94:33 | access to property Text | CleartextStorage.cs:94:21:94:33 | access to property Text | CleartextStorage.cs:94:21:94:33 | access to property Text | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:94:21:94:33 | access to property Text | access to property Text | +| CleartextStorage.cs:95:21:95:29 | access to property Text | CleartextStorage.cs:95:21:95:29 | access to property Text | CleartextStorage.cs:95:21:95:29 | access to property Text | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:95:21:95:29 | access to property Text | access to property Text | +| CleartextStorage.cs:96:21:96:29 | access to property Text | CleartextStorage.cs:96:21:96:29 | access to property Text | CleartextStorage.cs:96:21:96:29 | access to property Text | This stores sensitive data returned by $@ as clear text. | CleartextStorage.cs:96:21:96:29 | access to property Text | access to property Text | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-312/options b/csharp/ql/test/query-tests/Security Features/CWE-312/options index da018b1a099..4b3bf418201 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-312/options +++ b/csharp/ql/test/query-tests/Security Features/CWE-312/options @@ -1 +1 @@ -semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs /r:System.Collections.Specialized.dll {testdir}/../../../../resources/stubs/System.Windows.cs +semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs /r:System.Collections.Specialized.dll /r:System.Security.Cryptography.dll {testdir}/../../../../resources/stubs/System.Windows.cs diff --git a/csharp/ql/test/query-tests/Security Features/CWE-359/ExposureOfPrivateInformation.cs b/csharp/ql/test/query-tests/Security Features/CWE-359/ExposureOfPrivateInformation.cs index f7d0755d65e..675b05c29de 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-359/ExposureOfPrivateInformation.cs +++ b/csharp/ql/test/query-tests/Security Features/CWE-359/ExposureOfPrivateInformation.cs @@ -1,4 +1,6 @@ using System.Web; +using System.Security.Cryptography; +using System.IO; public class Person { @@ -21,9 +23,29 @@ public class ExposureOfPrivateInformationHandler : IHttpHandler ILogger logger = new ILogger(); logger.Warn(p.getTelephone()); + // BAD: Storing sensitive data in unencrypted local file + using (var writeStream = File.Open("telephones.txt", FileMode.Create)) + { + var writer = new StreamWriter(writeStream); + writer.Write(p.getTelephone()); + writer.Close(); + } + + // GOOD: Storing encrypted sensitive data + using (var writeStream = File.Open("telephones.txt", FileMode.Create)) + { + var writer = new StreamWriter(new CryptoStream(writeStream, GetEncryptor(), CryptoStreamMode.Write)); + writer.Write(p.getTelephone()); + writer.Close(); + } + // GOOD: Don't write these values to sensitive locations in the first place } + public ICryptoTransform GetEncryptor(){ + return null; + } + public bool IsReusable { get diff --git a/csharp/ql/test/query-tests/Security Features/CWE-359/ExposureOfPrivateInformation.expected b/csharp/ql/test/query-tests/Security Features/CWE-359/ExposureOfPrivateInformation.expected index d9f5ee50ee1..417692ff79e 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-359/ExposureOfPrivateInformation.expected +++ b/csharp/ql/test/query-tests/Security Features/CWE-359/ExposureOfPrivateInformation.expected @@ -1,12 +1,14 @@ edges nodes -| ExposureOfPrivateInformation.cs:16:50:16:84 | access to indexer | semmle.label | access to indexer | -| ExposureOfPrivateInformation.cs:18:50:18:65 | call to method getTelephone | semmle.label | call to method getTelephone | -| ExposureOfPrivateInformation.cs:22:21:22:36 | call to method getTelephone | semmle.label | call to method getTelephone | -| ExposureOfPrivateInformation.cs:40:21:40:33 | access to property Text | semmle.label | access to property Text | +| ExposureOfPrivateInformation.cs:18:50:18:84 | access to indexer | semmle.label | access to indexer | +| ExposureOfPrivateInformation.cs:20:50:20:65 | call to method getTelephone | semmle.label | call to method getTelephone | +| ExposureOfPrivateInformation.cs:24:21:24:36 | call to method getTelephone | semmle.label | call to method getTelephone | +| ExposureOfPrivateInformation.cs:30:26:30:41 | call to method getTelephone | semmle.label | call to method getTelephone | +| ExposureOfPrivateInformation.cs:62:21:62:33 | access to property Text | semmle.label | access to property Text | subpaths #select -| ExposureOfPrivateInformation.cs:16:50:16:84 | access to indexer | ExposureOfPrivateInformation.cs:16:50:16:84 | access to indexer | ExposureOfPrivateInformation.cs:16:50:16:84 | access to indexer | Private data returned by $@ is written to an external location. | ExposureOfPrivateInformation.cs:16:50:16:84 | access to indexer | access to indexer | -| ExposureOfPrivateInformation.cs:18:50:18:65 | call to method getTelephone | ExposureOfPrivateInformation.cs:18:50:18:65 | call to method getTelephone | ExposureOfPrivateInformation.cs:18:50:18:65 | call to method getTelephone | Private data returned by $@ is written to an external location. | ExposureOfPrivateInformation.cs:18:50:18:65 | call to method getTelephone | call to method getTelephone | -| ExposureOfPrivateInformation.cs:22:21:22:36 | call to method getTelephone | ExposureOfPrivateInformation.cs:22:21:22:36 | call to method getTelephone | ExposureOfPrivateInformation.cs:22:21:22:36 | call to method getTelephone | Private data returned by $@ is written to an external location. | ExposureOfPrivateInformation.cs:22:21:22:36 | call to method getTelephone | call to method getTelephone | -| ExposureOfPrivateInformation.cs:40:21:40:33 | access to property Text | ExposureOfPrivateInformation.cs:40:21:40:33 | access to property Text | ExposureOfPrivateInformation.cs:40:21:40:33 | access to property Text | Private data returned by $@ is written to an external location. | ExposureOfPrivateInformation.cs:40:21:40:33 | access to property Text | access to property Text | +| ExposureOfPrivateInformation.cs:18:50:18:84 | access to indexer | ExposureOfPrivateInformation.cs:18:50:18:84 | access to indexer | ExposureOfPrivateInformation.cs:18:50:18:84 | access to indexer | Private data returned by $@ is written to an external location. | ExposureOfPrivateInformation.cs:18:50:18:84 | access to indexer | access to indexer | +| ExposureOfPrivateInformation.cs:20:50:20:65 | call to method getTelephone | ExposureOfPrivateInformation.cs:20:50:20:65 | call to method getTelephone | ExposureOfPrivateInformation.cs:20:50:20:65 | call to method getTelephone | Private data returned by $@ is written to an external location. | ExposureOfPrivateInformation.cs:20:50:20:65 | call to method getTelephone | call to method getTelephone | +| ExposureOfPrivateInformation.cs:24:21:24:36 | call to method getTelephone | ExposureOfPrivateInformation.cs:24:21:24:36 | call to method getTelephone | ExposureOfPrivateInformation.cs:24:21:24:36 | call to method getTelephone | Private data returned by $@ is written to an external location. | ExposureOfPrivateInformation.cs:24:21:24:36 | call to method getTelephone | call to method getTelephone | +| ExposureOfPrivateInformation.cs:30:26:30:41 | call to method getTelephone | ExposureOfPrivateInformation.cs:30:26:30:41 | call to method getTelephone | ExposureOfPrivateInformation.cs:30:26:30:41 | call to method getTelephone | Private data returned by $@ is written to an external location. | ExposureOfPrivateInformation.cs:30:26:30:41 | call to method getTelephone | call to method getTelephone | +| ExposureOfPrivateInformation.cs:62:21:62:33 | access to property Text | ExposureOfPrivateInformation.cs:62:21:62:33 | access to property Text | ExposureOfPrivateInformation.cs:62:21:62:33 | access to property Text | Private data returned by $@ is written to an external location. | ExposureOfPrivateInformation.cs:62:21:62:33 | access to property Text | access to property Text | diff --git a/csharp/ql/test/query-tests/Security Features/CWE-359/options b/csharp/ql/test/query-tests/Security Features/CWE-359/options index 35e6ec9b23f..aa088071c5d 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-359/options +++ b/csharp/ql/test/query-tests/Security Features/CWE-359/options @@ -1 +1 @@ -semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs /r:System.Collections.Specialized.dll ${testdir}/../../../resources/stubs/System.Windows.cs +semmle-extractor-options: ${testdir}/../../../resources/stubs/System.Web.cs /r:System.Collections.Specialized.dll /r:System.Security.Cryptography.dll ${testdir}/../../../resources/stubs/System.Windows.cs