├── .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 |
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 |
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 | 
61 | 2. 根据返回的数据,在config.yaml中完成相关配置
62 |
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 |
80 |
81 | 2. 安装根目录下的https证书[windows环境]
82 | 1. 找到根目录下的ca.crt证书
83 |
84 |
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 |
94 |
95 |
96 |
97 | 3. 配置成功后,当发现漏洞时,会立即推送漏洞信息
98 |
99 |
100 |
101 |
102 |
103 | 3. 一键启动【配置文件位于./config/目录下(默认是config.release.yaml),或自己指定】
104 |
105 |
106 |
107 |
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 |
128 | 2. 启动项目,访问接口
129 |
130 |
131 |
132 | 3. **成功检测出越权和csrf**
133 |
134 |
135 |
136 | ### 403 bypass模块
137 | > 当前可以进行大小写、path fuzz、api版本降级等方式,来进行探测
138 |
139 |
140 |
141 |
142 |
143 | ### CSRF检测
144 | > 基于pikachu靶场,进行漏洞检测
145 |
146 |
147 | 处理csrf模块的配置
148 |
149 |
150 |
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 | {{ config.name }}
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | {{ paramItem[config.name] }}
84 |
85 |
86 | {{ getFormattedTime(paramItem[config.name]) }}
87 |
88 |
89 |
90 |
96 |
97 |
98 |
106 |
107 |
108 |
109 |
110 |
121 |
125 |
126 |
127 | {{ paramItem[config.name] }}
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
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
--------------------------------------------------------------------------------