diff --git a/tests/README.md b/tests/README.md index 8b635bedc..87e46a1fa 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,7 +1,16 @@ # 测试靶场 +## Evil Pot -这里依靠社区力量收集了几个常用的靶站,可以通过 docker/docker-compose 一键启用。活动开始后,我们注意到在这个repo 中 https://github.com/c0ny1/vulstudy ,作者已经收集了 12 个靶站,这些靶站基本符合我们的要求,所以后续提交中与该 repo 中靶站有所重复的将不再收录。 +`evilpot`目录下是我们实现的一个用于提高插件质量的测试靶场。 + +`evilpot`集成了一些常见的容易导致扫描器误报的情况,编写插件的过程应该尽量避免能在这个靶场扫描出结果。 + +## 常用靶场 + +这里依靠社区力量收集了几个常用的靶站,可以通过 docker/docker-compose 一键启用。活动开始后,我们注意到在这个repo +中 https://github.com/c0ny1/vulstudy ,作者已经收集了 12 个靶站,这些靶站基本符合我们的要求,所以后续提交中与该 repo +中靶站有所重复的将不再收录。 已有的靶站列表: diff --git a/tests/evilpot/README.md b/tests/evilpot/README.md new file mode 100644 index 000000000..55b0d364f --- /dev/null +++ b/tests/evilpot/README.md @@ -0,0 +1,24 @@ +Evil Pot +=== + +邪恶的罐子 + +一个专门用于让扫描器产生误报的靶场 + +编写插件应该尽量避免能在这个靶场扫描出结果 + +## 默认监听端口 + +- 8887: evil server 让扫描器产生误报 困难模式 + - 普通模式的基础上对所有请求元素进行拆解计算sha1/md5/base64 +- 8888: evil server 让扫描器产生误报 普通模式 + - 常见状态码 + - 常见报错信息 + - 常见页面 + - 常见登录框 + - 常见xml头 + - 1-1000的sha1/md5/base64 + - 回显完整请求 + - 尝试计算请求中的算式 + - 尝试进行`sleep`和`wait for`的执行 +- 8889: echo server 回显所有读到的数据 \ No newline at end of file diff --git a/tests/evilpot/evil/echo.go b/tests/evilpot/evil/echo.go new file mode 100644 index 000000000..47bd581c2 --- /dev/null +++ b/tests/evilpot/evil/echo.go @@ -0,0 +1,24 @@ +package evil + +import ( + "io" + "log" + "net" +) + +func ServeEchoServer(addr string) error { + server, err := net.Listen("tcp", addr) + if err != nil { + return err + } + for { + conn, err := server.Accept() + if err != nil { + log.Println(err) + continue + } + go func() { + _, _ = io.Copy(conn, conn) + }() + } +} diff --git a/tests/evilpot/evil/evil.go b/tests/evilpot/evil/evil.go new file mode 100644 index 000000000..ce1024cdc --- /dev/null +++ b/tests/evilpot/evil/evil.go @@ -0,0 +1,223 @@ +package evil + +import ( + "bytes" + "crypto/md5" + "crypto/sha1" + "encoding/base64" + "encoding/hex" + "log" + "net/http" + "net/http/httputil" + "net/url" + "regexp" + "strconv" + "sync" + "time" + + "github.com/dengsgo/math-engine/engine" +) + +func ServeEvilServer(addr string, hard bool) error { + return http.ListenAndServe(addr, NewEvilServeMux(hard)) +} + +func NewEvilServeMux(hard bool) *http.ServeMux { + s := http.NewServeMux() + mathRe := regexp.MustCompile(`\d+\s*[-+*/]\s*\d+`) + sleepRe := regexp.MustCompile(`(?i)sleep\((\d+)\)`) + waitForRe := regexp.MustCompile(`(?i)waitfor\s+delay\s+'0:0:(\d+)'`) + s.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { + buf := bufPool.Get().(*bytes.Buffer) + defer func() { + buf.Reset() + bufPool.Put(buf) + }() + + buf.Write(CommonEvilResponse) + + data, err := httputil.DumpRequest(request, true) + if err != nil { + log.Println(err) + } + buf.Write(data) + + if hard { + Split(data, SepFunc, func(bytes []byte) bool { + GenEvilContent(buf, bytes) + return true + }) + } + + unescape, _ := url.PathUnescape(string(data)) + unescape, _ = url.QueryUnescape(unescape) + if hard { + Split([]byte(unescape), SepFunc, func(bytes []byte) bool { + GenEvilContent(buf, bytes) + return true + }) + } + + // 处理 sleep 和 WAITFOR DELAY + sleepMatches := sleepRe.FindAllStringSubmatch(unescape, -1) + for _, match := range sleepMatches { + if len(match) > 1 { + sleepTime, _ := strconv.Atoi(match[1]) + if sleepTime > 50 { + time.Sleep(time.Millisecond * time.Duration(sleepTime)) + } else { + time.Sleep(time.Second * time.Duration(sleepTime)) + } + } + } + + waitForMatches := waitForRe.FindAllStringSubmatch(unescape, -1) + for _, match := range waitForMatches { + if len(match) > 1 { + waitTime, _ := strconv.Atoi(match[1]) + if waitTime > 50 { + time.Sleep(time.Millisecond * time.Duration(waitTime)) + } else { + time.Sleep(time.Second * time.Duration(waitTime)) + } + } + } + + for _, expr := range mathRe.FindAllString(unescape, -1) { + r, err := engine.ParseAndExec(expr) + if err != nil { + log.Println(err) + continue + } + GenEvilContent(buf, []byte(strconv.Itoa(int(r)))) + } + + _, _ = writer.Write(buf.Bytes()) + }) + return s +} + +var bufPool = sync.Pool{New: func() any { + return bytes.NewBuffer(nil) +}} + +func GenEvilContent(dst *bytes.Buffer, data []byte) { + dst.Write(data) + hashMD5 := md5.Sum(data) + dst.Write(hashMD5[:]) + dst.WriteString(" ") + dst.WriteString(hex.EncodeToString(hashMD5[:])) + dst.WriteString(" ") + dst.WriteString(base64.StdEncoding.EncodeToString([]byte(hex.EncodeToString(hashMD5[:])))) + dst.WriteString(" ") + hashSha1 := sha1.Sum(data) + dst.Write(hashSha1[:]) + dst.WriteString(hex.EncodeToString(hashSha1[:])) + dst.WriteString(" ") + dst.WriteString(base64.StdEncoding.EncodeToString([]byte(hex.EncodeToString(hashSha1[:])))) + dst.WriteString(" ") + dst.WriteString(base64.StdEncoding.EncodeToString(data)) + dst.WriteString(" ") +} + +// CommonEvilResponse +// 常见md5/sha1/base64 (1-1000的数字) +// 常见登录表单 +// 常见错误信息 +var CommonEvilResponse = []byte(` + + Level1 + OA系统 + + +

