Skip to content

Golang : Add query to detect JWT signing vulnerabilities #9378

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 2, 2022

Conversation

ghost
Copy link

@ghost ghost commented May 30, 2022

@ghost ghost self-requested a review as a code owner May 30, 2022 20:23
@ghost ghost force-pushed the goJwtSign branch from 639e6d4 to bd1ddc1 Compare May 30, 2022 20:27
@ghost
Copy link
Author

ghost commented May 30, 2022

@smowton @owen-mc This PR is now ready for review. I have added the changes you requested in the earlier PR as well as some new sanitizers to reduce FP's.

Comment on lines 226 to 229
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*())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I follow. Do you mean to say I should create a new taint tracking config and add this step there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smowton I think I partially understand what you wish to say. I have made some changes did you mean something on these lines?

@github-actions github-actions bot added the Go label Jun 2, 2022
@ghost
Copy link
Author

ghost commented Jun 2, 2022

@smowton Changes done! PTAL.

Comment on lines 240 to 243
(
this.(DataFlow::ElementReadNode).reads(_, randomValue) or
any(DataFlow::ElementReadNode r).reads(this, index)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(
this.(DataFlow::ElementReadNode).reads(_, randomValue) or
any(DataFlow::ElementReadNode r).reads(this, index)
)
this.(DataFlow::ElementReadNode).reads(_, index)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smowton That would make the tests fail. Please see the test file for the cases which fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 160 to 166
private class CompareExprSanitizer extends Sanitizer {
CompareExprSanitizer() {
exists(BinaryExpr c |
c.getAnOperand().getGlobalValueNumber() = this.asExpr().getGlobalValueNumber()
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 test if x != nil, where x is 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?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smowton

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.

Comment on lines 168 to 178
/** 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()
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this has no effect on the test results, so it either doesn't work or isn't tested

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a better test-case now.

@smowton
Copy link
Contributor

smowton commented Jun 2, 2022

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.

@ghost
Copy link
Author

ghost commented Jun 2, 2022

@smowton Looks good to me. Please don't forget to move the bounty application to the next stage after the merge.

Happy Holidays!

@smowton smowton merged commit 04422ee into github:main Jun 2, 2022
@ghost ghost deleted the goJwtSign branch June 2, 2022 20:03
@ghost
Copy link
Author

ghost commented Jun 2, 2022

Thanks for the quick TAT @smowton

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant