Compare commits
730 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21221d48d0 | ||
|
|
1f1a190c84 | ||
|
|
82d79c4662 | ||
|
|
08771a6d5d | ||
|
|
e01d18f224 | ||
|
|
8496650542 | ||
|
|
399245cd0f | ||
|
|
adfa5dddcf | ||
|
|
85971e0e91 | ||
|
|
3a3376ec41 | ||
|
|
d988b6ccbf | ||
|
|
6654f446a4 | ||
|
|
88808db9a5 | ||
|
|
dfad0fd6bd | ||
|
|
3fe49a24c7 | ||
|
|
ac609445fb | ||
|
|
0223f74a53 | ||
|
|
607a5b3fda | ||
|
|
e3ac331a71 | ||
|
|
e09b4cc76d | ||
|
|
c24ed707ef | ||
|
|
a8c55ddee3 | ||
|
|
e080835224 | ||
|
|
2fe675abce | ||
|
|
d230221999 | ||
|
|
91a01265e5 | ||
|
|
77286301a7 | ||
|
|
7c39827c16 | ||
|
|
8f789994eb | ||
|
|
79cb9c8142 | ||
|
|
de1d047c08 | ||
|
|
8252cb486b | ||
|
|
fb8ad72335 | ||
|
|
bc4f0c002b | ||
|
|
0a53c52645 | ||
|
|
7941a8accb | ||
|
|
5389923b34 | ||
|
|
9c1149cb25 | ||
|
|
c5130de805 | ||
|
|
020633503b | ||
|
|
74b9776801 | ||
|
|
5a605d686c | ||
|
|
4ba9bdf605 | ||
|
|
3f647348c3 | ||
|
|
de4f90dd72 | ||
|
|
4a7b4754f0 | ||
|
|
fe05534a95 | ||
|
|
c7c7e75b32 | ||
|
|
efc6aed388 | ||
|
|
197521d5b1 | ||
|
|
23420f62df | ||
|
|
33149caede | ||
|
|
67bec7136b | ||
|
|
57a12114dc | ||
|
|
e32abea46b | ||
|
|
b12b271a61 | ||
|
|
f337cccc68 | ||
|
|
7f9e178f75 | ||
|
|
b19696090f | ||
|
|
d7488bd402 | ||
|
|
604299a1ac | ||
|
|
6800d51347 | ||
|
|
7cecd249a8 | ||
|
|
a214ea9341 | ||
|
|
884a19b13d | ||
|
|
771f4ae766 | ||
|
|
9273398c0e | ||
|
|
a5ed6ad134 | ||
|
|
1bbf575e91 | ||
|
|
49582fd841 | ||
|
|
bdaf12c1fa | ||
|
|
ef27c98056 | ||
|
|
722f3ce384 | ||
|
|
9d084a7b2f | ||
|
|
c31a06e255 | ||
|
|
ea36c79c26 | ||
|
|
cbe33caeef | ||
|
|
8b44354fec | ||
|
|
619302cd11 | ||
|
|
3e94ca11df | ||
|
|
f818778e0a | ||
|
|
280a91f139 | ||
|
|
82367a81c9 | ||
|
|
93b1234d0f | ||
|
|
571bc5cf90 | ||
|
|
91de353307 | ||
|
|
7ec394a8f2 | ||
|
|
451c6c07ca | ||
|
|
a6b0548426 | ||
|
|
f89463c4d8 | ||
|
|
3be63d85f2 | ||
|
|
d271b63aa4 | ||
|
|
fb46fd7101 | ||
|
|
64513bb9d1 | ||
|
|
ef56f82de9 | ||
|
|
f775379f42 | ||
|
|
1b377dd674 | ||
|
|
99837127a6 | ||
|
|
f2d205e576 | ||
|
|
bf5bde0e36 | ||
|
|
18314adce2 | ||
|
|
d1a7a0ee1f | ||
|
|
eb73025338 | ||
|
|
66cd3e08a0 | ||
|
|
aa8e525681 | ||
|
|
7a36f89124 | ||
|
|
3e56acab64 | ||
|
|
012670b349 | ||
|
|
44cb13644a | ||
|
|
bd8e6db092 | ||
|
|
96ae8ade5d | ||
|
|
04b1cee71e | ||
|
|
bd07cf859f | ||
|
|
e937906647 | ||
|
|
03618f38b5 | ||
|
|
94fdddb056 | ||
|
|
12dfc60f75 | ||
|
|
a383d12061 | ||
|
|
3131c6cb5d | ||
|
|
5f53297f58 | ||
|
|
cebd808674 | ||
|
|
30a07f037e | ||
|
|
4ef1387781 | ||
|
|
1578ce2ebd | ||
|
|
391fd6c960 | ||
|
|
ef7ac1d77b | ||
|
|
ca2610d74f | ||
|
|
8d8aa52b9b | ||
|
|
84ec0c3964 | ||
|
|
f55736599e | ||
|
|
b890235a82 | ||
|
|
2cc3bc5759 | ||
|
|
ca100ef7e9 | ||
|
|
721cad75a2 | ||
|
|
c3110a4ab7 | ||
|
|
452aabf89b | ||
|
|
adcd6734ef | ||
|
|
a68c1f1cf7 | ||
|
|
712eaf9f1e | ||
|
|
7e119fa2ac | ||
|
|
ac90ad0129 | ||
|
|
6b61e273a0 | ||
|
|
aab8e85f9d | ||
|
|
3959892c20 | ||
|
|
420ad6cd37 | ||
|
|
664bff544e | ||
|
|
6716de6635 | ||
|
|
4f50fbdfe4 | ||
|
|
009abb3fd5 | ||
|
|
191b4402e1 | ||
|
|
13bc347897 | ||
|
|
187e2f1330 | ||
|
|
8d2ec115f5 | ||
|
|
921596f6f8 | ||
|
|
a00987efc8 | ||
|
|
b1a35d9df8 | ||
|
|
08dfa4cab2 | ||
|
|
63ca695b51 | ||
|
|
55310247c2 | ||
|
|
456334af75 | ||
|
|
38ce047d9e | ||
|
|
14be7dead5 | ||
|
|
ab2e368c6f | ||
|
|
0e7ca594ed | ||
|
|
f742287496 | ||
|
|
cb37919e76 | ||
|
|
933fc26b66 | ||
|
|
8ea94175ac | ||
|
|
013fb12c00 | ||
|
|
1e6b5a1e4d | ||
|
|
aed20db328 | ||
|
|
332684f4e2 | ||
|
|
12d275c26b | ||
|
|
9b1312c7d9 | ||
|
|
874b069357 | ||
|
|
03a917c326 | ||
|
|
6a5560a0b1 | ||
|
|
6b0bbdc605 | ||
|
|
4c0608d47d | ||
|
|
2e1aede8b4 | ||
|
|
2c3e968710 | ||
|
|
ecf45803e0 | ||
|
|
2e4ede4251 | ||
|
|
4f52649f28 | ||
|
|
11e58ff88d | ||
|
|
a7c097a5a9 | ||
|
|
8c53686697 | ||
|
|
6754f1467a | ||
|
|
fb98b3cc9a | ||
|
|
729f6fd308 | ||
|
|
e2b0711271 | ||
|
|
eb8cffb1a8 | ||
|
|
d7e534ca74 | ||
|
|
347e261748 | ||
|
|
7deb9c4fbf | ||
|
|
c1ab5ad929 | ||
|
|
2686c37aa1 | ||
|
|
45edb9973d | ||
|
|
2bd4ce08c4 | ||
|
|
460d1ac86c | ||
|
|
9df8da0b6f | ||
|
|
baaf85f567 | ||
|
|
6ffe817e86 | ||
|
|
edbdbdac56 | ||
|
|
11c3c6d20e | ||
|
|
81019b9fc8 | ||
|
|
4514123279 | ||
|
|
0fc4a448aa | ||
|
|
bde51cc946 | ||
|
|
4f7e29163f | ||
|
|
7837d1f6e8 | ||
|
|
99bb300559 | ||
|
|
f1108ef7d1 | ||
|
|
bd6cfec71c | ||
|
|
ef3ed86096 | ||
|
|
097898b120 | ||
|
|
6fbf2643a1 | ||
|
|
a1989c105e | ||
|
|
97426e6d7d | ||
|
|
b2e1b65ae5 | ||
|
|
06c8d34451 | ||
|
|
ecba81ea5b | ||
|
|
26e0066c82 | ||
|
|
6ebb9b6f66 | ||
|
|
102e30c29a | ||
|
|
a8a716e0bd | ||
|
|
9e23aaa5c0 | ||
|
|
9b059c3985 | ||
|
|
2d3c7e65d2 | ||
|
|
9e7d3462ab | ||
|
|
aececf980b | ||
|
|
e5f3b4bf1d | ||
|
|
9b629bb1c4 | ||
|
|
9e39a57231 | ||
|
|
47e9608aa2 | ||
|
|
2f012caa3e | ||
|
|
e835fc3ac0 | ||
|
|
5240e9ce98 | ||
|
|
b8ab2c839f | ||
|
|
1ee81b90bf | ||
|
|
1354f2debb | ||
|
|
7373ea24d8 | ||
|
|
7fc03461ba | ||
|
|
a5e45e2d79 | ||
|
|
42cf2ac19b | ||
|
|
6098d064a7 | ||
|
|
cd79f576b7 | ||
|
|
4039a4a820 | ||
|
|
919085d829 | ||
|
|
88f27b39c4 | ||
|
|
b1f38a51fe | ||
|
|
ef0253ee9e | ||
|
|
3cf33af0e2 | ||
|
|
3eaa060aac | ||
|
|
cf4efee340 | ||
|
|
0b04a96e15 | ||
|
|
c1d928c503 | ||
|
|
479b21a722 | ||
|
|
ceeaeaf487 | ||
|
|
d5179b742b | ||
|
|
b38055c497 | ||
|
|
ff1e9e63d6 | ||
|
|
a1a4293851 | ||
|
|
f39cc8ee53 | ||
|
|
9fc5b90f25 | ||
|
|
e7c9c884e9 | ||
|
|
0469128917 | ||
|
|
656e38eae7 | ||
|
|
95721350da | ||
|
|
98d9e87356 | ||
|
|
a69cb4f4c2 | ||
|
|
f42ea74e26 | ||
|
|
d9b86f9922 | ||
|
|
605e9cfe6d | ||
|
|
dd53c7b200 | ||
|
|
f9b10dc9db | ||
|
|
daef491d3e | ||
|
|
95fde17d97 | ||
|
|
2c6cbb7799 | ||
|
|
3498d4317a | ||
|
|
611ef49d03 | ||
|
|
fd2212db7b | ||
|
|
4d8b8ba64c | ||
|
|
32fe70a354 | ||
|
|
e2652df546 | ||
|
|
d1427d5f99 | ||
|
|
ef814f3602 | ||
|
|
00acc8289e | ||
|
|
341f980974 | ||
|
|
1b7ea5bed3 | ||
|
|
ee1c92ffa0 | ||
|
|
0114a50f61 | ||
|
|
c7c9e105ef | ||
|
|
3e31e71116 | ||
|
|
e74b1d2210 | ||
|
|
1ca2f28187 | ||
|
|
9fc75b651e | ||
|
|
934a6acdd2 | ||
|
|
88adc5676f | ||
|
|
f27483ea7b | ||
|
|
6a11c620cf | ||
|
|
7281f15051 | ||
|
|
b1196fb09b | ||
|
|
27fb875c0d | ||
|
|
3cdeac5dfb | ||
|
|
e1c47ce5c3 | ||
|
|
d9523cb1bc | ||
|
|
bd2403388e | ||
|
|
7511208b8b | ||
|
|
4586d44c1f | ||
|
|
717e5b07d1 | ||
|
|
2c6082f454 | ||
|
|
586239292b | ||
|
|
7bd1c87bf6 | ||
|
|
6eded4cdc2 | ||
|
|
5e693f2274 | ||
|
|
b0e3aeed6c | ||
|
|
dd9ef38636 | ||
|
|
32df782470 | ||
|
|
1d9162930c | ||
|
|
152d02bcbe | ||
|
|
2cc4dc724b | ||
|
|
1ee14f4c69 | ||
|
|
7748019a76 | ||
|
|
8241a1d8a3 | ||
|
|
83e3373561 | ||
|
|
c8c126d444 | ||
|
|
c12b1d0670 | ||
|
|
5caf4f45a9 | ||
|
|
8f51ff2910 | ||
|
|
6b8436f825 | ||
|
|
c4b146b36b | ||
|
|
098b14884d | ||
|
|
47d8818028 | ||
|
|
e3bc50a163 | ||
|
|
34ba6a86c9 | ||
|
|
2369ef53ac | ||
|
|
8d3907ff65 | ||
|
|
2760eaca85 | ||
|
|
af0319cc66 | ||
|
|
e050539747 | ||
|
|
451756c764 | ||
|
|
afa40df7ad | ||
|
|
fb2b606d26 | ||
|
|
e0ebd47730 | ||
|
|
e97f4e8020 | ||
|
|
79f07b7350 | ||
|
|
c1c8829536 | ||
|
|
ef20371562 | ||
|
|
c08f275cf7 | ||
|
|
6f995fe350 | ||
|
|
68aec92d3a | ||
|
|
dda328210c | ||
|
|
c3f4f232e3 | ||
|
|
302f1da066 | ||
|
|
a6e65d33dd | ||
|
|
7b0cb29466 | ||
|
|
a3eedb14f4 | ||
|
|
74d73a1d17 | ||
|
|
92e7e8516c | ||
|
|
61ac0b7d20 | ||
|
|
8b4ea7fd7c | ||
|
|
90846cf770 | ||
|
|
a9e33ea282 | ||
|
|
eea006e787 | ||
|
|
9e58098e5c | ||
|
|
ff49f574d4 | ||
|
|
599d816a88 | ||
|
|
4b0dd04114 | ||
|
|
36a0fbf181 | ||
|
|
3cfd8f40da | ||
|
|
23173c4bf4 | ||
|
|
722c31ccd9 | ||
|
|
7623a01272 | ||
|
|
1b90fdce73 | ||
|
|
ce57db9526 | ||
|
|
8b9e47cd11 | ||
|
|
a87a261b80 | ||
|
|
989d561679 | ||
|
|
b349990b19 | ||
|
|
2104b49cee | ||
|
|
77606efb77 | ||
|
|
6164ad2ab1 | ||
|
|
a60d58dcb0 | ||
|
|
4e45d17ad2 | ||
|
|
c8e614d064 | ||
|
|
544beeb078 | ||
|
|
a00f0d8367 | ||
|
|
ea57290792 | ||
|
|
603a4afaf3 | ||
|
|
19e2772729 | ||
|
|
82ebb82a5f | ||
|
|
516ae6b68c | ||
|
|
59225a4b9c | ||
|
|
1da9d36ecc | ||
|
|
0787e5c738 | ||
|
|
8a661088bb | ||
|
|
91659a32af | ||
|
|
5f8cd7e5c4 | ||
|
|
bc3f62a766 | ||
|
|
92faedb4a5 | ||
|
|
6f7008dd91 | ||
|
|
447589541f | ||
|
|
45f5f50262 | ||
|
|
f77a27ecc3 | ||
|
|
09de2eb194 | ||
|
|
7747a5665b | ||
|
|
aa617b5106 | ||
|
|
8038e2e01a | ||
|
|
212b068a8d | ||
|
|
2bcea0b566 | ||
|
|
512d4f2fb9 | ||
|
|
b524546e9c | ||
|
|
f91e287207 | ||
|
|
0fcf0a6a88 | ||
|
|
f9f3d74a3b | ||
|
|
2bfd0f2cc0 | ||
|
|
cdeb2eebe5 | ||
|
|
5134516afc | ||
|
|
e14b5d99eb | ||
|
|
a0375f52cf | ||
|
|
69dbcf3947 | ||
|
|
79387730bc | ||
|
|
3e6b019044 | ||
|
|
1886b90be7 | ||
|
|
85421f9b4b | ||
|
|
3749ef8433 | ||
|
|
dfb3e98fec | ||
|
|
a40442c8a9 | ||
|
|
a7396d6bee | ||
|
|
507c4c67ff | ||
|
|
cbdc04e929 | ||
|
|
a6582a1da3 | ||
|
|
3d8f3df8f1 | ||
|
|
7b8dbdedb5 | ||
|
|
53b13eacaa | ||
|
|
fcc76d99ef | ||
|
|
9a2141025a | ||
|
|
bfe3285375 | ||
|
|
5cf9c6e073 | ||
|
|
5e00d77e69 | ||
|
|
a4dfb05d0c | ||
|
|
97a798e357 | ||
|
|
47aafe88f8 | ||
|
|
bc356ab8e5 | ||
|
|
dd5cdfd848 | ||
|
|
de2cb08a6b | ||
|
|
9a2550ad48 | ||
|
|
2f6e4c0736 | ||
|
|
a9c5cbf11f | ||
|
|
facd0d398a | ||
|
|
1b2fc5d403 | ||
|
|
9d290aa533 | ||
|
|
9046291128 | ||
|
|
59131b6b51 | ||
|
|
3725a23c49 | ||
|
|
812c42c89a | ||
|
|
285d7d3265 | ||
|
|
134789602b | ||
|
|
55f49b5f78 | ||
|
|
cd8067285f | ||
|
|
10c09e9375 | ||
|
|
bb70c0733a | ||
|
|
3dad2b31bb | ||
|
|
7844acd70f | ||
|
|
f01b0b3404 | ||
|
|
10fee6e144 | ||
|
|
c4c8a8b703 | ||
|
|
7053a1e42a | ||
|
|
fd3c4261de | ||
|
|
d21e475d12 | ||
|
|
48bdf0b2a9 | ||
|
|
ac1c652854 | ||
|
|
3b286de9db | ||
|
|
cbcb696cfd | ||
|
|
13320ea8cc | ||
|
|
bdf45f6a0b | ||
|
|
667b1e9f99 | ||
|
|
e52e82fb78 | ||
|
|
78cb3f8ee2 | ||
|
|
a0f476fb24 | ||
|
|
13c32bfe53 | ||
|
|
a54da059ef | ||
|
|
76fe0def4f | ||
|
|
9a25b747ae | ||
|
|
aeefd3e873 | ||
|
|
f9efdcb654 | ||
|
|
be672cbcba | ||
|
|
c4ca7e471a | ||
|
|
bbce082ec2 | ||
|
|
781214a2f0 | ||
|
|
7942eaf64b | ||
|
|
36cd3c0c42 | ||
|
|
620bccb102 | ||
|
|
3ca79bebc7 | ||
|
|
a1add0cfdb | ||
|
|
dc22ad1ca1 | ||
|
|
010df5a081 | ||
|
|
71e8b22849 | ||
|
|
17dcc7ec80 | ||
|
|
138c17d4a3 | ||
|
|
565eed99ef | ||
|
|
1f13c47a46 | ||
|
|
ec5d12c940 | ||
|
|
bac19b2486 | ||
|
|
5fbfa1453c | ||
|
|
dab2001dcc | ||
|
|
5bff9eb4d8 | ||
|
|
e2102bdefa | ||
|
|
2a3e06610f | ||
|
|
d186240cfc | ||
|
|
0f0d4f06bb | ||
|
|
249af325c9 | ||
|
|
a7532f2154 | ||
|
|
1c0a00e9b2 | ||
|
|
b1b8066fe5 | ||
|
|
25c2f3adad | ||
|
|
68e47d70fd | ||
|
|
af3b3f4ee5 | ||
|
|
1b63d514ac | ||
|
|
c6453a6e10 | ||
|
|
3f3f5fdaa0 | ||
|
|
2f72721ee2 | ||
|
|
92d99c6d64 | ||
|
|
f3b26b1101 | ||
|
|
4f0a5bcf15 | ||
|
|
ffb1a8ad84 | ||
|
|
70c07cf731 | ||
|
|
b2d946794b | ||
|
|
90617c5433 | ||
|
|
278dc34b44 | ||
|
|
dcd5d7b534 | ||
|
|
2d48465e1d | ||
|
|
6bda493f6e | ||
|
|
5459b8bb7a | ||
|
|
e410939bde | ||
|
|
047de2d747 | ||
|
|
997f4d35c2 | ||
|
|
ca547f1239 | ||
|
|
c9665773e2 | ||
|
|
eeeea6b735 | ||
|
|
bbd6a2b5d5 | ||
|
|
d33db2a0fe | ||
|
|
1f2588d677 | ||
|
|
f30bad5c5a | ||
|
|
5a5bf77b1c | ||
|
|
22c6597005 | ||
|
|
9761d70f23 | ||
|
|
518135006c | ||
|
|
9327eb7e75 | ||
|
|
13080ade9e | ||
|
|
9c3947a7b1 | ||
|
|
f33cbcaa53 | ||
|
|
2c8fe7971f | ||
|
|
85e10330cb | ||
|
|
a02fd4b6f6 | ||
|
|
9395119749 | ||
|
|
2c0bbe9082 | ||
|
|
b1535864aa | ||
|
|
fd880da057 | ||
|
|
6c62b06e46 | ||
|
|
b44869ec49 | ||
|
|
b3a6251b04 | ||
|
|
80667bc38f | ||
|
|
cff35ea845 | ||
|
|
cb93397ae4 | ||
|
|
c805a8d624 | ||
|
|
3f775c081c | ||
|
|
864b892da0 | ||
|
|
f7773d2113 | ||
|
|
cf471d0f36 | ||
|
|
d14791679c | ||
|
|
f358d34e54 | ||
|
|
7ba3a7dd2f | ||
|
|
196af51e2c | ||
|
|
cbe959da66 | ||
|
|
9512fd4cca | ||
|
|
e2b6853c46 | ||
|
|
2578bfd0ff | ||
|
|
277fff5f2c | ||
|
|
4c72e9d206 | ||
|
|
7a44a303e5 | ||
|
|
e3796045eb | ||
|
|
85ad8d8b9f | ||
|
|
f737c92988 | ||
|
|
5d05ecaee3 | ||
|
|
8cc83a8df9 | ||
|
|
43b0f1bea9 | ||
|
|
a12ee4a61e | ||
|
|
d595a45f2b | ||
|
|
76a6d59837 | ||
|
|
bfe9bf2d5b | ||
|
|
59cac169e7 | ||
|
|
c24ee89b16 | ||
|
|
a73c2d7292 | ||
|
|
97671b3592 | ||
|
|
dd38586ead | ||
|
|
e11de68203 | ||
|
|
97ce286ff2 | ||
|
|
99e4e5a219 | ||
|
|
316b9aa68a | ||
|
|
7318d6615f | ||
|
|
6782b108b0 | ||
|
|
494af0ad23 | ||
|
|
55cff03747 | ||
|
|
599cc7c3af | ||
|
|
bfa7a7a6dc | ||
|
|
b243a5404b | ||
|
|
2e40a7377a | ||
|
|
62e86a0474 | ||
|
|
8c1ee9cf21 | ||
|
|
3afda882de | ||
|
|
69fd90ed3a | ||
|
|
554530dd60 | ||
|
|
2472e5546f | ||
|
|
a7ee4c09f5 | ||
|
|
7effac73b9 | ||
|
|
125924d125 | ||
|
|
00aaac866f | ||
|
|
170299c13c | ||
|
|
ac6f56dbf4 | ||
|
|
09c494c6c5 | ||
|
|
fdd685d20c | ||
|
|
fcff22981b | ||
|
|
7f82a41e66 | ||
|
|
17d12b41db | ||
|
|
2f7d68f7f1 | ||
|
|
dc1c65b418 | ||
|
|
23de013c86 | ||
|
|
a922b00716 | ||
|
|
babe21a3dc | ||
|
|
59ef0b05a1 | ||
|
|
3da7ed76c5 | ||
|
|
bbf0cd7578 | ||
|
|
25be97e65c | ||
|
|
175bb9206e | ||
|
|
68605ad409 | ||
|
|
4500687d47 | ||
|
|
fc38e6f270 | ||
|
|
f1936fea6b | ||
|
|
24bdcf66ed | ||
|
|
8688523ced | ||
|
|
856944e463 | ||
|
|
84f6894bc4 | ||
|
|
9bf33f89a9 | ||
|
|
c0fa7c42cd | ||
|
|
3281a85ae9 | ||
|
|
0db965a95f | ||
|
|
0732ea1162 | ||
|
|
c88497afba | ||
|
|
d4892c19b4 | ||
|
|
3ed25b069d | ||
|
|
e51b1f7643 | ||
|
|
7d07b27d4f | ||
|
|
57fdc90623 | ||
|
|
72bec2ef49 | ||
|
|
58a3805788 | ||
|
|
40f96dd2bd | ||
|
|
fca13d453e | ||
|
|
32e590f398 | ||
|
|
301b44dcae | ||
|
|
e45e91b0bf | ||
|
|
fe31dbca26 | ||
|
|
51fa754ec5 | ||
|
|
2ce10af051 | ||
|
|
c107422353 | ||
|
|
1e1fdee5a7 | ||
|
|
fffcd61cc4 | ||
|
|
2fd11cba15 | ||
|
|
f1ec85aef5 | ||
|
|
4c4b363035 | ||
|
|
8555e0c7e7 | ||
|
|
c07f7390ec | ||
|
|
34f6e15271 | ||
|
|
135f0677e4 | ||
|
|
63ad255f2b | ||
|
|
16ac8ea196 | ||
|
|
0e00773238 | ||
|
|
cf1f676268 | ||
|
|
d2f304b047 | ||
|
|
0a9bb138c5 | ||
|
|
6236391c86 | ||
|
|
78257cb1c0 | ||
|
|
c4cb75668c | ||
|
|
9880bcacd6 | ||
|
|
52aedd0a31 | ||
|
|
c7ce741857 | ||
|
|
bcbf012406 | ||
|
|
ec75b4418b | ||
|
|
716b84f507 | ||
|
|
e95b3a59ef | ||
|
|
9dc179b2cd | ||
|
|
efe1b01936 | ||
|
|
cd26b0c5a9 | ||
|
|
b075f71f11 | ||
|
|
44145ef265 | ||
|
|
82ef21b821 | ||
|
|
5655d7456c | ||
|
|
a7992102af | ||
|
|
ee2cd412ce | ||
|
|
1c34f48c39 | ||
|
|
0c2937936b | ||
|
|
b0c3f6c82a | ||
|
|
e047525be9 | ||
|
|
7f1ad590f6 | ||
|
|
fbf2d827c2 | ||
|
|
72619df02c | ||
|
|
c58d8992cf | ||
|
|
c98177ea20 | ||
|
|
766409e581 | ||
|
|
c076d61cc9 | ||
|
|
92e265ecc9 | ||
|
|
e223df64cf | ||
|
|
629ebf5ff2 | ||
|
|
0019eb70b5 | ||
|
|
e5f1be0b30 | ||
|
|
dd74934638 | ||
|
|
8d4973a9cc | ||
|
|
a49c091cd5 | ||
|
|
7d0268a4ce | ||
|
|
44e53e7225 | ||
|
|
1a1689da1f | ||
|
|
a28802e84e | ||
|
|
2798e71a52 | ||
|
|
e3647092b0 | ||
|
|
117cc13ed7 | ||
|
|
2d39a04575 | ||
|
|
d3e9457532 | ||
|
|
afc737b9bd | ||
|
|
32e597f6a4 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,3 +11,5 @@ log.txt
|
||||
.yardoc
|
||||
debug.log
|
||||
wordlist.txt
|
||||
rspec_results.html
|
||||
data/
|
||||
|
||||
1
.ruby-gemset
Normal file
1
.ruby-gemset
Normal file
@@ -0,0 +1 @@
|
||||
wpscan
|
||||
1
.ruby-version
Normal file
1
.ruby-version
Normal file
@@ -0,0 +1 @@
|
||||
2.1.5
|
||||
20
.travis.yml
20
.travis.yml
@@ -1,6 +1,18 @@
|
||||
language: ruby
|
||||
rvm:
|
||||
- "1.9.2"
|
||||
- "1.9.3"
|
||||
- "2.0.0"
|
||||
script: bundle exec rspec --format documentation
|
||||
- 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
|
||||
script: bundle exec rspec
|
||||
notifications:
|
||||
email:
|
||||
- wpscanteam@gmail.com
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rvm: 1.9.2
|
||||
|
||||
257
CHANGELOG.md
257
CHANGELOG.md
@@ -1,9 +1,248 @@
|
||||
# Changelog
|
||||
## Master
|
||||
[Work in progress](https://github.com/wpscanteam/wpscan/compare/2.6...master)
|
||||
|
||||
## Version 2.2
|
||||
## 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
|
||||
|
||||
Added
|
||||
New
|
||||
* Output the vulnerability fix if available
|
||||
* Added 'WordPress Version Vulnerability' statistics
|
||||
* Added Kali Linux on the list of pre-installed Linux distributions
|
||||
@@ -82,13 +321,13 @@ Vulnerabilities
|
||||
* Update timthumb due to Secunia #54801
|
||||
* Added WP vuln: 3.4 - 3.5.1 wp-admin/users.php FPD
|
||||
|
||||
WPScan Databse Statistics:
|
||||
* Total vulnerable versions: 76, 4 are new
|
||||
* Total vulnerable plugins: 606, 197 are new
|
||||
* Total vulnerable themes: 194, 45 are new
|
||||
* Total version vulnerabilities: 274, 53 are new
|
||||
* Total plugin vulnerabilities: 764, 270 are new
|
||||
* Total theme vulnerabilities: 198, 46 are new
|
||||
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
|
||||
|
||||
9
CREDITS
9
CREDITS
@@ -6,15 +6,16 @@ This file is to give credit to WPScan's contributors. If you feel your name shou
|
||||
|
||||
Erwan.LR - @erwan_lr - (Project Developer)
|
||||
Christian Mehlmauer - @_FireFart_ - (Project Developer)
|
||||
Gianluca Brindisi - @gbrindisi (Project Developer)
|
||||
Peter van der Laan - pvdl - (Vuln Hunter and Code Cleaner)
|
||||
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.
|
||||
michee08 - Reported and gave potential solutions to bugs
|
||||
Callum Pember - Implemented proxy support - callumpember at gmail.com
|
||||
g0tmi1k - Additional timthumb checks + bug reports.
|
||||
g0tmi1k - Additional timthumb checks + bug reports
|
||||
Melvin Lammerts - Reported a couple of fake vulnerabilities - melvin at 12k.nl
|
||||
Paolo Perego - @thesp0nge - Basic authentication
|
||||
Peter van der Laan - The Vuln Hunter and Code Cleaner
|
||||
Gianluca Brindisi - @gbrindisi - Project Developer
|
||||
|
||||
2
DISCLAIMER.txt
Normal file
2
DISCLAIMER.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
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.
|
||||
21
Gemfile
21
Gemfile
@@ -1,15 +1,14 @@
|
||||
source "https://rubygems.org"
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Seg fault in Typhoeus 0.6.3 (and ethon > 0.5.11) with rspec
|
||||
gem "typhoeus", ">=0.6.3"
|
||||
gem "nokogiri"
|
||||
gem "json"
|
||||
gem "terminal-table"
|
||||
gem "ruby-progressbar", ">=1.2.0"
|
||||
gem 'typhoeus', '~>0.6.8'
|
||||
gem 'nokogiri'
|
||||
gem 'json'
|
||||
gem 'terminal-table'
|
||||
gem 'ruby-progressbar', '>=1.6.0'
|
||||
|
||||
group :test do
|
||||
gem "webmock", ">=1.9.3"
|
||||
gem "simplecov"
|
||||
gem "rspec", :require => "spec"
|
||||
gem "rspec-mocks", "<=2.14.2" # 2.14.3 just messed around :/
|
||||
gem 'webmock', '>=1.17.2'
|
||||
gem 'simplecov'
|
||||
gem 'rspec', '>=3.0'
|
||||
gem 'rspec-its'
|
||||
end
|
||||
|
||||
30
LICENSE
30
LICENSE
@@ -1,15 +1,21 @@
|
||||
WPScan - WordPress Security Scanner
|
||||
Copyright (C) 2012-2013
|
||||
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
Cases of commercialization are:
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
- 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.
|
||||
|
||||
Cases which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to):
|
||||
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause.
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
|
||||
If you need to acquire a commercial license or are unsure about whether you need to acquire a commercial license, please get in touch, we will be happy to clarify things for you and work with you to accommodate your requirements.
|
||||
|
||||
wpscanteam at gmail.com
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
149
README
149
README
@@ -9,47 +9,73 @@ __________________________________________________
|
||||
|
||||
==LICENSE==
|
||||
|
||||
WPScan - WordPress Security Scanner
|
||||
Copyright (C) 2011-2013 The WPScan Team
|
||||
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
Cases of commercialization are:
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
- 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.
|
||||
|
||||
ryandewhurst at gmail
|
||||
Cases which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to):
|
||||
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause.
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
|
||||
If you need to acquire a commercial license or are unsure about whether you need to acquire a commercial license, please get in touch, we will be happy to clarify things for you and work with you to accommodate your requirements.
|
||||
|
||||
wpscanteam at gmail.com
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
==INSTALL==
|
||||
|
||||
WPScan comes pre-installed on the following Linux distributions:
|
||||
|
||||
* BackBox Linux
|
||||
* BackTrack Linux
|
||||
* Kali Linux
|
||||
* Pentoo
|
||||
* SamuraiWTF
|
||||
* ArchAssault
|
||||
|
||||
Prerequisites:
|
||||
|
||||
* Windows not supported
|
||||
* Ruby >= 1.9.2 - Recommended: 1.9.3
|
||||
* Ruby >= 1.9.2 - Recommended: 2.1.4
|
||||
* Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
|
||||
* RubyGems - Recommended: latest
|
||||
* Git
|
||||
|
||||
-> Installing on Debian/Ubuntu:
|
||||
Windows is not supported.
|
||||
|
||||
sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev
|
||||
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
|
||||
sudo gem install bundler
|
||||
bundle install --without test --path vendor/bundle
|
||||
|
||||
-> Installing on Fedora:
|
||||
|
||||
@@ -76,13 +102,26 @@ ryandewhurst at gmail
|
||||
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
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.1.4
|
||||
rvm use 2.1.4 --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)
|
||||
See http://code.google.com/p/wpscan/issues/detail?id=81
|
||||
|
||||
- Proxy not working:
|
||||
Update cURL to version => 7.21.7 (may have to install from source).
|
||||
@@ -117,7 +156,7 @@ ryandewhurst at gmail
|
||||
|
||||
==WPSCAN ARGUMENTS==
|
||||
|
||||
--update Update to the latest revision
|
||||
--update Update the databases.
|
||||
|
||||
--url | -u <target url> The WordPress URL/domain to scan.
|
||||
|
||||
@@ -132,15 +171,19 @@ ryandewhurst at gmail
|
||||
ap all plugins (can take a long time)
|
||||
tt timthumbs
|
||||
t themes
|
||||
vp only vulnerable 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'
|
||||
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)
|
||||
--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
|
||||
--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
|
||||
|
||||
@@ -148,23 +191,39 @@ ryandewhurst at gmail
|
||||
|
||||
--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 <[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 (will override the one from conf/browser.conf.json).
|
||||
--proxy-auth <username:password> Supply the proxy login credentials.
|
||||
|
||||
--basic-auth <username:password> Set the HTTP Basic authentication
|
||||
--basic-auth <username:password> Set the HTTP Basic authentication.
|
||||
|
||||
--wordlist | -w <wordlist> Supply a wordlist for the password bruter and do the brute.
|
||||
--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. (will override the value from conf/browser.conf.json)
|
||||
--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 behaviour.
|
||||
|
||||
--no-color Do not use colors in the output.
|
||||
|
||||
--log Save STDOUT to log.txt
|
||||
|
||||
==WPSCAN EXAMPLES==
|
||||
|
||||
Do 'non-intrusive' checks...
|
||||
@@ -191,7 +250,7 @@ Use custom content directory...
|
||||
|
||||
ruby wpscan.rb -u www.example.com --wp-content-dir custom-content
|
||||
|
||||
Update WPScan...
|
||||
Update WPScan's databases...
|
||||
|
||||
ruby wpscan.rb --update
|
||||
|
||||
@@ -201,19 +260,15 @@ Debug output...
|
||||
|
||||
==WPSTOOLS ARGUMENTS==
|
||||
|
||||
--help | -h This help screen.
|
||||
--Verbose | -v Verbose output.
|
||||
--update | -u Update to the latest revision.
|
||||
--generate_plugin_list [number of pages] Generate a new data/plugins.txt file. (supply number of *pages* to parse, default : 150)
|
||||
--gpl Alias for --generate_plugin_list
|
||||
--check-local-vulnerable-files | --clvf <local directory> Perform a recursive scan in the <local directory> to find vulnerable files or shells
|
||||
-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==
|
||||
|
||||
- Generate a new 'most popular' plugin list, up to 150 pages ...
|
||||
ruby wpstools.rb --generate_plugin_list 150
|
||||
|
||||
- Locally scan a wordpress installation for vulnerable files or shells :
|
||||
Locally scan a wordpress installation for vulnerable files or shells:
|
||||
ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/
|
||||
|
||||
===PROJECT HOME===
|
||||
@@ -232,8 +287,6 @@ https://github.com/wpscanteam/wpscan/issues
|
||||
|
||||
http://rdoc.info/github/wpscanteam/wpscan/frames
|
||||
|
||||
===SPONSOR===
|
||||
===SPECIAL THANKS===
|
||||
|
||||
WPScan is sponsored by the RandomStorm Open Source Initiative.
|
||||
|
||||
Visit RandomStorm at http://www.randomstorm.com
|
||||
RandomStorm - https://www.randomstorm.com
|
||||
|
||||
215
README.md
215
README.md
@@ -1,104 +1,129 @@
|
||||

|
||||

|
||||
|
||||
[](https://travis-ci.org/wpscanteam/wpscan)
|
||||
|
||||
[](https://travis-ci.org/wpscanteam/CMSScanner)
|
||||
[](https://codeclimate.com/github/wpscanteam/wpscan)
|
||||
[](https://gemnasium.com/wpscanteam/wpscan)
|
||||
|
||||
#### LICENSE
|
||||
|
||||
WPScan - WordPress Security Scanner
|
||||
Copyright (C), 2011-2013 The WPScan Team
|
||||
The WPScan software and its data (henceforth both referred to simply as "WPScan") is dual-licensed - copyright 2011-2014 The WPScan Team.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
Cases that include commercialization of WPScan require a commercial, non-free license. Otherwise, the system can be used under the terms of the GNU General Public License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
Cases of commercialization are:
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
- 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.
|
||||
|
||||
ryandewhurst at gmail
|
||||
Cases which do not require a commercial license, and thus fall under the terms of GNU General Public License, include (but are not limited to):
|
||||
|
||||
- Penetration testers (or penetration testing organizations) using WPScan as part of their assessment toolkit. So long as that does not conflict with the commercialization clause.
|
||||
- Using WPScan to test your own systems.
|
||||
- Any non-commercial use of WPScan.
|
||||
|
||||
If you need to acquire a commercial license or are unsure about whether you need to acquire a commercial license, please get in touch, we will be happy to clarify things for you and work with you to accommodate your requirements.
|
||||
|
||||
wpscanteam at gmail.com
|
||||
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#### INSTALL
|
||||
|
||||
WPScan comes pre-installed on the following Linux distributions:
|
||||
|
||||
- [BackBox Linux](http://www.backbox.org/)
|
||||
- [BackTrack Linux](http://www.backtrack-linux.org/)
|
||||
- [Kali Linux](http://www.kali.org/)
|
||||
- [Pentoo](http://www.pentoo.ch/)
|
||||
- [SamuraiWTF](http://samurai.inguardians.com/)
|
||||
- [ArchAssault](https://archassault.org/)
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Windows not supported
|
||||
- Ruby >= 1.9.2 - Recommended: 1.9.3
|
||||
- Ruby >= 1.9.2 - Recommended: 2.1.4
|
||||
- Curl >= 7.21 - Recommended: latest - FYI the 7.29 has a segfault
|
||||
- RubyGems - Recommended: latest
|
||||
- Git
|
||||
|
||||
*Installing on Debian/Ubuntu:*
|
||||
Windows is not supported.
|
||||
If installed from Github update the code base with ```git pull```. The databases are updated with ```wpscan.rb --update```.
|
||||
|
||||
```sudo apt-get install libcurl4-gnutls-dev libopenssl-ruby libxml2 libxml2-dev libxslt1-dev ruby-dev```
|
||||
####Installing on Ubuntu:
|
||||
|
||||
```git clone https://github.com/wpscanteam/wpscan.git```
|
||||
Before Ubuntu 14.04:
|
||||
|
||||
```cd wpscan```
|
||||
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
|
||||
|
||||
```sudo gem install bundler && bundle install --without test```
|
||||
From Ubuntu 14.04:
|
||||
|
||||
*Installing on Fedora:*
|
||||
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
|
||||
|
||||
```sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel```
|
||||
####Installing on Debian:
|
||||
|
||||
```git clone https://github.com/wpscanteam/wpscan.git```
|
||||
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
|
||||
|
||||
```cd wpscan```
|
||||
####Installing on Fedora:
|
||||
|
||||
```sudo gem install bundler && bundle install --without test```
|
||||
sudo yum install gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel libcurl-devel
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && bundle install --without test
|
||||
|
||||
*Installing on Archlinux:*
|
||||
####Installing on Archlinux:
|
||||
|
||||
```pacman -Syu ruby```
|
||||
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
|
||||
|
||||
```pacman -Syu libyaml```
|
||||
####Installing on Mac OSX:
|
||||
|
||||
```git clone https://github.com/wpscanteam/wpscan.git```
|
||||
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)
|
||||
|
||||
```cd wpscan```
|
||||
git clone https://github.com/wpscanteam/wpscan.git
|
||||
cd wpscan
|
||||
sudo gem install bundler && sudo bundle install --without test
|
||||
|
||||
```sudo gem install bundler && bundle install --without test```
|
||||
####Installing with RVM:
|
||||
|
||||
```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
|
||||
|
||||
```git clone https://github.com/wpscanteam/wpscan.git```
|
||||
|
||||
```cd wpscan```
|
||||
|
||||
```sudo gem install bundler && bundle install --without test```
|
||||
cd ~
|
||||
curl -sSL https://get.rvm.io | bash -s stable
|
||||
source ~/.rvm/scripts/rvm
|
||||
echo "source ~/.rvm/scripts/rvm" >> ~/.bashrc
|
||||
rvm install 2.1.4
|
||||
rvm use 2.1.4 --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)
|
||||
See http://code.google.com/p/wpscan/issues/detail?id=81
|
||||
|
||||
- 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
|
||||
@@ -106,21 +131,21 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
Run make
|
||||
Run sudo make install
|
||||
Run sudo ldconfig
|
||||
```
|
||||
|
||||
|
||||
- cannot load such file -- readline:
|
||||
|
||||
```sudo aptitude install libreadline5-dev libncurses5-dev```
|
||||
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/ for more details
|
||||
|
||||
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
|
||||
|
||||
@@ -128,11 +153,11 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
|
||||
And select your ruby version
|
||||
|
||||
See https://github.com/wpscanteam/wpscan/issues/148
|
||||
See [https://github.com/wpscanteam/wpscan/issues/148](https://github.com/wpscanteam/wpscan/issues/148)
|
||||
|
||||
#### WPSCAN ARGUMENTS
|
||||
|
||||
--update Update to the latest revision
|
||||
--update Update the databases.
|
||||
|
||||
--url | -u <target url> The WordPress URL/domain to scan.
|
||||
|
||||
@@ -149,13 +174,17 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
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'
|
||||
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)
|
||||
--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
|
||||
--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
|
||||
|
||||
@@ -163,23 +192,39 @@ Apple Xcode, Command Line Tools and the libffi are needed (to be able to install
|
||||
|
||||
--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 <[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 (will override the one from conf/browser.conf.json).
|
||||
--proxy-auth <username:password> Supply the proxy login credentials.
|
||||
|
||||
--basic-auth <username:password> Set the HTTP Basic authentication
|
||||
--basic-auth <username:password> Set the HTTP Basic authentication.
|
||||
|
||||
--wordlist | -w <wordlist> Supply a wordlist for the password bruter and do the brute.
|
||||
--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. (will override the value from conf/browser.conf.json)
|
||||
--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 behaviour.
|
||||
|
||||
--no-color Do not use colors in the output.
|
||||
|
||||
--log Save STDOUT to log.txt
|
||||
|
||||
#### WPSCAN EXAMPLES
|
||||
|
||||
Do 'non-intrusive' checks...
|
||||
@@ -206,7 +251,7 @@ Use custom content directory...
|
||||
|
||||
```ruby wpscan.rb -u www.example.com --wp-content-dir custom-content```
|
||||
|
||||
Update WPScan...
|
||||
Update WPScan's databases...
|
||||
|
||||
```ruby wpscan.rb --update```
|
||||
|
||||
@@ -216,39 +261,39 @@ Debug output...
|
||||
|
||||
#### WPSTOOLS ARGUMENTS
|
||||
|
||||
--help | -h This help screen.
|
||||
--Verbose | -v Verbose output.
|
||||
--update | -u Update to the latest revision.
|
||||
--generate_plugin_list [number of pages] Generate a new data/plugins.txt file. (supply number of *pages* to parse, default : 150)
|
||||
--gpl Alias for --generate_plugin_list
|
||||
--check-local-vulnerable-files | --clvf <local directory> Perform a recursive scan in the <local directory> to find vulnerable files or shells
|
||||
-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
|
||||
|
||||
Generate a new 'most popular' plugin list, up to 150 pages...
|
||||
Locally scan a wordpress installation for vulnerable files or shells:
|
||||
|
||||
```ruby wpstools.rb --generate_plugin_list 150```
|
||||
|
||||
Locally scan a wordpress installation for vulnerable files or shells :
|
||||
```ruby wpstools.rb --check-local-vulnerable-files /var/www/wordpress/```
|
||||
|
||||
|
||||
#### PROJECT HOME
|
||||
|
||||
www.wpscan.org
|
||||
[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](https://github.com/wpscanteam/wpscan)
|
||||
|
||||
#### ISSUES
|
||||
|
||||
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](http://rdoc.info/github/wpscanteam/wpscan/frames)
|
||||
|
||||
#### SPONSOR
|
||||
#### SPECIAL THANKS
|
||||
|
||||
WPScan is sponsored by the [RandomStorm](http://www.randomstorm.com) Open Source Initiative.
|
||||
[RandomStorm](https://www.randomstorm.com)
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0",
|
||||
/* Modes :
|
||||
static : will use the defined user_agent for each request
|
||||
semi-static : will randomly choose a user agent into available_user_agents before each scan
|
||||
random : each request will choose a random user agent in available_user_agents
|
||||
*/
|
||||
"user_agent_mode": "static",
|
||||
|
||||
/* Uncomment the "proxy" line to use the proxy
|
||||
SOCKS proxies (4, 4A, 5) are supported, ie : "proxy": "socks5://127.0.0.1:9000"
|
||||
If you do not specify the protocol, http will be used
|
||||
*/
|
||||
//"proxy": "127.0.0.1:3128",
|
||||
//"proxy_auth": "username:password",
|
||||
|
||||
"cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
|
||||
|
||||
"request_timeout": 2000, // 2s
|
||||
|
||||
"connect_timeout": 1000, // 1s
|
||||
|
||||
"max_threads": 20,
|
||||
|
||||
// Some user_agents can be found there http://techpatterns.com/downloads/firefox/useragentswitcher.xml (thx to Gianluca Brindisi)
|
||||
"available_user_agents":
|
||||
[
|
||||
// Windows
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.27 (KHTML, like Gecko) Chrome/12.0.712.0 Safari/534.27",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)",
|
||||
"Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.1) Gecko/20100101 Firefox/10.0.1",
|
||||
"Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/12.0",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20120427 Firefox/15.0a1",
|
||||
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)",
|
||||
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
|
||||
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/6.0)",
|
||||
"Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00",
|
||||
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5",
|
||||
|
||||
// MAC
|
||||
"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13",
|
||||
"Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.5; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1",
|
||||
"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/418.8 (KHTML, like Gecko) Safari/419.3",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2; rv:10.0.1) Gecko/20100101 Firefox/10.0.1",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10",
|
||||
|
||||
// Linux
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.703.0 Chrome/12.0.703.0 Safari/534.24",
|
||||
"Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.9) Gecko/20100915 Gentoo Firefox/3.6.9",
|
||||
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.16) Gecko/20120421 Gecko Firefox/11.0",
|
||||
"Mozilla/5.0 (X11; Linux i686; rv:12.0) Gecko/20100101 Firefox/12.0",
|
||||
"Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00",
|
||||
"Mozilla/5.0 (X11; U; Linux x86_64; us; rv:1.9.1.19) Gecko/20110430 shadowfox/7.0 (like Firefox/7.0"
|
||||
]
|
||||
}
|
||||
2
data/.gitignore
vendored
Normal file
2
data/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
Only he following extensions are scanned : js, php, swf, html, htm
|
||||
If you want to add one, modify the variable file_extension_to_scan, line 191 in wpstools.rb
|
||||
-->
|
||||
|
||||
<hashes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="local_vulnerable_files.xsd">
|
||||
|
||||
<hash sha1="17c372678aafb3bc1a7b37320b5cc1d8af433527">
|
||||
<title>XSS in swfupload.swf</title>
|
||||
<file>swfupload.swf</file>
|
||||
<reference>http://brindi.si/g/blog/vulnerable-swf-bundled-in-wordpress-plugins.html</reference>
|
||||
</hash>
|
||||
|
||||
<hash sha1="775dc1089829ef07838406def28a4d8bfef69d66">
|
||||
<title>Arbitrary File Upload Vulnerability</title>
|
||||
<file>php.php</file>
|
||||
<reference>http://packetstormsecurity.com/files/119241/wpvalums-shell.txt</reference>
|
||||
</hash>
|
||||
|
||||
<!-- This one a is the same as above, but the postSize verification has been removed -->
|
||||
<hash sha1="5e8f0d5a917d2937318a9bafd0529135bd473e70">
|
||||
<title>Arbitrary File Upload Vulnerability</title>
|
||||
<file>php.php</file>
|
||||
<reference>http://packetstormsecurity.com/files/119218/wpreflexgallery-shell.txt</reference>
|
||||
</hash>
|
||||
|
||||
<hash sha1="3f9ad05b05b65ee2b6efa1373f708293dd2005c7">
|
||||
<title>Arbitrary File Upload Vulnerability</title>
|
||||
<file>uploadify.php</file>
|
||||
<reference>http://packetstormsecurity.com/files/119219/wpuploader104-shell.txt</reference>
|
||||
</hash>
|
||||
|
||||
<hash sha1="ac638cc38f011b74a8d9a4e7d3d60358e472166c">
|
||||
<title>Inline phpinfo()</title>
|
||||
<file>phpinfo.php</file>
|
||||
<reference>http://php.net/manual/en/function.phpinfo.php</reference>
|
||||
</hash>
|
||||
|
||||
<hash sha1="012ee25cceff745e681fbb3697a06f3712f55554">
|
||||
<title>phpinfo()</title>
|
||||
<file>phpinfo.php</file>
|
||||
<reference>http://php.net/manual/en/function.phpinfo.php</reference>
|
||||
</hash>
|
||||
|
||||
</hashes>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:simpleType name="stringtype">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="uritype">
|
||||
<xs:restriction base="xs:anyURI">
|
||||
<xs:minLength value="1" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="sha1type">
|
||||
<xs:restriction base="stringtype">
|
||||
<xs:pattern value="[0-9a-f]{40}"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="hashtype">
|
||||
<xs:sequence minOccurs="1" maxOccurs="1">
|
||||
<xs:element name="title" type="stringtype"/>
|
||||
<xs:element name="file" type="stringtype"/>
|
||||
<xs:element name="reference" type="uritype"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="sha1type" name="sha1" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="hashes">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="hash" type="hashtype" maxOccurs="unbounded" minOccurs="1"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
</xs:schema>
|
||||
@@ -1,3 +0,0 @@
|
||||
http://.*\.rr\.nu
|
||||
http://www\.thesea\.org/media\.php
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
2205
data/plugins.txt
2205
data/plugins.txt
File diff suppressed because it is too large
Load Diff
38946
data/plugins_full.txt
38946
data/plugins_full.txt
File diff suppressed because it is too large
Load Diff
2067
data/theme_vulns.xml
2067
data/theme_vulns.xml
File diff suppressed because it is too large
Load Diff
300
data/themes.txt
300
data/themes.txt
@@ -1,300 +0,0 @@
|
||||
2013-black-and-white
|
||||
academica
|
||||
accessible-zen
|
||||
adamos
|
||||
adelle
|
||||
admired
|
||||
adventure
|
||||
adventure-journal
|
||||
aldehyde
|
||||
alexandria
|
||||
alhena-lite
|
||||
analytical-lite
|
||||
andrina-lite
|
||||
annotum-base
|
||||
ascetica
|
||||
aspen
|
||||
atahualpa
|
||||
atheros
|
||||
attitude
|
||||
attorney
|
||||
autofocus
|
||||
beach
|
||||
birdsite
|
||||
birdtips
|
||||
biznez-lite
|
||||
bizstudio-lite
|
||||
bizway
|
||||
black-rider
|
||||
blackbird
|
||||
blain
|
||||
blankslate
|
||||
blogbox
|
||||
blogolife
|
||||
bold-headline
|
||||
boldr-lite
|
||||
book-lite
|
||||
boot-store
|
||||
bota
|
||||
bouquet
|
||||
bresponzive
|
||||
bueno
|
||||
business-lite
|
||||
busiprof
|
||||
buzz
|
||||
careta
|
||||
carton
|
||||
catch-box
|
||||
catch-everest
|
||||
catch-evolution
|
||||
cazuela
|
||||
celebrate
|
||||
celestial-lite
|
||||
central
|
||||
chaostheory
|
||||
cherry-blossom
|
||||
childishly-simple
|
||||
church
|
||||
clean-black
|
||||
clean-retina
|
||||
coller
|
||||
colorway
|
||||
contango
|
||||
coraline
|
||||
corpo
|
||||
custom-community
|
||||
customizr
|
||||
cyberchimps
|
||||
cycnus
|
||||
d5-business-line
|
||||
d5-design
|
||||
d5-socialia
|
||||
dailypost
|
||||
decode
|
||||
delicate
|
||||
delighted
|
||||
designfolio
|
||||
destro
|
||||
deux-milles-douze
|
||||
discover
|
||||
dms
|
||||
duena
|
||||
dusk-to-dawn
|
||||
duster
|
||||
dw-minion
|
||||
easel
|
||||
eclipse
|
||||
elegantwhite
|
||||
emphaino
|
||||
encounters-lite
|
||||
engrave-lite
|
||||
epic
|
||||
esplanade
|
||||
espressionista
|
||||
esquire
|
||||
estate
|
||||
evolve
|
||||
expound
|
||||
fashionistas
|
||||
fine
|
||||
firmasite
|
||||
fluxipress
|
||||
focus
|
||||
forever
|
||||
frisco-for-buddypress
|
||||
frontier
|
||||
fruitful
|
||||
futuristica
|
||||
gamepress
|
||||
golden-eagle-lite
|
||||
graphene
|
||||
greenpage
|
||||
gridiculous
|
||||
gridster-lite
|
||||
hannari
|
||||
hatch
|
||||
hazen
|
||||
heatmap-adaptive
|
||||
hero
|
||||
hiero
|
||||
highwind
|
||||
hostmarks
|
||||
houston
|
||||
hro
|
||||
hybrid
|
||||
iconic-one
|
||||
icy
|
||||
ifeature
|
||||
imag-mag
|
||||
impressio-lite
|
||||
impulse
|
||||
infoway
|
||||
innovative
|
||||
intuition
|
||||
irex-lite
|
||||
iribbon
|
||||
kabbo
|
||||
klasik
|
||||
koenda
|
||||
lamya
|
||||
landscape
|
||||
leaf
|
||||
litesta
|
||||
lobster
|
||||
local-business
|
||||
lugada
|
||||
luminescence-lite
|
||||
magazine
|
||||
magazine-basic
|
||||
magazine-style
|
||||
magazino
|
||||
manchester
|
||||
mantra
|
||||
marla
|
||||
max-magazine
|
||||
melany
|
||||
mh-magazine-lite
|
||||
minimatica
|
||||
minimize
|
||||
mixfolio
|
||||
modern-estate
|
||||
mon-cahier
|
||||
montezuma
|
||||
multipurpose
|
||||
my-depressive
|
||||
my-world-with-grass-and-dew
|
||||
mystique
|
||||
narga
|
||||
neuro
|
||||
newp
|
||||
next-saturday
|
||||
omega
|
||||
openstrap
|
||||
opulus-sombre
|
||||
origami
|
||||
origin
|
||||
oxygen
|
||||
p2
|
||||
p2-categories
|
||||
pagelines
|
||||
parabola
|
||||
parament
|
||||
path
|
||||
phonix
|
||||
photographic
|
||||
photolistic
|
||||
photologger
|
||||
pilcrow
|
||||
pilot-fish
|
||||
pinbin
|
||||
pinboard
|
||||
pink-touch-2
|
||||
pitch
|
||||
platform
|
||||
point
|
||||
portfolio-press
|
||||
pr-news
|
||||
prana
|
||||
preference-lite
|
||||
quark
|
||||
r2d2
|
||||
raindrops
|
||||
raptor
|
||||
raven
|
||||
response
|
||||
responsive
|
||||
restaurateur
|
||||
retro
|
||||
road-fighter
|
||||
ryu
|
||||
sampression-lite
|
||||
sensitive
|
||||
serene
|
||||
shprink-one
|
||||
silverclean-lite
|
||||
simple-and-clean
|
||||
simple-catch
|
||||
simpleo
|
||||
simplicity-lite
|
||||
simplify
|
||||
sixteen
|
||||
sliding-door
|
||||
small-business
|
||||
snaps
|
||||
snapshot
|
||||
snowblind
|
||||
socially-awkward
|
||||
spartan
|
||||
spasalon
|
||||
spine
|
||||
spun
|
||||
squirrel
|
||||
startupwp
|
||||
steira
|
||||
strapvert
|
||||
striker
|
||||
suevafree
|
||||
suffusion
|
||||
suits
|
||||
sukelius-magazine
|
||||
sundance
|
||||
sunny-blue-sky
|
||||
sunspot
|
||||
supernova
|
||||
surfarama
|
||||
sweet-tech
|
||||
swift-basic
|
||||
tampa
|
||||
target
|
||||
teal
|
||||
terrifico
|
||||
tesla
|
||||
the-bootstrap
|
||||
thematic
|
||||
themia-lite
|
||||
theron-lite
|
||||
timeturner
|
||||
tiny-forge
|
||||
tonic
|
||||
toolbox
|
||||
travel-blogger
|
||||
travelify
|
||||
tribbiani
|
||||
twentyeleven
|
||||
twentyten
|
||||
twentythirteen
|
||||
twentytwelve
|
||||
unique
|
||||
untitled
|
||||
uptown
|
||||
vantage
|
||||
viper
|
||||
virtue
|
||||
visitpress
|
||||
visual
|
||||
vortex
|
||||
voyage
|
||||
ward
|
||||
weaver-ii
|
||||
wordpost
|
||||
wp-creativix
|
||||
wp-flatthirteen
|
||||
wp-knowledge-base
|
||||
wp-opulus
|
||||
xin-magazine
|
||||
yoko
|
||||
zalive
|
||||
zbench
|
||||
zeebizzcard
|
||||
zeebusiness
|
||||
zeedynamic
|
||||
zeeflow
|
||||
zeefocus
|
||||
zeemagazine
|
||||
zeeminty
|
||||
zeenews
|
||||
zeenoble
|
||||
zeestyle
|
||||
zeesynergie
|
||||
zeetasty
|
||||
zenon-lite
|
||||
7549
data/themes_full.txt
7549
data/themes_full.txt
File diff suppressed because it is too large
Load Diff
2459
data/timthumbs.txt
2459
data/timthumbs.txt
File diff suppressed because it is too large
Load Diff
106
data/vuln.xsd
106
data/vuln.xsd
@@ -1,106 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:simpleType name="stringtype">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="inttype">
|
||||
<xs:restriction base="xs:positiveInteger" />
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="uritype">
|
||||
<xs:restriction base="xs:anyURI">
|
||||
<xs:minLength value="1" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="cvetype">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:pattern value="[0-9]{4}-[0-9]{4,}"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="typetype">
|
||||
<xs:restriction base="stringtype">
|
||||
<xs:enumeration value="SQLI"/>
|
||||
<xs:enumeration value="MULTI"/>
|
||||
<xs:enumeration value="REDIRECT"/>
|
||||
<xs:enumeration value="RCE"/>
|
||||
<xs:enumeration value="RFI"/>
|
||||
<xs:enumeration value="LFI"/>
|
||||
<xs:enumeration value="UPLOAD"/>
|
||||
<xs:enumeration value="UNKNOWN"/>
|
||||
<xs:enumeration value="XSS"/>
|
||||
<xs:enumeration value="CSRF"/>
|
||||
<xs:enumeration value="SSRF"/>
|
||||
<xs:enumeration value="AUTHBYPASS"/>
|
||||
<xs:enumeration value="FPD"/>
|
||||
<xs:enumeration value="XXE"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="itemtype">
|
||||
<xs:sequence minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:element name="vulnerability" type="vulntype" />
|
||||
</xs:sequence>
|
||||
<xs:attribute type="stringtype" name="name" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="wordpresstype">
|
||||
<xs:sequence minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:element name="vulnerability" type="vulntype"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="stringtype" name="version" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="vulntype">
|
||||
<xs:sequence minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:choice>
|
||||
<xs:element name="title" type="stringtype"/>
|
||||
<xs:element name="type" type="typetype"/>
|
||||
<xs:element name="fixed_in" type="stringtype"/>
|
||||
<xs:element name="references" type="referencetype"/>
|
||||
</xs:choice>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="referencetype">
|
||||
<xs:sequence minOccurs="1" maxOccurs="unbounded">
|
||||
<xs:choice>
|
||||
<xs:element name="url" type="uritype" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="cve" type="cvetype" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="secunia" type="inttype" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="osvdb" type="inttype" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="metasploit" type="stringtype" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="exploitdb" type="inttype" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:choice>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="vulnerabilities">
|
||||
<xs:complexType>
|
||||
<xs:choice>
|
||||
<xs:element name="plugin" type="itemtype" maxOccurs="unbounded" minOccurs="0"/>
|
||||
<xs:element name="theme" type="itemtype" maxOccurs="unbounded" minOccurs="0"/>
|
||||
<xs:element name="wordpress" type="wordpresstype" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
<xs:unique name="uniquePlugin">
|
||||
<xs:selector xpath="plugin"/>
|
||||
<xs:field xpath="@name"/>
|
||||
</xs:unique>
|
||||
<xs:unique name="uniqueTheme">
|
||||
<xs:selector xpath="theme"/>
|
||||
<xs:field xpath="@name"/>
|
||||
</xs:unique>
|
||||
<xs:unique name="uniqueWordpress">
|
||||
<xs:selector xpath="wordpress"/>
|
||||
<xs:field xpath="@version"/>
|
||||
</xs:unique>
|
||||
</xs:element>
|
||||
|
||||
</xs:schema>
|
||||
@@ -1,223 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
This file contains identification data to identify WordPress versions.
|
||||
http://wordpress.org/download/release-archive/
|
||||
|
||||
Position is important, DO NOT change anything unless you know what you are doing :p
|
||||
-->
|
||||
|
||||
<wp-versions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="wp_versions.xsd">
|
||||
|
||||
<file src="wp-includes/js/tinymce/plugins/wpeditimage/editor_plugin_src.js">
|
||||
<hash md5="5d01c0e812cdcd6356b78ee0cb4e5426">
|
||||
<version>3.7.1</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
<file src="wp-includes/js/jquery/jquery.form.js">
|
||||
<hash md5="e5afd8e41d2ec22c19932b068cd90a71">
|
||||
<version>3.7</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
<file src="wp-admin/js/common.js">
|
||||
<hash md5="03eaffeef39119f0523a49c7f9767f3b">
|
||||
<version>3.6.1</version>
|
||||
</hash>
|
||||
<hash md5="4516252d47a73630280869994d510180">
|
||||
<version>3.3</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
<file src="wp-includes/js/jquery/jquery.js">
|
||||
<hash md5="9dcde2d5e8aeda556a0c52239fa2f44c">
|
||||
<version>3.6</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
<file src="wp-includes/js/tinymce/tiny_mce.js">
|
||||
<hash md5="eddb5fda74d41dbdac018167536d8d53">
|
||||
<version>3.5.2</version>
|
||||
</hash>
|
||||
|
||||
<hash md5="6e79ab6d786c5c95920064add33ee599">
|
||||
<version>3.5.1</version>
|
||||
</hash>
|
||||
|
||||
<hash md5="55cd8e5ceca9c1763b1401164d70df50">
|
||||
<version>3.5</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
<file src="wp-includes/js/wp-lists.js">
|
||||
<hash md5="46e1341cd4ea49f31046f7d7962adc7f">
|
||||
<version>3.4.2</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
<file src="wp-includes/js/customize-preview.js">
|
||||
<hash md5="617d9fd858e117c7d1d087be168b5643">
|
||||
<version>3.4.1</version>
|
||||
</hash>
|
||||
|
||||
<hash md5="da36bc2dfcb13350c799b62de68dfa4b">
|
||||
<version>3.4</version>
|
||||
</hash>
|
||||
|
||||
<hash md5="a8a259fc5197a78ffe62d6be38dc52f8">
|
||||
<version>3.4-beta4</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
<file src="wp-includes/js/plupload/plupload.js">
|
||||
<hash md5="85199c05db63fcb5880de4af8be7b571">
|
||||
<version>3.3.2</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
|
||||
<file src="$wp-content$/themes/twentyeleven/style.css">
|
||||
|
||||
<!-- same md5 for 3.3.2 -->
|
||||
<hash md5="030d3bac906ba69e9fbc99c5bac54a8e">
|
||||
<version>3.3.1</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="wp-admin/js/wp-fullscreen.js">
|
||||
|
||||
<hash md5="5675f7793f171b6424bf72f9d7bf4d9a">
|
||||
<version>3.2.1</version>
|
||||
</hash>
|
||||
|
||||
<hash md5="7b423e0b7c9221092737ad5271d09863">
|
||||
<version>3.2</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
<file src="wp-includes/css/admin-bar.css">
|
||||
<hash md5="181250fab3a7e2549a7e7fa21c2e6079">
|
||||
<version>3.1</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
|
||||
<file src="$wp-content$/themes/twentyten/style.css">
|
||||
|
||||
<hash md5="6211e2ac1463bf99e98f28ab63e47c54">
|
||||
<version>3.0</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="$wp-plugins$/akismet/readme.txt">
|
||||
|
||||
<hash md5="4d5e52da417aa0101054bd41e6243389">
|
||||
<version>2.8.6</version>
|
||||
</hash>
|
||||
|
||||
<hash md5="58e086dea9d24ed074fe84ba87386c69">
|
||||
<version>2.8.5</version>
|
||||
</hash>
|
||||
|
||||
<hash md5="48c52025b5f28731e9a0c864c189c2e7">
|
||||
<version>2.8.2</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="wp-includes/js/wp-ajax-response.js">
|
||||
|
||||
<hash md5="0289d1c13821599764774d55516ab81a">
|
||||
<version>2.7.1</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="wp-includes/js/thickbox/thickbox.css">
|
||||
|
||||
<hash md5="9c2bd2be0893adbe02a0f864526734c2">
|
||||
<version>2.7</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="wp-includes/js/tinymce/plugins/wpeditimage/editor_plugin.js">
|
||||
|
||||
<hash md5="5b140ddf0f08034402ae78b31d8a1a28">
|
||||
<version>2.6</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="wp-includes/js/tinymce/themes/advanced/js/image.js">
|
||||
|
||||
<hash md5="088245408531c58bb52cc092294cc384">
|
||||
<version>2.5.1</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="wp-includes/js/tinymce/themes/advanced/js/link.js">
|
||||
|
||||
<hash md5="19c6f3118728c38eb7779aab4847d2d9">
|
||||
<version>2.5</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="wp-includes/js/wp-ajax.js">
|
||||
|
||||
<hash md5="c5dbce0c3232c477033e0ce486c62755">
|
||||
<version>2.2</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="$wp-content$/themes/default/style.css">
|
||||
|
||||
<hash md5="e44545f529a54de88209ce588676231c">
|
||||
<version>2.0.1</version>
|
||||
</hash>
|
||||
|
||||
<hash md5="f786f66d3a40846aa22dcdfeb44fa562">
|
||||
<version>2.0</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
|
||||
<file src="wp-layout.css">
|
||||
|
||||
<hash md5="7140e06c00ed03d2bb3dad7672557510">
|
||||
<version>1.2.1</version>
|
||||
</hash>
|
||||
|
||||
<hash md5="1bcc9253506c067eb130c9fc4f211a2f">
|
||||
<version>1.2-delta</version>
|
||||
</hash>
|
||||
</file>
|
||||
|
||||
|
||||
<file src="layout2b.css">
|
||||
|
||||
<hash md5="baec6b6ccbf71d8dced9f1bf67c751e1">
|
||||
<version>0.71-gold</version>
|
||||
</hash>
|
||||
|
||||
</file>
|
||||
|
||||
</wp-versions>
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<xs:simpleType name="stringtype">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:minLength value="1" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="filetype">
|
||||
<xs:sequence>
|
||||
<xs:element name="hash" type="hashtype" maxOccurs="unbounded" minOccurs="1"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="stringtype" name="src" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="md5type">
|
||||
<xs:restriction base="stringtype">
|
||||
<xs:pattern value="[0-9a-f]{32}"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="hashtype">
|
||||
<xs:sequence minOccurs="1" maxOccurs="1">
|
||||
<xs:element name="version" type="stringtype"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute type="md5type" name="md5" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:element name="wp-versions">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="file" type="filetype" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
</xs:schema>
|
||||
2230
data/wp_vulns.xml
2230
data/wp_vulns.xml
File diff suppressed because it is too large
Load Diff
41
dev/pre-commit-hook.rb
Executable file
41
dev/pre-commit-hook.rb
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# from the top level dir:
|
||||
# ln -sf ../../dev/pre-commit-hook.rb .git/hooks/pre-commit
|
||||
|
||||
require 'pty'
|
||||
html_path = 'rspec_results.html'
|
||||
|
||||
begin
|
||||
PTY.spawn( "rspec spec --format h > #{html_path}" ) do |stdin, stdout, pid|
|
||||
begin
|
||||
stdin.each { |line| print line }
|
||||
rescue Errno::EIO => e
|
||||
puts "Error: #{e.to.s}"
|
||||
return 1
|
||||
end
|
||||
end
|
||||
rescue PTY::ChildExited
|
||||
puts 'Child process exit!'
|
||||
end
|
||||
|
||||
# find out if there were any errors
|
||||
html = open(html_path).read
|
||||
examples = html.match(/(\d+) examples/)[0].to_i rescue 0
|
||||
errors = html.match(/(\d+) errors/)[0].to_i rescue 0
|
||||
if errors == 0 then
|
||||
errors = html.match(/(\d+) failure/)[0].to_i rescue 0
|
||||
end
|
||||
pending = html.match(/(\d+) pending/)[0].to_i rescue 0
|
||||
|
||||
if errors.zero?
|
||||
puts "0 failed! #{examples} run, #{pending} pending"
|
||||
sleep 1
|
||||
exit 0
|
||||
else
|
||||
puts "\aCOMMIT FAILED!!"
|
||||
puts "View your rspec results at #{File.expand_path(html_path)}"
|
||||
puts
|
||||
puts "#{errors} failed! #{examples} run, #{pending} pending"
|
||||
exit 1
|
||||
end
|
||||
18
example.conf.json
Normal file
18
example.conf.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0",
|
||||
|
||||
/* Uncomment the "proxy" line to use the proxy
|
||||
SOCKS proxies (4, 4A, 5) are supported, ie : "proxy": "socks5://127.0.0.1:9000"
|
||||
If you do not specify the protocol, http will be used
|
||||
*/
|
||||
//"proxy": "127.0.0.1:3128",
|
||||
//"proxy_auth": "username:password",
|
||||
|
||||
"cache_ttl": 600, // 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
|
||||
|
||||
"request_timeout": 2000, // 2s
|
||||
|
||||
"connect_timeout": 1000, // 1s
|
||||
|
||||
"max_threads": 20
|
||||
}
|
||||
@@ -9,30 +9,35 @@ class Browser
|
||||
include Browser::Options
|
||||
|
||||
OPTIONS = [
|
||||
:available_user_agents,
|
||||
:basic_auth,
|
||||
:cache_ttl,
|
||||
:max_threads,
|
||||
:user_agent,
|
||||
:user_agent_mode,
|
||||
:proxy,
|
||||
:proxy_auth,
|
||||
:request_timeout,
|
||||
:connect_timeout
|
||||
:connect_timeout,
|
||||
:cookie
|
||||
]
|
||||
|
||||
@@instance = nil
|
||||
|
||||
attr_reader :hydra, :config_file, :cache_dir
|
||||
attr_reader :hydra, :cache_dir
|
||||
|
||||
attr_accessor :referer, :cookie
|
||||
|
||||
# @param [ Hash ] options
|
||||
#
|
||||
# @return [ Browser ]
|
||||
def initialize(options = {})
|
||||
@config_file = options[:config_file] || CONF_DIR + '/browser.conf.json'
|
||||
@cache_dir = options[:cache_dir] || CACHE_DIR + '/browser'
|
||||
@cache_dir = options[:cache_dir] || CACHE_DIR + '/browser'
|
||||
|
||||
load_config
|
||||
# sets browser defaults
|
||||
browser_defaults
|
||||
# load config file
|
||||
conf = options[:config_file]
|
||||
load_config(conf) if conf
|
||||
# overrides defaults with user supplied values (overwrite values from config)
|
||||
override_config(options)
|
||||
|
||||
unless @hydra
|
||||
@@ -61,6 +66,20 @@ class Browser
|
||||
@@instance = nil
|
||||
end
|
||||
|
||||
#
|
||||
# sets browser default values
|
||||
#
|
||||
def browser_defaults
|
||||
@max_threads = 20
|
||||
# 10 minutes, at this time the cache is cleaned before each scan. If this value is set to 0, the cache will be disabled
|
||||
@cache_ttl = 600
|
||||
# 2s
|
||||
@request_timeout = 2000
|
||||
# 1s
|
||||
@connect_timeout = 1000
|
||||
@user_agent = "WPScan v#{WPSCAN_VERSION} (http://wpscan.org)"
|
||||
end
|
||||
|
||||
#
|
||||
# If an option was set but is not in the new config_file
|
||||
# it's value is kept
|
||||
@@ -69,21 +88,20 @@ class Browser
|
||||
#
|
||||
# @return [ void ]
|
||||
def load_config(config_file = nil)
|
||||
@config_file = config_file || @config_file
|
||||
|
||||
if File.symlink?(@config_file)
|
||||
if File.symlink?(config_file)
|
||||
raise '[ERROR] Config file is a symlink.'
|
||||
else
|
||||
data = JSON.parse(File.read(@config_file))
|
||||
data = JSON.parse(File.read(config_file))
|
||||
end
|
||||
|
||||
OPTIONS.each do |option|
|
||||
option_name = option.to_s
|
||||
|
||||
unless data[option_name].nil?
|
||||
self.send(:"#{option_name}=", data[option_name])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# @param [ String ] url
|
||||
@@ -101,7 +119,7 @@ class Browser
|
||||
params = Browser.append_params_header_field(
|
||||
params,
|
||||
'User-Agent',
|
||||
self.user_agent
|
||||
@user_agent
|
||||
)
|
||||
|
||||
if @proxy
|
||||
@@ -120,18 +138,15 @@ class Browser
|
||||
)
|
||||
end
|
||||
|
||||
if @request_timeout
|
||||
params = params.merge(timeout: @request_timeout)
|
||||
end
|
||||
|
||||
if @connect_timeout
|
||||
params = params.merge(connecttimeout: @connect_timeout)
|
||||
end
|
||||
params.merge!(referer: referer)
|
||||
params.merge!(timeout: @request_timeout) if @request_timeout
|
||||
params.merge!(connecttimeout: @connect_timeout) if @connect_timeout
|
||||
|
||||
# Used to enable the cache system if :cache_ttl > 0
|
||||
unless params.has_key?(:cache_ttl)
|
||||
params = params.merge(cache_ttl: @cache_ttl)
|
||||
end
|
||||
params.merge!(cache_ttl: @cache_ttl) unless params.has_key?(:cache_ttl)
|
||||
|
||||
# Prevent infinite self redirection
|
||||
params.merge!(maxredirs: 3) unless params.has_key?(:maxredirs)
|
||||
|
||||
# Disable SSL-Certificate checks
|
||||
params.merge!(ssl_verifypeer: false)
|
||||
@@ -139,6 +154,7 @@ class Browser
|
||||
|
||||
params.merge!(cookiejar: @cache_dir + '/cookie-jar')
|
||||
params.merge!(cookiefile: @cache_dir + '/cookie-jar')
|
||||
params.merge!(cookie: @cookie) if @cookie
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
class Browser
|
||||
module Options
|
||||
|
||||
USER_AGENT_MODES = %w{ static semi-static random }
|
||||
|
||||
attr_accessor :available_user_agents, :cache_ttl, :request_timeout, :connect_timeout
|
||||
attr_reader :basic_auth, :user_agent_mode, :proxy, :proxy_auth
|
||||
attr_accessor :cache_ttl, :request_timeout, :connect_timeout
|
||||
attr_reader :basic_auth, :proxy, :proxy_auth
|
||||
attr_writer :user_agent
|
||||
|
||||
# Sets the Basic Authentification credentials
|
||||
@@ -41,42 +39,6 @@ class Browser
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the user_agent_mode, which can be one of the following:
|
||||
# static: The UA is defined by the user, and will be the same in each requests
|
||||
# semi-static: The UA is randomly chosen at the first request, and will not change
|
||||
# random: UA randomly chosen each request
|
||||
#
|
||||
# UA are from @available_user_agents
|
||||
#
|
||||
# @param [ String ] ua_mode
|
||||
#
|
||||
# @return [ void ]
|
||||
def user_agent_mode=(ua_mode)
|
||||
ua_mode ||= 'static'
|
||||
|
||||
if USER_AGENT_MODES.include?(ua_mode)
|
||||
@user_agent_mode = ua_mode
|
||||
# For semi-static user agent mode, the user agent has to
|
||||
# be nil the first time (it will be set with the getter)
|
||||
@user_agent = nil if ua_mode === 'semi-static'
|
||||
else
|
||||
raise "Unknow user agent mode : '#{ua_mode}'"
|
||||
end
|
||||
end
|
||||
|
||||
# @return [ String ] The user agent, according to the user_agent_mode
|
||||
def user_agent
|
||||
case @user_agent_mode
|
||||
when 'semi-static'
|
||||
unless @user_agent
|
||||
@user_agent = @available_user_agents.sample
|
||||
end
|
||||
when 'random'
|
||||
@user_agent = @available_user_agents.sample
|
||||
end
|
||||
@user_agent
|
||||
end
|
||||
|
||||
# Sets the proxy
|
||||
# Accepted format:
|
||||
# [protocol://]host:post
|
||||
|
||||
@@ -9,17 +9,19 @@
|
||||
#
|
||||
|
||||
require 'yaml'
|
||||
require 'fileutils'
|
||||
|
||||
class CacheFileStore
|
||||
attr_reader :storage_path, :serializer
|
||||
attr_reader :storage_path, :cache_dir, :serializer
|
||||
|
||||
# The serializer must have the 2 methods .load and .dump
|
||||
# (Marshal and YAML have them)
|
||||
# YAML is Human Readable, contrary to Marshal which store in a binary format
|
||||
# Marshal does not need any "require"
|
||||
def initialize(storage_path, serializer = Marshal)
|
||||
@storage_path = File.expand_path(storage_path)
|
||||
@serializer = serializer
|
||||
@cache_dir = File.expand_path(storage_path)
|
||||
@storage_path = File.expand_path(File.join(storage_path, storage_dir))
|
||||
@serializer = serializer
|
||||
|
||||
# File.directory? for ruby <= 1.9 otherwise,
|
||||
# it makes more sense to do Dir.exist? :/
|
||||
@@ -29,16 +31,22 @@ class CacheFileStore
|
||||
end
|
||||
|
||||
def clean
|
||||
Dir[File.join(@storage_path, '*')].each do |f|
|
||||
File.delete(f) unless File.symlink?(f)
|
||||
# clean old directories
|
||||
Dir[File.join(@cache_dir, '*')].each do |f|
|
||||
if File.directory?(f)
|
||||
# delete directory if create time is older than 4 hours
|
||||
FileUtils.rm_rf(f) if File.mtime(f) < (Time.now - (60*240))
|
||||
else
|
||||
File.delete(f) unless File.symlink?(f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def read_entry(key)
|
||||
entry_file_path = get_entry_file_path(key)
|
||||
|
||||
if File.exists?(entry_file_path)
|
||||
return @serializer.load(File.read(entry_file_path))
|
||||
begin
|
||||
@serializer.load(File.read(get_entry_file_path(key)))
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,4 +66,11 @@ class CacheFileStore
|
||||
File::join(@storage_path, key)
|
||||
end
|
||||
|
||||
def storage_dir
|
||||
time = Time.now
|
||||
random = (0...8).map { (65 + rand(26)).chr }.join
|
||||
|
||||
Digest::MD5.hexdigest("#{time}#{random}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
class Vulnerabilities < Array
|
||||
module Output
|
||||
|
||||
def output
|
||||
def output(verbose = false)
|
||||
self.each do |v|
|
||||
v.output
|
||||
v.output(verbose)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class WpItems < Array
|
||||
self.wp_target = wp_target
|
||||
end
|
||||
|
||||
# @param [String,] argv
|
||||
# @param [String] argv
|
||||
#
|
||||
# @return [ void ]
|
||||
def add(*args)
|
||||
|
||||
@@ -17,15 +17,13 @@ class WpItems < Array
|
||||
hydra = browser.hydra
|
||||
targets = targets_items(wp_target, options)
|
||||
progress_bar = progress_bar(targets.size, options)
|
||||
queue_count = 0
|
||||
exist_options = {
|
||||
error_404_hash: wp_target.error_404_hash,
|
||||
homepage_hash: wp_target.homepage_hash,
|
||||
exclude_content: options[:exclude_content] ? %r{#{options[:exclude_content]}} : nil
|
||||
}
|
||||
|
||||
# If we only want the vulnerable ones, the passive detection is ignored
|
||||
# Otherwise, a passive detection is performed, and results will be merged
|
||||
results = options[:only_vulnerable] ? new : passive_detection(wp_target, options)
|
||||
results = passive_detection(wp_target, options)
|
||||
|
||||
targets.each do |target_item|
|
||||
request = browser.forge_request(target_item.url, request_params)
|
||||
@@ -43,11 +41,22 @@ class WpItems < Array
|
||||
end
|
||||
|
||||
hydra.queue(request)
|
||||
queue_count += 1
|
||||
|
||||
if queue_count >= browser.max_threads
|
||||
hydra.run
|
||||
queue_count = 0
|
||||
puts "Sent #{browser.max_threads} requests ..." if options[:verbose]
|
||||
end
|
||||
end
|
||||
|
||||
# run the remaining requests
|
||||
hydra.run
|
||||
|
||||
results.select!(&:vulnerable?) if options[:only_vulnerable]
|
||||
results.sort!
|
||||
results # can't just return results.sort because the #sort returns an array, and we want a WpItems
|
||||
|
||||
results # can't just return results.sort as it would return an array, and we want a WpItems
|
||||
end
|
||||
|
||||
# @param [ Integer ] targets_size
|
||||
@@ -71,13 +80,31 @@ class WpItems < Array
|
||||
#
|
||||
# @return [ WpItems ]
|
||||
def passive_detection(wp_target, options = {})
|
||||
results = new(wp_target)
|
||||
body = Browser.get(wp_target.url).body
|
||||
results = new(wp_target)
|
||||
# improves speed
|
||||
body = remove_base64_images_from_html(body)
|
||||
names = body.scan(passive_detection_pattern(wp_target))
|
||||
body = remove_base64_images_from_html(Browser.get(wp_target.url).body)
|
||||
page = Nokogiri::HTML(body)
|
||||
names = []
|
||||
|
||||
names.flatten.uniq.each { |name| results.add(name) }
|
||||
page.css('link,script,style').each do |tag|
|
||||
%w(href src).each do |attribute|
|
||||
attr_value = tag.attribute(attribute).to_s
|
||||
next unless attr_value
|
||||
|
||||
names << Regexp.last_match[1] if attr_value.match(attribute_pattern(wp_target))
|
||||
end
|
||||
|
||||
next unless tag.name == 'script' || tag.name == 'style'
|
||||
|
||||
code = tag.text.to_s
|
||||
next if code.empty?
|
||||
|
||||
code.scan(code_pattern(wp_target)).flatten.uniq.each do |item_name|
|
||||
names << item_name
|
||||
end
|
||||
end
|
||||
|
||||
names.uniq.each { |name| results.add(name) }
|
||||
|
||||
results.sort!
|
||||
results
|
||||
@@ -88,13 +115,29 @@ class WpItems < Array
|
||||
# @param [ WpTarget ] wp_target
|
||||
#
|
||||
# @return [ Regex ]
|
||||
def passive_detection_pattern(wp_target)
|
||||
type = self.to_s.gsub(/Wp/, '').downcase
|
||||
regex1 = %r{(?:[^=:\(]+)\s?(?:=|:|\()\s?(?:"|')[^"']+\\?/}
|
||||
regex2 = %r{\\?/}
|
||||
regex3 = %r{\\?/([^/\\"']+)\\?(?:/|"|')}
|
||||
def item_pattern(wp_target)
|
||||
type = to_s.gsub(/Wp/, '').downcase
|
||||
wp_content_dir = wp_target.wp_content_dir
|
||||
wp_content_url = wp_target.uri.merge(wp_content_dir).to_s
|
||||
|
||||
/#{regex1}#{Regexp.escape(wp_target.wp_content_dir)}#{regex2}#{Regexp.escape(type)}#{regex3}/i
|
||||
url = /#{wp_content_url.gsub(%r{\A(?:http|https)}, 'https?').gsub('/', '\\\\\?\/')}/i
|
||||
content_dir = %r{(?:#{url}|\\?\/\\?\/?#{wp_content_dir})}i
|
||||
|
||||
%r{#{content_dir}\\?/#{type}\\?/}
|
||||
end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
#
|
||||
# @return [ Regex ]
|
||||
def attribute_pattern(wp_target)
|
||||
/\A#{item_pattern(wp_target)}([^\/]+)/i
|
||||
end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
#
|
||||
# @return [ Regex ]
|
||||
def code_pattern(wp_target)
|
||||
/["'\(]#{item_pattern(wp_target)}([^\\\/\)"']+)/i
|
||||
end
|
||||
|
||||
# The default request parameters
|
||||
@@ -133,16 +176,17 @@ class WpItems < Array
|
||||
# @return [ Array<WpItem> ]
|
||||
def vulnerable_targets_items(wp_target, item_class, vulns_file)
|
||||
targets = []
|
||||
xml = xml(vulns_file)
|
||||
json = json(vulns_file)
|
||||
|
||||
xml.xpath(item_xpath).each do |node|
|
||||
[*json].each do |item|
|
||||
targets << create_item(
|
||||
item_class,
|
||||
node.attribute('name').text,
|
||||
item.keys.inject,
|
||||
wp_target,
|
||||
vulns_file
|
||||
)
|
||||
end
|
||||
|
||||
targets
|
||||
end
|
||||
|
||||
@@ -181,6 +225,7 @@ class WpItems < Array
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
targets
|
||||
end
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
class WpItems < Array
|
||||
module Output
|
||||
|
||||
def output
|
||||
self.each { |item| item.output }
|
||||
def output(verbose = false)
|
||||
self.each { |item| item.output(verbose) }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -9,9 +9,9 @@ class WpPlugins < WpItems
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def item_xpath
|
||||
'//plugin'
|
||||
end
|
||||
# def item_xpath
|
||||
# '//plugin'
|
||||
# end
|
||||
|
||||
# @param [ WpTarget ] wp_target
|
||||
# @param [ Hash ] options
|
||||
@@ -68,6 +68,10 @@ class WpPlugins < WpItems
|
||||
wp_plugins.add('all-in-one-seo-pack', version: $1)
|
||||
end
|
||||
|
||||
if body =~ /<!-- This site is optimized with the Yoast WordPress SEO plugin v([^\s]+) -/i
|
||||
wp_plugins.add('wordpress-seo', version: $1)
|
||||
end
|
||||
|
||||
wp_plugins
|
||||
end
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ class WpThemes < WpItems
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def item_xpath
|
||||
'//theme'
|
||||
end
|
||||
# def item_xpath
|
||||
# '//theme'
|
||||
# end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,6 +38,7 @@ class WpUsers < WpItems
|
||||
junk = get_equal_string_end(display_names)
|
||||
unless junk.nil? or junk.empty?
|
||||
self.each do |u|
|
||||
u.display_name ||= ''
|
||||
u.display_name = u.display_name.sub(/#{Regexp.escape(junk)}$/, '')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
LIB_DIR = File.expand_path(File.dirname(__FILE__) + '/..')
|
||||
ROOT_DIR = File.expand_path(LIB_DIR + '/..') # expand_path is used to get "wpscan/" instead of "wpscan/lib/../"
|
||||
DATA_DIR = ROOT_DIR + '/data'
|
||||
CONF_DIR = ROOT_DIR + '/conf'
|
||||
CACHE_DIR = ROOT_DIR + '/cache'
|
||||
WPSCAN_LIB_DIR = LIB_DIR + '/wpscan'
|
||||
WPSTOOLS_LIB_DIR = LIB_DIR + '/wpstools'
|
||||
UPDATER_LIB_DIR = LIB_DIR + '/updater'
|
||||
COMMON_LIB_DIR = LIB_DIR + '/common'
|
||||
MODELS_LIB_DIR = COMMON_LIB_DIR + '/models'
|
||||
COLLECTIONS_LIB_DIR = COMMON_LIB_DIR + '/collections'
|
||||
LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
||||
ROOT_DIR = File.expand_path(File.join(LIB_DIR, '..')) # expand_path is used to get "wpscan/" instead of "wpscan/lib/../"
|
||||
DATA_DIR = File.join(ROOT_DIR, 'data')
|
||||
CONF_DIR = File.join(ROOT_DIR, 'conf')
|
||||
CACHE_DIR = File.join(ROOT_DIR, 'cache')
|
||||
WPSCAN_LIB_DIR = File.join(LIB_DIR, 'wpscan')
|
||||
WPSTOOLS_LIB_DIR = File.join(LIB_DIR, 'wpstools')
|
||||
UPDATER_LIB_DIR = File.join(LIB_DIR, 'updater')
|
||||
COMMON_LIB_DIR = File.join(LIB_DIR, 'common')
|
||||
MODELS_LIB_DIR = File.join(COMMON_LIB_DIR, 'models')
|
||||
COLLECTIONS_LIB_DIR = File.join(COMMON_LIB_DIR, 'collections')
|
||||
|
||||
LOG_FILE = ROOT_DIR + '/log.txt'
|
||||
LOG_FILE = File.join(ROOT_DIR, 'log.txt')
|
||||
|
||||
# Plugins directories
|
||||
COMMON_PLUGINS_DIR = COMMON_LIB_DIR + '/plugins'
|
||||
WPSCAN_PLUGINS_DIR = WPSCAN_LIB_DIR + '/plugins' # Not used ATM
|
||||
WPSTOOLS_PLUGINS_DIR = WPSTOOLS_LIB_DIR + '/plugins'
|
||||
COMMON_PLUGINS_DIR = File.join(COMMON_LIB_DIR, 'plugins')
|
||||
WPSCAN_PLUGINS_DIR = File.join(WPSCAN_LIB_DIR, 'plugins') # Not used ATM
|
||||
WPSTOOLS_PLUGINS_DIR = File.join(WPSTOOLS_LIB_DIR, 'plugins')
|
||||
|
||||
# Data files
|
||||
PLUGINS_FILE = DATA_DIR + '/plugins.txt'
|
||||
PLUGINS_FULL_FILE = DATA_DIR + '/plugins_full.txt'
|
||||
PLUGINS_VULNS_FILE = DATA_DIR + '/plugin_vulns.xml'
|
||||
THEMES_FILE = DATA_DIR + '/themes.txt'
|
||||
THEMES_FULL_FILE = DATA_DIR + '/themes_full.txt'
|
||||
THEMES_VULNS_FILE = DATA_DIR + '/theme_vulns.xml'
|
||||
WP_VULNS_FILE = DATA_DIR + '/wp_vulns.xml'
|
||||
WP_VERSIONS_FILE = DATA_DIR + '/wp_versions.xml'
|
||||
LOCAL_FILES_FILE = DATA_DIR + '/local_vulnerable_files.xml'
|
||||
VULNS_XSD = DATA_DIR + '/vuln.xsd'
|
||||
WP_VERSIONS_XSD = DATA_DIR + '/wp_versions.xsd'
|
||||
LOCAL_FILES_XSD = DATA_DIR + '/local_vulnerable_files.xsd'
|
||||
PLUGINS_FILE = File.join(DATA_DIR, 'plugins.txt')
|
||||
PLUGINS_FULL_FILE = File.join(DATA_DIR, 'plugins_full.txt')
|
||||
PLUGINS_VULNS_FILE = File.join(DATA_DIR, 'plugin_vulns.json')
|
||||
THEMES_FILE = File.join(DATA_DIR, 'themes.txt')
|
||||
THEMES_FULL_FILE = File.join(DATA_DIR, 'themes_full.txt')
|
||||
THEMES_VULNS_FILE = File.join(DATA_DIR, 'theme_vulns.json')
|
||||
WP_VULNS_FILE = File.join(DATA_DIR, 'wp_vulns.json')
|
||||
WP_VERSIONS_FILE = File.join(DATA_DIR, 'wp_versions.xml')
|
||||
LOCAL_FILES_FILE = File.join(DATA_DIR, 'local_vulnerable_files.xml')
|
||||
# VULNS_XSD = File.join(DATA_DIR, 'vuln.xsd')
|
||||
WP_VERSIONS_XSD = File.join(DATA_DIR, 'wp_versions.xsd')
|
||||
LOCAL_FILES_XSD = File.join(DATA_DIR, 'local_vulnerable_files.xsd')
|
||||
USER_AGENTS_FILE = File.join(DATA_DIR, 'user-agents.txt')
|
||||
|
||||
WPSCAN_VERSION = '2.2'
|
||||
WPSCAN_VERSION = '2.6'
|
||||
|
||||
$LOAD_PATH.unshift(LIB_DIR)
|
||||
$LOAD_PATH.unshift(WPSCAN_LIB_DIR)
|
||||
@@ -53,7 +54,7 @@ require 'environment'
|
||||
def require_files_from_directory(absolute_dir_path, files_pattern = '*.rb')
|
||||
files = Dir[File.join(absolute_dir_path, files_pattern)]
|
||||
|
||||
# Files in the root dir are loaded first, then thoses in the subdirectories
|
||||
# Files in the root dir are loaded first, then those in the subdirectories
|
||||
files.sort_by { |file| [file.count("/"), file] }.each do |f|
|
||||
f = File.expand_path(f)
|
||||
#puts "require #{f}" # Used for debug
|
||||
@@ -72,18 +73,56 @@ def add_trailing_slash(url)
|
||||
url =~ /\/$/ ? url : "#{url}/"
|
||||
end
|
||||
|
||||
# loading the updater
|
||||
require_files_from_directory(UPDATER_LIB_DIR)
|
||||
@updater = UpdaterFactory.get_updater(ROOT_DIR)
|
||||
|
||||
if @updater
|
||||
REVISION = @updater.local_revision_number()
|
||||
else
|
||||
REVISION = nil
|
||||
def missing_db_file?
|
||||
DbUpdater::FILES.each do |db_file|
|
||||
return true unless File.exist?(File.join(DATA_DIR, db_file))
|
||||
end
|
||||
false
|
||||
end
|
||||
|
||||
def version
|
||||
REVISION ? "v#{WPSCAN_VERSION}r#{REVISION}" : "v#{WPSCAN_VERSION}"
|
||||
# Define colors
|
||||
def colorize(text, color_code)
|
||||
if $COLORSWITCH
|
||||
"#{text}"
|
||||
else
|
||||
"\e[#{color_code}m#{text}\e[0m"
|
||||
end
|
||||
end
|
||||
|
||||
def bold(text)
|
||||
colorize(text, 1)
|
||||
end
|
||||
|
||||
def red(text)
|
||||
colorize(text, 31)
|
||||
end
|
||||
|
||||
def green(text)
|
||||
colorize(text, 32)
|
||||
end
|
||||
|
||||
def amber(text)
|
||||
colorize(text, 33)
|
||||
end
|
||||
|
||||
def blue(text)
|
||||
colorize(text, 34)
|
||||
end
|
||||
|
||||
def critical(text)
|
||||
red(text)
|
||||
end
|
||||
|
||||
def warning(text)
|
||||
amber(text)
|
||||
end
|
||||
|
||||
def info(text)
|
||||
green(text)
|
||||
end
|
||||
|
||||
def notice(text)
|
||||
blue(text)
|
||||
end
|
||||
|
||||
# our 1337 banner
|
||||
@@ -97,35 +136,30 @@ def banner
|
||||
puts ' \\/ \\/ |_| |_____/ \\___|\\__,_|_| |_|'
|
||||
puts
|
||||
puts ' WordPress Security Scanner by the WPScan Team '
|
||||
if REVISION
|
||||
puts " Version #{version}"
|
||||
else
|
||||
puts " Version #{version}"
|
||||
end
|
||||
puts ' Sponsored by the RandomStorm Open Source Initiative'
|
||||
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, @gbrindisi, @_FireFart_'
|
||||
puts " Version #{WPSCAN_VERSION}"
|
||||
puts ' Sponsored by Sucuri - https://sucuri.net'
|
||||
puts ' @_WPScan_, @ethicalhack3r, @erwan_lr, pvdl, @_FireFart_'
|
||||
puts '_______________________________________________________________'
|
||||
puts
|
||||
end
|
||||
|
||||
def colorize(text, color_code)
|
||||
"\e[#{color_code}m#{text}\e[0m"
|
||||
end
|
||||
|
||||
def red(text)
|
||||
colorize(text, 31)
|
||||
end
|
||||
|
||||
def green(text)
|
||||
colorize(text, 32)
|
||||
end
|
||||
|
||||
def xml(file)
|
||||
Nokogiri::XML(File.open(file)) do |config|
|
||||
config.noblanks
|
||||
end
|
||||
end
|
||||
|
||||
def json(file)
|
||||
content = File.open(file).read
|
||||
|
||||
begin
|
||||
JSON.parse(content)
|
||||
rescue => e
|
||||
puts "[ERROR] In JSON file parsing #{file} #{e}"
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def redefine_constant(constant, value)
|
||||
Object.send(:remove_const, constant)
|
||||
Object.const_set(constant, value)
|
||||
@@ -177,3 +211,35 @@ end
|
||||
def count_file_lines(file)
|
||||
`wc -l #{file.shellescape}`.split[0].to_i
|
||||
end
|
||||
|
||||
# Truncates a string to a specific length and adds ... at the end
|
||||
def truncate(input, size, trailing = '...')
|
||||
size = size.to_i
|
||||
trailing ||= ''
|
||||
return input if input.nil? or size <= 0 or input.length <= size or
|
||||
trailing.length >= input.length or size-trailing.length-1 >= input.length
|
||||
return "#{input[0..size-trailing.length-1]}#{trailing}"
|
||||
end
|
||||
|
||||
# Gets a random User-Agent
|
||||
#
|
||||
# @return [ String ] A random user-agent from data/user-agents.txt
|
||||
def get_random_user_agent
|
||||
user_agents = []
|
||||
f = File.open(USER_AGENTS_FILE, 'r')
|
||||
f.each_line do |line|
|
||||
# ignore comments
|
||||
next if line.empty? or line =~ /^\s*(#|\/\/)/
|
||||
user_agents << line.strip
|
||||
end
|
||||
f.close
|
||||
# return ransom user-agent
|
||||
user_agents.sample
|
||||
end
|
||||
|
||||
# Directory listing enabled on url?
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def directory_listing_enabled?(url)
|
||||
Browser.get(url.to_s).body[%r{<title>Index of}] ? true : false
|
||||
end
|
||||
|
||||
115
lib/common/db_updater.rb
Normal file
115
lib/common/db_updater.rb
Normal file
@@ -0,0 +1,115 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# DB Updater
|
||||
class DbUpdater
|
||||
FILES = %w(
|
||||
local_vulnerable_files.xml local_vulnerable_files.xsd
|
||||
plugins_full.txt plugins.txt themes_full.txt themes.txt
|
||||
timthumbs.txt user-agents.txt wp_versions.xml wp_versions.xsd
|
||||
plugin_vulns.json theme_vulns.json wp_vulns.json
|
||||
)
|
||||
|
||||
attr_reader :repo_directory
|
||||
|
||||
def initialize(repo_directory)
|
||||
@repo_directory = repo_directory
|
||||
|
||||
fail "#{repo_directory} is not writable" unless \
|
||||
Pathname.new(repo_directory).writable?
|
||||
end
|
||||
|
||||
# @return [ Hash ] The params for Typhoeus::Request
|
||||
def request_params
|
||||
{
|
||||
ssl_verifyhost: 2,
|
||||
ssl_verifypeer: true
|
||||
}
|
||||
end
|
||||
|
||||
# @return [ String ] The raw file URL associated with the given filename
|
||||
def remote_file_url(filename)
|
||||
"https://raw.githubusercontent.com/wpscanteam/vulndb/master/#{filename}"
|
||||
end
|
||||
|
||||
# @return [ String ] The checksum of the associated remote filename
|
||||
def remote_file_checksum(filename)
|
||||
url = "#{remote_file_url(filename)}.sha512"
|
||||
|
||||
res = Browser.get(url, request_params)
|
||||
fail "Unable to get #{url}" unless res.code == 200
|
||||
res.body
|
||||
end
|
||||
|
||||
def local_file_path(filename)
|
||||
File.join(repo_directory, "#{filename}")
|
||||
end
|
||||
|
||||
def local_file_checksum(filename)
|
||||
Digest::SHA512.file(local_file_path(filename)).hexdigest
|
||||
end
|
||||
|
||||
def backup_file_path(filename)
|
||||
File.join(repo_directory, "#{filename}.back")
|
||||
end
|
||||
|
||||
def create_backup(filename)
|
||||
return unless File.exist?(local_file_path(filename))
|
||||
FileUtils.cp(local_file_path(filename), backup_file_path(filename))
|
||||
end
|
||||
|
||||
def restore_backup(filename)
|
||||
return unless File.exist?(backup_file_path(filename))
|
||||
FileUtils.cp(backup_file_path(filename), local_file_path(filename))
|
||||
end
|
||||
|
||||
def delete_backup(filename)
|
||||
FileUtils.rm(backup_file_path(filename))
|
||||
end
|
||||
|
||||
# @return [ String ] The checksum of the downloaded file
|
||||
def download(filename)
|
||||
file_path = local_file_path(filename)
|
||||
file_url = remote_file_url(filename)
|
||||
|
||||
res = Browser.get(file_url, request_params)
|
||||
fail "Error while downloading #{file_url}" unless res.code == 200
|
||||
File.open(file_path, 'wb') { |f| f.write(res.body) }
|
||||
|
||||
local_file_checksum(filename)
|
||||
end
|
||||
|
||||
def update(verbose = false)
|
||||
FILES.each do |filename|
|
||||
begin
|
||||
puts "[+] Checking #{filename}" if verbose
|
||||
db_checksum = remote_file_checksum(filename)
|
||||
|
||||
# Checking if the file needs to be updated
|
||||
if File.exist?(local_file_path(filename)) && db_checksum == local_file_checksum(filename)
|
||||
puts ' [i] Already Up-To-Date' if verbose
|
||||
next
|
||||
end
|
||||
|
||||
puts ' [i] Needs to be updated' if verbose
|
||||
create_backup(filename)
|
||||
puts ' [i] Backup Created' if verbose
|
||||
puts ' [i] Downloading new file' if verbose
|
||||
dl_checksum = download(filename)
|
||||
puts " [i] Downloaded File Checksum: #{dl_checksum}" if verbose
|
||||
|
||||
unless dl_checksum == db_checksum
|
||||
fail "#{filename}: checksums do not match"
|
||||
end
|
||||
rescue => e
|
||||
puts ' [i] Restoring Backup due to error' if verbose
|
||||
restore_backup(filename)
|
||||
raise e
|
||||
ensure
|
||||
if File.exist?(backup_file_path(filename))
|
||||
puts ' [i] Deleting Backup' if verbose
|
||||
delete_backup(filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -49,11 +49,11 @@ end
|
||||
|
||||
# Override for puts to enable logging
|
||||
def puts(o = '')
|
||||
# remove color for logging
|
||||
if o.respond_to?(:gsub)
|
||||
temp = o.gsub(/\e\[\d+m(.*)?\e\[0m/, '\1')
|
||||
if $log && o.respond_to?(:gsub)
|
||||
temp = o.gsub(/\e\[\d+m/, '') # remove color for logging
|
||||
File.open(LOG_FILE, 'a+') { |f| f.puts(temp) }
|
||||
end
|
||||
|
||||
super(o)
|
||||
end
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ require 'vulnerability/urls'
|
||||
|
||||
class Vulnerability
|
||||
include Vulnerability::Output
|
||||
include Vulnerability::Urls
|
||||
include Vulnerability::Urls
|
||||
|
||||
attr_accessor :title, :references, :type, :fixed_in
|
||||
|
||||
@@ -35,27 +35,26 @@ class Vulnerability
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# Create the Vulnerability from the xml_node
|
||||
# Create the Vulnerability from the json_item
|
||||
#
|
||||
# @param [ Nokogiri::XML::Node ] xml_node
|
||||
# @param [ Hash ] json_item
|
||||
#
|
||||
# @return [ Vulnerability ]
|
||||
def self.load_from_xml_node(xml_node)
|
||||
references = {}
|
||||
refs = xml_node.search('references')
|
||||
if refs
|
||||
references[:url] = refs.search('url').map(&:text)
|
||||
references[:cve] = refs.search('cve').map(&:text)
|
||||
references[:secunia] = refs.search('secunia').map(&:text)
|
||||
references[:osvdb] = refs.search('osvdb').map(&:text)
|
||||
references[:metasploit] = refs.search('metasploit').map(&:text)
|
||||
references[:exploitdb] = refs.search('exploitdb').map(&:text)
|
||||
end
|
||||
def self.load_from_json_item(json_item)
|
||||
references = {}
|
||||
|
||||
%w(id url cve secunia osvdb metasploit exploitdb).each do |key|
|
||||
if json_item[key]
|
||||
json_item[key] = [json_item[key]] if json_item[key].class != Array
|
||||
references[key] = json_item[key]
|
||||
end
|
||||
end
|
||||
|
||||
new(
|
||||
xml_node.search('title').text,
|
||||
xml_node.search('type').text,
|
||||
json_item['title'],
|
||||
json_item['type'],
|
||||
references,
|
||||
xml_node.search('fixed_in').text,
|
||||
json_item['fixed_in'],
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -4,18 +4,18 @@ class Vulnerability
|
||||
module Output
|
||||
|
||||
# output the vulnerability
|
||||
def output
|
||||
puts ' |'
|
||||
puts ' | ' + red("* Title: #{title}")
|
||||
def output(verbose = false)
|
||||
puts
|
||||
puts "#{critical('[!]')} Title: #{title}"
|
||||
references.each do |key, urls|
|
||||
methodname = "url_#{key}"
|
||||
urls.each do |u|
|
||||
url = send(methodname, u)
|
||||
puts ' | ' + red("* Reference: #{url}") if url
|
||||
end
|
||||
end
|
||||
if !fixed_in.empty?
|
||||
puts " | * Fixed in: #{fixed_in}"
|
||||
methodname = "url_#{key}"
|
||||
urls.each do |u|
|
||||
url = send(methodname, u)
|
||||
puts " Reference: #{url}" if url
|
||||
end
|
||||
end
|
||||
if !fixed_in.nil?
|
||||
puts "#{notice('[i]')} Fixed in: #{fixed_in}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class Vulnerability
|
||||
module Urls
|
||||
# @return [ String ] The url to the metasploit module page
|
||||
def url_metasploit(module_path)
|
||||
# remove leading slash
|
||||
module_path = module_path.sub(/^\//, '')
|
||||
"http://www.metasploit.com/modules/#{module_path}"
|
||||
end
|
||||
module Urls
|
||||
# @return [ String ] The url to the metasploit module page
|
||||
def url_metasploit(module_path)
|
||||
# remove leading slash
|
||||
module_path = module_path.sub(/^\//, '')
|
||||
"http://www.rapid7.com/db/modules/#{module_path}"
|
||||
end
|
||||
|
||||
def url_url(url)
|
||||
url
|
||||
end
|
||||
def url_url(url)
|
||||
url
|
||||
end
|
||||
|
||||
def url_cve(cve)
|
||||
"http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-#{cve}"
|
||||
end
|
||||
def url_cve(cve)
|
||||
"http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-#{cve}"
|
||||
end
|
||||
|
||||
def url_osvdb(id)
|
||||
"http://osvdb.org/#{id}"
|
||||
end
|
||||
def url_osvdb(id)
|
||||
"http://osvdb.org/#{id}"
|
||||
end
|
||||
|
||||
def url_secunia(id)
|
||||
"http://secunia.com/advisories/#{id}"
|
||||
end
|
||||
def url_secunia(id)
|
||||
"https://secunia.com/advisories/#{id}"
|
||||
end
|
||||
|
||||
def url_exploitdb(id)
|
||||
"http://www.exploit-db.com/exploits/#{id}/"
|
||||
end
|
||||
def url_exploitdb(id)
|
||||
"http://www.exploit-db.com/exploits/#{id}/"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
def url_id(id)
|
||||
"https://wpvulndb.com/vulnerabilities/#{id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,7 +29,10 @@ class WpItem
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def exists_from_response?(response, options = {})
|
||||
if [200, 401, 403].include?(response.code)
|
||||
# 301 included as some items do a self-redirect
|
||||
# Redirects to the 404 and homepage should be ignored (unless dynamic content is used)
|
||||
# by the page hashes (error_404_hash & homepage_hash)
|
||||
if [200, 401, 403, 301].include?(response.code)
|
||||
if response.has_valid_hash?(options[:error_404_hash], options[:homepage_hash])
|
||||
if options[:exclude_content]
|
||||
unless response.body.match(options[:exclude_content])
|
||||
|
||||
@@ -12,7 +12,9 @@ class WpItem
|
||||
|
||||
# @return [ String,nil ] The url to the readme file, nil if not found
|
||||
def readme_url
|
||||
%w{readme.txt README.txt}.each do |readme|
|
||||
# See https://github.com/wpscanteam/wpscan/pull/737#issuecomment-66375445
|
||||
# for any question about the order
|
||||
%w{readme.txt README.txt Readme.txt ReadMe.txt README.TXT readme.TXT}.each do |readme|
|
||||
url = @uri.merge(readme).to_s
|
||||
return url if url_is_200?(url)
|
||||
end
|
||||
@@ -40,7 +42,7 @@ class WpItem
|
||||
|
||||
# @return [ Boolean ]
|
||||
def has_directory_listing?
|
||||
Browser.get(@uri.to_s).body[%r{<title>Index of}] ? true : false
|
||||
directory_listing_enabled?(@uri)
|
||||
end
|
||||
|
||||
# Discover any error_log files created by WordPress
|
||||
|
||||
@@ -4,21 +4,24 @@ class WpItem
|
||||
module Output
|
||||
|
||||
# @return [ Void ]
|
||||
def output
|
||||
def output(verbose = false)
|
||||
puts
|
||||
puts " | Name: #{self}" #this will also output the version number if detected
|
||||
puts " | Location: #{url}"
|
||||
puts "#{info('[+]')} Name: #{self}" #this will also output the version number if detected
|
||||
puts " | Location: #{url}"
|
||||
#puts " | WordPress: #{wordpress_url}" if wordpress_org_item?
|
||||
puts ' | Directory listing enabled: Yes' if has_directory_listing?
|
||||
puts " | Readme: #{readme_url}" if has_readme?
|
||||
puts " | Changelog: #{changelog_url}" if has_changelog?
|
||||
puts " | Readme: #{readme_url}" if has_readme?
|
||||
puts " | Changelog: #{changelog_url}" if has_changelog?
|
||||
puts "#{warning('[!]')} Directory listing is enabled: #{url}" if has_directory_listing?
|
||||
puts "#{warning('[!]')} An error_log file has been found: #{error_log_url}" if has_error_log?
|
||||
|
||||
additional_output(verbose) if respond_to?(:additional_output)
|
||||
|
||||
if version.nil? && vulnerabilities.length > 0
|
||||
puts
|
||||
puts "#{warning('[+]')} We could not determine a version so all vulnerabilities are printed out"
|
||||
end
|
||||
|
||||
vulnerabilities.output
|
||||
|
||||
if has_error_log?
|
||||
puts ' | ' + red('[!]') + " An error_log file has been found : #{error_log_url}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ class WpItem
|
||||
# This check is needed because readme_url can return nil
|
||||
if has_readme?
|
||||
response = Browser.get(readme_url)
|
||||
@version = response.body[%r{stable tag: #{WpVersion.version_pattern}}i, 1]
|
||||
@version = response.body[%r{(?:stable tag|version): #{WpVersion.version_pattern}}i, 1]
|
||||
end
|
||||
end
|
||||
@version
|
||||
@@ -22,7 +22,7 @@ class WpItem
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
item_version = self.version
|
||||
"#@name#{' v' + item_version.strip if item_version}"
|
||||
"#@name#{' - v' + item_version.strip if item_version}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -2,22 +2,27 @@
|
||||
|
||||
class WpItem
|
||||
module Vulnerable
|
||||
attr_accessor :vulns_file, :vulns_xpath
|
||||
attr_accessor :vulns_file, :identifier
|
||||
|
||||
# Get the vulnerabilities associated to the WpItem
|
||||
# Filters out already fixed vulnerabilities
|
||||
#
|
||||
# @return [ Vulnerabilities ]
|
||||
def vulnerabilities
|
||||
xml = xml(vulns_file)
|
||||
json = json(vulns_file)
|
||||
vulnerabilities = Vulnerabilities.new
|
||||
|
||||
xml.xpath(vulns_xpath).each do |node|
|
||||
vuln = Vulnerability.load_from_xml_node(node)
|
||||
if vulnerable_to?(vuln)
|
||||
vulnerabilities << vuln
|
||||
json.each do |item|
|
||||
asset = item[identifier]
|
||||
|
||||
if asset
|
||||
asset['vulnerabilities'].each do |vulnerability|
|
||||
vulnerability = Vulnerability.load_from_json_item(vulnerability)
|
||||
vulnerabilities << vulnerability if vulnerable_to?(vulnerability)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vulnerabilities
|
||||
end
|
||||
|
||||
@@ -32,7 +37,7 @@ class WpItem
|
||||
# @return [ Boolean ]
|
||||
def vulnerable_to?(vuln)
|
||||
if version && vuln && vuln.fixed_in && !vuln.fixed_in.empty?
|
||||
unless VersionCompare::is_newer_or_same?(vuln.fixed_in, version)
|
||||
unless VersionCompare::lesser_or_equal?(vuln.fixed_in, version)
|
||||
return true
|
||||
end
|
||||
else
|
||||
@@ -41,5 +46,4 @@ class WpItem
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -12,8 +12,8 @@ class WpPlugin < WpItem
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def vulns_xpath
|
||||
"//plugin[@name='#{@name}']/vulnerability"
|
||||
def identifier
|
||||
@name
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3,15 +3,21 @@
|
||||
require 'wp_theme/findable'
|
||||
require 'wp_theme/versionable'
|
||||
require 'wp_theme/vulnerable'
|
||||
require 'wp_theme/info'
|
||||
require 'wp_theme/output'
|
||||
require 'wp_theme/childtheme'
|
||||
|
||||
class WpTheme < WpItem
|
||||
extend WpTheme::Findable
|
||||
include WpTheme::Versionable
|
||||
include WpTheme::Vulnerable
|
||||
include WpTheme::Info
|
||||
include WpTheme::Output
|
||||
include WpTheme::Childtheme
|
||||
|
||||
attr_writer :style_url
|
||||
attr_accessor :referenced_url
|
||||
|
||||
def allowed_options; super << :style_url end
|
||||
def allowed_options; super << :referenced_url end
|
||||
|
||||
# Sets the @uri
|
||||
#
|
||||
@@ -24,10 +30,7 @@ class WpTheme < WpItem
|
||||
|
||||
# @return [ String ] The url to the theme stylesheet
|
||||
def style_url
|
||||
unless @style_url
|
||||
@style_url = uri.merge('style.css').to_s
|
||||
end
|
||||
@style_url
|
||||
@uri.merge('style.css').to_s
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
37
lib/common/models/wp_theme/childtheme.rb
Normal file
37
lib/common/models/wp_theme/childtheme.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Childtheme
|
||||
|
||||
def parent_theme_limit
|
||||
3
|
||||
end
|
||||
|
||||
def is_child_theme?
|
||||
return true unless @theme_template.nil?
|
||||
false
|
||||
end
|
||||
|
||||
def get_parent_theme_style_url
|
||||
if is_child_theme?
|
||||
return style_url.sub("/#{name}/style.css", "/#@theme_template/style.css")
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def get_parent_theme
|
||||
if is_child_theme?
|
||||
base_url = @uri.clone
|
||||
base_url.path = base_url.path.sub(/(?<url>.*\/)#{Regexp.escape(@wp_content_dir)}\/.+/, '\k<url>')
|
||||
return WpTheme.new(base_url,
|
||||
{
|
||||
name: @theme_template,
|
||||
style_url: get_parent_theme_style_url,
|
||||
wp_content_dir: @wp_content_dir
|
||||
})
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -30,21 +30,19 @@ class WpTheme < WpItem
|
||||
response = Browser.get_and_follow_location(target_uri.to_s)
|
||||
|
||||
# https + domain is optional because of relative links
|
||||
matches = %r{(?:https?://[^"']+)?/([^/]+)/themes/([^"']+)/style.css}i.match(response.body)
|
||||
matches = /(?:https?:\/\/[^"']+)?\/([^\/]+)\/themes\/([^"'\/]+)[^"']*\/style.css/i.match(response.body)
|
||||
if matches
|
||||
return new(
|
||||
target_uri,
|
||||
{
|
||||
name: matches[2],
|
||||
style_url: matches[0],
|
||||
referenced_url: matches[0],
|
||||
wp_content_dir: matches[1]
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# http://code.google.com/p/wpscan/issues/detail?id=141
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ WpTheme ]
|
||||
|
||||
34
lib/common/models/wp_theme/info.rb
Normal file
34
lib/common/models/wp_theme/info.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Info
|
||||
|
||||
attr_reader :theme_name, :theme_uri, :theme_description,
|
||||
:theme_author, :theme_author_uri, :theme_template,
|
||||
:theme_license, :theme_license_uri, :theme_tags,
|
||||
:theme_text_domain
|
||||
|
||||
def parse_style
|
||||
style = Browser.get(style_url).body
|
||||
@theme_name = parse_style_tag(style, 'Theme Name')
|
||||
@theme_uri = parse_style_tag(style, 'Theme URI')
|
||||
@theme_description = parse_style_tag(style, 'Description')
|
||||
@theme_author = parse_style_tag(style, 'Author')
|
||||
@theme_author_uri = parse_style_tag(style, 'Author URI')
|
||||
@theme_template = parse_style_tag(style, 'Template')
|
||||
@theme_license = parse_style_tag(style, 'License')
|
||||
@theme_license_uri = parse_style_tag(style, 'License URI')
|
||||
@theme_tags = parse_style_tag(style, 'Tags')
|
||||
@theme_text_domain = parse_style_tag(style, 'Text Domain')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_style_tag(style, tag)
|
||||
value = style[/^\s*#{Regexp.escape(tag)}:\s*(.*)/i, 1]
|
||||
return value.strip if value
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
26
lib/common/models/wp_theme/output.rb
Normal file
26
lib/common/models/wp_theme/output.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTheme
|
||||
module Output
|
||||
|
||||
# @return [ Void ]
|
||||
def additional_output(verbose = false)
|
||||
parse_style
|
||||
|
||||
theme_desc = verbose ? @theme_description : truncate(@theme_description, 100)
|
||||
puts " | Style URL: #{style_url}"
|
||||
puts " | Referenced style.css: #{referenced_url}" if referenced_url && referenced_url != style_url
|
||||
puts " | Theme Name: #@theme_name" if @theme_name
|
||||
puts " | Theme URI: #@theme_uri" if @theme_uri
|
||||
puts " | Description: #{theme_desc}"
|
||||
puts " | Author: #@theme_author" if @theme_author
|
||||
puts " | Author URI: #@theme_author_uri" if @theme_author_uri
|
||||
puts " | Template: #@theme_template" if @theme_template and verbose
|
||||
puts " | License: #@theme_license" if @theme_license and verbose
|
||||
puts " | License URI: #@theme_license_uri" if @theme_license_uri and verbose
|
||||
puts " | Tags: #@theme_tags" if @theme_tags and verbose
|
||||
puts " | Text Domain: #@theme_text_domain" if @theme_text_domain and verbose
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -2,16 +2,8 @@
|
||||
|
||||
class WpTheme < WpItem
|
||||
module Versionable
|
||||
|
||||
def version
|
||||
unless @version
|
||||
@version = Browser.get(style_url).body[%r{Version:\s([^\s]+)}i, 1]
|
||||
|
||||
# Get Version from readme.txt
|
||||
@version ||= super
|
||||
end
|
||||
@version
|
||||
@version ||= Browser.get(style_url).body[%r{Version:\s*([^\s]+)}i, 1]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,9 +12,8 @@ class WpTheme < WpItem
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def vulns_xpath
|
||||
"//theme[@name='#{@name}']/vulnerability"
|
||||
def identifier
|
||||
@name
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
require 'wp_timthumb/versionable'
|
||||
require 'wp_timthumb/existable'
|
||||
require 'wp_timthumb/output'
|
||||
require 'wp_timthumb/vulnerable'
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
include WpTimthumb::Versionable
|
||||
include WpTimthumb::Existable
|
||||
include WpTimthumb::Output
|
||||
include WpTimthumb::Vulnerable
|
||||
|
||||
# @param [ WpTimthumb ] other
|
||||
#
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
class WpTimthumb < WpItem
|
||||
module Output
|
||||
|
||||
def output
|
||||
puts ' | ' + red('[!]') + " #{self}"
|
||||
def output(verbose = false)
|
||||
puts
|
||||
puts "#{info('[+]')} #{self}" #this will also output the version number if detected
|
||||
|
||||
vulnerabilities.output
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
55
lib/common/models/wp_timthumb/vulnerable.rb
Normal file
55
lib/common/models/wp_timthumb/vulnerable.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTimthumb < WpItem
|
||||
module Vulnerable
|
||||
# @return [ Vulnerabilities ]
|
||||
def vulnerabilities
|
||||
vulns = Vulnerabilities.new
|
||||
|
||||
[:check_rce_132, :check_rce_webshot].each do |method|
|
||||
vuln = self.send(method)
|
||||
|
||||
vulns << vuln if vuln
|
||||
end
|
||||
vulns
|
||||
end
|
||||
|
||||
def check_rce_132
|
||||
return rce_132_vuln unless VersionCompare.lesser_or_equal?('1.33', version)
|
||||
end
|
||||
|
||||
# Vulnerable versions : > 1.35 (or >= 2.0) and < 2.8.14
|
||||
def check_rce_webshot
|
||||
return if VersionCompare.lesser_or_equal?('2.8.14', version) || VersionCompare.lesser_or_equal?(version, '1.35')
|
||||
|
||||
response = Browser.get(uri.merge('?webshot=1&src=http://' + default_allowed_domains.sample))
|
||||
|
||||
return rce_webshot_vuln unless response.body =~ /WEBSHOT_ENABLED == true/
|
||||
end
|
||||
|
||||
# @return [ Array<String> ] The default allowed domains (between the 2.0 and 2.8.13)
|
||||
def default_allowed_domains
|
||||
%w(flickr.com picasa.com img.youtube.com upload.wikimedia.org)
|
||||
end
|
||||
|
||||
# @return [ Vulnerability ] The RCE in the <= 1.32
|
||||
def rce_132_vuln
|
||||
Vulnerability.new(
|
||||
'Timthumb <= 1.32 Remote Code Execution',
|
||||
'RCE',
|
||||
{ exploitdb: ['17602'] },
|
||||
'1.33'
|
||||
)
|
||||
end
|
||||
|
||||
# @return [ Vulnerability ] The RCE due to the WebShot in the <= 2.8.13
|
||||
def rce_webshot_vuln
|
||||
Vulnerability.new(
|
||||
'Timthumb <= 2.8.13 WebShot Remote Code Execution',
|
||||
'RCE',
|
||||
{ url: ['http://seclists.org/fulldisclosure/2014/Jun/117'] },
|
||||
'2.8.14'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -12,7 +12,7 @@ class WpUser < WpItem
|
||||
# @return [ Array<Symbol> ]
|
||||
def allowed_options; [:id, :login, :display_name, :password] end
|
||||
|
||||
# @return [ URI ] The uri to the auhor page
|
||||
# @return [ URI ] The uri to the author page
|
||||
def uri
|
||||
if id
|
||||
return @uri.merge("?author=#{id}")
|
||||
@@ -23,14 +23,39 @@ class WpUser < WpItem
|
||||
|
||||
# @return [ String ]
|
||||
def login_url
|
||||
@uri.merge('wp-login.php').to_s
|
||||
unless @login_url
|
||||
@login_url = @uri.merge('wp-login.php').to_s
|
||||
|
||||
# Let's check if the login url is redirected (to https url for example)
|
||||
if redirection = redirection(@login_url)
|
||||
@login_url = redirection
|
||||
end
|
||||
end
|
||||
|
||||
@login_url
|
||||
end
|
||||
|
||||
def redirection(url)
|
||||
redirection = nil
|
||||
response = Browser.get(url)
|
||||
|
||||
if response.code == 301 || response.code == 302
|
||||
redirection = response.headers_hash['location']
|
||||
|
||||
# Let's check if there is a redirection in the redirection
|
||||
if other_redirection = redirection(redirection)
|
||||
redirection = other_redirection
|
||||
end
|
||||
end
|
||||
|
||||
redirection
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def to_s
|
||||
s = "#{id}"
|
||||
s += " | #{login}" if login
|
||||
s += " | #{display_name}" if display_name
|
||||
s << " | #{login}" if login
|
||||
s << " | #{display_name}" if display_name
|
||||
s
|
||||
end
|
||||
|
||||
|
||||
@@ -25,11 +25,11 @@ class WpUser < WpItem
|
||||
hydra = browser.hydra
|
||||
queue_count = 0
|
||||
found = false
|
||||
progress_bar = self.progress_bar(count_file_lines(wordlist), options)
|
||||
progress_bar = self.progress_bar(count_file_lines(wordlist)+1, options)
|
||||
|
||||
File.open(wordlist).each do |password|
|
||||
password.chop!
|
||||
|
||||
password.chomp!
|
||||
|
||||
# A successfull login will redirect us to the redirect_to parameter
|
||||
# Generate a random one on each request
|
||||
unless redirect_url
|
||||
@@ -63,6 +63,7 @@ class WpUser < WpItem
|
||||
|
||||
# run all of the remaining requests
|
||||
hydra.run
|
||||
puts if options[:show_progression] # mandatory to avoid the output of the progressbar to be overriden
|
||||
end
|
||||
|
||||
# @param [ Integer ] targets_size
|
||||
@@ -103,19 +104,19 @@ class WpUser < WpItem
|
||||
# @return [ Boolean ]
|
||||
def valid_password?(response, password, redirect_url, options = {})
|
||||
if response.code == 302 && response.headers_hash && response.headers_hash['Location'] == redirect_url
|
||||
progression = "#{green('[SUCCESS]')} Login : #{login} Password : #{password}\n\n"
|
||||
progression = "#{info('[SUCCESS]')} Login : #{login} Password : #{password}\n\n"
|
||||
valid = true
|
||||
elsif response.body =~ /login_error/i
|
||||
verbose = "\n Incorrect login and/or password."
|
||||
elsif response.timed_out?
|
||||
progression = "#{red('ERROR:')} Request timed out."
|
||||
progression = "#{critical('ERROR:')} Request timed out."
|
||||
elsif response.code == 0
|
||||
progression = "#{red('ERROR:')} No response from remote server. WAF/IPS?"
|
||||
progression = "#{critical('ERROR:')} No response from remote server. WAF/IPS?"
|
||||
elsif response.code.to_s =~ /^50/
|
||||
progression = "#{red('ERROR:')} Server error, try reducing the number of threads."
|
||||
progression = "#{critical('ERROR:')} Server error, try reducing the number of threads."
|
||||
else
|
||||
progression = "#{red('ERROR:')} We received an unknown response for #{password}..."
|
||||
verbose = red(" Code: #{response.code}\n Body: #{response.body}\n")
|
||||
progression = "#{critical('ERROR:')} We received an unknown response for #{password}..."
|
||||
verbose = critical(" Code: #{response.code}\n Body: #{response.body}\n")
|
||||
end
|
||||
|
||||
puts "\n " + progression if progression && options[:show_progression]
|
||||
|
||||
@@ -22,6 +22,8 @@ class WpUser < WpItem
|
||||
if response.code == 301 # login in location?
|
||||
location = response.headers_hash['Location']
|
||||
|
||||
return if location.nil? || location.empty?
|
||||
|
||||
@login = Existable.login_from_author_pattern(location)
|
||||
@display_name = Existable.display_name_from_body(
|
||||
Browser.get(location).body
|
||||
@@ -66,9 +68,12 @@ class WpUser < WpItem
|
||||
title_tag.force_encoding('UTF-8') if title_tag.encoding == Encoding::ASCII_8BIT
|
||||
title_tag = Nokogiri::HTML::DocumentFragment.parse(title_tag).to_s
|
||||
# & are not decoded with Nokogiri
|
||||
title_tag.sub!('&', '&')
|
||||
title_tag.gsub!('&', '&')
|
||||
|
||||
name = title_tag[%r{([^|«]+) }, 1]
|
||||
# replace UTF chars like » with dummy character
|
||||
title_tag.gsub!(/&#(\d+);/, '|')
|
||||
|
||||
name = title_tag[%r{([^|«»]+) }, 1]
|
||||
|
||||
return name.strip if name
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ class WpVersion < WpItem
|
||||
#
|
||||
# @return [ WpVersion ]
|
||||
def find(target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
methods.grep(/find_from_/).each do |method|
|
||||
methods.grep(/^find_from_/).each do |method|
|
||||
|
||||
if method === :find_from_advanced_fingerprinting
|
||||
version = send(method, target_uri, wp_content_dir, wp_plugins_dir, versions_xml)
|
||||
@@ -100,18 +100,6 @@ class WpVersion < WpItem
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the RSS2 feed source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def find_from_rss2_generator(target_uri)
|
||||
# scan_url(
|
||||
# target_uri,
|
||||
# %r{<generator>http://wordpress.org/?v=(#{WpVersion.version_pattern})</generator>}i,
|
||||
# 'feed/rss/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the Atom source.
|
||||
#
|
||||
@@ -126,18 +114,6 @@ class WpVersion < WpItem
|
||||
)
|
||||
end
|
||||
|
||||
# Attempts to find the WordPress version from,
|
||||
# the generator tag in the comment rss source.
|
||||
#
|
||||
# Have not been able to find an example of this - Ryan
|
||||
#def find_from_comments_rss_generator(target_uri)
|
||||
# scan_url(
|
||||
# target_uri,
|
||||
# %r{<!-- generator="WordPress/#{WpVersion.version_pattern}" -->}i,
|
||||
# 'comments/feed/'
|
||||
# )
|
||||
#end
|
||||
|
||||
# Uses data/wp_versions.xml to try to identify a
|
||||
# wordpress version.
|
||||
#
|
||||
@@ -190,8 +166,6 @@ class WpVersion < WpItem
|
||||
|
||||
# Attempts to find the WordPress version from the sitemap.xml file.
|
||||
#
|
||||
# See: http://code.google.com/p/wpscan/issues/detail?id=109
|
||||
#
|
||||
# @param [ URI ] target_uri
|
||||
#
|
||||
# @return [ String ] The version number
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
class WpVersion < WpItem
|
||||
module Output
|
||||
|
||||
def output
|
||||
puts green('[+]') + " WordPress version #{self.number} identified from #{self.found_from}"
|
||||
def output(verbose = false)
|
||||
puts
|
||||
puts "#{info('[+]')} WordPress version #{self.number} identified from #{self.found_from}"
|
||||
|
||||
vulnerabilities = self.vulnerabilities
|
||||
|
||||
unless vulnerabilities.empty?
|
||||
puts
|
||||
puts red('[!]') + " #{vulnerabilities.size} vulnerabilities identified from the version number:"
|
||||
puts "#{critical('[!]')} #{vulnerabilities.size} vulnerabilities identified from the version number"
|
||||
|
||||
vulnerabilities.output
|
||||
end
|
||||
|
||||
@@ -12,9 +12,14 @@ class WpVersion < WpItem
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def vulns_xpath
|
||||
"//wordpress[@version='#{@number}']/vulnerability"
|
||||
end
|
||||
def identifier
|
||||
@number
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
# def vulns_xpath
|
||||
# "//wordpress[@version='#{@number}']/vulnerability"
|
||||
# end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,25 +2,14 @@
|
||||
|
||||
require 'common/cache_file_store'
|
||||
|
||||
# Implementaion of a cache_key (Typhoeus::Request#hash has too many options)
|
||||
module Typhoeus
|
||||
class Request
|
||||
module Cacheable
|
||||
def cache_key
|
||||
Digest::SHA2.hexdigest("#{url}-#{options[:body]}-#{options[:method]}")[0..32]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TyphoeusCache < CacheFileStore
|
||||
|
||||
def get(request)
|
||||
read_entry(request.cache_key)
|
||||
read_entry(request.hash.to_s)
|
||||
end
|
||||
|
||||
def set(request, response)
|
||||
write_entry(request.cache_key, response, request.cache_ttl)
|
||||
write_entry(request.hash.to_s, response, request.cache_ttl)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/updater/updater'
|
||||
|
||||
class GitUpdater < Updater
|
||||
|
||||
def is_installed?
|
||||
%x[git #{repo_directory_arguments()} status 2>&1] =~ /On branch/ ? true : false
|
||||
end
|
||||
|
||||
# Git has not a revsion number like SVN,
|
||||
# so we will take the 7 first chars of the last commit hash
|
||||
def local_revision_number
|
||||
git_log = %x[git #{repo_directory_arguments()} log -1 2>&1]
|
||||
git_log[/commit ([0-9a-z]{7})/i, 1].to_s
|
||||
end
|
||||
|
||||
def update
|
||||
%x[git #{repo_directory_arguments()} pull]
|
||||
end
|
||||
|
||||
def has_local_changes?
|
||||
%x[git #{repo_directory_arguments()} diff --exit-code 2>&1] =~ /diff/ ? true : false
|
||||
end
|
||||
|
||||
def reset_head
|
||||
%x[git #{repo_directory_arguments()} reset --hard HEAD]
|
||||
end
|
||||
|
||||
protected
|
||||
def repo_directory_arguments
|
||||
if @repo_directory
|
||||
return "--git-dir=\"#{@repo_directory}/.git\" --work-tree=\"#{@repo_directory}\""
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,23 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'common/updater/updater'
|
||||
|
||||
class SvnUpdater < Updater
|
||||
|
||||
REVISION_PATTERN = /revision="(\d+)"/i
|
||||
TRUNK_URL = 'https://github.com/wpscanteam/wpscan'
|
||||
|
||||
def is_installed?
|
||||
%x[svn info "#@repo_directory" --xml 2>&1] =~ /revision=/ ? true : false
|
||||
end
|
||||
|
||||
def local_revision_number
|
||||
local_revision = %x[svn info "#@repo_directory" --xml 2>&1]
|
||||
local_revision[REVISION_PATTERN, 1].to_s
|
||||
end
|
||||
|
||||
def update
|
||||
%x[svn up "#@repo_directory"]
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,25 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# This class act as an absract one
|
||||
class Updater
|
||||
|
||||
attr_reader :repo_directory
|
||||
|
||||
# TODO : add a last '/ to repo_directory if it's not present
|
||||
def initialize(repo_directory = nil)
|
||||
@repo_directory = repo_directory
|
||||
end
|
||||
|
||||
def is_installed?
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def local_revision_number
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def update
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,23 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class UpdaterFactory
|
||||
|
||||
def self.get_updater(repo_directory)
|
||||
self.available_updaters_classes().each do |updater_symbol|
|
||||
updater = Object.const_get(updater_symbol).new(repo_directory)
|
||||
|
||||
if updater.is_installed?
|
||||
return updater
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# return array of class symbols
|
||||
def self.available_updaters_classes
|
||||
Object.constants.grep(/^.+Updater$/)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -2,14 +2,18 @@
|
||||
|
||||
class VersionCompare
|
||||
|
||||
# Compares two version strings. Returns true if version1 is equal to version2
|
||||
# or when version1 is older than version2
|
||||
# Compares two version strings. Returns true if version1 <= version2
|
||||
# and false otherwise
|
||||
#
|
||||
# @param [ String ] version1
|
||||
# @param [ String ] version2
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def self.is_newer_or_same?(version1, version2)
|
||||
def self.lesser_or_equal?(version1, version2)
|
||||
# Prepend a '0' if the version starts with a '.'
|
||||
version1 = "0#{version1}" if version1 && version1[0,1] == '.'
|
||||
version2 = "0#{version2}" if version2 && version2[0,1] == '.'
|
||||
|
||||
return true if (version1 == version2)
|
||||
# Both versions must be set
|
||||
return false unless (version1 and version2)
|
||||
@@ -22,5 +26,5 @@ class VersionCompare
|
||||
raise
|
||||
end
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,6 +28,7 @@ begin
|
||||
require 'pp'
|
||||
require 'shellwords'
|
||||
require 'fileutils'
|
||||
require 'pathname'
|
||||
# Third party libs
|
||||
require 'typhoeus'
|
||||
require 'json'
|
||||
|
||||
@@ -32,7 +32,7 @@ class WebSite
|
||||
|
||||
def has_xml_rpc?
|
||||
response = Browser.get_and_follow_location(xml_rpc_url)
|
||||
response.body =~ %r{XML-RPC server accepts POST requests only}i
|
||||
response.body =~ %r{XML-RPC server accepts POST requests only}i
|
||||
end
|
||||
|
||||
# See http://www.hixie.ch/specs/pingback/pingback-1.0#TOC2.3
|
||||
@@ -52,8 +52,12 @@ class WebSite
|
||||
url ||= @uri.to_s
|
||||
response = Browser.get(url)
|
||||
|
||||
redirected_uri = URI.parse(add_trailing_slash(add_http_protocol(url)))
|
||||
if response.code == 301 || response.code == 302
|
||||
redirection = response.headers_hash['location']
|
||||
if redirection[0] == '/'
|
||||
redirection = "#{redirected_uri.scheme}://#{redirected_uri.host}#{redirection}"
|
||||
end
|
||||
|
||||
# Let's check if there is a redirection in the redirection
|
||||
if other_redirection = redirection(redirection)
|
||||
@@ -71,7 +75,7 @@ class WebSite
|
||||
#
|
||||
# @return [ String ] The MD5 hash of the page
|
||||
def self.page_hash(page)
|
||||
page = Browser.get(page) unless page.is_a?(Typhoeus::Response)
|
||||
page = Browser.get(page, { followlocation: true, cache_ttl: 0 }) unless page.is_a?(Typhoeus::Response)
|
||||
|
||||
Digest::MD5.hexdigest(page.body.gsub(/<!--.*?-->/m, ''))
|
||||
end
|
||||
|
||||
@@ -12,9 +12,7 @@ class WebSite
|
||||
# Gets a robots.txt URL
|
||||
# @return [ String ]
|
||||
def robots_url
|
||||
temp = @uri.clone
|
||||
temp.path = '/robots.txt'
|
||||
temp.to_s
|
||||
@uri.clone.merge('robots.txt').to_s
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
require 'web_site'
|
||||
require 'wp_target/malwares'
|
||||
require 'wp_target/wp_readme'
|
||||
require 'wp_target/wp_registrable'
|
||||
require 'wp_target/wp_config_backup'
|
||||
require 'wp_target/wp_must_use_plugins'
|
||||
require 'wp_target/wp_login_protection'
|
||||
require 'wp_target/wp_custom_directories'
|
||||
require 'wp_target/wp_full_path_disclosure'
|
||||
|
||||
class WpTarget < WebSite
|
||||
include WpTarget::Malwares
|
||||
include WpTarget::WpReadme
|
||||
include WpTarget::WpRegistrable
|
||||
include WpTarget::WpConfigBackup
|
||||
include WpTarget::WpMustUsePlugins
|
||||
include WpTarget::WpLoginProtection
|
||||
include WpTarget::WpCustomDirectories
|
||||
include WpTarget::WpFullPathDisclosure
|
||||
@@ -28,7 +28,7 @@ class WpTarget < WebSite
|
||||
@wp_plugins_dir = options[:wp_plugins_dir]
|
||||
@multisite = nil
|
||||
|
||||
Browser.instance(options.merge(:max_threads => options[:threads]))
|
||||
Browser.instance.referer = url
|
||||
end
|
||||
|
||||
# check if the target website is
|
||||
@@ -38,6 +38,11 @@ class WpTarget < WebSite
|
||||
|
||||
response = Browser.get_and_follow_location(@uri.to_s)
|
||||
|
||||
# Note: in the future major WPScan version, change the user-agent to see
|
||||
# if the response is a 200 ?
|
||||
fail "The target is responding with a 403, this might be due to a WAF or a plugin\n" \
|
||||
'You should try to supply a valid user-agent via the --user-agent option' if response.code == 403
|
||||
|
||||
if response.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i
|
||||
wordpress = true
|
||||
else
|
||||
@@ -93,7 +98,7 @@ class WpTarget < WebSite
|
||||
end
|
||||
# :nocov:
|
||||
|
||||
# The version is not yet considerated
|
||||
# The version is not yet considered
|
||||
#
|
||||
# @param [ String ] name
|
||||
# @param [ String ] version
|
||||
@@ -116,11 +121,16 @@ class WpTarget < WebSite
|
||||
|
||||
# @return [ String ]
|
||||
def debug_log_url
|
||||
@uri.merge("#{wp_content_dir()}/debug.log").to_s
|
||||
@uri.merge("#{wp_content_dir}/debug.log").to_s
|
||||
end
|
||||
|
||||
# @return [ String ]
|
||||
def upload_dir_url
|
||||
@uri.merge("#{wp_content_dir}/uploads/").to_s
|
||||
end
|
||||
|
||||
# Script for replacing strings in wordpress databases
|
||||
# reveals databse credentials after hitting submit
|
||||
# reveals database credentials after hitting submit
|
||||
# http://interconnectit.com/124/search-and-replace-for-wordpress-databases/
|
||||
#
|
||||
# @return [ String ]
|
||||
@@ -133,4 +143,8 @@ class WpTarget < WebSite
|
||||
resp = Browser.get(search_replace_db_2_url)
|
||||
resp.code == 200 && resp.body[%r{by interconnect}i]
|
||||
end
|
||||
|
||||
def upload_directory_listing_enabled?
|
||||
directory_listing_enabled?(upload_dir_url)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTarget < WebSite
|
||||
module Malwares
|
||||
# Used as cache :
|
||||
# nil => malwares not checked,
|
||||
# [] => no malwares,
|
||||
# otherwise array of malwares url found
|
||||
@malwares = nil
|
||||
|
||||
def has_malwares?(malwares_file_path = nil)
|
||||
!malwares(malwares_file_path).empty?
|
||||
end
|
||||
|
||||
# return array of string (url of malwares found)
|
||||
def malwares(malwares_file_path = nil)
|
||||
unless @malwares
|
||||
malwares_found = []
|
||||
malwares_file = Malwares.malwares_file(malwares_file_path)
|
||||
index_page_body = Browser.get(@uri.to_s).body
|
||||
|
||||
File.open(malwares_file, 'r') do |file|
|
||||
file.readlines.collect do |url|
|
||||
chomped_url = url.chomp
|
||||
|
||||
if chomped_url.length > 0
|
||||
malwares_found += index_page_body.scan(Malwares.malware_pattern(chomped_url))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
malwares_found.flatten!
|
||||
malwares_found.uniq!
|
||||
|
||||
@malwares = malwares_found
|
||||
end
|
||||
@malwares
|
||||
end
|
||||
|
||||
def self.malwares_file(malwares_file_path)
|
||||
malwares_file_path || DATA_DIR + '/malwares.txt'
|
||||
end
|
||||
|
||||
def self.malware_pattern(url_regex)
|
||||
# no need to escape regex here, because malware.txt contains regex
|
||||
%r{<(?:script|iframe).* src=(?:"|')(#{url_regex}[^"']*)(?:"|')[^>]*>}i
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -40,9 +40,9 @@ class WpTarget < WebSite
|
||||
# @return [ Array ]
|
||||
def self.config_backup_files
|
||||
%w{
|
||||
wp-config.php~ #wp-config.php# wp-config.php.save wp-config.php.swp wp-config.php.swo wp-config.php_bak
|
||||
wp-config.bak wp-config.php.bak wp-config.save wp-config.old wp-config.php.old wp-config.php.orig
|
||||
wp-config.orig wp-config.php.original wp-config.original wp-config.txt
|
||||
wp-config.php~ #wp-config.php# wp-config.php.save .wp-config.php.swp wp-config.php.swp wp-config.php.swo
|
||||
wp-config.php_bak wp-config.bak wp-config.php.bak wp-config.save wp-config.old wp-config.php.old
|
||||
wp-config.php.orig wp-config.orig wp-config.php.original wp-config.original wp-config.txt
|
||||
} # thanks to Feross.org for these
|
||||
end
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ class WpTarget < WebSite
|
||||
end
|
||||
|
||||
# Checks if a login protection plugin is enabled
|
||||
# http://code.google.com/p/wpscan/issues/detail?id=111
|
||||
# return a WpPlugin object or nil if no one is found
|
||||
def login_protection_plugin
|
||||
unless @login_protection_plugin
|
||||
|
||||
26
lib/wpscan/wp_target/wp_must_use_plugins.rb
Normal file
26
lib/wpscan/wp_target/wp_must_use_plugins.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class WpTarget < WebSite
|
||||
module WpMustUsePlugins
|
||||
|
||||
# Checks to see if the must use plugin folder exists
|
||||
#
|
||||
# @return [ Boolean ]
|
||||
def has_must_use_plugins?
|
||||
response = Browser.get(must_use_url)
|
||||
|
||||
if response && WpTarget.valid_response_codes.include?(response.code)
|
||||
hash = WebSite.page_hash(response.body)
|
||||
return true if hash != error_404_hash && hash != homepage_hash
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# @return [ String ] The must use plugins directory URL
|
||||
def must_use_url
|
||||
@uri.merge("#{wp_content_dir}/mu-plugins/").to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -46,7 +46,7 @@ def usage
|
||||
puts '-Use custom plugins directory ...'
|
||||
puts "ruby #{script_name} -u www.example.com --wp-plugins-dir wp-content/custom-plugins"
|
||||
puts
|
||||
puts '-Update ...'
|
||||
puts '-Update the DB ...'
|
||||
puts "ruby #{script_name} --update"
|
||||
puts
|
||||
puts '-Debug output ...'
|
||||
@@ -60,13 +60,12 @@ end
|
||||
def help
|
||||
puts 'Help :'
|
||||
puts
|
||||
puts 'Some values are settable in conf/browser.conf.json :'
|
||||
puts ' user-agent, proxy, proxy-auth, threads, cache timeout and request timeout'
|
||||
puts 'Some values are settable in a config file, see the example.conf.json'
|
||||
puts
|
||||
puts '--update Update to the latest revision'
|
||||
puts '--url | -u <target url> The WordPress URL/domain to scan.'
|
||||
puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
|
||||
puts '--enumerate | -e [option(s)] Enumeration.'
|
||||
puts '--update Update to the database to the latest version.'
|
||||
puts '--url | -u <target url> The WordPress URL/domain to scan.'
|
||||
puts '--force | -f Forces WPScan to not check if the remote site is running WordPress.'
|
||||
puts '--enumerate | -e [option(s)] Enumeration.'
|
||||
puts ' option :'
|
||||
puts ' u usernames from id 1 to 10'
|
||||
puts ' u[10-20] usernames from id 10 to 20 (you must write [] chars)'
|
||||
@@ -80,20 +79,42 @@ def help
|
||||
puts ' Multiple values are allowed : "-e tt,p" will enumerate timthumbs and plugins'
|
||||
puts ' If no option is supplied, the default is "vt,tt,u,vp"'
|
||||
puts
|
||||
puts '--exclude-content-based "<regexp or string>" Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied'
|
||||
puts ' You do not need to provide the regexp delimiters, but you must write the quotes (simple or double)'
|
||||
puts '--config-file | -c <config file> Use the specified config file'
|
||||
puts '--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not'
|
||||
puts '--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'
|
||||
puts '--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'
|
||||
puts '--proxy <[protocol://]host:port> Supply a proxy (will override the one from conf/browser.conf.json).'
|
||||
puts ' HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported. If no protocol is given (format host:port), HTTP will be used'
|
||||
puts '--proxy-auth <username:password> Supply the proxy login credentials (will override the one from conf/browser.conf.json).'
|
||||
puts '--basic-auth <username:password> Set the HTTP Basic authentication'
|
||||
puts '--wordlist | -w <wordlist> Supply a wordlist for the password bruter and do the brute.'
|
||||
puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests. (will override the value from conf/browser.conf.json)'
|
||||
puts '--username | -U <username> Only brute force the supplied username.'
|
||||
puts '--help | -h This help screen.'
|
||||
puts '--verbose | -v Verbose output.'
|
||||
puts '--exclude-content-based "<regexp or string>"'
|
||||
puts ' Used with the enumeration option, will exclude all occurrences based on the regexp or string supplied.'
|
||||
puts ' You do not need to provide the regexp delimiters, but you must write the quotes (simple or double).'
|
||||
puts '--config-file | -c <config file> Use the specified config file, see the example.conf.json.'
|
||||
puts '--user-agent | -a <User-Agent> Use the specified User-Agent.'
|
||||
puts '--cookie <String> String to read cookies from.'
|
||||
puts '--random-agent | -r Use a random User-Agent.'
|
||||
puts '--follow-redirection If the target url has a redirection, it will be followed without asking if you wanted to do so or not'
|
||||
puts '--batch Never ask for user input, use the default behaviour.'
|
||||
puts '--no-color Do not use colors in the output.'
|
||||
puts '--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.'
|
||||
puts ' Subdirectories are allowed.'
|
||||
puts '--wp-plugins-dir <wp plugins dir> Same thing than --wp-content-dir but for the plugins directory.'
|
||||
puts ' If not supplied, WPScan will use wp-content-dir/plugins. Subdirectories are allowed'
|
||||
puts '--proxy <[protocol://]host:port> Supply a proxy. HTTP, SOCKS4 SOCKS4A and SOCKS5 are supported.'
|
||||
puts ' If no protocol is given (format host:port), HTTP will be used.'
|
||||
puts '--proxy-auth <username:password> Supply the proxy login credentials.'
|
||||
puts '--basic-auth <username:password> Set the HTTP Basic authentication.'
|
||||
puts '--wordlist | -w <wordlist> Supply a wordlist for the password brute forcer.'
|
||||
puts '--username | -U <username> Only brute force the supplied username.'
|
||||
puts '--usernames <path-to-file> Only brute force the usernames from the file.'
|
||||
puts '--threads | -t <number of threads> The number of threads to use when multi-threading requests.'
|
||||
puts '--cache-ttl <cache-ttl> Typhoeus cache TTL.'
|
||||
puts '--request-timeout <request-timeout> Request Timeout.'
|
||||
puts '--connect-timeout <connect-timeout> Connect Timeout.'
|
||||
puts '--max-threads <max-threads> Maximum Threads.'
|
||||
puts '--help | -h This help screen.'
|
||||
puts '--verbose | -v Verbose output.'
|
||||
puts '--version Output the current version and exit.'
|
||||
puts
|
||||
end
|
||||
|
||||
# Hook to check if the target if down during the scan
|
||||
# The target is considered down after 10 requests with status = 0
|
||||
down = 0
|
||||
Typhoeus.on_complete do |response|
|
||||
down += 1 if response.code == 0
|
||||
fail 'The target seems to be down' if down >= 10
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
class WpscanOptions
|
||||
|
||||
ACCESSOR_OPTIONS = [
|
||||
:batch,
|
||||
:enumerate_plugins,
|
||||
:enumerate_only_vulnerable_plugins,
|
||||
:enumerate_all_plugins,
|
||||
@@ -12,6 +13,8 @@ class WpscanOptions
|
||||
:enumerate_timthumbs,
|
||||
:enumerate_usernames,
|
||||
:enumerate_usernames_range,
|
||||
:no_color,
|
||||
:log,
|
||||
:proxy,
|
||||
:proxy_auth,
|
||||
:threads,
|
||||
@@ -21,16 +24,24 @@ class WpscanOptions
|
||||
:update,
|
||||
:verbose,
|
||||
:username,
|
||||
:usernames,
|
||||
:password,
|
||||
:follow_redirection,
|
||||
:wp_content_dir,
|
||||
:wp_plugins_dir,
|
||||
:help,
|
||||
:config_file,
|
||||
:cookie,
|
||||
:exclude_content_based,
|
||||
:basic_auth,
|
||||
:debug_output,
|
||||
:version
|
||||
:version,
|
||||
:user_agent,
|
||||
:random_agent,
|
||||
:cache_ttl,
|
||||
:request_timeout,
|
||||
:connect_timeout,
|
||||
:max_threads
|
||||
]
|
||||
|
||||
attr_accessor *ACCESSOR_OPTIONS
|
||||
@@ -59,6 +70,12 @@ class WpscanOptions
|
||||
end
|
||||
end
|
||||
|
||||
def usernames=(file)
|
||||
fail "The file #{file} does not exist" unless File.exists?(file)
|
||||
|
||||
@usernames = file
|
||||
end
|
||||
|
||||
def proxy=(proxy)
|
||||
if proxy.index(':') == nil
|
||||
raise 'Invalid proxy format. Should be host:port.'
|
||||
@@ -136,6 +153,10 @@ class WpscanOptions
|
||||
!to_h.empty?
|
||||
end
|
||||
|
||||
def random_agent=(useless)
|
||||
@user_agent = get_random_user_agent
|
||||
end
|
||||
|
||||
# return Hash
|
||||
def to_h
|
||||
options = {}
|
||||
@@ -224,9 +245,12 @@ class WpscanOptions
|
||||
['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--enumerate', '-e', GetoptLong::OPTIONAL_ARGUMENT],
|
||||
['--username', '-U', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--usernames', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--wordlist', '-w', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--threads', '-t', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--force', '-f', GetoptLong::NO_ARGUMENT],
|
||||
['--user-agent', '-a', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--random-agent', '-r', GetoptLong::NO_ARGUMENT],
|
||||
['--help', '-h', GetoptLong::NO_ARGUMENT],
|
||||
['--verbose', '-v', GetoptLong::NO_ARGUMENT],
|
||||
['--proxy', GetoptLong::REQUIRED_ARGUMENT],
|
||||
@@ -239,7 +263,15 @@ class WpscanOptions
|
||||
['--exclude-content-based', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--basic-auth', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--debug-output', GetoptLong::NO_ARGUMENT],
|
||||
['--version', GetoptLong::NO_ARGUMENT]
|
||||
['--version', GetoptLong::NO_ARGUMENT],
|
||||
['--cache-ttl', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--request-timeout', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--connect-timeout', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--max-threads', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--batch', GetoptLong::NO_ARGUMENT],
|
||||
['--no-color', GetoptLong::NO_ARGUMENT],
|
||||
['--cookie', GetoptLong::REQUIRED_ARGUMENT],
|
||||
['--log', GetoptLong::NO_ARGUMENT]
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -29,13 +29,22 @@ class CheckerPlugin < Plugin
|
||||
puts '[+] Checking vulnerabilities reference urls'
|
||||
|
||||
vuln_ref_files.each do |vuln_ref_file|
|
||||
xml = xml(vuln_ref_file)
|
||||
json = json(vuln_ref_file)
|
||||
|
||||
urls = []
|
||||
xml.xpath('//reference').each { |node| urls << node.text }
|
||||
|
||||
json.each do |asset|
|
||||
asset[asset.keys.inject]['vulnerabilities'].each do |url|
|
||||
unless url['url'].nil?
|
||||
url['url'].each do |url|
|
||||
urls << url
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
urls.uniq!
|
||||
|
||||
puts "[!] No URLs found in #{vuln_ref_file}!" if urls.empty?
|
||||
|
||||
dead_urls = []
|
||||
queue_count = 0
|
||||
request_count = 0
|
||||
@@ -73,17 +82,19 @@ class CheckerPlugin < Plugin
|
||||
end
|
||||
|
||||
def check_local_vulnerable_files(dir_to_scan)
|
||||
if Dir::exist?(dir_to_scan)
|
||||
if Dir.exist?(dir_to_scan)
|
||||
xml_file = LOCAL_FILES_FILE
|
||||
local_hashes = {}
|
||||
file_extension_to_scan = '*.{js,php,swf,html,htm}'
|
||||
|
||||
print '[+] Generating local hashes ... '
|
||||
|
||||
Dir[File::join(dir_to_scan, '**', file_extension_to_scan)].each do |filename|
|
||||
Dir[File.join(dir_to_scan, '**', file_extension_to_scan)]
|
||||
.select { |f| File.file?(f) }
|
||||
.each do |filename|
|
||||
sha1sum = Digest::SHA1.file(filename).hexdigest
|
||||
|
||||
if local_hashes.has_key?(sha1sum)
|
||||
if local_hashes.key?(sha1sum)
|
||||
local_hashes[sha1sum] << filename
|
||||
else
|
||||
local_hashes[sha1sum] = [filename]
|
||||
|
||||
91
lib/wpstools/plugins/checker/checker_spelling.rb
Normal file
91
lib/wpstools/plugins/checker/checker_spelling.rb
Normal file
@@ -0,0 +1,91 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class CheckerSpelling < Plugin
|
||||
|
||||
def initialize
|
||||
super(author: 'WPScanTeam - @ethicalhack3r')
|
||||
|
||||
register_options(['--spellcheck', '--sc', 'Check all files for common spelling mistakes.'])
|
||||
end
|
||||
|
||||
def run(options = {})
|
||||
spellcheck if options[:spellcheck]
|
||||
end
|
||||
|
||||
def spellcheck
|
||||
mistakes = 0
|
||||
|
||||
puts '[+] Checking for spelling mistakes'
|
||||
puts
|
||||
|
||||
files.each do |file_name|
|
||||
if File.exists?(file_name)
|
||||
file = File.open(file_name, 'r')
|
||||
|
||||
misspellings.each_key do |misspelling|
|
||||
begin
|
||||
file.read.scan(/#{misspelling}/).each do |match|
|
||||
mistakes += 1
|
||||
puts "[MISSPELLING] File: #{file_name} Bad: #{match} Good: #{misspellings[misspelling]}"
|
||||
end
|
||||
rescue => e
|
||||
puts "Error in #{file_name} #{e}"
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
file.close
|
||||
end
|
||||
end
|
||||
|
||||
puts
|
||||
puts "[+] Found #{mistakes} spelling mistakes"
|
||||
|
||||
mistakes
|
||||
end
|
||||
|
||||
def misspellings
|
||||
{
|
||||
/databse/i => 'database',
|
||||
/whith/i => 'with',
|
||||
/wich/i => 'which',
|
||||
/verions/i => 'versions',
|
||||
/vulnerabilitiy/i => 'vulnerability',
|
||||
/unkown/i => 'unknown',
|
||||
/recieved/i => 'received',
|
||||
/acheive/i => 'achieve',
|
||||
/wierd/i => 'weird',
|
||||
/untill/i => 'until',
|
||||
/alot/i => 'a lot',
|
||||
/randomstorm/ => 'RandomStorm',
|
||||
/wpscan/ => 'WPScan',
|
||||
/Wordpress/ => 'WordPress'
|
||||
}
|
||||
end
|
||||
|
||||
def files
|
||||
files = Dir['**/*'].reject {|fn| File.directory?(fn) }
|
||||
|
||||
ignore.each do |ignore|
|
||||
files.delete_if { |data| data.match(ignore) }
|
||||
end
|
||||
|
||||
files
|
||||
end
|
||||
|
||||
def ignore
|
||||
ignore = []
|
||||
|
||||
ignore << File.basename(__FILE__)
|
||||
ignore << 'spec/cache/'
|
||||
ignore << 'spec/spec_session/'
|
||||
ignore << 'cache/'
|
||||
ignore << 'coverage/'
|
||||
ignore << 'wordlist-iso-8859-1'
|
||||
ignore << 'log.txt'
|
||||
ignore << 'debug.log'
|
||||
ignore << 'wordlist.txt'
|
||||
|
||||
ignore
|
||||
end
|
||||
end
|
||||
@@ -1,104 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# This tool generates a list to use for plugin and theme enumeration
|
||||
class GenerateList
|
||||
|
||||
attr_accessor :verbose
|
||||
|
||||
# type = themes | plugins
|
||||
def initialize(type, verbose)
|
||||
if type =~ /plugins/i
|
||||
@type = 'plugin'
|
||||
@svn_url = 'http://plugins.svn.wordpress.org/'
|
||||
@popular_url = 'http://wordpress.org/plugins/browse/popular/'
|
||||
@popular_regex = %r{<h3><a href="http://wordpress.org/plugins/([^/]+)/">.+</a></h3>}i
|
||||
elsif type =~ /themes/i
|
||||
@type = 'theme'
|
||||
@svn_url = 'http://themes.svn.wordpress.org/'
|
||||
@popular_url = 'http://wordpress.org/themes/browse/popular/'
|
||||
@popular_regex = %r{<h3><a href="http://wordpress.org/themes/([^/]+)">.+</a></h3>}i
|
||||
else
|
||||
raise "Type #{type} not defined"
|
||||
end
|
||||
@verbose = verbose
|
||||
@browser = Browser.instance(request_timeout: 20000, connect_timeout: 20000, max_threads: 1, cache_ttl: 0)
|
||||
end
|
||||
|
||||
def set_file_name(type)
|
||||
case @type
|
||||
when 'plugin'
|
||||
case type
|
||||
when :full
|
||||
@file_name = PLUGINS_FULL_FILE
|
||||
when :popular
|
||||
@file_name = PLUGINS_FILE
|
||||
else
|
||||
raise 'Unknown type'
|
||||
end
|
||||
when 'theme'
|
||||
case type
|
||||
when :full
|
||||
@file_name = THEMES_FULL_FILE
|
||||
when :popular
|
||||
@file_name = THEMES_FILE
|
||||
else
|
||||
raise 'Unknown type'
|
||||
end
|
||||
else
|
||||
raise "Unknown type #@type"
|
||||
end
|
||||
end
|
||||
|
||||
def generate_full_list
|
||||
set_file_name(:full)
|
||||
items = SvnParser.new(@svn_url).parse
|
||||
save items
|
||||
end
|
||||
|
||||
def generate_popular_list(pages)
|
||||
set_file_name(:popular)
|
||||
items = get_popular_items(pages)
|
||||
save items
|
||||
end
|
||||
|
||||
# Send a HTTP request to the WordPress most popular theme or plugin webpage
|
||||
# parse the response for the names.
|
||||
def get_popular_items(pages)
|
||||
found_items = []
|
||||
page_count = 1
|
||||
|
||||
(1...(pages.to_i + 1)).each do |page|
|
||||
# First page has another URL
|
||||
url = (page == 1) ? @popular_url : @popular_url + 'page/' + page.to_s + '/'
|
||||
puts "[+] Parsing page #{page_count}" if @verbose
|
||||
code = 0
|
||||
while code != 200
|
||||
puts red("[!] Retrying request for page #{page} (Code: #{code})") unless code == 0
|
||||
request = @browser.forge_request(url)
|
||||
response = request.run
|
||||
code = response.code
|
||||
sleep(5) unless code == 200
|
||||
end
|
||||
page_count += 1
|
||||
found = 0
|
||||
response.body.scan(@popular_regex).each do |item|
|
||||
found_items << item[0]
|
||||
found = found + 1
|
||||
end
|
||||
puts "[+] Found #{found} items on page #{page}" if @verbose
|
||||
end
|
||||
|
||||
found_items.sort!
|
||||
found_items.uniq
|
||||
end
|
||||
|
||||
# Save the file
|
||||
def save(items)
|
||||
items.sort!
|
||||
items.uniq!
|
||||
puts "[*] We have parsed #{items.length} #{@type}s"
|
||||
File.open(@file_name, 'w') { |f| f.puts(items) }
|
||||
puts "New #@file_name file created"
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,53 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
class ListGeneratorPlugin < Plugin
|
||||
|
||||
def initialize
|
||||
super(author: 'WPScanTeam - @FireFart')
|
||||
|
||||
register_options(
|
||||
['--generate-plugin-list [NUMBER_OF_PAGES]', '--gpl', Integer, 'Generate a new data/plugins.txt file. (supply number of *pages* to parse, default : 150)'],
|
||||
['--generate-full-plugin-list', '--gfpl', 'Generate a new full data/plugins.txt file'],
|
||||
|
||||
['--generate-theme-list [NUMBER_OF_PAGES]', '--gtl', Integer, 'Generate a new data/themes.txt file. (supply number of *pages* to parse, default : 20)'],
|
||||
['--generate-full-theme-list', '--gftl', 'Generate a new full data/themes.txt file'],
|
||||
|
||||
['--generate-all', '--ga', 'Generate a new full plugins, full themes, popular plugins and popular themes list']
|
||||
)
|
||||
end
|
||||
|
||||
def run(options = {})
|
||||
@verbose = options[:verbose] || false
|
||||
generate_all = options[:generate_all] || false
|
||||
|
||||
if options.has_key?(:generate_plugin_list) || generate_all
|
||||
most_popular('plugin', options[:generate_plugin_list] || 150)
|
||||
end
|
||||
|
||||
if options[:generate_full_plugin_list] || generate_all
|
||||
full('plugin')
|
||||
end
|
||||
|
||||
if options.has_key?(:generate_theme_list) || generate_all
|
||||
most_popular('theme', options[:generate_theme_list] || 20)
|
||||
end
|
||||
|
||||
if options[:generate_full_theme_list] || generate_all
|
||||
full('theme')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def most_popular(type, number_of_pages)
|
||||
puts "[+] Generating new most popular #{type} list"
|
||||
puts
|
||||
GenerateList.new(type + 's', @verbose).generate_popular_list(number_of_pages)
|
||||
end
|
||||
|
||||
def full(type)
|
||||
puts "[+] Generating new full #{type} list"
|
||||
puts
|
||||
GenerateList.new(type + 's', @verbose).generate_full_list
|
||||
end
|
||||
end
|
||||
@@ -1,31 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# This Class Parses SVN Repositories via HTTP
|
||||
class SvnParser
|
||||
|
||||
attr_accessor :verbose, :svn_root, :keep_empty_dirs
|
||||
|
||||
def initialize(svn_root)
|
||||
@svn_root = svn_root
|
||||
end
|
||||
|
||||
def parse
|
||||
get_root_directories
|
||||
end
|
||||
|
||||
#Private methods start here
|
||||
private
|
||||
|
||||
# Gets all directories in the SVN root
|
||||
def get_root_directories
|
||||
dirs = []
|
||||
rootindex = Browser.get(@svn_root).body
|
||||
|
||||
rootindex.scan(%r{<li><a href=".+">(.+)/</a></li>}i).each do |dir|
|
||||
dirs << dir[0]
|
||||
end
|
||||
|
||||
dirs.sort!
|
||||
dirs.uniq
|
||||
end
|
||||
end
|
||||
@@ -6,48 +6,81 @@ class StatsPlugin < Plugin
|
||||
super(author: 'WPScanTeam - Christian Mehlmauer')
|
||||
|
||||
register_options(
|
||||
['--stats', '--s', 'Show WpScan Database statistics']
|
||||
['--stats', '-s', 'Show WpScan Database statistics.']
|
||||
)
|
||||
end
|
||||
|
||||
def run(options = {})
|
||||
if options[:stats]
|
||||
puts 'Wpscan Databse Statistics:'
|
||||
puts '--------------------------'
|
||||
puts "[#] Total vulnerable versions: #{vuln_core_count}"
|
||||
puts "[#] Total vulnerable plugins: #{vuln_plugin_count}"
|
||||
puts "[#] Total vulnerable themes: #{vuln_theme_count}"
|
||||
puts "[#] Total version vulnerabilities: #{version_vulns_count}"
|
||||
puts "[#] Total plugin vulnerabilities: #{plugin_vulns_count}"
|
||||
puts "[#] Total theme vulnerabilities: #{theme_vulns_count}"
|
||||
puts "[#] Total plugins to enumerate: #{total_plugins}"
|
||||
puts "[#] Total themes to enumerate: #{total_themes}"
|
||||
date_wp = File.mtime(WP_VULNS_FILE)
|
||||
date_plugins = File.mtime(PLUGINS_VULNS_FILE)
|
||||
date_themes = File.mtime(THEMES_VULNS_FILE)
|
||||
date_plugins_full = File.mtime(PLUGINS_FULL_FILE)
|
||||
date_themes_full = File.mtime(THEMES_FULL_FILE)
|
||||
|
||||
puts "WPScan Database Statistics:"
|
||||
puts "---------------------------"
|
||||
puts
|
||||
puts "[#] Total vulnerable versions: #{vuln_core_count}"
|
||||
puts "[#] Total vulnerable plugins: #{vuln_plugin_count}"
|
||||
puts "[#] Total vulnerable themes: #{vuln_theme_count}"
|
||||
puts
|
||||
puts "[#] Total version vulnerabilities: #{version_vulns_count}"
|
||||
puts "[#] Total fixed vulnerabilities: #{fix_version_count}"
|
||||
puts
|
||||
puts "[#] Total plugin vulnerabilities: #{plugin_vulns_count}"
|
||||
puts "[#] Total fixed vulnerabilities: #{fix_plugin_count}"
|
||||
puts
|
||||
puts "[#] Total theme vulnerabilities: #{theme_vulns_count}"
|
||||
puts "[#] Total fixed vulnerabilities: #{fix_theme_count}"
|
||||
puts
|
||||
puts "[#] Total plugins to enumerate: #{total_plugins}"
|
||||
puts "[#] Total themes to enumerate: #{total_themes}"
|
||||
puts
|
||||
puts "[+] WordPress DB modified: #{date_wp.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts "[+] Plugins DB modified: #{date_plugins.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts "[+] Themes DB modified: #{date_themes.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts "[+] Enumeration plugins: #{date_plugins_full.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts "[+] Enumeration themes: #{date_themes_full.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
puts
|
||||
puts "[+] Report generated: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
end
|
||||
end
|
||||
|
||||
def vuln_core_count(file=WP_VULNS_FILE)
|
||||
xml(file).xpath('count(//wordpress)').to_i
|
||||
json(file).size
|
||||
end
|
||||
|
||||
def vuln_plugin_count(file=PLUGINS_VULNS_FILE)
|
||||
xml(file).xpath('count(//plugin)').to_i
|
||||
json(file).size
|
||||
end
|
||||
|
||||
def vuln_theme_count(file=THEMES_VULNS_FILE)
|
||||
xml(file).xpath('count(//theme)').to_i
|
||||
json(file).size
|
||||
end
|
||||
|
||||
def version_vulns_count(file=WP_VULNS_FILE)
|
||||
xml(file).xpath('count(//vulnerability)').to_i
|
||||
asset_vulns_count(json(file))
|
||||
end
|
||||
|
||||
def fix_version_count(file=WP_VULNS_FILE)
|
||||
asset_fixed_in_count(json(file))
|
||||
end
|
||||
|
||||
def plugin_vulns_count(file=PLUGINS_VULNS_FILE)
|
||||
xml(file).xpath('count(//vulnerability)').to_i
|
||||
asset_vulns_count(json(file))
|
||||
end
|
||||
|
||||
def fix_plugin_count(file=PLUGINS_VULNS_FILE)
|
||||
asset_fixed_in_count(json(file))
|
||||
end
|
||||
|
||||
def theme_vulns_count(file=THEMES_VULNS_FILE)
|
||||
xml(file).xpath('count(//vulnerability)').to_i
|
||||
asset_vulns_count(json(file))
|
||||
end
|
||||
|
||||
def fix_theme_count(file=THEMES_VULNS_FILE)
|
||||
asset_fixed_in_count(json(file))
|
||||
end
|
||||
|
||||
def total_plugins(file=PLUGINS_FULL_FILE)
|
||||
@@ -62,4 +95,12 @@ class StatsPlugin < Plugin
|
||||
IO.readlines(file).size
|
||||
end
|
||||
|
||||
def asset_vulns_count(json)
|
||||
json.map { |asset| asset[asset.keys.inject]['vulnerabilities'].size }.inject(:+)
|
||||
end
|
||||
|
||||
def asset_fixed_in_count(json)
|
||||
json.map { |asset| asset[asset.keys.inject]['vulnerabilities'].map {|a| a['fixed_in'].nil? ? 0 : 1 }.inject(:+) }.inject(:+)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -12,21 +12,6 @@ def usage
|
||||
puts
|
||||
puts 'Examples:'
|
||||
puts
|
||||
puts "- Generate a new 'most popular' plugin list, up to 150 pages ..."
|
||||
puts "ruby #{script_name} --generate-plugin-list 150"
|
||||
puts
|
||||
puts '- Generate a new full plugin list'
|
||||
puts "ruby #{script_name} --generate-full-plugin-list"
|
||||
puts
|
||||
puts "- Generate a new 'most popular' theme list, up to 150 pages ..."
|
||||
puts "ruby #{script_name} --generate-theme-list 150"
|
||||
puts
|
||||
puts '- Generate a new full theme list'
|
||||
puts "ruby #{script_name} --generate-full-theme-list"
|
||||
puts
|
||||
puts '- Generate all list'
|
||||
puts "ruby #{script_name} --generate-all"
|
||||
puts
|
||||
puts 'Locally scan a wordpress installation for vulnerable files or shells'
|
||||
puts "ruby #{script_name} --check-local-vulnerable-files /var/www/wordpress/"
|
||||
puts
|
||||
|
||||
@@ -6,9 +6,9 @@ describe Browser do
|
||||
it_behaves_like 'Browser::Actions'
|
||||
it_behaves_like 'Browser::Options'
|
||||
|
||||
CONFIG_FILE_WITHOUT_PROXY = SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf.json'
|
||||
CONFIG_FILE_WITH_PROXY = SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf_proxy.json'
|
||||
#CONFIG_FILE_WITH_PROXY_AND_AUTH = SPEC_FIXTURES_CONF_DIR + '/browser/browser.conf_proxy_auth.json'
|
||||
CONFIG_FILE_WITHOUT_PROXY = SPEC_FIXTURES_CONF_DIR + '/browser.conf.json'
|
||||
CONFIG_FILE_WITH_PROXY = SPEC_FIXTURES_CONF_DIR + '/browser.conf_proxy.json'
|
||||
#CONFIG_FILE_WITH_PROXY_AND_AUTH = SPEC_FIXTURES_CONF_DIR + '/browser.conf_proxy_auth.json'
|
||||
|
||||
subject(:browser) {
|
||||
Browser.reset
|
||||
@@ -16,17 +16,16 @@ describe Browser do
|
||||
}
|
||||
let(:options) { {} }
|
||||
let(:instance_vars_to_check) {
|
||||
['user_agent', 'user_agent_mode', 'available_user_agents', 'proxy',
|
||||
'max_threads', 'cache_ttl', 'request_timeout', 'connect_timeout']
|
||||
['proxy', 'max_threads', 'cache_ttl', 'request_timeout', 'connect_timeout']
|
||||
}
|
||||
let(:json_config_without_proxy) { JSON.parse(File.read(CONFIG_FILE_WITHOUT_PROXY)) }
|
||||
let(:json_config_with_proxy) { JSON.parse(File.read(CONFIG_FILE_WITH_PROXY)) }
|
||||
|
||||
def check_instance_variables(browser, json_expected_vars)
|
||||
json_expected_vars['max_threads'] ||= 1 # max_thread can not be nil
|
||||
json_expected_vars['max_threads'] ||= 20 # max_thread can not be nil
|
||||
|
||||
instance_vars_to_check.each do |variable_name|
|
||||
browser.send(:"#{variable_name}").should === json_expected_vars[variable_name]
|
||||
expect(browser.send(:"#{variable_name}")).to be === json_expected_vars[variable_name]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,12 +38,6 @@ describe Browser do
|
||||
describe '::instance' do
|
||||
after { check_instance_variables(browser, @json_expected_vars) }
|
||||
|
||||
context "when default config_file = #{CONFIG_FILE_WITHOUT_PROXY}" do
|
||||
it 'will check the instance vars' do
|
||||
@json_expected_vars = json_config_without_proxy
|
||||
end
|
||||
end
|
||||
|
||||
context "when :config_file = #{CONFIG_FILE_WITH_PROXY}" do
|
||||
let(:options) { { config_file: CONFIG_FILE_WITH_PROXY } }
|
||||
|
||||
@@ -57,7 +50,7 @@ describe Browser do
|
||||
let(:cache_dir) { CACHE_DIR + '/somewhere' }
|
||||
let(:options) { { cache_dir: cache_dir } }
|
||||
|
||||
after { subject.cache_dir.should == cache_dir }
|
||||
after { expect(subject.cache_dir).to eq cache_dir }
|
||||
|
||||
it 'sets @cache_dir' do
|
||||
@json_expected_vars = json_config_without_proxy
|
||||
@@ -91,11 +84,11 @@ describe Browser do
|
||||
|
||||
describe '::append_params_header_field' do
|
||||
after :each do
|
||||
Browser.append_params_header_field(
|
||||
expect(Browser.append_params_header_field(
|
||||
@params,
|
||||
@field,
|
||||
@field_value
|
||||
).should === @expected
|
||||
)).to be === @expected
|
||||
end
|
||||
|
||||
context 'when there is no headers' do
|
||||
@@ -137,15 +130,17 @@ describe Browser do
|
||||
headers: { 'User-Agent' => 'SomeUA' },
|
||||
ssl_verifypeer: false, ssl_verifyhost: 0,
|
||||
cookiejar: cookie_jar, cookiefile: cookie_jar,
|
||||
timeout: 2000, connecttimeout: 1000
|
||||
timeout: 2000, connecttimeout: 1000,
|
||||
maxredirs: 3,
|
||||
referer: nil
|
||||
}
|
||||
}
|
||||
|
||||
after :each do
|
||||
browser.stub(user_agent: 'SomeUA')
|
||||
browser.user_agent = 'SomeUA'
|
||||
browser.cache_ttl = 250
|
||||
|
||||
browser.merge_request_params(params).should == @expected
|
||||
expect(browser.merge_request_params(params)).to eq @expected
|
||||
end
|
||||
|
||||
it 'sets the User-Agent header field and cache_ttl' do
|
||||
@@ -187,18 +182,35 @@ describe Browser do
|
||||
@expected = default_expectation.merge(params)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the maxredirs is alreday set' do
|
||||
let(:params) { { maxredirs: 100 } }
|
||||
|
||||
it 'does not override it' do
|
||||
@expected = default_expectation.merge(params)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when @cookie' do
|
||||
let(:cookie) { 'foor=bar;bar=foo' }
|
||||
before { browser.cookie = cookie }
|
||||
|
||||
it 'sets the cookie' do
|
||||
@expected = default_expectation.merge(cookie: cookie)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#forge_request' do
|
||||
let(:url) { 'http://example.localhost' }
|
||||
|
||||
it 'returns the correct Typhoeus::Request' do
|
||||
subject.stub(merge_request_params: { cache_ttl: 10 })
|
||||
allow(subject).to receive_messages(merge_request_params: { cache_ttl: 10 })
|
||||
|
||||
request = subject.forge_request(url)
|
||||
request.should be_a Typhoeus::Request
|
||||
request.url.should == url
|
||||
request.cache_ttl.should == 10
|
||||
expect(request).to be_a Typhoeus::Request
|
||||
expect(request.url).to eq url
|
||||
expect(request.cache_ttl).to eq 10
|
||||
end
|
||||
|
||||
end
|
||||
@@ -213,7 +225,7 @@ describe Browser do
|
||||
response1 = Browser.get(url)
|
||||
response2 = Browser.get(url)
|
||||
|
||||
response1.body.should == response2.body
|
||||
expect(response1.body).to eq response2.body
|
||||
#WebMock.should have_requested(:get, url).times(1) # This one fail, dunno why :s (but it works without mock)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,33 +17,50 @@ describe CacheFileStore do
|
||||
|
||||
describe '#storage_path' do
|
||||
it 'returns the storage path given in the #new' do
|
||||
@cache.storage_path.should == cache_dir
|
||||
expect(@cache.storage_path).to match(/#{cache_dir}/)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#serializer' do
|
||||
it 'should return the default serializer : Marshal' do
|
||||
@cache.serializer.should == Marshal
|
||||
@cache.serializer.should_not == YAML
|
||||
expect(@cache.serializer).to eq Marshal
|
||||
expect(@cache.serializer).not_to eq YAML
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clean' do
|
||||
it "should remove all files from the cache dir (#{@cache_dir}" do
|
||||
# let's create some files into the directory first
|
||||
(0..5).each do |i|
|
||||
File.new(cache_dir + "/file_#{i}.txt", File::CREAT)
|
||||
end
|
||||
|
||||
count_files_in_dir(cache_dir, 'file_*.txt').should == 6
|
||||
# clean is executed by other tests before
|
||||
before = count_files_in_dir(@cache.cache_dir)
|
||||
test_dir = File.expand_path("#{@cache.cache_dir}/test")
|
||||
Dir.mkdir test_dir
|
||||
#change the modification date
|
||||
%x[ touch -t 200701310846.26 #{test_dir} ]
|
||||
expect(count_files_in_dir(@cache.cache_dir)).to eq (before + 1)
|
||||
@cache.clean
|
||||
count_files_in_dir(cache_dir).should == 0
|
||||
expect(count_files_in_dir(@cache.cache_dir)).to eq before
|
||||
end
|
||||
end
|
||||
|
||||
describe '#read_entry (nonexistent entry)' do
|
||||
it 'should return nil' do
|
||||
@cache.read_entry(Digest::SHA1.hexdigest('hello world')).should be_nil
|
||||
describe '#read_entry' do
|
||||
after { expect(@cache.read_entry(key)).to eq @expected }
|
||||
|
||||
context 'when the entry does not exist' do
|
||||
let(:key) { Digest::SHA1.hexdigest('hello world') }
|
||||
|
||||
it 'should return nil' do
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the file exist but is empty (marshal data too short error)' do
|
||||
let(:key) { 'empty-file' }
|
||||
|
||||
it 'returns nil' do
|
||||
File.new(File.join(@cache.storage_path, key), File::CREAT)
|
||||
|
||||
@expected = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,7 +68,7 @@ describe CacheFileStore do
|
||||
|
||||
after :each do
|
||||
@cache.write_entry(@key, @data, @timeout)
|
||||
@cache.read_entry(@key).should === @expected
|
||||
expect(@cache.read_entry(@key)).to be === @expected
|
||||
end
|
||||
|
||||
it 'should get the correct entry (string)' do
|
||||
@@ -70,4 +87,16 @@ describe CacheFileStore do
|
||||
|
||||
## TODO write / read for an object
|
||||
end
|
||||
|
||||
describe '#storage_dir' do
|
||||
it 'should create a unique storage dir' do
|
||||
storage_dirs = []
|
||||
|
||||
(1..5).each do |i|
|
||||
storage_dirs << CacheFileStore.new(cache_dir).storage_path
|
||||
end
|
||||
|
||||
expect(storage_dirs.uniq.size).to eq 5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,20 +10,15 @@ describe WpItems do
|
||||
|
||||
let(:expected) do
|
||||
{
|
||||
request_params: { cache_ttl: 0, followlocation: true },
|
||||
targets_items_from_file: [ WpItem.new(uri, name: 'item1'),
|
||||
WpItem.new(uri, name:'item-2'),
|
||||
WpItem.new(uri, name: 'mr-smith')],
|
||||
request_params: { cache_ttl: 0, followlocation: true },
|
||||
targets_items_from_file: [ WpItem.new(uri, name: 'item1'),
|
||||
WpItem.new(uri, name: 'item-2'),
|
||||
WpItem.new(uri, name: 'mr-smith')],
|
||||
|
||||
vulnerable_targets_items: [ WpItem.new(uri, name: 'mr-smith'),
|
||||
WpItem.new(uri, name: 'neo')],
|
||||
vulnerable_targets_items: [ WpItem.new(uri, name: 'mr-smith'),
|
||||
WpItem.new(uri, name: 'neo')],
|
||||
|
||||
passive_detection: WpItems.new << WpItem.new(uri, name: 'js-source') <<
|
||||
WpItem.new(uri, name: 'escaped-url') <<
|
||||
WpItem.new(uri, name: 'link-tag') <<
|
||||
WpItem.new(uri, name: 'script-tag') <<
|
||||
WpItem.new(uri, name: 'style-tag') <<
|
||||
WpItem.new(uri, name: 'style-tag-import')
|
||||
passive_detection: (1..13).reduce(WpItems.new) { |o, i| o << WpItem.new(uri, name: "detect-me-#{i}") }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ describe 'WpPlugins::Detectable' do
|
||||
context 'when no header' do
|
||||
it 'returns an empty WpPlugins' do
|
||||
stub_request(:get, url).to_return(status: 200)
|
||||
subject.send(:from_header, wp_target).should == subject.new
|
||||
expect(subject.send(:from_header, wp_target)).to eq subject.new
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ describe 'WpPlugins::Detectable' do
|
||||
|
||||
after :each do
|
||||
stub_request(:get, url).to_return(status: 200, headers: headers, body: '')
|
||||
subject.send(:from_header, wp_target).should == expected
|
||||
expect(subject.send(:from_header, wp_target)).to eq expected
|
||||
end
|
||||
|
||||
context 'when w3-total-cache detected' do
|
||||
@@ -66,7 +66,7 @@ describe 'WpPlugins::Detectable' do
|
||||
context 'when no body' do
|
||||
it 'returns an empty WpPlugins' do
|
||||
stub_request(:get, url).to_return(status: 200, body: '')
|
||||
subject.send(:from_content, wp_target).should == subject.new
|
||||
expect(subject.send(:from_content, wp_target)).to eq subject.new
|
||||
end
|
||||
end
|
||||
|
||||
@@ -77,7 +77,7 @@ describe 'WpPlugins::Detectable' do
|
||||
after :each do
|
||||
stub_request(:get, url).to_return(status: 200, body: @body)
|
||||
stub_request(:get, /readme\.txt/i).to_return(status: 404)
|
||||
subject.send(:from_content, wp_target).should == expected
|
||||
expect(subject.send(:from_content, wp_target)).to eq expected
|
||||
end
|
||||
|
||||
context 'when w3 total cache detected' do
|
||||
|
||||
@@ -19,8 +19,7 @@ describe WpPlugins do
|
||||
vulnerable_targets_items: [ WpPlugin.new(uri, name: 'mr-smith'),
|
||||
WpPlugin.new(uri, name: 'neo')],
|
||||
|
||||
passive_detection: WpPlugins.new << WpPlugin.new(uri, name: 'js-source') <<
|
||||
WpPlugin.new(uri, name: 'escaped-url') <<
|
||||
passive_detection: WpPlugins.new << WpPlugin.new(uri, name: 'escaped-url') <<
|
||||
WpPlugin.new(uri, name: 'link-tag') <<
|
||||
WpPlugin.new(uri, name: 'script-tag') <<
|
||||
WpPlugin.new(uri, name: 'style-tag') <<
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe WpThemes do
|
||||
before { stub_request(:get, /.+\/style.css$/).to_return(status: 200) }
|
||||
|
||||
it_behaves_like 'WpItems::Detectable' do
|
||||
subject(:wp_themes) { WpThemes }
|
||||
let(:item_class) { WpTheme }
|
||||
|
||||
@@ -38,7 +38,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
|
||||
describe '::passive_detection' do
|
||||
it 'returns an empty WpTimthumbs' do
|
||||
subject.passive_detection(wp_target).should == subject.new
|
||||
expect(subject.passive_detection(wp_target)).to eq subject.new
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,7 +46,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
after do
|
||||
targets = subject.send(:targets_items_from_file, file, wp_target)
|
||||
|
||||
targets.map { |t| t.url }.should == @expected.map { |t| t.url }
|
||||
expect(targets.map { |t| t.url }).to eq @expected.map { |t| t.url }
|
||||
end
|
||||
|
||||
context 'when an empty file' do
|
||||
@@ -71,7 +71,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
theme = 'hello-world'
|
||||
targets = subject.send(:theme_timthumbs, theme, wp_target)
|
||||
|
||||
targets.map { |t| t.url }.should == expected_targets_from_theme(theme).map { |t| t.url }
|
||||
expect(targets.map { |t| t.url }).to eq expected_targets_from_theme(theme).map { |t| t.url }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -81,7 +81,7 @@ describe 'WpTimthumbs::Detectable' do
|
||||
after do
|
||||
targets = subject.send(:targets_items, wp_target, options)
|
||||
|
||||
targets.map { |t| t.url }.should == @expected.sort.map { |t| t.url }
|
||||
expect(targets.map { |t| t.url }).to eq @expected.sort.map { |t| t.url }
|
||||
end
|
||||
|
||||
context 'when no :theme_name' do
|
||||
|
||||
@@ -22,13 +22,13 @@ describe 'WpUsers::Detectable' do
|
||||
|
||||
describe '::request_params' do
|
||||
it 'return an empty Hash' do
|
||||
subject.request_params.should === {}
|
||||
expect(subject.request_params).to be === {}
|
||||
end
|
||||
end
|
||||
|
||||
describe '::passive_detection' do
|
||||
it 'return an empty WpUsers' do
|
||||
subject.passive_detection(wp_target).should == subject.new
|
||||
expect(subject.passive_detection(wp_target)).to eq subject.new
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,7 +36,7 @@ describe 'WpUsers::Detectable' do
|
||||
after do
|
||||
targets = subject.send(:targets_items, wp_target, options)
|
||||
|
||||
targets.should == @expected
|
||||
expect(targets).to eq @expected
|
||||
end
|
||||
|
||||
context 'when no :range' do
|
||||
|
||||
@@ -25,19 +25,19 @@ describe 'WpUsers::Output' do
|
||||
subject.push(@input)
|
||||
subject.flatten!
|
||||
subject.remove_junk_from_display_names
|
||||
subject.should === @expected
|
||||
expect(subject).to eq @expected
|
||||
end
|
||||
|
||||
it 'should return an empty array' do
|
||||
it 'returns an empty array' do
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return input object' do
|
||||
it 'returns input object' do
|
||||
@input.push(WpUser.new(nil))
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return input object' do
|
||||
it 'returns input object' do
|
||||
@input.push(WpUser.new(''))
|
||||
@expected = @input
|
||||
end
|
||||
@@ -50,23 +50,37 @@ describe 'WpUsers::Output' do
|
||||
@expected.push(WpUser.new('', login: '', id: 2, display_name: 'ijrjd'))
|
||||
end
|
||||
|
||||
it 'should return unmodified input object' do
|
||||
it 'returns unmodified input object' do
|
||||
@input.push(WpUser.new('', login: '', id: 1, display_name: 'lkjh asdfa'))
|
||||
@input.push(WpUser.new('', login: '', id: 2, display_name: 'ijrjd asdf'))
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return input object' do
|
||||
it 'returns input object' do
|
||||
@input.push(WpUser.new('', login: '', id: 1, display_name: 'lkjh asdf'))
|
||||
@expected = @input
|
||||
end
|
||||
|
||||
it 'should return an empty display_name' do
|
||||
it 'returns an empty display_name' do
|
||||
@input.push(WpUser.new('', login: '', id: 1, display_name: 'lkhj asdf'))
|
||||
@input.push(WpUser.new('', login: '', id: 2, display_name: 'lkhj asdf'))
|
||||
@expected = WpUsers.new(0)
|
||||
@expected.push(WpUser.new('', login: '', id: 1, display_name: ''))
|
||||
@expected.push(WpUser.new('', login: '', id: 2, display_name: ''))
|
||||
end
|
||||
|
||||
context 'when a user has no display_name' do
|
||||
it 'returns an empty display_name' do
|
||||
@input.push(WpUser.new('', login: '', id: 1, display_name: 'lkhj asdf'))
|
||||
@input.push(WpUser.new('', login: '', id: 2, display_name: 'lkhj asdf'))
|
||||
@input.push(WpUser.new('', login: '', id: 3))
|
||||
|
||||
@expected = WpUsers.new(0)
|
||||
|
||||
(1..3).each do |id|
|
||||
@expected.push(WpUser.new('', login: '', id: id, display_name: ''))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user