mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 12:41:29 +00:00
If you ran the (fortunately non-default) http-domino-enum-passwords script with the (fortunately also non-default) domino-enum-passwords.idpath parameter against a malicious server, it could cause an arbitrarily named file to to be written to the client system. Thanks to Trustwave researcher Piotr Duszynski for discovering and reporting the problem. We've fixed that script, and also updated several other scripts to use a new stdnse.filename_escape function for extra safety. This breaks our record of never having a vulnerability in the 16 years that Nmap has existed, but that's still a fairly good run. [David, Fyodor]
235 lines
6.7 KiB
Lua
235 lines
6.7 KiB
Lua
local coroutine = require "coroutine"
|
|
local http = require "http"
|
|
local io = require "io"
|
|
local shortport = require "shortport"
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
local table = require "table"
|
|
local url = require "url"
|
|
|
|
description = [[
|
|
Checks for backups and swap files of common content management system
|
|
and web server configuration files.
|
|
|
|
When web server files are edited in place, the text editor can leave
|
|
backup or swap files in a place where the web server can serve them. The
|
|
script checks for these files:
|
|
|
|
* <code>wp-config.php</code>: WordPress
|
|
* <code>config.php</code>: phpBB, ExpressionEngine
|
|
* <code>configuration.php</code>: Joomla
|
|
* <code>LocalSettings.php</code>: MediaWiki
|
|
* <code>/mediawiki/LocalSettings.php</code>: MediaWiki
|
|
* <code>mt-config.cgi</code>: Movable Type
|
|
* <code>mt-static/mt-config.cgi</code>: Movable Type
|
|
* <code>settings.php</code>: Drupal
|
|
* <code>.htaccess</code>: Apache
|
|
|
|
And for each of these file applies the following transformations (using
|
|
<code>config.php</code> as an example):
|
|
|
|
* <code>config.bak</code>: Generic backup.
|
|
* <code>config.php.bak</code>: Generic backup.
|
|
* <code>config.php~</code>: Vim, Gedit.
|
|
* <code>#config.php#</code>: Emacs.
|
|
* <code>config copy.php</code>: Mac OS copy.
|
|
* <code>Copy of config.php</code>: Windows copy.
|
|
* <code>config.php.save</code>: GNU Nano.
|
|
* <code>.config.php.swp</code>: Vim swap.
|
|
* <code>config.php.swp</code>: Vim swap.
|
|
* <code>config.php.old</code>: Generic backup.
|
|
|
|
This script is inspired by the CMSploit program by Feross Aboukhadijeh:
|
|
http://www.feross.org/cmsploit/.
|
|
]];
|
|
|
|
---
|
|
-- @usage
|
|
-- nmap --script=http-config-backup <target>
|
|
--
|
|
-- @output
|
|
-- PORT STATE SERVICE REASON
|
|
-- 80/tcp open http syn-ack
|
|
-- | http-config-backup:
|
|
-- | /%23wp-config.php%23 HTTP/1.1 200 OK
|
|
-- |_ /config.php~ HTTP/1.1 200 OK
|
|
--
|
|
-- @args http-config-backup.path the path where the CMS is installed
|
|
-- @args http-config-backup.save directory to save all the valid config files found
|
|
--
|
|
|
|
author = "Riccardo Cecolin";
|
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html";
|
|
categories = { "auth", "intrusive" };
|
|
|
|
|
|
portrule = shortport.http;
|
|
|
|
local function make_grep(pattern)
|
|
return function(s)
|
|
return string.match(s, pattern)
|
|
end
|
|
end
|
|
|
|
local grep_php = make_grep("<%?php");
|
|
local grep_cgipath = make_grep("CGIPath");
|
|
|
|
local function check_htaccess(s)
|
|
return string.match("<Files") or string.match(s, "RewriteRule")
|
|
end
|
|
|
|
local CONFIGS = {
|
|
{ filename = "wp-config.php", check = grep_php }, -- WordPress
|
|
{ filename = "config.php", check = grep_php }, -- phpBB, ExpressionEngine
|
|
{ filename = "configuration.php", check = grep_php }, -- Joomla
|
|
{ filename = "LocalSettings.php", check = grep_php }, -- MediaWiki
|
|
{ filename = "/mediawiki/LocalSettings.php", check = grep_php }, -- MediaWiki
|
|
{ filename = "mt-config.cgi", check = grep_cgipath }, -- Movable Type
|
|
{ filename = "mt-static/mt-config.cgi", check = grep_cgipath }, -- Movable Type
|
|
{ filename = "settings.php", check = grep_php }, -- Drupal
|
|
{ filename = ".htaccess", check = check_htaccess }, -- Apache
|
|
};
|
|
|
|
-- Return directory, filename pair. directory may be empty.
|
|
local function splitdir(path)
|
|
local dir, filename
|
|
|
|
dir, filename = string.match(path, "^(.*/)(.*)$")
|
|
if not dir then
|
|
dir = ""
|
|
filename = path
|
|
end
|
|
|
|
return dir, filename
|
|
end
|
|
|
|
-- Return basename, extension pair. extension may be empty.
|
|
local function splitext(filename)
|
|
local base, ext;
|
|
|
|
base, ext = string.match(filename, "^(.+)(%..+)")
|
|
if not base then
|
|
base = filename
|
|
ext = ""
|
|
end
|
|
|
|
return base, ext
|
|
end
|
|
|
|
-- Functions mangling filenames.
|
|
local TRANSFORMS = {
|
|
function(fn)
|
|
local base, ext = splitext(fn);
|
|
if ext ~= "" then
|
|
return base .. ".bak" -- generic bak file
|
|
end
|
|
end,
|
|
function(fn) return fn .. ".bak" end,
|
|
function(fn) return fn .. "~" end, -- vim, gedit
|
|
function(fn) return "#" .. fn .. "#" end, -- Emacs
|
|
function(fn)
|
|
local base, ext = splitext(fn);
|
|
return base .. " copy" .. ext -- mac copy
|
|
end,
|
|
function(fn) return "Copy of " .. fn end, -- windows copy
|
|
function(fn) return fn .. ".save" end, -- nano
|
|
function(fn) if string.sub(fn, 1, 1) ~= "." then return "." .. fn .. ".swp" end end, -- vim swap
|
|
function(fn) return fn .. ".swp" end, -- vim swap
|
|
function(fn) return fn .. ".old" end, -- generic backup
|
|
};
|
|
|
|
---
|
|
--Creates combinations of backup names for a given filename
|
|
--Taken from: http-backup-finder.nse
|
|
local function backupNames (filename)
|
|
local dir, basename;
|
|
|
|
dir, basename = splitdir(filename);
|
|
return coroutine.wrap(function()
|
|
for _, transform in ipairs(TRANSFORMS) do
|
|
local result = transform(basename);
|
|
|
|
if result == nil then
|
|
elseif type(result) == "string" then
|
|
coroutine.yield(dir .. result);
|
|
result = {result}
|
|
elseif type(result) == "table" then
|
|
for _, r in ipairs(result) do
|
|
coroutine.yield(dir .. r);
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
---
|
|
--Writes string to file
|
|
--Taken from: hostmap.nse
|
|
-- @param filename Filename to write
|
|
-- @param contents Content of file
|
|
-- @return True if file was written successfully
|
|
local function write_file (filename, contents)
|
|
local f, err = io.open(filename, "w");
|
|
if not f then
|
|
return f, err;
|
|
end
|
|
f:write(contents);
|
|
f:close();
|
|
return true;
|
|
end
|
|
|
|
action = function (host, port)
|
|
local path = stdnse.get_script_args("http-config-backup.path") or "/";
|
|
local save = stdnse.get_script_args("http-config-backup.save");
|
|
|
|
local backups = {};
|
|
|
|
if not path:match("/$") then
|
|
path = path .. "/";
|
|
end
|
|
|
|
if not path:match("^/") then
|
|
path = "/" .. path;
|
|
end
|
|
|
|
if (save and not(save:match("/$") ) ) then
|
|
save = save .. "/";
|
|
end
|
|
|
|
-- for each config file
|
|
for _, cfg in ipairs(CONFIGS) do
|
|
-- for each alteration of the filename
|
|
for entry in backupNames(cfg.filename) do
|
|
local url_path
|
|
|
|
url_path = url.build({path = path .. entry});
|
|
-- http request
|
|
local response = http.get(host, port, url_path);
|
|
|
|
if (response.status == 200) then
|
|
-- check it if is valid before inserting
|
|
if cfg.check(response.body) then
|
|
local filename = stdnse.escape_filename((host.targetname or host.ip) .. url_path)
|
|
|
|
-- save the content
|
|
if save then
|
|
local status, err = write_file(save .. filename, response.body);
|
|
if status then
|
|
stdnse.print_debug(1, "%s saved", filename);
|
|
else
|
|
stdnse.print_debug(1, "error saving %s", err);
|
|
end
|
|
end
|
|
|
|
table.insert(backups, url_path .. " " .. response["status-line"]);
|
|
else
|
|
stdnse.print_debug(1, SCRIPT_NAME .. ": %s: found but not matching: %s",
|
|
host.targetname or host.ip, url_path);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return stdnse.format_output(true, backups);
|
|
end;
|