pax_global_header00006660000000000000000000000064140003254660014512gustar00rootroot0000000000000052 comment=1b3516975571c3f59d686f82a6d2dbf6f1011029 WhatWeb-0.5.5/000077500000000000000000000000001400032546600130625ustar00rootroot00000000000000WhatWeb-0.5.5/.gitignore000066400000000000000000000057331400032546600150620ustar00rootroot00000000000000.project # Created by https://www.gitignore.io/api/ruby,macos,linux,windows,eclipse ### Eclipse ### .metadata #bin/ tmp/ *.tmp *.bak *.swp *~.nib local.properties .settings/ .loadpath .recommenders # IntelliJ .idea # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # PyDev specific (Python IDE for Eclipse) *.pydevproject # CDT-specific (C/C++ Development Tooling) .cproject # Java annotation processor (APT) .factorypath # PDT-specific (PHP Development Tools) .buildpath # sbteclipse plugin .target # Tern plugin .tern-project # TeXlipse plugin .texlipse # STS (Spring Tool Suite) .springBeans # Code Recommenders .recommenders/ # Scala IDE specific (Scala & Java development for Eclipse) .cache-main .scala_dependencies .worksheet ### Eclipse Patch ### # Eclipse Core .project # JDT-specific (Eclipse Java Development Tools) .classpath ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Ruby ### *.gem *.rbc /.config /coverage/ /InstalledFiles /pkg/ /spec/reports/ /spec/examples.txt /test/tmp/ /test/version_tmp/ /tmp/ # Used by dotenv library to load environment variables. # .env ## Specific to RubyMotion: .dat* .repl_history build/ *.bridgesupport build-iPhoneOS/ build-iPhoneSimulator/ ## Specific to RubyMotion (use of CocoaPods): # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # vendor/Pods/ ## Documentation cache and generated files: /.yardoc/ /_yardoc/ /doc/ /rdoc/ ## Environment normalization: /.bundle/ /vendor/bundle /lib/bundler/man/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: Gemfile.lock .ruby-version # .ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc ### Windows ### # Windows thumbnail cache files Thumbs.db ehthumbs.db ehthumbs_vista.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # Windows shortcuts *.lnk # End of https://www.gitignore.io/api/ruby,macos,linux,windows,eclipse WhatWeb-0.5.5/.rubocop.yml000066400000000000000000000007021400032546600153330ustar00rootroot00000000000000AllCops: Exclude: - 'addons/*' - 'test/**/*' TargetRubyVersion: 2.5 Metrics/AbcSize: Enabled: false Metrics/BlockLength: Enabled: false Metrics/ClassLength: Enabled: false Metrics/LineLength: Enabled: false Metrics/MethodLength: Enabled: false Metrics/PerceivedComplexity: Enabled: false Metrics/CyclomaticComplexity: Enabled: false Style/FrozenStringLiteralComment: Enabled: false Style/SafeNavigation: Enabled: false WhatWeb-0.5.5/.simplecov000066400000000000000000000000271400032546600150630ustar00rootroot00000000000000SimpleCov.start do end WhatWeb-0.5.5/.travis.yml000066400000000000000000000001341400032546600151710ustar00rootroot00000000000000language: ruby rvm: - 2.0 - 2.1 - 2.2 - 2.3 - 2.4 script: - bundle exec rake allWhatWeb-0.5.5/CHANGELOG.md000066400000000000000000000663611400032546600147070ustar00rootroot00000000000000Version 0.5.5 - January 16, 2021 ## FIXES * #358 Fixed escape_for_sql method (@juananpe) ## NEW PLUGINS * Apache Flink (@juananpe) * Dell-OpenManage-Switch-Administrator (@themaxdavitt) * FLIR AX8 (@urbanadventurer) * Huginn (@urbanadventurer) * OpenResty (@urbanadventurer) * Telerik UI (@definity) * Umbraco (@definity / @ChadBrigance * VMware Horizon (@themaxdavitt) ## PLUGIN UPDATES * Joomla (@juananpe) * phpMyAdmin (@juananpe) * Microsoft IIS (@themaxdavitt) Version 0.5.4 - December 14, 2020 ## FIXES * #345 Fixed colour output problem with white text being invisible when users have a white terminal background (@urbanadventurer) * #347 Fixed MongoDB compatibility logging issue (@juananpe) ## NEW PLUGINS * BlockScout (@urbanadventurer) * ElasticSearch (@urbanadventurer) * Grafana (@urbanadventurer) ## PLUGIN UPDATES * Kibana (@urbanadventurer) Version 0.5.3 - October 1, 2020 This is a minor release with miscellaneous changes, seven new plugins, and two plugin updates. ## MISC * #319 MongoDB logging now uses upsert (update by default, insert if new) (@juananpe) * #314 Makefile now allows supports the PREFIX environment variable (@bfontaine) ## NEW PLUGINS * Adobe Experience Manager (AEM) (@definity) * JFrog Artifactory (@bcoles) * Matomo (@urbanadventurer) * MobileIron-MDM (@bcoles) * Slack-Workspace (@bcoles) * Wobserver (@urbanadventurer) * Zoom (@bcoles) ## PLUGIN UPDATES * Magento (@huntertl) * phpMyAdmin (@juananpe) Version 0.5.2 - June 9, 2020 This is a minor update with bug fixes, and one new plugin, PHP-Slim. ## FIXES * #299 Fixed `warning: URI.escape is obsolete` error by using the Using Addressable Gem. Thanks @weidsom (Weidsom Nascimento) * #306, #307 Improvements to Makefile. @xambroz * #304 Log level for mongodb-logger is set to "FATAL", unless WhatWeb is run with debug-mode enabled. @helsecert ## NEW PLUGINS * PHP-Slim Marcelo Gimenes (@cgimenes) Version 0.5.1 - February 25, 2020 This is a minor release with bug fixes, one new plugin, and a couple of plugin updates. ## FIXES * #275 Makefile now installs on macOS * #286 typo for --cookie-jar commandline option. * #283 path loading issue when whatweb is installed as a symlink. @abenson and @blshkv * #288 error with JSON output when the target list is empty * #289 removed empty object from end of JSON output * #293 and #294 Fix MongoDB output @LrsK ## MISC * Makefile now installs a symlink in the PATH * CHANGELOG.md and INSTALL.md files are now in MarkDown format * Removed requirement for open3 gem in Integration Tests ### NEW PLUGINS * Bootstrap - @phylu ## PLUGIN UPDATES * Kayako-SupportSuite - @urbanadventurer * ASP_NET - @urbanadventurer Version 0.5.0 - October 4, 2019 Version 0.5.0 is a major version release form @urbanadventurer and @bcoles. 👠With the help of the WhatWeb community we have reached over 1800 plugins! âš ï¸ Plugin authors should take note that this release is not backwards compatible, and we have made a migration tool to help you update your private or unreleased plugins. ## FEATURES * Added WhatWeb Logo and icons * Added IDN (International Domain Name) support * Merged 31 new plugins by Bhavin Senjaliya and 22 plugin updates from the unofficial WhatWeb gem at https://rubygems.org/gems/whatweb/ * Added 9 unit tests * Added loading cookies from a FILE with --cookie-jar=FILE in the document.cookie format * Added Migration tool to convert plugins to the v0.5 format in plugin-development/migrate-plugins-to-v0.5.rb * Refactored and modularised the codebase -- @urbanadventurer and @bcoles ## FIXES * Bug fix - Ruby 2.4 and 2.5 OpenSSL issue * Bug fix - Fixed ElasticSearch version 6+ issue * Bug fix - SQL output issue * Bug fix - In HTTP Auth parsing @rmaksimov * Fix bug causing WhatWeb to exit with no output. Thanks @mguillau42 for tracking down this race condition. * Typo fix. Thanks Jose Nazario (@paralax) * Bug fix - clean incorrent UTF-8 byte sequences when loading from file @dirtyfilthy ## MISC * Updated all plugins with an authors array to show multiple authors * Plugin list now shows plugin names and websites instead of a truncated plugin description. * Updated the usage help. Reduced the size of the short usage help. * Better error reporting in commandline options * Updated README and converted to Markdown format * Updated manpage * Plugins instance variable handling improvement. @Code0x58 * Plugin name defined within plugin as variable. @Code0x58 * Changed from functions to blocks for passive and aggressive plugins. @Code0x58 * Changed plugin locking @Code0x58 * Changed the behaviour of --grep to only output results that match the grep plugin * Changed the behaviour of --grep so regexp or text can be matched * Removed feature --follow-redirect same-domain because it relies on the out-dated TLD library for valid TLDs and SLDs * Removed requirement for 'nmap' and now use the IPAddr gem for IP address ranges. CIDR, x.x.x.x-x.x.x.x and x.x.x.x-x supported ## PLUGINS * 48 new plugins from contributors * 31 plugins updated from contributors * 100+ plugin updates by Andrew Horton (@urbanadventurer) ### NEW PLUGINS * 1n1-hosting.rb - Bhavin Senjaliya (@bhavin1223) * amazon-elastic-load-balancer.rb - Bhavin Senjaliya (@bhavin1223) * azure.rb - Bhavin Senjaliya (@bhavin1223) * bad-behaviour-anti-spam-plugin.rb - Bhavin Senjaliya (@bhavin1223) * clickmotive.rb - Bhavin Senjaliya (@bhavin1223) * cdk-connected-website.rb - Bhavin Senjaliya (@bhavin1223) * craftcms.rb - Bhavin Senjaliya (@bhavin1223) * f5-big-ip.rb - Bhavin Senjaliya (@bhavin1223) * hubspot.rb - Bhavin Senjaliya (@bhavin1223) * jw-player.rb - Bhavin Senjaliya (@bhavin1223) * laravel.rb - Bhavin Senjaliya (@bhavin1223) * mezzanine.rb - Bhavin Senjaliya (@bhavin1223) * moonfruit.rb - Bhavin Senjaliya (@bhavin1223) * netsuite.rb - Bhavin Senjaliya (@bhavin1223) * nop-commerce.rb - Bhavin Senjaliya (@bhavin1223) * pyro-cms.rb - Bhavin Senjaliya (@bhavin1223) * sailsjs.rb - Bhavin Senjaliya (@bhavin1223) * schoolwires-centricity.rb - Bhavin Senjaliya (@bhavin1223) * shopify.rb - Bhavin Senjaliya (@bhavin1223) * sitecore.rb - Bhavin Senjaliya (@bhavin1223) * square-space.rb - Bhavin Senjaliya (@bhavin1223) * tealeaf.rb - Bhavin Senjaliya (@bhavin1223) * teleflora.rb - Bhavin Senjaliya (@bhavin1223) * unbounce.rb - Bhavin Senjaliya (@bhavin1223) * website-tonight.rb - Bhavin Senjaliya (@bhavin1223) * webtrends.rb - Bhavin Senjaliya (@bhavin1223) * weebly.rb - Bhavin Senjaliya (@bhavin1223) * wix.rb - Bhavin Senjaliya (@bhavin1223) * wordfence.rb - Bhavin Senjaliya (@bhavin1223) * world-now.rb - Bhavin Senjaliya (@bhavin1223) * wpml.rb - Bhavin Senjaliya (@bhavin1223) * bigcommerce.rb - Claudio Salazar (@csalazar) * demandware.rb - Claudio Salazar (@csalazar) *ocp.rb - Claudio Salazar (@csalazar) * aspforum.rb - Mateusz Golewski (@golewski) * lithium.rb - Mateusz Golewski (@golewski) * stackexchange.rb - Mateusz Golewski (@golewski) * xenforo.rb - Mateusz Golewski (@golewski) * kinja.rb - Sigit Dewanto * trbas.rb - Sigit Dewanto * advance_digitalmg.rb - Elias Dorneles (@eliasdorneles) * newbay_media.rb - Elias Dorneles (@eliasdorneles) * gannett.rb - Eugene Amirov (@Allactaga) * yaf.rb - Eugene Amirov (@Allactaga) * blox.rb - Shuai Lin (@lins05) * mcclatchy_interactive.rb - Shuai Lin (@lins05) * tribune.rb - Shuai Lin (@lins05) * genexus.rb - Daniel Maldonado (@elcodigok) * odoo.rb - Naglis Jonaitis (@naglis) ### PLUGIN UPDATES * 3dcart.rb - Bhavin Senjaliya (@bhavin1223) * asp-net.rb - Bhavin Senjaliya (@bhavin1223) * big-commerce.rb - Bhavin Senjaliya (@bhavin1223) * blox.rb - Bhavin Senjaliya (@bhavin1223) * demandware.rb - Bhavin Senjaliya (@bhavin1223) * django.rb - Bhavin Senjaliya (@bhavin1223) * dot-cms.rb - Bhavin Senjaliya (@bhavin1223) * DotNetNuke.rb - Bhavin Senjaliya (@bhavin1223) * drupal.rb - Bhavin Senjaliya (@bhavin1223) * ektron.rb - Bhavin Senjaliya (@bhavin1223) * expression-engine.rb - Bhavin Senjaliya (@bhavin1223) * incapsula.rb - Bhavin Senjaliya (@bhavin1223) * kentico.rb - Bhavin Senjaliya (@bhavin1223) * magento.rb - Bhavin Senjaliya (@bhavin1223) * nitix-netscaler.rb - Bhavin Senjaliya (@bhavin1223) * piwik.rb - Bhavin Senjaliya (@bhavin1223) * prestashop.rb - Bhavin Senjaliya (@bhavin1223) * ruby-on-rails.rb - Bhavin Senjaliya (@bhavin1223) * symfony.rb - Bhavin Senjaliya (@bhavin1223) * blox.rb - Bhavin Senjaliya (@bhavin1223) * typo3.rb - Bhavin Senjaliya (@bhavin1223) * vbulletin.rb - Bhavin Senjaliya (@bhavin1223) * xenforo.rb - Bhavin Senjaliya (@bhavin1223) * smf.rb - Claudio Salazar (@csalazar) * vbulletin.rb - Claudio Salazar (@csalazar) * bbpress.rb - Shuai Lin (@lins05) * joomla.rb (aggressive version detection) - @anozoozian * jquery.rb - Janosch Maier (@Phylu)Janosch Maier (@Phylu) * typo3.rb - Janosch Maier (@Phylu) * wordpress.rb - @ajgon (Igor Rzegocki) - aggressive matches up to 5.0.2. * wordpress.rb - @SlivTaMere * wordpress.rb - @anozoozian Version 0.4.9 November 23rd 2017 * Added unit testing with rake @bcoles * Added Elastic Search output @SlivTaMere * Source code formatting cleanup @Code0x58 * Thread reuse and logging through a single thread @Code0x58 * Fixed max-redirection bug @Code0x58 * Fixed bug when using a proxy and HTTPS (unknown user) * Fixed timeout deprecation warning @iGeek098 * New plugins and plugin updates @guikcd @bcoles @andreas-becker * Added proxy and user-agent to logging @rdubourguais * Updated Alexa top websites lists * Updated update-alexa script * Updated IP to Country database * Updated man page * Updated Mongo DB output for Mongo 2.x Version 0.4.8-dev (Continuous release 2012 - 2017) * Added support for all ciphers - TLSv1:TLSv1.1:TLSv1.2:SSLv3:SSLv2. Thanks @milo2012 * New colour scheme for brief output * New Verbose output * --color, --colour now takes case insensitive arguments, and can be enabled under Windows * HTTP Status descriptions are now included in brief and verbose output * Added short usage help * Converted all plugins to lowercase filenames * Updated plugins. Moved 100s of patterns in passive functions to matches[] * Added --search-plugins * Fixed bug with relative :url matching * Fixed bug with JSON output parsing * Fixed bug where :search => headers[xyz] was not case insensitive * Fixed bugs for Ruby 1.9.x and Ruby 2.x. Dropped support for Ruby 1.8.x. Thanks to nil0x42 and pvdl for bug fixes * Added TLSv1 support for Ruby 1.9.x and Ruby 2.x * Updated plugin list output * Updated plugin info output * Renamed scripts in plugin-development/ that update the Alexa lists and IP Country Database * Updated get-pattern for Ruby 2.x * Added over 700 new plugins * Added aggressive version detection using md5 static file matches to several plugins * Added support for raw HTTP headers when scanning local files * Added --dorks to return google dorks for the selected plugin * Added google dorks to more than 500 plugins * Added ./addons/hunter * Added ./addons/gggooglescan * Added ./addons/country-scanner * Added SQL logging with `--log-sql` and `--log-sql-create` arguments. * Added raw header support by monkey patching the net/http library * Added context searching for plugin matches[]. Added the matches keyword, :search. Values can be "headers","headers[server]"(or any other HTTP header),"body"(default), "all" (the raw headers + body) * Added methods for aggressive plugins to send HEAD and POST requests * Added --grep, -g option. Similar usage to --custom-plugin. (Requested by Scott Bell) * Removed the spidering feature and dependence on the customised and unsupported Anemone gem * Removed the extra_urls feature * Removed dependency on em-resolv-replace * Updated whatweb.xsl * Fixed a bug causing Mongo DB logging to fail * Fixed a bug causing brief logging to not escape special characters * Fixed meta refresh redirection but with HTML entities in the URL * Redesigned and refactored much of Whatweb's code. Introduced the Target class * Targets from input files are now executed ascending order * Better support for UTF-8 encoded strings in plugins. * :status and :url are now logical AND with other matches. They cannot match in isolation unless with each other. * Updated Country plugin. Fixed IPv6 bug * Changed version from 0.4.8 to 0.4.8-dev to show development version * Plugin brief output is now sorted alphabetically by plugin name * Removed plugin example URLs Version 0.4.7 Released April 5th 2011 * Performance enhancements & bug fixes * Added -p + as a shortcut for -p +plugins-disabled * Added --quiet, -q - to not display brief logging to STDOUT * Fix Makefile - you can now install whatweb over an old version * Removed certainty from Mongo and JSON output unless certainty < 100 * Removed certainty info from verbose output unless certainty <100 * Bugfixes for error reporting * Updated some error messages * Changed default open and read timeouts to 15 and 30 seconds respectively * Updated slow plugins * Added plugins: TVersity, Ultimate-Bulletin-Board, * Moved plugins to plugins-disabled: atom_feed, meta-city, meta-contact, meta-country, meta-geography, meta-state, meta-zipcode and script * Renamed mailto plugin to email Version 0.4.6 Released March 25th 2011 * Updated ~230 plugins * Added ~600 new plugins * Added Escenic CMS plugin from Erik Inge Bolsø * Added EscenicEngine5 plugin by nikosk * Added barracuda-load-balancer, binarysec-firewall, citrix-netscaler, cloudflare, evercookie, juniper-netscreen-secure-access, juniper-load-balancer, profense-firewall, vTigerCRM, watchguard-firewall, www-authenticate plugins by Aung Khant * Moved some plugins into disabled-plugins, as they clutter output. adobe_flash.rb, footer-hash.rb, frame.rb, header-hash.rb, md5.rb, script.rb, shortcut-icon.rb, tagpattern-hash.rb * Renamed disabled-plugins/ to plugins-disabled/ * Changed $ANEMONE_SKIP_REGEX=Regexp.union line to be compatible with Ruby 1.8.6. Thanks to Michal Ambroz * Added plugin reporting support for :model=>, :firmware=>, :module=> * Added --wait SECONDS between connections. Combine with -t 1 if preferred. * Added meta-refresh redirect support. eg. . Only for non-spidering * Added {:version=>/regexp/, :offset} to remove cargo cult programming. eg. {:version=>/2, :name=>"meta generator tag" } * Replaced :probability with :certainty in my-plugins/plugin-template.rb.txt. Thanks Erik Inge Bolsø * Added support for em-resolv-replace which speeds up whatweb many times. http://github.com/mperham/em-resolv-replace * Added XML stylesheet "whatweb.xsl" for XML reports * Added reporting of version detection with matches to the Plugin Info, eg. whatweb -I * Changed whatweb -I behaviour to search plugins for keywords. eg. './whatweb -I nuke' brings up ASP-Nuke, PHPNuke, DotNetNuke, etc. * Bugfix: Changed webpage data for when working with files, not URIs. Now it passes empty hashes, etc instead of nil which caused plugins to report errors. * Added MongoDB logging. Use with --log-mongo-database, --log-mongo-host, --log-mongo-collection, --log-mongo-username, --log-mongo-password. Only database has no default. * Added JSON logging. Must have the json ruby gem installed or be using Ruby 1.9 * Added MagicTree logging. * MagicTree logging updated by Gremwell. * Added error logging. * Added Verbose logging. * Added XML header and footer to XML logs * Modified XML logging to record modules separately * Bug fix: Escaping the XML log properly for &, <, >, " * All logs are now flushed/synced * Bug fix: References to :probability instead of :certainty in some logging * Changed error message for non resolving hostnames from "undefined method `closed?' for nil:NilClass" to "Cannot resolve hostname" * Added ascii whatweb logo * Moved Plugin class into lib/plugins.rb * Added startup and shutdown for plugins * Model and Firmware results now display in dark green * Added :filepath match type * Added vulnerability matching support, this is still in the experimental phase and not supported. * Added vulnerability matching code to the awstats plugin. * Precompiled regular expressions in matches[] for speed improvement * Changed internal sleep times from 1s to 0.5s * Added --debug to raise errors found in plugins * Usage displays faster when no arguments are provided * Added version string to the help usage * Added advanced plugin template * Removed How to write whatweb plugins text file as it's deprecated by the wiki * Brief output escapes [] and all characters before SPACE with URL encoding * Added --quiet, -q to suppress Brief Output on stdout by default. Thanks to cdybedahl for this idea. * Improved OSX compatibility with a patch from matti for symlinks * Added :status for HTTP Status codes to match[]. :status has a logical AND with a :url, it can't be by itself. * Updated plugin list and plugin info output * Bug fix: Now redirects for HTTP statuses 300 through 399. Previously redirected for 301,302 and 307. * Bug fix: :account didn't have regular expression support * Changed :modules to :module, deprecated :accounts to :account * Added redirect control. options are 'never',`http-only', `meta-only', `same-site', `same-domain', 'always' * Added --max-redirects. Control the maximum number of contiguous redirects followed * Added custom headers. Can be used multiple times. Examples: --header or -H. eg. "foo:bar" or "user-agent: blinky". Specifying a default header will replace it. Specifying an empty value removes hte header, eg. "User-Agent:" * Added support for HTTP basic authentication. -u and --user * Added plugin-development/get-pattern by Aung Khant * Added to plugin-development/: wget-alexa-top-1m, wget-ip-to-country, alexa-top-1000.txt, alexa-top-100.txt, wikipedia-top-1000.txt * Added nmap-style IP address range support Version 0.4.5 Released August 17th 2010 * Added 5 plugins from Tonmoy Saikia. They are: Commonspot, TextPattern, Mediawiki, DUclassified and Mailman * Added 119 plugins from Brendan Coles. They are: Alcatel-Lucent-Omniswitch, Allinta-CMS, anyInventory, Arab-Portal, AVTech-Video-Web-Server, Barracuda-Spam-Firewall, Basilic, Biromsoft-WebCam, BlueNet-Video-Server, BM-Classifieds, Brother-Printer, BusinessSpace, BXR, Campsite, Canon-Network-Camera, Cisco-VPN-3000-Concentrator, CMSQLite, ColdFusion, coWiki, cpCommerce, CruxCMS, CruxPA, Dell-Printer, D-Link-Network-Camera, DMXReady, DT-Centrepiece, EazyCMS, eLitius, EMO-Realty-Manager, Empire-CMS, envezion~media, eSyndiCat, Evo-Cam, FestOS, Flax-Article-Manager, FluentNET, Forest-Blog, GuppY, HP-LaserJet-Printer, i-Catcher-Console, iDVR, Intellinet-IP-Camera, Interspire-Shopping-Cart, IPCop-Firewall, IQeye-Netcam, iRealty, iScripts-CyberMatch, iScripts-EasySnaps, iScripts-MultiCart, iScripts-ReserveLogic, iScripts-SocialWare, JAMM-CMS, Jamroom, Linksys-NAS, Linksys-Network-Camera, Linksys-Wireless-G-Camera, LocazoList-Classifieds, Lucky-Tech-iGuard, Mobotix-Network-Camera, MyioSoft-Ajax-Portal, My-PHP-Indexer, My-WebCamXP-Server, NetBotz-Network-Monitoring-Device, Netious-CMS, Netsnap-Web-Camera, Nukedit, Open-Blog, ORCA-Platform, ORITE-301-Camera, PageUp-People, Panasonic-Network-Camera, Parked-Domain, PHPDirector, PHPEasyData, phPhotoAlbum, Pixel-Ads-Script, Pixie, Pligg-CMS, PortalApp, Pressflow, RunCMS, sabros.us, samPHPweb, SHOUTcast-Administrator, SimpNews, SkaLinks, SmodCMS, Snap-Appliance-Server, Softbiz-Freelancers-Script, Softbiz-Online-Auctions-Script, Softbiz-Online-Classifieds, Sony-Network-Camera, Sony-Video-Network-Station, Stardot-Express, StarDot-NetCam, Star-Network, Subdreamer-CMS, Subrion-CMS, SyndeoCMS, syntaxCMS, TaskFreak, Team-Board, The-PHP-Real-Estate-Script, TomatoCMS, Toshiba-Network-Camera, Veo-Observer, VisionGS-Webcam, WebDVR, WebEye-Network-Camera, WebPress, WhiteBoard, Winamp-Web-Interface, Windows-Internet-Printing, Xerox-Printers, xGB, XHP-CMS, Zeus-Cart, Zoph, Zyxel-Vantage-Service-Gateway * Added 11 plugins from Caleb Anderson. They are: AdobeFlash, AtomFeed, CodeIgniterProfiler, DublinCore, MicrosoftODBCError, MysqlSyntaxError, OpenGraphProtocol, OpenID, OpenSearch, PasswordField, RSSFeed * Updated plugins: Aardvark-Topsites-PHP, Confluence, Open-Source-Ticket-Request-System, PHP-Link-Directory, PHP-Shell, Vulnerable-to-XSS, Zoph * Updated mailto plugin * Verbose output now shows which patterns were matched within a plugin * Fixed bug: Removed Makefile reference to 'disabled-plugins' folder * Ruby 1.9 compatability fix. requires digest/md5 instead of md5 * Ruby 1.9 compatability fix. Replace UTF8 chars in frog-cms, dotnetnuke and mno-go-search and wordpress-supercache * Fixed spelling error of verion in help information * Fixed a typo where -t is shown as the command line option for proxies * Modified command line usage and is now in 80x24 terminal format * MD5sum of body is now available as @md5sum to all plugins * :md5 is available in matches[], eg. {:name=>"must be treshna.com",:md5=>"8666257030b94d3bdb46e05945f60b42"} * tag pattern of HTML elements in body is now available as @tagpattern to all plugins * :tagpattern is available in matches[], eg. {:name=>"must be google.com",:tagpattern=>""!doctype,html,head,meta,title,/title,script,/script,style,/style, etc...."} * :url is available in plugins. eg. {:url=>"/wp-login.php", :text=>'action=lostpassword'}, this will match the url and the text passively and when scanning aggressively, it will request the specified url and check for the text. Another example, {:url=>"/readme.html", :md5=>'9ea06ab0184049bf4ea2410bf51ce402', :version=>"3.0"}, * Added --url-prefix, eg. whatweb --url-prefix www.morningstarsecurity.com/ -i ./guess-files * Added --url-suffix, eg. whatweb --url-suffix /robots.txt -i ./target-urls * Added --url-pattern, eg. whatweb --url-pattern www.example.com/%insert%/.htaccess -i ./folder-list * Added --custom-plugin to define a plugin on the command line. eg, ./whatweb --custom-plugin ":text=>'powered by abc'" -i ./targets or --custom-plugin "{:text=>'powered by abc'},{:regexp=>/meta abc/i}" -i ./targets * Plugin errors are now in red, added target name * Added --open-timeout and --read-timeout * Removed div-span plugin, replaced with HTML tag pattern hash * Added --spider-skip-extensions. Redefine the file extensions that Anemone will skip. The list is comma delimited. * Moved plugin-template.rb to my-plugins and added more example, comments, etc * Added $DEBUG = false. If set to true, it will raise errors in plugins to assist plugin development. Version 0.4.4 Released June 29th 2010 * :probability is renamed to :certainty. :certainty in plugins is no longer required, it defaults to 100 if not specified. * Fixed bug with ruby 1.8.5 when loading plugins * Added author names to plugin info, eg. whatweb -I * Added 67 plugins from Brendan Coles, bringing WhatWeb up to 163 plugins. 360-Web-Manager,ANECMS,AWStats,Aardvark-Topsites-PHP,ArGoSoft-Mail-Server,Axis-Network-Camera,BeEF,BlognPlus,Burning-Board-Lite,CGI,CGIProxy,CMScontrol,CMSimple,Confluence,DUforum,DUgallery,F3Site,File-Upload-Manager,Google-API,Google-Hack-Honeypot,IMGallery,JGS-Portal,Kloxo,Liferay,Lime-Survey,Linksys-USB-HDD,Loggix,Microsoft-Sharepoint,Open-Freeway,Open-Source-Ticket-Request-System,PG-Roomate-Finder-Solution,PHP-Fusion,PHP-Layers,PHP-Link-Directory,PHP-Shell,PHPFM,PHPraid,PhilBoard,Piwik,QNAP-NAS,Saurus-CMS,Site-Sift,TWiki,Trac,Turbo-Seek,Umbraco,VideoShareEnterprise,Virtualmin,Vulnerable-To-XSS,WWWBoard,Web-Calendar-System,Web-Data-Administrator,WoW-Raid-Manager,X7-Chat,Zen-Cart,Zikula,boastMachine,ezBOO-WebStats,jobberBase,mojoPortal,php-ping,phpFreeChat,phpMyAdmin,phpPgAdmin,phpSysInfo,phpinfo,uPortal * Added references to Security-Assessment.com * Updates to README, CHANGELOG, plugin-template.rb.txt Version 0.4.3 Released May 24th 2010 * Added GPLv2 notices * Added Makefile (Thanks Michal Ambroz ) * Added man pages (Thanks Michal Ambroz ) * Added --version * Added Invalid command line argument handling * Added @cookie variable to plugins but is not availble for recursive use * Changed output colour of page titles * Changed plugin names to use a CamelCase convention * Merged the google analytics GA and Urchin plugins * Modified MovableType plugin * Added Cookie names plugin * Added Concrete5 CMS plugin * Added CushyCMS plugin * Added FrogCMS plugin * Added ModxCMS plugin * Added TypoLight plugin * Added ExpressionEngine plugin * Fixed a bug in Tomcat plugin * New feature, my-plugins/ folder. Keep your personal plugins separate. * Usage info shows correct defaults * Fixed a bug where aggressive plugins didn't use the proxy settings * Added XML (naive) logging * Updated usage to show how to pipe HTML to /dev/stdin * Added --no-redirect option. Do not follow HTTP 3xx redirects Version 0.4.2 Released April 30th 2010 * Added header-hash plugin. Makes a hash of the first 500 characters. This is useful to identify unknown systems * Added footer-hash plugin. Makes a hash of the last 500 characters, only if the page has > 1000 characters. This is useful to identify unknown systems * Added div-span-structure plugin. Makes a hash of a signature of div and span tags. This is useful to identify unknown systems * Added MikroTik Router plugin. Recognises version * Fixed a bug where the URL had a ? suffix. This caused some types of http servers to repspond incorrectly. * Added SquirrelMail plugin. Recognises version * Added SearchFitShoppingCart plugin. Recognises version * Added RoundCube plugin. * Modified OSCommerce plugin. Recognises security warnings about file permissions and installation directory. * Changed output colour to be more readable. Plugins that create hashes are in grey * Changed output order of plugins, so plugins that create hashes come last Version 0.4.1 Released April 28th 2010 * Removed dependency on rubygems and libxslt by modifying and locally including the Anemone gem. This also simplified installation * Fixed a bug which didn't send URL parameters. eg. would send /index.php instead of /index.php?q=foo * Improved installation instructions. Henri Salo contacted me to say ruby-dev is required for Anemone * Removed UTF-8 character in formmail * Changed require 'md5' to require 'digest/md5' for compatibility with ruby 1.9 * Fixed bug in Tomcat plugin * Added SilverStripe plugin * Added DotNetNuke plugin * Added HTML5 plugin * Added PHP error plugin * Modified PHP-Nuke plugin * Changed the plugin development script, wget-list to retry only twice * Added proxy support * Default threads is now 25 * Default max recursive spidering depth is now 10 * Default max number of links to follow on a single page is now 250 Version 0.4 Released March 13th 2010 * Added HTTPS support * Improved installation instructions * Improved documentation * Better compatibility with ruby 1.9. Changed a case statement syntax, changed when 0: to when 0 then. * Removed UTF-8 characters in plugins that were causing crashes * Added php-nuke plugin, passively recognises modules * Added Fluxbb plugin, can identify versions aggressively * Added meta powered-by plugin. Matches tags like * Added powered by plugin. Matches "Powered by BobsCMS", any text following powered by * Improved plugin info listing invoked by ./whatweb -I. Shows number of examples and matches, and shows presence of passive and aggressive functions * Changed output style. Before strings are surrounded by single quotes, now all strings are surrounded by square brackets * Added OpenCMS plugin submitted by Emilio Casbas * Added TomCat plugin submitted by Louis Nyffenegger * Improved meta-generator plugin * Fixed a bug in processing a target list from a file where a trailing space would be interpreted incorrectly Version 0.3 Released November 2nd 2009 at Kiwicon III WhatWeb-0.5.5/Gemfile000066400000000000000000000015511400032546600143570ustar00rootroot00000000000000## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb ## source 'https://rubygems.org' # IP Address Ranges gem 'ipaddr' # IDN Domains gem 'addressable' # JSON logging gem 'json' # MongoDB logging - optional group :mongo do #gem 'mongo' #gem 'rchardet' end # Character set detection - optional group :rchardet do #gem 'rchardet' end # Development dependencies required for tests group :test do gem 'rake' gem 'minitest' gem 'rubocop' gem 'rdoc' gem 'bundler-audit' gem 'simplecov', require: false end # Needed for debugging WhatWeb group :development do gem 'pry', :require => false gem 'rb-readline', :require => false # needed by pry on some systems end WhatWeb-0.5.5/INSTALL.md000066400000000000000000000001551400032546600145130ustar00rootroot00000000000000# INSTALL Visit https://github.com/urbanadventurer/WhatWeb/wiki/Installation for installation instructions. WhatWeb-0.5.5/LICENSE000066400000000000000000000432541400032546600140770ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 2 of the License, or (at your option) any later version. 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. WhatWeb-0.5.5/Makefile000066400000000000000000000032021400032546600145170ustar00rootroot00000000000000NAME = whatweb PREFIX ?= /usr BINPATH = $(PREFIX)/bin LIBPATH = $(PREFIX)/share MANPATH = $(PREFIX)/share/man DOCPATH = $(PREFIX)/share/doc ifeq "$(PLATFORM)" "" PLATFORM := $(shell uname) endif ifeq "$(PLATFORM)" "Darwin" INSTALLD = PREFIX ?= /usr/local else INSTALLD = -D endif install: # upgrade/installation will leave the my-plugins folder rm -Rf $(DESTDIR)$(BINPATH)/$(NAME) $(DESTDIR)$(MANPATH)/man1/$(NAME).1 $(DESTDIR)$(MANPATH)/man1/$(NAME).1.gz $(DESTDIR)$(DOCPATH)/$(NAME) $(DESTDIR)$(LIBPATH)/$(NAME)/plugins-disabled $(DESTDIR)$(LIBPATH)/$(NAME)/plugins $(DESTDIR)$(LIBPATH)/$(NAME)/lib $(DESTDIR)$(LIBPATH)/$(NAME)/plugin-development $(DESTDIR)$(LIBPATH)/$(NAME)/addons install -d $(DESTDIR)$(MANPATH)/man1 install -p $(INSTALLD) -m 644 $(NAME).1 $(DESTDIR)$(MANPATH)/man1/$(NAME).1 gzip -f $(DESTDIR)$(MANPATH)/man1/$(NAME).1 install -d $(DESTDIR)$(LIBPATH)/$(NAME) install -d $(DESTDIR)$(DOCPATH)/$(NAME) install -d $(DESTDIR)$(BINPATH) # copy whatweb into LIBPATH/NAME/ and create a symbolic link in the BINPATH install -p $(INSTALLD) -m 755 $(NAME) $(DESTDIR)$(LIBPATH)/$(NAME) ln -s $(LIBPATH)/$(NAME)/$(NAME) $(DESTDIR)$(BINPATH)/$(NAME) cp -p -r my-plugins $(DESTDIR)$(LIBPATH)/$(NAME)/ cp -p -r plugins-disabled plugins lib plugin-development addons $(DESTDIR)$(LIBPATH)/$(NAME)/ cp -p -r CHANGELOG.md INSTALL.md LICENSE README.md whatweb.xsl $(DESTDIR)$(DOCPATH)/$(NAME)/ bundle install clean: # clean will remove your my-plugins folder. be warned rm -Rf $(DESTDIR)$(BINPATH)/$(NAME) $(DESTDIR)$(MANPATH)/man1/$(NAME).1 $(DESTDIR)$(MANPATH)/man1/$(NAME).1.gz $(DESTDIR)$(LIBPATH)/$(NAME) $(DESTDIR)$(DOCPATH)/$(NAME) WhatWeb-0.5.5/README.md000066400000000000000000000526711400032546600143540ustar00rootroot00000000000000[![License](https://img.shields.io/badge/license-GPLv2-brightgreen.svg)](https://raw.githubusercontent.com/urbanadventurer/whatweb/master/LICENSE) ![Stable Release](https://img.shields.io/badge/stable_release-0.5.5-blue.svg) ![WhatWeb Plugins](https://img.shields.io/badge/plugins-1824-brightgreen.svg) [![Repositories](https://repology.org/badge/tiny-repos/whatweb.svg)](https://repology.org/project/whatweb/versions) ![logo](https://www.morningstarsecurity.com/wp-content/uploads/2019/02/WhatWeb-Logo-800px.png "WhatWeb Logo") # WhatWeb - Next generation web scanner Developed by Andrew Horton [urbanadventurer](https://github.com/urbanadventurer/) and Brendan Coles [bcoles](https://github.com/bcoles/) Latest Release: v0.5.5. January 16, 2021 License: GPLv2 This product is subject to the terms detailed in the license agreement. For more information about WhatWeb visit: Homepage: https://www.morningstarsecurity.com/research/whatweb Wiki: https://github.com/urbanadventurer/WhatWeb/wiki/ If you have any questions, comments or concerns regarding WhatWeb, please consult the documentation prior to contacting one of the developers. Your feedback is always welcome. ## Contents * About WhatWeb * Example Usage * Usage * Logging & Output * Plugins * Aggression * Performance & Stability * Optional Dependencies * Writing Plugins * Updates & Additional Information * Release History * Credits ## About WhatWeb WhatWeb identifies websites. Its goal is to answer the question, "What is that Website?". WhatWeb recognises web technologies including content management systems (CMS), blogging platforms, statistic/analytics packages, JavaScript libraries, web servers, and embedded devices. WhatWeb has over 1800 plugins, each to recognise something different. WhatWeb also identifies version numbers, email addresses, account IDs, web framework modules, SQL errors, and more. WhatWeb can be stealthy and fast, or thorough but slow. WhatWeb supports an aggression level to control the trade off between speed and reliability. When you visit a website in your browser, the transaction includes many hints of what web technologies are powering that website. Sometimes a single webpage visit contains enough information to identify a website but when it does not, WhatWeb can interrogate the website further. The default level of aggression, called 'stealthy', is the fastest and requires only one HTTP request of a website. This is suitable for scanning public websites. More aggressive modes were developed for use in penetration tests. Most WhatWeb plugins are thorough and recognise a range of cues from subtle to obvious. For example, most WordPress websites can be identified by the meta HTML tag, e.g. '', but a minority of WordPress websites remove this identifying tag but this does not thwart WhatWeb. The WordPress WhatWeb plugin has over 15 tests, which include checking the favicon, default installation files, login pages, and checking for "/wp-content/" within relative links. ### Features * Over 1800 plugins * Control the trade off between speed/stealth and reliability * Performance tuning. Control how many websites to scan concurrently. * Multiple log formats: Brief (greppable), Verbose (human readable), XML, JSON, MagicTree, RubyObject, MongoDB, ElasticSearch, SQL. * Proxy support including TOR * Custom HTTP headers * Basic HTTP authentication * Control over webpage redirection * IP address ranges * Fuzzy matching * Result certainty awareness * Custom plugins defined on the command line * IDN (International Domain Name) support ## Example Usage Using WhatWeb to scan reddit.com. ``` $ ./whatweb reddit.com http://reddit.com [301 Moved Permanently] Country[UNITED STATES][US], HTTPServer[snooserv], IP[151.101.65.140], RedirectLocation[https://www.reddit.com/], UncommonHeaders[retry-after,x-served-by,x-cache-hits,x-timer], Via-Proxy[1.1 varnish] https://www.reddit.com/ [200 OK] Cookies[edgebucket,eu_cookie_v2,loid,rabt,rseor3,session_tracker,token], Country[UNITED STATES][US], Email[banner@2x.png,snoo-home@2x.png], Frame, HTML5, HTTPServer[snooserv], HttpOnly[token], IP[151.101.37.140], Open-Graph-Protocol[website], Script[text/javascript], Strict-Transport-Security[max-age=15552000; includeSubDomains; preload], Title[reddit: the front page of the internet], UncommonHeaders[fastly-restarts,x-served-by,x-cache-hits,x-timer], Via-Proxy[1.1 varnish], X-Frame-Options[SAMEORIGIN] ``` ## Usage ``` .$$$ $. .$$$ $. $$$$ $$. .$$$ $$$ .$$$$$$. .$$$$$$$$$$. $$$$ $$. .$$$$$$$. .$$$$$$. $ $$ $$$ $ $$ $$$ $ $$$$$$. $$$$$ $$$$$$ $ $$ $$$ $ $$ $$ $ $$$$$$. $ `$ $$$ $ `$ $$$ $ `$ $$$ $$' $ `$ `$$ $ `$ $$$ $ `$ $ `$ $$$' $. $ $$$ $. $$$$$$ $. $$$$$$ `$ $. $ :' $. $ $$$ $. $$$$ $. $$$$$. $::$ . $$$ $::$ $$$ $::$ $$$ $::$ $::$ . $$$ $::$ $::$ $$$$ $;;$ $$$ $$$ $;;$ $$$ $;;$ $$$ $;;$ $;;$ $$$ $$$ $;;$ $;;$ $$$$ $$$$$$ $$$$$ $$$$ $$$ $$$$ $$$ $$$$ $$$$$$ $$$$$ $$$$$$$$$ $$$$$$$$$' WhatWeb - Next generation web scanner version 0.5.5. Developed by Andrew Horton (urbanadventurer) and Brendan Coles (bcoles) Homepage: https://www.morningstarsecurity.com/research/whatweb Usage: whatweb [options] TARGET SELECTION: Enter URLs, hostnames, IP addresses, filenames or IP ranges in CIDR, x.x.x-x, or x.x.x.x-x.x.x.x format. --input-file=FILE, -i Read targets from a file. You can pipe hostnames or URLs directly with -i /dev/stdin. TARGET MODIFICATION: --url-prefix Add a prefix to target URLs. --url-suffix Add a suffix to target URLs. --url-pattern Insert the targets into a URL. Requires --input-file, eg. www.example.com/%insert%/robots.txt AGGRESSION: The aggression level controls the trade-off between speed/stealth and reliability. --aggression, -a=LEVEL Set the aggression level. Default: 1. Aggression levels are: 1. Stealthy Makes one HTTP request per target. Also follows redirects. 3. Aggressive If a level 1 plugin is matched, additional requests will be made. 4. Heavy Makes a lot of HTTP requests per target. Aggressive tests from all plugins are used for all URLs. HTTP OPTIONS: --user-agent, -U=AGENT Identify as AGENT instead of WhatWeb/0.5.5. --header, -H Add an HTTP header. eg "Foo:Bar". Specifying a default header will replace it. Specifying an empty value, eg. "User-Agent:" will remove the header. --follow-redirect=WHEN Control when to follow redirects. WHEN may be `never', `http-only', `meta-only', `same-site', or `always'. Default: always. --max-redirects=NUM Maximum number of contiguous redirects. Default: 10. AUTHENTICATION: --user, -u= HTTP basic authentication. --cookie, -c=COOKIES Provide cookies, e.g. 'name=value; name2=value2'. --cookiejar=FILE Read cookies from a file. PROXY: --proxy Set proxy hostname and port. Default: 8080. --proxy-user Set proxy user and password. PLUGINS: --list-plugins, -l List all plugins. --info-plugins, -I=[SEARCH] List all plugins with detailed information. Optionally search with keywords in a comma delimited list. --search-plugins=STRING Search plugins for a keyword. --plugins, -p=LIST Select plugins. LIST is a comma delimited set of selected plugins. Default is all. Each element can be a directory, file or plugin name and can optionally have a modifier, eg. + or - Examples: +/tmp/moo.rb,+/tmp/foo.rb title,md5,+./plugins-disabled/ ./plugins-disabled,-md5 -p + is a shortcut for -p +plugins-disabled. --grep, -g=STRING|REGEXP Search for STRING or a Regular Expression. Shows only the results that match. Examples: --grep "hello" --grep "/he[l]*o/" --custom-plugin=DEFINITION\tDefine a custom plugin named Custom-Plugin, --custom-plugin=DEFINITION Define a custom plugin named Custom-Plugin, Examples: ":text=>'powered by abc'" ":version=>/powered[ ]?by ab[0-9]/" ":ghdb=>'intitle:abc \"powered by abc\"'" ":md5=>'8666257030b94d3bdb46e05945f60b42'" --dorks=PLUGIN List Google dorks for the selected plugin. OUTPUT: --verbose, -v Verbose output includes plugin descriptions. Use twice for debugging. --colour,--color=WHEN control whether colour is used. WHEN may be `never', `always', or `auto'. --quiet, -q Do not display brief logging to STDOUT. --no-errors Suppress error messages. LOGGING: --log-brief=FILE Log brief, one-line output. --log-verbose=FILE Log verbose output. --log-errors=FILE Log errors. --log-xml=FILE Log XML format. --log-json=FILE Log JSON format. --log-sql=FILE Log SQL INSERT statements. --log-sql-create=FILE Create SQL database tables. --log-json-verbose=FILE Log JSON Verbose format. --log-magictree=FILE Log MagicTree XML format. --log-object=FILE Log Ruby object inspection format. --log-mongo-database Name of the MongoDB database. --log-mongo-collection Name of the MongoDB collection. Default: whatweb. --log-mongo-host MongoDB hostname or IP address. Default: 0.0.0.0. --log-mongo-username MongoDB username. Default: nil. --log-mongo-password MongoDB password. Default: nil. --log-elastic-index Name of the index to store results. Default: whatweb --log-elastic-host Host:port of the elastic http interface. Default: 127.0.0.1:9200 PERFORMANCE & STABILITY: --max-threads, -t Number of simultaneous threads. Default: 25. --open-timeout Time in seconds. Default: 15. --read-timeout Time in seconds. Default: 30. --wait=SECONDS Wait SECONDS between connections. This is useful when using a single thread. HELP & MISCELLANEOUS: --short-help Short usage help. --help, -h Complete usage help. --debug Raise errors in plugins. --version Display version information. (WhatWeb 0.5.5). EXAMPLE USAGE: * Scan example.com. ./whatweb example.com * Scan reddit.com slashdot.org with verbose plugin descriptions. ./whatweb -v reddit.com slashdot.org * An aggressive scan of wired.com detects the exact version of WordPress. ./whatweb -a 3 www.wired.com * Scan the local network quickly and suppress errors. whatweb --no-errors 192.168.0.0/24 * Scan the local network for https websites. whatweb --no-errors --url-prefix https:// 192.168.0.0/24 * Scan for crossdomain policies in the Alexa Top 1000. ./whatweb -i plugin-development/alexa-top-100.txt \ --url-suffix /crossdomain.xml -p crossdomain_xml ``` ## Logging & Output The following types of logging are supported: * --log-brief=FILE Brief, one-line, greppable format * --log-verbose=FILE Verbose * --log-xml=FILE XML format. XSL stylesheet is provided * --log-json=FILE JSON format * --log-json-verbose=FILE JSON verbose format * --log-magictree=FILE MagicTree XML format * --log-object=FILE Ruby object inspection format * --log-mongo-database Name of the MongoDB database * --log-mongo-collection Name of the MongoDB collection. Default: whatweb * --log-mongo-host MongoDB hostname or IP address. Default: 0.0.0.0 * --log-mongo-username MongoDB username. Default: nil * --log-mongo-password MongoDB password. Default: nil * --log-elastic-index Name of the index to store results. Default: whatweb * --log-elastic-host Host:port of the elastic http interface. Default: 127.0.0.1:9200 * --log-errors=FILE Log errors. This is usually printed to the screen in red. You can output to multiple logs simultaneously by specifying multiple command line logging options. Advanced users who want SQL output should read the source code to see unsupported features. ## Plugins Matches are made with: * Text strings (case sensitive) * Regular expressions * Google Hack Database queries (limited set of keywords) * MD5 hashes * URL recognition * HTML tag patterns * Custom ruby code for passive and aggressive operations To list the plugins supported: $ ./whatweb -l ### WhatWeb Plugin List Plugin Name - Description -------------------------------------------------------------------------------- 1024-CMS - 1024 is one of a few CMS's leading the way with the implementation... 360-Web-Manager - 360-Web-Manager 3COM-NBX - 3COM NBX phone system. The NBX NetSet utility is a web interface i... 3dcart - 3dcart - The 3dcart Shopping Cart Software is a complete ecommerce s... 4D - 4D web application deployment server 4images - 4images is a powerful web-based image gallery management system. Fe... ... (truncated) ### Search Plugins To view more detail about a plugin or search plugins for a keyword: $ ./whatweb -I phpBB WhatWeb Detailed Plugin List Searching for phpBB ================================================================================ Plugin: phpBB -------------------------------------------------------------------------------- Description: phpBB is a free forum Website: http://phpbb.org/ Author: Andrew Horton Version: 0.3 Features: [Yes] Pattern Matching (7) [Yes] Version detection from pattern matching [Yes] Function for passive matches [Yes] Function for aggressive matches [Yes] Google Dorks (1) Google Dorks: [1] "Powered by phpBB" ================================================================================ ### Plugin Selection All plugins are loaded by default. Plugins can be selected by directories, files or plugin names as a comma delimited list with the -p or --plugin command line option. Each list item may have a modifier: + adds to the full set, - removes from the full set and no modifier overrides the defaults. ### Examples * --plugins +plugins-disabled,-foobar * --plugins +/tmp/moo.rb * --plugins foobar (only select foobar) * -p title,md5,+./plugins-disabled/ * -p ./plugins-disabled,-md5 The --dorks command line option returns google dorks for the selected plugin. For example, --dorks wordpress returns "is proudly powered by WordPress" The --grep, -g command line option searches the target page for the selected string and returns a match in a plugin called Grep if it is found. ## Aggression WhatWeb features several levels of aggression. By default the aggression level is set to 1 (stealthy) which sends a single HTTP GET request and also follows redirects. --aggression, -a 1. Stealthy Makes one HTTP request per target. Also follows redirects. 2. Unused 3. Aggressive Can make a handful of HTTP requests per target. This triggers aggressive plugins for targets only when those plugins are identified with a level 1 request first. 4. Heavy Makes a lot of HTTP requests per target. Aggressive tests from all plugins are used for all URLs. Level 3 aggressive plugins will guess more URLs and perform actions that are potentially unsuitable without permission. WhatWeb currently does not support any intrusion/exploit level tests in plugins. ### An example of the different results between level 1 and level 3: A level 1, stealthy scan identifies that smartor.is-root.com/forum/ uses phpBB version 2: $ ./whatweb smartor.is-root.com/forum/ http://smartor.is-root.com/forum/ [200] PasswordField[password], HTTPServer[Apache/2.2.15], PoweredBy[phpBB], Apache[2.2.15], IP[88.198.177.36], phpBB[2], PHP[5.2.13], X-Powered-By[PHP/5.2.13], Cookies[phpbb2mysql_data,phpbb2mysql_sid], Title[Smartors Mods Forums - Reloaded], Country[GERMANY][DE] A level 3, aggressive scan triggers additional tests in the phpBB plugin which identifies that the website uses phpBB version 2.0.20 or higher: $ ./whatweb -p plugins/phpbb.rb -a 3 smartor.is-root.com/forum/ http://smartor.is-root.com/forum/ [200] phpBB[2,>2.0.20] Note the use of the -p argument to select only the phpBB plugin. It is advisable, but not mandatory, to select a specific plugin when attempting to fingerprint software versions in aggressive mode. This approach is far more stealthy as it will limit the number of requests. WhatWeb has no caching so if you use aggressive plugins on redirecting URLs you may fetch the same files multiple times. ## Performance & Stability WhatWeb features several options to increase performance and stability. * --max-threads, -t Number of simultaneous threads. Default: 25. * --open-timeout Time in seconds. Default: 15 * --read-timeout Time in seconds. Default: 30 * --wait=SECONDS Wait SECONDS between connections This is useful when using a single thread. The --wait and --max-threads commands can be used to assist in IDS evasion. Changing the user-agent using the -U or --user-agent command line option will avoid the Snort IDS rule for WhatWeb. If you are scanning ranges of IP addresses, it is much more efficient to use a port scanner like massscan to discover which have port 80 open before scanning with WhatWeb. Character set detection, with the Charset plugin dramatically decreases performance by requiring more CPU. This is required by JSON and MongoDB logging. ## Optional Dependencies To enable MongoDB logging install the mongo gem. gem install mongo To enable character set detection and MongoDB logging install the rchardet gem. gem install rchardet cp plugins-disabled/charset.rb my-plugins/ ## Writing Plugins Plugins are easy to write. Start by going through the plugin tutorials in the *my-plugins/* folder. * [Plugin Tutorials](https://github.com/urbanadventurer/WhatWeb/tree/master/my-plugins). An overview of the plugin tutorials is here. [plugin-tutorials.txt](https://github.com/urbanadventurer/WhatWeb/tree/master/plugin-development/plugin-tutorials.txt) After progressing through the tutorials read through the Development section of the [wiki](https://github.com/urbanadventurer/WhatWeb/wiki/). * [Sources for Plugin Writing](https://github.com/urbanadventurer/WhatWeb/wiki/Sources-for-Plugin-Writing) * [How to Develop WhatWeb Plugins (not up to date)](https://github.com/urbanadventurer/WhatWeb/wiki/How-to-develop-WhatWeb-plugins) ## Updates & Additional Information The WhatWeb development build features regular updates. * Check the development branches for unreleased updates. Browse the wiki for more documentation and advanced usage techniques. * Wiki: https://github.com/urbanadventurer/WhatWeb/wiki/ ## Release History - Version 0.5.5 Released January 16th, 2021 - Version 0.5.4 Released December 14th, 2020 - Version 0.5.3 Released October 1st, 2020 - Version 0.5.2 Released June 9th, 2020 - Version 0.5.1 Released Feburary 25th, 2020 - Version 0.5.0 Released June 9th, 2019 - Version 0.4.9 Released November 23rd, 2017 - Version 0.4.8-dev (Continuous release from 2012 to 2017) - Version 0.4.7 Released April 5th, 2011 - Version 0.4.6 Released March 25th, 2011 - Version 0.4.5 Released August 17th, 2010 - Version 0.4.4 Released June 29th, 2010 - Version 0.4.3 Released May 24th, 2010 - Version 0.4.2 Released April 30th, 2010 - Version 0.4.1 Released April 28th, 2010 - Version 0.4 Released March 14th, 2010 - Version 0.3 Released at Kiwicon III (kiwicon.org), November 2nd, 2009 ## Credits ### Developers + Andrew Horton (@urbanadventurer) + Brendan Coles (@bcoles) ### Contributors Thank you to the following people who have contributed to WhatWeb. + Emilio Casbas + Louis Nyffenegger + Patrik Wallström (@pawal) + Caleb Anderson (@dirtyfilthy) + Tonmoy Saikia + Aung Khant (@yehgdotnet) + Erik Inge Bolsø + nk@dsigned.gr + Steve Milner (@ashcrow) + Michal Ambroz + Gremwell + Sagar Prakash Junnarkar (@sagarjunnarkar) + GertBerger + Quintin Poirier + Eric Sesterhenn + dengjw (@jawa) + Pedro Worcel (@droop) + Matthieu Keller (@maggick) + Peter (2pvdl) + Napz (@RootCon) + @nilx042 + Fabian Affolter (@fabaff) + Andrew Silvernail (@buff3r) + Andre Ricardo (@andrericardo) + nikosk + Patrick Thomas (@coffeetocode) + Guillaume Delcaour (@guikcd) + Sean (@wiifm69) + Matthieu Keller (@maggick) + Raul (@raurodse) + Andrew Petro (@apetro) + Artem Taranyuk (@610) + Matti Paksula (@matti) + Tim Smith (@tas50) + Sarthak Munshi (@saru95) + @rdubourguais + @SlivTaMere + @Code0x58 + @iGeek098 + @andreas-becker + @csalazar + @golewski + @Allactaga + @lins05 + @eliasdorneles + @sigit + dewanto + @elcodigok + @SlivTaMere + @anozoozian + Bhavin Senjaliya (@bhavin1223) + Janosch Maier (@Phylu) + @rmaksimov + Naglis Jonaitis (@naglis) + Igor Rzegocki (@ajgon) + Melvil Guillaume (@mguillau42) + @LrsK + Janosch Maier (@phylu) + @abenson + @blshkv + Weidsom Nascimento (@weidsom) + Marcelo Gimenes @cgimenes + @xambroz + Baptiste Fontaine (@bfontaine) + @juananpe + @definity + @huntertl + Max Davitt (@themaxdavitt) It is difficult to keep track of all the people who have contributed to WhatWeb. If your name is missing then please let me know. WhatWeb-0.5.5/Rakefile000066400000000000000000000036621400032546600145360ustar00rootroot00000000000000## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb ## $DEBUG = false $VERBOSE = false require 'rake/testtask' require 'rubocop/rake_task' desc 'Run all tests' task :all do puts 'Running unit tests' Rake::Task['unit'].invoke puts 'Running integration tests' Rake::Task['integration'].invoke end task :default => :unit Rake::TestTask.new(:unit) do |t| t.description = 'Run unit tests' t.test_files = FileList['test/enable_coverage.rb', 'test/unit/test_*.rb'] t.verbose = true end Rake::TestTask.new(:integration) do |t| t.description = 'Run integration tests' t.test_files = FileList['test/integration.rb'] end desc 'Run bundle-audit' task :bundle_audit do Rake::Task['bundle_audit:update'].invoke Rake::Task['bundle_audit:check'].invoke end desc 'Generate documentation to doc/rdocs/index.html' task :rdoc do Rake::Task['rdoc:rerdoc'].invoke end RuboCop::RakeTask.new ############################################################ # rdoc ############################################################ namespace :rdoc do require 'rdoc/task' desc 'Generate documentation to doc/rdocs/index.html' Rake::RDocTask.new do |rd| rd.title = 'WhatWeb' rd.rdoc_dir = 'doc/rdocs' rd.main = 'README.md' rd.rdoc_files.include( 'whatweb', 'lib/**/*\.rb') rd.options << '--line-numbers' rd.options << '--all' end end ############################################################ # bundle-audit ############################################################ namespace :bundle_audit do require 'bundler/audit/cli' desc 'Update bundle-audit database' task :update do Bundler::Audit::CLI.new.update end desc 'Check gems for vulns using bundle-audit' task :check do Bundler::Audit::CLI.new.check end end WhatWeb-0.5.5/addons/000077500000000000000000000000001400032546600143325ustar00rootroot00000000000000WhatWeb-0.5.5/addons/country-scanner000077500000000000000000000065401400032546600174170ustar00rootroot00000000000000#!/bin/bash # country scanner v1.1 # Illustrative example of how to write scripts using whatweb and nmap. # Automatically discover samples of web servers and test whatweb # GRAY="\033[1;30m" RED="\033[0;31m" LIGHT_RED="\033[1;31m" GREEN="\033[0;32m" LIGHT_GRAY="\033[0;37m" CYAN="\033[0;36m" LIGHT_CYAN="\033[1;36m" DARK_GREY="\033[1;30m" BROWN="\033[0;33m" WHITE="\033[1;37m" BLUE="\033[0;34m" LIGHT_BLUE="\033[1;34m" NO_COLOUR="\033[0m" SUBTITLE=$DARK_GREY echo -e " $LIGHT_RED __ ____ ____ __ __ _____/ |________ ___.__.$RED _/ ___\ / \| | \/ \ __\_ __ \\\\\ | | \ \___( // ) | / | \ | | | \/ \___ | \_____>\____/|____/|___| /__| |__| / ____| \/ \/ $LIGHT_BLUE ______ ____ _____ ____ ____ ____ _______ $BLUE / ___/_/ ___\ \__ \ / \ / \_/ __ \\\\\_ __ \\ \___ \ \ \___ / \\\\\ \_| | \ | \ ___/ | | \/ /______> \___ >(____ /|___| /___| /\_____>|__| \/ \/ \/ \/ $NO_COLOUR Country Scanner - Sample the web around the world Version v1.1 by urbanadventurer " N=1000 SCANBLOCK=10000 AGENT="Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; eSobiSubscriber 2.0.4.16; .NET CLR 3.0.30618)" usage(){ echo "Usage: $0 " echo -e "-c\tCountry" echo -e "-l\tList countries" echo -e "-h\tHelp" echo -e "-n\tNumber of whatweb log entries. Default: $N" echo } checkdependencies(){ for c in `echo "nmap geoipgen"`; do if [ -z "`which $c`" ]; then echo "$c not found. Aborting" echo "You need to install geoipgen to generate country IP lists" echo "Visit https://www.morningstarsecurity.com/research/geoipgen" exit 1 fi done } checkdependencies while getopts "hln:c:" OPTION do case $OPTION in h) usage;exit 1;; n) N="$OPTARG";; l) geoipgen -l;exit 1;; c) CC="$OPTARG";; ?) usage; exit ;; esac done if [ -z "$CC" ]; then usage; exit 1 fi # check CC is sane # find whatweb WHATWEB=`dirname "$0"`"/../whatweb" FOUND=0 f=`tempfile -d . --prefix scan- --suffix -geo` g=`tempfile -d . --prefix scan- --suffix -nmp` h=`tempfile -d . --prefix scan- --suffix "-$CC"` echo "Scan of $CC started at `date`" echo "--------------------------------------------------" echo while [ "$FOUND" -le "$N" ]; do echo "[*] Generating $SCANBLOCK IP addresses in $CC" echo " geoipgen -n \"$SCANBLOCK\" \"$CC\" > \"$f\"" geoipgen -n "$SCANBLOCK" "$CC" > "$f" echo echo "[*] Port scanning for web servers" echo " nmap --open -PN -n -p 80 -i \"$f\" -oG \"$g\" --max-retries 1 --max-rtt-timeout 30s --min-hostgroup 4096 --host-timeout 30s > /dev/null 2>/dev/null" nmap --open -PN -n -p 80 -i "$f" -oG "$g" --max-rtt-timeout 10s --min-hostgroup 4096 --host-timeout 10s > /dev/null 2>/dev/null echo fgrep open "$g" | cut -d ' ' -f 2 > "$f" #rm -f "$g" echo "[*] Found "`wc -l "$f" |cut -d ' ' -f 1`" IPs with TCP port 80 open" echo echo "[*] Scanning with WhatWeb. Logging to $h" # -p + echo " $WHATWEB --no-errors -U \"$AGENT\" -t 500 --read-timeout 30 --log-brief \"$h\" -i \"$f\"" $WHATWEB --no-errors -U "$AGENT" -t 50 --log-brief "$h" -i "$f" echo FOUND=`wc -l "$h" | cut -d ' ' -f 1` #rm -f "$f" echo "[*] Found $FOUND web servers so far" echo done echo "[*] Finished at `date`" rm -f "$f" "$g" WhatWeb-0.5.5/addons/verify-nikto000077500000000000000000000025551400032546600167150ustar00rootroot00000000000000#!/bin/bash # Verify Nikto logs # Copyright 2014, Andrew Horton VERSION=0.1b if [ -z "$1" ]; then echo "Usage: $0 " echo "Version: $VERSION" echo "Verifies Nikto logs using WhatWeb to separate false positives / true positives" exit 1 fi fname="$1" if [ ! -f "$fname" ]; then echo "Cannot read $fname" exit 1 fi tfile=`tempfile --prefix=verify-nikto-` tlog=`tempfile --prefix=whatweb-log-` grep -A 999999 "Start Time:" "$fname" | egrep -o "^+.*(/[^:]+)" | cut -d/ -f 2-|cut -d: -f 1| sed 's/^/\//g' > "$tfile" hostname=`egrep "+ Target (Host|Hostname): " "$fname" | cut -d : -f2 | tr -d ' '` port=`grep "+ Target Port: " "$fname"| cut -d : -f2 | tr -d ' '` if grep -q 'SSL Info' "$fname"; then prefix='https://' else prefix='http://' fi echo "------------------------------------------------------------------------------------------------" echo "Checking Nikto Log: $fname" echo "Running whatweb on $prefix$hostname:$port with `wc -l $tfile` url paths" whatweb --log-brief "$tlog" -p+ -i $tfile --url-prefix "$prefix$hostname:$port" > /dev/null 2>/dev/null echo echo "Different HTML Tag Hashes" echo "------------------------------" egrep -o "Tag-Hash[^ ]+" $tlog|sort -u echo "Plugin analysis" echo -e "Count\tPlugin" echo "------------------------------" egrep -o "[^ ]+\[" "$tlog" |sort|uniq -c | sort -rn echo -e "WhatWeb log: $tlog\nURL path list: $tfile\n" WhatWeb-0.5.5/icons/000077500000000000000000000000001400032546600141755ustar00rootroot00000000000000WhatWeb-0.5.5/icons/whatweb_icon256x256.png000066400000000000000000001155061400032546600202460ustar00rootroot00000000000000‰PNG  IHDR\r¨f pHYs.#.#x¥?v IDATxÚì}€$e™öóU§éɳ³9çØDò‚ ‚œ T<ÑÓó¼;/˜ÿ3ܧb¸Ó fTLxŠŠ HÉaÙ6ç¼³³3&÷tªïß﫪®ª®žéž´3ãôvOÇ ßû¼Ï›ƶQ±µ··GB¡Ð”l6[ìGæÑíá0m™LæzüRo ·‚Þ›®ªª:>vÆGÇ&ÆNÁˆò‰±X¬,™L®ŒF£SS©TŒž~­¢\J9Ž/)ñÚ—[÷]t3‹ü\7Ý6ØH›W ÃØoš&þi“öãXyyyvìjÀØÖÇ„HtttL§‡óIàÀ¯'›A·Š„½’oeeeü>ð©Ïð=içRGÝHˆ‹_0B ‰8ùƬƒÀ¨Ãzn+}çqúÎGhŸ:âñøÓ‰Dâ4±†Ö±+;c[°f/'žÕÝÝ}!ýy ûbªsèqiSƒ…›•Ü~Ì7¾aVðÆàÀThŸ;hŸöÑq=•N§ÙÔØJ€Ð0våÇàOr#Aà, áx-ýù:˜…t›ËôÞÖê$(ÃJÐû $øêÆ @ÇίµÒ߯ÐkÏHRYY9Æ Æ`h¶Ó§OGÊÊÊ.¢øôËãñx{ìíݘоπ‡ |ÞiãHƒÁ=Ä NŒ¥1”­­­­’¨è;èá»HÐ×r"N2™TÚ~LèÏë‚en5Ðß÷ÿÁ–±34Eóg°Cnï%­³ÄŽÑiûáÇ  º»»;èïŸ0€àÙ±34}üÙ´¨þ޾B g&{óYë ý躞Ýôøº¦_󌀳m۶͘5kÖ{Ø«OT) ½¥7&ü£ ,Çm™?&fpÁÞ1øÓ¶ó/ ‡Ã¢Û˜æwŸ3‡C5tñ~*«ŸéÒ|ñxœ…GMÓüºîw×ÔÔœ€?-;>-„’ ¿›hĪPÖ‚oЮÅBÀénàGÛ%¦W×/HdÍ1(8»³ ‰l£ëþáýû÷ÿvÅŠr Fñvâĉheeåôð_È.Ç‚?œÃy¶vgÁOd$î'Âú¥^9.Á=9>°Và#çIÔ–ɬ€³éJ»¹‰U‚ý3b«®®Þ7£Së_D¨ÿºà—Ú™{ÃVð­ ±õûR©Ôúûß»ºº¾9qâÄ쌂íÔ©Stq?FùïÊ9{o¸Ó}ü0Ý^m–¤ñÿÛAÆ$©?¾xùG@Ëaà‚÷ ® èF]™‰]¼ïþœP¾‘ê¸Û„T˜bF3„`f›ì,$åÀMJþ±ªªjÓŒ|­ÿEÒúçÚE:ÃÝÁÇt‹Äÿ¼ |o[-ݤõ[öÏ~ ØôCøvýh%pùÇ€ ß1 Âu‹Ï],0¯Š Œ¡½ñ0°å$ðŸ/J\>¸a!ƒöq Õq¸Ó‹i½´ÑSÿ–H$þs„ É1AÛáÇqãÆý#¡ù?‡B¡ þáªõm_”ÿTBâ[¯½làh; ugðÒw¾´蘵äÀ5_êp¯1Ì'ñ¹K´ƒ0+‡·ƒ=lh£ç§;?)p¼=¢Âo&øø¹¬¬ÏXŽN1¤l€£ì 6ð Ý>X]]½g FÀÖÚÚ:´þWév5ÛùÃUëÛZ-jH$H[ß·øÊK[N‘à'[€Í÷Oÿ¡B>©Ú™ÀÕŸ–_d²¤9³xïjO\ 1®lhµh)ÇÎl§¡C⟟~°Àˆ{¼H ªÄ¸«>ˆ÷_³ï[‘Äx2s’Clðƾt:}˜ÖÑ'É$øÞ ÓmëÖ­˜={ö„Ü_&áŸ>\C{öf;Ÿv8$ñÅ ‚± ¡YÀL€€g|‡î?R[[{z †ÑvòäIê²/’à¿.’Á^þáªõyá‡Hë¿|ø2 þ}ûˆf²Š>L’ðä—€=öð !”W× «­E¯~ਞy>ðÆ/Òýy ªb&>FOýÕÖ¸ÚAÈW}¨ÏŽ`Ññ·$9ªü×&$ʈåÿðCÀ®ßxök«>ƒ7_q>¶>Uã³ †Ê¼qw'"V¹@àmd¼:ÃòϦ s7iþK‡k™®;ƒo_‹Ä×6÷l£-IZ»ñe¢ú_¶ÜG” ]ð;-^w¾ëvL:ßÿþñØÃD•}¡Ì¸ÄËÚâ㈠ü?b聯è72i\½øÂ%ô=㺇8ƒÐÈô|ð¡?JcÒ¤IÿÇßg×êá÷;ø9H¶î‹Ï4°g¿Øüà©ÿNï/øµ¦ãÖ[߆‹ÿìB<öøñ“Ÿþm§NšÄ4å·-jÒè+î¼ã6üöw¿Çïû0ÙRƒ€_£†è³ÿ=pé?ÑÎU(ßÀÊÉÀ—.£§f ¼ƒÐöu'¼b=$´!’ο<ü)Úÿ–~§EQUU‰èƒØ»ï~ú㟢»ãTðÕÍ^ûIÔ]p#þf½÷­H£ž¾¢{Í öÑ:|Ö¶1Ä­¥¥e:iþO»c8Q~;u—•°!$64_Ù ð«QdyòÍîIð¿Dš¯°g¿¢n n¼ñ¼áõ¯Ã 6âžþÍ ÄbQíá·)±;á?•BM]>ðþ¿DGG;¾þÍ»µI S@6 weî¥Àµ_¦®$)I "*ñç\'T"N²ŸBxïSÏßãÊûiý¶ÃÀC^½7`Ò¢L!=t”PQ:î›n¸«W¯Â/y?ž~üqz¡@>β€Ëþ眳[ŸÄçšttøs°9¯AnPšÉdö’Yð‡Æ`ðœ}¿"XÕÕÕ5Œ†cæ|»ÏH|•düG;#èHÐ ‡žÐ‚߃g?R5o¾îMxÓµWcûÎݸûû?±ƒD}iQ!òI¼Ì¿|YÍáo|Ëu8oýZ|ý[wcïŽÝ„F´ú“ùºr"ðz²½×¾CÓ”tW̾@ذ|‚@7™,²HàóoÏ/äOðy`öóÐàÔØÑlhcïþÄ>NîÎÿ’p\ ¿›Ýð¯KS1•sfã¶w¼U!Ë÷¾wŽîßìàãºèÃçß——ã#ëRX1ahÌ;TH÷i‚÷|k Vø×Öÿ.!íªD"1lßvð5vJ|g3ðõÍ4¶ÓB>a{öAB<²[ÄjqͯÁ-7^ÃGð]ü½ªÌ/öq à ^B&Á²UËñîw½?ò8~û ™=Ÿj'HåïÀ:²¡_ÿ/D=&*A›T|Ž@àæÅ:ƒ°§ÅnýdÆX™0ÑJ?s™÷ ‚I“ßìž |ò‹dšø´7 |¬ZÓþž6ynfñg½7\-^Úø ~ö³Ÿ!Ýq2øýó^Klà3¨_z>þvu ïY‘%³`ð#¶_€ÎKš˜À?>|økË–-€þnmmm7º~›Nl5§ôžmáwÓÜvZð÷î”øÊÆöœ!Á=½x®Ï~¤ —¾î Üvë- løö÷~Œ­¯n%MÒ¾’ ß–J£ª¦ xß„I2 ¾‡.6 ² þm-Л¾ L!“ ™Áª©DXÞ,$î9~|8ñj÷îݘ3{V¯XŠííUøÇÇ%ž;Ê'‡@áÄVíèÛûXþ—±O"^«yAÇ‘'©ôw2…²ªJÜ|Û°rÅ2üìç¿Ä üC°Y#4»à¯éö·8gþ8|œÌ‚kæÊA7 làÀw™ ÔÖÖv@ß…ÿ&þïÒ‰­å»¶s‹Ãg¿? ñå—žk EÜΞý»¿Y8gŸýk.ÂDgyÉß}ϽØðÒ&} "‘`¡î©À_"€%]&Lœ÷Í׿œ»ßøÎ=Ø»—Žô|ât>#Yu3©ý¨b¢µ¸I›4²€Ö?~ü8^zé%4ž8¡ÎEš(ö+±õ¸¿}%Z,Ü´¯þTÛûyç‚ÃÕZó QÈ<Çí:¦©€`öü9¸íí7!A¬çž{~„ãv 0Z0•C†Ÿ‚X|n\hâÃçšX6~pC†îòâd2y/ýý®ªªªÎ1ÁÂoÛù¼/‡*Ñ}à`²³ØJ‹ý™¯ÎÙ•cùºóqÇmoG5i°üä><ùÌóºJ‡aüš¢Hèe‡‰,%Myçm·àáÇžÀï{’ž¦ßì:•Kµåmá•À_üRíϺi ‘K»0†ý.[¶lÁŽ;Të´XX 1[ƒûë±%9S›-]MÀ#Ÿ6Ü/جíËëÉÞ ñ½euãƒK/y þüê+ð†MøÅÏÿ™Žæ€óO¦ÈÚÛTJñøi3ðU)ܹJ¨h³Á\UVUá°1L…ÿfþïœmá·í|Öt;Hn¾¶Qâ§»Ãèê V·‡=ûdç+P2.bX°rn'ÁŸ6e~öËñ»GŸ€äqVìàŠ~‹CjŸøðßà‘?>ƒGyB‡ ¹Ÿ€½-ºŠà>UDäÓåd%{7nä±hd©hg݆ô\ü:±-fµFǃOkÊßðJþ~D‰’WðáÂZ¿'KGxžL‘r:¾·Þø&,œ7G™Ÿ~\×ø·ú¹À뜖¾çLÊâ£WÏцƒY#aÀOi-ß9A@ GáF£ß%ªül ¿»#‡³¾ý*ð­-!4·qÎþ“$ø_¶mõjÇôEËñÎÛÞ… HH|¿þÝcHw%´G¼OÇ#‹×Òú'Ñk¯»ãëëp÷w¬c•-‡Àm²cÛÛÛñòË/“ ±W¥XsÉr‹YŽûkð\j¡JÚI`øìÿŽXG›ou…´à—Õ Çì¾þllÌ_8·Þ|=Ú;:pÏîAÓ¡ùïåé¼TNU Q±øòkuÄ`6ÎL <Ì„ÿþïœMá‡%ø­´®ºSâ«› ì?MûÑ´‚4¾Üz_Ï~ç,Á­·ÞŠukVà!Ò¶_úúÐÕÚ¦5~¼¼P$K‡oûµpápDɉÒÀFÍ‘úûËÂL÷ H3‹=»÷*á§kBZ?¤ºõlKOÃ/çâxf¼¦E§ö¿û°ýþ€“X®Csìå—ƒ(^*G°÷À|ês_Åë.ý3|䣯ïù~ÿ›_8´ºL‡”N”bO ”••ÝÜÝÝ- †9& ÿÝgCøm/€gI#p‰îoq±Îq`“îÆ31܉PYežŽ×ÏÀ[nº×\u96¾ºøègÑÔШ5~EÅÙ7¼”ƒ4º~C ¤?M˜Cf¬E³&šO6ãw¿{mt¼¡0Éy3õøy×yØ—™¦=üLóþ¼v|ú«ÙÑWMvD¼&÷ûý:Øž’ ìs)¼‰ngéX::»I¥½ãè‡ ùIúZ¥Õq9;4—ÄÊ›¸%‘H$-ÈüÉ@kkë\îàCB?¤Âo;øµ=)ñŸ‰òïŽ ÕfyöŸþªÊÙç5?ÿÂ×àÅ^Ì´ªI¸þ†›p=ÙÖ;÷À‡>óÞX ~ye/¶»(bѰ¨?³ÊMr9žÁHg$®0ì]Jëg¤Àº—áî5H˜q}¢ØÁ÷›¿>“ÿy¶ó« $œd 8EôlµuµèêNéH‹4}`uXÉŒê¡w„1D"‘ÛÒéôfúóËÒ@(È:x¨2üì%ÈŒñH›Ä7_¾³5ŒÓlôï¹_e­´ÐíesΚµhkmA&“†ˆÃëßxÞvó 8ÖØ„O~áëØ³k¿çUT¬@;êb?ï~¯ë<”——ãÔ™VË _¸Û5'Êp‹²Æl5Ùúçasj¶FIƒÎÆ ßÕE<]'óMŒê)ÚÞW¦„TþYÄþô•…úÝ:€ÖŽ.k_¤—¸’­fTC= „ê\ÀFJïsÄ|»ª««¿þ' $üÌžMÚikž¡Òüìàk!YÿÕ©<û›O0F{ì³À¾?¨÷Lš2EÙlŒÖK—.Å~ök\øú·àŽÛß®D7îúßbË–šR—— ¯COû¸Äêt°€‚ST ýX…ˆ~óÅÔ<Û?“­ÑNNæùýÿ#sèž`G_í,mWCú²kDÿ…¹ð^[]öÎD>”Y €Ó’ Ø&•®2˜h »iM„”ßW v<þ'$üÌÃþƒ„ÿN0lá·{í'³÷ïîz)„WÂÍ/c«ß@MóÄË’K– ¥µ©T¯}í¥¤Ôk#;ÿ¿¿ùmLœ8w}ý‡xþ…W¬B÷²Â‹|PG–" J+k€å(¸s&öf&c{zºÖà,üû×±ýU®ìäãF#ŠR›(©ŒP™PQjººó¥›MÅRˆéÐÊŸP®s†zãõn±o‘<\¶&Ÿ ôÙŠŠŠ÷vUŸmçó퉣ÚÁ÷ða¢€M{‰Òþ72›~ˆã©vg“€ì÷ºÚZL!í?cö<¼é†›qÕ—cÃ–Ýøî½¿Áý>ƒtg—KðåPž°œf Lî„„¡Ï¯b=ù$R2¢Y ‡Êžúðøçt—÷}\²[7[S~‡í½ø$| Ù Röc=ÐþWU–ãxó™|3¤¬:çÀ¥•_zàöy‘œ—J¥î#¸Œ@àȨ+Ñç¯Ùæì’ÞpH ©#‹O<-ð³]RÜLsÃ7¬œ}o7ŽÑži'6RF;™¹{›:ñØs/ã¡'_ÄÉ3mX·r -ªÓ8qê ‰¤^¤†( zÍéïÅ9&ú!$Ò~£€k€9$Ø´ ø-7è (k/«%©™Gš´2ç\ƒåÐëÈ*÷CXù­¿¯ÜN Ð1j€p¡aÿC‚Ìæ6º>p¿|¹?ܾî¨óܸ+ØN®š‚ìø98!c¨lOªPØý–,˜…W¶íEeEõuÕ˜?{Ré Nµ¶£Ã)œÓ/{Z1ÈÂAB/à f…йü¯þŒ„ÿÃ„Ò ¾Ÿ£ÏÔÌ Í?ËÓU¿?ˆ],£pŸgÃ@]«Ž®dþï¹À¸¸nWv¶'ªÚ @ u1bî4ü/£Høëéî§d÷Œ¬~¶ƒåÈ‘#ª\µ©±§ÓãaDèyÎ;örþ¢d*Ë”¶¬Y^ éÖ¬_‰Ç_؂ζNlÞu"F =niíT‚&²Œ…5M&›A¢[;{[OtÛ•Äa@?`¾»xøŸuJ¯_hØÑ7~!§ñ9§§—â‹è«@÷Ɉ“™Ö‘HåKv¤Üùúº2 ‡A2<¯[fÄ‘HäãÄVWWß=ª€„Ÿ çÏÅãñÕíô³k°Yð¹T•ÿèÑ£ê•çÉZËÌz…ŸëàIóëû:v­¨$eV‚²8RËÁÎðdèk::“–“Mh»!ú&·åü*¸ºÂ¹D ѹó›ìðýëOå¶r20a‘•Îk–H÷-ý c–zBé0Ëéºup€Ÿà; JDhb:œ4‡GAŒÕY)FëõßH^^*§`xˆîw²ð´àóÖÔÔ¤Ÿ«Ö˜ú³Ðsëfá^¶'Üýw¼Î¢²ÒI‡__‹­{ÓCiµäê¶Kûûz5üe/ô_ô"}Xª!‹– àßÏ veœÑÇÝ„Ù˯ÀQöò5ëÑæŒˆéëT:ÀºYy¿ô±ú2ÙÃ¥Î園J¥¾I pÅPøè@^CÔæ ÕÄÓÝ™†ËS·mÛ†('ž-ø^q“……Dzs6¤A¶cJÙÿ:aDa¿ÌbŸ+ņî ß–ˆE#H¦³céqcGÙä¥GŸ\9î1XJ¤@Òe «—ÒÌtüiÏÑ\²Ö¸2 »Íå<Ÿî¿HO½wD€e÷ÿXå@´ï¶…Ÿ+Ô¸1—©2°ø?·D$2dóšžo»8ÆÕq—èj;ŒÐÙ_ e;ÓñU”ÇÑÅÝ>9° &§ï7›(ÿbêS”€ÎƒÇI*ý›ÉáÜý‚@:|.ì¸_Ý0X™E£ÑwÒ:ÿmuuõý#Hø Ø-//?g ì~Öú øãÿ¨:ÔðßA‚ kÇ— „Nl\žEA/A´O†• È÷F(8ò ¢üÓ]Ž¾Ð‚ÐI"Ѩðú|ج)«tÎkUÃvcEgšf”Ý’í S`ψ:ˆëÅÞÍz Ñ‘›Tpêp$RLMk¦¼($x†·ç~¿5t`ôbB ¶pˆP. ÐJ ö:ƈêÏ8WçÉK»,ÎpÑðdB…H¨Þ\Ó`J_²“ )<€áºÙþ€x<>/‘H|ÖüMé´ÃÓH;žÝ.ï·»®–¾ˆdÓ>íöšÅjçá ÅjgD.$èÞ*&èú}>7gËÑ7P癃¡0YÌýŒ“ÏêvÊÉÃg•÷Vhðºt:};=õ­õÿ4g7 EžñöaÖÛ›5(ܳ p¶w} lg«X²ðV4 Èê‹ß£Wí\¤ˆ àyf'>I¶Ü&û5¢UÄÃö›×B¦ÀgH®þ8¦À`œ†ë¸Þy°Ê{s“hJ=›Ò›ø­cB}7ú+Ð¥¦÷y…­z‹ñø¿Ôv„Šb£Ø)¢ãª|Ì3´)f÷{”røƒ;ÏËÊÊ&“)}—e ¤†-ПÄgIðÕçÏÒ>JB~8Ë(äEÒíÒwaèØ…Ì#ð²3ýý ñž… +{¿dr0\YA'Y 5ƒ¥"rv*ûÂØFßD¦À[è©[ ¡ÿ‡x<¾h0›{ô™ž]W`¨BUƒfïË`´±Ò€…}¬"o…¹ÎÃp4kJ@Mш\T1ÄEý+ÕüßIÉ>5Uƒœð‡ÿŠûd ¸XN±@`ƒÂ׊Z•ý9\®´`¡€<€b#!ÃÐÞÏ?V¨ÊGéäxï¹µ÷€, Àg Ì$6ðI«ŸàðÚ!n}û¯¡P¨8F‘ƒ IDATŠ«ã†ã¯Ç3J@uàé`ç;Œ¸úÓa{+í“vBá0Â*QJ—4„¨¾ÈùF ª(+C;—k[©Ý  i©gû?Y`g Òµ|«išß §6 'ð¶X,vé`Uù Ø r_q^­=0)îyÏ£¬*'X…Bƒ ‡B8Âa• Ü•Ì8þ€‚&ÀHzXŒ.²œžD °Õ°Tz#¬1måÄþƒ”îkÂ!Øo°ÿÀe°|ô]:ììºì\NŠá ¿Üð’gâlƒúÖ@‡4;SbáõÑ( „Ššf:x ˆƒ®¼,ªY\È•íÃ-ÌSƒ[×0,€à‘HäüL&3 Áð ;þ– UWßR·nnsÕ—:sÖ¬ýípá Q×A6„ÌeŠ BÅ…ôzòœõ£`qÀp^À•øTÈ0rÀ2ø"þ )ßG‰45 X`Æ}Í þ¦júrѹϽ=ÓÎSÓÛ8ë‚q¶ÌC³2éz!€ÙìãëÍD°¡èvm¡üòo1²…ßq_i‡à\2¹ßMþÛYÒþF£õC©ýK öy·*'éFysÅðŒž„Ã*†N€¬#1«™É †#¢Ðëî1h£t³MÚ>HJøGÄ9Xuþ7µã£ }³„°¸2Ât/Ñ`‡×„Ì9úŒP¾ë›«äŠ5ú£{cJf¹(‰@rT€§Çb±z’¿OПw)ð‡Hˆ=’ŒDC }î½Çaÿøê‚ÂÄM0§  &)!›mL€„Kð”B"XSqá–sͽòQ ™‘Dh . v¯K¾þÜr”˜6 ¼…äñkÄ^Jpiÿ«8Eq¨}þ=YBbÛþ\%‡`1Sζ3ÌÇdN@1À©À¶©ÔVFèh#àf‹ñÒ« ë%ºN#EŸãÈs8Ä£·*1aÒTWWÕŽ ³£ÍMM˜5{6Ö{ :‡±™§0³ršN4B÷…´#<'©dÐuþ!ö„|­yè©Ð !•5%‹Vxü=¤¤¿Af@ó€}ñÒþïê°ß€ïoš4Y¦ˆŒEž„Slf\Ÿ÷'à ÛïU8J´#ÈN×5òVésÖ—GÁºD ÑùƒZà™Öw6ëYÀ–ÉšhmkS7eaTMÄÌÅ«qéîÄ.\Žë^w!zå(šŒ‰xþ»Ÿ€ÓÖ ©Š^T¯è;r²Ò”Ê ¨šÆF|hÕ÷–`4mVvà bï¢??7 àšX,6þl÷ù+5¨ ½àxáçeõù6žϽñuwÉ"4ô ”üô©¥ï;C«=ÍV:ÛÄ~¿ÆÎßêà EmÄÆMÇ”y˱ôœµXµb¦M™„$iÔty™Z[q¦µ ç¬^‰–3-8vôNÇÊ‘QåÈ™žU¯,  _™Uf W@ÂT‹§ÓùH"3Ò«òYwæ)\V^@ç€}!U}ÏppüñA–b&f¤Këô¶ïì¥v&âô^;L¦hÁ²·åÎâý"ºO•o& b›!OõòÃ5SP?g –¬Z‡µç¬ÂÌ3Tœýpsö?ƒ—£©¥ëLFeYOílÂÖ½GxêIÔ××£¶¶õ3æ"1¹gÚ:ÐÑFÖ”…‡'õç<Ó!'’TÆËrÃ@í­»ÍùÖ$FÝÆ²AÌg>Ý¿žþ¼o Àå¤ýW v·Ÿb‘®´÷»ü½ù&-Óá¿lúì8ðú“NkÛø ^¬Ñ[Oê'?¹gÉÖ"w"¤ê jf-Åâ•k±š~Μ9ˆÆÊp¢¥ û[°{Ë1ÔW–aÙÌ:¼qÍ ¬˜5»ZñÉ{7`_cvkA*--å¹|C÷xDÇ%QF̪vº;;1¾"ÅÓjqÍšE˜3± µ1-Ì1M¥ÁíõP aï‰v$»³ˆÑc%ãF@Ù±òRJ«{}Dg’ <ªMwk0®álEº–gºGgÛ V[¿‰þü—€£Ñhåˆ ý9ëÄÏk`Í­Àâ«uNø}ïö>–{óÔU\KB$}æôÙØqánÕe¦!ÙƒÍ!9¢ô8¹h;î°¯r5¢“ç`ÎÒ5X¶j-–.[‚ñãÆ«Ys['šºÒHÉ,žRƒ«VN¬ •¨.¨,ìœËßÊx®¿-ð8.Õ~,Fw&À‰*\)Ã=bS­ xÚ ˆªÄsf‘í° ‚ÎiÊʳëÖ,3€YÁÛ‰½‰Ì€®>}A9}Ñ[Ù¹0µ–´æúÈL¨lÇÁÌ<‘\ŒNf¬ ^ñÒÿÉ+­†1¡¡³÷=ŒBxRl%·#k=œ"oÞ£Ól‹®f,GhÂLÌX´ ËV¯Ç²å+0uêdulm])tu'‘ʤ1R^¿bfÔW ¢L7NÉfMEë»SÙÞ¿µ²[]žÚ?(0ÈuúÓû fO áUê3í€2z„Ý&q´mì{‰D" Hn/£?èXE_´¸ÏûðbF ç‡1ÅhÁãÉ¥zîxÐëý¿P—ÿ¢ˆ‘Øjï §S­2I:µ†g;žcò½zè]—±n:¦/\‰E«ÎÅÊ•«0sæ DËÊ$Í"P60c\æNš€©µqÄc!k±He¿'‰¸}<¥¾Š²Úà陸d„нx};φDkWFû8€Y@&‘ó°¿$ZF&áYƨHÌwå†BÉí ý€ë "#5ï_3ž+°!5 “;ü mõEHf¬×qnº·.ÀýæŽBg¦yhÜFš~ŸväÉlqŸ­š‚‰s—aÉ9çbùª5˜;w6ª*«¬f¸$1“kÊ0k|9&V—);]ù Í`/U胨§.*½´ œc Üy œîÌ ®"¦¯)gu&N[>€v ±Zœ"@D•‘Ñ.3àuÄâëÈ 8S2Ыè nNÎ?^°¥6á9±]2Šé9º†«ÒޏZªsíÿÔÕÚNt/ÚÁNØáßÚôcàÀŽ?>u³–`áÊuX±šw 1¡~™Ý)=Qñ´š§ÖÅQ_C,l¨…­4¼dº;°Ÿ³ý™Äˆ„C®L?Î8 H9öO ŒÒjâô-‰,âÑ01ýrH{cÀí$0¨š†N@ΘPŽQIXN¢Ñè4‚‹éÏ_÷…œOÊöp¢ÿÜ©Ôý1ˆìÍNűl=-­¦-÷y5=Ùþ¨šdåþ€}*óGizU™uϼÆ-…¿'RÊió1oÅz¬^{.–-[Š©S&‘­USm¢DuÇUDH裨) Ê/µvç-ðþ’xÑŸÃR{Ï¡?¾ñãîtLjKï9ьǶTN@Çðg]2H@¡ÌLƒ }ôE(«¨@"Zå€®Ó „XûŸ$2°pœ‚¦ÑÆ„¾àïè+\&õ2’Ë~moþ ÉyP .*Ûö+ï›æ¼& ãæ!>sÞü†Ë°úœ•˜3s:êªÊ¥—ÊBÕe!TÇ茅é¢SjOYI5þ: þø(•cßx>î®dû›Øz¬ Ïí=M[±ëx;Nµ%õ9‰†{vúM€Þ¿ÏDðXÿ…2¥T¥Ï•¨(‹!Q>λ":›tO‰¦.+z1J7Ë ¸„Øüd2‹‹þ¿~¸ ©sYûŸ0k°=3];Ûv?¬“eì­šžŸ¼<Ÿþç‰o¥õÛ<h[]1¸üˆÔGפqX4w*Ö̬RT>1” Jèb¾g÷Ÿ‹þ\)f, ,,ðl:´&Ò8@jò•íx~_ ^¦ûýMhçZÚ¬©ù?»ÑmÍo ¬¶|' = T Þ¢Å_ •8Kç&‘!†TÃIŽú¸7ŸªîEDÀ:GW0ˆ1sý™ñ—П÷–ÂÖ7úßÛâõ ƒTgâ¥Ôíüi©—}ƒTæü©ÖZ='p¨€Î*óT²©Ö=hëHà—ÏÂíLÂÄê¨vÖ1šgå€Ñy[¹òLƒn (§:ÒØK¾‘4û ZHðÛpøt7R‰´Uckh¡g{? tàqäE"Ïgjt·‚¸ýôI:– ÕqìŽ÷¾ÖzÔÙŸÃíÕ›Ka^Y*\A0,éÿêÕ«±téR=ì‚Vñ‹/¾ˆææfÕöÊïüë0ËæjçÇý>ã:r…¹çè÷À"R/Þíã«míÓ :L™Ë•ï·íwvØÑ?ýÕÉ¢±5…íÇ;±á@+^:Ø‚-Ç:ȦOÂLZÀo |4RÔ1—‘­Â8ÁM9úk”vžó;ŸîÊ`rMLg€º7n]fÍz<Ҧͨі `\Y( ÿúÀeÃ5ù§®®Îi7ÝØØˆ3gΨÇþ-„,¶¦gãD–@DhíŸq%€O=G§ÿrj¨m‹N¼’®G5Æ+× ÒÃTcŽçWûo¿ëç8=ö ÷¶†ÒîmJËï<Ñ…¦–¤î’ÉYÀ‹–VkÑA?&Õ¢ Awå vi5wck³'Tåãu´íøåž…\úLûBاʂ= ‘FÙÆÑRŽÓÈ Ç‹aÜökÉp¥ÿ¬õÙ¶áÛ¶mÛÂ…Ãá<›=M íÙÔB½õ·üÒ»r^¡+ÿ| %Šr6÷››|e´éá\‰˜•»®[xqç)‹ëY¥&b í¡Ú«ÝÖÁ¡SÝØ|T üK‡Ú±¯© ­—ý®Ówû9Ê ñ±ÐÛ‚{:>€Á.­& khË`ý\üò:°€}œ «ÁÑvb‰dÝ0Q£Sþ•²ŒF£¢»»û²bà¼X,V5œsÿy¿N:…ÇÐþ&ve¦`of²¶U·’ð·7¸hiþéë4415µ¦³DÊ|úoáCM<\¸ï¨OÃsr'¿hîÆ¦#íxñP6î$H ‹¨°ªƒ0àµßK±+z2ixÚ7Rás©ÍÕ‹ÀÏ¥+؞褜èÈ ¶‚>^©Í€n«šÃ€|+«Ãéî,è¡®lt†íÍRæl|ŠÌ€lop…Æ”·šØ®]»T7TÕúÉ·x÷Ÿ!íoòáñ…ßôCï—,¼’@µ¥Dw¨° Ù+ýÂf®òÆ!—…à5 Kàùž§ßœhOaω6‘†ßp°›:•Ã.ÝmZÀfiøH¸'$í¿Iïñ¹äÖߦk2¯éKØâ´ÜþøJ™…@´v›êð+ªkÐÉf€ýá®@íÄÆ/@G* ÂK,÷'˘ÝÓm_A „¨%»|8ÿð~±ÝàÀ<ÇŸû£ÙzlNÏÔ¡¿Ç]óX,¸,§‰ Ú¢ƒØâËɈó…m­)´7]™ã´Š»R&·¤°ƒ~Qù‡:°½± Ç[Si3g(о̹{â¾N[²<‹ \Ë`;6ùºd|Íc8#Ï9î¾Øû%T†Øç¡ó#jªâèä.ÏÎ>ÓùjmPûÁEd b4Çá”—“¼¤G m U½išÃö@XûïÞ½[5›Ì·ýéÚÓ~*¹)ɼŽlÒ¿íý’y—BpüŸ§äªy6PÌÐŽ2_P[X€$,{¾¹=Ï>Ü@ö{öLâTG j‡°ž÷?bô"D–ð‹ž„GôÍ—ÉÞv\Ú­·œTà€D Û4CT~—5q¦+‹©µeh¨šî}ë.,'ìž3õ›¥ÌùŸU½ùV’ý_v¶öt ÄR°oß¾@ÛŸµÿq³Ósõb;øpàI¯£méŸë8µ¢£C‚Zù¡*g„µWp¥‘clÛ# ÿ?7åbx,l1—Öô˜j²wâ×¢îÆÎ¾¢¥ʹÀšÝš¢ÌN>{N ìÁ<`ÒÓØžÅ¬qex©jªöZŸ‘zRPO–Ühجšõ,?îVa~¸t¸Úÿ¶öß³g:;; h©lÿ„I)djíïîï7óÝöËú+Õ7¡*gX§O =½„ú±Hnuªkãn.½ûâ¿vîì<|Ì@ºûìiÙ›@ð¸æÞÉðÓ‚¯œ}ANÀ‚àné%¼Ÿ•=œwÿ{}à´·Eböør=é‰Ç Û}¸“’ 8ØJ¦/«òFõfu ^BÍuïÇò€›Ðݺájÿó>±àïÝ»WA^G•ö[’óµ¶<¶Øö¯Ð­¸Ö_$·„Jr, ¬  È÷å.' c·‹PN0t20œ9Òþù~ÒÅ2„ú¼¾á+a*j˜*G´Òñ_„Caµw&€›FÖçzïSh‡C{Xå÷ˆh*°=õ”À.ƒæîßµ›«Äëp¬Cª¢ 9ÑÑí´fV“|¯Ú8grn©å¶CµóìðÛ¿?]˶<çŸmû?MڿìÐGµá;¹F¼qâ×ý›®‘0yŽ%ßÐ~ ¿ìÆt ë9  sóy¨‡ê,í´5éÓðyEx˜D~:¡ðLËõ2‹Ú3Èö"€’¬c—º"÷ÌJ¦ÔàÁ—ãç— ûQ—“µxþ`¯ŽKèï:ÓX©S´_)>º)lÒÊýí ´èÇ`y_ë/ßKRäò ú§#e*¶c˽cä ¨ýÍ×èÚoøœu.& õóR °q¬4pØrìIu/ WG ÿñͿӮ|/î\G$ÔS)¥©ûöBXXÛYúi³àÕä°é†£ <}€8þƒ$båÁ°bà°à”*‹WÎýÎ@+xq!'àTZlb8¶þf!8xð  ÿÒþO&“ö':È”ï…oݹИ°Xçý+í_lKª€ž}~á곉`äkoÃÊ  §Ê£BÝš;¥Z îøvž`ç9øD0ð¸BŒ9– rlÀ·{=ú0ò¾3jžf¦>›¼¹=‰ŸlJ…&¬oHÄ—\U,Ô­'ÅLÜõHôÀkâ!ܶ®/4œAºÖ'á*`(¦±õdŽX戠Åîgpµ/mí~¸t¸öýç®DœøSHû7dëð§ýòškÚ™_õ·úmÜùGŦârú£[ó¹žSè®M(«¦õQ#¸y’ךּ(±•¾åiú ;¾ Ÿà¼Oxn½ù¤Ì…ýà IV$`Ç)b˜éÑÙ 4æ qbD¸oÒüá˜ÿÏÚÿСC8yòd^èOYžtK.C{þ9þùoxµÝ`ÑUº×žj{–ü9a J6ry nZnÐn·¼ V×"ÀÓ/}ôßþ˜½‹ö  °“TåØß¯÷_XÙ<DåÛ»·Xµ~>îç‰D/˜ÜdÆÄ‰Ž,ÔÔÌñÎàNËV$`+pš°xbyéå£h‹D"!’ó¥°‚ì«Áí¿ç 7 kž9oûÜçüÈNÀKéyª,šè}/ÿÄû%<  b<-†d༾kïJì5!PxP-g/»%Ïy4³C§óv¢€­ŸgçÃ'øþÇ›’,½Ð3TJïXq{±ÝéȯN^ºÛ¸”Y+t {Og1¿>Œ'j¦+r”w]æ1•ÐÜe*˜R©KF¹#`ß°’^(n ÀÖþ\ýuªç£ÝË‘2£Zû¿ð-ºÀ®Ô®úùÀòë=Ú¿gJÛ÷.À%¹ ÅÉm°šidLØZËÌyé-圧Ùs¯çg3 UWfDVÏÎsgCú| ²ÀùÞŠ\À.ÎC[×0XÝ–Ýf‡ÄΓ&Þº*¢;.Õ4ZÀ£Ó8 j22iÛOMýŽ@KÉ/õÀ€ðpjb—ýîܹSÝûXûïÌLÁæô¬œíÿŠOû¯}!üDmk÷v\C9¶»PBŒ-PÖo¥¤¥YM_ø W•'äÒäöcíͥ×W×ûxL·ÃB Øû¢'„s9]Ñ ) õ4 8ñ<+<Ô&Q(«©A7›ƒ[-Ìèù ³.P»¿±‘îVŽþŒ@+Í?Vœe`Ç%/=ÊM?„Ëó/mí“FH<Ú½ YI0Ïy³Ïþ·P”ÎÑþÄr–]af·ä·7›?p‘‰<@&+´&vøÏtis%dyvÂåÌ“~[ÅЯªlȰÝ7ªgTè’ #Ï„ûýë Ü—ÌÌO,’*7ajmû'кßáJã"1eIl9)À­#!ŒêÍb³íF¡N¶?ß8îÏ;ìÖþR%œšØ”š™:ëÛ}½z¯7‹ý¼;%Ûw2íhá8¶úìu*eÊ`u"%‚;!g7[éʨNPÞ6»‰œÐ›ÌÙß¡|-î‹î¾·§Å!o¾“óy£wÍÌ×ÈV-üñˆTñù,z f›¢¿ô¿Àç$íÐKÇ3,ñ¾ÈY¬õ±,ƒ†`níèÎd@˜ÏI5|lÉÞ?Ï%ä˜CCƒÛöWj-Ì6³ $—Ó4ô¥bÏÒÕáqÒ2‰eoâ:SŒe®$NŠ^¹mÑËãì ³>+ùÇ-àº}¸Ýõ7?ÓÑZÌoâ*Dé^Œ°ý¤ÄÊ)°OˆS—Súµ“»õøµŠ hí΂G,¨ýî…&*ÀÓמ% ^CÚÞY‰¶)rðàAÁÙyÚŸ¦æâHv’^Ì|·ýÚÍ[¥Xw›ãXû³š”¶Ðè߃”n+ ô³«åüMü­çâýBKÒòÒ›¦«ðÅ%Hà’á+’Na€… •‡@À!KxOHЯ®u«/'ráï lGzHÄ’¢Ÿ+)èÉÄÎÓo\D׿n díL i»~¹£YWVM†LeñB#ðæ…bT'Åb±p2™¼ˆîfé"™’UCÝÀt¾lIº/w”=ǵþGŽ>í¯ô÷3Ž'S‹µÀó‚âI?‰3¹Ë?nÂK¯á °4 U4/Ô/ oþ»¨…$(Ò…FNˆPäÌöY„ oÌYõÑ!kõæ× :vÎß"MË~w9Ù´°å¾þ’^[øMÛqhÅ™¿gÕg‹>t‘/\Òí`ûß@.´èOŠÄt9pþÙË+®R,åzïªmÂøú:4sV¨ lßB¼ÿµß/—èJî&¡šL*Áª±€SèïØPk~Kã'@A…ýZZZ<™Zû›<äSœäN¿Ìþ™¾mù¥Çö/½ÑqÓM%„ÐM7„öL )‚†xL‚BÅð2€Z 8ºB¸Á#î#ÐyÄâÉe IDAT?–fa§¦!ÔTŸœ,¤Óœ™J[2’RŠ_iS “[![ƒúÏ3º ß1ÀÕŠÌB#aïåˆK ™MÑ×…ûÜX—0r@È5ĦÙö.Åë4˜ÑM8ðJ‘¹Rd“%“á)@K&FÐ#ËŒG+AË ÔŒ`³F‰¾éZÉÖA3fd…ŠЗ9§'àX­'ÒÂÑ*Öf0šÉ¸¿™ýF¨p ×‘ký d™î5“žœ²Ê›È‘î‰ãT°ñW’fÅ`E¸/Þ'Ã8;‘-9”náç­½½];vÌSóÏbŶÿÖôtq§)-W¬1ýÏÓá™ëd|özB•¬ ý·2U¥Tߥ‹è¥´¥ÜmÛZ ²˜n82„tؽMøõÙoòæí¸cýy ì·”.Á !#õ0é 1lŸ†Ë™(|<†~ŸËr Ó}ŸÊÔ¿ÈdºH #ÖùóJ·oŒ)ݦÒ2nìp£’~ØýÌ Œ´ÙØLö.÷ù{Ø3<»/%[ .žM:eLÎ i·FåqF 'ñˆ¸LOnZ4ºó,y6Ⱥ¼ƒgÅ#év²ðsÛ"Ëþw¸w’´ Òþ¼€X@ÎØù ×¹±öfÈh¥”É.O­Pc;§sÙ1RšŽ€ê÷’þö]x){"vZž°\lÒ §m‡ó¼ ¬! +ËÎmÿ¨ø,°îv¥XvŸÊ\X?Y÷_Å BŠ–[ºà³áý·îÝ¡?É£¾š››…7ï_Oùy2µ„t7  ×Ýlþ?Ï÷F—\‰p=q¼LŠ¥LÉšEÉE.ANHéé‘+ò|õÒv—Ù®—W 'Ø‚²2 ׄ[—yk'Þ):o„¥JIµ¾%dïðåð [ø­Ô]ÃB öN2ȳÑM+4'µ+@êÞ¢!õûÙpLcM‘.oé~ÃöäºLn‚Èòʶíý>ÐÖk…C|n77K¬™hâ¡Yç#ëö0ènS³ ’¤ýŸ"3à‚©p‡žÕÍySñõ·*Q7¬X4-6²4·ÐÏkå”»Ie†ý:Þ¹YËØPŸg½É,Ü7{Q ×cû»´ÉëzÝù6¶ÃÙ-Aê†ÛiYçEí/ÝÂÂ{1¤ðeð¹„_CHÏIÔ÷ÖÚÏ9~>ë ðÇ9D—IèqbôýÅÜÂ|ëoè›öih§Šš¶¡f.È@ú;ì»EìÇFþkî[ÈJ/ õr3œýEÞÁi+ÆyÕiÔÌ¢µ_íêÌÍANï·´€Çdó[,Ž2…=6f¨½ÿð$ÿ°ÝâÄ ÷¾H]ñ'ðTjѱˆeÌÒÎoú‘Ûù‡ðÌ5ˆÎ=OwB8ñb;ìfõ¡—.J©íiù"µRÛõRúZi¸êøýÙ¶¶½.=áá¼w‚4O¨rîk3Ô®Ûûo¸Hm\ôc‡"ípkÿƒ™ñØÆS~x¥²Ç›«þv=äùÒòu· TVÉR¸bžþZùI¯$ÃqŸkÛ_æRõ!s½ø„ð•«zÃ|6âx˜”í` Ä£¾EîxI…QQ/«®ý42 Û©™$ãó.f&Éi½žõŠžL§¤iBX‚¯…ßê"&\äÀýÓ–OÀBa }FsÈvóÙ¬}í‘h¸(·f@°LÀ2mr‹.r^òìÈÂBSÚèùî ÷ÿ“X;%ŒÍs/†¹õ×¹7ì8ïÝê-]O6,§œ£9+ðlø:=^¾cÚßÐÐ 8ѪüÓQlZÉO'!%£´êI…‘¦T¶²5§…j¦¡bÕµó¤Ãc{zb(.´z¶eÒŸ“.'Ÿn*ó#vìÛR€¹€_NÛKŸãÐp¢ L$1™‰KexÒrõCÝüå)†0D¹ßEÎ>d¸„_Xqw ·øíý4 ë`¬{¾Ï§R‡þt×áÞŰ…èdV;7™;o‰/‰Uæ+ (µìeãlÀWš®^AŒcÚ=6üèíTæÑqt¼¿?ܾ¼“rôl½Í•’­££Ceÿ¹3ÿXûÊÔcKf†pzÔq+çWêùlåê뫟I˜ÍÙá–pWذP÷"lXŽjCÛˆÊl¶Ìhv•œ$g9©C9§¶öX¾2ësÎ>!rç\Ž>aiCÃöÊß Û7 ˆl4[¾>Û~ñª.CY(†ãIÏÙî–kè~(ößaûozÓç"Ä€! õ:·ÂÊpïká›íÓðÞ„•¯`EW­$ ]Ô(½¡<çšHk1è&мq˜r× >–Á”¹‹aL\”ÛG!|³Oï{öYm–cv”3€ØP:¥•++\m¯ØûoÓigÐÐ.½œš¥{ý©X]˜m¿Ò‰öº*«FÕïGÏaxˆ`¾j$Ñ¥1<Wɰôס@}W,Áfvi…Í(t»}”{®æWÂoÑvà!ãvžiÉ’Ê ÜBÖÍ0òs‹ì˹La~ÎTÂþ€´ÊŒ‰"{@ˆ`Òí´÷T`ä8ZèæµozòZöBé‹QÀ²„&OânË+§×àÐü‹âZÍCµÀ“£éxš:„J º}…Ê$€ÏZ‚×Ôì¡ê¨¢òB¸=‘*$Á?œØ¿²G˩լ ÛÕ‚`¦j[å ýMZˆ²©K`½©´²KáhlïÍÒäî÷X·¥Q-o3·\è¾#a—g9÷Y[ó KKKK-¨^iz¯ “DVDMé&‰¢ DI¢¯Y˜+Í–g?dÅ×C6³±ž³µ}Øýغqÿý°•€£nÖç¹tšAS ­ì£æµ}V]–™Ü{Œ^´µûÚø®UÐÍfAAçÙyÍb.ü÷+MÀºI&b /óî$Ï P5 ±»jnà(ð©ý•2ˆ ÀvúiÕïV¾ì‹&ágêÏ=æºußÍ‘ Ûé–My;þЖ<ú*Z~-î°š3¬{}ty7kênð"Ñæ‚ '9 &ZÓæDŽn‡üE—B¡è¦ý[aûÒ4”«ý”à‡Ø4Qæ‰~~Þ?ñ<¤„X °zÝGøÝ¿«Kz¬nü[Qz£}‹……'#“´Ì‰þPoáJ!(àWôƒeO·>ízyÍZQÒr›U™AýüÕ0j\á@nÒ¼[‡é}ϰÐ:jÍ€pYYÙ?„YãÃöóÐ~™§þrù¯Ûû¯;ÖVg{…ôx'÷–MãÔƒŸE(Çø+ÿžL錕ý#©«„è‘ÒÚƒw¤6'}Þeß<=‰¶õ[¼nLW,ŸßräKáè¡§} Ç©æ¹Jª%  O¨(@È>›m„‚Lk/ù}YÝAš*Ω}+]ÉV„Ã1Åj¤@A7PojA8Çe1+;Èë`å\¥NKH*ÎDÈ]3Þ¿æ.R.i‹gMÆñ¹"ùòÏ­p`'™Oép ™Il…–'¾H$jisé‡ûîᦀ£-Òuäõ7<‰&–“ÑHáúN¦ôÒ1øó›š3å' 0ûµM˜ß'ø½9êŸ{¿ðÒ_ßqF,f¡M Û$ 3™NZÉMÁ”Úݬ·›;JZ®§JQä÷yL¸^®ï½ÜüfN–ÖÑŽ“마-»Ê»£{~o ÑGð«½VFàès° çffC¡ÐªÁêd ¼2<è³µµÕ§“­ ™šùfæ^⸲Ø„‹ ÿ¿(GýŸÝ¦gÐ ²¥#»ys}&Y¹l×û…*ÆUÉ2ª.ÁÒB!‰ð¾¸k}¿OBËÚÛ)K°)rÈñ x€;µÖ–66]l†X‡èB•µy©¹¥†Ý쀔œ]>m)¥c6 ¥$Ù‘ˆ¬ô2¤8Nö¹c%™‹.DGE=LÊÛá :3pü•vþL°÷ · uµ¼<7„­¸üà]ƒœ÷?äbj™röŸMÿmñ²=㼨RpU+óÊÊ+%Ó ýÑß‘aSúso"—(ÝYÑ;š•ô…®–Nóa%ùGüÉ#¾#Š:å,;Œfk|ÇñfUá»Ïš ¤´Ìn.šév| ÃRŠdé†vð©ð +SB4…“£× ˆ¸¿m¾qâOkR #-1­’S•õ‰d6t¤]_“Åsg¢qÞkØ|¿Å‹[€dLZÊ]¥ÐÚ)TNÀÒzR™™QÅøH¢a n„C:Q œ#Àñð$bö Îtš\?6Öz‚gjm/g_¦ºpèûïG8GÝ9×*P(*æÔ—ØË©å¶{:°(¬\®"HÀ—*ëi­çÝ×(ý娉ù`À7æÏõUêoSÛÿšSe`°¿#ðÔˆÒ@UlºÀˆ¿WvåD$¦€YùŒÁ9õö¡iv"€gèÖ™N;ÄáQE$GÒ˜ÖóLÀ󦇱aå59àmǺ:Ð:í÷íÞ³* Íá(؆ĿéóhZÆT4W $óOxœn%ðZx¾ó´ïx¡h™öóÞDÐsœcÛŠF?o®b •°<ùV$A†ôÍñ#xo~€DTy÷¥Q9a¸¯¹/ ê2Eq΄*ž¤¦¾&à¸ÁÀpí›tw<Ôù:¨Àw…U‹¹Ùç#E‡ÕH"·÷fíÞ‘Êu`ç­#%=þ.ÚÜ ,«Ë`ü²KTm†³zΚR;µ©QªJ¨1}ŠØ‚îvúC‚¶r”¾áÕ}Ñ™ö“Øû;й÷yx^ÖYP¨ÉΰëÍùTÊÍ>†KP]ô_Z]½¤í!·+í<@|ÎùÒŸèqº³]Ï ¢æf¢½`ØL”yç¶Zxß´Ò¡GáË·²3#{Ì84ŠËè3¬ãáŸoIêÂ=d§7uj @Àê$úž–¹™,¼¿‡ÛuŸü%óf"¾è—{œ¾p÷#:+›Ñ˜ürU©-G! V@€oAØ™@áp˜'þ¯#YÎL“¥ýtËqìúŸ[ žE$qyÍ…7eØwv‰po>mgkFae Qå²ôz4äN^‚-ìnƒÕVu}F}16•KOµ7­vê$´]Ïì¢+£‡kD wU3¯/·ˆ;'Âu+VÓÛјdV¢¡]b×)-ü­I«õ¡¨9¬îE]iáü^ØJ‚ßNfÀùÓjÖ\çý O²GÊÑIü@sBæ1´‘¸¹Z‚i_ÛP·s§Ïí3ÞáEl©S‡±ïž ßaåãK" ¸…œ ¾þßr™€ÒÊ2”NP…õh‡Ø1êÞLîÍIn²é1 º~=÷{65yh6‘s•<%sûhhÐ õrs²í¸¾bvÖñ ú4 ßx1n@Ê@V ë°GÁWçLCÍ’Ç}g$vžÒ¥½ÉÚ>ÐA@_Кòþ.ƒ-GÕd0yùEˆŒ›‘û w æÄ î?Aû°ŸÆcŠÝÈϬrΪkãGŒ Ø2óJL‹Ù:lE¶õ¸êSê…ê†åfå$Ø6¼•_à[ý͹„°/—+`æœ+vþ9¥rœ…Ò”™”)"ùñrôO·÷Ç®ü3œÎ ®j@ëÜÖ¯¿ÖÄ"Ô­¾ñ‰sTb± *lgwF*›ž§öp8ŽAÀ”îÊ«â»íI©rls‹£±K"A¿±rî$T­x½K{tòÀ} VrÙ·k'ãHXݶwWUU5ð£ŽÁb…L OÏ¿bá´1d#G˜b€€«2<Ñ +Þéb?ÞÌ‚Œ9áRžf j¤ž•K í¹õ/¹û‘gX¸I…̺Ûeº¥º¼ã(*£ÎÝNV5“+8dS6©—½U3W Ýq5K/%s:F{™ Náõ°‡ž3öØÆ7]é %¹â­8,óÜš˜À¸xèàº/ 8²ÄÓç¾§Ÿú^NÑl¿¸øƒDAÊU(•‹ƒ8xå £a »ý B.G€žòßÁÚ¦^ð”×OÓ«È(®÷]¢bEG­]Mt¿Ò=”ÂNøÒî3 r²mWàÁÉQòNïÊ‹ªÐûe:ÄS åì÷|n„«M¸Ä]µ®¯]|‘j@bò2™Íkâkòwðt^úæ§êºã} ñ€Â¢ðŒÕ” Znç±+ÀùŒÝ]ÐÌ!¼~ ¯E®ù/Óv™M;Å;( „…'|dXÏ;,EôŽ–nmßÔÅ#»ôã\Sɘª¾ëXÛ+Ó©¿'‰Eœiõ ÙûlVL®È-'v4nh^;+ƒÇÖ]‹Æß~™N«òt×ïéËöõsÙ@ÃAzú}À+­Ù#p×­t:ýˆ ©î`2[ð‹~§¦ŒÕ;e2©žÅ+Áœ+߃EoùbÕˆz&‹Z”¤ÜKz«(À2¤‹J ‘+öÉûÓ)½No~éK—r¦ÉÜ÷À~¡k¿ÍŒ0„Kh‹BAT†sûÒ nšÁÌâðœÕhßoùIåïû°üzÕO‘uÑw6WÍ™&@*•ÊеzÕfê~€íQÀPÒ8'—Wj×™¼<€²ÚIX~Ë'0÷uïT3ò¤•ãî÷º‹"†ûö&IJ—·yš‡ô 9i¡—qK0mîÄË¢t\)+R—pͦ…ȯֳـ™ìrÞmä1¶€í?”vDÀí»J›jÞ$ƒ>Øö¼†Æ—KL®Ô‰;lBl;ÅN½Í—½U…ÒþN¥un”߀ØÁê™UØwÑ­9à¿ô}`ɵ·8)èÒI¯™¦‹#,f®*¸Ýð8=ù¦¡¶ˆû:usÏÁø:ÆÍ_‹dw’°!íË-DðÎÿzðšÎyð”×ö„¢'Êgðxs'šÁCÍ\¸MÍúsëi ÿ¾s6„ÇÙîd*ä7t$¹‹þ0 '99ù}Òöe´°€š¥F \ ‚£³k Jƒ¹×;«¢.g }ßK$ÔÓÓxvÝqèþ»<Ó _ä~ ¯Ð‡WÓN¤L |g‹€‘ÄØhš&wÖmt@“{d×@1€ö\xþœ¼übL\t.²‰,¬ÕµÞéüEÌ™`*›Ëo™VHæ;^|~§ÇéÓÙD)0§ÚÎÞ+˜càÎ6¼‚æ¦ûÜ5Øîb\ܽ ü´* ‚Væén¨A)wÙaéßY_Ì­Ñ€’Ì ìo•8Ô*d$þlšÎµ°\TtÅ,î0.x ýö«–“€€sã÷€ék­úgàþ½[šÉ2˜@€gŽŒÎÁ4‘éßê€]Éd²Û0Œ29RÙ˦•'öíK9¶‹¼1NfÀ¡°sz©èXÐý­ŸøT°­iúÌþ;‘É:¦‚]i¿–x±Q`×éœ7¿7¬6h•a×ÐMä2ð¤p/É9¤ßdò‚ÈE2ÉD!Lµ4¹Dàú~8eï6•äulÛ % |nÙo°³KÐõùcŠ|¶Ùl˜GÊ¡>®Ëƒ·žÒ~ƒ´öƒªùì§P!AǨ³ýΛlbë%·âØîF¦Ûò=mþb—ü-’5³Ug¥vúì·ˆ|í²¼ì®a»YuO9 Óº?”Íf µ=ÒBv² ½šÎ98‘¿68?ÎÑ;ñ(™âpz,¥f‹ÞI“ôdÔYÍ5„W©y<ÿ¢0xäŸ#Cï̦»½~äfªì@Ã{€=@®ûYlŸåÒÌ5D¿gp×–q2ޤs$Ð’t;õJMûÕ—§ƒ˜_Çé¿ZÀ76jPqrìc$ÓŠ£S*s_Ã~†WH«¿sy³.ÃuWãØÓ÷jGaê4jö?ˆ¦5 5¼.ν;$Þ· X4Î*7Ðä€6ö F"‘KR©Ô€Ê¸ß!è¡ãý˜G ¬ª7!¤·_/Ž÷Ð$ @ZW°Xæ»nÇÝc9CÎ÷€aÕûæû²œgìÚÑ ¸ð5­\ d>ˆ|Ça¡HHÈõ^¸ºÙ&€6«G6‡BZãΨÒßÁ¶ýÞãB±²¾ç°Ï@`v5k|‰ zÌßû 2gÊBaG¡Ã“ >ÌL i9ly”cý2.¿ Ïþ’ç‚E‹±ÿÙ» ]n*'¨0a }þ{[».Õ-Æ3X]¸˜£¾à²Òííí‡ìI³ƒè}0Ÿ¢Ý@"$­„ Ù?A¼ '­|wéØš=£!LO<ËÌkù-`q²z,‡µ@‚øu¶;áhmáC¯]_8ª&|&€ÝíØí|t5YYK•¤gV L(—ÊLÚß"Tο4û®íÕÚiš?§VïÃQbëÏך¿Ç|i+!à ±€ ®ŽóœU¸¡QâMó2˜±ü|ìY~ Œã¯ VG×±d ü¸èï´_€Þû£À»‰Ì©ÑÌg¸bË)Ý4Ý7úoÑ 1@Ž@ézËÁØ/úd»¶H,®ëmÃÙsruê5ÿÑ×Q7¤‡¸H‚´|PÂËÇ{1.¤j8ƒoN;Îûe£Õº¯§n`7+q€i}Ûá³jØI«µ1ÓñŽJwêùŸ+ûŽ“ôÝB5ûànD»Ï•í.Âa(u¤š¹@;z¤M`¡e2Øç€÷•3WNŽà赋I;<j‘ IDAT¾‡‡¶*ç_ü°ú­@¼F]foo¾p‰#6\€Ë€3™ÌNzx4ÚLÓäÞý¡R}V‘;°©2ÜÆÛ<¯TöŒ¹p–){÷^=í´¤“ÒÛ«Ý; g³Á™ïçL9ÎsÊ©Œ:7ì[03Ètµz"p™Pî9 åÇeç§Í¼>à6ÚÜI—®‡Û´$›EéN=—à³Ð2 ,®×¡rƒ%2ƒl6“D¤Z=4>l¥ «q‰aÙö,ÅB'Âpù!øóv§"~Ž;ò2Ígjݒ裶wû èsÓ«5 pú/óp¼žµ¾²h¿ÛRþl]LbÕD-üì®­ªa(_³Ó ‰š¨ÀU³ckÞˆ•Û7¢ñDx˜ žÿpÎÍDmª5 è€Ê øÜÅÐ G&ªçÎ=ëa®Ç§HP·D£Ñ‹úâ´„Ü]í'ÜZ?¨à¨'P0¥¤ 4ÜmEÂêÞ]aöîëé4®? i3!’ŠŠ†­ÞÞ‘Zù@! hŽb¢y¾ k‚n/²ÏöI͆ !k|ž¤Û)ðÑ\’_øyL T”¯k Pvœ’À¢&x~¾®ÊQ²©§!=ªo®Y»¬0Ž·+"M¹·5;ׯ„»ó<®»îztwwã|C=Zl§a7ß<=ùš7û†9@ˆ×,$`ðϲÌþ3õ ÁºÐ›rFRNfck-CПåäŽÿÏEÂï蔓˜-Zo|·ÙW ôù†¬‚ÜQêÛì D·Ý>Tœœ^E¤u«F©"§ÎŸïd;óþÑ3 !`‘“Y0*‡çÊ$ ÊÀ»É¬>ÞC#Ô÷ZðòŸC˺'qô¨”¼¼<äcBé8òh‘«`³#®9 ^¯—û¶„€Ýd&ô`æÆˆ„¡,ŠÈ8 ,šF.Éc_Îðž<¬Ê  üZ˜xnypðÈÎ`à”ýâÅW2äpËMÀ¾fs¯â7â èßi—Ól50eZä×l ,Ø%áóXMÑsï–@,{ÞXe¢ àhk„ÓÖæ³´£ß½ÆM‘Púõ‹/¢_œ Ì-Óy•Y“*Ñ6mK$~`x~&WŒcÊ®|6oä^\ƒÁâWžù>ùÃ÷óT§Ës04´ÃÜc‚Õ뛿¶™(Ùds¿Ì@À¿/ОµX,Õq–+abˆ%1¢iâáÕxüjÀyÅ×J›µƒ©ýæ÷IágkAW¶’êÖ- Íš@"E¿+fÐy¤)Ìdj ÕÚ[‡£Øª†  Õ˜S7…ÈòEêâ©nìMP=Î>¶ê½Æõ¹Æv^Á*£úÅ”ø„Ò+uN,T„^Z ïïž ²åw—C Ýã?Fw‚ù׎WDðÐ#[ªEÈšÊé!#2¼XùÔ÷±åû)²³J =nQí·ÓÍÿ'óŸïÈþ°@æA·ÍfÛÀ@h‘€jæ?B¥åè´UÞÄ}£#ï»~fà¦ÎÑâJ˜t"ßJ§öÀ]Zaoà^ÝýpI+CÚ÷$Øíðh‰ÇëKGkñ?² O…µŠ7q)ºP@_†D×þƉQ¼Š±:Pÿª„D¿ãµà›™®mJpe±¤à2agS­lù-J|Mqöú™ûÀˆl`n‰"Ò‘r$¸Yt¨i2‘¦´÷`ûë?ÁúÒdƒVÄeì­^é?§ÓÙJ2·5’Àk!Æß8•È?H0j0Gé{EókÆŠ?%”–U¤Y™pN%@ozŒ¯YF7„vAøûu6ÙÏЦKïa^ËG¾v¶@]mÁö­ÞïÒðù• ~  `ÒM–(7+¨"a¼¼Xç07ÿý“2•çò(ñEôƒ˜û|—‘ŸÍ8YêëÖ˜ˆæã×ý„ßlEoG3Öÿâ1œÜº"fm®‰Òm1ˆúnRð磀­‡£•Pc´×›Ú¾§‰Ô(ú”œÀíª×îqù2QŒ²Vd$°¿€0ÿµïqLÀ£ø\…µœÍPûÜš@W'>‰06éõ¢§£9H 5>7žÉªÎHÕÍi<6õ™tÃz7Óµž±iÀ‰jùà¹è† *&ÈÖê^¯jà2¨aÿÞl±¢³©ïÿ÷ÿBíÞ ñZ½iÔüÿ÷‚‚Cïq`·Õj]Ìã»S¹Ñ8ƒGF‘jm³ËI;2‚¯ôù³j”îr2—,€þ…HÆJF½­–) 1fds«ýî—v? ø(²ðzß³;=AŒ YË_^¬ˆà_ÓtwŸWÄß„"úA¬‹\Ò¼sÇËÌã÷x}à)d"2/h9s«üU\¨Ù‹¡´ØÊ¶ÛÙæÄÆ¨€ÌÕf³}@xq*†…$+ Oµqt¬›Ùt¿Ë‹ÌÇQwU<‹OÏÉÆz~\IR‘†L›Ü4 ¨(’÷Î"º¯½E>á?±í¬ùï¿CWK}>loÚYòœXMн78Bt’þ¸:™­Â£ƪ(¢ñ#Ì•“Óf!zÇÝ1…»ÎÊòR¤qcGžöƒ'êm>Î;³Ê8(@!‡¾Òë‚,&ò¨Z·E"ûâ P\¤úMùU´þúzðNÑ3uá…?ŒºfÙs:záè±ùýUüñyNÞ餯/‘M8øÞx 8Ü,³¾‰£J’_ÖýÏ%ÁŸ3N]ÜAÌýðø(oÏ‹<°îU¬yü›pvw&`Î¥oòŸ-xòýù‚? i!û&¡E¹¯ü Ñt`¼Ë¡Zá6†…{;ü~žWP(wÐøÿ9Òüd¡Õ[sµh&[íŠNÇq£>øŠÓÏ*;Äê–·œæ!ü3‹6–Z€Ñ\³É¬ÕµhC; ™³aÛ4Da¶(L™¶wÃÞm p¡:n)•%Ƶ6oSq´Ëa“ÊëgîË‹¬ ,˜$Ó‡/Bjýp7‹“[&‹;W<~ý]xœ ÕÅJ’8ÓwÇÚZåp8þ-A7 ÎÍ¥Ê9xŠAõZf³¬±×{Ý$TpþéÊŠ²e˜µƒ„àF“ÌYç¯ÌìrªètÈÁÁŽS0(eÉxBa¦*ró™fô%Ƀ¼ 2òßóg´ä攚{}æv,mÌûm€^ƒ¦ÓA&V@g`2êt;°­8Ù¡ùäÉ|ÍÜIà~Ë$Ù|Ó¬µS“«ð› MØüÒO±þw߃êõb(¯ŒŒ .z;þ-$7`?d[ ý\Æ+DßkÆv©9ãZAÍ% ;Îê]}™Õ'龪Ru:e³ Î*4÷øÌrÎùdH÷aT–~ÍE&­Ì×è:èîC´†“^ølhÃÕÇdT"G²Ã¡W:z:…`YT£ üéŸE0öšå,½z›Ð&DW룹Ïcìç{1¢Ì±s_/¢Ñúý¦6‚g>|ê_ñÉë¿ÀP_šùÏþû‹aƒ„¡~@¨á$7à‹Å2‡óõJš•9)ÆV[úw" Ñ{Ê fýÑH³ÌL!ƒH‚êë”Ã'Zµ8ƒ§»¸C û¸Oé‹%0[Á€c\œÒá”L¾n­.€›Jêñ½Þ€A]ÎBäh®ƒY“lOütŠ_¼Ìjò¹æ(«æìÈLéjõ')Yùâ3yƒÔýèÖ¿¸Ð-µ=§òD»­¾št•%¸ÈùänÝR QµÇ÷Á£†òEùþfKº;[ðæÿ‡·¼›äÏìíw é¢ýÙü·Ûí¯“"ïJNŠ|–™™y+“‚ÒÉ ØµþmL¬¬Æä³±«9 ÇÚ˜”'Ù`Ì>c°b| }~½öwV­›üºŸÎÖ[íé6ðk‡FëePàé·z ´˜½àÏÆ `5«~CÀ­âÈÎM˜yí-¸å¿ÂôknF—·':](ÎòˆFÀ“ƒµi:J|Íiô‰A&=–`•‘h>'“]8˜È®‚pìÜVZE¯Gš­`dàf&­Ù@f!°ëP˜©ìhíµ K€ õp#ln_ÝtÎÿÙf (? ý˵æ$ö‚øú%ù –LQ0s”ª5‚ ŒéƒQÂÓüZ—pñS¯-uŸ[ ̺Kû¾5"+=dCãþï$‘Ý•hk iÿfrÆ$»6 Ñ.‡{7¿‡ý—ÍÁw?‚«oY wñ4ؽ(´¸01O «¨ðƒÎœbamÎôª¾¿gá_”ÍÓ‰d»f è|î;ÏVƒ…—+‚Ïf8çÓùœÒ䕬>aðk®0Ó›í!dmRø,€ê!hðû^¶Uí `úÇ d_1ú̌Ԥ¹µè>7V¹‰üüù“d*7tN?öÁ$ŸšëS#üú¦È+†1ôkNÉ{F4ËU”ß“÷& t’¦ÎÎÎWɧx¬··7©n@²Åëõàø¾Ïıæ…'qýí`Þ3ÑÑaB 9îc³¥{Àæ¼hP2ˆ«uÖU_›±±¹²?W»q,¡Ã¡»¾¾rT™eÍ |­Ã,~é:%¬·Ô: àõºÑÑTp ’p¤´±ê…Tmf­:)¼w’ÖŸ”¯ôUK&³˜F!Ð+ž4cJÐÙTŸüë0™‘Ž‹…Ÿ6çþ£ xX¢;åuÇóuújI.ˆ&—¯=‰Ïþk_{×,¸ ·>ð—¨$ë Þž…z²Õ¹Ñ†ÆæH ¤ÇŒUUIZ?·>z¼ÖÂÛBþ~í—Â,ùQL À=žÐ¥u%r¦ ½¯!ˆ‚x<6½ÓÖ‰îöf?ñgE°Þÿ~qdVQiZt®dnÎ+ßÛ­½‡)RUÌ@ãFqi¾þó8°qºÚ[Ãî7{oÜ»ß[[´.É2…Î߇úšÏ&†DË4 Ž3Øgff2¬ Å}!i@ëS2××ÑÉoˆ` ê'Fñí†îÎ6l\ù>ûð-̼ö&ܲìK¸bÞ"ج£p¨Õ…íŒËQ…{P˜¥±þT¥Ï2HÆ*†WzR׸zË0n‡¥Ç˜>Ë€ÀBåfì¨ê¡æî?ïQ»­¶öV¿{iRÝrìXÀ¬OU%9)>Mð9§?¿¸­\î9–p³C•D@Hþù¤©—aJõÕ¸l Á+ZyUµ8 Ϩí6ã¹zÐŒ®@š€Ë%ùþü{>êxA4¿Dhâ±Ùl¿'óâö¹ëìu ˆo7:ì=ؽé}qT̼ –~so[†â’2ÔöxP×å&·€¬‚Fðå|pgG;Ýý6K°,€(óMÆ Ðrú•#UÜ3U6ñ‚ª¦™%ŽÄü·¹`.lŒÞG×]:®jìèVÐÖЄš=[‘îK þíŠ&ø«Àk­Ýn?nµZ§¦¾>@µ.Õì{’ö΄Ïzâànq¼óǟ㦻­˿ŒÒÊjؼ&jq"»ÃK®B¾;£¿,—ÕSƒß‘  ÷3Tµž¾a¡‘:«ûœRó­µýrÖ’`¤ˆ#ðû}G"£ D ²¥¹ÏL>}ØFÔÁ2%:c'ZœrÆXÓ–)ç²£×Cæÿ‘ÝèlnHká7Œáû%+줴½³³óqÒ(¿Iu… ¨€^  mk·=içon¨Ã[Ï=޵o<9 îÀm÷~ÕW_Õšƒ³äŒŸëòˆ.Ã<†ŠY~f­=0Á^¥ÁN Zë‚Z#Á,€¾þŠjœ‚/¸º*®Ÿ,­PÄ$`ÎéÇZ¸ãÇw½Ñ>Þ›?+3D¹xÌKþÿ±Ó^ûsÙ/¹éœú{;&«!FãO\ D 0É“ÒùGŠßÄžxLßhûÅëW¼ŒÍkÞÀ¬97<Š97ÝŽœ‘cÐáv¡­Å\‹ŠÑ9Ò=àȾI¸ p³0†;ŽØƒiSÝ©þ±³ ü}Ä¡õ'–“¹U±ü¶_3ÎOÍßDâv¨±¾©ö…Ÿw›]…³§GvlH{í¯¥þž"EíHÐÉ;È x+##ã[===iWïr9سõ#q”WÍÄ¢e_Ä-w?ˆq“&Ãåöà¬Í…†nY*Ìí¥™Õg5û‡)Sq'LÚ,“WT©œ?wºÿ¹|æ€Éã| Mðsèž,!Á_\ÎÁ6G€Øsã5œ¦O;†¤„Ÿµ™Ð\WƒÆÓGÓzÿj©¿F’ÇÕ1Ç âˆ4þŠÞì+ô¦E¡êRÝJ,•ëôуxî§ÿŽ•/=›ï¸‹–?‚Šé—A1YÐjwŠ‘l pC6õFŸ‚m˜Ôæ!jÿb ˆ]€û8uØíäòÔ—`Õ€¾ñVQ\‰–Ó¿j3 ýèËj ¥5É1þŸÈ\³àøîÍ¢+Stšøâh-õ÷L´©¿„€Þ¤†¬€—rrr f…0Á…úZüùwOâ½?ÿsæ/Âí÷W\{#rr󃱶Ӌó]2…ÈDòÙ*P´B52m5Ú-nÖ&õöw|C ÷œ[Œµ4µ ­©1¤ Ð? Å3Ó´~q¾üë'Èt^М¾žÀ‰"¢§F+¦ª’T‘#­UŒÌ– —Û‰ƒŸ­é :ñ‡^>ÏßÇEì¡ ò zÓ/›Íæ *®@°ÕÕÙŽW¿Mï¯À¬Ù×aÉ}_ÂüÛ–¢ht1iYnÑ?@Ì—×Jƒ™Ú›Œ;âk¦ø Sø‡cš…ðwµ7udÇa%à=•ÐÁ7-Èg¥ë[4XZI¾2 ‹Óë›kØÿóÄÎãJó's˩ƹöŠ`‹Öw)èl:‡“wF}6:¢À`Ÿ••…ÞÞÞß4 hVÀ²³³¿Íôàp0¦$B[^/.F¿Áx—ÇãÆ¾í[ÄñÆï‰ÅäÜr罘T^)´¾ÝéÂ9m¸G¡ÖbŒ‰$‚lƒ¸8|}CA³jˆ˜¨ðFœ¨¯= Õë jÈr`ÕOóÍD´^ëÜãÿáj`æh-wGÝòâ“øS®¨Úy&O©œ9´¶¶æ¨O‘aX`އÝn¯%9{&ÞsÄMí¥7ý)½ùË¥„Ù(ìѸü³ÁÆuòØA<ýÓÿÀ›/<…›߃Ûï{Õ—_ «5 .rÚì^Q”Cv±^ñ—iñœ¢ÙèªæF˜LJôé³ óÏ>òWñc0³×ÕßÜ/Ê‘ÑýEe’Åèòúc ÝõX(ajÈÁf`aV.Ýf‹†Y›>û(m÷ ËÈÿI¼Ú?! + ‘¬€§èCüg<‚“Û¦—›ë žÆš7_ÂÜâŽÅÜù ‘—_ÈŒ,ô’{ÔÓ%' ³5ÀÜ‚ktõýªê›,  F ª‚öø!‘q1œ“« -¯©çþýx9ÄóærN&æI oD­opѓśˆ'Û+=€1­(K¥Ý=]8²û“´Îû“ð3ë'¡âzó_Ó‡ø‹`ìÀh€JØ©4†$›ˆÎxÒöaôvwaÃ+ÅqÅœyXöðW0Ñ]<^X9|´Ûyc)bŒx!m2Ö4z¿ÿPÝ‹ô1a~Y€0.°_=<ýç´÷¢6„ s ¼†sª |-ÈIÊGÎàéºòçnÓ¨Ôt’‹‰b9]¤ß ¦Sø~3P·Ų́?y gj"˜hG3\ìŸqï‹;ðÿÑy.°aHBS Çlûp½íƒÂ=Ø·ãq”M™Š%ËÄËFyÅ4ÑŽš~ØI«roNn/–›¡ ÷ [O%úÑŽUßXq%ú ®oI³ÉŒÆ– h“„º:ÚBÿ²É*@Õêš=²ö¡b„ÿ€šd/&ýµÍ”ßIÖ9“Öá‡>Ô ™nµZ­å))V‡ÆFñ{vŠãO?‰;–Þ‡¥|UÕ3Å䦲àsKrÙ¶+´ø÷'×ùþÁýkí+,Á¨Àwbœ’DíäLœjͰXÑæVÐÖÜ„­¬ŠÑ@ñ Ñk@¿˜ÛDzT„©0VuíOnö¤ýÓÈ 8CVÀ÷ÍfóïÞÉIØ ~ÑšŒ< µUwæžýÕÿàõ—EKîÆý_ø2.¿zŽ`wqæ@ík›Êãv_gÏàìéS¡µ‰ièˆ6¥$Uø%1(ùÚßwAL¤á–i\ºkßlY» »¶¬Ccíéð§˜<Ÿü¤"FYñÏìL`a™/K’ŠÅi?~üý*™çMv¿—ÉD¹—6ð²D‚bÄ·â†UuÃ¥fÊàŒ»Ý/ÒMïr@ÐÑÖ†7_}«Þz×Ýx3Á£˜¿`ŠFŽ@À¬I%†=«Å,‚í-a@³½áœ|½À)™Dž‹Ëe3š'õtwá( ýÖu«±mãû8}ü0¢~C7£òV_M†WÁÌÑ ªGùÈQ©X´z´À_OÚ€6Gà_ȸžÐµ8Þ®¿¬÷&šÛðwyá3g%&WÈIDAT¹&¢»bðÀï€O Ôn'óË9䀀g9lZÿ¡8ªg]†{ü"î¸{JËÊ…[àÕ{0(¡›pÀJ¦èþÝÛà ‚Ùˆµ}ztJ"ù¡ÁH.‚$Ê 3H)Ù²YÓ¦OÇý~<ø0&•–‰MÃäž­ ÑD÷ÊLQ®_÷`5Œ–V“/øœé0“;ÔÕÙ}Ûwa™÷[É·¯;}2u7¿¸3@Æ x8Ðâr¥ÏQ’(üšßÿAªLÿ”€&ä¿u: ÉîÕÇŠéýâ>§|ü˜&[m=‡*K=ê=EØYT‰]³ïAó¬û3ŸÊ8Á‘5@O3†â:vä~üÃïã¿÷,»}á[6mˆâÙp#É A@%Ö§åß%ÚÞ[4¾4Ãbå~7Ž܇¾‹kWãÄ‘ƒñõLþát4g¢Hâ'sÆAô?L¦ÀnYÍHqþ)RÛ rœ6›í;.—ë2«ÕZÉ!]û³U(È8El扖6:¶ãfÏçØi®ÀΪY¨ò Ô ÇÈ5x•\„7€æãC4NpO?õ+¼ôÂó˜V55ÇEÞdj$@´&xl8»1 ¡ç6l§ÂæÞÇÇï¯Âá{ ï§YLm©XèÇþ»q‚1'H[IDr›tmß#á?”ê˲¤ú Nuttü=¹+Ù²dN[éŽh-'a¶÷Å Y&àÓ 3P3þÿÊ8Áá•Ò*¨Û™ÖåÅñ®®®.ìÞ] «P.@²ƒ~±2þDXO‹àóÐ×úÚ³ødã:¬[½{w~†Þžî‹ssG³?þò>óŸ‘“; ›’X¥h¨ôû ÿÓqY–x“ÂÂÂ:;;ÿƒ\Ÿé-ÄXèçÎË?ÃÙ³g¡×˜L¦„€À+‚†2NpmÆI\e=Sî1Ø:röÏûKô^õ% f°óÀ‰ C’X™)›Èá z£ÍºèÄá%Dú=zþõó ØñÉ&¬]ý}݈ö¶Ö‹ÃÊ®òÆhq%crUÑ •£ÿJ…Ÿ4ÿ’ƒÇê²,õFLa´Ûí³è"¿Êæ¯Ñ£Gã†nÀÌ™3qâÄ œ}555hii1Nw¶yfà¬{4Ô¦àó7€}¯]ðÊÊ0wÞ|Q¥8°ISUDïyÃÛlø|ßnúU‚ýxªæXzÞ¬¬À77£*e àÉEÀ7®Jûd‚”¿ëþ7òÒœåA¾š.–›­ ¬Ô…G×ÖÖâØ±c8þ|Âq‚@ 0‰Ã‹¯Ÿ»&áSw5Ž{'ÂmkŽ­v¿œùÄWí5׫«|ƒYgežÁ'mÏ,¼£‡âãµïcý‡«qèÀ>ÄK°5eðµwûn· Ûô0}dâå¿ZsOÕívÿ# ÿÿ ô¥Yú é"|—\gI dëAA¾úk®|ª¬¬DYYÔÕÕ‰H°¨àJ0`è‡ ™&/®Í<‰+3Έ8Á6söÏy]—=œÞ ìy8ú>Á|Û€HD è>ç´lÚ[ɯwºœ8uâ6’–ÿpõJìÛ½F 0íWÅ-$)Y2^D>²jÿ A¿whO?y1.Ír1Þ”@àe+]üÓddêŒ@#;Á€…}Ò¤I˜0aššš„kÀ.‚Î)ˆ7`èÛ¨L,²ˆ\ø4ëyLµ6¢Ñ½;LØ1}.š+Ÿ“Eðpðm ½vH€Y‰¾ÏŸßdr%ÜU`±Êþ9²â¶lúï­ZÛ>A—Í6øn”™Ô}ÅÍti}œàþsí"æ?ïsVt¤ùWÓ^þ Ò]ŒË»¨w~ý ®äÎ`aç×ííí~C£ë÷À+\„VOöºÊ±ÍS³ž1P[ϼEVÁ+ ƒŸnüæšu¸î†\€07N¦í,âŽ\¸pÛ?Ù‚÷Þ}[7m@KsÓàFJfÿ}}#`ÍŸeQ±Ž ÄkÆ)q—ÿê=ýIÉ¥ãfRˆ§/ÖåY.æ½¥óò÷ó233¿b`CFt?qĈ¸æšk0}úta ?~\ c݇¶r¡ÙŽ…æC¸Î{GÝ%ø”6ÂáÑÿ.A7þPZ'7‘h”ûºßàÑx”#mbî>ÜÖÖŠÝ»vM¿aýZ¡ù‡Ì*¿‘Ô=7ÿ çL:ºªXS‘Ü ?[¶ôµ–öôýSø/ºÀ«µµ•<Œ×–ÇŒE3S€žÿ†ãG%ís!iCÝ*`‹€³Ü¡ø¤{ >sMÇolNÚm2NÀtã® ƒj_¿ûáÌ»q¾ Õ†Ü?ró²öbsþÀþ}xÍ»X÷Á{8.±5îràþç€ñ—a|Ž K|õ2Étyã3ÿµôvíÓå$ü}ôPZôÞ$S¾Œ„v5ùD3šIëîoJ*544ˆ€á¹sçM4™@ §ÙHiðb‡«»=ÓqÁ“OvïQ­,ùσ†n|÷=Ëñ¿¿ý]\yÕÕâß\7ôÂ5ËJ´¤rØqŒ€víûkðÞêwñù}KLj•7˜¼˜± …³nÆÜŠ‘¸¯Â%š~L*¤ŸD¨¿´WÉíw3Çÿ—éb ¦Åêè訢MöÝ *Øzáûâì*pÀ]ƒ3gÎ$-`,NÐîÉÆw)¶¸gâ¬wÔÎód ¼ ì|^v-Jó8÷¼gù}øú7ÃW^%¶j@`RLz §Nĺµ`Í»«°{çÁÚR‹óü“æ’Ðß‹Ìê[pUUî™jÂíe.TyaVDÁGÚBøÿ‰„ÿ‰tr‘F PM›îíh@ X¼@_zÀÎ'2ÉbúL(šŸ ‡Ýã±Üƒ#j9, 'Ö‘{ð2P³pv¥·ÒËËÃý>Œ¿þ›¿Áå—_)î]]]-6o܈U+W`ë–-¢ÁZœÖ›p 0ýn(3–`ú´i¸sZ––»pÅhò3TAóe__MB·Ÿtþ´ ¦¬Áªƒýž.ì©bú⪺TκGá3×4ì÷NC‡ËœÛ%€éÆçÒZ&òóóñÀCÃa·ã£uk kH-.à/ž TÝ%šÌ–M¿ ·Vfcù7æŽucd–*FÒ±ŸÌ_é*üi Kà-ÒéìÛ j¨2âP¿§» (\xÄîon6o!…Šð¿Î{ °ÍY‰]'ð’‰Ù|B£¿ œ?ˆá5€‹)¼•KHè—bä´Ù¸iê,¯ð`ÁJòä“szRÓ×/…?mÀh àV`@ 3°à××× HÃ0xœ@Æ Ú½Ù8à*Å6rNª%ðvµÇÞ“V77õ8†4«`0åÌËž6s*Æ`ùT·Mr¡²Ð ‹ICO½jr‡&áOkˆÕˆVxƒ u†!·øûÉúÜ'èU­8î‹OÕ8¬N†CÉg·ÊöeGz[‡…6Ñ•=(»Q½¹jfUNÄ= î"¿~æH/²­²{[MŽ_ji{È£ñûŸHç[–ö#ø4wມׄJ9‚@4ƒI†mmm"XÈ,C›Í–´€aŸUÀì/EÆ Î¸G“E0{=Ó`ód {€½¯ 9ºñ€,fèq«îéËè¸ S*+±¤ÂŠe“]˜]ìÁˆL½wyS'ôÆ=¥‘|ZI¹ü3 ÿsé~ûÅ ÎÖÖÖÂŒŒŒ_Óñ¨#uÕ±L&ÖÿN ²ðsúÝzߤ q‚FO!¶9§a»» ­j!ÐvZ‚ÀÞ?õ{†…;¤g‘Ýy¦ÝTß…q3±`JùõnÜXâFq×”$?˜iYdKòZÇó0 ÿ§ƒáVš!¼¤¡³Èx†Ž¯°%`l-fŒ$CPYà™šœ*†¡’OàE›'û]¥Øâš…ZŒzZ€šµ²îàÄFR_ÝÃBÏ[uL0õv‘º+¨¼×O)À² Mt£´@ãÎXèYãäÆÖ {hOž¦}ò ÿŽAtWÏjii±dee}ƒ[%ÓMÏÒç „k.‹[èè C.If `†a2†Æ8t{3Ä´®™8®–Éá“uÛ€Ý/G×\r]‹Ä*¢û0e!iúeȨœ‹«&Æ=S¼¸ƒI:ä×û‘t.†6´òZMð­Âƒ Vßêììü*!î“tó 8¢o, ðíU,Pb~¸¡†©²{À@àTÍ8â*Á&Ç TË…Ó€¦#Àþ7èx]¾Ê‹çï•Ï~½2õ&T—Ç’) ùõN\1Æ‹¼ IÐq€_i±2 ½±‚”ÑWHø]½³2X÷Àlºù/úN,"¢b§¯$ÀEô-‹ÌSb±‚þ·\’Ì™ƒS§Nõ1 S\€tÄ5[U8ì-ƒ]É“d¢C+dœ v‡6¤r¬Ì ô:ú¥äÛ/Biùd,šlÁ¼ëÆ{IÇ£JÁ÷^d¡×ƒ}ôÜ=ôúŸÉ*üeQQÑ œV« æ=ÓÑÑQIð_‹å!½œXÓüMôµ”’Å úÕ¸Íôvww‹ÌƒAª† ‚÷Xìq–a§J‘ŠzâcÝØÑ9ø 7Û(¹’„þ êNŒ,¯Âü² Q|sÓD&¤˜¤“ˆÉO–g6¸ãσ<²2¸WCCƒ5??ÿ{$xße=¢¹^zP|˜`hdÅF€1ÖDæ¢cœ€ÁÇÈ0LvI²±¡i›7û¥øØ9 ÊXÙ·ðÜn-øNÚÓÅ£;ƒ´üBÛg—_Ž9e¹¸·ÂÅ¥nL)T„¤ïbá§ç»‘öÙ·Hø÷ vùô /›Íöd üˆ¯Äáp¨-Æø:t è7ÎA‹ˆŸÅbãœ0–$§*`È`ÐåÍÄ.çd|ì¸  1¨¾­†€à5I7n:š^häd ò6¡íM“¯Å¬Ò",ì$Y£UÑbKà«i¢íM~úÊÓd^¦çü™üCAn† h.Á•V«õ7×1_Àè󓹦jÿVµx€Aðj‚b8âb²ØÃ0ù Cvܰy³¦Ì@£Z, ^l'xGº'P/R×Ýüq²£nõr`òúW2ù÷5yr`p –°=AAµn è   ŸŸ¥ot nâ`CaÈîÓŽS0´j@pÀYЧ<ãyâ„ nùðÙÓ@W J|¹¶~â-‚¿ù+1¯ÔŠå“¹“Ž×GÒñˆ™i¿é ÄN/ýˆ,¸Ÿ=Ú>ådȯööö1ô ¿GBö×td’§†nÕØžú%}ýÙˆ#:‡ºl\À‹ÌîQô€¿Cö÷$€ùÑ4 z5”5h.Ä:Ã]}Jrb@ a$Ðk/÷´#×|ÌdÉŸ‡Že¡¾“@ÀJßl —öÓ§€oöŽÐ§,œHþüB)ô“ç¡´d4•z°œüú¹ãUŒÌJÿ`^¸gÁ÷[{Lçýiý]—Š\\2`°fÓÃ~œ^ÞÄBf¤Ã2†ÒCÐP1ÜKÕJ$ë ØÐ¶˜OÀ®B°ÌÑzésYÞ¿Ï®k´ñãÇ‹ KÆ#› ¦MÅ3{?~N=ƒ€€Îß@ߨüsàów|…G9£dOüËE—ÜQ%p#áÀ½.,˜èAIž|·t"éÄn-)º¯Žþýßäëÿ†|}ç¥$—ð"ßÛ’››û ×È-˜jt "€ö{&-f VÓPŸž§h¿×A´»þ}ÎèTcîgh‰Àóé hŸ- îöËs«ªª0f̘¾Ô¤n‘_n¡Oõy3Ù¹»U¼vèvA€ºÀÙmä&ä .~öø)¸v‚EhúÅenLAÒÑ›j Ö d0÷9°÷ýûßI럻eá’Cp4»ôò›ù¡€@{­z (:W1äuPBYÁâÁ2Ü „Á€­ãÏ­pÖf7ëÐ Åvгþ9=ó7Š‹‹—úÞÃ"sû Ú$? —wÒ†±tò«Ò¯ , BVƒ¤ué÷,?0­ß÷Œ™¦ë½ rrrPQQ©S§Š™‰Á4~dá¡ g0p R¿>”à3X’¹îÉzzzÞ$îÄ: a-‚›hÓ0›ðN<Ý"Bê§Î­ƒ0bt}D#Q ò\”`S’9pÉ]Џ?éËK}é‘}öó ÄÑ=û}}Ü¢Žá»3 Q­ºº:3m˜¥´yþžÌìh3e…âèX$è'Ì€€ß  TÂYzÐOQ†£ÑÇ×^×Ðñ*å“………ÍÃwgâZ³gÏÆ† 8uøMŽÐËc‹@oGÁÍ6šþÅðþ@ѳ+¸Ô…žï k{-ý¹“þý½^››;¬ñ‡ ¹1‚¯Ò溗6\ƒ@¨¡%¡–V•è Wvl‹@^Á0øî[Aœò$ËÌA÷e-/tww¯öñ‡ ¥‹6Ù(Ú||‘ 袰 |ä?† ¼¶?G¾>×ç¿•ŸŸ¿mxgÀ€®¦¦&sNNÎÕ¤Õÿ–6â­ä”³O,h€þ/¢|V1ö*R‚ÏÑ|üÞÞ^Ýë­t~G÷z™ù†wâ0\ôÕÓÓ3Š6å}´)—Ó†G›uoÜp` š„“ìHÖÂPzê‘…Ï.ÓA²º¸.ÿíam? é+(£ »˜^Þ«Aa80 _Ò&~€Ð¢c +IøwfffûöÃ0¨ÁàzÚÈ#xƒs.ŸK]à9_¯³ô ôõذÐÀPnSΙ„e´ù¯öx<³²²²¬ºòç "ƒB4ÁÄÁ&èzå^ê¬7G¡ŸqÎ!úºŠ,¥uôõÝûðn€¡®ý,3 Ë’3µPÀ­ôz%LEÖgV¡×Ótœ!!`ë¢ôz½Üaç¼FØ9Hßß——7È€áÅ«»»»ˆ„¦¸§§g> L5 Ì·ãsÒX‹­$è?#!?N¯wåççŸ~ÂÃkx ¯á5¼†×ð^Ãkx ¯áuÑÖÿ¨qZͱÿ\?IEND®B`‚WhatWeb-0.5.5/icons/whatweb_icon32x32.png000066400000000000000000000044011400032546600200550ustar00rootroot00000000000000‰PNG  IHDR szzô pHYs.#.#x¥?v³IDATXÃ¥W{pTÕÿÝ»ïW’ ! $£€T‘ÚÒH©‚S™A˜–ÎØqœÖ±¢ÿàŒÓÚiÕ>°ƒv$èt´¶µÔ:ÌPŠ*’>Œ@¤BÂ+¼"Ùl^…;÷Þ½÷~ýÎ=» ´€½3gïÞóø~ßã÷}ç×ùd³Ù‰üZhYÖ€¦¡™ˆ|ܧð“á÷YUUqkãþ6ŸÏ7x=r•kMH§Ów²Ð'YèR—Ë5ÞétØË̼†œnH!Š§Ó ‡ÃÁM–ÓÏ[À»–i®ƒGðYžx<Ìd2kØò¤iš¤å²d:í‹Ñ“­Ûhó~2ò:¥S J$’”J&IÓ4:ÂãÉŒFâÉf3É4Ë Þx2™¼Á÷™–EÙLš =G±,ýø7;è–¹K‰§PhÚBzfÃ~Id)ŸKS"kÐso·Sxj =ðíU´ÿX„Xc"Ë$–µWȼ.ðD"q/ˆ†Á‚3OëôƶšùÀc6pY«›A?Ùr”öÄ¥ØDkÛ3̓jgØ ‰mAÉ3ù,dù{(®¡Áï(S@àLöĨœ6°­®¡ãx/Ú-XÏÙ bï“ ëä±vŸà+^]ÇTöB‰Èlªã°.ÉSý£øbc‡Â*…B` L~>/fÝ,>„Vb£éD±éàŒîþ%|kœB":˜c!ˆe_®°Š‘…)<ãôCÍzíi¹@ƒ­€æ’YÒq<‚‡çŠÔ*ãªà?“DÆ=`æóhïÂßµ'5†½°ò…´bè7OFóË]º4Tû`$SÐ~X/|qÆq¬ˆ¡Ú ‹€íí:‰„þ¸™Y—ò€±kUáÈ¢[r¹ Îe ýc̾ü0ê‚Ôú ã}šêÇ£±y2‚. ̱G a·‰0ëVl—¢– 7ÎKð@±#¯?Žƒ9¸¥véãP8Îû}Gf­ï»ìà 2Dº1ä––t>s'ª¼ªX WM3¾ðâahìÅ¡“/eá¦We­ØØ~šIç*“'0;&ûÄaBâÓ Ÿ„õBõD|긽16Þͧté•7¿{7zâ*œêEų̀PàD±ú]ŽïŽ»¦Õ^ÅV"‘³ çMøœ ‡HºÛ4Lüîã1{Þ¦OÎÁíTíþbòÓ-ÔøÛ#Å4´ %÷^ÿIÜtû—`p)µH ì<ÇûŽ[ZÕ’ókü*DÕeo à4[ŸåÄ™Q«`õ, skd=‰etv¹PÀ¼4ÛüÓ&Ž]ì’ÐÌ0$Àò…³Q% _â®ÏéB\#„\„&Yèð8ð/¦»ÆÀ©ÐT ,â­­9¬à¡ï"{¥ËNI2¶õ“±÷¨¼#õòÈn.ÒÒÌ\oª;KuÛÑYÒœÇךx3ᮡpÊ‹çøà §0ósùçxw™ÜU§  èxåííœés¥g‹f°‘‹Ÿ]Œ)¦à:Þ L°‚ ždëì)õ¨ª8qü(<תÊJ8=~ø=n¤L7&ødš…¼*æ4~>°b8]E˜5'kÃØøç­¥ê'Ù¯€÷“¿[Q¬¬É?¹óOÁ [+º&/.‘ìÞ–Y˜PÄÚ—^FÏé“,Ø)õA¬˜Yi·4(x¾¸£F’¯š9Q_éÁİÎìÚ7ÿZr¦&dW?Þö…›æåçÁI¼=®}í·4ïáïÓø)³ˆÓ ìÈåñúéËó”õèMÐÇ‘}ÒŸ£®aƒ>:£×7í¦o<²²lž3ùt-0ÖU/&|^[é=»5íCOÿŽ÷£ëØ)tíÄhÏÁ«¦aÿ…tKE÷±#xÿ½mØýÞVž¸lÎüçv`ç³÷ñ!…÷¿¼±< mÿ¯7£t:cS1•Jbxh‘h?+sݽÿF×ñ“èØöÖu©ù˾ƒåË–aéý ¹¤«fNÓWVTT¼qÍ«™8ÁŠR)šˆ[">†ÁÁAôEÐ3x§¢çÑÞÉžéæêûÛë´îX²h>îhž—DÞâãYM‚Áÿx#·£Ùœ«{ÅQ:—ˉã´}´÷…¶vÑ×m éýŒPÑTŠó®Žn¾ˆhöE„ë™|32„¬Ït?ŒÅb^qxd!'ÄÅ¢¨Ìðð0=sŠÎ¥iw×õOÚc\E¹ö%„×à¶rttÔûÝŽ âêuWʹÍá6Y±W8>nq˜L‹DØz¸»ƒÛVè}&[êZ²ÿó÷ C6ÃIEND®B`‚WhatWeb-0.5.5/icons/whatweb_icon_48x48.png000066400000000000000000000073451400032546600202440ustar00rootroot00000000000000‰PNG  IHDR00Wù‡ pHYs.#.#x¥?v—IDAThÞµZ tÕþfggŸÙ¼ IÀÂ¥ªhiåQmEZµÚVZEE”е"ÔS‹©Õ£(Úƒö`µÒj­F© #’ ƒø€„„$$ÙB^»ÙÝdwgúßǾ’̓ÒÎ9ÿÙ™™{ÿïßÿŽ‚s<<ûq-$º”(_RžüeG‘[þ2zŸh‘ÏårÓüÊ0Lvd}›h©dÞ~–sû%ˆ×ˆ^'jKöÐPÿ)D÷ÝÇÆ¿a¢Ñ,f‚a¡°A£+CO §!zœè)¢î³`: ‰iD+‰ŽÅ3¯À€Ý¢¢É¯áɽgPÙ©Á¡™øÿ†‘8»6)4³šÅÈÆú•›Í¡1Ƈc^CƒœGTFô;¢‘qìÀFŒú6½]‹7®ÁºÅXrדxù(iCÓ šAô¼è;°úŃXóÄv´úÙsæÈ€¹rŽ2Òüyø_4Ð\¢f"#F]FÏkx!ã¥ê™ßßÀäl%%%üœÑÊ?3ÚüF·×cø}ÝF0à3vUúŒ o}&úÌ”ùËŒ­oäcù¼ñsð9ç éă8);~H´•È"ÔOªW˜ÌVì«ñbãsÅ¿6ÝÁ|ê™-èö ‡Ã°ÙíX{÷Jþÿµ[bû£ñyõI¼ü…ǯ›üÑMÏ`ýOïâçW­Þˆ~r3¦çZgu=D·½4¨“@?ŒH£««‹K1Ø0>iì6~ôøŽ¨™ÔýôÙ“ äL50rªQ´ñY£´ü0¿?{Ý?çÿUnLYùÇè;șŸ-)=È)òÞm/'ÎôÝ Úæôƒø4€kŽ”¼ð^r¸6¿Žßíøßt)ÿoÿþýØ{¨ ¡P<ùe‚ôèËþæEØÓÄõÑ6wo~ž÷¶‰›sñìù×݉’üõÍ(• •汪€ÞŸŸ­ÒÁËúÞHà<›¹Ù„É$êjªðÂGxbÅþÀGG*qñ´ €- 󮧈jK:5?tŠj.~ˆRW¹ø"¥ #‘½ù7Þ‹Gï¿]$–4fƒ?¢$Æ_«äi6ÑÉñ¡ŠT¢É£‘¦¢¢ŸV”ã”´øãük0û†õØ¿ýÌ[þ 1ß'Ô$Îüôk%ãFqÅu9,Ð(jéFR«)ycÖH·ÍŠz8‰Áï÷ósÕ$#®¿f‰y·>FrIM2ÍÎ80[Äå¤+Ȉä™ù(qXù¬Í¬òd¨To7ý¡’>˰’†*¢+ª¸¨Ù Lþ&:Š˜KFŒ~RÐ(c\ A'˜ÌBªb '²3·“åt÷Md÷$&)Á8 Fßê#c 1Ex­.!]æQrqßPÌ6ñ¸œÙâöýû#SzŸª“*žeï8CµH\¡áÈ•¼Æ4@Òg…ÙÚdO3 H8b˜)„1½ &͈Ù0¯cZbÿ{qïwçŠ[=Ç}òfNŸÊ£×%ëv@·¦ ³bãJüt9¬êi-ñü[ÒB[Ä„¾-‹´¤¥Ù¤ åN£'GPxêMÔÊŽ• ï½h¦O‚ÎIßÁŽ5—`ɳGñVaóæÍƒò_ HÔUM˜q—A`2úE¡~Ñ·ˆ^ŒX:ó§iÁX·V]ª›I¼ËÍO_¿Ái×áo÷\ΞiîP£E»Ocb^ Jµˆá,Òü¤cëÒ„ÚÛÏÀV2¼v)@ªˆ,F’ãÇGsc=ÊêÂ2K\D6lŽ&#~ÞQÏOßøËøz ”Õvc[Y+;{àîèÁiO/<þ.È‘.F>{†Ðd&Ô~º= RmƒF¢È±ˆñnh1¤¯ª*:ÚZá¦àÝG¾\òS2ò¡pO\=KCXüô½ª.ÜüJ|žv´í‚ §iT«S¼òÃÔëVaŽžÃíycí(ÄŒç+Lr˜4tvwwãxÕ1”¶J÷8ŸÌN³ÉèGšàö¨oñ¡µ×.b=~HaÒN@edÊJ±`T†£Ó5ŒNÓðÜBT.U§0™†NÙq©9nÝš }3Õ%u'ªQÝeÁ[–‰Ù¸ôâ*gÌ.OÉé±àÌm\t K„O:^}èûãÆ±¨æl]õ­PfäÈè7$€ü¤"Ç›ª°£¢Uä£û¿ YhRMKkÒ`Xí`Ö¬3š.ʦbf£Ç À)äèzO7ù»ˆó7WCÉ-’3!²—ý·à÷~^ÀUyôΰr7Ð]Æüám"gdŽ$«f@¯ÓŽVqi*Ò]NŠ@"´»¼[ÉlT3¼ù”PÝé€ÙéäâiÒàè±ùÕR¬¾f.êVõ¢ÐDxèœ7¨–]˜Ãò<Ôzæì±H7º`sŸè5C!èãgb̦hJ™vfú ä¾N/‚– âMá+õÍ guZL´f¢W¢&VÖÆå³Äb§¤^ǤóiäÐù Ð5qÄ|Øqæ‘ 03Qæ¹YÑÛi.;UÙ“nB <Ýj ‹€œ,ºN*S4ÁÈÏæŠxïÔ(Ök'½ßÙTõû y™bì7>n$“T‡ÕÑ0ËFÓ„¾7Xb¨5ÞÆÊd×À˜Ít+C–@f™¸SˆY“&äüÔ~S)«»[¨Ò P‰BQÙ•Ç£ž»[ÁŒë×cçmãpüê Æ9ý nFM&Ù1K8XügÉ«%ìŠ2¿èOí\ºU’ñ¿ñÄŽ.JXŒÚ¼sQHÊ¢gÄÂþÜMŸ£öt7|Ä|¯3zj>BnÛ›0’ÜâçkDI²ãX˜Ößè×–és¸#H8‚Á *ÅAOð”å[íP£÷[}á„ß„"Ūô»VU¡i¢âDKœ…Ép‡yD!ÿýëgA,/‚Å ïÁm3¦!E ÁBý°ELGG½(˜™tÑ¥ø¸9„@HGžKE I5q“Iz-|€åý¬Ñc"i‡;ÝP² ¡å"Ugjpï+_ W&ZS4;Ž›x~Ê„ÞMG=&á½Ý!“”ª))õ½¹N‘>@IÍu5°Úl°ã9”ÀÓÉôÚj¤©ò ù’rï±vþîßËOQu:hœyß,¬þøzH”Ð BѦ3:A]Á//%ÃC{}ÈrªIGLµ©ý¯I̪×À™[È=HÎÛR_Ãk¶Z]0³Ór-xzOv?q kïx@x æðn¶¨÷QUÇ@\Ýwá¥ëz‚]³(âж«Spß.oòBÝjêwm¦ªó–…ØYCùvCmMÔ9/˜RˆK()g»¬hm¬EñÃ7¡éó‚YN àÈo3Þ#ë×â Ä*ÂL©›+|bå?Ë‹DÌ~´$ˆË‚~ׇ›ÒFPmc<ïK…XH+ÓI™¬D2£©æž]~qìEG.•¦äŒÏÑEýë²ÅíŠ_…E^Í$•¯ž#Ú'ÍÞ0þþ©rí4™•øs™‡ë,$F!v½«–|ôd¸Yˆ¯Qp»¼€%4ʶº¨×,•U'0"+›)…sa²ØÀx}# €­-ÉŒ6ʶyÔ„TMˆóOk¯AçM«p×÷–CÜ0÷+}ØsÜÛí° s&Öz±ëŒq˜žkà;”2Ǧ ƹ6X¬§rdo£fm4féf´W‘¦h},ö’xœñÜ·/´‰èNÖ™ˆ˜P$Œ–ïÛéhµÓf~ï¾û®œìH±´ÖÏØû`×¹” –M0°€¤Î’cÞª&öÂVS…CÈ£jöØÉãä¢^îø‰M$ar#$q@öY6qÃ"̱æU¨Ç‡Š²½È¤€ž?"K/†·íTZ¡-œèà›“ÒuLË1cÿKJÍ(øÅ\໓("I‹äöË4ä¢ß4ÊrÙi¾Ö~àþûxó,ËeKÖ+Úé %Ûà`Øy%Æäz.Ô•ÔƒÝ(ybGÊÇ7_…MÏüþÓuÈ´ê8òa ."X>­Ò­ª"‹8H"¦mfd§:8wÕ¼²¹_™}Ž}ö 0ù |}œLpˆèùA÷ÈØÎ1¸¸¸8£³ý <º ëî¾}Ø"™™Y˜uñÅø üSøÏ4â°;€`o¬c¢ºB#ßb1þ$…Ó}ïáÓòèé DÙJ½s'Jñ5ŒIé‰ôŠ˜éÌ&é7 ¹ÉÇvFÄ>·Û­ž¬¯CK³_«î¸ã¬wxjÛzˆ± ÕÿŸ®ÅÝ„ýûÞîâ×pàƒ÷ÐÑžts—miÂ[ßO¥\dD6:.#æ {—²³³s9­‹_`ç>ŸÍÍͨ«=Ó§¨¤q׫†à‹êzh´äü°¬ů¿Š½{v£©±aÐwfßôk¬_w/ôFVel§hû9m1™dwÚëõ¢±±õuµ8ÓzþÞð`&LœˆªÊÊ!ÁÞó䟱tá|\X˜MÙ;Ä)“üŠ˜î>ñœ¾{l½ÀÊ ÒÐ@fÖN¦@ˬ^×bÔ([½ÌLIÊðãÆûŸÆ²+â«3 ‘å0qÑE$dm¼¥d6eç´Ñql bVßÊ•i†9d{{;q¸¢Ëô㸕ž>»º±ž[e³_buФ5HBÝH2òœ?5è³{s³ìÏç&k„1 o£åÔ)ÔÚÏÇ¿VâÈ¶Õ Ï•ŸhÃäüTPâ §pb˜o–ã?OÌÿ_ßJ8e~mßO âÁ>h6‘­÷ïÀW&Œ¤Ø¯#ì¥L,2½k9°I#zúl?58×=¾%»Ä‹ú؃—l‡G–¶¦)ã³zþmišoüß>ö8‹Ïm®8ËÏmvÿ/>·ùŸ5 U 2FIEND®B`‚WhatWeb-0.5.5/lib/000077500000000000000000000000001400032546600136305ustar00rootroot00000000000000WhatWeb-0.5.5/lib/colour.rb000066400000000000000000000031471400032546600154650ustar00rootroot00000000000000# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see . def colorize(text, color_code) "#{color_code}#{text}\e[0m" end def red(text) colorize(text, "\e[1m\e[31m") end def dark_red(text) colorize(text, "\e[31m") end def green(text) colorize(text, "\e[1m\e[32m") end def dark_green(text) colorize(text, "\e[32m") end def yellow(text) colorize(text, "\e[1m\e[33m") end def dark_yellow(text) colorize(text, "\e[33m") end def blue(text) colorize(text, "\e[1m\e[34m") end def dark_blue(text) colorize(text, "\e[34m") end def magenta(text) colorize(text, "\e[1m\e[35m") end def dark_magenta(text) colorize(text, "\e[35m") end def cyan(text) colorize(text, "\e[1m\e[36m") end def dark_cyan(text) colorize(text, "\e[36m") end def white(text) # quick patch for issue #345 so it's readable with a white terminal background # not white, but bold colorize(text, "\e[1m") end def grey(text) # not grey, but reset colorize(text, "\e[0m\e[22m") end WhatWeb-0.5.5/lib/extend-http.rb000066400000000000000000000160651400032546600164310ustar00rootroot00000000000000require 'net/protocol' require 'uri' require 'timeout' # This works with Ruby 2.x. Based on HTTP library from Ruby 2.2.3p173 # Modified to return the HTTP headers as a string # added @raw # added @raw_lines # # # The ExtendedHTTP class is used in place of the HTTP class # for example, # http=ExtendedHTTP.new(@uri.host,@uri.port) # The ExtendedHTTP class uses the ExtendedHTTPResponse class class ExtendedHTTP < Net::HTTP #:nodoc: include Net # Creates a new Net::HTTP object for the specified server address, # without opening the TCP connection or initializing the HTTP session. # The +address+ should be a DNS hostname or IP address. def initialize(address, port = nil) @address = address @port = (port || HTTP.default_port) @local_host = nil @local_port = nil @curr_http_version = HTTPVersion @keep_alive_timeout = 2 @last_communicated = nil @close_on_empty_response = false @socket = nil @started = false @open_timeout = nil @read_timeout = 60 @continue_timeout = nil @debug_output = nil @proxy_from_env = false @proxy_uri = nil @proxy_address = nil @proxy_port = nil @proxy_user = nil @proxy_pass = nil @use_ssl = false @ssl_context = nil @ssl_session = nil @enable_post_connection_check = true @sspi_enabled = false SSL_IVNAMES.each do |ivname| instance_variable_set ivname, nil end # added for whatweb @raw = [] end # ExtendedHTTP :: raw # added for whatweb attr_reader :raw # added @raw for whatweb def connect @raw = [] if proxy? conn_address = proxy_address conn_port = proxy_port else conn_address = address conn_port = port end D "opening connection to #{conn_address}:#{conn_port}..." s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do TCPSocket.open(conn_address, conn_port, @local_host, @local_port) end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) D 'opened' if use_ssl? ssl_parameters = {} iv_list = instance_variables SSL_IVNAMES.each_with_index do |ivname, i| if iv_list.include?(ivname) && (value = instance_variable_get(ivname)) ssl_parameters[SSL_ATTRIBUTES[i]] = value if value end end @ssl_context = OpenSSL::SSL::SSLContext.new @ssl_context.set_params(ssl_parameters) D "starting SSL for #{conn_address}:#{conn_port}..." s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context) s.sync_close = true D 'SSL established' end @socket = BufferedIO.new(s) @socket.read_timeout = @read_timeout @socket.continue_timeout = @continue_timeout @socket.debug_output = @debug_output if use_ssl? begin if proxy? buf = "CONNECT #{@address}:#{@port} HTTP/#{HTTPVersion}\r\n" buf << "Host: #{@address}:#{@port}\r\n" if proxy_user credential = ["#{proxy_user}:#{proxy_pass}"].pack('m') credential.delete!("\r\n") buf << "Proxy-Authorization: Basic #{credential}\r\n" end buf << "\r\n" @socket.write(buf) # HTTPResponse.read_new(@socket).value # added this _x, raw = ExtendedHTTPResponse.read_new(@socket) @raw << raw # res = x.value # end if @ssl_session && Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout s.session = @ssl_session if @ssl_session end # Server Name Indication (SNI) RFC 3546 s.hostname = @address if s.respond_to? :hostname= Timeout.timeout(@open_timeout, Net::OpenTimeout) { s.connect } if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE s.post_connection_check(@address) end @ssl_session = s.session rescue => exception D "Conn close because of connect error #{exception}" @socket.close if @socket && !@socket.closed? raise exception end end on_connect end private :connect def transport_request(req) count = 0 begin begin_transport req res = catch(:response) do req.exec @socket, @curr_http_version, edit_path(req.path) begin # added for whatweb # res = HTTPResponse.read_new(@socket) res, y = ExtendedHTTPResponse.read_new(@socket) @raw << y # res.decode_content = req.decode_content end while res.is_a?(HTTPContinue) res.uri = req.uri res.reading_body(@socket, req.response_body_permitted?) do yield res if block_given? end res end rescue Net::OpenTimeout raise rescue Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, # avoid a dependency on OpenSSL defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : IOError, Timeout::Error => exception if count == 0 && IDEMPOTENT_METHODS_.include?(req.method) count += 1 @socket.close if @socket && !@socket.closed? D "Conn close because of error #{exception}, and retry" retry end D "Conn close because of error #{exception}" @socket.close if @socket && !@socket.closed? raise end end_transport req, res res rescue => exception D "Conn close because of error #{exception}" @socket.close if @socket && !@socket.closed? raise exception end end # added @raw class ExtendedHTTPResponse < Net::HTTPResponse # reopen include Net class << self def read_new(sock) #:nodoc: internal use only x, httpv, code, msg = read_status_line(sock) @rawlines = x + "\n" res = response_class(code).new(httpv, code, msg) each_response_header(sock) do |k, v| res.add_field k, v end # added for whatweb real = @rawlines [res, real] end private def read_status_line(sock) str = sock.readline (m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in.match(str)) || raise(HTTPBadResponse, "wrong status line: #{str.dump}") [str] + m.captures end def each_response_header(sock) key = value = nil loop do line = sock.readuntil("\n", true).sub(/\s+\z/, '') # added for whatweb @rawlines << line + "\n" unless line.nil? # break if line.empty? if line[0] == ' ' || line[0] == "\t" && value value << ' ' unless value.empty? value << line.strip else yield key, value if key key, value = line.strip.split(/\s*:\s*/, 2) raise Net::HTTPBadResponse, 'wrong header line format' if value.nil? end end yield key, value if key end end ################### public # include HTTPHeader def initialize(httpv, code, msg) #:nodoc: internal use only @http_version = httpv @code = code @message = msg initialize_http_header nil @body = nil @read = false @uri = nil @decode_content = false # added for whatweb @rawlines = '' end end WhatWeb-0.5.5/lib/gems.rb000066400000000000000000000026721400032546600151170ustar00rootroot00000000000000# # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see . def gem_available?(gemname) Gem::Specification.find_by_name(gemname) ? true : false rescue LoadError false end # check required gems required_gems = %w[ipaddr addressable json] missing_gems = required_gems.map.select { |g| g unless gem_available?(g) } unless missing_gems.empty? puts "WhatWeb is not installed and is missing dependencies.\nThe following gems are missing:" missing_gems.sort.each do |g| puts " - #{g}" end puts "\nTo install run the following command from the WhatWeb folder:\n'bundle install'\n\n" exit 1 end required_gems.each { |g| require g } optional_gems = %w[mongo rchardet pry rb-readline] optional_gems.each do |g| next unless gem_available?(g) begin require g rescue LoadError # that failed.. no big deal raise if $WWDEBUG == true end end WhatWeb-0.5.5/lib/helper.rb000066400000000000000000000044571400032546600154460ustar00rootroot00000000000000# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see . # # Helper methods for output and conversion # module Helper # converts Hash, Array, or String to UTF-8 def self.utf8_elements!(obj) if obj.class == Hash obj.each_value do |x| utf8_elements!(x) end elsif obj.class == Array obj.each do |x| utf8_elements!(x) end elsif obj.class == String convert_to_utf8(obj) end end # converts a string to UTF-8 def self.convert_to_utf8(str) if defined?(String.new.scrub) # Defined in Ruby 2.1 str.force_encoding("UTF-8").scrub else # Ruby 2.0 str.encode('UTF-16', 'UTF-8', invalid: :replace, replace: '').encode('UTF-8') end rescue => e raise "Can't convert to UTF-8 #{e}" end # # Takes an integer of certainty (between 1 - 100) # # returns String a word representing the certainty # def self.certainty_to_words(certainty) case certainty when 0..49 'maybe' when 50..99 'probably' when 100 'certain' end end # # Word wraps a string. Used by plugin_info and OutputVerbose. # # returns Array an array of lines. # def self.word_wrap(str, width = 10) ret = [] line = '' str.to_s.split.each do |word| if line.size + word.size + 1 <= width line += "#{word} " next end ret << line if word.size <= width line = "#{word} " next end line = '' w = word.clone while w.size > width ret << w[0..(width - 1)] w = w[width.to_i..-1] end ret << w unless w.empty? end ret << line unless line.empty? ret end end WhatWeb-0.5.5/lib/http-status.rb000066400000000000000000000062661400032546600164670ustar00rootroot00000000000000# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see . class HTTP_Status # return HTTP status code as a string def self.code(number) number = number.to_s abort('HTTP_Status must be initialized') unless @status_codes if @status_codes[number] @status_codes[number] else 'Unassigned' end end # Status code list from: # https://raw.githubusercontent.com/Mr-Pi/httpStatusCodes/master/priv/http-status-codes-1.csv def self.initialize @status_codes = {} text = " 100,Continue,[RFC2616] 101,Switching Protocols,[RFC2616] 102,Processing,[RFC2518] 103-199,Unassigned, 200,OK,[RFC2616] 201,Created,[RFC2616] 202,Accepted,[RFC2616] 203,Non-Authoritative Information,[RFC2616] 204,No Content,[RFC2616] 205,Reset Content,[RFC2616] 206,Partial Content,[RFC2616] 207,Multi-Status,[RFC4918] 208,Already Reported,[RFC5842] 209-225,Unassigned, 226,IM Used,[RFC3229] 227-299,Unassigned, 300,Multiple Choices,[RFC2616] 301,Moved Permanently,[RFC2616] 302,Found,[RFC2616] 303,See Other,[RFC2616] 304,Not Modified,[RFC2616] 305,Use Proxy,[RFC2616] 306,Reserved,[RFC2616] 307,Temporary Redirect,[RFC2616] 308,Permanent Redirect,[RFC-reschke-http-status-308-07] 309-399,Unassigned, 400,Bad Request,[RFC2616] 401,Unauthorized,[RFC2616] 402,Payment Required,[RFC2616] 403,Forbidden,[RFC2616] 404,Not Found,[RFC2616] 405,Method Not Allowed,[RFC2616] 406,Not Acceptable,[RFC2616] 407,Proxy Authentication Required,[RFC2616] 408,Request Timeout,[RFC2616] 409,Conflict,[RFC2616] 410,Gone,[RFC2616] 411,Length Required,[RFC2616] 412,Precondition Failed,[RFC2616] 413,Request Entity Too Large,[RFC2616] 414,Request-URI Too Long,[RFC2616] 415,Unsupported Media Type,[RFC2616] 416,Requested Range Not Satisfiable,[RFC2616] 417,Expectation Failed,[RFC2616] 422,Unprocessable Entity,[RFC4918] 423,Locked,[RFC4918] 424,Failed Dependency,[RFC4918] 425,Unassigned, 426,Upgrade Required,[RFC2817] 427,Unassigned, 428,Precondition Required,[RFC6585] 429,Too Many Requests,[RFC6585] 430,Unassigned, 431,Request Header Fields Too Large,[RFC6585] 432-499,Unassigned, 500,Internal Server Error,[RFC2616] 501,Not Implemented,[RFC2616] 502,Bad Gateway,[RFC2616] 503,Service Unavailable,[RFC2616] 504,Gateway Timeout,[RFC2616] 505,HTTP Version Not Supported,[RFC2616] 506,Variant Also Negotiates (Experimental),[RFC2295] 507,Insufficient Storage,[RFC4918] 508,Loop Detected,[RFC5842] 509,Unassigned, 510,Not Extended,[RFC2774] 511,Network Authentication Required,[RFC6585] 512-599,Unassigned, " text.scan(/^([0-9]+),([^,]+)/).each do |k, v| @status_codes[k] = v end end end WhatWeb-0.5.5/lib/logging.rb000066400000000000000000000040311400032546600156010ustar00rootroot00000000000000# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see . class Logging include Helper # if no f, output to STDOUT, # if f is a filename then open it, if f is a file use it def initialize(f = STDOUT) f = STDOUT if f == '-' @f = f if f.class == IO || f.class == File @f = File.open(f, 'a') if f.class == String @f.sync = true # we want flushed output end def close @f.close unless @f.class == IO end # perform sort, uniq and join on each plugin result def suj(plugin_results) suj = {} [:certainty, :version, :os, :string, :account, :model, :firmware, :module, :filepath].map do |thissymbol| t = plugin_results.map { |x| x[thissymbol] unless x[thissymbol].class == Regexp }.flatten.compact.sort.uniq.join(',') suj[thissymbol] = t end suj[:certainty] = plugin_results.map { |x| x[:certainty] }.flatten.compact.sort.last.to_i # this is different, it's a number suj end # sort and uniq but no join. just for one plugin result def sortuniq(p) su = {} [:name, :certainty, :version, :os, :string, :account, :model, :firmware, :module, :filepath].map do |thissymbol| next if p[thissymbol].class == Regexp t = p[thissymbol] t = t.flatten.compact.sort.uniq if t.is_a?(Array) su[thissymbol] = t unless t.nil? end # certainty is different, it's a number su[:certainty] = p[:certainty].to_i su end end WhatWeb-0.5.5/lib/logging/000077500000000000000000000000001400032546600152565ustar00rootroot00000000000000WhatWeb-0.5.5/lib/logging/brief.rb000066400000000000000000000077241400032546600167040ustar00rootroot00000000000000 class LoggingBrief < Logging def escape(s) # Encode all special characters # More info: http://www.asciitable.com/ # r=/[^\x20-\x5A\x5E-\x7E]/ # Encode low ascii non printable characters r = /[\x00-\x1F]/ # based on code for CGI.escape s.gsub(r) { |x| "%#{x.unpack('H2' * x.size).join('%').upcase}" } end # don't use colours if not to STDOUT def out(target, status, results) brief_results = [] # sort results so plugins that are less important at a glance are last # last_plugins=%w| CSS MD5 Header-Hash Footer-Hash Tag-Hash| # results=results.sort_by {|x,y| last_plugins.include?(x) ? 1 : 0 } results = results.sort # sort results by plugin name alphabetically results.each do |plugin_name, plugin_results| next if plugin_results.empty? suj = suj(plugin_results) certainty = suj[:certainty].to_i version = escape(suj[:version]) os = escape(suj[:os]) string = escape(suj[:string]) accounts = escape(suj[:account]) model = escape(suj[:model]) firmware = escape(suj[:firmware]) modules = escape(suj[:module]) filepath = escape(suj[:filepath]) # colour the output # be more DRY # if plugins have categories or tags this would be better, eg. all hash plugins are grey if (@f == STDOUT && $use_colour == 'auto') || ($use_colour == 'always') coloured_string = grey(string) coloured_string = cyan(string) if plugin_name == 'HTTPServer' coloured_string = yellow(string) if plugin_name == 'Title' coloured_string = grey(string) if plugin_name == 'MD5' coloured_string = grey(string) if plugin_name == 'Header-Hash' coloured_string = grey(string) if plugin_name == 'Footer-Hash' coloured_string = grey(string) if plugin_name == 'CSS' coloured_string = grey(string) if plugin_name == 'Tag-Hash' coloured_plugin = white(plugin_name) coloured_plugin = grey(plugin_name) if plugin_name == 'MD5' coloured_plugin = grey(plugin_name) if plugin_name == 'Header-Hash' coloured_plugin = grey(plugin_name) if plugin_name == 'Footer-Hash' coloured_plugin = grey(plugin_name) if plugin_name == 'CSS' coloured_plugin = grey(plugin_name) if plugin_name == 'Tag-Hash' p = ((certainty && certainty < 100) ? "#{grey(Helper::certainty_to_words(certainty))} " : '') + coloured_plugin + (!version.empty? ? "[#{green(version)}]" : '') + (!os.empty? ? "[#{red(os)}]" : '') + (!string.empty? ? "[#{coloured_string}]" : '') + (!accounts.empty? ? "[#{cyan(accounts)}]" : '') + (!model.empty? ? "[#{dark_green(model)}]" : '') + (!firmware.empty? ? "[#{dark_green(firmware)}]" : '') + (!filepath.empty? ? "[#{dark_green(filepath)}]" : '') + (!modules.empty? ? "[#{red(modules)}]" : '') brief_results << p else brief_results << ((certainty && certainty < 100) ? "#{Helper::certainty_to_words(certainty)} " : '') + plugin_name + (!version.empty? ? "[#{version}]" : '') + (!os.empty? ? "[#{os}]" : '') + (!string.empty? ? "[#{string}]" : '') + (!accounts.empty? ? " [#{accounts}]" : '') + (!model.empty? ? "[#{model}]" : '') + (!firmware.empty? ? "[#{firmware}]" : '') + (!filepath.empty? ? "[#{filepath}]" : '') + (!modules.empty? ? "[#{modules}]" : '') end end status_code = HTTP_Status.code(status) if (@f == STDOUT && $use_colour == 'auto') || ($use_colour == 'always') brief_results_final = "#{blue(target)} [#{status} #{status_code}] #{brief_results.join(', ')}" else brief_results_final = "#{target} [#{status} #{status_code}] #{brief_results.join(', ')}" end $semaphore.synchronize do @f.puts brief_results_final end end end WhatWeb-0.5.5/lib/logging/elasticsearch.rb000066400000000000000000000064731400032546600204270ustar00rootroot00000000000000# Elasticseach Output, copy of JSON ouput then a HTTP request to send result to elastic # Does not use elasticsearch gem. Instead only sends HTTP class LoggingElastic < Logging def initialize(s) @host = s[:host] || '127.0.0.1:9200' @index = s[:index] || 'whatweb' end def close # nothin' end def flatten_elements!(obj) if obj.class == Hash obj.each_value do |x| flatten_elements!(x) end end obj.flatten! if obj.class == Array end def out(target, status, results) # nice | date be like 2009-11-15T14:12:12 to be autodetected by elastic foo = { target: target.to_s, http_status: status, date: Time.now.strftime('%FT%T'), plugins: {} } results.each do |plugin_name, plugin_results| # thisplugin = {:name=>plugin_name} thisplugin = {} next if plugin_results.empty? # important info in brief mode is version, type and ? # what's the highest probability for the match? certainty = plugin_results.map { |x| x[:certainty] unless x[:certainty].class == Regexp }.flatten.compact.sort.uniq.last version = plugin_results.map { |x| x[:version] unless x[:version].class == Regexp }.flatten.compact.sort.uniq os = plugin_results.map { |x| x[:os] unless x[:os].class == Regexp }.flatten.compact.sort.uniq string = plugin_results.map { |x| x[:string] unless x[:string].class == Regexp }.flatten.compact.sort.uniq accounts = plugin_results.map { |x| x[:account] unless x[:account].class == Regexp }.flatten.compact.sort.uniq model = plugin_results.map { |x| x[:model] unless x[:model].class == Regexp }.flatten.compact.sort.uniq firmware = plugin_results.map { |x| x[:firmware] unless x[:firmware].class == Regexp }.flatten.compact.sort.uniq modules = plugin_results.map { |x| x[:module] unless x[:module].class == Regexp }.flatten.compact.sort.uniq filepath = plugin_results.map { |x| x[:filepath] unless x[:filepath].class == Regexp }.flatten.compact.sort.uniq thisplugin[:certainty] = certainty if !certainty.nil? && certainty != 100 thisplugin[:version] = version unless version.empty? thisplugin[:os] = os unless os.empty? thisplugin[:string] = string unless string.empty? thisplugin[:account] = accounts unless accounts.empty? thisplugin[:model] = model unless model.empty? thisplugin[:firmware] = firmware unless firmware.empty? thisplugin[:module] = modules unless modules.empty? thisplugin[:filepath] = filepath unless filepath.empty? # foo[:plugins] << thisplugin foo[:plugins][plugin_name.to_sym] = thisplugin end @charset = results.map { |n, r| r[0][:string] if n == 'Charset' }.compact.first if @charset.nil? || @charset == 'Failed' # could not find encoding force UTF-8 anyway Helper::utf8_elements!(foo) else Helper::utf8_elements!(foo) # convert foo to utf-8 flatten_elements!(foo) end url = URI('http://' + @host + '/' + @index + '/whatwebresult') req = Net::HTTP::Post.new(url) req.add_field('Content-Type', 'application/json') req.body = JSON.generate(foo) res = Net::HTTP.start(url.hostname, url.port) do |http| http.request(req) end case res when Net::HTTPSuccess # ok else error(res.code + ' ' + res.message + "\n" + res.body) end end end WhatWeb-0.5.5/lib/logging/errors.rb000066400000000000000000000015471400032546600171260ustar00rootroot00000000000000# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see . class LoggingErrors < Logging # don't need semaphore.synchronize, as it's locked by the error handling routine def out(error) @f.puts error end end WhatWeb-0.5.5/lib/logging/json.rb000066400000000000000000000065761400032546600165720ustar00rootroot00000000000000 # JSON Output # class LoggingJSON < Logging def initialize(f = STDOUT) super @is_first_log_entry = true # opening bracket @f.puts '[' end def close # closing bracket @f.puts ']' @f.close end def flatten_elements!(obj) if obj.class == Hash obj.each_value do |x| flatten_elements!(x) end end obj.flatten! if obj.class == Array end def out(target, status, results) # nice foo = { target: target.to_s, http_status: status, request_config: {}, plugins: {} } # request-config req_config = {} if $USE_PROXY req_config[:proxy] = { proxy_host: $PROXY_HOST, proxy_port: $PROXY_PORT } req_config[:proxy][:proxy_user] = $PROXY_USER if $PROXY_USER end req_config[:headers] = {} unless $CUSTOM_HEADERS.empty? $CUSTOM_HEADERS.each do |header_name, header_value| req_config[:headers][header_name] = header_value.dup end foo[:request_config] = req_config # plugins results.each do |plugin_name, plugin_results| # thisplugin = {name: plugin_name} thisplugin = {} next if plugin_results.empty? # important info in brief mode is version, type and ? # what's the highest probability for the match? certainty = plugin_results.map { |x| x[:certainty] unless x[:certainty].class == Regexp }.flatten.compact.sort.uniq.last version = plugin_results.map { |x| x[:version] unless x[:version].class == Regexp }.flatten.compact.sort.uniq os = plugin_results.map { |x| x[:os] unless x[:os].class == Regexp }.flatten.compact.sort.uniq string = plugin_results.map { |x| x[:string] unless x[:string].class == Regexp }.flatten.compact.sort.uniq accounts = plugin_results.map { |x| x[:account] unless x[:account].class == Regexp }.flatten.compact.sort.uniq model = plugin_results.map { |x| x[:model] unless x[:model].class == Regexp }.flatten.compact.sort.uniq firmware = plugin_results.map { |x| x[:firmware] unless x[:firmware].class == Regexp }.flatten.compact.sort.uniq modules = plugin_results.map { |x| x[:module] unless x[:module].class == Regexp }.flatten.compact.sort.uniq filepath = plugin_results.map { |x| x[:filepath] unless x[:filepath].class == Regexp }.flatten.compact.sort.uniq thisplugin[:certainty] = certainty if !certainty.nil? && certainty != 100 thisplugin[:version] = version unless version.empty? thisplugin[:os] = os unless os.empty? thisplugin[:string] = string unless string.empty? thisplugin[:account] = accounts unless accounts.empty? thisplugin[:model] = model unless model.empty? thisplugin[:firmware] = firmware unless firmware.empty? thisplugin[:module] = modules unless modules.empty? thisplugin[:filepath] = filepath unless filepath.empty? # foo[:plugins] << thisplugin foo[:plugins][plugin_name.to_sym] = thisplugin end @charset = results.map { |n, r| r[0][:string] if n == 'Charset' }.compact.first if @charset.nil? || @charset == 'Failed' # could not find encoding force UTF-8 anyway Helper::utf8_elements!(foo) else Helper::utf8_elements!(foo) # convert foo to utf-8 flatten_elements!(foo) end $semaphore.synchronize do unless @is_first_log_entry @f.puts "," else @is_first_log_entry = false end @f.puts JSON.generate(foo) end end end WhatWeb-0.5.5/lib/logging/jsonverbose.rb000066400000000000000000000003621400032546600201430ustar00rootroot00000000000000 # This is not JSON compliant as a list class LoggingJSONVerbose < Logging def out(target, status, results) # brutal and simple $semaphore.synchronize do @f.puts JSON.fast_generate([target, status, results]) end end end WhatWeb-0.5.5/lib/logging/magictreexml.rb000066400000000000000000000132671400032546600202750ustar00rootroot00000000000000 # MagicTree # # Output XML file in MagicTree XML format class LoggingMagicTreeXML < Logging def initialize(f = STDOUT) super @substitutions = { '&' => '&', '"' => '"', '<' => '<', '>' => '>' } # only output ' if @f.empty? @f.puts '' end def close @f.puts '' @f.close end def escape(t) text = t.to_s.dup # use sort_by so that & is before ", etc. @substitutions.sort_by { |a, _| a == '&' ? 0 : 1 }.map { |from, to| text.gsub!(from, to) } # Encode all special characters # More info: http://www.asciitable.com/ r = /[^\x20-\x5A\x5E-\x7E]/ # based on code for CGI.escape text.gsub!(r) { |x| "%#{x.unpack('H2' * x.size).join('%').upcase}" } text end def out(target, _status, results) $semaphore.synchronize do # Parse target URL and initialize host node details uri = URI.parse(target.to_s) @host_os = [] @host_port = uri.port @host_scheme = uri.scheme # Set host node details if uri.host =~ /^[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}$/i @host_ip = uri.host @host_name = nil else @host_name = uri.host @host_ip = nil end # Loop through plugin results # get host IP, country and OS results.each do |plugin_name, plugin_results| next if plugin_results.empty? # Host IP @host_ip = plugin_results.map { |x| x[:string] unless x[:string].nil? }.to_s if plugin_name =~ /^IP$/ # Host Country @host_country = plugin_results.map { |x| x[:string] unless x[:string].nil? }.to_s if plugin_name =~ /^Country$/ # Host OS @host_os << plugin_results.map { |x| x[:os] unless x[:os].class == Regexp }.to_s end # testdata branch @f.write "#{escape(@host_ip)}" # hostname @f.write "#{escape(@host_name)}" unless @host_name.nil? # os @host_os.compact.sort.uniq.map { |x| @f.write "#{escape(x.to_s)}" unless x.empty? } unless @host_os.empty? # country and port nodes @f.write "#{escape(@host_country)}tcp#{escape(@host_port)}open" # https @f.write 'ssl' if @host_scheme == 'https' # Service node # Loop through remaining results # software, headers, firmware, modules, etc. are all related to a specific URL and therefore are placed under the url node @f.puts 'http' results.each do |plugin_name, plugin_results| next unless !plugin_results.empty? && plugin_name !~ /^IP$/ && plugin_name !~ /^Country$/ certainty = plugin_results.map { |x| x[:certainty] unless x[:certainty].class == Regexp }.flatten.compact.sort.uniq.last versions = plugin_results.map { |x| x[:version] unless x[:version].class == Regexp }.flatten.compact.sort.uniq strings = plugin_results.map { |x| x[:string] unless x[:string].class == Regexp }.flatten.compact.sort.uniq models = plugin_results.map { |x| x[:model] unless x[:model].class == Regexp }.flatten.compact.sort.uniq firmwares = plugin_results.map { |x| x[:firmware] unless x[:firmware].class == Regexp }.flatten.compact.sort.uniq filepaths = plugin_results.map { |x| x[:filepath] unless x[:filepath].class == Regexp }.flatten.compact.sort.uniq accounts = plugin_results.map { |x| x[:account] unless x[:account].class == Regexp }.flatten.compact.sort.uniq modules = plugin_results.map { |x| x[:module] unless x[:module].class == Regexp }.flatten.compact.sort.uniq # URL node # plugin node @f.write "#{escape(target)}<#{escape(plugin_name)}>" # Print certainty if certainty < 100 if certainty && certainty < 100 @f.write "#{escape(certainty)}" end # Strings unless strings.empty? strings.map { |x| @f.write escape(x).to_s } unless plugin_name =~ /^IP$/ || plugin_name =~ /^Country$/ end # Versions unless versions.empty? versions.map { |x| @f.write "#{escape(x)}" } end # Models unless models.empty? models.map { |x| @f.puts "#{escape(x)}" } end # Firmware unless firmwares.empty? firmwares.map { |x| @f.write "#{escape(x)}" } end # Modules unless modules.empty? modules.map { |x| @f.write "#{escape(x)}" } unless plugin_name =~ /^Country$/ end # Accounts # MagicTree generally uses "user" nodes for account unless accounts.empty? accounts.map { |x| @f.write "#{escape(x)}" } end # Local File Filepaths # Not to be confused with file paths in the web root which are returned in Strings unless filepaths.empty? filepaths.map { |x| @f.write "#{escape(x)}" } end # debug node # Uncomment to debug # @f.write "Identifying: #{escape(target)}\nHTTP-Status: #{escape(status)}" # @f.write "#{escape(results.pretty_inspect)}" unless results.empty? # @f.write "" # Close plugin name and URL nodes @f.write "" end @f.write '' # end https node @f.write '' if @host_scheme == 'https' # testdata # close port, host and testdata nodes @f.write '' end end end WhatWeb-0.5.5/lib/logging/mongodb.rb000066400000000000000000000071651400032546600172410ustar00rootroot00000000000000 # basically the same as OutputJSON class LoggingMongo < Logging def initialize(s) host = s[:host] || '0.0.0.0' database = s[:database] || raise('Missing MongoDB database name') collection = s[:collection] || 'whatweb' options = { database: database } if s[:username] options[:user] = s[:username] options[:password] = s[:password] end if ! $WWDEBUG # Default is DEBUG-level logging Mongo::Logger.logger.level = Logger::FATAL end @db = Mongo::Client.new([host], options) @coll = @db[collection] @charset = nil end def close @db.close end def flatten_elements!(obj) if obj.class == Hash obj.each_value do |x| flatten_elements!(x) end end obj.flatten! if obj.class == Array end def out(target, status, results) # nice foo = { target: target.to_s, http_status: status, request_config: {}, plugins: {} } # request-config req_config = {} if $USE_PROXY req_config[:proxy] = { proxy_host: $PROXY_HOST, proxy_port: $PROXY_PORT } req_config[:proxy][:proxy_user] = $PROXY_USER if $PROXY_USER end req_config[:headers] = {} unless $CUSTOM_HEADERS.empty? $CUSTOM_HEADERS.each do |header_name, header_value| req_config[:headers][header_name] = header_value.dup end foo[:request_config] = req_config results.each do |plugin_name, plugin_results| # thisplugin = {name: plugin_name} thisplugin = {} next if plugin_results.empty? # important info in brief mode is version, type and ? # what's the highest probability for the match? certainty = plugin_results.map { |x| x[:certainty] unless x[:certainty].class == Regexp }.compact.sort.uniq.last version = plugin_results.map { |x| x[:version] unless x[:version].class == Regexp }.flatten.compact.sort.uniq os = plugin_results.map { |x| x[:os] unless x[:os].class == Regexp }.flatten.compact.sort.uniq string = plugin_results.map { |x| x[:string] unless x[:string].class == Regexp }.flatten.compact.sort.uniq accounts = plugin_results.map { |x| x[:account] unless x[:account].class == Regexp }.flatten.compact.sort.uniq model = plugin_results.map { |x| x[:model] unless x[:model].class == Regexp }.flatten.compact.sort.uniq firmware = plugin_results.map { |x| x[:firmware] unless x[:firmware].class == Regexp }.flatten.compact.sort.uniq modules = plugin_results.map { |x| x[:module] unless x[:module].class == Regexp }.flatten.compact.sort.uniq filepath = plugin_results.map { |x| x[:filepath] unless x[:filepath].class == Regexp }.flatten.compact.sort.uniq thisplugin[:certainty] = certainty if !certainty.nil? && certainty != 100 thisplugin[:version] = version unless version.empty? thisplugin[:os] = os unless os.empty? thisplugin[:string] = string unless string.empty? thisplugin[:account] = accounts unless accounts.empty? thisplugin[:model] = model unless model.empty? thisplugin[:firmware] = firmware unless firmware.empty? thisplugin[:module] = modules unless modules.empty? thisplugin[:filepath] = filepath unless filepath.empty? # foo[:plugins] << thisplugin foo[:plugins][plugin_name.to_sym] = thisplugin end @charset = results.map { |n, r| r[0][:string] if n == 'Charset' }.compact.first if @charset.nil? || @charset == 'Failed' error("#{target}: Failed to detect Character set and log to MongoDB") else Helper::utf8_elements!(foo) # convert foo to utf-8 flatten_elements!(foo) @coll.replace_one({ target: foo[:target]}, foo, { upsert: true}) end end end WhatWeb-0.5.5/lib/logging/object.rb000066400000000000000000000004101400032546600170440ustar00rootroot00000000000000class LoggingObject < Logging def out(target, status, results) $semaphore.synchronize do @f.puts "Identifying: #{target}" @f.puts "HTTP-Status: #{status}" @f.puts results.pretty_inspect unless results.empty? @f.puts end end end WhatWeb-0.5.5/lib/logging/sql.rb000066400000000000000000000136271400032546600164130ustar00rootroot00000000000000 class LoggingSQL < Logging def initialize(f = STDOUT) super insert_request_config end def insert_request_config # the config doesn't change between targets foo = {} req_config = {} # when this is called by OutputSQL.new there won't be a user-agent set # $CUSTOM_HEADERS["User-Agent"] isn't set until after the command option loop $CUSTOM_HEADERS['User-Agent'] = $USER_AGENT unless $CUSTOM_HEADERS['User-Agent'] if $USE_PROXY req_config[:proxy] = { proxy_host: $PROXY_HOST, proxy_port: $PROXY_PORT } req_config[:proxy][:proxy_user] = $PROXY_USER if $PROXY_USER end req_config[:headers] = {} unless $CUSTOM_HEADERS.empty? $CUSTOM_HEADERS.each do |header_name, header_value| req_config[:headers][header_name] = header_value.dup end foo[:request_config] = req_config flatten_elements!(foo) # pp foo[:request_config] insert = [escape_for_sql(JSON.dump(foo[:request_config]))].join(',') query = "INSERT IGNORE INTO request_configs (value) VALUES (#{insert});" @f.puts query ## end def flatten_elements!(obj) if obj.class == Hash obj.each_value { |x| flatten_elements!(x) } elsif obj.class == Array obj.flatten! end end def escape_for_sql(s) s = s.to_s "'" + s.gsub("'"){"\\'"} + "'" end def create_tables # Note that you may encounter the error "1709 - Index column size too large. The maximum column size is 767 bytes." # when using MySQL <= 5.6 with the innodb engine and the utf8mb4 character set # # max_hostname_length = 253 # max_uri_prefix = 10 # covers https:// # max_url_length = 2048 # old IE limit max_target_length = 2048 # feel free to modify this @f.puts 'CREATE TABLE plugins (plugin_id int NOT NULL AUTO_INCREMENT, name varchar(255) NOT NULL,PRIMARY KEY (plugin_id), UNIQUE (name));' @f.puts "CREATE TABLE targets (target_id int NOT NULL AUTO_INCREMENT, target varchar(#{max_target_length}) NOT NULL, status varchar(10),PRIMARY KEY (target_id), UNIQUE (target, status) );" @f.puts 'CREATE TABLE scans (scan_id int NOT NULL AUTO_INCREMENT, config_id INT NOT NULL, plugin_id INT NOT NULL, target_id INT NOT NULL, version varchar(255), os varchar(255), string varchar(1024), account varchar(1024), model varchar(1024), firmware varchar(1024), module varchar(1024), filepath varchar(1024), certainty varchar(10) ,PRIMARY KEY (scan_id));' @f.puts 'CREATE TABLE request_configs (config_id int NOT NULL AUTO_INCREMENT, value TEXT NOT NULL, PRIMARY KEY (config_id) );' # plugins table @f.puts "INSERT INTO plugins (name) VALUES ('Custom-Plugin');" @f.puts "INSERT INTO plugins (name) VALUES ('Grep');" Plugin.registered_plugins.each do |n, _| @f.puts "INSERT INTO plugins (name) VALUES (#{escape_for_sql(n)});" end end def out(target, status, results) # nice foo = { target: target, http_status: status, plugins: {}, request_config: {} } results.each do |plugin_name, plugin_results| thisplugin = {} next if plugin_results.empty? # important info in brief mode is version, type and ? # what's the highest probability for the match? certainty = plugin_results.map { |x| x[:certainty] unless x[:certainty].class == Regexp }.flatten.compact.sort.uniq.last version = plugin_results.map { |x| x[:version] unless x[:version].class == Regexp }.flatten.compact.sort.uniq os = plugin_results.map { |x| x[:os] unless x[:os].class == Regexp }.flatten.compact.sort.uniq string = plugin_results.map { |x| x[:string] unless x[:string].class == Regexp }.flatten.compact.sort.uniq accounts = plugin_results.map { |x| x[:account] unless x[:account].class == Regexp }.flatten.compact.sort.uniq model = plugin_results.map { |x| x[:model] unless x[:model].class == Regexp }.flatten.compact.sort.uniq firmware = plugin_results.map { |x| x[:firmware] unless x[:firmware].class == Regexp }.flatten.compact.sort.uniq modules = plugin_results.map { |x| x[:module] unless x[:module].class == Regexp }.flatten.compact.sort.uniq filepath = plugin_results.map { |x| x[:filepath] unless x[:filepath].class == Regexp }.flatten.compact.sort.uniq thisplugin[:certainty] = certainty if !certainty.nil? && certainty != 100 # empty arrays thisplugin[:version] = version.empty? ? [] : version thisplugin[:os] = os.empty? ? [] : os thisplugin[:string] = string.empty? ? [] : string thisplugin[:account] = accounts.empty? ? [] : accounts thisplugin[:model] = model.empty? ? [] : model thisplugin[:firmware] = firmware.empty? ? [] : firmware thisplugin[:module] = modules.empty? ? [] : modules thisplugin[:filepath] = filepath.empty? ? [] : filepath foo[:plugins][plugin_name.to_sym] = thisplugin end flatten_elements!(foo) i_target = escape_for_sql(foo[:target]) insert = [escape_for_sql(foo[:http_status]), i_target].join(',') query = "INSERT IGNORE INTO targets (status,target) VALUES (#{insert});" @f.puts query foo[:plugins].each do |x| plugin_name = escape_for_sql(x.first.to_s) insert = [escape_for_sql(x[1][:version].join(',').to_s), escape_for_sql(x[1][:os].join(',').to_s), escape_for_sql(x[1][:string].join(',').to_s), escape_for_sql(x[1][:account].join(',').to_s), escape_for_sql(x[1][:model].join(',').to_s), escape_for_sql(x[1][:firmware].join(',').to_s), escape_for_sql(x[1][:module].join(',').to_s), escape_for_sql(x[1][:filepath].join(',').to_s), escape_for_sql(x[1][:certainty].to_s)].join(',') query = "INSERT INTO scans (target_id, config_id, plugin_id, version, os, string, account, model, firmware, module, filepath, certainty) VALUES ( (SELECT target_id from targets WHERE target = #{i_target}),(SELECT MAX(config_id) from request_configs),(SELECT plugin_id from plugins WHERE name = #{plugin_name}), #{insert} );" @f.puts query end end end WhatWeb-0.5.5/lib/logging/verbose.rb000066400000000000000000000174351400032546600172620ustar00rootroot00000000000000 class LoggingVerbose < Logging def coloured(s, colour) use_colour = ((@f == STDOUT && $use_colour == 'auto') || ($use_colour == 'always')) if use_colour send colour, s else s end end def out(target, status, results) $semaphore.synchronize do # make a hash of the matches array results_hash = {} results.map { |k, v| results_hash[k] = v } display = { title: '', ip: '', country: '', status: '' } display[:country] = results_hash['Country'].map { |r| "#{r[:string]}, #{r[:module]}" }.join(',') if results_hash['Country'] display[:ip] = results_hash['IP'].map { |r| r[:string] }.join(',') if results_hash['Country'] display[:title] = results_hash['Title'].map { |r| r[:string] }.join(',') if results_hash['Title'] display[:status] = status.to_s + ' ' + HTTP_Status.code(status) @f.puts "WhatWeb report for #{coloured(target, 'blue')}" @f.puts 'Status'.ljust(9) + ' : ' + display[:status] @f.puts 'Title'.ljust(9) + " : #{coloured(display[:title], 'yellow')}" @f.puts 'IP'.ljust(9) + ' : ' + display[:ip] @f.puts 'Country'.ljust(9) + " : #{coloured(display[:country], 'red')}" @f.puts ################### Short list ################### Basically Output Brief brief_results = [] results.each do |plugin_name, plugin_results| next if %w(Title IP Country).include? plugin_name next if plugin_results.empty? suj = suj(plugin_results) certainty = suj[:certainty].to_i version = suj[:version] os = suj[:os] string = suj[:string] accounts = suj[:account] model = suj[:model] firmware = suj[:firmware] modules = suj[:module] filepath = suj[:filepath] # colour the output # be more DRY # if plugins have categories or tags this would be better, eg. all hash plugins are grey if (@f == STDOUT && $use_colour == 'auto') || ($use_colour == 'always') coloured_string = grey(string) coloured_string = cyan(string) if plugin_name == 'HTTPServer' coloured_string = yellow(string) if plugin_name == 'Title' coloured_string = grey(string) if plugin_name == 'MD5' coloured_string = grey(string) if plugin_name == 'Header-Hash' coloured_string = grey(string) if plugin_name == 'Footer-Hash' coloured_string = grey(string) if plugin_name == 'CSS' coloured_string = grey(string) if plugin_name == 'Tag-Hash' coloured_plugin = white(plugin_name) coloured_plugin = grey(plugin_name) if plugin_name == 'MD5' coloured_plugin = grey(plugin_name) if plugin_name == 'Header-Hash' coloured_plugin = grey(plugin_name) if plugin_name == 'Footer-Hash' coloured_plugin = grey(plugin_name) if plugin_name == 'CSS' coloured_plugin = grey(plugin_name) if plugin_name == 'Tag-Hash' p = ((certainty && certainty < 100) ? "#{grey(Helper::certainty_to_words(certainty))} " : '') + coloured_plugin + (!version.empty? ? "[#{green(version)}]" : '') + (!os.empty? ? "[#{red(os)}]" : '') + (!string.empty? ? "[#{coloured_string}]" : '') + (!accounts.empty? ? "[#{cyan(accounts)}]" : '') + (!model.empty? ? "[#{dark_green(model)}]" : '') + (!firmware.empty? ? "[#{dark_green(firmware)}]" : '') + (!filepath.empty? ? "[#{dark_green(filepath)}]" : '') + (!modules.empty? ? "[#{magenta(modules)}]" : '') brief_results << p else brief_results << ((certainty && certainty < 100) ? "#{Helper::certainty_to_words(certainty)} " : '') + plugin_name + (!version.empty? ? "[#{version}]" : '') + (!os.empty? ? "[#{os}]" : '') + (!string.empty? ? "[#{string}]" : '') + (!accounts.empty? ? " [#{accounts}]" : '') + (!model.empty? ? "[#{model}]" : '') + (!firmware.empty? ? "[#{firmware}]" : '') + (!filepath.empty? ? "[#{filepath}]" : '') + (!modules.empty? ? "[#{modules}]" : '') end end brief_results_final = brief_results.join(', ') @f.puts 'Summary'.ljust(9) + ' : ' + brief_results_final @f.puts @f.puts 'Detected Plugins:' results.sort.each do |plugin_name, plugin_results| next if %w(Title IP Country).include?(plugin_name) next if plugin_results.empty? @f.puts "[ #{coloured(plugin_name, 'white')} ]" description = [''] if Plugin.registered_plugins[plugin_name].description d = Plugin.registered_plugins[plugin_name].description description = Helper::word_wrap(d, 60) end # @f.puts "\tCategory : " + Plugin.registered_plugins[plugin_name].category.first unless Plugin.registered_plugins[plugin_name].category.nil? @f.puts "\t" + description.first description[1..-1].each { |line| @f.puts "\t" + line } @f.puts top_certainty = suj(plugin_results)[:certainty].to_i unless top_certainty == 100 @f.puts "\t" + 'Certainty'.ljust(13) + ': ' + Helper::certainty_to_words(top_certainty) end plugin_results.map { |x| sortuniq(x) }.each do |pr| if pr[:name] name_of_match = pr[:name] else name_of_match = [pr[:regexp_compiled], pr[:text], pr[:regexp].to_s, pr[:ghdb], pr[:md5], pr[:tagpattern]].compact.join('|') end pr.each do |key, value| next unless [:version, :os, :string, :account, :model, :firmware, :module, :filepath, :url].include?(key) next if value.class == Regexp if key == :os @f.print "\t" + 'OS'.ljust(13) + ': ' else @f.print "\t" + key.to_s.capitalize.ljust(13) + ': ' end c = case key when :version then 'green' when :string then 'cyan' when :certainty then 'grey' when :os then 'red' when :account then 'cyan' when :model then 'dark_green' when :firmware then 'dark_green' when :module then 'magenta' when :filepath then 'dark_green' else 'grey' end if value.is_a?(String) @f.print coloured(value.to_s, c) elsif value.is_a?(Array) @f.print coloured(value.join(',').to_s, c) else @f.print coloured(value.inspect, c) end @f.print " (from #{name_of_match})" unless name_of_match.empty? unless pr[:certainty] == 100 @f.print " (Certainty: #{ertainty_to_words pr[:certainty]} )" end @f.puts end @f.puts "\t" + coloured(pr.inspect.to_s, 'dark_blue') if $verbose > 1 end if Plugin.registered_plugins[plugin_name].aggressive @f.puts "\tAggressive function available (check plugin file or details)." end if Plugin.registered_plugins[plugin_name].dorks.any? @f.puts "\tGoogle Dorks".ljust(13) + ": (#{Plugin.registered_plugins[plugin_name].dorks.size})" end if Plugin.registered_plugins[plugin_name].website @f.puts "\tWebsite".ljust(13) + ": #{Plugin.registered_plugins[plugin_name].website}" end @f.puts end @f.puts 'HTTP Headers:' target.raw_headers.each_line do |header| @f.puts "\t#{header}" # pp target.raw_headers end end end end WhatWeb-0.5.5/lib/logging/xml.rb000066400000000000000000000103061400032546600164030ustar00rootroot00000000000000 # XML Output # # Does anyone use XML output? # We'd love to hear any suggestions you may have! # Does it bother you that some types of output are joined by commas # but other types aren't? class LoggingXML < Logging def initialize(f = STDOUT) super @substitutions = { '&' => '&', '"' => '"', '<' => '<', '>' => '>' } # only output ' end @f.puts '' end def close @f.puts '' @f.close end def escape(t) text = t.to_s.dup # use sort_by so that & is before ", etc. @substitutions.sort_by { |a, _| a == '&' ? 0 : 1 }.map { |from, to| text.gsub!(from, to) } # Encode all special characters # More info: http://www.asciitable.com/ r = /[^\x20-\x5A\x5E-\x7E]/ # based on code for CGI.escape text.gsub!(r) { |x| "%#{x.unpack('H2' * x.size).join('%').upcase}" } text end def out(target, status, results) $semaphore.synchronize do @f.puts '' @f.puts "\t#{escape(target)}" @f.puts "\t#{escape(status)}" @f.puts "\t" @f.puts "\t\t" if $USE_PROXY @f.puts "\t\t\t#{escape($PROXY_HOST)}:#{escape($PROXY_PORT)}" if $PROXY_HOST && $PROXY_PORT @f.puts "\t\t\t#{escape($PROXY_USER)}" if $PROXY_USER @f.puts "\t\t" if $USE_PROXY $CUSTOM_HEADERS.each do |header_name, header_value| @f.puts "\t\t
" @f.puts "\t\t\t#{escape(header_name)}" @f.puts "\t\t\t#{escape(header_value)}" @f.puts "\t\t
" end @f.puts "\t
" results.each do |plugin_name, plugin_results| @f.puts "\t" @f.puts "\t\t#{escape(plugin_name)}" unless plugin_results.empty? # important info in brief mode is version, type and ? # what's the highest probability for the match? certainty = plugin_results.map do |x| x[:certainty] unless x[:certainty].class == Regexp end.flatten.compact.sort.uniq.last version = plugin_results.map do |x| x[:version] unless x[:version].class == Regexp end.flatten.compact.sort.uniq os = plugin_results.map do |x| x[:os] unless x[:os].class == Regexp end.flatten.compact.sort.uniq string = plugin_results.map do |x| x[:string] unless x[:string].class == Regexp end.flatten.compact.sort.uniq model = plugin_results.map do |x| x[:model] unless x[:model].class == Regexp end.flatten.compact.sort.uniq firmware = plugin_results.map do |x| x[:firmware] unless x[:firmware].class == Regexp end.flatten.compact.sort.uniq filepath = plugin_results.map do |x| x[:filepath] unless x[:filepath].class == Regexp end.flatten.compact.sort.uniq account = plugin_results.map do |x| x[:account] unless x[:account].class == Regexp end.flatten.compact.sort.uniq modules = plugin_results.map do |x| x[:module] unless x[:module].class == Regexp end.flatten.compact.sort.uniq # Output results @f.puts "\t\t#{escape(certainty)}" if certainty && certainty < 100 version.map { |x| @f.puts "\t\t#{escape(x)}" } os.map { |x| @f.puts "\t\t#{escape(x)}" } string.map { |x| @f.puts "\t\t#{escape(x)}" } model.map { |x| @f.puts "\t\t#{escape(x)}" } firmware.map { |x| @f.puts "\t\t#{escape(x)}" } filepath.map { |x| @f.puts "\t\t#{escape(x)}" } account.map { |x| @f.puts "\t\t#{escape(x)}" } modules.map { |x| @f.puts "\t\t#{escape(x)}" } end @f.puts "\t" end @f.puts '
' end end end WhatWeb-0.5.5/lib/plugin_support.rb000066400000000000000000000277771400032546600172730ustar00rootroot00000000000000# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see . # this class contains stuff related to plugins but not necessary to repeat in each plugin we create class PluginSupport # this is used by load_plugins def self.load_plugin(f) load f rescue ArgumentError => err if err.message =~ /wrong number of arguments \(given 1, expected 0\)/ # this error can be caused by other reasons error("Error loading plugin #{f}. This plugin may be using a deprecated plugin format for WhatWeb version < 0.5.0. Error message: #{err.message}") end raise if $WWDEBUG == true rescue SyntaxError => err error("Error loading plugin #{f}. Error details: #{err.message}") raise if $WWDEBUG == true rescue Interrupt error("Interrupt detected. Failed to load plugin #{f}.") raise if $WWDEBUG == true exit 1 # exit with ctrl-c end # precompile regular expressions in plugins for performance def self.precompile_regular_expressions Plugin.registered_plugins.each do |thisplugin| matches = thisplugin[1].matches next if matches.nil? matches.each do |thismatch| unless thismatch[:regexp].nil? # pp thismatch thismatch[:regexp_compiled] = Regexp.new(thismatch[:regexp]) end [:version, :os, :string, :account, :model, :firmware, :module, :filepath].each do |label| if !thismatch[label].nil? && thismatch[label].class == Regexp thismatch[:regexp_compiled] = Regexp.new(thismatch[label]) # pp thismatch end end unless thismatch[:text].nil? thismatch[:regexp_compiled] = Regexp.new(Regexp.escape(thismatch[:text])) end end end end # for adding/removing sets of plugins. # # --plugins +plugins-disabled,-foobar (+ adds to the full set, -removes from the fullset. items can be directories, files or plugin names) # --plugins +/tmp/moo.rb # --plugins foobar (only select foobar) # --plugins ./plugins-disabled,-md5 (select only plugins from the plugins-disabled folder, remove the md5 plugin from the selected list) # # does not work correctly with mixed plugin names and files # def self.load_plugins(list = nil) a = [] b = [] plugin_dirs = PLUGIN_DIRS.clone plugin_dirs.map { |p| p = File.expand_path(p) } # no selection, so it's default unless list plugin_dirs.each do |d| Dir.glob("#{d}/*.rb").each { |x| PluginSupport.load_plugin(x) } end return Plugin.registered_plugins end # separate list into a and b # a = make list of dir & filenames # b = make list of assumed pluginnames list.split(',').each do |p| choice = PluginChoice.new choice.fill(p) a << choice if choice.type == 'file' b << choice if choice.type == 'plugin' end # puts "a: list of dir + filenames" # pp a # puts "b: list of plugin names" # pp b # puts "Plugin Dirs" # pp plugin_dirs # sort by neither, add, minus a = a.sort # plugin_dirs gets wiped out if no modifier is used on a file/folder plugin_dirs = [] if a.map(&:modifier).include?(nil) minus_files = [] # make list of files not to load a.map do |c| plugin_dirs << c.name if c.modifier.nil? || c.modifier == '+' plugin_dirs -= [c.name] if c.modifier == '-' # for Dirs minus_files << c.name if c.modifier == '-' # for files end # puts "Plugin Dirs" # pp plugin_dirs # puts "before plugin_dirs.each " # pp Plugin.registered_plugins.size # load files from plugin_dirs unless a file is minused plugin_dirs.each do |d| # if a folder, then load all files if File.directory?(d) (Dir.glob("#{d}/*.rb") - minus_files).each { |x| PluginSupport.load_plugin(x) } elsif File.exist?(d) PluginSupport.load_plugin(d) else error("Error: #{d} is not Dir or File") end end # puts "after plugin_dirs.each " # pp Plugin.registered_plugins.size # make list of plugins to run # go through all plugins, remove from list any that match b minus selected_plugin_names = [] if b.map(&:modifier).include?(nil) selected_plugin_names = [] else selected_plugin_names = Plugin.registered_plugins.map { |n, _p| n.downcase } end b.map do |c| selected_plugin_names << c.name if c.modifier.nil? || c.modifier == '+' selected_plugin_names -= [c.name] if c.modifier == '-' end # pp selected_plugin_names # Plugin.registered_plugins is getting wiped out plugins_to_use = Plugin.registered_plugins.map do |n, p| [n, p] if selected_plugin_names.include?(n.downcase) end.compact # puts "after " # report on plugins that couldn't be found unfound_plugins = selected_plugin_names - plugins_to_use.map { |n, _p| n.downcase } unless unfound_plugins.empty? puts 'Error: The following plugins were not found: ' + unfound_plugins.join(',') end # puts "-" * 80 # pp plugins_to_use plugins_to_use end def self.custom_plugin(c, *option) if option == ['grep'] plugin_name = 'Grep' matches = if c[0] == '/' && c[-1] == '/' # it's a regex. this might cause some issues? "matches([:regexp=>/#{c[1..-2]}/])" else "matches([:text=>\"#{c}\"])" end else # define a custom plugin on the cmdline # ":text=>'powered by abc'" or # "{:text=>'powered by abc'},{:regexp=>/abc [ ]?1/i}" # then it's ok.. if c =~ /:(text|ghdb|md5|regexp|tagpattern)=>[\/'"].*/ matches = "matches([\{#{c}\}])" end # this isn't checked for sanity... loading plugins = cmd exec anyway matches = "matches([#{c}])" if c =~ /\{.*\}/ plugin_name = 'Custom-Plugin' abort("Invalid custom plugin syntax: #{c}") if matches.nil? end custom = %( Plugin.define do name "#{plugin_name}" authors ["WhatWeb"] description "User defined" website "User defined" #{matches} end ) begin pp custom if $verbose > 2 eval(custom) true rescue SyntaxError error('Error: Cannot load custom plugin') false end end ### some UI stuff def self.plugin_list terminal_width = 80 puts 'WhatWeb Plugin List' puts puts "Plugin" + " " * 24 + "Website" puts '-' * terminal_width Plugin.registered_plugins.sort_by { |a, _b| a.downcase }.each do |n, p| next if n == "?" # skip the Easter Egg plugin # output fits more description onto a line line = "#{n}" spaces = terminal_width - 50 - n.size spaces = 1 if spaces <= 0 line += " " * spaces line += p.website if p.website puts line end puts '-' * terminal_width puts puts "Total: #{Plugin.registered_plugins.size} Plugins" puts puts 'Hint:' puts 'For complete plugin descriptions use : whatweb --info-plugins ' puts 'Use it without a search term for a complete description of all plugins.' puts end # Show Google Dorks def self.plugin_dorks(plugin_name) dorks = [] # Loop through plugins Plugin.registered_plugins.each do |n, p| if n.casecmp(plugin_name.downcase).zero? pp "Google Dorks for #{n}:" if $verbose > 2 dorks << p.dorks unless p.dorks.empty? end end # Show results if present, else show error message if !dorks.empty? puts dorks else error('Google dork lookup failed: Invalid plugin name or no dorks available') end end # Show plugin information def self.plugin_info(keywords = nil) terminal_width = 80 puts 'WhatWeb Detailed Plugin List' puts 'Searching for ' + keywords.join(',') unless keywords.empty? count = { plugins: 0, version_detection: 0, matches: 0, dorks: 0, aggressive: 0, passive: 0 } Plugin.registered_plugins.sort_by { |a, _b| a.downcase }.each do |name, plugin| next if name == "?" # skip the Easter Egg plugin dump = [name, plugin.authors, plugin.description, plugin.website, plugin.matches].flatten.compact.to_a.join.downcase # this will fail is an expected variable is not defined or empty next unless keywords.empty? || keywords.map { |k| dump.include?(k.downcase) }.compact.include?(true) puts '=' * terminal_width puts 'Plugin:'.ljust(16) + name puts '-' * terminal_width if plugin.description Helper::word_wrap(plugin.description, terminal_width - 16).each_with_index do |line, index| if index == 0 print 'Description:'.ljust(16) else print ' ' * 16 end puts line end else puts 'Description:'.ljust(16) + '' end puts 'Website:'.ljust(16) + (plugin.website || '') puts authors = (if plugin.authors.empty? '' else plugin.authors.join(', ') end) puts 'Authors:'.ljust(16) + authors puts 'Version:'.ljust(16) + (plugin.version || '') puts print 'Features:'.ljust(16) print "[#{plugin.matches.any? ? 'Yes' : 'No'}]".ljust(7) + 'Pattern Matching' if plugin.matches.any? puts " (#{plugin.matches.size})" else puts end puts ' ' * 16 + "[#{plugin.version_detection? ? 'Yes' : 'No'}]".ljust(7) + 'Version detection from pattern matching' puts ' ' * 16 + "[#{plugin.passive ? 'Yes' : 'No'}]".ljust(7) + 'Function for passive matches' puts ' ' * 16 + "[#{plugin.aggressive ? 'Yes' : 'No'}]".ljust(7) + 'Function for aggressive matches' count[:version_detection] += 1 if plugin.version_detection? count[:passive] += 1 if plugin.passive count[:aggressive] += 1 if plugin.aggressive print ' ' * 16 + "[#{plugin.dorks.any? ? 'Yes' : 'No'}]".ljust(7) + 'Google Dorks' if plugin.dorks.empty? puts else puts " (#{plugin.dorks.size})" end puts unless plugin.dorks.empty? puts 'Google Dorks:' plugin.dorks.each_with_index do |dork, index| puts "[#{index + 1}] #{dork}" end puts count[:dorks] += plugin.dorks.size end count[:matches] += plugin.matches.size if plugin.matches count[:plugins] += 1 end puts '=' * terminal_width puts "Total plugins: #{count[:plugins]}" puts "Total plugins with version detection from pattern matching: #{count[:version_detection]}" puts "Total patterns (regular expressions, text, MD5 hashes, etc): #{count[:matches]}" puts "Total Google dorks: #{count[:dorks]}" puts "Total aggressive functions: #{count[:aggressive]}" puts "Total passive functions: #{count[:passive]}" puts end end # This is used in plugin selection by load_plugins class PluginChoice attr_accessor :modifier, :type, :name def <=>(_s) x = -1 if modifier.nil? x = 0 if modifier == '+' x = 1 if modifier == '-' x end def fill(s) self.modifier = nil self.modifier = s[0].chr if ['+', '-'].include?(s[0].chr) self.name = if modifier s[1..-1] else s end # figure out and store the filename or pluginname if File.exist?(File.expand_path(name)) self.type = 'file' self.name = File.expand_path(name) else name.downcase! self.type = 'plugin' end end end WhatWeb-0.5.5/lib/plugins.rb000066400000000000000000000176461400032546600156540ustar00rootroot00000000000000# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License along with WhatWeb. If not, see . class Plugin class << self attr_reader :registered_plugins, :attributes private :new end @registered_plugins = {} @attributes = %i( aggressive authors description dorks matches name passive version website ) @attributes.each do |symbol| define_method(symbol) do |*value, &block| name = "@#{symbol}" if block instance_variable_set(name, block) elsif !value.empty? instance_variable_set(name, *value) else instance_variable_get(name) end end end def initialize @matches = [] @dorks = [] @passive = nil @aggressive = nil @variables = {} @website = nil end def self.define(&block) # TODO: plugins should isolated p = new p.instance_eval(&block) p.startup # TODO: make sure required attributes are set Plugin.attributes.each { |symbol| p.instance_variable_get("@#{symbol}").freeze } Plugin.registered_plugins[p.name] = p end def self.shutdown_all Plugin.registered_plugins.each { |_, plugin| plugin.shutdown } end def version_detection? return false unless @matches !@matches.map { |m| m[:version] }.compact.empty? end # individual plugins can override this def startup; end # individual plugins can override this def shutdown; end def scan(target) scan_context = ScanContext.new(plugin: self, target: target, scanner: nil) scan_context.instance_variable_set(:@variables, @variables) scan_context.x end end class ScanContext def initialize(plugin: nil, target: nil, scanner: nil) @plugin = plugin @matches = plugin.matches define_singleton_method(:passive_scan, plugin.passive) if plugin.passive define_singleton_method(:aggressive_scan, plugin.aggressive) if plugin.aggressive @target = target @body = target.body @headers = target.headers @status = target.status @base_uri = target.uri @md5sum = target.md5sum @tagpattern = target.tag_pattern @ip = target.ip @raw_response = target.raw_response @raw_headers = target.raw_headers @scanner = scanner end def make_matches(target, match) r = [] # search location search_context = target.body # by default if match[:search] case match[:search] when 'all' search_context = target.raw_response when 'headers' search_context = target.raw_headers when /headers\[(.*)\]/ header = Regexp.last_match(1).downcase if target.headers[header] search_context = target.headers[header] else # error "Invalid search context :search => #{match[:search]}" return r end end end if match[:ghdb] r << match if match_ghdb(match[:ghdb], target.body, target.headers, target.status, target.uri) end if match[:text] r << match if match[:regexp_compiled] =~ search_context end if match[:md5] r << match if target.md5sum == match[:md5] end if match[:tagpattern] r << match if target.tag_pattern == match[:tagpattern] end if match[:regexp_compiled] && search_context [:regexp, :account, :version, :os, :module, :model, :string, :firmware, :filepath].each do |symbol| next unless match[symbol] && match[symbol].class == Regexp regexpmatch = search_context.scan(match[:regexp_compiled]) next if regexpmatch.empty? m = match.dup m[symbol] = regexpmatch.map do |eachmatch| if eachmatch.is_a?(Array) && match[:offset] eachmatch[match[:offset]] elsif eachmatch.is_a?(Array) eachmatch.first elsif eachmatch.is_a?(String) eachmatch end end.flatten.compact.sort.uniq r << m end end # all previous matches are OR # these are ARE. e.g. required if present return r if r.empty? # if url and status are present, they must both match # url and status cannot be alone. there must be something else that has already matched url_matched = false status_matched = false if match[:status] status_matched = true if match[:status] == target.status end if match[:url] # url is not relative if :url starts with / # url is relative if :url starts with [^/] # url query is only checked if :url has a ? # {:url="edit?action=stop" } will only match if the end of the path and the entire query matches. # :url is for URIs not regexes is_relative = if match[:url] =~ /^\// false else true end has_query = if match[:url] =~ /\?/ true else false end if is_relative && !has_query url_matched = true if target.uri.path =~ /#{match[:url]}$/ end if is_relative && has_query if target.uri.query url_matched = true if "#{target.uri.path}?#{target.uri.query}" =~ /#{match[:url]}$/ end end if !is_relative && has_query if target.uri.query url_matched = true if "#{target.uri.path}?#{target.uri.query}" == match[:url] end end if !is_relative && !has_query url_matched = true if target.uri.path == match[:url] end end # determine whether to return a match if match[:status] && match[:url] if url_matched && status_matched r << match else r = [] end elsif match[:status] && match[:url].nil? if status_matched r << match else r = [] end elsif !match[:status] && match[:url] if url_matched r << match else r = [] end elsif !match[:status] && !match[:url] # nothing to do end r end # execute plugin def x results = [] unless @matches.nil? @matches.each do |match| results += make_matches(@target, match) end end # if the plugin has a passive method, use it results += passive_scan if @plugin.passive # if the plugin has an aggressive method and we're in aggressive mode, use it # or if we're guessing all URLs if ($AGGRESSION == 3 && results.any?) || ($AGGRESSION == 4) results += aggressive_scan if @plugin.aggressive # if any of our matches have a url then fetch it # and check the matches[] # later we can do some caching # we have no caching, so we sort the URLs to fetch and only get 1 unique url per plugin. not great.. if @matches @matches.map { |x| x if x[:url] }.compact.sort_by { |x| x[:url] }.map do |match| newbase_uri = URI.join(@base_uri.to_s, match[:url]).to_s # todo: use scanner here aggressivetarget = Target.new(newbase_uri) aggressivetarget.open # if $verbose >1 # puts "#{@plugin_name} Aggressive: #{aggressivetarget.uri.to_s} [#{aggressivetarget.status}]" # end results += make_matches(aggressivetarget, match) end end end # clean up results unless results.empty? results.each do |r| # default certainty is 100% r[:certainty] = 100 if r[:certainty].nil? end end results end end WhatWeb-0.5.5/lib/target.rb000066400000000000000000000267301400032546600154530ustar00rootroot00000000000000# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see . # backwards compatible convenience method for plugins to use def open_target(url) newtarget = Target.new(url) begin newtarget.open rescue StandardError => err error("ERROR Opening: #{newtarget} - #{err}") end # it doesn't matter if the plugin only pulls 5 instead of 6 variables [newtarget.status, newtarget.uri, newtarget.ip, newtarget.body, newtarget.headers, newtarget.raw_headers] end def decode_html_entities(s) html_entities = { '"' => '"', ''' => "'", '&' => '&', '<' => '<', '>' => '>' } s.gsub( /#{html_entities.keys.join("|")}/, html_entities) end ### matching # fuzzy matching ftw def make_tag_pattern(b) # remove stuff between script and /script # don't bother with !--, --> or noscript and /noscript inscript = false b.scan(/<([^\s>]*)/).flatten.map do |x| x.downcase! r = nil r = x if inscript == false inscript = true if x == 'script' (inscript = false; r = x) if x == '/script' r end.compact.join(',') end # some plugins want a random string in URLs def randstr rand(36**8).to_s(36) end def match_ghdb(ghdb, body, _meta, _status, base_uri) # this could be made faster by creating code to eval once for each plugin pp 'match_ghdb', ghdb if $verbose > 2 # take a GHDB string and turn it into code to be evaluated matches = [] # fill with true or false. succeeds if all true s = ghdb # does it contain intitle? if s =~ /intitle:/i # extract either the next word or the following words enclosed in "s, it can't possibly be both intitle = (s.scan(/intitle:"([^"]*)"/i) + s.scan(/intitle:([^"]\w+)/i)).to_s matches << ((body =~ /[^<]*#{Regexp.escape(intitle)}[^<]*<\/title>/i).nil? ? false : true) # strip out the intitle: part s = s.gsub(/intitle:"([^"]*)"/i, '').gsub(/intitle:([^"]\w+)/i, '') end if s =~ /filetype:/i filetype = (s.scan(/filetype:"([^"]*)"/i) + s.scan(/filetype:([^"]\w+)/i)).to_s # lame method: check if the URL ends in the filetype unless base_uri.nil? unless base_uri.path.split('?')[0].nil? matches << ((base_uri.path.split('?')[0] =~ /#{Regexp.escape(filetype)}$/i).nil? ? false : true) end end s = s.gsub(/filetype:"([^"]*)"/i, '').gsub(/filetype:([^"]\w+)/i, '') end if s =~ /inurl:/i inurl = (s.scan(/inurl:"([^"]*)"/i) + s.scan(/inurl:([^"]\w+)/i)).flatten # can occur multiple times. inurl.each { |x| matches << ((base_uri.to_s =~ /#{Regexp.escape(x)}/i).nil? ? false : true) } # strip out the inurl: part s = s.gsub(/inurl:"([^"]*)"/i, '').gsub(/inurl:([^"]\w+)/i, '') end # split the remaining words except those enclosed in quotes, remove the quotes and sort them remaining_words = s.scan(/([^ "]+)|("[^"]+")/i).flatten.compact.each { |w| w.delete!('"') }.sort.uniq pp 'Remaining GHDB words', remaining_words if $verbose > 2 remaining_words.each do |w| # does it start with a - ? if w[0..0] == '-' # reverse true/false if it begins with a - matches << ((body =~ /#{Regexp.escape(w[1..-1])}/i).nil? ? true : false) else w = w[1..-1] if w[0..0] == '+' # if it starts with +, ignore the 1st char matches << ((body =~ /#{Regexp.escape(w)}/i).nil? ? false : true) end end pp matches if $verbose > 2 # if all matcbhes are true, then true if matches.uniq == [true] true else false end end # # Target # class Target include Helper attr_reader :target attr_reader :uri, :status, :ip, :body, :headers, :raw_headers, :raw_response attr_reader :cookies attr_reader :md5sum attr_reader :tag_pattern attr_reader :is_url, :is_file attr_accessor :http_options attr_reader :redirect_counter @@meta_refresh_regex = /<meta[\s]+http\-equiv[\s]*=[\s]*['"]?refresh['"]?[^>]+content[\s]*=[^>]*[0-9]+;[\s]*url=['"]?([^"'>]+)['"]?[^>]*>/i def pretty_print # "#{target} " + [@uri,@status,@ip,@body,@headers,@raw_headers,@raw_response,@cookies,@md5sum,@tag_pattern,@is_url,@is_file].join(",") "URI\n#{'*' * 40}\n#{@uri}\n" \ "status\n#{'*' * 40}\n#{@status}\n" \ "ip\n#{'*' * 40}\n#{@ip}\n" \ "redirects\n#{'*' * 40}\n#{@redirect_counter}\n" \ "header\n#{'*' * 40}\n#{@headers}\n" \ "cookies\n#{'*' * 40}\n#{@cookies}\n" \ "raw_headers\n#{'*' * 40}\n#{@raw_headers}\n" \ "raw_response\n#{'*' * 40}\n#{@raw_response}\n" \ "body\n#{'*' * 40}\n#{@body}\n" \ "md5sum\n#{'*' * 40}\n#{@md5sum}\n" \ "tag_pattern\n#{'*' * 40}\n#{@tag_pattern}\n" \ "is_url\n#{'*' * 40}\n#{@is_url}\n" \ "is_file\n#{'*' * 40}\n#{@is_file}\n" end def to_s @target end def self.meta_refresh_regex @@meta_refresh_regex end def is_file? @is_file end def is_url? @is_url end def initialize(target = nil, redirect_counter = 0) @target = target @redirect_counter = redirect_counter @headers = {} @http_options = { method: 'GET' } # @status=0 @is_url = if @target =~ /^http[s]?:\/\// true else false end if File.exist?(@target) @is_file = true raise "Error: #{@target} is a directory" if File.directory?(@target) if File.readable?(@target) == false raise "Error: You do not have permission to view #{@target}" end else @is_file = false end if is_url? @uri = URI.parse(Addressable::URI.encode(@target)) # is this taking control away from the user? # [400] http://www.alexa.com [200] http://www.alexa.com/ @uri.path = '/' if @uri.path.empty? else # @uri=URI.parse("file://"+@target) @uri = URI.parse('') end end def open if is_file? open_file else open_url(@http_options) end sleep $WAIT if $WAIT ## after open if @body.nil? # Initialize @body variable if the connection is terminated prematurely # This is usually caused by HTTP status codes: 101, 102, 204, 205, 305 @body = '' else @md5sum = Digest::MD5.hexdigest(@body) @tag_pattern = make_tag_pattern(@body) if @raw_headers @raw_response = @raw_headers + @body else @raw_response = @body @raw_headers = '' @cookies = [] end end end def open_file # target is a file @body = File.open(@target).read @body = @body.encode('UTF-16', 'UTF-8', invalid: :replace, replace: '').encode('UTF-8') # target is a http packet file if @body =~ /^HTTP\/1\.\d [\d]{3} (.+)\r\n\r\n/m # extract http header @headers = {} pageheaders = body.to_s.split(/\r\n\r\n/).first.to_s.split(/\r\n/) @raw_headers = pageheaders.join("\n") + "\r\n\r\n" @status = pageheaders.first.scan(/^HTTP\/1\.\d ([\d]{3}) /).flatten.first.to_i @cookies = [] for k in 1...pageheaders.length section = pageheaders[k].split(/:/).first.to_s.downcase if section =~ /^set-cookie$/i @cookies << pageheaders[k].scan(/:[\s]*(.+)$/).flatten.first else @headers[section] = pageheaders[k].scan(/:[\s]*(.+)$/).flatten.first end end @headers['set-cookie'] = @cookies.join("\n") unless @cookies.nil? || @cookies.empty? # extract html source if @body =~ /^HTTP\/1\.\d [\d]{3} .+?\r\n\r\n(.+)/m @body = @body.scan(/^HTTP\/1\.\d [\d]{3} .+?\r\n\r\n(.+)/m).flatten.first end end rescue StandardError => err raise err end def open_url(options) begin @ip = Resolv.getaddress(@uri.host) rescue StandardError => err raise err end begin if $USE_PROXY == true http = ExtendedHTTP::Proxy($PROXY_HOST, $PROXY_PORT, $PROXY_USER, $PROXY_PASS).new(@uri.host, @uri.port) else http = ExtendedHTTP.new(@uri.host, @uri.port) end # set timeouts http.open_timeout = $HTTP_OPEN_TIMEOUT http.read_timeout = $HTTP_READ_TIMEOUT # if it's https:// # i wont worry about certificates, verfication, etc if @uri.class == URI::HTTPS http.use_ssl = true OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers] = 'TLSv1:TLSv1.1:TLSv1.2:SSLv3:SSLv2' http.verify_mode = OpenSSL::SSL::VERIFY_NONE end getthis = @uri.path + (@uri.query.nil? ? '' : '?' + @uri.query) req = nil if options[:method] == 'GET' req = ExtendedHTTP::Get.new(getthis, $CUSTOM_HEADERS) end if options[:method] == 'HEAD' req = ExtendedHTTP::Head.new(getthis, $CUSTOM_HEADERS) end if options[:method] == 'POST' req = ExtendedHTTP::Post.new(getthis, $CUSTOM_HEADERS) req.set_form_data(options[:data]) end req.basic_auth $BASIC_AUTH_USER, $BASIC_AUTH_PASS if $BASIC_AUTH_USER res = http.request(req) @raw_headers = http.raw.join("\n") @headers = {} @body = res.body @body = Helper::convert_to_utf8(@body) @raw_headers = Helper::convert_to_utf8(@raw_headers) res.each_header do |x, y| newx, newy = x.dup, y.dup @headers[ Helper::convert_to_utf8(newx) ] = Helper::convert_to_utf8(newy) end @headers['set-cookie'] = res.get_fields('set-cookie').join("\n") unless @headers['set-cookie'].nil? @status = res.code.to_i puts @uri.to_s + " [#{status}]" if $verbose > 1 rescue StandardError => err raise err end end def get_redirection_target newtarget_m, newtarget_h, newtarget = nil if @@meta_refresh_regex =~ @body metarefresh = @body.scan(@@meta_refresh_regex).flatten.first metarefresh = decode_html_entities(metarefresh).strip newtarget_m = URI.join(@target, metarefresh).to_s # this works for relative and absolute end # HTTP 3XX redirect if (300..399) === @status && @headers && @headers['location'] location = @headers['location'] begin newtarget_h = URI.join(@target, location).to_s rescue StandardError => err # the combinaton of the current target and the new location must be invalid error("Error: Invalid redirection from #{@target} to #{location}. #{err}") return nil end end # if both meta refresh location and HTTP location are set, then the HTTP location overrides if newtarget_m || newtarget_h case $FOLLOW_REDIRECT when 'never' # no_redirects = true # this never gets back to main loop but no prob when 'http-only' newtarget = newtarget_h when 'meta-only' newtarget = newtarget_m when 'same-site' newtarget = (newtarget_h || newtarget_m) if URI.parse((newtarget_h || newtarget_m)).host == @uri.host # defaults to _h if both are present when 'always' newtarget = (newtarget_h || newtarget_m) else error('Error: Invalid REDIRECT mode') end end newtarget = nil if newtarget == @uri.to_s # circular redirection not allowed newtarget end end ����������������������������������������WhatWeb-0.5.5/lib/version_class.rb������������������������������������������������������������������0000664�0000000�0000000�00000004606�14000325466�0017035�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see <http://www.gnu.org/licenses/>. class Version def initialize(name_product = nil, versions = nil, url = nil) raise 'You must specify the name of the product' if name_product.nil? raise 'You must specify the available versions of the product' if versions.nil? raise 'You must specify the available url of the website' if url.nil? @name = name_product @versions = versions @files = Hash['filenames' => [], 'files' => [], 'md5' => []] @url = url @got_best_versions = false @best_versions = [] versions.each do |version| version[1].each do |file| next if @files['filenames'].include? file[0] @files['filenames'].push(file[0]) @files['files'].push(URI.join(@url.to_s, file[0]).to_s) _status, url, _ip, body, _headers = open_target(@files['files'].last) @files['md5'].push(Digest::MD5.hexdigest(body)) end end end def best_matches return @best_versions if @got_best_versions == true @versions.each do |version| count = 0 version[1].each do |file| i = @files['filenames'].index(file[0]) count += 1 if @files['md5'][i] == file[1] end while !@best_versions.empty? && @best_versions[0][1] < count @best_versions.delete_at(0) end if count > 0 && (@best_versions.empty? || @best_versions[0][1] == count) && \ !@best_versions.include?([version[0], count]) @best_versions.insert(0, [version[0], count]) end end @got_best_versions = true @best_versions.flatten! @best_versions.each_index { |i| @best_versions.delete_at(i + 1) }.sort! @best_versions end def matches_format best_matches if @got_best_versions == false @best_versions end end ��������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/lib/whatweb.rb������������������������������������������������������������������������0000664�0000000�0000000�00000006040�14000325466�0015616�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see <http://www.gnu.org/licenses/>. # Debugging # require 'profile' # debugging # Standard Ruby require 'getoptlong' require 'net/http' require 'open-uri' require 'cgi' require 'thread' require 'rbconfig' # detect environment, e.g. windows or linux require 'resolv' require 'resolv-replace' # asynchronous DNS require 'open-uri' require 'digest/md5' require 'openssl' # required for Ruby version ~> 2.4 require 'pp' # WhatWeb libs require_relative 'whatweb/version.rb' require_relative 'whatweb/banner.rb' require_relative 'whatweb/scan.rb' require_relative 'whatweb/parser.rb' require_relative 'whatweb/redirect.rb' require_relative 'gems.rb' require_relative 'helper.rb' require_relative 'target.rb' require_relative 'plugins.rb' require_relative 'plugin_support.rb' require_relative 'logging.rb' require_relative 'colour.rb' require_relative 'version_class.rb' require_relative 'http-status.rb' require_relative 'extend-http.rb' # load the lib/logging/ folder Dir["#{File.expand_path(File.dirname(__FILE__))}/logging/*.rb"].each {|file| require file } # Output options $WWDEBUG = false # raise exceptions in plugins, etc $verbose = 0 # $VERBOSE is reserved in ruby $use_colour = 'auto' $QUIET = false $NO_ERRORS = false $LOG_ERRORS = nil $PLUGIN_TIMES = Hash.new(0) # HTTP connection options $USER_AGENT = "WhatWeb/#{WhatWeb::VERSION}" $AGGRESSION = 1 $FOLLOW_REDIRECT = 'always' $USE_PROXY = false $PROXY_HOST = nil $PROXY_PORT = 8080 $PROXY_USER = nil $PROXY_PASS = nil $HTTP_OPEN_TIMEOUT = 15 $HTTP_READ_TIMEOUT = 30 $WAIT = nil $CUSTOM_HEADERS = {} $BASIC_AUTH_USER = nil $BASIC_AUTH_PASS = nil # Ruby Version Compatability if Gem::Version.new(RUBY_VERSION) < Gem::Version.new(2.0) raise('Unsupported version of Ruby. WhatWeb requires Ruby 2.0 or later.') end # Initialize HTTP Status class HTTP_Status.initialize PLUGIN_DIRS = [] # Load plugins from only one location # Check for plugins in folders relative to the whatweb file first # __dir__ follows symlinks # this will work when whatweb is a symlink in /usr/bin/ $load_path_plugins = [ File.expand_path('../', __dir__), "/usr/share/whatweb" # location Makefile installs into, also used in Kali ] $load_path_plugins.each do |dir| if Dir.exist?(File.expand_path("plugins", dir)) and Dir.exist?(File.expand_path("my-plugins", dir)) PLUGIN_DIRS << File.expand_path("plugins", dir) PLUGIN_DIRS << File.expand_path("my-plugins", dir) break end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/lib/whatweb/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14000325466�0015271�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/lib/whatweb/banner.rb�����������������������������������������������������������������0000664�0000000�0000000�00000002530�14000325466�0017063�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see <http://www.gnu.org/licenses/>. module WhatWeb BANNER = " .$$$ $. .$$$ $. $$$$ $$. .$$$ $$$ .$$$$$$. .$$$$$$$$$$. $$$$ $$. .$$$$$$$. .$$$$$$. $ $$ $$$ $ $$ $$$ $ $$$$$$. $$$$$ $$$$$$ $ $$ $$$ $ $$ $$ $ $$$$$$. $ `$ $$$ $ `$ $$$ $ `$ $$$ $$' $ `$ `$$ $ `$ $$$ $ `$ $ `$ $$$' $. $ $$$ $. $$$$$$ $. $$$$$$ `$ $. $ :' $. $ $$$ $. $$$$ $. $$$$$. $::$ . $$$ $::$ $$$ $::$ $$$ $::$ $::$ . $$$ $::$ $::$ $$$$ $;;$ $$$ $$$ $;;$ $$$ $;;$ $$$ $;;$ $;;$ $$$ $$$ $;;$ $;;$ $$$$ $$$$$$ $$$$$ $$$$ $$$ $$$$ $$$ $$$$ $$$$$$ $$$$$ $$$$$$$$$ $$$$$$$$$' ".freeze end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/lib/whatweb/parser.rb�����������������������������������������������������������������0000664�0000000�0000000�00000004253�14000325466�0017116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see <http://www.gnu.org/licenses/>. module WhatWeb class Parser def self.run_plugins(target, plugins, scanner: nil) results = [] raise('No plugins selected') if plugins.empty? plugins.each do |name, plugin| begin # execute the plugin # start_time = Time.now result = plugin.scan(target) # end_time = Time.now # $PLUGIN_TIMES[name] += end_time - start_time rescue => e error("ERROR: Plugin #{name} failed for #{target}. #{e}") raise if $WWDEBUG == true end results << [name, result] unless result.nil? || result.empty? end results end def self.parse(target, result, logging_list: nil, grep_plugin: false) logging_list.each do |log| begin # Hide log if Grep plugin did not match if grep_plugin if result.map { |plugin_name, _plugin_result| plugin_name }.include? 'Grep' log.out(target, target.status, result) end else log.out(target, target.status, result) end rescue => e error("ERROR Logging failed: #{target} - #{e}") raise if $WWDEBUG == true end end unless logging_list.nil? result = { 'target' => target, 'status' => target.status, 'result' => result } return result unless grep_plugin return result if result.map { |plugin_name, _plugin_result| plugin_name }.include? 'Grep' end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/lib/whatweb/redirect.rb���������������������������������������������������������������0000664�0000000�0000000�00000002500�14000325466�0017414�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see <http://www.gnu.org/licenses/> # module WhatWeb class Redirect # Checks for redirect and adds redirection target URL to Scanner def initialize(target, scanner, max_redirects) redirect_url = target.get_redirection_target return if redirect_url.nil? if target.redirect_counter >= max_redirects error("ERROR Too many redirects: #{target} => #{redirect_url}") return end # pp target.redirect_counter, redirect_url puts "redirect #{target.redirect_counter + 1} from #{target.target} to #{redirect_url}" if $verbose > 1 scanner.add_target(redirect_url, target.redirect_counter + 1) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/lib/whatweb/scan.rb�������������������������������������������������������������������0000664�0000000�0000000�00000015442�14000325466�0016550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see <http://www.gnu.org/licenses/>. module WhatWeb class Scan def initialize(urls, input_file: nil, url_prefix: nil, url_suffix: nil, url_pattern: nil, max_threads: 25) urls = [urls] if urls.is_a?(String) @targets = make_target_list( urls, input_file: input_file, url_prefix: url_prefix, url_suffix: url_suffix, url_pattern: url_pattern ) error('No targets selected') if @targets.empty? @max_threads = max_threads.to_i || 25 @target_queue = Queue.new # workers consume from this end def scan Thread.abort_on_exception = true if $WWDEBUG workers = (1..@max_threads).map do Thread.new do # keep reading in root tasks until a nil is received loop do target = @target_queue.pop Thread.exit unless target # keep processing until there are no more redirects or the limit is hit # while target begin target.open rescue => e error("ERROR Opening: #{target} - #{e}") target = nil # break target loop next end yield target end end end # initialize target_queue @targets.each do |url| target = prepare_target(url) next unless target @target_queue << target end # exit loop do # this might miss redirects from final targets # more defensive than comparing against max_threads alive = workers.map { |worker| worker if worker.alive? }.compact.length break if alive == @target_queue.num_waiting && @target_queue.empty? end # Shut down workers, logging, and plugins (1..@max_threads).each { @target_queue << nil } workers.each(&:join) end # for use by Plugins def scan_from_plugin(target: nil) raise 'No target' unless target begin target.open rescue => e error("ERROR Opening: #{target} - #{e}") end target end def add_target(url, redirect_counter = 0) # TODO: REVIEW: should this use prepare_target? target = Target.new(url, redirect_counter) unless target error("Add Target Failed - #{url}") return end @target_queue << target end private # try to make a new Target object, may return nil def prepare_target(url) Target.new(url) rescue => e error("Prepare Target Failed - #{e}") nil end # # Make Target List # # Make a list of targets from a list of URLs and/or input file # def make_target_list(urls, opts = {}) url_list = [] # parse URLs if urls.is_a?(Array) urls.flatten.reject { |u| u.nil? }.map { |u| u.strip }.reject { |u| u.eql?('') }.each do |url| url_list << url end end # parse input file # read each line as a url, skipping lines that begin with a # inputfile = opts[:input_file] || nil if !inputfile.nil? && File.exist?(inputfile) pp "loading input file: #{inputfile}" if $verbose > 2 File.open(inputfile).readlines.each(&:strip!).reject { |line| line.start_with?('#') || line.eql?('') }.each do |line| url_list << line end end return [] if url_list.empty? # TODO: refactor this ip_range = url_list.map do |x| range = nil # Parse IP ranges if x =~ %r{^[0-9\.\-\/]+$} && x !~ %r{^[\d\.]+$} begin # CIDR notation if x =~ %r{\d+\.\d+\.\d+\.\d+/\d+$} range = IPAddr.new(x).to_range.map(&:to_s) # x.x.x.x-x elsif x =~ %r{^(\d+\.\d+\.\d+\.\d+)-(\d+)$} start_ip = IPAddr.new(Regexp.last_match(1), Socket::AF_INET) end_ip = IPAddr.new("#{start_ip.to_s.split('.')[0..2].join('.')}.#{Regexp.last_match(2)}", Socket::AF_INET) range = (start_ip..end_ip).map(&:to_s) # x.x.x.x-x.x.x.x elsif x =~ %r{^(\d+\.\d+\.\d+\.\d+)-(\d+\.\d+\.\d+\.\d+)$} start_ip = IPAddr.new(Regexp.last_match(1), Socket::AF_INET) end_ip = IPAddr.new(Regexp.last_match(2), Socket::AF_INET) range = (start_ip..end_ip).map(&:to_s) end rescue => e # Something went horribly wrong parsing the target IP range raise "Error parsing target IP range: #{e}" end end range end.compact.flatten # TODO: refactor this. data which matches these regexs should be taken care of above url_list = url_list.select { |x| !(x =~ %r{^[0-9\.\-*\/]+$}) || x =~ /^[\d\.]+$/ } url_list += ip_range unless ip_range.empty? # make urls friendlier, test if it's a file, if test for not assume it's http:// # http, https, ftp, etc push_to_urllist = [] # TODO: refactor this url_list = url_list.map do |x| if File.exist?(x) x else # use url pattern x = opts[:url_pattern].gsub('%insert%', x) unless opts[:url_pattern].to_s.eql?('') # add prefix & suffix x = "#{opts[:url_prefix]}#{x}#{opts[:url_suffix]}" # need to move this into a URI parsing function # # check for URI prefix if x !~ %r{^[a-z]+:\/\/} # add missing URI prefix x.sub!(/^/, 'http://') end # is it a valid domain? begin domain = Addressable::URI.parse(x) # check validity raise 'Unable to parse invalid target. No hostname.' if domain.host.empty? # convert IDN domain x = domain.normalize.to_s if domain.host !~ %r{^[a-zA-Z0-9\.:\/]*$} rescue => e # if it fails it's not valid x = nil # TODO: print something more useful error("Unable to parse invalid target #{x}: #{e}") end # return x x end end url_list += push_to_urllist unless push_to_urllist.empty? # compact removes nils url_list = url_list.flatten.compact # .sort.uniq end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/lib/whatweb/version.rb����������������������������������������������������������������0000664�0000000�0000000�00000001365�14000325466�0017310�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright 2009 to 2020 Andrew Horton and Brendan Coles # # This file is part of WhatWeb. # # WhatWeb 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 2 of the License, or # at your option) any later version. # # WhatWeb 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. # # You should have received a copy of the GNU General Public License # along with WhatWeb. If not, see <http://www.gnu.org/licenses/>. module WhatWeb VERSION = '0.5.5'.freeze end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/my-plugins/���������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14000325466�0015166�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/my-plugins/plugin-tutorial-1.rb�������������������������������������������������������0000664�0000000�0000000�00000002134�14000325466�0021010�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb # Plugin.define do name "Plugin-Tutorial-1" authors [ "Your preferred name <email@address>", # v0.1 # 2019-01-01 # Created plugin ] version "0.1" description "Generic CMS is an open-source Content Management System developed in PHP." website "http://example.com/" # This is the matches array. # Each match is treated independently. # Matches # matches [ # This searches for a text string. { :text => "This page was generated by <b>Generic CMS</b>" }, # This searches for a regular expression. Note that the slashes are escaped. { :regexp => /This page was generated by <a href="http:\/\/www.genericcms.com\/en\/products\/generic-cms\/">Generic CMS<\/a>/ }, # This extracts the version of Generic CMS from the Mega generator tag. { :name => "Meta generator", :version => /<meta name="generator" content="Generic CMS version ([a-z0-9])+/ }, ] end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������WhatWeb-0.5.5/my-plugins/plugin-tutorial-2.rb�������������������������������������������������������0000664�0000000�0000000�00000002256�14000325466�0021016�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb # Plugin.define do name "Plugin-Tutorial-2" authors [ "Your preferred name <email@address>", # v0.1 # 2019-01-01 # Created plugin ] version "0.1" description "GenericServer is an HTTP server for head-mounted devices that use the FOOT processor." website "http://example.com/" # This is the matches array. # Each match is treated independently. # Matches # matches [ # Title { :text => "<title>Welcome to GenericServer" }, # HTTP Server Header # This detects the presence of the text "GenericServer" within the HTTP Server header { :regexp => /^GenericServer /, :search => "headers[server]" }, # HTTP Server Header # Version Detection # # This extracts the version of the "GenericServer" from the HTTP Server header # Note that many HTTP servers can be configured to hide the version so there are two patterns in this plugin { :version => /^GenericServer V([^\s]+)/, :search => "headers[server]" }, ] end WhatWeb-0.5.5/my-plugins/plugin-tutorial-3.rb000066400000000000000000000032351400032546600210150ustar00rootroot00000000000000## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb # Plugin.define do name "Plugin-Tutorial-3" authors [ "Your preferred name ", # v0.1 # 2019-01-01 # Created plugin "@examplename" # v0.2 # 2019-03-12 # Added cookie detection # <----- updated from tutorial 1 ] version "0.2" # <----- updated from tutorial 1 description "Generic CMS is an open-source Content Management System developed in PHP." website "http://example.com/" # The Google Dorks list is an array. This can help people find the product using a search engine. # Dorks # # <----- updated from tutorial 1 dorks [ '"Generic CMS login"', 'Generic login register linkname', ] # This is the matches array. # Each match is treated independently. # Matches # matches [ # This searches for a text string. { :text => "This page was generated by Generic CMS" }, # This searches for a regular expression. Note that the slashes are escaped. { :regexp => /This page was generated by Generic CMS<\/a>/ }, # This extracts the version of Generic CMS from the Mega generator tag. { :name => "Meta generator", :version => / "headers[set-cookie]", :regexp => /genericcms=[^;]+;/ }, ] end WhatWeb-0.5.5/my-plugins/plugin-tutorial-4.rb000066400000000000000000000013471400032546600210200ustar00rootroot00000000000000## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb # Plugin.define do name "Plugin-Tutorial-4" authors [ "Your preferred name ", # v0.1 # 2019-01-01 # Created plugin ] version "0.1" description "GenericWAF is a commercial Web Application Firewall (WAF)." website "http://example.com/" # Matches # matches [ # HTTP Server Header { :search => "headers[server]", :regexp => /genericwaf\-nginx/ }, # Cookie { :search => "headers[set-cookie]", :regexp => /__genericwafuid/, :name=>"__genericwafuid cookie" }, ] end WhatWeb-0.5.5/my-plugins/plugin-tutorial-5.rb000066400000000000000000000032121400032546600210120ustar00rootroot00000000000000## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb # Plugin.define do name "Plugin-Tutorial-5" authors [ "Your preferred name ", # v0.1 # 2019-01-01 # Created plugin ] version "0.1" description "GenericRouter is a home router using the ARM processor and busybox Linux." website "http://example.com/" # ShodanHQ results as at 1999-12-31 # # 1,234 for GenericRouter # Matches # matches [ # Title { :text => "GenericRouter" }, # The www-authenticate message { :search => "headers[www-authenticate]", :text => 'Basic realm="GenericRouter"' }, # Check MD5sum hashes of images to detect the exact version # In aggressive mode 3 these will be checked only if this plugin already matches # In aggressive mode 4 these will be checked anyway { :model => 'gsl2540b', :md5 => "d076eed06cafe1e4a74f83c7fdfe2e67", :url => '/generic/images/gsl2540b.jpg' }, { :model => 'gsl2640b', :md5 => "01aa666a65a72bb4ab0deadbeef525f4", :url => '/generic/images/gsl2640b.jpg' }, { :model => 'ggl3420', :md5 => "c3bb6c8124fe7106339cde087da6bb30", :url => '/generic/images/ggl3420.jpg' }, { :model => 'gwl2100ap', :md5 => "fa6350a0feedf00d9651c9aaf05f2187", :url => '/generic/images/gwl2100ap.jpg' }, { :model => 'gwl2230ap', :md5 => "71c307b6d7d82eeab5babe23c1ff41a9", :url => '/generic/images/gwl2230ap.jpg' }, { :model => 'gwl2700ap', :md5 => "3573c663c0ffeec53c0886518045a6f3", :url => '/generic/images/gwl2700ap.jpg' }, ] end WhatWeb-0.5.5/my-plugins/plugin-tutorial-6.rb000066400000000000000000000043021400032546600210140ustar00rootroot00000000000000## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb # Plugin.define do name "Plugin-Tutorial-6" authors [ "Your preferred name ", # v0.1 # 2019-01-01 # Created plugin ] version "0.1" description "Describe what the plugin identifies" website "http://example.com/" # Dorks # dorks [ '"Generic CMS login"', 'Generic login register linkname', ] # Matches # matches [ # This searches for a text string. { :text => "This page was generated by Generic CMS" }, ] # You can write custom Ruby code in plugins for more control # There can be a passive function and an aggressive function. # The Passive function will always execute # ## # The following variables are available # # @body # @headers # @cookies # @status # @base_uri # @md5sum # @tagpattern # @ip ## passive do # make a matches array m = [] # If the HTTP status is 302 and the redirection location is /admin/genericcms.php then match if @status.to_s =~ /^302$/ and @headers["location"] =~ /^\/admin\/genericcms\.php$/ m << { :name => "302 redirection to /admin/genericcms.php" } end # You can add debugging and check the value of variables # pp @status # pp @headers # return the matches array, even if it's empty m end # Check other plugins with passive functions for examples. ## # The Aggressive function will only sometimes execute # At aggressive level 3 if a match is found, then the aggressive function executes # At aggressive level 4, the aggressive function always executes ## aggressive do @variables[:my_var] += 1 # make a matches array. this returns the equivalent of the matches[] block above m = [] # return the matches array, even if it's emtpy m end ## Very few plugins need startup and shutdown functions # # This executes when the plugin is first loaded def startup @variables = {my_var: 1} end # This executes when the plugin is closed on whatweb shutdown def shutdown # puts("my_var is #{@variables[:my_var]}") end end WhatWeb-0.5.5/my-plugins/plugin-tutorial-7.rb000066400000000000000000000140341400032546600210200ustar00rootroot00000000000000## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb ## Plugin.define do name "Plugin-Tutorial-7" authors [ "Your preferred name ", # v0.1 # 2019-01-01 # Created plugin # Your preferred name # v0.2 # 2019-01-11 # Added cookie detection # Your preferred name # v0.3 # 2019-03-17 # Added plenty of example matches ] version "0.3" description "Describe what the plugin identifies" website "http://example.com/" # Dorks # dorks [ '"Generic CMS login"', 'Generic login register linkname', ] # A comment block here is a good place to make notes for yourself and others # This is the matches array. # Each match is treated independently. ## # These are the symbols that can be used in matches. # ## Pattern Matching ## # :regexp -- A regular expression in Ruby format, eg. /^foobar$/ # :text -- Case insensitive text # :ghdb -- Google Hack Database format. This supports the use of intitle:, inurl: and minus. # :md5 -- The MD5 sum hash of the HTTP response body # :tagpattern -- A list of HTML tag names. # ## Where to Search ## # :search -- Can be "body" (default), "all", "headers", or "headers[x]" for a specific HTTP header # ## Naming the plugin match ## # :name -- You can optionally name the match. This name is displayed in verbose output. # ## Returning Data ## # Each of these symbols can be a regular expression or text. # # :version -- The version # :account -- A user account name # :module -- A module name # :make -- The make, e.g. NetGear # :model -- The model, e.g. SpeedErr # :firmware -- The firmware, e.g. 6.14.14 # :filepath -- A filepath. These can be displayed in error messages # :string -- This is to return data that isn't covered by the symbols above. E.g. an email address. # ## How certain is this pattern? ## # :certainty -- how certain is this match. 100 is certain (default), 75 is probably, and 25 is maybe # ## Limit the match to a URL path or an HTTP status ## # :url -- you can combine this with other variables or use by itself # :status -- The HTTP status of the response ## # Matches # matches [ # This searches for a text string. { :text => "This page was generated by Generic CMS" }, # This searches for a regular expression. Note that the slashes are escaped. { :regexp => /This page was generated by Generic CMS<\/a>/ }, # This searches for a text string and the match has a name that appears in verbose output { :name => "Meta generator", :text => ' 100 is certain' (default) # :certainty => 75 is probably # :certainty => 25 is maybe # :certainty is 25. If the title tag is matched, then maybe this webpage is the Generic CMS { :name => "title", :certainty => 25, :text => "Generic Links from Generic CMS" }, # :certainty is 75. This means that if the title tag is matched, then this webpage is probably the Generic CMS # This plugin match is split across multiple lines. This can aid readability. { :name => "title", :certainty => 75, :text => "Generic CMS Homepage" }, # check the presence of an HTTP header { :search => "headers[genericxxx]", :regexp => /^.*$/ }, # return the contents of an HTTP header { :search => "headers[genericversion]", :version => /^(.*)$/ }, # This returns the version. # The :version symbol is a regular expression. Whatever is found within the parenthesis is returned as the value of :version { :name => "Meta Generator", :version => / "Version in Meta Generator", :version => / 1. This returns the result of the second set of parentheses within the regular expression # The :module symbol is also a regular expression like :version. Note that the parenthesis is now after the text, "with modules" { :name => "Version in Meta Generator", :version => / 1 }, # :url can be used to limit a match to a URL path # This matches if both the URL path and the text is found # When WhatWeb is in aggressive mode, it will check if /admin/login.php exists { :url => '/admin/generic-cms-login.php', :text => 'Generic CMS Login Panel' }, # Generic CMS has a default favicon that displays the logo of the web application # In aggressive mdoe, the /favicon.ico path will be fetched # This matches if the HTTP response body, in this case the favicon.co image, has the follow MD5sum hash. { :url => "/favicon.ico", :md5 => '12dead87beef7f00d90cafed82babe5' }, # GHDB matches pages with a Google Hack Database format. # Very few plugins use the :ghdb symbol. { :ghdb => 'intitle:"Generic CMS" login register', :certainty => 25 }, # This match uses :tagpattern. This is a comma delimited list of all the HTML tags in the webpage. # Very few plugins use :tagpattern { :url => "/generic-login.php", :name => "Tag pattern for login page", :tagpattern => "!DOCTYPE,html,head,title,/title,meta,link,link,script,/script,meta,/head,body,div,h1,a,/a,/h1,form,p,label,br,input,/label,/p,p,label,br,input,/label,/p,p,label,input,/label,/p,p,input,input,input,/p,/form,p,a,/a,/p,p,a,/a,/p,/div,script,/script,/body,/html" }, ] end WhatWeb-0.5.5/plugin-development/000077500000000000000000000000001400032546600167005ustar00rootroot00000000000000WhatWeb-0.5.5/plugin-development/alexa-top-10.txt000066400000000000000000000001551400032546600215520ustar00rootroot00000000000000google.com youtube.com tmall.com qq.com baidu.com facebook.com sohu.com login.tmall.com taobao.com yahoo.com WhatWeb-0.5.5/plugin-development/alexa-top-100.txt000066400000000000000000000021621400032546600216320ustar00rootroot00000000000000google.com youtube.com tmall.com qq.com baidu.com facebook.com sohu.com login.tmall.com taobao.com yahoo.com jd.com wikipedia.org 360.cn amazon.com sina.com.cn pages.tmall.com weibo.com live.com reddit.com vk.com okezone.com xinhuanet.com netflix.com blogspot.com csdn.net office.com alipay.com yahoo.co.jp instagram.com zhanqi.tv bongacams.com microsoftonline.com naver.com panda.tv bing.com google.com.hk microsoft.com stackoverflow.com tribunnews.com livejasmin.com china.com.cn twitch.tv google.co.in aliexpress.com ebay.com amazon.co.jp mama.cn twitter.com myshopify.com msn.com tianya.cn linkedin.com amazon.in ok.ru adobe.com yandex.ru pornhub.com apple.com wordpress.com dropbox.com mail.ru google.com.br sogou.com whatsapp.com bilibili.com yy.com huanqiu.com chaturbate.com trello.com detail.tmall.com instructure.com imdb.com medium.com cnn.com aparat.com nytimes.com booking.com 17ok.com xvideos.com bbc.com detik.com amazonaws.com google.co.jp indeed.com spotify.com chase.com google.cn grid.id ettoday.net tumblr.com babytree.com kompas.com amazon.de google.de amazon.co.uk pixnet.net hao123.com imgur.com 1688.com google.ru WhatWeb-0.5.5/plugin-development/alexa-top-1000.txt000066400000000000000000000303601400032546600217130ustar00rootroot00000000000000google.com youtube.com tmall.com qq.com baidu.com facebook.com sohu.com login.tmall.com taobao.com yahoo.com jd.com wikipedia.org 360.cn amazon.com sina.com.cn pages.tmall.com weibo.com live.com reddit.com vk.com okezone.com xinhuanet.com netflix.com blogspot.com csdn.net office.com alipay.com yahoo.co.jp instagram.com zhanqi.tv bongacams.com microsoftonline.com naver.com panda.tv bing.com google.com.hk microsoft.com stackoverflow.com tribunnews.com livejasmin.com china.com.cn twitch.tv google.co.in aliexpress.com ebay.com amazon.co.jp mama.cn twitter.com myshopify.com msn.com tianya.cn linkedin.com amazon.in ok.ru adobe.com yandex.ru pornhub.com apple.com wordpress.com dropbox.com mail.ru google.com.br sogou.com whatsapp.com bilibili.com yy.com huanqiu.com chaturbate.com trello.com detail.tmall.com instructure.com imdb.com medium.com cnn.com aparat.com nytimes.com booking.com 17ok.com xvideos.com bbc.com detik.com amazonaws.com google.co.jp indeed.com spotify.com chase.com google.cn grid.id ettoday.net tumblr.com babytree.com kompas.com amazon.de google.de amazon.co.uk pixnet.net hao123.com imgur.com 1688.com google.ru freepik.com stackexchange.com github.com onlinesbi.com bbc.co.uk jrj.com.cn google.es google.fr espn.com rakuten.co.jp theguardian.com indiatimes.com youm7.com soundcloud.com fandom.com quizlet.com xhamster.com digikala.com thestartmagazine.com liputan6.com google.it shutterstock.com nih.gov alibaba.com office365.com paypal.com cnblogs.com flipkart.com wetransfer.com google.com.tw yao.tmall.com 6.cn udemy.com slideshare.net intuit.com globo.com roblox.com discordapp.com mailchimp.com uol.com.br iqoption.com zhihu.com gome.com.cn google.com.mx etsy.com daum.net gmw.cn walmart.com 163.com researchgate.net mercadolivre.com.br soso.com okta.com canva.com zendesk.com google.com.tr ltn.com.tw google.com.sg google.co.uk pinterest.com cnet.com savefrom.net bukalapak.com avito.ru telegram.org pixabay.com salesforce.com grammarly.com archive.org primevideo.com craigslist.org washingtonpost.com wikihow.com sindonews.com zillow.com metropoles.com abs-cbn.com varzesh3.com so.com duckduckgo.com rednet.cn aliyun.com iqiyi.com force.com godaddy.com setn.com vimeo.com bet9ja.com mediafire.com thepiratebay.org healthline.com speedtest.net google.com.eg forbes.com xnxx.com ebay.de ladbible.com y2mate.com google.ca unsplash.com t.co google.com.sa eastday.com wellsfargo.com scribd.com businessinsider.com w3schools.com jianshu.com tradingview.com hdfcbank.com 3c.tmall.com investing.com redd.it nianhuo.tmall.com academia.edu gfycat.com scol.com.cn taboola.com steamcommunity.com youth.cn albawabhnews.com momoshop.com.tw wix.com nvzhuang.tmall.com amazon.it bet365.com google.com.ar cnbc.com uniqlo.tmall.com amazon.ca ilovepdf.com ebc.net.tw quora.com google.co.th chouftv.ma postlnk.com icicibank.com skype.com foxnews.com dailymotion.com manoramaonline.com google.co.id patria.org.ve thesaurus.com ndtv.com aliexpress.ru list.tmall.com food.tmall.com ask.com subject.tmall.com airbnb.com op.gg bestbuy.com arcgis.com messenger.com douban.com namnak.com sciencedirect.com behance.net smallpdf.com amazon.es hulu.com slack.com suara.com hotstar.com mozilla.org capitalone.com merdeka.com weather.com shaparak.ir mercari.com dailymail.co.uk blogger.com sahibinden.com idntimes.com ebay.co.uk alwafd.news weebly.com elbalad.news googlevideo.com breitbart.com tripadvisor.com cricbuzz.com kumparan.com ikea.com google.pl amazon.fr gamepedia.com lazada.sg cloudfront.net intoday.in nicovideo.jp 51sole.com tistory.com fc2.com 51.la coinmarketcap.com fedex.com hootsuite.com softonic.com ups.com allegro.pl hurriyet.com.tr box.com ebay-kleinanzeigen.de chinadaily.com.cn telewebion.com cambridge.org gatustox.net asos.com samsung.com sberbank.ru geeksforgeeks.org zoom.us goodreads.com google.co.kr line.me patch.com fadspms.com yts.mx amazon.cn reverso.net ensonhaber.com n11.com pinimg.com twimg.com khanacademy.org bloomberg.com dostor.org neiyi.tmall.com espncricinfo.com wikimedia.org glassdoor.com 14nuzznszbdp.com naukri.com rambler.ru cnnindonesia.com hepsiburada.com blackboard.com onemboaran.com kinopoisk.ru marketwatch.com od0gddq27wkk.com rt.com 9gag.com indiamart.com dmm.co.jp google.com.au kakao.com t.me google.gr divar.ir eventbrite.com homedepot.com spao.tmall.com kapanlagi.com wixsite.com souq.com who.int japanpost.jp mercadolibre.com.ar investopedia.com la1dwne9cn5c.com gmx.net zoho.com miao.tmall.com irs.gov freelancer.com google.com.vn gap.tmall.com google.com.ua ouedkniss.com irctc.co.in pulzo.com fadskis.com ninisite.com doubleclick.net surveymonkey.com myhome.tmall.com elwatannews.com crabsecret.tmall.com trustpilot.com fidelity.com youdao.com hp.com beytoote.com outbrain.com lee.tmall.com aol.com adp.com namasha.com tiktok.com newstrend.news utorrent.com flaticon.com worldometers.info wsj.com moneycontrol.com genius.com deviantart.com dbs.com.sg justdial.com err.tmall.com youporn.com buzzfeed.com uselnk.com xfinity.com douyu.com codepen.io 104.com.tw zerodha.com qoo10.sg mercadolibre.com.mx prothomalo.com aminoapps.com gismeteo.ru prnt.sc theepochtimes.com z48gntbbu9pl.com youku.com banvenez.com gosuslugi.ru wildberries.ru alodokter.com target.com files.wordpress.com ecosia.org trendingnow.video citi.com libero.it in.gr 1337x.to emol.com wordreference.com livejournal.com shimo.im spankbang.com fiverr.com bp.blogspot.com gstatic.com udn.com coursera.org tutorialspoint.com vnexpress.net issuu.com popads.net realtor.com sportbible.com squarespace.com expressvpn.com elpais.com otvfoco.com.br sourceforge.net win-rar.com aimer.tmall.com patreon.com td.com chegg.com oracle.com trendyol.com kompasiana.com yenisafak.com repubblica.it expedia.com giphy.com orange.fr google.co.ve zippyshare.com reuters.com gsmarena.com hotmart.com norton.com ca.gov webmd.com heavy.com lun.com namu.wiki onet.pl cdc.gov 81.cn steampowered.com shopee.tw eksisozluk.com canada.ca filimo.com tokopedia.com tencent.com list-manage.com mathrubhumi.com mgid.com tandfonline.com infobae.com rediff.com chess.com nownews.com haofang.net wish.com prezi.com bankofamerica.com indiatoday.in taleo.net slickdeals.net mileroticos.com marca.com usatoday.com web.de duolingo.com google.com.pk inquirer.net wp.pl ameblo.jp chinaz.com go.com dell.com leboncoin.fr tonlion.tmall.com pixiv.net ivi.ru rutracker.org segmentfault.com fast.com aksam.com.tr springer.com news18.com coupang.com memurlar.net myworkday.com tahiamasr.com kaskus.co.id dcard.tw istockphoto.com deepl.com nypost.com medicalnewstoday.com cnnic.cn coursehero.com europa.eu bankmellat.ir mheducation.com focus.cn google.com.co watch.tmall.com sozcu.com.tr freshdesk.com tsetmc.com wayfair.com shopee.co.id google.dz feedly.com milliyet.com.tr elsevier.com alnaharegypt.com ebay.com.au rokna.net marriott.com pchome.com.tw yandex.com.tr azure.com lifo.gr mawdoo3.com ria.ru crunchyroll.com google.co.za redtube.com klix.ba gmanetwork.com merriam-webster.com seznam.cz motor1.com plurk.com motorsport.com meetup.com 510hr.com langsha.tmall.com google.cl jia.tmall.com accuweather.com efu.com.cn creditkarma.com theverge.com vidio.com webex.com agoda.com mit.edu nbcnews.com tempo.co sonhoo.com wiley.com toutiao.com liansuo.com earthmusic.tmall.com google.ro olx.ua ouo.io padlet.com ibicn.com autodesk.com nike.tmall.com nicsorts-accarade.com enimerotiko.gr kissanime.ru stremanp.com olx.pl olx.com.br khabarfarsi.com wattpad.com cdninstagram.com xero.com yjc.ir ifeng.com cnbcindonesia.com zing.vn yna.co.kr 11st.co.kr makemytrip.com qualtrics.com scofield.tmall.com amazon.com.br bancodevenezuela.com techradar.com brilio.net ytmp3.cc huaban.com airtable.com npr.org usps.com ptt.cc sarkariresult.com dagospia.com hotels.com elyamnelaraby.com as.com blog.jp spanishdict.com rarbg.to axisbank.co.in basichouse.tmall.com uptodown.com gamespot.com evernote.com disneyplus.com xianjichina.com ameritrade.com yallakora.com herokuapp.com britannica.com google.com.pe google.az bolasport.com crptgate.com cnki.net mfisp.com myworkdayjobs.com uchi.ru sdamgia.ru rezka.ag qiannaimei.tmall.com nikkei.com epicgames.com gmarket.co.kr bhphotovideo.com myway.com concursolutions.com auction.co.kr etrade.com pandora.com threegun.tmall.com bershka.tmall.com dictionary.com ceconline.com wondershare.com 360doc.com wordpress.org sputniknews.com study.com shopify.com ih5.cn google.be borna.news chron.com turkiye.gov.tr baike.com dai.tmall.com car.tmall.com att.com timeanddate.com atlassian.net gusuwang.com kooora.com umeng.com asriran.com dmm.com yeniakit.com.tr mega.nz kundelik.kz epfindia.gov.in lenta.ru hollisterco.tmall.com dalfak.com hubspot.com goo.ne.jp huffpost.com mofidonline.com eastmoney.com banggood.com google.com.my lachapelle.tmall.com zomato.com agah.com uidai.gov.in kitco.com smartsheet.com schwab.com yelp.com harvard.edu zhaopin.com ign.com entrepreneur.com jamnews.com elintransigente.com chaoxing.com ku.tmall.com ibm.com depositphotos.com leagueoflegends.com argaam.com nur.kz livedoor.jp runoob.com bitly.com nvxie.tmall.com hm.com corriere.it uzone.id livescore.com cpic.com.cn nba.com akamaized.net playstation.com gucn.tmall.com payu.in emofid.com maniform.tmall.com ekaie.com iyiou.com newbalance.tmall.com aofex.com kijiji.ca anta.tmall.com lining.tmall.com gamersky.com usnews.com upwork.com calvinklein.tmall.com discogs.com house365.com superuser.com animeflv.net gainreel.tmall.com royalbank.com cnbeta.com hatenablog.com bag.tmall.com fairwhale.tmall.com 91jm.com drom.ru skroutz.gr farsnews.ir mynet.com wiktionary.org spiegel.de it168.com kayak.com google.ch osskanger.com mayoclinic.org jb51.net lenovo.com intentmedia.net hqq.tv urdupoint.com service-now.com winshang.com hawaaworld.com mlb.com google.ae cisco.com jooble.org google.pt honeys.tmall.com ozon.ru elmundo.es dnevnik.ru zara.com freejobalert.com shopee.vn asus.com mi.com gamer.com.tw billdesk.com xe.com discoveryexpedition.tmall.com nanzhuang.tmall.com americanexpress.com biblegateway.com smzdm.com tueren-fachhandel.de friv.com flickr.com gds.it t-online.de bmi.ir kknews.cc online-convert.com vseigru.net ukr.net verizonwireless.com mufg.jp custhelp.com 312168.com tgju.org 52pojie.cn drudgereport.com wp.com shouji.tmall.com caixa.gov.br epwk.com americanas.com.br 5ch.net newtalk.tw 58.com wowhead.com peacebird.tmall.com fivethirtyeight.com w3school.com.cn 126.com baby.tmall.com dw.com semir.tmall.com nhk.or.jp sf-express.com over-blog.com eland.tmall.com hh.ru hamariweb.com banma.com hespress.com duba.com donya-e-eqtesad.com 123rf.com joins.com seasonvar.ru inc.com aljazeera.com isna.ir sparknotes.com it.tmall.com howtogeek.com septwolves.tmall.com sporx.com zougla.gr kolonsport.tmall.com google.nl hi.ru forever21.tmall.com dafont.com dawn.com manyvids.com storm.mg farfetch.com bzw315.com chinachugui.com sdpnoticias.com live.net ytimg.com www.gob.mx free.fr discover.com nairaland.com nate.com sports.tmall.com ziprecruiter.com ojooo.com 2m.ma amazon.com.au figma.com nta.go.jp myanimelist.net dickies.tmall.com marksandspencer.tmall.com portaldoholanda.com.br ultimate-guitar.com cookpad.com teacherspayteachers.com nyaa.si westernjournal.com haberturk.com alicdn.com torob.com chosun.com zhibo8.cc cengage.com sabq.org gaana.com 360.com uber.com pngtree.com jw.org shomanews.com addthis.com ynet.co.il esprit.tmall.com google.at urfs.tmall.com beeg.com css-tricks.com dytt8.net bild.de iherb.com aastocks.com engadget.com chinamenwang.com indosport.com gotowebinar.com livemint.com bola.net neobux.com imis.tmall.com jackjones.tmall.com multiurok.ru sincenturypro.pro viva.co.id dcinside.com championat.com triumph.tmall.com apkpure.com eenadu.net teamviewer.com vectorstock.com state.gov sabah.com.tr zol.com.cn mercadolibre.com.ve fararu.com banesconline.com pantip.com ansa.it incometaxindiaefiling.gov.in y8.com jeanswest.tmall.com cyberleninka.ru history.com haberler.com soft98.ir premierleague.com kemdikbud.go.id sbrf.ru nhentai.net 3dmgame.com lefigaro.fr pcbaby.com.cn cna.com.tw 9384.com semanticscholar.org pelisplus.me ssc.nic.in newegg.com book.tmall.com gazetaexpress.com thejakartapost.com new3c.tmall.com tapd.cn goal.com edh.tw narcity.com rbc.ru pearsoned.com chinanetrank.com zhcw.com thefreedictionary.com topshop.tmall.com sauwoaptain.com khabaronline.ir news.com.au bandcamp.com tasnimnews.com interia.pl ticketmaster.com chaduo.com subscene.com dreamstime.com cmoney.tw audible.com sci-hub.tw mobile.de jia400.com wunderground.com shopee.com.my axisbank.com theatlantic.com WhatWeb-0.5.5/plugin-development/charset-test-list.txt000066400000000000000000000001571400032546600230230ustar00rootroot00000000000000www.amazon.co.jp www.pravda.ru www.118114.cn 360.cn www.cntv.cn fastpic.ru http://www.columbia.edu/~fdc/utf8/ WhatWeb-0.5.5/plugin-development/find-common-stuff000077500000000000000000000043711400032546600221660ustar00rootroot00000000000000#!/usr/bin/env ruby # helper for plugin research # identify common tags, paths / quoted text in tags, link text # add paths, truncated paths # require 'getoptlong' require 'pp' ignore_tags=%w| ' }, { :text=>'Article Publisher PRO Administrator Control Panel' }, { :text=>'
Admin Control Panel
' }, { :text=>'
' }, # Error page { :text=>'

