+
+
+
+Parsing untrusted XML files with a weakly configured XML parser may lead to an XML External Entity (XXE) attack. This type of attack
+uses external entity references to access arbitrary files on a system, carry out denial-of-service attacks, or server-side
+request forgery. Even when the result of parsing is not returned to the user, out-of-band
+data retrieval techniques may allow attackers to steal sensitive data. Denial of services can also be
+carried out in this situation.
+
+
+
+
+
+The easiest way to prevent XXE attacks is to disable external entity handling when
+parsing untrusted data. How this is done depends on the library being used. Note that some
+libraries, such as recent versions of XMLParser
, disable entity expansion by default,
+so unless you have explicitly enabled entity expansion, no further action needs to be taken.
+
+
+
+
+
+The following example uses the XMLParser
class to parse a string data
.
+If that string is from an untrusted source, this code may be vulnerable to an XXE attack, since
+the parser is also setting its shouldResolveExternalEntities
option to true
:
+
+
+
+
+To guard against XXE attacks, the shouldResolveExternalEntities
option should be
+left unset or explicitly set to false
.
+
+
+
+
+
+
+
+OWASP:
+XML External Entity (XXE) Processing.
+
+
+OWASP:
+XML External Entity Prevention Cheat Sheet.
+
+
+Timothy D. Morgan and Omar Al Ibrahim
+XML Schema, DTD, and Entity Attacks: A Compendium of Known Techniques.
+
+
+Timur Yunusov, Alexey Osipov:
+XML Out-Of-Band Data Retrieval.
+
+
+
diff --git a/swift/ql/src/queries/Security/CWE-611/XXE.ql b/swift/ql/src/queries/Security/CWE-611/XXE.ql
new file mode 100644
index 000000000000..04c27949c11b
--- /dev/null
+++ b/swift/ql/src/queries/Security/CWE-611/XXE.ql
@@ -0,0 +1,25 @@
+/**
+ * @name Resolving XML external entity in user-controlled data
+ * @description Parsing user-controlled XML documents and allowing expansion of external entity
+ * references may lead to disclosure of confidential data or denial of service.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 9.1
+ * @precision high
+ * @id swift/xxe
+ * @tags security
+ * external/cwe/cwe-611
+ * external/cwe/cwe-776
+ * external/cwe/cwe-827
+ */
+
+import swift
+import codeql.swift.dataflow.DataFlow
+import codeql.swift.security.XXEQuery
+import DataFlow::PathGraph
+
+from DataFlow::PathNode source, DataFlow::PathNode sink
+where any(XxeConfiguration c).hasFlowPath(source, sink)
+select sink.getNode(), source, sink,
+ "XML parsing depends on a $@ without guarding against external entity expansion.",
+ source.getNode(), "user-provided value"
diff --git a/swift/ql/src/queries/Security/CWE-611/XXEBad.swift b/swift/ql/src/queries/Security/CWE-611/XXEBad.swift
new file mode 100644
index 000000000000..19ff4a7383d0
--- /dev/null
+++ b/swift/ql/src/queries/Security/CWE-611/XXEBad.swift
@@ -0,0 +1,2 @@
+let parser = XMLParser(data: remoteData) // BAD (parser explicitly enables external entities)
+parser.shouldResolveExternalEntities = true
diff --git a/swift/ql/src/queries/Security/CWE-611/XXEGood.swift b/swift/ql/src/queries/Security/CWE-611/XXEGood.swift
new file mode 100644
index 000000000000..5552186c7591
--- /dev/null
+++ b/swift/ql/src/queries/Security/CWE-611/XXEGood.swift
@@ -0,0 +1,2 @@
+let parser = XMLParser(data: remoteData) // GOOD (parser explicitly disables external entities)
+parser.shouldResolveExternalEntities = false
diff --git a/swift/ql/test/query-tests/Security/CWE-311/CleartextTransmission.expected b/swift/ql/test/query-tests/Security/CWE-311/CleartextTransmission.expected
index de02d0db461f..5580af94d229 100644
--- a/swift/ql/test/query-tests/Security/CWE-311/CleartextTransmission.expected
+++ b/swift/ql/test/query-tests/Security/CWE-311/CleartextTransmission.expected
@@ -1,4 +1,8 @@
edges
+| testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : | file://:0:0:0:0 | [summary] to write: return (return) in init(_:) : |
+| testSend.swift:33:14:33:32 | call to init(_:) : | testSend.swift:37:19:37:19 | data2 |
+| testSend.swift:33:19:33:19 | passwordPlain : | testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : |
+| testSend.swift:33:19:33:19 | passwordPlain : | testSend.swift:33:14:33:32 | call to init(_:) : |
| testSend.swift:41:10:41:18 | data : | testSend.swift:41:45:41:45 | data : |
| testSend.swift:45:13:45:13 | password : | testSend.swift:52:27:52:27 | str1 |
| testSend.swift:46:13:46:13 | password : | testSend.swift:53:27:53:27 | str2 |
@@ -8,7 +12,12 @@ edges
| testURL.swift:13:54:13:54 | passwd : | testURL.swift:13:22:13:54 | ... .+(_:_:) ... |
| testURL.swift:16:55:16:55 | credit_card_no : | testURL.swift:16:22:16:55 | ... .+(_:_:) ... |
nodes
+| file://:0:0:0:0 | [summary] to write: return (return) in init(_:) : | semmle.label | [summary] to write: return (return) in init(_:) : |
+| testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : | semmle.label | [summary param] 0 in init(_:) : |
| testSend.swift:29:19:29:19 | passwordPlain | semmle.label | passwordPlain |
+| testSend.swift:33:14:33:32 | call to init(_:) : | semmle.label | call to init(_:) : |
+| testSend.swift:33:19:33:19 | passwordPlain : | semmle.label | passwordPlain : |
+| testSend.swift:37:19:37:19 | data2 | semmle.label | data2 |
| testSend.swift:41:10:41:18 | data : | semmle.label | data : |
| testSend.swift:41:45:41:45 | data : | semmle.label | data : |
| testSend.swift:45:13:45:13 | password : | semmle.label | password : |
@@ -24,9 +33,11 @@ nodes
| testURL.swift:16:55:16:55 | credit_card_no : | semmle.label | credit_card_no : |
| testURL.swift:20:22:20:22 | passwd | semmle.label | passwd |
subpaths
+| testSend.swift:33:19:33:19 | passwordPlain : | testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : | file://:0:0:0:0 | [summary] to write: return (return) in init(_:) : | testSend.swift:33:14:33:32 | call to init(_:) : |
| testSend.swift:47:17:47:17 | password : | testSend.swift:41:10:41:18 | data : | testSend.swift:41:45:41:45 | data : | testSend.swift:47:13:47:25 | call to pad(_:) : |
#select
| testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | This operation transmits 'passwordPlain', which may contain unencrypted sensitive data from $@. | testSend.swift:29:19:29:19 | passwordPlain | passwordPlain |
+| testSend.swift:37:19:37:19 | data2 | testSend.swift:33:19:33:19 | passwordPlain : | testSend.swift:37:19:37:19 | data2 | This operation transmits 'data2', which may contain unencrypted sensitive data from $@. | testSend.swift:33:19:33:19 | passwordPlain : | passwordPlain |
| testSend.swift:52:27:52:27 | str1 | testSend.swift:45:13:45:13 | password : | testSend.swift:52:27:52:27 | str1 | This operation transmits 'str1', which may contain unencrypted sensitive data from $@. | testSend.swift:45:13:45:13 | password : | password |
| testSend.swift:53:27:53:27 | str2 | testSend.swift:46:13:46:13 | password : | testSend.swift:53:27:53:27 | str2 | This operation transmits 'str2', which may contain unencrypted sensitive data from $@. | testSend.swift:46:13:46:13 | password : | password |
| testSend.swift:54:27:54:27 | str3 | testSend.swift:47:17:47:17 | password : | testSend.swift:54:27:54:27 | str3 | This operation transmits 'str3', which may contain unencrypted sensitive data from $@. | testSend.swift:47:17:47:17 | password : | password |
diff --git a/swift/ql/test/query-tests/Security/CWE-311/testSend.swift b/swift/ql/test/query-tests/Security/CWE-311/testSend.swift
index 506b3a921e3b..aaf2e3487f24 100644
--- a/swift/ql/test/query-tests/Security/CWE-311/testSend.swift
+++ b/swift/ql/test/query-tests/Security/CWE-311/testSend.swift
@@ -34,7 +34,7 @@ func test1(passwordPlain : String, passwordHash : String) {
let data3 = Data(passwordHash)
nw.send(content: data1, completion: .idempotent) // GOOD (not sensitive)
- nw.send(content: data2, completion: .idempotent) // BAD [NOT DETECTED]
+ nw.send(content: data2, completion: .idempotent) // BAD
nw.send(content: data3, completion: .idempotent) // GOOD (not sensitive)
}
diff --git a/swift/ql/test/query-tests/Security/CWE-611/XXETest.expected b/swift/ql/test/query-tests/Security/CWE-611/XXETest.expected
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/swift/ql/test/query-tests/Security/CWE-611/XXETest.ql b/swift/ql/test/query-tests/Security/CWE-611/XXETest.ql
new file mode 100644
index 000000000000..817f55678ad2
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-611/XXETest.ql
@@ -0,0 +1,20 @@
+import swift
+import codeql.swift.security.XXEQuery
+import TestUtilities.InlineExpectationsTest
+
+class XxeTest extends InlineExpectationsTest {
+ XxeTest() { this = "XxeTest" }
+
+ override string getARelevantTag() { result = "hasXXE" }
+
+ override predicate hasActualResult(Location location, string element, string tag, string value) {
+ exists(XxeConfiguration config, DataFlow::Node source, DataFlow::Node sink, Expr sinkExpr |
+ config.hasFlow(source, sink) and
+ sinkExpr = sink.asExpr() and
+ location = sinkExpr.getLocation() and
+ element = sinkExpr.toString() and
+ tag = "hasXXE" and
+ value = source.asExpr().getLocation().getStartLine().toString()
+ )
+ }
+}
diff --git a/swift/ql/test/query-tests/Security/CWE-611/testXXE.swift b/swift/ql/test/query-tests/Security/CWE-611/testXXE.swift
new file mode 100644
index 000000000000..75538f014f9f
--- /dev/null
+++ b/swift/ql/test/query-tests/Security/CWE-611/testXXE.swift
@@ -0,0 +1,92 @@
+// --- stubs ---
+
+class Data {
+ init