Skip to content

index

overview

为红队设计的基于端口的全能自动化引擎

repo: https://github.com/chainreactors/gogo

规则库: https://github.com/chainreactors/gogo-templates

下载: https://github.com/chainreactors/gogo/releases/latest

快速入门: https://chainreactors.github.io/wiki/gogo/start/

理论上支持包括windows 2003在内的全操作系统, release中的版本使用了go 1.17+ubuntu-latest以及github action自动编译. 出现过在较低系统版本上无法运行的问题. 如果出现了兼容下问题, 可以参考文档中的编译一节自行编译

Features.

  • 让红队的A段(或大于A段)扫描成为可能
  • 支持主动/被动指纹识别
  • 关键信息提取, 如title, cert 以及自定义提取信息的正则
  • 支持nuclei poc, 无害化的扫描, 每个添加的poc都经过人工审核
  • 自由组合的扫描逻辑, 高度可控的扫描行为
  • 超强的性能, 最快的速度, 尽可能小的内存与CPU占用.
  • 最小发包原则, 尽可能少地发包获取最多的信息
  • 支持丰富的DSL, 可以通过简单的配置自定义自己的gogo
  • 完善的输出与输出设计
  • 几乎不依赖第三方库, 纯原生go编写, 支持全功能全版本操作系统

Description/基本信息

gogo是一款几乎能解决一切内网自动化操作的工具, 并非是一个简单的缝合怪, 也不是仅仅实现了功能的demo, 而是一个经过两年打磨, 对几乎所有场景(webshell, cs, 多级代理等等)下都有大量优化的 红队向工具.

gogo将内网自动化 从某些工具的一键无脑操作提升到有目的的行动策略组, 几乎可以在所有奇怪复杂场景下通过特定的参数组合解决. 甚至在高防护的内网中, 也能进行一定程度的规避与探测.

Memu/目录

  1. 参数与功能介绍
  2. 设计理念与解决方案 (建议优先阅读此章)
  3. 设计细节
  4. 拓展与二次开发
  5. 实战与应用场景

Background/背景

当我们开始从事红队工作时, 不管是内网探测还是外网信息收集, 都还没有成熟的方法论.

历史上有不少类似的工具, masscan/nmap的端口扫描, serverscan用go实现了部分nmap的功能. 以及一些图形化的工具.

但红队面临的场景是复杂的, 并非所有情况都能本机接入, 超过90%的场景都需要在webshell/c2/代理下操作. 那么非落地的工具就不能满足的我们的需求了.

最开始, 有一些人打包了单文件版本的masscan与nmap, 不过对系统的要求很高, 只有特定操作系统才能运行, 后来有人用go写了serverscan. 一定程度上解决了兼容性问题.

但serverscan也是照着nmap设计的, 到了实战中会发现, 我们需要的不是端口指纹, 而是http应用的指纹与信息. 并且serverscan在识别端口指纹的时候有大量的主动发包, 速度极慢, 扫描的范围很难超过一个C段.

于是我们开始尝试编写一个适用于红队场景的扫描器. 当然, 这个时期也有很多同行也遇到了类似的问题, fscan, kscan, TailorScan等等扫描器都是在这个阶段诞生的.

gogo与这些工具同样经历了一两年的发展迭代, 其实在绝大多数功能上并没有突破性的差距, 更多的差距是在细节上的优化, 唯一能做到远远领先的就是gogo的可操控性与可拓展性. 这两个特性让gogo能够做到在同样功能的情况下, 能发现多得多的有效数据.

在很多次实战中, 我们发现过使用fscan的友商与靶标插肩而过, 原因很简单. fscan反馈太少了, 使用者没办法感知到fscan的漏报. 或者一旦有网络隔离, fscan只能一遍一遍重扫, 一遍一遍尝试作着大量的无用功.

而这个扫描器经过两年的打磨, 最终形态就是现在的gogo. 以工件(artifact)的形态, 作为与其他工具联动的一环, 高度可控的高拓展的内网自动化引擎.

Q&A

为什么没有服务的口令爆破

刚才提到了, 对web端默认口令的爆破gogo可以实现, 但有很多人问我, 为什么不把ssh, mysql之类的爆破也做进去?

最初我也是这么想的, 但是考虑到加上了这些功能, 二进制文件和代码会变得臃肿, 并且这些功能其实可以已另一种形式实现。最终我们决定另起炉灶, 维护了另外一个工具zombie, 主要用于批量的服务弱口令爆破与利用。gogo与zombie可以联动, gogo导出的zombie可识别的格式, 然后再通过代理或者上传zombie本体进行爆破。

这也是变相鼓励使用者不要在内网进行大规模的服务弱口令爆破, 大多数厂商的流量设备对服务弱口令爆破是很敏感的, 大多数都是严重或者高危的告警。gogo默认情况下只进行高速率的http发包, 不会发送poc, 经常会被其他告警淹没而忽略了gogo的扫描行为。实际使用体验因为高速率发包而被发现的情况较少。

通过gogo的筛选功能与zombie的自定义功能, 可以对想要爆破的密码本与目标进行精准管控, 尽量减少不必要的发包以规避流量设备的检测。如果有可能, 最好只进行密码喷洒, 而非爆破。

在我们的规划中, zombie能做的不仅仅是口令的爆破, 还可以实现一些自动化的利用, 比如mysql爆破之后, 判断下是否是root, 有多少数据, 自定义的命令批量执行等等功能。批量的rce更不是梦, 只不过因为工作量问题, 这部分只能一小块一小块的实现, 目前的zombie已经支持了十几种协议, 能覆盖到90%以上的hw使用场景。让护网不再是一个一个登录上去截图, 而是gogo与zombie的快乐联动, 内网刷分, 一行命令!

