-
Notifications
You must be signed in to change notification settings - Fork 125
Polish CWE-327 (weak TLS config) query #256
Polish CWE-327 (weak TLS config) query #256
Conversation
|
||
predicate isSource(DataFlow::Node source, int val) { | ||
val = source.getIntValue() and | ||
unsafeTlsVersion(val, _) | ||
} | ||
|
||
predicate isSink(DataFlow::Node sink, Field fld) { | ||
predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) { | ||
fld.hasQualifiedName("crypto/tls", "Config", ["MinVersion", "MaxVersion"]) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be beneficial to not consider MaxVersion
here; is there any case where a bad MaxVersion
is not accompanied by a bad MinVersion
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the only case would be if MinVersion is left as its implicit default, which we wouldn't otherwise spot. I haven't seen any cases in the wild (yet) where MaxVersion is a problem.
32053b4
to
0c05636
Compare
@sauyon your suggested change is in, along with various other changes updated in the description; please re-review |
CI failures are spurious, caused by checking out and testing an old revision of this PR, in turn caused by an ongoing Github incident |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of my comments are minor. The big thing you haven't done is actually move the query out of the experimental folder. (Don't forget to move the tests too.)
or | ||
suiteName = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" | ||
) | ||
suiteName = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trivial comment (feel free to ignore): I think it looks slightly nicer to use suiteName in [...]
instead of suiteName = [...]
.
// Exclude sinks guarded by a feature flag | ||
not getAFeatureFlagCheck().dominatesNode(sink.getNode().asInstruction()) and | ||
// Exclude results in functions whose name documents the insecurity | ||
not exists(FuncDef fn | fn = sink.getNode().asInstruction().getRoot() | | ||
isFeatureFlagName(fn.getEnclosingFunction*().getName()) | ||
) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better to put these in the definition of the sink? I assume it would be slightly more efficient to rule out some sinks earlier in the calculation.
predicate secureTlsVersionFlowsToSink(DataFlow::PathNode sink, Field fld) { | ||
secureTlsVersionFlow(_, _, sink, fld) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be neater to just inline this in the one place it's used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used in secureTlsVersionFlowsToSink
and secureTlsVersionFlowsToField
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was talking about secureTlsVersionFlowsToSink
, which I've just double-checked is only used in one place. I'm guessing you thought I was talking about secureTlsVersionFlow
.
@@ -65,7 +65,7 @@ module InsecureFeatureFlag { | |||
/** | |||
* Flags suggesting support for an old or legacy feature. | |||
*/ | |||
string legacyFlag() { result = "feature" } | |||
string legacyFlag() { result = "legacy" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be in the previous commit, I guess, but it doesn't really matter
Wow, you've put hella lot of work into it. This might easily be the most advanced query I've seen in here. I realized that the default So, if there is a Pretty much anything Go and Google-related (golang.org, proxy.golang.org, google.com, cloud.google.com, console.developers.google.com, etc.) all support TLSv1.0 (along with the secure versions). The biggest issue is when |
@@ -0,0 +1,2 @@ | |||
lgtm,codescanning | |||
* Query "Insecure TLS configuration" (`go/insecure-tls`) is promoted from experimental status. This checks for use of insecure SSL/TLS versions and cipher suites. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there an already existing change note for this query?
If not, why mention that it is promoted from experimental status?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose to inform anyone that has used it before that their query has moved, but I defer to anyone that knows the proper convention to describe a promotion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had a similar change note when go/email-injection
was promoted from the experimental folder, so I think it's fine: https://github.com/github/codeql-go/blob/master/change-notes/2020-06-16-email-injection.md .
@gagliardetto good thought -- I think we should add that as a different PR, as it will require a wholly different strategy. To avoid false-positives we'll have to look for something like a config where we're confident we know all of its aliases, and that none of those aliases ever see a MinVersion write. |
// or the highest available version setting for MaxVersion. | ||
val = 0 and name = "" | ||
( | ||
val = 768 and name = "VersionSSL30" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, is there a reason we're checking the implementation-specific tls enum values here? If it's because there's not way to get them, that's probably something that we should have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some people use an uint16 (eg 0x0301 for tls.VersionTLS10) instead of the hardcoded version consts from the tls package; this approach catches them both.
Those version values are not golang-specific, but part of the tls protocol spec.
* | ||
* For example, `0` in `func tryGetInt() (int, error) { return 0, errors.New("no good") }` | ||
*/ | ||
predicate isReturnedWithError(DataFlow::Node node) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it would be useful in general, though I'm not entirely sure where it might go.
* @id go/insecure-tls | ||
* @tags security | ||
* external/cwe/cwe-327 | ||
*/ | ||
|
||
import go | ||
import DataFlow::PathGraph | ||
import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag | ||
|
||
/** | ||
* Check whether the file where the node is located is a test file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the predicate below should be replaced by instanceof TestFile
a la DisabledCertificateCheck
.
We could make files that match the heuristic into TestFile
s, or if we don't think that's a good idea, move the heuristic logic into frameworks/Testing
and add a predicate we can use in all queries where we exclude test file results.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we want to keep this filter at all? I think filtering out boring results in tests is the job of the presentation layer (Code Scanning or LGTM), not of the query. There are exceptions (usually if the thing the query flags is legitimately OK in tests), but I'm not sure this is one of them.
/** | ||
* Holds if `fieldWrite` writes `sink` to `base`.`fld`, where `fld` is a TLS version field. | ||
*/ | ||
predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) { | ||
fld.hasQualifiedName("crypto/tls", "Config", ["MinVersion", "MaxVersion"]) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason I can't add a comment to the discussion above, but FWIW in my opinion it's fine to flag any project with an insecure TLS MinVersion
, including the default, since it is actually vulnerable to a downgrade attack and therefore a valid result.
That would mean we could probably not worry about MaxVersion
at all and simplify the query, as a bonus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean the discussion with @gagliardetto? I agree we should flag the default, it's just harder to detect since we're looking for a negative property (that no write reaches that field or any of its aliases), so it's more likely to throw false-positives. That's why I wanted to pursue that separately.
* Holds if `fieldWrite` writes `sink` to `base`.`fld`, and `fld` is `tls.Config.CipherSuites`, | ||
* and no parent of `base` is named suggesting an intentionally insecure configuration. | ||
*/ | ||
predicate isSink(DataFlow::Node sink, Field fld, DataFlow::Node base, Write fieldWrite) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't seem to me like fieldWrite
is used anywhere, can it be removed?
predicate checkTlsInsecureCipherSuites( | ||
DataFlow::PathNode source, DataFlow::PathNode sink, string message | ||
) { | ||
predicate isInsecureTlsCipherFlow(DataFlow::PathNode source, DataFlow::PathNode sink, string message) { | ||
exists(TlsInsecureCipherSuitesFlowConfig cfg | cfg.hasFlowPath(source, sink) | | ||
exists(string name | cfg.isSourceValueEntity(source.getNode(), name) | | ||
message = "Use of an insecure cipher suite: " + name + "." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't comment on it directly, but I think the message on line 239 doesn't need to mention InsecureCipherSuites()
.
| UnsafeTLS.go:304:18:306:4 | slice literal | UnsafeTLS.go:305:5:305:47 | selection of TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 : uint16 | UnsafeTLS.go:304:18:306:4 | slice literal | Use of an insecure cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256. | | ||
| UnsafeTLS.go:312:18:314:4 | slice literal | UnsafeTLS.go:313:5:313:45 | selection of TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 : uint16 | UnsafeTLS.go:312:18:314:4 | slice literal | Use of an insecure cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256. | | ||
| UnsafeTLS.go:329:25:329:94 | call to append | UnsafeTLS.go:329:53:329:93 | selection of TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 : uint16 | UnsafeTLS.go:329:25:329:94 | call to append | Use of an insecure cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256. | | ||
| UnsafeTLS.go:362:18:364:4 | slice literal | UnsafeTLS.go:363:5:363:47 | selection of TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 : uint16 | UnsafeTLS.go:362:18:364:4 | slice literal | Use of an insecure cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this is missing results for the tests you marked bad on lines 336, 346, and 355?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, thanks -- that's because they were using a variable name that suggests deliberate insecurity. Now fixed.
not [getAFeatureFlagCheck(), getALegacyVersionCheck()] | ||
.dominatesNode([source, sink].getNode().asInstruction()) and | ||
// Exclude sources or sinks that occur lexically within a block related to a feature or legacy flag | ||
not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), [featureFlag(), legacyFlag()]) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of interest, what kinds of blocks are you excluding with this as opposed to the above checks logic?
not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), [featureFlag(), legacyFlag()]) and | |
not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), [any(FeatureFlag f), any(LegacyFlag l)]) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lexically related but not dominated by a test:
insecureSetup := getBadCiphers()
Dominated but not lexically surrounded:
if !insecureConfig {
return
}
config.MinVersion = 0
FeatureFlag() { this = "feature" } | ||
|
||
bindingset[result] | ||
override string getAFlagName() { isFeatureFlagName(result) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels a bit odd to expose both this and isFeatureFlagName
.
Maybe you could have a utility predicate like getFlagFunction
for the use of isFeatureFlagName
above? Or maybe you could just do any(FeatureFlag f).getAFlagName()
, though that's a bit clunkier
/** | ||
* Gets a control-flow node that represents a (likely) feature-flag check for certificate checking. | ||
*/ | ||
ControlFlow::ConditionGuardNode getAFeatureFlagCheck() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be nicer to add this and getAFlag
as a member predicates of Flag
, like so:
ControlFlow::ConditionGuardNode getACheck() { result.ensures(this.getAFlag().getANode(), _) }
I think that would also eliminate the need for featureFlag
and legacyFlag
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for tackling this! A few comments from me as well; I haven't read all of it yet.
deleteme
Outdated
@@ -0,0 +1 @@ | |||
sdlkfjdskl;fjdfls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤨
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Workaround for github breakage, will drop
* @id go/insecure-tls | ||
* @tags security | ||
* external/cwe/cwe-327 | ||
*/ | ||
|
||
import go | ||
import DataFlow::PathGraph | ||
import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag | ||
|
||
/** | ||
* Check whether the file where the node is located is a test file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we want to keep this filter at all? I think filtering out boring results in tests is the job of the presentation layer (Code Scanning or LGTM), not of the query. There are exceptions (usually if the thing the query flags is legitimately OK in tests), but I'm not sure this is one of them.
predicate isSource(DataFlow::Node source, int val) { | ||
val = source.getIntValue() and | ||
unsafeTlsVersion(val, _) | ||
not isReturnedWithError(source) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, does this mean we are now tracking all integer constants anywhere? That seems like it might easily blow up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was concerned too, but the all-go-projects run on lgtm.com doesn't seem to have extraordinary cost. Discussing with Pavel suggests a broad source and a narrow sink performs better than you might suppose. I can restrict it to more specific constants, but we'll always care about zero since that specifies the default (TLS1.0)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. I note, though, that all-projects runs are a very coarse indication of performance at best; all they tell you is whether something timed out, but not whether it went from very fast to almost-timing-out. I personally wouldn't rely on the data-flow library being clever enough to prune the search space aggressively enough, particularly since it seems that in this case we could help it fairly easily be restricting the set of sources ourselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean just restricting to zero + the interesting values, or do you have something cleverer in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushed a commit just now doing the simple restriction. Will do a distcompare once my current distcompare for SSH-host-keys is done.
* Holds if an insecure TLS version flows from `source` to `sink`, which is in turn written | ||
* to a field of `base`. `message` describes the specific problem found. | ||
*/ | ||
query predicate isInsecureTlsVersionFlow( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a query
predicate? Having extra query predicates confuses parts of our toolchain, so I don't think this is safe to add.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doh, debugging stuff left over
/** | ||
* Returns `node` or an implicit-deref node referring to it | ||
*/ | ||
DataFlow::Node nodeOrDeref(DataFlow::Node node) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this a workaround for? I'd rather fix it properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't remember 100% anymore, but it has to do with isSink using writesField which in turn will return both the implicit-deref and the underlying as base candidates. A secure-version flow then needs to eliminate them both. An alternative would be to filter out implicit-deref nodes as base candidates in isSink
or its caller?
) | ||
/** | ||
* Holds if `fieldWrite` writes `sink` to `base`.`fld`, and `fld` is `tls.Config.CipherSuites`, | ||
* and no parent of `base` is named suggesting an intentionally insecure configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is the latter bit ("no parent (...) is named (...)") checked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This moved into the top-level query; fixed.
// Exclude sources or sinks that occur lexically within a block related to a feature or legacy flag | ||
not astNodeIsFlag([source, sink].getNode().asExpr().getParent*(), [featureFlag(), legacyFlag()]) and | ||
// Exclude results in functions whose name documents insecurity | ||
not exists(FuncDef fn | fn = sink.getNode().asInstruction().getRoot() | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a predicate Node.getEnclosingFunction()
would be convenient, I think. With that in hand, I'd suggest rewriting this to
not exists(FuncDef fn | fn = sink.getNode().asInstruction().getRoot() | | |
not exists(FuncDef fn | fn = sink.getNode().getEnclosingFunction().getEnclosingFunction*() | |
(which looks slightly redundant, but isn't equivalent to getEnclosingFunction+()
, since the two getEnclosingFunction()
predicates are actually different)
and then just use is*FlagName(fn.getName())
below.
/** | ||
* Holds if `name` suggests an old or legacy version. | ||
* | ||
* We accept 'intermediate' because it appears to be common for TLS users |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, but seems very specific to the TLS query. Move there instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've split the flags into 3 pieces. I can't see an easy way to split the code actually into the TLS query and yet allow using the shared infrastructure though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is that? The shared infrastructure just needs to know about FlagKind
, the concrete subclass can be moved into the query.
For this particular query it's hardly ever interesting to complain about a bad cipher suite being configured, then read from the list and re-added elsewhere. In such a case the longer path will be detected when the shorter one is fixed in any case.
… DisabledCertificateCheck
…g a good version I'm supposing these usually indicate something configurable, rather than a hard-coded insecure choice. The *default* being insecure is still a problem, but probably not amenable to automated analyses.
It is common to return 0 has a dummy value with an error; these are very likely not going to be used as a real TLS version.
Note: if accepted, merge this into a previous commit before submitting the PR
The same path is now used to classify flags relating to old/legacy versions.
These are now checked of all source *and* sink nodes, and the checks are factored with similar paths for is-insecure and is-old flags.
This now checks various carve-outs for probable feature / compatibility flags
These stopped producing an alert because they used a variable name that acknowledges an insecure setup
The case noted works fine.
There are now three categories: general security or option flags, those related to TLS version selection, and those related to certificate configuration. The TLS and disabled-certificate-check queries use two categories each.
ae8a688
to
88cb435
Compare
@max-schaefer review comments addressed for the most part. I wasn't sure how best to structure the shared InsecureFeatureFlag stuff; any pointers appreciated. |
445c2dd
to
abfae43
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handling of flag kinds looks a little awkward to me, but perhaps I'm not understanding the difficulties that gave rise to the current design, so I've added a few questions about that.
*/ | ||
abstract class FlagKind extends string { | ||
FlagKind() { | ||
this = "securityFeature" or this = "legacyTlsVersion" or this = "insecureCertificate" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need this? It's an abstract class, so this should just fall out from the usual semantics.
Of course, since it extends string
you'll still have to put a binding-set annotation on the (trivial) characteristic predicate, like this: https://github.com/github/codeql-go/blob/master/ql/src/semmle/go/dataflow/internal/DataFlowImpl.qll#L51.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, that's the very demonic incantation I was looking for, thanks
/** | ||
* Flags suggesting an optional feature, perhaps deliberately insecure. | ||
*/ | ||
string securityFeatureFlag() { result = "securityFeature" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need this? Couldn't you just use any(SecurityFeatureFlag f)
?
} | ||
|
||
/** | ||
* Holds if `node` suggests an old TLS version according to `flagKind`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Outdated comment.
* Holds if `name` may be the name of a feature flag that controls a security feature. | ||
*/ | ||
bindingset[name] | ||
predicate isSecurityFlagName(string name) { name.regexpMatch("(?i).*(secure|(en|dis)able).*") } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need this both as a top-level predicate and as a member predicate of SecurityFeatureFlag
below?
/** | ||
* Holds if `name` suggests an old or legacy version. | ||
* | ||
* We accept 'intermediate' because it appears to be common for TLS users |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is that? The shared infrastructure just needs to know about FlagKind
, the concrete subclass can be moved into the query.
Move the flag regexes inline, use `any` instead of a constructor function to select a particular flag kind, and remove explicit limitation on the common superclass FlagKind.
@max-schaefer I believe all comments are now addressed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handling of flags looks much nicer now, thanks! A few more minor comments.
/** | ||
* @name Insecure TLS configuration | ||
* @description If an application supports insecure TLS versions or ciphers, it may be vulnerable to | ||
* man-in-the-middle and other attacks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* man-in-the-middle and other attacks. | |
* machine-in-the-middle and other attacks. |
import semmle.go.security.InsecureFeatureFlag::InsecureFeatureFlag | ||
|
||
/** | ||
* Holds if it is insecure to assign TLS version `val` named `named` to `tls.Config` field `fieldName` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Holds if it is insecure to assign TLS version `val` named `named` to `tls.Config` field `fieldName` | |
* Holds if it is insecure to assign TLS version `val` named `name` to `tls.Config` field `fieldName`. |
*/ | ||
predicate isInsecureTlsVersion(int val, string name, string fieldName) { | ||
(fieldName = "MinVersion" or fieldName = "MaxVersion") and | ||
// tls.VersionSSL30 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, did autoformat put that here?
* Integer values corresponding to versions are defined at https://golang.org/pkg/crypto/tls/#pkg-constants | ||
* Zero means the default version; at the time of writing, TLS 1.0. | ||
*/ | ||
int getATlsVersion() { result in [768, 769, 770, 771, 772, 0] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit unfortunate to have two copies of these magic constants (though admittedly they are close enough to each other that this shouldn't be a major problem). How about defining an isSecureTlsVersion
predicate and then have
int getATlsVersion() { isInsecureTlsVersion(result, _) or isSecureTlsVersion(result) }
exists(ReturnStmt ret | | ||
ret.getExpr(0) = node.asExpr() and | ||
ret.getNumExpr() = 2 and | ||
ret.getExpr(1).getType().implements(Builtin::error().getType().getUnderlyingType()) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Optional:) This seems worth having in the standard library, perhaps as
class ErrorType extends Type {
ErrorType() { this.implements(Builtin::error().getType().getUnderlyingType()) }
}
(We are using the same idiom in at least one other place.)
ret.getExpr(0) = node.asExpr() and | ||
ret.getNumExpr() = 2 and | ||
ret.getExpr(1).getType().implements(Builtin::error().getType().getUnderlyingType()) and | ||
ret.getExpr(1) != Builtin::nil().getAReference() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nil
doesn't implement error
, so I don't think this is necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced with a comment
} | ||
|
||
/** | ||
* Holds if a secure TLS version may reach `base`.`fld` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Holds if a secure TLS version may reach `base`.`fld` | |
* Holds if a secure TLS version may reach `accessPath`.`fld`. |
} | ||
|
||
/** Gets a global value number representing a (likely) security flag. */ | ||
GVN getAFlag(FlagKind flagKind) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it perhaps make sense for this to be a member predicate of FlagKind
?
@max-schaefer all changes applied, including the optional ones (as separate commits) |
I haven't looked at this very carefully since the changes were made, but generally I think my comments have been addressed. |
OK, let's merge this, then. (@owen-mc, if you have any follow-up questions or comments feel free to post here, we can address them in a future PR.) |
Did we ever get @shati-patel to take a look at the docs for this one? |
From a quick glance, the docs are all good! It looks like previous reviewers caught any issues ☀️ |
Great, thanks! |
Still to-do: check if isTestFile can be made broader to exclude some more FPs