11/**
2- * @name Bad MAC order: MAC on an encrypt plaintext
2+ * @name Bad MAC order: Mac and Encryption share the same plaintext
33 * @description MAC should be on a cipher, not a raw message
44 * @id java/quantum/bad-mac-order-encrypt-plaintext-also-in-mac
5- * @kind problem
5+ * @kind path- problem
66 * @problem.severity error
77 * @tags quantum
88 * experimental
99 */
1010
1111import java
1212import experimental.quantum.Language
13+ import codeql.util.Option
1314
14- // NOTE: I must look for a common data flow node rather than
15- // starting from a message source, since the message source
16- // might not be known.
17- // TODO: can we approximate a message source better?
18- module CommonDataFlowNodeConfig implements DataFlow:: ConfigSig {
19- predicate isSource ( DataFlow:: Node source ) {
20- exists ( source .asParameter ( ) )
15+ module ArgToSinkConfig implements DataFlow:: ConfigSig {
16+ predicate isSource ( DataFlow:: Node source ) { exists ( Call c | c .getAnArgument ( ) = source .asExpr ( ) ) }
17+
18+ predicate isSink ( DataFlow:: Node sink ) { targetSinks ( sink ) }
19+
20+ // Don't go in to a known out node, this will prevent the plaintext
21+ // from tracing out of cipher operations for example, we just want to trace
22+ // the plaintext to uses.
23+ // NOTE: we are not using a barrier out on input nodes, because
24+ // that would remove 'use-use' flows, which we need
25+ predicate isBarrierIn ( DataFlow:: Node node ) {
26+ node = any ( Crypto:: FlowAwareElement element ) .getOutputNode ( )
27+ }
28+
29+ predicate isAdditionalFlowStep ( DataFlow:: Node node1 , DataFlow:: Node node2 ) {
30+ node1 .( AdditionalFlowInputStep ) .getOutput ( ) = node2
2131 or
32+ exists ( MethodCall m |
33+ m .getMethod ( ) .hasQualifiedName ( "java.lang" , "String" , "getBytes" ) and
34+ node1 .asExpr ( ) = m .getQualifier ( ) and
35+ node2 .asExpr ( ) = m
36+ )
37+ }
38+ }
39+
40+ module ArgToSinkFlow = TaintTracking:: Global< ArgToSinkConfig > ;
41+
42+ /**
43+ * Target sinks for this query are either encryption operations or mac operation message inputs
44+ */
45+ predicate targetSinks ( DataFlow:: Node n ) {
46+ exists ( Crypto:: CipherOperationNode cipherOp |
47+ cipherOp .getKeyOperationSubtype ( ) = Crypto:: TEncryptMode ( ) and
48+ cipherOp .getAnInputArtifact ( ) .asElement ( ) = n .asExpr ( )
49+ )
50+ or
51+ exists ( Crypto:: MacOperationNode macOp | macOp .getAnInputArtifact ( ) .asElement ( ) = n .asExpr ( ) )
52+ }
53+
54+ /**
55+ * An argument of a target sink or a parent call whose parameter flows to a target sink
56+ */
57+ class InterimArg extends DataFlow:: Node {
58+ DataFlow:: Node targetSink ;
59+
60+ InterimArg ( ) {
61+ targetSinks ( targetSink ) and
62+ (
63+ this = targetSink
64+ or
65+ ArgToSinkFlow:: flow ( this , targetSink ) and
66+ this .getEnclosingCallable ( ) .calls + ( targetSink .getEnclosingCallable ( ) )
67+ )
68+ }
69+
70+ DataFlow:: Node getTargetSink ( ) { result = targetSink }
71+ }
72+
73+ /**
74+ * A wrapper class to represent a target argument dataflow node.
75+ */
76+ class TargetArg extends DataFlow:: Node {
77+ TargetArg ( ) { targetSinks ( this ) }
78+
79+ predicate isCipher ( ) {
80+ exists ( Crypto:: CipherOperationNode cipherOp |
81+ cipherOp .getKeyOperationSubtype ( ) = Crypto:: TEncryptMode ( ) and
82+ cipherOp .getAnInputArtifact ( ) .asElement ( ) = this .asExpr ( )
83+ )
84+ }
85+
86+ predicate isMac ( ) {
87+ exists ( Crypto:: MacOperationNode macOp | macOp .getAnInputArtifact ( ) .asElement ( ) = this .asExpr ( ) )
88+ }
89+ }
90+
91+ module PlaintextUseAsMacAndCipherInputConfig implements DataFlow:: StateConfigSig {
92+ class FlowState = Option< TargetArg > :: Option ;
93+
94+ // TODO: can we approximate a message source better?
95+ predicate isSource ( DataFlow:: Node source , FlowState state ) {
96+ // TODO: can we find the 'closest' parameter to the sinks?
97+ // i.e., use a generic source if we have it, but also isolate the
98+ // lowest level in the flow to the closest parameter node in the call graph?
2299 exists ( Crypto:: GenericSourceNode other |
23100 other .asElement ( ) = CryptoInput:: dfn_to_element ( source )
101+ ) and
102+ state .isNone ( )
103+ }
104+
105+ predicate isSink ( DataFlow:: Node sink , FlowState state ) {
106+ sink instanceof TargetArg and
107+ (
108+ sink .( TargetArg ) .isMac ( ) and state .asSome ( ) .isCipher ( )
109+ or
110+ sink .( TargetArg ) .isCipher ( ) and state .asSome ( ) .isMac ( )
24111 )
25112 }
26113
27- predicate isSink ( DataFlow:: Node sink ) {
28- sink = any ( Crypto:: FlowAwareElement other ) .getInputNode ( )
114+ predicate isBarrierOut ( DataFlow:: Node node , FlowState state ) {
115+ // Stop at the first sink for now
116+ isSink ( node , state )
29117 }
30118
31119 // Don't go in to a known out node, this will prevent the plaintext
@@ -46,19 +134,32 @@ module CommonDataFlowNodeConfig implements DataFlow::ConfigSig {
46134 node2 .asExpr ( ) = m
47135 )
48136 }
137+
138+ predicate isAdditionalFlowStep (
139+ DataFlow:: Node node1 , FlowState state1 , DataFlow:: Node node2 , FlowState state2
140+ ) {
141+ ( exists ( state1 .asSome ( ) ) or state1 .isNone ( ) ) and
142+ targetSinks ( node1 ) and
143+ node1 instanceof TargetArg and
144+ //use-use flow, either flow directly from the node1 use
145+ //or find a parent call in the call in the call stack
146+ //and continue flow from that parameter
147+ node2 .( InterimArg ) .getTargetSink ( ) = node1 and
148+ state2 .asSome ( ) = node1
149+ }
49150}
50151
51- module CommonDataFlowNodeFlow = TaintTracking:: Global< CommonDataFlowNodeConfig > ;
152+ module PlaintextUseAsMacAndCipherInputFlow =
153+ TaintTracking:: GlobalWithState< PlaintextUseAsMacAndCipherInputConfig > ;
52154
53- from DataFlow:: Node src , DataFlow:: Node sink1 , DataFlow:: Node sink2
155+ import PlaintextUseAsMacAndCipherInputFlow:: PathGraph
156+
157+ from
158+ PlaintextUseAsMacAndCipherInputFlow:: PathNode src ,
159+ PlaintextUseAsMacAndCipherInputFlow:: PathNode sink , InterimArg arg
54160where
55- not src .asExpr ( ) instanceof NullLiteral and
56- CommonDataFlowNodeFlow:: flow ( src , sink1 ) and
57- CommonDataFlowNodeFlow:: flow ( src , sink2 ) and
58- exists ( Crypto:: CipherOperationNode cipherOp |
59- cipherOp .getKeyOperationSubtype ( ) = Crypto:: TEncryptMode ( ) and
60- cipherOp .getAnInputArtifact ( ) .asElement ( ) = sink1 .asExpr ( )
61- ) and
62- exists ( Crypto:: MacOperationNode macOp | macOp .getAnInputArtifact ( ) .asElement ( ) = sink2 .asExpr ( ) )
63- select src , "Message used for encryption operation at $@, also used for MAC at $@." , sink1 ,
64- sink1 .toString ( ) , sink2 , sink2 .toString ( )
161+ PlaintextUseAsMacAndCipherInputFlow:: flowPath ( src , sink ) and
162+ arg = sink .getState ( ) .asSome ( )
163+ select sink , src , sink ,
164+ "Source is used as plaintext to MAC and encryption operation. Indicates possible misuse of MAC. Path shows plaintext to final use through intermediate mac or encryption operation here $@" ,
165+ arg .asExpr ( ) , arg .asExpr ( ) .toString ( )
0 commit comments