Level1

+ +
+

OA系统

+ +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ +
+

新闻动态

+ +
+ +
+

通知公告

+ +
+
+ + + +

Oops! Something went wrong.

+

We're sorry, but an error has occurred while processing your request. Please try again later.

+

Error Code: 400

+

Error Code: 401

+

Error Code: 403

+

Error Code: 404

+

Error Code: 500

+

Error Code: 501

+

Error Code: 502

+

Error Code: 503

+ { + "error": { + "code": 404, + "message": "未找到请求的资源", + "details": "请检查您的请求URL是否正确,并确保所请求的资源存在。" + } + } + + + + + + + + + +`) + +func init() { + buf := bytes.NewBuffer(nil) + buf.Write(CommonEvilResponse) + for i := 0; i < 1000; i++ { + GenEvilContent(buf, []byte(strconv.Itoa(i))) + } + buf.WriteString("\nroot:x:0:0:root:/root:/bin/bash\n") + CommonEvilResponse = buf.Bytes() +} diff --git a/tests/evilpot/evil/util.go b/tests/evilpot/evil/util.go new file mode 100644 index 000000000..3ef071e0f --- /dev/null +++ b/tests/evilpot/evil/util.go @@ -0,0 +1,44 @@ +package evil + +var SepFunc = func(b byte) bool { + if 48 <= b && b <= 57 { + return false + } + if b >= 128 { + return false + } + if b >= 65 && b <= 90 { + return false + } + if b >= 97 && b <= 122 { + return false + } + return true +} + +func Split(data []byte, sep func(b byte) bool, handler func([]byte) bool) { + if sep == nil { + sep = SepFunc + } + visible := 0 + invisible := 0 + for i := 0; i < len(data); i++ { + if !sep(data[i]) { + continue + } + invisible = i + if invisible == visible { + visible++ + continue + } + b := data[visible:invisible] + if !handler(b) { + return + } + visible = invisible + 1 + } + if visible < len(data) { + handler(data[visible:]) + } + return +} diff --git a/tests/evilpot/go.mod b/tests/evilpot/go.mod new file mode 100644 index 000000000..fc37a9af1 --- /dev/null +++ b/tests/evilpot/go.mod @@ -0,0 +1,5 @@ +module github.com/chaitin/xray/tests/evilpot + +go 1.21 + +require github.com/dengsgo/math-engine v0.0.0-20230823154425-78f211b48149 diff --git a/tests/evilpot/go.sum b/tests/evilpot/go.sum new file mode 100644 index 000000000..de1df6761 --- /dev/null +++ b/tests/evilpot/go.sum @@ -0,0 +1,2 @@ +github.com/dengsgo/math-engine v0.0.0-20230823154425-78f211b48149 h1:TkVfb0s14IUHDGvjQfq3f0PZnV1zh609did4DrnD4q4= +github.com/dengsgo/math-engine v0.0.0-20230823154425-78f211b48149/go.mod h1:zkR27k4K0I8FS6rkEd8qBhPeS8i3X2FKfvSPdF64OpQ= diff --git a/tests/evilpot/main.go b/tests/evilpot/main.go new file mode 100644 index 000000000..62222e115 --- /dev/null +++ b/tests/evilpot/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "flag" + "log" + + "github.com/chaitin/xray/tests/evilpot/evil" +) + +func main() { + evilHardAddr := flag.String("evil-hard", ":8887", "evil server 困难模式 监听地址") + evilAddr := flag.String("evil", ":8888", "evil server 监听地址") + echoAddr := flag.String("echo", ":8889", "echo server 监听地址") + flag.Parse() + go func() { log.Fatalln(evil.ServeEvilServer(*evilHardAddr, true)) }() + go func() { log.Fatalln(evil.ServeEvilServer(*evilAddr, false)) }() + go func() { log.Fatalln(evil.ServeEchoServer(*echoAddr)) }() + select {} +}