|
4 | 4 |
|
5 | 5 | import javascript
|
6 | 6 | import semmle.javascript.Concepts::Cryptography
|
| 7 | +private import semmle.javascript.security.internal.CryptoAlgorithmNames |
7 | 8 |
|
8 | 9 | /**
|
9 | 10 | * A key used in a cryptographic algorithm.
|
@@ -274,6 +275,47 @@ private module NodeJSCrypto {
|
274 | 275 | * A model of the crypto-js library.
|
275 | 276 | */
|
276 | 277 | private module CryptoJS {
|
| 278 | + private class InstantiatedAlgorithm extends DataFlow::CallNode { |
| 279 | + private string algorithmName; |
| 280 | + |
| 281 | + InstantiatedAlgorithm() { |
| 282 | + /* |
| 283 | + * ``` |
| 284 | + * const crypto = require("crypto-js"); |
| 285 | + * const cipher = crypto.algo.SHA256.create(); |
| 286 | + * ``` |
| 287 | + * matched as: |
| 288 | + * ``` |
| 289 | + * const crypto = require("crypto-js"); |
| 290 | + * const cipher = crypto.algo.<algorithmName>.create(); |
| 291 | + * ``` |
| 292 | + */ |
| 293 | + |
| 294 | + exists(DataFlow::SourceNode mod, DataFlow::PropRead propRead | |
| 295 | + mod = DataFlow::moduleImport("crypto-js") and |
| 296 | + propRead = mod.getAPropertyRead("algo").getAPropertyRead() and |
| 297 | + this = propRead.getAMemberCall("create") and |
| 298 | + algorithmName = propRead.getPropertyName() and |
| 299 | + not isStrongPasswordHashingAlgorithm(algorithmName) |
| 300 | + ) |
| 301 | + } |
| 302 | + |
| 303 | + CryptographicAlgorithm getAlgorithm() { result.matchesName(algorithmName) } |
| 304 | + |
| 305 | + private BlockMode getExplicitBlockMode() { result.matchesString(algorithmName) } |
| 306 | + |
| 307 | + BlockMode getBlockMode() { |
| 308 | + isBlockEncryptionAlgorithm(this.getAlgorithm()) and |
| 309 | + ( |
| 310 | + if exists(this.getExplicitBlockMode()) |
| 311 | + then result = this.getExplicitBlockMode() |
| 312 | + else |
| 313 | + // CBC is the default if not explicitly specified |
| 314 | + result = "CBC" |
| 315 | + ) |
| 316 | + } |
| 317 | + } |
| 318 | + |
277 | 319 | /**
|
278 | 320 | * Matches `CryptoJS.<algorithmName>` and `require("crypto-js/<algorithmName>")`
|
279 | 321 | */
|
@@ -325,13 +367,44 @@ private module CryptoJS {
|
325 | 367 | input = result.getParameter(0)
|
326 | 368 | }
|
327 | 369 |
|
| 370 | + private API::CallNode getUpdatedApplication(API::Node input, InstantiatedAlgorithm instantiation) { |
| 371 | + /* |
| 372 | + * ``` |
| 373 | + * var CryptoJS = require("crypto-js"); |
| 374 | + * var hash = CryptoJS.algo.SHA256.create(); |
| 375 | + * hash.update('message'); |
| 376 | + * hash.update('password'); |
| 377 | + * var hashInHex = hash.finalize(); |
| 378 | + * ``` |
| 379 | + * Matched as: |
| 380 | + * ``` |
| 381 | + * var CryptoJS = require("crypto-js"); |
| 382 | + * var hash = CryptoJS.algo.<algorithmName>.create(); |
| 383 | + * hash.update(<input>); |
| 384 | + * hash.update(<input>); |
| 385 | + * var hashInHex = hash.finalize(); |
| 386 | + * ``` |
| 387 | + * Also matches where `CryptoJS.algo.<algorithmName>` has been |
| 388 | + * replaced by `require("crypto-js/<algorithmName>")` |
| 389 | + */ |
| 390 | + |
| 391 | + result = instantiation.getAMemberCall("update") and |
| 392 | + input = result.getParameter(0) |
| 393 | + } |
| 394 | + |
328 | 395 | private class Apply extends CryptographicOperation::Range instanceof API::CallNode {
|
329 | 396 | API::Node input;
|
330 | 397 | CryptographicAlgorithm algorithm; // non-functional
|
331 | 398 |
|
332 | 399 | Apply() {
|
333 |
| - this = getEncryptionApplication(input, algorithm) or |
| 400 | + this = getEncryptionApplication(input, algorithm) |
| 401 | + or |
334 | 402 | this = getDirectApplication(input, algorithm)
|
| 403 | + or |
| 404 | + exists(InstantiatedAlgorithm instantiation | |
| 405 | + this = getUpdatedApplication(input, instantiation) and |
| 406 | + algorithm = instantiation.getAlgorithm() |
| 407 | + ) |
335 | 408 | }
|
336 | 409 |
|
337 | 410 | override DataFlow::Node getAnInput() { result = input.asSink() }
|
|
0 commit comments