研究本地服务系列文章:

  1. 图一乐研究之初识Yakit
  2. 图一乐研究之本地服务猎手(本文)
  3. 本地服务猎手终章

前言

上篇回顾

上篇文章“图一乐研究之初识Yakit”有的师傅不明白为啥巴斯要搞个grpc和yak engine通信。现如今有些桌面软件,特别是非原生开发的桌面软件,会将核心部分与gui分离,通过网络接口通信,虽然是监听本地的 localhost 但也不安全。

上篇文中的 gRPC 不能在浏览器中跑起来,假设暴露是 http 协议,就能诱导用户访问构造的恶意页面,在用户浏览器对本地服务发送恶意payload,相关案例:https://xlab.tencent.com/cn/2018/10/23/weixin-cheater-risks/

关于gRPC

gRPC是基于HTTP2协议,默认使用H2C(H2C和H2都是HTTP/2协议的版本,其中H2C是不加密的版本,而H2是加密的版本。)

Yak默认也是使用的h2c,只可惜Chrome和Firefox浏览器虽然支持HTTP/2协议,但是它们都不支持非加密的H2C版本。

实践 分别用 net 或者 h2c 写客户端请求服务,编译为wasm后运行请求均无效:

// example.go
// net.dial
c, err := net.Dial("tcp", "127.0.0.1:8233")
if err != nil {
	fmt.Println(err)
	return
}
defer c.Close()
fmt.Println(c.LocalAddr(), c.RemoteAddr())
// ...
// h2c
client := http.Client{
	// Skip TLS dial
	Transport: &http2.Transport{
		AllowHTTP: true,
		DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
			return net.Dial(network, addr)
		},
	},
}
resp, err := client.Get("<http://127.0.0.1:49024>")
if err != nil {
	log.Fatal(fmt.Errorf("error making request: %v", err))
}
fmt.Println(resp.StatusCode, resp.Proto)

找 AI 给我润色一下标准答案:

Untitled

Go Wasm

还有哪些注意事项呢🤔️:

Untitled

Web客户端的本地服务探测

https://github.com/avilum/portsscan 使用Go Wasm实现Web客户端端口扫描器,用于发现主机上可用的任何 TCP 开放端口。

试用扫描效果还不错:

Untitled

原理分析

发起请求主体函数为 [ScanPort](<https://github.com/avilum/portsscan/blob/82c48015fcccf9040480f217bf29cb550574dca0/main.go#L25>) 简化乱七八糟的代码,向目标发起http请求判定端口是否开放,代码如下:

// http client
client := http.Client{
	Transport: &http.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true,
		},
	},
	Timeout: time.Second,
}

req, err := http.NewRequest("GET", "<http://localhost:2333>", nil)
// ...
// IMPORTANT - enables better HTTP(S) discovery, because many browsers block CORS by default.
req.Header.Add("js.fetch:mode", "no-cors")
if _, err := client.Do(req); err != nil {
	// TODO: Get more exception strings for major browsers
	errs := strings.ToLower(err.Error())
	if strings.Contains(errs, "exceeded while awaiting") ||
		strings.Contains(errs, "ssl") ||
		strings.Contains(errs, "cors") ||
		strings.Contains(errs, "invalid") ||
		strings.Contains(errs, "protocol") {
		// 端口开放 open
	} else {
		// 端口关闭 closed
		return
	}
}
// 端口开放 open

no-cors

特别之处就是在请求头中添加 no-cors :req.Header.Add("js.fetch:mode", "no-cors")

Web request mode 中关于 no-cors 的说明:

no-cors  - 保证请求对应的 method 只有 HEADGET 或 POST 方法,并且请求的 headers 只能有简单请求头 (simple headers)。如果 ServiceWorker 劫持了此类请求,除了 simple header 之外,不能添加或修改其他 header。另外 JavaScript 不会读取 [Response](<https://developer.mozilla.org/zh-CN/docs/Web/API/Response>) 的任何属性。这样将会确保 ServiceWorker 不会影响 Web 语义 (semantics of the Web),同时保证了在跨域时不会发生安全和隐私泄露的问题。

简单来说:告诉浏览器,我本来就知道服务端对于这个请求是没有配置CORS,就算无法获取到响应,也别抛异常。

部署服务

部署到 github page 测测效果,原作者代码部署到 page 上跑不了。

优化代码

代码比较简单,去除无效代码,优化扫描功能等。

源码:https://github.com/ac0d3r/portsscan

在线测试地址:https://ac0d3r.github.io/portsscan/

本地测试方法:

  1. git clone <https://github.com/ac0d3r/portsscan>
  2. go install github.com/Buzz2d0/nicu/cmd/http.server@latest
  3. sh build.sh
  4. http.server -d docs

扫描的时候别打开开发者工具就挺稳的,默认扫描 127.0.0.1 1-65535 的端口:

Untitled

总结

服务探测完毕就要对某个打开的服务构造恶意请求,这时得注意 localhost cors 问题,或者使用 DNSRebind。

参考


Powered by Kali-Team