可以发现gogo与zombie的场景也不完全一致, zombie完全可以放在外网, 通过代理接入, 减轻zombie引入大量库的免杀压力。

在未来, 我打算编写一个gui界面的结果解析器与联动工具, 也可以是与c2 webshell的联动, 进一步简化操作, 让gogo与zombie的联动无缝衔接.

命令行参数为什么这么复杂?

gogo的命令行并不复杂, 复杂的是进阶功能的组合. 对于绝大多数基础场景, 使用两三个参数即可构建扫描任务. 并且在更复杂的场景上添加了workflow缓解这一问题.

为什么gogo扫不到明明存在的目标?

原因有很多, 可能是系统资源耗尽(常在windows和mac上出现); 路由器过载(甚至可能会过热重启); 网络环境延迟过高(国内扫国外); 有安全设备(自动封禁策略) 等等. 在实战中选择合适的场景扫描并不是简单的事.

为什么不使用SYN扫描?

SYN快是一个误区, 可能因为masscan很快, masscan默认使用了SYN扫描, 所以下意识认为SYN能提高扫描速度.实际上SYN扫描并不会提高扫描速度.

假设某个端口关闭, 我向该端口发送握手包的第一阶段SYN包. 可能会drop, 也可能会回复一个RST包告诉我该端口已关闭.

假设某个端口开放, 同样是发送SYN包, 收到ACK回复, 然后继续建立后续的握手.

我们会发现, SYN扫描与全连接扫描都是先发送一个SYN包, 然后等待返回.如果端口关闭, 等待的时间都是设置的timeout时间.

也就是说, SYN扫描与TCP全扫描只有在端口开放的情况下有速率差异, 而实际上大规模的端口扫描过程中, 绝大部分端口都是关闭的.

也因为绝大部分端口是关闭的, gogo也只会发送一个syn包, 只有获取相应之后才会建立TCP握手.

而如果使用raw socket最大的好处是不占用系统的fd, 可以突破fd限制, 但是就算突破了这个限制, 也还会有来自路由器的限制, 这个在设计文档中快的那部分提到过.

所以, SYN扫描与TCP全扫描在网络上是同样的性能, 只会在系统的性能上略低一点, 而TCP扫描有更多的优势, 后面会提到.

此外, 引入SYN扫描还需要依赖pcap, 并且依赖特定的系统版本, 会导致大量的依赖问题, 在实际情况中并不好用.这也是我没有直接使用naabu, 选择重新编写gogo的一部分原因.

要想SYN扫描速度达到masscan自称的速度, 需要提前知道系统的CPU/内存以及最重要的带宽, 然后做出对应的配置才能打到理论速度, 实际上在渗透的时候很难提前知道这些数值, 并且就算知道了, 配置起来也会很麻烦.

最终在socket与raw socket之间选择了前者, raw socket带来的特定场景微弱优势并不会改变整体的效率.

gogo是怎么做的?

gogo首先会采用socket尝试建立连接, 也就是类似SYN扫描的场景.

成功建立握手后, gogo会发送一个最基本的GET的HTTP包, 注意, 这里使用的是刚才建立的TCP信道, 而不是使用HTTP库重新发包, 这样可以减少一次三次握手.

如果返回的30x状态码, 则会使用net/HTTP库发送一个完整的HTTP请求, 如果返回400状态码或者非HTTP协议, 则会升级到HTTPs发送一个请求, 进一步的获取需要的信息以及排除掉干扰.也就是说, 关闭着的端口只需要1个syn包判断其状态, 而如果是普通的HTTP服务或TCP协议的服务, 仅仅需要3次TCP握手+一个最简get请求即可完成基本信息的获取.只有是HTTPs才需要5次TCP交互以及9次tls交互.

为什么不使用tls hello?

首先需要知道, 如果HTTP/HTTPs收到不符合标准的数据包, 例如对HTTPs服务发送明文GET请求, 会响应400或者TCP链接收到rst包.

建立TCP握手的时候还不能确定是否是HTTP/HTTPs服务. 这时候发送一个tls hello, 如果是HTTPs服务, 则能接收到server hello, 继续接下去握手认证直到发送HTTPs GET.如果是HTTP服务可能会回应400协议错误或者RST包, 这样我们还需要发送要给GET请求, 再次通过返回的报文或者状态码判断是否是HTTP服务.

先使用GET服务, 如果是HTTPs服务, 则会回应400或者rst;如果是某些TCP服务, 例如mysql, 则会回应banner;如果是HTTP服务, 顺便还可以帮助我们获取目标网站的首页;如果收到了rst或400, 再使用系统库中的HTTP api, 再次发送一个HTTPs的GET请求;如果还不成功, 则判断该端口运行着TCP服务.

这两种方式的理论TTL都是4-5ttl(两次 1.5TCP握手 + 1 HTTP request/response).但是显然HTTP服务在内网更加常见, 而且不用考虑TLS版本的问题(HTTP也需要在极少数情况下考虑HTTP1.0问题).因此, 我采用了更简单的HTTP GET请求的方式.

通过建立TCP握手, 然后发送GET请求, 再根据返回决定是否发送HTTPs的GET请求的方式就是目前gogo对每个端口最基础的探测.

这个探测, 不仅能获取到端口open的信息, 如果是HTTP/HTTPs服务, 还能获取到index页面、title、证书信息、状态码、header中的Server、set-cookie, 甚至进行被动的指纹收集.如果是TCP服务, 则有更多的情况, 后面会一一解释.

所以使用socket构造的HTTP get 会比tls hello 少上大概0.5 ttl