整体介绍 crawlergo
是一个使用chrome headless
模式进行URL收集的浏览器爬虫。它对整个网页的关键位置与DOM渲染阶段进行HOOK,自动进行表单填充并提交,配合智能的JS事件触发, 尽可能的收集网站暴露出的入口。内置URL去重模块,过滤掉了大量伪静态URL,对于大型网站仍保持较快的解析与抓取速度,最后得到高质量的请求结果集合。
crawlergo
目前支持以下特性:
原生浏览器环境,协程池调度任务
表单智能填充、自动化提交
完整DOM事件收集,自动化触发
智能URL去重,去掉大部分的重复请求
全面分析收集,包括javascript文件内容、页面注释、robots.txt文件和常见路径Fuzz
支持Host绑定,自动添加Referer
支持请求代理,支持爬虫结果主动推送
项目整体结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 . ├── [4.0K] cmd │ └── [4.0K] crawlergo │ ├── [7.4K] flag.go │ └── [8.5K] main.go ├── [ 941] Disclaimer.md ├── [ 858] dockerfile ├── [4.0K] examples │ ├── [1.2K] host_binding.py │ ├── [1.2K] request_with_cookie.py │ ├── [ 513] subprocess_call.py │ └── [1.6K] zombie_clean.py ├── [ 690] get_chrome.sh ├── [ 588] go.mod ├── [9.6K] go.sum ├── [4.0K] imgs │ ├── [ 36K] bypass.png │ ├── [ 15K] chrome_path.png │ ├── [1.1M] demo.gif │ └── [220K] skp.png ├── [ 34K] LICENSE ├── [ 925] Makefile ├── [4.0K] pkg │ ├── [4.0K] config │ │ ├── [4.5K] config.go │ │ └── [ 424] config_test.go │ ├── [ 861] domain_collect.go │ ├── [4.0K] engine │ │ ├── [6.0K] after_dom_tasks.go │ │ ├── [5.4K] after_loaded_tasks.go │ │ ├── [2.8K] browser.go │ │ ├── [2.0K] collect_links.go │ │ ├── [8.8K] intercept_request.go │ │ ├── [ 11K] tab.go │ │ └── [ 525] tab_test.go │ ├── [4.0K] filter │ │ ├── [1.9K] simple_filter.go │ │ ├── [ 20K] smart_filter.go │ │ └── [1.7K] smart_filter_test.go │ ├── [4.0K] js │ │ └── [ 16K] javascript.go │ ├── [4.0K] logger │ │ └── [ 449] logger.go │ ├── [4.0K] model │ │ ├── [4.0K] request.go │ │ ├── [3.6K] url.go │ │ └── [2.0K] url_test.go │ ├── [5.3K] path_expansion.go │ ├── [5.3K] taskconfig.go │ ├── [1.7K] taskconfig_test.go │ ├── [7.5K] task_main.go │ └── [4.0K] tools │ ├── [1.4K] common.go │ ├── [1.3K] random.go │ └── [4.0K] requests │ ├── [5.7K] requests.go │ ├── [ 581] response.go │ └── [ 510] utils.go ├── [ 11K] README.md └── [9.6K] README_zh-cn.md
运行 首先确保已经安装了Go语言环境。
执行make build
,会在当前目录下生成一个bin
目录的文件夹。
假设你的chromium安装在 /tmp/chromium/
,开启最大10标签页,爬取AWVS靶场:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ bin/crawlergo -c /home/neo/Downloads/chrome-linux/chrome -t 10 http://testphp.vulnweb.com/ INFO[0000] Init crawler task, host: testphp.vulnweb.com, max tab count: 10, max crawl count: 200. INFO[0000] filter mode: smart INFO[0000] If no matches, default form input text: Crawlergo INFO[0000] Start crawling. INFO[0000] filter repeat, target count: 2 INFO[0000] Crawling GET https://testphp.vulnweb.com/ INFO[0000] Crawling GET http://testphp.vulnweb.com/ WARN[0005] navigate timeout https://testphp.vulnweb.com/ INFO[0007] Crawling GET http://testphp.vulnweb.com/index.php INFO[0007] Crawling GET http://testphp.vulnweb.com/artists.php INFO[0007] Crawling GET http://testphp.vulnweb.com/AJAX/index.php INFO[0007] Crawling GET http://testphp.vulnweb.com/guestbook.php ...
工作流程 初始化,声明作者,项目名称等信息 1 2 3 4 5 6 7 8 9 10 11 12 13 14 author := cli.Author{ Name: "9ian1i" , Email: "9ian1itp@gmail.com" , } app := &cli.App{ Name: "crawlergo" , Usage: "A powerful browser crawler for web vulnerability scanners" , UsageText: "crawlergo [global options] url1 url2 url3 ... (must be same host)" , Version: Version, Authors: []*cli.Author{&author}, Flags: cliFlags, Action: run, }
项目启动 1 2 3 4 5 6 err := app.Run(os.Args) if err != nil { logger.Logger.Fatal(err) } func run (c *cli.Context) error {...}
事件监听 使用channel来等待信号的到来,从而达到等待程序退出的目的。如果程序需要在接收到信号时进行一些特定的处理,可以在程序中添加对应的处理逻辑。
1 2 3 4 5 6 7 signalChan = make (chan os.Signal, 1 ) signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT) if c.Args().Len() == 0 { logger.Logger.Error("url must be set" ) return errors.New("url must be set" ) }
设置日志输出级别 1 2 3 4 5 level, err := logrus.ParseLevel(logLevel) if err != nil { logger.Logger.Fatal(err) } logger.Logger.SetLevel(level)
检查自定义的表单参数配置 1 2 3 4 5 6 7 8 taskConfig.CustomFormValues, err = parseCustomFormValues(customFormTypeValues.Value()) if err != nil { logger.Logger.Fatal(err) } taskConfig.CustomFormKeywordValues, err = keywordStringToMap(customFormKeywordValues.Value()) if err != nil { logger.Logger.Fatal(err) }
开始爬虫任务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 task, err := pkg.NewCrawlerTask(targets, taskConfig) if err != nil { logger.Logger.Error("create crawler task failed." ) os.Exit(-1 ) } if len (targets) != 0 { logger.Logger.Info(fmt.Sprintf("Init crawler task, host: %s, max tab count: %d, max crawl count: %d." , targets[0 ].URL.Host, taskConfig.MaxTabsCount, taskConfig.MaxCrawlCount)) logger.Logger.Info("filter mode: " , taskConfig.FilterMode) } if len (taskConfig.CustomFormValues) > 0 { logger.Logger.Info("Custom form values, " + tools.MapStringFormat(taskConfig.CustomFormValues)) } if len (taskConfig.CustomFormKeywordValues) > 0 { logger.Logger.Info("Custom form keyword values, " + tools.MapStringFormat(taskConfig.CustomFormKeywordValues)) } if _, ok := taskConfig.CustomFormValues["default" ]; !ok { logger.Logger.Info("If no matches, default form input text: " + config.DefaultInputText) taskConfig.CustomFormValues["default" ] = config.DefaultInputText } go handleExit(task) logger.Logger.Info("Start crawling." ) task.Run() result := task.Result if pushAddress != "" { logger.Logger.Info("pushing results to " , pushAddress, ", max pool number:" , pushProxyPoolMax) Push2Proxy(result.ReqList) }
结果输出 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func outputResult (result *pkg.Result) { if outputMode == "json" { fmt.Println("--[Mission Complete]--" ) resBytes := getJsonSerialize(result) fmt.Println(string (resBytes)) } else if outputMode == "console" { for _, req := range result.ReqList { req.FormatPrint() } } if len (outputJsonPath) != 0 { resBytes := getJsonSerialize(result) tools.WriteFile(outputJsonPath, resBytes) } }