├── LICENSE ├── README.md ├── hostmap-hackertarget.nse ├── http-wordpress-info.nse ├── http-wordpress-plugins.nse ├── http-wordpress-themes.nse ├── wp-plugins.lst └── wp-themes.lst /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nmap-nse-scripts 2 | ---- 3 | Nmap NSE scripts that we have created or customised. At this stage these are custom scripts that you will have to copy into your Nmap Scripts directory manually to use them. 4 | 5 | Installation of Custom Nmap Scripts 6 | ---- 7 | Depending on your installation method and distribution the exact location of the Nmap script files could be slightly different. If you have installed from source then copying these into the `/usr/local/share/nmap/scripts/` folder will do the trick. 8 | 9 | You will also need to copy the **wp-themes.lst** and **wp-plugins.lst** into the `/usr/local/share/nmap/nselib/data/` folder for the script to access the theme list. 10 | 11 | If you have a Windows installation of Nmap or are using a package, then the location of the files could be slightly different (it should not be too hard to find). 12 | 13 | http-wordpress-info.nse 14 | ---- 15 | This script is non-intrusive and parses the source HTML of a WordPress page to find plugins, theme and the version of WordPress. 16 | 17 | - Version is detected from the Meta Generator Tag or `/feed/atom/` if not present. 18 | - Plugins are detected from HTML source /wp-content/plugins/**$pluginname**/ in addition a number of known HTML strings for common plugins are checked. 19 | - Theme is detected from HTML source /wp-content/themes/**$themename**/ 20 | - PHP Version is shown if present in HTTP Headers (Server or X-Powered-By) 21 | - HTTP Server is shown from HTTP Header (Server) 22 | 23 | http-wordpress-plugins.nse 24 | ---- 25 | #### Deprecated as http-wordpress-enum.nse has been updated with this functionality 26 | A modified version of the original `http-wordpress-plugins.nse` script that will also attempt to identify the version of the plugins that have been detected following the brute force of the plugin paths. 27 | 28 | http-wordpress-themes.nse 29 | ---- 30 | #### Deprecated as http-wordpress-enum.nse has been updated with this functionality 31 | Another modified version of the `http-wordpress-plugins.nse` script this script will identify themes installed in the **/wp-content/themes/** folder and also attempt to identify the version of the themes from the **style.css** file. The **wp-theme.lst** was created by crawling the Top 1 million WordPress sites and ranking the themes by popularity. 32 | 33 | Themes that are installed but not in use by a WordPress installation can still contain vulnerabilities that could lead to the compromise of the WordPress installation and server. 34 | 35 | hostmap-hackertarget.nse 36 | ---- 37 | Similar to the hostmap-robtex.nse this script will attempt to identify hosts sharing the IP address that is being scanned. The hosts are found using the [Reverse IP Lookup API](https://hackertarget.com/reverse-ip-lookup/ "Reverse IP Lookup") that utilises DNS records from the [Scans.IO](https://scans.io) project, Certificate Transparency and other Data Sources. 38 | 39 | About HackerTarget Pty Ltd 40 | ---- 41 | - [HackerTarget](https://hackertarget.com) provides hosted open source tools and network intelligence to help organizations with attack surface discovery and identification of security vulnerabilities. 42 | - Expensive appliances with flashing blue lights are not always the best solution. Through promotion of open source security solutions we make organisations big and small better at protecting what matters to them. 43 | 44 | -------------------------------------------------------------------------------- /hostmap-hackertarget.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local ipOps = require "ipOps" 3 | local stdnse = require "stdnse" 4 | local string = require "string" 5 | local table = require "table" 6 | 7 | description = [[ 8 | Discovers hostnames (DNS A records) that resolve to the target's IP address by querying the online reverse IP lookup at http://hackertarget.com/reverse-ip-lookup/. 9 | 10 | Script based on hostmap-robtex.nse by Arturo 'Buanzo' Busleiman. 11 | 12 | Nmap 6.47 may error with: 13 | /usr/local/bin/../share/nmap/nselib/shortport.lua:200: attempt to index field 'version' (a nil value) 14 | Fix issue by getting latest shortport.lua from the Nmap svn. 15 | 16 | ]] 17 | 18 | --- 19 | -- @usage 20 | -- nmap --script hostmap-hackertarget -p 80 -Pn nmap.org 21 | -- 22 | -- @output 23 | -- | hostmap-hackertarget: 24 | -- | hosts: 25 | -- | cgi.insecure.org 26 | -- | download.insecure.org 27 | -- | images.insecure.org 28 | -- | insecure.com 29 | -- | insecure.org 30 | -- | nmap.com 31 | -- | nmap.net 32 | -- | nmap.org 33 | -- | seclists.org 34 | -- | sectools.org 35 | -- | svn.nmap.org 36 | -- | www.insecure.org 37 | -- | www.nmap.org 38 | -- |_ www.sectools.org 39 | 40 | -- 41 | -- @xmloutput 42 | -- 43 | -- nmap.org 44 | --
45 | --- 46 | 47 | author = "Peter Hill" 48 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 49 | categories = { 50 | "discovery", 51 | "safe", 52 | "external" 53 | } 54 | 55 | 56 | -- Scrape domains sharing target host ip from hackertarget.com website 57 | -- @param data string containing the retrieved web page 58 | -- @return table containing the host names sharing host.ip 59 | function parse_hackertarget_response (data) 60 | local result = {} 61 | 62 | for domain in string.gmatch(data, "([0-9a-z-.]+)") do 63 | if not stdnse.contains(result, domain) then 64 | table.insert(result, domain) 65 | end 66 | end 67 | return result 68 | end 69 | 70 | hostrule = function (host) 71 | return not ipOps.isPrivate(host.ip) 72 | end 73 | 74 | action = function (host) 75 | local link = "http://api.hackertarget.com/reverseiplookup/?q=" .. host.ip 76 | local htmldata = http.get_url(link) 77 | local domains = parse_hackertarget_response(htmldata.body) 78 | local output_tab = stdnse.output_table() 79 | if (#domains > 0) then 80 | output_tab.hosts = domains 81 | end 82 | return output_tab 83 | end 84 | -------------------------------------------------------------------------------- /http-wordpress-info.nse: -------------------------------------------------------------------------------- 1 | local http = require "http" 2 | local shortport = require "shortport" 3 | local stdnse = require "stdnse" 4 | local string = require "string" 5 | 6 | description = [[ 7 | Finds the WordPress version, theme and plugins observed in the page response. 8 | - WordPress version tests for a meta generator html tag, if this is not found an attempt 9 | is made to match version in page HTML or /feed/atom/ a default page in all versions of WordPress. 10 | - Theme is determined by searching HTML resposne for /wp-content/themes/$themename 11 | - Discovered plugins are those that match /wp-content/plugins/$pluginname in the HTML 12 | response. This will not find all plugins, to find all plugins you will need the 13 | http-wordpress-plugins nse script to brute force the plugin paths. 14 | - Additional checks are performed to match comments or other identifiers in the HTML for known plugins. 15 | 16 | Original script based on code from Michael Kohl's http-generator.nse 17 | ]] 18 | 19 | author = "Peter Hill " 20 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 21 | categories = {"default", "discovery", "safe"} 22 | 23 | --- 24 | -- @usage 25 | -- nmap --script http-wordpress-info [--script-args http-wordpress-info.path=,http-wordpress-info.redirects=,...] 26 | -- 27 | -- @output 28 | -- PORT STATE SERVICE 29 | -- 80/tcp open http 30 | -- | http-wordpress-info: 31 | -- | version: WordPress 4.0 32 | -- | theme: canvas 33 | -- | plugins: 34 | -- | w3-total-cache 35 | -- |_ simple-tooltips 36 | 37 | -- @args http-wordpress-info.path Specify the path you want to check for a generator meta tag (default to '/'). 38 | -- @args http-wordpress-info.redirects Specify the maximum number of redirects to follow (defaults to 3). 39 | 40 | portrule = shortport.http 41 | 42 | local follow_redirects = function(host, port, path, n) 43 | local loc 44 | local pattern = "^[hH][tT][tT][pP]/1.[01] 30[12]" 45 | local response = http.get(host, port, path) 46 | while (response['status-line'] or ""):match(pattern) and n > 0 do 47 | n = n - 1 48 | loc = response.header['location'] 49 | response = http.get_url(loc) 50 | end 51 | return response, loc 52 | end 53 | 54 | -- unique key for plugin slug 55 | -- can check multiple matches in value {} 56 | local plugin_patterns = { 57 | ["autoptimize"] = {"autoptimize/js","autoptimize/css"}, 58 | ["w3-total-cache"] = {"by W3 Total Cache"}, 59 | ["wp-super-cache"] = {" generated by WP-Super-Cache"}, 60 | ["wordpress-seo"] = {"optimized with the Yoast SEO plugin"}, 61 | ["wordpress-seo-premium"] = {"optimized with the Yoast SEO Premium plugin"}, 62 | ["all-in-one-seo-pack"] = {"-- All in One SEO Pack"}, 63 | ["ubermenu"] = {"UberMenu CSS"}, 64 | ["hyper-cache"] = {"-- hyper cache 20"}, 65 | ["cookie-notice"] = {"Cookie Notice plugin"}, 66 | ["imp-download"] = {"Powered by iMP Download"}, 67 | ["optinmonster"] = {"-- This site is converting visitors into subscribers and customers with OptinMonster"}, 68 | ["duracelltomi-google-tag-manager"] = {"for WordPress by gtm4wp.com"}, 69 | ["woocommerce"] = {"generator[\"\'] content=[\"\']WooCommerce"}, 70 | ["custom-css-js"] = {"Simple Custom CSS and JS"}, 71 | ["simple-social-buttons"] = {"Tags generated by Simple Social Buttons"}, 72 | ["contact-form-7"] = {"var wpcf7 ="}, 73 | ["jetpack"] = {"-- Jetpack Open Graph Tags --","Jetpack Site Verification Tags --","jetpack.js"}, 74 | ["wp-rocket"] = {"-- This website is like a Rocket,"}, 75 | ["autodescription"] = {"The SEO Framework by Sybre Waaijer --"}, 76 | ["slider-revolution"] = {"-->

', 122 | 'wp-includes/js/wp-embed.min.js?ver=([.0-9]+)', 123 | 'wp-includes/js/comment-reply.min.js?ver=([.0-9]+)', 124 | 'wp-emoji-release.min.js?ver=([.0-9]+)', 125 | 'wp-includes/css/dist/block-library/style.min.css?ver=([.0-9]+)'} 126 | for key, pattern in pairs(patterns) do 127 | -- make pattern case-insensitive 128 | pattern = pattern:gsub("%a", function (c) 129 | return string.format("[%s%s]", string.lower(c), 130 | string.upper(c)) 131 | end) 132 | if wpver == nil then 133 | wpver = response.body:match(pattern) 134 | end 135 | end 136 | -- Find version in /feed/atom/ default XML feed if no other match in html 137 | if ( not wpver and response.body:match("wp%-")) then 138 | local feedpattern = 'generator uri="https://wordpress.org/" version="([.0-9]*)' 139 | feedpath = path .. 'feed/atom/' 140 | -- if redirect occured get feed from loc 141 | if not loc then 142 | feedresponse = http.get(host, port, feedpath) 143 | else 144 | feedresponse = http.get_url(loc) 145 | end 146 | if ( feedresponse and feedresponse.body ) then 147 | wpver = feedresponse.body:match(feedpattern) 148 | end 149 | end 150 | return wpver 151 | end 152 | 153 | -- php version from headers only 154 | -- http-php-version.nse makes additional requests 155 | function php_version_check (response) 156 | local phpmatch = 'PHP/([0-9.]+)' 157 | local phpver 158 | for name, value in pairs(response.header) do 159 | if value:match(phpmatch) then 160 | phpver = value:match(phpmatch) 161 | end 162 | end 163 | return(phpver) 164 | end 165 | 166 | -- server header only (good if not using -sV to increase speed) 167 | function server_check (headers) 168 | local servermatch = '([a-zA-Z%-%/%)%(0-9.% ]+)' 169 | local server 170 | for name, value in pairs(headers) do 171 | if value:match(servermatch) and name == "server" then 172 | server = value:match(servermatch) 173 | end 174 | end 175 | return(server) 176 | end 177 | 178 | 179 | 180 | action = function(host, port) 181 | local response, loc, generator, testcheck, plugins, themes, wpversion, phpversion, servercheck 182 | local path = stdnse.get_script_args('http-wordpress-info.path') or '/' 183 | local redirects = stdnse.get_script_args('http-wordpress-info.redirects') or 3 184 | local output_tab = stdnse.output_table() 185 | 186 | -- Find Version in "meta generator tag" 187 | local themematch = 'wp%-content/themes/([0-9a-zA-Z%-]+)' 188 | 189 | -- Get HTML to check for WordPress installation 190 | response, loc = follow_redirects( host, port, path, redirects ) 191 | 192 | stdnse.debug1("HTTP Status: %s", response["status-line"]) 193 | output_tab.status = response["status"] 194 | output_tab.redirect = loc 195 | if ( response and response.body ) then 196 | wpversion = wp_version_check(response, host, port, path, loc) 197 | themes = response.body:match(themematch) 198 | plugins = parse_plugins_response(response.body, plugin_patterns) 199 | phpversion = php_version_check(response) 200 | servercheck = server_check(response.header) 201 | 202 | -- Store results in output table 203 | -- output_tab.testing = feedresponse.body 204 | if wpversion then 205 | output_tab.version = 'WordPress ' .. wpversion 206 | else 207 | if (response.body:match("wp%-json") or response.body:match("wp%-admin") or response.body:match("wp%-includes") or response.body:match("wp%-content")) and not wpversion then 208 | output_tab.version = 'WordPress ???' 209 | end 210 | end 211 | if ( themes and #themes > 0 ) then 212 | output_tab.theme = themes 213 | end 214 | if ( plugins and #plugins > 0 ) then 215 | output_tab.plugins = plugins 216 | end 217 | if ( phpversion ) then 218 | output_tab.php = phpversion 219 | end 220 | if ( servercheck ) then 221 | output_tab.server = servercheck 222 | end 223 | end 224 | if ( output_tab.version or output_tab.plugins or output_tab.theme or output_tab.status ) then 225 | return output_tab 226 | end 227 | end 228 | -------------------------------------------------------------------------------- /http-wordpress-plugins.nse: -------------------------------------------------------------------------------- 1 | local coroutine = require "coroutine" 2 | local http = require "http" 3 | local io = require "io" 4 | local nmap = require "nmap" 5 | local shortport = require "shortport" 6 | local stdnse = require "stdnse" 7 | local string = require "string" 8 | local table = require "table" 9 | 10 | description = [[ 11 | Tries to obtain a list of installed WordPress plugins by brute force 12 | testing for known plugins. 13 | 14 | The script will brute force the /wp-content/plugins/ folder with a dictionary 15 | of 14K (and counting) known WP plugins. Anything but a 404 means that a given 16 | plugin directory probably exists, so the plugin probably also does. 17 | 18 | The available plugins for Wordpress is huge and despite the efforts of Nmap to 19 | parallelize the queries, a whole search could take an hour or so. That's why 20 | the plugin list is sorted by popularity and by default the script will only 21 | check the first 100 ones. Users can tweak this with an option (see below). 22 | ]] 23 | 24 | --- 25 | -- @args http-wordpress-plugins.root If set, points to the blog root directory on the website. If not, the script will try to find a WP directory installation or fall back to root. 26 | -- @args http-wordpress-plugins.search As the plugins list contains tens of thousand of plugins, this script will only search the 100 most popular ones by default. 27 | -- Use this option with a number or "all" as an argument for a more comprehensive brute force. 28 | -- @args http-wordpress-plugins.apicheck Set to true or false to enable the API latest version check. The check gets the latest version of the plugin from the wordpress.org plugin API. Default is true. 29 | -- 30 | -- @usage 31 | -- nmap --script=http-wordpress-plugins --script-args http-wordpress-plugins.root="/blog/",http-wordpress-plugins.search=500 32 | -- 33 | --@output 34 | -- Interesting ports on my.woot.blog (123.123.123.123): 35 | -- PORT STATE SERVICE REASON 36 | -- 80/tcp open http syn-ack 37 | -- | http-wordpress-plugins: 38 | -- | search amongst the 500 most popular plugins 39 | -- | akismet 3.0.4 (latest version: 3.0.4) 40 | -- | wordpress-seo 1.7 (latest version: 1.7.1) 41 | -- | disqus-comment-system 2.83 (latest version: 2.84) 42 | -- |_ wp-to-twitter 1.2 (latest version: 1.45) 43 | 44 | author = "Ange Gutek" 45 | 46 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 47 | 48 | categories = {"discovery", "intrusive"} 49 | 50 | 51 | local DEFAULT_PLUGINS_SEARCH = 100 52 | 53 | 54 | portrule = shortport.service("http") 55 | 56 | local function read_data_file(file) 57 | return coroutine.wrap(function() 58 | for line in file:lines() do 59 | if not line:match("^%s*#") and not line:match("^%s*$") then 60 | coroutine.yield(line) 61 | end 62 | end 63 | end) 64 | end 65 | 66 | action = function(host, port) 67 | 68 | local result = {} 69 | local all = {} 70 | local bfqueries = {} 71 | 72 | --Check if the wp plugins list exists 73 | local wp_plugins_file = nmap.fetchfile("nselib/data/wp-plugins.lst") 74 | if not wp_plugins_file then 75 | return false, "Couldn't find wp-plugins.lst (should be in nselib/data)" 76 | end 77 | 78 | local file = io.open(wp_plugins_file, "r") 79 | if not file then 80 | return false, "Couldn't find wp-plugins.lst (should be in nselib/data)" 81 | end 82 | 83 | local wp_autoroot 84 | local wp_root = stdnse.get_script_args("http-wordpress-plugins.root") 85 | local plugins_search = DEFAULT_PLUGINS_SEARCH 86 | local plugins_search_arg = stdnse.get_script_args("http-wordpress-plugins.search") 87 | local apicheck = stdnse.get_script_args("http-wordpress-plugins.apicheck") or "true" 88 | 89 | if plugins_search_arg == "all" then 90 | plugins_search = nil 91 | elseif plugins_search_arg then 92 | plugins_search = tonumber(plugins_search_arg) 93 | end 94 | 95 | stdnse.print_debug(1, "%s plugins search range: %s", SCRIPT_NAME, plugins_search or "unlimited") 96 | 97 | 98 | -- search the website root for evidences of a Wordpress path 99 | if not wp_root then 100 | local target_index = http.get(host,port, "/") 101 | 102 | if target_index.status and target_index.body then 103 | wp_autoroot = string.match(target_index.body, "http://[%w%-%.]-/([%w%-%./]-)wp%-content") 104 | if wp_autoroot then 105 | wp_autoroot = "/" .. wp_autoroot 106 | stdnse.print_debug(1, "%s WP root directory: %s", SCRIPT_NAME, wp_autoroot) 107 | else 108 | stdnse.print_debug(1, "%s WP root directory: wp_autoroot was unable to find a WP content dir (root page returns %d).", SCRIPT_NAME, target_index.status) 109 | end 110 | end 111 | end 112 | 113 | 114 | --identify the 404 115 | local status_404, result_404, body_404 = http.identify_404(host, port) 116 | if not status_404 then 117 | return stdnse.format_output(false, SCRIPT_NAME .. " unable to handle 404 pages (" .. result_404 .. ")") 118 | end 119 | 120 | 121 | --build a table of both directories to brute force and the corresponding WP plugins' name 122 | local plugin_count = 0 123 | for line in read_data_file(file) do 124 | if plugins_search and plugin_count >= plugins_search then 125 | break 126 | end 127 | 128 | local target 129 | if wp_root then 130 | -- Give user-supplied argument the priority 131 | target = wp_root .. "/wp-content/plugins/" .. line .. "/" 132 | elseif wp_autoroot then 133 | -- Maybe the script has discovered another Wordpress content directory 134 | target = wp_autoroot .. "wp-content/plugins/" .. line .. "/" 135 | else 136 | -- Default WP directory is root 137 | target = "/wp-content/plugins/" .. line .. "/" 138 | end 139 | 140 | 141 | target = string.gsub(target, "//", "/") 142 | table.insert(bfqueries, {target, line}) 143 | all = http.pipeline_add(target, nil, all, "GET") 144 | plugin_count = plugin_count + 1 145 | 146 | end 147 | 148 | -- release hell... 149 | local pipeline_returns = http.pipeline_go(host, port, all) 150 | if not pipeline_returns then 151 | stdnse.print_debug(1, "%s : got no answers from pipelined queries", SCRIPT_NAME) 152 | end 153 | 154 | for i, data in pairs(pipeline_returns) do 155 | -- if it's not a four-'o-four, it probably means that the plugin is present 156 | if http.page_exists(data, result_404, body_404, bfqueries[i][1], true) then 157 | stdnse.print_debug(1, "http-wordpress-plugins.nse: Found a plugin: %s", bfqueries[i][2]) 158 | 159 | -- now try and get the version of the plugin from readme.txt 160 | readmepath = bfqueries[i][1] .. "readme.txt" 161 | local pluginversion = nil 162 | local latestpluginapi = nil 163 | pluginfound = nil 164 | stdnse.print_debug(1, "http-wordpress-plugins.nse: Readme path: %s", readmepath) 165 | local readmecheck = http.get(host, port, readmepath) 166 | local readmepattern = 'Stable tag: ([.0-9]*)' 167 | local pluginversion = readmecheck.body:match(readmepattern) 168 | if pluginversion then 169 | if apicheck == "true" then 170 | local apiurl = "http://api.wordpress.org/plugins/info/1.0/" .. bfqueries[i][2] .. ".json" 171 | local latestpluginapi = http.get('api.wordpress.org', '80', apiurl) 172 | local latestpluginpattern = '","version":"([.0-9]*)' 173 | local latestpluginversion = latestpluginapi.body:match(latestpluginpattern) 174 | stdnse.print_debug(1, "http-wordpress-plugins.nse: latest version check : %s", latestpluginversion) 175 | if latestpluginversion then 176 | pluginversion = pluginversion .. " (latest version: " .. latestpluginversion .. ")" 177 | end 178 | end 179 | pluginfound = bfqueries[i][2] .. " " .. pluginversion 180 | else 181 | pluginfound = bfqueries[i][2] 182 | end 183 | 184 | table.insert(result, pluginfound) 185 | end 186 | end 187 | 188 | 189 | if #result > 0 then 190 | result.name = "search amongst the " .. plugin_count .. " most popular plugins" 191 | return stdnse.format_output(true, result) 192 | else 193 | return "nothing found amongst the " .. plugin_count .. " most popular plugins, use --script-args http-wordpress-plugins.search= for deeper analysis)\n" 194 | end 195 | 196 | end 197 | 198 | -------------------------------------------------------------------------------- /http-wordpress-themes.nse: -------------------------------------------------------------------------------- 1 | local coroutine = require "coroutine" 2 | local http = require "http" 3 | local io = require "io" 4 | local nmap = require "nmap" 5 | local shortport = require "shortport" 6 | local stdnse = require "stdnse" 7 | local string = require "string" 8 | local table = require "table" 9 | 10 | description = [[ 11 | Discover a list of installed WordPress themes. Brute force of the theme path 12 | /wp-content/themes/$themename/ testing for known themes. As seen in the widespread 13 | timthumb vulnerability themes installed but not activated can still be exploited. 14 | 15 | After detection of a valid theme path, the script will attempt to GET the style.css 16 | of the theme. This is a standard file in WordPress themes that contains the theme 17 | version, if found the version will be included in the output. 18 | 19 | The theme list has been created and sorted by theme popularity after crawling the 20 | top 1 million sites. Also includes themes from wordpress.org. Anything but a 404 21 | means that a given theme directory probably exists, so the theme probably also does. 22 | 23 | The available themes for Wordpress is huge and despite the efforts of Nmap to 24 | parallelize the queries, a whole search could take an hour or so. That's why 25 | the theme list is sorted by popularity and by default the script will only 26 | check the first 100 ones. Users can tweak this with an option (see below). 27 | 28 | NSE Script is a clone of the http-wordpress-plugins.nse script by Ange Gutek. 29 | ]] 30 | 31 | --- 32 | -- @args http-wordpress-themes.root If set, points to the blog root directory on the website. If not, the script will try to find a WP directory installation or fall back to root. 33 | -- @args http-wordpress-themes.search As the themes list contains about 3000 themes, this script will only search the 100 most popular ones by default. 34 | -- Use this option with a number or "all" as an argument for a more comprehensive brute force. 35 | -- 36 | -- @usage 37 | -- nmap --script=http-wordpress-themes --script-args http-wordpress-themes.root="/blog/",http-wordpress-themes.search=500 38 | -- 39 | --@output 40 | -- Interesting ports on my.woot.blog (123.123.123.123): 41 | -- PORT STATE SERVICE REASON 42 | -- 80/tcp open http syn-ack 43 | -- | http-wordpress-themes: 44 | -- | search amongst the 500 most popular themes 45 | -- | twentyfourteen 1.3 46 | -- | canvas 5.8.7 47 | -- |_ twentytwelve 1.5 48 | -- 49 | -- Created 18/10/2014 - v0.1 - created (and themes crawled) by Peter Hill 50 | 51 | author = "Peter Hill " 52 | 53 | license = "Same as Nmap--See http://nmap.org/book/man-legal.html" 54 | 55 | categories = {"discovery", "intrusive"} 56 | 57 | 58 | local DEFAULT_THEME_SEARCH = 100 59 | 60 | 61 | portrule = shortport.service("http") 62 | 63 | local function read_data_file(file) 64 | return coroutine.wrap(function() 65 | for line in file:lines() do 66 | if not line:match("^%s*#") and not line:match("^%s*$") then 67 | coroutine.yield(line) 68 | end 69 | end 70 | end) 71 | end 72 | 73 | action = function(host, port) 74 | 75 | local result = {} 76 | local all = {} 77 | local bfqueries = {} 78 | 79 | --Check if the wp themes list exists 80 | local wp_themes_file = nmap.fetchfile("nselib/data/wp-themes.lst") 81 | if not wp_themes_file then 82 | return false, "Couldn't find wp-themes.lst (should be in nselib/data)" 83 | end 84 | 85 | local file = io.open(wp_themes_file, "r") 86 | if not file then 87 | return false, "Couldn't find wp-themes.lst (should be in nselib/data)" 88 | end 89 | 90 | local wp_autoroot 91 | local wp_root = stdnse.get_script_args("http-wordpress-themes.root") 92 | local themes_search = DEFAULT_THEME_SEARCH 93 | local themes_search_arg = stdnse.get_script_args("http-wordpress-themes.search") 94 | 95 | if themes_search_arg == "all" then 96 | themes_search = nil 97 | elseif themes_search_arg then 98 | themes_search = tonumber(themes_search_arg) 99 | end 100 | 101 | stdnse.print_debug(1, "%s themes search range: %s", SCRIPT_NAME, themes_search or "unlimited") 102 | 103 | 104 | -- search the website root for evidences of a Wordpress path 105 | if not wp_root then 106 | local target_index = http.get(host,port, "/") 107 | 108 | if target_index.status and target_index.body then 109 | wp_autoroot = string.match(target_index.body, "http://[%w%-%.]-/([%w%-%./]-)wp%-content") 110 | if wp_autoroot then 111 | wp_autoroot = "/" .. wp_autoroot 112 | stdnse.print_debug(1, "%s WP root directory: %s", SCRIPT_NAME, wp_autoroot) 113 | else 114 | stdnse.print_debug(1, "%s WP root directory: wp_autoroot was unable to find a WP content dir (root page returns %d).", SCRIPT_NAME, target_index.status) 115 | end 116 | end 117 | end 118 | 119 | 120 | --identify the 404 121 | local status_404, result_404, body_404 = http.identify_404(host, port) 122 | if not status_404 then 123 | return stdnse.format_output(false, SCRIPT_NAME .. " unable to handle 404 pages (" .. result_404 .. ")") 124 | end 125 | 126 | 127 | --build a table of both directories to brute force and the corresponding WP themes' name 128 | local theme_count = 0 129 | for line in read_data_file(file) do 130 | if themes_search and theme_count >= themes_search then 131 | break 132 | end 133 | 134 | local target 135 | if wp_root then 136 | -- Give user-supplied argument the priority 137 | target = wp_root .. "/wp-content/themes/" .. line .. "/" 138 | elseif wp_autoroot then 139 | -- Maybe the script has discovered another Wordpress content directory 140 | target = wp_autoroot .. "wp-content/themes/" .. line .. "/" 141 | else 142 | -- Default WP directory is root 143 | target = "/wp-content/themes/" .. line .. "/" 144 | end 145 | 146 | 147 | target = string.gsub(target, "//", "/") 148 | table.insert(bfqueries, {target, line}) 149 | all = http.pipeline_add(target, nil, all, "GET") 150 | theme_count = theme_count + 1 151 | 152 | end 153 | 154 | -- release hell... 155 | local pipeline_returns = http.pipeline_go(host, port, all) 156 | if not pipeline_returns then 157 | stdnse.print_debug(1, "%s : got no answers from pipelined queries", SCRIPT_NAME) 158 | end 159 | 160 | for i, data in pairs(pipeline_returns) do 161 | -- if it's not a four-'o-four, it probably means that the theme is present 162 | if http.page_exists(data, result_404, body_404, bfqueries[i][1], true) then 163 | stdnse.print_debug(1, "http-wordpress-themes.nse: Found a theme: %s", bfqueries[i][2]) 164 | 165 | -- now try and get the version of the plugin from readme.txt 166 | stylecsspath = bfqueries[i][1] .. "style.css" 167 | local themeversion = nil 168 | themefound = nil 169 | stdnse.print_debug(1, "http-wordpress-plugins.nse: Style CSS path: %s", stylecsspath) 170 | local themeversioncheck = http.get(host, port, stylecsspath) 171 | local versionpattern = 'Version: ([.0-9]*)' 172 | local themeversion = themeversioncheck.body:match(versionpattern) 173 | if themeversion then 174 | themefound = bfqueries[i][2] .. " " .. themeversion 175 | else 176 | themefound = bfqueries[i][2] 177 | end 178 | 179 | table.insert(result, themefound) 180 | end 181 | end 182 | 183 | 184 | if #result > 0 then 185 | result.name = "search amongst the " .. theme_count .. " most popular themes" 186 | return stdnse.format_output(true, result) 187 | else 188 | return "nothing found amongst the " .. theme_count .. " most popular themes, use --script-args http-wordpress-themes.search= for deeper analysis)\n" 189 | end 190 | 191 | end 192 | 193 | --------------------------------------------------------------------------------