Please use a proper method to browse article(s) - The method you are using is not allowed...
' }, # Version detection # Powered by text { :version=>/
Powered by Article Publisher PRO<\/a> v([\d\.]+)/ }, ] end WhatWeb-0.5.5/plugins/artifactory.rb000066400000000000000000000017471400032546600174300ustar00rootroot00000000000000## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb ## Plugin.define do name "Artifactory" authors [ "bcoles", # 2020-09-29 ] version "0.1" description "JFrog Artifactory enterprise universal repository manager supports all major packages, enterprise ready secure, clustered, HA, Docker registry, multi-site replication and scalable." website "https://jfrog.com/artifactory/" dorks [ '"Artifactory is happily serving" "Artifactory Version" intitle:"Artifactory"' ] matches [ { :text=>'' }, { :text=>'' }, { :url=>'/ui/auth/screen/footer', :version=>/"buildNumber":"([\d\.]+ rev \d+)"/ }, { :url=>'/artifactory/ui/auth/screen/footer', :version=>/"buildNumber":"([\d\.]+ rev \d+)"/ }, ] end WhatWeb-0.5.5/plugins/artiphp-cms.rb000066400000000000000000000021661400032546600173240ustar00rootroot00000000000000## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions. Please see the WhatWeb # web site for more information on licensing and terms of use. # https://www.morningstarsecurity.com/research/whatweb ## Plugin.define do name "Artiphp-CMS" authors [ "Brendan Coles ", # 2012-05-17 ] version "0.1" description "Artiphp CMS - open source CMS - Requires PHP" website "http://www.artiphp.com/" # Google results as at 2012-05-17 # # 251 for "Artiphp" "2001" "est un logiciel libre" "sous licence GPL" # Dorks # dorks [ '"Artiphp" "2001" "est un logiciel libre" "sous licence GPL"' ] # Matches # matches [ # HTML Comments { :text=>'' }, { :text=>'' }, { :text=>'' }, # Version Detection # Copyright Footer { :version=>/