Compare commits
674 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fa7b96f27 | ||
|
|
7c8e259072 | ||
|
|
743d067042 | ||
|
|
50ea410718 | ||
|
|
e71182aed2 | ||
|
|
97f7963e0b | ||
|
|
6cea6a10bd | ||
|
|
344d41e365 | ||
|
|
597a8adfed | ||
|
|
5682e5483a | ||
|
|
18779edd7d | ||
|
|
63aeaea77a | ||
|
|
f51e48cb40 | ||
|
|
193372c79c | ||
|
|
34d0afe7e5 | ||
|
|
d33a9dd56d | ||
|
|
af2be90176 | ||
|
|
701fb21544 | ||
|
|
c8f010d9a6 | ||
|
|
c1ca7580e2 | ||
|
|
11d3c2cbf1 | ||
|
|
412f576aee | ||
|
|
ff98a7b23b | ||
|
|
507bac8542 | ||
|
|
3bd6cf4805 | ||
|
|
5712b31869 | ||
|
|
b0f9a0b18f | ||
|
|
f7665b460e | ||
|
|
100029b640 | ||
|
|
2b89bddf0f | ||
|
|
ca46bad8ec | ||
|
|
1ecd2600a3 | ||
|
|
28306b126b | ||
|
|
5c842e192b | ||
|
|
f9f307118d | ||
|
|
2266fa4f4b | ||
|
|
6df2564d1a | ||
|
|
b2a62ebd26 | ||
|
|
2fca30752a | ||
|
|
210eced369 | ||
|
|
08c574aff8 | ||
|
|
f4db2d65f1 | ||
|
|
23b02ade96 | ||
|
|
71d35b16ac | ||
|
|
200058c52a | ||
|
|
edb5fb202a | ||
|
|
d114c25cdb | ||
|
|
64e469568b | ||
|
|
c63d777372 | ||
|
|
ae343b8cb0 | ||
|
|
86eb5d2d57 | ||
|
|
b562d241db | ||
|
|
49b1829b78 | ||
|
|
1a5bf4035c | ||
|
|
f3810a1504 | ||
|
|
4831760c11 | ||
|
|
f375d8991e | ||
|
|
8145a4a3a6 | ||
|
|
12c9b49d4c | ||
|
|
c8eb81161e | ||
|
|
8ab246a66c | ||
|
|
8dfc4797fa | ||
|
|
7888fe1176 | ||
|
|
8a6f3056a3 | ||
|
|
5fbdf9e013 | ||
|
|
1da2f5e823 | ||
|
|
888779f81b | ||
|
|
352286e497 | ||
|
|
025ce37c05 | ||
|
|
d6c2c63679 | ||
|
|
49efbf25ea | ||
|
|
02cdee2776 | ||
|
|
7c9d4d5b05 | ||
|
|
609b7551f8 | ||
|
|
e8f215ae00 | ||
|
|
2e00aea16e | ||
|
|
dd274d77f5 | ||
|
|
58171a7b8c | ||
|
|
8b05179401 | ||
|
|
51d61a7e88 | ||
|
|
d653ce4e0e | ||
|
|
07b3826806 | ||
|
|
1baa3e23b2 | ||
|
|
0aa1f20d47 | ||
|
|
1cf330b389 | ||
|
|
1771c4b346 | ||
|
|
4c053b4873 | ||
|
|
743ba0541b | ||
|
|
cfab2a9cd7 | ||
|
|
32270efd65 | ||
|
|
7ea1acb7c1 | ||
|
|
bf91f60242 | ||
|
|
660885c0b1 | ||
|
|
15fd3b969f | ||
|
|
f1d15ca7f2 | ||
|
|
6f4f4a5924 | ||
|
|
9af0520701 | ||
|
|
2edeab558e | ||
|
|
87bf59f50b | ||
|
|
eeb69e63f7 | ||
|
|
f9435906e7 | ||
|
|
6c8adbe50e | ||
|
|
23bdb6c579 | ||
|
|
264411bfb9 | ||
|
|
2104237584 | ||
|
|
0ae2525737 | ||
|
|
b12973a837 | ||
|
|
fa0582ce0b | ||
|
|
231f5157bf | ||
|
|
8b18204a69 | ||
|
|
95eb6a732c | ||
|
|
047a188b34 | ||
|
|
d407815c30 | ||
|
|
1f0f87633b | ||
|
|
c15ff4e32e | ||
|
|
72bddca314 | ||
|
|
496fc4ebee | ||
|
|
f414e6eeb7 | ||
|
|
f09606cfa3 | ||
|
|
6304fe4c19 | ||
|
|
5f2b8f8a2e | ||
|
|
898e8d4546 | ||
|
|
f1657164d5 | ||
|
|
357e13be2b | ||
|
|
9685568c75 | ||
|
|
b316940790 | ||
|
|
2ced489e1e | ||
|
|
5969fe08d8 | ||
|
|
4a427f1ff6 | ||
|
|
9a3db275f3 | ||
|
|
475dd4d1ff | ||
|
|
57c99c4a34 | ||
|
|
966f5691a2 | ||
|
|
5088ece8a1 | ||
|
|
943d87fe17 | ||
|
|
b5363b2689 | ||
|
|
c15cb16ca8 | ||
|
|
18b7f088fc | ||
|
|
4f9822743c | ||
|
|
e7925de5bc | ||
|
|
27fc6a7279 | ||
|
|
ab5f46e955 | ||
|
|
d30d212cc5 | ||
|
|
adff971d62 | ||
|
|
23b22f71b8 | ||
|
|
fee3671e32 | ||
|
|
26c6be7268 | ||
|
|
01c5bcf2be | ||
|
|
1ab8a5ab98 | ||
|
|
b54aaca28a | ||
|
|
86a29ae000 | ||
|
|
a5dbee93ff | ||
|
|
e0465e6e10 | ||
|
|
7da48b9dd1 | ||
|
|
a64895c3a6 | ||
|
|
21f1a5d4c4 | ||
|
|
d60f79ca33 | ||
|
|
2d5cea5033 | ||
|
|
b0615215fe | ||
|
|
7a0f98b2cb | ||
|
|
cdc1dab4a6 | ||
|
|
431739ab19 | ||
|
|
1780399050 | ||
|
|
eb75d38716 | ||
|
|
06f82d78f4 | ||
|
|
dee4da1c0e | ||
|
|
e341ec7c60 | ||
|
|
9146609e4a | ||
|
|
f90615ca41 | ||
|
|
8a2a6a05ff | ||
|
|
5a787f8ed5 | ||
|
|
a904053002 | ||
|
|
70ecd30dcc | ||
|
|
b0976d7e47 | ||
|
|
bb5e55016c | ||
|
|
abdf285c69 | ||
|
|
fd4da23d4f | ||
|
|
bb8f58c83b | ||
|
|
077da6ae86 | ||
|
|
d5222d7e9a | ||
|
|
01702c127b | ||
|
|
87902cbfb4 | ||
|
|
fcaa393ffe | ||
|
|
18bac6e792 | ||
|
|
9a21efebe3 | ||
|
|
357182ef17 | ||
|
|
5fad540a4c | ||
|
|
c1fc153420 | ||
|
|
73a1974f85 | ||
|
|
dec73c21b6 | ||
|
|
46a00cc864 | ||
|
|
62455be165 | ||
|
|
17ef5ef918 | ||
|
|
922b6fffd0 | ||
|
|
b47bf006d0 | ||
|
|
d60269f4bc | ||
|
|
1ce057a78e | ||
|
|
a0fe04b990 | ||
|
|
31c9172e19 | ||
|
|
7f23cbef71 | ||
|
|
4884defaed | ||
|
|
3039218c40 | ||
|
|
8bbc2f32ae | ||
|
|
4ca46ab3ba | ||
|
|
7442c72d01 | ||
|
|
01cd8350bc | ||
|
|
8b5ea589db | ||
|
|
3555ca1d1e | ||
|
|
ae034a47ed | ||
|
|
ec3862c930 | ||
|
|
c63804d1c5 | ||
|
|
c5e6752f75 | ||
|
|
e4f3e9d11c | ||
|
|
f3713536b9 | ||
|
|
fb751c0a51 | ||
|
|
9d3464055a | ||
|
|
0fea814f5d | ||
|
|
ae70a6df9d | ||
|
|
4afc756ccd | ||
|
|
adc5841261 | ||
|
|
41cca5fb8a | ||
|
|
498da1a06b | ||
|
|
48dab90313 | ||
|
|
d1ff642957 | ||
|
|
2b5613d84a | ||
|
|
09d28fae26 | ||
|
|
7517e247d9 | ||
|
|
998951e629 | ||
|
|
d89fcbb68a | ||
|
|
d3e0ff1e66 | ||
|
|
804a8c34c6 | ||
|
|
57942e1826 | ||
|
|
5657735b55 | ||
|
|
791fce2424 | ||
|
|
c34fa45875 | ||
|
|
e0fd79f800 | ||
|
|
f9d9cda4a4 | ||
|
|
d6f44b2f42 | ||
|
|
bd90da7ed2 | ||
|
|
3a1a976e35 | ||
|
|
db1309af83 | ||
|
|
0e47441a36 | ||
|
|
375bea9a8b | ||
|
|
3a42772879 | ||
|
|
e9956593dc | ||
|
|
fda6000c4c | ||
|
|
99b4eb969d | ||
|
|
dadd55ba32 | ||
|
|
b40e06b2ea | ||
|
|
3f20edc41f | ||
|
|
baaa11bb64 | ||
|
|
44e1179ce4 | ||
|
|
808521fb70 | ||
|
|
ad8e97f432 | ||
|
|
3c47652cc0 | ||
|
|
220ff0e3f7 | ||
|
|
d268a86795 | ||
|
|
28b9c15256 | ||
|
|
4f594d59cc | ||
|
|
a25b493064 | ||
|
|
2acf88d83e | ||
|
|
baf3b4bc2b | ||
|
|
750411d9e1 | ||
|
|
aa7b922d30 | ||
|
|
fd660632e0 | ||
|
|
c7df7265ab | ||
|
|
42685a45b3 | ||
|
|
ce5d26a220 | ||
|
|
0e73774bd9 | ||
|
|
85b491472a | ||
|
|
4b382acbad | ||
|
|
12d15bfc7e | ||
|
|
ea1b6b9c17 | ||
|
|
5cb2d16601 | ||
|
|
913717bcf7 | ||
|
|
99fe1855d9 | ||
|
|
e2eb94be22 | ||
|
|
aca1b487ba | ||
|
|
5820c53d0f | ||
|
|
9298758acd | ||
|
|
a981c2b17b | ||
|
|
a783b53107 | ||
|
|
cf2881fda6 | ||
|
|
59368a72bd | ||
|
|
439900a1ea | ||
|
|
44557797b0 | ||
|
|
ba065d5974 | ||
|
|
105e9cbcac | ||
|
|
fe277c1e89 | ||
|
|
b5e3e6280e | ||
|
|
f90a64ce81 | ||
|
|
b9fa1e3587 | ||
|
|
4333ecb989 | ||
|
|
715d3d4ad6 | ||
|
|
38f70a88ae | ||
|
|
4b4b968710 | ||
|
|
3b94fc49a7 | ||
|
|
e41aab3a80 | ||
|
|
9450ba6cc5 | ||
|
|
ae3c164350 | ||
|
|
24e6820a90 | ||
|
|
0e05f77fb7 | ||
|
|
de960ff9db | ||
|
|
1d0128af72 | ||
|
|
285b1a1733 | ||
|
|
ab67816dd9 | ||
|
|
fea6665876 | ||
|
|
6cbc8c9924 | ||
|
|
f542a50213 | ||
|
|
fa430606ce | ||
|
|
05d27c64be | ||
|
|
0cd680bb29 | ||
|
|
ced94a7338 | ||
|
|
b65a4d0a60 | ||
|
|
2b85b44bd1 | ||
|
|
991c87a89e | ||
|
|
37a72f0c72 | ||
|
|
6c0a21c80d | ||
|
|
dc48008d43 | ||
|
|
5720d29492 | ||
|
|
358f3d59d8 | ||
|
|
b6c6a46d25 | ||
|
|
25c393d557 | ||
|
|
435fb34233 | ||
|
|
2c40913a64 | ||
|
|
e437b952da | ||
|
|
282c595b38 | ||
|
|
c2c8d63e75 | ||
|
|
ad21d97d11 | ||
|
|
5c27c78ed0 | ||
|
|
a53e9a5e12 | ||
|
|
c8036692ee | ||
|
|
b9535a3648 | ||
|
|
651c364fa9 | ||
|
|
958410d4c9 | ||
|
|
e9fba126d2 | ||
|
|
95d39cce5a | ||
|
|
32d9afdf9b | ||
|
|
7e9a4168ff | ||
|
|
9d6415a89b | ||
|
|
1499b07176 | ||
|
|
9c7188a312 | ||
|
|
b63e28c150 | ||
|
|
50d48902cf | ||
|
|
aa6899cbc5 | ||
|
|
94e6b2eab6 | ||
|
|
54c0e79c58 | ||
|
|
859d7f1c60 | ||
|
|
166112209e | ||
|
|
952395d0c1 | ||
|
|
c7061f8a51 | ||
|
|
0c71bce221 | ||
|
|
b2b4eebd78 | ||
|
|
5257a8b997 | ||
|
|
9844f9d8ab | ||
|
|
000f275263 | ||
|
|
e5077c490a | ||
|
|
d76968c15f | ||
|
|
289ef5b0dd | ||
|
|
7ec227873c | ||
|
|
1deccfd477 | ||
|
|
286e6bd51a | ||
|
|
8167fa2e17 | ||
|
|
c960df0bb1 | ||
|
|
ebf8d31c6c | ||
|
|
082ae650fc | ||
|
|
2f5599c863 | ||
|
|
a764bdd993 | ||
|
|
ef46d2c956 | ||
|
|
d2c2c1defb | ||
|
|
dede023ec8 | ||
|
|
d8a9b3aa77 | ||
|
|
ad364e6a2e | ||
|
|
523954e507 | ||
|
|
872bbdb8e0 | ||
|
|
3ca8727b64 | ||
|
|
1d3ca87772 | ||
|
|
90c42f42a1 | ||
|
|
641108e7eb | ||
|
|
0e87384b0a | ||
|
|
5175170c4b | ||
|
|
79864cae7b | ||
|
|
ca5f92ca61 | ||
|
|
d29de83c41 | ||
|
|
1f42ce6e2f | ||
|
|
0dc7128582 | ||
|
|
21f4de2ec1 | ||
|
|
d65567fc8f | ||
|
|
20af778fa1 | ||
|
|
5f77832386 | ||
|
|
6ccfe70775 | ||
|
|
6b0f687abb | ||
|
|
67ba526b5b | ||
|
|
e186ec7534 | ||
|
|
23ef1e75b3 | ||
|
|
8170390f92 | ||
|
|
c148295f64 | ||
|
|
37b99f9baa | ||
|
|
8e4643874d | ||
|
|
0522023fd4 | ||
|
|
711ee730a0 | ||
|
|
f3bd995528 | ||
|
|
beec0bd35a | ||
|
|
9d7f35f3b2 | ||
|
|
c7488e28f7 | ||
|
|
9150e0ca52 | ||
|
|
475288deeb | ||
|
|
82335d7399 | ||
|
|
338eacd63b | ||
|
|
0b9b79f55f | ||
|
|
5303b28957 | ||
|
|
11c05a3590 | ||
|
|
862c0a9014 | ||
|
|
487a483aa6 | ||
|
|
030c20a11b | ||
|
|
ec831f7fed | ||
|
|
50fa79b331 | ||
|
|
edab0e812a | ||
|
|
f0126ca860 | ||
|
|
01261d4d29 | ||
|
|
f97d3436a5 | ||
|
|
0bcb8b4b3b | ||
|
|
489545dd75 | ||
|
|
f6c152f58a | ||
|
|
16734418be | ||
|
|
b17ee20f58 | ||
|
|
aaee6f1e6d | ||
|
|
64d8240b8a | ||
|
|
0a6d430c9f | ||
|
|
7bf0314561 | ||
|
|
409897fec4 | ||
|
|
91b0d20665 | ||
|
|
f6644eebf9 | ||
|
|
88bddd4f87 | ||
|
|
c61b023fb7 | ||
|
|
1b5df8751f | ||
|
|
314c98f101 | ||
|
|
8274e2efe9 | ||
|
|
2bff063805 | ||
|
|
53d9956829 | ||
|
|
6e98678c3c | ||
|
|
f0f21f5ac2 | ||
|
|
aa233b1c4d | ||
|
|
93f9123f45 | ||
|
|
5c710d88e4 | ||
|
|
ded70ff743 | ||
|
|
9df7443aa4 | ||
|
|
8362975691 | ||
|
|
49771419ae | ||
|
|
d344f84824 | ||
|
|
89c0b8d4d0 | ||
|
|
3c74ee8d97 | ||
|
|
785c6efa5b | ||
|
|
4e2bf5322e | ||
|
|
54ed148c87 | ||
|
|
b08e298eba | ||
|
|
89e2088357 | ||
|
|
f3cc35bd74 | ||
|
|
a007d283e5 | ||
|
|
70902aa013 | ||
|
|
91151fc53b | ||
|
|
d4ee82dac5 | ||
|
|
88d3c26113 | ||
|
|
054a4ee6aa | ||
|
|
c291022753 | ||
|
|
2fc488b602 | ||
|
|
009ddd690e | ||
|
|
88b5cd8751 | ||
|
|
cfd19d02b1 | ||
|
|
19ce30d862 | ||
|
|
c6df6e0e89 | ||
|
|
e942a5bcf6 | ||
|
|
c0f5163d07 | ||
|
|
f5aa9f117f | ||
|
|
498d93377d | ||
|
|
52242e706b | ||
|
|
22d69a1bf9 | ||
|
|
0b1fa13696 | ||
|
|
19b15b5327 | ||
|
|
e63e96f5ed | ||
|
|
e8ac8f26a7 | ||
|
|
13e4327de4 | ||
|
|
c22a1ed12a | ||
|
|
be5662b5f1 | ||
|
|
6e840ca920 | ||
|
|
8492190f4c | ||
|
|
93ab6ee2a0 | ||
|
|
7075e01886 | ||
|
|
436a83434c | ||
|
|
d270391b56 | ||
|
|
7f2762eb6f | ||
|
|
2cc5bb0311 | ||
|
|
d697127261 | ||
|
|
825523a851 | ||
|
|
0f3f9cac33 | ||
|
|
f9b545b100 | ||
|
|
943bfc39b3 | ||
|
|
b1a8f445c6 | ||
|
|
5435df4345 | ||
|
|
8e9d29e94f | ||
|
|
1afa761f09 | ||
|
|
d626913ce9 | ||
|
|
9c52e4a5ee | ||
|
|
72c2c1992b | ||
|
|
e1b4b5e8e5 | ||
|
|
0243522854 | ||
|
|
5118c68f45 | ||
|
|
442884b5c5 | ||
|
|
f832e27b49 | ||
|
|
6ce29f73c5 | ||
|
|
920338fb62 | ||
|
|
49d0a9e6d9 | ||
|
|
fe401e622b | ||
|
|
6e32cb0db2 | ||
|
|
73171eb39d | ||
|
|
2e05f4171e | ||
|
|
75b8c303e2 | ||
|
|
bd7a493f1c | ||
|
|
9dada7c8f4 | ||
|
|
fe7aede458 | ||
|
|
cdf2b38780 | ||
|
|
a09dbab6a8 | ||
|
|
49a6d275d2 | ||
|
|
8192a4a215 | ||
|
|
1d6593fd4d | ||
|
|
bf99e31e70 | ||
|
|
5386496bdc | ||
|
|
6451510449 | ||
|
|
cd68aa719c | ||
|
|
b328dc4ff9 | ||
|
|
1e1c79aa56 | ||
|
|
08650ce156 | ||
|
|
a1929719f3 | ||
|
|
d34da72cd3 | ||
|
|
816b18b604 | ||
|
|
a78a13bf3f | ||
|
|
33f8aaf1dc | ||
|
|
26ab95d822 | ||
|
|
cea01d8aa0 | ||
|
|
0e61f1e284 | ||
|
|
ddef061b90 | ||
|
|
addeab8947 | ||
|
|
55dc665404 | ||
|
|
8f8538e9e9 | ||
|
|
348ca55bee | ||
|
|
1bb5bc7f33 | ||
|
|
3be5e1fcf5 | ||
|
|
9df8cc9243 | ||
|
|
e28c84aa34 | ||
|
|
7db6b54761 | ||
|
|
e3a06f5694 | ||
|
|
7c5d15e098 | ||
|
|
d683c0f151 | ||
|
|
1e67fa26ff | ||
|
|
0ae6ef59ec | ||
|
|
e27ef40e0f | ||
|
|
380760d028 | ||
|
|
18cfdafc19 | ||
|
|
0934a2e329 | ||
|
|
d1a320324e | ||
|
|
361c96d746 | ||
|
|
e7dbf9278d | ||
|
|
6564fddb27 | ||
|
|
d382874e86 | ||
|
|
91b30bee9f | ||
|
|
7804aad776 | ||
|
|
b7552ac8aa | ||
|
|
a76c94cccf | ||
|
|
c0ae5c7cad | ||
|
|
cc55b39b83 | ||
|
|
d8a6884ab6 | ||
|
|
5ce3581386 | ||
|
|
2208f2a8c0 | ||
|
|
a4a14c7e63 | ||
|
|
aa464b476c | ||
|
|
3c92712a6e | ||
|
|
fd0c47f5d7 | ||
|
|
c03a44d225 | ||
|
|
d31d45ba71 | ||
|
|
db528b27f4 | ||
|
|
e6d29f6f18 | ||
|
|
e4d6b988ef | ||
|
|
ec68291bf0 | ||
|
|
3a6a451db1 | ||
|
|
7ec095d708 | ||
|
|
57f6206aee | ||
|
|
390f10e83f | ||
|
|
8727935cb2 | ||
|
|
d0e868f556 | ||
|
|
01c357e146 | ||
|
|
a0fed4a9d0 | ||
|
|
c4aed0ec89 | ||
|
|
cc737090a2 | ||
|
|
1652c09e95 | ||
|
|
2538b88579 | ||
|
|
8c2eb63840 | ||
|
|
36df5ee6e4 | ||
|
|
9720b4edf1 | ||
|
|
13d35b7607 | ||
|
|
13c2c51cfd | ||
|
|
f43175b0c3 | ||
|
|
1508aba8b2 | ||
|
|
5414ab05e5 | ||
|
|
bd5d2db634 | ||
|
|
3259dd29d8 | ||
|
|
6e56013a95 | ||
|
|
252f762209 | ||
|
|
15c0448cf1 | ||
|
|
4c800bacaa | ||
|
|
5902a483b4 | ||
|
|
ca73e4b93e | ||
|
|
ace64d88ce | ||
|
|
4cc9f7c8b5 | ||
|
|
f4f1390b67 | ||
|
|
14115761f9 | ||
|
|
ac3409e376 | ||
|
|
86a73229c0 | ||
|
|
cc41b96e88 | ||
|
|
e16c5584d1 | ||
|
|
94bab3f550 | ||
|
|
9d04b23fb2 | ||
|
|
2657e5050f | ||
|
|
3d6e5b2b9e | ||
|
|
bdd6b9727d | ||
|
|
6c8172c7cf | ||
|
|
ae5bae9899 | ||
|
|
b6bf306042 | ||
|
|
9c5196dfec | ||
|
|
3d7b8592ea | ||
|
|
e03f7691f2 | ||
|
|
7a54ac62d6 | ||
|
|
8db06d37d2 | ||
|
|
5ee5e76544 | ||
|
|
090cd999cb | ||
|
|
50b75354e0 | ||
|
|
c7b6b25851 | ||
|
|
b931df654d | ||
|
|
b5d5c4177d | ||
|
|
b22550ea55 | ||
|
|
04d50ebea5 | ||
|
|
202180909c | ||
|
|
0d806e6d74 | ||
|
|
54f31ebe7f | ||
|
|
227a39d2fa | ||
|
|
99d8faa38b | ||
|
|
9a7afe1549 | ||
|
|
e6751e0d89 | ||
|
|
371f1df830 | ||
|
|
8e1ba352ee | ||
|
|
7ebfe42eb2 | ||
|
|
df514d3b9f | ||
|
|
acae16e7ee | ||
|
|
deb8508ea5 | ||
|
|
a4bbf41086 | ||
|
|
4fbc535b0c | ||
|
|
36f6f98ce7 | ||
|
|
21cc7d604c | ||
|
|
44207161e6 | ||
|
|
dc20ef0754 | ||
|
|
413ee7a6d3 | ||
|
|
5b94714ca7 | ||
|
|
3675fe1ed7 | ||
|
|
e074a03c40 | ||
|
|
a7860f72a2 | ||
|
|
4b587593ee | ||
|
|
0aa8a97070 | ||
|
|
3c16f84853 | ||
|
|
346898e549 | ||
|
|
bcef4b2de7 | ||
|
|
e42bf7fd7c | ||
|
|
48cd0602d8 | ||
|
|
814e837ae5 | ||
|
|
a58b34eba8 | ||
|
|
7d790f8f79 |
16
.dockerignore
Normal file
16
.dockerignore
Normal file
@@ -0,0 +1,16 @@
|
||||
git/
|
||||
bundle/
|
||||
.idea/
|
||||
.yardoc/
|
||||
cache/
|
||||
coverage/
|
||||
spec/
|
||||
.*
|
||||
**/*.md
|
||||
*.md
|
||||
!README.md
|
||||
Dockerfile
|
||||
**/*.orig
|
||||
*.orig
|
||||
bin/wpscan-*
|
||||
.wpscan/
|
||||
5
.github/CONTRIBUTING.md
vendored
Normal file
5
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Contributing to WPScan
|
||||
|
||||
## Licensing
|
||||
|
||||
By submitting code contributions to the WPScan development team via Github Pull Requests, or any other method, it is understood that the contributor is offering the WPScan company (company number 83421476900012), which is registered in France, the unlimited, non-exclusive right to reuse, modify, and relicense the code.
|
||||
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Before submitting an issue, please make sure you fully read any potential error messages output and did some research on your own.
|
||||
|
||||
### Subject of the issue
|
||||
Describe your issue here.
|
||||
|
||||
### Your environment
|
||||
* Version of WPScan:
|
||||
* Version of Ruby:
|
||||
* Operating System (OS):
|
||||
|
||||
### Steps to reproduce
|
||||
Tell us how to reproduce this issue.
|
||||
|
||||
### Expected behavior
|
||||
Tell us what should happen.
|
||||
|
||||
### Actual behavior
|
||||
Tell us what happens instead.
|
||||
|
||||
### What have you already tried
|
||||
Tell us what you have already tried to do to fix the issue you are having.
|
||||
|
||||
Things you have tried (where relevant):
|
||||
|
||||
* Update WPScan to the latest version [ ]
|
||||
* Update Ruby to the latest version [ ]
|
||||
* Ensure you can reach the target site using cURL [ ]
|
||||
* Proxied WPScan through a HTTP proxy to view the raw traffic [ ]
|
||||
* Ensure you are using a supported Operating System (Linux and macOS) [ ]
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
10
.github/ISSUE_TEMPLATE/other-issue.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/other-issue.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Other Issue
|
||||
about: Create a report which is not a related to a Bug or Feature
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Before submitting an issue, please make sure you fully read any potential error messages output and did some research on your own.
|
||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
## Licensing
|
||||
|
||||
By submitting code contributions to the WPScan development team via Github Pull Requests, or any other method, it is understood that the contributor is offering the WPScan company (company number 83421476900012), which is registered in France, the unlimited, non-exclusive right to reuse, modify, and relicense the code.
|
||||
33
.gitignore
vendored
33
.gitignore
vendored
@@ -1,15 +1,26 @@
|
||||
cache
|
||||
coverage
|
||||
*.gem
|
||||
*.rbc
|
||||
.bundle
|
||||
.config
|
||||
coverage
|
||||
pkg
|
||||
rdoc
|
||||
Gemfile.lock
|
||||
|
||||
# YARD artifacts
|
||||
.yardoc
|
||||
_yardoc
|
||||
doc/
|
||||
.wpscan/
|
||||
|
||||
.ash_history
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
*.sublime-*
|
||||
.idea
|
||||
.*.swp
|
||||
Gemfile.lock
|
||||
log.txt
|
||||
.yardoc
|
||||
debug.log
|
||||
wordlist.txt
|
||||
rspec_results.html
|
||||
.idea/
|
||||
|
||||
# Old files from v2
|
||||
cache/
|
||||
data/
|
||||
|
||||
# Profiling reports
|
||||
bin/memprof*.report
|
||||
|
||||
27
.rubocop.yml
Normal file
27
.rubocop.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
require: rubocop-performance
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.4
|
||||
Exclude:
|
||||
- '*.gemspec'
|
||||
- 'vendor/**/*'
|
||||
ClassVars:
|
||||
Enabled: false
|
||||
LineLength:
|
||||
Max: 120
|
||||
MethodLength:
|
||||
Max: 20
|
||||
Lint/UriEscapeUnescape:
|
||||
Enabled: false
|
||||
Metrics/AbcSize:
|
||||
Max: 25
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
Metrics/ClassLength:
|
||||
Max: 150
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 8
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
Style/FormatStringToken:
|
||||
Enabled: false
|
||||
@@ -1 +1 @@
|
||||
wpscan
|
||||
wpscanv3
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.2.1
|
||||
2.6.2
|
||||
|
||||
10
.simplecov
10
.simplecov
@@ -1,8 +1,4 @@
|
||||
SimpleCov.start do
|
||||
add_filter "/spec/"
|
||||
add_filter "_helper.rb"
|
||||
add_filter "environment.rb"
|
||||
add_filter "_plugin.rb"
|
||||
add_filter "hacks.rb"
|
||||
add_filter "output.rb"
|
||||
end
|
||||
add_filter '/spec/'
|
||||
add_filter 'helper'
|
||||
end
|
||||
45
.travis.yml
45
.travis.yml
@@ -1,20 +1,33 @@
|
||||
language: ruby
|
||||
sudo: false
|
||||
cache: bundler
|
||||
rvm:
|
||||
- 1.9.2
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
- 2.1.0
|
||||
- 2.1.1
|
||||
- 2.1.2
|
||||
- 2.1.3
|
||||
- 2.1.4
|
||||
- 2.1.5
|
||||
- 2.2.0
|
||||
- 2.2.1
|
||||
script: bundle exec rspec
|
||||
notifications:
|
||||
email:
|
||||
- wpscanteam@gmail.com
|
||||
- 2.4.1
|
||||
- 2.4.2
|
||||
- 2.4.3
|
||||
- 2.4.4
|
||||
- 2.4.5
|
||||
- 2.4.6
|
||||
- 2.5.0
|
||||
- 2.5.1
|
||||
- 2.5.2
|
||||
- 2.5.3
|
||||
- 2.5.4
|
||||
- 2.5.5
|
||||
- 2.6.0
|
||||
- 2.6.1
|
||||
- 2.6.2
|
||||
- 2.6.3
|
||||
- ruby-head
|
||||
before_install:
|
||||
- "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc"
|
||||
- gem update --system
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: 1.9.2
|
||||
- rvm: ruby-head
|
||||
script:
|
||||
- bundle exec rubocop
|
||||
- bundle exec rspec
|
||||
notifications:
|
||||
email:
|
||||
- team@wpscan.org
|
||||
|
||||
413
CHANGELOG.md
413
CHANGELOG.md
@@ -1,413 +0,0 @@
|
||||
# Changelog
|
||||
## Master
|
||||
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.7...master)
|
||||
|
||||
## Version 2.7
|
||||
Released: 2015-03-16
|
||||
|
||||
New
|
||||
* Detects version in release date format
|
||||
* Copyrights updated
|
||||
* WP version detection from stylesheets
|
||||
* New license
|
||||
* Global HTTP request counter
|
||||
* Add security-protection plugin detection
|
||||
* Add GHOST warning if XMLRPC enabled
|
||||
* Update databases from wpvulndb.com
|
||||
* Enumerate usernames from WP <= 3.0 (thanks berotti3)
|
||||
|
||||
Removed
|
||||
* README.txt
|
||||
|
||||
General core
|
||||
* Update to Ruby 2.2.1
|
||||
* Update to Ruby 2.2.0
|
||||
* Add addressable gem
|
||||
* Update Typhoeus gem to 0.7.0
|
||||
* IDN support: encode non-ascii domain names (thanks dctabuyz)
|
||||
* Improve page hash calculation (thanks dctabuyz)
|
||||
* Version detection regex improved
|
||||
|
||||
Fixed issues
|
||||
* Fix #745 - Plugin version pattern in readme.txt file not detected
|
||||
* Fix #746 - Add a global counter for all active requests to server.
|
||||
* Fix #747 - Add 'security-protection' plugin to wp_login_protection module
|
||||
* Fix #753 - undefined method `round' for "10":String for request or connect timeouts
|
||||
* Fix #760 - typhoeus issue (infinite loop)
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 89
|
||||
* Total vulnerable plugins: 953
|
||||
* Total vulnerable themes: 329
|
||||
* Total version vulnerabilities: 1070
|
||||
* Total plugin vulnerabilities: 1451
|
||||
* Total theme vulnerabilities: 378
|
||||
|
||||
## Version 2.6
|
||||
Released: 2014-12-19
|
||||
|
||||
New
|
||||
* Updates the readmes to reflect the new --usernames option
|
||||
* Improves plugin/theme version detection by looking at the "Version:"
|
||||
* Solution to avoid mandatory blank newline at the end of the wordlist
|
||||
* Add check for valid credentials
|
||||
* Add Sucuri sponsor to banner
|
||||
* Add protocol to sucuri url in banner
|
||||
* Add response code to proxy error output
|
||||
* Add a statement about mendatory newlines at the end of list
|
||||
* Give warning if default username 'admin' is still used
|
||||
* License amendment to make it more clear about value added usage
|
||||
|
||||
Removed
|
||||
* remove malwares
|
||||
* remove malware folder
|
||||
* Removes the theme version check from the readme, unrealistic scenario
|
||||
|
||||
General core
|
||||
* Update to Ruby 2.1.5 and travis
|
||||
* Prevent parent theme infinite loop
|
||||
* Fixes the progressbar being overriden by next brute forcing attempts
|
||||
|
||||
Fixed issues
|
||||
* Fix UTF-8 encode on security db file download
|
||||
* Fix #703 - Disable logging by default. Implement log option.
|
||||
* Fix #705 - Installation instructions for Ubuntu < 14.04 apparently incomplete
|
||||
* Fix #717 - Expand on readme.html finding output
|
||||
* Fix #716 - Adds the --version in the help
|
||||
* Fix #715 - Add new updating info to docs
|
||||
* Fix #727 - WpItems detection: Perform the passive check and filter only vulnerable results at the end if required
|
||||
* Fix #737 - Adds some readme files to check for plugin versions
|
||||
* Fix #739 - Adds the --usernames option
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 88
|
||||
* Total vulnerable plugins: 901
|
||||
* Total vulnerable themes: 313
|
||||
* Total version vulnerabilities: 1050
|
||||
* Total plugin vulnerabilities: 1355
|
||||
* Total theme vulnerabilities: 349
|
||||
|
||||
## Version 2.5.1
|
||||
Released: 2014-09-29
|
||||
|
||||
Fixes reference URL to WPVDB
|
||||
|
||||
## Version 2.5
|
||||
Released: 2014-09-26 (@ BruCON 2014)
|
||||
|
||||
New
|
||||
* Exit program after --update
|
||||
* Detect directory listing in upload folder
|
||||
* Be more verbose when no version can be detected
|
||||
* Added detection for Yoast Wordpress SEO plugin
|
||||
* Also ensure to not process empty Location headers
|
||||
* Ensures a nil location is not processed when enumerating usernames
|
||||
* Fix #626 - Detect 'Must_Use_Plugins'
|
||||
* better username extraction
|
||||
* Add a --cookie option. Ref #485
|
||||
* Add a --no-color option
|
||||
* Output: Give 'Fixed in' an informational tag
|
||||
* Added ArchAssault distro - WPScan comes pre-installed with this distro
|
||||
* Layout changes with new colors
|
||||
|
||||
Removed
|
||||
* Removes the source code updaters
|
||||
* Removes the ListGenerator plugin from WPStools
|
||||
* Removes all files from data/
|
||||
|
||||
General core
|
||||
* Update docs to reflect new updating logic
|
||||
* Little output change and coloring
|
||||
* Adds a missing verbose output
|
||||
* Re-build redirection url if begin with slash '/'
|
||||
* Fixes the remove_conditional_comments function
|
||||
* Ensures to give a string to Typhoeus
|
||||
* Fix wpstools check-vuln-ref-urls
|
||||
* Fix rspecs for new json
|
||||
* Only output if different from style_url
|
||||
* Add exception so 'ruby wpscan.rb http://domain.com' is detected
|
||||
* Added make to Debian installation, which is needed in minimal installation.
|
||||
* Add build-essentials requirement to Ubuntu > 14.04
|
||||
* Updated installation instr. for GNU/Linux Debian.
|
||||
* Changes VersionCompare#is_newer_or_same? by lesser_or_equal?
|
||||
* Fixes the location of the robots.txt check
|
||||
* Updates the recommended ruby version
|
||||
* Rspec 3.0 support
|
||||
* Adds ruby 2.1.2 to Travis
|
||||
* Updated ruby-progressbar to 1.5.0
|
||||
|
||||
WordPress Fingerprints
|
||||
* Adds WP 4.0 fingerprints
|
||||
* Adds WP 3.9.2, 3.8.4 & 3.7.4 fingerprints - Ref #652
|
||||
* Adds 3.9.1 fingerprints
|
||||
|
||||
Fixed issues
|
||||
* Fix #689 - Adds config file to check
|
||||
* Fix #694 - Output Arrays
|
||||
* Fix #693 - Adds pathname require statement
|
||||
* Fix #657 - generate method
|
||||
* Fix #685 - Potenial fix for 'marshal data too short' error
|
||||
* Fix #686 - Adds specs for relative URI in Location headers
|
||||
* Fix #435 - Update license
|
||||
* Fix #674 - Improves the Plugins & Themes passive detection
|
||||
* Fix #673 - Problem with the output
|
||||
* Fix #661 - Don't hash directories named like a file
|
||||
* Fix #653 - Fix for infinite loop in wpstools
|
||||
* Fix #625 - Only parse styles when needed
|
||||
* Fix #481 - Fix for Jetpack plugin false positive
|
||||
* Fix #480 - Properly removes the colour sequence from log
|
||||
* Fix #472 - WPScan stops after redirection if not WordPress website
|
||||
* Fix #464 - Readmes updated to reflect recent changes about the config file & batch mode
|
||||
|
||||
Vulnerabilities
|
||||
* geoplaces4 also uses name GeoPlaces4beta
|
||||
* Added metasploit module's
|
||||
* Added some timthumb detections
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 87
|
||||
* Total vulnerable plugins: 854
|
||||
* Total vulnerable themes: 303
|
||||
* Total version vulnerabilities: 752
|
||||
* Total plugin vulnerabilities: 1351
|
||||
* Total theme vulnerabilities: 345
|
||||
|
||||
## Version 2.4
|
||||
Released: 2014-04-17
|
||||
|
||||
New
|
||||
* '--batch' switch option added - Fix #454
|
||||
* Add random-agent
|
||||
* Added more CLI options
|
||||
* Switch over to nist - Fix #301
|
||||
* New choice added when a redirection is detected - Fix #438
|
||||
|
||||
Removed
|
||||
* Removed 'Total WordPress Sites in the World' counter from stats
|
||||
* Old wpscan repo links removed - Fix #440
|
||||
* Fingerprinting Dev script removed
|
||||
* Useless code removed
|
||||
|
||||
General core
|
||||
* Rspecs update
|
||||
* Forcing Travis notify the team
|
||||
* Ruby 2.1.1 added to Travis
|
||||
* Equal output layout for interaction questions
|
||||
* Only output error trace if verbose if enabled
|
||||
* Memory improvements during wp-items enumerations
|
||||
* Fixed broken link checker, fixed some broken links
|
||||
* Couple more 404s fixed
|
||||
* Themes & Plugins list updated
|
||||
|
||||
WordPress Fingerprints
|
||||
* WP 3.8.2 & 3.7.2 Fingerprints added - Fix #448
|
||||
* WP 3.8.3 & 3.7.3 fingerprints
|
||||
* WP 3.9 fingerprints
|
||||
|
||||
Fixed issues
|
||||
* Fix #380 - Redirects in WP 3.6-3.0
|
||||
* Fix #413 - Check the version of the Timthumbs files found
|
||||
* Fix #429 - Error WpScan Cache Browser
|
||||
* Fix #431 - Version number comparison between '2.3.3' and '0.42b'
|
||||
* Fix #439 - Detect if the target goes down during the scan
|
||||
* Fix #451 - Do not rely only on files in wp-content for fingerprinting
|
||||
* Fix #453 - Documentation or inplemention of option parameters
|
||||
* Fix #455 - Fails with a message if the target returns a 403 during the wordpress check
|
||||
|
||||
Vulnerabilities
|
||||
* Update WordPress Vulnerabilities
|
||||
* Fixed some duplicate vulnerabilities
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 79; 1 is new
|
||||
* Total vulnerable plugins: 748; 55 are new
|
||||
* Total vulnerable themes: 292; 41 are new
|
||||
* Total version vulnerabilities: 617; 326 are new
|
||||
* Total plugin vulnerabilities: 1162; 146 are new
|
||||
* Total theme vulnerabilities: 330; 47 are new
|
||||
|
||||
## Version 2.3
|
||||
Released: 2014-02-11
|
||||
|
||||
New
|
||||
* Brute forcing over https!
|
||||
* Detect and output parent theme!
|
||||
* Complete fingerprint script & hash search
|
||||
* New spell checker!
|
||||
* Added database modification dates in status report
|
||||
* Added 'Total WordPress Sites in the World' statistics
|
||||
* Added separator between Name and Version in Item
|
||||
* Added a "Work in progress" URL in the CHANGELOG
|
||||
|
||||
Removed
|
||||
* Removed "Exiting!" sentence
|
||||
* Removed Backtrack Linux. Not maintained anymore.
|
||||
|
||||
General core
|
||||
* Ruby 2.1.0 added to Travis
|
||||
* Updated the version of WebMock required
|
||||
* Better string concatenation in code (improves speed)
|
||||
* Some modifications in the output of an item
|
||||
* Output cosmetics
|
||||
* rspec-mocks version constraint released
|
||||
* Tabs replaced by spaces
|
||||
* Rspecs update
|
||||
* Indent code cleanup
|
||||
* Themes & Plugins lists regenerated
|
||||
|
||||
Vulnerabilities
|
||||
* Update WordPress Vulnerabilities
|
||||
* Disabled some fake reported vulnerabilities
|
||||
* Fixed some duplicate vulnerabilities
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 78; 2 are new
|
||||
* Total vulnerable plugins: 693; 83 are new
|
||||
* Total vulnerable themes: 251; 55 are new
|
||||
* Total version vulnerabilities: 291 17 are new
|
||||
* Total plugin vulnerabilities: 1016; 236 are new
|
||||
* Total theme vulnerabilities: 283; 79 are new
|
||||
|
||||
WordPress Fingerprints
|
||||
* Better fingerprints
|
||||
* WP 3.8.1 Fingerprinting
|
||||
* WP 3.8 Fingerprinting
|
||||
|
||||
Fixed issues
|
||||
* Fix #404 - Brute forcing issue over https
|
||||
* Fix #398 - Removed a fake vuln in WP Super Cache
|
||||
* Fix #393 - sudo added to the bundle install cmd for Mac OSX
|
||||
* Fix #228, #327 - Infinite loop when self-redirect
|
||||
* Fix #201 - Incorrect Paramter Parsing when no url was supplied
|
||||
|
||||
## Version 2.2
|
||||
Released: 2013-11-12
|
||||
|
||||
New
|
||||
* Output the vulnerability fix if available
|
||||
* Added 'WordPress Version Vulnerability' statistics
|
||||
* Added Kali Linux on the list of pre-installed Linux distributions
|
||||
* Added hosted wordpress detection. See issue #343.
|
||||
* Add detection for all-in-one-seo-pack
|
||||
* Use less memory when brute forcing with a large wordlist
|
||||
* Memory Usage output
|
||||
* Added cve tag to xml file
|
||||
* Add documentation to readme
|
||||
* Add --version switch
|
||||
* Parse robots.txt
|
||||
* Show twitter usernames
|
||||
* Clean logfile on wpstools too
|
||||
* Added pingback header
|
||||
* Request_timeout and connect_timeout implemented
|
||||
* Output interesting http-headers
|
||||
* Kali Linux detection
|
||||
* Ensure that brute forcing results are output even if an error occurs or the user exits
|
||||
* Added debug output
|
||||
* Fixed Version compare for issue #179
|
||||
* Added ruby-progressbar version to Gemfile
|
||||
* Use the redirect_to parameter on bruteforce
|
||||
* Readded "junk removal" from usernames before output
|
||||
* Add license file
|
||||
* Output the timthumb version if found
|
||||
* New enumeration system
|
||||
* More error details for XSD checks
|
||||
* Added default wp-content dir detection, see Issue #141.
|
||||
* Added checks for well formed xml
|
||||
|
||||
Changed
|
||||
* Trying a fix for Kali Linux
|
||||
* Make a seperator between plugin name and vulnerability name
|
||||
* It's WordPress, not Wordpress
|
||||
* Changed wordpress.com scanning error to warning. See issue #343.
|
||||
* Make output lines consistent
|
||||
* Replace packetstormsecurity.org to packetstormsecurity.com
|
||||
* Same URL syntax for all Packet Storm Security URL's
|
||||
* Packet Storm Security URL's don't need the 'friendly part' of the URL. So it can be neglected.
|
||||
* Use online documentation
|
||||
* User prompt on same line
|
||||
* Don't skip passwords that start with a hash. This is fairly common (see RockYou list for example).
|
||||
* Updated Fedora install instructions as per Issue #92
|
||||
* Slight update to security plugin warning. Issue #212.
|
||||
* Ruby-progressbar Gemfile version bump
|
||||
* Fix error with the -U option (undefined method 'merge' for #WpTarget:)
|
||||
* Banner artwork
|
||||
* Fix hacks.rb conflict
|
||||
* Handle when there are 2 headers of the same name
|
||||
* Releasing the Typhoeus version constraint
|
||||
* Amended Arch Linux install instructions. See issue #183.
|
||||
|
||||
Updated
|
||||
* Plugins & Themes updated
|
||||
* Update README.md
|
||||
* Updated documentation
|
||||
|
||||
Removed
|
||||
* Removed 'smileys' in output messages
|
||||
* Removed 'for WordPress' and 'plugin' in title strings.
|
||||
* Removed reference
|
||||
* Removed useless code
|
||||
* Removed duplicate vulnerabilities
|
||||
|
||||
General core
|
||||
* Code cleaning
|
||||
* Fix typo's
|
||||
* Clean up rspecs
|
||||
* Themes & Plugins lists regenerated
|
||||
* Rspecs update
|
||||
* Code Factoring
|
||||
* Added checks for old ruby. Otherwise there will be syntax errors
|
||||
|
||||
Vulnerabilities
|
||||
* Update WordPress Vulnerabilities
|
||||
* Update timthumb due to Secunia #54801
|
||||
* Added WP vuln: 3.4 - 3.5.1 wp-admin/users.php FPD
|
||||
|
||||
WPScan Database Statistics:
|
||||
* Total vulnerable versions: 76; 4 are new
|
||||
* Total vulnerable plugins: 610; 201 are new
|
||||
* Total vulnerable themes: 196; 47 are new
|
||||
* Total version vulnerabilities: 274; 53 are new
|
||||
* Total plugin vulnerabilities: 780; 286 are new
|
||||
* Total theme vulnerabilities: 204; 52 are new
|
||||
|
||||
Add WP Fingerprints
|
||||
* WP 3.7.1 Fingerprinting
|
||||
* WP 3.7 Fingerprinting
|
||||
* Ref #280 WP 3.6.1 fingerprint
|
||||
* Added WP 3.6 advanced fingerprint hash. See Issue #255.
|
||||
* Updated MD5 hash of WP 3.6 detection. See Issue #277.
|
||||
* WP 3.5.2 Fingerprint
|
||||
* Bug Fix : Wp 3.5 & 3.5.1 not detected from advanced fingerprinting.
|
||||
|
||||
Fixed issues
|
||||
* Fix #249 - [ERROR] "\xF1" on US-ASCII
|
||||
* Fix #275 - [ERROR] "\xC3" on US-ASCII
|
||||
* Fix #271 - Further Instructions added to the Mac Install
|
||||
* Fix #266 - passive detection regex
|
||||
* Fix #265 - remove base64 images before passive detection
|
||||
* Fix #262 - [ERROR] bad component(expected absolute path component)
|
||||
* Fix #260 - Fixes Travis Fail, due to rspec-mock v2.14.3
|
||||
* Fix #208 - Fixed vulnerable plugins still appear in the results
|
||||
* Fix #245 - all theme enumeration error
|
||||
* Fix #241 - Cant convert array to string
|
||||
* Fix #232 - Crash while enumerating usernames
|
||||
* Fix #223 - New wordpress urls for most popular plugins & themes
|
||||
* Fix #177 - Passive Cache plugins detection (no spec)
|
||||
* Fix #169 - False reports
|
||||
* Fix #182 - Remove the progress-bar static length (120), and let it to automatic
|
||||
* Fix #181 - Don't exit if no usernames found during a simple enumeration (but exit if a brute force is asked)
|
||||
* Fix #200 - Log file not recording the list of username retireved
|
||||
* Fix #164 - README.txt detection
|
||||
* Fix #166 - ListGenerator using the old Browser#get method for full generation
|
||||
* Fix #153 - Disable error trace when it's from the main script
|
||||
* Fix #163 - in the proper way
|
||||
* Fix #144 - Use cookie jar to prevent infinite redirections loop
|
||||
* Fix #158 - Add the solution to 'no such file to load -- rubygems' in the README
|
||||
* Fix #152 - invalid ssl_certificate - response code 0
|
||||
* Fix #147 - can't modify frozen string
|
||||
* Fix #140 - xml_rpc_url in the body
|
||||
* Fix #153 - No error trace when 'No argument supplied'
|
||||
|
||||
## Version 2.1
|
||||
Released 2013-3-4
|
||||
|
||||
21
CREDITS
21
CREDITS
@@ -1,21 +0,0 @@
|
||||
**CREDITS**
|
||||
|
||||
This file is used to state the individual WPScan Team members (core developers) and give credit to WPScan's other contributors. If you feel your name should be in here email wpscanteam@gmail.com.
|
||||
|
||||
*WPScan Team*
|
||||
|
||||
Erwan.LR - @erwan_lr - (Project Developer)
|
||||
Christian Mehlmauer - @_FireFart_ - (Project Developer)
|
||||
Peter van der Laan - pvdl - (Project Developer)
|
||||
Ryan Dewhurst - @ethicalhack3r (Project Lead)
|
||||
|
||||
*Other Contributors*
|
||||
|
||||
Henri Salo AKA fgeek - Reported lots of vulnerabilities
|
||||
Alip AKA Undead - alip.aswalid at gmail.com
|
||||
michee08 - Reported and gave potential solutions to bugs
|
||||
Callum Pember - Implemented proxy support - callumpember at gmail.com
|
||||
g0tmi1k - Additional timthumb checks + bug reports
|
||||
Melvin Lammerts - Reported a couple of fake vulnerabilities - melvin at 12k.nl
|
||||
Paolo Perego - @thesp0nge - Basic authentication
|
||||
Gianluca Brindisi - @gbrindisi - Ex Project Developer
|
||||
@@ -1,2 +0,0 @@
|
||||
WPScan is not responsible for misuse or for any damage that you may cause!
|
||||
You agree that you use this software at your own risk.
|
||||
41
Dockerfile
Normal file
41
Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
||||
FROM ruby:2.6.2-alpine3.9 AS builder
|
||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||
|
||||
ARG BUNDLER_ARGS="--jobs=8 --without test development"
|
||||
|
||||
RUN echo "gem: --no-ri --no-rdoc" > /etc/gemrc
|
||||
|
||||
COPY . /wpscan
|
||||
|
||||
RUN apk add --no-cache git libcurl ruby-dev libffi-dev make gcc musl-dev zlib-dev procps sqlite-dev && \
|
||||
bundle install --system --clean --no-cache --gemfile=/wpscan/Gemfile $BUNDLER_ARGS && \
|
||||
# temp fix for https://github.com/bundler/bundler/issues/6680
|
||||
rm -rf /usr/local/bundle/cache
|
||||
|
||||
WORKDIR /wpscan
|
||||
RUN rake install --trace
|
||||
|
||||
# needed so non superusers can read gems
|
||||
RUN chmod -R a+r /usr/local/bundle
|
||||
|
||||
|
||||
FROM ruby:2.6.2-alpine3.9
|
||||
LABEL maintainer="WPScan Team <team@wpscan.org>"
|
||||
|
||||
RUN adduser -h /wpscan -g WPScan -D wpscan
|
||||
|
||||
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
||||
|
||||
RUN chown -R wpscan:wpscan /wpscan
|
||||
|
||||
# runtime dependencies
|
||||
RUN apk add --no-cache libcurl procps sqlite-libs
|
||||
|
||||
WORKDIR /wpscan
|
||||
|
||||
USER wpscan
|
||||
|
||||
RUN /usr/local/bundle/bin/wpscan --update --verbose
|
||||
|
||||
ENTRYPOINT ["/usr/local/bundle/bin/wpscan"]
|
||||
CMD ["--help"]
|
||||
17
Gemfile
17
Gemfile
@@ -1,15 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
gemspec
|
||||
|
||||
gem 'typhoeus', '~>0.7.0'
|
||||
gem 'nokogiri'
|
||||
gem 'addressable'
|
||||
gem 'json'
|
||||
gem 'terminal-table'
|
||||
gem 'ruby-progressbar', '>=1.6.0'
|
||||
|
||||
group :test do
|
||||
gem 'webmock', '>=1.17.2'
|
||||
gem 'simplecov'
|
||||
gem 'rspec', '>=3.0'
|
||||
gem 'rspec-its'
|
||||
end
|
||||
# gem 'cms_scanner', branch: 'xxx', git: 'https://github.com/wpscanteam/CMSScanner.git'
|
||||
|
||||
16
LICENSE
16
LICENSE
@@ -1,14 +1,14 @@
|
||||
WPScan Public Source License
|
||||
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2015 WPScan Team.
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2019 WPScan Team.
|
||||
|
||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1 “License” means this document.
|
||||
1.2 “Contributor” means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
|
||||
1.3 “WPScan Team” means WPScan’s core developers, an updated list of whom can be found within the CREDITS file.
|
||||
1.1 "License" means this document.
|
||||
1.2 "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
|
||||
1.3 "WPScan Team" means WPScan’s core developers.
|
||||
|
||||
2. Commercialization
|
||||
|
||||
@@ -27,7 +27,7 @@ Example cases which do not require a commercial license, and thus fall under the
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - wpscanteam@gmail.com.
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||
|
||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
||||
|
||||
@@ -59,7 +59,7 @@ WPScan is provided under an AS-IS basis and without any support, updates or main
|
||||
|
||||
8. Disclaimer of Warranty
|
||||
|
||||
WPScan is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
WPScan is provided under this License on an "as is" basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
|
||||
9. Limitation of Liability
|
||||
|
||||
@@ -68,3 +68,7 @@ To the extent permitted under Law, WPScan is provided under an AS-IS basis. The
|
||||
10. Disclaimer
|
||||
|
||||
Running WPScan against websites without prior mutual consent may be illegal in your country. The WPScan Team accept no liability and are not responsible for any misuse or damage caused by WPScan.
|
||||
|
||||
11. Trademark
|
||||
|
||||
The "wpscan" term is a registered trademark. This License does not grant the use of the "wpscan" trademark or the use of the WPScan logo.
|
||||
|
||||
450
README.md
450
README.md
@@ -1,350 +1,204 @@
|
||||

|
||||
<p align="center">
|
||||
<a href="https://wpscan.org/">
|
||||
<img src="https://raw.githubusercontent.com/wpscanteam/wpscan/gh-pages/images/wpscan_logo.png" alt="WPScan logo">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h3 align="center">WPScan</h3>
|
||||
|
||||
[](https://travis-ci.org/wpscanteam/wpscan)
|
||||
[](https://codeclimate.com/github/wpscanteam/wpscan)
|
||||
[](https://gemnasium.com/wpscanteam/wpscan)
|
||||
<p align="center">
|
||||
WordPress Vulnerability Scanner
|
||||
<br>
|
||||
<br>
|
||||
<a href="https://wpscan.org/" title="homepage" target="_blank">Homepage</a> - <a href="https://wpscan.io/" title="wpscan.io" target="_blank">WPScan.io</a> - <a href="https://wpvulndb.com/" title="vulnerability database" target="_blank">Vulnerability Database</a> - <a href="https://wordpress.org/plugins/wpscan/" title="wordpress plugin" target="_blank">WordPress Plugin</a>
|
||||
</p>
|
||||
|
||||
#### LICENSE
|
||||
<p align="center">
|
||||
<a href="https://badge.fury.io/rb/wpscan" target="_blank"><img src="https://badge.fury.io/rb/wpscan.svg"></a>
|
||||
<a href="https://travis-ci.org/wpscanteam/wpscan" target="_blank"><img src="https://travis-ci.org/wpscanteam/wpscan.svg?branch=master"></a>
|
||||
<a href="https://codeclimate.com/github/wpscanteam/wpscan" target="_blank"><img src="https://codeclimate.com/github/wpscanteam/wpscan/badges/gpa.svg"></a>
|
||||
<a href="https://www.patreon.com/wpscan" target="_blank"><img src="https://img.shields.io/badge/patreon-donate-green.svg"></a>
|
||||
</p>
|
||||
|
||||
#### WPScan Public Source License
|
||||
# INSTALL
|
||||
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2015 WPScan Team.
|
||||
## Prerequisites
|
||||
|
||||
- (Optional but highly recommended: [RVM](https://rvm.io/rvm/install))
|
||||
- Ruby >= 2.3 - Recommended: latest
|
||||
- Ruby 2.5.0 to 2.5.3 can cause an 'undefined symbol: rmpd_util_str_to_d' error in some systems, see [#1283](https://github.com/wpscanteam/wpscan/issues/1283)
|
||||
- Curl >= 7.21 - Recommended: latest
|
||||
- The 7.29 has a segfault
|
||||
- RubyGems - Recommended: latest
|
||||
- Nokogiri might require packages to be installed via your package manager depending on your OS, see https://nokogiri.org/tutorials/installing_nokogiri.html
|
||||
|
||||
### From RubyGems (Recommended)
|
||||
|
||||
```shell
|
||||
gem install wpscan
|
||||
```
|
||||
|
||||
On MacOSX, if a ```Gem::FilePermissionError``` is raised due to the Apple's System Integrity Protection (SIP), either install RVM and install wpscan again, or run ```sudo gem install -n /usr/local/bin wpscan``` (see [#1286](https://github.com/wpscanteam/wpscan/issues/1286))
|
||||
|
||||
### From sources (NOT Recommended)
|
||||
|
||||
Prerequisites: Git
|
||||
|
||||
```shell
|
||||
git clone https://github.com/wpscanteam/wpscan
|
||||
|
||||
cd wpscan/
|
||||
|
||||
bundle install && rake install
|
||||
```
|
||||
|
||||
# Updating
|
||||
|
||||
You can update the local database by using ```wpscan --update```
|
||||
|
||||
Updating WPScan itself is either done via ```gem update wpscan``` or the packages manager (this is quite important for distributions such as in Kali Linux: ```apt-get update && apt-get upgrade```) depending how WPScan was (pre)installed
|
||||
|
||||
# Docker
|
||||
|
||||
Pull the repo with ```docker pull wpscanteam/wpscan```
|
||||
|
||||
Enumerating usernames
|
||||
|
||||
```shell
|
||||
docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u
|
||||
```
|
||||
|
||||
Enumerating a range of usernames
|
||||
|
||||
```shell
|
||||
docker run -it --rm wpscanteam/wpscan --url https://target.tld/ --enumerate u1-100
|
||||
```
|
||||
|
||||
** replace u1-100 with a range of your choice.
|
||||
|
||||
# Usage
|
||||
|
||||
```wpscan --url blog.tld``` This will scan the blog using default options with a good compromise between speed and accuracy. For example, the plugins will be checked passively but their version with a mixed detection mode (passively + aggressively). Potential config backup files will also be checked, along with other interesting findings. If a more stealthy approach is required, then ```wpscan --stealthy --url blog.tld``` can be used.
|
||||
As a result, when using the ```--enumerate``` option, don't forget to set the ```--plugins-detection``` accordingly, as its default is 'passive'.
|
||||
|
||||
For more options, open a terminal and type ```wpscan --help``` (if you built wpscan from the source, you should type the command outside of the git repo)
|
||||
|
||||
The DB is located at ~/.wpscan/db
|
||||
|
||||
WPScan can load all options (including the --url) from configuration files, the following locations are checked (order: first to last):
|
||||
|
||||
- ~/.wpscan/cli_options.json
|
||||
- ~/.wpscan/cli_options.yml
|
||||
- pwd/.wpscan/cli_options.json
|
||||
- pwd/.wpscan/cli_options.yml
|
||||
|
||||
If those files exist, options from them will be loaded and overridden if found twice.
|
||||
|
||||
e.g:
|
||||
|
||||
~/.wpscan/cli_options.yml:
|
||||
|
||||
```yml
|
||||
proxy: 'http://127.0.0.1:8080'
|
||||
verbose: true
|
||||
```
|
||||
|
||||
pwd/.wpscan/cli_options.yml:
|
||||
|
||||
```yml
|
||||
proxy: 'socks5://127.0.0.1:9090'
|
||||
url: 'http://target.tld'
|
||||
```
|
||||
|
||||
Running ```wpscan``` in the current directory (pwd), is the same as ```wpscan -v --proxy socks5://127.0.0.1:9090 --url http://target.tld```
|
||||
|
||||
Enumerating usernames
|
||||
|
||||
```shell
|
||||
wpscan --url https://target.tld/ --enumerate u
|
||||
```
|
||||
|
||||
Enumerating a range of usernames
|
||||
|
||||
```shell
|
||||
wpscan --url https://target.tld/ --enumerate u1-100
|
||||
```
|
||||
|
||||
** replace u1-100 with a range of your choice.
|
||||
|
||||
# LICENSE
|
||||
|
||||
## WPScan Public Source License
|
||||
|
||||
The WPScan software (henceforth referred to simply as "WPScan") is dual-licensed - Copyright 2011-2019 WPScan Team.
|
||||
|
||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, WPScan can be used without charge under the terms set out below.
|
||||
|
||||
##### 1. Definitions
|
||||
### 1. Definitions
|
||||
|
||||
1.1 "License" means this document.
|
||||
|
||||
1.2 "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns WPScan.
|
||||
|
||||
1.3 "WPScan Team" means WPScan’s core developers, an updated list of whom can be found within the CREDITS file.
|
||||
1.3 "WPScan Team" means WPScan’s core developers.
|
||||
|
||||
##### 2. Commercialization
|
||||
### 2. Commercialization
|
||||
|
||||
A commercial use is one intended for commercial advantage or monetary compensation.
|
||||
|
||||
Example cases of commercialization are:
|
||||
|
||||
- Using WPScan to provide commercial managed/Software-as-a-Service services.
|
||||
- Distributing WPScan as a commercial product or as part of one.
|
||||
- Using WPScan as a value added service/product.
|
||||
- Using WPScan to provide commercial managed/Software-as-a-Service services.
|
||||
- Distributing WPScan as a commercial product or as part of one.
|
||||
- Using WPScan as a value added service/product.
|
||||
|
||||
Example cases which do not require a commercial license, and thus fall under the terms set out below, include (but are not limited to):
|
||||
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit.
|
||||
- Penetration Testing Linux Distributions including but not limited to Kali Linux, SamuraiWTF, BackBox Linux.
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit.
|
||||
- Penetration Testing Linux Distributions including but not limited to Kali Linux, SamuraiWTF, BackBox Linux.
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - wpscanteam@gmail.com.
|
||||
|
||||
We may grant commercial licenses at no monetary cost at our own discretion if the commercial usage is deemed by the WPScan Team to significantly benefit WPScan.
|
||||
If you need to purchase a commercial license or are unsure whether you need to purchase a commercial license contact us - team@wpscan.org.
|
||||
|
||||
Free-use Terms and Conditions;
|
||||
|
||||
##### 3. Redistribution
|
||||
### 3. Redistribution
|
||||
|
||||
Redistribution is permitted under the following conditions:
|
||||
|
||||
- Unmodified License is provided with WPScan.
|
||||
- Unmodified Copyright notices are provided with WPScan.
|
||||
- Does not conflict with the commercialization clause.
|
||||
- Unmodified License is provided with WPScan.
|
||||
- Unmodified Copyright notices are provided with WPScan.
|
||||
- Does not conflict with the commercialization clause.
|
||||
|
||||
##### 4. Copying
|
||||
### 4. Copying
|
||||
|
||||
Copying is permitted so long as it does not conflict with the Redistribution clause.
|
||||
|
||||
##### 5. Modification
|
||||
### 5. Modification
|
||||
|
||||
Modification is permitted so long as it does not conflict with the Redistribution clause.
|
||||
|
||||
##### 6. Contributions
|
||||
### 6. Contributions
|
||||
|
||||
Any Contributions assume the Contributor grants the WPScan Team the unlimited, non-exclusive right to reuse, modify and relicense the Contributor's content.
|
||||
|
||||
##### 7. Support
|
||||
### 7. Support
|
||||
|
||||
WPScan is provided under an AS-IS basis and without any support, updates or maintenance. Support, updates and maintenance may be given according to the sole discretion of the WPScan Team.
|
||||
|
||||
##### 8. Disclaimer of Warranty
|
||||
### 8. Disclaimer of Warranty
|
||||
|
||||
WPScan is provided under this License on an “as is” basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the WPScan is free of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
|
||||
##### 9. Limitation of Liability
|
||||
### 9. Limitation of Liability
|
||||
|
||||
To the extent permitted under Law, WPScan is provided under an AS-IS basis. The WPScan Team shall never, and without any limit, be liable for any damage, cost, expense or any other payment incurred as a result of WPScan's actions, failure, bugs and/or any other interaction between WPScan and end-equipment, computers, other software or any 3rd party, end-equipment, computer or services.
|
||||
|
||||
##### 10. Disclaimer
|
||||
### 10. Disclaimer
|
||||
|
||||
Running WPScan against websites without prior mutual consent may be illegal in your country. The WPScan Team accept no liability and are not responsible for any misuse or damage caused by WPScan.
|
||||
|
||||
#### INSTALL
|
||||
### 11. Trademark
|
||||
|
||||
WPScan comes pre-installed on the following Linux distributions:
|
||||
|
||||
- [BackBox Linux](http://www.backbox.org/)
|
||||
- [Kali Linux](http://www.kali.org/)
|
||||
- [Pentoo](http://www.pentoo.ch/)
|
||||
- [SamuraiWTF](http://samurai.inguardians.com/)
|
||||
- [ArchAssault](https://archassault.org/)
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Ruby >= 1.9.2 - Recommended: 2.2.1
|
||||
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
|
||||
- RubyGems - Recommended: latest
|
||||
- Git
|
||||
|
||||
Windows is not supported.
|
||||
If installed from Github update the code base with ```git pull```. The databases are updated with ```wpscan.rb --update```.
|
||||
|
||||
####Installing on Ubuntu:
|
||||
|
||||
Before Ubuntu 14.04:
|
||||
|
||||
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
From Ubuntu 14.04:
|
||||
|
||||
sudo apt-get install libcurl4-gnutls-dev libxml2 libxml2-dev libxslt1-dev ruby-dev build-essential
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
####Installing on Debian:
|
||||
|
||||
sudo apt-get install git ruby ruby-dev libcurl4-gnutls-dev make
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler
|
||||
bundle install --without test --path vendor/bundle
|
||||
|
||||
####Installing on Fedora:
|
||||
|
||||
sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel patch
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
####Installing on Archlinux:
|
||||
|
||||
pacman -Syu ruby
|
||||
pacman -Syu libyaml
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
gem install typhoeus
|
||||
gem install nokogiri
|
||||
|
||||
####Installing on Mac OSX:
|
||||
|
||||
Apple Xcode, Command Line Tools and the libffi are needed (to be able to install the FFI gem), See [http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error](http://stackoverflow.com/questions/17775115/cant-setup-ruby-environment-installing-fii-gem-error)
|
||||
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && sudo bundle install --without test
|
||||
|
||||
####Installing with RVM:
|
||||
|
||||
cd ~
|
||||
curl -sSL https://get.rvm.io | bash -s stable
|
||||
source ~/.rvm/scripts/rvm
|
||||
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
|
||||
rvm install 2.2.1
|
||||
rvm use 2.2.1 --default
|
||||
echo "gem: --no-ri --no-rdoc" > ~/.gemrc
|
||||
gem install bundler
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
bundle install --without test
|
||||
|
||||
#### KNOWN ISSUES
|
||||
|
||||
- Typhoeus segmentation fault
|
||||
|
||||
Update cURL to version => 7.21 (may have to install from source)
|
||||
|
||||
- Proxy not working
|
||||
|
||||
Update cURL to version => 7.21.7 (may have to install from source).
|
||||
|
||||
Installation from sources :
|
||||
|
||||
Grab the sources from http://curl.haxx.se/download.html
|
||||
Decompress the archive
|
||||
Open the folder with the extracted files
|
||||
Run ./configure
|
||||
Run make
|
||||
Run sudo make install
|
||||
Run sudo ldconfig
|
||||
|
||||
|
||||
- cannot load such file -- readline:
|
||||
|
||||
sudo aptitude install libreadline5-dev libncurses5-dev
|
||||
|
||||
Then, open the directory of the readline gem (you have to locate it)
|
||||
|
||||
cd ~/.rvm/src/ruby-1.9.2-p180/ext/readline
|
||||
ruby extconf.rb
|
||||
make
|
||||
make install
|
||||
|
||||
|
||||
See [http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/](http://vvv.tobiassjosten.net/ruby-on-rails/fixing-readline-for-the-ruby-on-rails-console/) for more details
|
||||
|
||||
- no such file to load -- rubygems
|
||||
|
||||
```update-alternatives --config ruby```
|
||||
|
||||
And select your ruby version
|
||||
|
||||
See [https://github.com/wpscanteam/wpscan/issues/148](https://github.com/wpscanteam/wpscan/issues/148)
|
||||
|
||||
#### WPSCAN ARGUMENTS
|
||||
|
||||
--update Update the databases.
|
||||
|
||||
--url | -u <target url> The WordPress URL/domain to scan.
|
||||
|
||||
--force | -f Forces WPScan to not check if the remote site is running WordPress.
|
||||
|
||||
--enumerate | -e [option(s)] Enumeration.
|
||||
option :
|
||||
u usernames from id 1 to 10
|
||||
u[10-20] usernames from id 10 to 20 (you must write [] chars)
|
||||
p plugins
|
||||
vp only vulnerable plugins
|
||||
ap all plugins (can take a long time)
|
||||
tt timthumbs
|
||||
t themes
|
||||
vt only vulnerable themes
|
||||
at all themes (can take a long time)
|
||||
Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins
|
||||
If no option is supplied, the default is "vt,tt,u,vp"
|
||||
|
||||
--exclude-content-based "<regexp or string>" Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied
|
||||
You do not need to provide the regexp delimiters, but you must write the quotes (simple or double)
|
||||
|
||||
--config-file | -c <config file> Use the specified config file, see the example.conf.json
|
||||
|
||||
--user-agent | -a <User-Agent> Use the specified User-Agent
|
||||
|
||||
--random-agent | -r Use a random User-Agent
|
||||
|
||||
--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not
|
||||
|
||||
--wp-content-dir <wp content dir> WPScan try to find the content directory (ie wp-content) by scanning the index page, however you can specified it. Subdirectories are allowed
|
||||
|
||||
--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory. If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed
|
||||
|
||||
--proxy <[protocol://]host:port> Supply a proxy (will override the one from conf/browser.conf.json).
|
||||
HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported. If no protocol is given (format host:port), HTTP will be used
|
||||
|
||||
--proxy-auth <username:password> Supply the proxy login credentials.
|
||||
|
||||
--basic-auth <username:password> Set the HTTP Basic authentication.
|
||||
|
||||
--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.
|
||||
|
||||
--threads | -t <number of threads> The number of threads to use when multi-threading requests.
|
||||
|
||||
--username | -U <username> Only brute force the supplied username.
|
||||
|
||||
--usernames <path-to-file> Only brute force the usernames from the file.
|
||||
|
||||
--cache-ttl <cache-ttl> Typhoeus cache TTL.
|
||||
|
||||
--request-timeout <request-timeout> Request Timeout.
|
||||
|
||||
--connect-timeout <connect-timeout> Connect Timeout.
|
||||
|
||||
--max-threads <max-threads> Maximum Threads.
|
||||
|
||||
--help | -h This help screen.
|
||||
|
||||
--verbose | -v Verbose output.
|
||||
|
||||
--batch Never ask for user input, use the default behavior.
|
||||
|
||||
--no-color Do not use colors in the output.
|
||||
|
||||
--log Save STDOUT to log.txt
|
||||
|
||||
#### WPSCAN EXAMPLES
|
||||
|
||||
Do 'non-intrusive' checks...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com```
|
||||
|
||||
Do wordlist password brute force on enumerated users using 50 threads...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --threads 50```
|
||||
|
||||
Do wordlist password brute force on the 'admin' username only...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com --wordlist darkc0de.lst --username admin```
|
||||
|
||||
Enumerate installed plugins...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com --enumerate p```
|
||||
|
||||
Run all enumeration tools...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com --enumerate```
|
||||
|
||||
Use custom content directory...
|
||||
|
||||
```ruby wpscan.rb -u www.example.com --wp-content-dir custom-content```
|
||||
|
||||
Update WPScan's databases...
|
||||
|
||||
```ruby wpscan.rb --update```
|
||||
|
||||
Debug output...
|
||||
|
||||
```ruby wpscan.rb --url www.example.com --debug-output 2>debug.log```
|
||||
|
||||
#### WPSTOOLS ARGUMENTS
|
||||
|
||||
-v, --verbose Verbose output
|
||||
--check-vuln-ref-urls, --cvru Check all the vulnerabilities reference urls for 404
|
||||
--check-local-vulnerable-files, --clvf LOCAL_DIRECTORY Perform a recursive scan in the LOCAL_DIRECTORY to find vulnerable files or shells
|
||||
-s, --stats Show WpScan Database statistics.
|
||||
--spellcheck, --sc Check all files for common spelling mistakes.
|
||||
|
||||
|
||||
#### WPSTOOLS EXAMPLES
|
||||
|
||||
Locally scan a wordpress installation for vulnerable files or shells:
|
||||
|
||||
```ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/```
|
||||
|
||||
#### PROJECT HOME
|
||||
|
||||
[http://www.wpscan.org](http://www.wpscan.org)
|
||||
|
||||
#### VULNERABILITY DATABASE
|
||||
|
||||
[https://www.wpvulndb.com](https://www.wpvulndb.com)
|
||||
|
||||
#### GIT REPOSITORY
|
||||
|
||||
[https://github.com/wpscanteam/wpscan](https://github.com/wpscanteam/wpscan)
|
||||
|
||||
#### ISSUES
|
||||
|
||||
[https://github.com/wpscanteam/wpscan/issues](https://github.com/wpscanteam/wpscan/issues)
|
||||
|
||||
#### DEVELOPER DOCUMENTATION
|
||||
|
||||
[http://rdoc.info/github/wpscanteam/wpscan/frames](http://rdoc.info/github/wpscanteam/wpscan/frames)
|
||||
|
||||
#### SPECIAL THANKS
|
||||
|
||||
[RandomStorm](https://www.randomstorm.com)
|
||||
The "wpscan" term is a registered trademark. This License does not grant the use of the "wpscan" trademark or the use of the WPScan logo.
|
||||
|
||||
24
Rakefile
Normal file
24
Rakefile
Normal file
@@ -0,0 +1,24 @@
|
||||
# rubocop:disable all
|
||||
|
||||
require 'bundler/gem_tasks'
|
||||
|
||||
exec = []
|
||||
|
||||
begin
|
||||
require 'rubocop/rake_task'
|
||||
RuboCop::RakeTask.new
|
||||
exec << :rubocop
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
begin
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
exec << :spec
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
# Run rubocop & rspec before the build (only if installed)
|
||||
task build: exec
|
||||
|
||||
# rubocop:enable all
|
||||
5
app/app.rb
Normal file
5
app/app.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'models'
|
||||
require_relative 'finders'
|
||||
require_relative 'controllers'
|
||||
9
app/controllers.rb
Normal file
9
app/controllers.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'controllers/core'
|
||||
require_relative 'controllers/custom_directories'
|
||||
require_relative 'controllers/wp_version'
|
||||
require_relative 'controllers/main_theme'
|
||||
require_relative 'controllers/enumeration'
|
||||
require_relative 'controllers/password_attack'
|
||||
require_relative 'controllers/aliases'
|
||||
15
app/controllers/aliases.rb
Normal file
15
app/controllers/aliases.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Controller to add the aliases in the CLI
|
||||
class Aliases < CMSScanner::Controller::Base
|
||||
def cli_options
|
||||
[
|
||||
OptAlias.new(['--stealthy'],
|
||||
alias_for: '--random-user-agent --detection-mode passive --plugins-version-detection passive')
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
109
app/controllers/core.rb
Normal file
109
app/controllers/core.rb
Normal file
@@ -0,0 +1,109 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Specific Core controller to include WordPress checks
|
||||
class Core < CMSScanner::Controller::Core
|
||||
# @return [ Array<OptParseValidator::Opt> ]
|
||||
def cli_options
|
||||
[OptURL.new(['--url URL', 'The URL of the blog to scan'],
|
||||
required_unless: %i[update help hh version], default_protocol: 'http')] +
|
||||
super.drop(1) + # delete the --url from CMSScanner
|
||||
[
|
||||
OptChoice.new(['--server SERVER', 'Force the supplied server module to be loaded'],
|
||||
choices: %w[apache iis nginx],
|
||||
normalize: %i[downcase to_sym],
|
||||
advanced: true),
|
||||
OptBoolean.new(['--force', 'Do not check if the target is running WordPress']),
|
||||
OptBoolean.new(['--[no-]update', 'Whether or not to update the Database'])
|
||||
]
|
||||
end
|
||||
|
||||
# @return [ DB::Updater ]
|
||||
def local_db
|
||||
@local_db ||= DB::Updater.new(DB_DIR)
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
def update_db_required?
|
||||
if local_db.missing_files?
|
||||
raise Error::MissingDatabaseFile if ParsedCli.update == false
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return ParsedCli.update unless ParsedCli.update.nil?
|
||||
|
||||
return false unless user_interaction? && local_db.outdated?
|
||||
|
||||
output('@notice', msg: 'It seems like you have not updated the database for some time.')
|
||||
print '[?] Do you want to update now? [Y]es [N]o, default: [N]'
|
||||
|
||||
/^y/i.match?(Readline.readline) ? true : false
|
||||
end
|
||||
|
||||
def update_db
|
||||
output('db_update_started')
|
||||
output('db_update_finished', updated: local_db.update, verbose: ParsedCli.verbose)
|
||||
|
||||
exit(0) unless ParsedCli.url
|
||||
end
|
||||
|
||||
def before_scan
|
||||
@last_update = local_db.last_update
|
||||
|
||||
maybe_output_banner_help_and_version # From CMSScanner
|
||||
|
||||
update_db if update_db_required?
|
||||
setup_cache
|
||||
check_target_availability
|
||||
load_server_module
|
||||
check_wordpress_state
|
||||
rescue Error::NotWordPress => e
|
||||
target.maybe_add_cookies
|
||||
raise e unless target.wordpress?(ParsedCli.detection_mode)
|
||||
end
|
||||
|
||||
# Raises errors if the target is hosted on wordpress.com or is not running WordPress
|
||||
# Also check if the homepage_url is still the install url
|
||||
def check_wordpress_state
|
||||
raise Error::WordPressHosted if target.wordpress_hosted?
|
||||
|
||||
if %r{/wp-admin/install.php$}i.match?(Addressable::URI.parse(target.homepage_url).path)
|
||||
|
||||
output('not_fully_configured', url: target.homepage_url)
|
||||
|
||||
exit(WPScan::ExitCode::VULNERABLE)
|
||||
end
|
||||
|
||||
raise Error::NotWordPress unless target.wordpress?(ParsedCli.detection_mode) || ParsedCli.force
|
||||
end
|
||||
|
||||
# Loads the related server module in the target
|
||||
# and includes it in the WpItem class which will be needed
|
||||
# to check if directory listing is enabled etc
|
||||
#
|
||||
# @return [ Symbol ] The server module loaded
|
||||
def load_server_module
|
||||
server = target.server || :Apache # Tries to auto detect the server
|
||||
|
||||
# Force a specific server module to be loaded if supplied
|
||||
case ParsedCli.server
|
||||
when :apache
|
||||
server = :Apache
|
||||
when :iis
|
||||
server = :IIS
|
||||
when :nginx
|
||||
server = :Nginx
|
||||
end
|
||||
|
||||
mod = CMSScanner::Target::Server.const_get(server)
|
||||
|
||||
target.extend mod
|
||||
Model::WpItem.include mod
|
||||
|
||||
server
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
app/controllers/custom_directories.rb
Normal file
27
app/controllers/custom_directories.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Controller to ensure that the wp-content and wp-plugins
|
||||
# directories are found
|
||||
class CustomDirectories < CMSScanner::Controller::Base
|
||||
def cli_options
|
||||
[
|
||||
OptString.new(['--wp-content-dir DIR',
|
||||
'The wp-content directory if custom or not detected, such as "wp-content"']),
|
||||
OptString.new(['--wp-plugins-dir DIR',
|
||||
'The plugins directory if custom or not detected, such as "wp-content/plugins"'])
|
||||
]
|
||||
end
|
||||
|
||||
def before_scan
|
||||
target.content_dir = ParsedCli.wp_content_dir if ParsedCli.wp_content_dir
|
||||
target.plugins_dir = ParsedCli.wp_plugins_dir if ParsedCli.wp_plugins_dir
|
||||
|
||||
return if target.content_dir(ParsedCli.detection_mode)
|
||||
|
||||
raise Error::WpContentDirNotDetected
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/controllers/enumeration.rb
Normal file
33
app/controllers/enumeration.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'enumeration/cli_options'
|
||||
require_relative 'enumeration/enum_methods'
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Enumeration Controller
|
||||
class Enumeration < CMSScanner::Controller::Base
|
||||
def before_scan
|
||||
DB::DynamicFinders::Plugin.create_versions_finders
|
||||
DB::DynamicFinders::Theme.create_versions_finders
|
||||
|
||||
# Force the Garbage Collector to run due to the above method being
|
||||
# quite heavy in objects allocation
|
||||
GC.start
|
||||
end
|
||||
|
||||
def run
|
||||
enum = ParsedCli.enumerate || {}
|
||||
|
||||
enum_plugins if enum_plugins?(enum)
|
||||
enum_themes if enum_themes?(enum)
|
||||
|
||||
%i[timthumbs config_backups db_exports medias].each do |key|
|
||||
send("enum_#{key}".to_sym) if enum.key?(key)
|
||||
end
|
||||
|
||||
enum_users if enum_users?(enum)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
171
app/controllers/enumeration/cli_options.rb
Normal file
171
app/controllers/enumeration/cli_options.rb
Normal file
@@ -0,0 +1,171 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Enumeration CLI Options
|
||||
class Enumeration < CMSScanner::Controller::Base
|
||||
def cli_options
|
||||
cli_enum_choices + cli_plugins_opts + cli_themes_opts +
|
||||
cli_timthumbs_opts + cli_config_backups_opts + cli_db_exports_opts +
|
||||
cli_medias_opts + cli_users_opts
|
||||
end
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def cli_enum_choices
|
||||
[
|
||||
OptMultiChoices.new(
|
||||
['-e', '--enumerate [OPTS]', 'Enumeration Process'],
|
||||
choices: {
|
||||
vp: OptBoolean.new(['--vulnerable-plugins']),
|
||||
ap: OptBoolean.new(['--all-plugins']),
|
||||
p: OptBoolean.new(['--plugins']),
|
||||
vt: OptBoolean.new(['--vulnerable-themes']),
|
||||
at: OptBoolean.new(['--all-themes']),
|
||||
t: OptBoolean.new(['--themes']),
|
||||
tt: OptBoolean.new(['--timthumbs']),
|
||||
cb: OptBoolean.new(['--config-backups']),
|
||||
dbe: OptBoolean.new(['--db-exports']),
|
||||
u: OptIntegerRange.new(['--users', 'User IDs range. e.g: u1-5'], value_if_empty: '1-10'),
|
||||
m: OptIntegerRange.new(['--medias',
|
||||
'Media IDs range. e.g m1-15',
|
||||
'Note: Permalink setting must be set to "Plain" for those to be detected'],
|
||||
value_if_empty: '1-100')
|
||||
},
|
||||
value_if_empty: 'vp,vt,tt,cb,dbe,u,m',
|
||||
incompatible: [%i[vp ap p], %i[vt at t]],
|
||||
default: { all_plugins: true, config_backups: true }
|
||||
),
|
||||
OptRegexp.new(
|
||||
[
|
||||
'--exclude-content-based REGEXP_OR_STRING',
|
||||
'Exclude all responses matching the Regexp (case insensitive) during parts of the enumeration.',
|
||||
'Both the headers and body are checked. Regexp delimiters are not required.'
|
||||
], options: Regexp::IGNORECASE
|
||||
)
|
||||
]
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
def cli_plugins_opts
|
||||
[
|
||||
OptSmartList.new(['--plugins-list LIST', 'List of plugins to enumerate'], advanced: true),
|
||||
OptChoice.new(
|
||||
['--plugins-detection MODE',
|
||||
'Use the supplied mode to enumerate Plugins, instead of the global (--detection-mode) mode.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :passive
|
||||
),
|
||||
OptBoolean.new(
|
||||
['--plugins-version-all',
|
||||
'Check all the plugins version locations according to the choosen mode (--detection-mode, ' \
|
||||
'--plugins-detection and --plugins-version-detection)'],
|
||||
advanced: true
|
||||
),
|
||||
OptChoice.new(
|
||||
['--plugins-version-detection MODE',
|
||||
'Use the supplied mode to check plugins versions instead of the --detection-mode ' \
|
||||
'or --plugins-detection modes.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, default: :mixed
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
def cli_themes_opts
|
||||
[
|
||||
OptSmartList.new(['--themes-list LIST', 'List of themes to enumerate'], advanced: true),
|
||||
OptChoice.new(
|
||||
['--themes-detection MODE',
|
||||
'Use the supplied mode to enumerate Themes, instead of the global (--detection-mode) mode.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
),
|
||||
OptBoolean.new(
|
||||
['--themes-version-all',
|
||||
'Check all the themes version locations according to the choosen mode (--detection-mode, ' \
|
||||
'--themes-detection and --themes-version-detection)'],
|
||||
advanced: true
|
||||
),
|
||||
OptChoice.new(
|
||||
['--themes-version-detection MODE',
|
||||
'Use the supplied mode to check themes versions instead of the --detection-mode ' \
|
||||
'or --themes-detection modes.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
def cli_timthumbs_opts
|
||||
[
|
||||
OptFilePath.new(
|
||||
['--timthumbs-list FILE-PATH', 'List of timthumbs\' location to use'],
|
||||
exists: true, default: DB_DIR.join('timthumbs-v3.txt').to_s, advanced: true
|
||||
),
|
||||
OptChoice.new(
|
||||
['--timthumbs-detection MODE',
|
||||
'Use the supplied mode to enumerate Timthumbs, instead of the global (--detection-mode) mode.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
def cli_config_backups_opts
|
||||
[
|
||||
OptFilePath.new(
|
||||
['--config-backups-list FILE-PATH', 'List of config backups\' filenames to use'],
|
||||
exists: true, default: DB_DIR.join('config_backups.txt').to_s, advanced: true
|
||||
),
|
||||
OptChoice.new(
|
||||
['--config-backups-detection MODE',
|
||||
'Use the supplied mode to enumerate Config Backups, instead of the global (--detection-mode) mode.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
def cli_db_exports_opts
|
||||
[
|
||||
OptFilePath.new(
|
||||
['--db-exports-list FILE-PATH', 'List of DB exports\' paths to use'],
|
||||
exists: true, default: DB_DIR.join('db_exports.txt').to_s, advanced: true
|
||||
),
|
||||
OptChoice.new(
|
||||
['--db-exports-detection MODE',
|
||||
'Use the supplied mode to enumerate DB Exports, instead of the global (--detection-mode) mode.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
def cli_medias_opts
|
||||
[
|
||||
OptChoice.new(
|
||||
['--medias-detection MODE',
|
||||
'Use the supplied mode to enumerate Medias, instead of the global (--detection-mode) mode.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
# @return [ Array<OptParseValidator::OptBase> ]
|
||||
def cli_users_opts
|
||||
[
|
||||
OptSmartList.new(
|
||||
['--users-list LIST',
|
||||
'List of users to check during the users enumeration from the Login Error Messages'],
|
||||
advanced: true
|
||||
),
|
||||
OptChoice.new(
|
||||
['--users-detection MODE',
|
||||
'Use the supplied mode to enumerate Users, instead of the global (--detection-mode) mode.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
205
app/controllers/enumeration/enum_methods.rb
Normal file
205
app/controllers/enumeration/enum_methods.rb
Normal file
@@ -0,0 +1,205 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Enumeration Methods
|
||||
class Enumeration < CMSScanner::Controller::Base
|
||||
# @param [ String ] type (plugins or themes)
|
||||
# @param [ Symbol ] detection_mode
|
||||
#
|
||||
# @return [ String ] The related enumration message depending on the ParsedCli and type supplied
|
||||
def enum_message(type, detection_mode)
|
||||
return unless %w[plugins themes].include?(type)
|
||||
|
||||
details = if ParsedCli.enumerate[:"vulnerable_#{type}"]
|
||||
'Vulnerable'
|
||||
elsif ParsedCli.enumerate[:"all_#{type}"]
|
||||
'All'
|
||||
else
|
||||
'Most Popular'
|
||||
end
|
||||
|
||||
"Enumerating #{details} #{type.capitalize} #{enum_detection_message(detection_mode)}"
|
||||
end
|
||||
|
||||
# @param [ Symbol ] detection_mode
|
||||
#
|
||||
# @return [ String ]
|
||||
def enum_detection_message(detection_mode)
|
||||
detection_method = if detection_mode == :mixed
|
||||
'Passive and Aggressive'
|
||||
else
|
||||
detection_mode.to_s.capitalize
|
||||
end
|
||||
|
||||
"(via #{detection_method} Methods)"
|
||||
end
|
||||
|
||||
# @param [ String ] type (plugins, themes etc)
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def default_opts(type)
|
||||
mode = ParsedCli.options[:"#{type}_detection"] || ParsedCli.detection_mode
|
||||
|
||||
{
|
||||
mode: mode,
|
||||
exclude_content: ParsedCli.exclude_content_based,
|
||||
show_progression: user_interaction?,
|
||||
version_detection: {
|
||||
mode: ParsedCli.options[:"#{type}_version_detection"] || mode,
|
||||
confidence_threshold: ParsedCli.options[:"#{type}_version_all"] ? 0 : 100
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Boolean ] Wether or not to enumerate the plugins
|
||||
def enum_plugins?(opts)
|
||||
opts[:plugins] || opts[:all_plugins] || opts[:vulnerable_plugins]
|
||||
end
|
||||
|
||||
def enum_plugins
|
||||
opts = default_opts('plugins').merge(
|
||||
list: plugins_list_from_opts(ParsedCli.options),
|
||||
sort: true
|
||||
)
|
||||
|
||||
output('@info', msg: enum_message('plugins', opts[:mode])) if user_interaction?
|
||||
# Enumerate the plugins & find their versions to avoid doing that when #version
|
||||
# is called in the view
|
||||
plugins = target.plugins(opts)
|
||||
|
||||
if user_interaction? && !plugins.empty?
|
||||
output('@info',
|
||||
msg: "Checking Plugin Versions #{enum_detection_message(opts[:version_detection][:mode])}")
|
||||
end
|
||||
|
||||
plugins.each(&:version)
|
||||
|
||||
plugins.select!(&:vulnerable?) if ParsedCli.enumerate[:vulnerable_plugins]
|
||||
|
||||
output('plugins', plugins: plugins)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<String> ] The plugins list associated to the cli options
|
||||
def plugins_list_from_opts(opts)
|
||||
# List file provided by the user via the cli
|
||||
return opts[:plugins_list] if opts[:plugins_list]
|
||||
|
||||
if opts[:enumerate][:all_plugins]
|
||||
DB::Plugins.all_slugs
|
||||
elsif opts[:enumerate][:plugins]
|
||||
DB::Plugins.popular_slugs
|
||||
else
|
||||
DB::Plugins.vulnerable_slugs
|
||||
end
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Boolean ] Wether or not to enumerate the themes
|
||||
def enum_themes?(opts)
|
||||
opts[:themes] || opts[:all_themes] || opts[:vulnerable_themes]
|
||||
end
|
||||
|
||||
def enum_themes
|
||||
opts = default_opts('themes').merge(
|
||||
list: themes_list_from_opts(ParsedCli.options),
|
||||
sort: true
|
||||
)
|
||||
|
||||
output('@info', msg: enum_message('themes', opts[:mode])) if user_interaction?
|
||||
# Enumerate the themes & find their versions to avoid doing that when #version
|
||||
# is called in the view
|
||||
themes = target.themes(opts)
|
||||
|
||||
if user_interaction? && !themes.empty?
|
||||
output('@info',
|
||||
msg: "Checking Theme Versions #{enum_detection_message(opts[:version_detection][:mode])}")
|
||||
end
|
||||
|
||||
themes.each(&:version)
|
||||
|
||||
themes.select!(&:vulnerable?) if ParsedCli.enumerate[:vulnerable_themes]
|
||||
|
||||
output('themes', themes: themes)
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<String> ] The themes list associated to the cli options
|
||||
def themes_list_from_opts(opts)
|
||||
# List file provided by the user via the cli
|
||||
return opts[:themes_list] if opts[:themes_list]
|
||||
|
||||
if opts[:enumerate][:all_themes]
|
||||
DB::Themes.all_slugs
|
||||
elsif opts[:enumerate][:themes]
|
||||
DB::Themes.popular_slugs
|
||||
else
|
||||
DB::Themes.vulnerable_slugs
|
||||
end
|
||||
end
|
||||
|
||||
def enum_timthumbs
|
||||
opts = default_opts('timthumbs').merge(list: ParsedCli.timthumbs_list)
|
||||
|
||||
output('@info', msg: "Enumerating Timthumbs #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||
output('timthumbs', timthumbs: target.timthumbs(opts))
|
||||
end
|
||||
|
||||
def enum_config_backups
|
||||
opts = default_opts('config_backups').merge(list: ParsedCli.config_backups_list)
|
||||
|
||||
output('@info', msg: "Enumerating Config Backups #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||
output('config_backups', config_backups: target.config_backups(opts))
|
||||
end
|
||||
|
||||
def enum_db_exports
|
||||
opts = default_opts('db_exports').merge(list: ParsedCli.db_exports_list)
|
||||
|
||||
output('@info', msg: "Enumerating DB Exports #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||
output('db_exports', db_exports: target.db_exports(opts))
|
||||
end
|
||||
|
||||
def enum_medias
|
||||
opts = default_opts('medias').merge(range: ParsedCli.enumerate[:medias])
|
||||
|
||||
if user_interaction?
|
||||
output('@info',
|
||||
msg: "Enumerating Medias #{enum_detection_message(opts[:mode])} "\
|
||||
'(Permalink setting must be set to "Plain" for those to be detected)')
|
||||
end
|
||||
|
||||
output('medias', medias: target.medias(opts))
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Boolean ] Wether or not to enumerate the users
|
||||
def enum_users?(opts)
|
||||
opts[:users] || (ParsedCli.passwords && !ParsedCli.username && !ParsedCli.usernames)
|
||||
end
|
||||
|
||||
def enum_users
|
||||
opts = default_opts('users').merge(
|
||||
range: enum_users_range,
|
||||
list: ParsedCli.users_list
|
||||
)
|
||||
|
||||
output('@info', msg: "Enumerating Users #{enum_detection_message(opts[:mode])}") if user_interaction?
|
||||
output('users', users: target.users(opts))
|
||||
end
|
||||
|
||||
# @return [ Range ] The user ids range to enumerate
|
||||
# If the --enumerate is used, the default value is handled by the Option
|
||||
# However, when using --passwords alone, the default has to be set by the code below
|
||||
def enum_users_range
|
||||
ParsedCli.enumerate[:users] || cli_enum_choices[0].choices[:u].validate(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
28
app/controllers/main_theme.rb
Normal file
28
app/controllers/main_theme.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Main Theme Controller
|
||||
class MainTheme < CMSScanner::Controller::Base
|
||||
def cli_options
|
||||
[
|
||||
OptChoice.new(
|
||||
['--main-theme-detection MODE',
|
||||
'Use the supplied mode for the Main theme detection, instead of the global (--detection-mode) mode.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
def run
|
||||
output(
|
||||
'theme',
|
||||
theme: target.main_theme(
|
||||
mode: ParsedCli.main_theme_detection || ParsedCli.detection_mode
|
||||
),
|
||||
verbose: ParsedCli.verbose
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
114
app/controllers/password_attack.rb
Normal file
114
app/controllers/password_attack.rb
Normal file
@@ -0,0 +1,114 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Password Attack Controller
|
||||
class PasswordAttack < CMSScanner::Controller::Base
|
||||
def cli_options
|
||||
[
|
||||
OptFilePath.new(
|
||||
['--passwords FILE-PATH', '-P',
|
||||
'List of passwords to use during the password attack.',
|
||||
'If no --username/s option supplied, user enumeration will be run.'],
|
||||
exists: true
|
||||
),
|
||||
OptSmartList.new(['--usernames LIST', '-U', 'List of usernames to use during the password attack.']),
|
||||
OptInteger.new(['--multicall-max-passwords MAX_PWD',
|
||||
'Maximum number of passwords to send by request with XMLRPC multicall'],
|
||||
default: 500),
|
||||
OptChoice.new(['--password-attack ATTACK',
|
||||
'Force the supplied attack to be used rather than automatically determining one.'],
|
||||
choices: %w[wp-login xmlrpc xmlrpc-multicall],
|
||||
normalize: %i[downcase underscore to_sym])
|
||||
]
|
||||
end
|
||||
|
||||
def run
|
||||
return unless ParsedCli.passwords
|
||||
|
||||
if user_interaction?
|
||||
output('@info',
|
||||
msg: "Performing password attack on #{attacker.titleize} against #{users.size} user/s")
|
||||
end
|
||||
|
||||
attack_opts = {
|
||||
show_progression: user_interaction?,
|
||||
multicall_max_passwords: ParsedCli.multicall_max_passwords
|
||||
}
|
||||
|
||||
begin
|
||||
found = []
|
||||
|
||||
attacker.attack(users, passwords(ParsedCli.passwords), attack_opts) do |user|
|
||||
found << user
|
||||
|
||||
attacker.progress_bar.log("[SUCCESS] - #{user.username} / #{user.password}")
|
||||
end
|
||||
ensure
|
||||
output('users', users: found)
|
||||
end
|
||||
end
|
||||
|
||||
# @return [ CMSScanner::Finders::Finder ] The finder used to perform the attack
|
||||
def attacker
|
||||
@attacker ||= attacker_from_cli_options || attacker_from_automatic_detection
|
||||
end
|
||||
|
||||
# @return [ Model::XMLRPC ]
|
||||
def xmlrpc
|
||||
@xmlrpc ||= target.xmlrpc
|
||||
end
|
||||
|
||||
# @return [ CMSScanner::Finders::Finder ]
|
||||
def attacker_from_cli_options
|
||||
return unless ParsedCli.password_attack
|
||||
|
||||
case ParsedCli.password_attack
|
||||
when :wp_login
|
||||
WPScan::Finders::Passwords::WpLogin.new(target)
|
||||
when :xmlrpc
|
||||
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||
|
||||
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||
when :xmlrpc_multicall
|
||||
raise Error::XMLRPCNotDetected unless xmlrpc
|
||||
|
||||
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||
end
|
||||
end
|
||||
|
||||
# @return [ CMSScanner::Finders::Finder ]
|
||||
def attacker_from_automatic_detection
|
||||
if xmlrpc&.enabled? && xmlrpc.available_methods.include?('wp.getUsersBlogs')
|
||||
wp_version = target.wp_version
|
||||
|
||||
if wp_version && wp_version < '4.4'
|
||||
WPScan::Finders::Passwords::XMLRPCMulticall.new(xmlrpc)
|
||||
else
|
||||
WPScan::Finders::Passwords::XMLRPC.new(xmlrpc)
|
||||
end
|
||||
else
|
||||
WPScan::Finders::Passwords::WpLogin.new(target)
|
||||
end
|
||||
end
|
||||
|
||||
# @return [ Array<Users> ] The users to brute force
|
||||
def users
|
||||
return target.users unless ParsedCli.usernames
|
||||
|
||||
ParsedCli.usernames.reduce([]) do |acc, elem|
|
||||
acc << Model::User.new(elem.chomp)
|
||||
end
|
||||
end
|
||||
|
||||
# @param [ String ] wordlist_path
|
||||
#
|
||||
# @return [ Array<String> ]
|
||||
def passwords(wordlist_path)
|
||||
@passwords ||= File.open(wordlist_path).reduce([]) do |acc, elem|
|
||||
acc << elem.chomp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
35
app/controllers/wp_version.rb
Normal file
35
app/controllers/wp_version.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Controller
|
||||
# Wp Version Controller
|
||||
class WpVersion < CMSScanner::Controller::Base
|
||||
def cli_options
|
||||
[
|
||||
OptBoolean.new(['--wp-version-all', 'Check all the version locations'], advanced: true),
|
||||
OptChoice.new(
|
||||
['--wp-version-detection MODE',
|
||||
'Use the supplied mode for the WordPress version detection, ' \
|
||||
'instead of the global (--detection-mode) mode.'],
|
||||
choices: %w[mixed passive aggressive], normalize: :to_sym, advanced: true
|
||||
)
|
||||
]
|
||||
end
|
||||
|
||||
def before_scan
|
||||
WPScan::DB::DynamicFinders::Wordpress.create_versions_finders
|
||||
end
|
||||
|
||||
def run
|
||||
output(
|
||||
'version',
|
||||
version: target.wp_version(
|
||||
mode: ParsedCli.wp_version_detection || ParsedCli.detection_mode,
|
||||
confidence_threshold: ParsedCli.wp_version_all ? 0 : 100,
|
||||
show_progression: user_interaction?
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
17
app/finders.rb
Normal file
17
app/finders.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'finders/interesting_findings'
|
||||
require_relative 'finders/wp_items'
|
||||
require_relative 'finders/wp_version'
|
||||
require_relative 'finders/main_theme'
|
||||
require_relative 'finders/timthumb_version'
|
||||
require_relative 'finders/timthumbs'
|
||||
require_relative 'finders/config_backups'
|
||||
require_relative 'finders/db_exports'
|
||||
require_relative 'finders/medias'
|
||||
require_relative 'finders/users'
|
||||
require_relative 'finders/plugins'
|
||||
require_relative 'finders/plugin_version'
|
||||
require_relative 'finders/theme_version'
|
||||
require_relative 'finders/themes'
|
||||
require_relative 'finders/passwords'
|
||||
19
app/finders/config_backups.rb
Normal file
19
app/finders/config_backups.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'config_backups/known_filenames'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module ConfigBackups
|
||||
# Config Backup Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders << ConfigBackups::KnownFilenames.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
47
app/finders/config_backups/known_filenames.rb
Normal file
47
app/finders/config_backups/known_filenames.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module ConfigBackups
|
||||
# Config Backup finder
|
||||
class KnownFilenames < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list
|
||||
# @option opts [ Boolean ] :show_progression
|
||||
#
|
||||
# @return [ Array<ConfigBackup> ]
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||
next unless res.body =~ /define/i && res.body !~ /<\s?html/i
|
||||
|
||||
found << Model::ConfigBackup.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list Mandatory
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def potential_urls(opts = {})
|
||||
urls = {}
|
||||
|
||||
File.open(opts[:list]).each_with_index do |file, index|
|
||||
urls[target.url(file.chomp)] = index
|
||||
end
|
||||
|
||||
urls
|
||||
end
|
||||
|
||||
def create_progress_bar(opts = {})
|
||||
super(opts.merge(title: ' Checking Config Backups -'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/finders/db_exports.rb
Normal file
19
app/finders/db_exports.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'db_exports/known_locations'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module DbExports
|
||||
# DB Exports Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders << DbExports::KnownLocations.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
61
app/finders/db_exports/known_locations.rb
Normal file
61
app/finders/db_exports/known_locations.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module DbExports
|
||||
# DB Exports finder
|
||||
# See https://github.com/wpscanteam/wpscan-v3/issues/62
|
||||
class KnownLocations < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
SQL_PATTERN = /(?:DROP|(?:UN)?LOCK|CREATE) TABLE|INSERT INTO/.freeze
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list
|
||||
# @option opts [ Boolean ] :show_progression
|
||||
#
|
||||
# @return [ Array<DBExport> ]
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(potential_urls(opts), opts.merge(check_full_response: 200)) do |res|
|
||||
if res.effective_url.end_with?('.zip')
|
||||
next unless res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
||||
else
|
||||
next unless res.body =~ SQL_PATTERN
|
||||
end
|
||||
|
||||
found << Model::DbExport.new(res.request.url, found_by: DIRECT_ACCESS, confidence: 100)
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
def full_request_params
|
||||
@full_request_params ||= { headers: { 'Range' => 'bytes=0-3000' } }
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list Mandatory
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def potential_urls(opts = {})
|
||||
urls = {}
|
||||
domain_name = target.uri.host[/(^[\w|-]+)/, 1]
|
||||
|
||||
File.open(opts[:list]).each_with_index do |path, index|
|
||||
path.gsub!('{domain_name}', domain_name)
|
||||
|
||||
urls[target.url(path.chomp)] = index
|
||||
end
|
||||
|
||||
urls
|
||||
end
|
||||
|
||||
def create_progress_bar(opts = {})
|
||||
super(opts.merge(title: ' Checking DB Exports -'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
37
app/finders/interesting_findings.rb
Normal file
37
app/finders/interesting_findings.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'interesting_findings/readme'
|
||||
require_relative 'interesting_findings/wp_cron'
|
||||
require_relative 'interesting_findings/multisite'
|
||||
require_relative 'interesting_findings/debug_log'
|
||||
require_relative 'interesting_findings/backup_db'
|
||||
require_relative 'interesting_findings/mu_plugins'
|
||||
require_relative 'interesting_findings/registration'
|
||||
require_relative 'interesting_findings/tmm_db_migrate'
|
||||
require_relative 'interesting_findings/upload_sql_dump'
|
||||
require_relative 'interesting_findings/full_path_disclosure'
|
||||
require_relative 'interesting_findings/duplicator_installer_log'
|
||||
require_relative 'interesting_findings/upload_directory_listing'
|
||||
require_relative 'interesting_findings/emergency_pwd_reset_script'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# Interesting Files Finder
|
||||
class Base < CMSScanner::Finders::InterestingFindings::Base
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
super(target)
|
||||
|
||||
%w[
|
||||
Readme DebugLog FullPathDisclosure BackupDB DuplicatorInstallerLog
|
||||
Multisite MuPlugins Registration UploadDirectoryListing TmmDbMigrate
|
||||
UploadSQLDump EmergencyPwdResetScript WPCron
|
||||
].each do |f|
|
||||
finders << InterestingFindings.const_get(f).new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
26
app/finders/interesting_findings/backup_db.rb
Normal file
26
app/finders/interesting_findings/backup_db.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# BackupDB finder
|
||||
class BackupDB < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
path = 'wp-content/backup-db/'
|
||||
res = target.head_and_get(path, [200, 403])
|
||||
|
||||
return unless [200, 403].include?(res.code) && !target.homepage_or_404?(res)
|
||||
|
||||
Model::BackupDB.new(
|
||||
target.url(path),
|
||||
confidence: 70,
|
||||
found_by: DIRECT_ACCESS,
|
||||
interesting_entries: target.directory_listing_entries(path),
|
||||
references: { url: 'https://github.com/wpscanteam/wpscan/issues/422' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
23
app/finders/interesting_findings/debug_log.rb
Normal file
23
app/finders/interesting_findings/debug_log.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# debug.log finder
|
||||
class DebugLog < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
path = 'wp-content/debug.log'
|
||||
|
||||
return unless target.debug_log?(path)
|
||||
|
||||
Model::DebugLog.new(
|
||||
target.url(path),
|
||||
confidence: 100, found_by: DIRECT_ACCESS,
|
||||
references: { url: 'https://codex.wordpress.org/Debugging_in_WordPress' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
24
app/finders/interesting_findings/duplicator_installer_log.rb
Normal file
24
app/finders/interesting_findings/duplicator_installer_log.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# DuplicatorInstallerLog finder
|
||||
class DuplicatorInstallerLog < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
path = 'installer-log.txt'
|
||||
|
||||
return unless target.head_and_get(path).body =~ /DUPLICATOR INSTALL-LOG/
|
||||
|
||||
Model::DuplicatorInstallerLog.new(
|
||||
target.url(path),
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
references: { url: 'https://www.exploit-db.com/ghdb/3981/' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# Emergency Password Reset Script finder
|
||||
class EmergencyPwdResetScript < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
path = 'emergency.php'
|
||||
res = target.head_and_get(path)
|
||||
|
||||
return unless res.code == 200 && !target.homepage_or_404?(res)
|
||||
|
||||
Model::EmergencyPwdResetScript.new(
|
||||
target.url(path),
|
||||
confidence: /password/i.match?(res.body) ? 100 : 40,
|
||||
found_by: DIRECT_ACCESS,
|
||||
references: {
|
||||
url: 'https://codex.wordpress.org/Resetting_Your_Password#Using_the_Emergency_Password_Reset_Script'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
26
app/finders/interesting_findings/full_path_disclosure.rb
Normal file
26
app/finders/interesting_findings/full_path_disclosure.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# Full Path Disclosure finder
|
||||
class FullPathDisclosure < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
path = 'wp-includes/rss-functions.php'
|
||||
fpd_entries = target.full_path_disclosure_entries(path)
|
||||
|
||||
return if fpd_entries.empty?
|
||||
|
||||
Model::FullPathDisclosure.new(
|
||||
target.url(path),
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
interesting_entries: fpd_entries,
|
||||
references: { url: 'https://www.owasp.org/index.php/Full_Path_Disclosure' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
app/finders/interesting_findings/mu_plugins.rb
Normal file
51
app/finders/interesting_findings/mu_plugins.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# Must Use Plugins Directory checker
|
||||
class MuPlugins < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def passive(_opts = {})
|
||||
pattern = %r{#{target.content_dir}/mu\-plugins/}i
|
||||
|
||||
target.in_scope_uris(target.homepage_res) do |uri|
|
||||
next unless uri.path =~ pattern
|
||||
|
||||
url = target.url('wp-content/mu-plugins/')
|
||||
|
||||
target.mu_plugins = true
|
||||
|
||||
return Model::MuPlugins.new(
|
||||
url,
|
||||
confidence: 70,
|
||||
found_by: 'URLs In Homepage (Passive Detection)',
|
||||
to_s: "This site has 'Must Use Plugins': #{url}",
|
||||
references: { url: 'http://codex.wordpress.org/Must_Use_Plugins' }
|
||||
)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
url = target.url('wp-content/mu-plugins/')
|
||||
res = Browser.get_and_follow_location(url)
|
||||
|
||||
return unless [200, 401, 403].include?(res.code)
|
||||
return if target.homepage_or_404?(res)
|
||||
|
||||
target.mu_plugins = true
|
||||
|
||||
Model::MuPlugins.new(
|
||||
url,
|
||||
confidence: 80,
|
||||
found_by: DIRECT_ACCESS,
|
||||
to_s: "This site has 'Must Use Plugins': #{url}",
|
||||
references: { url: 'http://codex.wordpress.org/Must_Use_Plugins' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
31
app/finders/interesting_findings/multisite.rb
Normal file
31
app/finders/interesting_findings/multisite.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# Multisite checker
|
||||
class Multisite < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
url = target.url('wp-signup.php')
|
||||
res = Browser.get(url)
|
||||
location = res.headers_hash['location']
|
||||
|
||||
return unless [200, 302].include?(res.code)
|
||||
return if res.code == 302 && location =~ /wp-login\.php\?action=register/
|
||||
return unless res.code == 200 || res.code == 302 && location =~ /wp-signup\.php/
|
||||
|
||||
target.multisite = true
|
||||
|
||||
Model::Multisite.new(
|
||||
url,
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
to_s: 'This site seems to be a multisite',
|
||||
references: { url: 'http://codex.wordpress.org/Glossary#Multisite' }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
28
app/finders/interesting_findings/readme.rb
Normal file
28
app/finders/interesting_findings/readme.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# Readme.html finder
|
||||
class Readme < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
potential_files.each do |path|
|
||||
res = target.head_and_get(path)
|
||||
|
||||
next unless res.code == 200 && res.body =~ /wordpress/i
|
||||
|
||||
return Model::Readme.new(target.url(path), confidence: 100, found_by: DIRECT_ACCESS)
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# @retun [ Array<String> ] The list of potential readme files
|
||||
def potential_files
|
||||
%w[readme.html olvasdel.html lisenssi.html liesmich.html]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/finders/interesting_findings/registration.rb
Normal file
33
app/finders/interesting_findings/registration.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# Registration Enabled checker
|
||||
class Registration < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def passive(_opts = {})
|
||||
# Maybe check in the homepage if there is the registration url ?
|
||||
end
|
||||
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
res = Browser.get_and_follow_location(target.registration_url)
|
||||
|
||||
return unless res.code == 200
|
||||
return if res.html.css('form#setupform').empty? &&
|
||||
res.html.css('form#registerform').empty?
|
||||
|
||||
target.registration_enabled = true
|
||||
|
||||
Model::Registration.new(
|
||||
res.effective_url,
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
to_s: "Registration is enabled: #{res.effective_url}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
26
app/finders/interesting_findings/tmm_db_migrate.rb
Normal file
26
app/finders/interesting_findings/tmm_db_migrate.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# Tmm DB Migrate finder
|
||||
class TmmDbMigrate < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
path = 'wp-content/uploads/tmm_db_migrate/tmm_db_migrate.zip'
|
||||
url = target.url(path)
|
||||
res = browser.forge_request(url, target.head_or_get_request_params).run
|
||||
|
||||
return unless res.code == 200 && res.headers['Content-Type'] =~ %r{\Aapplication/zip}i
|
||||
|
||||
Model::TmmDbMigrate.new(
|
||||
url,
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
references: { packetstorm: 131_957 }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
26
app/finders/interesting_findings/upload_directory_listing.rb
Normal file
26
app/finders/interesting_findings/upload_directory_listing.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# UploadDirectoryListing finder
|
||||
class UploadDirectoryListing < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
path = 'wp-content/uploads/'
|
||||
|
||||
return unless target.directory_listing?(path)
|
||||
|
||||
url = target.url(path)
|
||||
|
||||
Model::UploadDirectoryListing.new(
|
||||
url,
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS,
|
||||
to_s: "Upload directory has listing enabled: #{url}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
26
app/finders/interesting_findings/upload_sql_dump.rb
Normal file
26
app/finders/interesting_findings/upload_sql_dump.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# UploadSQLDump finder
|
||||
class UploadSQLDump < CMSScanner::Finders::Finder
|
||||
SQL_PATTERN = /(?:DROP|CREATE|(?:UN)?LOCK) TABLE|INSERT INTO/.freeze
|
||||
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
path = 'wp-content/uploads/dump.sql'
|
||||
res = target.head_and_get(path, [200], get: { headers: { 'Range' => 'bytes=0-3000' } })
|
||||
|
||||
return unless res.body =~ SQL_PATTERN
|
||||
|
||||
Model::UploadSQLDump.new(
|
||||
target.url(path),
|
||||
confidence: 100,
|
||||
found_by: DIRECT_ACCESS
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/finders/interesting_findings/wp_cron.rb
Normal file
33
app/finders/interesting_findings/wp_cron.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module InterestingFindings
|
||||
# wp-cron.php finder
|
||||
class WPCron < CMSScanner::Finders::Finder
|
||||
# @return [ InterestingFinding ]
|
||||
def aggressive(_opts = {})
|
||||
res = Browser.get(wp_cron_url)
|
||||
|
||||
return unless res.code == 200
|
||||
|
||||
Model::WPCron.new(
|
||||
wp_cron_url,
|
||||
confidence: 60,
|
||||
found_by: DIRECT_ACCESS,
|
||||
references: {
|
||||
url: [
|
||||
'https://www.iplocation.net/defend-wordpress-from-ddos',
|
||||
'https://github.com/wpscanteam/wpscan/issues/1299'
|
||||
]
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def wp_cron_url
|
||||
@wp_cron_url ||= target.url('wp-cron.php')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
24
app/finders/main_theme.rb
Normal file
24
app/finders/main_theme.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'main_theme/css_style'
|
||||
require_relative 'main_theme/woo_framework_meta_generator'
|
||||
require_relative 'main_theme/urls_in_homepage'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# Main Theme Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::UniqueFinder
|
||||
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders <<
|
||||
MainTheme::CssStyle.new(target) <<
|
||||
MainTheme::WooFrameworkMetaGenerator.new(target) <<
|
||||
MainTheme::UrlsInHomepage.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
45
app/finders/main_theme/css_style.rb
Normal file
45
app/finders/main_theme/css_style.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# From the css style
|
||||
class CssStyle < CMSScanner::Finders::Finder
|
||||
include Finders::WpItems::URLsInHomepage
|
||||
|
||||
def create_theme(slug, style_url, opts)
|
||||
Model::Theme.new(
|
||||
slug,
|
||||
target,
|
||||
opts.merge(found_by: found_by, confidence: 70, style_url: style_url)
|
||||
)
|
||||
end
|
||||
|
||||
def passive(opts = {})
|
||||
passive_from_css_href(target.homepage_res, opts) || passive_from_style_code(target.homepage_res, opts)
|
||||
end
|
||||
|
||||
def passive_from_css_href(res, opts)
|
||||
target.in_scope_uris(res, '//style/@src|//link/@href') do |uri|
|
||||
next unless uri.path =~ %r{/themes/([^\/]+)/style.css\z}i
|
||||
|
||||
return create_theme(Regexp.last_match[1], uri.to_s, opts)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def passive_from_style_code(res, opts)
|
||||
res.html.css('style').each do |tag|
|
||||
code = tag.text.to_s
|
||||
next if code.empty?
|
||||
|
||||
next unless code =~ %r{#{item_code_pattern('themes')}\\?/style\.css[^"'\( ]*}i
|
||||
|
||||
return create_theme(Regexp.last_match[1], Regexp.last_match[0].strip, opts)
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
app/finders/main_theme/urls_in_homepage.rb
Normal file
27
app/finders/main_theme/urls_in_homepage.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# URLs In Homepage Finder
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<Theme> ]
|
||||
def passive(opts = {})
|
||||
found = []
|
||||
|
||||
slugs = items_from_links('themes', false) + items_from_codes('themes', false)
|
||||
|
||||
slugs.each_with_object(Hash.new(0)) { |slug, counts| counts[slug] += 1 }.each do |slug, occurences|
|
||||
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 2 * occurences))
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
24
app/finders/main_theme/woo_framework_meta_generator.rb
Normal file
24
app/finders/main_theme/woo_framework_meta_generator.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module MainTheme
|
||||
# From the WooFramework meta generators
|
||||
class WooFrameworkMetaGenerator < CMSScanner::Finders::Finder
|
||||
THEME_PATTERN = %r{<meta name="generator" content="([^\s"]+)\s?([^"]+)?"\s+/?>}.freeze
|
||||
FRAMEWORK_PATTERN = %r{<meta name="generator" content="WooFramework\s?([^"]+)?"\s+/?>}.freeze
|
||||
PATTERN = /#{THEME_PATTERN}\s+#{FRAMEWORK_PATTERN}/i.freeze
|
||||
|
||||
def passive(opts = {})
|
||||
return unless target.homepage_res.body =~ PATTERN
|
||||
|
||||
Model::Theme.new(
|
||||
Regexp.last_match[1],
|
||||
target,
|
||||
opts.merge(found_by: found_by, confidence: 80)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/finders/medias.rb
Normal file
19
app/finders/medias.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'medias/attachment_brute_forcing'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Medias
|
||||
# Medias Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders << Medias::AttachmentBruteForcing.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
46
app/finders/medias/attachment_brute_forcing.rb
Normal file
46
app/finders/medias/attachment_brute_forcing.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Medias
|
||||
# Medias Finder, see https://github.com/wpscanteam/wpscan/issues/172
|
||||
class AttachmentBruteForcing < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ Range ] :range Mandatory
|
||||
#
|
||||
# @return [ Array<Media> ]
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts) do |res|
|
||||
next unless res.code == 200
|
||||
|
||||
found << Model::Media.new(res.effective_url, opts.merge(found_by: found_by, confidence: 100))
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ Range ] :range Mandatory
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def target_urls(opts = {})
|
||||
urls = {}
|
||||
|
||||
opts[:range].each do |id|
|
||||
urls[target.uri.join("?attachment_id=#{id}").to_s] = id
|
||||
end
|
||||
|
||||
urls
|
||||
end
|
||||
|
||||
def create_progress_bar(opts = {})
|
||||
super(opts.merge(title: ' Brute Forcing Attachment IDs -'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
5
app/finders/passwords.rb
Normal file
5
app/finders/passwords.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'passwords/wp_login'
|
||||
require_relative 'passwords/xml_rpc'
|
||||
require_relative 'passwords/xml_rpc_multicall'
|
||||
25
app/finders/passwords/wp_login.rb
Normal file
25
app/finders/passwords/wp_login.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Passwords
|
||||
# Password attack against the wp-login.php
|
||||
class WpLogin < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
|
||||
|
||||
def login_request(username, password)
|
||||
target.login_request(username, password)
|
||||
end
|
||||
|
||||
def valid_credentials?(response)
|
||||
response.code == 302 &&
|
||||
response.headers['Set-Cookie']&.any? { |cookie| cookie =~ /wordpress_logged_in_/i }
|
||||
end
|
||||
|
||||
def errored_response?(response)
|
||||
response.code != 200 && response.body !~ /login_error/i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
24
app/finders/passwords/xml_rpc.rb
Normal file
24
app/finders/passwords/xml_rpc.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Passwords
|
||||
# Password attack against the XMLRPC interface
|
||||
class XMLRPC < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::BreadthFirstDictionaryAttack
|
||||
|
||||
def login_request(username, password)
|
||||
target.method_call('wp.getUsersBlogs', [username, password])
|
||||
end
|
||||
|
||||
def valid_credentials?(response)
|
||||
response.code == 200 && response.body =~ /blogName/
|
||||
end
|
||||
|
||||
def errored_response?(response)
|
||||
response.code != 200 && response.body !~ /login_error/i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
104
app/finders/passwords/xml_rpc_multicall.rb
Normal file
104
app/finders/passwords/xml_rpc_multicall.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Passwords
|
||||
# Password attack against the XMLRPC interface with the multicall method
|
||||
# WP < 4.4 is vulnerable to such attack
|
||||
class XMLRPCMulticall < CMSScanner::Finders::Finder
|
||||
# @param [ Array<User> ] users
|
||||
# @param [ Array<String> ] passwords
|
||||
#
|
||||
# @return [ Typhoeus::Response ]
|
||||
def do_multi_call(users, passwords)
|
||||
methods = []
|
||||
|
||||
users.each do |user|
|
||||
passwords.each do |password|
|
||||
methods << ['wp.getUsersBlogs', user.username, password]
|
||||
end
|
||||
end
|
||||
|
||||
target.multi_call(methods).run
|
||||
end
|
||||
|
||||
# @param [ Array<Model::User> ] users
|
||||
# @param [ Array<String> ] passwords
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ Boolean ] :show_progression
|
||||
# @option opts [ Integer ] :multicall_max_passwords
|
||||
#
|
||||
# @yield [ Model::User ] When a valid combination is found
|
||||
#
|
||||
# TODO: Make rubocop happy about metrics etc
|
||||
#
|
||||
# rubocop:disable all
|
||||
def attack(users, passwords, opts = {})
|
||||
wordlist_index = 0
|
||||
max_passwords = opts[:multicall_max_passwords]
|
||||
current_passwords_size = passwords_size(max_passwords, users.size)
|
||||
|
||||
create_progress_bar(total: (passwords.size / current_passwords_size.round(1)).ceil,
|
||||
show_progression: opts[:show_progression])
|
||||
|
||||
loop do
|
||||
current_users = users.select { |user| user.password.nil? }
|
||||
current_passwords = passwords[wordlist_index, current_passwords_size]
|
||||
wordlist_index += current_passwords_size
|
||||
|
||||
break if current_users.empty? || current_passwords.nil? || current_passwords.empty?
|
||||
|
||||
res = do_multi_call(current_users, current_passwords)
|
||||
|
||||
progress_bar.increment
|
||||
|
||||
check_and_output_errors(res)
|
||||
|
||||
# Avoid to parse the response and iterate over all the structs in the document
|
||||
# if there isn't any tag matching a valid combination
|
||||
next unless res.body =~ /isAdmin/ # maybe a better one ?
|
||||
|
||||
Nokogiri::XML(res.body).xpath('//struct').each_with_index do |struct, index|
|
||||
next if struct.text =~ /faultCode/
|
||||
|
||||
user = current_users[index / current_passwords.size]
|
||||
user.password = current_passwords[index % current_passwords.size]
|
||||
|
||||
yield user
|
||||
|
||||
# Updates the current_passwords_size and progress_bar#total
|
||||
# given that less requests will be done due to a valid combination found.
|
||||
current_passwords_size = passwords_size(max_passwords, current_users.size - 1)
|
||||
|
||||
if current_passwords_size == 0
|
||||
progress_bar.log('All Found') # remove ?
|
||||
progress_bar.stop
|
||||
break
|
||||
end
|
||||
|
||||
progress_bar.total = progress_bar.progress + ((passwords.size - wordlist_index) / current_passwords_size.round(1)).ceil
|
||||
end
|
||||
end
|
||||
# Maybe a progress_bar.stop ?
|
||||
end
|
||||
# rubocop:disable all
|
||||
|
||||
def passwords_size(max_passwords, users_size)
|
||||
return 1 if max_passwords < users_size
|
||||
return 0 if users_size == 0
|
||||
|
||||
max_passwords / users_size
|
||||
end
|
||||
|
||||
# @param [ Typhoeus::Response ] res
|
||||
def check_and_output_errors(res)
|
||||
progress_bar.log("Incorrect response: #{res.code} / #{res.return_message}") unless res.code == 200
|
||||
|
||||
progress_bar.log('Parsing error, might be caused by a too high --max-passwords value (such as >= 2k)') if res.body =~ /parse error. not well formed/i
|
||||
|
||||
progress_bar.log('The requested method is not supported') if res.body =~ /requested method [^ ]+ does not exist/i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
40
app/finders/plugin_version.rb
Normal file
40
app/finders/plugin_version.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'plugin_version/readme'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module PluginVersion
|
||||
# Plugin Version Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::UniqueFinder
|
||||
|
||||
# @param [ Model::Plugin ] plugin
|
||||
def initialize(plugin)
|
||||
finders << PluginVersion::Readme.new(plugin)
|
||||
|
||||
load_specific_finders(plugin)
|
||||
end
|
||||
|
||||
# Load the finders associated with the plugin
|
||||
#
|
||||
# @param [ Model::Plugin ] plugin
|
||||
def load_specific_finders(plugin)
|
||||
module_name = plugin.classify
|
||||
|
||||
return unless Finders::PluginVersion.constants.include?(module_name)
|
||||
|
||||
mod = Finders::PluginVersion.const_get(module_name)
|
||||
|
||||
mod.constants.each do |constant|
|
||||
c = mod.const_get(constant)
|
||||
|
||||
next unless c.is_a?(Class)
|
||||
|
||||
finders << c.new(plugin)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
83
app/finders/plugin_version/readme.rb
Normal file
83
app/finders/plugin_version/readme.rb
Normal file
@@ -0,0 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module PluginVersion
|
||||
# Plugin Version Finder from the readme.txt file
|
||||
class Readme < CMSScanner::Finders::Finder
|
||||
# @return [ Version ]
|
||||
def aggressive(_opts = {})
|
||||
found_by_msg = 'Readme - %s (Aggressive Detection)'
|
||||
|
||||
# The target(plugin)#readme_url can't be used directly here
|
||||
# as if the --detection-mode is passive, it will always return nil
|
||||
Model::WpItem::READMES.each do |file|
|
||||
res = target.head_and_get(file)
|
||||
|
||||
next unless res.code == 200 && !(numbers = version_numbers(res.body)).empty?
|
||||
|
||||
return numbers.reduce([]) do |a, e|
|
||||
a << Model::Version.new(
|
||||
e[0],
|
||||
found_by: format(found_by_msg, e[1]),
|
||||
confidence: e[2],
|
||||
interesting_entries: [res.effective_url]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# @return [ Array<String, String, Integer> ] number, found_by, confidence
|
||||
def version_numbers(body)
|
||||
numbers = []
|
||||
|
||||
if (number = from_stable_tag(body))
|
||||
numbers << [number, 'Stable Tag', 80]
|
||||
end
|
||||
|
||||
if (number = from_changelog_section(body))
|
||||
numbers << [number, 'ChangeLog Section', 50]
|
||||
end
|
||||
|
||||
numbers
|
||||
end
|
||||
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String, nil ] The version number detected from the stable tag
|
||||
def from_stable_tag(body)
|
||||
return unless body =~ /\b(?:stable tag|version):\s*(?!trunk)([0-9a-z\.-]+)/i
|
||||
|
||||
number = Regexp.last_match[1]
|
||||
|
||||
number if number =~ /[0-9]+/
|
||||
end
|
||||
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String, nil ] The best version number detected from the changelog section
|
||||
def from_changelog_section(body)
|
||||
extracted_versions = body.scan(%r{[=]+\s+(?:v(?:ersion)?\s*)?([0-9\.-]+)[ \ta-z0-9\(\)\.\-\/]*[=]+}i)
|
||||
|
||||
return if extracted_versions.nil? || extracted_versions.empty?
|
||||
|
||||
extracted_versions.flatten!
|
||||
# must contain at least one number
|
||||
extracted_versions = extracted_versions.select { |x| x =~ /[0-9]+/ }
|
||||
|
||||
sorted = extracted_versions.sort do |x, y|
|
||||
begin
|
||||
Gem::Version.new(x) <=> Gem::Version.new(y)
|
||||
rescue StandardError
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
sorted.last
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
35
app/finders/plugins.rb
Normal file
35
app/finders/plugins.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'plugins/urls_in_homepage'
|
||||
require_relative 'plugins/known_locations'
|
||||
# From the DynamicFinders
|
||||
require_relative 'plugins/comment'
|
||||
require_relative 'plugins/xpath'
|
||||
require_relative 'plugins/header_pattern'
|
||||
require_relative 'plugins/body_pattern'
|
||||
require_relative 'plugins/javascript_var'
|
||||
require_relative 'plugins/query_parameter'
|
||||
require_relative 'plugins/config_parser' # Not loaded below as not implemented
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders <<
|
||||
Plugins::UrlsInHomepage.new(target) <<
|
||||
Plugins::HeaderPattern.new(target) <<
|
||||
Plugins::Comment.new(target) <<
|
||||
Plugins::Xpath.new(target) <<
|
||||
Plugins::BodyPattern.new(target) <<
|
||||
Plugins::JavascriptVar.new(target) <<
|
||||
Plugins::KnownLocations.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
29
app/finders/plugins/body_pattern.rb
Normal file
29
app/finders/plugins/body_pattern.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from Dynamic Finder 'BodyPattern'
|
||||
class BodyPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 30
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ String ] slug
|
||||
# @param [ String ] klass
|
||||
# @param [ Hash ] config The related dynamic finder config hash
|
||||
#
|
||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||
def process_response(opts, response, slug, klass, config)
|
||||
return unless response.body =~ config['pattern']
|
||||
|
||||
Model::Plugin.new(
|
||||
slug,
|
||||
target,
|
||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/finders/plugins/comment.rb
Normal file
33
app/finders/plugins/comment.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from the Dynamic Finder 'Comment'
|
||||
class Comment < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 30
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ String ] slug
|
||||
# @param [ String ] klass
|
||||
# @param [ Hash ] config The related dynamic finder config hash
|
||||
#
|
||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||
def process_response(opts, response, slug, klass, config)
|
||||
response.html.xpath(config['xpath'] || '//comment()').each do |node|
|
||||
comment = node.text.to_s.strip
|
||||
|
||||
next unless comment =~ config['pattern']
|
||||
|
||||
return Model::Plugin.new(
|
||||
slug,
|
||||
target,
|
||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/finders/plugins/config_parser.rb
Normal file
33
app/finders/plugins/config_parser.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from Dynamic Finder 'ConfigParser'
|
||||
class ConfigParser < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 40
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ String ] slug
|
||||
# @param [ String ] klass
|
||||
# @param [ Hash ] config The related dynamic finder config hash
|
||||
#
|
||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||
def _process_response(_opts, _response, slug, klass, config)
|
||||
#
|
||||
# TODO. Currently not implemented, and not even loaded by the Finders, as this
|
||||
# finder only has an aggressive method, which has been disabled (globally)
|
||||
# when checking for plugins
|
||||
#
|
||||
|
||||
Model::Plugin.new(
|
||||
slug,
|
||||
target,
|
||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
app/finders/plugins/header_pattern.rb
Normal file
43
app/finders/plugins/header_pattern.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from Dynamic Finder 'HeaderPattern'
|
||||
class HeaderPattern < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 30
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<Plugin> ]
|
||||
def passive(opts = {})
|
||||
found = []
|
||||
headers = target.homepage_res.headers
|
||||
|
||||
return found if headers.empty?
|
||||
|
||||
DB::DynamicFinders::Plugin.passive_header_pattern_finder_configs.each do |slug, configs|
|
||||
configs.each do |klass, config|
|
||||
next unless headers[config['header']] && headers[config['header']].to_s =~ config['pattern']
|
||||
|
||||
found << Model::Plugin.new(
|
||||
slug,
|
||||
target,
|
||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ nil ]
|
||||
def aggressive(_opts = {})
|
||||
# None
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
31
app/finders/plugins/javascript_var.rb
Normal file
31
app/finders/plugins/javascript_var.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from the Dynamic Finder 'JavascriptVar'
|
||||
class JavascriptVar < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 60
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ String ] slug
|
||||
# @param [ String ] klass
|
||||
# @param [ Hash ] config The related dynamic finder config hash
|
||||
#
|
||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||
def process_response(opts, response, slug, klass, config)
|
||||
response.html.xpath(config['xpath'] || '//script[not(@src)]').each do |node|
|
||||
next if config['pattern'] && !node.text.match(config['pattern'])
|
||||
|
||||
return Model::Plugin.new(
|
||||
slug,
|
||||
target,
|
||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
app/finders/plugins/known_locations.rb
Normal file
50
app/finders/plugins/known_locations.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Known Locations Plugins Finder
|
||||
class KnownLocations < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
# @return [ Array<Integer> ]
|
||||
def valid_response_codes
|
||||
@valid_response_codes ||= [200, 401, 403, 500].freeze
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list
|
||||
#
|
||||
# @return [ Array<Plugin> ]
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def target_urls(opts = {})
|
||||
slugs = opts[:list] || DB::Plugins.vulnerable_slugs
|
||||
urls = {}
|
||||
|
||||
slugs.each do |slug|
|
||||
urls[target.plugin_url(slug)] = slug
|
||||
end
|
||||
|
||||
urls
|
||||
end
|
||||
|
||||
def create_progress_bar(opts = {})
|
||||
super(opts.merge(title: ' Checking Known Locations -'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
app/finders/plugins/query_parameter.rb
Normal file
27
app/finders/plugins/query_parameter.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from Dynamic Finder 'QueryParameter'
|
||||
class QueryParameter < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 10
|
||||
|
||||
def passive(_opts = {})
|
||||
# Handled by UrlsInHomePage, so no need to check this twice
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ String ] slug
|
||||
# @param [ String ] klass
|
||||
# @param [ Hash ] config The related dynamic finder config hash
|
||||
#
|
||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||
def process_response(opts, response, slug, klass, config)
|
||||
# TODO: when a real case will be found
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
27
app/finders/plugins/urls_in_homepage.rb
Normal file
27
app/finders/plugins/urls_in_homepage.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# URLs In Homepage Finder
|
||||
# Typically, the items detected from URLs like
|
||||
# /wp-content/plugins/<slug>/
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<Plugin> ]
|
||||
def passive(opts = {})
|
||||
found = []
|
||||
|
||||
(items_from_links('plugins') + items_from_codes('plugins')).uniq.sort.each do |slug|
|
||||
found << Model::Plugin.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
31
app/finders/plugins/xpath.rb
Normal file
31
app/finders/plugins/xpath.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Plugins
|
||||
# Plugins finder from the Dynamic Finder 'Xpath'
|
||||
class Xpath < WPScan::Finders::DynamicFinder::WpItems::Finder
|
||||
DEFAULT_CONFIDENCE = 40
|
||||
|
||||
# @param [ Hash ] opts The options from the #passive, #aggressive methods
|
||||
# @param [ Typhoeus::Response ] response
|
||||
# @param [ String ] slug
|
||||
# @param [ String ] klass
|
||||
# @param [ Hash ] config The related dynamic finder config hash
|
||||
#
|
||||
# @return [ Plugin ] The detected plugin in the response, related to the config
|
||||
def process_response(opts, response, slug, klass, config)
|
||||
response.html.xpath(config['xpath']).each do |node|
|
||||
next if config['pattern'] && !node.text.match(config['pattern'])
|
||||
|
||||
return Model::Plugin.new(
|
||||
slug,
|
||||
target,
|
||||
opts.merge(found_by: found_by(klass), confidence: config['confidence'] || DEFAULT_CONFIDENCE)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
app/finders/theme_version.rb
Normal file
43
app/finders/theme_version.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'theme_version/style'
|
||||
require_relative 'theme_version/woo_framework_meta_generator'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module ThemeVersion
|
||||
# Theme Version Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::UniqueFinder
|
||||
|
||||
# @param [ Model::Theme ] theme
|
||||
def initialize(theme)
|
||||
finders <<
|
||||
ThemeVersion::Style.new(theme) <<
|
||||
ThemeVersion::WooFrameworkMetaGenerator.new(theme)
|
||||
|
||||
load_specific_finders(theme)
|
||||
end
|
||||
|
||||
# Load the finders associated with the theme
|
||||
#
|
||||
# @param [ Model::Theme ] theme
|
||||
def load_specific_finders(theme)
|
||||
module_name = theme.classify
|
||||
|
||||
return unless Finders::ThemeVersion.constants.include?(module_name)
|
||||
|
||||
mod = Finders::ThemeVersion.const_get(module_name)
|
||||
|
||||
mod.constants.each do |constant|
|
||||
c = mod.const_get(constant)
|
||||
|
||||
next unless c.is_a?(Class)
|
||||
|
||||
finders << c.new(theme)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
45
app/finders/theme_version/style.rb
Normal file
45
app/finders/theme_version/style.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module ThemeVersion
|
||||
# Theme Version Finder from the style.css file
|
||||
class Style < CMSScanner::Finders::Finder
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Version ]
|
||||
def passive(_opts = {})
|
||||
return unless cached_style?
|
||||
|
||||
style_version
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Version ]
|
||||
def aggressive(_opts = {})
|
||||
return if cached_style?
|
||||
|
||||
style_version
|
||||
end
|
||||
|
||||
# @return [ Boolean ]
|
||||
def cached_style?
|
||||
Typhoeus::Config.cache.get(browser.forge_request(target.style_url)) ? true : false
|
||||
end
|
||||
|
||||
# @return [ Version ]
|
||||
def style_version
|
||||
return unless Browser.get(target.style_url).body =~ /Version:[\t ]*(?!trunk)([0-9a-z\.-]+)/i
|
||||
|
||||
Model::Version.new(
|
||||
Regexp.last_match[1],
|
||||
found_by: found_by,
|
||||
confidence: 80,
|
||||
interesting_entries: ["#{target.style_url}, Match: '#{Regexp.last_match}'"]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
21
app/finders/theme_version/woo_framework_meta_generator.rb
Normal file
21
app/finders/theme_version/woo_framework_meta_generator.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module ThemeVersion
|
||||
# Theme Version Finder from the WooFramework generators
|
||||
class WooFrameworkMetaGenerator < CMSScanner::Finders::Finder
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Version ]
|
||||
def passive(_opts = {})
|
||||
return unless target.blog.homepage_res.body =~ Finders::MainTheme::WooFrameworkMetaGenerator::PATTERN
|
||||
|
||||
return unless Regexp.last_match[1] == target.slug
|
||||
|
||||
Model::Version.new(Regexp.last_match[2], found_by: found_by, confidence: 80)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
app/finders/themes.rb
Normal file
22
app/finders/themes.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'themes/urls_in_homepage'
|
||||
require_relative 'themes/known_locations'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Themes
|
||||
# themes Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders <<
|
||||
Themes::UrlsInHomepage.new(target) <<
|
||||
Themes::KnownLocations.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
app/finders/themes/known_locations.rb
Normal file
50
app/finders/themes/known_locations.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Themes
|
||||
# Known Locations Themes Finder
|
||||
class KnownLocations < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
# @return [ Array<Integer> ]
|
||||
def valid_response_codes
|
||||
@valid_response_codes ||= [200, 401, 403, 500].freeze
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list
|
||||
#
|
||||
# @return [ Array<Theme> ]
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |_res, slug|
|
||||
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def target_urls(opts = {})
|
||||
slugs = opts[:list] || DB::Themes.vulnerable_slugs
|
||||
urls = {}
|
||||
|
||||
slugs.each do |slug|
|
||||
urls[target.theme_url(slug)] = slug
|
||||
end
|
||||
|
||||
urls
|
||||
end
|
||||
|
||||
def create_progress_bar(opts = {})
|
||||
super(opts.merge(title: ' Checking Known Locations -'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
25
app/finders/themes/urls_in_homepage.rb
Normal file
25
app/finders/themes/urls_in_homepage.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Themes
|
||||
# URLs In Homepage Finder
|
||||
class UrlsInHomepage < CMSScanner::Finders::Finder
|
||||
include WpItems::URLsInHomepage
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<Theme> ]
|
||||
def passive(opts = {})
|
||||
found = []
|
||||
|
||||
(items_from_links('themes') + items_from_codes('themes')).uniq.sort.each do |slug|
|
||||
found << Model::Theme.new(slug, target, opts.merge(found_by: found_by, confidence: 80))
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/finders/timthumb_version.rb
Normal file
19
app/finders/timthumb_version.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'timthumb_version/bad_request'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module TimthumbVersion
|
||||
# Timthumb Version Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::UniqueFinder
|
||||
|
||||
# @param [ Model::Timthumb ] target
|
||||
def initialize(target)
|
||||
finders << TimthumbVersion::BadRequest.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
23
app/finders/timthumb_version/bad_request.rb
Normal file
23
app/finders/timthumb_version/bad_request.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module TimthumbVersion
|
||||
# Timthumb Version Finder from the body of a bad request
|
||||
# See https://code.google.com/p/timthumb/source/browse/trunk/timthumb.php#435
|
||||
class BadRequest < CMSScanner::Finders::Finder
|
||||
# @return [ Version ]
|
||||
def aggressive(_opts = {})
|
||||
return unless Browser.get(target.url).body =~ /(TimThumb version\s*: ([^<]+))/
|
||||
|
||||
Model::Version.new(
|
||||
Regexp.last_match[2],
|
||||
found_by: 'Bad Request (Aggressive Detection)',
|
||||
confidence: 90,
|
||||
interesting_entries: ["#{target.url}, Match: '#{Regexp.last_match[1]}'"]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/finders/timthumbs.rb
Normal file
19
app/finders/timthumbs.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'timthumbs/known_locations'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Timthumbs
|
||||
# Timthumbs Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders << Timthumbs::KnownLocations.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
65
app/finders/timthumbs/known_locations.rb
Normal file
65
app/finders/timthumbs/known_locations.rb
Normal file
@@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Timthumbs
|
||||
# Known Locations Timthumbs Finder
|
||||
# Note: A vulnerable version, 2.8.13 can be found here:
|
||||
# https://github.com/GabrielGil/TimThumb/blob/980c3d6a823477761570475e8b83d3e9fcd2d7ae/timthumb.php
|
||||
class KnownLocations < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
# @return [ Array<Integer> ]
|
||||
def valid_response_codes
|
||||
@valid_response_codes ||= [400]
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list Mandatory
|
||||
#
|
||||
# @return [ Array<Timthumb> ]
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: 400)) do |res|
|
||||
next unless res.body =~ /no image specified/i
|
||||
|
||||
found << Model::Timthumb.new(res.request.url, opts.merge(found_by: found_by, confidence: 100))
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list Mandatory
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def target_urls(opts = {})
|
||||
urls = {}
|
||||
|
||||
File.open(opts[:list]).each_with_index do |path, index|
|
||||
urls[target.url(path.chomp)] = index
|
||||
end
|
||||
|
||||
# Add potential timthumbs located in the main theme
|
||||
if target.main_theme
|
||||
main_theme_timthumbs_paths.each do |path|
|
||||
urls[target.main_theme.url(path)] = 1 # index not important there
|
||||
end
|
||||
end
|
||||
|
||||
urls
|
||||
end
|
||||
|
||||
def main_theme_timthumbs_paths
|
||||
%w[timthumb.php lib/timthumb.php inc/timthumb.php includes/timthumb.php
|
||||
scripts/timthumb.php tools/timthumb.php functions/timthumb.php]
|
||||
end
|
||||
|
||||
def create_progress_bar(opts = {})
|
||||
super(opts.merge(title: ' Checking Known Locations -'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
32
app/finders/users.rb
Normal file
32
app/finders/users.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'users/author_posts'
|
||||
require_relative 'users/wp_json_api'
|
||||
require_relative 'users/oembed_api'
|
||||
require_relative 'users/rss_generator'
|
||||
require_relative 'users/author_id_brute_forcing'
|
||||
require_relative 'users/login_error_messages'
|
||||
require_relative 'users/yoast_seo_author_sitemap.rb'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Users
|
||||
# Users Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::SameTypeFinder
|
||||
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
finders <<
|
||||
Users::AuthorPosts.new(target) <<
|
||||
Users::WpJsonApi.new(target) <<
|
||||
Users::OembedApi.new(target) <<
|
||||
Users::RSSGenerator.new(target) <<
|
||||
Users::YoastSeoAuthorSitemap.new(target) <<
|
||||
Users::AuthorIdBruteForcing.new(target) <<
|
||||
Users::LoginErrorMessages.new(target)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
118
app/finders/users/author_id_brute_forcing.rb
Normal file
118
app/finders/users/author_id_brute_forcing.rb
Normal file
@@ -0,0 +1,118 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Users
|
||||
# Author Id Brute Forcing
|
||||
class AuthorIdBruteForcing < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Enumerator
|
||||
|
||||
# @return [ Array<Integer> ]
|
||||
def valid_response_codes
|
||||
@valid_response_codes ||= [200, 301, 302]
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ Range ] :range Mandatory
|
||||
#
|
||||
# @return [ Array<User> ]
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
found_by_msg = 'Author Id Brute Forcing - %s (Aggressive Detection)'
|
||||
|
||||
enumerate(target_urls(opts), opts.merge(check_full_response: true)) do |res, id|
|
||||
username, found_by, confidence = potential_username(res)
|
||||
|
||||
next unless username
|
||||
|
||||
found << Model::User.new(
|
||||
username,
|
||||
id: id,
|
||||
found_by: format(found_by_msg, found_by),
|
||||
confidence: confidence
|
||||
)
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ Range ] :range
|
||||
#
|
||||
# @return [ Hash ]
|
||||
def target_urls(opts = {})
|
||||
urls = {}
|
||||
|
||||
opts[:range].each do |id|
|
||||
urls[target.uri.join("?author=#{id}").to_s] = id
|
||||
end
|
||||
|
||||
urls
|
||||
end
|
||||
|
||||
def create_progress_bar(opts = {})
|
||||
super(opts.merge(title: ' Brute Forcing Author IDs -'))
|
||||
end
|
||||
|
||||
def full_request_params
|
||||
{ followlocation: true }
|
||||
end
|
||||
|
||||
# @param [ Typhoeus::Response ] res
|
||||
#
|
||||
# @return [ Array<String, String, Integer>, nil ] username, found_by, confidence
|
||||
def potential_username(res)
|
||||
username = username_from_author_url(res.effective_url) || username_from_response(res)
|
||||
|
||||
return username, 'Author Pattern', 100 if username
|
||||
|
||||
username = display_name_from_body(res.body)
|
||||
|
||||
return username, 'Display Name', 50 if username
|
||||
end
|
||||
|
||||
# @param [ String ] url
|
||||
#
|
||||
# @return [ String, nil ]
|
||||
def username_from_author_url(url)
|
||||
url[%r{/author/([^/\b]+)/?}i, 1]
|
||||
end
|
||||
|
||||
# @param [ Typhoeus::Response ] res
|
||||
#
|
||||
# @return [ String, nil ] The username found
|
||||
def username_from_response(res)
|
||||
# Permalink enabled
|
||||
target.in_scope_uris(res, '//link/@href|//a/@href') do |uri|
|
||||
username = username_from_author_url(uri.to_s)
|
||||
return username if username
|
||||
end
|
||||
|
||||
# No permalink
|
||||
res.body[/<body class="archive author author-([^\s]+)[ "]/i, 1]
|
||||
end
|
||||
|
||||
# @param [ String ] body
|
||||
#
|
||||
# @return [ String, nil ]
|
||||
def display_name_from_body(body)
|
||||
page = Nokogiri::HTML.parse(body)
|
||||
# WP >= 3.0
|
||||
page.css('h1.page-title span').each do |node|
|
||||
return node.text.to_s
|
||||
end
|
||||
|
||||
# WP < 3.0
|
||||
page.xpath('//link[@rel="alternate" and @type="application/rss+xml"]').each do |node|
|
||||
title = node['title']
|
||||
|
||||
next unless title =~ /Posts by (.*) Feed\z/i
|
||||
|
||||
return Regexp.last_match[1] unless Regexp.last_match[1].empty?
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
61
app/finders/users/author_posts.rb
Normal file
61
app/finders/users/author_posts.rb
Normal file
@@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Users
|
||||
# Author Posts
|
||||
class AuthorPosts < CMSScanner::Finders::Finder
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<User> ]
|
||||
def passive(opts = {})
|
||||
found_by_msg = 'Author Posts - %s (Passive Detection)'
|
||||
|
||||
usernames(opts).reduce([]) do |a, e|
|
||||
a << Model::User.new(
|
||||
e[0],
|
||||
found_by: format(found_by_msg, e[1]),
|
||||
confidence: e[2]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<Array>> ]
|
||||
def usernames(_opts = {})
|
||||
found = potential_usernames(target.homepage_res)
|
||||
|
||||
return found unless found.empty?
|
||||
|
||||
target.homepage_res.html.css('header.entry-header a').each do |post_url_node|
|
||||
url = post_url_node['href']
|
||||
|
||||
next if url.nil? || url.empty?
|
||||
|
||||
found += potential_usernames(Browser.get(url))
|
||||
end
|
||||
|
||||
found.compact.uniq
|
||||
end
|
||||
|
||||
# @param [ Typhoeus::Response ] res
|
||||
#
|
||||
# @return [ Array<Array> ]
|
||||
def potential_usernames(res)
|
||||
usernames = []
|
||||
|
||||
target.in_scope_uris(res, '//a/@href') do |uri, node|
|
||||
if uri.path =~ %r{/author/([^/\b]+)/?\z}i
|
||||
usernames << [Regexp.last_match[1], 'Author Pattern', 100]
|
||||
elsif /author=[0-9]+/.match?(uri.query)
|
||||
usernames << [node.text.to_s.strip, 'Display Name', 30]
|
||||
end
|
||||
end
|
||||
|
||||
usernames.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
47
app/finders/users/login_error_messages.rb
Normal file
47
app/finders/users/login_error_messages.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Users
|
||||
# Login Error Messages
|
||||
#
|
||||
# Existing username:
|
||||
# WP < 3.1 - Incorrect password.
|
||||
# WP >= 3.1 - The password you entered for the username admin is incorrect.
|
||||
# Non existent username: Invalid username.
|
||||
#
|
||||
class LoginErrorMessages < CMSScanner::Finders::Finder
|
||||
# @param [ Hash ] opts
|
||||
# @option opts [ String ] :list
|
||||
#
|
||||
# @return [ Array<User> ]
|
||||
def aggressive(opts = {})
|
||||
found = []
|
||||
|
||||
usernames(opts).each do |username|
|
||||
res = target.do_login(username, SecureRandom.hex[0, 8])
|
||||
error = res.html.css('div#login_error').text.strip
|
||||
|
||||
return found if error.empty? # Protection plugin / error disabled
|
||||
|
||||
next unless error =~ /The password you entered for the username|Incorrect Password/i
|
||||
|
||||
found << Model::User.new(username, found_by: found_by, confidence: 100)
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] List of usernames to check
|
||||
def usernames(opts = {})
|
||||
# usernames from the potential Users found
|
||||
unames = opts[:found].map(&:username)
|
||||
|
||||
[*opts[:list]].each { |uname| unames << uname.chomp }
|
||||
|
||||
unames.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
57
app/finders/users/oembed_api.rb
Normal file
57
app/finders/users/oembed_api.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Users
|
||||
# Since WP 4.4, the oembed API can disclose a user
|
||||
# https://github.com/wpscanteam/wpscan/issues/1049
|
||||
class OembedApi < CMSScanner::Finders::Finder
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<User> ]
|
||||
def passive(_opts = {})
|
||||
# TODO: get the api_url from the Homepage and query it if present,
|
||||
# then discard the aggressive check if same/similar URL
|
||||
end
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<User> ]
|
||||
def aggressive(_opts = {})
|
||||
oembed_data = JSON.parse(Browser.get(api_url).body)
|
||||
details = user_details_from_oembed_data(oembed_data)
|
||||
|
||||
return [] unless details
|
||||
|
||||
[Model::User.new(details[0],
|
||||
found_by: format(found_by_msg, details[1]),
|
||||
confidence: details[2],
|
||||
interesting_entries: [api_url])]
|
||||
rescue JSON::ParserError
|
||||
[]
|
||||
end
|
||||
|
||||
def user_details_from_oembed_data(oembed_data)
|
||||
return unless oembed_data
|
||||
|
||||
if oembed_data['author_url'] =~ %r{/author/([^/]+)/?\z}
|
||||
details = [Regexp.last_match[1], 'Author URL', 90]
|
||||
elsif oembed_data['author_name'] && !oembed_data['author_name'].empty?
|
||||
details = [oembed_data['author_name'], 'Author Name', 70]
|
||||
end
|
||||
|
||||
details
|
||||
end
|
||||
|
||||
def found_by_msg
|
||||
'Oembed API - %s (Aggressive Detection)'
|
||||
end
|
||||
|
||||
# @return [ String ] The URL of the API listing the Users
|
||||
def api_url
|
||||
@api_url ||= target.url("wp-json/oembed/1.0/embed?url=#{target.url}&format=json")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
46
app/finders/users/rss_generator.rb
Normal file
46
app/finders/users/rss_generator.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Users
|
||||
# Users disclosed from the dc:creator field in the RSS
|
||||
# The names disclosed are display names, however depending on the configuration of the blog,
|
||||
# they can be the same than usernames
|
||||
class RSSGenerator < WPScan::Finders::WpVersion::RSSGenerator
|
||||
def process_urls(urls, _opts = {})
|
||||
found = []
|
||||
|
||||
urls.each do |url|
|
||||
res = Browser.get_and_follow_location(url)
|
||||
|
||||
next unless res.code == 200 && res.body =~ /<dc\:creator>/i
|
||||
|
||||
potential_usernames = []
|
||||
|
||||
begin
|
||||
res.xml.xpath('//item/dc:creator').each do |node|
|
||||
username = node.text.to_s
|
||||
|
||||
# Ignoring potential username longer than 60 characters and containing accents
|
||||
# as they are considered invalid. See https://github.com/wpscanteam/wpscan/issues/1215
|
||||
next if username.strip.empty? || username.length > 60 || username =~ /[^\x00-\x7F]/
|
||||
|
||||
potential_usernames << username
|
||||
end
|
||||
rescue Nokogiri::XML::XPath::SyntaxError
|
||||
next
|
||||
end
|
||||
|
||||
potential_usernames.uniq.each do |username|
|
||||
found << Model::User.new(username, found_by: found_by, confidence: 50)
|
||||
end
|
||||
|
||||
break
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
69
app/finders/users/wp_json_api.rb
Normal file
69
app/finders/users/wp_json_api.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Users
|
||||
# WP JSON API
|
||||
#
|
||||
# Since 4.7 - Need more investigation as it seems WP 4.7.1 reduces the exposure, see https://github.com/wpscanteam/wpscan/issues/1038)
|
||||
# For the pagination, see https://github.com/wpscanteam/wpscan/issues/1285
|
||||
#
|
||||
class WpJsonApi < CMSScanner::Finders::Finder
|
||||
MAX_PER_PAGE = 100 # See https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/
|
||||
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<User> ]
|
||||
def aggressive(_opts = {})
|
||||
found = []
|
||||
current_page = 0
|
||||
|
||||
loop do
|
||||
current_page += 1
|
||||
|
||||
res = Typhoeus.get(api_url, params: { per_page: MAX_PER_PAGE, page: current_page })
|
||||
|
||||
total_pages ||= res.headers['X-WP-TotalPages'].to_i
|
||||
|
||||
users_in_page = users_from_response(res)
|
||||
found += users_in_page
|
||||
|
||||
break if current_page >= total_pages || users_in_page.empty?
|
||||
end
|
||||
|
||||
found
|
||||
rescue JSON::ParserError, TypeError
|
||||
found
|
||||
end
|
||||
|
||||
# @param [ Typhoeus::Response ] response
|
||||
#
|
||||
# @return [ Array<User> ] The users from the response
|
||||
def users_from_response(response)
|
||||
found = []
|
||||
|
||||
JSON.parse(response.body)&.each do |user|
|
||||
found << Model::User.new(user['slug'],
|
||||
id: user['id'],
|
||||
found_by: found_by,
|
||||
confidence: 100,
|
||||
interesting_entries: [response.effective_url])
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ String ] The URL of the API listing the Users
|
||||
def api_url
|
||||
return @api_url if @api_url
|
||||
|
||||
target.in_scope_uris(target.homepage_res, "//link[@rel='https://api.w.org/']/@href").each do |uri|
|
||||
return @api_url = uri.join('wp/v2/users/').to_s if uri.path.include?('wp-json')
|
||||
end
|
||||
|
||||
@api_url = target.url('wp-json/wp/v2/users/')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/finders/users/yoast_seo_author_sitemap.rb
Normal file
36
app/finders/users/yoast_seo_author_sitemap.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module Users
|
||||
# The YOAST SEO plugin has an author-sitemap.xml which can leak usernames
|
||||
# See https://github.com/wpscanteam/wpscan/issues/1228
|
||||
class YoastSeoAuthorSitemap < CMSScanner::Finders::Finder
|
||||
# @param [ Hash ] opts
|
||||
#
|
||||
# @return [ Array<User> ]
|
||||
def aggressive(_opts = {})
|
||||
found = []
|
||||
|
||||
Browser.get(sitemap_url).html.xpath('//url/loc').each do |user_tag|
|
||||
username = user_tag.text.to_s[%r{/author/([^\/]+)/}, 1]
|
||||
|
||||
next unless username && !username.strip.empty?
|
||||
|
||||
found << Model::User.new(username,
|
||||
found_by: found_by,
|
||||
confidence: 100,
|
||||
interesting_entries: [sitemap_url])
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
# @return [ String ] The URL of the author-sitemap
|
||||
def sitemap_url
|
||||
@sitemap_url ||= target.url('author-sitemap.xml')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
3
app/finders/wp_items.rb
Normal file
3
app/finders/wp_items.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'wp_items/urls_in_homepage'
|
||||
70
app/finders/wp_items/urls_in_homepage.rb
Normal file
70
app/finders/wp_items/urls_in_homepage.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module WpItems
|
||||
# URLs In Homepage Module to use in plugins & themes finders
|
||||
module URLsInHomepage
|
||||
# @param [ String ] type plugins / themes
|
||||
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
|
||||
#
|
||||
# @return [Array<String> ] The plugins/themes detected in the href, src attributes of the homepage
|
||||
def items_from_links(type, uniq = true)
|
||||
found = []
|
||||
|
||||
target.in_scope_uris(target.homepage_res) do |uri|
|
||||
next unless uri.to_s =~ item_attribute_pattern(type)
|
||||
|
||||
found << Regexp.last_match[1]
|
||||
end
|
||||
|
||||
uniq ? found.uniq.sort : found.sort
|
||||
end
|
||||
|
||||
# @param [ String ] type plugins / themes
|
||||
# @param [ Boolean ] uniq Wether or not to apply the #uniq on the results
|
||||
#
|
||||
# @return [Array<String> ] The plugins/themes detected in the javascript/style of the homepage
|
||||
def items_from_codes(type, uniq = true)
|
||||
found = []
|
||||
|
||||
target.homepage_res.html.css('script,style').each do |tag|
|
||||
code = tag.text.to_s
|
||||
next if code.empty?
|
||||
|
||||
code.scan(item_code_pattern(type)).flatten.uniq.each { |slug| found << slug }
|
||||
end
|
||||
|
||||
uniq ? found.uniq.sort : found.sort
|
||||
end
|
||||
|
||||
# @param [ String ] type
|
||||
#
|
||||
# @return [ Regexp ]
|
||||
def item_attribute_pattern(type)
|
||||
@item_attribute_pattern ||= %r{\A#{item_url_pattern(type)}([^/]+)/}i
|
||||
end
|
||||
|
||||
# @param [ String ] type
|
||||
#
|
||||
# @return [ Regexp ]
|
||||
def item_code_pattern(type)
|
||||
@item_code_pattern ||= %r{["'\( ]#{item_url_pattern(type)}([^\\\/\)"']+)}i
|
||||
end
|
||||
|
||||
# @param [ String ] type
|
||||
#
|
||||
# @return [ Regexp ]
|
||||
def item_url_pattern(type)
|
||||
item_dir = type == 'plugins' ? target.plugins_dir : target.content_dir
|
||||
item_url = type == 'plugins' ? target.plugins_url : target.content_url
|
||||
|
||||
url = /#{item_url.gsub(/\A(?:http|https)/i, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
item_dir = %r{(?:#{url}|\\?\/#{item_dir.gsub('/', '\\\\\?\/')}\\?/)}i
|
||||
|
||||
type == 'plugins' ? item_dir : %r{#{item_dir}#{type}\\?\/}i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
44
app/finders/wp_version.rb
Normal file
44
app/finders/wp_version.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'wp_version/rss_generator'
|
||||
require_relative 'wp_version/atom_generator'
|
||||
require_relative 'wp_version/rdf_generator'
|
||||
require_relative 'wp_version/readme'
|
||||
require_relative 'wp_version/unique_fingerprinting'
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
# Specific Finders container to filter the version detected
|
||||
# and remove the one with low confidence to avoid false
|
||||
# positive when there is not enought information to accurately
|
||||
# determine it.
|
||||
class WpVersionFinders < UniqueFinders
|
||||
def filter_findings
|
||||
best_finding = super
|
||||
|
||||
best_finding && best_finding.confidence >= 40 ? best_finding : false
|
||||
end
|
||||
end
|
||||
|
||||
module WpVersion
|
||||
# Wp Version Finder
|
||||
class Base
|
||||
include CMSScanner::Finders::UniqueFinder
|
||||
|
||||
# @param [ WPScan::Target ] target
|
||||
def initialize(target)
|
||||
(%w[RSSGenerator AtomGenerator RDFGenerator] +
|
||||
WPScan::DB::DynamicFinders::Wordpress.versions_finders_configs.keys +
|
||||
%w[Readme UniqueFingerprinting]
|
||||
).each do |finder_name|
|
||||
finders << WpVersion.const_get(finder_name.to_sym).new(target)
|
||||
end
|
||||
end
|
||||
|
||||
def finders
|
||||
@finders ||= Finders::WpVersionFinders.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
42
app/finders/wp_version/atom_generator.rb
Normal file
42
app/finders/wp_version/atom_generator.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module WpVersion
|
||||
# Atom Generator Version Finder
|
||||
class AtomGenerator < CMSScanner::Finders::Finder
|
||||
include Finder::WpVersion::SmartURLChecker
|
||||
|
||||
def process_urls(urls, _opts = {})
|
||||
found = Findings.new
|
||||
|
||||
urls.each do |url|
|
||||
res = Browser.get_and_follow_location(url)
|
||||
|
||||
res.html.css('generator').each do |node|
|
||||
next unless node.text.to_s.strip.casecmp('wordpress').zero?
|
||||
|
||||
found << create_version(
|
||||
node['version'],
|
||||
found_by: found_by,
|
||||
entries: ["#{res.effective_url}, #{node.to_s.strip}"]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
def passive_urls_xpath
|
||||
'//link[@rel="alternate" and @type="application/atom+xml"]/@href'
|
||||
end
|
||||
|
||||
def aggressive_urls(_opts = {})
|
||||
%w[feed/atom/ ?feed=atom].reduce([]) do |a, uri|
|
||||
a << target.url(uri)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
40
app/finders/wp_version/rdf_generator.rb
Normal file
40
app/finders/wp_version/rdf_generator.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module WpVersion
|
||||
# RDF Generator Version Finder
|
||||
class RDFGenerator < CMSScanner::Finders::Finder
|
||||
include Finder::WpVersion::SmartURLChecker
|
||||
|
||||
def process_urls(urls, _opts = {})
|
||||
found = Findings.new
|
||||
|
||||
urls.each do |url|
|
||||
res = Browser.get_and_follow_location(url)
|
||||
|
||||
res.html.xpath('//generatoragent').each do |node|
|
||||
next unless node['rdf:resource'] =~ %r{\Ahttps?://wordpress\.(?:[a-z.]+)/\?v=(.*)\z}i
|
||||
|
||||
found << create_version(
|
||||
Regexp.last_match[1],
|
||||
found_by: found_by,
|
||||
entries: ["#{res.effective_url}, #{node.to_s.strip}"]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
def passive_urls_xpath
|
||||
'//a[contains(@href, "rdf")]/@href'
|
||||
end
|
||||
|
||||
def aggressive_urls(_opts = {})
|
||||
[target.url('feed/rdf/')]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
31
app/finders/wp_version/readme.rb
Normal file
31
app/finders/wp_version/readme.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module WpVersion
|
||||
# Readme Version Finder
|
||||
class Readme < CMSScanner::Finders::Finder
|
||||
# @return [ WpVersion ]
|
||||
def aggressive(_opts = {})
|
||||
readme_url = target.url('readme.html') # Maybe move this into the Target ?
|
||||
|
||||
node = Browser.get(readme_url).html.css('h1#logo').last
|
||||
|
||||
return unless node&.text.to_s.strip =~ /\AVersion (.*)\z/i
|
||||
|
||||
number = Regexp.last_match(1)
|
||||
|
||||
return unless Model::WpVersion.valid?(number)
|
||||
|
||||
Model::WpVersion.new(
|
||||
number,
|
||||
found_by: 'Readme (Aggressive Detection)',
|
||||
# Since WP 4.7, the Readme only contains the major version (ie 4.7, 4.8 etc)
|
||||
confidence: number >= '4.7' ? 10 : 90,
|
||||
interesting_entries: ["#{readme_url}, Match: '#{node.text.to_s.strip}'"]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
45
app/finders/wp_version/rss_generator.rb
Normal file
45
app/finders/wp_version/rss_generator.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module WpVersion
|
||||
# RSS Generator Version Finder
|
||||
class RSSGenerator < CMSScanner::Finders::Finder
|
||||
include Finder::WpVersion::SmartURLChecker
|
||||
|
||||
def process_urls(urls, _opts = {})
|
||||
found = Findings.new
|
||||
|
||||
urls.each do |url|
|
||||
res = Browser.get_and_follow_location(url)
|
||||
|
||||
res.html.xpath('//comment()[contains(., "wordpress")] | //generator').each do |node|
|
||||
node_text = node.text.to_s.strip
|
||||
|
||||
next unless node_text =~ %r{\Ahttps?://wordpress\.(?:[a-z]+)/\?v=(.*)\z}i ||
|
||||
node_text =~ %r{\Agenerator="wordpress/([^"]+)"\z}i
|
||||
|
||||
found << create_version(
|
||||
Regexp.last_match[1],
|
||||
found_by: found_by,
|
||||
entries: ["#{res.effective_url}, #{node.to_s.strip}"]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
found
|
||||
end
|
||||
|
||||
def passive_urls_xpath
|
||||
'//link[@rel="alternate" and @type="application/rss+xml"]/@href'
|
||||
end
|
||||
|
||||
def aggressive_urls(_opts = {})
|
||||
%w[feed/ comments/feed/ feed/rss/ feed/rss2/].reduce([]) do |a, uri|
|
||||
a << target.url(uri)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
32
app/finders/wp_version/unique_fingerprinting.rb
Normal file
32
app/finders/wp_version/unique_fingerprinting.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Finders
|
||||
module WpVersion
|
||||
# Unique Fingerprinting Version Finder
|
||||
class UniqueFingerprinting < CMSScanner::Finders::Finder
|
||||
include CMSScanner::Finders::Finder::Fingerprinter
|
||||
|
||||
# @return [ WpVersion ]
|
||||
def aggressive(opts = {})
|
||||
fingerprint(DB::Fingerprints.wp_unique_fingerprints, opts) do |version_number, url, md5sum|
|
||||
hydra.abort
|
||||
progress_bar.finish
|
||||
|
||||
return Model::WpVersion.new(
|
||||
version_number,
|
||||
found_by: 'Unique Fingerprinting (Aggressive Detection)',
|
||||
confidence: 100,
|
||||
interesting_entries: ["#{url} md5sum is #{md5sum}"]
|
||||
)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def create_progress_bar(opts = {})
|
||||
super(opts.merge(title: 'Fingerprinting the version -'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
18
app/models.rb
Normal file
18
app/models.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module WPScan
|
||||
module Model
|
||||
include CMSScanner::Model
|
||||
end
|
||||
end
|
||||
|
||||
require_relative 'models/interesting_finding'
|
||||
require_relative 'models/wp_version'
|
||||
require_relative 'models/xml_rpc'
|
||||
require_relative 'models/wp_item'
|
||||
require_relative 'models/timthumb'
|
||||
require_relative 'models/media'
|
||||
require_relative 'models/plugin'
|
||||
require_relative 'models/theme'
|
||||
require_relative 'models/config_backup'
|
||||
require_relative 'models/db_export'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user