function file_exists(name) local f=io.open(name,"r") if f~=nil then io.close(f) return true else return false end end function check_scan_enabled() local function read_file(path) local f = io.open(path, "rb") if f == nil then return nil end local content = f:read("*all") io.close(f) return content end local nonpriv = "/etc/sysconfig/imunify360/imunify360-merged-nonprivileged.config" local merged = "/etc/sysconfig/imunify360/imunify360-merged.config" -- prefer the world-readable nonprivileged config; fall back to the -- privileged file only when nonpriv lacks the key (upgrade window) local content = read_file(nonpriv) if content == nil or not string.match(content, 'enable_scan_modsec:') then content = read_file(merged) end -- fail closed: an unreadable config means scan anyway, never silently skip if content == nil then return true end return string.match(content, 'enable_scan_modsec:%s*true') ~= nil end function write_tmp(input_str) -- Write input_str to a file inside a PRIVATE temp directory. -- WPT-2589 (audit MRs !31/!32): the previous os.tmpname()+io.open("w") was a -- TOCTOU/symlink hole, and the fallback names (os.time() + unseeded math.random -- in world-writable /tmp,/var/tmp) were predictable, letting a co-tenant on a -- shared host symlink-clobber an arbitrary file. `mktemp -d` creates an -- unpredictable 0700 directory atomically, so the write target cannot be -- pre-planted. Returns (filepath, dirpath); the caller must remove dirpath. input_str = string.gsub(input_str, "^raw_code:", "") local p = io.popen("mktemp -d 2>/dev/null") if not p then return nil end local tmpdir = p:read("*l") p:close() if not tmpdir or tmpdir == "" then return nil end local tmpname = tmpdir .. "/code.tmp" local tmpfile = io.open(tmpname, "w") if not tmpfile then os.execute("rm -rf -- '" .. tmpdir .. "'") return nil end tmpfile:write(input_str) tmpfile:close() return tmpname, tmpdir end function main(filename) local scan_script -- WPT-2589 (!32): declared at function scope. It was block-scoped inside the -- raw_code branch below, so the cleanup check read a nil global and the temp -- file was never removed (leak on every raw_code scan). local cleanup_dir = nil -- Check if modsec scan enabled in config file if not check_scan_enabled() then return nil end -- cover use-case where we send raw php code for scanning if string.sub(filename, 1, 9) == "raw_code:" then -- save content to a private tmp dir local tmpname, tmpdir = write_tmp(filename) if not tmpname then return nil end filename = tmpname cleanup_dir = tmpdir end -- Remove the private temp dir (and the file inside it) on EVERY exit path -- below. mktemp -d output is alphanumeric, so single-quoting is safe. local function cleanup() if cleanup_dir then os.execute("rm -rf -- '" .. cleanup_dir .. "'") cleanup_dir = nil end end -- Check if scan script exist, if not - do nothing if file_exists("/usr/share/imunify360/scripts/modsec_scan_real.py") then scan_script = "/usr/share/imunify360/scripts/modsec_scan_real.py" elseif file_exists("/opt/alt/python35/share/imunify360/scripts/modsec_scan_real.py") then scan_script = "/opt/alt/python35/share/imunify360/scripts/modsec_scan_real.py" else cleanup() return nil end -- WPT-2589 (!41): POSIX single-quote-escape the filename before it reaches -- /bin/sh via io.popen, so shell metacharacters in the path cannot inject -- commands. '\'' is the canonical way to embed a single quote inside '...'. local safe_filename = "'" .. string.gsub(filename, "'", "'\\''") .. "'" -- Execute scan script and get a output as a result local handle = io.popen(scan_script .. " " .. safe_filename) local scan_result = "" if handle then scan_result = handle:read("*a") or "" handle:close() end -- cleanup the private temp dir, if created cleanup() -- Check if scan script return something useful -- 1 OK - means OK, no malware found -- 0 Attempt to upload malware - means harmful file -- empty string or any other output means error or execution failure, and do nothing if string.match(scan_result, '0 Attempt to upload malware') then return 1 elseif string.match(scan_result, '1 OK') then return nil end return nil end