diff --git a/nselib/data/http-devframework-fingerprints.lua b/nselib/data/http-devframework-fingerprints.lua
new file mode 100644
index 000000000..09179bd82
--- /dev/null
+++ b/nselib/data/http-devframework-fingerprints.lua
@@ -0,0 +1,363 @@
+local http = require "http"
+local table = require "table"
+local url = require "url"
+
+---
+-- http-devframework-fingerprints.lua
+-- This file contains fingerprint data for http-devframework.nse
+--
+-- STRUCTURE:
+-- * name - Descriptive name
+-- * rapidDetect - Callback function that is called in the beginning
+-- of detection process. It takes the host and port of the target website as
+-- arguments.
+-- * consumingDetect - Callback function that is called for each
+-- spidered page. It takes the body of the response (HTML source code) and the
+-- requested path as arguments.
+---
+
+
+tools = { Django = { rapidDetect = function(host, port)
+
+ -- Check if the site gives that familiar Django admin login page.
+ response = http.get(host, port, "/admin/")
+
+ if response.body then
+ if string.find(response.body, "Log in | Django site admin") or
+ string.find(response.body, "this_is_the_login_form") or
+ string.find(response.body, "csrfmiddlewaretoken") then
+ return "Django detected. Found Django admin login page on /admin/"
+ end
+ end
+
+ -- In Django, the cookie sessionid is being set when you log in
+ -- and forms will probably set a cookie called csrftoken.
+ if response.cookies then
+ for _, c in pairs(response.cookies) do
+ if c.name == "csrftoken" then
+ return "Django detected. Found sessionid cookie which means the contrib.auth package for authentication is enabled."
+ elseif c.name == "sessionid" then
+ return "Django detected. Found csrftoken cookie."
+ end
+ end
+ end
+
+ -- See if DEBUG mode still happens to be true.
+ response = http.get(host, port, "/random404page/")
+
+ if response.body then
+ if string.find(response.body, "DEBUG = True") then
+ return "Django detected. Found Django error page on /random404page/"
+ end
+ end
+
+ end,
+
+ consumingDetect = function(page, path)
+ if page then
+ if string.find(page, "csrfmiddlewaretoken") then
+ return "Django detected. Found csrfmiddlewaretoken on " .. path
+ end
+ if string.find(page, "id=\"id_") then
+ return "Django detected. Found id_ preffix in id attribute name on " .. path
+ end
+ if string.find(page, "%-TOTAL%-FORMS") or string.find(page, "%-DELETE") then
+ return "Django detected. Found -TOTAL-FORMS and -DELETE hidden inputs, which means there is a Django formset on " .. path
+ end
+ end
+ end
+ },
+
+ RubyOnRails = { rapidDetect = function(host, port)
+
+ response = http.get(host, port, "/")
+
+ -- Check for Mongrel or Passenger in the "Server" or "X-Powered-By" header
+ for h, v in pairs(response.header) do
+ if h == "x-powered-by" or h == "server" then
+ local vl = v:lower()
+ local m = vl:match("mongrel") or vl:match("passenger")
+ if m then
+ return "RoR detected. Found '" .. m .. "' in " .. h .. " header sent by the server."
+ end
+ end
+ end
+
+ -- /rails/info/propertires shows project info when in development mode
+ response = http.get(host, port, "/rails/info/properties")
+
+ if response.body then
+ if string.find(response.body, "Ruby version") then
+ return "RoR detected. Found properties file on /rails/info/properties/"
+ end
+ end
+
+ -- Make up a bad path and match the error page
+ response = http.get(host, port, "/random404page/")
+
+ if response.body then
+ if string.find(response.body, "Routing Error") then
+ return "RoR detected. Found RoR routing error page on /random404page/"
+ end
+ end
+
+ end,
+
+ consumingDetect = function(page, path)
+
+ -- Check the source and look for csrf patterns.
+ if page then
+ if string.find(page, "csrf%-param") or string.find(page, "csrf%-token") then
+ return "RoR detected. Found csrf field on" .. path
+ end
+ end
+
+ end
+ },
+
+
+ ASPdotNET = { rapidDetect = function(host, port)
+
+ response = http.get(host, port, "/")
+
+ -- Look for an ASP.NET header.
+ for h, v in pairs(response.header) do
+ vl = v:lower()
+ if h == "x-aspnet-version" or string.find(vl, "asp") then
+ return "ASP.NET detected. Found related header."
+ end
+ end
+
+ if response.cookies then
+ for _, c in pairs(response.cookies) do
+ if c.name == "aspnetsessionid" then
+ return "ASP.NET detected. Found aspnetsessionid cookie."
+ end
+ end
+ end
+ end,
+
+ consumingDetect = function(page, path)
+ -- Check the source and look for common traces.
+ if page then
+ if string.find(page, " __VIEWSTATE") or
+ string.find(page, "__EVENT") or
+ string.find(page, "__doPostBack") or
+ string.find(page, "aspnetForm") or
+ string.find(page, "ctl00_") then
+ return "ASP.NET detected. Found common traces on" .. path
+ end
+ end
+ end
+ },
+
+ CodeIgniter = { rapidDetect = function(host, port)
+
+ -- Match default error page.
+ response = http.get(host, port, "/random404page/")
+
+ if response.body then
+ if string.find(response.body, "#990000") and
+ string.find(response.body, "404 Page Not Found") then
+ return "CodeIgniter detected. Found CodeIgniter default error page on /random404page/"
+ end
+ end
+
+ end,
+
+ consumingDetect = function(page, path)
+ return
+ end
+ },
+
+ CakePHP = { rapidDetect = function(host, port)
+
+
+ -- Find CAKEPHP header.
+ response = http.get(host, port, "/")
+
+ for h, v in pairs(response.header) do
+ vl = v:lower()
+ if string.find(vl, "cakephp") then
+ return "CakePHP detected. Found related header."
+ end
+ end
+
+ end,
+
+ consumingDetect = function(page, path)
+ return
+ end
+ },
+
+ Symfony = { rapidDetect = function(host, port)
+
+ -- Find Symfony header.
+ response = http.get(host, port, "/")
+
+ for h, v in pairs(response.header) do
+ vl = v:lower()
+ if string.find(vl, "symfony") then
+ return "Symfony detected. Found related header."
+ end
+ end
+
+ end,
+
+ consumingDetect = function(page, path)
+ return
+ end
+ },
+
+ Wordpress = { rapidDetect = function(host, port)
+
+ -- Check for common traces in the source code.
+ response = http.get(host, port, "/")
+
+ if response.body then
+ if string.find(response.body, "content=[\"']WordPress") or
+ string.find(response.body, "wp%-content") then
+ return "Wordpress detected. Found common traces on /"
+ end
+ end
+
+ -- Check if the default login page exists.
+ response = http.get(host, port, "/wp%-login")
+
+ if response.status == "200" then
+ return "Wordpress detected. Found WP login page on /wp-login"
+ end
+ end,
+
+ consumingDetect = function(page, path)
+ if page then
+ if string.find(page, "content=[\"']WordPress") or
+ string.find(page, "wp-content") then
+ return "Wordpress detected. Found common traces on " .. page
+ end
+ end
+ end
+ },
+
+ Joomla = { rapidDetect = function(host, port)
+
+
+ -- Check for common traces in the source code.
+ response = http.get(host, port, "/")
+
+ if response.body then
+ if string.find(response.body, "content=[\"']Joomla!") then
+ return "Joomla detected. Found common traces on /"
+ end
+ end
+
+ -- Check if the default login page exists.
+ response = http.get(host, port, "/administrator")
+
+ if response.body and string.find(response.body, "Joomla") then
+ return "Joomla detected. Found Joomla login page on /administrator/"
+ end
+
+ end,
+
+ consumingDetect = function(page, path)
+ if page and string.find(page, "content=[\"']Joomla!") then
+ return "Joomla detected. Found common traces on " .. page
+ end
+ end
+ },
+
+ Drupal = { rapidDetect = function(host, port)
+
+ -- Check for common traces in the source code.
+ response = http.get(host, port, "/")
+
+ if response.body then
+ if string.find(response.body, "content=[\"']Drupal") then
+ return "Drupal detected. Found common traces on /"
+ end
+ end
+ end,
+
+ consumingDetect = function(page, path)
+ if page and string.find(page, "content=[\"']Drupal") then
+ return "Drupal detected. Found common traces on " .. page
+ end
+ end
+ },
+
+ MediaWiki = { rapidDetect = function(host, port)
+
+ -- Check for common traces in the source code.
+ response = http.get(host, port, "/")
+
+ if response.body then
+ if string.find(response.body, "content=[\"']MediaWiki") or
+ string.find(response.body, "/mediawiki/") then
+ return "MediaWiki detected. Found common traces on /"
+ end
+ end
+ end,
+
+ consumingDetect = function(page, path)
+ if page and string.find(page, "content=[\"']MediaWiki") or
+ string.find(page, "/mediawiki/") then
+ return "MediaWiki detected. Found common traces on " .. page
+ end
+ end
+ },
+
+ ColdFusion = { rapidDetect = function(host, port)
+
+ response = http.get(host, port, "/")
+
+ if response.cookies then
+ for _, c in pairs(response.cookies) do
+ if c.name == "cfid" or c.name == "cftoken" then
+ return "ColdFusion detected. Found " .. c.name .. " cookie."
+ end
+ end
+ end
+ end,
+
+ consumingDetect = function(page, path)
+ return
+ end
+ },
+
+ Broadvision = { rapidDetect = function(host, port)
+
+ response = http.get(host, port, "/")
+
+ if response.cookies then
+ for _, c in pairs(response.cookies) do
+ if string.find(c.name, "bv_") then
+ return "Broadvision detected. Found " .. c.name .. " cookie."
+ end
+ end
+ end
+ end,
+
+ consumingDetect = function(page, path)
+ return
+ end
+ },
+
+ WebSphereCommerce = { rapidDetect = function(host, port)
+
+ response = http.get(host, port, "/")
+
+ if response.cookies then
+ for _, c in pairs(response.cookies) do
+ if string.find(c.name, "wc_") then
+ return "WebSphere Commerce detected. Found " .. c.name .. " cookie."
+ end
+ end
+ end
+ end,
+
+ consumingDetect = function(page, path)
+ return
+ end
+ },
+ }
diff --git a/scripts/http-devframework.nse b/scripts/http-devframework.nse
new file mode 100644
index 000000000..03f468800
--- /dev/null
+++ b/scripts/http-devframework.nse
@@ -0,0 +1,145 @@
+description = [[
+
+Tries to find out the technology behind the target website.
+
+The script checks for certain defaults that might not have been changed, like
+common headers or URLs or HTML content.
+
+While the script does some guessing, note that overall there's no way to
+determine what technologies a given site is using.
+
+You can help improve this script by adding new entries to
+nselib/data/http-tools-fingerprints.lua
+
+Each entry must have:
+* rapidDetect - Callback function that is called in the beginning
+of detection process. It takes the host and port of target website as arguments.
+* consumingDetect - Callback function that is called for each
+spidered page. It takes the body of the response (HTML code) and the requested
+path as arguments.
+
+Note that the consumingDetect callback will not take place only if
+rapid option is enabled.
+
+]]
+
+---
+-- @usage nmap -p80 --script http-devframework.nse
+--
+-- @args http-errors.rapid boolean value that determines if a rapid detection
+-- should take place. The main difference of a rapid vs a lengthy detection
+-- is that second one requires crawling through the website. Default: false
+-- (lengthy detection is performed)
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 80/tcp open http syn-ack
+-- |_http-devframework: Django detected. Found Django admin login page on /admin/
+---
+
+categories = {"discovery", "intrusive"}
+author = "George Chatzisofroniou"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+
+local http = require "http"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local table = require "table"
+local string = require "string"
+local httpspider = require "httpspider"
+
+portrule = shortport.port_or_service( {80, 443}, {"http", "https"}, "tcp", "open")
+
+local function loadFingerprints(filename, cat)
+
+ local file, fingerprints
+
+ -- Find the file
+ filename = nmap.fetchfile('nselib/data/' .. filename) or filename
+
+ -- Load the file
+ stdnse.print_debug(1, "%s: Loading fingerprints: %s", SCRIPT_NAME, filename)
+ local env = setmetatable({fingerprints = {}}, {__index = _G});
+ file = loadfile(filename, "t", env)
+
+ if( not(file) ) then
+ stdnse.print_debug(1, "%s: Couldn't load the file: %s", SCRIPT_NAME, filename)
+ return
+ end
+
+ file()
+ fingerprints = env.tools
+
+ return fingerprints
+
+end
+
+action = function(host, port)
+
+ local tools = stdnse.get_script_args("http-devframework.fingerprintfile") or loadFingerprints("nselib/data/http-devframework-fingerprints.lua")
+ local rapid = stdnse.get_script_args("http-devframework.rapid")
+
+ local d
+
+ -- Run rapidDetect() callbacks.
+ for f, method in pairs(tools) do
+ d = method["rapidDetect"](host, port)
+ if d then
+ return d
+ end
+ end
+
+ local crawler = httpspider.Crawler:new(host, port, '/', { scriptname = SCRIPT_NAME,
+ maxpagecount = 40,
+ maxdepth = -1,
+ withinhost = 1
+ })
+
+ if rapid then
+ return "Couldn't determine the underlying framework or CMS. Try turning off 'rapid' mode."
+ end
+
+ crawler.options.doscraping = function(url)
+ if crawler:iswithinhost(url)
+ and not crawler:isresource(url, "js")
+ and not crawler:isresource(url, "css") then
+ return true
+ end
+ end
+
+ crawler:set_timeout(10000)
+
+ while (true) do
+
+ local response, path
+
+ status, r = crawler:crawl()
+ -- if the crawler fails it can be due to a number of different reasons
+ -- most of them are "legitimate" and should not be reason to abort
+ if (not(status)) then
+ if (r.err) then
+ return stdnse.format_output(true, ("ERROR: %s"):format(r.reason))
+ else
+ break
+ end
+ end
+
+ response = r.response
+ path = tostring(r.url)
+
+ if (response.body) then
+
+ -- Run consumingDetect() callbacks.
+ for f, method in pairs(tools) do
+ d = method["consumingDetect"](response.body, path)
+ if d then
+ return d
+ end
+ end
+ end
+
+ return "Couldn't determine the underlying framework or CMS. Try increasing 'httpspider.maxpagecount' value to spider more pages."
+
+ end
+
+end