Golang : Add query to detect JWT signing vulnerabilities#9378
Conversation
| randint.getTarget().hasQualifiedName("crypto/rand", "Int") and | ||
| bigint.getTarget().hasQualifiedName("math/big", "Int", "Int64") and | ||
| bigint.getReceiver() = randint.getResult(0).getASuccessor*() and | ||
| r.reads(this, bigint.getAResult().getASuccessor*()) |
There was a problem hiding this comment.
This is similar enough to the previous case, you could replace getASuccessor* with an augmented get-a-successor that includes the taint step across big.Int.Int64 (or always include that as a taint step, and use localTaint like the case below)
There was a problem hiding this comment.
I am not sure I follow. Do you mean to say I should create a new taint tracking config and add this step there?
There was a problem hiding this comment.
No, I mean instead of using getASuccessor* aka localFlow to track flow from rand.Int() etc to Int.Int64() and then using it again to track from Int64() to an array-read, you could extend AdditionalTaintStep or FunctionModel to make Int64() taint-conserving and then use a single localTaint call to track flow from rand.Int() through to an array-read that you want to sanitize. Similarly you could make % modulus operations taint-conserving so they wouldn't have to be sanitized.
One other related question: it looks like in your sanitizers you sanitize the input to constant[random], whereas logically you should probably sanitize the result instead.
The ideal version would look something like
using source = post-update-node of rand.Read(_) or result of rand.Int() etc, and additional taint steps = big.Int functions and modulus operations, sanitize track taint forwards to any index operand and sanitize that array-read's result.
There was a problem hiding this comment.
@smowton I think I partially understand what you wish to say. I have made some changes did you mean something on these lines?
|
@smowton Changes done! PTAL. |
| ( | ||
| this.(DataFlow::ElementReadNode).reads(_, randomValue) or | ||
| any(DataFlow::ElementReadNode r).reads(this, index) | ||
| ) |
There was a problem hiding this comment.
| ( | |
| this.(DataFlow::ElementReadNode).reads(_, randomValue) or | |
| any(DataFlow::ElementReadNode r).reads(this, index) | |
| ) | |
| this.(DataFlow::ElementReadNode).reads(_, index) |
There was a problem hiding this comment.
@smowton That would make the tests fail. Please see the test file for the cases which fail.
There was a problem hiding this comment.
It does not; I've re-verified that this works and will push it myself shortly along with some other last cleanups.
It's possible you're thinking of this.(DataFlow::ElementReadNode).reads(_, randomValue) which indeed does not work because we didn't follow the taint trail to index.
| private class CompareExprSanitizer extends Sanitizer { | ||
| CompareExprSanitizer() { | ||
| exists(BinaryExpr c | | ||
| c.getAnOperand().getGlobalValueNumber() = this.asExpr().getGlobalValueNumber() | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
I tried removing this sanitizer, and it doesn't seem to be useful:
- It handles a few cases where an empty string is returned alongside an error value, but this would be better handled by looking for the pattern of an empty string
""guarded by a testif x != nil, wherexis of error type. - The other two cases were truly unreachable code:
x := "hello"; if x != "hello" { ... }, which doesn't seem like a useful test (but perhaps you could change the test to better represent a real-world situation?)
There was a problem hiding this comment.
It handles a few cases where an empty string is returned alongside an error value, but this would be better handled by looking for the pattern of an empty string "" guarded by a test if x != nil, where x is of error type.
This may not be ideal. There could be multiple instances where a error code may be returned without a test to nil, say for ex:
func t()(string, error){
if config.hasKey(){
key:= config.getKey()
return key,nil
} else {
return getDefaultKey(), errors.New("no key")
}To avoid this, I just check for cases where there the taint is compared with anything and mark that as sanitized. This handles the if x!=nil and if y !="" cases very well.
The other two cases were truly unreachable code: x := "hello"; if x != "hello" { ... }, which doesn't seem like a useful test (but perhaps you could change the test to better represent a real-world situation?)
I have added a better test case now. The test case for this sanitizer was earlier in main.go. I have moved that to sanitizer.go. Also, I have removed the test-case you mention.
| /** Mark an empty string returned with an error as a sanitizer */ | ||
| private class EmptyErrorSanitizer extends Sanitizer { | ||
| EmptyErrorSanitizer() { | ||
| exists(ReturnStmt r, DataFlow::CallNode c | | ||
| c.getTarget().hasQualifiedName("errors", "New") and | ||
| r.getNumChild() > 1 and | ||
| r.getAChild() = c.getAResult().getASuccessor*().asExpr() and | ||
| r.getAChild() = this.asExpr() | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
Removing this has no effect on the test results, so it either doesn't work or isn't tested
There was a problem hiding this comment.
I have added a better test-case now.
- Document - Only actually consider comparisons - Don't sanitize literals
…nedAlongsideErrorSanitizerGuard
|
Just pushed the remaining changes I'd want to see in the interests of getting this merged before I go on holiday. Please take a look over them; will merge this to experimental after CI completes. |
|
@smowton Looks good to me. Please don't forget to move the bounty application to the next stage after the merge. Happy Holidays! |
|
Thanks for the quick TAT @smowton |
Supersedes github/codeql-go#705