mirror of
https://github.com/nmap/nmap.git
synced 2026-11-15 10:47:29 +00:00
Some checks are pending
nmap multiplatform autobuilds / build (arm64, gcc, ubuntu-latest-gcc-arm64, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, freebsd-15-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, macos-15-clang, macos-15) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, macos-26-clang, macos-26) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, netbsd-10-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, openbsd-7-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, solaris-11-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (clang, ubuntu-latest-clang, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (egcc, openbsd-7-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (gcc, freebsd-15-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (gcc, netbsd-10-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (gcc, solaris-11-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (gcc, ubuntu-latest-gcc, ubuntu-latest) (push) Waiting to run
nmap multiplatform autobuilds / build (msvc, windows-latest-msvc, windows-latest) (push) Waiting to run
245 lines
7.3 KiB
Lua
245 lines
7.3 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 stringaux = require "stringaux"
|
|
local tab = require "tab"
|
|
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.0 200 OK
|
|
-- |_ /config.php~ HTTP/1.0 200 OK
|
|
--
|
|
-- @xmloutput
|
|
-- <table key="/%23wp-config.php%23">
|
|
-- <elem key="status">HTTP/1.0 200 OK</elem>
|
|
-- </table>
|
|
-- <table key="/config.php~">
|
|
-- <elem key="status">HTTP/1.0 200 OK</elem>
|
|
-- </table>
|
|
--
|
|
-- @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 https://nmap.org/book/man-legal.html"
|
|
categories = { "auth", "intrusive" }
|
|
|
|
|
|
portrule = shortport.http
|
|
|
|
local function make_grep(pattern)
|
|
return function(s)
|
|
return s:find(pattern)
|
|
end
|
|
end
|
|
|
|
local grep_php = make_grep("<%?php")
|
|
local grep_cgipath = make_grep("CGIPath")
|
|
|
|
local function check_htaccess(s)
|
|
return s:find("<Files") or s:find("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 = 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 = 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 = splitdir(filename)
|
|
return coroutine.wrap(function()
|
|
for _, transform in ipairs(TRANSFORMS) do
|
|
local result = transform(basename)
|
|
if type(result) == "string" then
|
|
coroutine.yield(dir .. 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")
|
|
|
|
if not path:find("/$") then
|
|
path = path .. "/"
|
|
end
|
|
|
|
if not path:find("^/") then
|
|
path = "/" .. path
|
|
end
|
|
|
|
if save and not save:find("/$") then
|
|
save = save .. "/"
|
|
end
|
|
|
|
local status_404, result_404, known_404 = http.identify_404(host, port)
|
|
if not status_404 then
|
|
stdnse.debug1("Can't distinguish 404 response. Quitting.")
|
|
return stdnse.format_output(false, "Can't determine file existence")
|
|
end
|
|
|
|
local backups = stdnse.output_table()
|
|
local tbl = tab.new()
|
|
|
|
-- 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 it's not 200, don't bother. If it is, check that it's not a false 404
|
|
if response.status == 200 and http.page_exists(response, result_404, known_404, url_path) then
|
|
-- check it if is valid before inserting
|
|
if cfg.check(response.body) then
|
|
|
|
-- save the content
|
|
if save then
|
|
local filename = stringaux.filename_escape((host.targetname or host.ip) .. url_path)
|
|
local status, err = write_file(save .. filename, response.body)
|
|
if status then
|
|
stdnse.debug1("%s saved", filename)
|
|
else
|
|
stdnse.debug1("error saving %s", err)
|
|
end
|
|
end
|
|
|
|
local hstatus = response["status-line"]:match("^(.-)%s*$")
|
|
backups[url_path] = {status = hstatus}
|
|
tab.addrow(tbl, url_path, hstatus)
|
|
else
|
|
stdnse.debug1("%s: found but not matching: %s",
|
|
host.targetname or host.ip, url_path)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return backups, stdnse.format_output(true, tab.dump(tbl))
|
|
end
|