|
| 1 | +/** |
| 2 | + * @name Timing attacks due to comparison of sensitive secrets |
| 3 | + * @description using a non-constant time comparison method to compare secrets can lead to authoriztion vulnerabilities |
| 4 | + * @kind path-problem |
| 5 | + * @problem.severity warning |
| 6 | + * @id go/timing-attack |
| 7 | + * @tags security |
| 8 | + * experimental |
| 9 | + * external/cwe/cwe-203 |
| 10 | + */ |
| 11 | + |
| 12 | +import go |
| 13 | +import DataFlow::PathGraph |
| 14 | +import semmle.go.security.SensitiveActions |
| 15 | + |
| 16 | +private predicate isBadResult(DataFlow::Node e) { |
| 17 | + exists(string path | path = e.asExpr().getFile().getAbsolutePath().toLowerCase() | |
| 18 | + path.matches(["%fake%", "%dummy%", "%test%", "%example%"]) and not path.matches("%ql/test%") |
| 19 | + ) |
| 20 | +} |
| 21 | + |
| 22 | +/** |
| 23 | + * A data flow sink for timing attack vulnerabilities. |
| 24 | + */ |
| 25 | +abstract class Sink extends DataFlow::Node { } |
| 26 | + |
| 27 | +/** A taint-tracking sink which models comparisons of sensitive variables. */ |
| 28 | +private class SensitiveCompareSink extends Sink { |
| 29 | + ComparisonExpr c; |
| 30 | + |
| 31 | + SensitiveCompareSink() { |
| 32 | + // We select a comparison where a secret or password is tested. |
| 33 | + exists(SensitiveVariableAccess op1, Expr op2 | |
| 34 | + op1.getClassification() = [SensitiveExpr::secret(), SensitiveExpr::password()] and |
| 35 | + // exclude grant to avoid FP from OAuth |
| 36 | + not op1.getClassification().matches("%grant%") and |
| 37 | + op1 = c.getAnOperand() and |
| 38 | + op2 = c.getAnOperand() and |
| 39 | + not op1 = op2 and |
| 40 | + not ( |
| 41 | + // Comparisons with `nil` should be excluded. |
| 42 | + op2 = Builtin::nil().getAReference() |
| 43 | + or |
| 44 | + // Comparisons with empty string should also be excluded. |
| 45 | + op2.getStringValue().length() = 0 |
| 46 | + ) |
| 47 | + | |
| 48 | + // It is important to note that the name of both the operands need not be |
| 49 | + // `sensitive`. Even if one of the operands appears to be sensitive, we consider it a potential sink. |
| 50 | + c.getAnOperand() = this.asExpr() |
| 51 | + ) |
| 52 | + } |
| 53 | + |
| 54 | + DataFlow::Node getOtherOperand() { result.asExpr() = c.getAnOperand() and not result = this } |
| 55 | +} |
| 56 | + |
| 57 | +class SecretTracking extends TaintTracking::Configuration { |
| 58 | + SecretTracking() { this = "SecretTracking" } |
| 59 | + |
| 60 | + override predicate isSource(DataFlow::Node source) { |
| 61 | + source instanceof UntrustedFlowSource and not isBadResult(source) |
| 62 | + } |
| 63 | + |
| 64 | + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink and not isBadResult(sink) } |
| 65 | +} |
| 66 | + |
| 67 | +from SecretTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink |
| 68 | +where |
| 69 | + cfg.hasFlowPath(source, sink) and |
| 70 | + not cfg.hasFlowTo(sink.getNode().(SensitiveCompareSink).getOtherOperand()) |
| 71 | +select sink.getNode(), source, sink, "$@ may be vulnerable to timing attacks.", source.getNode(), |
| 72 | + "Hardcoded String" |
0 commit comments