├── .gitignore ├── LICENSE ├── README.md ├── apikiller.sql ├── ca.crt ├── config ├── config.dev.yaml └── config.release.yaml ├── dbDeploy.sh ├── go.mod ├── go.sum ├── hooks └── .gitkeep ├── internal ├── core │ ├── ahttp │ │ ├── ahttp.go │ │ ├── ahttpModify.go │ │ ├── ahttpModify_test.go │ │ ├── ahttp_test.go │ │ ├── dumpHttp.go │ │ └── hook │ │ │ ├── hook.go │ │ │ └── requestHook.go │ ├── aio │ │ └── repeatReadCloser.go │ ├── async │ │ ├── asyncCheckEngineX.go │ │ └── asyncCheckEngineX_test.go │ ├── data │ │ ├── buildResult.go │ │ └── meta.go │ ├── database │ │ ├── db.go │ │ ├── mysql.go │ │ └── mysql_test.go │ ├── filter │ │ ├── duplicateFilter.go │ │ ├── filter.go │ │ ├── httpFilter.go │ │ └── staticResourceFilter.go │ ├── handler.go │ ├── module │ │ ├── CSRF │ │ │ └── CSRFDetector.go │ │ ├── DoS │ │ │ ├── DoSDetector.go │ │ │ ├── rateLimitDetector.go │ │ │ └── resourceSizeDetector.go │ │ ├── OpenRedirect │ │ │ └── OpenRedirectDetector.go │ │ ├── SSRF │ │ │ └── SSRFDetector.go │ │ ├── authorize │ │ │ ├── authGroup.go │ │ │ ├── authoriedDetector.go │ │ │ └── authoriedDetector_test.go │ │ └── detect.go │ ├── notify │ │ ├── dingding.go │ │ ├── lark.go │ │ └── notify.go │ └── origin │ │ ├── fileInputOrigin │ │ ├── burpFile.go │ │ ├── burpFile_test.go │ │ └── fileInputOrigin.go │ │ ├── origin.go │ │ └── realTimeOrigin │ │ ├── realTimeOrigin.go │ │ └── realTimeOrigin_test.go ├── runner │ ├── banner.go │ ├── option.go │ └── runner.go └── web │ ├── backend │ ├── web.go │ └── web_test.go │ └── frontend │ └── www │ └── index.html ├── main.go ├── pkg ├── logger │ ├── formatter.go │ ├── logger.go │ └── rotate.go └── util │ ├── encode.go │ ├── randomIdGenerator.go │ └── randomIdGenerator_test.go └── static └── img ├── 2.jpg ├── architecture.jpg ├── img-0401.png ├── img.png ├── img_030101.png ├── img_030102.png ├── img_030103.png ├── img_030301.png ├── img_030801.png ├── img_030901.png ├── img_070201.jpg ├── img_1.png ├── img_2.png ├── img_20.png ├── img_21.png ├── img_22.png ├── img_23.png ├── img_24.png ├── img_25.png ├── img_3.png ├── img_4.png ├── img_5.png ├── img_6.png └── img_logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | ### Example user template template 2 | ### Example user template 3 | 4 | # IntelliJ project files 5 | .idea 6 | *.iml 7 | .dev 8 | out 9 | gen 10 | log/* 11 | 12 | test.go 13 | test_test.go 14 | /APIKiller.exe 15 | # 基于自搭建的TCP高效反连平台 16 | /internal/core/async/asyncCheckEngine.go 17 | /internal/core/async/asyncCheckEngine_test.go 18 | /internal/core/async/client.go 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

logo.png

2 | APIKiller-企业API安全保护神

3 |

4 | 简介 • 5 | 架构 • 6 | Feature • 7 | 食用宝典 • 8 | 二次开发文档 • 9 | 更新 • 10 | 项目社区 • 11 | 致谢 12 |


