Nmap Development mailing list archives

[NSE script] web application fingerprinting


From: Sven Klemm <sven () c3d2 de>
Date: Sun, 24 Aug 2008 22:08:06 +0200

Hi,

I've updated my web application fingerprinting script to current nmap.
The script requires libxml2. You can checkout the code from
svn://svn.insecure.org/nmap-exp/sven/nse_sedusa

I've also attached patch which includes all required changes.


The output looks like this:


./nmap host -p 80 --script webapp

Starting Nmap 4.68 ( http://nmap.org ) at 2008-08-24 21:41 CEST
Interesting ports on host:
PORT   STATE SERVICE
80/tcp open  http
|_ WebApp: WordPress 2.0.11

Nmap done: 1 IP address (1 host up) scanned in 2.52 seconds

./nmap host -p443 --script webapp

Starting Nmap 4.68 ( http://nmap.org ) at 2008-08-24 21:42 CEST
Interesting ports on host:
PORT    STATE SERVICE
443/tcp open  https
|_ WebApp: MediaWiki 1.13alpha (r31491)

Nmap done: 1 IP address (1 host up) scanned in 4.07 seconds


Cheers,
Sven

--
Sven Klemm
http://cthulhu.c3d2.de/~sven/

Index: scripts/webapp.nse
===================================================================
--- scripts/webapp.nse  (.../nmap)      (revision 0)
+++ scripts/webapp.nse  (.../nmap-exp/sven/nse_sedusa)  (revision 9708)
@@ -0,0 +1,64 @@
+--- 
+-- Try to guess running webapp and their version.
+--
+--@output
+--@output
 80/tcp open  http
 |_ WebApp: Trac 0.11dev-r6306
 80/tcp open  http
 |_ WebApp: MediaWiki 1.14alpha
 80/tcp open  http
 |_ WebApp: WordPress 2.0.11
+
+require "stdnse"
+require "shortport"
+require "sedusa"
+
+id = "WebApp"
+description = "Try to guess running webapp and their version."
+author = "Sven Klemm <sven () c3d2 de>"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html";
+categories = {"intrusive","safe","discovery"}
+
+
+portrule = shortport.port_or_service({80,443},{'http', 'https', 'http-proxy'})
+
+action = function(host, port)
+  local scheme, hostname, app, exps, index, xpath, u, doc
+  local target = {}
+
+  if port.service == 'https' or port.version.service_tunnel == 'ssl' then
+    target.scheme = "https"
+    if port.number ~= 443 then target.port = port.number end
+  else
+    target.scheme = "http"
+    if port.number ~= 80 then target.port = port.number end
+  end
+
+  target.host = host.targetname or ( host.name and host.name ~= "" and host.name ) or host.ip
+  target.path = nmap.registry.args.webapp or "/"
+
+  u = url.build( target )
+  doc = sedusa.get_document( u )
+
+  for app, exps in pairs( sedusa.hints ) do
+    for index, xpath in pairs( exps ) do
+      if doc.xml:find( xpath ) then
+        if sedusa.verify[app] then
+          local version = sedusa.verify[app]( u )
+          if version then
+            return app .. " " .. version
+          else
+            return app
+          end
+        else
+          stdnse.print_debug( "No verify function for %s found.", app )
+          return app
+        end
+        break
+      end
+    end
+  end
+
+end
+
Index: nselib-bin/xml.c
===================================================================
--- nselib-bin/xml.c    (.../nmap)      (revision 0)
+++ nselib-bin/xml.c    (.../nmap-exp/sven/nse_sedusa)  (revision 9708)
@@ -0,0 +1,151 @@
+
+#include "xml.h"
+
+#include <libxml/HTMLparser.h>
+#include <libxml/xpath.h>
+
+typedef struct XmlDocumentData {
+  xmlDocPtr document;
+} XmlDocumentData;
+
+// create a lua XmlDocument
+int create_document( lua_State * L, xmlDocPtr doc ) {
+  if ( doc ) {
+    // parsing successful
+    XmlDocumentData * doc_data;
+    doc_data = (XmlDocumentData *) lua_newuserdata( L, sizeof(XmlDocumentData));
+    // set metatable for userdata
+    luaL_getmetatable( L, "XmlDocument" );
+    lua_setmetatable( L, -2 );
+    doc_data->document = doc;
+  } else  {
+    // parsing failed
+    luaL_error( L , "parsing document failed." );
+  }
+  return 1;
+}
+
+// takes an html document as string and returns a XmlDocument
+int xml_parse_html( lua_State * L ) {
+  const char * doc_string = luaL_checkstring( L, 1 );
+  lua_pop(L, 1);
+  char * url = NULL;
+  char * encoding = NULL;
+
+  int options = HTML_PARSE_RECOVER | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING | HTML_PARSE_NONET;
+  htmlDocPtr doc = htmlReadDoc( doc_string, url, encoding, options );
+
+  return create_document( L, doc );
+}
+
+// takes an xml document as string and returns a XmlDocument
+int xml_parse_xml( lua_State * L ) {
+  const char * doc_string = luaL_checkstring( L, 1 );
+  lua_pop(L, 1);
+  char * url = NULL;
+  char * encoding = NULL;
+
+  int options = XML_PARSE_RECOVER | XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NONET;
+  xmlDocPtr doc = xmlReadDoc( doc_string, url, encoding, options );
+
+  return create_document( L, doc );
+}
+
+
+static const struct luaL_reg xml_methods[] = {
+  { "parse_html", xml_parse_html },
+  { "parse_xml", xml_parse_xml },
+  { NULL, NULL }
+};
+
+// takes an xpath expression and return the match(es) as string(s)
+int xmldoc_find( lua_State * L ) {
+  XmlDocumentData * doc = (XmlDocumentData *) luaL_checkudata(L, 1, "XmlDocument");
+  const char * xpath = luaL_checkstring( L, 2 );
+  xmlXPathContextPtr context = xmlXPathNewContext( doc->document );
+
+  if ( !context ) luaL_error( L, "Error creating context." );
+
+  xmlXPathObjectPtr result = xmlXPathEvalExpression(xpath, context);
+  xmlXPathFreeContext(context);
+  if ( !result ) luaL_error( L, "Error while evaluating XPath expression." );
+
+  if( xmlXPathNodeSetIsEmpty( result->nodesetval ) ) {
+    // empty resultset
+    lua_pushnil( L );
+    xmlXPathFreeNodeSetList( result );
+    return 1;
+  } else {
+    int i;
+    // we found something .. return first match as string
+    xmlNodeSetPtr nodeset = result->nodesetval;
+    char * tmp = xmlXPathCastNodeToString( nodeset->nodeTab[0] );
+    lua_pushstring( L, tmp );
+    free( tmp );
+    xmlXPathFreeNodeSetList( result );
+    return 1;
+  }
+}
+
+// takes an xpath expression and return the match(es) as table containing the string(s)
+int xmldoc_find_all( lua_State * L ) {
+  XmlDocumentData * doc = (XmlDocumentData *) luaL_checkudata(L, 1, "XmlDocument");
+  const char * xpath = luaL_checkstring( L, 2 );
+  xmlXPathContextPtr context = xmlXPathNewContext( doc->document );
+
+  if ( !context ) luaL_error( L, "Error creating context." );
+
+  xmlXPathObjectPtr result = xmlXPathEvalExpression(xpath, context);
+  xmlXPathFreeContext(context);
+  if ( !result ) luaL_error( L, "Error while evaluating XPath expression." );
+
+  lua_newtable( L );
+
+  if( xmlXPathNodeSetIsEmpty( result->nodesetval ) ) {
+    // empty resultset
+  } else {
+    int i;
+    // we found something
+    xmlNodeSetPtr nodeset = result->nodesetval;
+    for ( i = 0; i < nodeset->nodeNr; i++) {
+      lua_pushnumber( L, i + 1 );
+      char * tmp = xmlXPathCastNodeToString( nodeset->nodeTab[i] );
+      lua_pushstring( L, tmp );
+      free( tmp );
+      lua_rawset( L, -3 );
+    }
+  }
+  xmlXPathFreeNodeSetList( result );
+  return 1;
+}
+
+int xmldoc_free( lua_State * L ) {
+  XmlDocumentData * doc = (XmlDocumentData *) luaL_checkudata(L, 1, "XmlDocument");
+  free( doc->document );
+  return 0;
+}
+
+// XmlDocument methods
+static const struct luaL_reg xmldoc_methods[] = {
+  { "find", xmldoc_find },
+  { "find_all", xmldoc_find_all},
+  { "__gc", xmldoc_free },
+  { NULL, NULL }
+};
+
+
+// initializer function, called when library is required
+int luaopen_xml( lua_State * L ) {
+
+  // create metatable
+  luaL_newmetatable( L, "XmlDocument" );
+  // metatable.__index = metatable
+  lua_pushvalue( L, -1 );
+  lua_setfield( L, -2, "__index" );
+  // register methods
+  luaL_register( L, NULL, xmldoc_methods );
+
+  luaL_register( L, "xml", xml_methods );
+  return 1;
+}
+
Index: nselib-bin/Makefile.in
===================================================================
--- nselib-bin/Makefile.in      (.../nmap)      (revision 9708)
+++ nselib-bin/Makefile.in      (.../nmap-exp/sven/nse_sedusa)  (revision 9708)
@@ -15,15 +15,20 @@
 LIBTOOL= ./libtool
 LTFLAGS = --tag=CC --silent
 
-all: bit.so
+all: bit.so xml.so
 
 bit.so: bit.c @LIBTOOL_DEPS@
        $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS) -c bit.c
        $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -avoid-version -module -rpath $(nselib_bindir) $(LDFLAGS) -o bit.la 
bit.lo $(LIBS)
        mv .libs/bit.so bit.so
 
+xml.so: xml.c @LIBTOOL_DEPS@
+       $(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS) -I/usr/include/libxml2 -c xml.c
+       $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -avoid-version -module -rpath $(nselib_bindir) $(LDFLAGS) -o xml.la 
xml.lo $(LIBS)
+       mv .libs/xml.so xml.so
+
 clean: 
-       rm -f bit.so *.la *.lo
+       rm -f bit.so xml.so *.la *.lo
        rm -rf .libs
 
 distclean: clean
Index: nselib-bin/xml.h
===================================================================
--- nselib-bin/xml.h    (.../nmap)      (revision 0)
+++ nselib-bin/xml.h    (.../nmap-exp/sven/nse_sedusa)  (revision 9708)
@@ -0,0 +1,13 @@
+
+#ifndef XMLLIB
+#define XMLLIB
+
+#define XMLLIBNAME "xml"
+
+#include "lauxlib.h"
+#include "lua.h"
+
+LUALIB_API int luaopen_xml(lua_State *L);
+
+#endif
+
Index: nselib/http.lua
===================================================================
--- nselib/http.lua     (.../nmap)      (revision 9708)
+++ nselib/http.lua     (.../nmap-exp/sven/nse_sedusa)  (revision 9708)
@@ -73,8 +73,8 @@
 -- @param url The url of the host.
 -- @param options Options passed to http.get.
 -- @see http.get
-get_url = function(url, options )
-  local parsed = url.parse( url )
+get_url = function( u, options )
+  local parsed = url.parse( u )
   local port = {}
 
   port.service = parsed.scheme
@@ -143,7 +143,7 @@
     return result
   end
 
-  local buffer = stdnse.make_buffer( socket, "\r?\n" )
+  local buffer = stdnse.make_buffer( socket, "\r\n" )
 
   local line, _
   local header, body = {}, {}
@@ -184,15 +184,32 @@
     end
   end
 
-  -- body loop
-  while true do
-    line = buffer()
-    if not line then break end
-    table.insert(body,line)
+  -- handle body
+  if result.header['transfer-encoding'] == 'chunked' then
+    -- if the server used chunked encoding we have to 'dechunk' the answer
+    local counter, chunk_size
+    counter = 0; chunk_size = 0
+    while true do
+      if counter >= chunk_size then
+        counter = 0
+        chunk_size = tonumber( buffer(), 16 )
+        if chunk_size == 0 or not chunk_size then break end
+      end
+      line = buffer()
+      if not line then break end
+      counter = counter + #line + 2
+      table.insert(body,line)
+    end
+  else
+    while true do
+      line = buffer()
+      if not line then break end
+      table.insert(body,line)
+    end
   end
 
   socket:close()
-  result.body = table.concat( body, "\n" )
+  result.body = table.concat( body, "\r\n" )
 
   return result
 
Index: nselib/sedusa.lua
===================================================================
--- nselib/sedusa.lua   (.../nmap)      (revision 0)
+++ nselib/sedusa.lua   (.../nmap-exp/sven/nse_sedusa)  (revision 9708)
@@ -0,0 +1,316 @@
+---
+--@author Sven Klemm <sven () c3d2 de>
+
+module(... or "sedusa", package.seeall)
+
+require 'stdnse'
+require 'http'
+require 'xml'
+
+hints = {}
+verify = {}
+
+document_cache = {}
+
+Document = {
+  new = function( doc )
+    doc.xml = nil
+    if string.len( doc.body ) > 0 then
+      doc.xml = xml.parse_html( doc.body )
+    end
+    return doc
+  end
+}
+
+http_get = function( u )
+  stdnse.print_debug( 2, "Fetching %s", u )
+  local document = http.get_url( u )
+  return Document.new( document )
+end
+
+get_document = function( url )
+  local document
+
+  if not document_cache[ url ] then
+    document = http_get( url )
+    document_cache[ url ] = document
+  else
+    document = document_cache[ url ]
+  end
+  if document.status == 301 or
+     document.status == 302
+   then
+    -- take care of relative redirects
+    if not document.header.location:match('https?://') then
+      local base = url:match('^(.*/)[^/]*$')
+      document.header.location = base .. document.header.location
+    end
+    document = get_document( document.header.location )
+  end
+
+  return document
+end
+
+
+-- setup default detector for some web applications
+for key, app in pairs( {'b2evolution', 'bBlog', 'C3D2-Web', 'DokuWiki', 'gitweb', 'Midgard', 'Nucleus CMS', 
'Pentabarf', 'PhpWiki', 'Plone', 'PostNuke', 'TYPO3', 'vBulletin'} ) do
+  hints[app] = { '//meta[@name="generator" and starts-with(@content,"' .. app .. '")]' }
+  verify[app] = function( url )
+    local document = get_document( url )
+    -- look in generator meta tag
+    local generator = document.xml:find( '//meta[@name="generator"]/@content' )
+    if generator and string.match( generator, app .. " (.*)" ) then
+      return string.match( generator, app .. " (.*)" )
+    end
+  end
+end
+
+hints['Bugzilla'] = {
+  '//div[@id="banner"]/p[@id="banner-version"]/a[@href="http://www.bugzilla.org/"]/span[text()="Bugzilla"]',
+  '//div[@id="header"]/table/tr/td[@id="title"]/p[starts-with(text(),"Bugzilla")]',
+}
+
+verify['Bugzilla'] = function( u )
+  local doc = get_document( u )
+  local version = doc.xml:find( '//div[@id="banner"]/p[@id="banner-version" and 
a[@href="http://www.bugzilla.org/"]/span[text()="Bugzilla"]]/span[starts-with(text(),"Version ")]/text()' )
+  if version then
+    return version:match('Version (.*)')
+  end
+  version = doc.xml:find( '//div[@id="header"]/table/tr/td[@id="information"]/p[@class="header_addl_info"]/text()' )
+  if version then
+    return version:match('version (.*)')
+  end
+end
+
+hints['Burning Board'] = {
+  '//span[@class="smallfont" and text()="Powered by "]/b[starts-with(text(),"WoltLab Burning Board")]',
+  '//span[@class="smallfont"]/a[@href="http://www.woltlab.de"]/b[contains(text(),"Burning Board")]',
+}
+
+verify['Burning Board'] = function( u )
+  local doc = get_document( u )
+  local version = doc.xml:find( hints['Burning Board'][1] )
+  if not version then version = doc.xml:find( hints['Burning Board'][2] ) end
+  if version then
+    return version:match('Burning Board (.*)')
+  end
+end
+
+hints['CommunityServer'] = {
+  '//meta[@name="GENERATOR" and starts-with(@content, "CommunityServer")]/@content',
+  '//a[@href="http://communityserver.org/r.ashx?1"; and @target="_blank"]/img[starts-with(@alt,"Powered by 
CommunityServer")]',
+}
+
+verify['CommunityServer'] = function( u )
+  local doc = get_document( u )
+  local version = doc.xml:find( hints['CommunityServer'][1] )
+  if version then
+    return version:match("^CommunityServer (.*)$")
+  end
+end
+
+hints['Cisco VPN 3000 Concentrator'] = {
+  '//title[starts-with(@text,"Cisco Systems, Inc. VPN 3000 Concentrator [")]',
+  '//form/table/tr/td[@align="right" and @colspan="2" and text()="VPN 3000 Concentrator"]',
+}
+
+hints['Drupal'] = {
+  '//div[@id="block-user-0" and h2[text()="User 
login"]]/div[@class="content"]/form[@id="user-login-form"]/div[div[@class="form-item"]/input[@type="text" and 
@class="form-text required"]]',
+  '//p[@class="foot2"]/a[@href="http://drupal.org"]/img[contains(@title,"Powered by Drupal")]',
+}
+
+hints['Flyspray'] = {
+  '//p[@id="footer"]/a[@href="http://flyspray.org/"; and @class="offsite" and starts-with(text(),"Powered by 
Flyspray")]',
+  '//p[@id="footer"]/a[@href="http://flyspray.rocks.cc/"; and @class="offsite" and starts-with(text(),"Powered by 
Flyspray")]',
+}
+
+verify['Flyspray'] = function( u )
+  local doc = get_document( u )
+  local foot = doc.xml:find( hints['Flyspray'][1] )
+  if foot then return foot:match('Powered by Flyspray (.*)') end
+end
+
+hints['Joomla!'] = {
+  '//meta[@name="Generator" and starts-with(@content,"Joomla!")]'
+}
+
+hints['GForge'] = {
+  '//a[@href="http://gforge.org"]/img[@alt="Powered by GForge"]',
+}
+
+hints['MediaWiki'] = {
+  '//body[contains(@class, "mediawiki")]',
+  '//div[@id="footer"]/div[@id="f-poweredbyico"]/a[@href="http://www.mediawiki.org/"]/img&apos;,
+  '//head/script[@type="text/javascript" and contains(@text,"var wgVersion")]'
+}
+
+verify['MediaWiki'] = function( u )
+  local document = get_document( u )
+  -- find out location of index.php
+  local link = document.xml:find( '//a[contains(@href,"index.php?title=") and starts-with(@href, "/")]/@href' )
+  if link then
+    link = link:match( "^(.*/index.php[?]title=).*")
+  else
+    link = document.xml:find( '//script[@type="text/javascript" and contains(text(), "var wgScriptPath = ")]' )
+    link = link:gsub( "[\r\n]", "" )
+    if link then
+      link = link:match( 'var wgScriptPath = "([^"]+)"') .. '/index.php?title='
+    end
+  end
+
+  -- Special:Version has the most exact version including svn revision
+  local version = get_document( url.absolute( u, link .. "Special:Version" ) )
+  if version.xml:find('//div/ul/li[a[@href="http://www.mediawiki.org/"; and text()="MediaWiki"]]') then
+    return string.match( version.xml:find('//div/ul/li[a[@href="http://www.mediawiki.org/"; and text()="MediaWiki"]]'), 
"MediaWiki: ([^\r\n]+)" )
+  end
+  if version.xml:find('//table[@id="sv-software"]/tr[td[a[@href="http://www.mediawiki.org/"; and 
text()="MediaWiki"]]]') then
+    return version.xml:find('//table[@id="sv-software"]/tr[td[a[@href="http://www.mediawiki.org/"; and 
text()="MediaWiki"]]]/td[2]')
+  end
+
+  -- the version without revision is included on every page as javascript variable on recent versions
+  local js = document.xml:find('//script[@type="text/javascript" and contains(text(), "var wgVersion = ")]')
+  if js then
+    js = js:gsub( "[\r\n]", "" )
+    return js:match( 'var wgVersion = "([^"]+)"')
+  end
+
+  -- get version from atom feed
+  local atom = get_document( url.absolute( u, link .. "Special:Recentchanges&feed=atom" ) )
+  if atom.xml:find( '//generator[starts-with(text(), "MediaWiki")]' ) then
+    return string.match( atom.xml:find( '//generator/text()' ), "^MediaWiki (.*)$" )
+  end
+end
+
+hints['OpenWRT'] = {
+  '//head/title[text()="OpenWrt Administrative Console"]'
+}
+
+verify['OpenWRT'] = function( u )
+  local document = get_document( u )
+  local webif = document.xml:find( '//head/meta[@http-equiv="refresh"]/@content' )
+  if webif and webif:match('0; URL=') then
+    document = get_document( url.absolute( u, webif:match( '0; URL=(.*)' )) )
+    local version = document.xml:find( 
'//div[@id="header"]/div[@id="header-title"]/div[@id="short-status"]/ul/li[strong[text()="Version:"]]/text()' )
+    if version then
+      return version:match( ' +(.*)' )
+    end
+  end
+end
+
+hints['PHP-Nuke'] = {
+  '//meta[@name="GENERATOR" and starts-with(@content,"PHP-Nuke")]'
+}
+
+hints['phpMyAdmin'] = {
+  '//title[starts-with(text(), "phpMyAdmin")]',
+  '//link[@rel="stylesheet" and  @type="text/css" and contains(@href,"css/phpmyadmin.css.php")]',
+  '//a[@href="http://www.phpmyadmin.net"; and @class="logo" and @target="_blank"]/img[@alt="phpMyAdmin"]'
+}
+
+verify['phpMyAdmin'] = function( url )
+  local document = get_document( url )
+  local title = document.xml:find( '//title[starts-with(text(), "phpMyAdmin")]/text()' )
+  local version = title:match('^phpMyAdmin (.*)$')
+  if version then
+    return version
+  end
+end
+
+hints['phpSysInfo'] = {
+  '//a[@href="http://phpsysinfo.sourceforge.net"; and @target="_blank" and starts-with(text(),"phpSysInfo")]'
+}
+
+verify['phpSysInfo'] = function( url )
+  local document = get_document( url )
+  local version = document.xml:find( '//a[@href="http://phpsysinfo.sourceforge.net"; and @target="_blank" and 
starts-with(text(),"phpSysInfo")]/text()' )
+  version = version:match('^phpSysInfo[-](.*)$')
+  if version then
+    return version
+  end
+end
+
+verify['PhpWiki'] = function( url )
+  local document = get_document( url )
+  local generator = document.xml:find( '//meta[@name="PHPWIKI_VERSION"]/@content' )
+  if generator then return generator end
+end
+
+verify['Plone'] = function( u )
+  local document = get_document( u )
+  -- look in header
+  if document.header.server then
+    local version = document.header.server:match( 'Plone/(%d.%d.%d)' )
+    if version then return version end
+  end
+end
+
+hints['Serendipity'] = {
+  '//meta[@name="Powered-By" and starts-with(@content, "Serendipity")]',
+  '//div[@id="serendipity_banner"]',
+  '//div[@class="serendipity_entry_body"]',
+  '//div[@class="serendipity_entryFooter"]',
+}
+
+verify['Serendipity'] = function( u )
+  local document = get_document( u )
+  local generator = document.xml:find( '//meta[@name="Powered-By"]/@content' )
+  if generator and string.match( generator, 'Serendipity v[.](.*)' ) then
+    return string.match( generator, 'Serendipity v[.](.*)' )
+  end
+  local rss_url = document.xml:find( '//link[@rel="alternate" and @type="application/rss+xml"]/@href' )
+  if rss_url then
+    document = get_document( url.absolute( u, rss_url ) )
+    generator = document.xml:find( '//generator')
+    if generator and string.match( generator, 'Serendipity [0-9.]+' ) then
+      return string.match( generator, 'Serendipity ([0-9.]+)' )
+    end
+  end
+end
+
+hints['SMF'] = {
+  '//span[@class="smalltext"]/a[@href="http://www.simplemachines.org/"; and contains(text(),"Powered by SMF")]',
+}
+
+verify['SMF'] = function(u)
+  local doc = get_document( u )
+  local foot = doc.xml:find( hints['SMF'][1] )
+  if foot then
+    return foot:match('Powered by SMF (.*)')
+  end
+end
+
+hints['Trac'] = {
+  '//div[@id="footer"]/a[@id="tracpowered" and @href="http://trac.edgewall.org/"]/img[@alt="Trac Powered"]',
+}
+
+verify['Trac'] = function( u )
+  local document = get_document( u )
+  local version = document.xml:find( '//div[@id="footer" and a[@id="tracpowered" and 
@href="http://trac.edgewall.org/"]]/p[@class="left"]/a/strong/text()' )
+  if version and version:match( 'Trac (.*)' ) then
+    return version:match( 'Trac (.*)' )
+  end
+end
+
+hints['WordPress'] = {
+  '//meta[@name="generator" and starts-with(@content,"WordPress")]',
+  '//head/link[@rel="stylesheet" and @type="text/css" and contains( @href, "/wp-content/")]',
+  '//head/style[contains( text(), "/wp-content/")]',
+  '//div[@id="content"]/div[@class="post" and starts-with(@id, "post-") and div[@class="posttitle"] and 
div[@class="postmeta"] and div[@class="postbody"] and div[@class="postfooter"]]',
+  '//ul/li/a[@href="http://wordpress.org/"; and starts-with(@title,"Powered by WordPress")]',
+}
+
+verify['WordPress'] = function( u )
+  local document = get_document( u )
+  -- look in generator meta tag
+  local generator = document.xml:find( '//meta[@name="generator"]/@content' )
+  if generator and string.match( generator, "WordPress (.*)" ) then
+    return string.match( generator, "WordPress (.*)" )
+  end
+  -- look in atom feed
+  local atom = document.xml:find( '//link[@rel="alternate" and @type="application/atom+xml"]/@href' )
+  local feed = get_document( atom )
+  if feed.xml:find( '//generator[text()="WordPress"]/@version' ) then
+    return feed.xml:find( '//generator[text()="WordPress"]/@version' )
+  end
+end
+

_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://SecLists.Org

Current thread: