From 0d47952611198ef6b1163f366dc03922d20b1475 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 09:42:04 +0200 Subject: Adding upstream version 7.94+git20230807.3be01efb1+dfsg. Signed-off-by: Daniel Baumann --- scripts/http-security-headers.nse | 315 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 scripts/http-security-headers.nse (limited to 'scripts/http-security-headers.nse') diff --git a/scripts/http-security-headers.nse b/scripts/http-security-headers.nse new file mode 100644 index 0000000..17329ff --- /dev/null +++ b/scripts/http-security-headers.nse @@ -0,0 +1,315 @@ +local http = require "http" +local shortport = require "shortport" +local stdnse = require "stdnse" +local table = require "table" +local string = require "string" + +description = [[ +Checks for the HTTP response headers related to security given in OWASP Secure Headers Project +and gives a brief description of the header and its configuration value. + +The script requests the server for the header with http.head and parses it to list headers founds with their +configurations. The script checks for HSTS(HTTP Strict Transport Security), HPKP(HTTP Public Key Pins), +X-Frame-Options, X-XSS-Protection, X-Content-Type-Options, Content-Security-Policy, +X-Permitted-Cross-Domain-Policies, Set-Cookie, Expect-CT, Cache-Control, Pragma and Expires. + +References: https://www.owasp.org/index.php/OWASP_Secure_Headers_Project +https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers + +]] + +--- +-- @usage +-- nmap -p --script http-security-headers +-- +-- @output +-- 80/tcp open http syn-ack +-- | http-security-headers: +-- | Strict_Transport_Security: +-- | Header: Strict-Transport-Security: max-age=15552000; preload +-- | Public_Key_Pins_Report_Only: +-- | Header: Public-Key-Pins-Report-Only: max-age=500; pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; pin-sha256="q4PO2G2cbkZhZ82+JgmRUyGMoAeozA+BSXVXQWB8XWQ="; report-uri="http://reports.fb.com/hpkp/" +-- | X_Frame_Options: +-- | Header: X-Frame-Options: DENY +-- | Description: The browser must not display this content in any frame. +-- | X_XSS_Protection: +-- | Header: X-XSS-Protection: 0 +-- | Description: The XSS filter is disabled. +-- | X_Content_Type_Options: +-- | Header: X-Content-Type-Options: nosniff +-- | Will prevent the browser from MIME-sniffing a response away from the declared content-type. +-- | Content-Security-Policy: +-- | Header: Content-Security-Policy: script-src 'self' +-- | Description: Loading policy for all resources type in case of a resource type dedicated directive is not defined (fallback). +-- | X-Permitted-Cross-Domain-Policies: +-- | Header: X-Permitted-Cross-Domain-Policies: none +-- | Description : No policy files are allowed anywhere on the target server, including this master policy file. +-- | Cache_Control: +-- | Header: Cache-Control: private, no-cache, no-store, must-revalidate +-- | Pragma: +-- | Header: Pragma: no-cache +-- | Expires: +-- |_ Header: Expires: Sat, 01 Jan 2000 00:00:00 GMT +-- +-- +-- @xmloutput +-- +-- Header: Strict-Transport-Security: max-age=31536000 +--
+-- +-- Header: Public-Key-Pins-Report-Only: pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; report-uri="http://example.com/pkp-report"; max-age=10000; includeSubDomains +--
+-- +-- Header: X-Frame-Options: DENY +-- Description: The browser must not display this content in any frame. +--
+-- +-- Header: X-XSS-Protection: 1; mode=block +-- Description: Rather than sanitize the page, when a XSS attack is detected, the browser will prevent rendering of the page. +--
+-- +-- Header: X-Content-Type-Options: nosniff +-- Description: Will prevent the browser from MIME-sniffing a response away from the declared content-type. +--
+-- +-- Header: Content-Security-Policy: script-src 'self' +-- Description: Loading policy for all resources type in case of a resource type dedicated directive is not defined (fallback). +--
+-- +-- Header: X-Permitted-Cross-Domain-Policies: none +-- Description: No policy files are allowed anywhere on the target server, including this master policy file. +--
+-- +-- Header: Cache-Control: private, no-cache, no-store, must-revalidate +--
+-- +-- Header: Pragma: no-cache +--
+-- Header: Expires: Sat, 01 Jan 2000 00:00:00 GMT +-- +-- @args http-security-headers.path The URL path to request. The default path is "/". +--- + +author = {"Icaro Torres", "Vinamra Bhatia"} +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"discovery", "safe"} + +portrule = shortport.port_or_service({80,443}, "http", "tcp") + +local function fail (err) return stdnse.format_output(false, err) end + +action = function(host, port) + local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/" + local response + local output_info = {} + local hsts_header + local hpkp_header + local xframe_header + local x_xss_header + local x_content_type_header + local csp_header + local x_cross_domain_header + local cookie + local req_opt = {redirect_ok=function(host,port) + local c = 2 + return function(uri) + if ( c==0 ) then return false end + c = c - 1 + return true + end + end} + + response = http.head(host, port, path, req_opt) + + output_info = stdnse.output_table() + + if response == nil then + return fail("Request failed") + end + + if response.header == nil then + return fail("Response didn't include a proper header") + end + + if response.header['strict-transport-security'] then + output_info.Strict_Transport_Security = {} + table.insert(output_info.Strict_Transport_Security, "Header: Strict-Transport-Security: " .. response.header['strict-transport-security']) + elseif shortport.ssl(host,port) then + output_info.Strict_Transport_Security = {} + table.insert(output_info.Strict_Transport_Security, "HSTS not configured in HTTPS Server") + end + + if response.header['public-key-pins-report-only'] then + output_info.Public_Key_Pins_Report_Only = {} + table.insert(output_info.Public_Key_Pins_Report_Only, "Header: Public-Key-Pins-Report-Only: " .. response.header['public-key-pins-report-only']) + end + + if response.header['x-frame-options'] then + output_info.X_Frame_Options = {} + table.insert(output_info.X_Frame_Options, "Header: X-Frame-Options: " .. response.header['x-frame-options']) + + xframe_header = string.lower(response.header['x-frame-options']) + if string.match(xframe_header,'deny') then + table.insert(output_info.X_Frame_Options, "Description: The browser must not display this content in any frame.") + elseif string.match(xframe_header,'sameorigin') then + table.insert(output_info.X_Frame_Options, "Description: The browser must not display this content in any frame from a page of different origin than the content itself.") + elseif string.match(xframe_header,'allow.from') then + table.insert(output_info.X_Frame_Options, "Description: The browser must not display this content in a frame from any page with a top-level browsing context of different origin than the specified origin.") + end + + end + + if response.header['x-xss-protection'] then + output_info.X_XSS_Protection = {} + table.insert(output_info.X_XSS_Protection, "Header: X-XSS-Protection: " .. response.header['x-xss-protection']) + + x_xss_header = string.lower(response.header['x-xss-protection']) + if string.match(x_xss_header,'block') then + table.insert(output_info.X_XSS_Protection, "Description: The browser will prevent the rendering of the page when XSS is detected.") + elseif string.match(x_xss_header,'report') then + table.insert(output_info.X_XSS_Protection, "Description: The browser will sanitize the page and report the violation if XSS is detected.") + elseif string.match(x_xss_header,'0') then + table.insert(output_info.X_XSS_Protection, "Description: The XSS filter is disabled.") + end + + end + + if response.header['x-content-type-options'] then + output_info.X_Content_Type_Options = {} + table.insert(output_info.X_Content_Type_Options, "Header: X-Content-Type-Options: " .. response.header['x-content-type-options']) + + x_content_type_header = string.lower(response.header['x-content-type-options']) + if string.match(x_content_type_header,'nosniff') then + table.insert(output_info.X_Content_Type_Options, "Description: Will prevent the browser from MIME-sniffing a response away from the declared content-type. ") + end + + end + + if response.header['content-security-policy'] then + output_info.Content_Security_Policy = {} + table.insert(output_info.Content_Security_Policy, "Header: Content-Security-Policy: " .. response.header['content-security-policy']) + + csp_header = string.lower(response.header['content-security-policy']) + if string.match(csp_header,'base.uri') then + table.insert(output_info.Content_Security_Policy, "Description: Define the base uri for relative uri.") + end + if string.match(csp_header,'default.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define loading policy for all resources type in case of a resource type dedicated directive is not defined (fallback).") + end + if string.match(csp_header,'script.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define which scripts the protected resource can execute.") + end + if string.match(csp_header,'object.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define from where the protected resource can load plugins.") + end + if string.match(csp_header,'style.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define which styles (CSS) the user applies to the protected resource.") + end + if string.match(csp_header,'img.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define from where the protected resource can load images.") + end + if string.match(csp_header,'media.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define from where the protected resource can load video and audio.") + end + if string.match(csp_header,'frame.src') then + table.insert(output_info.Content_Security_Policy, "Description: Deprecated and replaced by child-src. Define from where the protected resource can embed frames.") + end + if string.match(csp_header,'child.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define from where the protected resource can embed frames.") + end + if string.match(csp_header,'frame.ancestors') then + table.insert(output_info.Content_Security_Policy, "Description: Define from where the protected resource can be embedded in frames.") + end + if string.match(csp_header,'font.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define from where the protected resource can load fonts.") + end + if string.match(csp_header,'connect.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define which URIs the protected resource can load using script interfaces.") + end + if string.match(csp_header,'mailfest.src') then + table.insert(output_info.Content_Security_Policy, "Description: Define from where the protected resource can load manifest.") + end + if string.match(csp_header,'form.action') then + table.insert(output_info.Content_Security_Policy, "Description: Define which URIs can be used as the action of HTML form elements.") + end + if string.match(csp_header,'sandbox') then + table.insert(output_info.Content_Security_Policy, "Description: Specifies an HTML sandbox policy that the user agent applies to the protected resource.") + end + if string.match(csp_header,'script.nonce') then + table.insert(output_info.Content_Security_Policy, "Description: Define script execution by requiring the presence of the specified nonce on script elements.") + end + if string.match(csp_header,'plugin.types') then + table.insert(output_info.Content_Security_Policy, "Description: Define the set of plugins that can be invoked by the protected resource by limiting the types of resources that can be embedded.") + end + if string.match(csp_header,'reflected.xss') then + table.insert(output_info.Content_Security_Policy, "Description: Instructs a user agent to activate or deactivate any heuristics used to filter or block reflected cross-site scripting attacks, equivalent to the effects of the non-standard X-XSS-Protection header.") + end + if string.match(csp_header,'block.all.mixed.content') then + table.insert(output_info.Content_Security_Policy, "Description: Prevent user agent from loading mixed content.") + end + if string.match(csp_header,'upgrade.insecure.requests') then + table.insert(output_info.Content_Security_Policy, "Description: Instructs user agent to download insecure resources using HTTPS.") + end + if string.match(csp_header,'referrer') then + table.insert(output_info.Content_Security_Policy, "Description: Define information user agent must send in Referer header.") + end + if string.match(csp_header,'report.uri') then + table.insert(output_info.Content_Security_Policy, "Description: Specifies a URI to which the user agent sends reports about policy violation.") + end + if string.match(csp_header,'report.to') then + table.insert(output_info.Content_Security_Policy, "Description: Specifies a group (defined in Report-To header) to which the user agent sends reports about policy violation. ") + end + + end + + if response.header['x-permitted-cross-domain-policies'] then + output_info.X_Permitted_Cross_Domain_Policies = {} + table.insert(output_info.X_Permitted_Cross_Domain_Policies, "Header: X-Permitted-Cross-Domain-Policies: " .. response.header['x-permitted-cross-domain-policies']) + + x_cross_domain_header = string.lower(response.header['x-permitted-cross-domain-policies']) + if string.match(x_cross_domain_header,'none') then + table.insert(output_info.X_Permitted_Cross_Domain_Policies, "Description: No policy files are allowed anywhere on the target server, including this master policy file. ") + elseif string.match(x_cross_domain_header,'master.only') then + table.insert(output_info.X_Permitted_Cross_Domain_Policies, "Description: Only this master policy file is allowed. ") + elseif string.match(x_cross_domain_header,'by.content.type') then + table.insert(output_info.X_Permitted_Cross_Domain_Policies, "Description: Define which scripts the protected resource can execute.") + elseif string.match(x_cross_domain_header,'all') then + table.insert(output_info.X_Permitted_Cross_Domain_Policies, "Description: All policy files on this target domain are allowed.") + end + + end + + if response.header['set-cookie'] then + cookie = string.lower(response.header['set-cookie']) + if string.match(cookie,'secure') and shortport.ssl(host,port) then + output_info.Cookie = {} + table.insert(output_info.Cookie, "Cookies are secured with Secure Flag in HTTPS Connection") + end + end + + if response.header['expect-ct'] then + output_info.Expect_CT = {} + table.insert(output_info.Expect_CT, "Header: Expect-CT: " .. response.header['expect-ct']) + end + + if response.header['cache-control'] then + output_info.Cache_Control = {} + table.insert(output_info.Cache_Control, "Header: Cache-Control: " .. response.header['cache-control']) + end + + if response.header['pragma'] then + output_info.Pragma = {} + table.insert(output_info.Pragma, "Header: Pragma: " .. response.header['pragma']) + end + + if response.header['expires'] then + output_info.Expires = {} + table.insert(output_info.Expires, "Header: Expires: " .. response.header['expires']) + end + + return output_info, stdnse.format_output(true, output_info) + +end + -- cgit v1.2.3