13 | 14 | 15 | 16 | 17 | ## 项目简介 18 | **一款高度可定制化的DAST&API Security平台** 19 | 20 | 详细参考: [快看~项目精髓](https://aur0ra.cn/3-apikiller/) 21 | 22 | ## 项目架构 23 | img_architecture.png 24 | 25 | 26 | ## Feature 27 | - 支持HTTP/HTTPS流量检测 28 | - 多来源检测 29 | - 支持流量监听 30 | - 支持历史流量回扫\[目前只支持burpsuite存储流量\] 31 | - 支持测试流量区分、流量清洗 32 | - 允许通过hook,对所有测试请求进行添加标识header等方式,区分测试流量或者将测试流量导入到pre、boe等非生产环境中 33 | - 多功能扫描模块 34 | - 越权检测模块,高效精准,支持多情景检测 35 | - 40x bypass 模块 36 | - CSRF检测模块 37 | - open-redirect 检测模块 38 | - DoS检测模块【谨慎配置,避免出现大量脏数据】 39 | - 【欢迎大家积极提PR】 40 | - 多功能Filter处理,默认自带多个filter 41 | - 针对性扫描,例如只对 baidu.com域名进行扫描 42 | - 去重扫描,提高效率 43 | - 自动过滤静态文件(js,gif,jpg,png,css,jpeg,xml,img,svg...) 44 | - API 运维 45 | - 提供简易的API Security运维平台 46 | - 多方式漏洞发现提醒 47 | - Lark飞书 48 | - 钉钉 49 | - ... 50 | - 对抗常见风控手段 51 | - 频控 52 | - **【重磅】以上都可以快速进行二次开发** 53 | 54 | ## 食用宝典 55 | > 详细请查看:https://github.com/Aur0ra-m/APIKiller/wiki 56 | 1. 安装好数据库环境(我个人采用的是docker) 57 | 1. **一键部署** 58 | 1. 将项目clone到服务器后,直接运行 ```sudo bash dbDeploy.sh``` 59 | 60 | ![img.png](static/img/img-0401.png) 61 | 2. 根据返回的数据,在config.yaml中完成相关配置 62 | img_6.png 63 | 2. **手动部署** 64 | 1. docker pull 数据库镜像 65 | ```shell 66 | sudo docker run --name mysql-server -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql 67 | ``` 68 | 2. 导入apikiller.sql文件 69 | ```shell 70 | sudo docker cp /tmp/apikiller.sql mysql-server:/tmp/apikiller.sql 71 | ``` 72 | 3. 登入mysql 73 | ```shell 74 | docker exec -it mysql-server mysql -uroot -p123456 75 | source /tmp/apikiller.sql 76 | ``` 77 | 4. 【重点】在 config.yaml 中进行相关配置 78 | 79 | img_6.png 80 | 81 | 2. 安装根目录下的https证书[windows环境] 82 | 1. 找到根目录下的ca.crt证书 83 | 84 | img.png 85 | 86 | 2. 点击安装即可,将其添加到系统根信任证书颁发位置 87 | 88 | 3. 配置漏洞发现通知Webhook 89 | 1. 根据[飞书指导](https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN),开启一个bot,并复制相关的webhook【支持secret鉴权操作】 90 | 91 | 2. 在根路径下的config.json中进行配置(如果有secret,就进行配置) 92 | 93 | img_1.png 94 | 95 | 96 | 97 | 3. 配置成功后,当发现漏洞时,会立即推送漏洞信息 98 | 99 | img_2.png 100 | 101 | 102 | 103 | 3. 一键启动【配置文件位于./config/目录下(默认是config.release.yaml),或自己指定】 104 | 105 | img_4.png 106 | 107 | img_3.png 108 | 109 | 4. **ding~,发现新漏洞,快去看鸭** 110 | 111 | 5. 漏洞运营,及时对漏洞进行研判和修复 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | > **基本配置:数据库模块、过滤器模块、通知模块** 120 | > 121 | > **除基本配置外,还必须进行如下的模块配置。(其中的option必须配置为1,才代表启动该模块)** 122 | ### API越权检测 123 | > 这里基于[VAPI越权靶场](https://www.freebuf.com/vuls/332312.html) 进行实战模拟 124 | > 配好环境后,先根据项目鉴权机制,提供另一个不同权限的账号,配置好config.yaml 125 | 1. 根据企业开发规范,配置好越权模块的相关配置 126 | 127 | img_1.png 128 | 2. 启动项目,访问接口
129 | img_1.png
130 | img.png 131 | 132 | 3. **成功检测出越权和csrf** 133 | 134 | img_3.png 135 | 136 | ### 403 bypass模块 137 | > 当前可以进行大小写、path fuzz、api版本降级等方式,来进行探测 138 | 139 | img.png
140 | img.png 141 | 142 | 143 | ### CSRF检测 144 | > 基于pikachu靶场,进行漏洞检测 145 | 146 | 147 | 处理csrf模块的配置
148 | img.png 149 | img_1.png 150 | img.png 151 | 152 | 153 | 154 | 155 | ## HTTP HOOK机制 156 | > 为避免扫描时造成过无效流量,可以通过提供的HTTP HOOK机制,对请求流量自定义修改,例如添加header,来区分测试流量和实际流量 157 | 158 | 【注意】当前由于golang plugin机制特性,暂不支持windows下的流量修改 159 | 160 | 1. HTTP HOOK 样例 161 | ```go 162 | package main 163 | 164 | import ( 165 | "fmt" 166 | "net/http" 167 | ) 168 | 169 | type RequestHook interface { 170 | HookBefore(*http.Request) // hook before initiating http newReq 171 | HookAfter(*http.Request) // hook after finishing http newReq 172 | } 173 | 174 | type AddHeaderHook struct { 175 | } 176 | 177 | func (a AddHeaderHook) HookBefore(newReq *http.Request) { 178 | fmt.Println("HOOK Before: hhhhhhh") 179 | // .... 180 | } 181 | 182 | func (a AddHeaderHook) HookAfter(newReq *http.Request) { 183 | 184 | } 185 | 186 | // Hook this is exported, and this name must be set Hook 187 | var Hook AddHeaderHook 188 | ``` 189 | 190 | 【严格按照上面的代码规范,其中最后一行代码,命名必须设置为Hook】 191 | 192 | 2. 生成对应的so链接库 193 | ```shell 194 | go build -buildmode=plugin APIKillerHookSample.go 195 | ``` 196 | 197 | ```shell 198 | $ ls 199 | APIKillerHookSample.go APIKillerHookSample.so go.mod 200 | ``` 201 | 3. 将生成的so放置到项目的hooks目录下 202 | ```shell 203 | $ ls ./hooks 204 | APIKillerHookSample.so 205 | ``` 206 | 4. 启动项目即可完成流量更改 207 | 208 | 209 | ## 二次开发文档 210 | https://github.com/Aur0ra-m/APIKiller/wiki 211 | 212 | ## 更新记录 213 | ### v0.0.2 214 | - 【功能】修正对https请求的处理 215 | - 【功能】优化csrf检测模块 216 | - 【功能】添加对钉钉通知的支持 217 | - 【优化】对整体架构进行优化,提高效率(通知模块优化、数据库存储模块优化) 218 | - 【优化】重改数据库设计,同时数据库存储时进行base64转码操作 219 | 220 | 221 | 222 | ### v0.0.3 223 | - 【功能】新增40xbypass模块,支持常见架构层绕过和接口层绕过 224 | - 【优化】优化权限检测模块,向甲方实际情况靠齐 225 | - 【优化】调整配置解析,从json迁移至yaml,同时优化全局解析过程,提高检测效率 226 | - 【优化】调整filter顺序,同时对duplicationFilter查询过程由数据库查询到成员变量查询 227 | - 【bugFix】修复线程安全导致的数据重复等问题 228 | - 【bugFix】调整全局的chance-recovery 机制为clone机制 229 | 230 | ### v0.0.4 231 | - 【功能】添加HTTP HOOK功能,可满足区分测试产生的http脏数据、流量清洗功能。HOOK食用方式 232 | - 【功能】新增开放重定向检测模块,支持对常见的GET Query方式进行测试 233 | - 【功能】新增DoS安全测试模块,目前可以对查询资源大小未控制导致的DoS进行检测,例如size设置为超大数 234 | - 【优化】针对之前试用时产生的各种不适进行了一个优化 235 | 236 | ### v1.0.0 237 | - 【发布】项目wiki以及二次开发文档第一版发布 238 | - 【发布】发布项目Release 1.0.0 239 | - 【优化】多处增添多线程操作,再次优化整体性能 240 | - 【功能】增添可指定配置文件操作 241 | - 【优化】提供数据库一键部署方案 242 | - 【优化】调整部分结构,修复部分bug 243 | 244 | ### v1.0.1 245 | - 【优化】web运营平台启动优化,当本地启动时,会自动打开浏览器访问运营平台 246 | - 【优化】删去origin中不需要的参数,优化meta结构 247 | 248 | ### v1.1.0 249 | - 【重构】调整项目目录和框架,向标准化靠齐 250 | - 【功能】配置加载进行多套方案,允许在开发和正式部署阶段采用不同的配置文件,或加载时指定配置文件 251 | - 【优化】对日志打印及保存进行优化,将日志按日期记录在./log目录下 252 | - 【优化】对项目早期的代码进行标准化处理,删除无效代码或测试代码,确保高质量程序代码 253 | 254 | ### v1.2.0 255 | - 【功能】越权检测模块新增对不同站点进行不同的替换能力,且支持query、header、post body多点替换 256 | 257 | ### v1.2.1 258 | - 【优化】调整了开发环境和部署环境的判定逻辑 259 | - 【bugFix】修复前端中文乱码 260 | - 【bugfix】修复mysql的版本问题,直接拉取最新版,避免拉取不到指定版本的docker镜像 261 | 262 | ## 项目社区 263 | 如想对项目进行深入了解,或加入研发团队欢迎加入APIKiller项目社区。
264 | 群二维码失效可加我微信,备注:私聊回复APIKiller 实时进群
265 | 266 | 267 | ## 致谢 268 | 【**最后感谢项目中所使用到的各种开源组件的作者**】 269 | -------------------------------------------------------------------------------- /apikiller.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE `apikiller` character set utf8; 2 | 3 | use apikiller; 4 | 5 | CREATE TABLE `data_item_strs` ( 6 | `id` varchar(50) NOT NULL, 7 | `domain` varchar(100) DEFAULT NULL, 8 | `Url` varchar(500) DEFAULT NULL, 9 | `method` varchar(10) DEFAULT NULL, 10 | `https` tinyint(1) DEFAULT NULL, 11 | `source_request` varchar(50) DEFAULT NULL, 12 | `source_response` varchar(50) DEFAULT NULL, 13 | `vuln_type` varchar(100) DEFAULT NULL, 14 | `vuln_request` varchar(500) DEFAULT NULL, 15 | `vuln_response` varchar(500) DEFAULT NULL, 16 | `check_state` tinyint(1) DEFAULT NULL, 17 | `report_time` varchar(20) DEFAULT NULL 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; 19 | 20 | 21 | 22 | CREATE TABLE `http_items` ( 23 | `id` int(11) NOT NULL AUTO_INCREMENT, 24 | `item` varchar(15000) CHARACTER SET utf8 DEFAULT NULL, 25 | PRIMARY KEY (`id`) 26 | ) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=latin1; 27 | 28 | 29 | -- v0.0.4 -------------------------------------------------------------------------------- /ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD 3 | VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM 4 | B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0 5 | aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0 6 | MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE 7 | CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV 8 | BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI 9 | hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP 10 | ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9 11 | 3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP 12 | sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9 13 | V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh 14 | hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr 15 | lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq 16 | j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo 17 | WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD 18 | fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj 19 | YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh 20 | WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj 21 | UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4 22 | uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB 23 | CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F 24 | AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0 25 | C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3 26 | Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin 27 | o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye 28 | i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr 29 | bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY 30 | VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft 31 | 8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86 32 | NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV 33 | BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA== 34 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /config/config.dev.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # APIKiller Project Development Configuration 3 | # 4 | app: # 系统全局配置文件 5 | db: # 数据库配置(当前只支持mysql) 6 | mysql: # mysql 7 | host: 10.1.1.10 8 | port: '3306' 9 | dbname: apikiller 10 | username: root 11 | password: '123456' 12 | origin: # 数据源配置 13 | realTime: # 实时监听数据源配置 14 | address: 127.0.0.1 15 | port: '8080' # 监听端口 16 | module: # 核心模块配置 17 | authorizedDetector: # 未授权&越权检测模块配置 18 | option: 1 # 模块开关 19 | authGroup: # position type code 0-header,1-query param, 2-body param 20 | - domain: 21 | - "127.0.0.1" 22 | - "10.10.10.10" 23 | - "127.0.0.1:8000" 24 | replaceGroup : 25 | - position: 0 26 | key: "Cookie" 27 | value: "Aur0ra" 28 | - position: 1 29 | key: "key" 30 | value: "Aur0ra" 31 | - position: 2 32 | key: "postKey" 33 | value: "Aur0ra" 34 | - domain: 35 | - "127.0.0.1" 36 | - 37 | - 38 | replaceItem: 39 | - position: 0 40 | key: "Cookie" 41 | value: "TEST" 42 | - position: 0 43 | key: "Cookie" 44 | value: "TEST" 45 | 46 | ipHeader: # 后端常见请求ip后门(通过特定的header来判断是否是本地请求,从而进行豁免) 47 | - Access-Control-Allow-Origin 48 | - Base-Url 49 | - CF-Connecting_IP 50 | - CF-Connecting-IP 51 | - Client-IP 52 | - Cluster-Client-IP 53 | - Destination 54 | - Forwarded-For-Ip 55 | - Forwarded-For 56 | - Forwarded-Host 57 | - Forwarded 58 | - Host 59 | - Http-Url 60 | - Origin 61 | - Profile 62 | - Proxy-Host 63 | - Proxy-Url 64 | - Proxy 65 | - Real-Ip 66 | - Redirect 67 | - Referer 68 | - Referrer 69 | - Request-Uri 70 | - True-Client-IP 71 | - Uri 72 | - Url 73 | - X-Arbitrary 74 | - X-Client-IP 75 | - X-Custom-IP-Authorization 76 | - X-Forward-For 77 | - X-Forward 78 | - X-Forwarded-By 79 | - X-Forwarded-For-Original 80 | - X-Forwarded-For 81 | - X-Forwarded-Host 82 | - X-Forwarded-Proto 83 | - X-Forwarded-Server 84 | - X-Forwarded 85 | - X-Forwarder-For 86 | - X-Host 87 | - X-HTTP-DestinationURL 88 | - X-HTTP-Host-Override 89 | - X-Original-Remote-Addr 90 | - X-Original-URL 91 | - X-Originally-Forwarded-For 92 | - X-Originating-IP 93 | - X-Proxy-Url 94 | - X-ProxyUser-Ip 95 | - X-Real-Ip 96 | - X-Real-IP 97 | - X-Referrer 98 | - X-Remote-Addr 99 | - X-Remote-IP 100 | - X-Rewrite-URL 101 | - X-True-IP 102 | - X-WAP-Profile 103 | ip: 127.0.0.1 # 特定豁免ip 104 | apiVersion: # api版本格式,例如有 /apiv1/或者/api/v1/等,如下是/api1/的示例 105 | format: "api\\d" 106 | prefix: "api" 107 | pathFuzz: # 路径fuzz列表 108 | midPadding: 109 | - "" 110 | - "." 111 | - "..;" 112 | - ".;" 113 | endPadding: 114 | - "?" 115 | - "??" 116 | - "." 117 | - ".." 118 | - "./" 119 | - "%20" 120 | - "%09" 121 | - "%0a" 122 | - "#" 123 | judgement: # 判断引擎配置 124 | blackStatusCodes: # 鉴权失败响应码 125 | - 403 126 | - 401 127 | blackKeywords: # 鉴权失败响应关键字 128 | - forbidden 129 | - error 130 | CSRFDetector: # csrf检测模块 131 | option: 0 # 模块开关 132 | csrfTokenPattern: csrf # token对应的参数名或者请求头 133 | csrfInvalidPattern: # csrf鉴权失败返回的标识 134 | - invalid 135 | - csrf 136 | openRedirectDetector: # openRedirect检测 137 | option: 0 138 | rawQueryParams: 139 | - url 140 | - redirect 141 | - uri 142 | - redirection 143 | - next 144 | - returnto 145 | - return_to 146 | - origin 147 | - callback 148 | - authorize_callback 149 | - target 150 | - link 151 | failFlag: 152 | - error 153 | - fail 154 | DoSDetector: # dos检测模块 155 | option: 0 156 | sizeParam: # 资源查询大小控制参数 157 | - size 158 | - Size 159 | rateLimit: 160 | failFlag: # 存在频控的标识 161 | - exceed 162 | - captcha 163 | - too many 164 | - rate limit 165 | - Maximum login 166 | SSRFDetector: 167 | option: 0 168 | filter: # 过滤器配置 169 | httpFilter: # http过滤器配置:目前只支持根据指定的host,对其进行检测,如果未设置,则默认对所有流量进行检测 170 | host: 171 | - "127.0.0.1:8000" 172 | staticFileFilter: # 静态文件过滤器:对获取静态资源的流量不做处理 173 | ext: 174 | - js 175 | - gif 176 | - jpg 177 | - png 178 | - css 179 | - jpeg 180 | - xml 181 | - img 182 | - svg 183 | - ico 184 | notifier: # 通知方式配置 185 | Lark: # Lark飞书(支持token检验) 186 | webhookUrl: 'https://open.feishu.cn/open-apis/bot/v2/hook/a814553d-3fc0-4c5b-8e98-694830cc3121' 187 | secret: '' 188 | Dingding: # 钉钉 189 | webhookUrl: '' 190 | other: # 其他配置 191 | reverseTarget: 127.0.0.1 # 反连平台目标 192 | web: # web运营平台配置 193 | ipaddr: 127.0.0.1 194 | port: '80' 195 | -------------------------------------------------------------------------------- /config/config.release.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # APIKiller Project Release Configuration 3 | # 4 | app: # 系统全局配置文件 5 | db: # 数据库配置(当前只支持mysql) 6 | mysql: # mysql 7 | host: 10.1.1.10 8 | port: '3306' 9 | dbname: apikiller 10 | username: root 11 | password: '123456' 12 | origin: # 数据源配置 13 | realTime: # 实时监听数据源配置 14 | address: 127.0.0.1 15 | port: '8080' # 监听端口 16 | module: # 核心模块配置 17 | authorizedDetector: # 未授权&越权检测模块配置 18 | option: 1 # 模块开关 19 | authGroup: # position type code 0-header,1-query param, 2-body param 20 | - domain: 21 | - "127.0.0.1" 22 | - "10.10.10.10" 23 | - "127.0.0.1:8000" 24 | replaceGroup: 25 | - position: 0 26 | key: "Cookie" 27 | value: "Aur0ra" 28 | - position: 1 29 | key: "key" 30 | value: "Aur0ra" 31 | - position: 2 32 | key: "postKey" 33 | value: "Aur0ra" 34 | - domain: 35 | - "127.0.0.1" 36 | - 37 | - 38 | replaceItem: 39 | - position: 0 40 | key: "Cookie" 41 | value: "TEST" 42 | - position: 0 43 | key: "Cookie" 44 | value: "TEST" 45 | ipHeader: # 后端常见请求ip后门(通过特定的header来判断是否是本地请求,从而进行豁免) 46 | - Access-Control-Allow-Origin 47 | - Base-Url 48 | - CF-Connecting_IP 49 | - CF-Connecting-IP 50 | - Client-IP 51 | - Cluster-Client-IP 52 | - Destination 53 | - Forwarded-For-Ip 54 | - Forwarded-For 55 | - Forwarded-Host 56 | - Forwarded 57 | - Host 58 | - Http-Url 59 | - Origin 60 | - Profile 61 | - Proxy-Host 62 | - Proxy-Url 63 | - Proxy 64 | - Real-Ip 65 | - Redirect 66 | - Referer 67 | - Referrer 68 | - Request-Uri 69 | - True-Client-IP 70 | - Uri 71 | - Url 72 | - X-Arbitrary 73 | - X-Client-IP 74 | - X-Custom-IP-Authorization 75 | - X-Forward-For 76 | - X-Forward 77 | - X-Forwarded-By 78 | - X-Forwarded-For-Original 79 | - X-Forwarded-For 80 | - X-Forwarded-Host 81 | - X-Forwarded-Proto 82 | - X-Forwarded-Server 83 | - X-Forwarded 84 | - X-Forwarder-For 85 | - X-Host 86 | - X-HTTP-DestinationURL 87 | - X-HTTP-Host-Override 88 | - X-Original-Remote-Addr 89 | - X-Original-URL 90 | - X-Originally-Forwarded-For 91 | - X-Originating-IP 92 | - X-Proxy-Url 93 | - X-ProxyUser-Ip 94 | - X-Real-Ip 95 | - X-Real-IP 96 | - X-Referrer 97 | - X-Remote-Addr 98 | - X-Remote-IP 99 | - X-Rewrite-URL 100 | - X-True-IP 101 | - X-WAP-Profile 102 | ip: 127.0.0.1 # 特定豁免ip 103 | apiVersion: # api版本格式,例如有 /apiv1/或者/api/v1/等,如下是/api1/的示例 104 | format: "api\\d" 105 | prefix: "api" 106 | pathFuzz: # 路径fuzz列表 107 | midPadding: 108 | - "" 109 | - "." 110 | - "..;" 111 | - ".;" 112 | endPadding: 113 | - "?" 114 | - "??" 115 | - "." 116 | - ".." 117 | - "./" 118 | - "%20" 119 | - "%09" 120 | - "%0a" 121 | - "#" 122 | judgement: # 判断引擎配置 123 | blackStatusCodes: # 鉴权失败响应码 124 | - 403 125 | - 401 126 | blackKeywords: # 鉴权失败响应关键字 127 | - forbidden 128 | - error 129 | CSRFDetector: # csrf检测模块 130 | option: 1 # 模块开关 131 | csrfTokenPattern: csrf # token对应的参数名或者请求头 132 | csrfInvalidPattern: # csrf鉴权失败返回的标识 133 | - invalid 134 | - csrf 135 | openRedirectDetector: # openRedirect检测 136 | option: 1 137 | rawQueryParams: 138 | - url 139 | - redirect 140 | - uri 141 | - redirection 142 | - next 143 | - returnto 144 | - return_to 145 | - origin 146 | - callback 147 | - authorize_callback 148 | - target 149 | - link 150 | failFlag: 151 | - error 152 | - fail 153 | DoSDetector: # dos检测模块 154 | option: 1 155 | sizeParam: # 资源查询大小控制参数 156 | - size 157 | - Size 158 | rateLimit: 159 | failFlag: # 存在频控的标识 160 | - exceed 161 | - captcha 162 | - too many 163 | - rate limit 164 | - Maximum login 165 | SSRFDetector: 166 | option: 1 167 | filter: # 过滤器配置 168 | httpFilter: # http过滤器配置:目前只支持根据指定的host,对其进行检测,如果未设置,则默认对所有流量进行检测 169 | host: 170 | - "127.0.0.1:8000" 171 | staticFileFilter: # 静态文件过滤器:对获取静态资源的流量不做处理 172 | ext: 173 | - js 174 | - gif 175 | - jpg 176 | - png 177 | - css 178 | - jpeg 179 | - xml 180 | - img 181 | - svg 182 | - ico 183 | notifier: # 通知方式配置 184 | Lark: # Lark飞书(支持token检验) 185 | webhookUrl: 'https://open.feishu.cn/open-apis/bot/v2/hook/a814553d-3fc0-4c5b-8e98-694830cc3121' 186 | secret: '' 187 | Dingding: # 钉钉 188 | webhookUrl: '' 189 | other: # 其他配置 190 | reverseTarget: 127.0.0.1 # 反连平台目标 191 | web: # web运营平台配置 192 | ipaddr: 127.0.0.1 193 | port: '80' 194 | -------------------------------------------------------------------------------- /dbDeploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define variables 4 | MYSQL_ROOT_PASSWORD="123456" 5 | MYSQL_USER="apikiller" 6 | MYSQL_PASSWORD="password" 7 | MYSQL_DATABASE="apikiller" 8 | 9 | # ANSI color codes 10 | bold=$(tput bold) 11 | normal=$(tput sgr0) 12 | green=$(tput setaf 2) 13 | 14 | # Pull mysql:5.7 image 15 | docker pull mysql 16 | 17 | # Start mysql container 18 | docker run --name mysql-server -e MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} -p 3306:3306 -d mysql 19 | 20 | # Wait for mysql service to start 21 | until docker exec -it mysql-server mysqladmin ping --silent &> /dev/null; do 22 | echo "waiting for mysql to start ..." 23 | sleep 10 24 | done 25 | 26 | 27 | # Create new user and import data 28 | docker cp ./apikiller.sql mysql-server:/tmp/apikiller.sql 29 | docker exec -i mysql-server mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e "CREATE USER '${MYSQL_USER}'@'%' IDENTIFIED BY '${MYSQL_PASSWORD}'; GRANT ALL PRIVILEGES ON ${MYSQL_DATABASE}.* TO '${MYSQL_USER}'@'%'; FLUSH PRIVILEGES; source /tmp/apikiller.sql;" 30 | 31 | echo "MySQL setup complete!" 32 | 33 | # Print username and password in green color 34 | echo "\r\n${bold}${green}\r\n======================================================================" 35 | echo "Login MySQL server IP address (default returns info of the first network interface): `hostname -I | awk '{print $1}'`" 36 | echo "Login MySQL server username: ${MYSQL_USER}" 37 | echo "Login MySQL server password: ${MYSQL_PASSWORD}" 38 | echo "Project database: ${MYSQL_DATABASE}${normal}" 39 | echo "======================================================================" -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module APIKiller 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/antlabs/strsim v0.0.3 7 | github.com/beevik/etree v1.1.0 8 | github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 9 | github.com/gin-gonic/gin v1.8.2 10 | github.com/sirupsen/logrus v1.9.0 11 | github.com/spf13/viper v1.15.0 12 | golang.org/x/exp v0.0.0-20230223210539-50820d90acfd 13 | golang.org/x/net v0.5.0 14 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 15 | gorm.io/driver/mysql v1.4.5 16 | gorm.io/gorm v1.24.3 17 | ) 18 | 19 | require ( 20 | github.com/fsnotify/fsnotify v1.6.0 // indirect 21 | github.com/gin-contrib/sse v0.1.0 // indirect 22 | github.com/go-playground/locales v0.14.1 // indirect 23 | github.com/go-playground/universal-translator v0.18.0 // indirect 24 | github.com/go-playground/validator/v10 v10.11.1 // indirect 25 | github.com/go-sql-driver/mysql v1.7.0 // indirect 26 | github.com/goccy/go-json v0.10.0 // indirect 27 | github.com/hashicorp/hcl v1.0.0 // indirect 28 | github.com/jinzhu/inflection v1.0.0 // indirect 29 | github.com/jinzhu/now v1.1.5 // indirect 30 | github.com/json-iterator/go v1.1.12 // indirect 31 | github.com/leodido/go-urn v1.2.1 // indirect 32 | github.com/magiconair/properties v1.8.7 // indirect 33 | github.com/mattn/go-isatty v0.0.17 // indirect 34 | github.com/mitchellh/mapstructure v1.5.0 // indirect 35 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 36 | github.com/modern-go/reflect2 v1.0.2 // indirect 37 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 38 | github.com/spf13/afero v1.9.3 // indirect 39 | github.com/spf13/cast v1.5.0 // indirect 40 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 41 | github.com/spf13/pflag v1.0.5 // indirect 42 | github.com/subosito/gotenv v1.4.2 // indirect 43 | github.com/tidwall/gjson v1.14.4 // indirect 44 | github.com/tidwall/match v1.1.1 // indirect 45 | github.com/tidwall/pretty v1.2.1 // indirect 46 | github.com/ugorji/go/codec v1.2.8 // indirect 47 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect 48 | golang.org/x/sys v0.4.0 // indirect 49 | golang.org/x/text v0.6.0 // indirect 50 | google.golang.org/protobuf v1.28.1 // indirect 51 | gopkg.in/ini.v1 v1.67.0 // indirect 52 | gopkg.in/yaml.v2 v2.4.0 // indirect 53 | gopkg.in/yaml.v3 v3.0.1 // indirect 54 | ) 55 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 41 | github.com/antlabs/strsim v0.0.3 h1:J9AHxnybJZHKBoxeup1VZNWt3ST8QD+ieDJsm/nEpRo= 42 | github.com/antlabs/strsim v0.0.3/go.mod h1:bIcymn+2jtt01korFun0bs8PsYZeQa82aHoYMi7cm30= 43 | github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= 44 | github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= 45 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 46 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 47 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 48 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 49 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 50 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 51 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 52 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 53 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 54 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 55 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 56 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 57 | github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= 58 | github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 59 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= 60 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 61 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 62 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 63 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 64 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 65 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 66 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 67 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 68 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 69 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 70 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 71 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 72 | github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY= 73 | github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398= 74 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 75 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 76 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 77 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= 78 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 79 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 80 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 81 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 82 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 83 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 84 | github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= 85 | github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= 86 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 87 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 88 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= 89 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 90 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 91 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 92 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 93 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 94 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 95 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 96 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 97 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 98 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 99 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 100 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 101 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 102 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 103 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 104 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 105 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 106 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 107 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 108 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 109 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 110 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 111 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 112 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 113 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 114 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 115 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 116 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 117 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 118 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 119 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 120 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 121 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 122 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 123 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 124 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 125 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 126 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 127 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 128 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 129 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 130 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 131 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 132 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 133 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 134 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 135 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 136 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 137 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 138 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 139 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 140 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 141 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 142 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 143 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 144 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 145 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 146 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 147 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 148 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 149 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 150 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 151 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 152 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 153 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 154 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 155 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 156 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 157 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 158 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 159 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 160 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 161 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 162 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 163 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 164 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 165 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 166 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 167 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 168 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 169 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 170 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 171 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 172 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 173 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 174 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 175 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 176 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 177 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 178 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 179 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 180 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 181 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 182 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 183 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 184 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 185 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 186 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 187 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 188 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 189 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 190 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 191 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 192 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 193 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 194 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 195 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 196 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 197 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 198 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 199 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 200 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 201 | github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= 202 | github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= 203 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 204 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 205 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 206 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 207 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 208 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 209 | github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= 210 | github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= 211 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 212 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 213 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 214 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 215 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 216 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 217 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 218 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 219 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 220 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 221 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 222 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 223 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 224 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= 225 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 226 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 227 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 228 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 229 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 230 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 231 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 232 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 233 | github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0= 234 | github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 235 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 236 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 237 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 238 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 239 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 240 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 241 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 242 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 243 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 244 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 245 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 246 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 247 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 248 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 249 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 250 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 251 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 252 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 253 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= 254 | golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 255 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 256 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 257 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 258 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 259 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 260 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 261 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 262 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 263 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 264 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 265 | golang.org/x/exp v0.0.0-20230223210539-50820d90acfd h1:wtFuj4DoOcAdb82Zh2PI90xiaqgp7maYA7KxjQXVtkY= 266 | golang.org/x/exp v0.0.0-20230223210539-50820d90acfd/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 267 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 268 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 269 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 270 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 271 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 272 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 273 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 274 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 275 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 276 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 277 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 278 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 279 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 280 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 281 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 282 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 283 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 284 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 285 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 286 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 287 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 288 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 289 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 290 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 291 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 292 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 293 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 294 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 295 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 296 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 297 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 298 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 299 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 300 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 301 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 302 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 303 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 304 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 305 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 306 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 307 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 308 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 309 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 310 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 311 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 312 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 313 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 314 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 315 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 316 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 317 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 318 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 319 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 320 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 321 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 322 | golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= 323 | golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 324 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 325 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 326 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 327 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 328 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 329 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 330 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 331 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 332 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 333 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 334 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 335 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 336 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 337 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 338 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 339 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 340 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 341 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 342 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 343 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 344 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 345 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 346 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 347 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 348 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 349 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 350 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 351 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 352 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 353 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 354 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 355 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 356 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 357 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 358 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 359 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 360 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 361 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 362 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 363 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 364 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 365 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 366 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 367 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 368 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 369 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 370 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 371 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 372 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 373 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 374 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 375 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 376 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 377 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 378 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 379 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 380 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 381 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 382 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= 383 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 384 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 385 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 386 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 387 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 388 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 389 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 390 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 391 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 392 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 393 | golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= 394 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 395 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 396 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 397 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 398 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 399 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 400 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 401 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 402 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 403 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 404 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 405 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 406 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 407 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 408 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 409 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 410 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 411 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 412 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 413 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 414 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 415 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 416 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 417 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 418 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 419 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 420 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 421 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 422 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 423 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 424 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 425 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 426 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 427 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 428 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 429 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 430 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 431 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 432 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 433 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 434 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 435 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 436 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 437 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 438 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 439 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 440 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 441 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 442 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 443 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 444 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 445 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 446 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 447 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 448 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 449 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 450 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 451 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 452 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 453 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 454 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 455 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 456 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 457 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 458 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 459 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 460 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 461 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 462 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 463 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 464 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 465 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 466 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 467 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 468 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 469 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 470 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 471 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 472 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 473 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 474 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 475 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 476 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 477 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 478 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 479 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 480 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 481 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 482 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 483 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 484 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 485 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 486 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 487 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 488 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 489 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 490 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 491 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 492 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 493 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 494 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 495 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 496 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 497 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 498 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 499 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 500 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 501 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 502 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 503 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 504 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 505 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 506 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 507 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 508 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 509 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 510 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 511 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 512 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 513 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 514 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 515 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 516 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 517 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 518 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 519 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 520 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 521 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 522 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 523 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 524 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 525 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 526 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 527 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 528 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 529 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 530 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 531 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 532 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 533 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 534 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 535 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 536 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 537 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 538 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 539 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 540 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 541 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 542 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 543 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 544 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 545 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 546 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 547 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 548 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 549 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 550 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 551 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 552 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 553 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 554 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 555 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 556 | gorm.io/driver/mysql v1.4.5 h1:u1lytId4+o9dDaNcPCFzNv7h6wvmc92UjNk3z8enSBU= 557 | gorm.io/driver/mysql v1.4.5/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc= 558 | gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= 559 | gorm.io/gorm v1.24.3 h1:WL2ifUmzR/SLp85CSURAfybcHnGZ+yLSGSxgYXlFBHg= 560 | gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= 561 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 562 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 563 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 564 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 565 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 566 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 567 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 568 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 569 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 570 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 571 | -------------------------------------------------------------------------------- /hooks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/hooks/.gitkeep -------------------------------------------------------------------------------- /internal/core/ahttp/ahttp.go: -------------------------------------------------------------------------------- 1 | package ahttp 2 | 3 | import ( 4 | "APIKiller/internal/core/ahttp/hook" 5 | "APIKiller/internal/core/aio" 6 | "APIKiller/pkg/logger" 7 | "bufio" 8 | "crypto/tls" 9 | "io/ioutil" 10 | "net/http" 11 | "net/url" 12 | "strings" 13 | ) 14 | 15 | type requestCacheBlock struct { 16 | key *http.Request //method-domain-url 17 | value string 18 | } 19 | 20 | var ( 21 | cache = make([]*requestCacheBlock, 128) 22 | updatePoint = 0 23 | ) 24 | 25 | // 26 | // DoRequest 27 | // @Description: make a http request without auto 30x redirect 28 | // @param r 29 | // @return *http.Response 30 | // 31 | func DoRequest(r *http.Request) *http.Response { 32 | var Client http.Client 33 | 34 | logger.Debugln("Do request: ", r.URL) 35 | 36 | // https request 37 | if r.URL.Scheme == "https" { 38 | // ignore certificate verification 39 | tr := &http.Transport{ 40 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 41 | } 42 | // https client 43 | Client = http.Client{ 44 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 45 | return http.ErrUseLastResponse 46 | }, 47 | Transport: tr, 48 | } 49 | } else { 50 | // http client 51 | Client = http.Client{ 52 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 53 | return http.ErrUseLastResponse 54 | }, 55 | } 56 | } 57 | 58 | // hook before initiating http request 59 | for _, requestHook := range hook.Hooks { 60 | requestHook.HookBefore(r) 61 | } 62 | 63 | response, err := Client.Do(r) 64 | if err != nil { 65 | logger.Errorln(err) 66 | return nil 67 | } 68 | 69 | // hook after finishing http request 70 | for _, requestHook := range hook.Hooks { 71 | requestHook.HookAfter(r) 72 | } 73 | 74 | // transform aio.Reader 75 | if response.Body != nil { 76 | response.Body = aio.TransformReadCloser(response.Body) 77 | } 78 | 79 | return response 80 | } 81 | 82 | // 83 | // RequestClone 84 | // @Description: clone request with source request 85 | // @param src 86 | // @return *http.Request 87 | // 88 | func RequestClone(src *http.Request) *http.Request { 89 | // dump request 90 | reqStr := "" 91 | for _, c := range cache { 92 | if c == nil { 93 | break 94 | } 95 | if c.key == src { 96 | reqStr = c.value 97 | break 98 | } 99 | } 100 | if reqStr == "" { 101 | reqStr = DumpRequest(src) 102 | 103 | if cache[updatePoint] == nil { 104 | cache[updatePoint] = &requestCacheBlock{} 105 | } 106 | cache[updatePoint].key = src 107 | cache[updatePoint].value = reqStr 108 | updatePoint = (updatePoint + 1) % 128 109 | } 110 | 111 | // http.ReadRequest 112 | request, err := http.ReadRequest(bufio.NewReader(strings.NewReader(reqStr))) 113 | if err != nil { 114 | logger.Errorln("read request error: ", err) 115 | } 116 | 117 | // we can't have this set. And it only contains "/pkg/net/http/" anyway 118 | request.RequestURI = "" 119 | 120 | // set url 121 | u, err := url.Parse(src.URL.String()) 122 | if err != nil { 123 | logger.Errorln("parse url error: ", err) 124 | } 125 | request.URL = u 126 | 127 | // transform body 128 | if request.Body != nil { 129 | request.Body = aio.TransformReadCloser(request.Body) 130 | 131 | // update content-length 132 | all, _ := ioutil.ReadAll(request.Body) 133 | request.ContentLength = int64(len(all)) 134 | } 135 | 136 | return request 137 | } 138 | 139 | func ResponseClone(src *http.Response, req *http.Request) (dst *http.Response) { 140 | 141 | // dump response 142 | respStr := DumpResponse(src) 143 | 144 | // http.ReadResponse 145 | response, err := http.ReadResponse(bufio.NewReader(strings.NewReader(respStr)), req) 146 | if err != nil { 147 | logger.Errorln("read response error: ", err) 148 | } 149 | 150 | // transform body 151 | response.Body = aio.TransformReadCloser(response.Body) 152 | return response 153 | } 154 | 155 | // 156 | // ExistsParam 157 | // @Description: 158 | // @param req 159 | // @param paramName 160 | // @return bool 161 | // 162 | func ExistsParam(req *http.Request, paramName string) bool { 163 | return false 164 | } 165 | -------------------------------------------------------------------------------- /internal/core/ahttp/ahttpModify.go: -------------------------------------------------------------------------------- 1 | package ahttp 2 | 3 | import ( 4 | "APIKiller/internal/core/aio" 5 | "APIKiller/pkg/logger" 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | // 16 | // ModifyMethod 17 | // @Description: modify to target method 18 | // @param method 19 | // @param req 20 | // @return *http.Request 21 | // 22 | func ModifyMethod(method string, req *http.Request) *http.Request { 23 | 24 | // get URL and read body 25 | url := req.URL.String() 26 | 27 | body := "" 28 | if req.Body != nil { 29 | bytes, _ := ioutil.ReadAll(req.Body) 30 | body = string(bytes) 31 | } 32 | 33 | var ( 34 | request *http.Request 35 | ) 36 | 37 | if body == "" { 38 | request, _ = http.NewRequest(method, url, nil) 39 | } else { 40 | request, _ = http.NewRequest(method, url, bytes.NewReader([]byte(body))) 41 | 42 | // transform body 43 | request.Body = aio.TransformReadCloser(request.Body) 44 | } 45 | 46 | // copy headers 47 | for s, _ := range req.Header { 48 | request.Header.Set(s, req.Header.Get(s)) 49 | } 50 | 51 | return request 52 | } 53 | 54 | // 55 | // ModifyParameterFormat 56 | // @Description: modify http parameter format. E.g., switch json to www-urlencoded 57 | // @param req 58 | // @return *http.Request 59 | // 60 | func ModifyParameterFormat(req *http.Request) *http.Request { 61 | return nil 62 | } 63 | 64 | // 65 | // ModifyURLPathCase 66 | // @Description: modify the first letter of path to lowercase and the rest are uppercase. E.g. "/xxxx-->/xXXX" 67 | // @param Url 68 | // 69 | func ModifyURLPathCase(Url *url.URL) { 70 | srcPath := Url.Path 71 | 72 | splits := strings.Split(srcPath, "/") 73 | 74 | // switch to upper case 75 | lastPart := splits[len(splits)-1] 76 | 77 | lastPart = strings.ToUpper(lastPart) 78 | lastPart = strings.ToLower(lastPart[0:1]) + lastPart[1:] 79 | 80 | splits[len(splits)-1] = lastPart 81 | 82 | // join them into a new path 83 | newPath := strings.Join(splits, "/") 84 | 85 | Url.Path = newPath 86 | } 87 | 88 | // 89 | // ModifyURLPathMidPad 90 | // @Description: modify the end of url path with padding 91 | // @param Url 92 | // @param padding 93 | // @return *url.URL 94 | // 95 | func ModifyURLPathMidPad(Url *url.URL, padding string) { 96 | srcPath := Url.Path 97 | 98 | // trim the last separator 99 | if strings.HasSuffix(Url.Path, "/") { 100 | srcPath = strings.TrimRight(srcPath, "/") 101 | } 102 | 103 | // trim the first separator 104 | srcPath = strings.TrimLeft(srcPath, "/") 105 | 106 | // split path into fragments 107 | splits := strings.Split(srcPath, "/") 108 | 109 | firstPart := splits[0] 110 | thirdPart := "" 111 | newPath := "" 112 | 113 | if len(splits) > 1 { 114 | thirdPart = strings.Join(splits[1:], "/") 115 | 116 | newPath = firstPart + "/" + padding + "/" + thirdPart 117 | } else { 118 | newPath = "/" + padding + "/" + firstPart 119 | } 120 | Url.Path = newPath 121 | } 122 | 123 | // 124 | // ModifyURLPathEndPad 125 | // @Description: modify the end of url path with padding 126 | // @param Url 127 | // @param padding 128 | // @return *url.URL 129 | // 130 | func ModifyURLPathEndPad(Url *url.URL, padding string) { 131 | srcPath := Url.Path 132 | 133 | // trim the last separator 134 | if strings.HasSuffix(Url.Path, "/") { 135 | srcPath = strings.TrimRight(srcPath, "/") 136 | } 137 | 138 | Url.Path = srcPath + padding 139 | } 140 | 141 | // 142 | // ModifyURLPathAPIVerion 143 | // @Description: replace API version in path with target string 144 | // @param Url 145 | // @param srcString 146 | // @param targetString 147 | // 148 | func ModifyURLPathAPIVerion(Url *url.URL, srcString, targetString string) { 149 | Url.Path = strings.Replace(Url.Path, srcString, targetString, 1) 150 | } 151 | 152 | // 153 | // ModifyParam 154 | // @Description: modify all positions parameter 155 | // @param req 156 | // @param paramName 157 | // @param newValue 158 | // @return bool 159 | // 160 | func ModifyParam(req *http.Request, paramName string, newValue string) *http.Request { 161 | 162 | newReq := RequestClone(req) 163 | if ModifyQueryParam(newReq, paramName, newValue) == nil { 164 | if ModifyPostParam(newReq, paramName, newValue) == nil { 165 | return nil 166 | } 167 | } 168 | 169 | return newReq 170 | } 171 | 172 | // 173 | // ModifyQueryParam 174 | // @Description: modify url query parameter 175 | // @param req 176 | // @param paramName 177 | // @param newValue 178 | // @return *http.Request 179 | // 180 | func ModifyQueryParam(req *http.Request, paramName string, newValue string) *http.Request { 181 | queryParams := req.URL.Query() 182 | if queryParams.Get(paramName) != "" { 183 | 184 | req.URL.RawQuery = strings.Replace(req.URL.RawQuery, paramName+"="+req.URL.Query()[paramName][0], paramName+"="+newValue, 1) 185 | return req 186 | } 187 | 188 | return nil 189 | } 190 | 191 | // 192 | // ModifyPostParam 193 | // @Description: modify post body parameter through Content-Type in request 194 | // @param req 195 | // @param paramName 196 | // @param newValue 197 | // @return *http.Request 198 | // 199 | func ModifyPostParam(req *http.Request, paramName string, newValue string) *http.Request { 200 | ct := req.Header.Get("Content-Type") 201 | 202 | // determine whether the request does have a body or not 203 | if ct == "" { 204 | logger.Debugln("target request does not have Content-Type header") 205 | return nil 206 | } 207 | 208 | readAll, _ := ioutil.ReadAll(req.Body) 209 | bodyStr := string(readAll) 210 | 211 | var newReq *http.Request 212 | if ct == "application/x-www-form-urlencoded" { 213 | if strings.Contains(bodyStr, paramName+"=") { 214 | 215 | modifyPostFormParam(req, paramName, newValue) 216 | } 217 | } else if ct == "application/json" { 218 | if strings.Contains(bodyStr, "\""+paramName+"\"") { 219 | 220 | modifyPostJsonParam(req, paramName, newValue) 221 | } 222 | } else if ct == "application/xml" { 223 | if strings.Contains(bodyStr, paramName+">") { 224 | 225 | modifyPostXMLParam(req, paramName, newValue) 226 | } 227 | } else if strings.Contains(ct, "multipart/form-data") { 228 | if strings.Contains(bodyStr, ";name=\""+paramName) { 229 | 230 | modifyPostMultiDataParam(req, paramName, newValue) 231 | } 232 | } else { 233 | logger.Errorln("Not support other Content-Type") 234 | return nil 235 | } 236 | 237 | return newReq 238 | } 239 | 240 | // 241 | // modifyPostXMLParam 242 | // @Description: modify xml data with newValue 243 | // @param req 244 | // @param name 245 | // @param value 246 | // 247 | func modifyPostXMLParam(req *http.Request, paramName string, newValue string) { 248 | paramItemRegExp := `<` + paramName + `>(.*?)<` 249 | 250 | modifyPostBody(req, paramItemRegExp, ">", newValue) 251 | } 252 | 253 | // 254 | // modifyPostJsonParam 255 | // @Description: modify simple post json data with newValue 256 | // @param req 257 | // @param paramName 258 | // @param newValue 259 | // 260 | func modifyPostJsonParam(req *http.Request, paramName string, newValue string) { 261 | paramItemRegExp := `"` + paramName + `"\s*?:\s*"?(.*?)"?[\s,\}]` 262 | 263 | modifyPostBody(req, paramItemRegExp, ":", newValue) 264 | } 265 | 266 | //{"size": "10000" 267 | // 268 | // modifyPostFormParam 269 | // @Description: modify simple post form data with newValue 270 | // @param req 271 | // @param paramName 272 | // @param newValue 273 | // 274 | func modifyPostFormParam(req *http.Request, paramName string, newValue string) { 275 | paramItemRegExp := paramName + `=([^&]*)` 276 | 277 | modifyPostBody(req, paramItemRegExp, "=", newValue) 278 | } 279 | 280 | // 281 | // modifyPostMultiDataParam 282 | // @Description: modify post parameter in form of multi-data 283 | // @param req 284 | // @param paramName 285 | // @param newValue 286 | // 287 | func modifyPostMultiDataParam(req *http.Request, paramName string, newValue string) { 288 | paramItemRegExp := "name=\"" + paramName + "\".*?" + "\r\n\r\n" + "([^\r\n]*)" 289 | 290 | modifyPostBody(req, paramItemRegExp, "\r\n\r\n", newValue) 291 | } 292 | 293 | // 294 | // modifyPostBody 295 | // @Description: modify parameter item in post body 296 | // @param req 297 | // @param paramItemRegExp 298 | // @param paramKVSeparator the separator between parameter key and parameter value 299 | // @param newValue 300 | // 301 | func modifyPostBody(req *http.Request, paramItemRegExp string, paramKVSeparator string, newValue string) { 302 | // read data from body 303 | all, err := ioutil.ReadAll(req.Body) 304 | if err != nil { 305 | logger.Errorln(fmt.Sprintf("modify post data error:%v", err)) 306 | panic(err) 307 | } 308 | body := string(all) 309 | 310 | // extract paramItem and value that will be replaced from body with regular expression 311 | r := regexp.MustCompile(paramItemRegExp) 312 | submatchs := r.FindStringSubmatch(body) 313 | paramItem := submatchs[0] 314 | srcValue := submatchs[1] 315 | 316 | if srcValue == "" { 317 | logger.Infoln("original parameter has no value") 318 | return 319 | } 320 | 321 | // split paramItem into two parts, key-part and value-part 322 | splits := strings.Split(paramItem, paramKVSeparator) 323 | 324 | // replace source value with new value 325 | newValuePart := strings.Replace(splits[1], srcValue, newValue, 1) 326 | 327 | // join key-part and value-part 328 | newParamItem := splits[0] + paramKVSeparator + newValuePart 329 | 330 | // replace paramItem in body 331 | newBody := strings.Replace(body, paramItem, newParamItem, 1) 332 | 333 | // refill body 334 | req.Body = aio.TransformReadCloser(bytes.NewReader([]byte(newBody))) 335 | 336 | // update Content-Length 337 | req.ContentLength = int64(len(newBody)) 338 | } 339 | 340 | // 341 | // ModifyQueryParamByRegExp 342 | // @Description: modify query parameter value through specified value format 343 | // @param req 344 | // @param valueRegExp 345 | // @param newValue 346 | // @return *http.Request 347 | // 348 | func ModifyQueryParamByRegExp(req *http.Request, valueRegExp string, newValue string) *http.Request { 349 | re := regexp.MustCompile(valueRegExp) 350 | 351 | findAllString := re.FindAllString(req.URL.RawQuery, -1) 352 | if len(findAllString) > 0 { 353 | // clone request 354 | newReq := RequestClone(req) 355 | 356 | newReq.URL.RawQuery = strings.Replace(req.URL.RawQuery, findAllString[0], newValue, 1) 357 | return newReq 358 | } 359 | 360 | return nil 361 | } 362 | 363 | // 364 | // ModifyPostParamByRegExp 365 | // @Description: modify parameter value in post body through specified value format 366 | // @param req 367 | // @param valueRegExp value format 368 | // @param newValue 369 | // @return *http.Request 370 | // 371 | func ModifyPostParamByRegExp(req *http.Request, valueRegExp string, newValue string) *http.Request { 372 | 373 | re := regexp.MustCompile(valueRegExp) 374 | 375 | findAllString := re.FindAllString(req.URL.RawQuery, -1) 376 | if len(findAllString) > 0 { 377 | // clone request 378 | newReq := RequestClone(req) 379 | 380 | newReq.URL.RawQuery = strings.Replace(req.URL.RawQuery, findAllString[0], newValue, 1) 381 | return newReq 382 | } 383 | 384 | ct := req.Header.Get("Content-Type") 385 | 386 | // determine whether the request does have a body or not 387 | if ct == "" { 388 | logger.Debugln("target request does not have Content-Type header") 389 | return nil 390 | } 391 | 392 | readAll, _ := ioutil.ReadAll(req.Body) 393 | bodyStr := string(readAll) 394 | 395 | matches := re.FindAllString(bodyStr, -1) 396 | 397 | if len(matches) <= 0 { 398 | return nil 399 | } 400 | 401 | // clone request 402 | newReq := RequestClone(req) 403 | 404 | // replace parameter matching the value format 405 | newBody := strings.Replace(bodyStr, matches[0], newValue, 1) 406 | 407 | // refill body 408 | req.Body = aio.TransformReadCloser(bytes.NewReader([]byte(newBody))) 409 | 410 | // update Content-Length 411 | req.ContentLength = int64(int(len(newBody))) 412 | 413 | return newReq 414 | } 415 | 416 | // 417 | // AppendHeader 418 | // @Description: append new header to request 419 | // @param req 420 | // @param header 421 | // @param value 422 | // 423 | func AppendHeader(req *http.Request, header string, value string) { 424 | req.Header.Add(header, value) 425 | } 426 | 427 | // 428 | // RemoveHeader 429 | // @Description: remove the specified header from the request 430 | // @param req 431 | // @param header 432 | // 433 | func RemoveHeader(req *http.Request, header string) { 434 | if req.Header.Get(header) != "" { 435 | req.Header.Del(header) 436 | } else { 437 | logger.Errorln("no specified header which will be removed in request") 438 | } 439 | } 440 | 441 | // 442 | // UpdateHeader 443 | // @Description: update the specified header in the request 444 | // @param req 445 | // @param header 446 | // @param value 447 | // 448 | func UpdateHeader(req *http.Request, header string, value string) { 449 | if req.Header.Get(header) != "" { 450 | req.Header.Set(header, value) 451 | } else { 452 | logger.Errorln("no specified header which will be updated in request") 453 | } 454 | } 455 | -------------------------------------------------------------------------------- /internal/core/ahttp/ahttpModify_test.go: -------------------------------------------------------------------------------- 1 | package ahttp 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | func TestModifyPostFormParameter(t *testing.T) { 10 | type args struct { 11 | req *http.Request 12 | paramName string 13 | newValue string 14 | } 15 | request, _ := http.NewRequest("POST", "https://localhost", bytes.NewBuffer([]byte("test=123"))) 16 | tests := []struct { 17 | name string 18 | args args 19 | }{ 20 | // TODO: Add test cases. 21 | { 22 | name: "test", 23 | args: args{ 24 | req: request, 25 | paramName: "test", 26 | newValue: "hacker", 27 | }, 28 | }, 29 | } 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | modifyPostFormParam(tt.args.req, tt.args.paramName, tt.args.newValue) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/core/ahttp/ahttp_test.go: -------------------------------------------------------------------------------- 1 | package ahttp 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | ) 7 | 8 | func TestCopyRequest(t *testing.T) { 9 | type args struct { 10 | src *http.Request 11 | } 12 | request, _ := http.NewRequest("GET", "http://127.0.0.1/list", nil) 13 | tests := []struct { 14 | name string 15 | args args 16 | wantDst *http.Request 17 | }{ 18 | // TODO: Add test cases. 19 | { 20 | name: "13213", 21 | args: args{request}, 22 | wantDst: nil, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | 28 | //request.Body = aio.TransformReadCloser(request.Body) 29 | 30 | client := http.Client{} 31 | client.Do(request) 32 | client.Do(request) 33 | RequestClone(request) 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/core/ahttp/dumpHttp.go: -------------------------------------------------------------------------------- 1 | package ahttp 2 | 3 | import ( 4 | "APIKiller/pkg/logger" 5 | "net/http" 6 | "net/http/httputil" 7 | ) 8 | 9 | // 10 | // DumpRequest 11 | // @Description: convert *http.Request to string 12 | // @param request 13 | // @return string 14 | // 15 | func DumpRequest(request *http.Request) string { 16 | if request == nil { 17 | logger.Debugln("dump response error: request is nil") 18 | return "" 19 | } 20 | 21 | dumpRequest, _ := httputil.DumpRequest(request, request.Body != nil) 22 | 23 | return string(dumpRequest) 24 | } 25 | 26 | // 27 | // DumpResponse 28 | // @Description: convert *http.Response to string 29 | // @param response 30 | // @return string 31 | // 32 | func DumpResponse(response *http.Response) string { 33 | if response == nil { 34 | logger.Debugln("dump response error: response is nil") 35 | return "" 36 | } 37 | 38 | dumpRequest, _ := httputil.DumpResponse(response, response.Body != nil) 39 | 40 | return string(dumpRequest) 41 | } 42 | 43 | // 44 | // DumpRequests 45 | // @Description: convert []*http.Request to []string 46 | // @param requests 47 | // @return []string 48 | // 49 | func DumpRequests(requests []*http.Request) []string { 50 | var result []string 51 | for _, request := range requests { 52 | dumpRequest := DumpRequest(request) 53 | result = append(result, dumpRequest) 54 | } 55 | 56 | return result 57 | } 58 | 59 | // 60 | // DumpResponses 61 | // @Description: convert []*http.Response to []string 62 | // @param requests 63 | // @return []string 64 | // 65 | func DumpResponses(responses []*http.Response) []string { 66 | var result []string 67 | for _, response := range responses { 68 | dumpResponse := DumpResponse(response) 69 | result = append(result, dumpResponse) 70 | } 71 | 72 | return result 73 | } 74 | -------------------------------------------------------------------------------- /internal/core/ahttp/hook/hook.go: -------------------------------------------------------------------------------- 1 | package hook 2 | 3 | var Hooks []RequestHook 4 | 5 | // 6 | // RegisterHooks 7 | // @Description: append http request hook to modify request data 8 | // @param requestHook 9 | // 10 | func RegisterHooks(requestHook RequestHook) { 11 | Hooks = append(Hooks, requestHook) 12 | } 13 | -------------------------------------------------------------------------------- /internal/core/ahttp/hook/requestHook.go: -------------------------------------------------------------------------------- 1 | package hook 2 | 3 | import "net/http" 4 | 5 | type RequestHook interface { 6 | HookBefore(*http.Request) // hook before initiating http request 7 | HookAfter(*http.Request) // hook after finishing http request 8 | } 9 | -------------------------------------------------------------------------------- /internal/core/aio/repeatReadCloser.go: -------------------------------------------------------------------------------- 1 | package aio 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "unsafe" 8 | ) 9 | 10 | type RepeatReadCloser struct { 11 | Reader *bytes.Buffer 12 | } 13 | 14 | // 15 | // Read reset point after each read 16 | // @Description: 17 | // @receiver p 18 | // @param val 19 | // @return n 20 | // @return err 21 | // 22 | func (p *RepeatReadCloser) Read(val []byte) (n int, err error) { 23 | if p.Reader.Len() == 0 { 24 | // reset offset 25 | p.resetBufferOffset() 26 | 27 | return 0, io.EOF 28 | } 29 | 30 | n, err = p.Reader.Read(val) 31 | 32 | return 33 | } 34 | 35 | // 36 | // resetBufferOffset 37 | // @Description: reset offset and lastRead of buffer 38 | // @receiver p 39 | // 40 | func (p *RepeatReadCloser) resetBufferOffset() { 41 | r := reflect.ValueOf(p.Reader) 42 | buffer := r.Elem() 43 | 44 | // set buffer.off = 0 45 | offValue := buffer.FieldByName("off") 46 | offValue = reflect.NewAt(offValue.Type(), unsafe.Pointer(offValue.UnsafeAddr())).Elem() 47 | offValue.SetInt(0) 48 | 49 | // sync set buffer.lastRead = opInvalid 50 | lastReadValue := buffer.FieldByName("lastRead") 51 | lastReadValue = reflect.NewAt(lastReadValue.Type(), unsafe.Pointer(lastReadValue.UnsafeAddr())).Elem() 52 | lastReadValue.SetInt(0) 53 | } 54 | 55 | func (p *RepeatReadCloser) Close() error { 56 | 57 | return nil 58 | } 59 | 60 | // 61 | // TransformReadCloser 62 | // @Description: quickly transform aio.Reader into RepeatReadCloser 63 | // @param r 64 | // 65 | func TransformReadCloser(r io.Reader) *RepeatReadCloser { 66 | buf := new(bytes.Buffer) 67 | buf.ReadFrom(r) 68 | 69 | return &RepeatReadCloser{Reader: buf} 70 | } 71 | -------------------------------------------------------------------------------- /internal/core/async/asyncCheckEngineX.go: -------------------------------------------------------------------------------- 1 | package async 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | "APIKiller/internal/core/database" 6 | "APIKiller/internal/core/notify" 7 | "APIKiller/pkg/logger" 8 | "github.com/tidwall/gjson" 9 | "strings" 10 | "time" 11 | 12 | "fmt" 13 | "io/ioutil" 14 | "net/http" 15 | ) 16 | 17 | type AsyncCheckEngine struct { 18 | httpAPI string 19 | lastRecordId string 20 | } 21 | 22 | func NewAsyncCheckEngine() *AsyncCheckEngine { 23 | return &AsyncCheckEngine{ 24 | httpAPI: "http://api.ceye.io/v1/records?token=0920449a5ed8b9db7a287a66a6632498&type=http", 25 | lastRecordId: "0", 26 | } 27 | } 28 | 29 | // 30 | // Start 31 | // @Description: start to check 32 | // @receiver e 33 | // 34 | func (e *AsyncCheckEngine) Start() { 35 | // heart beat detection 36 | if !e.heartbeat() { 37 | logger.Errorln("cannot access target website successfully: http://api.ceye.io") 38 | return 39 | } 40 | 41 | // build a request 42 | request, _ := http.NewRequest("GET", e.httpAPI, nil) 43 | client := http.Client{} 44 | // polling API interface 45 | for { 46 | // make a http request 47 | response, _ := client.Do(request) 48 | 49 | // get data from json body 50 | if response.Body != nil { 51 | all, err := ioutil.ReadAll(response.Body) 52 | if err != nil { 53 | logger.Errorln(err) 54 | } 55 | results := gjson.Get(string(all), "data").Array() 56 | 57 | for _, result := range results { 58 | if result.Get("id").String() <= e.lastRecordId { 59 | continue 60 | } 61 | 62 | name := result.Get("name") 63 | token := strings.Replace(name.String(), "http://zpysri.ceye.io/", "", 1) 64 | 65 | go e.check(token) 66 | } 67 | 68 | if len(results) > 0 { 69 | e.lastRecordId = results[0].Get("id").String() 70 | } 71 | 72 | } 73 | 74 | // sleep 75 | time.Sleep(5 * 1000 * time.Millisecond) 76 | } 77 | } 78 | 79 | // 80 | // heartbeat 81 | // @Description: check the health of http://api.ceye.io/ three times 82 | // @receiver e 83 | // @return bool 84 | // 85 | func (e *AsyncCheckEngine) heartbeat() bool { 86 | request, _ := http.NewRequest("GET", e.httpAPI, nil) 87 | client := http.Client{} 88 | 89 | for i := 0; i < 3; i++ { 90 | response, err := client.Do(request) 91 | if err != nil || response.StatusCode >= 500 { 92 | logger.Debugln(err) 93 | } else { 94 | return true 95 | } 96 | } 97 | return false 98 | } 99 | 100 | // 101 | // check 102 | // @Description: notify async check token and update the corresponding vulnerability record 103 | // @receiver e 104 | // @param token 105 | // 106 | func (e *AsyncCheckEngine) check(token string) { 107 | logger.Infoln(fmt.Sprintf("[async check] token: %s", token)) 108 | 109 | // notify 110 | notify.CreateNotification(&data.DataItem{ 111 | Id: "", 112 | Domain: "异步检测", 113 | Url: "", 114 | Method: "", 115 | Https: false, 116 | SourceRequest: nil, 117 | SourceResponse: nil, 118 | VulnType: token, 119 | VulnRequest: nil, 120 | VulnResponse: nil, 121 | ReportTime: "", 122 | CheckState: false, 123 | }) 124 | 125 | // update database 126 | database.CreateUpdateTask(token) 127 | } 128 | -------------------------------------------------------------------------------- /internal/core/async/asyncCheckEngineX_test.go: -------------------------------------------------------------------------------- 1 | package async 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAsyncCheckEngine_Start(t *testing.T) { 8 | type fields struct { 9 | httpAPI string 10 | } 11 | tests := []struct { 12 | name string 13 | fields fields 14 | }{ 15 | { 16 | name: "test", 17 | fields: fields{httpAPI: "http://api.ceye.io/v1/records?token=0920449a5ed8b9db7a287a66a6632498&type=http"}, 18 | }, // TODO: Add test cases. 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | e := &AsyncCheckEngine{ 23 | httpAPI: tt.fields.httpAPI, 24 | } 25 | e.Start() 26 | }) 27 | } 28 | } 29 | 30 | func TestAsyncCheckEngine_heartbeat(t *testing.T) { 31 | type fields struct { 32 | httpAPI string 33 | lastRecordId string 34 | } 35 | tests := []struct { 36 | name string 37 | fields fields 38 | want bool 39 | }{ 40 | { 41 | name: "", 42 | fields: fields{ 43 | httpAPI: "http://api.ceye.io/v1/records?token=0920449a5ed8b9db7a287a66a6632498&type=http", 44 | lastRecordId: "xxxxx", 45 | }, 46 | want: false, 47 | }, // TODO: Add test cases. 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | e := &AsyncCheckEngine{ 52 | httpAPI: tt.fields.httpAPI, 53 | lastRecordId: tt.fields.lastRecordId, 54 | } 55 | if got := e.heartbeat(); got != tt.want { 56 | t.Errorf("heartbeat() = %v, want %v", got, tt.want) 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/core/data/buildResult.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "APIKiller/pkg/util" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | // 11 | // BuildResult 12 | // @Description: build result through create a new *data.DateItem 13 | // @param dataItem 14 | // @param vulnType 15 | // @param vulnReq 16 | // @param vulnResp 17 | // @return *data.DataItem 18 | // 19 | func BuildResult(dataItem *DataItem, vulnType string, vulnReq *http.Request, vulnResp *http.Response) *DataItem { 20 | return &DataItem{ 21 | Id: util.GenerateRandomId(), 22 | Domain: dataItem.Domain, 23 | Url: dataItem.Url, 24 | Method: dataItem.Method, 25 | Https: dataItem.Https, 26 | SourceRequest: dataItem.SourceRequest, 27 | SourceResponse: dataItem.SourceResponse, 28 | VulnType: vulnType, 29 | VulnRequest: vulnReq, 30 | VulnResponse: vulnResp, 31 | ReportTime: fmt.Sprintf("%v", time.Now().Unix()), 32 | CheckState: false, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/core/data/meta.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | //_ "gorm.aio/gorm" 5 | "net/http" 6 | ) 7 | 8 | type DataItem struct { 9 | Id string 10 | Domain string 11 | Url string 12 | Method string 13 | Https bool //http/https flag 14 | SourceRequest *http.Request 15 | SourceResponse *http.Response 16 | VulnType string 17 | VulnRequest *http.Request 18 | VulnResponse *http.Response 19 | ReportTime string 20 | CheckState bool 21 | } 22 | 23 | type DataItemStr struct { 24 | Id string `json:"Id" form:"Id" ` 25 | Domain string `json:"Domain" form:"Domain" ` 26 | Url string `json:"Url" form:"Url" ` 27 | Method string `json:"Method" form:"Method"` 28 | Https bool `json:"Https" form:"Https" ` 29 | SourceRequest string `json:"SourceRequest" form:"SourceRequest" ` 30 | SourceResponse string `json:"SourceResponse" form:"SourceResponse" ` 31 | VulnType string `json:"VulnType" form:"VulnType" ` 32 | VulnRequest string `json:"VulnRequest" form:"VulnRequest" ` 33 | VulnResponse string `json:"VulnResponse" form:"VulnResponse" ` 34 | ReportTime string `json:"ReportTime" form:"ReportTime" ` 35 | CheckState bool `json:"CheckState" form:"CheckState" ` 36 | } 37 | 38 | type HttpItem struct { 39 | // 40 | Id int64 `json:"id" form:"id" gorm:"primaryKey" ` 41 | // string format of http 42 | Item string `json:"item" form:"item" ` 43 | } 44 | -------------------------------------------------------------------------------- /internal/core/database/db.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | ) 6 | 7 | type Database interface { 8 | ListAllInfo() []data.DataItemStr 9 | AddInfo(item *data.DataItem) 10 | Exist(domain, url, method string) bool 11 | UpdateVulnType(vulnType string) 12 | } 13 | 14 | var ( 15 | saveTaskQueue chan *data.DataItem 16 | updateTaskQueue chan string 17 | db Database 18 | ) 19 | 20 | // 21 | // CreateSaveTask 22 | // @Description: create save result task 23 | // @param item 24 | // 25 | func CreateSaveTask(item *data.DataItem) { 26 | saveTaskQueue <- item 27 | } 28 | 29 | func CreateUpdateTask(vulnType string) { 30 | updateTaskQueue <- vulnType 31 | } 32 | 33 | // 34 | // BindDatabase 35 | // @Description: bind global database with provided db object 36 | // @param database 37 | // 38 | func BindDatabase(database Database) { 39 | db = database 40 | 41 | // create result save task system 42 | saveTaskQueue = make(chan *data.DataItem, 1024) 43 | // result-save queue 44 | go func() { 45 | var item *data.DataItem 46 | for { 47 | item = <-saveTaskQueue 48 | db.AddInfo(item) 49 | } 50 | }() 51 | 52 | // create update task system 53 | updateTaskQueue = make(chan string, 1024) 54 | // update vulnType queue 55 | go func() { 56 | var vulnType string 57 | for { 58 | vulnType = <-updateTaskQueue 59 | // update vuln type in db 60 | db.UpdateVulnType(vulnType) 61 | } 62 | }() 63 | } 64 | -------------------------------------------------------------------------------- /internal/core/database/mysql.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "APIKiller/internal/core/ahttp" 5 | "APIKiller/internal/core/data" 6 | "APIKiller/internal/core/module" 7 | log "APIKiller/pkg/logger" 8 | "encoding/base64" 9 | "fmt" 10 | "github.com/spf13/viper" 11 | "gorm.io/driver/mysql" 12 | "gorm.io/gorm" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | type Mysql struct { 18 | db *gorm.DB 19 | MaxCount int //the max num of per-query 20 | } 21 | 22 | func (m *Mysql) UpdateVulnType(token string) { 23 | m.db.Model(&data.DataItemStr{}).Where("vuln_type = ?", token).Update("vuln_type", strings.Split(token, module.AsyncDetectVulnTypeSeperator)[0]) 24 | } 25 | 26 | // ListAllInfo fetch all results and return 27 | func (m *Mysql) ListAllInfo() []data.DataItemStr { 28 | items := make([]data.DataItemStr, m.MaxCount) //需要动态设置,能先查有多少条记录,再创建? 29 | 30 | m.db.Where("vuln_type not like ?", "%"+module.AsyncDetectVulnTypeSeperator+"%").Order("domain").Order("url").Find(&items) 31 | 32 | // recover http item string from id 33 | for i, item := range items { 34 | // item.SourceRequest 35 | items[i].SourceRequest = m.getHttpItembyId(item.SourceRequest) 36 | 37 | //item.SourceResponse 38 | items[i].SourceResponse = m.getHttpItembyId(item.SourceResponse) 39 | 40 | //item.VulnRequest 41 | items[i].VulnRequest = m.getHttpItembyId(item.VulnRequest) 42 | 43 | //item.VulnResponse 44 | items[i].VulnResponse = m.getHttpItembyId(item.VulnResponse) 45 | } 46 | 47 | return items 48 | } 49 | 50 | func (m *Mysql) Exist(domain, url, method string) bool { 51 | var count int64 52 | v := &data.DataItemStr{} 53 | 54 | m.db.Model(&v).Where("url = ?", url).Where("domain = ?", domain).Where("method = ?", method).Count(&count) 55 | 56 | if count > 0 { 57 | return true 58 | } 59 | 60 | return false 61 | } 62 | 63 | // addInfo append new result 64 | func (m *Mysql) AddInfo(item *data.DataItem) { 65 | // transfer DataItem to DataItemStr 66 | itemStr := data.DataItemStr{ 67 | Id: item.Id, 68 | Domain: item.Domain, 69 | Url: item.Url, 70 | Https: item.Https, 71 | Method: item.Method, 72 | SourceRequest: m.addHttpItem(ahttp.DumpRequest(item.SourceRequest)), 73 | SourceResponse: m.addHttpItem(ahttp.DumpResponse(item.SourceResponse)), 74 | VulnType: item.VulnType, 75 | VulnRequest: m.addHttpItem(ahttp.DumpRequest(item.VulnRequest)), 76 | VulnResponse: m.addHttpItem(ahttp.DumpResponse(item.VulnResponse)), 77 | ReportTime: item.ReportTime, 78 | CheckState: item.CheckState, 79 | } 80 | 81 | // store DataItemStr 82 | m.db.Create(&itemStr) 83 | } 84 | 85 | // addHttpItem 86 | // 87 | // @Description: store request or response in form of string and return id 88 | // @receiver m 89 | // @param item 90 | // @return string 91 | func (m *Mysql) addHttpItem(itemStr string) string { 92 | // substr if itemStr is too long 93 | if len(itemStr) > 10000 { 94 | itemStr = itemStr[:10000] 95 | } 96 | 97 | // base64 encode 98 | b64 := base64.StdEncoding.EncodeToString([]byte(itemStr)) 99 | 100 | httpItem := &data.HttpItem{ 101 | Item: b64, 102 | } 103 | 104 | m.db.Create(&httpItem) 105 | 106 | return fmt.Sprintf("%v", httpItem.Id) 107 | } 108 | 109 | func (m *Mysql) getHttpItembyId(Id string) string { 110 | // convert string to id 111 | id, _ := strconv.Atoi(Id) 112 | 113 | item := &data.HttpItem{} 114 | 115 | m.db.Find(item).Where("id = ?", id) 116 | 117 | // decode base64 118 | decodeString, _ := base64.StdEncoding.DecodeString(item.Item) 119 | 120 | return string(decodeString) 121 | } 122 | 123 | // addHttpItems 124 | // 125 | // @Description: store requests or responses in form of string and return ids seperated by comma 126 | // @receiver m 127 | // @param item 128 | // @return string 129 | func (m *Mysql) addHttpItems(items []string) string { 130 | if len(items) == 0 { 131 | return "" 132 | } 133 | 134 | var Ids []string 135 | 136 | for _, item := range items { 137 | Id := m.addHttpItem(item) 138 | Ids = append(Ids, Id) 139 | } 140 | 141 | return strings.Join(Ids, ",") 142 | } 143 | 144 | // test data: connect("192.168.52.153", "3306","apikiller", "root","123456") 145 | func (m *Mysql) connect(host, port, dbname, username, password string) { 146 | //dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" 147 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", username, password, host, port, dbname) 148 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 149 | 150 | if err != nil { 151 | log.Errorln("Connect database error", err) 152 | panic(err) 153 | } 154 | 155 | m.db = db 156 | } 157 | 158 | // init 159 | // 160 | // @Description: 161 | // @receiver m 162 | func (m *Mysql) init() { 163 | // disable logging 164 | m.db.Logger.LogMode(1) 165 | } 166 | 167 | func NewMysqlClient() *Mysql { 168 | mysqlcli := &Mysql{} 169 | 170 | //parse config 171 | host := viper.GetString("app.db.mysql.host") 172 | port := viper.GetString("app.db.mysql.port") 173 | dbname := viper.GetString("app.db.mysql.dbname") 174 | username := viper.GetString("app.db.mysql.username") 175 | password := viper.GetString("app.db.mysql.password") 176 | 177 | //connect db and return DB object 178 | mysqlcli.connect(host, port, dbname, username, password) 179 | 180 | // init mysql 181 | mysqlcli.init() 182 | 183 | return mysqlcli 184 | } 185 | -------------------------------------------------------------------------------- /internal/core/database/mysql_test.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "gorm.io/gorm" 6 | "testing" 7 | ) 8 | 9 | func TestMysql_Init(t *testing.T) { 10 | 11 | } 12 | 13 | func TestMysql_ListAllInfo(t *testing.T) { 14 | fmt.Println("Test") 15 | m := new(Mysql) 16 | m.connect("192.168.52.153", "3306", "apikiller", "root", "123456") 17 | m.addHttpItem("123123") 18 | } 19 | 20 | func TestMysql_addHttpItem(t *testing.T) { 21 | type fields struct { 22 | db *gorm.DB 23 | MaxCount int 24 | } 25 | type args struct { 26 | item string 27 | } 28 | tests := []struct { 29 | name string 30 | fields fields 31 | args args 32 | want string 33 | }{ 34 | // TODO: Add test cases. 35 | { 36 | name: "", 37 | fields: fields{}, 38 | args: args{}, 39 | want: "", 40 | }, 41 | } 42 | for _, tt := range tests { 43 | t.Run(tt.name, func(t *testing.T) { 44 | m := &Mysql{ 45 | db: tt.fields.db, 46 | MaxCount: tt.fields.MaxCount, 47 | } 48 | if got := m.addHttpItem(tt.args.item); got != tt.want { 49 | t.Errorf("addHttpItem() = %v, want %v", got, tt.want) 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/core/filter/duplicateFilter.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "APIKiller/pkg/logger" 5 | "fmt" 6 | "golang.org/x/exp/slices" 7 | "net/http" 8 | ) 9 | 10 | type DuplicateFilter struct { 11 | history []string // []string{"GET domain /admin/index",} 12 | } 13 | 14 | func (f *DuplicateFilter) Filter(req *http.Request) bool { 15 | logger.Debugln("[Filter] duplicate") 16 | 17 | // format 18 | curr := fmt.Sprintf("%s %s %s", req.Method, req.Host, req.URL.Path) 19 | 20 | // duplication 21 | if slices.Contains(f.history, curr) { 22 | logger.Infoln("duplicate data") 23 | return FilterBlocked 24 | } 25 | 26 | // append to history 27 | f.history = append(f.history, curr) 28 | 29 | return FilterPass 30 | } 31 | 32 | func NewDuplicateFilter() *DuplicateFilter { 33 | logger.Infoln("[Load Filter] duplicate filter") 34 | return &DuplicateFilter{} 35 | } 36 | -------------------------------------------------------------------------------- /internal/core/filter/filter.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | const ( 8 | FilterPass = true 9 | FilterBlocked = false 10 | ) 11 | 12 | var ( 13 | filters []Filter 14 | ) 15 | 16 | type Filter interface { 17 | // 18 | // Filter 19 | // @Description: filter out *http.Request that do not meet the conditions 20 | // @param *http.Request 21 | // @return bool 22 | // 23 | Filter(*http.Request) bool 24 | } 25 | 26 | func RegisterFilter(filter Filter) { 27 | if filter == nil { 28 | return 29 | } 30 | 31 | filters = append(filters, filter) 32 | } 33 | 34 | func GetFilters() []Filter { 35 | if filters != nil { 36 | return filters 37 | } 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /internal/core/filter/httpFilter.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "APIKiller/pkg/logger" 5 | "github.com/spf13/viper" 6 | "net/http" 7 | "regexp" 8 | ) 9 | 10 | type HttpFilter struct { 11 | hostsExp []string 12 | } 13 | 14 | func (f *HttpFilter) Filter(req *http.Request) bool { 15 | logger.Debugln("[Filter] ahttp filter") 16 | 17 | // match through RegExp 18 | if len(f.hostsExp) != 0 { 19 | reqHost := req.Host 20 | flag := FilterBlocked 21 | for _, hostExp := range f.hostsExp { 22 | if matched, _ := regexp.Match(hostExp, []byte(reqHost)); matched { 23 | flag = FilterPass 24 | break 25 | } 26 | } 27 | return flag 28 | } 29 | 30 | return FilterPass // default 31 | } 32 | 33 | func NewHttpFilter() Filter { 34 | logger.Infoln("[Load Filter] http filter") 35 | 36 | return &HttpFilter{ 37 | hostsExp: viper.GetStringSlice("app.filter.httpFilter.host"), 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/core/filter/staticResourceFilter.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "APIKiller/pkg/logger" 5 | "github.com/spf13/viper" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | type StaticResourceFilter struct { 11 | forbidenExts []string 12 | } 13 | 14 | func (f *StaticResourceFilter) Filter(req *http.Request) bool { 15 | logger.Debugln("[Filter] static file filter") 16 | 17 | // get request path extension 18 | lastIndex := strings.LastIndex(req.URL.Path, ".") 19 | if lastIndex == -1 { 20 | return FilterPass 21 | } 22 | ext := req.URL.Path[lastIndex+1:] 23 | 24 | // filter 25 | for _, forbidenExt := range f.forbidenExts { 26 | if forbidenExt == ext { 27 | return FilterBlocked 28 | } 29 | } 30 | 31 | return FilterPass 32 | } 33 | 34 | func NewStaticFileFilter() Filter { 35 | logger.Infoln("[Load Filter] static file filter") 36 | 37 | return &StaticResourceFilter{ 38 | forbidenExts: viper.GetStringSlice("app.filter.staticFileFilter.ext"), 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/core/handler.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | "APIKiller/internal/core/database" 6 | "APIKiller/internal/core/module" 7 | "APIKiller/internal/core/notify" 8 | "APIKiller/internal/core/origin" 9 | "APIKiller/pkg/logger" 10 | "fmt" 11 | "strings" 12 | ) 13 | 14 | var notifier notify.Notify 15 | 16 | func NewHandler(httpItem *origin.TransferItem) { 17 | r := httpItem.Req 18 | 19 | // assembly DataItem 20 | item := &data.DataItem{ 21 | Id: "", 22 | Domain: r.Host, 23 | Url: r.URL.Path, 24 | Https: r.URL.Scheme == "https", 25 | Method: r.Method, 26 | SourceRequest: r, 27 | SourceResponse: httpItem.Resp, 28 | VulnType: "", 29 | VulnRequest: nil, 30 | VulnResponse: nil, 31 | ReportTime: "", 32 | CheckState: false, 33 | } 34 | 35 | // enum all modules and detect 36 | modules := module.GetModules() 37 | for i, _ := range modules { 38 | go func(x int) { 39 | resultDataItem := modules[x].Detect(item) 40 | 41 | // exist vulnerable 42 | if resultDataItem != nil { 43 | if strings.Index(resultDataItem.VulnType, module.AsyncDetectVulnTypeSeperator) <= 0 { 44 | logger.Infoln(fmt.Sprintf("[Found Vulnerability] %s%s-->%s", resultDataItem.Domain, resultDataItem.Url, resultDataItem.VulnType)) 45 | // create notification 46 | notify.CreateNotification(resultDataItem) 47 | } 48 | // save result 49 | database.CreateSaveTask(resultDataItem) 50 | } 51 | }(i) 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /internal/core/module/CSRF/CSRFDetector.go: -------------------------------------------------------------------------------- 1 | package CSRF 2 | 3 | import ( 4 | http2 "APIKiller/internal/core/ahttp" 5 | "APIKiller/internal/core/data" 6 | "APIKiller/internal/core/module" 7 | "APIKiller/pkg/logger" 8 | "github.com/antlabs/strsim" 9 | "github.com/spf13/viper" 10 | "io/ioutil" 11 | "net/http" 12 | "regexp" 13 | "strings" 14 | "sync" 15 | ) 16 | 17 | type CSRFDetector struct { 18 | csrfTokenPattern string 19 | csrfInvalidPattern []string 20 | samesitePolicy map[string]string 21 | mu sync.Mutex 22 | } 23 | 24 | func (d *CSRFDetector) Detect(item *data.DataItem) (result *data.DataItem) { 25 | logger.Debugln("[Detect] CSRF detect") 26 | 27 | srcResp := item.SourceResponse 28 | srcReq := item.SourceRequest 29 | 30 | // same-site check with lock 31 | d.mu.Lock() 32 | if d.samesitePolicy[srcReq.Host] == "" { 33 | d.getSameSitePolicy(item) 34 | } 35 | d.mu.Unlock() 36 | 37 | policy := d.samesitePolicy[srcReq.Host] 38 | if policy == "Strict" { 39 | return 40 | } else if policy == "Lax" && item.Method != "GET" { 41 | return 42 | } else { 43 | // no same-site policy or the policy is fail 44 | } 45 | 46 | // cors--Access-Control-Allow-Origin 47 | value := srcResp.Header.Get("Access-Control-Allow-Origin") 48 | if value != "" && value != "*" { 49 | return 50 | } 51 | 52 | // copy newReq 53 | newReq := http2.RequestClone(srcReq) 54 | 55 | // delete referer and origin 56 | if newReq.Header.Get("Referer") != "" { 57 | newReq.Header.Del("Referer") 58 | } 59 | 60 | if newReq.Header.Get("Origin") != "" { 61 | newReq.Header.Del("Origin") 62 | } 63 | 64 | // find token position and detect before delete csrf token 65 | // 1. row query 66 | 67 | if newReq.URL.RawQuery != "" { 68 | editedRawQuery := []string{} 69 | 70 | for _, kv := range strings.Split(newReq.URL.RawQuery, "&") { 71 | splits := strings.Split(kv, "=") 72 | key := splits[0] 73 | //value := splits[1] 74 | 75 | match, _ := regexp.Match(d.csrfTokenPattern, []byte(key)) 76 | if match { 77 | continue 78 | } 79 | 80 | // add to editedRawQuery 81 | editedRawQuery = append(editedRawQuery, kv) 82 | } 83 | newReq.URL.RawQuery = strings.Join(editedRawQuery, "&") 84 | } 85 | 86 | // 2. post body(application/x-www-form-urlencoded,multipart/form-data ) 87 | for k, _ := range newReq.PostForm { 88 | match, _ := regexp.Match(d.csrfTokenPattern, []byte(k)) 89 | if match { 90 | newReq.PostForm.Del(k) 91 | } 92 | } 93 | 94 | // make newReq 95 | newResp := http2.DoRequest(newReq) 96 | if newResp == nil { 97 | return 98 | } 99 | 100 | // judge and save result 101 | if d.judge(srcResp, newResp) { 102 | return data.BuildResult(item, "CSRF", newReq, newResp) 103 | } 104 | 105 | return nil 106 | } 107 | 108 | // getSameSitePolicy 109 | // 110 | // @Description: get same-site policy from response received from the request deleted cookie 111 | // @receiver d 112 | // @param 113 | // @param item 114 | func (d *CSRFDetector) getSameSitePolicy(item *data.DataItem) { 115 | // copy request 116 | request := http2.RequestClone(item.SourceRequest) 117 | // delete cookie and get set-cookie header from response 118 | request.Header.Del("Cookie") 119 | response := http2.DoRequest(request) 120 | setCookie := response.Header.Get("Set-Cookie") 121 | 122 | var policy string 123 | // parse policy from Set-Cookie header 124 | if strings.Contains(setCookie, "SameSite=Lax") { 125 | policy = "Lax" 126 | } else if strings.Contains(setCookie, "SameSite=Strict") { 127 | policy = "Strict" 128 | } else { // if there is not same-site policy or the policy is None 129 | policy = "None" 130 | } 131 | 132 | // save policy to samesitePolicy 133 | key := request.Host 134 | d.samesitePolicy[key] = policy 135 | 136 | //logger.Infoln(fmt.Sprintf("Host: %s, Same-Site policy: %s", key, policy)) 137 | } 138 | 139 | // judge 140 | // 141 | // @Description: 142 | // @receiver d 143 | // @return bool true -- exists vulnerable 144 | func (d *CSRFDetector) judge(srcResponse, response *http.Response) bool { 145 | bytes2, _ := ioutil.ReadAll(response.Body) 146 | 147 | // black keyword match 148 | for _, s := range d.csrfInvalidPattern { 149 | if strings.Contains(string(bytes2), s) { 150 | return false 151 | } 152 | } 153 | 154 | // body similarity compare 155 | bytes, _ := ioutil.ReadAll(srcResponse.Body) 156 | 157 | sim := strsim.Compare(string(bytes), string(bytes2)) 158 | if sim > 0.9 { 159 | return true 160 | } 161 | 162 | return false 163 | } 164 | 165 | func NewCSRFDetector() module.Detecter { 166 | if viper.GetInt("app.module.CSRFDetector.option") == 0 { 167 | return nil 168 | } 169 | 170 | logger.Infoln("[Load Module] csrf detector module") 171 | 172 | // instantiate CSRFDetector 173 | detector := &CSRFDetector{ 174 | csrfTokenPattern: viper.GetString("app.module.CSRFDetector.csrfTokenPattern"), 175 | csrfInvalidPattern: viper.GetStringSlice("app.module.CSRFDetector.csrfInvalidPattern"), 176 | samesitePolicy: make(map[string]string, 100), 177 | } 178 | 179 | return detector 180 | } 181 | -------------------------------------------------------------------------------- /internal/core/module/DoS/DoSDetector.go: -------------------------------------------------------------------------------- 1 | package DoS 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | "APIKiller/internal/core/module" 6 | "APIKiller/pkg/logger" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | type DosDetector struct { 11 | typeFlag string 12 | d1 *rateLimitDetector 13 | d2 *resourceSizeDetector 14 | } 15 | 16 | func (d DosDetector) Detect(item *data.DataItem) (result *data.DataItem) { 17 | logger.Debugln("[Detect] DoS detect") 18 | 19 | // rate limit 20 | //d.d1.Detect( item) 21 | 22 | // the size of resource lack of control 23 | return d.d2.Detect(item) 24 | } 25 | 26 | func NewDoSDetector() module.Detecter { 27 | if viper.GetInt("app.module.DoSDetector.option") == 0 { 28 | return nil 29 | } 30 | 31 | logger.Infoln("[Load Module] DoS detect module") 32 | 33 | return &DosDetector{ 34 | typeFlag: viper.GetString("app.module.DoSDetector.typeFlag"), 35 | d1: newRateLimitDetector(), 36 | d2: newResourceSizeDetector(), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/core/module/DoS/rateLimitDetector.go: -------------------------------------------------------------------------------- 1 | package DoS 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | ) 6 | 7 | type rateLimitDetector struct { 8 | } 9 | 10 | func (r *rateLimitDetector) Detect(item *data.DataItem) { 11 | // 12 | } 13 | 14 | func newRateLimitDetector() *rateLimitDetector { 15 | return &rateLimitDetector{} 16 | } 17 | -------------------------------------------------------------------------------- /internal/core/module/DoS/resourceSizeDetector.go: -------------------------------------------------------------------------------- 1 | package DoS 2 | 3 | import ( 4 | ahttp2 "APIKiller/internal/core/ahttp" 5 | "APIKiller/internal/core/data" 6 | "github.com/spf13/viper" 7 | "net/http" 8 | "strconv" 9 | ) 10 | 11 | type resourceSizeDetector struct { 12 | sizeParams []string 13 | } 14 | 15 | func (d *resourceSizeDetector) Detect(item *data.DataItem) (result *data.DataItem) { 16 | srcReq := item.SourceRequest 17 | srcResp := item.SourceResponse 18 | 19 | for _, param := range d.sizeParams { 20 | 21 | // replace value of params in new newReq 22 | newReq := ahttp2.ModifyParam(srcReq, param, "10000") 23 | if newReq == nil { 24 | continue 25 | } 26 | 27 | // do newReq 28 | newResp := ahttp2.DoRequest(newReq) 29 | 30 | if newResp == nil { 31 | return 32 | } 33 | 34 | // judge 35 | if d.judge(srcResp, newResp) { 36 | return data.BuildResult(item, "DoS-ResourceSizeNotStrict", newReq, newResp) 37 | } 38 | 39 | return nil 40 | } 41 | return nil 42 | 43 | } 44 | 45 | func (d *resourceSizeDetector) judge(srcResp, newResp *http.Response) bool { 46 | srcCL, _ := strconv.Atoi(srcResp.Header.Get("Content-Length")) 47 | newCL, _ := strconv.Atoi(newResp.Header.Get("Content-Length")) 48 | if newCL/10 > srcCL { // successfully 49 | return true 50 | } 51 | return false 52 | } 53 | 54 | func newResourceSizeDetector() *resourceSizeDetector { 55 | return &resourceSizeDetector{ 56 | sizeParams: viper.GetStringSlice("app.module.DoSDetector.sizeParam"), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /internal/core/module/OpenRedirect/OpenRedirectDetector.go: -------------------------------------------------------------------------------- 1 | package OpenRedirect 2 | 3 | import ( 4 | ahttp2 "APIKiller/internal/core/ahttp" 5 | "APIKiller/internal/core/data" 6 | "APIKiller/internal/core/module" 7 | "APIKiller/pkg/logger" 8 | "github.com/spf13/viper" 9 | "io/ioutil" 10 | "net/http" 11 | "strings" 12 | ) 13 | 14 | type OpenRedirectDetector struct { 15 | rawQueryParams []string 16 | failFlag []string 17 | evilLink string 18 | } 19 | 20 | func (d *OpenRedirectDetector) Detect(item *data.DataItem) (result *data.DataItem) { 21 | logger.Debugln("[Detect] Open-Redirect detect") 22 | 23 | srcResp := item.SourceResponse 24 | srcReq := item.SourceRequest 25 | 26 | // filter by features of redirect 27 | if srcResp.Header.Get("Location") == "" { 28 | return 29 | } 30 | 31 | newReq := ahttp2.ModifyQueryParamByRegExp(srcReq, `https?://[^\s&]+`, d.evilLink) 32 | if newReq == nil { 33 | logger.Debugln("parameter not found") 34 | return 35 | } 36 | 37 | // do newReq 38 | newResp := ahttp2.DoRequest(newReq) 39 | 40 | // judge 41 | if d.judge(srcResp, newResp) { 42 | return data.BuildResult(item, "Open-Redirect", newReq, newResp) 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func (d *OpenRedirectDetector) judge(srcResp, newResp *http.Response) bool { 49 | if newResp.StatusCode == srcResp.StatusCode && strings.Index(newResp.Header.Get("Location"), d.evilLink) != -1 { 50 | return true 51 | } 52 | 53 | // black list 54 | if newResp.Body != nil { 55 | bytes, _ := ioutil.ReadAll(newResp.Body) 56 | for _, flag := range d.failFlag { 57 | if strings.Contains(string(bytes), flag) { 58 | return false 59 | } 60 | } 61 | } 62 | 63 | return false 64 | } 65 | 66 | func NewOpenRedirectDetector() module.Detecter { 67 | if viper.GetInt("app.module.openRedirectDetector.option") == 0 { 68 | return nil 69 | } 70 | 71 | logger.Infoln("[Load Module] Open-Redirect detect module") 72 | 73 | d := &OpenRedirectDetector{ 74 | rawQueryParams: viper.GetStringSlice("app.module.openRedirectDetector.rawQueryParams"), 75 | evilLink: "https://www.baidu.com", 76 | failFlag: viper.GetStringSlice("app.module.openRedirectDetector.failFlag"), 77 | } 78 | 79 | return d 80 | } 81 | -------------------------------------------------------------------------------- /internal/core/module/SSRF/SSRFDetector.go: -------------------------------------------------------------------------------- 1 | package SSRF 2 | 3 | import ( 4 | ahttp2 "APIKiller/internal/core/ahttp" 5 | "APIKiller/internal/core/data" 6 | "APIKiller/internal/core/module" 7 | "APIKiller/pkg/logger" 8 | util2 "APIKiller/pkg/util" 9 | "fmt" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | type SSRFDetector struct { 14 | ReverseConnectionPlatform string // end with “/” 15 | } 16 | 17 | func NewSSRFDetector() module.Detecter { 18 | if viper.GetInt("app.module.SSRFDetector.option") == 0 { 19 | return nil 20 | } 21 | 22 | logger.Infoln("[Load Module] SSRF detect module") 23 | 24 | return &SSRFDetector{ 25 | ReverseConnectionPlatform: "http://zpysri.ceye.io/", 26 | } 27 | } 28 | 29 | func (d *SSRFDetector) Detect(item *data.DataItem) (result *data.DataItem) { 30 | logger.Debugln("[Detect] SSRF detect") 31 | 32 | //srcResp := item.SourceResponse 33 | srcReq := item.SourceRequest 34 | 35 | token := util2.GenerateRandomId() 36 | 37 | newReq := ahttp2.ModifyQueryParamByRegExp(srcReq, `https?://[^\s&]+`, d.ReverseConnectionPlatform+fmt.Sprintf("%s%s%s", "SSRF", module.AsyncDetectVulnTypeSeperator, token)) 38 | if newReq == nil { 39 | logger.Debugln("parameter not found") 40 | return 41 | } 42 | 43 | // do newReq 44 | newResp := ahttp2.DoRequest(newReq) 45 | 46 | // asynchronous result 47 | return data.BuildResult(item, "SSRF"+module.AsyncDetectVulnTypeSeperator+token, newReq, newResp) 48 | } 49 | -------------------------------------------------------------------------------- /internal/core/module/authorize/authGroup.go: -------------------------------------------------------------------------------- 1 | package authorize 2 | 3 | type authGroup struct { 4 | Domain []string `mapstructure:"domain"` 5 | ReplaceGroups []replaceGroup `mapstructure:"replaceGroup"` 6 | } 7 | 8 | type replaceGroup struct { 9 | Position int `mapstructure:"position"` 10 | Key string `mapstructure:"key"` 11 | Value string `mapstructure:"value"` 12 | } 13 | 14 | // position codes 15 | const ( 16 | Replace_Position_Code_Header = 0 17 | Replace_Position_Code_Query = 1 18 | Replace_Position_Code_Body = 2 19 | ) 20 | -------------------------------------------------------------------------------- /internal/core/module/authorize/authoriedDetector.go: -------------------------------------------------------------------------------- 1 | package authorize 2 | 3 | import ( 4 | "APIKiller/internal/core/ahttp" 5 | "APIKiller/internal/core/data" 6 | "APIKiller/internal/core/module" 7 | "APIKiller/pkg/logger" 8 | "fmt" 9 | "github.com/antlabs/strsim" 10 | "github.com/spf13/viper" 11 | "golang.org/x/exp/slices" 12 | "io/ioutil" 13 | "net/http" 14 | "regexp" 15 | "strconv" 16 | "strings" 17 | "sync" 18 | ) 19 | 20 | var ( 21 | BYPASSED = true 22 | FAILED = false 23 | ) 24 | 25 | type AuthorizedDetector struct { 26 | authGroups []authGroup 27 | blackStatusCodes []int 28 | blackKeywords []string 29 | records []string 30 | midPaddings []string 31 | endPaddings []string 32 | ipHeaders []string 33 | ip string 34 | apiVersionFormat string 35 | apiVersionPrefix string 36 | 37 | mu sync.Mutex 38 | } 39 | 40 | func (d *AuthorizedDetector) Detect(item *data.DataItem) (result *data.DataItem) { 41 | logger.Debugln("[Detect] authorized detect") 42 | 43 | // match auth group 44 | var group = authGroup{} 45 | for _, group = range d.authGroups { 46 | if slices.Contains(group.Domain, item.SourceRequest.Host) { 47 | break 48 | } 49 | } 50 | 51 | resultDataItem := d.unauthorizedDetect(item, &group) 52 | if resultDataItem != nil { 53 | return resultDataItem 54 | } 55 | 56 | resultDataItem = d.multiRolesDetect(item, &group) 57 | if resultDataItem != nil { 58 | return resultDataItem 59 | } 60 | 61 | return nil 62 | } 63 | 64 | // 65 | // deleteAuthIdentifier 66 | // @Description: delete auth identifier according to the first replace group in auth group 67 | // @receiver d 68 | // @param group 69 | // 70 | func (d *AuthorizedDetector) deleteAuthIdentifier(group *authGroup, req *http.Request) { 71 | rg := group.ReplaceGroups[0] 72 | 73 | switch rg.Position { 74 | case Replace_Position_Code_Header: 75 | ahttp.RemoveHeader(req, rg.Key) 76 | break 77 | case Replace_Position_Code_Query: 78 | // ... 79 | break 80 | case Replace_Position_Code_Body: 81 | // ... 82 | break 83 | } 84 | } 85 | 86 | // 87 | // replaceAuthIdentifierandNecessaryParms 88 | // @Description: replace auth identifier and other necessary parameters(e.g. csrf token) with replace group items 89 | // @receiver d 90 | // @param group 91 | // @param req 92 | // 93 | func (d *AuthorizedDetector) replaceAuthIdentifierandNecessaryParms(group *authGroup, req *http.Request) { 94 | for _, rg := range group.ReplaceGroups { 95 | switch rg.Position { 96 | case Replace_Position_Code_Header: 97 | ahttp.UpdateHeader(req, rg.Key, rg.Value) 98 | break 99 | case Replace_Position_Code_Query: 100 | ahttp.ModifyQueryParam(req, rg.Key, rg.Value) 101 | break 102 | case Replace_Position_Code_Body: 103 | ahttp.ModifyPostParam(req, rg.Key, rg.Value) 104 | break 105 | default: 106 | logger.Errorln("cannot recognize the position code") 107 | } 108 | } 109 | } 110 | 111 | // unauthorizedDetect 112 | // 113 | // @Description: unauthorized header detect 114 | // @receiver d 115 | // @param 116 | // @param item 117 | func (d *AuthorizedDetector) unauthorizedDetect(item *data.DataItem, group *authGroup) (result *data.DataItem) { 118 | newReq := ahttp.RequestClone(item.SourceRequest) 119 | 120 | // delete auth identifier 121 | d.deleteAuthIdentifier(group, newReq) 122 | 123 | // make request and judge 124 | newResp := ahttp.DoRequest(newReq) 125 | 126 | if d.judge(item.SourceResponse, newResp) == BYPASSED { 127 | return data.BuildResult(item, "unauthorized", newReq, newResp) 128 | } 129 | 130 | return nil 131 | } 132 | 133 | // multiRolesDetect 134 | // 135 | // @Description: multiple roles detect 136 | // @receiver d 137 | // @param 138 | // @param item 139 | func (d *AuthorizedDetector) multiRolesDetect(item *data.DataItem, group *authGroup) (result *data.DataItem) { 140 | newReq := ahttp.RequestClone(item.SourceRequest) 141 | 142 | // replace necessary data 143 | d.replaceAuthIdentifierandNecessaryParms(group, newReq) 144 | 145 | // do request 146 | newResp := ahttp.DoRequest(newReq) 147 | 148 | // judge 149 | if d.judge(item.SourceResponse, newResp) == BYPASSED { 150 | return data.BuildResult(item, "authorize-multiRoles", newReq, newResp) 151 | } 152 | 153 | // bypass 154 | result = d.bypass(item) 155 | if result != nil { 156 | return 157 | } 158 | 159 | return nil 160 | } 161 | 162 | func (d *AuthorizedDetector) bypass(item *data.DataItem) (result *data.DataItem) { 163 | srcReq := item.SourceRequest 164 | srcResp := item.SourceResponse 165 | 166 | request := ahttp.RequestClone(srcReq) 167 | var ( 168 | vulnRequest *http.Request 169 | vulnResponse *http.Response 170 | ) 171 | 172 | // add ip headers and assign d value of 127.0.0.1 for each header 173 | for _, header := range d.ipHeaders { 174 | ahttp.AppendHeader(request, header, d.ip) 175 | } 176 | 177 | // architecture layer detect 178 | d.mu.Lock() 179 | 180 | if slices.Contains(d.records, srcReq.Host) == false { 181 | // record 182 | d.records = append(d.records, srcReq.Host) 183 | 184 | // path bypass 185 | if vulnRequest == nil { 186 | vulnRequest, vulnResponse = d.pathBypass(request, srcResp) 187 | } 188 | 189 | // protocol version 190 | } 191 | 192 | d.mu.Unlock() 193 | 194 | // api layer 195 | if vulnRequest == nil { 196 | // api version bypass 197 | vulnRequest, vulnResponse = d.apiVersionBypass(request, srcResp) 198 | } 199 | 200 | if vulnRequest != nil { 201 | return data.BuildResult(item, "authorize-bypass", vulnRequest, vulnResponse) 202 | } 203 | 204 | return nil 205 | } 206 | 207 | // 208 | // apiVersionBypass 209 | // @Description: 210 | // @receiver a 211 | // @param 212 | // @param srcRequest 213 | // @param srcResponse 214 | // @param t 215 | // @param v 216 | // @return *http.Request 217 | // @return *http.Response 218 | // 219 | func (d *AuthorizedDetector) apiVersionBypass(srcRequest *http.Request, srcResponse *http.Response) (*http.Request, *http.Response) { 220 | 221 | compiler, _ := regexp.Compile("/" + d.apiVersionFormat + "/") 222 | foundString := compiler.FindString(srcRequest.URL.Path) 223 | 224 | if foundString != "" { 225 | // get api version 226 | trimedString := strings.Trim(foundString, "/") 227 | version, _ := strconv.Atoi(strings.Trim(trimedString, d.apiVersionPrefix)) 228 | 229 | for i := 1; i < version; i++ { 230 | requestClone := ahttp.RequestClone(srcRequest) 231 | ahttp.ModifyURLPathAPIVerion(requestClone.URL, foundString, fmt.Sprintf("/%s%d/", d.apiVersionPrefix, i)) 232 | response := ahttp.DoRequest(requestClone) 233 | if d.judge(srcResponse, response) { 234 | return requestClone, response 235 | } 236 | } 237 | 238 | } 239 | 240 | return nil, nil 241 | } 242 | 243 | // 244 | // pathBypass 245 | // @Description: 246 | // @receiver a 247 | // @param 248 | // @param srcRequest 249 | // @param srcResponse 250 | // @param t 251 | // @param v 252 | // @return *http.Request 253 | // @return *http.Response 254 | // 255 | func (d *AuthorizedDetector) pathBypass(srcReq *http.Request, srcResp *http.Response) (*http.Request, *http.Response) { 256 | var requestClone *http.Request 257 | 258 | // filter url path equals "/" 259 | if srcReq.URL.Path == "/" { 260 | return nil, nil 261 | } 262 | 263 | // /admin/get --> /admin/Get 264 | requestClone = ahttp.RequestClone(srcReq) 265 | ahttp.ModifyURLPathCase(requestClone.URL) 266 | response := ahttp.DoRequest(requestClone) 267 | if d.judge(srcResp, response) { 268 | return requestClone, response 269 | } 270 | 271 | // /admin/get --> /admin/./get 272 | for _, midPadding := range d.midPaddings { 273 | requestClone = ahttp.RequestClone(srcReq) 274 | ahttp.ModifyURLPathMidPad(requestClone.URL, midPadding) 275 | response := ahttp.DoRequest(requestClone) 276 | if d.judge(srcResp, response) { 277 | return requestClone, response 278 | } 279 | } 280 | 281 | // /admin/get --> /admin/get;.css 282 | for _, endPadding := range d.endPaddings { 283 | requestClone = ahttp.RequestClone(srcReq) 284 | ahttp.ModifyURLPathEndPad(requestClone.URL, endPadding) 285 | response := ahttp.DoRequest(requestClone) 286 | if d.judge(srcResp, response) { 287 | return requestClone, response 288 | } 289 | } 290 | return nil, nil 291 | } 292 | 293 | // judge 294 | // 295 | // @Description: Judging whether there is an ultra vires 296 | // @param sourceResp 297 | // @param newResp 298 | // @return bool true-->bypass, false-->fail 299 | func (d *AuthorizedDetector) judge(srcResp, newResp *http.Response) bool { 300 | 301 | for _, code := range d.blackStatusCodes { 302 | if newResp.StatusCode == code { 303 | return FAILED 304 | } 305 | } 306 | 307 | // get body string 308 | newBody, _ := ioutil.ReadAll(newResp.Body) 309 | 310 | // keywords matching on the response body 311 | for _, split := range d.blackKeywords { 312 | if strings.Index(string(newBody), split) != -1 { 313 | return FAILED 314 | } 315 | } 316 | 317 | // textual similarity 318 | srcBody, _ := ioutil.ReadAll(srcResp.Body) 319 | sim := strsim.Compare(string(srcBody), string(newBody)) 320 | if sim > 0.9 { 321 | return BYPASSED 322 | } 323 | 324 | return FAILED 325 | } 326 | 327 | func NewAuthorizedDetector() module.Detecter { 328 | if viper.GetInt("app.module.authorizedDetector.option") == 0 { 329 | return nil 330 | } 331 | 332 | logger.Infoln("[Load Module] authorized module") 333 | 334 | detector := &AuthorizedDetector{ 335 | authGroups: []authGroup{}, 336 | blackStatusCodes: viper.GetIntSlice("app.module.authorizedDetector.judgement.blackStatusCodes"), 337 | blackKeywords: viper.GetStringSlice("app.module.authorizedDetector.judgement.blackKeywords"), 338 | records: nil, 339 | midPaddings: viper.GetStringSlice("app.module.authorizedDetector.pathFuzz.midPadding"), 340 | endPaddings: viper.GetStringSlice("app.module.authorizedDetector.pathFuzz.endPadding"), 341 | ipHeaders: viper.GetStringSlice("app.module.authorizedDetector.ipHeader"), 342 | ip: viper.GetString("app.module.authorizedDetector.ip"), 343 | apiVersionFormat: viper.GetString("app.module.authorizedDetector.apiVersion.format"), 344 | apiVersionPrefix: viper.GetString("app.module.authorizedDetector.apiVersion.prefix"), 345 | mu: sync.Mutex{}, 346 | } 347 | 348 | viper.UnmarshalKey("app.module.authorizedDetector.authGroup", &detector.authGroups) 349 | 350 | return detector 351 | } 352 | -------------------------------------------------------------------------------- /internal/core/module/authorize/authoriedDetector_test.go: -------------------------------------------------------------------------------- 1 | package authorize 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/viper" 6 | "testing" 7 | ) 8 | 9 | func Test(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | }{ 13 | // TODO: Add test cases. 14 | {}, 15 | } 16 | for _, tt := range tests { 17 | t.Run(tt.name, func(t *testing.T) { 18 | viper.SetConfigFile("D:\\Projects\\GO\\APIKiller\\config\\config.dev.yaml") 19 | viper.ReadInConfig() 20 | 21 | var authGroups = []authGroup{} 22 | viper.UnmarshalKey("app.module.authorizedDetector.authGroup", &authGroups) 23 | 24 | fmt.Println(authGroups) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/core/module/detect.go: -------------------------------------------------------------------------------- 1 | package module 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | ) 6 | 7 | const ( 8 | AsyncDetectVulnTypeSeperator = "^" 9 | ) 10 | 11 | var ( 12 | modules []Detecter 13 | ) 14 | 15 | type Detecter interface { 16 | // 17 | // Detect 18 | // @Description: detect the target api and return the result 19 | // @param item 20 | // @return result 21 | // 22 | Detect(item *data.DataItem) (result *data.DataItem) 23 | } 24 | 25 | func RegisterModule(d Detecter) { 26 | if d == nil { 27 | return 28 | } 29 | 30 | modules = append(modules, d) 31 | } 32 | 33 | func GetModules() []Detecter { 34 | if modules != nil { 35 | return modules 36 | } 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /internal/core/notify/dingding.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | "bytes" 6 | "fmt" 7 | "github.com/spf13/viper" 8 | "net/http" 9 | ) 10 | 11 | type Dingding struct { 12 | webhookUrl string 13 | } 14 | 15 | func (d *Dingding) Notify(item *data.DataItem) { 16 | //logger.Infoln("notify dingding robot") 17 | 18 | var jsonData []byte 19 | 20 | // Message format setting 21 | MessageFormat := fmt.Sprintf("%s-%s exists %s", item.Domain, item.Url, item.VulnType) 22 | 23 | jsonData = []byte(fmt.Sprintf(`{ 24 | "at": { 25 | "isAtAll": true 26 | }, 27 | "text": { 28 | "content":"%s" 29 | }, 30 | "msgtype":"text" 31 | }`, MessageFormat)) 32 | 33 | request, _ := http.NewRequest("POST", d.webhookUrl, bytes.NewBuffer(jsonData)) 34 | request.Header.Set("Content-Type", "application/json; charset=UTF-8") 35 | 36 | client := http.Client{} 37 | response, _ := client.Do(request) 38 | 39 | defer response.Body.Close() 40 | } 41 | 42 | func NewDingdingNotifer() *Dingding { 43 | // get config 44 | webhookUrl := viper.GetString("app.notifier.Dingding.webhookUrl") 45 | // create 46 | notifer := &Dingding{webhookUrl: webhookUrl} 47 | 48 | return notifer 49 | } 50 | -------------------------------------------------------------------------------- /internal/core/notify/lark.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | "APIKiller/pkg/logger" 6 | "bytes" 7 | "crypto/hmac" 8 | "crypto/sha256" 9 | "encoding/base64" 10 | "fmt" 11 | "github.com/spf13/viper" 12 | "net/http" 13 | "time" 14 | ) 15 | 16 | type Lark struct { 17 | webhookUrl string 18 | secret string 19 | signature string 20 | timestamp int64 21 | } 22 | 23 | func (l *Lark) genSign() { 24 | //get timestamp 25 | l.timestamp = time.Now().Unix() 26 | 27 | //timestamp + key 做sha256, 再进行base64 encode 28 | stringToSign := fmt.Sprintf("%v", l.timestamp) + "\n" + l.secret 29 | 30 | var data []byte 31 | h := hmac.New(sha256.New, []byte(stringToSign)) 32 | _, err := h.Write(data) 33 | if err != nil { 34 | logger.Errorln("lark generate signature error") 35 | panic("Lark generate signature error") 36 | } 37 | 38 | l.signature = base64.StdEncoding.EncodeToString(h.Sum(nil)) 39 | } 40 | 41 | func (l *Lark) init() { 42 | //generate signature 43 | if l.secret != "" { 44 | l.genSign() 45 | } 46 | 47 | } 48 | 49 | // NewLarkNotifier 50 | // 51 | // @Description: create a lark object 52 | // @param webhook lark webhook url 53 | // @param signature lark webhook authorize parameter(optional) 54 | // @return *Lark 55 | func NewLarkNotifier() *Lark { 56 | // get config 57 | webhookUrl := viper.GetString("app.notifier.Lark.webhookUrl") 58 | secret := viper.GetString("app.notifier.Lark.secret") 59 | 60 | // create 61 | lark := &Lark{ 62 | webhookUrl: webhookUrl, 63 | signature: secret, 64 | } 65 | 66 | // init object 67 | lark.init() 68 | 69 | return lark 70 | } 71 | 72 | func (l *Lark) Notify(item *data.DataItem) { 73 | //logger.Infoln("notify lark robot") 74 | 75 | var jsonData []byte 76 | 77 | // Message format setting 78 | MessageFormat := fmt.Sprintf("Domain:%s-Url:%s --> %s", item.Domain, item.Url, item.VulnType) 79 | 80 | if l.secret != "" { 81 | jsonData = []byte(fmt.Sprintf(` 82 | { 83 | "timestamp": "%v", 84 | "sign": "%v", 85 | "msg_type": "text", 86 | "content": { 87 | "text": "%v" 88 | } 89 | }`, l.timestamp, l.signature, MessageFormat)) 90 | } else { 91 | jsonData = []byte(fmt.Sprintf(`{"msg_type":"text","content":{"text":"%v"}}`, MessageFormat)) 92 | } 93 | 94 | request, _ := http.NewRequest("POST", l.webhookUrl, bytes.NewBuffer(jsonData)) 95 | request.Header.Set("Content-Type", "application/json; charset=UTF-8") 96 | 97 | client := http.Client{} 98 | response, _ := client.Do(request) 99 | 100 | defer response.Body.Close() 101 | } 102 | -------------------------------------------------------------------------------- /internal/core/notify/notify.go: -------------------------------------------------------------------------------- 1 | package notify 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | ) 6 | 7 | type Notify interface { 8 | // 9 | // Notify 10 | // @Description: 11 | // @param item 12 | // 13 | Notify(item *data.DataItem) 14 | } 15 | 16 | var ( 17 | notificationQueue chan *data.DataItem 18 | notifier Notify 19 | ) 20 | 21 | // 22 | // CreateNotification 23 | // @Description: create new notification and throw it into queue 24 | // @param notification 25 | // 26 | func CreateNotification(notification *data.DataItem) { 27 | if notificationQueue != nil { 28 | notificationQueue <- notification 29 | } 30 | } 31 | 32 | func BindNotifier(n Notify) { 33 | notifier = n 34 | 35 | // init notificationQueue 36 | notificationQueue = make(chan *data.DataItem, 1024) 37 | 38 | // notification queue handle 39 | go func() { 40 | var item *data.DataItem 41 | for { 42 | item = <-notificationQueue 43 | notifier.Notify(item) 44 | } 45 | }() 46 | } 47 | -------------------------------------------------------------------------------- /internal/core/origin/fileInputOrigin/burpFile.go: -------------------------------------------------------------------------------- 1 | package fileInputOrigin 2 | 3 | import ( 4 | "APIKiller/internal/core/origin" 5 | "APIKiller/pkg/logger" 6 | "encoding/base64" 7 | "github.com/beevik/etree" 8 | ) 9 | 10 | // parseData 11 | // 12 | // @Description: parse data from burpsuite file 13 | // @receiver o 14 | func (o *FileInputOrigin) parseDataFromBurpFile() { 15 | 16 | doc := etree.NewDocument() 17 | 18 | if err := doc.ReadFromFile(o.path); err != nil { 19 | panic(err) 20 | } 21 | 22 | root := doc.SelectElement("items") 23 | for _, item := range root.SelectElements("item") { 24 | url := item.SelectElement("url") 25 | //fmt.Println(url.Text()) 26 | rawUrl := url.Text() 27 | 28 | request := item.SelectElement("request") 29 | rawRequestBytes, err2 := base64.StdEncoding.DecodeString(request.Text()) 30 | if err2 != nil { 31 | logger.Errorln("base64 decode error", err2) 32 | panic(err2) 33 | } 34 | 35 | response := item.SelectElement("response") 36 | rawResponseBytes, err3 := base64.StdEncoding.DecodeString(response.Text()) 37 | if err2 != nil { 38 | logger.Errorln("base64 decode error", err3) 39 | panic(err3) 40 | } 41 | 42 | req, resp := RecoverHttpRequest(string(rawRequestBytes), rawUrl, string(rawResponseBytes)) 43 | 44 | //transport via channel 45 | origin.TransportOriginRequest(&origin.TransferItem{ 46 | Req: req, 47 | Resp: resp, 48 | }) 49 | } 50 | 51 | return 52 | } 53 | -------------------------------------------------------------------------------- /internal/core/origin/fileInputOrigin/burpFile_test.go: -------------------------------------------------------------------------------- 1 | package fileInputOrigin 2 | 3 | import "testing" 4 | 5 | func TestFileInputOrigin_parseData(t *testing.T) { 6 | type fields struct { 7 | path string 8 | } 9 | tests := []struct { 10 | name string 11 | fields fields 12 | }{ 13 | // TODO: Add test cases. 14 | { 15 | name: "", 16 | fields: fields{ 17 | path: "C:\\Users\\Lenovo\\Desktop\\src.txt", 18 | }, 19 | }, 20 | } 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | //o := &FileInputOrigin{ 24 | // path: tt.fields.path, 25 | //} 26 | //o.parseData() 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/core/origin/fileInputOrigin/fileInputOrigin.go: -------------------------------------------------------------------------------- 1 | package fileInputOrigin 2 | 3 | import ( 4 | "APIKiller/pkg/logger" 5 | "bufio" 6 | "fmt" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | type FileInputOrigin struct { 14 | path string 15 | } 16 | 17 | func (o *FileInputOrigin) LoadOriginRequest() { 18 | logger.Infoln("[Load Request] load request from file input origin") 19 | 20 | if stat, _ := os.Stat(o.path); stat.IsDir() { 21 | // load origin from target directory 22 | 23 | } else { 24 | // load origin from target file[eg. burp file] 25 | o.parseDataFromBurpFile() 26 | } 27 | 28 | } 29 | 30 | // RecoverHttpRequest 31 | // 32 | // @Description: create one new http.Request with rawRequest and rawURL 33 | // @param rawRequest 34 | // @param rawURL 35 | // @return *http.Request 36 | func RecoverHttpRequest(rawRequest, rawURL, rawResponse string) (*http.Request, *http.Response) { 37 | b := bufio.NewReader(strings.NewReader(rawRequest)) 38 | 39 | req, err := http.ReadRequest(b) 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | // We can't have this set. And it only contains "/pkg/net/http/" anyway 45 | req.RequestURI = "" 46 | 47 | // Since the req.URL will not have all the information set, 48 | // such as protocol scheme and host, we create a new URL 49 | u, err := url.Parse(rawURL) 50 | if err != nil { 51 | panic(err) 52 | } 53 | req.URL = u 54 | 55 | b2 := bufio.NewReader(strings.NewReader(rawResponse)) 56 | 57 | resp, _ := http.ReadResponse(b2, req) 58 | 59 | return req, resp 60 | } 61 | 62 | func NewFileInputOrigin(path string) *FileInputOrigin { 63 | logger.Infoln("[Origin] file input origin") 64 | 65 | // determine whether the path is a file or a directory 66 | _, err := os.Stat(path) 67 | if os.IsNotExist(err) { 68 | logger.Errorln(fmt.Sprintf("%s does not exist", path)) 69 | panic(fmt.Sprintf("%s does not exist", path)) 70 | } 71 | 72 | // instantiate FileInputOrigin 73 | return &FileInputOrigin{path: path} 74 | } 75 | -------------------------------------------------------------------------------- /internal/core/origin/origin.go: -------------------------------------------------------------------------------- 1 | package origin 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type TransferItem struct { 8 | Req *http.Request 9 | Resp *http.Response 10 | } 11 | 12 | var transferItemQueue = make(chan *TransferItem) 13 | 14 | type Origin interface { 15 | // 16 | // LoadOriginRequest 17 | // @Description: load request and transport via channel transferItemQueue 18 | // 19 | LoadOriginRequest() 20 | } 21 | 22 | // 23 | // TransportOriginRequest 24 | // @Description: transport requests from origin via transferItemQueue 25 | // @param item 26 | // 27 | func TransportOriginRequest(item *TransferItem) { 28 | transferItemQueue <- item 29 | } 30 | 31 | // 32 | // GetOriginRequest 33 | // @Description: get requests from transferItemQueue 34 | // 35 | func GetOriginRequest() *TransferItem { 36 | return <-transferItemQueue 37 | } 38 | -------------------------------------------------------------------------------- /internal/core/origin/realTimeOrigin/realTimeOrigin.go: -------------------------------------------------------------------------------- 1 | package realTimeOrigin 2 | 3 | import ( 4 | "APIKiller/internal/core/ahttp" 5 | "APIKiller/internal/core/aio" 6 | "APIKiller/internal/core/origin" 7 | "APIKiller/pkg/logger" 8 | "bytes" 9 | "crypto/tls" 10 | "crypto/x509" 11 | "fmt" 12 | "github.com/elazarl/goproxy" 13 | "github.com/spf13/viper" 14 | "io/ioutil" 15 | "net" 16 | "net/http" 17 | ) 18 | 19 | type RealTimeOrigin struct { 20 | address string 21 | port string 22 | } 23 | 24 | func (o *RealTimeOrigin) LoadOriginRequest() { 25 | logger.Infoln("[Load Request] load request from real time origin") 26 | if o.address == "" || o.port == "" { 27 | panic("Config error: have not set address or port properly") 28 | } 29 | 30 | // start to listen 31 | go func() { 32 | logger.Infoln(fmt.Sprintf("starting proxy: listen at %s:%s", o.address, o.port)) 33 | l, err := net.Listen("tcp", o.address+":"+o.port) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | proxy := proxyN() 39 | http.Serve(l, proxy) 40 | }() 41 | 42 | } 43 | 44 | func NewRealTimeOrigin() *RealTimeOrigin { 45 | logger.Infoln("[Origin] real-time origin") 46 | 47 | // get config 48 | address := viper.GetString("app.origin.realTime.address") 49 | port := viper.GetString("app.origin.realTime.port") 50 | 51 | return &RealTimeOrigin{ 52 | address: address, 53 | port: port, 54 | } 55 | } 56 | 57 | // proxyN 58 | // 59 | // @Description: Get httpItem objects through goproxy project 60 | // @param httpItemQueue 61 | // @return *goproxy.ProxyHttpServer 62 | func proxyN() *goproxy.ProxyHttpServer { 63 | proxy := goproxy.NewProxyHttpServer() 64 | 65 | setCA(caCert, caKey) //defined in this file 66 | 67 | proxy.OnRequest().DoFunc( 68 | func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { 69 | // transform body 70 | body, err := ioutil.ReadAll(req.Body) 71 | if err != nil { 72 | logger.Errorln("read req body failed, ", err.Error()) 73 | return nil, nil 74 | } 75 | 76 | bodyStr := string(body) 77 | 78 | req.Body = aio.TransformReadCloser(bytes.NewBuffer([]byte(bodyStr))) 79 | 80 | return req, nil 81 | }, 82 | ) 83 | 84 | proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { 85 | // filter 86 | 87 | // switch aio.ReadCloser to util.repeatReadCloser 88 | if ctx.Resp.Body != nil { 89 | ctx.Resp.Body = aio.TransformReadCloser(ctx.Resp.Body) 90 | } 91 | 92 | // copy request and response 93 | request := ahttp.RequestClone(ctx.Req) 94 | response := ahttp.ResponseClone(ctx.Resp, request) 95 | 96 | // transport ctx.Req via channel 97 | origin.TransportOriginRequest(&origin.TransferItem{ 98 | Req: request, 99 | Resp: response, 100 | }) 101 | 102 | return resp 103 | }) 104 | 105 | // handle https connection 106 | proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) 107 | 108 | return proxy 109 | } 110 | 111 | func setCA(caCert, caKey []byte) error { 112 | goproxyCa, err := tls.X509KeyPair(caCert, caKey) 113 | if err != nil { 114 | return err 115 | } 116 | if goproxyCa.Leaf, err = x509.ParseCertificate(goproxyCa.Certificate[0]); err != nil { 117 | return err 118 | } 119 | goproxy.GoproxyCa = goproxyCa 120 | goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 121 | goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 122 | goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 123 | goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 124 | return nil 125 | } 126 | 127 | var caCert = []byte(`-----BEGIN CERTIFICATE----- 128 | MIIF9DCCA9ygAwIBAgIJAODqYUwoVjJkMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYD 129 | VQQGEwJJTDEPMA0GA1UECAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoM 130 | B0dvUHJveHkxEDAOBgNVBAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0 131 | aHViLmlvMSAwHgYJKoZIhvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTAeFw0xNzA0 132 | MDUyMDAwMTBaFw0zNzAzMzEyMDAwMTBaMIGOMQswCQYDVQQGEwJJTDEPMA0GA1UE 133 | CAwGQ2VudGVyMQwwCgYDVQQHDANMb2QxEDAOBgNVBAoMB0dvUHJveHkxEDAOBgNV 134 | BAsMB0dvUHJveHkxGjAYBgNVBAMMEWdvcHJveHkuZ2l0aHViLmlvMSAwHgYJKoZI 135 | hvcNAQkBFhFlbGF6YXJsQGdtYWlsLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP 136 | ADCCAgoCggIBAJ4Qy+H6hhoY1s0QRcvIhxrjSHaO/RbaFj3rwqcnpOgFq07gRdI9 137 | 3c0TFKQJHpgv6feLRhEvX/YllFYu4J35lM9ZcYY4qlKFuStcX8Jm8fqpgtmAMBzP 138 | sqtqDi8M9RQGKENzU9IFOnCV7SAeh45scMuI3wz8wrjBcH7zquHkvqUSYZz035t9 139 | V6WTrHyTEvT4w+lFOVN2bA/6DAIxrjBiF6DhoJqnha0SZtDfv77XpwGG3EhA/qoh 140 | hiYrDruYK7zJdESQL44LwzMPupVigqalfv+YHfQjbhT951IVurW2NJgRyBE62dLr 141 | lHYdtT9tCTCrd+KJNMJ+jp9hAjdIu1Br/kifU4F4+4ZLMR9Ueji0GkkPKsYdyMnq 142 | j0p0PogyvP1l4qmboPImMYtaoFuYmMYlebgC9LN10bL91K4+jLt0I1YntEzrqgJo 143 | WsJztYDw543NzSy5W+/cq4XRYgtq1b0RWwuUiswezmMoeyHZ8BQJe2xMjAOllASD 144 | fqa8OK3WABHJpy4zUrnUBiMuPITzD/FuDx4C5IwwlC68gHAZblNqpBZCX0nFCtKj 145 | YOcI2So5HbQ2OC8QF+zGVuduHUSok4hSy2BBfZ1pfvziqBeetWJwFvapGB44nIHh 146 | WKNKvqOxLNIy7e+TGRiWOomrAWM18VSR9LZbBxpJK7PLSzWqYJYTRCZHAgMBAAGj 147 | UzBRMB0GA1UdDgQWBBR4uDD9Y6x7iUoHO+32ioOcw1ICZTAfBgNVHSMEGDAWgBR4 148 | uDD9Y6x7iUoHO+32ioOcw1ICZTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB 149 | CwUAA4ICAQAaCEupzGGqcdh+L7BzhX7zyd7yzAKUoLxFrxaZY34Xyj3lcx1XoK6F 150 | AqsH2JM25GixgadzhNt92JP7vzoWeHZtLfstrPS638Y1zZi6toy4E49viYjFk5J0 151 | C6ZcFC04VYWWx6z0HwJuAS08tZ37JuFXpJGfXJOjZCQyxse0Lg0tuKLMeXDCk2Y3 152 | Ba0noeuNyHRoWXXPyiUoeApkVCU5gIsyiJSWOjhJ5hpJG06rQNfNYexgKrrraEin 153 | o0jmEMtJMx5TtD83hSnLCnFGBBq5lkE7jgXME1KsbIE3lJZzRX1mQwUK8CJDYxye 154 | i6M/dzSvy0SsPvz8fTAlprXRtWWtJQmxgWENp3Dv+0Pmux/l+ilk7KA4sMXGhsfr 155 | bvTOeWl1/uoFTPYiWR/ww7QEPLq23yDFY04Q7Un0qjIk8ExvaY8lCkXMgc8i7sGY 156 | VfvOYb0zm67EfAQl3TW8Ky5fl5CcxpVCD360Bzi6hwjYixa3qEeBggOixFQBFWft 157 | 8wrkKTHpOQXjn4sDPtet8imm9UYEtzWrFX6T9MFYkBR0/yye0FIh9+YPiTA6WB86 158 | NCNwK5Yl6HuvF97CIH5CdgO+5C7KifUtqTOL8pQKbNwy0S3sNYvB+njGvRpR7pKV 159 | BUnFpB/Atptqr4CUlTXrc5IPLAqAfmwk5IKcwy3EXUbruf9Dwz69YA== 160 | -----END CERTIFICATE-----`) 161 | 162 | var caKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 163 | MIIJKAIBAAKCAgEAnhDL4fqGGhjWzRBFy8iHGuNIdo79FtoWPevCpyek6AWrTuBF 164 | 0j3dzRMUpAkemC/p94tGES9f9iWUVi7gnfmUz1lxhjiqUoW5K1xfwmbx+qmC2YAw 165 | HM+yq2oOLwz1FAYoQ3NT0gU6cJXtIB6Hjmxwy4jfDPzCuMFwfvOq4eS+pRJhnPTf 166 | m31XpZOsfJMS9PjD6UU5U3ZsD/oMAjGuMGIXoOGgmqeFrRJm0N+/vtenAYbcSED+ 167 | qiGGJisOu5grvMl0RJAvjgvDMw+6lWKCpqV+/5gd9CNuFP3nUhW6tbY0mBHIETrZ 168 | 0uuUdh21P20JMKt34ok0wn6On2ECN0i7UGv+SJ9TgXj7hksxH1R6OLQaSQ8qxh3I 169 | yeqPSnQ+iDK8/WXiqZug8iYxi1qgW5iYxiV5uAL0s3XRsv3Urj6Mu3QjVie0TOuq 170 | AmhawnO1gPDnjc3NLLlb79yrhdFiC2rVvRFbC5SKzB7OYyh7IdnwFAl7bEyMA6WU 171 | BIN+prw4rdYAEcmnLjNSudQGIy48hPMP8W4PHgLkjDCULryAcBluU2qkFkJfScUK 172 | 0qNg5wjZKjkdtDY4LxAX7MZW524dRKiTiFLLYEF9nWl+/OKoF561YnAW9qkYHjic 173 | geFYo0q+o7Es0jLt75MZGJY6iasBYzXxVJH0tlsHGkkrs8tLNapglhNEJkcCAwEA 174 | AQKCAgAwSuNvxHHqUUJ3XoxkiXy1u1EtX9x1eeYnvvs2xMb+WJURQTYz2NEGUdkR 175 | kPO2/ZSXHAcpQvcnpi2e8y2PNmy/uQ0VPATVt6NuWweqxncR5W5j82U/uDlXY8y3 176 | lVbfak4s5XRri0tikHvlP06dNgZ0OPok5qi7d+Zd8yZ3Y8LXfjkykiIrSG1Z2jdt 177 | zCWTkNmSUKMGG/1CGFxI41Lb12xuq+C8v4f469Fb6bCUpyCQN9rffHQSGLH6wVb7 178 | +68JO+d49zCATpmx5RFViMZwEcouXxRvvc9pPHXLP3ZPBD8nYu9kTD220mEGgWcZ 179 | 3L9dDlZPcSocbjw295WMvHz2QjhrDrb8gXwdpoRyuyofqgCyNxSnEC5M13SjOxtf 180 | pjGzjTqh0kDlKXg2/eTkd9xIHjVhFYiHIEeITM/lHCfWwBCYxViuuF7pSRPzTe8U 181 | C440b62qZSPMjVoquaMg+qx0n9fKSo6n1FIKHypv3Kue2G0WhDeK6u0U288vQ1t4 182 | Ood3Qa13gZ+9hwDLbM/AoBfVBDlP/tpAwa7AIIU1ZRDNbZr7emFdctx9B6kLINv3 183 | 4PDOGM2xrjOuACSGMq8Zcu7LBz35PpIZtviJOeKNwUd8/xHjWC6W0itgfJb5I1Nm 184 | V6Vj368pGlJx6Se26lvXwyyrc9pSw6jSAwARBeU4YkNWpi4i6QKCAQEA0T7u3P/9 185 | jZJSnDN1o2PXymDrJulE61yguhc/QSmLccEPZe7or06/DmEhhKuCbv+1MswKDeag 186 | /1JdFPGhL2+4G/f/9BK3BJPdcOZSz7K6Ty8AMMBf8AehKTcSBqwkJWcbEvpHpKJ6 187 | eDqn1B6brXTNKMT6fEEXCuZJGPBpNidyLv/xXDcN7kCOo3nGYKfB5OhFpNiL63tw 188 | +LntU56WESZwEqr8Pf80uFvsyXQK3a5q5HhIQtxl6tqQuPlNjsDBvCqj0x72mmaJ 189 | ZVsVWlv7khUrCwAXz7Y8K7mKKBd2ekF5hSbryfJsxFyvEaWUPhnJpTKV85lAS+tt 190 | FQuIp9TvKYlRQwKCAQEAwWJN8jysapdhi67jO0HtYOEl9wwnF4w6XtiOYtllkMmC 191 | 06/e9h7RsRyWPMdu3qRDPUYFaVDy6+dpUDSQ0+E2Ot6AHtVyvjeUTIL651mFIo/7 192 | OSUCEc+HRo3SfPXdPhSQ2thNTxl6y9XcFacuvbthgr70KXbvC4k6IEmdpf/0Kgs9 193 | 7QTZCG26HDrEZ2q9yMRlRaL2SRD+7Y2xra7gB+cQGFj6yn0Wd/07er49RqMXidQf 194 | KR2oYfev2BDtHXoSZFfhFGHlOdLvWRh90D4qZf4vQ+g/EIMgcNSoxjvph1EShmKt 195 | sjhTHtoHuu+XmEQvIewk2oCI+JvofBkcnpFrVvUUrQKCAQAaTIufETmgCo0BfuJB 196 | N/JOSGIl0NnNryWwXe2gVgVltbsmt6FdL0uKFiEtWJUbOF5g1Q5Kcvs3O/XhBQGa 197 | QbNlKIVt+tAv7hm97+Tmn/MUsraWagdk1sCluns0hXxBizT27KgGhDlaVRz05yfv 198 | 5CdJAYDuDwxDXXBAhy7iFJEgYSDH00+X61tCJrMNQOh4ycy/DEyBu1EWod+3S85W 199 | t3sMjZsIe8P3i+4137Th6eMbdha2+JaCrxfTd9oMoCN5b+6JQXIDM/H+4DTN15PF 200 | 540yY7+aZrAnWrmHknNcqFAKsTqfdi2/fFqwoBwCtiEG91WreU6AfEWIiJuTZIru 201 | sIibAoIBAAqIwlo5t+KukF+9jR9DPh0S5rCIdvCvcNaN0WPNF91FPN0vLWQW1bFi 202 | L0TsUDvMkuUZlV3hTPpQxsnZszH3iK64RB5p3jBCcs+gKu7DT59MXJEGVRCHT4Um 203 | YJryAbVKBYIGWl++sZO8+JotWzx2op8uq7o+glMMjKAJoo7SXIiVyC/LHc95urOi 204 | 9+PySphPKn0anXPpexmRqGYfqpCDo7rPzgmNutWac80B4/CfHb8iUPg6Z1u+1FNe 205 | yKvcZHgW2Wn00znNJcCitufLGyAnMofudND/c5rx2qfBx7zZS7sKUQ/uRYjes6EZ 206 | QBbJUA/2/yLv8YYpaAaqj4aLwV8hRpkCggEBAIh3e25tr3avCdGgtCxS7Y1blQ2c 207 | ue4erZKmFP1u8wTNHQ03T6sECZbnIfEywRD/esHpclfF3kYAKDRqIP4K905Rb0iH 208 | 759ZWt2iCbqZznf50XTvptdmjm5KxvouJzScnQ52gIV6L+QrCKIPelLBEIqCJREh 209 | pmcjjocD/UCCSuHgbAYNNnO/JdhnSylz1tIg26I+2iLNyeTKIepSNlsBxnkLmqM1 210 | cj/azKBaT04IOMLaN8xfSqitJYSraWMVNgGJM5vfcVaivZnNh0lZBv+qu6YkdM88 211 | 4/avCJ8IutT+FcMM+GbGazOm5ALWqUyhrnbLGc4CQMPfe7Il6NxwcrOxT8w= 212 | -----END RSA PRIVATE KEY-----`) 213 | -------------------------------------------------------------------------------- /internal/core/origin/realTimeOrigin/realTimeOrigin_test.go: -------------------------------------------------------------------------------- 1 | package realTimeOrigin 2 | 3 | import "testing" 4 | 5 | func TestProxyN(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | }{ 9 | // TODO: Add test cases. 10 | {}, 11 | } 12 | for _, tt := range tests { 13 | t.Run(tt.name, func(t *testing.T) { 14 | //ProxyN() 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/runner/banner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import "fmt" 4 | 5 | var banner = fmt.Sprintf(` 6 | █████╗ ██████╗ ██╗██╗ ██╗██╗██╗ ██╗ ███████╗██████╗ 7 | ██╔══██╗██╔══██╗██║██║ ██╔╝██║██║ ██║ ██╔════╝██╔══██╗ 8 | ███████║██████╔╝██║█████╔╝ ██║██║ ██║ █████╗ ██████╔╝ 9 | ██╔══██║██╔═══╝ ██║██╔═██╗ ██║██║ ██║ ██╔══╝ ██╔══██╗ 10 | ██║ ██║██║ ██║██║ ██╗██║███████╗███████╗███████╗██║ ██║ 11 | ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝ 12 | Version: %s`+"\n", VERSION) 13 | 14 | // 15 | // showBanner 16 | // @Description: show banner on the terminal 17 | // 18 | func showBanner() { 19 | fmt.Println(banner) 20 | } 21 | -------------------------------------------------------------------------------- /internal/runner/option.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import "flag" 4 | 5 | type CommandOptions struct { 6 | ConfigPath string 7 | Web bool 8 | Thread int 9 | FileInput string 10 | } 11 | 12 | // 13 | // ParseCommandOptions 14 | // @Description: parse command options through flag package 15 | // @return *CommandOptions 16 | // 17 | func ParseCommandOptions() *CommandOptions { 18 | c := &CommandOptions{} 19 | // bind data 20 | flag.StringVar(&c.ConfigPath, "conf", "", "project config path") 21 | flag.BoolVar(&c.Web, "web", false, "web operations platform option") 22 | flag.IntVar(&c.Thread, "thread", 100, "go routine concurrency control") 23 | flag.StringVar(&c.FileInput, "f", "", "load requests from target brup file") 24 | 25 | // parse cmd line 26 | flag.Parse() 27 | //fmt.Println(c) 28 | return c 29 | } 30 | -------------------------------------------------------------------------------- /internal/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "APIKiller/internal/core" 5 | hook2 "APIKiller/internal/core/ahttp/hook" 6 | "APIKiller/internal/core/aio" 7 | "APIKiller/internal/core/async" 8 | database2 "APIKiller/internal/core/database" 9 | filter2 "APIKiller/internal/core/filter" 10 | "APIKiller/internal/core/module" 11 | "APIKiller/internal/core/module/CSRF" 12 | "APIKiller/internal/core/module/DoS" 13 | "APIKiller/internal/core/module/OpenRedirect" 14 | "APIKiller/internal/core/module/SSRF" 15 | "APIKiller/internal/core/module/authorize" 16 | notify2 "APIKiller/internal/core/notify" 17 | "APIKiller/internal/core/origin" 18 | "APIKiller/internal/core/origin/fileInputOrigin" 19 | "APIKiller/internal/core/origin/realTimeOrigin" 20 | "APIKiller/internal/web/backend" 21 | "APIKiller/pkg/logger" 22 | "fmt" 23 | "github.com/sirupsen/logrus" 24 | "github.com/spf13/viper" 25 | "os" 26 | "plugin" 27 | "runtime" 28 | "strings" 29 | ) 30 | 31 | const ( 32 | VERSION = "1.2.1" 33 | LoggerLevel = logrus.InfoLevel 34 | ) 35 | 36 | type Runner struct { 37 | } 38 | 39 | // 40 | // Start 41 | // @Description: start the core application 42 | // @receiver r 43 | // 44 | func (r *Runner) Start(cmdOptions *CommandOptions) { 45 | 46 | // load database\modules\filters\notifier and so on 47 | loadLogger() 48 | loadConfig(cmdOptions.ConfigPath) 49 | loadDatabase() 50 | loadModules() 51 | loadAsyncCheckEngine() 52 | loadFilter() 53 | loadNotifer() 54 | loadHooks() 55 | 56 | // load ops platform 57 | if cmdOptions.Web { 58 | loadOPSPlatform() 59 | } 60 | 61 | // load request data from different origins 62 | loadRequestDatafromOrigin(cmdOptions.FileInput) 63 | 64 | // load data from channel and start to handle 65 | startHandle(cmdOptions.Thread) 66 | 67 | // 68 | } 69 | 70 | // 71 | // Stop 72 | // @Description: stop the core application 73 | // @receiver r 74 | // 75 | func (r *Runner) Stop() { 76 | 77 | } 78 | 79 | // 80 | // NewRunner 81 | // @Description: 82 | // 83 | func NewRunner() { 84 | R := &Runner{} 85 | 86 | // show banner 87 | showBanner() 88 | 89 | // parse command option 90 | cmdOptions := ParseCommandOptions() 91 | 92 | // start runner 93 | R.Start(cmdOptions) 94 | 95 | // and so on 96 | } 97 | 98 | func startHandle(MaxThreadNum int) { 99 | logger.Infoln("start handle") 100 | 101 | // goroutine control 102 | limit := make(chan int, MaxThreadNum) 103 | 104 | for { 105 | transferItem := origin.GetOriginRequest() 106 | 107 | // filter requests 108 | flag := true // true -pass false -block 109 | for _, f := range filter2.GetFilters() { 110 | if f.Filter(transferItem.Req) == filter2.FilterBlocked { 111 | flag = false 112 | 113 | logger.Infoln(fmt.Sprintf("filter %v, %v", transferItem.Req.Host, transferItem.Req.URL.Path)) 114 | break 115 | } 116 | } 117 | if !flag { 118 | continue 119 | } 120 | 121 | // transform io.Reader 122 | transferItem.Req.Body = aio.TransformReadCloser(transferItem.Req.Body) 123 | transferItem.Resp.Body = aio.TransformReadCloser(transferItem.Resp.Body) 124 | 125 | go func() { 126 | limit <- 1 127 | 128 | core.NewHandler(transferItem) 129 | 130 | <-limit 131 | }() 132 | } 133 | } 134 | 135 | func loadRequestDatafromOrigin(filePath string) { 136 | // load request from different origins 137 | go func() { 138 | if filePath != "" { 139 | inputOrigin := fileInputOrigin.NewFileInputOrigin(filePath) 140 | inputOrigin.LoadOriginRequest() 141 | } else { 142 | inputOrigin := realTimeOrigin.NewRealTimeOrigin() 143 | inputOrigin.LoadOriginRequest() 144 | } 145 | }() 146 | } 147 | 148 | func loadOPSPlatform() { 149 | logger.Infoln("loading OPS platform") 150 | 151 | go backend.NewAPIServer() 152 | } 153 | 154 | func loadLogger() { 155 | logger.Initial(LoggerLevel, ".") 156 | } 157 | 158 | func loadNotifer() { 159 | logger.Infoln("loading notifier") 160 | 161 | if viper.GetString("app.notifier.Lark.webhookUrl") != "" { 162 | notify2.BindNotifier(notify2.NewLarkNotifier()) 163 | } else if viper.GetString("app.notifier.Dingding.webhookUrl") != "" { 164 | notify2.BindNotifier(notify2.NewDingdingNotifer()) 165 | } else { 166 | } 167 | } 168 | 169 | func loadDatabase() { 170 | logger.Infoln("loading database") 171 | 172 | // bind global database 173 | database2.BindDatabase(database2.NewMysqlClient()) 174 | } 175 | 176 | func loadModules() { 177 | logger.Infoln("loading modules") 178 | 179 | module.RegisterModule(authorize.NewAuthorizedDetector()) 180 | module.RegisterModule(CSRF.NewCSRFDetector()) 181 | module.RegisterModule(OpenRedirect.NewOpenRedirectDetector()) 182 | module.RegisterModule(DoS.NewDoSDetector()) 183 | module.RegisterModule(SSRF.NewSSRFDetector()) 184 | 185 | } 186 | 187 | func loadFilter() { 188 | logger.Infoln("loading filters") 189 | 190 | filter2.RegisterFilter(filter2.NewHttpFilter()) 191 | filter2.RegisterFilter(filter2.NewStaticFileFilter()) 192 | filter2.RegisterFilter(filter2.NewDuplicateFilter()) 193 | 194 | } 195 | 196 | func loadConfig(configPath string) { 197 | logger.Infoln("loading config") 198 | 199 | // use the specified configuration file when configPath option is not blank 200 | if configPath == "" { 201 | // using file .env 202 | _, err := os.Stat("./.env") 203 | if err == nil { 204 | configPath = "./config/config.dev.yaml" 205 | } else { 206 | configPath = "./config/config.release.yaml" 207 | } 208 | } 209 | 210 | logger.Debugln(fmt.Sprintf("current config: %s", configPath)) 211 | 212 | viper.SetConfigFile(configPath) 213 | 214 | err := viper.ReadInConfig() 215 | if err != nil { 216 | panic(err) 217 | } 218 | } 219 | 220 | func loadHooks() { 221 | // except windows os 222 | if runtime.GOOS == "windows" { 223 | logger.Infoln("not support windows operation system") 224 | } 225 | 226 | logger.Infoln("loading hooks") 227 | 228 | // ./hooks directory does not exist 229 | _, err2 := os.Stat("./hooks") 230 | if os.IsNotExist(err2) { 231 | logger.Errorln("target directory does not exist") 232 | 233 | // make directory 234 | err := os.Mkdir("./hooks", os.ModePerm) 235 | if err != nil { 236 | panic(err) 237 | } 238 | } 239 | 240 | // list directory 241 | entries, err := os.ReadDir("./hooks") 242 | if err != nil { 243 | logger.Errorln(fmt.Sprintf("loading hooks error: %v", err)) 244 | panic(entries) 245 | } 246 | 247 | for _, entry := range entries { 248 | soName := entry.Name() 249 | 250 | // filter directory and none so file 251 | if entry.IsDir() == true || strings.Index(soName, ".so") == -1 { 252 | continue 253 | } 254 | 255 | // load plugins and register them via RegisterHooks 256 | logger.Infoln(fmt.Sprintf("[Load Hook] load hook %s", strings.Replace(soName, ".so", "", 1))) 257 | open, err := plugin.Open("./hooks/" + soName) 258 | if err != nil { 259 | logger.Errorln(fmt.Sprintf("load hook %s error: %v", soName, err)) 260 | panic(err) 261 | } 262 | 263 | Hook, err := open.Lookup("Hook") 264 | if err != nil { 265 | logger.Errorln(fmt.Sprintf("load hook %s error: %v", soName, err)) 266 | panic(err) 267 | } 268 | 269 | var Hookk hook2.RequestHook 270 | Hookk, ok := Hook.(hook2.RequestHook) 271 | if !ok { 272 | logger.Errorln(fmt.Sprintf("load hook %s error: unexpected type from module symbol", soName)) 273 | panic(err) 274 | } 275 | 276 | hook2.RegisterHooks(Hookk) 277 | } 278 | } 279 | 280 | func loadAsyncCheckEngine() { 281 | logger.Infoln("loading asynchronous check engine") 282 | 283 | // start asynchronous check engine 284 | go async.NewAsyncCheckEngine().Start() 285 | } 286 | -------------------------------------------------------------------------------- /internal/web/backend/web.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "APIKiller/internal/core/data" 5 | "APIKiller/internal/core/module" 6 | "APIKiller/pkg/logger" 7 | "encoding/base64" 8 | "fmt" 9 | "github.com/gin-gonic/gin" 10 | log "github.com/sirupsen/logrus" 11 | "github.com/spf13/viper" 12 | "gorm.io/driver/mysql" 13 | "gorm.io/gorm" 14 | "io/ioutil" 15 | "net/http" 16 | "os" 17 | "os/exec" 18 | "runtime" 19 | "strconv" 20 | "syscall" 21 | ) 22 | 23 | type APIServer struct { 24 | Page int `form:"page"` 25 | Size int `form:"size"` 26 | Ids []int `form:"ids"` 27 | db *gorm.DB 28 | } 29 | 30 | // 31 | // init 32 | // @Description: initial APIServer and start gin server 33 | // @receiver s 34 | // @param ipaddr 35 | // @param port 36 | // 37 | func (s *APIServer) init(ipaddr, port string) { 38 | // load database 39 | s.loadDatabase() 40 | 41 | server := gin.Default() 42 | 43 | // append route 44 | s.route(server) 45 | 46 | // start server 47 | server.Run(fmt.Sprintf("%s:%s", ipaddr, port)) 48 | } 49 | 50 | // 51 | // route 52 | // @Description: bind route to gin server 53 | // @receiver s 54 | // @param server 55 | // 56 | func (s *APIServer) route(server *gin.Engine) { 57 | 58 | // api path 59 | APIGroup := server.Group("/api") 60 | APIGroup.GET("/test", s.test) 61 | APIGroup.GET("/list", s.list) 62 | APIGroup.GET("/check", s.updateCheckState) 63 | 64 | // bind static directory path 65 | server.Static("/index", "./internal/web/frontend/www") 66 | } 67 | 68 | func (s *APIServer) loadDatabase() { 69 | //get config 70 | host := viper.GetString("app.db.mysql.host") 71 | port := viper.GetString("app.db.mysql.port") 72 | dbname := viper.GetString("app.db.mysql.dbname") 73 | username := viper.GetString("app.db.mysql.username") 74 | password := viper.GetString("app.db.mysql.password") 75 | 76 | //dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" 77 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, port, dbname) 78 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 79 | 80 | // disable logging 81 | db.Logger.LogMode(1) 82 | 83 | if err != nil { 84 | log.Errorln("Connect database error", err) 85 | panic(err) 86 | } 87 | 88 | s.db = db 89 | } 90 | 91 | func (s *APIServer) test(c *gin.Context) { 92 | 93 | c.JSON(http.StatusOK, "Test api") 94 | } 95 | 96 | func (s *APIServer) getHttpItembyId(Id string) string { 97 | // convert string to id 98 | id, _ := strconv.Atoi(Id) 99 | 100 | item := &data.HttpItem{ 101 | Id: int64(id), 102 | } 103 | 104 | s.db.Find(item) 105 | 106 | // decode base64 107 | //decodeString, _ := base64.StdEncoding.DecodeString(item.Item) 108 | 109 | return item.Item 110 | } 111 | 112 | // 113 | // updateCheckState 114 | // @Description: update vulnerability record 115 | // @receiver s 116 | // @param c 117 | // 118 | func (s *APIServer) updateCheckState(c *gin.Context) { 119 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") // ignore CORS 120 | 121 | logger.Debugln(c.Query("Id")) 122 | 123 | tx := s.db.Model(&data.DataItemStr{}).Where("Id=?", c.Query("Id")).Update("check_state", true) 124 | if tx.Error != nil { 125 | logger.Errorln(tx.Error.Error()) 126 | } 127 | c.JSON(http.StatusOK, "success!") 128 | } 129 | 130 | // 131 | // list 132 | // @Description: list vulnerability records 133 | // @receiver s 134 | // @param c 135 | // 136 | func (s *APIServer) list(c *gin.Context) { 137 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") // ignore CORS 138 | 139 | _ = c.Bind(&s) 140 | 141 | items := make([]data.DataItemStr, 1024) 142 | 143 | s.db.Where("vuln_type not like ?", "%"+module.AsyncDetectVulnTypeSeperator+"%").Order("domain").Order("url").Find(&items).Limit(128) 144 | 145 | // recover http item string from id 146 | for i, item := range items { 147 | // item.SourceRequest 148 | decodeString, _ := base64.StdEncoding.DecodeString(s.getHttpItembyId(item.SourceRequest)) 149 | items[i].SourceRequest = string(decodeString) 150 | 151 | //item.SourceResponse 152 | decodeString2, _ := base64.StdEncoding.DecodeString(s.getHttpItembyId(item.SourceResponse)) 153 | items[i].SourceResponse = string(decodeString2) 154 | 155 | //item.VulnRequest 156 | decodeString3, _ := base64.StdEncoding.DecodeString(s.getHttpItembyId(item.VulnRequest)) 157 | items[i].VulnRequest = string(decodeString3) 158 | 159 | //item.VulnResponse 160 | decodeString4, _ := base64.StdEncoding.DecodeString(s.getHttpItembyId(item.VulnResponse)) 161 | items[i].VulnResponse = string(decodeString4) 162 | } 163 | 164 | data := make(map[string]interface{}) 165 | 166 | data["list"] = items 167 | //data["total"] = total 168 | c.JSON(http.StatusOK, items) 169 | } 170 | 171 | // 172 | // autoWakeup 173 | // @Description: Automatically wake up the browser when running locally 174 | // @receiver s 175 | // 176 | func (s *APIServer) autoWakeup(ipaddr, port string) { 177 | var err error 178 | 179 | // secure handle 180 | _, err2 := strconv.Atoi(port) 181 | if err2 != nil { 182 | logger.Debugln("the format of port is invalid") 183 | logger.Errorln(err2) 184 | } 185 | 186 | targetUrl := "http://127.0.0.1:" + port + "/index/index.html" 187 | 188 | switch runtime.GOOS { 189 | case "linux": 190 | err = exec.Command("xdg-open", targetUrl).Start() 191 | break 192 | case "windows": 193 | cmd := exec.Command("cmd", "/c", "start", targetUrl) 194 | cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} 195 | err = cmd.Start() 196 | break 197 | case "darwin": 198 | err = exec.Command("open", targetUrl).Start() 199 | break 200 | default: 201 | err = os.ErrInvalid 202 | } 203 | 204 | if err != nil { 205 | fmt.Println(err) 206 | logger.Errorln(err) 207 | } 208 | } 209 | 210 | func NewAPIServer() { 211 | server := APIServer{} 212 | 213 | // disable logging 214 | gin.DefaultWriter = ioutil.Discard 215 | 216 | ipaddr := viper.GetString("app.web.ipaddr") 217 | port := viper.GetString("app.web.port") 218 | 219 | // wakeup browser 220 | if ipaddr == "127.0.0.1" { 221 | server.autoWakeup(ipaddr, port) 222 | } 223 | 224 | server.init(ipaddr, port) 225 | } 226 | -------------------------------------------------------------------------------- /internal/web/backend/web_test.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import "testing" 4 | 5 | func TestServer(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | }{ 9 | // TODO: Add test cases. 10 | {}, 11 | } 12 | for _, tt := range tests { 13 | t.Run(tt.name, func(t *testing.T) { 14 | NewAPIServer() 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/web/frontend/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | APIKiller 6 | 40 | 41 | 45 | 49 | 53 | 54 | 55 | 56 | 57 | 61 | 62 |
63 |
64 |
65 |

APIKiller Panel

66 |
67 |
68 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 134 | 135 | 136 |
{{ config.name }}
81 | 89 | 97 | 107 | 133 |
137 |
138 | 139 | 293 | 294 | 295 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "APIKiller/internal/runner" 4 | 5 | func main() { 6 | // create project runtime 7 | runner.NewRunner() 8 | } 9 | -------------------------------------------------------------------------------- /pkg/logger/formatter.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "github.com/sirupsen/logrus" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | const ( 10 | defaultLogFormat = "[%lvl%]: %time% - %msg%" 11 | defaultTimestampFormat = time.RFC3339 12 | ) 13 | 14 | // Formatter implements logrus.Formatter interface. 15 | type Formatter struct { 16 | // Timestamp format 17 | TimestampFormat string 18 | // Available standard keys: time, msg, lvl 19 | // Also can include custom fields but limited to strings. 20 | // All of fields need to be wrapped inside %% i.e %time% %msg% 21 | LogFormat string 22 | 23 | // Disables the truncation of the level text to 4 characters. 24 | DisableLevelTruncation bool 25 | } 26 | 27 | // 28 | // Format 29 | // @Description: building log message 30 | // @receiver f 31 | // @param entry 32 | // @return []byte 33 | // @return error 34 | // 35 | func (f *Formatter) Format(entry *logrus.Entry) ([]byte, error) { 36 | output := f.LogFormat 37 | if output == "" { 38 | output = defaultLogFormat 39 | } 40 | 41 | timestampFormat := f.TimestampFormat 42 | if timestampFormat == "" { 43 | timestampFormat = defaultTimestampFormat 44 | } 45 | 46 | output = strings.Replace(output, "%time%", entry.Time.Format(timestampFormat), 1) 47 | output = strings.Replace(output, "%msg%", entry.Message, 1) 48 | level := strings.ToUpper(entry.Level.String()) 49 | if !f.DisableLevelTruncation { 50 | level = level[:4] 51 | } 52 | output = strings.Replace(output, "%lvl%", level, 1) 53 | 54 | for k, v := range entry.Data { 55 | if s, ok := v.(string); ok { 56 | output = strings.Replace(output, "%"+k+"%", s, 1) 57 | } 58 | } 59 | output += "\n" 60 | 61 | return []byte(output), nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sirupsen/logrus" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | var logger = logrus.New() 11 | 12 | func Initial(level logrus.Level, logPath string) { 13 | formatter := &Formatter{ 14 | LogFormat: "%time% [%lvl%] %msg%", 15 | TimestampFormat: "2006-01-02 15:04:05", 16 | } 17 | 18 | // Output to stdout instead of the default stderr 19 | // Can be any io.Writer, see below for File example 20 | logger.SetFormatter(formatter) 21 | logger.SetOutput(os.Stdout) 22 | logger.SetLevel(level) 23 | 24 | // Output to file 25 | logFilePath := filepath.Join(logPath, "./log/new.log") 26 | rotateFileHook, err := NewRotateFileHook(RotateFileConfig{ 27 | Filename: logFilePath, 28 | MaxSize: 50, 29 | MaxBackups: 1024, 30 | MaxAge: 30, 31 | LocalTime: true, 32 | Level: level, 33 | Formatter: formatter, 34 | }) 35 | if err != nil { 36 | fmt.Printf("Create log rotate hooks error: %s\n", err) 37 | return 38 | } 39 | logger.AddHook(rotateFileHook) 40 | } 41 | 42 | func Debugln(args ...interface{}) { 43 | logger.Debug(args...) 44 | } 45 | 46 | func Debugf(format string, args ...interface{}) { 47 | logger.Debugf(format, args...) 48 | } 49 | 50 | func Infoln(args ...interface{}) { 51 | logger.Info(args...) 52 | } 53 | 54 | func Infof(format string, args ...interface{}) { 55 | logger.Infof(format, args...) 56 | } 57 | 58 | func Warnln(args ...interface{}) { 59 | logger.Warn(args...) 60 | } 61 | 62 | func Warnf(format string, args ...interface{}) { 63 | logger.Warnf(format, args...) 64 | } 65 | 66 | func Errorln(args ...interface{}) { 67 | logger.Error(args...) 68 | } 69 | 70 | func Errorf(format string, args ...interface{}) { 71 | logger.Errorf(format, args...) 72 | } 73 | 74 | func Panic(args ...interface{}) { 75 | logrus.Panic(args...) 76 | } 77 | 78 | func Fatal(args ...interface{}) { 79 | logrus.Fatal(args...) 80 | } 81 | 82 | func Fatalf(format string, args ...interface{}) { 83 | logrus.Fatalf(format, args...) 84 | } 85 | -------------------------------------------------------------------------------- /pkg/logger/rotate.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "github.com/sirupsen/logrus" 6 | "gopkg.in/natefinch/lumberjack.v2" 7 | "os" 8 | "time" 9 | ) 10 | 11 | type RotateFileConfig struct { 12 | Filename string 13 | MaxSize int 14 | MaxBackups int 15 | MaxAge int 16 | Level logrus.Level 17 | LocalTime bool 18 | Formatter logrus.Formatter 19 | } 20 | 21 | type RotateFileHook struct { 22 | Config RotateFileConfig 23 | nextRotateTime time.Time 24 | logWriter *lumberjack.Logger 25 | } 26 | 27 | func NewRotateFileHook(config RotateFileConfig) (logrus.Hook, error) { 28 | hook := RotateFileHook{ 29 | Config: config, 30 | } 31 | 32 | // load rotate log system 33 | err := hook.rotateLogFile() 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return &hook, nil 39 | } 40 | 41 | func (hook *RotateFileHook) Levels() []logrus.Level { 42 | return logrus.AllLevels[:hook.Config.Level+1] 43 | } 44 | 45 | func (hook *RotateFileHook) Fire(entry *logrus.Entry) (err error) { 46 | b, err := hook.Config.Formatter.Format(entry) 47 | if err != nil { 48 | return err 49 | } 50 | _, err = hook.logWriter.Write(b) 51 | return 52 | } 53 | 54 | func (hook *RotateFileHook) rotateLogFile() error { 55 | now := time.Now() 56 | if now.After(hook.nextRotateTime) { 57 | // close current log file 58 | if hook.logWriter != nil { 59 | err := hook.logWriter.Close() 60 | if err != nil { 61 | return err 62 | } 63 | } 64 | 65 | // calculate next rotate time 66 | hook.nextRotateTime = now.Truncate(24 * time.Hour).Add(24 * time.Hour) 67 | 68 | // rename log filename according to date 69 | date := fmt.Sprintf("%d-%d-%d", now.Year(), now.Month(), now.Day()) 70 | 71 | _, err1 := os.Stat(hook.Config.Filename) 72 | if err1 == nil { 73 | err := os.Rename(hook.Config.Filename, "./log/"+date+".log") 74 | if err != nil { 75 | return err 76 | } 77 | } 78 | 79 | // create new log file 80 | hook.logWriter = &lumberjack.Logger{ 81 | Filename: hook.Config.Filename, 82 | MaxSize: hook.Config.MaxSize, 83 | MaxBackups: hook.Config.MaxBackups, 84 | MaxAge: hook.Config.MaxAge, 85 | LocalTime: hook.Config.LocalTime, 86 | } 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /pkg/util/encode.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "encoding/base64" 4 | 5 | func B64Encode(targetStr string) string { 6 | return base64.StdEncoding.EncodeToString([]byte(targetStr)) 7 | } 8 | -------------------------------------------------------------------------------- /pkg/util/randomIdGenerator.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func GenerateRandomId() string { 10 | year := time.Now().Format("2006") 11 | month := time.Now().Format("01") 12 | day := time.Now().Format("02") 13 | 14 | return fmt.Sprintf("%v%v%v%v", year, month, day, rand.Int()) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/util/randomIdGenerator_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | func TestGenerateRandomId(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | want string 9 | }{ 10 | // TODO: Add test cases. 11 | {}, 12 | } 13 | for _, tt := range tests { 14 | t.Run(tt.name, func(t *testing.T) { 15 | if got := GenerateRandomId(); got != tt.want { 16 | t.Errorf("GenerateRandomId() = %v, want %v", got, tt.want) 17 | } 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /static/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/2.jpg -------------------------------------------------------------------------------- /static/img/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/architecture.jpg -------------------------------------------------------------------------------- /static/img/img-0401.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img-0401.png -------------------------------------------------------------------------------- /static/img/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img.png -------------------------------------------------------------------------------- /static/img/img_030101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_030101.png -------------------------------------------------------------------------------- /static/img/img_030102.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_030102.png -------------------------------------------------------------------------------- /static/img/img_030103.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_030103.png -------------------------------------------------------------------------------- /static/img/img_030301.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_030301.png -------------------------------------------------------------------------------- /static/img/img_030801.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_030801.png -------------------------------------------------------------------------------- /static/img/img_030901.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_030901.png -------------------------------------------------------------------------------- /static/img/img_070201.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_070201.jpg -------------------------------------------------------------------------------- /static/img/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_1.png -------------------------------------------------------------------------------- /static/img/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_2.png -------------------------------------------------------------------------------- /static/img/img_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_20.png -------------------------------------------------------------------------------- /static/img/img_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_21.png -------------------------------------------------------------------------------- /static/img/img_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_22.png -------------------------------------------------------------------------------- /static/img/img_23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_23.png -------------------------------------------------------------------------------- /static/img/img_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_24.png -------------------------------------------------------------------------------- /static/img/img_25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_25.png -------------------------------------------------------------------------------- /static/img/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_3.png -------------------------------------------------------------------------------- /static/img/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_4.png -------------------------------------------------------------------------------- /static/img/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_5.png -------------------------------------------------------------------------------- /static/img/img_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_6.png -------------------------------------------------------------------------------- /static/img/img_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/API-Security/APIKiller/563cb11e5e2221a2106594d42c637fc12160dcbf/static/img/img_logo.png --------------------------------------------------------------------------------