2
2
3
3
namespace marcocesarato \amwscan \Module ;
4
4
5
+ use GlobIterator ;
5
6
use marcocesarato \amwscan \Console ;
6
7
use marcocesarato \amwscan \VerifierInterface ;
7
8
8
9
class Wordpress implements VerifierInterface
9
10
{
10
11
protected static $ checksums = array ();
12
+ protected static $ pluginsChecksums = array ();
11
13
protected static $ roots = array ();
12
14
protected static $ DS = DIRECTORY_SEPARATOR ;
13
15
@@ -21,12 +23,16 @@ public static function init($path)
21
23
if (self ::isRoot ($ path )) {
22
24
$ version = self ::getVersion ($ path );
23
25
if ($ version && !empty ($ version ) && !isset (self ::$ roots [$ path ])) {
24
- Console::writeLine ('Found WordPress ' . $ version . ' at " ' . $ path . '" ' , 1 , 'green ' );
26
+ $ locale = self ::getLocale ($ path );
27
+ Console::writeLine ('Found WordPress ' . $ version . ' ( ' . $ locale . ') at " ' . $ path . '" ' , 1 , 'green ' );
28
+
29
+ $ plugins = self ::getPlugins ($ path );
25
30
self ::$ roots [$ path ] = array (
26
31
'path ' => $ path ,
27
32
'version ' => $ version ,
33
+ 'locale ' => $ locale ,
34
+ 'plugins ' => $ plugins ,
28
35
);
29
- self ::getChecksums ($ version );
30
36
}
31
37
}
32
38
}
@@ -45,7 +51,6 @@ public static function isRoot($path)
45
51
is_dir ($ path . self ::$ DS . 'wp-admin ' ) &&
46
52
is_dir ($ path . self ::$ DS . 'wp-content ' ) &&
47
53
is_dir ($ path . self ::$ DS . 'wp-includes ' ) &&
48
- is_file ($ path . self ::$ DS . 'wp-config.php ' ) &&
49
54
is_file ($ path . self ::$ DS . 'wp-includes ' . self ::$ DS . 'version.php ' )
50
55
;
51
56
}
@@ -62,7 +67,7 @@ public static function getVersion($root)
62
67
$ versionFile = $ root . self ::$ DS . 'wp-includes ' . self ::$ DS . 'version.php ' ;
63
68
if (is_file ($ versionFile )) {
64
69
$ versionContent = file_get_contents ($ versionFile );
65
- preg_match ('/\$wp_version[\s]*=[\s]*[ \'"]([0-9.]+)[ \'"]/ ' , $ versionContent , $ match );
70
+ preg_match ('/\$wp_version[\s]*=[\s]*[ \'"]([0-9.]+)[ \'"]/m ' , $ versionContent , $ match );
66
71
$ version = trim ($ match [1 ]);
67
72
if (!empty ($ version )) {
68
73
return $ version ;
@@ -72,19 +77,97 @@ public static function getVersion($root)
72
77
return null ;
73
78
}
74
79
80
+ /**
81
+ * Get locale.
82
+ *
83
+ * @param $root
84
+ *
85
+ * @return string
86
+ */
87
+ public static function getLocale ($ root )
88
+ {
89
+ $ versionFile = $ root . self ::$ DS . 'wp-includes ' . self ::$ DS . 'version.php ' ;
90
+ if (is_file ($ versionFile )) {
91
+ $ versionContent = file_get_contents ($ versionFile );
92
+ preg_match ('/\$wp_local_package[\s]*=[\s]*[ \'"]([A-Za-z_-]+)[ \'"]/m ' , $ versionContent , $ match );
93
+ $ locale = trim ($ match [1 ]);
94
+ if (!empty ($ locale )) {
95
+ return $ locale ;
96
+ }
97
+ }
98
+
99
+ return 'en_US ' ;
100
+ }
101
+
102
+ /**
103
+ * Get plugins.
104
+ *
105
+ * @param $root
106
+ *
107
+ * @return string[]
108
+ */
109
+ public static function getPlugins ($ root )
110
+ {
111
+ $ plugins = array ();
112
+ $ files = new GlobIterator ($ root . self ::$ DS . 'wp-content ' . self ::$ DS . 'plugins ' . self ::$ DS . '* ' . self ::$ DS . '*.php ' );
113
+ foreach ($ files as $ cur ) {
114
+ if ($ cur ->isFile ()) {
115
+ $ headers = self ::getPluginHeaders ($ cur ->getPathname ());
116
+ if (!empty ($ headers ['domain ' ]) && !empty ($ headers ['version ' ])) {
117
+ if (empty ($ headers ['domain ' ])) {
118
+ $ headers ['domain ' ] = $ cur ->getBasename ('. ' . $ cur ->getExtension ());
119
+ }
120
+ $ headers ['path ' ] = $ cur ->getPath ();
121
+ $ plugins [$ cur ->getPath ()] = $ headers ;
122
+ Console::writeLine ('Found WordPress Plugin ' . $ headers ['name ' ] . ' ' . $ headers ['version ' ], 1 , 'green ' );
123
+ }
124
+ }
125
+ }
126
+
127
+ return $ plugins ;
128
+ }
129
+
130
+ /**
131
+ * Get file headers.
132
+ *
133
+ * @param $file
134
+ *
135
+ * @return string[]
136
+ */
137
+ public static function getPluginHeaders ($ file )
138
+ {
139
+ $ headers = array ('name ' => 'Plugin Name ' , 'version ' => 'Version ' , 'domain ' => 'Text Domain ' );
140
+ $ file_data = file_get_contents ($ file );
141
+ $ file_data = str_replace ("\r" , "\n" , $ file_data );
142
+ foreach ($ headers as $ field => $ regex ) {
143
+ if (preg_match ('/^[ \t\/*#@]* ' . preg_quote ($ regex , '/ ' ) . ':(.*)$/mi ' , $ file_data , $ match ) && $ match [1 ]) {
144
+ $ headers [$ field ] = trim (preg_replace ('/\s*(?:\*\/|\?>).*/ ' , '' , $ match [1 ]));
145
+ } else {
146
+ $ headers [$ field ] = '' ;
147
+ }
148
+ }
149
+
150
+ return $ headers ;
151
+ }
152
+
75
153
/**
76
154
* Get checksums.
77
155
*
78
156
* @param $version
157
+ * @param string $locale
158
+ * @param array $plugins
79
159
*
80
- * @return array
160
+ * @return array|false
81
161
*/
82
- public static function getChecksums ($ version )
162
+ public static function getChecksums ($ version, $ locale = ' en_US ' )
83
163
{
84
- if (empty (self ::$ checksums [$ version ])) {
85
- $ checksums = file_get_contents ('https://api.wordpress.org/core/checksums/1.0/?version= ' . $ version );
86
- $ checksums = json_decode ($ checksums , true );
87
- $ versionChecksums = $ checksums ['checksums ' ][$ version ];
164
+ if (!isset (self ::$ checksums [$ version ])) {
165
+ Console::writeLine ('Retrieving checksums of Wordpress ' . $ version , 1 , 'grey ' );
166
+ $ checksums = self ::getData ('https://api.wordpress.org/core/checksums/1.0/?version= ' . $ version . '&locale= ' . $ locale );
167
+ if (!$ checksums ) {
168
+ return false ;
169
+ }
170
+ $ versionChecksums = $ checksums ['checksums ' ];
88
171
self ::$ checksums [$ version ] = array ();
89
172
// Sanitize paths and checksum
90
173
foreach ($ versionChecksums as $ filePath => $ checksum ) {
@@ -96,6 +179,39 @@ public static function getChecksums($version)
96
179
return self ::$ checksums [$ version ];
97
180
}
98
181
182
+ /**
183
+ * Get checksums.
184
+ *
185
+ * @param $version
186
+ * @param string $locale
187
+ * @param array $plugins
188
+ *
189
+ * @return array|false
190
+ */
191
+ public static function getPluginsChecksums ($ plugins = array ())
192
+ {
193
+ foreach ($ plugins as $ plugin ) {
194
+ if (!isset (self ::$ pluginsChecksums [$ plugin ['domain ' ]][$ plugin ['version ' ]])) {
195
+ Console::writeLine ('Retrieving checksums of Wordpress Plugin ' . $ plugin ['name ' ] . ' ' . $ plugin ['version ' ], 1 , 'grey ' );
196
+ $ checksums = self ::getData ('https://downloads.wordpress.org/plugin-checksums/ ' . $ plugin ['domain ' ] . '/ ' . $ plugin ['version ' ] . '.json ' );
197
+ if (!$ checksums ) {
198
+ self ::$ pluginsChecksums [$ plugin ['domain ' ]][$ plugin ['version ' ]] = array ();
199
+ continue ;
200
+ }
201
+ $ pluginChecksums = $ checksums ['files ' ];
202
+ foreach ($ pluginChecksums as $ filePath => $ checksum ) {
203
+ $ path = $ plugin ['path ' ] . self ::$ DS . $ filePath ;
204
+ $ root = self ::getRoot ($ path );
205
+ $ sanitizePath = str_replace ($ root , '' , $ path );
206
+ $ sanitizePath = self ::sanitizePath ($ sanitizePath );
207
+ self ::$ pluginsChecksums [$ plugin ['domain ' ]][$ plugin ['version ' ]][$ sanitizePath ] = strtolower ($ checksum ['md5 ' ]);
208
+ }
209
+ }
210
+ }
211
+
212
+ return self ::$ pluginsChecksums ;
213
+ }
214
+
99
215
/**
100
216
* Is verified file.
101
217
*
@@ -113,16 +229,28 @@ public static function isVerified($path)
113
229
if (!empty ($ root )) {
114
230
$ comparePath = str_replace ($ root ['path ' ], '' , $ path );
115
231
$ comparePath = self ::sanitizePath ($ comparePath );
116
- $ checksums = self ::getChecksums ($ root ['version ' ]);
232
+ $ checksums = self ::getChecksums ($ root ['version ' ], $ root ['locale ' ]);
233
+ $ pluginsChecksums = self ::getPluginsChecksums ($ root ['plugins ' ]);
117
234
if (!$ checksums ) {
118
235
return false ;
119
236
}
237
+ // Core
120
238
if (!empty ($ checksums [$ comparePath ])) {
121
239
$ checksum = md5_file ($ path );
122
240
$ checksum = strtolower ($ checksum );
123
241
124
242
return $ checksums [$ comparePath ] === $ checksum ;
125
243
}
244
+ // Plugins
245
+ foreach ($ root ['plugins ' ] as $ plugin ) {
246
+ $ checksums = $ pluginsChecksums [$ plugin ['domain ' ]][$ plugin ['version ' ]];
247
+ if (!empty ($ pluginsChecksums [$ plugin ['domain ' ]][$ plugin ['version ' ]]) && !empty ($ checksums [$ comparePath ])) {
248
+ $ checksum = md5_file ($ path );
249
+ $ checksum = strtolower ($ checksum );
250
+
251
+ return $ checksums [$ comparePath ] === $ checksum ;
252
+ }
253
+ }
126
254
}
127
255
128
256
return false ;
@@ -162,4 +290,23 @@ public static function sanitizePath($path)
162
290
163
291
return $ sanitized ;
164
292
}
293
+
294
+ /**
295
+ * HTTP request get data.
296
+ *
297
+ * @param $url
298
+ *
299
+ * @return mixed|null
300
+ */
301
+ protected static function getData ($ url )
302
+ {
303
+ $ headers = get_headers ($ url );
304
+ if (substr ($ headers [0 ], 9 , 3 ) != '200 ' ) {
305
+ return null ;
306
+ }
307
+
308
+ $ content = @file_get_contents ($ url );
309
+
310
+ return @json_decode ($ content , true );
311
+ }
165
312
}
0 commit comments