diff --git a/CHANGELOG b/CHANGELOG
index 2153a4f4c..1a4d99e55 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,7 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added http-cors by Toni Ruottu.
+
o [NSE] Added ganglia-info by Brendan Coles.
o [NSE] Added tftp-enum by Alexander Rudakov.
diff --git a/scripts/http-cors.nse b/scripts/http-cors.nse
new file mode 100644
index 000000000..272faf425
--- /dev/null
+++ b/scripts/http-cors.nse
@@ -0,0 +1,91 @@
+description = [[
+Tests an http server for Cross-Origin Resource Sharing.
+]]
+
+---
+-- @args http-cors.path The path to request. Defaults to
+-- /.
+--
+-- @args http-cors.origin The origin used with requests. Defaults to
+-- example.com.
+--
+-- @usage
+-- nmap -p 80 --script http-cors
+--
+-- @output
+-- 80/tcp open
+-- |_cors.nse: GET POST OPTIONS
+
+
+author = "Toni Ruottu"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"default", "discovery", "safe"}
+
+require("shortport")
+require("stdnse")
+require("http")
+
+portrule = shortport.http
+
+local methods = {"HEAD", "GET", "POST", "PUT", "DELETE", "TRACE", "OPTIONS", "CONNECT", "PATCH"}
+
+local function origin_ok(raw, origin)
+ if not raw then
+ return false
+ end
+ if raw == "*" then
+ return true
+ end
+ if raw == "null" then
+ return false
+ end
+ allowed = stdnse.strsplit(" ", raw)
+ for _, ao in ipairs(allowed) do
+ if origin == ao then
+ return true
+ end
+ end
+ return false
+end
+
+local function method_ok(raw, method)
+ if not raw then
+ return false
+ end
+ local stuff = stdnse.strsplit(" ", raw)
+ local nospace = stdnse.strjoin("", stuff)
+ local allowed = stdnse.strsplit(",", nospace)
+ for _, am in ipairs(allowed) do
+ if method == am then
+ return true
+ end
+ end
+ return false
+end
+
+local function test(host, port, method, origin)
+ header = {
+ ["Origin"] = origin,
+ ["Access-Control-Request-Method"] = method,
+ }
+ local response = http.generic_request(host, port, "OPTIONS", "/", {header = header})
+ local aorigins = response.header["access-control-allow-origin"]
+ local amethods = response.header["access-control-allow-methods"]
+ local ook = origin_ok(aorigins, response)
+ local mok = method_ok(amethods, method)
+ return ook and mok
+end
+
+action = function(host, port)
+ local path = nmap.registry.args["http-cors.path"] or "/"
+ local origin = nmap.registry.args["http-cors.origin"] or "example.com"
+ local allowed = {}
+ for _, method in ipairs(methods) do
+ if test(host, port, method, origin) then
+ table.insert(allowed, method)
+ end
+ end
+ if #allowed > 0 then
+ return stdnse.strjoin(" ", allowed)
+ end
+end
diff --git a/scripts/script.db b/scripts/script.db
index 604dad461..f17034545 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -77,6 +77,7 @@ Entry { filename = "http-axis2-dir-traversal.nse", categories = { "exploit", "in
Entry { filename = "http-barracuda-dir-traversal.nse", categories = { "auth", "exploit", "intrusive", } }
Entry { filename = "http-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "http-cakephp-version.nse", categories = { "discovery", "safe", } }
+Entry { filename = "http-cors.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "http-date.nse", categories = { "discovery", "safe", } }
Entry { filename = "http-default-accounts.nse", categories = { "auth", "discovery", "safe", } }
Entry { filename = "http-domino-enum-passwords.nse", categories = { "auth", "intrusive", } }