mirror of https://github.com/OWASP/Nettacker.git
Compare commits
804 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
65bf88e68f | |
|
|
2d3f39c7a3 | |
|
|
677f13ec2d | |
|
|
f6f8c60f11 | |
|
|
8c538fa065 | |
|
|
e2b4d7c2d8 | |
|
|
ad76ce537a | |
|
|
0fa6c156e3 | |
|
|
a0831bc70a | |
|
|
bf43de5f71 | |
|
|
e934f748ee | |
|
|
2fea1e44f0 | |
|
|
1d37e0e3a2 | |
|
|
cd0d168ab4 | |
|
|
5d905edce4 | |
|
|
1ff4258318 | |
|
|
6eb1f5731d | |
|
|
281a072675 | |
|
|
b465808c59 | |
|
|
518321718c | |
|
|
2cb512bbc0 | |
|
|
a9f48be405 | |
|
|
5fedd73868 | |
|
|
a373e23c28 | |
|
|
0f30544584 | |
|
|
c42460ce2f | |
|
|
c77246f700 | |
|
|
7c36e44a67 | |
|
|
cab9b2c2fe | |
|
|
8695749cc5 | |
|
|
070902df5c | |
|
|
630de628b9 | |
|
|
e04fd1c17e | |
|
|
9a0006ea42 | |
|
|
e450c819d8 | |
|
|
a08c328e83 | |
|
|
6e7a6519cf | |
|
|
7cbf897e30 | |
|
|
66c0e919b2 | |
|
|
cd34fba676 | |
|
|
bb90f09378 | |
|
|
dadb3ea9cb | |
|
|
e419d227c2 | |
|
|
04c2097fbe | |
|
|
74e494dd1a | |
|
|
8748df910b | |
|
|
6244176c99 | |
|
|
6275ead5ed | |
|
|
958e1bc075 | |
|
|
af7abb683c | |
|
|
4fd743a15d | |
|
|
10c95512e6 | |
|
|
75fc06bd31 | |
|
|
9bdb94039c | |
|
|
d77becc42a | |
|
|
5eb8f3a506 | |
|
|
423f66151a | |
|
|
4c88862c90 | |
|
|
6c0fbbfd0a | |
|
|
0c2d21405e | |
|
|
3f214b76cf | |
|
|
a72bdfc9b6 | |
|
|
f62da6a605 | |
|
|
cb1a011c13 | |
|
|
809b6e2e5c | |
|
|
f7ce6a0e2c | |
|
|
6ad4ce083c | |
|
|
9c4cd46a2d | |
|
|
426ad9f06b | |
|
|
7a8bd583d4 | |
|
|
2667369af0 | |
|
|
8d48b81467 | |
|
|
e6f526e5ac | |
|
|
71ea8a7c5e | |
|
|
65192c8fc6 | |
|
|
f257381c2a | |
|
|
c0962bcd9d | |
|
|
626a765708 | |
|
|
2456cd1951 | |
|
|
d143f4302b | |
|
|
507a098041 | |
|
|
6d427e2a3c | |
|
|
aff7fe3dc2 | |
|
|
d76eb0b7d9 | |
|
|
012bf5dda2 | |
|
|
34523c8e43 | |
|
|
cd3d4c6e2e | |
|
|
40781bf55f | |
|
|
4a7c6f3eb9 | |
|
|
84d78a1429 | |
|
|
f0ee67f924 | |
|
|
1463af88bc | |
|
|
dfc637cc4b | |
|
|
9aaa7033a5 | |
|
|
f65f9bc972 | |
|
|
caaa5e8784 | |
|
|
246611f731 | |
|
|
cec376a08b | |
|
|
d876e87909 | |
|
|
32d7d98847 | |
|
|
e806518878 | |
|
|
ccdc3be7d7 | |
|
|
9f51867da3 | |
|
|
4b7f89dbe1 | |
|
|
dda7b32d1c | |
|
|
5f6bc8e0b3 | |
|
|
a5f55386b3 | |
|
|
2dc69b6f3c | |
|
|
1391affeb4 | |
|
|
3583272ecc | |
|
|
cd8c7f41cc | |
|
|
0ba7392a9a | |
|
|
80615c984a | |
|
|
637aa76508 | |
|
|
3078b0c308 | |
|
|
4d4600620c | |
|
|
c26625de4e | |
|
|
942c398c1e | |
|
|
5dd0fdfb73 | |
|
|
7afe41519d | |
|
|
bffb531731 | |
|
|
dc40d1912b | |
|
|
d61e78a2f5 | |
|
|
4d4751905e | |
|
|
6f60092f42 | |
|
|
536b5f0c1f | |
|
|
a1b382cd56 | |
|
|
8b20d5c79d | |
|
|
2fbd9f6fa6 | |
|
|
546042dea5 | |
|
|
ce06bc504a | |
|
|
b0db4adb68 | |
|
|
ce60702ef0 | |
|
|
3f1a861200 | |
|
|
3131ab0093 | |
|
|
df1a12c9d0 | |
|
|
12170a372a | |
|
|
20f5f3cd3a | |
|
|
d4d0016498 | |
|
|
7c2479a00e | |
|
|
506b3b4371 | |
|
|
42bd36d5a1 | |
|
|
95f6d4f59f | |
|
|
4a2aba05e0 | |
|
|
453fefff1c | |
|
|
d01b1aa511 | |
|
|
0a0e2d6fc1 | |
|
|
d79426ece7 | |
|
|
efa2c4df94 | |
|
|
14933497e9 | |
|
|
04549d920e | |
|
|
1eb2d5e44c | |
|
|
8bb3a4927f | |
|
|
9b89749389 | |
|
|
987d4c3ed1 | |
|
|
165e734a27 | |
|
|
5eab2709c1 | |
|
|
0de0aa167b | |
|
|
cba79bb25c | |
|
|
8ab4e48ac0 | |
|
|
7abe4c2dc6 | |
|
|
ed9ab85d20 | |
|
|
6c68fb91a4 | |
|
|
a86b9095fd | |
|
|
424b8b66d8 | |
|
|
9c7bfd0ef3 | |
|
|
7d7e4158b4 | |
|
|
f08334c9fc | |
|
|
38c541201a | |
|
|
b62c037900 | |
|
|
4e6cabb331 | |
|
|
9859db19d4 | |
|
|
e53fca5707 | |
|
|
763e998437 | |
|
|
6ffbf882cc | |
|
|
17d53642d2 | |
|
|
97c4f99bb9 | |
|
|
13ced2dd6c | |
|
|
e8c6371594 | |
|
|
6bc979470a | |
|
|
84e1ed41a9 | |
|
|
9ffa45978e | |
|
|
f03ea61afe | |
|
|
c640dcd1cf | |
|
|
d94f11860a | |
|
|
10fd8afd68 | |
|
|
bd6a11a636 | |
|
|
58b18202a0 | |
|
|
ba0a30cc7e | |
|
|
0a6171bde7 | |
|
|
c5c541b726 | |
|
|
d9de1e4c56 | |
|
|
51b8c9fc53 | |
|
|
10eb61a06a | |
|
|
6a3aaed8bb | |
|
|
5e3103437b | |
|
|
008d673133 | |
|
|
05b24cd1fa | |
|
|
9e24c47992 | |
|
|
a397fa9e4b | |
|
|
5fe4b03725 | |
|
|
c91d2db971 | |
|
|
28f1d9001d | |
|
|
286c6ea231 | |
|
|
f1ff5ed124 | |
|
|
21af8cdd33 | |
|
|
820b198d71 | |
|
|
6bd1aaeef6 | |
|
|
0126926472 | |
|
|
4593e7790d | |
|
|
591414810a | |
|
|
63e790aaef | |
|
|
79cb19576f | |
|
|
0615a1fe2e | |
|
|
c054aa9d6c | |
|
|
97eb4f9c4c | |
|
|
45943e07a3 | |
|
|
3dd57520bb | |
|
|
c3259abf9c | |
|
|
0651a8596a | |
|
|
deaa85908e | |
|
|
4ed68f6333 | |
|
|
095909c91d | |
|
|
c783a8b5f2 | |
|
|
bdf281cf46 | |
|
|
781f9a8299 | |
|
|
9a51832c28 | |
|
|
a98c0ccecd | |
|
|
9e2c4b8ca4 | |
|
|
01766065b8 | |
|
|
f4b0bf827a | |
|
|
b44218bf7a | |
|
|
e47ef52929 | |
|
|
5518b140f6 | |
|
|
64b9457a8f | |
|
|
de4e02c2b1 | |
|
|
e8f57c1d16 | |
|
|
8c86f6239b | |
|
|
7c9dc72ca1 | |
|
|
2b30986ac9 | |
|
|
071bf36c20 | |
|
|
52fcf41df7 | |
|
|
b89c50339a | |
|
|
dd16c6b51d | |
|
|
75561812b0 | |
|
|
05a5de04be | |
|
|
cdbdd2e854 | |
|
|
c97296ebcf | |
|
|
81a5f973e4 | |
|
|
38e0181adf | |
|
|
4caa47c8f9 | |
|
|
4c85a2a55f | |
|
|
6a9f24ce70 | |
|
|
86a9cf564c | |
|
|
d39ed4a738 | |
|
|
21df30d287 | |
|
|
a992f24bd0 | |
|
|
a2bb1ae6e3 | |
|
|
ba7532606c | |
|
|
1b53d99ebf | |
|
|
477d5203b5 | |
|
|
44bd2ab7cd | |
|
|
310938b6cf | |
|
|
6c56fee299 | |
|
|
57cf98a036 | |
|
|
560d94aced | |
|
|
335c6cd7ee | |
|
|
030c0adf22 | |
|
|
6dfa9a231b | |
|
|
7cfb811c25 | |
|
|
3458f33406 | |
|
|
ae660c3e6b | |
|
|
c35c55022d | |
|
|
417821f0cd | |
|
|
5c77f90000 | |
|
|
cc26caf170 | |
|
|
22525603b7 | |
|
|
8a64187048 | |
|
|
365b9de0ac | |
|
|
c10b63837a | |
|
|
d006d3b3ef | |
|
|
14f6c06207 | |
|
|
6baee0fd5e | |
|
|
72838c498e | |
|
|
facf823cc7 | |
|
|
4251ab4bb8 | |
|
|
7e960a48c3 | |
|
|
1257ef9ed7 | |
|
|
91f54722bb | |
|
|
81b842563b | |
|
|
ecfcf42fb2 | |
|
|
617c16d4d8 | |
|
|
f4509a03af | |
|
|
ff0900d2c8 | |
|
|
b18497d426 | |
|
|
2615723d25 | |
|
|
2d3eb7654a | |
|
|
2b95d60dd3 | |
|
|
17a9751035 | |
|
|
13cd610e08 | |
|
|
273ebda720 | |
|
|
0f7e2d2e3f | |
|
|
0a34a65512 | |
|
|
1574b6ee97 | |
|
|
93c0dfa2a3 | |
|
|
9ffa548872 | |
|
|
b58e18dbc3 | |
|
|
a586444232 | |
|
|
e71b449b90 | |
|
|
29844b3452 | |
|
|
3e3f3fea0f | |
|
|
1042c20c0b | |
|
|
933906b56f | |
|
|
6936844574 | |
|
|
87ddce04f3 | |
|
|
31b3dc2e01 | |
|
|
4480f4eeb7 | |
|
|
c326796e4d | |
|
|
8deedc465e | |
|
|
625b507aef | |
|
|
9e1dbca293 | |
|
|
1ac1462a70 | |
|
|
bf45746350 | |
|
|
93d05aff28 | |
|
|
a2a8bc5d3e | |
|
|
c5956ba9df | |
|
|
07cc9945c7 | |
|
|
dadc22c1cf | |
|
|
3111921383 | |
|
|
468bcf60fc | |
|
|
39964d8ce8 | |
|
|
d7db43eca0 | |
|
|
a71a1acda9 | |
|
|
41300bf88d | |
|
|
a8e9274503 | |
|
|
4acfac0570 | |
|
|
f8a7f60b8f | |
|
|
5b3ace3ed4 | |
|
|
d2ea491b80 | |
|
|
af3a371122 | |
|
|
e9d0ca07fd | |
|
|
b7d3648aa0 | |
|
|
ae518acaf2 | |
|
|
0517f51736 | |
|
|
74c8ce94d5 | |
|
|
d170db99e7 | |
|
|
a000a235a5 | |
|
|
cfa711f400 | |
|
|
121bdf8da6 | |
|
|
d32ce138ae | |
|
|
826405c4af | |
|
|
f80ac79451 | |
|
|
62c5899f9b | |
|
|
90af15d583 | |
|
|
3a4ed268b7 | |
|
|
14ddc70adc | |
|
|
2d5e9285de | |
|
|
458465ac9c | |
|
|
57ea702764 | |
|
|
d005c662d3 | |
|
|
71d3f9d78b | |
|
|
3679ac7ec5 | |
|
|
716e7b6a8c | |
|
|
05db81c725 | |
|
|
bce2c8d442 | |
|
|
7423a79477 | |
|
|
2a0727200f | |
|
|
7af93ebb26 | |
|
|
32ada7decc | |
|
|
c24e3b14e3 | |
|
|
0de31c6d68 | |
|
|
b1a46436ae | |
|
|
157ef461d3 | |
|
|
f7abce978e | |
|
|
4bb4d91136 | |
|
|
4a1c42f023 | |
|
|
2181214c16 | |
|
|
a31fdf7735 | |
|
|
fbc60a2241 | |
|
|
40f7b921bb | |
|
|
faf711c60d | |
|
|
9a58504340 | |
|
|
c84355565f | |
|
|
b1a65b7b08 | |
|
|
83a1586160 | |
|
|
d1275caab1 | |
|
|
138df61103 | |
|
|
ba39a2c279 | |
|
|
7d57a3faf4 | |
|
|
1b4e6296a3 | |
|
|
d926de783b | |
|
|
9f161b7546 | |
|
|
98a71d87ae | |
|
|
c6813ed31e | |
|
|
6f847791fc | |
|
|
b27e4412a2 | |
|
|
7ef72940a9 | |
|
|
1cd95719a0 | |
|
|
219ff2280c | |
|
|
95a36d1c58 | |
|
|
da0bd3f33f | |
|
|
a5d52dd147 | |
|
|
7fb110f6ef | |
|
|
2e0c246357 | |
|
|
c570fb8c16 | |
|
|
73f92d09a2 | |
|
|
5ade63c4a4 | |
|
|
10ad53d1c4 | |
|
|
d8d887c852 | |
|
|
2e7886e6e8 | |
|
|
88df5a2851 | |
|
|
61dbef7f7a | |
|
|
ce82452f84 | |
|
|
f44f4c7c8f | |
|
|
add41136a6 | |
|
|
34e0622268 | |
|
|
8c24ed7a61 | |
|
|
b3c23de9dd | |
|
|
44ae9a67ea | |
|
|
25c6a17dc3 | |
|
|
baa0cf063c | |
|
|
70effc4287 | |
|
|
b5aa5da20f | |
|
|
22a63788f1 | |
|
|
3c6834523d | |
|
|
29a33945c9 | |
|
|
daee1f0452 | |
|
|
a7889aa474 | |
|
|
91c1454b01 | |
|
|
0a1b7506b0 | |
|
|
c3b00de419 | |
|
|
afa1eccea5 | |
|
|
032c54d208 | |
|
|
ee2e4440a2 | |
|
|
17c37b997b | |
|
|
c6cf21738f | |
|
|
be735f705b | |
|
|
eca9fe0bc2 | |
|
|
239ef4af06 | |
|
|
f93f8613a3 | |
|
|
af2c05bdb8 | |
|
|
f1ac6174d0 | |
|
|
c53cd8d046 | |
|
|
32080d4ca4 | |
|
|
c49f949261 | |
|
|
89d56572f8 | |
|
|
bd8abd17b0 | |
|
|
7269995647 | |
|
|
78d8d07b00 | |
|
|
6966ba9b51 | |
|
|
2e497dc087 | |
|
|
7b7e138c64 | |
|
|
4d944c25d4 | |
|
|
61d2d1c670 | |
|
|
395dde375b | |
|
|
bacce41df0 | |
|
|
0ea41a3d71 | |
|
|
fd93fac153 | |
|
|
feafb15e91 | |
|
|
09318468b9 | |
|
|
05a2953e6c | |
|
|
d6eefe7e69 | |
|
|
bcc5bb686e | |
|
|
8c14b48f1f | |
|
|
686cc2fbe6 | |
|
|
417ce113d8 | |
|
|
e9a32713e3 | |
|
|
deaafecffc | |
|
|
92004bc54a | |
|
|
0ac09aad56 | |
|
|
ca42de9aeb | |
|
|
7a3e89dc09 | |
|
|
917ba6995d | |
|
|
1289aa9e19 | |
|
|
77b6a8e9b9 | |
|
|
4b6a6a0461 | |
|
|
10b4b114fb | |
|
|
4a4d5d2a0c | |
|
|
0b65bc5808 | |
|
|
bd8dbc59fa | |
|
|
b28c517639 | |
|
|
cee831286a | |
|
|
506ebfc247 | |
|
|
195ebd127a | |
|
|
795f48a1eb | |
|
|
aa438996d6 | |
|
|
27cc83a615 | |
|
|
ad3f85bac5 | |
|
|
900b46a80b | |
|
|
40b9dce3d4 | |
|
|
6d532f35cb | |
|
|
5133a193d1 | |
|
|
649d29c951 | |
|
|
101e345f07 | |
|
|
5f913c75f6 | |
|
|
e1692fc82c | |
|
|
dbfd6f674b | |
|
|
0512da96bb | |
|
|
449fb83676 | |
|
|
d5e34b2469 | |
|
|
ff1f1a8cf6 | |
|
|
d2ae55a36d | |
|
|
993950dc9c | |
|
|
98164dd3d4 | |
|
|
8789947dd9 | |
|
|
d70d01a2ca | |
|
|
debdd34526 | |
|
|
d70609be5e | |
|
|
3d8c898d94 | |
|
|
3ec7542dbd | |
|
|
4ea76df5e1 | |
|
|
3721909808 | |
|
|
4a2f1cdb09 | |
|
|
69ae555c5e | |
|
|
141dcf2f1d | |
|
|
c8279ead6e | |
|
|
6a7c2936c1 | |
|
|
4be08d361e | |
|
|
12d3b6bc90 | |
|
|
917977ef79 | |
|
|
abca858c11 | |
|
|
8da7964cb8 | |
|
|
ec1c3c04ca | |
|
|
baf058b985 | |
|
|
5d05282b47 | |
|
|
89bebf95d6 | |
|
|
e1a4be7683 | |
|
|
c60f33ecaa | |
|
|
5f019acb94 | |
|
|
5a29248e94 | |
|
|
91fb4b516f | |
|
|
455510bf5c | |
|
|
10b7a4e793 | |
|
|
553d440ff6 | |
|
|
087014f7d7 | |
|
|
f84349b644 | |
|
|
1c34d8a326 | |
|
|
fe60d8538b | |
|
|
af1c903ee7 | |
|
|
32c0893090 | |
|
|
3ac41e720b | |
|
|
d5f2c387d0 | |
|
|
2211e6f33c | |
|
|
e5f45f9b4b | |
|
|
7fd106b5f3 | |
|
|
857b34c8de | |
|
|
335e31a4c3 | |
|
|
20c5722fca | |
|
|
97854ce767 | |
|
|
daafbe4b0a | |
|
|
445a4de653 | |
|
|
2ec1659843 | |
|
|
4c31b81faf | |
|
|
2d62ab7e28 | |
|
|
49c72744ca | |
|
|
76aa6e3adf | |
|
|
879b2147c7 | |
|
|
b77df28864 | |
|
|
52750e7928 | |
|
|
1ee00c892f | |
|
|
4a9df726ad | |
|
|
3ab917b926 | |
|
|
6a9c80f40b | |
|
|
fd7a922f31 | |
|
|
ac05f9ed5d | |
|
|
e9a40ac46b | |
|
|
7335d3cd8f | |
|
|
7496ecd572 | |
|
|
cdc3005387 | |
|
|
7bf152acb5 | |
|
|
80ea6728a1 | |
|
|
a30f37bdc9 | |
|
|
57d439214b | |
|
|
e9f6fbd66a | |
|
|
bf8736eead | |
|
|
f74380a2db | |
|
|
38878750e3 | |
|
|
8f5461ea61 | |
|
|
89e7b5cb40 | |
|
|
8a7daa5e1c | |
|
|
d5631b4273 | |
|
|
28c5eb1b16 | |
|
|
01c5b3fff6 | |
|
|
da4473aa46 | |
|
|
e1fba7e5d7 | |
|
|
fd503e2e92 | |
|
|
f47b73dff8 | |
|
|
45dda887ea | |
|
|
60342ccafa | |
|
|
8bcd0bfef3 | |
|
|
70467c777e | |
|
|
e4747032b7 | |
|
|
da126b3c07 | |
|
|
27f563e7f8 | |
|
|
25f015d4cb | |
|
|
efbf0d51e4 | |
|
|
0602baaba5 | |
|
|
121e6f1eb1 | |
|
|
0e49c377e0 | |
|
|
869ed25764 | |
|
|
ceabd1b969 | |
|
|
02d56f1e6c | |
|
|
58ec51736c | |
|
|
19fad5c47c | |
|
|
2481e47acd | |
|
|
7df6e9ca4f | |
|
|
7a67fd2f99 | |
|
|
80e6b317ad | |
|
|
d3985a43af | |
|
|
d79a49c1e7 | |
|
|
52830a4025 | |
|
|
cf7c9eec77 | |
|
|
3ca1d49869 | |
|
|
752473623e | |
|
|
c54d73e95d | |
|
|
95ca028907 | |
|
|
191c466d27 | |
|
|
2b621d62c8 | |
|
|
9fa3eebfc6 | |
|
|
c400f42a76 | |
|
|
cf94e642a3 | |
|
|
0d601e1a91 | |
|
|
594648c1c6 | |
|
|
3a7f9fb3d6 | |
|
|
c544ab4fbb | |
|
|
fae8f948b8 | |
|
|
7eb5e5289f | |
|
|
ea1892951d | |
|
|
07b78e832f | |
|
|
c2acd1c88e | |
|
|
b724556289 | |
|
|
5ff0a6a80b | |
|
|
802d945959 | |
|
|
50fbd67637 | |
|
|
ec9479c8a8 | |
|
|
a995bd6302 | |
|
|
c8b0025ab4 | |
|
|
7273e64e3f | |
|
|
06490a4813 | |
|
|
db85f5a491 | |
|
|
7e72ee95c0 | |
|
|
d84bc51f42 | |
|
|
72de76fb0a | |
|
|
93d651df38 | |
|
|
b6feb15d2b | |
|
|
33817a7028 | |
|
|
30acf79c15 | |
|
|
51823637e2 | |
|
|
20bf496f26 | |
|
|
818a29a043 | |
|
|
b53dbe3cbc | |
|
|
204ba6e33d | |
|
|
4ec8fdd129 | |
|
|
f90a89865a | |
|
|
66a9e2d629 | |
|
|
6d1653df01 | |
|
|
db443ff78a | |
|
|
0b79a5b4fc | |
|
|
6d1c7c9140 | |
|
|
508f1d90b1 | |
|
|
2ed04165d2 | |
|
|
f3b613689d | |
|
|
09d4ac090d | |
|
|
f2120a6baa | |
|
|
24964dfc61 | |
|
|
184db93520 | |
|
|
8933e227b0 | |
|
|
f82c25d6e5 | |
|
|
6089089d66 | |
|
|
899bfc7b97 | |
|
|
07916ea985 | |
|
|
25e4a8e2bc | |
|
|
0063a930f7 | |
|
|
bebc72234f | |
|
|
9ce824a0cc | |
|
|
6e304974b3 | |
|
|
cce3b90890 | |
|
|
ad4d604168 | |
|
|
a7f599963b | |
|
|
8c4b87ef8c | |
|
|
b085388e05 | |
|
|
831ec29175 | |
|
|
9c96a72c8c | |
|
|
7c4bc706d0 | |
|
|
96d376b23d | |
|
|
697ec574fb | |
|
|
895ba165ac | |
|
|
b523dafcae | |
|
|
ab66f7401a | |
|
|
0b19ba5a69 | |
|
|
59f3b0486a | |
|
|
72adba9fa7 | |
|
|
8c44b47885 | |
|
|
52a465a877 | |
|
|
6139738c4a | |
|
|
fb51ec51f8 | |
|
|
14a5da32c7 | |
|
|
87a56ae7cc | |
|
|
615d908550 | |
|
|
da75203b75 | |
|
|
727ab2ec4b | |
|
|
56e97f2c3b | |
|
|
b5dc786d8a | |
|
|
c232a57dd8 | |
|
|
9e2dce3f3d | |
|
|
e92360fb66 | |
|
|
bac63b5ef2 | |
|
|
d5a6834739 | |
|
|
814073ee5b | |
|
|
b263a3ec84 | |
|
|
bcfa286bea | |
|
|
a61cb3b296 | |
|
|
95dfc2d0ce | |
|
|
7fff5f62ef | |
|
|
e56f352999 | |
|
|
072ecaa24e | |
|
|
0d6a907f45 | |
|
|
74e6e95ec9 | |
|
|
f66a8eb9db | |
|
|
3001e0d972 | |
|
|
c50b5da508 | |
|
|
b4c8be7da5 | |
|
|
2ee70f8e8e | |
|
|
6e4b8574d5 | |
|
|
67c5f6b698 | |
|
|
7a4e3d98f9 | |
|
|
8d5a7bd7ba | |
|
|
23ca47aed3 | |
|
|
1070118b6b | |
|
|
04b5bc5845 | |
|
|
27fa3ad335 | |
|
|
753b1acafc | |
|
|
2e768f2494 | |
|
|
103fc0d83c | |
|
|
b09d4c6bb1 | |
|
|
641a47bd72 | |
|
|
de55ec6cad | |
|
|
ab78bc5819 | |
|
|
e5196b84b8 | |
|
|
19b907ec0d | |
|
|
3258de637c | |
|
|
de2b0f9c64 | |
|
|
89ff9e2683 | |
|
|
60de3df223 | |
|
|
1557d05ded | |
|
|
42a41dd597 | |
|
|
2c2bb1d7f5 | |
|
|
9ac0e334b0 | |
|
|
cf011a1643 | |
|
|
33f7cf54bb | |
|
|
ebdd9258cc | |
|
|
f93f61868d | |
|
|
f198079ccd | |
|
|
99283da039 | |
|
|
3627bbc8fb | |
|
|
d661386b66 | |
|
|
590cc890bb | |
|
|
905620ed17 | |
|
|
cf399c1925 | |
|
|
850e8e92df | |
|
|
34a8776a30 | |
|
|
23e8c575a9 | |
|
|
6c0fcabb0c | |
|
|
7bfb44f3bd | |
|
|
b9007c907f | |
|
|
b2d79f8dc2 | |
|
|
a0707ba6de | |
|
|
ecb8dd5725 | |
|
|
787a9f6005 | |
|
|
c40c3d55c9 | |
|
|
f7e6b6ebe7 | |
|
|
5f887a121d | |
|
|
9101139745 | |
|
|
64d75795f8 | |
|
|
f8bccfe05e | |
|
|
ff78900626 | |
|
|
9225cb3a57 | |
|
|
ff08b945c0 | |
|
|
474feaecbf | |
|
|
92f51a21c8 | |
|
|
50e86383ad | |
|
|
cdf67f4ac3 | |
|
|
36460883c6 | |
|
|
da3d105a62 | |
|
|
28e69eeadb | |
|
|
21e6c022f5 | |
|
|
d5d09de0ac | |
|
|
6a499b803c | |
|
|
19e79bbfbe | |
|
|
5088c0f7b5 | |
|
|
871b0e3a40 | |
|
|
3bcf6fa4a8 | |
|
|
f51554944f | |
|
|
2a7039ea62 | |
|
|
51dc0dc8f5 | |
|
|
2b678d0024 | |
|
|
a851870155 | |
|
|
c2cdb789dd | |
|
|
c34e67556b | |
|
|
440e4c358d | |
|
|
b043eff6ca | |
|
|
cb68af768d | |
|
|
47aac611dd | |
|
|
637855f53e |
|
|
@ -0,0 +1,22 @@
|
|||
chat:
|
||||
auto_reply: true
|
||||
code_generation:
|
||||
docstrings:
|
||||
language: en-US
|
||||
early_access: true
|
||||
language: en-US
|
||||
reviews:
|
||||
assess_linked_issues: true
|
||||
auto_apply_labels: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
drafts: true
|
||||
collapse_walkthrough: false
|
||||
high_level_summary: true
|
||||
high_level_summary_in_walkthrough: true
|
||||
labeling_instructions: []
|
||||
poem: false
|
||||
profile: chill
|
||||
request_changes_workflow: false
|
||||
review_status: true
|
||||
sequence_diagrams: false
|
||||
|
|
@ -1 +0,0 @@
|
|||
### Mettacker's data path
|
||||
|
|
@ -1 +0,0 @@
|
|||
### Nettacker's results path
|
||||
|
|
@ -1 +0,0 @@
|
|||
### Nettacker's tmp path
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
*.py linguist-detectable=true
|
||||
*.py linguist-language=Python
|
||||
*.html linguist-detectable=false
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# People marked here will be automatically requested for a review.
|
||||
#
|
||||
# For more information on CODEOWNERS, see:
|
||||
# https://help.github.com/en/articles/about-code-owners
|
||||
|
||||
* @arkid15r @securestep9
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<!--
|
||||
Thanks for contributing to OWASP Nettacker!
|
||||
-->
|
||||
|
||||
## Proposed change
|
||||
|
||||
<!--
|
||||
Describe the big picture of your changes.
|
||||
Don't forget to link your PR to an existing issue if any.
|
||||
-->
|
||||
|
||||
Your PR description goes here.
|
||||
|
||||
## Type of change
|
||||
|
||||
<!--
|
||||
Type of change you want to introduce. Please, check one (1) box only!
|
||||
If your PR requires multiple boxes to be checked, most likely it needs to
|
||||
be split into multiple PRs.
|
||||
-->
|
||||
|
||||
- [ ] New core framework functionality
|
||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||
- [ ] Code refactoring without any functionality changes
|
||||
- [ ] New or existing module/payload change
|
||||
- [ ] Documentation/localization improvement
|
||||
- [ ] Test coverage improvement
|
||||
- [ ] Dependency upgrade
|
||||
- [ ] Other improvement (best practice, cleanup, optimization, etc)
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--
|
||||
Put an `x` in the boxes that apply. You can change them after PR is created.
|
||||
-->
|
||||
|
||||
- [ ] I've followed the [contributing guidelines][contributing-guidelines]
|
||||
- [ ] I've run `make pre-commit`, it didn't generate any changes
|
||||
- [ ] I've run `make test`, all tests passed locally
|
||||
|
||||
<!--
|
||||
Thanks again for your contribution!
|
||||
-->
|
||||
|
||||
[contributing-guidelines]: https://nettacker.readthedocs.io/en/latest/Developers/
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build the code with docker
|
||||
run: docker build . -t nettacker
|
||||
|
||||
- name: Lint
|
||||
run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker flake8 --extend-exclude
|
||||
'*.txt,*.js,*.md,*.html' --count --select=E9,F63,F7,F82 --show-source"
|
||||
|
||||
- name: Help Menu
|
||||
run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker python nettacker.py --help"
|
||||
|
||||
- name: Help Menu in Persian
|
||||
run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker python nettacker.py --help
|
||||
-L fa"
|
||||
|
||||
- name: Show all modules
|
||||
run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker python nettacker.py
|
||||
--show-all-modules"
|
||||
|
||||
- name: Show all profiles
|
||||
run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm nettacker python nettacker.py --show-all-profiles"
|
||||
|
||||
- name: Test all modules command + check if it's finish successfully + without graph
|
||||
run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm -i nettacker python nettacker.py
|
||||
-i 127.0.0.1 -u user1,user2 -p pass1,pass2 -m all -g 21,25,80,443 -t 1000 -T 3 -v"
|
||||
|
||||
- name: Test all modules command + check if it's finish successfully + without graph + Persian
|
||||
run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm -i nettacker python nettacker.py
|
||||
-i 127.0.0.1 -L fa -u user1,user2 -p pass1,pass2 --profile all -g 21,25,80,443 -t 1000 -T 3 -v"
|
||||
|
||||
- name: Test all modules command + check if it's finish successfully + with graph + Persian
|
||||
run: "docker run -e github_ci=true -v $(pwd):/usr/src/owaspnettacker --rm -i nettacker python nettacker.py -i
|
||||
127.0.0.1 -L fa -u user1,user2 -p pass1,pass2 --profile all -g 21,25,80,443 -t 1000 -T 3
|
||||
--graph d3_tree_v2_graph -v"
|
||||
|
|
@ -0,0 +1,354 @@
|
|||
name: CI/CD
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.repository }}-${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
|
||||
|
||||
jobs:
|
||||
# Code quality checks.
|
||||
pre-commit:
|
||||
name: Run pre-commit
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Run pre-commit
|
||||
uses: pre-commit/action@v3.0.1
|
||||
|
||||
code-ql:
|
||||
name: CodeQL
|
||||
needs:
|
||||
- pre-commit
|
||||
permissions:
|
||||
security-events: write
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language:
|
||||
- javascript
|
||||
- python
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
|
||||
# Code tests.
|
||||
run-tests:
|
||||
name: Run tests
|
||||
needs:
|
||||
- pre-commit
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade poetry
|
||||
poetry install --with test
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
poetry run pytest
|
||||
|
||||
build-package:
|
||||
name: Build package
|
||||
needs:
|
||||
- run-tests
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip poetry
|
||||
poetry install
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
poetry build --no-interaction
|
||||
|
||||
- name: Upload package artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
|
||||
test-build-package:
|
||||
name: Test build on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: build-package
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- macos-latest
|
||||
- ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Get package artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
rm -rf nettacker
|
||||
python -m pip install dist/*.whl
|
||||
nettacker --version
|
||||
python -m pip uninstall -y nettacker
|
||||
python -m pip install dist/*.tar.gz
|
||||
nettacker --version
|
||||
|
||||
# Docker related jobs.
|
||||
test-docker-image:
|
||||
name: Test Docker image
|
||||
needs:
|
||||
- run-tests
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Build Docker image
|
||||
run: docker build . -t nettacker
|
||||
|
||||
- name: Test help menu
|
||||
run: |
|
||||
docker run -e github_ci=true --rm nettacker --help
|
||||
|
||||
- name: Test help menu in Persian
|
||||
run: |
|
||||
docker run -e github_ci=true --rm nettacker --help -L fa
|
||||
|
||||
- name: Show all modules
|
||||
run: |
|
||||
docker run -e github_ci=true --rm nettacker --show-all-modules
|
||||
|
||||
- name: Show all profiles
|
||||
run: |
|
||||
docker run -e github_ci=true --rm nettacker --show-all-profiles
|
||||
|
||||
- name: Test all modules command + check if it's finish successfully + csv
|
||||
run: |
|
||||
docker run -e github_ci=true --rm -i --add-host=host.docker.internal:host-gateway nettacker \
|
||||
-i host.docker.internal -u user1,user2 -p pass1,pass2 -m all -g 21,25,80,443 \
|
||||
-t 1000 -T 3 -o out.csv
|
||||
|
||||
- name: Test all modules command + check if it's finish successfully + csv
|
||||
run: |
|
||||
docker run -e github_ci=true --rm -i --add-host=host.docker.internal:host-gateway nettacker \
|
||||
-i host.docker.internal -u user1,user2 -p pass1,pass2 -m all -g 21,25,80,443 \
|
||||
-t 1000 -T 3 -o out.csv --skip-service-discovery
|
||||
|
||||
- name: Test all modules command + check if it's finish successfully + with graph + Persian
|
||||
run: |
|
||||
docker run -e github_ci=true --rm -i --add-host=host.docker.internal:host-gateway nettacker \
|
||||
-i host.docker.internal -L fa -u user1,user2 -p pass1,pass2 --profile all \
|
||||
-g 21,25,80,443 -t 1000 -T 3 --graph d3_tree_v2_graph -v
|
||||
|
||||
- name: Test all modules command + check if it's finish successfully + with graph + Persian
|
||||
run: |
|
||||
docker run -e github_ci=true --rm -i --add-host=host.docker.internal:host-gateway nettacker \
|
||||
-i host.docker.internal -L fa -u user1,user2 -p pass1,pass2 --profile all \
|
||||
-g 21,25,80,443 -t 1000 -T 3 --graph d3_tree_v2_graph -v --skip-service-discovery
|
||||
|
||||
test-docker-image-build:
|
||||
name: Test Docker ${{ matrix.docker-version }} image build
|
||||
needs:
|
||||
- run-tests
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
docker-version:
|
||||
- '27.5.0-1~ubuntu.24.04~noble'
|
||||
- '26.1.4-1~ubuntu.24.04~noble'
|
||||
- '26.0.0-1~ubuntu.24.04~noble'
|
||||
steps:
|
||||
- name: Uninstall pre-installed Docker
|
||||
run: |
|
||||
sudo apt-get remove docker-ce docker-ce-cli
|
||||
|
||||
# https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
|
||||
- name: Install Docker ${{ matrix.docker-version }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install ca-certificates curl gnupg
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
echo \
|
||||
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||||
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
|
||||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt-get install docker-ce=5:${{ matrix.docker-version }} docker-ce-cli=5:${{ matrix.docker-version }}
|
||||
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Print Docker version
|
||||
run: docker -v
|
||||
|
||||
- name: Build Nettacker image
|
||||
run: docker build . -t nettacker
|
||||
|
||||
publish-nettacker-dev-to-docker-registry:
|
||||
name: Publish nettacker:dev Docker image
|
||||
if: |
|
||||
github.repository == 'owasp/nettacker' &&
|
||||
github.event_name == 'push' &&
|
||||
github.ref_name == 'master'
|
||||
needs:
|
||||
- test-docker-image
|
||||
- test-docker-image-build
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
push: true
|
||||
tags: owasp/nettacker:dev
|
||||
|
||||
publish-nettacker-latest-to-docker-registry:
|
||||
name: Publish nettacker:latest Docker image
|
||||
if: |
|
||||
github.repository == 'owasp/nettacker' &&
|
||||
github.event_name == 'push' &&
|
||||
startsWith(github.event.ref, 'refs/tags/v')
|
||||
needs:
|
||||
- test-docker-image
|
||||
- test-docker-image-build
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
push: true
|
||||
tags: owasp/nettacker:latest
|
||||
|
||||
publish-to-test-pypi:
|
||||
name: Publish Test PyPI package
|
||||
if: |
|
||||
github.repository == 'OWASP/Nettacker' &&
|
||||
github.event_name == 'push' &&
|
||||
github.ref_name == 'master'
|
||||
environment: dev
|
||||
needs:
|
||||
- test-build-package
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Get package artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
|
||||
- name: Publish package distributions to Test PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
skip-existing: true
|
||||
|
||||
publish-to-pypi:
|
||||
name: Publish PyPI package
|
||||
if: |
|
||||
github.repository == 'OWASP/Nettacker' &&
|
||||
github.event_name == 'push' &&
|
||||
startsWith(github.event.ref, 'refs/tags/')
|
||||
environment: release
|
||||
needs:
|
||||
- test-build-package
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Get package artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
#ignore IDE settings
|
||||
*.idea*
|
||||
*.vscode*
|
||||
*.code-workspace
|
||||
|
||||
#setup
|
||||
build/*
|
||||
dist/*
|
||||
build
|
||||
dist
|
||||
*egg-info*
|
||||
|
||||
#tmp files
|
||||
|
|
@ -17,7 +19,10 @@ logs.txt
|
|||
*.log
|
||||
results.*
|
||||
.owasp-nettacker*
|
||||
.nettacker/data*
|
||||
.data*
|
||||
*.sarif
|
||||
*.dd.json
|
||||
*.DS_Store
|
||||
*.swp
|
||||
|
||||
|
|
@ -25,4 +30,4 @@ results.*
|
|||
.coverage
|
||||
coverage.xml
|
||||
|
||||
venv/*
|
||||
venv
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-ast
|
||||
- id: check-builtin-literals
|
||||
- id: check-yaml
|
||||
- id: fix-encoding-pragma
|
||||
args:
|
||||
- --remove
|
||||
- id: mixed-line-ending
|
||||
args:
|
||||
- --fix=lf
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.13
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
- --fix
|
||||
- id: ruff-format
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Read the Docs configuration file for MkDocs projects
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.12"
|
||||
|
||||
|
||||
mkdocs:
|
||||
configuration: mkdocs.yml
|
||||
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Adopters
|
||||
|
||||
This document highlights organizations, projects, and individuals using OWASP Nettacker in their security workflows.
|
||||
|
||||
## Why list adopters?
|
||||
Showcasing adoption encourages community engagement, provides credibility, and helps new users discover real-world use cases.
|
||||
|
||||
## How to add yourself
|
||||
If you or your organization use OWASP Nettacker, please:
|
||||
1. Fork this repository.
|
||||
2. Add your name, logo, and a short description below.
|
||||
3. Submit a pull request.
|
||||
|
||||
## Organizations
|
||||
|
||||
| Logo | Name | Description | Website |
|
||||
| ---- | ---- | ----------- | ------- |
|
||||
| <!--  --> | **Example Acme Corp** | Uses Nettacker for automated penetration testing. | https://acme.example.com |
|
||||
| <!--  --> | **Example SecurityCo** | Integrates Nettacker into their CI/CD pipeline for continuous security assessment. | https://securityco.example.org |
|
||||
|
||||
## Community Projects
|
||||
|
||||
- **Example project X** — integrates Nettacker for infrastructure scanning in Kubernetes environments.
|
||||
- **Example tool** — extends Nettacker modules for custom vulnerability detection.
|
||||
|
||||
## Individuals
|
||||
|
||||
- **Alice Smith example person** — security researcher (Twitter: @alice)
|
||||
|
||||
|
||||
## Thank You
|
||||
Thanks to everyone using and contributing to OWASP Nettacker! We appreciate your support and feedback.
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<!--
|
||||
Think of AGENTS.md as a README for AI agents: a dedicated, predictable place to provide the context and instructions to help AI coding agents work on your project.
|
||||
See https://agents.md for more info
|
||||
-->
|
||||
|
||||
# Repository Guidelines
|
||||
## Project Structure & Module Organization
|
||||
- Source: `nettacker/` (CLI: `nettacker/main.py`, API: `nettacker/api/`, core libs: `nettacker/core/`, modules: `nettacker/modules/`).
|
||||
- Entry points: `nettacker.py` (Python) and `poetry` script `nettacker`.
|
||||
- Tests: `tests/` (mirrors package layout: `tests/core/`, `tests/lib/`, etc.).
|
||||
- Docs & assets: `docs/`, `nettacker/web/static/`.
|
||||
- Runtime data (not for commit): `.nettacker/data/` (DB at `.nettacker/data/nettacker.db`, results in `.nettacker/data/results/`).
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- Install: `poetry install` (uses `pyproject.toml`).
|
||||
- Lint/format (all hooks): `make pre-commit` or `pre-commit run --all-files`.
|
||||
- Tests: `make test` or `poetry run pytest` (coverage configured via `pyproject.toml`).
|
||||
- Run CLI: `poetry run nettacker --help` or `python nettacker.py --help`.
|
||||
- Docker (web UI): `docker-compose up`.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Python 3.9–3.12 supported. Use 4-space indents.
|
||||
- Line length: 99 chars (`ruff`, `ruff-format`, `isort` profile=black).
|
||||
- Names: modules/files `lower_snake_case`; functions/vars `lower_snake_case`; classes `PascalCase`; constants `UPPER_SNAKE_CASE`.
|
||||
- Keep functions small, typed where practical, and add docstrings for public APIs.
|
||||
|
||||
## Testing Guidelines
|
||||
- Framework: `pytest` (+ `pytest-asyncio`, `xdist`).
|
||||
- Location/pattern: place tests under `tests/`; name files `test_*.py`; parametrize where useful.
|
||||
- Coverage: enforced via `--cov=nettacker` (see `tool.pytest.ini_options`). Add tests with new features and for bug fixes.
|
||||
- Run subsets: `poetry run pytest -k <expr>`.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Commit messages: imperative tense, concise subject; reference issues (`Fixes #123`).
|
||||
- Before pushing: `pre-commit run --all-files` and `make test` must pass.
|
||||
- PRs: include a clear description, rationale, linked issue(s), test evidence (logs or screenshots for web UI), and update docs if behavior changes.
|
||||
|
||||
## Security & Configuration Tips
|
||||
- Legal/ethics: only scan assets you are authorized to test.
|
||||
- Secrets: never commit API keys, DBs, or results; `.nettacker/data/` is runtime-only.
|
||||
- Config: defaults in `nettacker/config.py` (API key, DB path, paths). Review sensitive headers list before logging.
|
||||
61
Dockerfile
61
Dockerfile
|
|
@ -1,12 +1,53 @@
|
|||
FROM python:3.10.0rc2
|
||||
RUN apt update
|
||||
### Multi-stage Dockerfile
|
||||
# Define the base image only once as a build argument
|
||||
ARG PYTHON_IMAGE=python:3.11.13-slim
|
||||
|
||||
### Build stage
|
||||
FROM ${PYTHON_IMAGE} AS builder
|
||||
### Install OS dependencies and poetry package manager
|
||||
RUN apt-get update && \
|
||||
apt-get install -y gcc libssl-dev && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
pip install --upgrade pip poetry
|
||||
|
||||
WORKDIR /usr/src/owaspnettacker
|
||||
COPY . .
|
||||
RUN mkdir -p .data/results
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y < requirements-apt-get.txt
|
||||
RUN pip3 install --upgrade pip
|
||||
RUN pip3 install -r requirements.txt
|
||||
RUN pip3 install -r requirements-dev.txt
|
||||
|
||||
# Copy dependency files first to maximize Docker cache usage for installing dependencies
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
|
||||
# Install dependencies
|
||||
RUN poetry config virtualenvs.in-project true && \
|
||||
poetry install --no-cache --no-root --without dev --without test
|
||||
|
||||
# Now copy the rest of the required source code
|
||||
COPY nettacker nettacker
|
||||
COPY nettacker.py README.md ./
|
||||
|
||||
# Build the project only after all code is present
|
||||
RUN poetry build
|
||||
|
||||
### Runtime stage - start from a clean Python image
|
||||
FROM ${PYTHON_IMAGE} AS runtime
|
||||
WORKDIR /usr/src/owaspnettacker
|
||||
|
||||
# OCI Labels (attach to final image)
|
||||
LABEL org.opencontainers.image.title="OWASP Nettacker" \
|
||||
org.opencontainers.image.description="Automated Penetration Testing Framework" \
|
||||
org.opencontainers.image.url="https://owasp.org/nettacker" \
|
||||
org.opencontainers.image.source="https://github.com/OWASP/Nettacker" \
|
||||
org.opencontainers.image.licenses="Apache-2.0"
|
||||
|
||||
### Bring from 'builder' just the virtualenv and the packaged Nettacker as a wheel
|
||||
COPY --from=builder /usr/src/owaspnettacker/.venv ./.venv
|
||||
COPY --from=builder /usr/src/owaspnettacker/dist/*.whl .
|
||||
|
||||
ENV PATH=/usr/src/owaspnettacker/.venv/bin:$PATH
|
||||
### Use pip inside the venv to install just the nettacker wheel saving 50%+ space
|
||||
RUN pip install --no-deps --no-cache-dir nettacker-*.whl && \
|
||||
rm -f nettacker-*.whl
|
||||
|
||||
### We now have Nettacker installed in the virtualenv with 'nettacker' command which is the new entrypoint
|
||||
ENV docker_env=true
|
||||
CMD [ "python3", "./nettacker.py" ]
|
||||
ENTRYPOINT [ "nettacker" ]
|
||||
CMD ["--help"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
pre-commit:
|
||||
pre-commit run --all-files
|
||||
|
||||
test:
|
||||
poetry run pytest
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#### Checklist
|
||||
- [ ] I have followed the [Contributor Guidelines](https://github.com/OWASP/Nettacker/wiki/Developers#contribution-guidelines).
|
||||
- [ ] The code has been thoroughly tested in my local development environment with flake8 and pylint.
|
||||
- [ ] The code is Python 3 compatible.
|
||||
- [ ] The code follows the PEP8 styling guidelines with 4 spaces indentation.
|
||||
- [ ] This Pull Request relates to only one issue or only one feature
|
||||
- [ ] I have referenced the corresponding issue number in my commit message
|
||||
- [ ] I have added the relevant documentation.
|
||||
- [ ] My branch is up-to-date with the Upstream master branch.
|
||||
|
||||
#### Changes proposed in this pull request
|
||||
|
||||
#### Your development environment
|
||||
- OS: `x`
|
||||
- OS Version: `x`
|
||||
- Python Version: `x`
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
OWASP Nettacker
|
||||
=========
|
||||
[](https://github.com/OWASP/Nettacker/actions/workflows/ci_cd.yml/badge.svg?branch=master)
|
||||
[](https://github.com/OWASP/Nettacker/blob/master/LICENSE)
|
||||
[](https://twitter.com/iotscan)
|
||||

|
||||
[](https://nettacker.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://github.com/OWASP/Nettacker)
|
||||
[](https://hub.docker.com/r/owasp/nettacker)
|
||||
|
||||
|
||||
<img src="https://raw.githubusercontent.com/OWASP/Nettacker/master/nettacker/web/static/img/owasp-nettacker.png" width="200"><img src="https://raw.githubusercontent.com/OWASP/Nettacker/master/nettacker/web/static/img/owasp.png" width="500">
|
||||
|
||||
|
||||
**DISCLAIMER**
|
||||
|
||||
* ***THIS SOFTWARE WAS CREATED FOR AUTOMATED PENETRATION TESTING AND INFORMATION GATHERING. YOU MUST USE THIS SOFTWARE IN A RESPONSIBLE AND ETHICAL MANNER. DO NOT TARGET SYSTEMS OR APPLICATIONS WITHOUT OBTAINING PERMISSIONS OR CONSENT FROM THE SYSTEM OWNERS OR ADMINISTRATORS. CONTRIBUTORS WILL NOT BE RESPONSIBLE FOR ANY ILLEGAL USAGE.***
|
||||
|
||||

|
||||
|
||||
OWASP Nettacker is an open-source, Python-based automated penetration testing and information-gathering framework designed to help cyber security professionals and ethical hackers perform reconnaissance, vulnerability assessments, and network security audits efficiently. Nettacker automates tasks like port scanning, service detection, subdomain enumeration, network mapping, vulnerability scanning, credential brute-force testing making it a powerful tool for identifying weaknesses in networks, web applications, IoT devices and APIs.
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Modular architecture** - Each task — like port scanning, directory discovery, subdomain enumeration, vulnerability checks, or credential brute-forcing - is implemented as its own module, giving you control over what runs.
|
||||
- **Multi-protocol & multithreaded scanning** - Supports HTTP/HTTPS, FTP, SSH, SMB, SMTP, ICMP, TELNET, XML-RPC, and can run scans in parallel for speed.
|
||||
- **Comprehensive output** - Export reports in HTML, JSON, CSV, and plain text.
|
||||
- **Built-in database & drift detection** - Stores past scans in the database for easy search and comparison with current results: useful to detect new hosts, open ports, or vulnerabilities in CI/CD pipelines.
|
||||
- **CLI, REST API & Web UI** - Offers both programmatic integration and a user-friendly web interface for defining scans and viewing results.
|
||||
- **Evasion techniques** - Enables configurable delays, proxy support, and randomized user-agents to reduce detection by firewalls or IDS systems.
|
||||
- **Flexible targets** - Accepts single IPv4s, IP ranges, CIDR blocks, domain names, and full HTTP/HTTPS URLs. Targets can be mixed in a single command or loaded from a file using the `-l/--targets-list` flag.
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Penetration Testing**
|
||||
Automate reconnaissance, misconfiguration checks, service discovery, and vulnerability scanning to support efficient and repeatable penetration testing workflows.
|
||||
|
||||
- **Recon & Vulnerability Assessment**
|
||||
Map live hosts, open ports, services, default credentials, and directories, then perform credential brute-forcing or fuzzing using built-in or custom wordlists.
|
||||
|
||||
- **Attack Surface Mapping**
|
||||
Discover exposed hosts, ports, subdomains, and services quickly using built-in enumeration modules—ideal for both internal and external assets.
|
||||
|
||||
- **Bug Bounty Recon**
|
||||
Automate and scale common reconnaissance tasks like subdomain enumeration, directory brute-forcing, and default credential checks to speed up finding targets.
|
||||
|
||||
- **Network Vulnerability Scanning**
|
||||
Efficiently scan IPs, IP ranges, or entire CIDR blocks or all subdmains of the organisation in parallel using a modular, multithreaded approach for large-scale network assessments.
|
||||
|
||||
- **Shadow IT & Asset Discovery**
|
||||
Use historical scan data and drift detection to uncover unmanaged or forgotten hosts, open ports/services, and subdomains appearing over time.
|
||||
|
||||
- **CI/CD & Compliance Monitoring**
|
||||
Integrate Nettacker into pipelines to track infrastructure changes and detect new vulnerabilities via stored scan history and comparison features.
|
||||
|
||||
### Links
|
||||
|
||||
* OWASP Nettacker Project Home Page: https://owasp.org/nettacker
|
||||
* Documentation: https://nettacker.readthedocs.io
|
||||
* Slack: [#project-nettacker](https://owasp.slack.com/archives/CQZGG24FQ) on https://owasp.slack.com
|
||||
* Installation: https://nettacker.readthedocs.io/en/latest/Installation
|
||||
* Usage: https://nettacker.readthedocs.io/en/latest/Usage
|
||||
* GitHub repo: https://github.com/OWASP/Nettacker
|
||||
* Docker Image: https://hub.docker.com/r/owasp/nettacker
|
||||
* How to use the Dockerfile: https://nettacker.readthedocs.io/en/latest/Installation/#install-nettacker-using-docker
|
||||
* OpenHub: https://www.openhub.net/p/OWASP-Nettacker
|
||||
* **Donate**: https://owasp.org/donate/?reponame=www-project-nettacker&title=OWASP+Nettacker
|
||||
* **Read More**: https://www.secologist.com/open-source-projects
|
||||
|
||||
____________
|
||||
Quick Setup & Run
|
||||
============
|
||||
### CLI (Docker)
|
||||
```bash
|
||||
|
||||
# Basic port scan on a single IP address:
|
||||
$ docker run owasp/nettacker -i 192.168.0.1 -m port_scan
|
||||
# Scan the entire Class C network for any devices with port 22 open:
|
||||
$ docker run owasp/nettacker -i 192.168.0.0/24 -m port_scan -g 22
|
||||
# Scan all subdomains of 'owasp.org' for http/https services and return HTTP status code
|
||||
$ docker run owasp/nettacker -i owasp.org -d -s -m http_status_scan
|
||||
# Display Help
|
||||
$ docker run owasp/nettacker --help
|
||||
|
||||
|
||||
```
|
||||
### Web UI (Docker)
|
||||
|
||||
```bash
|
||||
$ docker-compose up
|
||||
```
|
||||
* Use the API Key displayed in the CLI to login to the Web GUI
|
||||
* Web GUI is accessible from your (https://localhost:5000) or https://nettacker-api.z3r0d4y.com:5000/ (pointed to your localhost)
|
||||
* The local database is `.nettacker/data/nettacker.db` (sqlite).
|
||||
* Default results path is `.nettacker/data/results`
|
||||
* `docker-compose` will share your nettacker folder, so you will not lose any data after `docker-compose down`
|
||||
* To see the API key in you can also run `docker logs nettacker_nettacker`.
|
||||
* More details and install without docker https://nettacker.readthedocs.io/en/latest/Installation
|
||||
_____________
|
||||
Thanks to our awesome contributors!
|
||||
============
|
||||
|
||||
OWASP Nettacker is an open-source project, built on the principles of collaboration and shared knowledge. The vibrant OWASP community contributes to its development, ensuring that the tool remains up-to-date, adaptable, and aligned with the latest security practices. Thanks to all our awesome contributors! 🚀
|
||||
|
||||

|
||||
|
||||
## Adopters
|
||||
|
||||
We’re grateful to the organizations, community projects, and individuals who adopt and rely on OWASP Nettacker for their security workflows.
|
||||
|
||||
If you’re using OWASP Nettacker in your organization or project, we’d love to hear from you! Feel free to add your details to the [ADOPTERS.md](ADOPTERS.md) file by submitting a pull request or reach out to us via GitHub issues. Let’s showcase how Nettacker is making a difference in the security community!
|
||||
|
||||
See [ADOPTERS.md](ADOPTERS.md) for details.
|
||||
|
||||
_____________
|
||||
|
||||
## ***Google Summer of Code (GSoC) Project***
|
||||
* ☀️ OWASP Nettacker Project is participating in the Google Summer of Code Initiative
|
||||
* 🙏 Thanks to Google Summer of Code Initiative and all the students who contributed to this project during their summer breaks:
|
||||
|
||||
|
||||
<a href="https://summerofcode.withgoogle.com"><img src="https://betanews.com/wp-content/uploads/2016/03/vertical-GSoC-logo.jpg" width="200"></img></a>
|
||||
|
||||
_____________
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starchart.cc/OWASP/Nettacker)
|
||||
|
||||
<img alt="" referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=8e922d16-445a-4c63-b4cf-5152fbbaf7fd" />
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
The latest release and the current master branch
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Report a vulnerability to the project maintainers by raising a security advisory [here](https://github.com/OWASP/Nettacker/security/advisories/new)
|
||||
|
||||
## Contacting Maintainers
|
||||
The Project Leaders are listed on the OWASP Nettacker Project page here: [https://owasp.org/nettacker](https://owasp.org/nettacker)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
pass
|
||||
631
api/engine.py
631
api/engine.py
|
|
@ -1,631 +0,0 @@
|
|||
# !/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import multiprocessing
|
||||
import time
|
||||
import random
|
||||
import csv
|
||||
import json
|
||||
import string
|
||||
import os
|
||||
import copy
|
||||
from types import SimpleNamespace
|
||||
from database.db import create_connection, get_logs_by_scan_unique_id
|
||||
from database.models import Report
|
||||
from flask import Flask
|
||||
from flask import jsonify
|
||||
from flask import request as flask_request
|
||||
from flask import render_template
|
||||
from flask import abort
|
||||
from flask import Response
|
||||
from flask import make_response
|
||||
from core.alert import write_to_api_console
|
||||
from core.alert import messages
|
||||
from core.die import die_success
|
||||
from core.time import now
|
||||
from api.api_core import structure
|
||||
from api.api_core import get_value
|
||||
from api.api_core import get_file
|
||||
from api.api_core import mime_types
|
||||
from api.api_core import scan_methods
|
||||
from api.api_core import profiles
|
||||
from api.api_core import graphs
|
||||
from api.api_core import languages_to_country
|
||||
from api.api_core import api_key_is_valid
|
||||
from database.db import select_reports
|
||||
from database.db import get_scan_result
|
||||
from database.db import last_host_logs
|
||||
from database.db import logs_to_report_json
|
||||
from database.db import search_logs
|
||||
from database.db import logs_to_report_html
|
||||
from config import nettacker_global_config
|
||||
from core.scan_targers import start_scan_processes
|
||||
from core.args_loader import check_all_required
|
||||
|
||||
app = Flask(
|
||||
__name__,
|
||||
template_folder=nettacker_global_config()['nettacker_paths']['web_static_files_path']
|
||||
)
|
||||
app.config.from_object(__name__)
|
||||
nettacker_application_config = nettacker_global_config()['nettacker_user_application_config']
|
||||
nettacker_application_config.update(nettacker_global_config()['nettacker_api_config'])
|
||||
del nettacker_application_config['api_access_key']
|
||||
|
||||
|
||||
@app.errorhandler(400)
|
||||
def error_400(error):
|
||||
"""
|
||||
handle the 400 HTTP error
|
||||
|
||||
Args:
|
||||
error: the flask error
|
||||
|
||||
Returns:
|
||||
400 JSON error
|
||||
"""
|
||||
return jsonify(
|
||||
structure(
|
||||
status="error",
|
||||
msg=error.description
|
||||
)
|
||||
), 400
|
||||
|
||||
|
||||
@app.errorhandler(401)
|
||||
def error_401(error):
|
||||
"""
|
||||
handle the 401 HTTP error
|
||||
|
||||
Args:
|
||||
error: the flask error
|
||||
|
||||
Returns:
|
||||
401 JSON error
|
||||
"""
|
||||
return jsonify(
|
||||
structure(
|
||||
status="error",
|
||||
msg=error.description
|
||||
)
|
||||
), 401
|
||||
|
||||
|
||||
@app.errorhandler(403)
|
||||
def error_403(error):
|
||||
"""
|
||||
handle the 403 HTTP error
|
||||
|
||||
Args:
|
||||
error: the flask error
|
||||
|
||||
Returns:
|
||||
403 JSON error
|
||||
"""
|
||||
return jsonify(
|
||||
structure(
|
||||
status="error",
|
||||
msg=error.description
|
||||
)
|
||||
), 403
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def error_404(error):
|
||||
"""
|
||||
handle the 404 HTTP error
|
||||
|
||||
Args:
|
||||
error: the flask error
|
||||
|
||||
Returns:
|
||||
404 JSON error
|
||||
"""
|
||||
return jsonify(
|
||||
structure(
|
||||
status="error",
|
||||
msg=messages("not_found")
|
||||
)
|
||||
), 404
|
||||
|
||||
|
||||
@app.before_request
|
||||
def limit_remote_addr():
|
||||
"""
|
||||
check if IP filtering applied and API address is in whitelist
|
||||
|
||||
Returns:
|
||||
None if it's in whitelist otherwise abort(403)
|
||||
"""
|
||||
# IP Limitation
|
||||
if app.config["OWASP_NETTACKER_CONFIG"]["api_client_whitelisted_ips"]:
|
||||
if flask_request.remote_addr not in app.config["OWASP_NETTACKER_CONFIG"]["api_client_whitelisted_ips"]:
|
||||
abort(403, messages("unauthorized_IP"))
|
||||
return
|
||||
|
||||
|
||||
@app.after_request
|
||||
def access_log(response):
|
||||
"""
|
||||
if access log enabled, its writing the logs
|
||||
|
||||
Args:
|
||||
response: the flask response
|
||||
|
||||
Returns:
|
||||
the flask response
|
||||
"""
|
||||
if app.config["OWASP_NETTACKER_CONFIG"]["api_access_log"]:
|
||||
log_request = open(
|
||||
app.config["OWASP_NETTACKER_CONFIG"]["api_access_log"],
|
||||
"ab"
|
||||
)
|
||||
log_request.write(
|
||||
"{0} [{1}] {2} \"{3} {4}\" {5} {6} {7}\r\n".format(
|
||||
flask_request.remote_addr,
|
||||
now(),
|
||||
flask_request.host,
|
||||
flask_request.method,
|
||||
flask_request.full_path,
|
||||
flask_request.user_agent,
|
||||
response.status_code,
|
||||
json.dumps(flask_request.form)
|
||||
).encode()
|
||||
)
|
||||
log_request.close()
|
||||
return response
|
||||
|
||||
|
||||
@app.route("/", defaults={"path": ""})
|
||||
@app.route("/<path:path>")
|
||||
def get_statics(path):
|
||||
"""
|
||||
getting static files and return content mime types
|
||||
|
||||
Args:
|
||||
path: path and filename
|
||||
|
||||
Returns:
|
||||
file content and content type if file found otherwise abort(404)
|
||||
"""
|
||||
static_types = mime_types()
|
||||
return Response(
|
||||
get_file(
|
||||
os.path.join(
|
||||
nettacker_global_config()['nettacker_paths']['web_static_files_path'],
|
||||
path
|
||||
)
|
||||
),
|
||||
mimetype=static_types.get(
|
||||
os.path.splitext(path)[1],
|
||||
"text/html"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@app.route("/", methods=["GET", "POST"])
|
||||
def index():
|
||||
"""
|
||||
index page for WebUI
|
||||
|
||||
Returns:
|
||||
rendered HTML page
|
||||
"""
|
||||
from config import nettacker_user_application_config
|
||||
filename = nettacker_user_application_config()["report_path_filename"]
|
||||
return render_template(
|
||||
"index.html",
|
||||
selected_modules=scan_methods(),
|
||||
profile=profiles(),
|
||||
languages=languages_to_country(),
|
||||
graphs=graphs(),
|
||||
filename=filename
|
||||
)
|
||||
|
||||
|
||||
@app.route("/new/scan", methods=["GET", "POST"])
|
||||
def new_scan():
|
||||
"""
|
||||
new scan through the API
|
||||
|
||||
Returns:
|
||||
a JSON message with scan details if success otherwise a JSON error
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
form_values = dict(flask_request.form)
|
||||
for key in nettacker_application_config:
|
||||
if key not in form_values:
|
||||
form_values[key] = nettacker_application_config[key]
|
||||
options = check_all_required(
|
||||
None,
|
||||
api_forms=SimpleNamespace(**copy.deepcopy(form_values))
|
||||
)
|
||||
app.config["OWASP_NETTACKER_CONFIG"]["options"] = options
|
||||
new_process = multiprocessing.Process(target=start_scan_processes, args=(options,))
|
||||
new_process.start()
|
||||
return jsonify(
|
||||
vars(
|
||||
options
|
||||
)
|
||||
), 200
|
||||
|
||||
|
||||
@app.route("/session/check", methods=["GET"])
|
||||
def session_check():
|
||||
"""
|
||||
check the session if it's valid
|
||||
|
||||
Returns:
|
||||
a JSON message if it's valid otherwise abort(401)
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
return jsonify(
|
||||
structure(
|
||||
status="ok",
|
||||
msg=messages("browser_session_valid")
|
||||
)
|
||||
), 200
|
||||
|
||||
|
||||
@app.route("/session/set", methods=["GET", "POST"])
|
||||
def session_set():
|
||||
"""
|
||||
set session on the browser
|
||||
|
||||
Returns:
|
||||
200 HTTP response if session is valid and a set-cookie in the
|
||||
response if success otherwise abort(403)
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
res = make_response(
|
||||
jsonify(
|
||||
structure(
|
||||
status="ok",
|
||||
msg=messages("browser_session_valid")
|
||||
)
|
||||
)
|
||||
)
|
||||
res.set_cookie("key", value=app.config["OWASP_NETTACKER_CONFIG"]["api_access_key"])
|
||||
return res
|
||||
|
||||
|
||||
@app.route("/session/kill", methods=["GET"])
|
||||
def session_kill():
|
||||
"""
|
||||
unset session on the browser
|
||||
|
||||
Returns:
|
||||
a 200 HTTP response with set-cookie to "expired"
|
||||
to unset the cookie on the browser
|
||||
"""
|
||||
res = make_response(
|
||||
jsonify(
|
||||
structure(
|
||||
status="ok",
|
||||
msg=messages("browser_session_killed")
|
||||
)
|
||||
)
|
||||
)
|
||||
res.set_cookie("key", "", expires=0)
|
||||
return res
|
||||
|
||||
|
||||
@app.route("/results/get_list", methods=["GET"])
|
||||
def get_results():
|
||||
"""
|
||||
get list of scan's results through the API
|
||||
|
||||
Returns:
|
||||
an array of JSON scan's results if success otherwise abort(403)
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
page = get_value(flask_request, "page")
|
||||
if not page:
|
||||
page = 1
|
||||
return jsonify(
|
||||
select_reports(
|
||||
int(page)
|
||||
)
|
||||
), 200
|
||||
|
||||
|
||||
@app.route("/results/get", methods=["GET"])
|
||||
def get_result_content():
|
||||
"""
|
||||
get a result HTML/TEXT/JSON content
|
||||
|
||||
Returns:
|
||||
content of the scan result
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
scan_id = get_value(flask_request, "id")
|
||||
if not scan_id:
|
||||
return jsonify(
|
||||
structure(
|
||||
status="error",
|
||||
msg=messages("invalid_scan_id")
|
||||
)
|
||||
), 400
|
||||
filename, file_content = get_scan_result(scan_id)
|
||||
return Response(
|
||||
file_content,
|
||||
mimetype=mime_types().get(
|
||||
os.path.splitext(filename)[1],
|
||||
"text/plain"
|
||||
),
|
||||
headers={
|
||||
'Content-Disposition': 'attachment;filename=' + filename.split('/')[-1]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/results/get_json", methods=["GET"])
|
||||
def get_results_json():
|
||||
"""
|
||||
get host's logs through the API in JSON type
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
session = create_connection()
|
||||
result_id = get_value(flask_request, "id")
|
||||
if not result_id:
|
||||
return jsonify(
|
||||
structure(
|
||||
status="error",
|
||||
msg=messages("invalid_scan_id")
|
||||
)
|
||||
), 400
|
||||
scan_details = session.query(Report).filter(Report.id == result_id).first()
|
||||
json_object = json.dumps(
|
||||
get_logs_by_scan_unique_id(
|
||||
scan_details.scan_unique_id
|
||||
)
|
||||
)
|
||||
filename = ".".join(scan_details.report_path_filename.split('.')[:-1])[1:] + '.json'
|
||||
return Response(
|
||||
json_object,
|
||||
mimetype='application/json',
|
||||
headers={
|
||||
'Content-Disposition': 'attachment;filename=' + filename
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/results/get_csv", methods=["GET"])
|
||||
def get_results_csv(): # todo: need to fix time format
|
||||
"""
|
||||
get host's logs through the API in JSON type
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
session = create_connection()
|
||||
result_id = get_value(flask_request, "id")
|
||||
if not result_id:
|
||||
return jsonify(
|
||||
structure(
|
||||
status="error",
|
||||
msg=messages("invalid_scan_id")
|
||||
)
|
||||
), 400
|
||||
scan_details = session.query(Report).filter(Report.id == result_id).first()
|
||||
data = get_logs_by_scan_unique_id(scan_details.scan_unique_id)
|
||||
keys = data[0].keys()
|
||||
filename = ".".join(scan_details.report_path_filename.split('.')[:-1])[1:] + '.csv'
|
||||
with open(filename, "w") as report_path_filename:
|
||||
dict_writer = csv.DictWriter(
|
||||
report_path_filename,
|
||||
fieldnames=keys,
|
||||
quoting=csv.QUOTE_ALL
|
||||
)
|
||||
dict_writer.writeheader()
|
||||
for event in data:
|
||||
dict_writer.writerow(
|
||||
{
|
||||
key: value for key, value in event.items() if key in keys
|
||||
}
|
||||
)
|
||||
with open(filename, 'r') as report_path_filename:
|
||||
reader = report_path_filename.read()
|
||||
return Response(
|
||||
reader,
|
||||
mimetype='text/csv',
|
||||
headers={
|
||||
'Content-Disposition': 'attachment;filename=' + filename
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/logs/get_list", methods=["GET"])
|
||||
def get_last_host_logs(): # need to check
|
||||
"""
|
||||
get list of logs through the API
|
||||
|
||||
Returns:
|
||||
an array of JSON logs if success otherwise abort(403)
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
page = get_value(flask_request, "page")
|
||||
if not page:
|
||||
page = 1
|
||||
return jsonify(
|
||||
last_host_logs(
|
||||
int(page)
|
||||
)
|
||||
), 200
|
||||
|
||||
|
||||
@app.route("/logs/get_html", methods=["GET"])
|
||||
def get_logs_html(): # todo: check until here - ali
|
||||
"""
|
||||
get host's logs through the API in HTML type
|
||||
|
||||
Returns:
|
||||
HTML report
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
target = get_value(flask_request, "target")
|
||||
return make_response(
|
||||
logs_to_report_html(target)
|
||||
)
|
||||
|
||||
|
||||
@app.route("/logs/get_json", methods=["GET"])
|
||||
def get_logs():
|
||||
"""
|
||||
get host's logs through the API in JSON type
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
target = get_value(flask_request, "target")
|
||||
data = logs_to_report_json(target)
|
||||
json_object = json.dumps(data)
|
||||
filename = "report-" + now(
|
||||
model="%Y_%m_%d_%H_%M_%S"
|
||||
) + "".join(
|
||||
random.choice(string.ascii_lowercase) for _ in range(10)
|
||||
)
|
||||
return Response(
|
||||
json_object,
|
||||
mimetype='application/json',
|
||||
headers={
|
||||
'Content-Disposition': 'attachment;filename=' + filename + '.json'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/logs/get_csv", methods=["GET"])
|
||||
def get_logs_csv():
|
||||
"""
|
||||
get target's logs through the API in JSON type
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
target = get_value(flask_request, "target")
|
||||
data = logs_to_report_json(target)
|
||||
keys = data[0].keys()
|
||||
filename = "report-" + now(
|
||||
model="%Y_%m_%d_%H_%M_%S"
|
||||
) + "".join(
|
||||
random.choice(
|
||||
string.ascii_lowercase
|
||||
) for _ in range(10)
|
||||
)
|
||||
with open(filename, "w") as report_path_filename:
|
||||
dict_writer = csv.DictWriter(
|
||||
report_path_filename,
|
||||
fieldnames=keys,
|
||||
quoting=csv.QUOTE_ALL
|
||||
)
|
||||
dict_writer.writeheader()
|
||||
for event in data:
|
||||
dict_writer.writerow(
|
||||
{
|
||||
key: value for key, value in event.items() if key in keys
|
||||
}
|
||||
)
|
||||
with open(filename, 'r') as report_path_filename:
|
||||
reader = report_path_filename.read()
|
||||
return Response(
|
||||
reader, mimetype='text/csv',
|
||||
headers={
|
||||
'Content-Disposition': 'attachment;filename=' + filename + '.csv'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/logs/search", methods=["GET"])
|
||||
def go_for_search_logs():
|
||||
"""
|
||||
search in all events
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
try:
|
||||
page = int(get_value(flask_request, "page"))
|
||||
if page > 0:
|
||||
page -= 1
|
||||
except Exception:
|
||||
page = 0
|
||||
try:
|
||||
query = get_value(flask_request, "q")
|
||||
except Exception:
|
||||
query = ""
|
||||
return jsonify(search_logs(page, query)), 200
|
||||
|
||||
|
||||
def start_api_subprocess(options):
|
||||
"""
|
||||
a function to run flask in a subprocess to make kill signal in a better
|
||||
way!
|
||||
|
||||
Args:
|
||||
options: all options
|
||||
"""
|
||||
app.config["OWASP_NETTACKER_CONFIG"] = {
|
||||
"api_access_key": options.api_access_key,
|
||||
"api_client_whitelisted_ips": options.api_client_whitelisted_ips,
|
||||
"api_access_log": options.api_access_log,
|
||||
"api_cert": options.api_cert,
|
||||
"api_cert_key": options.api_cert_key,
|
||||
"language": options.language,
|
||||
"options": options
|
||||
}
|
||||
if options.api_cert and options.api_cert_key:
|
||||
app.run(
|
||||
host=options.api_hostname,
|
||||
port=options.api_port,
|
||||
debug=options.api_debug_mode,
|
||||
ssl_context=(
|
||||
options.api_cert,
|
||||
options.api_cert_key
|
||||
),
|
||||
threaded=True
|
||||
)
|
||||
else:
|
||||
app.run(
|
||||
host=options.api_hostname,
|
||||
port=options.api_port,
|
||||
debug=options.api_debug_mode,
|
||||
ssl_context='adhoc',
|
||||
threaded=True
|
||||
)
|
||||
|
||||
|
||||
def start_api_server(options):
|
||||
"""
|
||||
entry point to run the API through the flask
|
||||
|
||||
Args:
|
||||
options: all options
|
||||
"""
|
||||
# Starting the API
|
||||
write_to_api_console(
|
||||
messages("API_key").format(
|
||||
options.api_port,
|
||||
options.api_access_key
|
||||
)
|
||||
)
|
||||
p = multiprocessing.Process(
|
||||
target=start_api_subprocess,
|
||||
args=(options,)
|
||||
)
|
||||
p.start()
|
||||
# Sometimes it's take much time to terminate flask with CTRL+C
|
||||
# So It's better to use KeyboardInterrupt to terminate!
|
||||
while len(multiprocessing.active_children()) != 0:
|
||||
try:
|
||||
time.sleep(0.3)
|
||||
except KeyboardInterrupt:
|
||||
for process in multiprocessing.active_children():
|
||||
process.terminate()
|
||||
break
|
||||
die_success()
|
||||
142
config.py
142
config.py
|
|
@ -1,142 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
from core.time import now
|
||||
from core.utility import generate_random_token
|
||||
|
||||
|
||||
def nettacker_paths():
|
||||
"""
|
||||
home path for the framework (could be modify by user)
|
||||
|
||||
Returns:
|
||||
a JSON contain the working, tmp and results path
|
||||
"""
|
||||
return {
|
||||
"requirements_path": os.path.join(sys.path[0], 'requirements.txt'),
|
||||
"requirements_dev_path": os.path.join(sys.path[0], 'requirements-dev.txt'),
|
||||
"home_path": os.path.join(sys.path[0]),
|
||||
"data_path": os.path.join(sys.path[0], '.data'),
|
||||
"tmp_path": os.path.join(sys.path[0], '.data/tmp'),
|
||||
"results_path": os.path.join(sys.path[0], '.data/results'),
|
||||
"database_path": os.path.join(sys.path[0], '.data/nettacker.db'),
|
||||
"version_file": os.path.join(sys.path[0], 'version.txt'),
|
||||
"logo_file": os.path.join(sys.path[0], 'logo.txt'),
|
||||
"messages_path": os.path.join(sys.path[0], 'lib/messages'),
|
||||
"modules_path": os.path.join(sys.path[0], 'modules'),
|
||||
"web_browser_user_agents": os.path.join(sys.path[0], 'lib/payloads/User-Agents/web_browsers_user_agents.txt'),
|
||||
"web_static_files_path": os.path.join(sys.path[0], 'web/static'),
|
||||
"payloads_path": os.path.join(sys.path[0], 'lib/payloads'),
|
||||
"module_protocols_path": os.path.join(sys.path[0], 'core/module_protocols'),
|
||||
}
|
||||
|
||||
|
||||
def nettacker_api_config():
|
||||
"""
|
||||
API Config (could be modify by user)
|
||||
|
||||
Returns:
|
||||
a JSON with API configuration
|
||||
"""
|
||||
return { # OWASP Nettacker API Default Configuration
|
||||
"start_api_server": False,
|
||||
"api_hostname": "0.0.0.0" if os.environ.get("docker_env") == "true" else "nettacker-api.z3r0d4y.com",
|
||||
"api_port": 5000,
|
||||
"api_debug_mode": False,
|
||||
"api_access_key": generate_random_token(32),
|
||||
"api_client_whitelisted_ips": [], # disabled - to enable please put an array with list of ips/cidr/ranges
|
||||
# [
|
||||
# "127.0.0.1",
|
||||
# "10.0.0.0/24",
|
||||
# "192.168.1.1-192.168.1.255"
|
||||
# ],
|
||||
"api_access_log": os.path.join(sys.path[0], '.data/nettacker.log'),
|
||||
}
|
||||
|
||||
|
||||
def nettacker_database_config():
|
||||
"""
|
||||
Database Config (could be modified by user)
|
||||
For sqlite database:
|
||||
fill the name of the DB as sqlite,
|
||||
DATABASE as the name of the db user wants
|
||||
other details can be left empty
|
||||
For mysql users:
|
||||
fill the name of the DB as mysql
|
||||
DATABASE as the name of the database you want to create
|
||||
USERNAME, PASSWORD, HOST and the PORT of the MySQL server
|
||||
need to be filled respectively
|
||||
|
||||
Returns:
|
||||
a JSON with Database configuration
|
||||
"""
|
||||
return {
|
||||
"DB": "sqlite",
|
||||
# "DB":"mysql", "DB": "postgres"
|
||||
"DATABASE": nettacker_paths()["database_path"],
|
||||
# Name of the database
|
||||
"USERNAME": "",
|
||||
"PASSWORD": "",
|
||||
"HOST": "",
|
||||
"PORT": ""
|
||||
}
|
||||
|
||||
|
||||
def nettacker_user_application_config():
|
||||
"""
|
||||
core framework default config (could be modify by user)
|
||||
|
||||
Returns:
|
||||
a JSON with all user default configurations
|
||||
"""
|
||||
from core.compatible import version_info
|
||||
return { # OWASP Nettacker Default Configuration
|
||||
"language": "en",
|
||||
"verbose_mode": False,
|
||||
"verbose_event": False,
|
||||
"show_version": False,
|
||||
"report_path_filename": "{results_path}/results_{date_time}_{random_chars}.html".format(
|
||||
results_path=nettacker_paths()["results_path"],
|
||||
date_time=now(model="%Y_%m_%d_%H_%M_%S"),
|
||||
random_chars=generate_random_token(10)
|
||||
),
|
||||
"graph_name": "d3_tree_v2_graph",
|
||||
"show_help_menu": False,
|
||||
"targets": None,
|
||||
"targets_list": None,
|
||||
"selected_modules": None,
|
||||
"excluded_modules": None,
|
||||
"usernames": None,
|
||||
"usernames_list": None,
|
||||
"passwords": None,
|
||||
"passwords_list": None,
|
||||
"ports": None,
|
||||
"timeout": 3.0,
|
||||
"time_sleep_between_requests": 0.0,
|
||||
"scan_ip_range": False,
|
||||
"scan_subdomains": False,
|
||||
"thread_per_host": 100,
|
||||
"parallel_module_scan": 1,
|
||||
"socks_proxy": None,
|
||||
"retries": 1,
|
||||
"ping_before_scan": False,
|
||||
"profiles": None,
|
||||
"set_hardware_usage": "maximum", # low, normal, high, maximum
|
||||
"user_agent": "Nettacker {version_number} {version_code}".format(
|
||||
version_number=version_info()[0], version_code=version_info()[1]
|
||||
),
|
||||
"show_all_modules": False,
|
||||
"show_all_profiles": False,
|
||||
"modules_extra_args": None
|
||||
}
|
||||
|
||||
|
||||
def nettacker_global_config():
|
||||
return {
|
||||
"nettacker_paths": nettacker_paths(),
|
||||
"nettacker_api_config": nettacker_api_config(),
|
||||
"nettacker_database_config": nettacker_database_config(),
|
||||
"nettacker_user_application_config": nettacker_user_application_config()
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
225
core/alert.py
225
core/alert.py
|
|
@ -1,225 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
from core import color
|
||||
from core.messages import load_message
|
||||
from core.time import now
|
||||
|
||||
message_cache = load_message().messages
|
||||
|
||||
|
||||
def run_from_api():
|
||||
"""
|
||||
check if framework run from API to prevent any alert
|
||||
|
||||
Returns:
|
||||
True if run from API otherwise False
|
||||
"""
|
||||
return "--start-api" in sys.argv
|
||||
|
||||
|
||||
def verbose_mode_is_enabled():
|
||||
return '--verbose' in sys.argv or '-v' in sys.argv
|
||||
|
||||
|
||||
def event_verbose_mode_is_enabled():
|
||||
return '--verbose-event' in sys.argv
|
||||
|
||||
|
||||
def messages(msg_id):
|
||||
"""
|
||||
load a message from message library with specified language
|
||||
|
||||
Args:
|
||||
msg_id: message id
|
||||
|
||||
Returns:
|
||||
the message content in the selected language if
|
||||
message found otherwise return message in English
|
||||
"""
|
||||
return message_cache[str(msg_id)]
|
||||
|
||||
|
||||
def info(content):
|
||||
"""
|
||||
build the info message, log the message in database if requested,
|
||||
rewrite the thread temporary file
|
||||
|
||||
Args:
|
||||
content: content of the message
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if not run_from_api():
|
||||
sys.stdout.buffer.write(
|
||||
bytes(
|
||||
color.color("yellow")
|
||||
+ "[{0}][+] ".format(now())
|
||||
+ color.color("green")
|
||||
+ content
|
||||
+ color.color("reset")
|
||||
+ "\n",
|
||||
"utf8",
|
||||
)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def verbose_event_info(content):
|
||||
"""
|
||||
build the info message, log the message in database if requested,
|
||||
rewrite the thread temporary file
|
||||
|
||||
Args:
|
||||
content: content of the message
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if (not run_from_api()) and (
|
||||
verbose_mode_is_enabled() or event_verbose_mode_is_enabled()
|
||||
): # prevent to stdout if run from API
|
||||
sys.stdout.buffer.write(
|
||||
bytes(
|
||||
color.color("yellow")
|
||||
+ "[{0}][+] ".format(now())
|
||||
+ color.color("green")
|
||||
+ content
|
||||
+ color.color("reset")
|
||||
+ "\n",
|
||||
"utf8",
|
||||
)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def success_event_info(content):
|
||||
"""
|
||||
build the info message, log the message in database if requested,
|
||||
rewrite the thread temporary file
|
||||
|
||||
Args:
|
||||
content: content of the message
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if not run_from_api():
|
||||
sys.stdout.buffer.write(
|
||||
bytes(
|
||||
color.color("red")
|
||||
+ "[{0}][+++] ".format(now())
|
||||
+ color.color("cyan")
|
||||
+ content
|
||||
+ color.color("reset")
|
||||
+ "\n",
|
||||
"utf8",
|
||||
)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def verbose_info(content):
|
||||
"""
|
||||
build the info message, log the message in database if requested,
|
||||
rewrite the thread temporary file
|
||||
|
||||
Args:
|
||||
content: content of the message
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if verbose_mode_is_enabled():
|
||||
sys.stdout.buffer.write(
|
||||
bytes(
|
||||
color.color("yellow")
|
||||
+ "[{0}][+] ".format(now())
|
||||
+ color.color("purple")
|
||||
+ content
|
||||
+ color.color("reset")
|
||||
+ "\n",
|
||||
"utf8",
|
||||
)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def write(content):
|
||||
"""
|
||||
simple print a message
|
||||
|
||||
Args:
|
||||
content: content of the message
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if not run_from_api():
|
||||
sys.stdout.buffer.write(
|
||||
bytes(content, "utf8") if isinstance(content, str) else content
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def warn(content):
|
||||
"""
|
||||
build the warn message
|
||||
|
||||
Args:
|
||||
content: content of the message
|
||||
|
||||
Returns:
|
||||
the message in warn structure - None
|
||||
"""
|
||||
if not run_from_api():
|
||||
sys.stdout.buffer.write(
|
||||
bytes(
|
||||
color.color("blue")
|
||||
+ "[{0}][!] ".format(now())
|
||||
+ color.color("yellow")
|
||||
+ content
|
||||
+ color.color("reset")
|
||||
+ "\n",
|
||||
"utf8",
|
||||
)
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def error(content):
|
||||
"""
|
||||
build the error message
|
||||
|
||||
Args:
|
||||
content: content of the message
|
||||
|
||||
Returns:
|
||||
the message in error structure - None
|
||||
"""
|
||||
data = (
|
||||
color.color("red")
|
||||
+ "[{0}][X] ".format(now())
|
||||
+ color.color("yellow")
|
||||
+ content
|
||||
+ color.color("reset")
|
||||
+ "\n"
|
||||
)
|
||||
sys.stdout.buffer.write(data.encode("utf8"))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def write_to_api_console(content):
|
||||
"""
|
||||
simple print a message in API mode
|
||||
|
||||
Args:
|
||||
content: content of the message
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
sys.stdout.buffer.write(bytes(content, "utf8"))
|
||||
sys.stdout.flush()
|
||||
|
|
@ -1,611 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from core.alert import write
|
||||
from core.alert import warn
|
||||
from core.alert import info
|
||||
from core.alert import messages
|
||||
from core.color import color
|
||||
from core.compatible import version_info
|
||||
from config import nettacker_global_config
|
||||
from core.load_modules import load_all_languages
|
||||
from core.utility import (application_language,
|
||||
select_maximum_cpu_core)
|
||||
from core.die import die_success
|
||||
from core.die import die_failure
|
||||
from core.color import reset_color
|
||||
from core.load_modules import load_all_modules
|
||||
from core.load_modules import load_all_graphs
|
||||
from core.load_modules import load_all_profiles
|
||||
|
||||
|
||||
def load_all_args():
|
||||
"""
|
||||
create the ARGS and help menu
|
||||
|
||||
Returns:
|
||||
the parser, the ARGS
|
||||
"""
|
||||
|
||||
nettacker_global_configuration = nettacker_global_config()
|
||||
|
||||
# Language Options
|
||||
language = application_language()
|
||||
languages_list = load_all_languages()
|
||||
if language not in languages_list:
|
||||
die_failure(
|
||||
"Please select one of these languages {0}".format(
|
||||
languages_list
|
||||
)
|
||||
)
|
||||
|
||||
reset_color()
|
||||
# Start Parser
|
||||
parser = argparse.ArgumentParser(prog="Nettacker", add_help=False)
|
||||
|
||||
# Engine Options
|
||||
engineOpt = parser.add_argument_group(
|
||||
messages("engine"), messages("engine_input")
|
||||
)
|
||||
engineOpt.add_argument(
|
||||
"-L",
|
||||
"--language",
|
||||
action="store",
|
||||
dest="language",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["language"],
|
||||
help=messages("select_language").format(languages_list),
|
||||
)
|
||||
engineOpt.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
dest="verbose_mode",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']['verbose_mode'],
|
||||
help=messages("verbose_mode"),
|
||||
)
|
||||
engineOpt.add_argument(
|
||||
"--verbose-event",
|
||||
action="store_true",
|
||||
dest="verbose_event",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']['verbose_event'],
|
||||
help=messages("verbose_event"),
|
||||
)
|
||||
engineOpt.add_argument(
|
||||
"-V",
|
||||
"--version",
|
||||
action="store_true",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']['show_version'],
|
||||
dest="show_version",
|
||||
help=messages("software_version"),
|
||||
)
|
||||
engineOpt.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
action="store",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']['report_path_filename'],
|
||||
dest="report_path_filename",
|
||||
help=messages("save_logs"),
|
||||
)
|
||||
engineOpt.add_argument(
|
||||
"--graph",
|
||||
action="store",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["graph_name"],
|
||||
dest="graph_name",
|
||||
help=messages("available_graph").format(load_all_graphs()),
|
||||
)
|
||||
engineOpt.add_argument(
|
||||
"-h",
|
||||
"--help",
|
||||
action="store_true",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["show_help_menu"],
|
||||
dest="show_help_menu",
|
||||
help=messages("help_menu"),
|
||||
)
|
||||
|
||||
# Target Options
|
||||
target = parser.add_argument_group(
|
||||
messages("target"), messages("target_input")
|
||||
)
|
||||
target.add_argument(
|
||||
"-i",
|
||||
"--targets",
|
||||
action="store",
|
||||
dest="targets",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["targets"],
|
||||
help=messages("target_list"),
|
||||
)
|
||||
target.add_argument(
|
||||
"-l",
|
||||
"--targets-list",
|
||||
action="store",
|
||||
dest="targets_list",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["targets_list"],
|
||||
help=messages("read_target"),
|
||||
)
|
||||
|
||||
# Exclude Module Name
|
||||
exclude_modules = list(load_all_modules(limit=10).keys())
|
||||
exclude_modules.remove("all")
|
||||
|
||||
# Methods Options
|
||||
modules = parser.add_argument_group(
|
||||
messages("Method"), messages("scan_method_options")
|
||||
)
|
||||
modules.add_argument(
|
||||
"-m",
|
||||
"--modules",
|
||||
action="store",
|
||||
dest="selected_modules",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["selected_modules"],
|
||||
help=messages("choose_scan_method").format(list(load_all_modules(limit=10).keys())),
|
||||
)
|
||||
modules.add_argument(
|
||||
"--modules-extra-args",
|
||||
action="store",
|
||||
dest="modules_extra_args",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']['modules_extra_args'],
|
||||
help=messages("modules_extra_args_help")
|
||||
)
|
||||
modules.add_argument(
|
||||
"--show-all-modules",
|
||||
action="store_true",
|
||||
dest="show_all_modules",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["show_all_modules"],
|
||||
help=messages("show_all_modules"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"--profile",
|
||||
action="store",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["profiles"],
|
||||
dest="profiles",
|
||||
help=messages("select_profile").format(list(load_all_profiles(limit=10).keys())),
|
||||
)
|
||||
modules.add_argument(
|
||||
"--show-all-profiles",
|
||||
action="store_true",
|
||||
dest="show_all_profiles",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["show_all_profiles"],
|
||||
help=messages("show_all_profiles"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-x",
|
||||
"--exclude-modules",
|
||||
action="store",
|
||||
dest="excluded_modules",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["excluded_modules"],
|
||||
help=messages("exclude_scan_method").format(exclude_modules),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-u",
|
||||
"--usernames",
|
||||
action="store",
|
||||
dest="usernames",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["usernames"],
|
||||
help=messages("username_list"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-U",
|
||||
"--users-list",
|
||||
action="store",
|
||||
dest="usernames_list",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["usernames_list"],
|
||||
help=messages("username_from_file"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-p",
|
||||
"--passwords",
|
||||
action="store",
|
||||
dest="passwords",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["passwords"],
|
||||
help=messages("password_seperator"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-P",
|
||||
"--passwords-list",
|
||||
action="store",
|
||||
dest="passwords_list",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["passwords_list"],
|
||||
help=messages("read_passwords"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-g",
|
||||
"--ports",
|
||||
action="store",
|
||||
dest="ports",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["ports"],
|
||||
help=messages("port_seperator"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"--user-agent",
|
||||
action="store",
|
||||
dest="user_agent",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["user_agent"],
|
||||
help=messages("select_user_agent"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-T",
|
||||
"--timeout",
|
||||
action="store",
|
||||
dest="timeout",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["timeout"],
|
||||
type=float,
|
||||
help=messages("read_passwords"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-w",
|
||||
"--time-sleep-between-requests",
|
||||
action="store",
|
||||
dest="time_sleep_between_requests",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["time_sleep_between_requests"],
|
||||
type=float,
|
||||
help=messages("time_to_sleep"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-r",
|
||||
"--range",
|
||||
action="store_true",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["scan_ip_range"],
|
||||
dest="scan_ip_range",
|
||||
help=messages("range"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-s",
|
||||
"--sub-domains",
|
||||
action="store_true",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["scan_subdomains"],
|
||||
dest="scan_subdomains",
|
||||
help=messages("subdomains"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-t",
|
||||
"--thread-per-host",
|
||||
action="store",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["thread_per_host"],
|
||||
type=int,
|
||||
dest="thread_per_host",
|
||||
help=messages("thread_number_connections"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"-M",
|
||||
"--parallel-module-scan",
|
||||
action="store",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["parallel_module_scan"],
|
||||
type=int,
|
||||
dest="parallel_module_scan",
|
||||
help=messages("thread_number_modules"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"--set-hardware-usage",
|
||||
action="store",
|
||||
dest="set_hardware_usage",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']['set_hardware_usage'],
|
||||
help=messages("set_hardware_usage")
|
||||
)
|
||||
modules.add_argument(
|
||||
"-R",
|
||||
"--socks-proxy",
|
||||
action="store",
|
||||
dest="socks_proxy",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["socks_proxy"],
|
||||
help=messages("outgoing_proxy"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"--retries",
|
||||
action="store",
|
||||
dest="retries",
|
||||
type=int,
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["retries"],
|
||||
help=messages("connection_retries"),
|
||||
)
|
||||
modules.add_argument(
|
||||
"--ping-before-scan",
|
||||
action="store_true",
|
||||
dest="ping_before_scan",
|
||||
default=nettacker_global_configuration['nettacker_user_application_config']["ping_before_scan"],
|
||||
help=messages("ping_before_scan"),
|
||||
)
|
||||
# API Options
|
||||
api = parser.add_argument_group(
|
||||
messages("API"),
|
||||
messages("API_options")
|
||||
)
|
||||
api.add_argument(
|
||||
"--start-api",
|
||||
action="store_true",
|
||||
dest="start_api_server",
|
||||
default=nettacker_global_configuration['nettacker_api_config']["start_api_server"],
|
||||
help=messages("start_api_server")
|
||||
)
|
||||
api.add_argument(
|
||||
"--api-host",
|
||||
action="store",
|
||||
dest="api_hostname",
|
||||
default=nettacker_global_configuration['nettacker_api_config']["api_hostname"],
|
||||
help=messages("API_host")
|
||||
)
|
||||
api.add_argument(
|
||||
"--api-port",
|
||||
action="store",
|
||||
dest="api_port",
|
||||
default=nettacker_global_configuration['nettacker_api_config']["api_port"],
|
||||
help=messages("API_port")
|
||||
)
|
||||
api.add_argument(
|
||||
"--api-debug-mode",
|
||||
action="store_true",
|
||||
dest="api_debug_mode",
|
||||
default=nettacker_global_configuration['nettacker_api_config']["api_debug_mode"],
|
||||
help=messages("API_debug")
|
||||
)
|
||||
api.add_argument(
|
||||
"--api-access-key",
|
||||
action="store",
|
||||
dest="api_access_key",
|
||||
default=nettacker_global_configuration['nettacker_api_config']["api_access_key"],
|
||||
help=messages("API_access_key")
|
||||
)
|
||||
api.add_argument(
|
||||
"--api-client-whitelisted-ips",
|
||||
action="store",
|
||||
dest="api_client_whitelisted_ips",
|
||||
default=nettacker_global_configuration['nettacker_api_config']["api_client_whitelisted_ips"],
|
||||
help=messages("define_whie_list")
|
||||
)
|
||||
api.add_argument(
|
||||
"--api-access-log",
|
||||
action="store",
|
||||
dest="api_access_log",
|
||||
default=nettacker_global_configuration['nettacker_api_config']["api_access_log"],
|
||||
help=messages("API_access_log_file")
|
||||
)
|
||||
api.add_argument(
|
||||
"--api-cert",
|
||||
action="store",
|
||||
dest="api_cert",
|
||||
help=messages("API_cert")
|
||||
)
|
||||
api.add_argument(
|
||||
"--api-cert-key",
|
||||
action="store",
|
||||
dest="api_cert_key",
|
||||
help=messages("API_cert_key")
|
||||
)
|
||||
# Return Options
|
||||
return parser
|
||||
|
||||
|
||||
def check_all_required(parser, api_forms=None):
|
||||
"""
|
||||
check all rules and requirements for ARGS
|
||||
|
||||
Args:
|
||||
parser: parser from argparse
|
||||
api_forms: values from API
|
||||
|
||||
Returns:
|
||||
all ARGS with applied rules
|
||||
"""
|
||||
# Checking Requirements
|
||||
options = parser.parse_args() if not api_forms else api_forms
|
||||
modules_list = load_all_modules(full_details=True)
|
||||
profiles_list = load_all_profiles()
|
||||
|
||||
# Check Help Menu
|
||||
if options.show_help_menu:
|
||||
parser.print_help()
|
||||
write("\n\n")
|
||||
write(messages("license"))
|
||||
die_success()
|
||||
|
||||
# Check version
|
||||
if options.show_version:
|
||||
info(
|
||||
messages("current_version").format(
|
||||
color("yellow"),
|
||||
version_info()[0],
|
||||
color("reset"),
|
||||
color("cyan"),
|
||||
version_info()[1],
|
||||
color("reset"),
|
||||
color("green"),
|
||||
)
|
||||
)
|
||||
die_success()
|
||||
if options.show_all_modules:
|
||||
messages("loading_modules")
|
||||
for module in modules_list:
|
||||
info(
|
||||
messages("module_profile_full_information").format(
|
||||
color('cyan'),
|
||||
module,
|
||||
color('green'),
|
||||
", ".join(
|
||||
[
|
||||
"{key}: {value}".format(
|
||||
key=key, value=modules_list[module][key]
|
||||
) for key in modules_list[module]
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
die_success()
|
||||
if options.show_all_profiles:
|
||||
messages("loading_profiles")
|
||||
for profile in profiles_list:
|
||||
info(
|
||||
messages("module_profile_full_information").format(
|
||||
color('cyan'),
|
||||
profile,
|
||||
color('green'),
|
||||
", ".join(profiles_list[profile])
|
||||
)
|
||||
)
|
||||
die_success()
|
||||
# API mode
|
||||
if options.start_api_server:
|
||||
if '--start-api' in sys.argv and api_forms:
|
||||
die_failure(messages("cannot_run_api_server"))
|
||||
from api.engine import start_api_server
|
||||
if options.api_client_whitelisted_ips:
|
||||
if type(options.api_client_whitelisted_ips) == str:
|
||||
options.api_client_whitelisted_ips = options.api_client_whitelisted_ips.split(',')
|
||||
whielisted_ips = []
|
||||
for ip in options.api_client_whitelisted_ips:
|
||||
from core.ip import (is_single_ipv4,
|
||||
is_single_ipv6,
|
||||
is_ipv4_cidr,
|
||||
is_ipv6_range,
|
||||
is_ipv6_cidr,
|
||||
is_ipv4_range,
|
||||
generate_ip_range)
|
||||
if is_single_ipv4(ip) or is_single_ipv6(ip):
|
||||
whielisted_ips.append(ip)
|
||||
elif is_ipv4_range(ip) or is_ipv6_range(ip) or is_ipv4_cidr(ip) or is_ipv6_cidr(ip):
|
||||
whielisted_ips += generate_ip_range(ip)
|
||||
options.api_client_whitelisted_ips = whielisted_ips
|
||||
start_api_server(options)
|
||||
|
||||
# Check the target(s)
|
||||
if not (options.targets or options.targets_list) or (options.targets and options.targets_list):
|
||||
parser.print_help()
|
||||
write("\n")
|
||||
die_failure(messages("error_target"))
|
||||
if options.targets:
|
||||
options.targets = list(set(options.targets.split(",")))
|
||||
if options.targets_list:
|
||||
try:
|
||||
options.targets = list(set(open(options.targets_list, "rb").read().decode().split()))
|
||||
except Exception:
|
||||
die_failure(
|
||||
messages("error_target_file").format(
|
||||
options.targets_list
|
||||
)
|
||||
)
|
||||
|
||||
# check for modules
|
||||
if not (options.selected_modules or options.profiles):
|
||||
die_failure(messages("scan_method_select"))
|
||||
if options.selected_modules:
|
||||
if options.selected_modules == 'all':
|
||||
options.selected_modules = modules_list
|
||||
del options.selected_modules['all']
|
||||
else:
|
||||
options.selected_modules = list(set(options.selected_modules.split(',')))
|
||||
for module_name in options.selected_modules:
|
||||
if module_name not in modules_list:
|
||||
die_failure(
|
||||
messages("scan_module_not_found").format(
|
||||
module_name
|
||||
)
|
||||
)
|
||||
if options.profiles:
|
||||
if not options.selected_modules:
|
||||
options.selected_modules = []
|
||||
if options.profiles == 'all':
|
||||
options.selected_modules = modules_list
|
||||
del options.selected_modules['all']
|
||||
else:
|
||||
options.profiles = list(set(options.profiles.split(',')))
|
||||
for profile in options.profiles:
|
||||
if profile not in profiles_list:
|
||||
die_failure(
|
||||
messages("profile_404").format(
|
||||
profile
|
||||
)
|
||||
)
|
||||
for module_name in profiles_list[profile]:
|
||||
if module_name not in options.selected_modules:
|
||||
options.selected_modules.append(module_name)
|
||||
# threading & processing
|
||||
if options.set_hardware_usage not in ['low', 'normal', 'high', 'maximum']:
|
||||
die_failure(
|
||||
messages("wrong_hardware_usage")
|
||||
)
|
||||
options.set_hardware_usage = select_maximum_cpu_core(options.set_hardware_usage)
|
||||
|
||||
options.thread_per_host = int(options.thread_per_host)
|
||||
if not options.thread_per_host >= 1:
|
||||
options.thread_per_host = 1
|
||||
options.parallel_module_scan = int(options.parallel_module_scan)
|
||||
if not options.parallel_module_scan >= 1:
|
||||
options.parallel_module_scan = 1
|
||||
|
||||
# Check for excluding modules
|
||||
if options.excluded_modules:
|
||||
options.excluded_modules = options.excluded_modules.split(",")
|
||||
if 'all' in options.excluded_modules:
|
||||
die_failure(messages("error_exclude_all"))
|
||||
for excluded_module in options.excluded_modules:
|
||||
if excluded_module in options.selected_modules:
|
||||
del options.selected_modules[excluded_module]
|
||||
# Check port(s)
|
||||
if options.ports:
|
||||
tmp_ports = []
|
||||
for port in options.ports.split(","):
|
||||
try:
|
||||
if "-" in port:
|
||||
for port_number in range(int(port.split('-')[0]), int(port.split('-')[1]) + 1):
|
||||
if port_number not in tmp_ports:
|
||||
tmp_ports.append(port_number)
|
||||
else:
|
||||
if int(port) not in tmp_ports:
|
||||
tmp_ports.append(int(port))
|
||||
except Exception:
|
||||
die_failure(messages("ports_int"))
|
||||
options.ports = tmp_ports
|
||||
|
||||
if options.user_agent == 'random_user_agent':
|
||||
options.user_agents = open(
|
||||
nettacker_global_config()['nettacker_paths']['web_browser_user_agents']
|
||||
).read().split('\n')
|
||||
|
||||
# Check user list
|
||||
if options.usernames:
|
||||
options.usernames = list(set(options.usernames.split(",")))
|
||||
elif options.usernames_list:
|
||||
try:
|
||||
options.usernames = list(set(open(options.usernames_list).read().split("\n")))
|
||||
except Exception:
|
||||
die_failure(
|
||||
messages("error_username").format(options.usernames_list)
|
||||
)
|
||||
# Check password list
|
||||
if options.passwords:
|
||||
options.passwords = list(set(options.passwords.split(",")))
|
||||
elif options.passwords_list:
|
||||
try:
|
||||
options.passwords = list(set(open(options.passwords_list).read().split("\n")))
|
||||
except Exception:
|
||||
die_failure(
|
||||
messages("error_passwords").format(options.passwords_list)
|
||||
)
|
||||
# Check output file
|
||||
try:
|
||||
temp_file = open(options.report_path_filename, "w")
|
||||
temp_file.close()
|
||||
except Exception:
|
||||
die_failure(
|
||||
messages("file_write_error").format(options.report_path_filename)
|
||||
)
|
||||
# Check Graph
|
||||
if options.graph_name:
|
||||
if options.graph_name not in load_all_graphs():
|
||||
die_failure(
|
||||
messages("graph_module_404").format(options.graph_name)
|
||||
)
|
||||
if not (options.report_path_filename.endswith(".html") or options.report_path_filename.endswith(".htm")):
|
||||
warn(messages("graph_output"))
|
||||
options.graph_name = None
|
||||
# check modules extra args
|
||||
if options.modules_extra_args:
|
||||
all_args = {}
|
||||
for args in options.modules_extra_args.split("&"):
|
||||
all_args[args.split('=')[0]] = args.split('=')[1]
|
||||
options.modules_extra_args = all_args
|
||||
options.timeout = float(options.timeout)
|
||||
options.time_sleep_between_requests = float(options.time_sleep_between_requests)
|
||||
options.retries = int(options.retries)
|
||||
return options
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
|
||||
|
||||
def reset_color():
|
||||
"""
|
||||
reset the color of terminal before exit
|
||||
"""
|
||||
sys.stdout.write("\033[0m")
|
||||
|
||||
|
||||
def color(color_name):
|
||||
"""
|
||||
color_names for terminal and windows cmd
|
||||
|
||||
Args:
|
||||
color_name: color name
|
||||
|
||||
Returns:
|
||||
color_name values or empty string
|
||||
"""
|
||||
if color_name == "reset":
|
||||
return "\033[0m"
|
||||
elif color_name == "grey":
|
||||
return "\033[1;30m"
|
||||
elif color_name == "red":
|
||||
return "\033[1;31m"
|
||||
elif color_name == "green":
|
||||
return "\033[1;32m"
|
||||
elif color_name == "yellow":
|
||||
return "\033[1;33m"
|
||||
elif color_name == "blue":
|
||||
return "\033[1;34m"
|
||||
elif color_name == "purple":
|
||||
return "\033[1;35m"
|
||||
elif color_name == "cyan":
|
||||
return "\033[1;36m"
|
||||
elif color_name == "white":
|
||||
return "\033[1;37m"
|
||||
return ""
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from core.die import die_failure
|
||||
from core import color
|
||||
|
||||
|
||||
def version_info():
|
||||
"""
|
||||
version information of the framework
|
||||
|
||||
Returns:
|
||||
an array of version and code name
|
||||
"""
|
||||
from config import nettacker_paths
|
||||
return open(nettacker_paths()['version_file']).read().split()
|
||||
|
||||
|
||||
def logo():
|
||||
"""
|
||||
OWASP Nettacker Logo
|
||||
"""
|
||||
from core.alert import write_to_api_console
|
||||
from core import color
|
||||
from core.color import reset_color
|
||||
from config import nettacker_paths
|
||||
from config import nettacker_user_application_config
|
||||
write_to_api_console(
|
||||
open(
|
||||
nettacker_paths()['logo_file']
|
||||
).read().format(
|
||||
version_info()[0],
|
||||
version_info()[1],
|
||||
color.color('red'),
|
||||
color.color('reset'),
|
||||
color.color('yellow'),
|
||||
color.color('reset'),
|
||||
color.color('cyan'),
|
||||
color.color('reset'),
|
||||
color.color('cyan'),
|
||||
color.color('reset'),
|
||||
color.color('cyan'),
|
||||
color.color('reset')
|
||||
)
|
||||
)
|
||||
reset_color()
|
||||
|
||||
|
||||
def python_version():
|
||||
"""
|
||||
version of python
|
||||
|
||||
Returns:
|
||||
integer version of python (2 or 3)
|
||||
"""
|
||||
return int(sys.version_info[0])
|
||||
|
||||
|
||||
def os_name():
|
||||
"""
|
||||
OS name
|
||||
|
||||
Returns:
|
||||
OS name in string
|
||||
"""
|
||||
return sys.platform
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
if python_version() == 2:
|
||||
sys.exit(color.color("red") + "[X] " + color.color("yellow") + "Python2 is No longer supported!" + color.color(
|
||||
"reset"))
|
||||
|
||||
# check os compatibility
|
||||
from config import nettacker_paths, nettacker_database_config
|
||||
external_modules = open(nettacker_paths()["requirements_path"]).read().split('\n')
|
||||
for module_name in external_modules:
|
||||
try:
|
||||
__import__(
|
||||
module_name.split('==')[0] if 'library_name=' not in module_name
|
||||
else module_name.split('library_name=')[1].split()[0]
|
||||
)
|
||||
except Exception:
|
||||
if 'is_optional=true' not in module_name:
|
||||
sys.exit(
|
||||
color.color("red") + "[X] " + color.color("yellow") + "pip3 install -r requirements.txt ---> " +
|
||||
module_name.split('#')[0].strip() + " not installed!" + color.color("reset")
|
||||
)
|
||||
logo()
|
||||
|
||||
from core.alert import messages
|
||||
if not ('linux' in os_name() or 'darwin' in os_name()):
|
||||
die_failure(messages("error_platform"))
|
||||
|
||||
if not os.path.exists(nettacker_paths()["home_path"]):
|
||||
try:
|
||||
os.mkdir(nettacker_paths()["home_path"])
|
||||
os.mkdir(nettacker_paths()["tmp_path"])
|
||||
os.mkdir(nettacker_paths()["results_path"])
|
||||
except Exception:
|
||||
die_failure("cannot access the directory {0}".format(
|
||||
nettacker_paths()["home_path"])
|
||||
)
|
||||
if not os.path.exists(nettacker_paths()["tmp_path"]):
|
||||
try:
|
||||
os.mkdir(nettacker_paths()["tmp_path"])
|
||||
except Exception:
|
||||
die_failure("cannot access the directory {0}".format(
|
||||
nettacker_paths()["results_path"])
|
||||
)
|
||||
if not os.path.exists(nettacker_paths()["results_path"]):
|
||||
try:
|
||||
os.mkdir(nettacker_paths()["results_path"])
|
||||
except Exception:
|
||||
die_failure("cannot access the directory {0}".format(
|
||||
nettacker_paths()["results_path"])
|
||||
)
|
||||
|
||||
if nettacker_database_config()["DB"] == "sqlite":
|
||||
try:
|
||||
if not os.path.isfile(nettacker_paths()["database_path"]):
|
||||
from database.sqlite_create import sqlite_create_tables
|
||||
sqlite_create_tables()
|
||||
except Exception:
|
||||
die_failure("cannot access the directory {0}".format(
|
||||
nettacker_paths()["home_path"])
|
||||
)
|
||||
elif nettacker_database_config()["DB"] == "mysql":
|
||||
try:
|
||||
from database.mysql_create import (
|
||||
mysql_create_tables,
|
||||
mysql_create_database
|
||||
)
|
||||
mysql_create_database()
|
||||
mysql_create_tables()
|
||||
except Exception:
|
||||
die_failure(messages("database_connection_failed"))
|
||||
elif nettacker_database_config()["DB"] == "postgres":
|
||||
try:
|
||||
from database.postgres_create import postgres_create_database
|
||||
postgres_create_database()
|
||||
except Exception:
|
||||
die_failure(messages("database_connection_failed"))
|
||||
else:
|
||||
die_failure(messages("invalid_database"))
|
||||
190
core/graph.py
190
core/graph.py
|
|
@ -1,190 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import csv
|
||||
import texttable
|
||||
from core.alert import messages
|
||||
from core.alert import info
|
||||
from core.compatible import version_info
|
||||
from core.time import now
|
||||
from core.die import die_failure
|
||||
from database.db import get_logs_by_scan_unique_id
|
||||
from database.db import submit_report_to_db
|
||||
|
||||
|
||||
def build_graph(graph_name, events):
|
||||
"""
|
||||
build a graph
|
||||
|
||||
Args:
|
||||
graph_name: graph name
|
||||
events: list of events
|
||||
|
||||
Returns:
|
||||
graph in HTML type
|
||||
"""
|
||||
info(messages("build_graph"))
|
||||
try:
|
||||
start = getattr(
|
||||
__import__(
|
||||
'lib.graph.{0}.engine'.format(
|
||||
graph_name.rsplit('_graph')[0]
|
||||
),
|
||||
fromlist=['start']
|
||||
),
|
||||
'start'
|
||||
)
|
||||
except Exception:
|
||||
die_failure(
|
||||
messages("graph_module_unavailable").format(graph_name)
|
||||
)
|
||||
|
||||
info(messages("finish_build_graph"))
|
||||
return start(
|
||||
events
|
||||
)
|
||||
|
||||
|
||||
def build_texttable(events):
|
||||
"""
|
||||
value['date'], value["target"], value['module_name'], value['scan_unique_id'],
|
||||
value['options'], value['event']
|
||||
build a text table with generated event related to the scan
|
||||
|
||||
:param events: all events
|
||||
:return:
|
||||
array [text table, event_number]
|
||||
"""
|
||||
_table = texttable.Texttable()
|
||||
table_headers = [
|
||||
'date',
|
||||
'target',
|
||||
'module_name',
|
||||
'scan_unique_id',
|
||||
'port',
|
||||
'event',
|
||||
'json_event'
|
||||
|
||||
]
|
||||
_table.add_rows(
|
||||
[
|
||||
table_headers
|
||||
]
|
||||
)
|
||||
for event in events:
|
||||
_table.add_rows(
|
||||
[
|
||||
table_headers,
|
||||
[
|
||||
event['date'],
|
||||
event['target'],
|
||||
event['module_name'],
|
||||
event['scan_unique_id'],
|
||||
event['port'],
|
||||
event['event'],
|
||||
event['json_event']
|
||||
|
||||
]
|
||||
]
|
||||
)
|
||||
return _table.draw().encode('utf8') + b'\n\n' + messages("nettacker_version_details").format(
|
||||
version_info()[0],
|
||||
version_info()[1],
|
||||
now()
|
||||
).encode('utf8') + b"\n"
|
||||
|
||||
|
||||
def create_report(options, scan_unique_id):
|
||||
"""
|
||||
sort all events, create log file in HTML/TEXT/JSON and remove old logs
|
||||
|
||||
Args:
|
||||
options: parsing options
|
||||
scan_unique_id: scan unique id
|
||||
|
||||
Returns:
|
||||
True if success otherwise None
|
||||
"""
|
||||
all_scan_logs = get_logs_by_scan_unique_id(scan_unique_id)
|
||||
if not all_scan_logs:
|
||||
info(messages("no_events_for_report"))
|
||||
return True
|
||||
report_path_filename = options.report_path_filename
|
||||
if (
|
||||
len(report_path_filename) >= 5 and report_path_filename[-5:] == '.html'
|
||||
) or (
|
||||
len(report_path_filename) >= 4 and report_path_filename[-4:] == '.htm'
|
||||
):
|
||||
if options.graph_name:
|
||||
html_graph = build_graph(options.graph_name, all_scan_logs)
|
||||
else:
|
||||
html_graph = ''
|
||||
|
||||
from lib.html_log import log_data
|
||||
html_table_content = log_data.table_title.format(
|
||||
html_graph,
|
||||
log_data.css_1,
|
||||
'date',
|
||||
'target',
|
||||
'module_name',
|
||||
'scan_unique_id',
|
||||
'port',
|
||||
'event',
|
||||
'json_event'
|
||||
)
|
||||
for event in all_scan_logs:
|
||||
html_table_content += log_data.table_items.format(
|
||||
event["date"],
|
||||
event["target"],
|
||||
event["module_name"],
|
||||
event["scan_unique_id"],
|
||||
event["port"],
|
||||
event["event"],
|
||||
event["json_event"]
|
||||
)
|
||||
html_table_content += log_data.table_end + '<p class="footer">' + messages("nettacker_version_details").format(
|
||||
version_info()[0],
|
||||
version_info()[1],
|
||||
now()
|
||||
) + '</p>'
|
||||
with open(report_path_filename, 'w', encoding='utf-8') as save:
|
||||
save.write(html_table_content + '\n')
|
||||
save.close()
|
||||
elif len(report_path_filename) >= 5 and report_path_filename[-5:] == '.json':
|
||||
with open(report_path_filename, 'w', encoding='utf-8') as save:
|
||||
save.write(
|
||||
str(
|
||||
json.dumps(all_scan_logs)
|
||||
) + '\n'
|
||||
)
|
||||
save.close()
|
||||
elif len(report_path_filename) >= 5 and report_path_filename[-4:] == '.csv':
|
||||
keys = all_scan_logs[0].keys()
|
||||
with open(report_path_filename, 'a') as csvfile:
|
||||
writer = csv.DictWriter(csvfile, fieldnames=keys)
|
||||
writer.writeheader()
|
||||
for log in all_scan_logs:
|
||||
dict_data = {
|
||||
key: value for key, value in log.items() if key in keys
|
||||
}
|
||||
writer.writerow(dict_data)
|
||||
csvfile.close()
|
||||
|
||||
else:
|
||||
with open(report_path_filename, 'wb') as save:
|
||||
save.write(
|
||||
build_texttable(all_scan_logs)
|
||||
)
|
||||
save.close()
|
||||
|
||||
submit_report_to_db(
|
||||
{
|
||||
"date": now(model=None),
|
||||
"scan_unique_id": scan_unique_id,
|
||||
"options": vars(options),
|
||||
}
|
||||
)
|
||||
|
||||
info(messages("file_saved").format(report_path_filename))
|
||||
return True
|
||||
|
|
@ -1,296 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
from glob import glob
|
||||
from io import StringIO
|
||||
|
||||
|
||||
def getaddrinfo(*args):
|
||||
"""
|
||||
same getaddrinfo() used in socket except its resolve addresses with socks proxy
|
||||
|
||||
Args:
|
||||
args: *args
|
||||
|
||||
Returns:
|
||||
getaddrinfo
|
||||
"""
|
||||
return [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))]
|
||||
|
||||
|
||||
def set_socks_proxy(socks_proxy):
|
||||
if socks_proxy:
|
||||
import socks
|
||||
socks_version = socks.SOCKS5 if socks_proxy.startswith('socks5://') else socks.SOCKS4
|
||||
socks_proxy = socks_proxy.split('://')[1] if '://' in socks_proxy else socks_proxy
|
||||
if '@' in socks_proxy:
|
||||
socks_username = socks_proxy.split(':')[0]
|
||||
socks_password = socks_proxy.split(':')[1].split('@')[0]
|
||||
socks.set_default_proxy(
|
||||
socks_version,
|
||||
str(socks_proxy.rsplit('@')[1].rsplit(':')[0]), # hostname
|
||||
int(socks_proxy.rsplit(':')[-1]), # port
|
||||
username=socks_username,
|
||||
password=socks_password
|
||||
)
|
||||
else:
|
||||
socks.set_default_proxy(
|
||||
socks_version,
|
||||
str(socks_proxy.rsplit(':')[0]), # hostname
|
||||
int(socks_proxy.rsplit(':')[1]) # port
|
||||
)
|
||||
return socks.socksocket, getaddrinfo
|
||||
else:
|
||||
return socket.socket, socket.getaddrinfo
|
||||
|
||||
|
||||
class NettackerModules:
|
||||
def __init__(self):
|
||||
from config import nettacker_paths
|
||||
self.module_name = None
|
||||
self.module_content = None
|
||||
self.scan_unique_id = None
|
||||
self.target = None
|
||||
self.process_number = None
|
||||
self.module_thread_number = None
|
||||
self.total_module_thread_number = None
|
||||
self.module_inputs = {}
|
||||
self.libraries = [
|
||||
module_protocol.split('.py')[0] for module_protocol in
|
||||
os.listdir(nettacker_paths()['module_protocols_path']) if
|
||||
module_protocol.endswith('.py') and module_protocol != '__init__.py'
|
||||
]
|
||||
|
||||
def load(self):
|
||||
import yaml
|
||||
from config import nettacker_paths
|
||||
from core.utility import find_and_replace_configuration_keys
|
||||
self.module_content = find_and_replace_configuration_keys(
|
||||
yaml.load(
|
||||
StringIO(
|
||||
open(
|
||||
nettacker_paths()['modules_path'] +
|
||||
'/' +
|
||||
self.module_name.split('_')[-1].split('.yaml')[0] +
|
||||
'/' +
|
||||
'_'.join(self.module_name.split('_')[:-1]) +
|
||||
'.yaml',
|
||||
'r'
|
||||
).read().format(
|
||||
**self.module_inputs
|
||||
)
|
||||
),
|
||||
Loader=yaml.FullLoader
|
||||
),
|
||||
self.module_inputs
|
||||
)
|
||||
|
||||
def generate_loops(self):
|
||||
from core.utility import expand_module_steps
|
||||
self.module_content['payloads'] = expand_module_steps(self.module_content['payloads'])
|
||||
|
||||
def start(self):
|
||||
from terminable_thread import Thread
|
||||
from core.utility import wait_for_threads_to_finish
|
||||
active_threads = []
|
||||
from core.alert import warn
|
||||
from core.alert import verbose_event_info
|
||||
from core.alert import messages
|
||||
|
||||
# counting total number of requests
|
||||
total_number_of_requests = 0
|
||||
for payload in self.module_content['payloads']:
|
||||
if payload['library'] not in self.libraries:
|
||||
warn(messages("library_not_supported").format(payload['library']))
|
||||
return None
|
||||
for step in payload['steps']:
|
||||
for _ in step:
|
||||
total_number_of_requests += 1
|
||||
request_number_counter = 0
|
||||
for payload in self.module_content['payloads']:
|
||||
protocol = getattr(
|
||||
__import__(
|
||||
'core.module_protocols.{library}'.format(library=payload['library']),
|
||||
fromlist=['Engine']
|
||||
),
|
||||
'Engine'
|
||||
)
|
||||
for step in payload['steps']:
|
||||
for sub_step in step:
|
||||
thread = Thread(
|
||||
target=protocol.run,
|
||||
args=(
|
||||
sub_step,
|
||||
self.module_name,
|
||||
self.target,
|
||||
self.scan_unique_id,
|
||||
self.module_inputs,
|
||||
self.process_number,
|
||||
self.module_thread_number,
|
||||
self.total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
)
|
||||
)
|
||||
thread.name = f"{self.target} -> {self.module_name} -> {sub_step}"
|
||||
request_number_counter += 1
|
||||
verbose_event_info(
|
||||
messages("sending_module_request").format(
|
||||
self.process_number,
|
||||
self.module_name,
|
||||
self.target,
|
||||
self.module_thread_number,
|
||||
self.total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
)
|
||||
)
|
||||
thread.start()
|
||||
time.sleep(self.module_inputs['time_sleep_between_requests'])
|
||||
active_threads.append(thread)
|
||||
wait_for_threads_to_finish(
|
||||
active_threads,
|
||||
maximum=self.module_inputs['thread_per_host'],
|
||||
terminable=True
|
||||
)
|
||||
wait_for_threads_to_finish(
|
||||
active_threads,
|
||||
maximum=None,
|
||||
terminable=True
|
||||
)
|
||||
|
||||
|
||||
def load_all_graphs():
|
||||
"""
|
||||
load all available graphs
|
||||
|
||||
Returns:
|
||||
an array of graph names
|
||||
"""
|
||||
from config import nettacker_paths
|
||||
graph_names = []
|
||||
for graph_library in glob(os.path.join(nettacker_paths()['home_path'] + '/lib/graph/*/engine.py')):
|
||||
graph_names.append(graph_library.split('/')[-2] + '_graph')
|
||||
return list(set(graph_names))
|
||||
|
||||
|
||||
def load_all_languages():
|
||||
"""
|
||||
load all available languages
|
||||
|
||||
Returns:
|
||||
an array of languages
|
||||
"""
|
||||
languages_list = []
|
||||
from config import nettacker_paths
|
||||
for language in glob(os.path.join(nettacker_paths()['home_path'] + '/lib/messages/*.yaml')):
|
||||
languages_list.append(language.split('/')[-1].split('.')[0])
|
||||
return list(set(languages_list))
|
||||
|
||||
|
||||
def load_all_modules(limit=-1, full_details=False):
|
||||
"""
|
||||
load all available modules
|
||||
|
||||
limit: return limited number of modules
|
||||
full: with full details
|
||||
|
||||
Returns:
|
||||
an array of all module names
|
||||
"""
|
||||
# Search for Modules
|
||||
from config import nettacker_paths
|
||||
from core.utility import sort_dictonary
|
||||
if full_details:
|
||||
import yaml
|
||||
module_names = {}
|
||||
for module_name in glob(os.path.join(nettacker_paths()['modules_path'] + '/*/*.yaml')):
|
||||
libname = module_name.split('/')[-1].split('.')[0]
|
||||
category = module_name.split('/')[-2]
|
||||
module_names[libname + '_' + category] = yaml.load(
|
||||
StringIO(
|
||||
open(
|
||||
nettacker_paths()['modules_path'] +
|
||||
'/' +
|
||||
category +
|
||||
'/' +
|
||||
libname +
|
||||
'.yaml',
|
||||
'r'
|
||||
).read().split('payload:')[0]
|
||||
),
|
||||
Loader=yaml.FullLoader
|
||||
)['info'] if full_details else None
|
||||
if len(module_names) == limit:
|
||||
module_names['...'] = {}
|
||||
break
|
||||
module_names = sort_dictonary(module_names)
|
||||
module_names['all'] = {}
|
||||
|
||||
return module_names
|
||||
|
||||
|
||||
def load_all_profiles(limit=-1):
|
||||
"""
|
||||
load all available profiles
|
||||
|
||||
Returns:
|
||||
an array of all profile names
|
||||
"""
|
||||
from core.utility import sort_dictonary
|
||||
all_modules_with_details = load_all_modules(limit=limit, full_details=True)
|
||||
profiles = {}
|
||||
if '...' in all_modules_with_details:
|
||||
del all_modules_with_details['...']
|
||||
del all_modules_with_details['all']
|
||||
for key in all_modules_with_details:
|
||||
for tag in all_modules_with_details[key]['profiles']:
|
||||
if tag not in profiles:
|
||||
profiles[tag] = []
|
||||
profiles[tag].append(key)
|
||||
else:
|
||||
profiles[tag].append(key)
|
||||
if len(profiles) == limit:
|
||||
profiles = sort_dictonary(profiles)
|
||||
profiles['...'] = []
|
||||
profiles['all'] = []
|
||||
return profiles
|
||||
profiles = sort_dictonary(profiles)
|
||||
profiles['all'] = []
|
||||
return profiles
|
||||
|
||||
|
||||
def perform_scan(options, target, module_name, scan_unique_id, process_number, thread_number, total_number_threads):
|
||||
from core.alert import (verbose_event_info,
|
||||
messages)
|
||||
|
||||
socket.socket, socket.getaddrinfo = set_socks_proxy(options.socks_proxy)
|
||||
options.target = target
|
||||
validate_module = NettackerModules()
|
||||
validate_module.module_name = module_name
|
||||
validate_module.process_number = process_number
|
||||
validate_module.module_thread_number = thread_number
|
||||
validate_module.total_module_thread_number = total_number_threads
|
||||
validate_module.module_inputs = vars(options)
|
||||
if options.modules_extra_args:
|
||||
for module_extra_args in validate_module.module_inputs['modules_extra_args']:
|
||||
validate_module.module_inputs[module_extra_args] = \
|
||||
validate_module.module_inputs['modules_extra_args'][module_extra_args]
|
||||
validate_module.scan_unique_id = scan_unique_id
|
||||
validate_module.target = target
|
||||
validate_module.load()
|
||||
validate_module.generate_loops()
|
||||
validate_module.start()
|
||||
verbose_event_info(
|
||||
messages("finished_parallel_module_scan").format(
|
||||
process_number,
|
||||
module_name,
|
||||
target,
|
||||
thread_number,
|
||||
total_number_threads
|
||||
)
|
||||
)
|
||||
return os.EX_OK
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import yaml
|
||||
from io import StringIO
|
||||
|
||||
|
||||
def load_yaml(filename):
|
||||
return yaml.load(
|
||||
StringIO(
|
||||
open(filename, 'r').read()
|
||||
),
|
||||
Loader=yaml.FullLoader
|
||||
)
|
||||
|
||||
|
||||
class load_message:
|
||||
def __init__(self):
|
||||
from core.utility import application_language
|
||||
from config import nettacker_global_config
|
||||
self.language = application_language()
|
||||
self.messages = load_yaml(
|
||||
"{messages_path}/{language}.yaml".format(
|
||||
messages_path=nettacker_global_config()['nettacker_paths']['messages_path'],
|
||||
language=self.language
|
||||
)
|
||||
)
|
||||
if self.language != 'en':
|
||||
self.messages_en = load_yaml(
|
||||
"{messages_path}/en.yaml".format(
|
||||
messages_path=nettacker_global_config()['nettacker_paths']['messages_path']
|
||||
)
|
||||
)
|
||||
for message_id in self.messages_en:
|
||||
if message_id not in self.messages:
|
||||
self.messages[message_id] = self.messages_en[message_id]
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import copy
|
||||
import ftplib
|
||||
# from core.utility import reverse_and_regex_condition
|
||||
from core.utility import process_conditions
|
||||
from core.utility import get_dependent_results_from_database
|
||||
from core.utility import replace_dependent_values
|
||||
|
||||
|
||||
# def response_conditions_matched(sub_step, response):
|
||||
# return response
|
||||
|
||||
|
||||
class NettackFTPLib:
|
||||
def ftp_brute_force(host, ports, usernames, passwords, timeout):
|
||||
ftp_connection = ftplib.FTP(timeout=int(timeout))
|
||||
ftp_connection.connect(host, int(ports))
|
||||
ftp_connection.login(usernames, passwords)
|
||||
ftp_connection.close()
|
||||
return {
|
||||
"host": host,
|
||||
"username": usernames,
|
||||
"password": passwords,
|
||||
"port": ports
|
||||
}
|
||||
|
||||
def ftps_brute_force(host, ports, usernames, passwords, timeout):
|
||||
ftp_connection = ftplib.FTP_TLS(timeout=int(timeout))
|
||||
ftp_connection.connect(host, int(ports))
|
||||
ftp_connection.login(usernames, passwords)
|
||||
ftp_connection.close()
|
||||
return {
|
||||
"host": host,
|
||||
"username": usernames,
|
||||
"password": passwords,
|
||||
"port": ports
|
||||
}
|
||||
|
||||
|
||||
class Engine:
|
||||
def run(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
):
|
||||
backup_method = copy.deepcopy(sub_step['method'])
|
||||
backup_response = copy.deepcopy(sub_step['response'])
|
||||
del sub_step['method']
|
||||
del sub_step['response']
|
||||
if 'dependent_on_temp_event' in backup_response:
|
||||
temp_event = get_dependent_results_from_database(
|
||||
target,
|
||||
module_name,
|
||||
scan_unique_id,
|
||||
backup_response['dependent_on_temp_event']
|
||||
)
|
||||
sub_step = replace_dependent_values(
|
||||
sub_step,
|
||||
temp_event
|
||||
)
|
||||
action = getattr(NettackFTPLib, backup_method, None)
|
||||
for _ in range(options['retries']):
|
||||
try:
|
||||
response = action(**sub_step)
|
||||
break
|
||||
except Exception as _:
|
||||
response = []
|
||||
sub_step['method'] = backup_method
|
||||
sub_step['response'] = backup_response
|
||||
sub_step['response']['conditions_results'] = response
|
||||
# sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response)
|
||||
return process_conditions(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
)
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
import requests
|
||||
import copy
|
||||
import random
|
||||
from core.utility import reverse_and_regex_condition
|
||||
from core.utility import process_conditions
|
||||
from core.utility import get_dependent_results_from_database
|
||||
from core.utility import replace_dependent_values
|
||||
|
||||
|
||||
def response_conditions_matched(sub_step, response):
|
||||
if not response:
|
||||
return []
|
||||
condition_type = sub_step['response']['condition_type']
|
||||
conditions = sub_step['response']['conditions']
|
||||
condition_results = {}
|
||||
for condition in conditions:
|
||||
if condition in ['reason', 'status_code', 'content']:
|
||||
regex = re.findall(re.compile(conditions[condition]['regex']), response[condition])
|
||||
reverse = conditions[condition]['reverse']
|
||||
condition_results[condition] = reverse_and_regex_condition(regex, reverse)
|
||||
if condition == 'headers':
|
||||
# convert headers to case insensitive dict
|
||||
for key in response["headers"].copy():
|
||||
response['headers'][key.lower()] = response['headers'][key]
|
||||
condition_results['headers'] = {}
|
||||
for header in conditions['headers']:
|
||||
reverse = conditions['headers'][header]['reverse']
|
||||
regex = re.findall(
|
||||
re.compile(conditions['headers'][header]['regex']),
|
||||
response['headers'][header.lower()] if header.lower() in response['headers'] else ""
|
||||
)
|
||||
condition_results['headers'][header] = reverse_and_regex_condition(regex, reverse)
|
||||
if condition == 'responsetime':
|
||||
if len(conditions[condition].split()) == 2 and conditions[condition].split()[0] in [
|
||||
"==",
|
||||
"!=",
|
||||
">=",
|
||||
"<=",
|
||||
">",
|
||||
"<"
|
||||
]:
|
||||
exec(
|
||||
"condition_results['responsetime'] = response['responsetime'] if (" +
|
||||
"response['responsetime'] {0} float(conditions['responsetime'].split()[-1])".format(
|
||||
conditions['responsetime'].split()[0]
|
||||
) +
|
||||
") else []"
|
||||
|
||||
)
|
||||
else:
|
||||
condition_results['responsetime'] = []
|
||||
if condition_type.lower() == "or":
|
||||
# if one of the values are matched, it will be a string or float object in the array
|
||||
# we count False in the array and if it's not all []; then we know one of the conditions is matched.
|
||||
if (
|
||||
'headers' not in condition_results and
|
||||
(
|
||||
list(condition_results.values()).count([]) != len(list(condition_results.values()))
|
||||
)
|
||||
) or (
|
||||
'headers' in condition_results and
|
||||
(
|
||||
(
|
||||
list(condition_results.values()).count([]) - 1 !=
|
||||
len(list(condition_results.values()))
|
||||
) and
|
||||
(
|
||||
list(condition_results['headers'].values()).count([]) !=
|
||||
len(list(condition_results['headers'].values()))
|
||||
)
|
||||
)
|
||||
):
|
||||
return condition_results
|
||||
else:
|
||||
return []
|
||||
if condition_type.lower() == "and":
|
||||
if [] in condition_results.values() or \
|
||||
('headers' in condition_results and [] in condition_results['headers'].values()):
|
||||
return []
|
||||
else:
|
||||
return condition_results
|
||||
return []
|
||||
|
||||
|
||||
class Engine:
|
||||
def run(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
):
|
||||
backup_method = copy.deepcopy(sub_step['method'])
|
||||
backup_response = copy.deepcopy(sub_step['response'])
|
||||
action = getattr(requests, backup_method, None)
|
||||
if options['user_agent'] == 'random_user_agent':
|
||||
sub_step['headers']['User-Agent'] = random.choice(options['user_agents'])
|
||||
del sub_step['method']
|
||||
del sub_step['response']
|
||||
if 'dependent_on_temp_event' in backup_response:
|
||||
temp_event = get_dependent_results_from_database(
|
||||
target,
|
||||
module_name,
|
||||
scan_unique_id,
|
||||
backup_response['dependent_on_temp_event']
|
||||
)
|
||||
sub_step = replace_dependent_values(
|
||||
sub_step,
|
||||
temp_event
|
||||
)
|
||||
for _ in range(options['retries']):
|
||||
try:
|
||||
response = action(**sub_step)
|
||||
response = {
|
||||
"reason": response.reason,
|
||||
"status_code": str(response.status_code),
|
||||
"content": response.content.decode(errors="ignore"),
|
||||
"headers": dict(response.headers),
|
||||
"responsetime": response.elapsed.total_seconds()
|
||||
}
|
||||
break
|
||||
except Exception:
|
||||
response = []
|
||||
sub_step['method'] = backup_method
|
||||
sub_step['response'] = backup_response
|
||||
sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response)
|
||||
return process_conditions(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
)
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import copy
|
||||
import smtplib
|
||||
# from core.utility import reverse_and_regex_condition
|
||||
from core.utility import process_conditions
|
||||
from core.utility import get_dependent_results_from_database
|
||||
from core.utility import replace_dependent_values
|
||||
|
||||
|
||||
# def response_conditions_matched(sub_step, response):
|
||||
# return response
|
||||
|
||||
|
||||
class NettackSMTPLib:
|
||||
def smtp_brute_force(host, ports, usernames, passwords, timeout):
|
||||
smtp_connection = smtplib.SMTP(host, int(ports), timeout=int(timeout))
|
||||
smtp_connection.login(usernames, passwords)
|
||||
smtp_connection.close()
|
||||
return {
|
||||
"host": host,
|
||||
"username": usernames,
|
||||
"password": passwords,
|
||||
"port": ports
|
||||
}
|
||||
|
||||
def smtps_brute_force(host, ports, usernames, passwords, timeout):
|
||||
smtp_connection = smtplib.SMTP(host, int(ports), timeout=int(timeout))
|
||||
smtp_connection.starttls()
|
||||
smtp_connection.login(usernames, passwords)
|
||||
smtp_connection.close()
|
||||
return {
|
||||
"host": host,
|
||||
"username": usernames,
|
||||
"password": passwords,
|
||||
"port": ports
|
||||
}
|
||||
|
||||
|
||||
class Engine:
|
||||
def run(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
):
|
||||
backup_method = copy.deepcopy(sub_step['method'])
|
||||
backup_response = copy.deepcopy(sub_step['response'])
|
||||
del sub_step['method']
|
||||
del sub_step['response']
|
||||
if 'dependent_on_temp_event' in backup_response:
|
||||
temp_event = get_dependent_results_from_database(
|
||||
target,
|
||||
module_name,
|
||||
scan_unique_id,
|
||||
backup_response['dependent_on_temp_event']
|
||||
)
|
||||
sub_step = replace_dependent_values(
|
||||
sub_step,
|
||||
temp_event
|
||||
)
|
||||
action = getattr(NettackSMTPLib, backup_method, None)
|
||||
for _ in range(options['retries']):
|
||||
try:
|
||||
response = action(**sub_step)
|
||||
break
|
||||
except Exception as _:
|
||||
response = []
|
||||
sub_step['method'] = backup_method
|
||||
sub_step['response'] = backup_response
|
||||
sub_step['response']['conditions_results'] = response
|
||||
# sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response)
|
||||
return process_conditions(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
)
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import socket
|
||||
import copy
|
||||
import re
|
||||
import os
|
||||
import select
|
||||
import struct
|
||||
import time
|
||||
from core.utility import reverse_and_regex_condition
|
||||
from core.utility import process_conditions
|
||||
from core.utility import get_dependent_results_from_database
|
||||
from core.utility import replace_dependent_values
|
||||
|
||||
|
||||
def response_conditions_matched(sub_step, response):
|
||||
conditions = sub_step['response']['conditions']
|
||||
condition_type = sub_step['response']['condition_type']
|
||||
condition_results = {}
|
||||
if sub_step['method'] == 'tcp_connect_only':
|
||||
return response
|
||||
if sub_step['method'] == 'tcp_connect_send_and_receive':
|
||||
if response:
|
||||
received_content = response['response']
|
||||
for condition in conditions:
|
||||
regex = re.findall(re.compile(conditions[condition]['regex']), received_content)
|
||||
reverse = conditions[condition]['reverse']
|
||||
condition_results[condition] = reverse_and_regex_condition(regex, reverse)
|
||||
for condition in copy.deepcopy(condition_results):
|
||||
if not condition_results[condition]:
|
||||
del condition_results[condition]
|
||||
if 'open_port' in condition_results and len(condition_results) > 1:
|
||||
del condition_results['open_port']
|
||||
del conditions['open_port']
|
||||
if condition_type == 'and':
|
||||
return condition_results if len(condition_results) == len(conditions) else []
|
||||
if condition_type == 'or':
|
||||
return condition_results if condition_results else []
|
||||
return []
|
||||
if sub_step['method'] == 'socket_icmp':
|
||||
return response
|
||||
return []
|
||||
|
||||
|
||||
class NettackerSocket:
|
||||
def tcp_connect_only(host, ports, timeout):
|
||||
socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
socket_connection.settimeout(timeout)
|
||||
socket_connection.connect((host, int(ports)))
|
||||
peer_name = socket_connection.getpeername()
|
||||
socket_connection.close()
|
||||
return {
|
||||
"peer_name": peer_name,
|
||||
"service": socket.getservbyport(int(ports))
|
||||
}
|
||||
|
||||
def tcp_connect_send_and_receive(host, ports, timeout):
|
||||
socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
socket_connection.settimeout(timeout)
|
||||
socket_connection.connect((host, int(ports)))
|
||||
peer_name = socket_connection.getpeername()
|
||||
try:
|
||||
socket_connection.send(b"ABC\x00\r\n" * 10)
|
||||
response = socket_connection.recv(1024 * 1024 * 10)
|
||||
socket_connection.close()
|
||||
except Exception:
|
||||
try:
|
||||
socket_connection.close()
|
||||
response = b""
|
||||
except Exception:
|
||||
response = b""
|
||||
return {
|
||||
"peer_name": peer_name,
|
||||
"service": socket.getservbyport(int(ports)),
|
||||
"response": response.decode(errors='ignore')
|
||||
}
|
||||
|
||||
def socket_icmp(host, timeout):
|
||||
"""
|
||||
A pure python ping implementation using raw socket.
|
||||
Note that ICMP messages can only be sent from processes running as root.
|
||||
Derived from ping.c distributed in Linux's netkit. That code is
|
||||
copyright (c) 1989 by The Regents of the University of California.
|
||||
That code is in turn derived from code written by Mike Muuss of the
|
||||
US Army Ballistic Research Laboratory in December, 1983 and
|
||||
placed in the public domain. They have my thanks.
|
||||
Bugs are naturally mine. I'd be glad to hear about them. There are
|
||||
certainly word - size dependenceies here.
|
||||
Copyright (c) Matthew Dixon Cowles, <http://www.visi.com/~mdc/>.
|
||||
Distributable under the terms of the GNU General Public License
|
||||
version 2. Provided with no warranties of any sort.
|
||||
Original Version from Matthew Dixon Cowles:
|
||||
-> ftp://ftp.visi.com/users/mdc/ping.py
|
||||
Rewrite by Jens Diemer:
|
||||
-> http://www.python-forum.de/post-69122.html#69122
|
||||
Rewrite by George Notaras:
|
||||
-> http://www.g-loaded.eu/2009/10/30/python-ping/
|
||||
Fork by Pierre Bourdon:
|
||||
-> http://bitbucket.org/delroth/python-ping/
|
||||
Revision history
|
||||
~~~~~~~~~~~~~~~~
|
||||
November 22, 1997
|
||||
-----------------
|
||||
Initial hack. Doesn't do much, but rather than try to guess
|
||||
what features I (or others) will want in the future, I've only
|
||||
put in what I need now.
|
||||
December 16, 1997
|
||||
-----------------
|
||||
For some reason, the checksum bytes are in the wrong order when
|
||||
this is run under Solaris 2.X for SPARC but it works right under
|
||||
Linux x86. Since I don't know just what's wrong, I'll swap the
|
||||
bytes always and then do an htons().
|
||||
December 4, 2000
|
||||
----------------
|
||||
Changed the struct.pack() calls to pack the checksum and ID as
|
||||
unsigned. My thanks to Jerome Poincheval for the fix.
|
||||
May 30, 2007
|
||||
------------
|
||||
little rewrite by Jens Diemer:
|
||||
- change socket asterisk import to a normal import
|
||||
- replace time.time() with time.clock()
|
||||
- delete "return None" (or change to "return" only)
|
||||
- in checksum() rename "str" to "source_string"
|
||||
November 8, 2009
|
||||
----------------
|
||||
Improved compatibility with GNU/Linux systems.
|
||||
Fixes by:
|
||||
* George Notaras -- http://www.g-loaded.eu
|
||||
Reported by:
|
||||
* Chris Hallman -- http://cdhallman.blogspot.com
|
||||
Changes in this release:
|
||||
- Re-use time.time() instead of time.clock(). The 2007 implementation
|
||||
worked only under Microsoft Windows. Failed on GNU/Linux.
|
||||
time.clock() behaves differently under the two OSes[1].
|
||||
[1] http://docs.python.org/library/time.html#time.clock
|
||||
September 25, 2010
|
||||
------------------
|
||||
Little modifications by Georgi Kolev:
|
||||
- Added quiet_ping function.
|
||||
- returns percent lost packages, max round trip time, avrg round trip
|
||||
time
|
||||
- Added packet size to verbose_ping & quiet_ping functions.
|
||||
- Bump up version to 0.2
|
||||
------------------
|
||||
5 Aug 2021 - Modified by Ali Razmjoo Qalaei (Reformat the code and more human readable)
|
||||
"""
|
||||
icmp_socket = socket.getprotobyname("icmp")
|
||||
socket_connection = socket.socket(
|
||||
socket.AF_INET,
|
||||
socket.SOCK_RAW,
|
||||
icmp_socket
|
||||
)
|
||||
random_integer = os.getpid() & 0xFFFF
|
||||
icmp_echo_request = 8
|
||||
# Make a dummy header with a 0 checksum.
|
||||
dummy_checksum = 0
|
||||
header = struct.pack("bbHHh", icmp_echo_request, 0, dummy_checksum, random_integer, 1)
|
||||
data = struct.pack("d", time.time()) + struct.pack("d", time.time()) + str(
|
||||
(76 - struct.calcsize("d")) * "Q"
|
||||
).encode() # packet size = 76 (removed 8 bytes size of header)
|
||||
source_string = header + data
|
||||
# Calculate the checksum on the data and the dummy header.
|
||||
calculate_data = 0
|
||||
max_size = (len(source_string) / 2) * 2
|
||||
counter = 0
|
||||
while counter < max_size:
|
||||
calculate_data += source_string[counter + 1] * 256 + source_string[counter]
|
||||
calculate_data = calculate_data & 0xffffffff # Necessary?
|
||||
counter += 2
|
||||
|
||||
if max_size < len(source_string):
|
||||
calculate_data += source_string[len(source_string) - 1]
|
||||
calculate_data = calculate_data & 0xffffffff # Necessary?
|
||||
|
||||
calculate_data = (calculate_data >> 16) + (calculate_data & 0xffff)
|
||||
calculate_data = calculate_data + (calculate_data >> 16)
|
||||
calculated_data = ~calculate_data & 0xffff
|
||||
|
||||
# Swap bytes. Bugger me if I know why.
|
||||
dummy_checksum = calculated_data >> 8 | (calculated_data << 8 & 0xff00)
|
||||
|
||||
header = struct.pack(
|
||||
"bbHHh", icmp_echo_request, 0, socket.htons(dummy_checksum), random_integer, 1
|
||||
)
|
||||
socket_connection.sendto(header + data, (socket.gethostbyname(host), 1)) # Don't know about the 1
|
||||
|
||||
while True:
|
||||
started_select = time.time()
|
||||
what_ready = select.select([socket_connection], [], [], timeout)
|
||||
how_long_in_select = (time.time() - started_select)
|
||||
if not what_ready[0]: # Timeout
|
||||
break
|
||||
time_received = time.time()
|
||||
received_packet, address = socket_connection.recvfrom(1024)
|
||||
icmp_header = received_packet[20:28]
|
||||
packet_type, packet_code, packet_checksum, packet_id, packet_sequence = struct.unpack(
|
||||
"bbHHh", icmp_header
|
||||
)
|
||||
if packet_id == random_integer:
|
||||
packet_bytes = struct.calcsize("d")
|
||||
time_sent = struct.unpack("d", received_packet[28:28 + packet_bytes])[0]
|
||||
delay = time_received - time_sent
|
||||
break
|
||||
|
||||
timeout = timeout - how_long_in_select
|
||||
if timeout <= 0:
|
||||
break
|
||||
socket_connection.close()
|
||||
return {
|
||||
"host": host,
|
||||
"response_time": delay
|
||||
}
|
||||
|
||||
|
||||
class Engine:
|
||||
def run(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
):
|
||||
backup_method = copy.deepcopy(sub_step['method'])
|
||||
backup_response = copy.deepcopy(sub_step['response'])
|
||||
del sub_step['method']
|
||||
del sub_step['response']
|
||||
if 'dependent_on_temp_event' in backup_response:
|
||||
temp_event = get_dependent_results_from_database(
|
||||
target,
|
||||
module_name,
|
||||
scan_unique_id,
|
||||
backup_response['dependent_on_temp_event']
|
||||
)
|
||||
sub_step = replace_dependent_values(
|
||||
sub_step,
|
||||
temp_event
|
||||
)
|
||||
action = getattr(NettackerSocket, backup_method, None)
|
||||
for _ in range(options['retries']):
|
||||
try:
|
||||
response = action(**sub_step)
|
||||
break
|
||||
except Exception:
|
||||
response = []
|
||||
sub_step['method'] = backup_method
|
||||
sub_step['response'] = backup_response
|
||||
sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response)
|
||||
return process_conditions(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
)
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import copy
|
||||
import paramiko
|
||||
import logging
|
||||
# from core.utility import reverse_and_regex_condition
|
||||
from core.utility import process_conditions
|
||||
from core.utility import get_dependent_results_from_database
|
||||
from core.utility import replace_dependent_values
|
||||
|
||||
|
||||
# def response_conditions_matched(sub_step, response):
|
||||
# return response
|
||||
|
||||
|
||||
class NettackSSHLib:
|
||||
def ssh_brute_force(host, ports, usernames, passwords, timeout):
|
||||
paramiko_logger = logging.getLogger("paramiko.transport")
|
||||
paramiko_logger.disabled = True
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect(
|
||||
hostname=host,
|
||||
username=usernames,
|
||||
password=passwords,
|
||||
port=int(ports),
|
||||
timeout=int(timeout)
|
||||
)
|
||||
ssh.close()
|
||||
return {
|
||||
"host": host,
|
||||
"username": usernames,
|
||||
"password": passwords,
|
||||
"port": ports
|
||||
}
|
||||
|
||||
|
||||
class Engine:
|
||||
def run(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
):
|
||||
backup_method = copy.deepcopy(sub_step['method'])
|
||||
backup_response = copy.deepcopy(sub_step['response'])
|
||||
del sub_step['method']
|
||||
del sub_step['response']
|
||||
if 'dependent_on_temp_event' in backup_response:
|
||||
temp_event = get_dependent_results_from_database(
|
||||
target,
|
||||
module_name,
|
||||
scan_unique_id,
|
||||
backup_response['dependent_on_temp_event']
|
||||
)
|
||||
sub_step = replace_dependent_values(
|
||||
sub_step,
|
||||
temp_event
|
||||
)
|
||||
action = getattr(NettackSSHLib, backup_method, None)
|
||||
for _ in range(options['retries']):
|
||||
try:
|
||||
response = action(**sub_step)
|
||||
break
|
||||
except Exception:
|
||||
response = []
|
||||
sub_step['method'] = backup_method
|
||||
sub_step['response'] = backup_response
|
||||
sub_step['response']['conditions_results'] = response
|
||||
# sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response)
|
||||
return process_conditions(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
)
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import copy
|
||||
import telnetlib
|
||||
# from core.utility import reverse_and_regex_condition
|
||||
from core.utility import process_conditions
|
||||
from core.utility import get_dependent_results_from_database
|
||||
from core.utility import replace_dependent_values
|
||||
|
||||
|
||||
# def response_conditions_matched(sub_step, response):
|
||||
# return response
|
||||
|
||||
|
||||
class NettackTelnetLib:
|
||||
def telnet_brute_force(host, ports, usernames, passwords, timeout):
|
||||
telnet_connection = telnetlib.Telnet(host, port=int(ports), timeout=int(timeout))
|
||||
telnet_connection.read_until(b"login: ")
|
||||
telnet_connection.write(usernames + "\n")
|
||||
telnet_connection.read_until(b"Password: ")
|
||||
telnet_connection.write(passwords + "\n")
|
||||
telnet_connection.close()
|
||||
return {
|
||||
"host": host,
|
||||
"username": usernames,
|
||||
"password": passwords,
|
||||
"port": ports
|
||||
}
|
||||
|
||||
|
||||
class Engine:
|
||||
def run(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
):
|
||||
backup_method = copy.deepcopy(sub_step['method'])
|
||||
backup_response = copy.deepcopy(sub_step['response'])
|
||||
del sub_step['method']
|
||||
del sub_step['response']
|
||||
if 'dependent_on_temp_event' in backup_response:
|
||||
temp_event = get_dependent_results_from_database(
|
||||
target,
|
||||
module_name,
|
||||
scan_unique_id,
|
||||
backup_response['dependent_on_temp_event']
|
||||
)
|
||||
sub_step = replace_dependent_values(
|
||||
sub_step,
|
||||
temp_event
|
||||
)
|
||||
action = getattr(NettackTelnetLib, backup_method, None)
|
||||
for _ in range(options['retries']):
|
||||
try:
|
||||
response = action(**sub_step)
|
||||
break
|
||||
except Exception as _:
|
||||
response = []
|
||||
sub_step['method'] = backup_method
|
||||
sub_step['response'] = backup_response
|
||||
sub_step['response']['conditions_results'] = response
|
||||
# sub_step['response']['conditions_results'] = response_conditions_matched(sub_step, response)
|
||||
return process_conditions(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
)
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from core.scan_targers import start_scan_processes
|
||||
from core.alert import info
|
||||
from core.alert import write
|
||||
from core.alert import messages
|
||||
from core.load_modules import load_all_modules
|
||||
from core.args_loader import load_all_args
|
||||
from core.args_loader import check_all_required
|
||||
|
||||
|
||||
def load():
|
||||
"""
|
||||
load all ARGS, Apply rules and go for attacks
|
||||
|
||||
Returns:
|
||||
True if success otherwise None
|
||||
"""
|
||||
write("\n\n")
|
||||
options = check_all_required(load_all_args())
|
||||
|
||||
info(messages("scan_started"))
|
||||
info(messages("loaded_modules").format(len(load_all_modules())))
|
||||
exit_code = start_scan_processes(options)
|
||||
info(messages("done"))
|
||||
return exit_code
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
OWASP Nettacker core functions
|
||||
==============================
|
||||
|
||||
OWASP Nettacker core functions are stored in here.
|
||||
|
||||
* `die.py` exit functions
|
||||
* `time.py` time functions
|
||||
* `alert.py` user alerts and printing functions
|
||||
* `args_loader.py` ARGV commands and apply rules
|
||||
* `attack.py` start new attacks and multi-processing managements
|
||||
* `color.py` color founds for windows and linux/mac.
|
||||
* `compatible.py` compatibility functions
|
||||
* `config.py` user configs (could be modify by user)
|
||||
* `config_builder.py` core static configs (same as user configs but should not be change by users)
|
||||
* `get_input.py` get inputs from users functions
|
||||
* `ip.py` IPv4 and IPv6 functions
|
||||
* `load_modules` load modules, requirements, paths functions
|
||||
* `log.py` log the scans and generate reports
|
||||
* `parse.py` parse the ARGV and pass it
|
||||
* `targets.py` process, calculate and count targets
|
||||
* `update.py` updates functions of the framework
|
||||
* `wizard.py` wizard mode for the framework
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import numpy
|
||||
import multiprocessing
|
||||
from core.alert import (info,
|
||||
verbose_event_info,
|
||||
messages)
|
||||
from core.targets import expand_targets
|
||||
from core.utility import generate_random_token
|
||||
from core.load_modules import perform_scan
|
||||
from terminable_thread import Thread
|
||||
from core.utility import wait_for_threads_to_finish
|
||||
from core.graph import create_report
|
||||
|
||||
|
||||
def parallel_scan_process(options, targets, scan_unique_id, process_number):
|
||||
active_threads = []
|
||||
verbose_event_info(messages("single_process_started").format(process_number))
|
||||
total_number_of_modules = len(targets) * len(options.selected_modules)
|
||||
total_number_of_modules_counter = 1
|
||||
for target in targets:
|
||||
for module_name in options.selected_modules:
|
||||
thread = Thread(
|
||||
target=perform_scan,
|
||||
args=(
|
||||
options,
|
||||
target,
|
||||
module_name,
|
||||
scan_unique_id,
|
||||
process_number,
|
||||
total_number_of_modules_counter,
|
||||
total_number_of_modules
|
||||
)
|
||||
)
|
||||
thread.name = f"{target} -> {module_name}"
|
||||
thread.start()
|
||||
verbose_event_info(
|
||||
messages("start_parallel_module_scan").format(
|
||||
process_number,
|
||||
module_name,
|
||||
target,
|
||||
total_number_of_modules_counter,
|
||||
total_number_of_modules
|
||||
)
|
||||
)
|
||||
total_number_of_modules_counter += 1
|
||||
active_threads.append(thread)
|
||||
if not wait_for_threads_to_finish(active_threads, options.parallel_module_scan, True):
|
||||
return False
|
||||
wait_for_threads_to_finish(active_threads, maximum=None, terminable=True)
|
||||
return True
|
||||
|
||||
|
||||
def start_scan_processes(options):
|
||||
"""
|
||||
preparing for attacks and managing multi-processing for host
|
||||
|
||||
Args:
|
||||
options: all options
|
||||
|
||||
Returns:
|
||||
True when it ends
|
||||
"""
|
||||
scan_unique_id = generate_random_token(32)
|
||||
# find total number of targets + types + expand (subdomain, IPRanges, etc)
|
||||
# optimize CPU usage
|
||||
info(messages("regrouping_targets"))
|
||||
options.targets = [
|
||||
targets.tolist() for targets in numpy.array_split(
|
||||
expand_targets(options, scan_unique_id),
|
||||
options.set_hardware_usage if options.set_hardware_usage >= len(options.targets) else len(options.targets)
|
||||
)
|
||||
]
|
||||
info(messages("removing_old_db_records"))
|
||||
from database.db import remove_old_logs
|
||||
for target_group in options.targets:
|
||||
for target in target_group:
|
||||
for module_name in options.selected_modules:
|
||||
remove_old_logs(
|
||||
{
|
||||
"target": target,
|
||||
"module_name": module_name,
|
||||
"scan_unique_id": scan_unique_id,
|
||||
}
|
||||
)
|
||||
for _ in range(options.targets.count([])):
|
||||
options.targets.remove([])
|
||||
active_processes = []
|
||||
info(messages("start_multi_process").format(len(options.targets)))
|
||||
process_number = 0
|
||||
for targets in options.targets:
|
||||
process_number += 1
|
||||
process = multiprocessing.Process(
|
||||
target=parallel_scan_process,
|
||||
args=(options, targets, scan_unique_id, process_number,)
|
||||
)
|
||||
process.start()
|
||||
active_processes.append(process)
|
||||
exit_code = wait_for_threads_to_finish(active_processes, sub_process=True)
|
||||
create_report(options, scan_unique_id)
|
||||
return exit_code
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import copy
|
||||
import json
|
||||
from core.ip import (get_ip_range,
|
||||
generate_ip_range,
|
||||
is_single_ipv4,
|
||||
is_ipv4_range,
|
||||
is_ipv4_cidr,
|
||||
is_single_ipv6,
|
||||
is_ipv6_range,
|
||||
is_ipv6_cidr)
|
||||
from database.db import find_events
|
||||
|
||||
|
||||
def expand_targets(options, scan_unique_id):
|
||||
"""
|
||||
analysis and calulcate targets.
|
||||
|
||||
Args:
|
||||
options: all options
|
||||
scan_unique_id: unique scan identifier
|
||||
|
||||
Returns:
|
||||
a generator
|
||||
"""
|
||||
from core.load_modules import perform_scan
|
||||
targets = []
|
||||
for target in options.targets:
|
||||
if '://' in target:
|
||||
# remove url proto; uri; port
|
||||
target = target.split('://')[1].split('/')[0].split(':')[0]
|
||||
targets.append(target)
|
||||
# single IPs
|
||||
elif is_single_ipv4(target) or is_single_ipv6(target):
|
||||
if options.scan_ip_range:
|
||||
targets += get_ip_range(target)
|
||||
else:
|
||||
targets.append(target)
|
||||
# IP ranges
|
||||
elif is_ipv4_range(target) or is_ipv6_range(target) or is_ipv4_cidr(target) or is_ipv6_cidr(target):
|
||||
targets += generate_ip_range(target)
|
||||
# domains
|
||||
elif options.scan_subdomains:
|
||||
targets.append(target)
|
||||
perform_scan(
|
||||
options,
|
||||
target,
|
||||
'subdomain_scan',
|
||||
scan_unique_id,
|
||||
'pre_process',
|
||||
'pre_process_thread',
|
||||
'unknown'
|
||||
)
|
||||
for row in find_events(target, 'subdomain_scan', scan_unique_id):
|
||||
for sub_domain in json.loads(row.json_event)['response']['conditions_results']['content']:
|
||||
if sub_domain not in targets:
|
||||
targets.append(sub_domain)
|
||||
else:
|
||||
targets.append(target)
|
||||
if options.ping_before_scan:
|
||||
for target in copy.deepcopy(targets):
|
||||
perform_scan(
|
||||
options,
|
||||
target,
|
||||
'icmp_scan',
|
||||
scan_unique_id,
|
||||
'pre_process',
|
||||
'pre_process_thread',
|
||||
'unknown'
|
||||
)
|
||||
if not find_events(target, 'icmp_scan', scan_unique_id):
|
||||
targets.remove(target)
|
||||
return list(set(targets))
|
||||
16
core/time.py
16
core/time.py
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
def now(model="%Y-%m-%d %H:%M:%S"):
|
||||
"""
|
||||
get now date and time
|
||||
Args:
|
||||
model: the date and time model, default is "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
Returns:
|
||||
the date and time of now
|
||||
"""
|
||||
return datetime.datetime.now().strftime(model) if model else datetime.datetime.now()
|
||||
498
core/utility.py
498
core/utility.py
|
|
@ -1,498 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import copy
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
import ctypes
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
import multiprocessing
|
||||
import yaml
|
||||
import hashlib
|
||||
from core.load_modules import load_all_languages
|
||||
from core.time import now
|
||||
from core.color import color
|
||||
|
||||
|
||||
def process_conditions(
|
||||
event,
|
||||
module_name,
|
||||
target,
|
||||
scan_unique_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
):
|
||||
from core.alert import (success_event_info,
|
||||
verbose_info,
|
||||
messages)
|
||||
|
||||
if 'save_to_temp_events_only' in event.get('response', ''):
|
||||
from database.db import submit_temp_logs_to_db
|
||||
submit_temp_logs_to_db(
|
||||
{
|
||||
"date": now(model=None),
|
||||
"target": target,
|
||||
"module_name": module_name,
|
||||
"scan_unique_id": scan_unique_id,
|
||||
"event_name": event['response']['save_to_temp_events_only'],
|
||||
"port": event.get('ports', ''),
|
||||
"event": event,
|
||||
"data": response
|
||||
}
|
||||
)
|
||||
if event['response']['conditions_results'] and 'save_to_temp_events_only' not in event.get('response', ''):
|
||||
from database.db import submit_logs_to_db
|
||||
|
||||
# remove sensitive information before submitting to db
|
||||
from config import nettacker_api_config
|
||||
options = copy.deepcopy(options)
|
||||
for key in nettacker_api_config():
|
||||
try:
|
||||
del options[key]
|
||||
except Exception:
|
||||
continue
|
||||
del event['response']['conditions']
|
||||
del event['response']['condition_type']
|
||||
event_request_keys = copy.deepcopy(event)
|
||||
del event_request_keys['response']
|
||||
submit_logs_to_db(
|
||||
{
|
||||
"date": now(model=None),
|
||||
"target": target,
|
||||
"module_name": module_name,
|
||||
"scan_unique_id": scan_unique_id,
|
||||
"port": event.get('ports') or event.get('port') or (
|
||||
event.get('url').split(':')[2].split('/')[0] if
|
||||
type(event.get('url')) == str and len(event.get('url').split(':')) >= 3 and
|
||||
event.get('url').split(':')[2].split('/')[0].isdigit() else ""
|
||||
),
|
||||
"event": " ".join(
|
||||
yaml.dump(event_request_keys).split()
|
||||
) + "conditions: " + " ".join(
|
||||
yaml.dump(event['response']['conditions_results']).split()
|
||||
),
|
||||
"json_event": event
|
||||
}
|
||||
)
|
||||
success_event_info(
|
||||
messages("send_success_event_from_module").format(
|
||||
process_number,
|
||||
module_name,
|
||||
target,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests,
|
||||
" ".join(
|
||||
[
|
||||
color('yellow') + key + color('reset') if ':' in key
|
||||
else color('green') + key + color('reset')
|
||||
for key in yaml.dump(event_request_keys).split()
|
||||
]
|
||||
),
|
||||
filter_large_content(
|
||||
"conditions: " + " ".join(
|
||||
[
|
||||
color('purple') + key + color('reset') if ':' in key
|
||||
else color('green') + key + color('reset')
|
||||
for key in yaml.dump(event['response']['conditions_results']).split()
|
||||
]
|
||||
),
|
||||
filter_rate=150
|
||||
)
|
||||
)
|
||||
)
|
||||
verbose_info(
|
||||
json.dumps(event)
|
||||
)
|
||||
return True
|
||||
else:
|
||||
del event['response']['conditions']
|
||||
verbose_info(
|
||||
messages("send_unsuccess_event_from_module").format(
|
||||
process_number,
|
||||
module_name,
|
||||
target,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests
|
||||
)
|
||||
)
|
||||
verbose_info(
|
||||
json.dumps(event)
|
||||
)
|
||||
return 'save_to_temp_events_only' in event['response']
|
||||
|
||||
|
||||
def filter_large_content(content, filter_rate=150):
|
||||
from core.alert import messages
|
||||
if len(content) <= filter_rate:
|
||||
return content
|
||||
else:
|
||||
filter_rate -= 1
|
||||
filter_index = filter_rate
|
||||
for char in content[filter_rate:]:
|
||||
if char == ' ':
|
||||
return content[0:filter_index] + messages('filtered_content')
|
||||
else:
|
||||
filter_index += 1
|
||||
return content
|
||||
|
||||
|
||||
def get_dependent_results_from_database(target, module_name, scan_unique_id, event_name):
|
||||
from database.db import find_temp_events
|
||||
while True:
|
||||
event = find_temp_events(target, module_name, scan_unique_id, event_name)
|
||||
if event:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
return json.loads(event.event)['response']['conditions_results']
|
||||
|
||||
|
||||
def find_and_replace_dependent_values(sub_step, dependent_on_temp_event):
|
||||
if type(sub_step) == dict:
|
||||
for key in copy.deepcopy(sub_step):
|
||||
if type(sub_step[key]) not in [str, float, int, bytes]:
|
||||
sub_step[key] = find_and_replace_dependent_values(
|
||||
copy.deepcopy(sub_step[key]), dependent_on_temp_event
|
||||
)
|
||||
else:
|
||||
if type(sub_step[key]) == str:
|
||||
if 'dependent_on_temp_event' in sub_step[key]:
|
||||
globals().update(locals())
|
||||
exec('sub_step[key] = {sub_step}'.format(sub_step=sub_step[key]), globals(), {})
|
||||
if type(sub_step) == list:
|
||||
value_index = 0
|
||||
for value in copy.deepcopy(sub_step):
|
||||
if type(sub_step[value_index]) not in [str, float, int, bytes]:
|
||||
sub_step[key] = find_and_replace_dependent_values(
|
||||
copy.deepcopy(sub_step[value_index]), dependent_on_temp_event
|
||||
)
|
||||
else:
|
||||
if type(sub_step[value_index]) == str:
|
||||
if 'dependent_on_temp_event' in sub_step[value_index]:
|
||||
globals().update(locals())
|
||||
exec('sub_step[value_index] = {sub_step}'.format(sub_step=sub_step[value_index]), globals(), {})
|
||||
value_index += 1
|
||||
return sub_step
|
||||
|
||||
|
||||
def replace_dependent_values(sub_step, dependent_on_temp_event):
|
||||
return find_and_replace_dependent_values(sub_step, dependent_on_temp_event)
|
||||
|
||||
|
||||
def reverse_and_regex_condition(regex, reverse):
|
||||
if regex:
|
||||
if reverse:
|
||||
return []
|
||||
return list(set(regex))
|
||||
else:
|
||||
if reverse:
|
||||
return True
|
||||
return []
|
||||
|
||||
|
||||
def select_maximum_cpu_core(mode):
|
||||
if mode == 'maximum':
|
||||
return int(multiprocessing.cpu_count() - 1) if int(multiprocessing.cpu_count() - 1) >= 1 else 1
|
||||
elif mode == 'high':
|
||||
return int(multiprocessing.cpu_count() / 2) if int(multiprocessing.cpu_count() - 1) >= 1 else 1
|
||||
elif mode == 'normal':
|
||||
return int(multiprocessing.cpu_count() / 4) if int(multiprocessing.cpu_count() - 1) >= 1 else 1
|
||||
elif mode == 'low':
|
||||
return int(multiprocessing.cpu_count() / 8) if int(multiprocessing.cpu_count() - 1) >= 1 else 1
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
def wait_for_threads_to_finish(threads, maximum=None, terminable=False, sub_process=False):
|
||||
while threads:
|
||||
try:
|
||||
for thread in threads[:]:
|
||||
if not thread.is_alive():
|
||||
threads.remove(thread)
|
||||
if maximum and len(threads) < maximum:
|
||||
break
|
||||
time.sleep(0.01)
|
||||
except KeyboardInterrupt:
|
||||
if terminable:
|
||||
for thread in threads:
|
||||
terminate_thread(thread)
|
||||
if sub_process:
|
||||
for thread in threads:
|
||||
thread.kill()
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def terminate_thread(thread, verbose=True):
|
||||
"""
|
||||
kill a thread https://stackoverflow.com/a/15274929
|
||||
Args:
|
||||
thread: an alive thread
|
||||
verbose: verbose mode/boolean
|
||||
Returns:
|
||||
True/None
|
||||
"""
|
||||
from core.alert import info
|
||||
if verbose:
|
||||
info("killing {0}".format(thread.name))
|
||||
if not thread.is_alive():
|
||||
return
|
||||
|
||||
exc = ctypes.py_object(SystemExit)
|
||||
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
|
||||
ctypes.c_long(thread.ident),
|
||||
exc
|
||||
)
|
||||
if res == 0:
|
||||
raise ValueError("nonexistent thread id")
|
||||
elif res > 1:
|
||||
# if it returns a number greater than one, you're in trouble,
|
||||
# and you should call it again with exc=NULL to revert the effect
|
||||
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
|
||||
raise SystemError("PyThreadState_SetAsyncExc failed")
|
||||
return True
|
||||
|
||||
|
||||
def find_args_value(args_name):
|
||||
try:
|
||||
return sys.argv[sys.argv.index(args_name) + 1]
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def application_language():
|
||||
from config import nettacker_global_config
|
||||
nettacker_global_configuration = nettacker_global_config()
|
||||
if "-L" in sys.argv:
|
||||
language = find_args_value('-L') or 'en'
|
||||
elif "--language" in sys.argv:
|
||||
language = find_args_value('--language') or 'en'
|
||||
else:
|
||||
language = nettacker_global_configuration['nettacker_user_application_config']['language']
|
||||
if language not in load_all_languages():
|
||||
language = 'en'
|
||||
return language
|
||||
|
||||
|
||||
def generate_random_token(length=10):
|
||||
return "".join(
|
||||
random.choice(string.ascii_lowercase) for _ in range(length)
|
||||
)
|
||||
|
||||
|
||||
def re_address_repeaters_key_name(key_name):
|
||||
return "".join(['[\'' + _key + '\']' for _key in key_name.split('/')[:-1]])
|
||||
|
||||
|
||||
def generate_new_sub_steps(sub_steps, data_matrix, arrays):
|
||||
original_sub_steps = copy.deepcopy(sub_steps)
|
||||
steps_array = []
|
||||
for array in data_matrix:
|
||||
array_name_position = 0
|
||||
for array_name in arrays:
|
||||
for sub_step in sub_steps:
|
||||
exec(
|
||||
"original_sub_steps{key_name} = {matrix_value}".format(
|
||||
key_name=re_address_repeaters_key_name(array_name),
|
||||
matrix_value='"' + str(array[array_name_position]) + '"' if type(
|
||||
array[array_name_position]) == int or type(array[array_name_position]) == str else array[
|
||||
array_name_position]
|
||||
)
|
||||
)
|
||||
array_name_position += 1
|
||||
steps_array.append(copy.deepcopy(original_sub_steps))
|
||||
return steps_array
|
||||
|
||||
|
||||
def find_repeaters(sub_content, root, arrays):
|
||||
if type(sub_content) == dict and 'nettacker_fuzzer' not in sub_content:
|
||||
temprory_content = copy.deepcopy(sub_content)
|
||||
original_root = root
|
||||
for key in sub_content:
|
||||
root = original_root
|
||||
root += key + '/'
|
||||
temprory_content[key], _root, arrays = find_repeaters(sub_content[key], root, arrays)
|
||||
sub_content = copy.deepcopy(temprory_content)
|
||||
root = original_root
|
||||
if (type(sub_content) not in [bool, int, float]) and (
|
||||
type(sub_content) == list or 'nettacker_fuzzer' in sub_content):
|
||||
arrays[root] = sub_content
|
||||
return (sub_content, root, arrays) if root != '' else arrays
|
||||
|
||||
|
||||
def find_and_replace_configuration_keys(module_content, module_inputs):
|
||||
if type(module_content) == dict:
|
||||
for key in copy.deepcopy(module_content):
|
||||
if key in module_inputs:
|
||||
if module_inputs[key]:
|
||||
module_content[key] = module_inputs[key]
|
||||
elif type(module_content[key]) in [dict, list]:
|
||||
module_content[key] = find_and_replace_configuration_keys(module_content[key], module_inputs)
|
||||
elif type(module_content) == list:
|
||||
array_index = 0
|
||||
for key in copy.deepcopy(module_content):
|
||||
module_content[array_index] = find_and_replace_configuration_keys(key, module_inputs)
|
||||
array_index += 1
|
||||
else:
|
||||
return module_content
|
||||
return module_content
|
||||
|
||||
|
||||
class value_to_class:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
|
||||
def class_to_value(arrays):
|
||||
original_arrays = copy.deepcopy(arrays)
|
||||
array_index = 0
|
||||
for array in arrays:
|
||||
value_index = 0
|
||||
for value in array:
|
||||
if type(value) == value_to_class:
|
||||
original_arrays[array_index][value_index] = value.value
|
||||
value_index += 1
|
||||
array_index += 1
|
||||
return original_arrays
|
||||
|
||||
|
||||
def generate_and_replace_md5(content):
|
||||
# todo: make it betetr and document it
|
||||
md5_content = content.split('NETTACKER_MD5_GENERATOR_START')[1].split('NETTACKER_MD5_GENERATOR_STOP')[0]
|
||||
md5_content_backup = md5_content
|
||||
if type(md5_content) == str:
|
||||
md5_content = md5_content.encode()
|
||||
md5_hash = hashlib.md5(md5_content).hexdigest()
|
||||
return content.replace(
|
||||
'NETTACKER_MD5_GENERATOR_START' + md5_content_backup + 'NETTACKER_MD5_GENERATOR_STOP',
|
||||
md5_hash
|
||||
)
|
||||
|
||||
|
||||
def arrays_to_matrix(arrays):
|
||||
import numpy
|
||||
return numpy.array(
|
||||
numpy.meshgrid(*[
|
||||
arrays[array_name] for array_name in arrays
|
||||
])
|
||||
).T.reshape(
|
||||
-1,
|
||||
len(arrays.keys())
|
||||
).tolist()
|
||||
|
||||
|
||||
def string_to_bytes(string):
|
||||
return string.encode()
|
||||
|
||||
|
||||
def fuzzer_function_read_file_as_array(filename):
|
||||
from config import nettacker_paths
|
||||
return open(
|
||||
os.path.join(
|
||||
nettacker_paths()['payloads_path'],
|
||||
filename
|
||||
)
|
||||
).read().split('\n')
|
||||
|
||||
|
||||
def apply_data_functions(data):
|
||||
original_data = copy.deepcopy(data)
|
||||
function_results = {}
|
||||
globals().update(locals())
|
||||
for data_name in data:
|
||||
if type(data[data_name]) == str and data[data_name].startswith('fuzzer_function'):
|
||||
exec(
|
||||
"fuzzer_function = {fuzzer_function}".format(
|
||||
fuzzer_function=data[data_name]
|
||||
),
|
||||
globals(),
|
||||
function_results
|
||||
)
|
||||
original_data[data_name] = function_results['fuzzer_function']
|
||||
return original_data
|
||||
|
||||
|
||||
def nettacker_fuzzer_repeater_perform(arrays):
|
||||
original_arrays = copy.deepcopy(arrays)
|
||||
for array_name in arrays:
|
||||
if 'nettacker_fuzzer' in arrays[array_name]:
|
||||
data = arrays[array_name]['nettacker_fuzzer']['data']
|
||||
data_matrix = arrays_to_matrix(apply_data_functions(data))
|
||||
prefix = arrays[array_name]['nettacker_fuzzer']['prefix']
|
||||
input_format = arrays[array_name]['nettacker_fuzzer']['input_format']
|
||||
interceptors = copy.deepcopy(arrays[array_name]['nettacker_fuzzer']['interceptors'])
|
||||
if interceptors:
|
||||
interceptors = interceptors.split(',')
|
||||
suffix = arrays[array_name]['nettacker_fuzzer']['suffix']
|
||||
processed_array = []
|
||||
for sub_data in data_matrix:
|
||||
formatted_data = {}
|
||||
index_input = 0
|
||||
for value in sub_data:
|
||||
formatted_data[list(data.keys())[index_input]] = value
|
||||
index_input += 1
|
||||
interceptors_function = ''
|
||||
interceptors_function_processed = ''
|
||||
if interceptors:
|
||||
interceptors_function += 'interceptors_function_processed = '
|
||||
for interceptor in interceptors[::-1]:
|
||||
interceptors_function += '{interceptor}('.format(interceptor=interceptor)
|
||||
interceptors_function += 'input_format.format(**formatted_data)' + str(
|
||||
')' * interceptors_function.count('('))
|
||||
expected_variables = {}
|
||||
globals().update(locals())
|
||||
exec(interceptors_function, globals(), expected_variables)
|
||||
interceptors_function_processed = expected_variables['interceptors_function_processed']
|
||||
else:
|
||||
interceptors_function_processed = input_format.format(**formatted_data)
|
||||
processed_sub_data = interceptors_function_processed
|
||||
if prefix:
|
||||
processed_sub_data = prefix + processed_sub_data
|
||||
if suffix:
|
||||
processed_sub_data = processed_sub_data + suffix
|
||||
processed_array.append(copy.deepcopy(processed_sub_data))
|
||||
original_arrays[array_name] = processed_array
|
||||
return original_arrays
|
||||
|
||||
|
||||
def expand_module_steps(content):
|
||||
original_content = copy.deepcopy(content)
|
||||
for protocol_lib in content:
|
||||
for sub_step in content[content.index(protocol_lib)]['steps']:
|
||||
arrays = nettacker_fuzzer_repeater_perform(find_repeaters(sub_step, '', {}))
|
||||
if arrays:
|
||||
original_content[content.index(protocol_lib)]['steps'][
|
||||
original_content[content.index(protocol_lib)]['steps'].index(sub_step)
|
||||
] = generate_new_sub_steps(sub_step, class_to_value(arrays_to_matrix(arrays)), arrays)
|
||||
else:
|
||||
original_content[content.index(protocol_lib)]['steps'][
|
||||
original_content[content.index(protocol_lib)]['steps'].index(sub_step)
|
||||
] = [ # minimum 1 step in array
|
||||
original_content[content.index(protocol_lib)]['steps'][
|
||||
original_content[content.index(protocol_lib)]['steps'].index(sub_step)
|
||||
]
|
||||
]
|
||||
return original_content
|
||||
|
||||
|
||||
def sort_dictonary(dictionary):
|
||||
etc_flag = '...' in dictionary
|
||||
if etc_flag:
|
||||
del dictionary['...']
|
||||
sorted_dictionary = {}
|
||||
for key in sorted(dictionary):
|
||||
sorted_dictionary[key] = dictionary[key]
|
||||
if etc_flag:
|
||||
sorted_dictionary['...'] = {}
|
||||
return sorted_dictionary
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
pass
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from config import nettacker_database_config
|
||||
from database.models import Base
|
||||
|
||||
|
||||
USER = nettacker_database_config()["USERNAME"]
|
||||
PASSWORD = nettacker_database_config()["PASSWORD"]
|
||||
HOST = nettacker_database_config()["HOST"]
|
||||
PORT = nettacker_database_config()["PORT"]
|
||||
DATABASE = nettacker_database_config()["DATABASE"]
|
||||
|
||||
|
||||
def mysql_create_database():
|
||||
"""
|
||||
when using mysql database, this is the function that is used to create the database for the first time when you run
|
||||
the nettacker module.
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
True if success otherwise False
|
||||
"""
|
||||
try:
|
||||
engine = create_engine('mysql://{0}:{1}@{2}:{3}'.format(USER, PASSWORD, HOST, PORT))
|
||||
existing_databases = engine.execute("SHOW DATABASES;")
|
||||
existing_databases = [
|
||||
d[0] for d in existing_databases
|
||||
]
|
||||
if DATABASE not in existing_databases:
|
||||
engine.execute("CREATE DATABASE {0} ".format(DATABASE))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def mysql_create_tables():
|
||||
"""
|
||||
when using mysql database, this is the function that is used to create the tables in the database for the first
|
||||
time when you run the nettacker module.
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
True if success otherwise False
|
||||
"""
|
||||
try:
|
||||
db_engine = create_engine('mysql://{0}:{1}@{2}:{3}/{4}'.format(USER, PASSWORD, HOST, PORT, DATABASE))
|
||||
Base.metadata.create_all(db_engine)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from config import nettacker_database_config
|
||||
from database.models import Base
|
||||
from sqlalchemy.exc import OperationalError
|
||||
|
||||
USER = nettacker_database_config()["USERNAME"]
|
||||
PASSWORD = nettacker_database_config()["PASSWORD"]
|
||||
HOST = nettacker_database_config()["HOST"]
|
||||
PORT = nettacker_database_config()["PORT"]
|
||||
DATABASE = nettacker_database_config()["DATABASE"]
|
||||
|
||||
|
||||
def postgres_create_database():
|
||||
"""
|
||||
when using postgres database, this is the function that is used to create the database for the first time when you
|
||||
the nettacker run module.
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
True if success otherwise False
|
||||
"""
|
||||
|
||||
try:
|
||||
engine = create_engine(
|
||||
'postgres+psycopg2://{0}:{1}@{2}:{3}/{4}'.format(USER, PASSWORD, HOST, PORT, DATABASE)
|
||||
)
|
||||
Base.metadata.create_all(engine)
|
||||
return True
|
||||
except OperationalError:
|
||||
# if the database does not exist
|
||||
engine = create_engine(
|
||||
"postgres+psycopg2://postgres:postgres@localhost/postgres")
|
||||
conn = engine.connect()
|
||||
conn.execute("commit")
|
||||
conn.execute('CREATE DATABASE {0}'.format(DATABASE))
|
||||
conn.close()
|
||||
engine = create_engine(
|
||||
'postgres+psycopg2://{0}:{1}@{2}:{3}/{4}'.format(
|
||||
USER,
|
||||
PASSWORD,
|
||||
HOST,
|
||||
PORT,
|
||||
DATABASE
|
||||
)
|
||||
)
|
||||
Base.metadata.create_all(engine)
|
||||
except Exception:
|
||||
return False
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from database.models import Base
|
||||
from config import nettacker_database_config
|
||||
|
||||
DATABASE = nettacker_database_config()["DATABASE"]
|
||||
|
||||
|
||||
def sqlite_create_tables():
|
||||
"""
|
||||
when using sqlite database, this is the function that is used to create the database schema for the first time when
|
||||
you run the nettacker module.
|
||||
|
||||
Args:
|
||||
None
|
||||
|
||||
Returns:
|
||||
True if success otherwise False
|
||||
"""
|
||||
try:
|
||||
db_engine = create_engine(
|
||||
'sqlite:///{0}'.format(DATABASE),
|
||||
connect_args={
|
||||
'check_same_thread': False
|
||||
}
|
||||
)
|
||||
Base.metadata.create_all(db_engine)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
nettacker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: "Dockerfile"
|
||||
command: python3 nettacker.py --start-api --api-host 0.0.0.0
|
||||
dockerfile: Dockerfile
|
||||
command: --start-api --api-host 0.0.0.0
|
||||
container_name: nettacker
|
||||
environment:
|
||||
- docker_env=true
|
||||
ports:
|
||||
- 5000:5000
|
||||
volumes:
|
||||
- ./:/usr/src/owaspnettacker
|
||||
environment:
|
||||
- docker_env=true
|
||||
- ./nettacker:/usr/src/owaspnettacker/nettacker
|
||||
|
|
|
|||
|
|
@ -0,0 +1,744 @@
|
|||
WebUI/API Manual usage explained in the [Usage](Usage#api-and-webui) page but let's get into the structure of the request now.
|
||||
|
||||
- [Purpose](#purpose)
|
||||
- [Requests Structure](#requests-structure)
|
||||
- [New Scan](#new-scan)
|
||||
- [Set Session](#set-session)
|
||||
* [Set Cookie](#set-cookie)
|
||||
* [Check Cookie](#check-cookie)
|
||||
* [UnSet Cookie](#unset-cookie)
|
||||
- [Results List](#results-list)
|
||||
* [Get a Scan Result](#get-a-scan-result)
|
||||
- [Hosts List](#hosts-list)
|
||||
* [Search in the Hosts](#search-in-the-hosts)
|
||||
- [Generate a HTML Scan Result for a Host](#generate-a-html-scan-result-for-a-host)
|
||||
* [Get the Scan Result in JSON Type](#get-the-scan-result-in-json-type)
|
||||
|
||||
|
||||
## Purpose
|
||||
|
||||
API usage purposes depend on the users, Some of them may want to scan their local company to monitor the network, This feature let all security staff use OWASP Nettacker on a shared server safely. API supports SSL. User can give their own Certificate and the key to run server on HTTPS.
|
||||
|
||||
|
||||
## Requests Structure
|
||||
|
||||
```
|
||||
am4n@am4n-HP-ProBook-450-G4:~/Documents/OWASP-Nettacker$ python nettacker.py --start-api
|
||||
|
||||
______ __ _____ _____
|
||||
/ __ \ \ / /\ / ____| __ \
|
||||
| | | \ \ /\ / / \ | (___ | |__) |
|
||||
| | | |\ \/ \/ / /\ \ \___ \| ___/
|
||||
| |__| | \ /\ / ____ \ ____) | | Version 0.0.1
|
||||
\____/ \/ \/_/ \_\_____/|_| SAME
|
||||
_ _ _ _ _
|
||||
| \ | | | | | | | |
|
||||
github.com/zdresearch | \| | ___| |_| |_ __ _ ___| | _____ _ __
|
||||
owasp.org | . ` |/ _ \ __| __/ _` |/ __| |/ / _ \ '__|
|
||||
zdresearch.com | |\ | __/ |_| || (_| | (__| < __/ |
|
||||
|_| \_|\___|\__|\__\__,_|\___|_|\_\___|_|
|
||||
|
||||
|
||||
|
||||
* API Key: 2608863752f1f89fa385e43c76c2853b
|
||||
* Serving Flask app "api.engine" (lazy loading)
|
||||
* Environment: production
|
||||
WARNING: This is a development server. Do not use it in a production deployment.
|
||||
Use a production WSGI server instead.
|
||||
* Debug mode: off
|
||||
* Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
|
||||
```
|
||||
|
||||
At the first, you must send an API key through the request each time you send a request in `GET`, `POST`, or `Cookies` in the value named `key` or you will get `401` error in the restricted area.
|
||||
|
||||
```python
|
||||
>>> import requests
|
||||
>>> from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||
>>> requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
>>> r = requests.get('https://127.0.0.1:5000/?key=8370bd0a0b9a98ac25b341833fb0fb07')
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> r = requests.post('https://127.0.0.1:5000/', data={"key": "8370bd0a0b9a98ac25b341833fb0fb07"})
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> r = requests.get('https://127.0.0.1:5000/', cookies={"key": "8370bd0a0b9a98ac25b341833fb0fb07"})
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> r = requests.get('https://127.0.0.1:5000/new/scan', cookies={"key": "wrong_key"})
|
||||
>>> r.status_code
|
||||
401
|
||||
```
|
||||
|
||||
## New Scan
|
||||
|
||||
To submit a new scan follow this step.
|
||||
|
||||
```python
|
||||
>>> r = requests.post('https://127.0.0.1:5000/new/scan', data={"key": "8370bd0a0b9a98ac25b341833fb0fb07", "targets": "127.0.0.1,owasp.org", "selected_modules": "port_scan", "report_path_filename": "/home/test.html"})
|
||||
>>> r.status_code
|
||||
200
|
||||
>>> import json
|
||||
>>> print json.dumps(json.loads(r.content), sort_keys=True, indent=4)
|
||||
{
|
||||
"backup_ports": null,
|
||||
"check_ranges": false,
|
||||
"check_subdomains": false,
|
||||
"database_host": "",
|
||||
"database_name": "/home/am4n/owasp-nettacker/.nettacker/data/nettacker.db",
|
||||
"database_password": "",
|
||||
"database_port": "",
|
||||
"database_type": "sqlite",
|
||||
"database_username": "",
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"home_path": "/home/am4n/owasp-nettacker/.nettacker/data",
|
||||
"language": "en",
|
||||
"log_in_file": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_10_36_56_mibtrtoacd.html",
|
||||
"methods_args": {
|
||||
"as_user_set": "set_successfully"
|
||||
},
|
||||
"passwds": null,
|
||||
"ping_flag": false,
|
||||
"ports": null,
|
||||
"profile": null,
|
||||
"results_path": "/home/am4n/owasp-nettacker/.nettacker/data/results",
|
||||
"retries": 3,
|
||||
"scan_method": [
|
||||
"port_scan"
|
||||
],
|
||||
"socks_proxy": null,
|
||||
"targets": [
|
||||
"owasp.org"
|
||||
],
|
||||
"thread_number": 100,
|
||||
"thread_number_host": 5,
|
||||
"time_sleep": 0.0,
|
||||
"timeout_sec": 3,
|
||||
"tmp_path": "/home/am4n/owasp-nettacker/.nettacker/data/tmp",
|
||||
"users": null,
|
||||
"verbose_level": 0
|
||||
}
|
||||
```
|
||||
|
||||
Please note, `targets` and `selected_modules` are **necessary** to submit a new scan unless you modify the config file before! The `selected_modules` could be empty if you define the `profile`.
|
||||
|
||||
```python
|
||||
>>> r = requests.post('https://127.0.0.1:5000/new/scan', data={"key": "8370bd0a0b9a98ac25b341833fb0fb07"})
|
||||
>>> r.content
|
||||
'{"msg":"Cannot specify the target(s)","status":"error"}\n'
|
||||
|
||||
>>> r = requests.post('https://127.0.0.1:5000/new/scan', data={"key": "09877e92c75f6afdca6ae61ad3f53727", "targets": "127.0.0.1"})
|
||||
>>> r.content
|
||||
u'{"msg":"please choose your scan method!","status":"error"}\n'
|
||||
|
||||
>>> r = requests.post('https://127.0.0.1:5000/new/scan', data={"key": "09877e92c75f6afdca6ae61ad3f53727", "targets": "127.0.0.1", "selected_modules": "dir_scan,port_scan", "report_path_filename": "/home/test.html"})
|
||||
>>> print json.dumps(json.loads(r.content), sort_keys=True, indent=4)
|
||||
{
|
||||
"backup_ports": null,
|
||||
"check_ranges": false,
|
||||
"check_subdomains": false,
|
||||
"database_host": "",
|
||||
"database_name": "/home/am4n/owasp-nettacker/.nettacker/data/nettacker.db",
|
||||
"database_password": "",
|
||||
"database_port": "",
|
||||
"database_type": "sqlite",
|
||||
"database_username": "",
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"home_path": "/home/am4n/owasp-nettacker/.nettacker/data",
|
||||
"language": "en",
|
||||
"log_in_file": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_10_47_08_dugacttfmf.html",
|
||||
"methods_args": {
|
||||
"as_user_set": "set_successfully"
|
||||
},
|
||||
"passwds": null,
|
||||
"ping_flag": false,
|
||||
"ports": null,
|
||||
"profile": null,
|
||||
"results_path": "/home/am4n/owasp-nettacker/.nettacker/data/results",
|
||||
"retries": 3,
|
||||
"scan_method": [
|
||||
"dir_scan",
|
||||
"port_scan"
|
||||
],
|
||||
"socks_proxy": null,
|
||||
"targets": [
|
||||
"127.0.0.1"
|
||||
],
|
||||
"thread_number": 100,
|
||||
"thread_number_host": 5,
|
||||
"time_sleep": 0.0,
|
||||
"timeout_sec": 3,
|
||||
"tmp_path": "/home/am4n/owasp-nettacker/.nettacker/data/tmp",
|
||||
"users": null,
|
||||
"verbose_level": 0
|
||||
}
|
||||
>>> r = requests.post('https://127.0.0.1:5000/new/scan', data={"key": "09877e92c75f6afdca6ae61ad3f53727", "targets": "127.0.0.1", "profile": "information_gathering"})
|
||||
>>> print json.dumps(json.loads(r.content), sort_keys=True, indent=4)
|
||||
{
|
||||
"backup_ports": null,
|
||||
"check_ranges": false,
|
||||
"check_subdomains": false,
|
||||
"database_host": "",
|
||||
"database_name": "/home/am4n/owasp-nettacker/.nettacker/data/nettacker.db",
|
||||
"database_password": "",
|
||||
"database_port": "",
|
||||
"database_type": "sqlite",
|
||||
"database_username": "",
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"home_path": "/home/am4n/owasp-nettacker/.nettacker/data",
|
||||
"language": "en",
|
||||
"log_in_file": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_10_50_09_xjqatmkngn.html",
|
||||
"methods_args": {
|
||||
"as_user_set": "set_successfully"
|
||||
},
|
||||
"passwds": null,
|
||||
"ping_flag": false,
|
||||
"ports": null,
|
||||
"profile": "information_gathering",
|
||||
"results_path": "/home/am4n/owasp-nettacker/.nettacker/data/results",
|
||||
"retries": 3,
|
||||
"scan_method": [
|
||||
"port_scan"
|
||||
],
|
||||
"socks_proxy": null,
|
||||
"targets": [
|
||||
"127.0.0.1"
|
||||
],
|
||||
"thread_number": 100,
|
||||
"thread_number_host": 5,
|
||||
"time_sleep": 0.0,
|
||||
"timeout_sec": 3,
|
||||
"tmp_path": "/home/am4n/owasp-nettacker/.nettacker/data/tmp",
|
||||
"users": null,
|
||||
"verbose_level": 0
|
||||
}
|
||||
|
||||
>>>
|
||||
|
||||
```
|
||||
|
||||
All variables in JSON you've got in results could be changed in `GET`/`POST`/`Cookies`, you can fill them all just like normal CLI commands. (e.g. same scan method name (modules), you can separate with `,`, you can use `ports` like `80,100-200,1000,2000`, set users and passwords `user1,user2`, `passwd1,passwd2`). You cannot use `read_from_file:/tmp/users.txt` syntax in `methods_args`. if you want to send a big password list, just send it through the `POST` requests and separated with `,`.
|
||||
|
||||
## Set Session
|
||||
|
||||
To enable session-based requests, like (e.g. Python `requests.session()` or browsers), I developed a feature to interact with Cookie.
|
||||
|
||||
### Set Cookie
|
||||
|
||||
```python
|
||||
>>> s = requests.session()
|
||||
>>> r = s.get("https://localhost:5000/session/set?key=09877e92c75f6afdca6ae61ad3f53727")
|
||||
>>> print json.dumps(json.loads(r.content), sort_keys=True, indent=4)
|
||||
{
|
||||
"msg": "your browser session is valid",
|
||||
"status": "ok"
|
||||
}
|
||||
>>> print r.cookies
|
||||
<RequestsCookieJar[<Cookie key=09877e92c75f6afdca6ae61ad3f53727 for localhost.local/>]>
|
||||
>>> r = s.get("https://localhost:5000/new/scan")
|
||||
>>> print r.content
|
||||
{
|
||||
"msg": "Cannot specify the target(s)",
|
||||
"status": "error"
|
||||
}
|
||||
|
||||
>>>
|
||||
```
|
||||
### Check Cookie
|
||||
|
||||
```python
|
||||
>>> r = s.get("https://localhost:5000/session/check")
|
||||
>>> print r.content
|
||||
{
|
||||
"msg": "your browser session is valid",
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
### UnSet Cookie
|
||||
|
||||
```python
|
||||
>>> r = s.get("https://localhost:5000/session/kill")
|
||||
>>> print r.content
|
||||
{
|
||||
"msg": "your browser session killed",
|
||||
"status": "ok"
|
||||
}
|
||||
|
||||
>>> print r.cookies
|
||||
<RequestsCookieJar[]>
|
||||
>>>
|
||||
```
|
||||
|
||||
## Results List
|
||||
|
||||
```python
|
||||
>>> r = s.get("https://localhost:5000/results/get_list?page=1")
|
||||
>>> print(json.dumps(json.loads(r.content), sort_keys=True, indent=4))
|
||||
[
|
||||
{
|
||||
"api_flag": 0,
|
||||
"category": "vuln,brute,scan",
|
||||
"date": "2020-06-09 11:08:45",
|
||||
"events_num": 317,
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"id": 8,
|
||||
"language": "en",
|
||||
"ports": "default",
|
||||
"profile": null,
|
||||
"report_filename": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_11_04_17_pisajfbfyp.html",
|
||||
"report_type": "HTML",
|
||||
"scan_cmd": "nettacker.py -i 127.0.0.1 -m all -M 100",
|
||||
"scan_id": "b745337b4feeb99cee3eb4ff4cb45fad",
|
||||
"scan_method": "XSS_protection_vuln,ProFTPd_directory_traversal_vuln,port_scan,telnet_brute,ssl_certificate_expired_vuln,http_form_brute,ProFTPd_integer_overflow_vuln,heartbleed_vuln,joomla_user_enum_scan,http_basic_auth_brute,http_ntlm_brute,wp_user_enum_scan,ProFTPd_restriction_bypass_vuln,http_cors_vuln,apache_struts_vuln,wordpress_version_scan,clickjacking_vuln,wp_xmlrpc_bruteforce_vuln,cms_detection_scan,wordpress_dos_cve_2018_6389_vuln,content_security_policy_vuln,pma_scan,ftp_brute,wp_theme_scan,wappalyzer_scan,wp_xmlrpc_brute,wp_xmlrpc_pingback_vuln,smtp_brute,drupal_version_scan,ProFTPd_memory_leak_vuln,wp_plugin_scan,ssh_brute,joomla_template_scan,wp_timthumbs_scan,self_signed_certificate_vuln,Bftpd_memory_leak_vuln,CCS_injection_vuln,dir_scan,viewdns_reverse_ip_lookup_scan,Bftpd_parsecmd_overflow_vuln,icmp_scan,ProFTPd_exec_arbitary_vuln,server_version_vuln,x_powered_by_vuln,admin_scan,citrix_cve_2019_19781_vuln,joomla_version_scan,sender_policy_scan,ProFTPd_cpu_consumption_vuln,Bftpd_double_free_vuln,drupal_theme_scan,ProFTPd_heap_overflow_vuln,weak_signature_algorithm_vuln,drupal_modules_scan,subdomain_scan,Bftpd_remote_dos_vuln,content_type_options_vuln,xdebug_rce_vuln,options_method_enabled_vuln,ProFTPd_bypass_sqli_protection_vuln",
|
||||
"verbose": 0
|
||||
},
|
||||
{
|
||||
"api_flag": 0,
|
||||
"category": "vuln,brute,scan",
|
||||
"date": "2020-06-09 11:08:42",
|
||||
"events_num": 372,
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"id": 7,
|
||||
"language": "en",
|
||||
"ports": "default",
|
||||
"profile": null,
|
||||
"report_filename": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_11_04_04_bdzipsmtcc.html",
|
||||
"report_type": "HTML",
|
||||
"scan_cmd": "nettacker.py -i 127.0.0.1 -m all",
|
||||
"scan_id": "8e9a1b2fd03cb7b969d99beea1cff2aa",
|
||||
"scan_method": "XSS_protection_vuln,ProFTPd_directory_traversal_vuln,port_scan,telnet_brute,ssl_certificate_expired_vuln,http_form_brute,ProFTPd_integer_overflow_vuln,heartbleed_vuln,joomla_user_enum_scan,http_basic_auth_brute,http_ntlm_brute,wp_user_enum_scan,ProFTPd_restriction_bypass_vuln,http_cors_vuln,apache_struts_vuln,wordpress_version_scan,clickjacking_vuln,wp_xmlrpc_bruteforce_vuln,cms_detection_scan,wordpress_dos_cve_2018_6389_vuln,content_security_policy_vuln,pma_scan,ftp_brute,wp_theme_scan,wappalyzer_scan,wp_xmlrpc_brute,wp_xmlrpc_pingback_vuln,smtp_brute,drupal_version_scan,ProFTPd_memory_leak_vuln,wp_plugin_scan,ssh_brute,joomla_template_scan,wp_timthumbs_scan,self_signed_certificate_vuln,Bftpd_memory_leak_vuln,CCS_injection_vuln,dir_scan,viewdns_reverse_ip_lookup_scan,Bftpd_parsecmd_overflow_vuln,icmp_scan,ProFTPd_exec_arbitary_vuln,server_version_vuln,x_powered_by_vuln,admin_scan,citrix_cve_2019_19781_vuln,joomla_version_scan,sender_policy_scan,ProFTPd_cpu_consumption_vuln,Bftpd_double_free_vuln,drupal_theme_scan,ProFTPd_heap_overflow_vuln,weak_signature_algorithm_vuln,drupal_modules_scan,subdomain_scan,Bftpd_remote_dos_vuln,content_type_options_vuln,xdebug_rce_vuln,options_method_enabled_vuln,ProFTPd_bypass_sqli_protection_vuln",
|
||||
"verbose": 0
|
||||
},
|
||||
{
|
||||
"api_flag": 0,
|
||||
"category": "vuln,brute,scan",
|
||||
"date": "2020-06-09 11:06:52",
|
||||
"events_num": 1016,
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"id": 6,
|
||||
"language": "en",
|
||||
"ports": "default",
|
||||
"profile": null,
|
||||
"report_filename": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_11_03_23_ubytvgauvj.html",
|
||||
"report_type": "HTML",
|
||||
"scan_cmd": "nettacker.py -i 127.0.0.1 -m all -M 100 -t 1000",
|
||||
"scan_id": "7d84af54f343e19671d1c52357bf928f",
|
||||
"scan_method": "XSS_protection_vuln,ProFTPd_directory_traversal_vuln,port_scan,telnet_brute,ssl_certificate_expired_vuln,http_form_brute,ProFTPd_integer_overflow_vuln,heartbleed_vuln,joomla_user_enum_scan,http_basic_auth_brute,http_ntlm_brute,wp_user_enum_scan,ProFTPd_restriction_bypass_vuln,http_cors_vuln,apache_struts_vuln,wordpress_version_scan,clickjacking_vuln,wp_xmlrpc_bruteforce_vuln,cms_detection_scan,wordpress_dos_cve_2018_6389_vuln,content_security_policy_vuln,pma_scan,ftp_brute,wp_theme_scan,wappalyzer_scan,wp_xmlrpc_brute,wp_xmlrpc_pingback_vuln,smtp_brute,drupal_version_scan,ProFTPd_memory_leak_vuln,wp_plugin_scan,ssh_brute,joomla_template_scan,wp_timthumbs_scan,self_signed_certificate_vuln,Bftpd_memory_leak_vuln,CCS_injection_vuln,dir_scan,viewdns_reverse_ip_lookup_scan,Bftpd_parsecmd_overflow_vuln,icmp_scan,ProFTPd_exec_arbitary_vuln,server_version_vuln,x_powered_by_vuln,admin_scan,citrix_cve_2019_19781_vuln,joomla_version_scan,sender_policy_scan,ProFTPd_cpu_consumption_vuln,Bftpd_double_free_vuln,drupal_theme_scan,ProFTPd_heap_overflow_vuln,weak_signature_algorithm_vuln,drupal_modules_scan,subdomain_scan,Bftpd_remote_dos_vuln,content_type_options_vuln,xdebug_rce_vuln,options_method_enabled_vuln,ProFTPd_bypass_sqli_protection_vuln",
|
||||
"verbose": 0
|
||||
},
|
||||
{
|
||||
"api_flag": 0,
|
||||
"category": "vuln,brute,scan",
|
||||
"date": "2020-06-09 11:01:14",
|
||||
"events_num": 1017,
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"id": 5,
|
||||
"language": "en",
|
||||
"ports": "default",
|
||||
"profile": null,
|
||||
"report_filename": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_10_59_29_oyzxmegtuk.html",
|
||||
"report_type": "HTML",
|
||||
"scan_cmd": "nettacker.py -i 127.0.0.1 -m all -t 1000",
|
||||
"scan_id": "d944c9a02053fd387d1e3343fec6b320",
|
||||
"scan_method": "XSS_protection_vuln,ProFTPd_directory_traversal_vuln,port_scan,telnet_brute,ssl_certificate_expired_vuln,http_form_brute,ProFTPd_integer_overflow_vuln,heartbleed_vuln,joomla_user_enum_scan,http_basic_auth_brute,http_ntlm_brute,wp_user_enum_scan,ProFTPd_restriction_bypass_vuln,http_cors_vuln,apache_struts_vuln,wordpress_version_scan,clickjacking_vuln,wp_xmlrpc_bruteforce_vuln,cms_detection_scan,wordpress_dos_cve_2018_6389_vuln,content_security_policy_vuln,pma_scan,ftp_brute,wp_theme_scan,wappalyzer_scan,wp_xmlrpc_brute,wp_xmlrpc_pingback_vuln,smtp_brute,drupal_version_scan,ProFTPd_memory_leak_vuln,wp_plugin_scan,ssh_brute,joomla_template_scan,wp_timthumbs_scan,self_signed_certificate_vuln,Bftpd_memory_leak_vuln,CCS_injection_vuln,dir_scan,viewdns_reverse_ip_lookup_scan,Bftpd_parsecmd_overflow_vuln,icmp_scan,ProFTPd_exec_arbitary_vuln,server_version_vuln,x_powered_by_vuln,admin_scan,citrix_cve_2019_19781_vuln,joomla_version_scan,sender_policy_scan,ProFTPd_cpu_consumption_vuln,Bftpd_double_free_vuln,drupal_theme_scan,ProFTPd_heap_overflow_vuln,weak_signature_algorithm_vuln,drupal_modules_scan,subdomain_scan,Bftpd_remote_dos_vuln,content_type_options_vuln,xdebug_rce_vuln,options_method_enabled_vuln,ProFTPd_bypass_sqli_protection_vuln",
|
||||
"verbose": 0
|
||||
},
|
||||
{
|
||||
"api_flag": 0,
|
||||
"category": "scan",
|
||||
"date": "2020-06-09 10:50:18",
|
||||
"events_num": 9,
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"id": 4,
|
||||
"language": "en",
|
||||
"ports": "default",
|
||||
"profile": "information_gathering",
|
||||
"report_filename": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_10_50_09_xjqatmkngn.html",
|
||||
"report_type": "HTML",
|
||||
"scan_cmd": "Through the OWASP Nettacker API",
|
||||
"scan_id": "05ba4e5b839b5ba525c9a35baa8864a1",
|
||||
"scan_method": "port_scan",
|
||||
"verbose": 0
|
||||
},
|
||||
{
|
||||
"api_flag": 0,
|
||||
"category": "scan",
|
||||
"date": "2020-06-09 10:47:17",
|
||||
"events_num": 9,
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"id": 3,
|
||||
"language": "en",
|
||||
"ports": "default",
|
||||
"profile": null,
|
||||
"report_filename": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_10_47_08_dugacttfmf.html",
|
||||
"report_type": "HTML",
|
||||
"scan_cmd": "Through the OWASP Nettacker API",
|
||||
"scan_id": "18af7af856b4ceefac659a59c4908088",
|
||||
"scan_method": "dir_scan,port_scan",
|
||||
"verbose": 0
|
||||
},
|
||||
{
|
||||
"api_flag": 0,
|
||||
"category": "scan",
|
||||
"date": "2020-06-09 10:38:50",
|
||||
"events_num": 0,
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"id": 2,
|
||||
"language": "en",
|
||||
"ports": "default",
|
||||
"profile": null,
|
||||
"report_filename": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_10_35_10_jvxotwxako.html",
|
||||
"report_type": "HTML",
|
||||
"scan_cmd": "Through the OWASP Nettacker API",
|
||||
"scan_id": "78d253c3a28d2bb4f467ac040ccaa854",
|
||||
"scan_method": "port_scan",
|
||||
"verbose": 0
|
||||
},
|
||||
{
|
||||
"api_flag": 0,
|
||||
"category": "scan",
|
||||
"date": "2020-06-09 10:38:49",
|
||||
"events_num": 3,
|
||||
"graph_flag": "d3_tree_v2_graph",
|
||||
"id": 1,
|
||||
"language": "en",
|
||||
"ports": "default",
|
||||
"profile": null,
|
||||
"report_filename": "/home/am4n/owasp-nettacker/.nettacker/data/results/results_2020_06_09_10_36_56_mibtrtoacd.html",
|
||||
"report_type": "HTML",
|
||||
"scan_cmd": "Through the OWASP Nettacker API",
|
||||
"scan_id": "708e1dcf0f2ce9fe71038ccea7bf28bb",
|
||||
"scan_method": "port_scan",
|
||||
"verbose": 0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get a Scan Result
|
||||
|
||||
```python
|
||||
>>> r = s.get("https://localhost:5000/results/get?id=8")
|
||||
>>> print r.content[:500]
|
||||
<!DOCTYPE html>
|
||||
<!-- THIS PAGE COPIED AND MODIFIED FROM http://bl.ocks.org/robschmuecker/7880033-->
|
||||
<title>OWASP Nettacker Report</title>
|
||||
<meta charset="utf-8">
|
||||
<div class="header">
|
||||
<h3><a href="https://github.com/zdresearch/nettacker">OWASP Nettacker</a></h3>
|
||||
<h3>Penetration Testing Graphs</h3>
|
||||
</div>
|
||||
<style type="text/css">
|
||||
|
||||
.header{
|
||||
margin:2%;
|
||||
text-align:center;
|
||||
}
|
||||
.node {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.overlay{
|
||||
background-color:#EEE;
|
||||
}
|
||||
|
||||
.node circle {
|
||||
fill: #f
|
||||
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
## Hosts List
|
||||
```python
|
||||
>>> r = s.get("https://localhost:5000/logs/search?q=&page=1")
|
||||
>>> print json.dumps(json.loads(r.content), sort_keys=True, indent=4)
|
||||
[
|
||||
{
|
||||
"host": "owasp.org",
|
||||
"info": {
|
||||
"category": [
|
||||
"scan"
|
||||
],
|
||||
"descriptions": [
|
||||
"8443/http/TCP_CONNECT",
|
||||
"80/http/TCP_CONNECT",
|
||||
"443/http/TCP_CONNECT"
|
||||
],
|
||||
"open_ports": [],
|
||||
"scan_methods": [
|
||||
"port_scan"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
>>>
|
||||
```
|
||||
|
||||
### Search in the Hosts
|
||||
|
||||
```python
|
||||
>>> r = s.get("https://localhost:5000/logs/search?q=port_scan&page=3")
|
||||
>>> print r.content
|
||||
[
|
||||
{
|
||||
"host": "owasp4.owasp.org",
|
||||
"info": {
|
||||
"category": [
|
||||
"scan"
|
||||
],
|
||||
"descriptions": [
|
||||
"22/TCP_CONNECT",
|
||||
"80/TCP_CONNECT"
|
||||
],
|
||||
"open_ports": [
|
||||
22,
|
||||
80
|
||||
],
|
||||
"scan_methods": [
|
||||
"port_scan"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"host": "new-wiki.owasp.org",
|
||||
"info": {
|
||||
"category": [
|
||||
"scan"
|
||||
],
|
||||
"descriptions": [
|
||||
"22/TCP_CONNECT",
|
||||
"80/TCP_CONNECT"
|
||||
],
|
||||
"open_ports": [
|
||||
22,
|
||||
80
|
||||
],
|
||||
"scan_methods": [
|
||||
"port_scan"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"host": "cheesemonkey.owasp.org",
|
||||
"info": {
|
||||
"category": [
|
||||
"scan"
|
||||
],
|
||||
"descriptions": [
|
||||
"80/TCP_CONNECT"
|
||||
],
|
||||
"open_ports": [
|
||||
80
|
||||
],
|
||||
"scan_methods": [
|
||||
"port_scan"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"host": "5.79.66.240",
|
||||
"info": {
|
||||
"category": [
|
||||
"scan"
|
||||
],
|
||||
"descriptions": [
|
||||
"filesmog.com",
|
||||
"\u062f\u0631\u06af\u0627\u0647 \u0628\u0627\u0632"
|
||||
],
|
||||
"open_ports": [
|
||||
5901,
|
||||
6001,
|
||||
22
|
||||
],
|
||||
"scan_methods": [
|
||||
"viewdns_reverse_ip_lookup_scan",
|
||||
"port_scan"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"host": "5.79.66.237",
|
||||
"info": {
|
||||
"category": [
|
||||
"scan"
|
||||
],
|
||||
"descriptions": [
|
||||
"\u062f\u0631\u06af\u0627\u0647 \u0628\u0627\u0632",
|
||||
"http://5.79.66.237/robots.txt \u067e\u06cc\u062f\u0627 \u0634\u062f!(OK:200)",
|
||||
"http://5.79.66.237/.htaccess.txt \u067e\u06cc\u062f\u0627 \u0634\u062f!(Forbidden:403)",
|
||||
"http://5.79.66.237/.htaccess.save \u067e\u06cc\u062f\u0627 \u0634\u062f!(Forbidden:403)",
|
||||
"http://5.79.66.237/phpmyadmin \u067e\u06cc\u062f\u0627 \u0634\u062f!(OK:200)",
|
||||
"http://5.79.66.237/.htaccess.old \u067e\u06cc\u062f\u0627 \u0634\u062f!(Forbidden:403)",
|
||||
"http://5.79.66.237/.htaccess \u067e\u06cc\u062f\u0627 \u0634\u062f!(Forbidden:403)",
|
||||
"http://5.79.66.237/server-status \u067e\u06cc\u062f\u0627 \u0634\u062f!(Forbidden:403)",
|
||||
"http://5.79.66.237//phpmyadmin/ \u067e\u06cc\u062f\u0627 \u0634\u062f!(OK:200)",
|
||||
"http://5.79.66.237//phpMyAdmin/ \u067e\u06cc\u062f\u0627 \u0634\u062f!(OK:200)",
|
||||
"offsec.ir"
|
||||
],
|
||||
"open_ports": [
|
||||
8083,
|
||||
8000,
|
||||
443,
|
||||
80,
|
||||
22,
|
||||
21
|
||||
],
|
||||
"scan_methods": [
|
||||
"port_scan",
|
||||
"dir_scan",
|
||||
"pma_scan",
|
||||
"viewdns_reverse_ip_lookup_scan"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"host": "192.168.1.124",
|
||||
"info": {
|
||||
"category": [
|
||||
"scan"
|
||||
],
|
||||
"descriptions": [
|
||||
"2179/TCP_CONNECT",
|
||||
"445/TCP_CONNECT",
|
||||
"135/TCP_CONNECT",
|
||||
"22/TCP_CONNECT",
|
||||
"139/TCP_CONNECT",
|
||||
"zhanpang.cn",
|
||||
"yowyeh.cn",
|
||||
"treelights.website",
|
||||
"sxyhed.com",
|
||||
"redlxin.com",
|
||||
"ppoo6.com",
|
||||
"miancan.cn",
|
||||
"maynard.top",
|
||||
"liyedai.site",
|
||||
"linterfund.com",
|
||||
"li5xs.com",
|
||||
"hxinglan.win",
|
||||
"heresylly.top",
|
||||
"gzptjwangye.bid",
|
||||
"eatpeanutfree.com",
|
||||
"comgmultiservices.com",
|
||||
"biyao123.com"
|
||||
],
|
||||
"open_ports": [
|
||||
2179,
|
||||
445,
|
||||
135,
|
||||
22,
|
||||
139
|
||||
],
|
||||
"scan_methods": [
|
||||
"port_scan",
|
||||
"viewdns_reverse_ip_lookup_scan"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"host": "192.168.1.127",
|
||||
"info": {
|
||||
"category": [
|
||||
"scan"
|
||||
],
|
||||
"descriptions": [
|
||||
"49152/TCP_CONNECT",
|
||||
"49154/TCP_CONNECT",
|
||||
"49155/TCP_CONNECT",
|
||||
"49153/TCP_CONNECT"
|
||||
],
|
||||
"open_ports": [
|
||||
49152,
|
||||
49154,
|
||||
49155,
|
||||
49153
|
||||
],
|
||||
"scan_methods": [
|
||||
"port_scan"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
>>>
|
||||
```
|
||||
## Generate a HTML Scan Result for a Host
|
||||
```python
|
||||
>>> r = s.get("https://localhost:5000/logs/get_html?target=127.0.0.1&key=<your_api_key>")
|
||||
>>> print r.content[:1000]
|
||||
<!DOCTYPE html>
|
||||
<!-- THIS PAGE COPIED AND MODIFIED FROM http://bl.ocks.org/robschmuecker/7880033-->
|
||||
<title>OWASP Nettacker Report</title>
|
||||
<meta charset="utf-8">
|
||||
<div class="header">
|
||||
<h3><a href="https://github.com/zdresearch/nettacker">OWASP Nettacker</a></h3>
|
||||
<h3>Penetration Testing Graphs</h3>
|
||||
</div>
|
||||
<style type="text/css">
|
||||
|
||||
.header{
|
||||
margin:2%;
|
||||
text-align:center;
|
||||
}
|
||||
.node {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.overlay{
|
||||
background-color:#EEE;
|
||||
}
|
||||
|
||||
.node circle {
|
||||
fill: #fff;
|
||||
stroke: steelblue;
|
||||
stroke-width: 1.5px;
|
||||
}
|
||||
|
||||
.node text {
|
||||
font-size:12px;
|
||||
font-family:sans-serif;
|
||||
}
|
||||
...
|
||||
...
|
||||
>>>
|
||||
```
|
||||
|
||||
### Get the Scan Result in JSON Type
|
||||
```python
|
||||
>>> r = s.get("https://localhost:5000/logs/get_json?target=owasp.org&key=<your_api_key>")
|
||||
>>> print(json.dumps(json.loads(r.content), sort_keys=True, indent=4))
|
||||
[
|
||||
{
|
||||
"DESCRIPTION": "443/http/TCP_CONNECT",
|
||||
"HOST": "owasp.org",
|
||||
"PASSWORD": "",
|
||||
"PORT": "443",
|
||||
"SCAN_ID": "708e1dcf0f2ce9fe71038ccea7bf28bb",
|
||||
"TIME": "2020-06-09 10:36:59",
|
||||
"TYPE": "port_scan",
|
||||
"USERNAME": ""
|
||||
},
|
||||
{
|
||||
"DESCRIPTION": "80/http/TCP_CONNECT",
|
||||
"HOST": "owasp.org",
|
||||
"PASSWORD": "",
|
||||
"PORT": "80",
|
||||
"SCAN_ID": "708e1dcf0f2ce9fe71038ccea7bf28bb",
|
||||
"TIME": "2020-06-09 10:36:59",
|
||||
"TYPE": "port_scan",
|
||||
"USERNAME": ""
|
||||
},
|
||||
{
|
||||
"DESCRIPTION": "8443/http/TCP_CONNECT",
|
||||
"HOST": "owasp.org",
|
||||
"PASSWORD": "",
|
||||
"PORT": "8443",
|
||||
"SCAN_ID": "708e1dcf0f2ce9fe71038ccea7bf28bb",
|
||||
"TIME": "2020-06-09 10:38:17",
|
||||
"TYPE": "port_scan",
|
||||
"USERNAME": ""
|
||||
}
|
||||
]
|
||||
>>>
|
||||
```
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
## OWASP Nettacker Codebase Overview
|
||||
OWASP Nettacker is an open‑source, Python‑based framework for automated penetration testing and information gathering. It supports modular tasks such as port scanning, service detection, subdomain enumeration, vulnerability scans, and credential brute forcing, all driven by a unified CLI, REST API, and Web UI.
|
||||
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
.
|
||||
├── docs
|
||||
├── nettacker
|
||||
│ ├── api
|
||||
│ ├── core
|
||||
│ │ ├── lib
|
||||
│ │ └── utils
|
||||
│ ├── database
|
||||
│ ├── lib
|
||||
│ │ ├── compare_report
|
||||
│ │ ├── graph
|
||||
│ │ │ ├── d3_tree_v1
|
||||
│ │ │ └── d3_tree_v2
|
||||
│ │ ├── html_log
|
||||
│ │ ├── icmp
|
||||
│ │ └── payloads
|
||||
│ │ ├── User-Agents
|
||||
│ │ ├── passwords
|
||||
│ │ └── wordlists
|
||||
│ ├── locale
|
||||
│ ├── modules
|
||||
│ │ ├── brute
|
||||
│ │ ├── scan
|
||||
│ │ └── vuln
|
||||
│ └── web
|
||||
│ └── static
|
||||
│ ├── css
|
||||
│ ├── fonts
|
||||
│ ├── img
|
||||
│ │ └── flags
|
||||
│ │ ├── 1x1
|
||||
│ │ └── 4x3
|
||||
│ ├── js
|
||||
│ └── report
|
||||
└── tests
|
||||
├── api
|
||||
├── core
|
||||
│ ├── lib
|
||||
│ └── utils
|
||||
├── database
|
||||
└── lib
|
||||
└── payloads
|
||||
|
||||
```
|
||||
|
||||
- **Entry point** – `nettacker/main.py` creates a `Nettacker` application instance and runs it when invoked via the provided script or CLI
|
||||
- **Core engine (`nettacker/core`)**
|
||||
- `app.py` orchestrates scans: parsing arguments, expanding targets (including IP ranges and subdomains), launching multiprocess/multithread modules, and generating reports
|
||||
- `module.py` loads YAML-defined modules, applies service discovery results, expands payload loops, and dispatches protocol-specific engines in threaded fashion
|
||||
- `arg_parser.py`, `ip.py`, `messages.py`, and `utils` provide CLI parsing, IP range handling, internationalized messages, and common helpers
|
||||
- Protocol engines reside in `core/lib` (e.g., HTTP, FTP, SSH, SMTP, socket) and are invoked by modules
|
||||
- **Modules (`nettacker/modules`)** – Scanning logic is defined declaratively in YAML under three categories (`brute`, `scan`, `vuln`). Each module contains an `info` block and a list of `payloads` that specify library, request parameters, fuzzing rules, and response conditions. Example: `dir_scan` performs directory discovery over HTTP using wordlists and response conditions
|
||||
- **Database layer (`nettacker/database`)** – Uses SQLAlchemy to interface with SQLite, MySQL, or PostgreSQL for persisting events and reports
|
||||
- **API & Web UI (`nettacker/api`, `nettacker/web`)** – Flask-based REST API plus static assets enabling web‑based scan management
|
||||
- **Supporting libraries (`nettacker/lib`)** – Reporting helpers, ICMP tools, graph generation, and payload wordlists
|
||||
- **Configuration** – `config.py` defines default paths, database settings, and runtime options
|
||||
- **Tests** – The `tests` directory includes unit tests and validation checks; for example, `test_yaml_regexes.py` ensures regex definitions in YAML modules compile correctly
|
||||
- **Build & dependencies** – `pyproject.toml` defines the project as a Poetry package and lists dependencies such as `aiohttp`, `multiprocess`, `paramiko`, and `sqlalchemy`
|
||||
|
||||
## Important concepts
|
||||
- **Modular architecture:** Modules are YAML files; the engine interprets them and runs protocol-specific steps.
|
||||
- **Target expansion:** Before scanning, the engine normalizes URLs, enumerates IP ranges, resolves subdomains, and runs preliminary checks like ICMP and port scans
|
||||
- **Service discovery:** Results from `port_scan` feed into subsequent modules, allowing conditional execution based on discovered services. Service discovery can be turned off during scans using `-d` or `--skip-service-discovery` run-time option.
|
||||
- **Concurrency:** Scans are distributed across processes and threads for performance, with configurable limits per host and module using the `-t` and `-M` runtime options. The requests can be rate-limited using the `-w` option.
|
||||
|
||||
## Where to go next
|
||||
- **Documentation:** Review `docs/Installation.md` and `docs/Usage.md` for setup and basic usage; `docs/Modules.md` explains module types and parameters; `docs/Developers.md` covers contribution guidelines and how to add languages or modules
|
||||
- **Explore modules:** Study YAML files under `nettacker/modules/*` to see how scans, brute-force checks, and vulnerability tests are structured.
|
||||
- **Understand protocol engines:** Read files in `nettacker/core/lib/` to learn how HTTP, socket, and other protocol interactions are implemented.
|
||||
- **Run locally:** Use the CLI (`nettacker`) or Docker instructions in [Installation](Installation.md) and [Usage](Usage.md)
|
||||
- **Contribute:** Follow the guidelines in `docs/Developers.md` and run `make pre-commit` and `make test` before submitting changes.
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
People who helped to create the OWASP Nettacker. You can see the complete list of Developers on [OpenHub](https://www.openhub.net/p/OWASP-Nettacker/contributors) and [GitHub](https://github.com/OWASP/Nettacker/graphs/contributors) contributors.
|
||||
|
||||
### Leaders & Mentors
|
||||
* [Ali Razmjoo Qalaei](mailto:ali.razmjoo@owasp.org)
|
||||
* [Arkadii Yakovets](mailto:arkadii.yakovets@owasp.org)
|
||||
* [Sam Stepanyan](mailto:sam.stepanyan@owasp.org)
|
||||
* [Sri Harsha Gajavalli](mailto:sriharsha.g@owasp.org)
|
||||
|
||||
|
||||
|
||||
### Contributors
|
||||
* [Shaddy Garg](mailto:shaddygarg1@gmail.com)
|
||||
* [Pradeep Jairamani](mailto:pradeepjairamani22@gmail.com)
|
||||
* [Hannah Brand](mailto:bran0793@umn.edu)
|
||||
* [Vahid Behzadan](mailto:behzadan@ksu.edu)
|
||||
* [Mohammad Reza Zamiri](mailto:mr.zamiri@ieee.org)
|
||||
* [Mojtaba MasoumPour](mailto:mojtaba6892@gmail.com)
|
||||
* [Ehsan Nezami](mailto:ehsan.empire1@gmail.com)
|
||||
* [camel32bit](https://github.com/camel32bit)
|
||||
* [Ravindra Sharma](mailto:sha.ravindra1307@gmail.com)
|
||||
* [Harshavardhan Reddy](mailto:harsha010@outlook.com)
|
||||
* [ArianPH](mailto:pandkhahiarian@gmail.com)
|
||||
* [omdmhd](mailto:om.mo1375@gmail.com)
|
||||
* [Mahdi Rasouli](mailto:mahdirasouli007@gmail.com)
|
||||
* [Tikam Singh Alma](mailto:timonalma81@gmail.com)
|
||||
* [Jecky](mailto:ht974@nyu.edu)
|
||||
* [VictorSuraj](https://github.com/VictorSuraj)
|
||||
* [Clarence Cromwell](mailto:clarencewcromwell@gmail.com)
|
||||
* [Aman Gupta](mailto:aman.gupta@owasp.org)
|
||||
* [Kunal Khandelwal](mailto:khandelwal.kunal4@gmail.com)
|
||||
* [Pinaki Mondal aka 0xInfection](https://github.com/0xInfection)
|
||||
* [Divyansh Jain](https://github.com/itsdivyanshjain)
|
||||
* [Akshay Behl](https://github.com/Captain-T2004)
|
||||
* **[FULL LIST](https://github.com/OWASP/Nettacker/graphs/contributors)**
|
||||
|
||||
|
||||
|
||||
The OWASP Nettacker Project Team is very grateful to Google's Summer of Code (GSoC - summerofcode.withgoogle.com) and to all GSoC students who helped to enhance Nettacker while working during their summer break!
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
We gladly support and appreciate anyone who is interested in contributing to OWASP Nettacker. Overall contributors may focus on developing core framework, modules or payloads, language libraries, and media. After reading this document you should be able to get the basic knowledge to start developing. Please note that we are using PEP8 Python code style and [GitHub Actions](https://github.com/OWASP/Nettacker/actions) for checking all new PRs automatically against supported Python versions. If you use any code/library/module with a license, add the license into external license file.
|
||||
|
||||
* [Code of Conduct](https://github.com/OWASP/Nettacker/blob/master/CODE_OF_CONDUCT.md)
|
||||
* [Issue Template](https://github.com/OWASP/Nettacker/blob/master/.github/ISSUE_TEMPLATE.md)
|
||||
* [PR Template](https://github.com/OWASP/Nettacker/blob/master/.github/PULL_REQUEST_TEMPLATE.md)
|
||||
* [License](https://github.com/OWASP/Nettacker/blob/master/LICENSE)
|
||||
* [External Licenses](https://github.com/OWASP/Nettacker/blob/master/EXTERNAL_LIBRARIES_LICENSES.md)
|
||||
|
||||
________
|
||||
|
||||
* [Contribution Guidelines](#contribution-guidelines)
|
||||
* [Roadmap](#roadmap)
|
||||
* [Creating Media](#creating-media)
|
||||
* [Contribute to Language Libraries](#contribute-to-language-libraries)
|
||||
* [Add a New Language Library](#add-a-new-language-library)
|
||||
* [Modify/Update Language Libraries](#modify-update-language-libraries)
|
||||
|
||||
# Contribution Guidelines
|
||||
|
||||
These are the guidelines you need to keep in mind while contributing:
|
||||
|
||||
* Start by familiarising yourself with the Nettacker Codebase: [Codebase Overview](CodebaseOverview.md)
|
||||
* Use the automated checks: run `make pre-commit` and `make test`
|
||||
* Thoroughly test your code locally.
|
||||
* Be sure to add/update related documentation.
|
||||
|
||||
In case of any doubts regarding the guidelines please contact the project leaders.
|
||||
|
||||
# Roadmap
|
||||
|
||||
Developers always could be aware of the OWASP Nettacker roadmap by checking
|
||||
|
||||
* 1- Project Management Page <https://github.com/OWASP/Nettacker/projects>
|
||||
* 2- Issues Page <https://github.com/OWASP/OWASP-Nettacker/issues>
|
||||
|
||||
# Creating Media
|
||||
|
||||
We appreciated all kind of media to demonstrate the OWASP Nettacker in any language and environment. It is a great activity to help us grow our framework and get more publicity. Currently, we collected a few media on [Media](https://github.com/OWASP/Nettacker/wiki/Media) page. Feel free to post your Media on [this](https://github.com/OWASP/Nettacker/issues/1) page.
|
||||
|
||||
# Contribute to Language Libraries
|
||||
|
||||
OWASP Nettacker is using multi-language libraries (default English) to create a better user experience. Currently we are supporting `Greek/el`, `French/fr`, `English/en`, `Dutch/nl`, `Pashto/ps`, `Turkish/tr`, `German/de`, `Korean/ko`, `Italian/it`, `Japanese/ja`, `Persian/fa`, `Armenian/hy`, `Arabic/ar`, `Chinese(Simplified)/zh-cn`, `Vietnamese/vi`, `Russian/ru`, `Hindi/hi`, `Urdu/ur`, `Indonesian/id`, `Spanish/es`, `Hebrew/iw`) languages. If you are an expert in one these languages, It would be a great favor to contribute to one of these. If any language you want to contribute is not listed, feel free to follow the below steps to add it.
|
||||
|
||||
## Add a New Language Library
|
||||
|
||||
In some cases language library does not exist, you can create a new file and add it to the framework.
|
||||
|
||||
* 1- Goto `nettacker/locale`
|
||||
* 2- Name your message library in accordance with the ISO two-letter code e.g. `fa.yaml`
|
||||
* 3- Copy the default language lib (`en.yaml`) and start your translation.
|
||||
* 4- **Please notice that you should not change the key-value like `scan_started`, `options` and etc. you just need to modify the Values.**
|
||||
|
||||
## Modify/Update Language Libraries
|
||||
|
||||
To contribute to the existing libraries, You may go to `lib/messages` select the file you want to contribute and
|
||||
|
||||
* 1- Translate English messages to the selected language.
|
||||
* 2- Compare the language library with **English** library and add new messages to this library and translate them.
|
||||
* 3- Modify the translated messages to better translations.
|
||||
|
||||
# Contribute to Modules
|
||||
|
||||
Modules exist in path `nettacker/modules/module_category`. Currently, we have three categories (scan, brute, vuln). if you need to add more just create a directory with a name! To start a new module you should understand what kind of protocol you want to use. The list of protocols and module functionalities are in `core/module_protocols`. To understand how they work read the below example.
|
||||
|
||||
```yaml
|
||||
info: # this section is to store information about module
|
||||
name: dir_scan
|
||||
author: OWASP Nettacker Team
|
||||
severity: 3
|
||||
description: Directory, Backup finder
|
||||
reference: https://www.zaproxy.org/docs/alerts/10095/
|
||||
profiles: # module will be added to below profiles and user can use --profile scan to run this and other modules in same profile
|
||||
- scan
|
||||
- http
|
||||
- backup
|
||||
- low_severity
|
||||
|
||||
payloads: # this section stores the payloads
|
||||
- library: http # the time of library, you can use multiple library if needed as an array
|
||||
verify: false
|
||||
timeout: 3
|
||||
cert: ""
|
||||
stream: false
|
||||
proxies: ""
|
||||
steps:
|
||||
- method: get # type of request
|
||||
headers: # headers
|
||||
User-Agent: "{user_agent}" # this will be replaced by default user-agent or user input
|
||||
URL: # URL is the input we want to fuzz
|
||||
nettacker_fuzzer:
|
||||
input_format: "{{schema}}://{target}:{{ports}}/{{urls}}" # format of url
|
||||
prefix: ""
|
||||
suffix: ""
|
||||
interceptors:
|
||||
data:
|
||||
urls:
|
||||
- "administrator"
|
||||
- "admin"
|
||||
- "old"
|
||||
- "_vti_bin"
|
||||
- "_private"
|
||||
- "cgi-bin"
|
||||
- "public_html"
|
||||
- "images"
|
||||
schema:
|
||||
- "http"
|
||||
- "https"
|
||||
ports:
|
||||
- 80
|
||||
- 443
|
||||
response: # response will check if the payload were success
|
||||
condition_type: or # could be and/or
|
||||
conditions: # could be in header/content/status_code/reason/timeresponse
|
||||
status_code:
|
||||
regex: 200|403|401
|
||||
reverse: false # if true, it will reverse the regex
|
||||
|
||||
|
||||
```
|
||||
|
||||
The `http` protocol uses exactly the same inputs as the python `requests` library. if we want to convert the yaml code to python requests it will be:
|
||||
|
||||
```python
|
||||
In [5]: import requests
|
||||
|
||||
In [6]: lib=requests
|
||||
|
||||
In [7]: lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:443/url", headers={'User-Agent': 'whatever'})
|
||||
```
|
||||
|
||||
The inputs such as `ports` will be replaced by user input and 80,443 is just a default value to hold in case the user did not enter any ports. you can see all user inputs from `config.py`.
|
||||
|
||||
Any value that comes in an array in the YAML files will be treated as a loop and it will regenerate the request until all loops are finished.
|
||||
|
||||
```python
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:443/url1", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:443/url2", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:443/url3", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:443/url4", headers={'User-Agent': 'whatever'})
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```python
|
||||
dynamics: http, https, url1, url2 , url3, url4, port 80, port 443
|
||||
# https
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:443/url1", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:443/url2", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:443/url3", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:443/url4", headers={'User-Agent': 'whatever'})
|
||||
# http
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="http://www.owasp.org:80/url1", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="http://www.owasp.org:80/url2", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="http://www.owasp.org:80/url3", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="http://www.owasp.org:80/url4", headers={'User-Agent': 'whatever'})
|
||||
|
||||
# https on 80
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:80/url1", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:80/url2", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:80/url3", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="https://www.owasp.org:80/url4", headers={'User-Agent': 'whatever'})
|
||||
|
||||
# http on 443
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="http://www.owasp.org:443/url1", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="http://www.owasp.org:443/url2", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="http://www.owasp.org:443/url3", headers={'User-Agent': 'whatever'})
|
||||
lib.get(verify=False, timeout=3, cert="", stream=False, proxies="", url="http://www.owasp.org:443/url4", headers={'User-Agent': 'whatever'})
|
||||
|
||||
|
||||
```
|
||||
|
||||
# Contribute to Code Functionality & API & WebUI
|
||||
|
||||
Go nuts!
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# Events
|
||||
The OWASP Nettacker Events page lists various conferences and meetups where the OWASP Nettacker project has been presented. These include notable appearances at OFFSECONF 2017, BlackHat Europe, OWASP Global AppSec and Security BSides Dublin and Athens, among others. The page provides links to presentations, webinars, podcasts, and video content from these events:
|
||||
|
||||
|
||||
* OWASP Nettacker was introduced in **OFFSECONF 2017** [[1](https://groups.google.com/forum/#!topic/owasp-nettacker/3gscDww2sf4)]
|
||||
* OFFSECONF 2017 Introduction Presentation [[1](https://drive.google.com/file/d/1Ox1xpvncPgSZPaFjvTQvkOwxP3to7Rqk/view?usp=sharing)]
|
||||
* OWASP Nettacker Accepted for **Google Summer of Code 2018** [[1](https://www.owasp.org/index.php/GSOC2018_Ideas)] [[2](https://summerofcode.withgoogle.com/organizations/6664778743808000/)]
|
||||
* OWASP Nettacker Video Conference/Webinar for GSoC Team 1 May 2018 - **Vahid Behzadan - ML/AI in CyberSecurity** [[1](https://www.youtube.com/watch?v=7RQH8oECSyg)]
|
||||
* **Shaddy Garg**'s GSoC Experince [[1](https://medium.com/@shaddygarg/google-summer-of-code-final-submission-12eb98993ba8)]
|
||||
* **Pradeep Jairamani**'s GSoC Experince [[1](https://medium.com/@pradeepjairamani/google-summer-of-code-final-submission-7a498856c914)]
|
||||
* OWASP Nettacker Tutorial by at **OWASP Bay Area** meetup (Presented by **Vahid Behzadan** - Sponsered by **OWASP Bay Area**) [[1](https://www.youtube.com/watch?v=4pu4hJMk6m8)]
|
||||
* OWASP Nettacker Presented By Ali Razmjoo in OWASP Iran Chapter Meeting July 2018 [[1](https://www.owasp.org/index.php/Iran#tab=Past_Events)]
|
||||
* OWASP Nettacker ICS Section Presented in **P0SCON 2018 By Mohammad Reza Zamiri** [[1](http://www.poscon.ir/)]
|
||||
* OWASP Nettacker ICS Section will be presented in **KasperSky Industrial Cybersecurity**: Opportunities and challenges in Digital Transformation 2018 by **Mohammad Reza Zamiri** [[1](https://github.com/zdresearch/OWASP-Nettacker/tree/master/lib/payload/scanner/ics_honeypot)] [[2](https://ics.kaspersky.com/conference/)]
|
||||
* OWASP Nettacker was presented at **BlackHat Europe Arsenal 2018** by Sam Stepanyan and Dr Grigorios "Greg" Fragkos
|
||||
* OWASP Nettacker was presented at **BlackHat Europe Arsenal 2019** (more In-Depth Demo) by Paul Harragan and Sam Stepanyan [[1](https://www.blackhat.com/eu-19/arsenal/schedule/#owasp-nettacker-updated---more-in-depth-demo-18100)]
|
||||
* OWASP Nettacker was presented at **AppSec California 2020** by **Sam Stepanyan** [[1](https://appseccalifornia2020.sched.com/event/XLtt/introducing-the-owasp-nettacker-project?iframe=no&w=100%&sidebar=yes&bg=no)] [[2](https://youtu.be/rZfCFFewfiU)]
|
||||
* OWASP Nettacker was presented at **OWASP Chapters All-Day** conference (June 7th 2020) by **Sam Stepanyan** [[1](https://youtu.be/-klGZ7AaMc4)]
|
||||
* OWASP Nettacker was presented at **BSides Athens 2020** conference by **Sam Stepanyan** [[1](https://youtu.be/vNNDC_ScxCA)]
|
||||
* OWASP Nettacker was presented at **AppSecIL 2020** conference by **Sam Stepanyan** [[1](https://appsecil2020.sched.com/event/fF7u/using-owasp-nettacker-for-recon-and-vulnerability-scanning)]
|
||||
* OWASP Nettacker was presented at **BlackHat Asia 2020** conference by **Sam Stepanyan** [[1](https://www.blackhat.com/asia-20/arsenal/schedule/#owasp-nettacker-19079)]
|
||||
* OWASP Nettacker Presentation Slides (as presented at **OWASP Jakarta** 2021 event): [[1](https://speakerdeck.com/samstepanyanowasp/using-owasp-nettacker-project-for-recon-and-vulnerability-scanning)]
|
||||
* OWASP Nettacker was presented at **OWASP Ottawa** Chapter by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=HvXPcByShgI)]
|
||||
* OWASP Nettacker was presented at **OWASP Kyiv** Chapter by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=KrwQlgeZn7I)]
|
||||
* OWASP Nettacker was presented at the **AppSec Engineer** session by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=eXzIPuTtqAQ)]
|
||||
* OWASP Nettacker was presented at **Security BSides Dublin 2022** conference by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=GcRFkZEaWqI)]
|
||||
* OWASP Nettacker was presented at the **Application Security Podcast** by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=tqZ8Lmucujw)]
|
||||
* OWASP Nettacker was presented at the **OWASP Global AppSec DC 2023 Conference** by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=yZxjBme029A)]
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
## Introduction
|
||||
|
||||
OWASP Nettacker is an automated penetration testing framework designed to help cyber security professionals and ethical hackers perform reconnaissance, vulnerability assessments, and network security audits efficiently.
|
||||
|
||||
Nettacker automates information gathering, vulnerability scanning, and credential brute forcing tasks, making it a powerful tool for identifying weaknesses in networks, web applications, IoT devices and APIs.
|
||||
|
||||
OWASP Nettacker is an open-source software written in Python language. OWASP Nettacker uses YAML files to define **modules** in a structured and human-readable format.
|
||||
|
||||
OWASP Nettacker's modular architecture is one of its core strengths, allowing users to perform specific tasks by leveraging a range of pre-built and customizable modules.
|
||||
|
||||
By leveraging a modular framework, Nettacker supports multiple protocols and scanning methods, making it highly adaptable to various security testing scenarios.
|
||||
|
||||
## Key Features
|
||||
|
||||
1. Multi-Protocol Support
|
||||
OWASP Nettacker can scan a wide range of protocols, including HTTP/HTTPS, FTP, SSH, SMTP, ICMP, TELNET, XML-RPC and more.
|
||||
This flexibility allows users to assess diverse systems and applications effectively.
|
||||
2. Automation of Information Gathering Security Tests
|
||||
With Nettacker, users can automate reconnaissance, port scanning, vulnerability detection, and brute forcing workflows, minimizing the time and effort required for manual security testing.
|
||||
3. Modular and Scalable
|
||||
Its modular design enables users to customize and extend functionality by adding new modules for specific tasks. Nettacker can scale from small, targeted security assessments to large, enterprise-wide scans.
|
||||
4. Built-In Port Scanner and Subdomain Enumeration module
|
||||
Nettacker includes powerful Built-In Port Scanner and Subdomain Enumeration modules that streamline the initial stages of penetration testing. The Port Scanner module automatically identifies open ports on target systems, providing valuable insights into the services and potential attack surfaces exposed by a system. This is crucial for mapping a network and targeting specific services during vulnerability assessments. The Subdomain Enumeration module helps uncover hidden subdomains within a domain, which can be critical for identifying additional attack vectors or overlooked assets. Together, these built-in modules simplify the reconnaissance phase, helping security professionals gather key information efficiently before moving on to more advanced testing.
|
||||
5. Multi-Format Reporting
|
||||
The tool generates scan reports in multiple formats, including HTML, JSON, CSV and text. Nettacker’s ability to generate reports in JSON and CSV formats offers significant advantages. JSON provides a structured, machine-readable format that is easily parsed and integrated with other tools or systems, making it ideal for automated processing, data analysis, and integration with custom workflows. CSV, on the other hand, offers a simple, tabular format that is easy to read and process using spreadsheets or other data analysis tools. These formats make it easy to analyze findings and share results with stakeholders.
|
||||
6. Built-in Database
|
||||
Nettacker includes a built-in database for storing scan results. This ensures data persistence, allowing users to track past assessments, easily search and retrieve previous data from scan results, and generate reports for audit and compliance purposes
|
||||
6. The Web UI and API provide enhanced user interaction and integration capabilities. The Web UI offers a user-friendly interface for configuring scans, visualizing results, andsearching the scan data, making Nettacker accessible to both technical and less-technical users. The API allows for programmatic access, enabling automation and integration with third-party tools, CI/CD pipelines, and custom applications.
|
||||
|
||||
|
||||
## Links
|
||||
|
||||
* OWASP Nettacker Project Page: [https://www.owasp.org/nettacker](https://www.owasp.org/nettacker)
|
||||
* GitHub Repo: [https://github.com/OWASP/Nettacker](https://github.com/OWASP/Nettacker)
|
||||
* Official Docker Image: [https://hub.docker.com/r/owasp/nettacker/](https://hub.docker.com/r/owasp/nettacker/)
|
||||
* Slack: **#project-nettacker** on https://owasp.slack.com (get OWASP Slack invite at https://owasp.org/slack/invite)
|
||||
|
||||
* OpenHub: [https://www.openhub.net/p/OWASP-Nettacker](https://www.openhub.net/p/OWASP-Nettacker)
|
||||
* CI: [https://github.com/OWASP/Nettacker/actions](https://github.com/OWASP/Nettacker/actions)
|
||||
* **Donate to support this project**: [https://www.owasp.org/](https://owasp.org/donate/?reponame=www-project-nettacker&title=OWASP+Nettacker)
|
||||
* Original Creator/Maintainer: [https://www.secologist.com/](https://www.secologist.com/)
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
# Installation
|
||||
|
||||
You have multiple options for installing OWASP Nettacker, each with specific instructions provided in dedicated sections below.
|
||||
|
||||
|
||||
### Supported Platforms
|
||||
|
||||
OWASP Nettacker is designed to run on Linux and macOS systems. However, you can leverage the Docker image to run it on other operating systems as well. Although native Windows support was initially dropped, we are currently working towards reintroducing it in future versions, along with FreeBSD support.
|
||||
|
||||
PLEASE NOTE: Starting from Nettacker version 0.3.1 the support for Python2 and Python <3.10 has been dropped. If you have a requirement to use Nettacker on Python 2.x or 3.0-3.9 you can use the legacy version of Nettacker [v0.0.2](https://github.com/OWASP/Nettacker/releases/tag/0.0.2)
|
||||
|
||||
|
||||
PLEASE NOTE: Python version 3.10-3.12 is required to run Nettacker. You can check the version of Python3 installed by running:
|
||||
|
||||
```
|
||||
python3 -V
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Pre-requisites
|
||||
|
||||
OWASP Nettacker depends on several libraries and tools which you might need to install if they are not already installed on your system:
|
||||
|
||||
* python3-dev
|
||||
* python3-pip
|
||||
* libcurl4-openssl-dev
|
||||
* libcurl4-gnutls-dev
|
||||
* librtmp-dev
|
||||
* libssl-dev
|
||||
* libpq-dev (required if you wish to use PostgreSQL database)
|
||||
* libffi-dev
|
||||
* musl-dev
|
||||
* make
|
||||
* gcc
|
||||
* git
|
||||
|
||||
Before using this software, please install the prerequisites by following the commands below):
|
||||
|
||||
|
||||
Install Python3, PIP and VENV first (e.g. on Debian Linux/Ubuntu):
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3 python3-dev python3-pip python3-venv
|
||||
pip3 install --upgrade pip3
|
||||
```
|
||||
|
||||
|
||||
### Install Nettacker From PyPI Using PIPX
|
||||
|
||||
Installing OWASP Nettacker using `pipx` is a convenient method for managing Python applications with isolated environments. `pipx` ensures that each installed tool has its own environment, avoiding dependency conflicts.
|
||||
|
||||
Here’s how you can install OWASP Nettacker using `pipx`:
|
||||
|
||||
1. Install pipx using apt or pip
|
||||
|
||||
|
||||
Using apt:
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt install pipx
|
||||
pipx ensurepath
|
||||
pipx --version
|
||||
```
|
||||
or install pipx using using pip:
|
||||
|
||||
```
|
||||
python3 -m pip install --user pipx
|
||||
python3 -m pipx ensurepath
|
||||
```
|
||||
|
||||
2. Install **nettacker** using pipx
|
||||
```
|
||||
pipx install nettacker
|
||||
nettacker --help
|
||||
```
|
||||
### Install Nettacker from PyPI using PIP
|
||||
|
||||
|
||||
Starting from version 0.4.0 Nettacker and can be installed directly from PyPI.
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt install python3-venv python3-pip
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
pip3 install nettacker
|
||||
nettacker --help
|
||||
```
|
||||
|
||||
### Install Nettacker using Git Clone and PIP
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt install python3-venv python3-pip git
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
git clone https://github.com/OWASP/Nettacker --depth 1
|
||||
cd Nettacker
|
||||
pip3 install .
|
||||
python3 nettacker.py --help
|
||||
```
|
||||
|
||||
You can also run Nettacker after installation like this:
|
||||
|
||||
```
|
||||
nettacker --help
|
||||
```
|
||||
|
||||
### Install Nettacker using Git Clone and Poetry
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
sudo apt install python3-poetry git
|
||||
git clone https://github.com/OWASP/Nettacker --depth 1
|
||||
cd Nettacker
|
||||
poetry install
|
||||
poetry run nettacker --help
|
||||
```
|
||||
|
||||
### What Happened to requirements.txt in Nettacker?
|
||||
|
||||
In recent updates to OWASP Nettacker, the project has transitioned away from using the traditional `requirements.txt` file for dependency management. Starting from version 0.4.0, Nettacker adopted Poetry as its package manager instead of the `requirements.txt` file. Poetry simplifies dependency management, handling both the installation of dependencies and packaging more efficiently.
|
||||
|
||||
Now, the dependencies for Nettacker are listed in `pyproject.toml`, which is a modern PEP 518 standard. `pyproject.toml` is also used by Poetry package manager, and the installation process follows a different approach:
|
||||
|
||||
You can install Nettacker directly from PyPI with the command:
|
||||
`pip3 install nettacker`
|
||||
or if you have already cloned Nettacker git repo you can run:
|
||||
|
||||
`pip install .`
|
||||
|
||||
inside the Nettacker folder.
|
||||
|
||||
|
||||
To see the list of command options you can use:
|
||||
|
||||
```
|
||||
nettacker --help
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
nettacker -h
|
||||
```
|
||||
|
||||
### Install Nettacker Using Docker
|
||||
|
||||
```
|
||||
docker pull owasp/nettacker
|
||||
docker run -it owasp/nettacker /bin/bash
|
||||
```
|
||||
|
||||
For usage instructions and examples please read [Usage.md](Usage.md)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
0. _**In the beginning, there was … GIT**_:
|
||||
Clone the latest revision of Nettacker
|
||||
|
||||
`> git clone https://github.com/zdresearch/OWASP-Nettacker.git && cd OWASP-Nettacker && pip install -r requirements.txt`
|
||||
|
||||
Make sure it works - in the command line, go to the root folder of Nettacker and run the following:
|
||||
|
||||
`> python nettacker.py -h`
|
||||
|
||||
1. _**Let there be targets:**_
|
||||
For the purposes of this tutorial, we have created a shooting range somewhere in the realm of z3r0d4y.com . Let's see what subdomains are available there:
|
||||
|
||||
`> python nettacker.py -i z3r0d4y.com -m subdomain_scan`
|
||||
|
||||
What sorcery is this? To see behind the curtains:
|
||||
|
||||
`> python nettacker.py -i z3r0d4y.com -m subdomain_scan -v 5`
|
||||
|
||||
OK, but which of these is smells fishier than others? Let's run a quick port scan:
|
||||
|
||||
`python nettacker.py -i z3r0d4y.com -m port_scan`
|
||||
|
||||
2. _**For thou shalt p4wn**_: Hmm... that "tg1" fellow smells funny. Let's see what its port 80 has to say:
|
||||
|
||||
`open tg1.z3r0d4y.com in browser`
|
||||
|
||||
A login page - we shall knock on its door soon. But now: Shall we see if we can bruteforce our way into its SSH service?
|
||||
|
||||
`> python nettacker.py -i tg1.z3r0d4y.com -m ssh_brute -T 10 -v 5`
|
||||
|
||||
Cool. Now, comrades, let us go back to the gates of the login page on port 80...
|
||||
|
||||
Shall we write our own fuzzer to brute force our way into this login page? Why not... https://drive.google.com/open?id=1aFgKrdzhV6jb9HDi7LvrM9fjCd8n_hly
|
||||
|
||||
Now that we are in, let's see what else is lurking in these dark corners. Notice the URL. Shall we fuzz and see if exploration in the numerical realm gets us anywhere? Why not... https://drive.google.com/open?id=1bG01UT5_VApHFLLf3FD8VV1o_lu_pRC1
|
||||
|
||||
Ok, we now have the IP address of an internal docker and access to the WebUI of Nettacker installed there. Let's play.
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
Gathered a few media to show how to work with OWASP Nettacker.
|
||||
|
||||
Simple Usage
|
||||
============
|
||||

|
||||
|
||||
API Usage
|
||||
=========
|
||||

|
||||
|
||||
Wizard Usage
|
||||
=============
|
||||

|
||||
|
||||
Youtube
|
||||
=======
|
||||
* Created By Volunteers
|
||||
|
||||
[](https://www.youtube.com/watch?v=2XQiA7fEFck)
|
||||
|
||||
* Created By Volunteers
|
||||
|
||||
[](https://www.youtube.com/watch?v=EUb8q0Whx4s)
|
||||
|
||||
* Created By Volunteers
|
||||
|
||||
[](https://www.youtube.com/watch?v=MnCOpiLY0Xc)
|
||||
|
||||
* Created By Volunteers
|
||||
|
||||
[](https://www.youtube.com/watch?v=6trmP4xn2Sw)
|
||||
|
||||
* Created By Volunteers
|
||||
|
||||
[](https://www.youtube.com/watch?v=cZKQja2YO3A)
|
||||
|
||||
* Created By Volunteers
|
||||
|
||||
[](https://www.youtube.com/watch?v=BF7G763xIKM)
|
||||
|
||||
|
||||
Feel free to send your media to us to share it in here.
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
# Nettacker Modules aka 'Methods'
|
||||
|
||||
OWASP Nettacker Modules can be of type **Scan** (scan for something), **Vuln** (check for some vulnerability) and **Brute** (Brute force)
|
||||
- [Scan Modules](#scan-modules)
|
||||
- [Ports Scanned by Nettacker](#ports-scanned-by-nettacker)
|
||||
- [Vuln Modules](#vuln-modules)
|
||||
- [Brute Modules](#brute-modules)
|
||||
|
||||
## Scan Modules
|
||||
|
||||
* '**adobe_aem_lastpatcheddate_scan**' - Scan the target for Adobe Experience Manager (AEM) and return its last patched date
|
||||
* '**admin_scan**' - Scan the target for various Admin folders such as /admin /phpmyadmin /cmsadmin /wp-admin etc
|
||||
* '**citrix_lastpatcheddate_scan**' Scan the target and try to detect Citrix Netscaler Gateway and it's last patched date
|
||||
* '**cms_detection_scan**' - Scan the target and try to detect the CMS (Wordpress, Drupal or Joomla) using response fingerprinting
|
||||
* '**confluence_version_scan**' - Scan the target and identify the Confluence version
|
||||
* '**crushftp_lastpatcheddate_scan**' - Scan the target and try to detect CrushFTP and its last patched date
|
||||
* '**cups_version_scan**' - Scan the target and identify the CUPS version (on port 631)
|
||||
* '**dir_scan**' - Scan the target for well-known directories
|
||||
* '**drupal_modules_scan**' - Scan the target for popular Drupal modules
|
||||
* '**drupal_theme_scan**' - Scan the target for popular Drupal themes
|
||||
* '**drupal_version_scan**' - Scan the target and identify the Drupal version
|
||||
* '**icmp_scan**' - Ping the target and log the response time if it responds.
|
||||
* '**http_redirect_scan**' - Scan the target and test if it returns an HTTP redirect 3xx response code and print the destination
|
||||
* '**http_status_scan**' - Scan the target and return the HTTP status code
|
||||
* '**ivanti_csa_lastpatcheddate_scan**' - Scan the target for Ivanti CSA appliance and return its last patched date
|
||||
* '**ivanti_vtm_version_scan**' - Scan the target for Ivanti vTM appliance and return its version number
|
||||
* '**joomla_template_scan**' - Scan the target for Joomla templates (identify Joomla sites)
|
||||
* '**joomla_user_enum_scan**' - Scan the target and enumerate Joomla users
|
||||
* '**joomla_version_scan**' - Scan the target and identify the Joomla version
|
||||
* '**moveit_version_scan**' - Scan the target and identify the Progress MOVEit version
|
||||
* '**pma_scan**' - Scan the target for PHP MyAdmin presence
|
||||
* '**port_scan**' - Scan the target for open ports identifying the popular services using signatures (.e.g SSH on port 2222)
|
||||
* '**sender_policy_scan**' - Scan the target domains/subdomains for SPF policy settings
|
||||
* '**shodan_scan**' - Scan the target domains/subdomains/IP in Shodan. Put your Shodan API key i "shodan_api_key" method arg, "shodan_query_override" to run any Shodan query overriding the Nettacker target
|
||||
* '**subdomain_scan**' - Scan the target for subdomains (target must be a domain e.g. owasp.org)
|
||||
* '**viewdns_reverse_ip_lookup_scan**' - Identify which sites/domains are hosted on the target host using ViewDNS.info
|
||||
* '**wappalyzer_scan**' - Scan the target and try to identify the technologies and libraries used using Wappalyzer
|
||||
* '**wordpress_version_scan**' - Scan the target and identify the WordPress version
|
||||
* '**wp_plugin_scan**' - Scan the target for popular WordPress Plugins
|
||||
* '**wp_theme_scan**' - Scan the target for popular WordPress themes
|
||||
* '**wp_timthumbs_scan**' - Scan the target for WordPress TimThumb.php script in various possible locations
|
||||
* '**wp_user_enum_scan**' - Scan the target WordPress site and Enumerate Users
|
||||
|
||||
|
||||
## Ports Scanned by Nettacker
|
||||
If you want to scan all ports please define -g 1-65535 range. Otherwise Nettacker will scan for these 1000 most popular ports:
|
||||
|
||||
|
||||
`[1, 3, 4, 6, 7, 9, 13, 17, 19, 20, 21, 22, 23, 24, 25, 26, 30, 32, 33, 37, 42,`
|
||||
`43, 49, 53, 67, 68, 69, 70, 79, 80, 81, 82, 83, 84, 85, 88, 89, 90, 99, 100, 106, 109, 110,`
|
||||
`111, 113, 119, 125, 135, 139, 143, 144, 146, 161, 162, 163, 179, 199, 211, 212, 222,`
|
||||
`254, 255, 256, 259, 264, 280, 301, 306, 311, 340, 366, 389, 406, 407, 416, 417,`
|
||||
`425, 427, 443, 444, 445, 458, 464, 465, 481, 497, 500, 512, 513, 514, 515, 524,`
|
||||
`541, 543, 544, 545, 548, 554, 555, 563, 587, 593, 616, 617, 625, 631, 636, 646,`
|
||||
`648, 666, 667, 668, 683, 687, 691, 700, 705, 711, 714, 720, 722, 726, 749, 765,`
|
||||
`777, 783, 787, 800, 801, 808, 843, 873, 880, 888, 898, 900, 901, 902, 903, 911,`
|
||||
`912, 981, 987, 990, 992, 993, 995, 999, 1000, 1001, 1002, 1007, 1009, 1010,`
|
||||
`1011, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032,`
|
||||
`1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045,`
|
||||
`1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058,`
|
||||
`1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071,`
|
||||
`1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084,`
|
||||
`1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097,`
|
||||
`1098, 1099, 1100, 1102, 1104, 1105, 1106, 1107, 1108, 1110, 1111, 1112, 1113,`
|
||||
`1114, 1117, 1119, 1121, 1122, 1123, 1124, 1126, 1130, 1131, 1132, 1137, 1138,`
|
||||
`1141, 1145, 1147, 1148, 1149, 1151, 1152, 1154, 1163, 1164, 1165, 1166, 1169,`
|
||||
`1174, 1175, 1183, 1185, 1186, 1187, 1192, 1198, 1199, 1201, 1213, 1216, 1217,`
|
||||
`1218, 1233, 1234, 1236, 1244, 1247, 1248, 1259, 1271, 1272, 1277, 1287, 1296,`
|
||||
`1300, 1301, 1309, 1310, 1311, 1322, 1328, 1334, 1352, 1417, 1433, 1434, 1443,`
|
||||
`1455, 1461, 1494, 1500, 1501, 1503, 1521, 1524, 1533, 1556, 1580, 1583, 1594,`
|
||||
`1600, 1641, 1658, 1666, 1687, 1688, 1700, 1717, 1718, 1719, 1720, 1721, 1723,`
|
||||
`1755, 1761, 1782, 1783, 1801, 1805, 1812, 1839, 1840, 1862, 1863, 1864, 1875,`
|
||||
`1900, 1914, 1935, 1947, 1971, 1972, 1974, 1984, 1998, 1999, 2000, 2001, 2002,`
|
||||
`2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013, 2020, 2021, 2022, 2030,`
|
||||
`2033, 2034, 2035, 2038, 2040, 2041, 2042, 2043, 2045, 2046, 2047, 2048, 2049,`
|
||||
`2065, 2068, 2099, 2100, 2103, 2105, 2106, 2107, 2111, 2119, 2121, 2126, 2135,`
|
||||
`2144, 2160, 2161, 2170, 2179, 2190, 2191, 2196, 2200, 2222, 2251, 2260, 2288,`
|
||||
`2301, 2323, 2366, 2381, 2382, 2383, 2393, 2394, 2399, 2401, 2492, 2500, 2522,`
|
||||
`2525, 2557, 2601, 2602, 2604, 2605, 2607, 2608, 2638, 2701, 2702, 2710, 2717,`
|
||||
`2718, 2725, 2800, 2809, 2811, 2869, 2875, 2909, 2910, 2920, 2967, 2968, 2998,`
|
||||
`3000, 3001, 3003, 3005, 3006, 3007, 3011, 3013, 3017, 3030, 3031, 3052, 3071,`
|
||||
`3077, 3128, 3168, 3211, 3221, 3260, 3261, 3268, 3269, 3283, 3300, 3301, 3306,`
|
||||
`3322, 3323, 3324, 3325, 3333, 3351, 3367, 3369, 3370, 3371, 3372, 3389, 3390,`
|
||||
`3404, 3476, 3493, 3517, 3527, 3546, 3551, 3580, 3659, 3689, 3690, 3703, 3737,`
|
||||
`3766, 3784, 3800, 3801, 3809, 3814, 3826, 3827, 3828, 3851, 3869, 3871, 3878,`
|
||||
`3880, 3889, 3905, 3914, 3918, 3920, 3945, 3971, 3986, 3995, 3998, 4000, 4001,`
|
||||
`4002, 4003, 4004, 4005, 4006, 4045, 4111, 4125, 4126, 4129, 4224, 4242, 4279,`
|
||||
`4321, 4343, 4443, 4444, 4445, 4446, 4449, 4550, 4567, 4662, 4848, 4899, 4900,`
|
||||
`4998, 5000, 5001, 5002, 5003, 5004, 5009, 5030, 5033, 5050, 5051, 5054, 5060,`
|
||||
`5061, 5080, 5087, 5100, 5101, 5102, 5120, 5190, 5200, 5214, 5221, 5222, 5225,`
|
||||
`5226, 5269, 5280, 5298, 5357, 5405, 5414, 5431, 5432, 5440, 5500, 5510, 5544,`
|
||||
`5550, 5555, 5560, 5566, 5631, 5633, 5666, 5678, 5679, 5718, 5730, 5800, 5801,`
|
||||
`5802, 5810, 5811, 5815, 5822, 5825, 5850, 5859, 5862, 5877, 5900, 5901, 5902,`
|
||||
`5903, 5904, 5906, 5907, 5910, 5911, 5915, 5922, 5925, 5950, 5952, 5959, 5960,`
|
||||
`5961, 5962, 5963, 5987, 5988, 5989, 5998, 5999, 6000, 6001, 6002, 6003, 6004,`
|
||||
`6005, 6006, 6007, 6009, 6025, 6059, 6100, 6101, 6106, 6112, 6123, 6129, 6156,`
|
||||
`6346, 6389, 6502, 6510, 6543, 6547, 6565, 6566, 6567, 6580, 6646, 6666, 6667,`
|
||||
`6668, 6669, 6689, 6692, 6699, 6779, 6788, 6789, 6792, 6839, 6881, 6901, 6969,`
|
||||
`7000, 7001, 7002, 7004, 7007, 7019, 7025, 7070, 7100, 7103, 7106, 7200, 7201,`
|
||||
`7402, 7435, 7443, 7496, 7512, 7625, 7627, 7676, 7741, 7777, 7778, 7800, 7911,`
|
||||
`7920, 7921, 7937, 7938, 7999, 8000, 8001, 8002, 8007, 8008, 8009, 8010, 8011,`
|
||||
`8021, 8022, 8031, 8042, 8045, 8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087,`
|
||||
`8088, 8089, 8090, 8093, 8099, 8100, 8180, 8181, 8192, 8193, 8194, 8200, 8222,`
|
||||
`8254, 8290, 8291, 8292, 8300, 8333, 8383, 8400, 8402, 8443, 8500, 8600, 8649,`
|
||||
`8651, 8652, 8654, 8701, 8800, 8873, 8888, 8899, 8994, 9000, 9001, 9002, 9003,`
|
||||
`9009, 9010, 9011, 9040, 9050, 9071, 9080, 9081, 9090, 9091, 9099, 9100, 9101,`
|
||||
`9102, 9103, 9110, 9111, 9200, 9207, 9220, 9290, 9415, 9418, 9485, 9500, 9502,`
|
||||
`9503, 9535, 9575, 9593, 9594, 9595, 9618, 9666, 9876, 9877, 9878, 9898, 9900,`
|
||||
`9917, 9929, 9943, 9944, 9968, 9998, 9999, 10000, 10001, 10002, 10003, 10004,`
|
||||
`10009, 10010, 10012, 10024, 10025, 10082, 10180, 10215, 10243, 10566, 10616,`
|
||||
`10617, 10621, 10626, 10628, 10629, 10778, 11110, 11111, 11967, 12000, 12174,`
|
||||
`12265, 12345, 13456, 13722, 13782, 13783, 14000, 14238, 14441, 14442, 15000,`
|
||||
`15002, 15003, 15004, 15660, 15742, 16000, 16001, 16012, 16016, 16018, 16080,`
|
||||
`16113, 16992, 16993, 17877, 17988, 18040, 18101, 18988, 19101, 19283, 19315,`
|
||||
`19350, 19780, 19801, 19842, 20000, 20005, 20031, 20221, 20222, 20828, 21571,`
|
||||
`22939, 23502, 24444, 24800, 25734, 25735, 26214, 27000, 27352, 27353, 27355,`
|
||||
`27356, 27715, 28201, 30000, 30718, 30951, 31038, 31337, 32768, 32769, 32770,`
|
||||
`32771, 32772, 32773, 32774, 32775, 32776, 32777, 32778, 32779, 32780, 32781,`
|
||||
`32782, 32783, 32784, 32785, 33354, 33899, 34571, 34572, 34573, 35500, 38292,`
|
||||
`40193, 40911, 41511, 42510, 44176, 44442, 44443, 44501, 45100, 48080, 49152,`
|
||||
`49153, 49154, 49155, 49156, 49157, 49158, 49159, 49160, 49161, 49163, 49165,`
|
||||
`49167, 49175, 49176, 49400, 49999, 50000, 50001, 50002, 50003, 50006, 50300,`
|
||||
`50389, 50500, 50636, 50800, 51103, 51493, 52673, 52822, 52848, 52869, 54045,`
|
||||
`54328, 55055, 55056, 55555, 55600, 56737, 56738, 57294, 57797, 58080, 60020,`
|
||||
`60443, 61532, 61900, 62078, 63331, 64623, 64680, 65000, 65129, 65389]`
|
||||
|
||||
|
||||
|
||||
## Vuln Modules
|
||||
|
||||
* '**apache_ofbiz_cve_2024_38856**' - check the target for Apache OFBiz CVE-2024-38856
|
||||
* '**apache_struts_vuln**' - check Apache Struts for CVE-2017-5638
|
||||
* '**Bftpd_double_free_vuln**' - check bftpd for CVE-2007-2010
|
||||
* '**Bftpd_memory_leak_vuln**' - check bftpd for CVE-2017-16892
|
||||
* '**Bftpd_parsecmd_overflow_vuln**'- check bftpd for CVE-2007-2051
|
||||
* '**Bftpd_remote_dos_vuln**' - check bftpd for CVE-2009-4593
|
||||
* '**CCS_injection_vuln**' - check SSL for Change Cipher Spec (CCS Injection) CVE-2014-0224
|
||||
* '**citrix_cve_2019_19781_vuln**' - check the target for Citrix CVE-2019-19781 vulnerability
|
||||
* '**citrix_cve_2023_24488_vuln**' - check the target for Citrix CVE-2023-24488 XSS vulnerability
|
||||
* '**clickjacking_vuln**' - check the web server for missing 'X-Frame-Options' header (clickjacking protection)
|
||||
* '**content_security_policy_vuln**' - check the web server for missing 'Content-Security-Policy' header
|
||||
* '**content_type_options_vuln**' - check the web server for missing 'X-Content-Type-Options'=nosniff header
|
||||
* '**crushftp_cve_2025_31161_vuln**' - check the target for CrushFTP CVE-2025-31161 vulnerability
|
||||
* '**f5_cve_2020_5902_vuln**' - check the target for F5 RCE CVE-2020-5902 vulnerability
|
||||
* '**heartbleed_vuln**' - check SSL for Heartbleed vulnerability (CVE-2014-0160)
|
||||
* '**msexchange_cve_2021_26855**' - check the target for MS Exchange SSRF CVE-2021-26855 (proxylogon/hafnium)
|
||||
* '**http_cors_vuln**' - check the web server for overly-permissive CORS (header 'Access-Control-Allow-Origin'=*)
|
||||
* '**options_method_enabled_vuln**' - check if OPTIONS method is enabled on the web server
|
||||
* '**paloalto_panos_cve_2025_0108_vuln**' - check the target for PaloAlto PAN-OS CVE-2025-0108 vulnerability
|
||||
* '**paloalto_globalprotect_cve_2025_0133_vuln**' - check the target for PaloAlto GlobalProtect CVE-2025-0133 XSS vulnerability
|
||||
* '**ProFTPd_bypass_sqli_protection_vuln**' - check ProFTPd for CVE-2009-0543
|
||||
* '**ProFTPd_cpu_consumption_vuln**' - check ProFTPd for CVE-2008-7265
|
||||
* '**ProFTPd_directory_traversal_vuln**' - check ProFTPd for CVE-2010-3867
|
||||
* '**ProFTPd_exec_arbitary_vuln**' - check ProFTPd for CVE-2011-4130
|
||||
* '**ProFTPd_heap_overflow_vuln**' - check ProFTPd for CVE-2010-4652
|
||||
* '**ProFTPd_integer_overflow_vuln**' - check ProFTPd for CVE-2011-1137
|
||||
* '**ProFTPd_memory_leak_vuln**' - check ProFTPd for CVE-2001-0136
|
||||
* '**ProFTPd_restriction_bypass_vuln**' - check ProFTPd for CVE-2009-3639
|
||||
* '**server_version_vuln**' - check if the web server is leaking server banner in 'Server' response header
|
||||
* '**sonicwall_sslvpn_cve_2024_53704_vuln**' - check the target for SonicWALL SSLVPN CVE-2024-53704 vulnerability
|
||||
* '**ssl_signed_certificate_vuln**' - check for self-signed & other signing issues(weak signing algorithm) in SSL certificate
|
||||
* '**ssl_expired_certificate_vuln**' - check if SSL certificate has expired or is close to expiring
|
||||
* '**ssl_version_vuln**' - check if the server's SSL configuration supports old and insecure SSL versions
|
||||
* '**ssl_weak_cipher_vuln**' - check if server's SSL configuration supports weak cipher suites
|
||||
* '**wordpress_dos_cve_2018_6389_vuln**' - check if Wordpress is vulnerable to CVE-2018-6389 Denial Of Service (DOS)
|
||||
* '**wp_plugin_cve_2023_47668_vuln**' - check the target for CVE-2023-47668
|
||||
* '**wp_xmlrpc_bruteforce_vuln**' - check if Wordpress is vulnerable to credential Brute Force via XMLRPC wp.getUsersBlogs
|
||||
* '**wp_xmlrpc_pingback_vuln**' - check if Wordpress is vulnerable to XMLRPC pingback
|
||||
* '**x_powered_by_vuln**' - check if the web server is leaking server configuration in 'X-Powered-By' response header
|
||||
* '**xdebug_rce_vuln**' - checks if web server is running XDebug version 2.5.5 vulnerable to RCE
|
||||
* '**XSS_protection_vuln**' - check if header 'X-XSS-Protection' header is set to '1; mode=block'
|
||||
* '**vbulletin_cve_2019_16759_vuln**' - check the target for vBulletin RCE CVE-2019-16759 vulnerability
|
||||
|
||||
## Brute Modules
|
||||
|
||||
If no extra users/passwords parameters are specified the following default usernames will be used on brute force checks: ["admin", "root", "test", "ftp", "anonymous", "user", "support", "1"] with the following passwords: ["admin", "root", "test", "ftp", "anonymous", "user", "1", "12345",123456", "124567", "12345678", "123456789", "1234567890", "admin1", "password!@#", "support", "1qaz2wsx", "qweasd", "qwerty", "!QAZ2wsx","password1", "1qazxcvbnm", "zxcvbnm", "iloveyou", "password", "p@ssw0rd","admin123", ""]
|
||||
|
||||
* '**ftp_brute**' - try to brute force FTP users.
|
||||
* '**http_basic_auth_brute**' - try to brute for HTTP Basic Auth users.
|
||||
* '**http_form_brute**' - try to brute force using HTTP form - assuming that the form has 'username' and 'password' fields
|
||||
* '**http_ntlm_brute**' - try to brute force using HTTP NTLM
|
||||
* '**smtp_brute**' - - try to brute force SMTP (ports ["25", "465", "587"])
|
||||
* '**ssh_brute**' - try to brute force SSH (port 22)
|
||||
* '**telnet_brute**' - try to brute force via telnet (port23) (expects "login" and "Password" prompt)
|
||||
* '**wp_xmlrpc_brute**' - try to brute force Wordpress users using XMLRPC and wp.getUsersBlogs method
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Documentation
|
||||
|
||||
OWASP Nettacker documentaion is now available on ReadTheDocs: [https://nettacker.readthedocs.io](https://nettacker.readthedocs.io)
|
||||
|
|
@ -0,0 +1,703 @@
|
|||
# Help Menu
|
||||
|
||||
- [Target inputs Option](#target-inputs-option)
|
||||
* [Command Examples](#command-examples)
|
||||
- [API and WebUI](#api-and-webui)
|
||||
* [API Options](#api-options)
|
||||
* [API Examples](#api-examples)
|
||||
- [Database](#database)
|
||||
* [SQLite configuration](#sqlite-configuration)
|
||||
* [MySQL configuration](#mysql-configuration)
|
||||
- [Maltego Transforms](#maltego-transforms)
|
||||
|
||||
By using the `--help`/`-h` switch you can read the help menu in the CLI:
|
||||
`python3 nettacker.py --help`
|
||||
|
||||
|
||||
|
||||
* Note: This example may not reflect the latest version.
|
||||
|
||||
```
|
||||
______ __ _____ _____
|
||||
/ __ \ \ / /\ / ____| __ \
|
||||
| | | \ \ /\ / / \ | (___ | |__) |
|
||||
| | | |\ \/ \/ / /\ \ \___ \| ___/
|
||||
| |__| | \ /\ / ____ \ ____) | | Version 0.4.0
|
||||
\____/ \/ \/_/ \_\_____/|_| QUIN
|
||||
_ _ _ _ _
|
||||
| \ | | | | | | | |
|
||||
github.com/OWASP | \| | ___| |_| |_ __ _ ___| | _____ _ __
|
||||
owasp.org | . ` |/ _ \ __| __/ _` |/ __| |/ / _ \ '__|
|
||||
z3r0d4y.com | |\ | __/ |_| || (_| | (__| < __/ |
|
||||
|_| \_|\___|\__|\__\__,_|\___|_|\_\___|_|
|
||||
|
||||
[2024-09-26 07:51:08][+] Nettacker engine started ...
|
||||
[2024-09-26 07:51:09][+] 106 modules loaded ...
|
||||
usage: Nettacker [-L LANGUAGE] [-v] [--verbose-event] [-V] [-o REPORT_PATH_FILENAME] [--graph GRAPH_NAME] [-h]
|
||||
[-i TARGETS] [-l TARGETS_LIST] [-m SELECTED_MODULES] [--modules-extra-args MODULES_EXTRA_ARGS]
|
||||
[--show-all-modules] [--profile PROFILES] [--show-all-profiles] [-x EXCLUDED_MODULES]
|
||||
[-X EXCLUDED_PORTS] [-u USERNAMES] [-U USERNAMES_LIST] [-p PASSWORDS] [-P PASSWORDS_LIST] [-g PORTS]
|
||||
[--user-agent USER_AGENT] [-T TIMEOUT] [-w TIME_SLEEP_BETWEEN_REQUESTS] [-r] [-s] [-d] [-t THREAD_PER_HOST]
|
||||
[--show-all-modules] [--profile PROFILES] [--show-all-profiles] [-x EXCLUDED_MODULES] [-H HTTP_HEADER]
|
||||
[-M PARALLEL_MODULE_SCAN] [--set-hardware-usage SET_HARDWARE_USAGE] [-R SOCKS_PROXY]
|
||||
[--retries RETRIES] [--ping-before-scan] [-K SCAN_COMPARE_ID] [-J COMPARE_REPORT_PATH_FILENAME]
|
||||
[--start-api] [--api-host API_HOSTNAME] [--api-port API_PORT] [--api-debug-mode]
|
||||
[--api-access-key API_ACCESS_KEY] [--api-client-whitelisted-ips API_CLIENT_WHITELISTED_IPS]
|
||||
[--api-access-log API_ACCESS_LOG] [--api-cert API_CERT] [--api-cert-key API_CERT_KEY]
|
||||
|
||||
Engine:
|
||||
Engine input options
|
||||
|
||||
-L LANGUAGE, --language LANGUAGE
|
||||
select a language ['iw', 'nl', 'es', 'ru', 'de', 'ur', 'pt-br', 'fr', 'el', 'hy', 'ko', 'en',
|
||||
'ja', 'bn', 'it', 'tr', 'ar', 'zh-cn', 'hi', 'vi', 'id', 'fa', 'ps']
|
||||
-v, --verbose verbose mode level (0-5) (default 0)
|
||||
--verbose-event enable verbose event to see state of each thread
|
||||
-V, --version show software version
|
||||
-o REPORT_PATH_FILENAME, --output REPORT_PATH_FILENAME
|
||||
save all logs in file (results.txt, results.csv, results.html, results.json, results.sarif, results.dd.json)
|
||||
--graph GRAPH_NAME build a graph of all activities and information, you must use HTML output. available graphs:
|
||||
['d3_tree_v2_graph', 'd3_tree_v1_graph']
|
||||
-h, --help Show Nettacker Help Menu
|
||||
|
||||
Target:
|
||||
Target input options
|
||||
|
||||
-i TARGETS, --targets TARGETS
|
||||
target(s) list, separate with ","
|
||||
-l TARGETS_LIST, --targets-list TARGETS_LIST
|
||||
read target(s) from file
|
||||
|
||||
Method:
|
||||
Scan method options
|
||||
|
||||
-m SELECTED_MODULES, --modules SELECTED_MODULES
|
||||
choose modules ['accela_cve_2021_34370_vuln', 'admin_scan',
|
||||
'adobe_coldfusion_cve_2023_26360_vuln', 'apache_cve_2021_41773_vuln',
|
||||
'apache_cve_2021_42013_vuln', 'apache_ofbiz_cve_2024_38856_vuln', 'apache_struts_vuln',
|
||||
'aviatrix_cve_2021_40870_vuln', 'cisco_hyperflex_cve_2021_1497_vuln',
|
||||
'citrix_cve_2019_19781_vuln'] to see full list use --show-all-modules
|
||||
--modules-extra-args MODULES_EXTRA_ARGS
|
||||
add extra args to pass to modules (e.g. --modules-extra-args "x_api_key=123&xyz_passwd=abc"
|
||||
--show-all-modules show all modules and their information
|
||||
--profile PROFILES select profile ['accela', 'adobe', 'apache', 'apache_ofbiz', 'apache_struts', 'atlassian',
|
||||
'aviatrix', 'backup', 'brute']
|
||||
--show-all-profiles show all profiles and their information
|
||||
-x EXCLUDED_MODULES, --exclude-modules EXCLUDED_MODULES
|
||||
choose scan method to exclude ['accela_cve_2021_34370_vuln', 'admin_scan',
|
||||
'adobe_coldfusion_cve_2023_26360_vuln', 'apache_cve_2021_41773_vuln',
|
||||
'apache_cve_2021_42013_vuln', 'apache_ofbiz_cve_2024_38856_vuln', 'apache_struts_vuln',
|
||||
'aviatrix_cve_2021_40870_vuln', 'cisco_hyperflex_cve_2021_1497_vuln']
|
||||
-X EXCLUDED_PORTS, --exclude-ports
|
||||
Ports to exclude (e.g. 80 || 80,443|| 1000-1300)
|
||||
-u USERNAMES, --usernames USERNAMES
|
||||
username(s) list, separate with ","
|
||||
-U USERNAMES_LIST, --users-list USERNAMES_LIST
|
||||
read username(s) from file
|
||||
-p PASSWORDS, --passwords PASSWORDS
|
||||
password(s) list, separate with ","
|
||||
-P PASSWORDS_LIST, --passwords-list PASSWORDS_LIST
|
||||
read password(s) from file
|
||||
-g PORTS, --ports PORTS
|
||||
port(s) list, separate with ","
|
||||
--user-agent USER_AGENT
|
||||
Select a user agent to send with HTTP requests or enter "random_user_agent" to randomize the
|
||||
User-Agent in the requests.
|
||||
-T TIMEOUT, --timeout TIMEOUT
|
||||
read password(s) from file
|
||||
-w TIME_SLEEP_BETWEEN_REQUESTS, --time-sleep-between-requests TIME_SLEEP_BETWEEN_REQUESTS
|
||||
time to sleep between each request
|
||||
-r, --range scan all IPs in the range
|
||||
-s, --sub-domains find and scan subdomains
|
||||
-d, --skip-service-discovery
|
||||
skip service discovery before scan and enforce all modules to scan anyway
|
||||
-t THREAD_PER_HOST, --thread-per-host THREAD_PER_HOST
|
||||
thread numbers for connections to a host
|
||||
-M PARALLEL_MODULE_SCAN, --parallel-module-scan PARALLEL_MODULE_SCAN
|
||||
parallel module scan for hosts
|
||||
--set-hardware-usage SET_HARDWARE_USAGE
|
||||
Set hardware usage while scanning. (low, normal, high, maximum)
|
||||
-R SOCKS_PROXY, --socks-proxy SOCKS_PROXY
|
||||
outgoing connections proxy (socks). example socks5: 127.0.0.1:9050, socks://127.0.0.1:9050
|
||||
socks5://127.0.0.1:9050 or socks4: socks4://127.0.0.1:9050, authentication: socks://username:
|
||||
password@127.0.0.1, socks4://username:password@127.0.0.1, socks5://username:password@127.0.0.1
|
||||
--retries RETRIES Retries when the connection timeout (default 3)
|
||||
--ping-before-scan ping before scan the host
|
||||
-K SCAN_COMPARE_ID, --scan-compare SCAN_COMPARE_ID
|
||||
compare current scan to old scans using the unique scan_id
|
||||
-J COMPARE_REPORT_PATH_FILENAME, --compare-report-path COMPARE_REPORT_PATH_FILENAME
|
||||
the file-path to store the compare_scan report
|
||||
-H HTTP_HEADER, --add-http-header HTTP_HEADER
|
||||
Add custom HTTP headers to requests (format: 'key: value'). For multiple headers, use multiple -H flags
|
||||
|
||||
|
||||
API:
|
||||
API options
|
||||
|
||||
--start-api start the API service
|
||||
--api-host API_HOSTNAME
|
||||
API host address
|
||||
--api-port API_PORT API port number
|
||||
--api-debug-mode API debug mode
|
||||
--api-access-key API_ACCESS_KEY
|
||||
API access key
|
||||
--api-client-whitelisted-ips API_CLIENT_WHITELISTED_IPS
|
||||
define white list hosts, separate with , (examples: 127.0.0.1, 192.168.0.1/24,
|
||||
10.0.0.1-10.0.0.255)
|
||||
--api-access-log API_ACCESS_LOG
|
||||
API access log filename
|
||||
--api-cert API_CERT API CERTIFICATE
|
||||
--api-cert-key API_CERT_KEY
|
||||
API CERTIFICATE Key
|
||||
|
||||
|
||||
Please read license and agreements https://github.com/OWASP/Nettacker%
|
||||
```
|
||||
|
||||
## Language Selection
|
||||
|
||||
You can choose from 21 languages when using Nettacker. Use the language flag:
|
||||
`$ nettacker -L fa`
|
||||
|
||||
The `-L` is the language flag and in this case sets the output language to Farsi, indicated by the `fa`. Farsi and 20 other languages are available, as listed in the command line help: `el`, `fr`, `en`, `nl`, `ps`, `tr`, `de`, `ko`, `it`, `ja`, `fa`, `hy`, `ar`, `zh-cn`, `vi`, `ru`, `hi`, `ur`, `id`, `es`, `iw`.
|
||||
|
||||
* Your CLI must support Unicode to make use of multiple languages. Search the web for "How to use Farsi on cmd/terminal."
|
||||
* You can fix Persian (Farsi) and other Unicode languages RTL and Chars with [bicon](https://www.google.com/search?q=Persian+support+with+bicon&oq=Persian+support+with+bicon&aqs=chrome..69i57.178j0j7&sourceid=chrome&ie=UTF-8) in terminal/windows bash.
|
||||
```
|
||||
$ python nettacker.py --help -L fa
|
||||
______ __ _____ _____
|
||||
/ __ \ \ / /\ / ____| __ \
|
||||
| | | \ \ /\ / / \ | (___ | |__) |
|
||||
| | | |\ \/ \/ / /\ \ \___ \| ___/
|
||||
| |__| | \ /\ / ____ \ ____) | | Version 0.4.0
|
||||
\____/ \/ \/_/ \_\_____/|_| QUIN
|
||||
_ _ _ _ _
|
||||
| \ | | | | | | | |
|
||||
github.com/OWASP | \| | ___| |_| |_ __ _ ___| | _____ _ __
|
||||
owasp.org | . ` |/ _ \ __| __/ _` |/ __| |/ / _ \ '__|
|
||||
z3r0d4y.com | |\ | __/ |_| || (_| | (__| < __/ |
|
||||
|_| \_|\___|\__|\__\__,_|\___|_|\_\___|_|
|
||||
|
||||
[2024-09-26 07:53:24][+] انجین Nettacker آغاز به کار کرد ...
|
||||
|
||||
|
||||
[2024-09-26 07:53:25][+] 106 ماژول بارگزاری شد ...
|
||||
usage: Nettacker [-L LANGUAGE] [-v] [--verbose-event] [-V] [-o REPORT_PATH_FILENAME] [--graph GRAPH_NAME] [-h]
|
||||
[-i TARGETS] [-l TARGETS_LIST] [-m SELECTED_MODULES] [--modules-extra-args MODULES_EXTRA_ARGS]
|
||||
[--show-all-modules] [--profile PROFILES] [--show-all-profiles] [-x EXCLUDED_MODULES] [-u USERNAMES]
|
||||
[-U USERNAMES_LIST] [-p PASSWORDS] [-P PASSWORDS_LIST] [-g PORTS] [--user-agent USER_AGENT]
|
||||
[-T TIMEOUT] [-w TIME_SLEEP_BETWEEN_REQUESTS] [-r] [-s] [-d] [-t THREAD_PER_HOST]
|
||||
[-M PARALLEL_MODULE_SCAN] [--set-hardware-usage SET_HARDWARE_USAGE] [-R SOCKS_PROXY]
|
||||
[--retries RETRIES] [--ping-before-scan] [-K SCAN_COMPARE_ID] [-J COMPARE_REPORT_PATH_FILENAME]
|
||||
[--start-api] [--api-host API_HOSTNAME] [--api-port API_PORT] [--api-debug-mode]
|
||||
[--api-access-key API_ACCESS_KEY] [--api-client-whitelisted-ips API_CLIENT_WHITELISTED_IPS]
|
||||
[--api-access-log API_ACCESS_LOG] [--api-cert API_CERT] [--api-cert-key API_CERT_KEY]
|
||||
|
||||
انجین:
|
||||
گزینه های ورودی انجین
|
||||
|
||||
-L LANGUAGE, --language LANGUAGE
|
||||
یک زبان انتخاب کنید ['bn', 'de', 'nl', 'iw', 'es', 'pt-br', 'ar', 'tr', 'el', 'ko', 'ru', 'hi',
|
||||
'it', 'en', 'fr', 'id', 'ps', 'ur', 'zh-cn', 'hy', 'fa', 'ja', 'vi']
|
||||
-v, --verbose سطح حالت پرگویی (0-5) (پیشفرض 0)
|
||||
--verbose-event enable verbose event to see state of each thread
|
||||
-V, --version نمایش ورژن نرم افزار
|
||||
-o REPORT_PATH_FILENAME, --output REPORT_PATH_FILENAME
|
||||
ذخیره کردن کل لاگ ها در فایل (results.txt، results.html، results.csv, results.json, results.sarif, results.dd.json)
|
||||
--graph GRAPH_NAME ساخت گراف از همه فعالیت ها و اطلاعات، شما باید از خروجی HTML استفاده کنید. گراف های در دسترس:
|
||||
['d3_tree_v1_graph', 'd3_tree_v2_graph']
|
||||
-h, --help نشان دادن منوی کمک Nettacker
|
||||
|
||||
هدف:
|
||||
گزینه های ورودی هدف
|
||||
|
||||
-i TARGETS, --targets TARGETS
|
||||
لیست هدف (ها)، با "," جدا کنید
|
||||
-l TARGETS_LIST, --targets-list TARGETS_LIST
|
||||
خواندن هدف (ها) از فایل
|
||||
|
||||
متود:
|
||||
گزینه های متود های اسکن
|
||||
|
||||
-m SELECTED_MODULES, --modules SELECTED_MODULES
|
||||
متود اسکن را انتخاب کنید ['accela_cve_2021_34370_vuln', 'admin_scan',
|
||||
'adobe_coldfusion_cve_2023_26360_vuln', 'apache_cve_2021_41773_vuln',
|
||||
'apache_cve_2021_42013_vuln', 'apache_ofbiz_cve_2024_38856_vuln', 'apache_struts_vuln',
|
||||
'aviatrix_cve_2021_40870_vuln', 'cisco_hyperflex_cve_2021_1497_vuln',
|
||||
'citrix_cve_2019_19781_vuln']
|
||||
--modules-extra-args MODULES_EXTRA_ARGS
|
||||
add extra args to pass to modules (e.g. --modules-extra-args "x_api_key=123&xyz_passwd=abc"
|
||||
--show-all-modules show all modules and their information
|
||||
--profile PROFILES انتخاب پروفایل ['accela', 'adobe', 'apache', 'apache_ofbiz', 'apache_struts', 'atlassian',
|
||||
'aviatrix', 'backup', 'brute']
|
||||
--show-all-profiles show all profiles and their information
|
||||
-x EXCLUDED_MODULES, --exclude-modules EXCLUDED_MODULES
|
||||
انتخاب متود اسکن استثنا ['accela_cve_2021_34370_vuln', 'admin_scan',
|
||||
'adobe_coldfusion_cve_2023_26360_vuln', 'apache_cve_2021_41773_vuln',
|
||||
'apache_cve_2021_42013_vuln', 'apache_ofbiz_cve_2024_38856_vuln', 'apache_struts_vuln',
|
||||
'aviatrix_cve_2021_40870_vuln', 'cisco_hyperflex_cve_2021_1497_vuln']
|
||||
-u USERNAMES, --usernames USERNAMES
|
||||
لیست نام کاربری (ها)، با "," جدا شود
|
||||
-U USERNAMES_LIST, --users-list USERNAMES_LIST
|
||||
خواندن نام کاربری (ها) از لیست
|
||||
-p PASSWORDS, --passwords PASSWORDS
|
||||
لیست کلمه عبور (ها)، با "," جدا شود
|
||||
-P PASSWORDS_LIST, --passwords-list PASSWORDS_LIST
|
||||
خواندن کلمه عبور (ها) از فایل
|
||||
-g PORTS, --ports PORTS
|
||||
لیست درگاه (ها)، با "," جدا شود
|
||||
--user-agent USER_AGENT
|
||||
Select a user agent to send with HTTP requests or enter "random_user_agent" to randomize the
|
||||
User-Agent in the requests.
|
||||
-T TIMEOUT, --timeout TIMEOUT
|
||||
خواندن کلمه عبور (ها) از فایل
|
||||
-w TIME_SLEEP_BETWEEN_REQUESTS, --time-sleep-between-requests TIME_SLEEP_BETWEEN_REQUESTS
|
||||
زمان مکث بین هر درخواست
|
||||
-r, --range اسکن تمام آی پی ها در رنج
|
||||
-s, --sub-domains پیدا کردن و اسکن کردن ساب دامین ها
|
||||
-d, --skip-service-discovery
|
||||
skip service discovery before scan and enforce all modules to scan anyway
|
||||
-t THREAD_PER_HOST, --thread-per-host THREAD_PER_HOST
|
||||
تعداد ریسه ها برای ارتباطات با یک هاست
|
||||
-M PARALLEL_MODULE_SCAN, --parallel-module-scan PARALLEL_MODULE_SCAN
|
||||
parallel module scan for hosts
|
||||
--set-hardware-usage SET_HARDWARE_USAGE
|
||||
Set hardware usage while scanning. (low, normal, high, maximum)
|
||||
-R SOCKS_PROXY, --socks-proxy SOCKS_PROXY
|
||||
پراکسی ارتباطات خروجی (socks) مثال: 127.0.0.1:9050، socks://127.0.0.1:9050،
|
||||
socks5:127.0.0.1:9050 یا socks4: socks4://127.0.0.1:9050, احراز هویت:
|
||||
socks://username:password@127.0.0.1, socks4://username:password@127.0.0.1,
|
||||
socks5://username:password@127.0.0.1
|
||||
--retries RETRIES سعی مجدد وقتی که ارتباط قطع شد (پیشفرض 3)
|
||||
--ping-before-scan پینگ کردن هست قبل از اسکن
|
||||
-K SCAN_COMPARE_ID, --scan-compare SCAN_COMPARE_ID
|
||||
compare current scan to old scans using the unique scan_id
|
||||
-J COMPARE_REPORT_PATH_FILENAME, --compare-report-path COMPARE_REPORT_PATH_FILENAME
|
||||
the file-path to store the compare_scan report
|
||||
|
||||
API:
|
||||
API گزینه های
|
||||
|
||||
--start-api شروع سرویس API
|
||||
--api-host API_HOSTNAME
|
||||
آدرس هاست API
|
||||
--api-port API_PORT شماره درگاه API
|
||||
--api-debug-mode حالت اشکال زدایی API
|
||||
--api-access-key API_ACCESS_KEY
|
||||
کلید دسترسی API
|
||||
--api-client-whitelisted-ips API_CLIENT_WHITELISTED_IPS
|
||||
تعریف کردن لیست سفید، با "," جدا کنید (مثال: 127.0.0.1, 192.168.1.1/24, 10.0.0.1-10.0.0.255)
|
||||
--api-access-log API_ACCESS_LOG
|
||||
اسم فایل لیست دسترسی به API
|
||||
--api-cert API_CERT API CERTIFICATE
|
||||
--api-cert-key API_CERT_KEY
|
||||
API CERTIFICATE Key
|
||||
|
||||
|
||||
لطفا مجوز و موافقت نامه را مطالعه فرمایید https://github.com/OWASP/Nettacker
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
# Target inputs Option
|
||||
|
||||
* OWASP Nettacker supports several types of targets, including `IPv4`, `IPv4_Range`, `IPv4_CIDR`, `DOMAIN`, and `HTTP` (which may be useful for some of the modules).
|
||||
|
||||
## Command Examples
|
||||
```
|
||||
192.168.1.1
|
||||
192.168.1.1-192.168.255.255
|
||||
192.168.1.1.1-192.255.255.255
|
||||
192.168.1.1/24
|
||||
owasp.org
|
||||
http://owasp.org
|
||||
https://owasp.org
|
||||
```
|
||||
|
||||
* Targets can be read from a list by using the `-l` or `--target-list` command or you can split them with a comma if you don't want to use a text list.
|
||||
|
||||
```
|
||||
python nettacker.py -i 192.168.1.1,192.168.1.2-192.168.1.10,127.0.0.1,owasp.org,192.168.2.1/24 -m port_scan -g 20-100 -t 10
|
||||
python nettacker.py -l targets.txt -m all -x port_scan -g 20-100 -t 5 -u root -p 123456,654321,123123
|
||||
python nettacker.py -l targets.txt -m all -t 100 -d -u root -p 12345,432123 -X 80
|
||||
```
|
||||
|
||||
* Here are some more command line examples:
|
||||
```
|
||||
python nettacker.py -i 192.168.1.1/24 -m port_scan -t 10 -M 35 -g 20-100 --graph d3_tree_v2_graph -o result.html
|
||||
python nettacker.py -i 192.168.1.1/24 -m port_scan -t 10 -M 35 -g 20-100 -o file.html --graph jit_circle_v1_graph
|
||||
python nettacker.py -i 192.168.1.1/24 -m all -t 10 -M 35 -g 20-100 -o result.json -u root,user -P passwords.txt
|
||||
python nettacker.py -i 192.168.1.1/24 -m all -x ssh_brute -t 10 -M 35 -g 20-100 -o file.txt -U users.txt -P passwords.txt -T 3 -w 2
|
||||
```
|
||||
|
||||
* Using Whatcms Scan: API key can be found [here](https://whatcms.org/APIKey)
|
||||
```
|
||||
python nettacker.py -i eng.uber.com -m whatcms_scan --method-args whatcms_api_key=XXXX
|
||||
```
|
||||
* Finding CVE 2020-5902:
|
||||
```
|
||||
python nettacker.py -i <CIDR/IP/Domain> -m f5_cve_2020_5902
|
||||
python nettacker.py -l <List of IP/CIDR/Domain> -m f5_cve_2020_5902
|
||||
python nettacker.py -i <CIDR/IP/Domain> -m f5_cve_2020_5902 -s
|
||||
```
|
||||
|
||||
* OWASP Nettacker can also scan subdomains by using this command: `-s`
|
||||
|
||||
```
|
||||
python nettacker.py -i owasp.org -s -m port_scan -t 10 -M 35 -g 20-100 --graph d3_tree_v2_graph
|
||||
```
|
||||
|
||||
* Using the `-H` command you can add your own HTTP headers to requests (useful for authentication) and chain it using multiple `-H` commands
|
||||
|
||||
```
|
||||
python nettacker.py -i owasp.org -s -m http_status_scan -H "Authorization: Basic abcd" -H "Content-Type: abcd" -t 100 -d
|
||||
```
|
||||
|
||||
* If you use `-r` command, it will scan the IP range automatically by getting the range from the RIPE database online.
|
||||
```
|
||||
python nettacker.py -i owasp.org -s -r -m port_scan -t 10 -M 35 -g 20-100 --graph d3_tree_v2_graph
|
||||
python nettacker.py -i nettackerwebsiteblabla.com,owasp.org,192.168.1.1 -s -r -m all -t 10 -M 35 -g 20-100 -o file.txt -u root,user -P passwords.txt
|
||||
```
|
||||
|
||||
* Note: If host scan finishes, and couldn't get any result nothing will be listed in the output file unless you change the verbosity mode to a value from 1 to 5.
|
||||
|
||||
```
|
||||
python nettacker.py -i 192.168.1.1/24 -m all -t 10 -M 35 -g 20-100 -o file.txt -u root,user -P passwords.txt -v 1
|
||||
```
|
||||
* Use `*` pattern for selecting modules
|
||||
|
||||
```
|
||||
python nettacker.py -i 192.168.1.1/24 -m *_scan
|
||||
python nettacker.py -i 192.168.1.1/24 -m *_scan,*_vuln
|
||||
```
|
||||
|
||||
* Use profiles for using all modules inside a given profile
|
||||
|
||||
```
|
||||
python nettacker.py -i 192.168.1.1/24 --profile info
|
||||
python nettacker.py -i 192.168.1.1/24 --profile info,vuln
|
||||
python nettacker.py -i 192.168.1.1/24 --profile all
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
* Use socks proxy for outgoing connections (default socks version is 5)
|
||||
```
|
||||
python nettacker.py -i 192.168.1.1 -m tcp_connect_port_scan -T 5 --socks-proxy socks://127.0.0.1:9050
|
||||
python nettacker.py -i 192.168.1.1 -m tcp_connect_port_scan -T 5 --socks-proxy socks4://127.0.0.1:9050
|
||||
python nettacker.py -i 192.168.1.1 -m tcp_connect_port_scan -T 5 --socks-proxy socks5://127.0.0.1:9050
|
||||
python nettacker.py -i 192.168.1.1 -m tcp_connect_port_scan -T 5 --socks-proxy socks://username:password@127.0.0.1:9050
|
||||
python nettacker.py -i 192.168.1.1 -m tcp_connect_port_scan -T 5 --socks-proxy socks4://username:password@127.0.0.1:9050
|
||||
python nettacker.py -i 192.168.1.1 -m tcp_connect_port_scan -T 5 --socks-proxy socks5://username:password@127.0.0.1:9050
|
||||
```
|
||||
|
||||
* Get the list of all modules with details about it using `--show-all-modules`
|
||||
```
|
||||
python nettacker.py --show-all-modules
|
||||
______ __ _____ _____
|
||||
/ __ \ \ / /\ / ____| __ \
|
||||
| | | \ \ /\ / / \ | (___ | |__) |
|
||||
| | | |\ \/ \/ / /\ \ \___ \| ___/
|
||||
| |__| | \ /\ / ____ \ ____) | | Version 0.0.2
|
||||
\____/ \/ \/_/ \_\_____/|_| BIST
|
||||
_ _ _ _ _
|
||||
| \ | | | | | | | |
|
||||
github.com/OWASP | \| | ___| |_| |_ __ _ ___| | _____ _ __
|
||||
owasp.org | . ` |/ _ \ __| __/ _` |/ __| |/ / _ \ '__|
|
||||
z3r0d4y.com | |\ | __/ |_| || (_| | (__| < __/ |
|
||||
|_| \_|\___|\__|\__\__,_|\___|_|\_\___|_|
|
||||
|
||||
|
||||
|
||||
|
||||
[2021-08-31 17:42:06][+] http_options_enabled_vuln: name: http_options_enabled_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] clickjacking_vuln: name: clickjacking_vuln, author: OWASP Nettacker Team, severity: 5, description: Clickjacking, also known as a "UI redress attack", is when an attacker uses multiple transparent or opaque layers to trick a user into clicking on a button, reference: https://cheatsheetseries.owasp.org/cheatsheets/Clickjacking_Defense_Cheat_Sheet.html, profiles: ['vuln', 'http', 'medium_severity']
|
||||
[2021-08-31 17:42:06][+] wp_xmlrpc_bruteforce_vuln: name: wp_xmlrpc_bruteforce_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity', 'wordpress']
|
||||
[2021-08-31 17:42:06][+] graphql_vuln: name: graphql_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity', 'graphql']
|
||||
[2021-08-31 17:42:06][+] content_security_policy_vuln: name: content_security_policy_vuln, author: OWASP Nettacker Team, severity: 3, description: Content-Security-Policy is the name of a HTTP response header that modern browsers use to enhance the security of the document (or web page). The Content-Security-Policy header allows you to restrict how resources such as JavaScript, CSS, or pretty much anything that the browser loads., reference: https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html, profiles: ['vuln', 'http', 'low_severity', 'csp']
|
||||
[2021-08-31 17:42:06][+] xdebug_rce_vuln: name: xdebug_rce_vuln, author: OWASP Nettacker Team, severity: 10, description: None, reference: None, profiles: ['vuln', 'http', 'critical_severity']
|
||||
[2021-08-31 17:42:06][+] x_powered_by_vuln: name: x_powered_by_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] wp_xmlrpc_pingback_vuln: name: wp_xmlrpc_pingback_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'wordpress']
|
||||
[2021-08-31 17:42:06][+] http_cors_vuln: name: http_cors_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] f5_cve_2020_5902_vuln: name: f5_cve_2020_5902_vuln, author: OWASP Nettacker Team, severity: 9, description: None, reference: None, profiles: ['vuln', 'http', 'critical_severity', 'cve', 'f5']
|
||||
[2021-08-31 17:42:06][+] subdomain_takeover_vuln: name: subdomain_takeover_vuln, author: OWASP Nettacker Team, severity: 5, description: let us assume that example.com is the target and that the team running example.com have a bug bounty programme. While enumerating all of the subdomains belonging to example.com — a process that we will explore later — a hacker stumbles across subdomain.example.com, a subdomain pointing to GitHub pages. We can determine this by reviewing the subdomain's DNS records; in this example, subdomain.example.com has multiple A records pointing to GitHub's dedicated IP addresses for custom pages., reference: https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/10-Test_for_Subdomain_Takeover, profiles: ['vuln', 'http', 'medium_severity', 'takeover']
|
||||
[2021-08-31 17:42:06][+] http_trace_enabled_vuln: name: http_trace_enabled_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] http_cookie_vuln: name: http_cookie_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] wp_xmlrpc_dos_vuln: name: wp_xmlrpc_dos_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'wordpress']
|
||||
[2021-08-31 17:42:06][+] server_version_vuln: name: server_version_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] x_xss_protection_vuln: name: x_xss_protection_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] citrix_cve_2019_19781_vuln: name: citrix_cve_2019_19781_vuln, author: OWASP Nettacker Team, severity: 8, description: None, reference: None, profiles: ['vuln', 'http', 'high_severity', 'cve', 'citrix']
|
||||
[2021-08-31 17:42:06][+] content_type_options_vuln: name: content_type_options_vuln, author: OWASP Nettacker Team, severity: 2, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] apache_struts_vuln: name: apache_struts_vuln, author: OWASP Nettacker Team, severity: 3, description: None, reference: None, profiles: ['vuln', 'http', 'low_severity', 'apache_struts']
|
||||
[2021-08-31 17:42:06][+] vbulletin_cve_2019_16759_vuln: name: vbulletin_cve_2019_16759_vuln, author: OWASP Nettacker Team, severity: 9, description: None, reference: None, profiles: ['vuln', 'http', 'critical_severity', 'vbulletin', 'cve']
|
||||
[2021-08-31 17:42:06][+] msexchange_cve_2021_26855_vuln: name: msexchange_cve_2021_26855_vuln, author: OWASP Nettacker Team, severity: 9, description: None, reference: None, profiles: ['vuln', 'http', 'critical_severity', 'msexchange', 'cve']
|
||||
[2021-08-31 17:42:06][+] telnet_brute: name: telnet_brute, author: OWASP Nettacker Team, severity: 3, description: Telnet Bruteforcer, reference: None, profiles: ['brute', 'telnet']
|
||||
[2021-08-31 17:42:06][+] ssh_brute: name: ssh_brute, author: OWASP Nettacker Team, severity: 3, description: SSH Bruteforcer, reference: None, profiles: ['brute', 'ssh']
|
||||
[2021-08-31 17:42:06][+] smtp_brute: name: smtp_brute, author: OWASP Nettacker Team, severity: 3, description: SMTP Bruteforcer, reference: None, profiles: ['brute', 'smtp']
|
||||
[2021-08-31 17:42:06][+] ftps_brute: name: ftps_brute, author: OWASP Nettacker Team, severity: 3, description: FTPS Bruteforcer, reference: None, profiles: ['brute', 'ftp']
|
||||
[2021-08-31 17:42:06][+] smtps_brute: name: smtps_brute, author: OWASP Nettacker Team, severity: 3, description: SMTPS Bruteforcer, reference: None, profiles: ['brute', 'smtp']
|
||||
[2021-08-31 17:42:06][+] ftp_brute: name: ftp_brute, author: OWASP Nettacker Team, severity: 3, description: FTP Bruteforcer, reference: None, profiles: ['brute', 'ftp']
|
||||
[2021-08-31 17:42:06][+] whatcms_scan: name: dir_scan, author: OWASP Nettacker Team, severity: 3, description: Directory, Backup finder, reference: https://www.zaproxy.org/docs/alerts/10095/, profiles: ['scan', 'http', 'backup', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] icmp_scan: name: icmp_scan, author: OWASP Nettacker Team, severity: 0, description: check if host is alive through ICMP, reference: None, profiles: ['scan', 'info', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] subdomain_scan: name: subdomain_scan, author: OWASP Nettacker Team, severity: 0, description: Find subdomains using different sources on internet, reference: None, profiles: ['scan', 'info', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] port_scan: id: port_scan, author: OWASP Nettacker Team, severity: 0, description: Find open ports and services, reference: None, profiles: ['scan', 'http', 'info', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] admin_scan: name: admin_scan, author: OWASP Nettacker Team, severity: 3, description: Admin Directory Finder, reference: None, profiles: ['scan', 'http', 'backup', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] dir_scan: name: dir_scan, author: OWASP Nettacker Team, severity: 3, description: Directory, Backup finder, reference: https://www.zaproxy.org/docs/alerts/10095/, profiles: ['scan', 'http', 'backup', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] viewdns_reverse_iplookup_scan: name: viewdns_reverse_iplookup_scan, author: OWASP Nettacker Team, severity: 3, description: reverse lookup for target ip, reference: None, profiles: ['scan', 'http', 'backup', 'low_severity', 'reverse_lookup']
|
||||
[2021-08-31 17:42:06][+] drupal_version_scan: name: drupal_version_scan, author: OWASP Nettacker Team, severity: 3, description: fetch drupal version from target, reference: None, profiles: ['scan', 'http', 'backup', 'low_severity', 'drupal']
|
||||
[2021-08-31 17:42:06][+] joomla_version_scan: name: drupal_version_scan, author: OWASP Nettacker Team, severity: 3, description: fetch drupal version from target, reference: None, profiles: ['scan', 'http', 'backup', 'low_severity', 'drupal']
|
||||
[2021-08-31 17:42:06][+] wordpress_version_scan: name: wordpress_version_scan, author: OWASP Nettacker Team, severity: 3, description: Directory, Backup finder, reference: None, profiles: ['scan', 'http', 'backup', 'low_severity', 'wordpress']
|
||||
[2021-08-31 17:42:06][+] pma_scan: name: pma_scan, author: OWASP Nettacker Team, severity: 3, description: php my admin finder, reference: None, profiles: ['scan', 'http', 'backup', 'low_severity']
|
||||
[2021-08-31 17:42:06][+] all:
|
||||
```
|
||||
|
||||
|
||||
- you can quick run the tool by using profiles
|
||||
```
|
||||
python nettacker.py -i example.com --profile vulnerabilities
|
||||
python nettacker.py -i example.com --profile high_severity
|
||||
```
|
||||
|
||||
* You may want to create a new profile. To do that, you need to edit the particular modules by adding profiles name to it inside modules directory. for e.g i want add profile as `asset_discovery` to subdomain_scan,port_scan module, then i can just edit profile field in `modules/scan/subdomain.yaml` and `port_scan.yaml`
|
||||
|
||||
```
|
||||
info:
|
||||
name: subdomain_scan
|
||||
author: OWASP Nettacker Team
|
||||
severity: 0
|
||||
description: Find subdomains using different sources on internet
|
||||
reference:
|
||||
profiles:
|
||||
- scan
|
||||
- info
|
||||
- low_severity
|
||||
- asset_discovery(new added profile)
|
||||
|
||||
```
|
||||
|
||||
* You may want to change the default values (`timeout`, `socks proxy`, `target`, `ports`) or anything that could be set with the command line.To do that, you will have to edit them in the config.py `nettacker_user_application_config()` function in the main directory in JSON style.
|
||||
|
||||
```python
|
||||
def nettacker_user_application_config():
|
||||
"""
|
||||
core framework default config (could be modify by user)
|
||||
|
||||
Returns:
|
||||
a JSON with all user default configurations
|
||||
"""
|
||||
from core.compatible import version_info
|
||||
return { # OWASP Nettacker Default Configuration
|
||||
"language": "en",
|
||||
"verbose_mode": False,
|
||||
"show_version": False,
|
||||
"report_path_filename": "{results_path}/results_{date_time}_{random_chars}.html".format(
|
||||
results_path=nettacker_paths()["results_path"],
|
||||
date_time=now(model="%Y_%m_%d_%H_%M_%S"),
|
||||
random_chars=generate_random_token(10)
|
||||
),
|
||||
"graph_name": "d3_tree_v2_graph",
|
||||
"show_help_menu": False,
|
||||
"targets": None,
|
||||
"url_base_path": None,
|
||||
"http_header": None,
|
||||
"targets_list": None,
|
||||
"selected_modules": None,
|
||||
"excluded_modules": None,
|
||||
"usernames": None,
|
||||
"usernames_list": None,
|
||||
"passwords": None,
|
||||
"passwords_list": None,
|
||||
"ports": None,
|
||||
"timeout": 3.0,
|
||||
"time_sleep_between_requests": 0.0,
|
||||
"scan_ip_range": False,
|
||||
"scan_subdomains": False,
|
||||
"thread_per_host": 250,
|
||||
"parallel_module_scan": 20,
|
||||
"socks_proxy": None,
|
||||
"retries": 1,
|
||||
"ping_before_scan": False,
|
||||
"profiles": None,
|
||||
"set_hardware_usage": "maximum", # low, normal, high, maximum
|
||||
"user_agent": "Nettacker {version_number} {version_code} - https://github.com/OWASP/Nettacker".format(
|
||||
version_number=version_info()[0], version_code=version_info()[1]
|
||||
),
|
||||
"show_all_modules": False,
|
||||
"show_all_profiles": False,
|
||||
"modules_extra_args": None
|
||||
}
|
||||
```
|
||||
|
||||
* Nettacker supports five different output types for the final report
|
||||
|
||||
1. HTML (.html) -> This also renders the graph
|
||||
2. CSV (.csv)
|
||||
3. JSON (.json)
|
||||
4. SARIF (.sarif)
|
||||
5. DefectDojo compatible json (.dd.json)
|
||||
|
||||
These output types will help with integration with different softwares and dashboards. To set the output mode use the `-o` or `--output` flag
|
||||
|
||||
```
|
||||
python nettacker.py -i 192.168.1.1/24 --profile information_gathering -o report.sarif
|
||||
python nettacker.py -i 192.168.1.1/24 --profile information_gathering -o report.json
|
||||
python nettacker.py -i 192.168.1.1/24 --profile information_gathering --output report.dd.json
|
||||
```
|
||||
|
||||
# API and WebUI
|
||||
API and WebUI are new interfaces through which you can send your commands to Nettacker. Technically WebUI was developed based on the present API to demonstrate an example of the current API and can be used as another easier interface. To start using this feature, simply run `python nettacker.py --start-api`.
|
||||
```
|
||||
______ __ _____ _____
|
||||
/ __ \ \ / /\ / ____| __ \
|
||||
| | | \ \ /\ / / \ | (___ | |__) |
|
||||
| | | |\ \/ \/ / /\ \ \___ \| ___/
|
||||
| |__| | \ /\ / ____ \ ____) | | Version 0.0.1
|
||||
\____/ \/ \/_/ \_\_____/|_| SAME
|
||||
_ _ _ _ _
|
||||
| \ | | | | | | | |
|
||||
github.com/zdresearch | \| | ___| |_| |_ __ _ ___| | _____ _ __
|
||||
owasp.org | . ` |/ _ \ __| __/ _` |/ __| |/ / _ \ '__|
|
||||
zdresearch.com | |\ | __/ |_| || (_| | (__| < __/ |
|
||||
|_| \_|\___|\__|\__\__,_|\___|_|\_\___|_|
|
||||
|
||||
|
||||
|
||||
* API Key: ec5e067581f29a28d8c8bbfc6e548f02
|
||||
* Serving Flask app "api.engine" (lazy loading)
|
||||
* Environment: production
|
||||
WARNING: This is a development server. Do not use it in a production deployment.
|
||||
Use a production WSGI server instead.
|
||||
* Debug mode: off
|
||||
* Running on https://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||
|
||||
```
|
||||
|
||||
As you can see, the API key will be a random MD5 hash every time you run the API. You don't need to set the key.
|
||||
You can also add your own SSL certificate and the key to run the API on an https connection.
|
||||
|
||||
```python nettacker.py --start-api --api-cert ~/cert.crt --api-cert-key ~/key.pem```
|
||||
|
||||
You can modify the default API config by editing the `config.py`.
|
||||
|
||||
```python
|
||||
def nettacker_api_config():
|
||||
"""
|
||||
API Config (could be modify by user)
|
||||
|
||||
Returns:
|
||||
a JSON with API configuration
|
||||
"""
|
||||
return { # OWASP Nettacker API Default Configuration
|
||||
"start_api_server": False,
|
||||
"api_hostname": "0.0.0.0" if os.environ.get("docker_env") == "true" else "nettacker-api.z3r0d4y.com",
|
||||
"api_port": 5000,
|
||||
"api_debug_mode": False,
|
||||
"api_access_key": generate_random_token(32),
|
||||
"api_client_whitelisted_ips": [], # disabled - to enable please put an array with list of ips/cidr/ranges
|
||||
# [
|
||||
# "127.0.0.1",
|
||||
# "10.0.0.0/24",
|
||||
# "192.168.1.1-192.168.1.255"
|
||||
# ],
|
||||
"api_access_log": os.path.join(sys.path[0], '.data/nettacker.log'),
|
||||
}
|
||||
```
|
||||
|
||||
## API Options
|
||||
```
|
||||
--start-api start the API service
|
||||
--api-host API_HOST API host address
|
||||
--api-port API_PORT API port number
|
||||
--api-debug-mode API debug mode
|
||||
--api-access-key API_ACCESS_KEY
|
||||
API access key
|
||||
--api-client-white-list
|
||||
just allow white list hosts to connect to the API
|
||||
--api-client-white-list-ips API_CLIENT_WHITE_LIST_IPS
|
||||
define white list hosts, separate with , (examples:
|
||||
127.0.0.1, 192.168.0.1/24, 10.0.0.1-10.0.0.255)
|
||||
--api-access-log generate API access log
|
||||
--api-access-log-filename API_ACCESS_LOG_FILENAME
|
||||
API access log filename
|
||||
--api-cert API_CERT API CERTIFICATE
|
||||
--api-cert-key API_CERT_KEY
|
||||
API CERTIFICATE Key
|
||||
|
||||
```
|
||||
|
||||
## API Examples
|
||||
|
||||
```
|
||||
python nettacker.py --start-api --api-cert ~/cert.crt --api-cert-key ~/key.pem
|
||||
python nettacker.py --start-api --api-access-key mysecretkey
|
||||
python nettacker.py --start-api --api-client-white-list
|
||||
python nettacker.py --start-api --api-client-white-list --api-client-white-list-ips 127.0.0.1,192.168.0.1/24,10.0.0.1-10.0.0.255
|
||||
python nettacker.py --start-api --api-access-log
|
||||
python nettacker.py --start-api --api-access-log --api-access-log-filename log.txt
|
||||
python nettacker.py --start-api --api-access-key mysecretkey --api-client-white-list --api-access-log
|
||||
python nettacker.py --start-api --api-access-key mysecretkey --api-client-white-list --api-access-log
|
||||
python nettacker.py --start-api --api-access-key mysecretkey --api-host 192.168.1.2 --api-port 80
|
||||
python nettacker.py --start-api --api-access-log --api-port 8080 --api-debug-mode
|
||||
```
|
||||
|
||||
* For further information on how to use the RESTful API please visit the [API page](https://github.com/zdresearch/OWASP-Nettacker/wiki/API).
|
||||
|
||||

|
||||
|
||||
# Database
|
||||
OWASP Nettacker, currently supports two databases:
|
||||
- SQLite
|
||||
- MySQL
|
||||
The default database is SQLite. You can, however, configure the db to your liking.
|
||||
## SQLite configuration
|
||||
The SQLite database can be configured in `core/config.py` file under the `_database_config()` function. Here is a sample configuration:
|
||||
```
|
||||
return {
|
||||
"DB": "sqlite",
|
||||
"DATABASE": _paths()["home_path"] + "/nettacker.db", # This is the location of your db
|
||||
"USERNAME": "",
|
||||
"PASSWORD": "",
|
||||
"HOST": "",
|
||||
"PORT": ""
|
||||
}
|
||||
```
|
||||
## MySQL configuration:
|
||||
The MySQL database can be configured in `core/config.py` file under the `_database_config()` function. Here is a sample configuration:
|
||||
```
|
||||
return {
|
||||
"DB": "mysql",
|
||||
"DATABASE": "nettacker", # This is the name of your db
|
||||
"USERNAME": "username",
|
||||
"PASSWORD": "password",
|
||||
"HOST": "localhost or some other host",
|
||||
"PORT": "3306 or some other custom port"
|
||||
}
|
||||
```
|
||||
After this configuration:
|
||||
1. Open the configuration file of mysql(`/etc/mysql/my.cnf` in case of linux) as a sudo user
|
||||
2. Add this to the end of the file :
|
||||
```
|
||||
[mysqld]
|
||||
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
|
||||
```
|
||||
3. Restart MySQL
|
||||
|
||||
## Postgres Configuration
|
||||
|
||||
The Postgres database can be configured in core/config.py file under the _database_config() function. Here is a sample configuration:
|
||||
`
|
||||
return {
|
||||
"DB": "postgreas",
|
||||
"DATABASE": "nettacker" # Name of db
|
||||
"USERNAME": "username",
|
||||
"PASSWORD": "password",
|
||||
"HOST": "localhost or some other host",
|
||||
"PORT": "5432 or some other custom port"
|
||||
}
|
||||
`
|
||||
After this configuration please comment out the following line in database/db.py `connect_args={'check_same_thread': False}`
|
||||
|
||||
|
||||
|
||||
Let me know if you have any more questions.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# OWASP Nettacker Documentation
|
||||
|
||||
This documentation is generated using [mkdocs.org](https://www.mkdocs.org) and [Material for MkDocs theme](https://github.com/squidfunk/mkdocs-material)
|
||||
|
||||
|
||||
## Nettacker
|
||||
|
||||
OWASP Nettacker is an automated penetration testing framework designed to help cyber security professionals and ethical hackers perform reconnaissance, vulnerability assessments, and network security audits efficiently. Nettacker automates information gathering, vulnerability scanning, and credential brute forcing tasks, making it a powerful tool for identifying weaknesses in networks, web applications, IoT devices and APIs.
|
||||
|
||||
Documentation [Home](Home.md)
|
||||
|
|
@ -0,0 +1 @@
|
|||
mkdocs-material
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
pass
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
pass
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
pass
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import os
|
||||
from core.alert import messages
|
||||
|
||||
|
||||
def start(events):
|
||||
"""
|
||||
generate the d3_tree_v1_graph with events
|
||||
|
||||
Args:
|
||||
events: all events
|
||||
|
||||
Returns:
|
||||
a graph in HTML
|
||||
"""
|
||||
|
||||
# define a normalised_json
|
||||
normalisedjson = {
|
||||
"name": "Started attack",
|
||||
"children": {}
|
||||
}
|
||||
# get data for normalised_json
|
||||
for event in events:
|
||||
if event['target'] not in normalisedjson['children']:
|
||||
normalisedjson['children'].update(
|
||||
{
|
||||
event['target']: {}
|
||||
}
|
||||
)
|
||||
normalisedjson['children'][event['target']].update(
|
||||
{
|
||||
event['module_name']: []
|
||||
}
|
||||
)
|
||||
|
||||
if event['module_name'] not in normalisedjson['children'][event['target']]:
|
||||
normalisedjson['children'][event['target']].update(
|
||||
{
|
||||
event['module_name']: []
|
||||
}
|
||||
)
|
||||
normalisedjson['children'][event['target']][event['module_name']].append(
|
||||
f"target: {event['target']}, module_name: {event['module_name']}, port: "
|
||||
f"{event['port']}, event: {event['event']}"
|
||||
)
|
||||
# define a d3_structure_json
|
||||
d3_structure = {
|
||||
"name": "Starting attack",
|
||||
"children": []
|
||||
}
|
||||
# get data for normalised_json
|
||||
for target in list(normalisedjson['children'].keys()):
|
||||
for module_name in list(normalisedjson['children'][target].keys()):
|
||||
for description in normalisedjson["children"][target][module_name]:
|
||||
children_array = [
|
||||
{
|
||||
"name": module_name,
|
||||
"children": [
|
||||
{
|
||||
"name": description
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
d3_structure["children"].append(
|
||||
{
|
||||
"name": target,
|
||||
"children": children_array
|
||||
}
|
||||
)
|
||||
|
||||
from config import nettacker_paths
|
||||
data = open(
|
||||
os.path.join(
|
||||
nettacker_paths()['web_static_files_path'],
|
||||
'report/d3_tree_v1.html'
|
||||
)
|
||||
).read().replace(
|
||||
'__data_will_locate_here__',
|
||||
json.dumps(d3_structure)
|
||||
).replace(
|
||||
'__title_to_replace__',
|
||||
messages("pentest_graphs")
|
||||
).replace(
|
||||
'__description_to_replace__',
|
||||
messages("graph_message")
|
||||
).replace(
|
||||
'__html_title_to_replace__',
|
||||
messages("nettacker_report")
|
||||
)
|
||||
return data
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
pass
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
pass
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
from config import nettacker_paths
|
||||
|
||||
css_1 = open(
|
||||
os.path.join(
|
||||
nettacker_paths()['web_static_files_path'],
|
||||
'report/html_table.css'
|
||||
)
|
||||
).read()
|
||||
|
||||
table_title = open(
|
||||
os.path.join(
|
||||
nettacker_paths()['web_static_files_path'],
|
||||
'report/table_title.html'
|
||||
)
|
||||
).read()
|
||||
|
||||
table_items = open(
|
||||
os.path.join(
|
||||
nettacker_paths()['web_static_files_path'],
|
||||
'report/table_items.html'
|
||||
)
|
||||
).read()
|
||||
|
||||
table_end = open(
|
||||
os.path.join(
|
||||
nettacker_paths()['web_static_files_path'],
|
||||
'report/table_end.html'
|
||||
)
|
||||
).read()
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
pass
|
||||
|
|
@ -1,272 +0,0 @@
|
|||
---
|
||||
scan_started: नेटटेकर इंजन शुरू हुआ ...
|
||||
options: पायथन nettacker.py [विकल्प]
|
||||
help_menu: नेटटेकर सहायता मेनू दिखाएं
|
||||
license: कृपया लाइसेंस और समझौते https://github.com/OWASP/Nettacker पढ़ें
|
||||
engine: इंजन
|
||||
engine_input: इंजन इनपुट विकल्प
|
||||
select_language: एक भाषा का चयन करें {0}
|
||||
range: सीमा में सभी आईपी स्कैन करें
|
||||
subdomains: सबडोमेन ढूंढें और स्कैन करें
|
||||
thread_number_connections: एक मेजबान के कनेक्शन के लिए धागे संख्या
|
||||
thread_number_hosts: स्कैन मेजबान के लिए धागे संख्या
|
||||
save_logs: फ़ाइल में सभी लॉग सहेजें (results.txt, results.html, results.json)
|
||||
target: लक्ष्य
|
||||
target_input: लक्ष्य इनपुट विकल्प
|
||||
target_list: लक्ष्य (ओं) सूची, "," से अलग
|
||||
read_target: फ़ाइल से लक्ष्य (लक्ष्य) पढ़ें
|
||||
scan_method_options: स्कैन विधि विकल्प
|
||||
choose_scan_method: स्कैन विधि का चयन करें {0}
|
||||
exclude_scan_method: "{0} को बाहर करने के लिए स्कैन विधि का चयन करें"
|
||||
username_list: उपयोगकर्ता नाम (ओं) सूची, "," से अलग
|
||||
username_from_file: फ़ाइल से उपयोगकर्ता नाम (ओं) पढ़ें
|
||||
password_seperator: पासवर्ड (ओं) सूची, "," से अलग
|
||||
read_passwords: फ़ाइल से पासवर्ड पढ़ें
|
||||
port_seperator: पोर्ट (ओं) सूची, "," से अलग
|
||||
time_to_sleep: प्रत्येक अनुरोध के बीच सोने के लिए समय
|
||||
error_target: लक्ष्य निर्दिष्ट नहीं कर सकते
|
||||
error_target_file:
|
||||
"फ़ाइल को खोलने में असमर्थ लक्ष्य (लक्ष्य) निर्दिष्ट नहीं कर सकते:
|
||||
{0}"
|
||||
thread_number_warning:
|
||||
100 से कम थ्रेड नंबर का उपयोग करना बेहतर है, बीटीडब्ल्यू हम
|
||||
जारी हैं ...
|
||||
settimeout:
|
||||
"{0} सेकंड में टाइमआउट सेट करें, यह बहुत बड़ा है, है ना? जिस तरह से हम
|
||||
जारी रहे हैं ..."
|
||||
scan_module_not_found: यह स्कैन मॉड्यूल [{0}] नहीं मिला!
|
||||
error_exclude_all: आप सभी स्कैन विधियों को बाहर नहीं कर सकते हैं
|
||||
exclude_module_error: आपके द्वारा चुने गए {0} मॉड्यूल को नहीं मिला है!
|
||||
method_inputs:
|
||||
"विधियों इनपुट दर्ज करें, उदाहरण: ftp_brute_users = परीक्षण, व्यवस्थापक
|
||||
और ftp_brute_passwds = read_from_file: /tmp/pass.txt&ftp_brute_port=21"
|
||||
error_reading_file: फ़ाइल नहीं पढ़ सकता {0}
|
||||
error_username: "फ़ाइल खोलने में असमर्थ उपयोगकर्ता नाम निर्दिष्ट नहीं कर सकते: {0}"
|
||||
found: "{0} मिला! ({1}: {2})"
|
||||
error_password_file:
|
||||
"फ़ाइल खोलने में असमर्थ पासवर्ड (पासवर्ड) निर्दिष्ट नहीं कर सकता:
|
||||
{0}"
|
||||
file_write_error: फ़ाइल "{0}" लिखने योग्य नहीं है!
|
||||
scan_method_select: कृपया अपनी स्कैन विधि चुनें!
|
||||
remove_temp: temp फ़ाइलों को हटा रहा है!
|
||||
sorting_results: सॉर्टिंग परिणाम!
|
||||
done: किया हुआ!
|
||||
start_attack: "{2} पर {0}, {1} पर हमला करना शुरू करें"
|
||||
module_not_available: यह मॉड्यूल "{0}" उपलब्ध नहीं है
|
||||
error_platform:
|
||||
दुर्भाग्यवश सॉफ़्टवेयर का यह संस्करण बस लिनक्स / ओएसएक्स / विंडोज़
|
||||
पर चलाया जा सकता है।
|
||||
python_version_error: आपका पायथन संस्करण समर्थित नहीं है!
|
||||
skip_duplicate_target:
|
||||
डुप्लिकेट लक्ष्य छोड़ें (कुछ सबडोमेन / डोमेन में एक ही आईपी
|
||||
और रेंज हो सकते हैं)
|
||||
unknown_target: अज्ञात प्रकार का लक्ष्य [{0}]
|
||||
checking_range: चेकिंग {0} रेंज ...
|
||||
checking: जांच {0} ...
|
||||
HOST: मेज़बान
|
||||
USERNAME: उपयोगकर्ता नाम
|
||||
PASSWORD: पारण शब्द
|
||||
PORT: बंदरगाह
|
||||
TYPE: प्रकार
|
||||
DESCRIPTION: विवरण
|
||||
verbose_mode: वर्बोज़ मोड स्तर (0-5) (डिफ़ॉल्ट 0)
|
||||
software_version: सॉफ्टवेयर संस्करण दिखाओ
|
||||
check_updates: अपडेट के लिये जांचें
|
||||
outgoing_proxy:
|
||||
"आउटगोइंग कनेक्शन प्रॉक्सी (मोजे)। उदाहरण मोजे 5: 127.0.0.1:9050,
|
||||
मोजे: //127.0.0.1: 9050 मोजे 5: //127.0.0.1: 9050 या मोजे 4: मोजे 4: //127.0.0.1:
|
||||
9050, प्रमाणीकरण: मोजे: // उपयोगकर्ता नाम: पासवर्ड @ 127.0.0.1, मोजे 4: // उपयोगकर्ता
|
||||
नाम: password@127.0.0.1, socks5: // उपयोगकर्ता नाम: password@127.0.0.1"
|
||||
valid_socks_address:
|
||||
"कृपया मान्य मोजे पता और बंदरगाह दर्ज करें। उदाहरण मोजे 5: 127.0.0.1:9050,
|
||||
मोजे: //127.0.0.1: 9050, मोजे 5: //127.0.0.1: 9050 या मोजे 4: मोजे 4: //127.0.0.1:
|
||||
9050, प्रमाणीकरण: मोजे: // उपयोगकर्ता नाम: पासवर्ड @ 127.0.0.1, मोजे 4: // उपयोगकर्ता
|
||||
नाम: password@127.0.0.1, socks5: // उपयोगकर्ता नाम: password@127.0.0.1"
|
||||
connection_retries: कनेक्शन टाइमआउट (डिफ़ॉल्ट 3) पर पुनः प्रयास करता है
|
||||
ftp_connectiontimeout: "{0} के लिए FTP कनेक्शन: {1} टाइमआउट, छोड़ना {2}: {3}"
|
||||
login_successful: सफलतापूर्वक लॉग इन हो चुका है!
|
||||
login_list_error: सफलतापूर्वक लॉग इन, लाइव कमांड के लिए अनुमति दी गई अनुमति!
|
||||
ftp_connection_failed:
|
||||
"{0} के लिए FTP कनेक्शन: {1} असफल रहा, पूरे चरण को छोड़ना [प्रक्रिया
|
||||
{2} {3}]! अगले कदम पर जा रहे हैं"
|
||||
input_target_error:
|
||||
"{0} मॉड्यूल के लिए इनपुट लक्ष्य DOMAIN, HTTP या SINGLE_IPv4 होना
|
||||
चाहिए, {1} छोड़ना"
|
||||
user_pass_found: "उपयोगकर्ता: {0} पास: {1} होस्ट: {2} पोर्ट: {3} मिला!"
|
||||
file_listing_error: "(सूची फाइलों के लिए कोई अनुमति नहीं)"
|
||||
trying_message:
|
||||
"{3} {4}: {5} ({6}) की प्रक्रिया {2} में {1} की {0} की कोशिश कर रहा
|
||||
है"
|
||||
smtp_connectiontimeout: "{0} के लिए smtp कनेक्शन: {1} टाइमआउट, छोड़ना {2}: {3}"
|
||||
smtp_connection_failed:
|
||||
"{0} के लिए smtp कनेक्शन: {1} असफल रहा, पूरे चरण को छोड़ना
|
||||
[प्रक्रिया {2} {3}]! अगले कदम पर जा रहे हैं"
|
||||
ssh_connectiontimeout: "एसएसएच कनेक्शन {0}: {1} टाइमआउट, छोड़ना {2}: {3}"
|
||||
ssh_connection_failed:
|
||||
"{0}: {1} से एसएसएच कनेक्शन विफल रहा, पूरे चरण को छोड़ना [प्रक्रिया
|
||||
{2} {3}]! अगले कदम पर जा रहे हैं"
|
||||
port/type: "{0} / {1}"
|
||||
port_found: "होस्ट: {0} पोर्ट: {1} ({2}) मिला!"
|
||||
target_submitted: लक्ष्य {0} जमा!
|
||||
current_version:
|
||||
आप OWASP Nettacker संस्करण {0} {1} {2} {6} कोड नाम {3} {4} {5} के
|
||||
साथ चल रहे हैं
|
||||
feature_unavailable:
|
||||
यह सुविधा अभी तक उपलब्ध नहीं है! अंतिम संस्करण प्राप्त करने के
|
||||
लिए कृपया "गिट क्लोन https://github.com/OWASP/Nettacker.git या पाइप इंस्टॉल -यू
|
||||
OWASP-Nettacker चलाएं।
|
||||
available_graph:
|
||||
"सभी गतिविधियों और जानकारी का एक ग्राफ बनाएं, आपको HTML आउटपुट का
|
||||
उपयोग करना होगा। उपलब्ध ग्राफ: {0}"
|
||||
graph_output:
|
||||
ग्राफ़ सुविधा का उपयोग करने के लिए आपका आउटपुट फ़ाइल नाम ".html" या
|
||||
".htm" के साथ समाप्त होना चाहिए!
|
||||
build_graph: ग्राफ निर्माण ...
|
||||
finish_build_graph: निर्माण ग्राफ खत्म करो!
|
||||
pentest_graphs: प्रवेश परीक्षण ग्राफ
|
||||
graph_message:
|
||||
यह ग्राफ OWASP Nettacker द्वारा बनाया गया है। ग्राफ़ में सभी मॉड्यूल
|
||||
गतिविधियां, नेटवर्क मानचित्र और संवेदनशील जानकारी शामिल है, अगर यह विश्वसनीय नहीं
|
||||
है तो कृपया इस फ़ाइल को किसी के साथ साझा न करें।
|
||||
nettacker_report: ओडब्ल्यूएएसपी नेटटेकर रिपोर्ट
|
||||
nettacker_version_details:
|
||||
"सॉफ्टवेयर विवरण: ओडब्ल्यूएएसपी नेटटेकर संस्करण {0} [{1}]
|
||||
{2} में"
|
||||
no_open_ports: कोई खुला बंदरगाह नहीं मिला!
|
||||
no_user_passwords: कोई उपयोगकर्ता / पासवर्ड नहीं मिला!
|
||||
loaded_modules: "{0} मॉड्यूल लोड ..."
|
||||
graph_module_404: "यह ग्राफ मॉड्यूल नहीं मिला: {0}"
|
||||
graph_module_unavailable: यह ग्राफ मॉड्यूल "{0}" उपलब्ध नहीं है
|
||||
ping_before_scan: मेजबान स्कैन करने से पहले पिंग
|
||||
skipping_target:
|
||||
पूरे लक्ष्य को छोड़ना {0} और स्कैनिंग विधि {1} - -पिंग-पहले-स्कैन
|
||||
सत्य होने के कारण और प्रतिक्रिया नहीं मिली!
|
||||
not_last_version:
|
||||
आप ओडब्ल्यूएएसपी नेटटेकर के अंतिम संस्करण का उपयोग नहीं कर रहे हैं,
|
||||
कृपया अपडेट करें।
|
||||
cannot_update: अद्यतन के लिए जांच नहीं कर सकते, कृपया अपना इंटरनेट कनेक्शन जांचें।
|
||||
last_version: आप ओडब्ल्यूएएसपी नेटटेकर के अंतिम संस्करण का उपयोग कर रहे हैं ...
|
||||
directoy_listing: "{0} में मिली निर्देशिका सूची"
|
||||
insert_port_message:
|
||||
कृपया यूआरएल के बजाय -g या - methods-args स्विच के माध्यम से
|
||||
पोर्ट डालें
|
||||
http_connectiontimeout: http कनेक्शन {0} टाइमआउट!
|
||||
wizard_mode: विज़ार्ड मोड शुरू करें
|
||||
directory_file_404: पोर्ट {1} में {0} के लिए कोई निर्देशिका या फ़ाइल नहीं मिली
|
||||
open_error: "{0} खोलने में असमर्थ"
|
||||
dir_scan_get:
|
||||
dir_scan_http_method मान GET या HEAD होना चाहिए, GET को डिफ़ॉल्ट सेट
|
||||
करें।
|
||||
list_methods: सभी विधियों के तर्क सूचीबद्ध करें
|
||||
module_args_error: "{0} मॉड्यूल तर्क नहीं मिल सकता है"
|
||||
trying_process: "{4} ({5} पर {3} की प्रक्रिया {2} में {1} की {0} की कोशिश कर रहा है"
|
||||
domain_found: "डोमेन मिला: {0}"
|
||||
TIME: पहर
|
||||
CATEGORY: वर्ग
|
||||
module_pattern_404: "{0} पैटर्न के साथ कोई मॉड्यूल नहीं मिल रहा है!"
|
||||
enter_default: कृपया {0} | दर्ज करें डिफ़ॉल्ट [{1}]>
|
||||
enter_choices_default: कृपया {0} | दर्ज करें विकल्प [{1}] | डिफ़ॉल्ट [{2}]>
|
||||
all_targets: लक्ष्य
|
||||
all_thread_numbers: धागा संख्या
|
||||
out_file: आउटपुट फ़ाइल नाम
|
||||
all_scan_methods: स्कैन विधियों
|
||||
all_scan_methods_exclude: बाहर करने के लिए स्कैन विधियों
|
||||
all_usernames: उपयोगकर्ता नाम
|
||||
all_passwords: पासवर्ड
|
||||
timeout_seconds: टाइमआउट सेकेंड
|
||||
Invalid_shodan_api_key: "{0}"
|
||||
shodan_api_key: शोदन के लिए शोडान एपीआई कुंजी
|
||||
shodan_results_found: शोदन के परिणाम मिले
|
||||
shodan_results_not_found: शोदन डेटाबेस में कुछ भी नहीं मिला
|
||||
shodan_plan_upgrade:
|
||||
कृपया फ़िल्टर या पेजिंग का उपयोग करने के लिए अपनी एपीआई योजना
|
||||
को अपग्रेड करें और बेहतर परिणाम प्राप्त करें
|
||||
searching_shodan_database: खोज रहे शोदन डेटाबेस...
|
||||
all_ports: बंदरगाह संख्या
|
||||
all_verbose_level: वर्बोज़ स्तर
|
||||
all_socks_proxy: मोजे प्रॉक्सी
|
||||
retries_number: retries संख्या
|
||||
graph: एक ग्राफ
|
||||
subdomain_found: "सबडोमेन मिला: {0}"
|
||||
select_profile: प्रोफ़ाइल का चयन करें {0}
|
||||
profile_404: प्रोफाइल "{0}" नहीं मिला!
|
||||
waiting: "{0} की प्रतीक्षा"
|
||||
vulnerable: "{0} के लिए कमजोर"
|
||||
target_vulnerable: "लक्ष्य {0}: {1} {2} के लिए कमजोर है!"
|
||||
no_vulnerability_found: कोई भेद्यता नहीं मिली! ({0})
|
||||
Method: तरीका
|
||||
API: एपीआई
|
||||
API_options: एपीआई विकल्प
|
||||
start_api_server: एपीआई सेवा शुरू करें
|
||||
API_host: एपीआई होस्ट पता
|
||||
API_port: एपीआई पोर्ट नंबर
|
||||
API_debug: एपीआई डीबग मोड
|
||||
API_access_key: एपीआई एक्सेस कुंजी
|
||||
white_list_API: बस सफेद सूची होस्ट को एपीआई से कनेक्ट करने की अनुमति दें
|
||||
define_whie_list:
|
||||
"सफेद सूची होस्ट को परिभाषित करें, इसके साथ अलग, (उदाहरण: 127.0.0.1,
|
||||
1 9 2.168.0.1/24, 10.0.0.1-10.0.0.255)"
|
||||
gen_API_access_log: एपीआई एक्सेस लॉग उत्पन्न करें
|
||||
API_access_log_file: एपीआई एक्सेस लॉग फ़ाइल नाम
|
||||
API_port_int: एपीआई पोर्ट एक पूर्णांक होना चाहिए!
|
||||
unknown_ip_input:
|
||||
अज्ञात इनपुट प्रकार, स्वीकृत प्रकार हैं SINGLE_IPv4, RANGE_IPv4,
|
||||
CIDR_IPv4
|
||||
API_key: "* एपीआई कुंजी: {0}"
|
||||
ports_int: बंदरगाह पूर्णांक होना चाहिए! (उदाहरण के लिए 80 || 80,1080 || 80,1080-1300,9000,12000-15000)
|
||||
through_API: ओडब्ल्यूएएसपी नेटटेकर एपीआई के माध्यम से
|
||||
API_invalid: अवैध एपीआई कुंजी
|
||||
API_cert: एपीआई प्रमाण पत्र
|
||||
API_cert_key: एपीआई प्रमाणपत्र कुंजी
|
||||
api_cert: कृपया अपने एसएसपी सर्टिफिकेट की लोकेशन --api-cert स्विच का उपयोग करके दें
|
||||
api_cert_key: कृपया --api-cert-key स्विच का उपयोग करके अपनी निजी कुंजी का स्थान दें
|
||||
wrong_values: कृपया सही एसएसएल प्रमाणपत्र और निजी कुंजी फ़ाइल प्रदान करें!
|
||||
unauthorized_IP: आपका आईपी अधिकृत नहीं है
|
||||
not_found: नहीं मिला!
|
||||
no_subdomain_found: "subdomain_scan: कोई सबडोमेन की स्थापना की!"
|
||||
viewdns_domain_404: "viewdns_reverse_ip_lookup_scan: कोई डोमेन नहीं मिला!"
|
||||
browser_session_valid: आपका ब्राउज़र सत्र मान्य है
|
||||
browser_session_killed: आपका ब्राउज़र सत्र मारे गए
|
||||
updating_database: डेटाबेस अपडेट कर रहा है ...
|
||||
database_connect_fail: डेटाबेस के कनेक्ट नहीं कर सके!
|
||||
inserting_report_db: डेटाबेस में रिपोर्ट डालना
|
||||
inserting_logs_db: डेटाबेस में लॉग डालने
|
||||
removing_logs_db: डीबी से पुराने लॉग को हटा रहा है
|
||||
len_subdomain_found: "{0} सबडोमेन मिला है!"
|
||||
len_domain_found: "{0} डोमेन पाए गए!"
|
||||
phpmyadmin_dir_404: कोई phpmyadmin डीआईआर नहीं मिला!
|
||||
DOS_send: "{0} को डीओएस पैकेट भेजना"
|
||||
host_up: "{0} ऊपर है! वापस पिंग करने के लिए समय {1} है"
|
||||
host_down: पिंग नहीं कर सकते {0}!
|
||||
root_required: इसे जड़ के रूप में चलाने की जरूरत है
|
||||
admin_scan_get:
|
||||
admin_scan_http_method मान GET या HEAD होना चाहिए, GET को डिफ़ॉल्ट
|
||||
सेट करें।
|
||||
telnet_connectiontimeout: "{0} के लिए टेलनेट कनेक्शन: {1} टाइमआउट, छोड़ना {2}: {3}"
|
||||
telnet_connection_failed:
|
||||
"{0} के लिए टेलनेट कनेक्शन: {1} असफल रहा, पूरे चरण को छोड़ना
|
||||
[प्रक्रिया {2} {3}]! अगले कदम पर जा रहे हैं"
|
||||
http_auth_success:
|
||||
"http मूल प्रमाणीकरण सफलता - होस्ट: {2}: {3}, उपयोगकर्ता: {0},
|
||||
पास: {1} मिला!"
|
||||
http_auth_failed: "http मूलभूत प्रमाणीकरण {0}: {3} {1} का उपयोग करके विफल रहा: {2}"
|
||||
http_form_auth_success:
|
||||
"http फॉर्म प्रमाणीकरण सफलता - होस्ट: {2}: {3}, उपयोगकर्ता:
|
||||
{0}, पास: {1} मिला!"
|
||||
http_form_auth_failed:
|
||||
"{1}: {3} का उपयोग करके http फॉर्म प्रमाणीकरण विफल रहा {0}:
|
||||
{2}"
|
||||
http_ntlm_success:
|
||||
"http ntlm प्रमाणीकरण सफलता - होस्ट: {2}: {3}, उपयोगकर्ता: {0},
|
||||
पास: {1} मिला!"
|
||||
http_ntlm_failed: "http ntlm प्रमाणीकरण {0}: {3} {1} का उपयोग करके विफल रहा: {2}"
|
||||
no_response: लक्ष्य से प्रतिक्रिया प्राप्त नहीं कर सकते हैं
|
||||
category_framework: "श्रेणी: {0}, ढांचे: {1} मिला!"
|
||||
nothing_found: "{0} में {1} में कुछ भी नहीं मिला!"
|
||||
no_auth: "{0}: {1} पर कोई लेख नहीं मिला"
|
||||
invalid_database: कृपया कॉन्फ़िगरेशन फ़ाइल में माई एसक्यूएल या sqlite से चुनें
|
||||
database_connection_failed: चयनित डाटाबेस से कनेक्शन विफल हुआ
|
||||
fuzzer_no_response: एचटीटीपी फ़ज़र को कोई आउटपुट नहीं मिला {0}
|
||||
summary_report: सारांश रिपोर्ट तालिका
|
||||
file_saved: रिपोर्ट डेटाबेस और {0} में सहेजी गई
|
||||
no_event_found: इस स्कैन में कोई ईवेंट नहीं मिला
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
---
|
||||
scan_started: Mesin Nettacker mulai ...
|
||||
options: python nettacker.py [opsi]
|
||||
help_menu: Tampilkan Menu Bantuan Nettacker
|
||||
license: Harap baca lisensi dan perjanjian https://github.com/OWASP/Nettacker
|
||||
engine: Mesin
|
||||
engine_input: Opsi masukan mesin
|
||||
select_language: pilih bahasa {0}
|
||||
range: pindai semua IP dalam rentang
|
||||
subdomains: cari dan pindai subdomain
|
||||
thread_number_connections: nomor utas untuk koneksi ke host
|
||||
thread_number_hosts: nomor utas untuk host pemindaian
|
||||
save_logs: simpan semua log dalam file (results.txt, results.html, results.json)
|
||||
target: Target
|
||||
target_input: Opsi masukan target
|
||||
target_list: daftar target (s), terpisah dengan ","
|
||||
read_target: baca target (s) dari file
|
||||
scan_method_options: Pindai opsi metode
|
||||
choose_scan_method: pilih metode pemindaian {0}
|
||||
exclude_scan_method: pilih metode pemindaian untuk mengecualikan {0}
|
||||
username_list: daftar nama pengguna (s), terpisah dengan ","
|
||||
username_from_file: baca nama pengguna (s) dari file
|
||||
password_seperator: daftar kata sandi, terpisah dengan ","
|
||||
read_passwords: baca kata sandi (s) dari file
|
||||
port_seperator: daftar port (s), terpisah dengan ","
|
||||
time_to_sleep: waktu untuk tidur di antara setiap permintaan
|
||||
error_target: Tidak dapat menentukan target (s)
|
||||
error_target_file: "Tidak dapat menentukan target (s), tidak dapat membuka file: {0}"
|
||||
thread_number_warning:
|
||||
lebih baik menggunakan nomor utas lebih rendah dari 100, BTW
|
||||
kami terus ...
|
||||
settimeout:
|
||||
mengatur waktu tunggu hingga {0} detik, itu terlalu besar, bukan? dengan
|
||||
cara kita melanjutkan ...
|
||||
scan_module_not_found: modul pemindaian ini [{0}] tidak ditemukan!
|
||||
error_exclude_all: Anda tidak dapat mengecualikan semua metode pemindaian
|
||||
exclude_module_error: "{0} modul yang Anda pilih untuk dikecualikan tidak ditemukan!"
|
||||
method_inputs:
|
||||
"masukkan input metode, contoh: ftp_brute_users = test, admin & ftp_brute_passwds
|
||||
= baca_from_file: /tmp/pass.txt&ftp_brute_port=21"
|
||||
error_reading_file: tidak bisa membaca file {0}
|
||||
error_username:
|
||||
"Tidak dapat menentukan nama pengguna (s), tidak dapat membuka file:
|
||||
{0}"
|
||||
found: "{0} ditemukan! ({1}: {2})"
|
||||
error_password_file:
|
||||
"Tidak dapat menentukan kata sandi (s), tidak dapat membuka file:
|
||||
{0}"
|
||||
file_write_error: file "{0}" tidak dapat ditulis!
|
||||
scan_method_select: silakan pilih metode pemindaian Anda!
|
||||
remove_temp: menghapus file temp!
|
||||
sorting_results: hasil penyortiran!
|
||||
done: selesai!
|
||||
start_attack: mulai menyerang {0}, {1} dari {2}
|
||||
module_not_available: modul ini "{0}" tidak tersedia
|
||||
error_platform:
|
||||
sayangnya versi perangkat lunak ini hanya bisa dijalankan di linux
|
||||
/ osx / windows.
|
||||
python_version_error: Versi Python Anda tidak didukung!
|
||||
skip_duplicate_target:
|
||||
lewati target duplikat (beberapa subdomain / domain mungkin
|
||||
memiliki IP dan Rentang yang sama)
|
||||
unknown_target: jenis target yang tidak diketahui [{0}]
|
||||
checking_range: memeriksa {0} rentang ...
|
||||
checking: memeriksa {0} ...
|
||||
HOST: TUAN RUMAH
|
||||
USERNAME: NAMA PENGGUNA
|
||||
PASSWORD: KATA SANDI
|
||||
PORT: PELABUHAN
|
||||
TYPE: MENGETIK
|
||||
DESCRIPTION: DESKRIPSI
|
||||
verbose_mode: tingkat modus verbose (0-5) (default 0)
|
||||
software_version: tampilkan versi perangkat lunak
|
||||
check_updates: memeriksa pembaruan
|
||||
outgoing_proxy:
|
||||
"proxy koneksi keluar (kaus kaki). contoh kaus kaki5: 127.0.0.1:9050,
|
||||
kaus kaki: //127.0.0.1: 9050 kaus kaki5: //127.0.0.1: 9050 atau kaus kaki4: kaus
|
||||
kaki4: //127.0.0.1: 9050, autentikasi: kaus kaki: // namapengguna: kata sandi @
|
||||
127.0.0.1, socks4: // username: password@127.0.0.1, socks5: // username: password@127.0.0.1"
|
||||
valid_socks_address:
|
||||
"masukkan alamat dan port kaus kaki yang valid. contoh kaus kaki5:
|
||||
127.0.0.1:9050, kaus kaki: //127.0.0.1: 9050, kaus kaki5: //127.0.0.1: 9050 atau
|
||||
kaus kaki4: kaus kaki4: //127.0.0.1: 9050, autentikasi: kaus kaki: // namapengguna:
|
||||
kata sandi @ 127.0.0.1, socks4: // username: password@127.0.0.1, socks5: // username:
|
||||
password@127.0.0.1"
|
||||
connection_retries: Retries ketika batas waktu koneksi (default 3)
|
||||
ftp_connectiontimeout: "koneksi ftp ke {0}: {1} timeout, skipping {2}: {3}"
|
||||
login_successful: DITERUKAN SECARA SUKSES!
|
||||
login_list_error: DITERUKAN SECARA SUKSES, IZIN DITOLAK UNTUK DAFTAR!
|
||||
ftp_connection_failed:
|
||||
"koneksi ftp ke {0}: {1} gagal, melewati seluruh langkah [proses
|
||||
{2} {3}]! akan ke langkah berikutnya"
|
||||
input_target_error:
|
||||
target input untuk {0} modul harus DOMAIN, HTTP atau SINGLE_IPv4,
|
||||
skipping {1}
|
||||
user_pass_found: "pengguna: {0} lulus: {1} host: {2} port: {3} ditemukan!"
|
||||
file_listing_error: "(TIDAK ADA IZIN UNTUK DAFTAR DAFTAR)"
|
||||
trying_message: "mencoba {0} dari {1} dalam proses {2} dari {3} {4}: {5} ({6})"
|
||||
smtp_connectiontimeout: "koneksi smtp ke {0}: {1} timeout, skipping {2}: {3}"
|
||||
smtp_connection_failed:
|
||||
"koneksi smtp ke {0}: {1} gagal, melewati seluruh langkah
|
||||
[proses {2} {3}]! akan ke langkah berikutnya"
|
||||
ssh_connectiontimeout: "koneksi ssh ke {0}: {1} timeout, skipping {2}: {3}"
|
||||
ssh_connection_failed:
|
||||
"koneksi ssh ke {0}: {1} gagal, melewati seluruh langkah [proses
|
||||
{2} {3}]! akan ke langkah berikutnya"
|
||||
port/type: "{0} / {1}"
|
||||
port_found: "host: {0} port: {1} ({2}) ditemukan!"
|
||||
target_submitted: target {0} dikirimkan!
|
||||
current_version:
|
||||
Anda menjalankan OWASP Nettacker versi {0} {1} {2} {6} dengan nama
|
||||
kode {3} {4} {5}
|
||||
feature_unavailable:
|
||||
fitur ini belum tersedia! silakan jalankan "git clone https://github.com/OWASP/Nettacker.git
|
||||
atau install pip -U OWASP-Nettacker untuk mendapatkan versi terakhir.
|
||||
available_graph:
|
||||
"membangun grafik dari semua aktivitas dan informasi, Anda harus
|
||||
menggunakan output HTML. grafik yang tersedia: {0}"
|
||||
graph_output:
|
||||
untuk menggunakan fitur grafik, nama file output Anda harus diakhiri
|
||||
dengan ".html" atau ".htm"!
|
||||
build_graph: membangun grafik ...
|
||||
finish_build_graph: selesaikan grafik bangunan!
|
||||
pentest_graphs: Grafik Pengujian Penetrasi
|
||||
graph_message:
|
||||
Grafik ini dibuat oleh OWASP Nettacker. Grafik berisi semua kegiatan
|
||||
modul, peta jaringan, dan informasi sensitif. Jangan bagikan file ini dengan siapa
|
||||
pun jika tidak dapat diandalkan.
|
||||
nettacker_report: Laporan OWASP Nettacker
|
||||
nettacker_version_details:
|
||||
"Detail Perangkat Lunak: OWASP Nettacker versi {0} [{1}]
|
||||
di {2}"
|
||||
no_open_ports: tidak ada port terbuka ditemukan!
|
||||
no_user_passwords: tidak ada pengguna / kata sandi yang ditemukan!
|
||||
loaded_modules: "{0} modul dimuat ..."
|
||||
graph_module_404: "modul grafik ini tidak ditemukan: {0}"
|
||||
graph_module_unavailable: modul grafik ini "{0}" tidak tersedia
|
||||
ping_before_scan: ping sebelum memindai host
|
||||
skipping_target:
|
||||
melewatkan seluruh target {0} dan metode pemindaian {1} karena --ping-before-scan
|
||||
adalah benar dan tidak ada respon!
|
||||
not_last_version: Anda tidak menggunakan versi terakhir OWASP Nettacker, harap perbarui.
|
||||
cannot_update: tidak dapat memeriksa pembaruan, periksa koneksi internet Anda.
|
||||
last_version: Anda menggunakan versi terakhir OWASP Nettacker ...
|
||||
directoy_listing: daftar direktori ditemukan di {0}
|
||||
insert_port_message:
|
||||
tolong masukkan port melalui switch -g atau --methods-args sebagai
|
||||
ganti url
|
||||
http_connectiontimeout: koneksi http {0} timeout!
|
||||
wizard_mode: mulai mode wizard
|
||||
directory_file_404:
|
||||
tidak ada direktori atau file yang ditemukan untuk {0} di port
|
||||
{1}
|
||||
open_error: tidak dapat membuka {0}
|
||||
dir_scan_get: nilai dir_scan_http_method harus GET atau HEAD, atur default ke GET.
|
||||
list_methods: daftar semua metode args
|
||||
module_args_error: tidak bisa mendapatkan argumen modul {0}
|
||||
trying_process: mencoba {0} dari {1} dalam proses {2} dari {3} pada {4} ({5})
|
||||
domain_found: "domain ditemukan: {0}"
|
||||
TIME: WAKTU
|
||||
CATEGORY: KATEGORI
|
||||
module_pattern_404: tidak dapat menemukan modul apa pun dengan pola {0}!
|
||||
enter_default: masukkan {0} | Default [{1}]>
|
||||
enter_choices_default: masukkan {0} | pilihan [{1}] | Default [{2}]>
|
||||
all_targets: targetnya
|
||||
all_thread_numbers: nomor utas
|
||||
out_file: nama file keluaran
|
||||
all_scan_methods: metode pemindaian
|
||||
all_scan_methods_exclude: metode pemindaian untuk dikecualikan
|
||||
all_usernames: nama pengguna
|
||||
all_passwords: kata sandi
|
||||
timeout_seconds: batas waktu detik
|
||||
all_ports: nomor port
|
||||
all_verbose_level: tingkat verbose
|
||||
all_socks_proxy: proxy kaus kaki
|
||||
retries_number: nomor retries
|
||||
graph: sebuah grafik
|
||||
subdomain_found: "subdomain ditemukan: {0}"
|
||||
select_profile: pilih profil {0}
|
||||
profile_404: profil "{0}" tidak ditemukan!
|
||||
waiting: menunggu {0}
|
||||
vulnerable: rentan terhadap {0}
|
||||
target_vulnerable: "target {0}: {1} rentan terhadap {2}!"
|
||||
no_vulnerability_found: tidak ditemukan kerentanan! ({0})
|
||||
Method: metode
|
||||
API: API
|
||||
API_options: Opsi API
|
||||
start_api_server: memulai layanan API
|
||||
API_host: Alamat host API
|
||||
API_port: Nomor port API
|
||||
API_debug: Mode debug API
|
||||
API_access_key: Kunci akses API
|
||||
white_list_API: cukup izinkan host daftar putih untuk terhubung ke API
|
||||
define_whie_list:
|
||||
"mendefinisikan host daftar putih, terpisah dengan, (contoh: 127.0.0.1,
|
||||
192.168.0.1/24, 10.0.0.1-10.0.0.255)"
|
||||
gen_API_access_log: menghasilkan log akses API
|
||||
API_access_log_file: Nama file log akses API
|
||||
API_port_int: Port API harus berupa bilangan bulat!
|
||||
unknown_ip_input:
|
||||
jenis masukan tidak dikenal, jenis yang diterima adalah SINGLE_IPv4,
|
||||
RANGE_IPv4, CIDR_IPv4
|
||||
API_key: "* Kunci API: {0}"
|
||||
ports_int: port harus berupa bilangan bulat! (mis. 80 || 80,1080 || 80,1080-1300,9000,12000-15000)
|
||||
through_API: Melalui API OWASP Nettacker
|
||||
API_invalid: kunci API tidak valid
|
||||
unauthorized_IP: IP Anda tidak diotorisasi
|
||||
not_found: Tidak ditemukan!
|
||||
no_subdomain_found: "subdomain_scan: tidak ada subdomain yang ditemukan!"
|
||||
viewdns_domain_404: "viewdns_reverse_ip_lookup_scan: tidak ada domain yang ditemukan!"
|
||||
browser_session_valid: sesi browser Anda valid
|
||||
browser_session_killed: sesi browser Anda terbunuh
|
||||
updating_database: memperbarui basis data ...
|
||||
database_connect_fail: tidak bisa terhubung ke database!
|
||||
inserting_report_db: memasukkan laporan ke database
|
||||
inserting_logs_db: memasukkan log ke database
|
||||
removing_logs_db: menghapus log lama dari db
|
||||
len_subdomain_found: "{0} subdomain (s) ditemukan!"
|
||||
len_domain_found: "{0} domain (s) ditemukan!"
|
||||
phpmyadmin_dir_404: tidak ada dir phpmyadmin ditemukan!
|
||||
DOS_send: mengirim paket DoS ke {0}
|
||||
host_up:
|
||||
"{0} sudah habis! Waktu yang diambil untuk melakukan ping kembali adalah
|
||||
{1}"
|
||||
host_down: Tidak bisa melakukan ping {0}!
|
||||
root_required: ini harus dijalankan sebagai root
|
||||
admin_scan_get:
|
||||
admin_scan_http_method value harus GET atau HEAD, atur default ke
|
||||
GET.
|
||||
telnet_connectiontimeout: "koneksi telnet ke {0}: {1} timeout, skipping {2}: {3}"
|
||||
telnet_connection_failed:
|
||||
"koneksi telnet ke {0}: {1} gagal, melewati seluruh langkah
|
||||
[proses {2} dari {3}]! akan ke langkah berikutnya"
|
||||
http_auth_success:
|
||||
"sukses otentikasi dasar http - host: {2}: {3}, pengguna: {0},
|
||||
lulus: {1} ditemukan!"
|
||||
http_auth_failed: "Otentikasi dasar http gagal {0}: {3} menggunakan {1}: {2}"
|
||||
http_form_auth_success:
|
||||
"keberhasilan otentikasi bentuk http - host: {2}: {3}, pengguna:
|
||||
{0}, lulus: {1} ditemukan!"
|
||||
http_form_auth_failed: "Otentikasi bentuk http gagal {0}: {3} menggunakan {1}: {2}"
|
||||
http_ntlm_success:
|
||||
"Keberhasilan autentikasi ntlm http: host: {2}: {3}, pengguna:
|
||||
{0}, lulus: {1} ditemukan!"
|
||||
http_ntlm_failed: "Otentikasi ntlm http gagal {0}: {3} menggunakan {1}: {2}"
|
||||
no_response: tidak bisa mendapatkan respons dari target
|
||||
category_framework: "kategori: {0}, kerangka kerja: {1} ditemukan!"
|
||||
nothing_found: tidak ditemukan apa pun di {0} dalam {1}!
|
||||
no_auth: "Tidak ada auth yang ditemukan pada {0}: {1}"
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
pass
|
||||
15
logo.txt
15
logo.txt
|
|
@ -1,15 +0,0 @@
|
|||
|
||||
______ __ _____ _____
|
||||
/ __ \ \ / /\ / ____| __ \
|
||||
| | | \ \ /\ / / \ | (___ | |__) |
|
||||
| | | |\ \/ \/ / /\ \ \___ \| ___/
|
||||
| |__| | \ /\ / ____ \ ____) | | {2}Version {0}{3}
|
||||
\____/ \/ \/_/ \_\_____/|_| {4}{1}{5}
|
||||
_ _ _ _ _
|
||||
| \ | | | | | | | |
|
||||
{6}github.com/OWASP {7} | \| | ___| |_| |_ __ _ ___| | _____ _ __
|
||||
{8}owasp.org{9} | . ` |/ _ \ __| __/ _` |/ __| |/ / _ \ '__|
|
||||
{10}z3r0d4y.com{11} | |\ | __/ |_| || (_| | (__| < __/ |
|
||||
|_| \_|\___|\__|\__\__,_|\___|_|\_\___|_|
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
site_name: OWASP Nettacker Documentation
|
||||
theme:
|
||||
name: material
|
||||
nav:
|
||||
- Home: Home.md
|
||||
- Installation: Installation.md
|
||||
- Usage: Usage.md
|
||||
- Modules: Modules.md
|
||||
- Media: Media.md
|
||||
- API: API.md
|
||||
- Contributing: Developers.md
|
||||
- Events: Events.md
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
info:
|
||||
name: subdomain_takeover_vuln
|
||||
author: OWASP Nettacker Team
|
||||
severity: 5
|
||||
description: "let us assume that example.com is the target and that the team running example.com have a bug bounty programme. While enumerating all of the subdomains belonging to example.com — a process that we will explore later — a hacker stumbles across subdomain.example.com, a subdomain pointing to GitHub pages. We can determine this by reviewing the subdomain's DNS records; in this example, subdomain.example.com has multiple A records pointing to GitHub's dedicated IP addresses for custom pages."
|
||||
reference: "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/10-Test_for_Subdomain_Takeover"
|
||||
profiles:
|
||||
- vuln
|
||||
- vulnerability
|
||||
- http
|
||||
- medium_severity
|
||||
- takeover
|
||||
|
||||
payloads:
|
||||
- library: http
|
||||
verify: false
|
||||
timeout: 3
|
||||
cert: ""
|
||||
stream: false
|
||||
proxies: ""
|
||||
steps:
|
||||
- method: get
|
||||
headers:
|
||||
User-Agent: "{user_agent}"
|
||||
allow_redirects: false
|
||||
url:
|
||||
nettacker_fuzzer:
|
||||
input_format: "{{schema}}://{target}:{{ports}}"
|
||||
prefix: ""
|
||||
suffix: ""
|
||||
interceptors:
|
||||
data:
|
||||
schema:
|
||||
- "http"
|
||||
- "https"
|
||||
ports:
|
||||
- 80
|
||||
- 443
|
||||
response:
|
||||
condition_type: or
|
||||
conditions:
|
||||
content:
|
||||
# more to be added
|
||||
regex: The specified bucket does not exist|Repository not found|The page you have requested does not exist|There isn\'t a GitHub Pages site here.
|
||||
reverse: false
|
||||
18
nettacker.py
18
nettacker.py
|
|
@ -1,18 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""OWASP Nettacker application entry point."""
|
||||
|
||||
from core.compatible import check_dependencies
|
||||
from nettacker.main import run
|
||||
|
||||
"""
|
||||
entry point of OWASP Nettacker framework
|
||||
"""
|
||||
|
||||
# __check_external_modules created to check requirements before load the engine
|
||||
if __name__ == "__main__":
|
||||
check_dependencies() # check for dependencies
|
||||
|
||||
# if dependencies and OS requirements are match then load the program
|
||||
from core.parse import load
|
||||
|
||||
load() # load and parse the ARGV
|
||||
# sys.exit(main())
|
||||
run()
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
all_module_severity_and_desc = {}
|
||||
|
|
@ -1,34 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
from core.load_modules import load_all_modules, load_all_profiles
|
||||
from core.load_modules import load_all_graphs
|
||||
from core.alert import messages
|
||||
|
||||
from flask import abort
|
||||
from config import nettacker_paths
|
||||
|
||||
|
||||
def structure(status="", msg=""):
|
||||
"""
|
||||
basic JSON message structure
|
||||
|
||||
Args:
|
||||
status: status (ok, failed)
|
||||
msg: the message content
|
||||
|
||||
Returns:
|
||||
a JSON message
|
||||
"""
|
||||
return {
|
||||
"status": status,
|
||||
"msg": msg
|
||||
}
|
||||
from nettacker.config import Config
|
||||
from nettacker.core.app import Nettacker
|
||||
from nettacker.core.messages import messages as _
|
||||
from nettacker.core.messages import get_languages
|
||||
|
||||
|
||||
def get_value(flask_request, key):
|
||||
"""
|
||||
get a value from GET, POST or CCOKIES
|
||||
get a value from GET, POST or COOKIES
|
||||
|
||||
Args:
|
||||
flask_request: the flask request
|
||||
|
|
@ -37,13 +19,12 @@ def get_value(flask_request, key):
|
|||
Returns:
|
||||
the value content if found otherwise None
|
||||
"""
|
||||
return dict(
|
||||
flask_request.args
|
||||
).get(key) or dict(
|
||||
flask_request.form
|
||||
).get(key) or dict(
|
||||
flask_request.cookies
|
||||
).get(key) or ""
|
||||
return (
|
||||
dict(flask_request.args).get(key)
|
||||
or dict(flask_request.form).get(key)
|
||||
or dict(flask_request.cookies).get(key)
|
||||
or ""
|
||||
)
|
||||
|
||||
|
||||
def mime_types():
|
||||
|
|
@ -54,6 +35,9 @@ def mime_types():
|
|||
all mime types in json
|
||||
"""
|
||||
return {
|
||||
".3g2": "video/3gpp2",
|
||||
".3gp": "video/3gpp",
|
||||
".7z": "application/x-7z-compressed",
|
||||
".aac": "audio/aac",
|
||||
".abw": "application/x-abiword",
|
||||
".arc": "application/octet-stream",
|
||||
|
|
@ -90,8 +74,8 @@ def mime_types():
|
|||
".ogv": "video/ogg",
|
||||
".ogx": "application/ogg",
|
||||
".otf": "font/otf",
|
||||
".png": "image/png",
|
||||
".pdf": "application/pdf",
|
||||
".png": "image/png",
|
||||
".ppt": "application/vnd.ms-powerpoint",
|
||||
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
".rar": "application/x-rar-compressed",
|
||||
|
|
@ -118,11 +102,8 @@ def mime_types():
|
|||
".xml": "application/xml",
|
||||
".xul": "application/vnd.mozilla.xul+xml",
|
||||
".zip": "application/zip",
|
||||
".3gp": "video/3gpp",
|
||||
"audio/3gpp": "video",
|
||||
".3g2": "video/3gpp2",
|
||||
"audio/3gpp2": "video",
|
||||
".7z": "application/x-7z-compressed"
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -136,10 +117,13 @@ def get_file(filename):
|
|||
Returns:
|
||||
content of the file or abort(404)
|
||||
"""
|
||||
if not os.path.normpath(filename).startswith(str(Config.path.web_static_dir)):
|
||||
abort(404)
|
||||
try:
|
||||
return open(filename, 'rb').read()
|
||||
return open(filename, "rb").read()
|
||||
except ValueError:
|
||||
abort(404)
|
||||
except IOError:
|
||||
print(filename)
|
||||
abort(404)
|
||||
|
||||
|
||||
|
|
@ -156,7 +140,7 @@ def api_key_is_valid(app, flask_request):
|
|||
|
||||
"""
|
||||
if app.config["OWASP_NETTACKER_CONFIG"]["api_access_key"] != get_value(flask_request, "key"):
|
||||
abort(401, messages("API_invalid"))
|
||||
abort(401, _("API_invalid"))
|
||||
return
|
||||
|
||||
|
||||
|
|
@ -167,39 +151,37 @@ def languages_to_country():
|
|||
Returns:
|
||||
HTML code for each language with its country flag
|
||||
"""
|
||||
from core.load_modules import load_all_languages
|
||||
languages = load_all_languages()
|
||||
languages = get_languages()
|
||||
res = ""
|
||||
flags = {
|
||||
"ar": "sa",
|
||||
"bn": "in",
|
||||
"de": "de",
|
||||
"el": "gr",
|
||||
"fr": "fr",
|
||||
"en": "us",
|
||||
"es": "es",
|
||||
"fa": "ir",
|
||||
"fr": "fr",
|
||||
"hi": "in",
|
||||
"hy": "am",
|
||||
"id": "id",
|
||||
"it": "it",
|
||||
"iw": "il",
|
||||
"ja": "jp",
|
||||
"ko": "kr",
|
||||
"nl": "nl",
|
||||
"ps": "ps",
|
||||
"tr": "tr",
|
||||
"de": "de",
|
||||
"ko": "kr",
|
||||
"it": "it",
|
||||
"ja": "jp",
|
||||
"fa": "ir",
|
||||
"hy": "am",
|
||||
"ar": "sa",
|
||||
"zh-cn": "cn",
|
||||
"vi": "vi",
|
||||
"pt-br": "br",
|
||||
"ru": "ru",
|
||||
"hi": "in",
|
||||
"tr": "tr",
|
||||
"ur": "pk",
|
||||
"id": "id",
|
||||
"es": "es",
|
||||
"iw": "il",
|
||||
"pt-br": "br"
|
||||
"vi": "vi",
|
||||
"zh-cn": "cn",
|
||||
}
|
||||
for language in languages:
|
||||
res += """<option {2} id="{0}" data-content='<span class="flag-icon flag-icon-{1}"
|
||||
value="{0}"></span> {0}'></option>""".format(
|
||||
language,
|
||||
flags[language],
|
||||
"selected" if language == "en" else ""
|
||||
language, flags[language], "selected" if language == "en" else ""
|
||||
)
|
||||
return res
|
||||
|
||||
|
|
@ -211,11 +193,15 @@ def graphs():
|
|||
Returns:
|
||||
HTML content or available graphs
|
||||
"""
|
||||
res = """<label><input id="" type="radio" name="graph_name" value="" class="radio"><a
|
||||
class="label label-default">None</a></label> """
|
||||
for graph in load_all_graphs():
|
||||
res += """<label><input id="{0}" type="radio" name="graph_name" value="{0}" class="radio"><a
|
||||
class="label label-default">{0}</a></label> """.format(graph)
|
||||
res = """
|
||||
<label><input id="" type="radio" name="graph_name" value="" class="radio">
|
||||
<a class="label label-default">None</a></label> """
|
||||
for graph in Nettacker.load_graphs():
|
||||
res += """
|
||||
<label><input id="{0}" type="radio" name="graph_name" value="{0}" class="radio">
|
||||
<a class="label label-default">{0}</a></label> """.format(
|
||||
graph
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
|
|
@ -227,16 +213,21 @@ def profiles():
|
|||
HTML content or available profiles
|
||||
"""
|
||||
res = ""
|
||||
for profile in sorted(load_all_profiles().keys()):
|
||||
label = "success" if (
|
||||
profile == "scan"
|
||||
) else "warning" if (
|
||||
profile == "brute"
|
||||
) else "danger" if (
|
||||
profile == "vulnerability"
|
||||
) else "default"
|
||||
res += """<label><input id="{0}" type="checkbox" class="checkbox checkbox-{0}"><a class="label
|
||||
label-{1}">{0}</a></label> """.format(profile, label)
|
||||
for profile in sorted(Nettacker.load_profiles().keys()):
|
||||
label = (
|
||||
"success"
|
||||
if (profile == "scan")
|
||||
else "warning"
|
||||
if (profile == "brute")
|
||||
else "danger"
|
||||
if (profile == "vulnerability")
|
||||
else "default"
|
||||
)
|
||||
res += """
|
||||
<label><input id="{0}" type="checkbox" class="checkbox checkbox-{0}">
|
||||
<a class="label label-{1}">{0}</a></label> """.format(
|
||||
profile, label
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
|
|
@ -247,24 +238,30 @@ def scan_methods():
|
|||
Returns:
|
||||
HTML content or available modules
|
||||
"""
|
||||
methods = load_all_modules()
|
||||
methods = Nettacker.load_modules()
|
||||
methods.pop("all")
|
||||
res = ""
|
||||
for sm in methods.keys():
|
||||
label = "success" if sm.endswith(
|
||||
"_scan"
|
||||
) else "warning" if sm.endswith(
|
||||
"_brute"
|
||||
) else "danger" if sm.endswith(
|
||||
"_vuln"
|
||||
) else "default"
|
||||
profile = "scan" if sm.endswith(
|
||||
"_scan"
|
||||
) else "brute" if sm.endswith(
|
||||
"_brute"
|
||||
) else "vuln" if sm.endswith(
|
||||
"_vuln"
|
||||
) else "default"
|
||||
label = (
|
||||
"success"
|
||||
if sm.endswith("_scan")
|
||||
else "warning"
|
||||
if sm.endswith("_brute")
|
||||
else "danger"
|
||||
if sm.endswith("_vuln")
|
||||
else "default"
|
||||
)
|
||||
profile = (
|
||||
"scan"
|
||||
if sm.endswith("_scan")
|
||||
else "brute"
|
||||
if sm.endswith("_brute")
|
||||
else "vuln"
|
||||
if sm.endswith("_vuln")
|
||||
else "default"
|
||||
)
|
||||
res += """<label><input id="{0}" type="checkbox" class="checkbox checkbox-{2}-module">
|
||||
<a class="label label-{1}">{0}</a></label> """.format(sm, label, profile)
|
||||
<a class="label label-{1}">{0}</a></label> """.format(
|
||||
sm, label, profile
|
||||
)
|
||||
return res
|
||||
|
|
@ -0,0 +1,615 @@
|
|||
import csv
|
||||
import json
|
||||
import multiprocessing
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
from threading import Thread
|
||||
from types import SimpleNamespace
|
||||
|
||||
from flask import Flask, jsonify
|
||||
from flask import request as flask_request
|
||||
from flask import render_template, abort, Response, make_response
|
||||
from werkzeug.serving import WSGIRequestHandler
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from nettacker import logger
|
||||
from nettacker.api.core import (
|
||||
get_value,
|
||||
get_file,
|
||||
mime_types,
|
||||
scan_methods,
|
||||
profiles,
|
||||
graphs,
|
||||
languages_to_country,
|
||||
api_key_is_valid,
|
||||
)
|
||||
from nettacker.api.helpers import structure
|
||||
from nettacker.config import Config
|
||||
from nettacker.core.app import Nettacker
|
||||
from nettacker.core.die import die_failure
|
||||
from nettacker.core.graph import create_compare_report
|
||||
from nettacker.core.messages import messages as _
|
||||
from nettacker.core.utils.common import now, generate_compare_filepath
|
||||
from nettacker.database.db import (
|
||||
create_connection,
|
||||
get_logs_by_scan_id,
|
||||
select_reports,
|
||||
get_scan_result,
|
||||
last_host_logs,
|
||||
logs_to_report_json,
|
||||
search_logs,
|
||||
logs_to_report_html,
|
||||
)
|
||||
from nettacker.database.models import Report
|
||||
|
||||
# Monkey-patching the Server header to avoid exposing the actual version
|
||||
WSGIRequestHandler.version_string = lambda self: "API"
|
||||
|
||||
log = logger.get_logger()
|
||||
|
||||
app = Flask(__name__, template_folder=str(Config.path.web_static_dir))
|
||||
app.config.from_object(__name__)
|
||||
|
||||
nettacker_path_config = Config.path
|
||||
nettacker_application_config = Config.settings.as_dict()
|
||||
nettacker_application_config.update(Config.api.as_dict())
|
||||
del nettacker_application_config["api_access_key"]
|
||||
|
||||
|
||||
@app.errorhandler(400)
|
||||
def error_400(error):
|
||||
"""
|
||||
handle the 400 HTTP error
|
||||
|
||||
Args:
|
||||
error: the flask error
|
||||
|
||||
Returns:
|
||||
400 JSON error
|
||||
"""
|
||||
return jsonify(structure(status="error", msg=error.description)), 400
|
||||
|
||||
|
||||
@app.errorhandler(401)
|
||||
def error_401(error):
|
||||
"""
|
||||
handle the 401 HTTP error
|
||||
|
||||
Args:
|
||||
error: the flask error
|
||||
|
||||
Returns:
|
||||
401 JSON error
|
||||
"""
|
||||
return jsonify(structure(status="error", msg=error.description)), 401
|
||||
|
||||
|
||||
@app.errorhandler(403)
|
||||
def error_403(error):
|
||||
"""
|
||||
handle the 403 HTTP error
|
||||
|
||||
Args:
|
||||
error: the flask error
|
||||
|
||||
Returns:
|
||||
403 JSON error
|
||||
"""
|
||||
return jsonify(structure(status="error", msg=error.description)), 403
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def error_404(error):
|
||||
"""
|
||||
handle the 404 HTTP error
|
||||
|
||||
Args:
|
||||
error: the flask error
|
||||
|
||||
Returns:
|
||||
404 JSON error
|
||||
"""
|
||||
return jsonify(structure(status="error", msg=_("not_found"))), 404
|
||||
|
||||
|
||||
@app.before_request
|
||||
def limit_remote_addr():
|
||||
"""
|
||||
check if IP filtering applied and API address is in whitelist
|
||||
|
||||
Returns:
|
||||
None if it's in whitelist otherwise abort(403)
|
||||
"""
|
||||
# IP Limitation
|
||||
if app.config["OWASP_NETTACKER_CONFIG"]["api_client_whitelisted_ips"]:
|
||||
if (
|
||||
flask_request.remote_addr
|
||||
not in app.config["OWASP_NETTACKER_CONFIG"]["api_client_whitelisted_ips"]
|
||||
):
|
||||
abort(403, _("unauthorized_IP"))
|
||||
return
|
||||
|
||||
|
||||
@app.after_request
|
||||
def set_security_headers(response):
|
||||
"""
|
||||
Add common security headers to every response.
|
||||
"""
|
||||
response.headers.setdefault("Content-Security-Policy", "upgrade-insecure-requests")
|
||||
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
||||
response.headers.setdefault("X-Frame-Options", "SAMEORIGIN")
|
||||
response.headers.setdefault("X-XSS-Protection", "1; mode=block")
|
||||
response.headers.setdefault("Referrer-Policy", "no-referrer-when-downgrade")
|
||||
return response
|
||||
|
||||
|
||||
@app.after_request
|
||||
def access_log(response):
|
||||
"""
|
||||
Write to the access log file if enabled.
|
||||
|
||||
Args:
|
||||
response: the flask response
|
||||
|
||||
Returns:
|
||||
the flask response
|
||||
"""
|
||||
if app.config["OWASP_NETTACKER_CONFIG"]["api_access_log"]:
|
||||
log_request = open(app.config["OWASP_NETTACKER_CONFIG"]["api_access_log"], "ab")
|
||||
log_request.write(
|
||||
'{0} [{1}] {2} "{3} {4}" {5} {6} {7}\r\n'.format(
|
||||
flask_request.remote_addr,
|
||||
now(),
|
||||
flask_request.host,
|
||||
flask_request.method,
|
||||
flask_request.full_path,
|
||||
flask_request.user_agent,
|
||||
response.status_code,
|
||||
json.dumps(flask_request.form),
|
||||
).encode()
|
||||
)
|
||||
log_request.close()
|
||||
return response
|
||||
|
||||
|
||||
@app.route("/<path:path>")
|
||||
def get_statics(path):
|
||||
"""
|
||||
getting static files and return content mime types
|
||||
|
||||
Args:
|
||||
path: path and filename
|
||||
|
||||
Returns:
|
||||
file content and content type if file found otherwise abort(404)
|
||||
"""
|
||||
static_types = mime_types()
|
||||
return Response(
|
||||
get_file(os.path.join(Config.path.web_static_dir, path)),
|
||||
mimetype=static_types.get(os.path.splitext(path)[1], "text/html"),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/", methods=["GET", "POST"])
|
||||
def index():
|
||||
"""
|
||||
index page for WebUI
|
||||
|
||||
Returns:
|
||||
rendered HTML page
|
||||
"""
|
||||
return render_template(
|
||||
"index.html",
|
||||
selected_modules=scan_methods(),
|
||||
profile=profiles(),
|
||||
languages=languages_to_country(),
|
||||
graphs=graphs(),
|
||||
filename=Config.settings.report_path_filename,
|
||||
)
|
||||
|
||||
|
||||
def sanitize_report_path_filename(report_path_filename):
|
||||
"""
|
||||
sanitize the report_path_filename
|
||||
|
||||
Args:
|
||||
report_path_filename: the report path filename
|
||||
|
||||
Returns:
|
||||
the sanitized report path filename
|
||||
"""
|
||||
filename = secure_filename(os.path.basename(report_path_filename))
|
||||
if not filename:
|
||||
return False
|
||||
# Define a list or tuple of valid extensions
|
||||
VALID_EXTENSIONS = (".html", ".htm", ".txt", ".json", ".csv")
|
||||
if "." in filename:
|
||||
if filename.endswith(VALID_EXTENSIONS):
|
||||
safe_report_path = nettacker_path_config.results_dir / filename
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
safe_report_path = nettacker_path_config.results_dir / filename
|
||||
if not safe_report_path.is_relative_to(nettacker_path_config.results_dir):
|
||||
return False
|
||||
return safe_report_path
|
||||
|
||||
|
||||
@app.route("/new/scan", methods=["GET", "POST"])
|
||||
def new_scan():
|
||||
"""
|
||||
new scan through the API
|
||||
|
||||
Returns:
|
||||
a JSON message with scan details if success otherwise a JSON error
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
form_values = dict(flask_request.form)
|
||||
# variables for future reference
|
||||
raw_report_path_filename = form_values.get("report_path_filename")
|
||||
http_header = form_values.get("http_header")
|
||||
report_path_filename = sanitize_report_path_filename(raw_report_path_filename)
|
||||
if not report_path_filename:
|
||||
return jsonify(structure(status="error", msg="Invalid report filename")), 400
|
||||
form_values["report_path_filename"] = str(report_path_filename)
|
||||
for key in nettacker_application_config:
|
||||
if key not in form_values:
|
||||
form_values[key] = nettacker_application_config[key]
|
||||
# Handle HTTP headers
|
||||
if http_header:
|
||||
form_values["http_header"] = [
|
||||
line.strip() for line in http_header.split("\n") if line.strip()
|
||||
]
|
||||
# Handle service discovery
|
||||
form_values["skip_service_discovery"] = form_values.get("skip_service_discovery", "") == "true"
|
||||
nettacker_app = Nettacker(api_arguments=SimpleNamespace(**form_values))
|
||||
app.config["OWASP_NETTACKER_CONFIG"]["options"] = nettacker_app.arguments
|
||||
thread = Thread(target=nettacker_app.run)
|
||||
thread.start()
|
||||
|
||||
return jsonify(vars(nettacker_app.arguments)), 200
|
||||
|
||||
|
||||
@app.route("/compare/scans", methods=["POST"])
|
||||
def compare_scans():
|
||||
"""
|
||||
compare two scans through the API
|
||||
Returns:
|
||||
Success if the comparision is successfull and report is saved and error if not.
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
|
||||
scan_id_first = get_value(flask_request, "scan_id_first")
|
||||
scan_id_second = get_value(flask_request, "scan_id_second")
|
||||
if not scan_id_first or not scan_id_second:
|
||||
return jsonify(structure(status="error", msg="Invalid Scan IDs")), 400
|
||||
|
||||
compare_report_path_filename = get_value(flask_request, "compare_report_path")
|
||||
if not compare_report_path_filename:
|
||||
compare_report_path_filename = generate_compare_filepath(scan_id_first)
|
||||
|
||||
compare_options = {
|
||||
"scan_compare_id": scan_id_second,
|
||||
"compare_report_path_filename": compare_report_path_filename,
|
||||
}
|
||||
|
||||
try:
|
||||
result = create_compare_report(compare_options, scan_id_first)
|
||||
if result:
|
||||
return jsonify(
|
||||
structure(
|
||||
status="success",
|
||||
msg="scan_comparison_completed",
|
||||
)
|
||||
), 200
|
||||
return jsonify(structure(status="error", msg="Scan ID not found")), 404
|
||||
except (FileNotFoundError, PermissionError, IOError):
|
||||
return jsonify(structure(status="error", msg="Invalid file path")), 400
|
||||
|
||||
|
||||
@app.route("/session/check", methods=["GET"])
|
||||
def session_check():
|
||||
"""
|
||||
check the session if it's valid
|
||||
|
||||
Returns:
|
||||
a JSON message if it's valid otherwise abort(401)
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
return jsonify(structure(status="ok", msg=_("browser_session_valid"))), 200
|
||||
|
||||
|
||||
@app.route("/session/set", methods=["GET", "POST"])
|
||||
def session_set():
|
||||
"""
|
||||
set session on the browser
|
||||
|
||||
Returns:
|
||||
200 HTTP response if session is valid and a set-cookie in the
|
||||
response if success otherwise abort(403)
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
res = make_response(jsonify(structure(status="ok", msg=_("browser_session_valid"))))
|
||||
res.set_cookie(
|
||||
"key",
|
||||
value=app.config["OWASP_NETTACKER_CONFIG"]["api_access_key"],
|
||||
httponly=True,
|
||||
samesite="Lax",
|
||||
secure=True,
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
@app.route("/session/kill", methods=["GET"])
|
||||
def session_kill():
|
||||
"""
|
||||
unset session on the browser
|
||||
|
||||
Returns:
|
||||
a 200 HTTP response with set-cookie to "expired"
|
||||
to unset the cookie on the browser
|
||||
"""
|
||||
res = make_response(jsonify(structure(status="ok", msg=_("browser_session_killed"))))
|
||||
res.set_cookie("key", "", expires=0)
|
||||
return res
|
||||
|
||||
|
||||
@app.route("/results/get_list", methods=["GET"])
|
||||
def get_results():
|
||||
"""
|
||||
get list of scan's results through the API
|
||||
|
||||
Returns:
|
||||
an array of JSON scan's results if success otherwise abort(403)
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
page = get_value(flask_request, "page")
|
||||
if not page:
|
||||
page = 1
|
||||
return jsonify(select_reports(int(page))), 200
|
||||
|
||||
|
||||
@app.route("/results/get", methods=["GET"])
|
||||
def get_result_content():
|
||||
"""
|
||||
get a result HTML/TEXT/JSON content
|
||||
|
||||
Returns:
|
||||
content of the scan result
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
scan_id = get_value(flask_request, "id")
|
||||
if not scan_id:
|
||||
return jsonify(structure(status="error", msg=_("invalid_scan_id"))), 400
|
||||
|
||||
try:
|
||||
filename, file_content = get_scan_result(scan_id)
|
||||
except Exception:
|
||||
return jsonify(structure(status="error", msg="database error!")), 500
|
||||
|
||||
return Response(
|
||||
file_content,
|
||||
mimetype=mime_types().get(os.path.splitext(filename)[1], "text/plain"),
|
||||
headers={"Content-Disposition": "attachment;filename=" + filename.split("/")[-1]},
|
||||
)
|
||||
|
||||
|
||||
@app.route("/results/get_json", methods=["GET"])
|
||||
def get_results_json():
|
||||
"""
|
||||
get host's logs through the API in JSON type
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
session = create_connection()
|
||||
result_id = get_value(flask_request, "id")
|
||||
if not result_id:
|
||||
return jsonify(structure(status="error", msg=_("invalid_scan_id"))), 400
|
||||
scan_details = session.query(Report).filter(Report.id == result_id).first()
|
||||
json_object = json.dumps(get_logs_by_scan_id(scan_details.scan_unique_id))
|
||||
filename = ".".join(scan_details.report_path_filename.split(".")[:-1])[1:] + ".json"
|
||||
return Response(
|
||||
json_object,
|
||||
mimetype="application/json",
|
||||
headers={"Content-Disposition": "attachment;filename=" + filename},
|
||||
)
|
||||
|
||||
|
||||
@app.route("/results/get_csv", methods=["GET"])
|
||||
def get_results_csv(): # todo: need to fix time format
|
||||
"""
|
||||
get host's logs through the API in JSON type
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
session = create_connection()
|
||||
result_id = get_value(flask_request, "id")
|
||||
if not result_id:
|
||||
return jsonify(structure(status="error", msg=_("invalid_scan_id"))), 400
|
||||
scan_details = session.query(Report).filter(Report.id == result_id).first()
|
||||
data = get_logs_by_scan_id(scan_details.scan_unique_id)
|
||||
keys = data[0].keys()
|
||||
filename = ".".join(scan_details.report_path_filename.split(".")[:-1])[1:] + ".csv"
|
||||
with open(filename, "w") as report_path_filename:
|
||||
dict_writer = csv.DictWriter(report_path_filename, fieldnames=keys, quoting=csv.QUOTE_ALL)
|
||||
dict_writer.writeheader()
|
||||
for event in data:
|
||||
dict_writer.writerow({key: value for key, value in event.items() if key in keys})
|
||||
with open(filename, "r") as report_path_filename:
|
||||
reader = report_path_filename.read()
|
||||
return Response(
|
||||
reader,
|
||||
mimetype="text/csv",
|
||||
headers={"Content-Disposition": "attachment;filename=" + filename},
|
||||
)
|
||||
|
||||
|
||||
@app.route("/logs/get_list", methods=["GET"])
|
||||
def get_last_host_logs(): # need to check
|
||||
"""
|
||||
get list of logs through the API
|
||||
|
||||
Returns:
|
||||
an array of JSON logs if success otherwise abort(403)
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
page = get_value(flask_request, "page")
|
||||
if not page:
|
||||
page = 1
|
||||
return jsonify(last_host_logs(int(page))), 200
|
||||
|
||||
|
||||
@app.route("/logs/get_html", methods=["GET"])
|
||||
def get_logs_html(): # todo: check until here - ali
|
||||
"""
|
||||
get host's logs through the API in HTML type
|
||||
|
||||
Returns:
|
||||
HTML report
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
target = get_value(flask_request, "target")
|
||||
return make_response(logs_to_report_html(target))
|
||||
|
||||
|
||||
@app.route("/logs/get_json", methods=["GET"])
|
||||
def get_logs():
|
||||
"""
|
||||
get host's logs through the API in JSON type
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
target = get_value(flask_request, "target")
|
||||
data = logs_to_report_json(target)
|
||||
json_object = json.dumps(data)
|
||||
filename = (
|
||||
"report-"
|
||||
+ now(format="%Y_%m_%d_%H_%M_%S")
|
||||
+ "".join(random.choice(string.ascii_lowercase) for _ in range(10))
|
||||
)
|
||||
return Response(
|
||||
json_object,
|
||||
mimetype="application/json",
|
||||
headers={"Content-Disposition": "attachment;filename=" + filename + ".json"},
|
||||
)
|
||||
|
||||
|
||||
@app.route("/logs/get_csv", methods=["GET"])
|
||||
def get_logs_csv():
|
||||
"""
|
||||
get target's logs through the API in JSON type
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
target = get_value(flask_request, "target")
|
||||
data = logs_to_report_json(target)
|
||||
keys = data[0].keys()
|
||||
filename = (
|
||||
"report-"
|
||||
+ now(format="%Y_%m_%d_%H_%M_%S")
|
||||
+ "".join(random.choice(string.ascii_lowercase) for _ in range(10))
|
||||
)
|
||||
with open(filename, "w") as report_path_filename:
|
||||
dict_writer = csv.DictWriter(report_path_filename, fieldnames=keys, quoting=csv.QUOTE_ALL)
|
||||
dict_writer.writeheader()
|
||||
for event in data:
|
||||
dict_writer.writerow({key: value for key, value in event.items() if key in keys})
|
||||
with open(filename, "r") as report_path_filename:
|
||||
reader = report_path_filename.read()
|
||||
return Response(
|
||||
reader,
|
||||
mimetype="text/csv",
|
||||
headers={"Content-Disposition": f"attachment;filename={filename}.csv"},
|
||||
)
|
||||
|
||||
|
||||
@app.route("/logs/search", methods=["GET"])
|
||||
def go_for_search_logs():
|
||||
"""
|
||||
search in all events
|
||||
|
||||
Returns:
|
||||
an array with JSON events
|
||||
"""
|
||||
api_key_is_valid(app, flask_request)
|
||||
try:
|
||||
page = int(get_value(flask_request, "page"))
|
||||
if page > 0:
|
||||
page -= 1
|
||||
except Exception:
|
||||
page = 0
|
||||
try:
|
||||
query = get_value(flask_request, "q")
|
||||
except Exception:
|
||||
query = ""
|
||||
return jsonify(search_logs(page, query)), 200
|
||||
|
||||
|
||||
def start_api_subprocess(options):
|
||||
"""
|
||||
a function to run flask in a subprocess to make kill signal in a better
|
||||
way!
|
||||
|
||||
Args:
|
||||
options: all options
|
||||
"""
|
||||
app.config["OWASP_NETTACKER_CONFIG"] = {
|
||||
"api_access_key": options.api_access_key,
|
||||
"api_client_whitelisted_ips": options.api_client_whitelisted_ips,
|
||||
"api_access_log": options.api_access_log,
|
||||
"api_cert": options.api_cert,
|
||||
"api_cert_key": options.api_cert_key,
|
||||
"language": options.language,
|
||||
"options": options,
|
||||
}
|
||||
try:
|
||||
if options.api_cert and options.api_cert_key:
|
||||
app.run(
|
||||
host=options.api_hostname,
|
||||
port=options.api_port,
|
||||
debug=options.api_debug_mode,
|
||||
ssl_context=(options.api_cert, options.api_cert_key),
|
||||
threaded=True,
|
||||
)
|
||||
else:
|
||||
app.run(
|
||||
host=options.api_hostname,
|
||||
port=options.api_port,
|
||||
debug=options.api_debug_mode,
|
||||
ssl_context="adhoc",
|
||||
threaded=True,
|
||||
)
|
||||
except Exception as e:
|
||||
die_failure(str(e))
|
||||
|
||||
|
||||
def start_api_server(options):
|
||||
"""
|
||||
entry point to run the API through the flask
|
||||
|
||||
Args:
|
||||
options: all options
|
||||
"""
|
||||
# Starting the API
|
||||
log.write_to_api_console(_("API_key").format(options.api_port, options.api_access_key))
|
||||
p = multiprocessing.Process(target=start_api_subprocess, args=(options,))
|
||||
p.start()
|
||||
# Sometimes it's take much time to terminate flask with CTRL+C
|
||||
# So It's better to use KeyboardInterrupt to terminate!
|
||||
while len(multiprocessing.active_children()) != 0:
|
||||
try:
|
||||
time.sleep(0.3)
|
||||
except KeyboardInterrupt:
|
||||
for process in multiprocessing.active_children():
|
||||
process.terminate()
|
||||
break
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
def structure(status="", msg=""):
|
||||
"""
|
||||
basic JSON message structure
|
||||
|
||||
Args:
|
||||
status: status (ok, failed)
|
||||
msg: the message content
|
||||
|
||||
Returns:
|
||||
a JSON message
|
||||
"""
|
||||
return {"status": status, "msg": msg}
|
||||
|
|
@ -7,4 +7,4 @@ OWASP Nettacker API files are stored in here.
|
|||
* `engine.py` is entry point of API and main functions
|
||||
* `api_core.py` has core functions
|
||||
* `start_scan.py` run new scans
|
||||
* `database.sqlite3` an empty API database for sample, its copy to `./.data/database.sqlite3` and stores data i there.
|
||||
* `database.sqlite3` an empty API database for sample, its copy to `./.nettacker/data/database.sqlite3` and stores data in there.
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
import inspect
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from nettacker import version
|
||||
from nettacker.core.utils.common import now, generate_random_token
|
||||
|
||||
CWD = Path.cwd()
|
||||
PACKAGE_PATH = Path(__file__).parent
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def version_info():
|
||||
"""
|
||||
version information of the framework
|
||||
|
||||
Returns:
|
||||
an array of version and code name
|
||||
"""
|
||||
|
||||
return version.__version__, version.__release_name__
|
||||
|
||||
|
||||
class ConfigBase:
|
||||
@classmethod
|
||||
def as_dict(cls):
|
||||
return {attr_name: getattr(cls, attr_name) for attr_name in cls()}
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.attributes = sorted(
|
||||
(
|
||||
attribute[0]
|
||||
for attribute in inspect.getmembers(self)
|
||||
if not attribute[0].startswith("_") and not inspect.ismethod(attribute[1])
|
||||
)
|
||||
)
|
||||
self.idx = 0
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.attributes
|
||||
|
||||
|
||||
# Some sensitive header fields for HTTP requests.
|
||||
# Please edit this if you don't want your HTTP header to be present in the logs
|
||||
sensitive_headers = {
|
||||
"authorization",
|
||||
"proxy-authorization",
|
||||
"cookie",
|
||||
"set-cookie",
|
||||
"x-api-key",
|
||||
"x-amz-security-token",
|
||||
"x-amz-credential",
|
||||
"x-amz-signature",
|
||||
"x-session-id",
|
||||
"x-csrf-token",
|
||||
"x-auth-token",
|
||||
"x-user-token",
|
||||
"x-id-token",
|
||||
}
|
||||
|
||||
|
||||
class ApiConfig(ConfigBase):
|
||||
"""OWASP Nettacker API Default Configuration"""
|
||||
|
||||
api_access_log = str(CWD / ".nettacker/data/nettacker.log")
|
||||
api_access_key = generate_random_token(32)
|
||||
api_client_whitelisted_ips = [] # disabled - to enable please put an array with list of ips/cidr/ranges
|
||||
# [
|
||||
# "127.0.0.1",
|
||||
# "10.0.0.0/24",
|
||||
# "192.168.1.1-192.168.1.255"
|
||||
# ],
|
||||
api_debug_mode = False
|
||||
api_hostname = "0.0.0.0"
|
||||
api_port = 5000
|
||||
start_api_server = False
|
||||
|
||||
|
||||
class DbConfig(ConfigBase):
|
||||
"""
|
||||
Database Config (could be modified by user)
|
||||
For sqlite database:
|
||||
fill the name of the DB as sqlite,
|
||||
DATABASE as the name of the db user wants
|
||||
other details can be left empty
|
||||
For mysql users:
|
||||
fill the ENGINE name of the DB as mysql
|
||||
NAME as the name of the database you want to create
|
||||
USERNAME, PASSWORD, HOST and the PORT of the MySQL server
|
||||
need to be filled respectively (default port is 3306)
|
||||
For postgres users:
|
||||
fill the Engine name of the DB as postgres
|
||||
NAME as the name of the database user wants
|
||||
USERNAME, PASSWORD, HOST and the PORT of the Postgres server
|
||||
need to be filled respectively (default port is 5432)
|
||||
Set ssl_mode to "require" if you need to use encrypted
|
||||
databases.
|
||||
"""
|
||||
|
||||
engine = "sqlite"
|
||||
name = str(CWD / ".nettacker/data/nettacker.db")
|
||||
host = ""
|
||||
port = ""
|
||||
username = ""
|
||||
password = ""
|
||||
ssl_mode = "disable"
|
||||
|
||||
|
||||
class PathConfig:
|
||||
"""
|
||||
home path for the framework (could be modify by user)
|
||||
|
||||
Returns:
|
||||
a JSON contain the working, tmp and results path
|
||||
"""
|
||||
|
||||
data_dir = CWD / ".nettacker/data"
|
||||
new_database_file = CWD / ".nettacker/data/nettacker.db"
|
||||
old_database_file = CWD / ".data/nettacker.db"
|
||||
graph_dir = PACKAGE_PATH / "lib/graph"
|
||||
home_dir = CWD
|
||||
locale_dir = PACKAGE_PATH / "locale"
|
||||
logo_file = PACKAGE_PATH / "logo.txt"
|
||||
module_protocols_dir = PACKAGE_PATH / "core/lib"
|
||||
modules_dir = PACKAGE_PATH / "modules"
|
||||
payloads_dir = PACKAGE_PATH / "lib/payloads"
|
||||
release_name_file = PACKAGE_PATH / "release_name.txt"
|
||||
results_dir = CWD / ".nettacker/data/results"
|
||||
tmp_dir = CWD / ".nettacker/data/tmp"
|
||||
web_static_dir = PACKAGE_PATH / "web/static"
|
||||
user_agents_file = PACKAGE_PATH / "lib/payloads/User-Agents/web_browsers_user_agents.txt"
|
||||
|
||||
|
||||
class DefaultSettings(ConfigBase):
|
||||
"""OWASP Nettacker Default Configuration"""
|
||||
|
||||
excluded_modules = None
|
||||
excluded_ports = None
|
||||
graph_name = "d3_tree_v2_graph"
|
||||
language = "en"
|
||||
modules_extra_args = None
|
||||
parallel_module_scan = 1
|
||||
passwords = None
|
||||
passwords_list = None
|
||||
ping_before_scan = False
|
||||
ports = None
|
||||
profiles = None
|
||||
report_path_filename = "{results_path}/results_{date_time}_{random_chars}.html".format(
|
||||
results_path=PathConfig.results_dir,
|
||||
date_time=now(format="%Y_%m_%d_%H_%M_%S"),
|
||||
random_chars=generate_random_token(10),
|
||||
)
|
||||
retries = 1
|
||||
scan_ip_range = False
|
||||
scan_subdomains = False
|
||||
selected_modules = None
|
||||
url_base_path = None
|
||||
http_header = None
|
||||
read_from_file = ""
|
||||
set_hardware_usage = "maximum" # low, normal, high, maximum
|
||||
show_all_modules = False
|
||||
show_all_profiles = False
|
||||
show_help_menu = False
|
||||
show_version = False
|
||||
skip_service_discovery = False
|
||||
socks_proxy = None
|
||||
targets = None
|
||||
targets_list = None
|
||||
thread_per_host = 100
|
||||
time_sleep_between_requests = 0.0
|
||||
timeout = 3.0
|
||||
user_agent = "Nettacker {version_number} {version_code}".format(
|
||||
version_number=version_info()[0], version_code=version_info()[1]
|
||||
)
|
||||
usernames = None
|
||||
usernames_list = None
|
||||
verbose_event = False
|
||||
verbose_mode = False
|
||||
scan_compare_id = None
|
||||
compare_report_path_filename = ""
|
||||
|
||||
|
||||
class Config:
|
||||
api = ApiConfig()
|
||||
db = DbConfig()
|
||||
path = PathConfig()
|
||||
settings = DefaultSettings()
|
||||
|
|
@ -0,0 +1,329 @@
|
|||
import copy
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
from threading import Thread
|
||||
|
||||
import multiprocess
|
||||
|
||||
from nettacker import logger
|
||||
from nettacker.config import Config, version_info
|
||||
from nettacker.core.arg_parser import ArgParser
|
||||
from nettacker.core.die import die_failure
|
||||
from nettacker.core.graph import create_report, create_compare_report
|
||||
from nettacker.core.ip import (
|
||||
get_ip_range,
|
||||
generate_ip_range,
|
||||
is_single_ipv4,
|
||||
is_ipv4_range,
|
||||
is_ipv4_cidr,
|
||||
is_single_ipv6,
|
||||
is_ipv6_range,
|
||||
is_ipv6_cidr,
|
||||
)
|
||||
from nettacker.core.messages import messages as _
|
||||
from nettacker.core.module import Module
|
||||
from nettacker.core.socks_proxy import set_socks_proxy
|
||||
from nettacker.core.utils import common as common_utils
|
||||
from nettacker.core.utils.common import wait_for_threads_to_finish
|
||||
from nettacker.database.db import find_events, remove_old_logs
|
||||
from nettacker.database.mysql import mysql_create_database, mysql_create_tables
|
||||
from nettacker.database.postgresql import postgres_create_database
|
||||
from nettacker.database.sqlite import sqlite_create_tables
|
||||
from nettacker.logger import TerminalCodes
|
||||
|
||||
log = logger.get_logger()
|
||||
|
||||
|
||||
class Nettacker(ArgParser):
|
||||
def __init__(self, api_arguments=None):
|
||||
if not api_arguments:
|
||||
self.print_logo()
|
||||
self.check_dependencies()
|
||||
|
||||
log.info(_("scan_started"))
|
||||
super().__init__(api_arguments=api_arguments)
|
||||
|
||||
@staticmethod
|
||||
def print_logo():
|
||||
"""
|
||||
OWASP Nettacker Logo
|
||||
"""
|
||||
log.write_to_api_console(
|
||||
open(Config.path.logo_file)
|
||||
.read()
|
||||
.format(
|
||||
cyan=TerminalCodes.CYAN.value,
|
||||
red=TerminalCodes.RED.value,
|
||||
rst=TerminalCodes.RESET.value,
|
||||
v1=version_info()[0],
|
||||
v2=version_info()[1],
|
||||
yellow=TerminalCodes.YELLOW.value,
|
||||
)
|
||||
)
|
||||
log.reset_color()
|
||||
|
||||
def check_dependencies(self):
|
||||
if sys.platform not in {"darwin", "freebsd13", "freebsd14", "freebsd15", "linux"}:
|
||||
die_failure(_("error_platform"))
|
||||
|
||||
try:
|
||||
Config.path.tmp_dir.mkdir(exist_ok=True, parents=True)
|
||||
Config.path.results_dir.mkdir(exist_ok=True, parents=True)
|
||||
except PermissionError:
|
||||
die_failure("Cannot access the directory {0}".format(Config.path.tmp_dir))
|
||||
|
||||
if Config.db.engine == "sqlite":
|
||||
try:
|
||||
if not Config.path.new_database_file.exists():
|
||||
Config.path.new_database_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
if Config.path.old_database_file.exists():
|
||||
shutil.copy(Config.path.old_database_file, Config.path.new_database_file)
|
||||
log.warn("Database files migrated from .data to .nettacker ...")
|
||||
else:
|
||||
sqlite_create_tables()
|
||||
except PermissionError:
|
||||
die_failure("cannot access the directory {0}".format(Config.path.home_dir))
|
||||
elif Config.db.engine == "mysql":
|
||||
try:
|
||||
mysql_create_database()
|
||||
mysql_create_tables()
|
||||
except Exception:
|
||||
die_failure(_("database_connection_failed"))
|
||||
elif Config.db.engine == "postgres":
|
||||
try:
|
||||
postgres_create_database()
|
||||
except Exception:
|
||||
die_failure(_("database_connection_failed"))
|
||||
else:
|
||||
die_failure(_("invalid_database"))
|
||||
|
||||
def expand_targets(self, scan_id):
|
||||
"""
|
||||
determine targets.
|
||||
|
||||
Args:
|
||||
options: all options
|
||||
scan_id: unique scan identifier
|
||||
|
||||
Returns:
|
||||
a generator
|
||||
"""
|
||||
targets = []
|
||||
base_path = ""
|
||||
for target in self.arguments.targets:
|
||||
if "://" in target:
|
||||
try:
|
||||
if not target.split("://")[1].split("/")[1]:
|
||||
base_path = ""
|
||||
else:
|
||||
base_path = "/".join(target.split("://")[1].split("/")[1:])
|
||||
if base_path[-1] != "/":
|
||||
base_path += "/"
|
||||
except IndexError:
|
||||
base_path = ""
|
||||
# remove url proto; uri; port
|
||||
target = target.split("://")[1].split("/")[0].split(":")[0]
|
||||
targets.append(target)
|
||||
# single IPs
|
||||
elif is_single_ipv4(target) or is_single_ipv6(target):
|
||||
if self.arguments.scan_ip_range:
|
||||
targets += get_ip_range(target)
|
||||
else:
|
||||
targets.append(target)
|
||||
# IP ranges
|
||||
elif (
|
||||
is_ipv4_range(target)
|
||||
or is_ipv6_range(target)
|
||||
or is_ipv4_cidr(target)
|
||||
or is_ipv6_cidr(target)
|
||||
):
|
||||
targets += generate_ip_range(target)
|
||||
# domains probably
|
||||
else:
|
||||
targets.append(target)
|
||||
self.arguments.targets = targets
|
||||
self.arguments.url_base_path = base_path
|
||||
|
||||
# subdomain_scan
|
||||
if self.arguments.scan_subdomains:
|
||||
selected_modules = self.arguments.selected_modules
|
||||
self.arguments.selected_modules = ["subdomain_scan"]
|
||||
self.start_scan(scan_id)
|
||||
self.arguments.selected_modules = selected_modules
|
||||
if "subdomain_scan" in self.arguments.selected_modules:
|
||||
self.arguments.selected_modules.remove("subdomain_scan")
|
||||
|
||||
for target in copy.deepcopy(self.arguments.targets):
|
||||
for row in find_events(target, "subdomain_scan", scan_id):
|
||||
for sub_domain in json.loads(row.json_event)["response"]["conditions_results"][
|
||||
"content"
|
||||
]:
|
||||
if sub_domain not in self.arguments.targets:
|
||||
self.arguments.targets.append(sub_domain)
|
||||
# icmp_scan
|
||||
if self.arguments.ping_before_scan:
|
||||
if os.geteuid() == 0:
|
||||
selected_modules = self.arguments.selected_modules
|
||||
self.arguments.selected_modules = ["icmp_scan"]
|
||||
self.start_scan(scan_id)
|
||||
self.arguments.selected_modules = selected_modules
|
||||
if "icmp_scan" in self.arguments.selected_modules:
|
||||
self.arguments.selected_modules.remove("icmp_scan")
|
||||
self.arguments.targets = self.filter_target_by_event(targets, scan_id, "icmp_scan")
|
||||
else:
|
||||
log.warn(_("icmp_need_root_access"))
|
||||
if "icmp_scan" in self.arguments.selected_modules:
|
||||
self.arguments.selected_modules.remove("icmp_scan")
|
||||
# port_scan
|
||||
if not self.arguments.skip_service_discovery:
|
||||
self.arguments.skip_service_discovery = True
|
||||
selected_modules = self.arguments.selected_modules
|
||||
self.arguments.selected_modules = ["port_scan"]
|
||||
self.start_scan(scan_id)
|
||||
self.arguments.selected_modules = selected_modules
|
||||
if "port_scan" in self.arguments.selected_modules:
|
||||
self.arguments.selected_modules.remove("port_scan")
|
||||
self.arguments.targets = self.filter_target_by_event(targets, scan_id, "port_scan")
|
||||
self.arguments.skip_service_discovery = False
|
||||
return list(set(self.arguments.targets))
|
||||
|
||||
def filter_target_by_event(self, targets, scan_id, module_name):
|
||||
for target in copy.deepcopy(targets):
|
||||
if not find_events(target, module_name, scan_id):
|
||||
targets.remove(target)
|
||||
return targets
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
preparing for attacks and managing multi-processing for host
|
||||
|
||||
Args:
|
||||
options: all options
|
||||
|
||||
Returns:
|
||||
True when it ends
|
||||
"""
|
||||
scan_id = common_utils.generate_random_token(32)
|
||||
log.info("ScanID: {0}".format(scan_id))
|
||||
log.info(_("regrouping_targets"))
|
||||
# find total number of targets + types + expand (subdomain, IPRanges, etc)
|
||||
# optimize CPU usage
|
||||
self.arguments.targets = self.expand_targets(scan_id)
|
||||
if not self.arguments.targets:
|
||||
log.info(_("no_live_service_found"))
|
||||
return True
|
||||
exit_code = self.start_scan(scan_id)
|
||||
create_report(self.arguments, scan_id)
|
||||
if self.arguments.scan_compare_id is not None:
|
||||
create_compare_report(self.arguments, scan_id)
|
||||
log.info("ScanID: {0} ".format(scan_id) + _("done"))
|
||||
|
||||
return exit_code
|
||||
|
||||
def start_scan(self, scan_id):
|
||||
target_groups = common_utils.generate_target_groups(
|
||||
self.arguments.targets, self.arguments.set_hardware_usage
|
||||
)
|
||||
log.info(_("removing_old_db_records"))
|
||||
|
||||
for target_group in target_groups:
|
||||
for target in target_group:
|
||||
for module_name in self.arguments.selected_modules:
|
||||
remove_old_logs(
|
||||
{
|
||||
"target": target,
|
||||
"module_name": module_name,
|
||||
"scan_id": scan_id,
|
||||
"scan_compare_id": self.arguments.scan_compare_id,
|
||||
}
|
||||
)
|
||||
|
||||
for _i in range(target_groups.count([])):
|
||||
target_groups.remove([])
|
||||
|
||||
log.info(_("start_multi_process").format(len(self.arguments.targets), len(target_groups)))
|
||||
active_processes = []
|
||||
for t_id, target_groups in enumerate(target_groups):
|
||||
process = multiprocess.Process(
|
||||
target=self.scan_target_group, args=(target_groups, scan_id, t_id)
|
||||
)
|
||||
process.start()
|
||||
active_processes.append(process)
|
||||
|
||||
return wait_for_threads_to_finish(active_processes, sub_process=True)
|
||||
|
||||
def scan_target(
|
||||
self,
|
||||
target,
|
||||
module_name,
|
||||
scan_id,
|
||||
process_number,
|
||||
thread_number,
|
||||
total_number_threads,
|
||||
):
|
||||
options = copy.deepcopy(self.arguments)
|
||||
|
||||
socket.socket, socket.getaddrinfo = set_socks_proxy(options.socks_proxy)
|
||||
module = Module(
|
||||
module_name,
|
||||
options,
|
||||
target,
|
||||
scan_id,
|
||||
process_number,
|
||||
thread_number,
|
||||
total_number_threads,
|
||||
)
|
||||
module.load()
|
||||
module.generate_loops()
|
||||
module.sort_loops()
|
||||
module.start()
|
||||
|
||||
log.verbose_event_info(
|
||||
_("finished_parallel_module_scan").format(
|
||||
process_number, module_name, target, thread_number, total_number_threads
|
||||
)
|
||||
)
|
||||
|
||||
return os.EX_OK
|
||||
|
||||
def scan_target_group(self, targets, scan_id, process_number):
|
||||
active_threads = []
|
||||
log.verbose_event_info(_("single_process_started").format(process_number))
|
||||
total_number_of_modules = len(targets) * len(self.arguments.selected_modules)
|
||||
total_number_of_modules_counter = 1
|
||||
|
||||
for target in targets:
|
||||
for module_name in self.arguments.selected_modules:
|
||||
thread = Thread(
|
||||
target=self.scan_target,
|
||||
args=(
|
||||
target,
|
||||
module_name,
|
||||
scan_id,
|
||||
process_number,
|
||||
total_number_of_modules_counter,
|
||||
total_number_of_modules,
|
||||
),
|
||||
)
|
||||
thread.name = f"{target} -> {module_name}"
|
||||
thread.start()
|
||||
log.verbose_event_info(
|
||||
_("start_parallel_module_scan").format(
|
||||
process_number,
|
||||
module_name,
|
||||
target,
|
||||
total_number_of_modules_counter,
|
||||
total_number_of_modules,
|
||||
)
|
||||
)
|
||||
total_number_of_modules_counter += 1
|
||||
active_threads.append(thread)
|
||||
if not wait_for_threads_to_finish(
|
||||
active_threads, self.arguments.parallel_module_scan, True
|
||||
):
|
||||
return False
|
||||
wait_for_threads_to_finish(active_threads, maximum=None, terminable=True)
|
||||
return True
|
||||
|
|
@ -0,0 +1,766 @@
|
|||
import json
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import yaml
|
||||
|
||||
from nettacker import all_module_severity_and_desc
|
||||
from nettacker.config import version_info, Config
|
||||
from nettacker.core.die import die_failure, die_success
|
||||
from nettacker.core.ip import (
|
||||
is_single_ipv4,
|
||||
is_single_ipv6,
|
||||
is_ipv4_cidr,
|
||||
is_ipv6_range,
|
||||
is_ipv6_cidr,
|
||||
is_ipv4_range,
|
||||
generate_ip_range,
|
||||
)
|
||||
from nettacker.core.messages import messages as _
|
||||
from nettacker.core.template import TemplateLoader
|
||||
from nettacker.core.utils import common as common_utils
|
||||
from nettacker.logger import TerminalCodes, get_logger
|
||||
|
||||
log = get_logger()
|
||||
|
||||
|
||||
class ArgParser(ArgumentParser):
|
||||
def __init__(self, api_arguments=None) -> None:
|
||||
super().__init__(prog="Nettacker", add_help=False)
|
||||
|
||||
self.api_arguments = api_arguments
|
||||
self.graphs = self.load_graphs()
|
||||
self.languages = self.load_languages()
|
||||
|
||||
self.modules = self.load_modules(full_details=True)
|
||||
log.info(_("loaded_modules").format(len(self.modules)))
|
||||
|
||||
self.profiles = self.load_profiles()
|
||||
|
||||
self.add_arguments()
|
||||
self.parse_arguments()
|
||||
|
||||
@staticmethod
|
||||
def load_graphs():
|
||||
"""
|
||||
load all available graphs
|
||||
|
||||
Returns:
|
||||
an array of graph names
|
||||
"""
|
||||
|
||||
graph_names = []
|
||||
for graph_library in Config.path.graph_dir.glob("*/engine.py"):
|
||||
graph_names.append(str(graph_library).split("/")[-2] + "_graph")
|
||||
return list(set(graph_names))
|
||||
|
||||
@staticmethod
|
||||
def load_languages():
|
||||
"""
|
||||
Get available languages
|
||||
|
||||
Returns:
|
||||
an array of languages
|
||||
"""
|
||||
languages_list = []
|
||||
|
||||
for language in Config.path.locale_dir.glob("*.yaml"):
|
||||
languages_list.append(str(language).split("/")[-1].split(".")[0])
|
||||
|
||||
return list(set(languages_list))
|
||||
|
||||
@staticmethod
|
||||
def load_modules(limit=-1, full_details=False):
|
||||
"""
|
||||
load all available modules
|
||||
|
||||
limit: return limited number of modules
|
||||
full: with full details
|
||||
|
||||
Returns:
|
||||
an array of all module names
|
||||
"""
|
||||
# Search for Modules
|
||||
module_names = {}
|
||||
for module_name in sorted(Config.path.modules_dir.glob("**/*.yaml")):
|
||||
library = str(module_name).split("/")[-1].split(".")[0]
|
||||
category = str(module_name).split("/")[-2]
|
||||
module = f"{library}_{category}"
|
||||
contents = yaml.safe_load(TemplateLoader(module).open().split("payload:")[0])
|
||||
module_names[module] = contents["info"] if full_details else None
|
||||
info = contents.get("info", {})
|
||||
all_module_severity_and_desc[module] = {
|
||||
"severity": info.get("severity", 0),
|
||||
"desc": info.get("description", ""),
|
||||
}
|
||||
if len(module_names) == limit:
|
||||
module_names["..."] = {}
|
||||
break
|
||||
module_names = common_utils.sort_dictionary(module_names)
|
||||
module_names["all"] = {}
|
||||
|
||||
return module_names
|
||||
|
||||
@staticmethod
|
||||
def load_profiles(limit=-1):
|
||||
"""
|
||||
load all available profiles
|
||||
|
||||
Returns:
|
||||
an array of all profile names
|
||||
"""
|
||||
all_modules_with_details = ArgParser.load_modules(full_details=True).copy()
|
||||
profiles = {}
|
||||
if "..." in all_modules_with_details:
|
||||
del all_modules_with_details["..."]
|
||||
del all_modules_with_details["all"]
|
||||
for key in all_modules_with_details:
|
||||
for tag in all_modules_with_details[key]["profiles"]:
|
||||
if tag not in profiles:
|
||||
profiles[tag] = []
|
||||
profiles[tag].append(key)
|
||||
else:
|
||||
profiles[tag].append(key)
|
||||
if len(profiles) == limit:
|
||||
profiles = common_utils.sort_dictionary(profiles)
|
||||
profiles["..."] = []
|
||||
profiles["all"] = []
|
||||
return profiles
|
||||
profiles = common_utils.sort_dictionary(profiles)
|
||||
profiles["all"] = []
|
||||
|
||||
return profiles
|
||||
|
||||
def add_arguments(self):
|
||||
# Engine Options
|
||||
engine_options = self.add_argument_group(_("engine"), _("engine_input"))
|
||||
engine_options.add_argument(
|
||||
"-L",
|
||||
"--language",
|
||||
action="store",
|
||||
dest="language",
|
||||
default=Config.settings.language,
|
||||
help=_("select_language").format(self.languages),
|
||||
)
|
||||
engine_options.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
dest="verbose_mode",
|
||||
default=Config.settings.verbose_mode,
|
||||
help=_("verbose_mode"),
|
||||
)
|
||||
engine_options.add_argument(
|
||||
"--verbose-event",
|
||||
action="store_true",
|
||||
dest="verbose_event",
|
||||
default=Config.settings.verbose_event,
|
||||
help=_("verbose_event"),
|
||||
)
|
||||
engine_options.add_argument(
|
||||
"-V",
|
||||
"--version",
|
||||
action="store_true",
|
||||
default=Config.settings.show_version,
|
||||
dest="show_version",
|
||||
help=_("software_version"),
|
||||
)
|
||||
engine_options.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
action="store",
|
||||
default=Config.settings.report_path_filename,
|
||||
dest="report_path_filename",
|
||||
help=_("save_logs"),
|
||||
)
|
||||
engine_options.add_argument(
|
||||
"--graph",
|
||||
action="store",
|
||||
default=Config.settings.graph_name,
|
||||
dest="graph_name",
|
||||
help=_("available_graph").format(self.graphs),
|
||||
)
|
||||
engine_options.add_argument(
|
||||
"-h",
|
||||
"--help",
|
||||
action="store_true",
|
||||
default=Config.settings.show_help_menu,
|
||||
dest="show_help_menu",
|
||||
help=_("help_menu"),
|
||||
)
|
||||
|
||||
# Target Options
|
||||
target_options = self.add_argument_group(_("target"), _("target_input"))
|
||||
target_options.add_argument(
|
||||
"-i",
|
||||
"--targets",
|
||||
action="store",
|
||||
dest="targets",
|
||||
default=Config.settings.targets,
|
||||
help=_("target_list"),
|
||||
)
|
||||
target_options.add_argument(
|
||||
"-l",
|
||||
"--targets-list",
|
||||
action="store",
|
||||
dest="targets_list",
|
||||
default=Config.settings.targets_list,
|
||||
help=_("read_target"),
|
||||
)
|
||||
|
||||
# Exclude Module Name
|
||||
exclude_modules = sorted(self.modules.keys())[:10]
|
||||
exclude_modules.remove("all")
|
||||
|
||||
# Method Options
|
||||
method_options = self.add_argument_group(_("Method"), _("scan_method_options"))
|
||||
method_options.add_argument(
|
||||
"-m",
|
||||
"--modules",
|
||||
action="store",
|
||||
dest="selected_modules",
|
||||
default=Config.settings.selected_modules,
|
||||
help=_("choose_scan_method").format(list(self.modules.keys())[:10]),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"--modules-extra-args",
|
||||
action="store",
|
||||
dest="modules_extra_args",
|
||||
default=Config.settings.modules_extra_args,
|
||||
help=_("modules_extra_args_help"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"--show-all-modules",
|
||||
action="store_true",
|
||||
dest="show_all_modules",
|
||||
default=Config.settings.show_all_modules,
|
||||
help=_("show_all_modules"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"--profile",
|
||||
action="store",
|
||||
default=Config.settings.profiles,
|
||||
dest="profiles",
|
||||
help=_("select_profile").format(list(self.profiles.keys())[:10]),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"--show-all-profiles",
|
||||
action="store_true",
|
||||
dest="show_all_profiles",
|
||||
default=Config.settings.show_all_profiles,
|
||||
help=_("show_all_profiles"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-x",
|
||||
"--exclude-modules",
|
||||
action="store",
|
||||
dest="excluded_modules",
|
||||
default=Config.settings.excluded_modules,
|
||||
help=_("exclude_scan_method").format(exclude_modules),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-X",
|
||||
"--exclude-ports",
|
||||
action="store",
|
||||
dest="excluded_ports",
|
||||
default=Config.settings.excluded_ports,
|
||||
help=_("exclude_ports"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-u",
|
||||
"--usernames",
|
||||
action="store",
|
||||
dest="usernames",
|
||||
default=Config.settings.usernames,
|
||||
help=_("username_list"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-U",
|
||||
"--users-list",
|
||||
action="store",
|
||||
dest="usernames_list",
|
||||
default=Config.settings.usernames_list,
|
||||
help=_("username_from_file"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-p",
|
||||
"--passwords",
|
||||
action="store",
|
||||
dest="passwords",
|
||||
default=Config.settings.passwords,
|
||||
help=_("password_separator"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-P",
|
||||
"--passwords-list",
|
||||
action="store",
|
||||
dest="passwords_list",
|
||||
default=Config.settings.passwords_list,
|
||||
help=_("read_passwords"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-g",
|
||||
"--ports",
|
||||
action="store",
|
||||
dest="ports",
|
||||
default=Config.settings.ports,
|
||||
help=_("port_separator"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"--user-agent",
|
||||
action="store",
|
||||
dest="user_agent",
|
||||
default=Config.settings.user_agent,
|
||||
help=_("select_user_agent"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-T",
|
||||
"--timeout",
|
||||
action="store",
|
||||
dest="timeout",
|
||||
default=Config.settings.timeout,
|
||||
type=float,
|
||||
help=_("read_passwords"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-w",
|
||||
"--time-sleep-between-requests",
|
||||
action="store",
|
||||
dest="time_sleep_between_requests",
|
||||
default=Config.settings.time_sleep_between_requests,
|
||||
type=float,
|
||||
help=_("time_to_sleep"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-r",
|
||||
"--range",
|
||||
action="store_true",
|
||||
default=Config.settings.scan_ip_range,
|
||||
dest="scan_ip_range",
|
||||
help=_("range"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-s",
|
||||
"--sub-domains",
|
||||
action="store_true",
|
||||
default=Config.settings.scan_subdomains,
|
||||
dest="scan_subdomains",
|
||||
help=_("subdomains"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-d",
|
||||
"--skip-service-discovery",
|
||||
action="store_true",
|
||||
default=Config.settings.skip_service_discovery,
|
||||
dest="skip_service_discovery",
|
||||
help=_("skip_service_discovery"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-t",
|
||||
"--thread-per-host",
|
||||
action="store",
|
||||
default=Config.settings.thread_per_host,
|
||||
type=int,
|
||||
dest="thread_per_host",
|
||||
help=_("thread_number_connections"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-M",
|
||||
"--parallel-module-scan",
|
||||
action="store",
|
||||
default=Config.settings.parallel_module_scan,
|
||||
type=int,
|
||||
dest="parallel_module_scan",
|
||||
help=_("thread_number_modules"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"--set-hardware-usage",
|
||||
action="store",
|
||||
dest="set_hardware_usage",
|
||||
default=Config.settings.set_hardware_usage,
|
||||
help=_("set_hardware_usage"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-R",
|
||||
"--socks-proxy",
|
||||
action="store",
|
||||
dest="socks_proxy",
|
||||
default=Config.settings.socks_proxy,
|
||||
help=_("outgoing_proxy"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"--retries",
|
||||
action="store",
|
||||
dest="retries",
|
||||
type=int,
|
||||
default=Config.settings.retries,
|
||||
help=_("connection_retries"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"--ping-before-scan",
|
||||
action="store_true",
|
||||
dest="ping_before_scan",
|
||||
default=Config.settings.ping_before_scan,
|
||||
help=_("ping_before_scan"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-K",
|
||||
"--scan-compare",
|
||||
action="store",
|
||||
dest="scan_compare_id",
|
||||
default=Config.settings.scan_compare_id,
|
||||
help=_("compare_scans"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-J",
|
||||
"--compare-report-path",
|
||||
action="store",
|
||||
dest="compare_report_path_filename",
|
||||
default=Config.settings.compare_report_path_filename,
|
||||
help=_("compare_report_path_filename"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-W",
|
||||
"--wordlist",
|
||||
action="store",
|
||||
default=Config.settings.read_from_file,
|
||||
dest="read_from_file",
|
||||
help=_("user_wordlist"),
|
||||
)
|
||||
method_options.add_argument(
|
||||
"-H",
|
||||
"--add-http-header",
|
||||
action="append",
|
||||
default=Config.settings.http_header,
|
||||
dest="http_header",
|
||||
help=_("http_header"),
|
||||
)
|
||||
|
||||
# API Options
|
||||
api_options = self.add_argument_group(_("API"), _("API_options"))
|
||||
api_options.add_argument(
|
||||
"--start-api",
|
||||
action="store_true",
|
||||
dest="start_api_server",
|
||||
default=Config.api.start_api_server,
|
||||
help=_("start_api_server"),
|
||||
)
|
||||
api_options.add_argument(
|
||||
"--api-host",
|
||||
action="store",
|
||||
dest="api_hostname",
|
||||
default=Config.api.api_hostname,
|
||||
help=_("API_host"),
|
||||
)
|
||||
api_options.add_argument(
|
||||
"--api-port",
|
||||
action="store",
|
||||
dest="api_port",
|
||||
default=Config.api.api_port,
|
||||
help=_("API_port"),
|
||||
)
|
||||
api_options.add_argument(
|
||||
"--api-debug-mode",
|
||||
action="store_true",
|
||||
dest="api_debug_mode",
|
||||
default=Config.api.api_debug_mode,
|
||||
help=_("API_debug"),
|
||||
)
|
||||
api_options.add_argument(
|
||||
"--api-access-key",
|
||||
action="store",
|
||||
dest="api_access_key",
|
||||
default=Config.api.api_access_key,
|
||||
help=_("API_access_key"),
|
||||
)
|
||||
api_options.add_argument(
|
||||
"--api-client-whitelisted-ips",
|
||||
action="store",
|
||||
dest="api_client_whitelisted_ips",
|
||||
default=Config.api.api_client_whitelisted_ips,
|
||||
help=_("define_white_list"),
|
||||
)
|
||||
api_options.add_argument(
|
||||
"--api-access-log",
|
||||
action="store",
|
||||
dest="api_access_log",
|
||||
default=Config.api.api_access_log,
|
||||
help=_("API_access_log_file"),
|
||||
)
|
||||
api_options.add_argument(
|
||||
"--api-cert",
|
||||
action="store",
|
||||
dest="api_cert",
|
||||
help=_("API_cert"),
|
||||
)
|
||||
api_options.add_argument(
|
||||
"--api-cert-key",
|
||||
action="store",
|
||||
dest="api_cert_key",
|
||||
help=_("API_cert_key"),
|
||||
)
|
||||
|
||||
def parse_arguments(self):
|
||||
"""
|
||||
check all rules and requirements for ARGS
|
||||
|
||||
Args:
|
||||
api_forms: values from nettacker.api
|
||||
|
||||
Returns:
|
||||
all ARGS with applied rules
|
||||
"""
|
||||
# Checking Requirements
|
||||
options = self.api_arguments or self.parse_args()
|
||||
|
||||
if options.language not in self.languages:
|
||||
die_failure("Please select one of these languages {0}".format(self.languages))
|
||||
|
||||
# Check Help Menu
|
||||
if options.show_help_menu:
|
||||
self.print_help()
|
||||
log.write("\n\n")
|
||||
log.write(_("license"))
|
||||
die_success()
|
||||
|
||||
# Check version
|
||||
if options.show_version:
|
||||
log.info(
|
||||
_("current_version").format(
|
||||
TerminalCodes.YELLOW.value,
|
||||
version_info()[0],
|
||||
TerminalCodes.RESET.value,
|
||||
TerminalCodes.CYAN.value,
|
||||
version_info()[1],
|
||||
TerminalCodes.RESET.value,
|
||||
TerminalCodes.GREEN.value,
|
||||
)
|
||||
)
|
||||
die_success()
|
||||
|
||||
if options.show_all_modules:
|
||||
log.info(_("loading_modules"))
|
||||
for module in self.modules:
|
||||
log.info(
|
||||
_("module_profile_full_information").format(
|
||||
TerminalCodes.CYAN.value,
|
||||
module,
|
||||
TerminalCodes.GREEN.value,
|
||||
", ".join(
|
||||
[
|
||||
"{key}: {value}".format(key=key, value=self.modules[module][key])
|
||||
for key in self.modules[module]
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
die_success()
|
||||
|
||||
if options.show_all_profiles:
|
||||
log.info(_("loading_profiles"))
|
||||
for profile in self.profiles:
|
||||
log.info(
|
||||
_("module_profile_full_information").format(
|
||||
TerminalCodes.CYAN.value,
|
||||
profile,
|
||||
TerminalCodes.GREEN.value,
|
||||
", ".join(self.profiles[profile]),
|
||||
)
|
||||
)
|
||||
die_success()
|
||||
|
||||
# API mode
|
||||
if options.start_api_server:
|
||||
if "--start-api" in sys.argv and self.api_arguments:
|
||||
die_failure(_("cannot_run_api_server"))
|
||||
from nettacker.api.engine import start_api_server
|
||||
|
||||
if options.api_client_whitelisted_ips:
|
||||
if isinstance(options.api_client_whitelisted_ips, str):
|
||||
options.api_client_whitelisted_ips = options.api_client_whitelisted_ips.split(
|
||||
","
|
||||
)
|
||||
whitelisted_ips = []
|
||||
for ip in options.api_client_whitelisted_ips:
|
||||
if is_single_ipv4(ip) or is_single_ipv6(ip):
|
||||
whitelisted_ips.append(ip)
|
||||
elif (
|
||||
is_ipv4_range(ip)
|
||||
or is_ipv6_range(ip)
|
||||
or is_ipv4_cidr(ip)
|
||||
or is_ipv6_cidr(ip)
|
||||
):
|
||||
whitelisted_ips += generate_ip_range(ip)
|
||||
options.api_client_whitelisted_ips = whitelisted_ips
|
||||
start_api_server(options)
|
||||
|
||||
# Check the target(s)
|
||||
if not (options.targets or options.targets_list) or (
|
||||
options.targets and options.targets_list
|
||||
):
|
||||
# self.print_help()
|
||||
# write("\n")
|
||||
die_failure(_("error_target"))
|
||||
if options.targets:
|
||||
options.targets = list(set(options.targets.split(",")))
|
||||
if options.targets_list:
|
||||
try:
|
||||
options.targets = list(
|
||||
set(open(options.targets_list, "rb").read().decode().split())
|
||||
)
|
||||
except Exception:
|
||||
die_failure(_("error_target_file").format(options.targets_list))
|
||||
|
||||
# check for modules
|
||||
if not (options.selected_modules or options.profiles):
|
||||
die_failure(_("scan_method_select"))
|
||||
if options.selected_modules:
|
||||
if options.selected_modules == "all":
|
||||
options.selected_modules = list(set(self.modules.keys()))
|
||||
options.selected_modules.remove("all")
|
||||
else:
|
||||
options.selected_modules = list(set(options.selected_modules.split(",")))
|
||||
for module_name in options.selected_modules:
|
||||
if module_name not in self.modules:
|
||||
die_failure(_("scan_module_not_found").format(module_name))
|
||||
if options.profiles:
|
||||
if not options.selected_modules:
|
||||
options.selected_modules = []
|
||||
if options.profiles == "all":
|
||||
options.selected_modules = list(set(self.modules.keys()))
|
||||
options.selected_modules.remove("all")
|
||||
else:
|
||||
options.profiles = list(set(options.profiles.split(",")))
|
||||
for profile in options.profiles:
|
||||
if profile not in self.profiles:
|
||||
die_failure(_("profile_404").format(profile))
|
||||
for module_name in self.profiles[profile]:
|
||||
if module_name not in options.selected_modules:
|
||||
options.selected_modules.append(module_name)
|
||||
# threading & processing
|
||||
if options.set_hardware_usage not in {"low", "normal", "high", "maximum"}:
|
||||
die_failure(_("wrong_hardware_usage"))
|
||||
options.set_hardware_usage = common_utils.select_maximum_cpu_core(
|
||||
options.set_hardware_usage
|
||||
)
|
||||
|
||||
options.thread_per_host = int(options.thread_per_host)
|
||||
if options.thread_per_host < 1:
|
||||
options.thread_per_host = 1
|
||||
options.parallel_module_scan = int(options.parallel_module_scan)
|
||||
if options.parallel_module_scan < 1:
|
||||
options.parallel_module_scan = 1
|
||||
|
||||
# Check for excluding modules
|
||||
if options.excluded_modules:
|
||||
options.excluded_modules = options.excluded_modules.split(",")
|
||||
if "all" in options.excluded_modules:
|
||||
die_failure(_("error_exclude_all"))
|
||||
for excluded_module in options.excluded_modules:
|
||||
if excluded_module in options.selected_modules:
|
||||
options.selected_modules.remove(excluded_module)
|
||||
# Check port(s)
|
||||
if options.ports:
|
||||
tmp_ports = set()
|
||||
for port in options.ports.split(","):
|
||||
try:
|
||||
if "-" in port:
|
||||
for port_number in range(
|
||||
int(port.split("-")[0]), int(port.split("-")[1]) + 1
|
||||
):
|
||||
tmp_ports.add(port_number)
|
||||
else:
|
||||
tmp_ports.add(int(port))
|
||||
except Exception:
|
||||
die_failure(_("ports_int"))
|
||||
options.ports = list(tmp_ports)
|
||||
# Check for excluded ports
|
||||
if options.excluded_ports:
|
||||
tmp_excluded_ports = set()
|
||||
|
||||
for excluded_port in options.excluded_ports.split(","):
|
||||
try:
|
||||
if "-" in excluded_port:
|
||||
for excluded_port_number in range(
|
||||
int(excluded_port.split("-")[0]), int(excluded_port.split("-")[1]) + 1
|
||||
):
|
||||
tmp_excluded_ports.add(excluded_port_number)
|
||||
else:
|
||||
tmp_excluded_ports.add(int(excluded_port))
|
||||
except Exception:
|
||||
die_failure(_("ports_int"))
|
||||
options.excluded_ports = list(tmp_excluded_ports)
|
||||
|
||||
if options.user_agent == "random_user_agent":
|
||||
options.user_agents = open(Config.path.user_agents_file).read().split("\n")
|
||||
|
||||
# Check user list
|
||||
if options.usernames:
|
||||
options.usernames = list(set(options.usernames.split(",")))
|
||||
elif options.usernames_list:
|
||||
try:
|
||||
options.usernames = list(set(open(options.usernames_list).read().split("\n")))
|
||||
except Exception:
|
||||
die_failure(_("error_username").format(options.usernames_list))
|
||||
# Check password list
|
||||
if options.passwords:
|
||||
options.passwords = list(set(options.passwords.split(",")))
|
||||
elif options.passwords_list:
|
||||
try:
|
||||
options.passwords = list(set(open(options.passwords_list).read().split("\n")))
|
||||
except Exception:
|
||||
die_failure(_("error_passwords").format(options.passwords_list))
|
||||
# Check custom wordlist
|
||||
if options.read_from_file:
|
||||
try:
|
||||
open(options.read_from_file).read().split("\n")
|
||||
except Exception:
|
||||
die_failure(_("error_wordlist").format(options.read_from_file))
|
||||
# Check output file
|
||||
try:
|
||||
temp_file = open(options.report_path_filename, "w")
|
||||
temp_file.close()
|
||||
except Exception:
|
||||
die_failure(_("file_write_error").format(options.report_path_filename))
|
||||
# Check Graph
|
||||
if options.graph_name:
|
||||
if options.graph_name not in self.graphs:
|
||||
die_failure(_("graph_module_404").format(options.graph_name))
|
||||
if not (
|
||||
options.report_path_filename.endswith(".html")
|
||||
or options.report_path_filename.endswith(".htm")
|
||||
):
|
||||
log.warn(_("graph_output"))
|
||||
options.graph_name = None
|
||||
# check modules extra args
|
||||
if options.modules_extra_args:
|
||||
all_args = {}
|
||||
for args in options.modules_extra_args.split("&"):
|
||||
value = args.split("=")[1]
|
||||
if value.lower() == "true":
|
||||
value = True
|
||||
elif value.lower() == "false":
|
||||
value = False
|
||||
elif "." in value:
|
||||
try:
|
||||
value = float(value)
|
||||
except Exception:
|
||||
pass
|
||||
elif "{" in value or "[" in value:
|
||||
try:
|
||||
value = json.loads(value)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
value = int(value)
|
||||
except Exception:
|
||||
pass
|
||||
all_args[args.split("=")[0]] = value
|
||||
options.modules_extra_args = all_args
|
||||
|
||||
options.timeout = float(options.timeout)
|
||||
options.time_sleep_between_requests = float(options.time_sleep_between_requests)
|
||||
options.retries = int(options.retries)
|
||||
|
||||
self.arguments = options
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
|
||||
from nettacker import logger
|
||||
|
||||
log = logger.get_logger()
|
||||
|
||||
|
||||
def die_success():
|
||||
"""
|
||||
exit the framework with code 0
|
||||
"""
|
||||
from core.color import reset_color
|
||||
reset_color()
|
||||
log.reset_color()
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
|
@ -20,8 +20,7 @@ def die_failure(msg):
|
|||
Args:
|
||||
msg: the error message
|
||||
"""
|
||||
from core.color import reset_color
|
||||
from core.alert import error
|
||||
error(msg)
|
||||
reset_color()
|
||||
|
||||
log.error(msg)
|
||||
log.reset_color()
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from nettacker.config import Config
|
||||
|
||||
|
||||
def read_from_file(file_path):
|
||||
return open(Config.path.payloads_dir / file_path).read().split("\n")
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
import csv
|
||||
import html
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import texttable
|
||||
|
||||
from nettacker import logger, all_module_severity_and_desc
|
||||
from nettacker.config import Config, version_info
|
||||
from nettacker.core.die import die_failure
|
||||
from nettacker.core.messages import messages as _
|
||||
from nettacker.core.utils.common import (
|
||||
merge_logs_to_list,
|
||||
now,
|
||||
sanitize_path,
|
||||
generate_compare_filepath,
|
||||
)
|
||||
from nettacker.database.db import get_logs_by_scan_id, submit_report_to_db, get_options_by_scan_id
|
||||
|
||||
log = logger.get_logger()
|
||||
nettacker_path_config = Config.path
|
||||
|
||||
|
||||
def build_graph(graph_name, events):
|
||||
"""
|
||||
build a graph
|
||||
|
||||
Args:
|
||||
graph_name: graph name
|
||||
events: list of events
|
||||
|
||||
Returns:
|
||||
graph in HTML type
|
||||
"""
|
||||
log.info(_("build_graph"))
|
||||
try:
|
||||
start = getattr(
|
||||
importlib.import_module(
|
||||
f"nettacker.lib.graph.{graph_name.rsplit('_graph')[0]}.engine"
|
||||
),
|
||||
"start",
|
||||
)
|
||||
except ModuleNotFoundError:
|
||||
die_failure(_("graph_module_unavailable").format(graph_name))
|
||||
|
||||
log.info(_("finish_build_graph"))
|
||||
return start(events)
|
||||
|
||||
|
||||
def build_compare_report(compare_results):
|
||||
"""
|
||||
build the compare report
|
||||
Args:
|
||||
compare_results: Final result of the comparision(dict)
|
||||
Returns:
|
||||
report in html format
|
||||
"""
|
||||
log.info(_("build_compare_report"))
|
||||
try:
|
||||
build_report = getattr(
|
||||
importlib.import_module("nettacker.lib.compare_report.engine"),
|
||||
"build_report",
|
||||
)
|
||||
except ModuleNotFoundError:
|
||||
die_failure(_("graph_module_unavailable").format("compare_report"))
|
||||
|
||||
log.info(_("finish_build_report"))
|
||||
return build_report(compare_results)
|
||||
|
||||
|
||||
def build_text_table(events):
|
||||
"""
|
||||
value['date'], value["target"], value['module_name'], value['scan_id'],
|
||||
value['options'], value['event']
|
||||
build a text table with generated event related to the scan
|
||||
|
||||
:param events: all events
|
||||
:return:
|
||||
array [text table, event_number]
|
||||
"""
|
||||
_table = texttable.Texttable()
|
||||
table_headers = ["date", "target", "module_name", "port", "logs"]
|
||||
_table.add_rows([table_headers])
|
||||
for event in events:
|
||||
log = merge_logs_to_list(json.loads(event["json_event"]), [])
|
||||
_table.add_rows(
|
||||
[
|
||||
table_headers,
|
||||
[
|
||||
event["date"],
|
||||
event["target"],
|
||||
event["module_name"],
|
||||
str(event["port"]),
|
||||
"\n".join(log) if log else "Detected",
|
||||
],
|
||||
]
|
||||
)
|
||||
return (
|
||||
_table.draw()
|
||||
+ "\n\n"
|
||||
+ _("nettacker_version_details").format(version_info()[0], version_info()[1], now())
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
|
||||
def create_compare_text_table(results):
|
||||
table = texttable.Texttable()
|
||||
table_headers = list(results.keys())
|
||||
table.add_rows([table_headers])
|
||||
table.add_rows(
|
||||
[
|
||||
table_headers,
|
||||
[results[col] for col in table_headers],
|
||||
]
|
||||
)
|
||||
table.set_cols_width([len(i) for i in table_headers])
|
||||
return table.draw() + "\n\n"
|
||||
|
||||
|
||||
def create_dd_specific_json(all_scan_logs):
|
||||
severity_mapping = {1: "Info", 2: "Low", 3: "Medium", 4: "High", 5: "Critical"}
|
||||
|
||||
findings = []
|
||||
|
||||
for log in all_scan_logs:
|
||||
module_name = log["module_name"].strip()
|
||||
date = datetime.strptime(log["date"], "%Y-%m-%d %H:%M:%S.%f").strftime("%m/%d/%Y")
|
||||
port = str(log.get("port", "")).strip()
|
||||
impact = log.get("event", "").strip()
|
||||
severity_justification = log.get("json_event", "").strip()
|
||||
service = log.get("target", "").strip()
|
||||
unique_id = log.get("scan_id", uuid.uuid4().hex)
|
||||
|
||||
metadata = all_module_severity_and_desc.get(module_name, {})
|
||||
severity_raw = metadata.get("severity", 0)
|
||||
description = metadata.get("desc", "")
|
||||
if severity_raw >= 9:
|
||||
severity = severity_mapping[5]
|
||||
elif severity_raw >= 7:
|
||||
severity = severity_mapping[4]
|
||||
elif severity_raw >= 4:
|
||||
severity = severity_mapping[3]
|
||||
elif severity_raw > 0:
|
||||
severity = severity_mapping[2]
|
||||
else:
|
||||
severity = severity_mapping[1]
|
||||
|
||||
findings.append(
|
||||
{
|
||||
"date": date,
|
||||
"title": module_name,
|
||||
"description": description.strip(),
|
||||
"severity": severity,
|
||||
"param": port,
|
||||
"impact": impact,
|
||||
"severity_justification": severity_justification,
|
||||
"service": service,
|
||||
"unique_id_from_tool": unique_id,
|
||||
"static_finding": False,
|
||||
"dynamic_finding": True,
|
||||
}
|
||||
)
|
||||
|
||||
return json.dumps({"findings": findings}, indent=4)
|
||||
|
||||
|
||||
def create_sarif_report(all_scan_logs):
|
||||
"""
|
||||
Takes all_scan_logs and converts them to a SARIF based json
|
||||
format. The schema and version used are 2.1.0 linked below.
|
||||
The following conversions are made:
|
||||
ruleId: name of the module
|
||||
message: event value for each log in all_scan_logs
|
||||
locations.physicalLocations.artifactLocation.uri: target value
|
||||
webRequest.properties.json_event: json_event value for each log in all_scan_logs
|
||||
properties.scan_id: scan_id unique value for each run
|
||||
properties.date: date field specified in all_scan_logs
|
||||
"""
|
||||
|
||||
sarif_structure = {
|
||||
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
|
||||
"version": "2.1.0",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "Nettacker",
|
||||
"version": "0.4.0",
|
||||
"informationUri": "https://github.com/OWASP/Nettacker",
|
||||
}
|
||||
},
|
||||
"results": [],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
for log in all_scan_logs:
|
||||
sarif_result = {
|
||||
"ruleId": log["module_name"],
|
||||
"message": {"text": log["event"]},
|
||||
"locations": [{"physicalLocation": {"artifactLocation": {"uri": log["target"]}}}],
|
||||
"properties": {
|
||||
"scan_id": log["scan_id"],
|
||||
"date": log["date"],
|
||||
"json_event": log["json_event"],
|
||||
},
|
||||
}
|
||||
sarif_structure["runs"][0]["results"].append(sarif_result)
|
||||
|
||||
return json.dumps(sarif_structure, indent=2)
|
||||
|
||||
|
||||
def create_report(options, scan_id):
|
||||
"""
|
||||
sort all events, create log file in HTML/TEXT/JSON and remove old logs
|
||||
|
||||
Args:
|
||||
options: parsing options
|
||||
scan_id: scan unique id
|
||||
|
||||
Returns:
|
||||
True if success otherwise None
|
||||
"""
|
||||
all_scan_logs = get_logs_by_scan_id(scan_id)
|
||||
if not all_scan_logs:
|
||||
log.info(_("no_events_for_report"))
|
||||
return True
|
||||
report_path_filename = options.report_path_filename
|
||||
if (len(report_path_filename) >= 5 and report_path_filename[-5:] == ".html") or (
|
||||
len(report_path_filename) >= 4 and report_path_filename[-4:] == ".htm"
|
||||
):
|
||||
if options.graph_name:
|
||||
html_graph = build_graph(options.graph_name, all_scan_logs)
|
||||
else:
|
||||
html_graph = ""
|
||||
|
||||
from nettacker.lib.html_log import log_data
|
||||
|
||||
html_table_content = log_data.table_title.format(
|
||||
html_graph,
|
||||
log_data.css_1,
|
||||
"date",
|
||||
"target",
|
||||
"module_name",
|
||||
"port",
|
||||
"logs",
|
||||
"json_event",
|
||||
)
|
||||
index = 1
|
||||
for event in all_scan_logs:
|
||||
log_list = merge_logs_to_list(json.loads(event["json_event"]), [])
|
||||
html_table_content += log_data.table_items.format(
|
||||
event["date"],
|
||||
event["target"],
|
||||
event["module_name"],
|
||||
event["port"],
|
||||
"<br>".join(log_list) if log_list else "Detected", # event["event"], #log
|
||||
index,
|
||||
html.escape(event["json_event"]),
|
||||
)
|
||||
index += 1
|
||||
html_table_content += (
|
||||
log_data.table_end
|
||||
+ '<div id="json_length">'
|
||||
+ str(index - 1)
|
||||
+ "</div>"
|
||||
+ '<p class="footer">'
|
||||
+ _("nettacker_version_details").format(version_info()[0], version_info()[1], now())
|
||||
+ " ScanID: {0}".format(scan_id)
|
||||
+ "</p>"
|
||||
+ log_data.json_parse_js
|
||||
)
|
||||
with Path(report_path_filename).open("w", encoding="utf-8") as report_file:
|
||||
report_file.write(html_table_content + "\n")
|
||||
|
||||
elif len(report_path_filename) >= 5 and report_path_filename[-8:].lower() == ".dd.json":
|
||||
with Path(report_path_filename).open("w", encoding="utf-8") as report_file:
|
||||
dd_content_json = create_dd_specific_json(all_scan_logs)
|
||||
report_file.write(dd_content_json + "\n")
|
||||
|
||||
elif len(report_path_filename) >= 5 and report_path_filename[-5:] == ".json":
|
||||
with Path(report_path_filename).open("w", encoding="utf-8") as report_file:
|
||||
report_file.write(str(json.dumps(all_scan_logs)) + "\n")
|
||||
|
||||
elif len(report_path_filename) >= 6 and report_path_filename[-6:].lower() == ".sarif":
|
||||
with Path(report_path_filename).open("w", encoding="utf-8") as report_file:
|
||||
sarif_content = create_sarif_report(all_scan_logs)
|
||||
report_file.write(sarif_content + "\n")
|
||||
|
||||
elif len(report_path_filename) >= 5 and report_path_filename[-4:] == ".csv":
|
||||
keys = all_scan_logs[0].keys()
|
||||
with Path(report_path_filename).open("a") as csvfile:
|
||||
writer = csv.DictWriter(csvfile, fieldnames=keys)
|
||||
writer.writeheader()
|
||||
for log_list in all_scan_logs:
|
||||
dict_data = {key: value for key, value in log_list.items() if key in keys}
|
||||
writer.writerow(dict_data)
|
||||
|
||||
else:
|
||||
with Path(report_path_filename).open("w", encoding="utf-8") as report_file:
|
||||
report_file.write(build_text_table(all_scan_logs))
|
||||
|
||||
log.write(build_text_table(all_scan_logs))
|
||||
submit_report_to_db(
|
||||
{
|
||||
"date": datetime.now(),
|
||||
"scan_id": scan_id,
|
||||
"options": vars(options),
|
||||
}
|
||||
)
|
||||
|
||||
log.info(_("file_saved").format(report_path_filename))
|
||||
return True
|
||||
|
||||
|
||||
def create_compare_report(options, scan_id):
|
||||
"""
|
||||
if compare_id is given then create the report of comparision b/w scans
|
||||
Args:
|
||||
options: parsing options
|
||||
scan_id: scan unique id
|
||||
Returns:
|
||||
True if success otherwise None
|
||||
"""
|
||||
comp_id = options["scan_compare_id"] if isinstance(options, dict) else options.scan_compare_id
|
||||
scan_log_curr = get_logs_by_scan_id(scan_id)
|
||||
scan_logs_comp = get_logs_by_scan_id(comp_id)
|
||||
|
||||
if not scan_log_curr:
|
||||
log.info(_("no_events_for_report"))
|
||||
return None
|
||||
if not scan_logs_comp:
|
||||
log.info(_("no_scan_to_compare"))
|
||||
return None
|
||||
|
||||
scan_opts_curr = get_options_by_scan_id(scan_id)
|
||||
scan_opts_comp = get_options_by_scan_id(comp_id)
|
||||
|
||||
def get_targets_set(item):
|
||||
return tuple(json.loads(item["options"])["targets"])
|
||||
|
||||
curr_target_set = set(get_targets_set(item) for item in scan_opts_curr)
|
||||
comp_target_set = set(get_targets_set(item) for item in scan_opts_comp)
|
||||
|
||||
def get_modules_ports(item):
|
||||
return (item["target"], item["module_name"], item["port"])
|
||||
|
||||
curr_modules_ports = set(get_modules_ports(item) for item in scan_log_curr)
|
||||
comp_modules_ports = set(get_modules_ports(item) for item in scan_logs_comp)
|
||||
|
||||
compare_results = {
|
||||
"curr_scan_details": (scan_id, scan_log_curr[0]["date"]),
|
||||
"comp_scan_details": (comp_id, scan_logs_comp[0]["date"]),
|
||||
"curr_target_set": tuple(curr_target_set),
|
||||
"comp_target_set": tuple(comp_target_set),
|
||||
"curr_scan_result": tuple(curr_modules_ports),
|
||||
"comp_scan_result": tuple(comp_modules_ports),
|
||||
"new_targets_discovered": tuple(curr_modules_ports - comp_modules_ports),
|
||||
"old_targets_not_detected": tuple(comp_modules_ports - curr_modules_ports),
|
||||
}
|
||||
if isinstance(options, dict):
|
||||
compare_report_path_filename = options["compare_report_path_filename"]
|
||||
else:
|
||||
compare_report_path_filename = (
|
||||
options.compare_report_path_filename
|
||||
if len(options.compare_report_path_filename) != 0
|
||||
else generate_compare_filepath(scan_id)
|
||||
)
|
||||
|
||||
base_path = str(nettacker_path_config.results_dir)
|
||||
compare_report_path_filename = sanitize_path(compare_report_path_filename)
|
||||
fullpath = os.path.normpath(os.path.join(base_path, compare_report_path_filename))
|
||||
|
||||
if not fullpath.startswith(base_path):
|
||||
raise PermissionError
|
||||
|
||||
if (len(fullpath) >= 5 and fullpath[-5:] == ".html") or (
|
||||
len(fullpath) >= 4 and fullpath[-4:] == ".htm"
|
||||
):
|
||||
html_report = build_compare_report(compare_results)
|
||||
with Path(fullpath).open("w", encoding="utf-8") as compare_report:
|
||||
compare_report.write(html_report + "\n")
|
||||
elif len(fullpath) >= 5 and fullpath[-5:] == ".json":
|
||||
with Path(fullpath).open("w", encoding="utf-8") as compare_report:
|
||||
compare_report.write(str(json.dumps(compare_results)) + "\n")
|
||||
elif len(fullpath) >= 5 and fullpath[-4:] == ".csv":
|
||||
keys = compare_results.keys()
|
||||
with Path(fullpath).open("a") as csvfile:
|
||||
writer = csv.DictWriter(csvfile, fieldnames=keys)
|
||||
if csvfile.tell() == 0:
|
||||
writer.writeheader()
|
||||
writer.writerow(compare_results)
|
||||
else:
|
||||
with Path(fullpath).open("w", encoding="utf-8") as compare_report:
|
||||
compare_report.write(create_compare_text_table(compare_results))
|
||||
|
||||
log.write(create_compare_text_table(compare_results))
|
||||
log.info(_("compare_report_saved").format(fullpath))
|
||||
return True
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
import netaddr
|
||||
import requests
|
||||
from netaddr import iprange_to_cidrs
|
||||
from netaddr import IPNetwork
|
||||
|
||||
|
||||
def generate_ip_range(ip_range):
|
||||
|
|
@ -18,13 +14,13 @@ def generate_ip_range(ip_range):
|
|||
Returns:
|
||||
an array with CIDRs
|
||||
"""
|
||||
if '/' in ip_range:
|
||||
return [
|
||||
ip.format() for ip in [cidr for cidr in IPNetwork(ip_range)]
|
||||
]
|
||||
if "/" in ip_range:
|
||||
return [ip.format() for ip in [cidr for cidr in netaddr.IPNetwork(ip_range)]]
|
||||
else:
|
||||
ips = []
|
||||
for generator_ip_range in [cidr.iter_hosts() for cidr in iprange_to_cidrs(*ip_range.rsplit('-'))]:
|
||||
for generator_ip_range in [
|
||||
cidr.iter_hosts() for cidr in netaddr.iprange_to_cidrs(*ip_range.rsplit("-"))
|
||||
]:
|
||||
for ip in generator_ip_range:
|
||||
ips.append(ip.format())
|
||||
return ips
|
||||
|
|
@ -44,9 +40,9 @@ def get_ip_range(ip):
|
|||
return generate_ip_range(
|
||||
json.loads(
|
||||
requests.get(
|
||||
'https://rest.db.ripe.net/search.json?query-string={ip}&flags=no-filtering'.format(ip=ip)
|
||||
f"https://rest.db.ripe.net/search.json?query-string={ip}&flags=no-filtering"
|
||||
).content
|
||||
)['objects']['object'][0]['primary-key']['attribute'][0]['value'].replace(' ', '')
|
||||
)["objects"]["object"][0]["primary-key"]["attribute"][0]["value"].replace(" ", "")
|
||||
)
|
||||
except Exception:
|
||||
return [ip]
|
||||
|
|
@ -67,14 +63,24 @@ def is_single_ipv4(ip):
|
|||
|
||||
def is_ipv4_range(ip_range):
|
||||
try:
|
||||
return '/' in ip_range and '.' in ip_range and '-' not in ip_range and netaddr.IPNetwork(ip_range)
|
||||
return (
|
||||
"/" in ip_range
|
||||
and "." in ip_range
|
||||
and "-" not in ip_range
|
||||
and bool(netaddr.IPNetwork(ip_range))
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def is_ipv4_cidr(ip_range):
|
||||
try:
|
||||
return '/' not in ip_range and '.' in ip_range and '-' in ip_range and iprange_to_cidrs(*ip_range.split('-'))
|
||||
return (
|
||||
"/" not in ip_range
|
||||
and "." in ip_range
|
||||
and "-" in ip_range
|
||||
and bool(netaddr.iprange_to_cidrs(*ip_range.split("-")))
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
|
@ -89,18 +95,28 @@ def is_single_ipv6(ip):
|
|||
Returns:
|
||||
True if it's IPv6 otherwise False
|
||||
"""
|
||||
return netaddr.valid_ipv6(str(ip))
|
||||
return netaddr.valid_ipv6(ip)
|
||||
|
||||
|
||||
def is_ipv6_range(ip_range):
|
||||
try:
|
||||
return '/' not in ip_range and ':' in ip_range and '-' in ip_range and iprange_to_cidrs(*ip_range.split('-'))
|
||||
return (
|
||||
"/" not in ip_range
|
||||
and ":" in ip_range
|
||||
and "-" in ip_range
|
||||
and bool(netaddr.iprange_to_cidrs(*ip_range.split("-")))
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def is_ipv6_cidr(ip_range):
|
||||
try:
|
||||
return '/' in ip_range and ':' in ip_range and '-' not in ip_range and netaddr.IPNetwork(ip_range)
|
||||
return (
|
||||
"/" in ip_range
|
||||
and ":" in ip_range
|
||||
and "-" not in ip_range
|
||||
and bool(netaddr.IPNetwork(ip_range))
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
import copy
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from abc import ABC
|
||||
from datetime import datetime
|
||||
|
||||
import yaml
|
||||
|
||||
from nettacker.config import Config
|
||||
from nettacker.core.messages import messages as _
|
||||
from nettacker.core.utils.common import merge_logs_to_list, remove_sensitive_header_keys
|
||||
from nettacker.database.db import find_temp_events, submit_temp_logs_to_db, submit_logs_to_db
|
||||
from nettacker.logger import get_logger, TerminalCodes
|
||||
|
||||
log = get_logger()
|
||||
|
||||
|
||||
class BaseLibrary(ABC):
|
||||
"""Nettacker library base class."""
|
||||
|
||||
client = None
|
||||
|
||||
def brute_force(self):
|
||||
"""Brute force method."""
|
||||
|
||||
|
||||
class BaseEngine(ABC):
|
||||
"""Nettacker engine base class."""
|
||||
|
||||
library = None
|
||||
|
||||
def apply_extra_data(self, *args, **kwargs):
|
||||
"""Add extra data into step context."""
|
||||
|
||||
def filter_large_content(self, content, filter_rate=150):
|
||||
if len(content) <= filter_rate:
|
||||
return content
|
||||
else:
|
||||
filter_rate -= 1
|
||||
filter_index = filter_rate
|
||||
for char in content[filter_rate:]:
|
||||
if char == " ":
|
||||
return content[0:filter_index] + _("filtered_content")
|
||||
else:
|
||||
filter_index += 1
|
||||
return content
|
||||
|
||||
def get_dependent_results_from_database(self, target, module_name, scan_id, event_names):
|
||||
events = []
|
||||
for event_name in event_names.split(","):
|
||||
while True:
|
||||
event = find_temp_events(target, module_name, scan_id, event_name)
|
||||
if event:
|
||||
events.append(json.loads(event.event)["response"]["conditions_results"])
|
||||
break
|
||||
time.sleep(0.1)
|
||||
return events
|
||||
|
||||
def find_and_replace_dependent_values(self, sub_step, dependent_on_temp_event):
|
||||
if isinstance(sub_step, dict):
|
||||
for key in copy.deepcopy(sub_step):
|
||||
if not isinstance(sub_step[key], (str, float, int, bytes)):
|
||||
sub_step[key] = self.find_and_replace_dependent_values(
|
||||
copy.deepcopy(sub_step[key]), dependent_on_temp_event
|
||||
)
|
||||
else:
|
||||
if isinstance(sub_step[key], str):
|
||||
if "dependent_on_temp_event" in sub_step[key]:
|
||||
globals().update(locals())
|
||||
generate_new_step = copy.deepcopy(sub_step[key])
|
||||
key_name = re.findall(
|
||||
re.compile(
|
||||
"dependent_on_temp_event\\[\\S+\\]\\['\\S+\\]\\[\\S+\\]"
|
||||
),
|
||||
generate_new_step,
|
||||
)[0]
|
||||
try:
|
||||
key_value = eval(key_name)
|
||||
except Exception:
|
||||
key_value = "error"
|
||||
sub_step[key] = sub_step[key].replace(key_name, key_value)
|
||||
if isinstance(sub_step, list):
|
||||
value_index = 0
|
||||
for key in copy.deepcopy(sub_step):
|
||||
if type(sub_step[value_index]) not in [str, float, int, bytes]:
|
||||
sub_step[key] = self.find_and_replace_dependent_values(
|
||||
copy.deepcopy(sub_step[value_index]), dependent_on_temp_event
|
||||
)
|
||||
else:
|
||||
if isinstance(sub_step[value_index], str):
|
||||
if "dependent_on_temp_event" in sub_step[value_index]:
|
||||
globals().update(locals())
|
||||
generate_new_step = copy.deepcopy(sub_step[key])
|
||||
key_name = re.findall(
|
||||
re.compile("dependent_on_temp_event\\['\\S+\\]\\[\\S+\\]"),
|
||||
generate_new_step,
|
||||
)[0]
|
||||
try:
|
||||
key_value = eval(key_name)
|
||||
except Exception:
|
||||
key_value = "error"
|
||||
sub_step[value_index] = sub_step[value_index].replace(
|
||||
key_name, key_value
|
||||
)
|
||||
value_index += 1
|
||||
return sub_step
|
||||
|
||||
def process_conditions(
|
||||
self,
|
||||
event,
|
||||
module_name,
|
||||
target,
|
||||
scan_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests,
|
||||
):
|
||||
# Remove sensitive keys from headers before submitting to DB
|
||||
event = remove_sensitive_header_keys(event)
|
||||
if "save_to_temp_events_only" in event.get("response", ""):
|
||||
submit_temp_logs_to_db(
|
||||
{
|
||||
"date": datetime.now(),
|
||||
"target": target,
|
||||
"module_name": module_name,
|
||||
"scan_id": scan_id,
|
||||
"event_name": event["response"]["save_to_temp_events_only"],
|
||||
"port": event.get("ports", ""),
|
||||
"event": event,
|
||||
"data": response,
|
||||
}
|
||||
)
|
||||
if event["response"]["conditions_results"] and "save_to_temp_events_only" not in event.get(
|
||||
"response", ""
|
||||
):
|
||||
# remove sensitive information before submitting to db
|
||||
|
||||
options = copy.deepcopy(options)
|
||||
for key in Config.api:
|
||||
try:
|
||||
del options[key]
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
del event["response"]["conditions"]
|
||||
del event["response"]["condition_type"]
|
||||
if "log" in event["response"]:
|
||||
del event["response"]["log"]
|
||||
event_request_keys = copy.deepcopy(event)
|
||||
del event_request_keys["response"]
|
||||
submit_logs_to_db(
|
||||
{
|
||||
"date": datetime.now(),
|
||||
"target": target,
|
||||
"module_name": module_name,
|
||||
"scan_id": scan_id,
|
||||
"port": event.get("ports")
|
||||
or event.get("port")
|
||||
or (
|
||||
event.get("url").split(":")[2].split("/")[0]
|
||||
if isinstance(event.get("url"), str)
|
||||
and len(event.get("url").split(":")) >= 3
|
||||
and event.get("url").split(":")[2].split("/")[0].isdigit()
|
||||
else ""
|
||||
),
|
||||
"event": " ".join(yaml.dump(event_request_keys).split())
|
||||
+ " conditions: "
|
||||
+ " ".join(yaml.dump(event["response"]["conditions_results"]).split()),
|
||||
"json_event": event,
|
||||
}
|
||||
)
|
||||
log_list = merge_logs_to_list(event["response"]["conditions_results"])
|
||||
if log_list:
|
||||
log.success_event_info(
|
||||
_("send_success_event_from_module").format(
|
||||
process_number,
|
||||
module_name,
|
||||
target,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests,
|
||||
" ",
|
||||
self.filter_large_content(
|
||||
"\n".join(
|
||||
[
|
||||
TerminalCodes.PURPLE.value + key + TerminalCodes.RESET.value
|
||||
for key in log_list
|
||||
]
|
||||
),
|
||||
filter_rate=100000,
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
log.success_event_info(
|
||||
_("send_success_event_from_module").format(
|
||||
process_number,
|
||||
module_name,
|
||||
target,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests,
|
||||
" ".join(
|
||||
[
|
||||
TerminalCodes.YELLOW.value + key + TerminalCodes.RESET.value
|
||||
if ":" in key
|
||||
else TerminalCodes.GREEN.value + key + TerminalCodes.RESET.value
|
||||
for key in yaml.dump(event_request_keys).split()
|
||||
]
|
||||
),
|
||||
self.filter_large_content(
|
||||
"conditions: "
|
||||
+ " ".join(
|
||||
[
|
||||
TerminalCodes.PURPLE.value + key + TerminalCodes.RESET.value
|
||||
if ":" in key
|
||||
else TerminalCodes.GREEN.value
|
||||
+ key
|
||||
+ TerminalCodes.RESET.value
|
||||
for key in yaml.dump(
|
||||
event["response"]["conditions_results"]
|
||||
).split()
|
||||
]
|
||||
),
|
||||
filter_rate=150,
|
||||
),
|
||||
)
|
||||
)
|
||||
log.verbose_info(json.dumps(event))
|
||||
return True
|
||||
else:
|
||||
del event["response"]["conditions"]
|
||||
log.verbose_info(
|
||||
_("send_unsuccess_event_from_module").format(
|
||||
process_number,
|
||||
module_name,
|
||||
target,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests,
|
||||
)
|
||||
)
|
||||
log.verbose_info(json.dumps(event))
|
||||
return "save_to_temp_events_only" in event["response"]
|
||||
|
||||
def replace_dependent_values(self, sub_step, dependent_on_temp_event):
|
||||
return self.find_and_replace_dependent_values(sub_step, dependent_on_temp_event)
|
||||
|
||||
def run(
|
||||
self,
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_id,
|
||||
options,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests,
|
||||
):
|
||||
"""Engine entry point."""
|
||||
backup_method = copy.deepcopy(sub_step["method"])
|
||||
backup_response = copy.deepcopy(sub_step["response"])
|
||||
del sub_step["method"]
|
||||
del sub_step["response"]
|
||||
|
||||
for attr_name in ("ports", "usernames", "passwords"):
|
||||
if attr_name in sub_step:
|
||||
value = sub_step.pop(attr_name)
|
||||
sub_step[attr_name.rstrip("s")] = int(value) if attr_name == "ports" else value
|
||||
|
||||
if "dependent_on_temp_event" in backup_response:
|
||||
temp_event = self.get_dependent_results_from_database(
|
||||
target, module_name, scan_id, backup_response["dependent_on_temp_event"]
|
||||
)
|
||||
sub_step = self.replace_dependent_values(sub_step, temp_event)
|
||||
|
||||
action = getattr(self.library(), backup_method)
|
||||
for _i in range(options["retries"]):
|
||||
try:
|
||||
response = action(**sub_step)
|
||||
break
|
||||
except Exception:
|
||||
response = []
|
||||
|
||||
sub_step["method"] = backup_method
|
||||
sub_step["response"] = backup_response
|
||||
sub_step["response"]["conditions_results"] = response
|
||||
|
||||
self.apply_extra_data(sub_step, response)
|
||||
|
||||
return self.process_conditions(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests,
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import ftplib
|
||||
|
||||
from nettacker.core.lib.base import BaseEngine, BaseLibrary
|
||||
|
||||
|
||||
class FtpLibrary(BaseLibrary):
|
||||
client = ftplib.FTP
|
||||
|
||||
def brute_force(self, host, port, username, password, timeout):
|
||||
connection = self.client(timeout=timeout)
|
||||
connection.connect(host, port)
|
||||
connection.login(username, password)
|
||||
connection.close()
|
||||
|
||||
return {
|
||||
"host": host,
|
||||
"port": port,
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
|
||||
|
||||
class FtpEngine(BaseEngine):
|
||||
library = FtpLibrary
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import ftplib
|
||||
|
||||
from nettacker.core.lib.ftp import FtpEngine, FtpLibrary
|
||||
|
||||
|
||||
class FtpsLibrary(FtpLibrary):
|
||||
client = ftplib.FTP_TLS
|
||||
|
||||
|
||||
class FtpsEngine(FtpEngine):
|
||||
library = FtpsLibrary
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
|
||||
import aiohttp
|
||||
import uvloop
|
||||
|
||||
from nettacker.core.lib.base import BaseEngine
|
||||
from nettacker.core.utils.common import (
|
||||
replace_dependent_response,
|
||||
reverse_and_regex_condition,
|
||||
get_http_header_key,
|
||||
get_http_header_value,
|
||||
)
|
||||
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
|
||||
|
||||
async def perform_request_action(action, request_options):
|
||||
start_time = time.time()
|
||||
async with action(**request_options) as response:
|
||||
return {
|
||||
"reason": response.reason,
|
||||
"url": str(response.url),
|
||||
"status_code": str(response.status),
|
||||
"content": await response.content.read(),
|
||||
"headers": dict(response.headers),
|
||||
"responsetime": time.time() - start_time,
|
||||
}
|
||||
|
||||
|
||||
async def send_request(request_options, method):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
action = getattr(session, method, None)
|
||||
response = await asyncio.gather(
|
||||
*[asyncio.ensure_future(perform_request_action(action, request_options))]
|
||||
)
|
||||
return response[0]
|
||||
|
||||
|
||||
def response_conditions_matched(sub_step, response):
|
||||
if not response:
|
||||
return {}
|
||||
condition_type = sub_step["response"]["condition_type"]
|
||||
conditions = sub_step["response"]["conditions"]
|
||||
condition_results = {}
|
||||
for condition in conditions:
|
||||
if condition in ["reason", "status_code", "content", "url"]:
|
||||
regex = re.findall(re.compile(conditions[condition]["regex"]), response[condition])
|
||||
reverse = conditions[condition]["reverse"]
|
||||
condition_results[condition] = reverse_and_regex_condition(regex, reverse)
|
||||
if condition == "headers":
|
||||
# convert headers to case insensitive dict
|
||||
for key in response["headers"].copy():
|
||||
response["headers"][key.lower()] = response["headers"][key]
|
||||
condition_results["headers"] = {}
|
||||
for header in conditions["headers"]:
|
||||
reverse = conditions["headers"][header]["reverse"]
|
||||
try:
|
||||
regex = re.findall(
|
||||
re.compile(conditions["headers"][header]["regex"]),
|
||||
response["headers"][header.lower()]
|
||||
if header.lower() in response["headers"]
|
||||
else False,
|
||||
)
|
||||
condition_results["headers"][header] = reverse_and_regex_condition(
|
||||
regex, reverse
|
||||
)
|
||||
except TypeError:
|
||||
condition_results["headers"][header] = []
|
||||
if condition == "responsetime":
|
||||
if len(conditions[condition].split()) == 2 and conditions[condition].split()[0] in [
|
||||
"==",
|
||||
"!=",
|
||||
">=",
|
||||
"<=",
|
||||
">",
|
||||
"<",
|
||||
]:
|
||||
exec(
|
||||
"condition_results['responsetime'] = response['responsetime'] if ("
|
||||
+ "response['responsetime'] {0} float(conditions['responsetime'].split()[-1])".format(
|
||||
conditions["responsetime"].split()[0]
|
||||
)
|
||||
+ ") else []"
|
||||
)
|
||||
else:
|
||||
condition_results["responsetime"] = []
|
||||
if condition_type.lower() == "or":
|
||||
# if one of the values are matched, it will be a string or float object in the array
|
||||
# we count False in the array and if it's not all []; then we know one of the conditions
|
||||
# is matched.
|
||||
if (
|
||||
"headers" not in condition_results
|
||||
and (
|
||||
list(condition_results.values()).count([]) != len(list(condition_results.values()))
|
||||
)
|
||||
) or (
|
||||
"headers" in condition_results
|
||||
and (
|
||||
len(list(condition_results.values()))
|
||||
+ len(list(condition_results["headers"].values()))
|
||||
- list(condition_results.values()).count([])
|
||||
- list(condition_results["headers"].values()).count([])
|
||||
- 1
|
||||
!= 0
|
||||
)
|
||||
):
|
||||
if sub_step["response"].get("log", False):
|
||||
condition_results["log"] = sub_step["response"]["log"]
|
||||
if "response_dependent" in condition_results["log"]:
|
||||
condition_results["log"] = replace_dependent_response(
|
||||
condition_results["log"], condition_results
|
||||
)
|
||||
return condition_results
|
||||
else:
|
||||
return {}
|
||||
if condition_type.lower() == "and":
|
||||
if [] in condition_results.values() or (
|
||||
"headers" in condition_results and [] in condition_results["headers"].values()
|
||||
):
|
||||
return {}
|
||||
else:
|
||||
if sub_step["response"].get("log", False):
|
||||
condition_results["log"] = sub_step["response"]["log"]
|
||||
if "response_dependent" in condition_results["log"]:
|
||||
condition_results["log"] = replace_dependent_response(
|
||||
condition_results["log"], condition_results
|
||||
)
|
||||
return condition_results
|
||||
return {}
|
||||
|
||||
|
||||
class HttpEngine(BaseEngine):
|
||||
def run(
|
||||
self,
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_id,
|
||||
options,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests,
|
||||
):
|
||||
if options["http_header"] is not None:
|
||||
for header in options["http_header"]:
|
||||
key = get_http_header_key(header).strip()
|
||||
value = get_http_header_value(header)
|
||||
if value is not None:
|
||||
sub_step["headers"][key] = value.strip()
|
||||
else:
|
||||
sub_step["headers"][key] = ""
|
||||
backup_method = copy.deepcopy(sub_step["method"])
|
||||
backup_response = copy.deepcopy(sub_step["response"])
|
||||
backup_iterative_response_match = copy.deepcopy(
|
||||
sub_step["response"]["conditions"].get("iterative_response_match", None)
|
||||
)
|
||||
if options["user_agent"] == "random_user_agent":
|
||||
sub_step["headers"]["User-Agent"] = random.choice(options["user_agents"])
|
||||
del sub_step["method"]
|
||||
if "dependent_on_temp_event" in backup_response:
|
||||
temp_event = self.get_dependent_results_from_database(
|
||||
target,
|
||||
module_name,
|
||||
scan_id,
|
||||
backup_response["dependent_on_temp_event"],
|
||||
)
|
||||
sub_step = self.replace_dependent_values(sub_step, temp_event)
|
||||
backup_response = copy.deepcopy(sub_step["response"])
|
||||
del sub_step["response"]
|
||||
for _i in range(options["retries"]):
|
||||
try:
|
||||
response = asyncio.run(send_request(sub_step, backup_method))
|
||||
response["content"] = response["content"].decode(errors="ignore")
|
||||
break
|
||||
except Exception:
|
||||
response = []
|
||||
sub_step["method"] = backup_method
|
||||
sub_step["response"] = backup_response
|
||||
|
||||
if backup_iterative_response_match is not None:
|
||||
backup_iterative_response_match = copy.deepcopy(
|
||||
sub_step["response"]["conditions"].get("iterative_response_match")
|
||||
)
|
||||
del sub_step["response"]["conditions"]["iterative_response_match"]
|
||||
|
||||
sub_step["response"]["conditions_results"] = response_conditions_matched(
|
||||
sub_step, response
|
||||
)
|
||||
|
||||
if backup_iterative_response_match is not None and (
|
||||
sub_step["response"]["conditions_results"]
|
||||
or sub_step["response"]["condition_type"] == "or"
|
||||
):
|
||||
sub_step["response"]["conditions"][
|
||||
"iterative_response_match"
|
||||
] = backup_iterative_response_match
|
||||
for key in sub_step["response"]["conditions"]["iterative_response_match"]:
|
||||
result = response_conditions_matched(
|
||||
sub_step["response"]["conditions"]["iterative_response_match"][key],
|
||||
response,
|
||||
)
|
||||
if result:
|
||||
sub_step["response"]["conditions_results"][key] = result
|
||||
|
||||
return self.process_conditions(
|
||||
sub_step,
|
||||
module_name,
|
||||
target,
|
||||
scan_id,
|
||||
options,
|
||||
response,
|
||||
process_number,
|
||||
module_thread_number,
|
||||
total_module_thread_number,
|
||||
request_number_counter,
|
||||
total_number_of_requests,
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import poplib
|
||||
|
||||
from nettacker.core.lib.base import BaseEngine, BaseLibrary
|
||||
|
||||
|
||||
class Pop3Library(BaseLibrary):
|
||||
client = poplib.POP3
|
||||
|
||||
def brute_force(self, host, port, username, password, timeout):
|
||||
connection = self.client(host, port=port, timeout=timeout)
|
||||
connection.user(username)
|
||||
connection.pass_(password)
|
||||
connection.quit()
|
||||
|
||||
return {
|
||||
"host": host,
|
||||
"port": port,
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
|
||||
|
||||
class Pop3Engine(BaseEngine):
|
||||
library = Pop3Library
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import poplib
|
||||
|
||||
from nettacker.core.lib.pop3 import Pop3Engine, Pop3Library
|
||||
|
||||
|
||||
class Pop3sLibrary(Pop3Library):
|
||||
client = poplib.POP3_SSL
|
||||
|
||||
|
||||
class Pop3sEngine(Pop3Engine):
|
||||
library = Pop3sLibrary
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue