Nmap Development mailing list archives
[NSE] Web application detection
From: Sven Klemm <sven () c3d2 de>
Date: Tue, 10 Jul 2007 11:36:28 +0200
Hello everyone, I've attached a nse script for fingerprinting web applications. The script requires my libxml2 wrapper for lua which you can check out with git-clone http://cthulhu.c3d2.de/~sven/git/luaxml.git You can check out the code directly from the repository with git-clone http://cthulhu.c3d2.de/~sven/git/sedusa.git In the repository you can also find a command line version of the script which runs without nmap und uses luacurl for page fetching. Example output: Interesting ports on 10.0.0.10: PORT STATE SERVICE 80/tcp open http |_ Sedusa: MediaWiki 1.9alpha (r18790) 443/tcp open https |_ Sedusa: MediaWiki 1.9alpha (r18790) Interesting ports on 10.0.0.11: PORT STATE SERVICE 80/tcp open http |_ Sedusa: Trac 0.11dev-r5790 443/tcp open https |_ Sedusa: Trac 0.11dev-r5790 Interesting ports on 10.0.0.12: PORT STATE SERVICE 80/tcp open http |_ Sedusa: WordPress 2.0.9 443/tcp open https |_ Sedusa: WordPress 2.0.9 Cheers, Sven
id = "Sedusa"
description = "Connects to an HTTP server and tries to guess the running web application."
author = "Sven Klemm <sven () c3d2 de>"
license = "See nmaps COPYING for licence"
categories = {"safe"}
require "stdnse"
require "shortport"
require "url"
require "xml"
portrule = shortport.service({'http', 'https', 'ssl/http'})
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
if (host.name and not host.name == "") then
target.host = host.name
else
target.host = host.ip
end
target.path = "/"
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:xpath( xpath ) then
if Sedusa.verify[app] then
local version = Sedusa.verify[app]( u )
if version then
return app .. " " .. version
else
return app .. " version not identified."
end
else
return "No verify function for " .. app .. " found."
end
break
end
end
end
end
Sedusa = {
hints = {},
verify = {},
document_cache = {},
Document = {
new = function( h, b )
local parsed = nil
if string.len( b ) > 0 then
parsed = XML.parse_html( b )
end
return { header = h, body = b, xml = parsed }
end
},
get_document = function( url )
local document
if not Sedusa.document_cache[ url ] then
document = Sedusa.http_get( url )
Sedusa.document_cache[ url ] = document
else
document = Sedusa.document_cache[ url ]
end
if document.header['Status'] == 301 or
document.header['Status'] == 302
then
document = Sedusa.get_document( document.header['Location'] )
end
return document
end,
http_get = function( u )
local protocol, port, request, query, socket
local parsed = url.parse( u )
if parsed.scheme == 'https' then
protocol = "ssl"
port = 443
else
protocol = "tcp"
port = 80
end
if ( parsed.port ) then port = parsed.port end
query = parsed.path
if ( parsed.query ) then
query = query .. '?' .. parsed.query
end
request = "GET "..query.." HTTP/1.1\r\nHost: "..parsed.host.."\r\n\r\n"
socket = nmap.new_socket()
socket:connect( parsed.host, port, protocol )
socket:send(request)
local buffer = stdnse.make_buffer( socket, "\r?\n")
local status, line, key, value, head, header
head, header = {}, {}
-- head loop
while true do
status, line = buffer()
if (not status or line == "") then break end
table.insert(header,line)
end
-- build nicer table for header
for key, value in pairs( header ) do
if key == 1 then
local code = select( 3, string.find( value, "HTTP/%d\.%d (%d+)") )
head['Status'] = tonumber(code)
else
key, value = select( 3, string.find( value, "(.+): (.*)" ) )
if key and value then
head[key] = value:gsub( '[\r\n]+', '' )
end
end
end
local body = {}
while true do
status, line = buffer()
if (not status) then break end
table.insert(body,line)
end
socket:close()
return Sedusa.Document.new( head, table.concat(body) )
end,
}
-- setup default detector for some web applications
for key, app in pairs( {"b2evolution", "bBlog", "C3D2-Web", "DokuWiki", "gitweb", "Midgard", "Pentabarf", "PhpWiki",
"Plone", "PostNuke", "TYPO3", "vBulletin"} ) do
Sedusa.hints[app] = { '//meta[@name="generator" and starts-with(@content,"' .. app .. '")]' }
Sedusa.verify[app] = function( url )
local document = Sedusa.get_document( url )
-- look in generator meta tag
local generator = document.xml:xpath( '//meta[@name="generator"]/@content' )
if generator and string.match( generator, app .. " (.*)" ) then
return string.match( generator, app .. " (.*)" )
end
end
end
Sedusa.verify["PhpWiki"] = function( url )
local document = Sedusa.get_document( url )
local generator = document.xml:xpath( '//meta[@name="PHPWIKI_VERSION"]/@content' )
if generator then
return generator
end
end
Sedusa.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"]]',
}
Sedusa.hints["Joomla!"] = {
'//meta[@name="Generator" and starts-with(@content,"Joomla!")]'
}
Sedusa.hints["MediaWiki"] = {
'//body[contains(@class, "mediawiki")]',
'//div[@id="footer"]/div[@id="f-poweredbyico"]/a[@href="http://www.mediawiki.org/"]/img'
}
Sedusa.verify["MediaWiki"] = function( u )
local document = Sedusa.get_document( u )
local link = document.xml:xpath( '//a[contains(@href,"index.php?title=") and starts-with(@href, "/")]/@href' )
if link then
link = link:match( "^(.*/index.php[?]title=).*")
else
link = document.xml:xpath( '//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
-- get version from special:version
local version = Sedusa.get_document( url.absolute( u, link .. "Special:Version" ) )
if version.xml:xpath('//div/ul/li[a[@href="http://www.mediawiki.org/" and text()="MediaWiki"]]') then
return string.match( version.xml:xpath('//div/ul/li[a[@href="http://www.mediawiki.org/" and text()="MediaWiki"]]'),
"MediaWiki: ([^\r\n]+)" )
end
-- get version from atom feed
local atom = Sedusa.get_document( url.absolute( u, link .. "Special:Recentchanges&feed=atom" ) )
if atom.xml:xpath( '//generator[starts-with(text(), "MediaWiki")]' ) then
return string.match( atom.xml:xpath( '//generator/text()' ), "^MediaWiki (.*)$" )
end
end
Sedusa.hints["WordPress"] = {
'//meta[@name="generator" and starts-with(@content,"WordPress")]',
'//head/link[@rel="stylesheet" and @type="text/css" and contains( @href, "/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"]]',
}
Sedusa.verify["WordPress"] = function( u )
local document = Sedusa.get_document( u )
-- look in generator meta tag
local generator = document.xml:xpath( '//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:xpath( '//link[@rel="alternate" and @type="application/atom+xml"]/@href' )
local feed = Sedusa.get_document( atom )
if feed.xml:xpath( '//generator[text()="WordPress"]/@version' ) then
return feed.xml:xpath( '//generator[text()="WordPress"]/@version' )
end
return "Version not identified."
end
Sedusa.hints["Serendipity"] = {
'//meta[@name="Powered-By" and starts-with(@content, "Serendipity")]'
}
Sedusa.verify["Serendipity"] = function( u )
local document = Sedusa.get_document( u )
local generator = document.xml:xpath( '//meta[@name="Powered-By"]/@content' )
if generator and string.match( generator, "Serendipity v[.](.*)" ) then
return string.match( generator, "Serendipity v[.](.*)" )
end
end
Sedusa.hints["Trac"] = {
'//div[@id="footer"]/a[@id="tracpowered" and @href="http://trac.edgewall.org/"]/img[@alt="Trac Powered"]',
}
Sedusa.verify["Trac"] = function( u )
local document = Sedusa.get_document( u )
local version = document.xml:xpath( '//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
Attachment:
signature.asc
Description: OpenPGP digital signature
_______________________________________________ Sent through the nmap-dev mailing list http://cgi.insecure.org/mailman/listinfo/nmap-dev Archived at http://SecLists.Org
Current thread:
- [NSE] Web application detection Sven Klemm (Jul 10)
- Re: [NSE] Web application detection Diman Todorov (Jul 10)
- Re: [NSE] Web application detection Sven Klemm (Jul 10)
- Re: [NSE] Web application detection Diman Todorov (Jul 10)
- Re: [NSE] Web application detection Sven Klemm (Jul 10)
- Re: [NSE] Web application detection Diman Todorov (Jul 10)
