mirror of
https://github.com/Li4n0/revsuit.git
synced 2026-09-22 06:40:43 +08:00
Compare commits
18
Commits
v0.1.5-beta
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acbdbee26a | ||
|
|
9863821dbc | ||
|
|
206808e83a | ||
|
|
a248c6e39e | ||
|
|
5e2020efff | ||
|
|
c0bda707b7 | ||
|
|
ffc4318725 | ||
|
|
8023a9b021 | ||
|
|
56153510e1 | ||
|
|
be7343191d | ||
|
|
b1773ee5e9 | ||
|
|
18707cec47 | ||
|
|
993d3867ee | ||
|
|
d4d7435265 | ||
|
|
70f17453fd | ||
|
|
ef38cbc790 | ||
|
|
6205772a59 | ||
|
|
807186be61 |
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Environments**
|
||||
Your operating system, such as `Mac OS Catalina 10.15.7`.
|
||||
|
||||
**RevSuit Version**
|
||||
Versions of RevSuit with bugs.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the new feature you expect to add**
|
||||
A clear and concise description.
|
||||
|
||||
**Why do you want to add this feature**
|
||||
A clear and concise description of why you need the feature, such as in what kind of usage scenarios it would be more convenient to have a certain feature.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -12,14 +12,11 @@ jobs:
|
||||
with:
|
||||
go-version: '^1.16.0'
|
||||
- name: compile
|
||||
env:
|
||||
CGO_ENABLE: 1
|
||||
CGO_LDFLAGS: -static
|
||||
run: |
|
||||
GOARCH="amd64" go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_linux_amd64" ./cmd/revsuit/revsuit.go
|
||||
GOARCH="386" go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_linux_i386" ./cmd/revsuit/revsuit.go
|
||||
GOARCH="arm" go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_linux_arm" ./cmd/revsuit/revsuit.go
|
||||
GOARCH="arm64" go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_linux_arm64" ./cmd/revsuit/revsuit.go
|
||||
GOARCH="amd64" go build -v -ldflags="-s -w -extldflags=-static" -tags netgo -trimpath -o "bin/revsuit_linux_amd64" ./cmd/revsuit/revsuit.go
|
||||
# GOARCH="386" go build -v -ldflags="-s -w -extldflags=-static" -tags netgo -trimpath -o "bin/revsuit_linux_i386" ./cmd/revsuit/revsuit.go
|
||||
# GOARCH="arm" go build -v -ldflags="-s -w -extldflags=-static" -tags netgo -trimpath -o "bin/revsuit_linux_arm" ./cmd/revsuit/revsuit.go
|
||||
# GOARCH="arm64" go build -v -ldflags="-s -w -extldflags=-static" -tags netgo -trimpath -o "bin/revsuit_linux_arm64" ./cmd/revsuit/revsuit.go
|
||||
- name: Upload the artifacts
|
||||
uses: li4n0/upload-release-action@v2
|
||||
with:
|
||||
@@ -37,11 +34,9 @@ jobs:
|
||||
with:
|
||||
go-version: '^1.16.0'
|
||||
- name: compile
|
||||
env:
|
||||
CGO_ENABLE: 1
|
||||
run: |
|
||||
GOARCH="amd64" go build -v -ldflags="-s -w" -trimpath -o "bin/revsuit_darwin_amd64" ./cmd/revsuit/revsuit.go
|
||||
GOARCH="arm64" go build -v -ldflags="-s -w" -trimpath -o "bin/revsuit_darwin_arm64" ./cmd/revsuit/revsuit.go
|
||||
# GOARCH="arm64" go build -v -ldflags="-s -w" -trimpath -o "bin/revsuit_darwin_arm64" ./cmd/revsuit/revsuit.go
|
||||
- name: Upload the artifacts
|
||||
uses: li4n0/upload-release-action@v2
|
||||
with:
|
||||
@@ -59,13 +54,10 @@ jobs:
|
||||
with:
|
||||
go-version: '^1.16.0'
|
||||
- name: compile
|
||||
env:
|
||||
CGO_ENABLE: 1
|
||||
CGO_LDFLAGS: -static
|
||||
run: |
|
||||
env GOARCH=amd64 go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_windows_amd64.exe" ./cmd/revsuit/revsuit.go
|
||||
env GOARCH=386 go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_windows_i386.exe" ./cmd/revsuit/revsuit.go
|
||||
env GOARCH=arm go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_windows_arm.exe" ./cmd/revsuit/revsuit.go
|
||||
# env GOARCH=386 go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_windows_i386.exe" ./cmd/revsuit/revsuit.go
|
||||
# env GOARCH=arm go build -v -ldflags="-s -w -extldflags=-static" -trimpath -o "bin/revsuit_windows_arm.exe" ./cmd/revsuit/revsuit.go
|
||||
- name: Upload the artifacts
|
||||
uses: li4n0/upload-release-action@v2
|
||||
with:
|
||||
|
||||
@@ -2,6 +2,36 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [0.2.0](https://github.com/Li4n0/revsuit/compare/v0.1.7...v0.2.0) (2021-07-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support deleting logs based on filtering results ([#35](https://github.com/Li4n0/revsuit/issues/35)) ([9863821](https://github.com/Li4n0/revsuit/commit/9863821dbc351734569b7ec1eda2b6712bd02bb0))
|
||||
|
||||
### [0.1.7](https://github.com/Li4n0/revsuit/compare/v0.1.6...v0.1.7) (2021-07-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **http:** fix the bug that http record url search does not work ([ffc4318](https://github.com/Li4n0/revsuit/commit/ffc4318725017c7eda7ec11ca67d5de16ad1e8a8))
|
||||
* **mysql:** fix the bug that crashes when mysql port conflicts ([c0bda70](https://github.com/Li4n0/revsuit/commit/c0bda707b750b7858fdd4efc414b726ee37e71da))
|
||||
|
||||
### [0.1.6](https://github.com/Li4n0/revsuit/compare/v0.1.5...v0.1.6) (2021-06-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** fix the bug that the prompt may not be output when generating config file ([5615351](https://github.com/Li4n0/revsuit/commit/56153510e11783c08843cbb8b0f827d5f8567842))
|
||||
* **ipinfo:** fix a potential panic when downloading ip location database ([#33](https://github.com/Li4n0/revsuit/issues/33)) ([b1773ee](https://github.com/Li4n0/revsuit/commit/b1773ee5e9ebd8c8be72f315981268992c08b3f1))
|
||||
|
||||
### [0.1.5](https://github.com/Li4n0/revsuit/compare/v0.1.5-beta...v0.1.5) (2021-06-22 公开该项目以庆祝毕业,愿工作多年后的我,归来时仍是少年)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **notice:** fix the buf of notice with null record field ([#31](https://github.com/Li4n0/revsuit/issues/31)) ([ef38cbc](https://github.com/Li4n0/revsuit/commit/ef38cbc790f69716a335167321e1c7c8bdee2e41))
|
||||
|
||||
### [0.1.5-beta](https://github.com/Li4n0/revsuit/compare/v0.1.4-beta-fix-1...v0.1.5-beta) (2021-06-13)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# RevSuit - A Flexible and Powerful Reverse Connection Platform
|
||||
|
||||
English |[简体中文](./README.zh-CN.md)
|
||||
|
||||
## Overview
|
||||
|
||||
RevSuit is a flexible and powerful reverse connection platform designed for receiving connection from target host in
|
||||
@@ -7,13 +9,13 @@ penetration. It currently supports HTTP, DNS, RMI, MySQL and FTP protocols.
|
||||
|
||||
Flexible:
|
||||
|
||||
1. Revsuit can set flexible rules to capture different connections.
|
||||
1. RevSuit can set flexible rules to capture different connections.
|
||||
2. Deploy and run via binaries without worrying about nasty dependency errors.
|
||||
|
||||
Powerful:
|
||||
|
||||
1. Revsuit allows users to customize the response for different connections through rules and supports dynamic response
|
||||
generation using template variables.
|
||||
1. Allows to customize the response for different connections through rules and supports dynamic response generation
|
||||
using template variables.
|
||||
2. Support a variety of protocols, and support the in-depth utilization of some protocols, such as DNS rebinding, MySQL
|
||||
Load Local Data, JDBC Deserialize Exploit, FTP Passive Mode SSRF, etc.
|
||||
3. Native support for use with scanners.
|
||||
@@ -24,12 +26,13 @@ Powerful:
|
||||
|
||||
### Preparation
|
||||
|
||||
Download the latest release firstly.
|
||||
Download the latest release firstly. For the time being, only the binary for amd64 architecture is available for
|
||||
download, if you need to use it in other architectures, you can compile it yourself.
|
||||
|
||||
RevSuit will generate default configuration file on first run. Modify the configuration file according to your needs,
|
||||
then re-run.
|
||||
|
||||
In order to confirm the IP location, you need to use the IP location database. `QQwry` is used as the data source by
|
||||
In order to confirm the IP location, it needs to use the IP location database. `QQwry` is used as the data source by
|
||||
default, you can also modify the configuration to use `GeoIP`. If the selected database is not available in the current
|
||||
directory or the database is updated for more than a week, RevSuit will automatically download the latest database. If
|
||||
the download fails, the `IpArea` field will always be null.
|
||||
@@ -65,8 +68,8 @@ A few notes are as follows:
|
||||
2. `FlagFormat` uses regular expression syntax, and for different protocols, the fields matched by flagFormat are
|
||||
different, you can check the corresponding hints to get details when creating rules.
|
||||
3. Rules with high rank will take effect first.
|
||||
4. For different protocol rules, you can make different custom configurations, and for some protocols, template
|
||||
variables can be used.
|
||||
4. For different protocol rules, you can make different custom responses, and for some protocols, template variables can
|
||||
be used.
|
||||
5. You can set the named grouping of regular expressions in `flagFormat`, and the result of the matching grouping will
|
||||
also be used as a template variable.
|
||||
|
||||
@@ -129,7 +132,7 @@ RevSuit was split from my scanner project, so its native support works with scan
|
||||
|
||||
From RevSuit's perspective, we call a scanner a client.
|
||||
|
||||
#### Create Connection
|
||||
#### Establish connection
|
||||
|
||||
RevSuit
|
||||
uses [HTTP Server-sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
|
||||
@@ -145,8 +148,8 @@ example.
|
||||
|
||||
#### Multi-client
|
||||
|
||||
As shown above, RevSuit supports multiple clients, and each client in the connected state receives a push of `flag`, so
|
||||
distributed scanning can be supported.
|
||||
As shown above, RevSuit supports multiple clients, and each client **in the connected state** receives a push of `flag`,
|
||||
so distributed scanning can be supported.
|
||||
|
||||
#### Temporary storage queue
|
||||
|
||||
@@ -156,15 +159,15 @@ helpful for discovering delay-triggered vulnerabilities.)
|
||||
|
||||
#### Use flagGroup
|
||||
|
||||
`FlagGroup` is the content matched by the anonymous group in the `flagFormat` field of the rule. The platform will check
|
||||
the content matched in the grouping,and the flag is only pushed to the client when then content(`flagGroup`) captured
|
||||
for the first time.
|
||||
|
||||
In a real-world vulnerability scanning scenario, you may send a large number of different payloads for a single
|
||||
vulnerability point, and they may all be valid, which can result in the backlink platform receiving many requests, yet
|
||||
they are caused by the same vulnerability. If you don't want the client to receive so many `flags` for the same
|
||||
vulnerability, you can take advantage of the `flagGroup` feature of rule's flagFormat.
|
||||
|
||||
`FlagGroup` is the content matched by the anonymous group in the `flagFormat` field of the rule. The platform will check
|
||||
the content matched in the grouping,and the flag is only pushed to the client when then content(`flagGroup`) captured
|
||||
for the first time.
|
||||
|
||||
For example, SSRF scanning.
|
||||
|
||||
Firstly create a rule like the following:
|
||||
@@ -183,23 +186,22 @@ http:
|
||||
|
||||
Suppose our target is `https://www.testvuln.com?url=api.com&p=useless`, and for SSRF we have 5 payloads. The final
|
||||
request sent by the scanner may
|
||||
be `['https://www.testvuln.com?url=http://revsuit.com/ssrfa98oni1&p=useless, ... ,'https://www.testvuln.com?url=//revsuit.com/ssrfa98oni5&p=useless`']
|
||||
be `['https://www.testvuln.com?url=http://revsuit.com/ssrfa98oni1&p=useless','https://www.testvuln.com?url=http://revsuit.com/ssrfa98oni2&p=useless', ... ,'https://www.testvuln.com?url=//revsuit.com/ssrfa98oni5&p=useless`']
|
||||
. They may all attack successfully.
|
||||
|
||||
However, because anonymous grouping is used in the rule's `flagFormat`, The platform will query the
|
||||
connected `flagGroup`, in this case `ssrfa98oni`, and push `flag` to the client only at its first appearance, so the
|
||||
client will only receive one flag:`ssrfa98oni1`. But this is enough, because it already proves that the `url` parameter
|
||||
of the target is vulnerable.
|
||||
client will only receive one flag:`ssrfa98oni1`. It already proves that the `url` parameter of the target is vulnerable.
|
||||
|
||||
### Multi-module collocation
|
||||
|
||||
In actual penetration testing scenarios, certain tasks can be done easily and quickly by combining and matching various
|
||||
modules of RevSuit. The following is an example of a blind XXE scenario, showing how to use RevSuit's HTTP and FTP
|
||||
modules of RevSuit. The following is an example of a blind XXE in Java Web, showing how to use RevSuit's HTTP and FTP
|
||||
modules, combined with template variables, to quickly complete a port scan.
|
||||
|
||||
First use create an HTTP rule to return evil.dtd, customize the response to the contents of dtd so that it goes to
|
||||
connect to RevSuit's FTP service, and use template variables to pass the Host and Port to be scanned to FTP via FTP's
|
||||
user and password.
|
||||
First create an HTTP rule to return evil.dtd, customize the response to the contents of dtd so that it goes to connect
|
||||
to RevSuit's FTP service, and use template variables to pass the Host and Port to be scanned to FTP via FTP's user and
|
||||
password.
|
||||
|
||||

|
||||
|
||||
@@ -225,14 +227,16 @@ and 8080 are open.
|
||||
|
||||
A more detailed wiki is being prepared, you can explore by yourself before it.
|
||||
|
||||
## Advice and Communication
|
||||
## Feedback, Suggestions and Communication
|
||||
|
||||
Submit an issue or contact me through Weixin: `Li4n06`
|
||||
Submit an issue or contact me through Weixin: `TGk0bjA2Cg==`
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
### Reference
|
||||
|
||||
This project draws on the code of the following outstanding projects:
|
||||
|
||||
* [https://github.com/rmb122/rogue_mysql_server](https://github.com/rmb122/rogue_mysql_server)
|
||||
* [https://github.com/256dpi/newdns](https://github.com/256dpi/newdns)
|
||||
|
||||
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
# RevSuit - A Flexible and Powerful Reverse Connection Platform
|
||||
|
||||
简体中文 |[English](./README.md)
|
||||
|
||||
## 简介
|
||||
|
||||
RevSuit 是一款灵活并且强大的反连平台。目前支持 HTTP、DNS、RMI、MySQL 和 FTP 协议。
|
||||
|
||||
灵活:
|
||||
|
||||
1. 通过灵活的规则来捕获连接
|
||||
2. 通过二进制文件部署和运行而无需担心讨厌的依赖错误
|
||||
|
||||
强大:
|
||||
|
||||
1. 可以通过规则,为不同类型的连接自定义响应,并且可以使用模板变量实现动态响应。
|
||||
2. 支持的协议数量相对较多,并且支持其中的部分协议的进一步利用,如 DNS重绑定、MySQL客户端文件读取和 JDBC 反序列化、FTP 被动模式 SSRF 等
|
||||
3. 原生支持扫描器
|
||||
4. 支持发送通知到流行的办公软件
|
||||
5. 其它有趣或贴心的功能
|
||||
|
||||
## 基础使用
|
||||
|
||||
### 安装
|
||||
|
||||
在 Releases 里下载最新版本的 RevSuit。暂时只提供 amd64 架构下的二进制文件下载,如需要在其它架构下使用,可以自行编译。
|
||||
|
||||
RevSuit 首次运行时将会生成配置文件模板,你需要编辑其中的内容然后重新运行。
|
||||
|
||||
为了确认连接 IP 的归属地,RevSuit 需要依赖 IP 归属地数据库。默认情况下使用纯真 IP 库,你也可以在配置文件中指定使用 GeoIP。如果当前目录下没有被选中的数据库文件,或者数据库文件更新的时间大于一周,RevSuit
|
||||
将会自动下载最新版的数据库文件到本地。如果下载失败,连接记录的`IpArea`字段将始终为空。
|
||||
|
||||
```bash
|
||||
$ ./revsuit
|
||||
2021/05/16 22:55:10 [ INFO] Downloading qqwry.dat...
|
||||
|
||||
____ _____ _ __
|
||||
/ __ \___ _ __/ ___/__ __(_) /_
|
||||
/ /_/ / _ \ | / /\__ \/ / / / / __/
|
||||
/ _, _/ __/ |/ /___/ / /_/ / / /_
|
||||
/_/ |_|\___/|___//____/\__,_/_/\__/
|
||||
vBeta0.1
|
||||
https://revsuit.pro
|
||||
2021/05/16 22:55:22 [ INFO] Starting HTTP Server at :80, token:your_token
|
||||
2021/05/16 22:55:22 [ INFO] Start to listen FTP PASV port at :2020, PasvIP is 10.9.8.7
|
||||
2021/05/16 22:55:22 [ INFO] Starting FTP Server at :21
|
||||
2021/05/16 22:55:22 [ INFO] Starting MySQL Server at :3306
|
||||
2021/05/16 22:55:22 [ INFO] Starting RMI Server at :1099
|
||||
2021/05/16 22:55:22 [ INFO] Starting DNS Server at :53
|
||||
```
|
||||
|
||||
在启动之后,你可以通过访问HTTP Server的`/revsuit/admin/`路径,并输入 token 来进入控制面板。
|
||||
|
||||
### 创建规则与接收连接
|
||||
|
||||
这里以创建一个HTTP规则作为例子:
|
||||
|
||||
一些需要注意的内容如下:
|
||||
|
||||
1. 规则的 `name` 和 `flagFormat` 字段是唯一的。
|
||||
2. `flagFormat` 使用正则表达式语法。对于不同的协议,其匹配的字段也是不同的。你可以在创建规则的时候查看对应的帮助信息来获知该协议的哪个字段是会被匹配的。
|
||||
3. `rank` 决定了规则的优先级,如果一个请求同时满足多条规则,只有 `rank` 值最大的那条规则会生效。
|
||||
4. 对于不同的协议,你可以设置不同的响应,部分协议可以使用模板变量来动态生成响应。
|
||||
5. 你可以在 `flagFormat` 中使用正则表达式中的命名分组,命名分组匹配的结果将会成为自定义的模板变量。
|
||||
|
||||
如下图所示,我们创建一个名为 `test_create_rule` 的HTTP协议的规则,其中使用了内置的模板变量和自定义的模板变量:
|
||||
|
||||

|
||||
|
||||
然后发送一个满足这条规则的请求来进行测试,可以看到响应的内容中各个模板变量都获取到了对应的值:
|
||||
|
||||

|
||||
|
||||
这条请求同时会被记录在平台:
|
||||
|
||||

|
||||
|
||||
### 通知
|
||||
|
||||
通过配置 Webhook 地址并打开对应规则的 `Notice` 开关,你可以通过办公软件接收新连接的通知。目前支持 钉钉、企业微信、飞书 和 slack。(计划会后续支持Discord 和 Telegram)
|
||||
|
||||
### 导入/导出规则
|
||||
|
||||
如果你想要迁移平台或者清除数据,重新创建规则可能是一件麻烦事。因此 RevSuit 支持导入和导出规则。
|
||||
|
||||
该功能位于 `Settings>RULES` 选项卡,如下所示,规则将会被以 yaml 的格式进行导入和导出:
|
||||
|
||||
```yaml
|
||||
http:
|
||||
- name: test_create_rule
|
||||
flag_format: (?P<what>\w+)\?
|
||||
rank: 0
|
||||
push_to_client: false
|
||||
notice: false
|
||||
response_status_code: "302"
|
||||
response_headers:
|
||||
Location: ${query.url}
|
||||
response_body: ${header.say} ${what}
|
||||
- name: other_rule
|
||||
flag_format: other
|
||||
rank: 1
|
||||
push_to_client: false
|
||||
notice: true
|
||||
response_status_code: "200"
|
||||
response_headers: { }
|
||||
response_body: Hello Revsuit!
|
||||
dns:
|
||||
... ...
|
||||
```
|
||||
|
||||
## 高级功能
|
||||
|
||||
### 与扫描器适配
|
||||
|
||||
RevSuit 拆分自我的扫描器项目,因此它原生支持搭配扫描器使用。
|
||||
|
||||
从 RevSuit 的视角,我们称扫描器为客户端。
|
||||
|
||||
#### 建立通信管道
|
||||
|
||||
RevSuit
|
||||
使用 [HTTP Server-sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
|
||||
(SSE) 与客户端建立一条单向通信管道。
|
||||
|
||||
通信管道的API为 `/revsuit/api/events?message`。客户端只需携带 `Token: your token` 请求头,然后请求这个 API 来建立通信管道。这样当 RevSuit
|
||||
捕获新连接的时候,匹配到的 `flag` 就会通过这条管道传递给客户端。
|
||||
|
||||

|
||||
|
||||
这里是一个使用 Go 语言的 sse 库编写客户端的[简单例子](https://gist.github.com/Li4n0/21aa0bec2d626114a729ca2677efb05a) 。
|
||||
|
||||
#### 多客户端
|
||||
|
||||
如上一节的图片里所展示的,RevSuit 支持多个客户端,每个在**连接状态**的客户端都会接收到 `flag` 的推送,因此分布式扫描也是支持的。
|
||||
|
||||
#### 暂存队列
|
||||
|
||||
当没有客户端连接的时候,RevSuit 会把 `flag` 暂时存储到队列里,当有客户端连接的时候再进行推送。因此你不必担心因为客户端与 RevSuit 断开连接就错过漏洞。(这对发现延时触发的漏洞尤为有帮助)
|
||||
|
||||
#### 使用flagGroup
|
||||
|
||||
在真实的漏洞扫描情景中,对于一个漏洞点,往往会发送许多不同的 payload,它们可能全部是有效 payload,这会导致 RevSuit
|
||||
收到很多个连接,然而这些连接背后的漏洞其实是同一个。如果你不想客户端收到代表同一个漏洞的 `flag`
|
||||
,你可以使用 `flagGroup`。
|
||||
|
||||
`flagGroup` 是被规则中的 `flagFormat` 中的匿名分组所匹配到的内容。RevSuit 将会检查 `flagGroup` 是否已经在之前的连接中被捕获过,只有当 `flagGroup`
|
||||
是首次出现时,才会推送 `flag`
|
||||
到客户端。
|
||||
|
||||
以针对 SSRF 漏洞扫描为例:
|
||||
|
||||
首先我们创建一个如下的规则:
|
||||
|
||||
```yaml
|
||||
http:
|
||||
- name: ssrf
|
||||
flag_format: (ssrf[a-z0-9]{6})[0-9]{1,3}
|
||||
rank: 0
|
||||
push_to_client: false
|
||||
notice: false
|
||||
response_status_code: "200"
|
||||
response_headers: { }
|
||||
response_body: "Here is a SSRF!"
|
||||
```
|
||||
|
||||
假设我们的目标是 `https://www.testvuln.com?url=api.com&p=useless`,并且针对 SSRF 漏洞,我们有5个
|
||||
payload,最后被扫描器发出的请求是:`['https://www.testvuln.com?url=http://revsuit.com/ssrfa98oni1&p=useless','https://www.testvuln.com?url=http://revsuit.com/ssrfa98oni2&p=useless', ... ,'https://www.testvuln.com?url=//revsuit.com/ssrfa98oni5&p=useless`']
|
||||
,它们可能全部攻击成功.
|
||||
|
||||
然而,因为我们在规则的 `flagFormat` 中使用了匿名分组,RevSuit 将会在数据库中查询连接的 `flagGroup`,在这个例子里也就是 `ssrfa98oni` ,只有当它是第一次出现时,才会向客户端推送 `flag`
|
||||
,所以客户端只会收到 `ssrfa98oni1` 这一个 `flag` ,这足以确认这个请求的 URL 参数存在漏洞了。
|
||||
|
||||
### 多模块联动
|
||||
|
||||
在实际的渗透测试场景中,一些特定的操作可以通过 RevSuit 多个模块配合,快速、简单地完成。下面以一个 Java Web 环境中 盲XXE 漏洞利用场景为例,展示如何使用 RevSuit 的 HTTP 和 FTP
|
||||
模块,配合模板变量,快速完成内网端口扫描。
|
||||
|
||||
首先创建一个 HTTP 规则来返回 evil.dtd, 使被攻击的目标去连接 RevSuit 的 FTP 模块,并且通过设置 FTP 连接的用户名和密码为模板变量来传递我们要扫描的 IP 和端口。
|
||||
|
||||

|
||||
|
||||
然后创建一个 FTP 规则,其中 `Pasv Address` 的 Host 和 Port 通过模板变量从 FTP 连接的用户名和密码动态获取。
|
||||
|
||||

|
||||
|
||||
然后使用 BurpSuit 即可进行扫描,通过设置 evil.dtd 请求中的 host 和 post 参数,来指定要扫描的目标端口:
|
||||
|
||||

|
||||
|
||||
运行效果如下:
|
||||
|
||||

|
||||
|
||||
因为在 FTP被动模式 中如果 `Passive Address` 是不可达的, FTP 的连接会中断,因此我们可以通过检查 FTP 的连接是否正常退出,来判断端口是否开放。对于上面这个例子,我们成功的探测出 8005 端口和 8080
|
||||
端口是开放的。
|
||||
|
||||
## 更多用法
|
||||
|
||||
一个更详细的 wiki 正在准备中,在此之前你可以自己探索...
|
||||
|
||||
## 反馈、建议与交流
|
||||
|
||||
提交 Issue 或者通过微信联系我`TGk0bjA2Cg==`
|
||||
|
||||
## 致谢
|
||||
|
||||
### 引用
|
||||
|
||||
本项目引用了如下优秀项目的代码:
|
||||
|
||||
* [https://github.com/rmb122/rogue_mysql_server](https://github.com/rmb122/rogue_mysql_server)
|
||||
* [https://github.com/256dpi/newdns](https://github.com/256dpi/newdns)
|
||||
|
||||
### 特别感谢
|
||||
|
||||
感谢我的朋友[@E99p1ant](https://github.com/wuhan005/) 在我开发这个项目过程中给予非常多的建议和帮助。
|
||||
|
||||
## 开源许可协议
|
||||
|
||||
@Apache License 2.0
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
.custom-icons-list[data-v-05a908c2] .anticon{margin-right:6px}body[data-v-1bc6d13e],html[data-v-1bc6d13e]{height:100%;margin:0}.fade-transform-enter-active[data-v-1bc6d13e],.fade-transform-leave-active[data-v-1bc6d13e]{transition:all .3s;opacity:0}.fade-transform-enter[data-v-1bc6d13e],.fade-transform-leave[data-v-1bc6d13e]{opacity:0}.fade-transform-enter-to[data-v-1bc6d13e]{opacity:0}.ant-menu-item>span>a[data-v-1bc6d13e]{color:hsla(0,0%,100%,.65)}.ant-menu-item-selected>span>a[data-v-1bc6d13e]{color:#fff}#app[data-v-1bc6d13e]{min-height:100%}#app .trigger[data-v-1bc6d13e]{font-size:18px;line-height:64px;padding:0 24px;cursor:pointer;transition:color .3s}#app .trigger[data-v-1bc6d13e]:hover{color:#1890ff}#app .logo[data-v-1bc6d13e]{height:32px;background:#0a1d2d;margin:16px;text-align:center;font-size:1.2rem;color:#fff;padding-bottom:5px;border-bottom:2px solid #b6befa}.copyright[data-v-1bc6d13e]{color:#888;text-align:right;margin-right:1rem}
|
||||
.custom-icons-list[data-v-05a908c2] .anticon{margin-right:6px}body[data-v-3b44ba10],html[data-v-3b44ba10]{height:100%;margin:0}.fade-transform-enter-active[data-v-3b44ba10],.fade-transform-leave-active[data-v-3b44ba10]{transition:all .3s;opacity:0}.fade-transform-enter[data-v-3b44ba10],.fade-transform-leave[data-v-3b44ba10]{opacity:0}.fade-transform-enter-to[data-v-3b44ba10]{opacity:0}.ant-menu-item>span>a[data-v-3b44ba10]{color:hsla(0,0%,100%,.65)}.ant-menu-item-selected>span>a[data-v-3b44ba10]{color:#fff}#app[data-v-3b44ba10]{min-height:100%}#app .trigger[data-v-3b44ba10]{font-size:18px;line-height:64px;padding:0 24px;cursor:pointer;transition:color .3s}#app .trigger[data-v-3b44ba10]:hover{color:#1890ff}#app .logo[data-v-3b44ba10]{height:32px;background:#0a1d2d;margin:16px;text-align:center;font-size:1.2rem;color:#fff;padding-bottom:5px;border-bottom:2px solid #b6befa}.copyright[data-v-3b44ba10]{color:#888;text-align:right;margin-right:1rem}
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+27
-4
@@ -62,7 +62,7 @@
|
||||
/>
|
||||
<div v-if="isLogMode" style="float: right; min-width:60% ;padding: 12px 0;line-height: 24px;">
|
||||
<a-row :gutter="24" type="flex">
|
||||
<a-col :span="21">
|
||||
<a-col :span="20">
|
||||
<a-form-model v-show="showSettings" ref="settings" layout="inline">
|
||||
<a-row :gutter="24" type="flex">
|
||||
<a-col :span="9" :offset="2">
|
||||
@@ -80,9 +80,23 @@
|
||||
</a-row>
|
||||
</a-form-model>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<a-col :span="4">
|
||||
<a-popconfirm
|
||||
:title="showLogNum?`This will delete all logs in the current filtered state, a total of ${this.$refs.content.pagination.total}, are you sure?`:'This will delete all logs in the current filtered state, a total of 0, are you sure?'"
|
||||
ok-text="Yes"
|
||||
cancel-text="No"
|
||||
@visibleChange="showLogNum?delayChangeShowLogNum():showLogNum=!showLogNum"
|
||||
@confirm="deleteLogs"
|
||||
>
|
||||
<a-icon slot="icon" type="question-circle-o" style="color: red"/>
|
||||
<a-icon
|
||||
:style="'font-size: 18px;padding: 12px 0;'"
|
||||
type="delete"
|
||||
@click="()=>{}"
|
||||
/>
|
||||
</a-popconfirm>
|
||||
<a-icon
|
||||
:style="'font-size: 18px;padding: 12px 0;'+(showSettings?'color: #1b90ff;':'')"
|
||||
:style="'font-size: 18px;padding:12px 15px;'+(showSettings?'color: #1b90ff;':'')"
|
||||
type="setting"
|
||||
@click="showSettings = !showSettings"
|
||||
/>
|
||||
@@ -123,6 +137,7 @@ export default {
|
||||
pageSize: store.pageSize,
|
||||
collapsed: false,
|
||||
showSettings: false,
|
||||
showLogNum: false,
|
||||
openKeys: [],
|
||||
version: "",
|
||||
};
|
||||
@@ -130,7 +145,7 @@ export default {
|
||||
computed: {
|
||||
isLogMode() {
|
||||
return this.$route.path.includes('logs')
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
timing() {
|
||||
@@ -145,6 +160,14 @@ export default {
|
||||
getVersion().then(res => {
|
||||
this.version = res.data.result
|
||||
})
|
||||
},
|
||||
deleteLogs() {
|
||||
this.$refs.content.delete()
|
||||
},
|
||||
delayChangeShowLogNum() {
|
||||
setTimeout(() => {
|
||||
this.showLogNum = !this.showLogNum
|
||||
}, 1000)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -9,6 +9,15 @@ export function getHttpRecord(params) {
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteHttpRecord(params) {
|
||||
return request({
|
||||
url: '/record/http',
|
||||
params: params,
|
||||
method: 'delete',
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
export function getDnsRecord(params) {
|
||||
return request({
|
||||
url: '/record/dns',
|
||||
@@ -17,6 +26,14 @@ export function getDnsRecord(params) {
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteDnsRecord(params) {
|
||||
return request({
|
||||
url: '/record/dns',
|
||||
params: params,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function getMysqlRecord(params) {
|
||||
return request({
|
||||
url: '/record/mysql',
|
||||
@@ -25,6 +42,14 @@ export function getMysqlRecord(params) {
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteMysqlRecord(params) {
|
||||
return request({
|
||||
url: '/record/mysql',
|
||||
params: params,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function getRmiRecord(params) {
|
||||
return request({
|
||||
url: '/record/rmi',
|
||||
@@ -33,10 +58,26 @@ export function getRmiRecord(params) {
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteRmiRecord(params) {
|
||||
return request({
|
||||
url: '/record/rmi',
|
||||
params: params,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function getFtpRecord(params) {
|
||||
return request({
|
||||
url: '/record/ftp',
|
||||
params: params,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteFtpRecord(params) {
|
||||
return request({
|
||||
url: '/record/ftp',
|
||||
params: params,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
</style>
|
||||
<script>
|
||||
|
||||
import {getDnsRecord} from '@/api/record'
|
||||
import {deleteDnsRecord, getDnsRecord} from '@/api/record'
|
||||
import {store} from '@/main'
|
||||
import FilterDropdown from '@/components/FilterDropdown'
|
||||
|
||||
@@ -145,6 +145,21 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
delete: function () {
|
||||
let params = {
|
||||
...this.filters,
|
||||
}
|
||||
this.loading = true;
|
||||
deleteDnsRecord(params).then(() => {
|
||||
this.$message.success('Deleted successfully')
|
||||
this.filters = {}
|
||||
this.fetch()
|
||||
}).catch(e => {
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Failed to delete: ' + e.response.data.error)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetch();
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
</style>
|
||||
<script>
|
||||
|
||||
import {getFtpRecord} from '@/api/record'
|
||||
import {deleteFtpRecord, getFtpRecord} from '@/api/record'
|
||||
import {store} from '@/main'
|
||||
import FilterDropdown from '@/components/FilterDropdown'
|
||||
|
||||
@@ -234,6 +234,21 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
delete: function () {
|
||||
let params = {
|
||||
...this.filters,
|
||||
}
|
||||
this.loading = true;
|
||||
deleteFtpRecord(params).then(() => {
|
||||
this.$message.success('Deleted successfully')
|
||||
this.filters = {}
|
||||
this.fetch()
|
||||
}).catch(e => {
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Failed to delete: ' + e.response.data.error)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetch();
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</style>
|
||||
<script>
|
||||
|
||||
import {getHttpRecord} from '@/api/record'
|
||||
import {getHttpRecord, deleteHttpRecord} from '@/api/record'
|
||||
import {store} from '@/main'
|
||||
import FilterDropdown from '@/components/FilterDropdown'
|
||||
|
||||
@@ -179,6 +179,21 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
delete: function () {
|
||||
let params = {
|
||||
...this.filters,
|
||||
}
|
||||
this.loading = true;
|
||||
deleteHttpRecord(params).then(() => {
|
||||
this.$message.success('Deleted successfully')
|
||||
this.filters = {}
|
||||
this.fetch()
|
||||
}).catch(e => {
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Failed to delete: ' + e.response.data.error)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetch();
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</style>
|
||||
<script>
|
||||
|
||||
import {getMysqlRecord} from '@/api/record'
|
||||
import {deleteMysqlRecord, getMysqlRecord} from '@/api/record'
|
||||
import {store} from '@/main'
|
||||
import FilterDropdown from '@/components/FilterDropdown'
|
||||
|
||||
@@ -231,6 +231,21 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
delete: function () {
|
||||
let params = {
|
||||
...this.filters,
|
||||
}
|
||||
this.loading = true;
|
||||
deleteMysqlRecord(params).then(() => {
|
||||
this.$message.success('Deleted successfully')
|
||||
this.filters = {}
|
||||
this.fetch()
|
||||
}).catch(e => {
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Failed to delete: ' + e.response.data.error)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetch();
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</style>
|
||||
<script>
|
||||
|
||||
import {getRmiRecord} from '@/api/record'
|
||||
import {deleteRmiRecord, getRmiRecord} from '@/api/record'
|
||||
import {store} from '@/main'
|
||||
import FilterDropdown from '@/components/FilterDropdown'
|
||||
|
||||
@@ -110,7 +110,8 @@ export default {
|
||||
onShowSizeChange: (current, size) => {
|
||||
store.pageSize = size
|
||||
}
|
||||
}, filters: {},
|
||||
},
|
||||
filters: {},
|
||||
order: "desc",
|
||||
loading: false,
|
||||
columns,
|
||||
@@ -146,6 +147,21 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
delete: function () {
|
||||
let params = {
|
||||
...this.filters,
|
||||
}
|
||||
this.loading = true;
|
||||
deleteRmiRecord(params).then(() => {
|
||||
this.$message.success('Deleted successfully')
|
||||
this.filters = {}
|
||||
this.fetch()
|
||||
}).catch(e => {
|
||||
if (e.response.status !== 403) {
|
||||
this.$message.error('Failed to delete: ' + e.response.data.error)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetch();
|
||||
|
||||
@@ -16,6 +16,7 @@ func Start() {
|
||||
log.ConsoleConfig{
|
||||
Level: log.LevelInfo,
|
||||
})
|
||||
defer log.Stop()
|
||||
app := &cli.App{
|
||||
Name: "RevSuit",
|
||||
Usage: "An Open-Sourced Reverse Platform Designed for Receive Various Kinds of Connection",
|
||||
|
||||
+11
-3
@@ -2,11 +2,19 @@ package database
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
var DB *gorm.DB
|
||||
var (
|
||||
DB *gorm.DB
|
||||
Driver DriverType
|
||||
)
|
||||
|
||||
func InitDB(driver, dsn string) (err error) {
|
||||
type DriverType = string
|
||||
|
||||
const Sqlite = "sqlite"
|
||||
|
||||
func InitDB(driver DriverType, dsn string) (err error) {
|
||||
Driver = driver
|
||||
switch driver {
|
||||
case "sqlite":
|
||||
case Sqlite:
|
||||
DB, err = NewSqlite3(dsn)
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -20,6 +20,7 @@ const (
|
||||
)
|
||||
|
||||
func get(url string) (b []byte, err error) {
|
||||
var resp *http.Response
|
||||
client := http.Client{
|
||||
Timeout: 90 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
@@ -28,11 +29,17 @@ func get(url string) (b []byte, err error) {
|
||||
request, _ := http.NewRequest(http.MethodGet, url, nil)
|
||||
request.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36")
|
||||
|
||||
resp, err := client.Do(request)
|
||||
resp, err = client.Do(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
log.Warn("%v", err)
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
@@ -58,7 +65,6 @@ func extractTarGz(gzipStream io.Reader) error {
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeReg:
|
||||
log.Trace(header.Name)
|
||||
if !strings.HasSuffix(header.Name, "GeoLite2-City.mmdb") {
|
||||
continue
|
||||
}
|
||||
@@ -66,10 +72,14 @@ func extractTarGz(gzipStream io.Reader) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outFile.Close()
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
_ = outFile.Close()
|
||||
return err
|
||||
}
|
||||
err = outFile.Close()
|
||||
if err != nil {
|
||||
log.Warn("%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -21,6 +21,7 @@ const (
|
||||
)
|
||||
|
||||
func get(url string) (b []byte, err error) {
|
||||
var resp *http.Response
|
||||
client := http.Client{
|
||||
Timeout: 90 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
@@ -29,11 +30,17 @@ func get(url string) (b []byte, err error) {
|
||||
request, _ := http.NewRequest("GET", url, nil)
|
||||
request.Header.Add("User-Agent", "Nali/2.1.2 (Nali CLI, https://nali.skk.moe)")
|
||||
|
||||
resp, err := client.Do(request)
|
||||
resp, err = client.Do(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
log.Warn("%v", err)
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
@@ -47,17 +54,25 @@ func getKey(b []byte) (key uint32, err error) {
|
||||
}
|
||||
|
||||
func decrypt(b []byte, key uint32) (_ []byte, err error) {
|
||||
var rc io.ReadCloser
|
||||
for i := 0; i < 0x200; i++ {
|
||||
key *= uint32(0x805)
|
||||
key++
|
||||
key &= uint32(0xff)
|
||||
b[i] = b[i] ^ byte(key)
|
||||
}
|
||||
rc, err := zlib.NewReader(bytes.NewBuffer(b))
|
||||
rc, err = zlib.NewReader(bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
defer func(rc io.ReadCloser) {
|
||||
err := rc.Close()
|
||||
if err != nil {
|
||||
log.Warn("%v", err)
|
||||
}
|
||||
}(rc)
|
||||
|
||||
return io.ReadAll(rc)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ type Record interface {
|
||||
}
|
||||
|
||||
type BaseRecord struct {
|
||||
Record `gorm:"-" json:"-"`
|
||||
Record `gorm:"-" json:"-" notice:"-"`
|
||||
ID uint `gorm:"primarykey" form:"id" json:"id" notice:"-"`
|
||||
RuleName string `gorm:"index" form:"rule_name" json:"rule_name" notice:"rule"`
|
||||
Flag string `gorm:"index" form:"flag" json:"flag" `
|
||||
|
||||
+29
-1
@@ -1,6 +1,7 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -8,6 +9,8 @@ import (
|
||||
"github.com/li4n0/revsuit/internal/database"
|
||||
"github.com/li4n0/revsuit/internal/notice"
|
||||
"github.com/li4n0/revsuit/internal/record"
|
||||
"gorm.io/gorm"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
var _ record.Record = (*Record)(nil)
|
||||
@@ -41,7 +44,7 @@ func newRecord(rule *Rule, flag, domain, remoteIp, ipArea string) (r *Record, er
|
||||
return r, database.DB.Create(r).Error
|
||||
}
|
||||
|
||||
func ListRecords(c *gin.Context) {
|
||||
func Records(c *gin.Context) {
|
||||
var (
|
||||
dnsRecord Record
|
||||
res []Record
|
||||
@@ -80,6 +83,31 @@ func ListRecords(c *gin.Context) {
|
||||
db.Where("rule_name = ?", dnsRecord.RuleName)
|
||||
}
|
||||
|
||||
//Delete records
|
||||
if c.Request.Method == http.MethodDelete {
|
||||
if err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&res).Error; err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"status": "failed",
|
||||
"error": err.Error(),
|
||||
"data": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if database.Driver == database.Sqlite {
|
||||
db.Exec("VACUUM")
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "succeed",
|
||||
"error": nil,
|
||||
})
|
||||
|
||||
log.Info("%d dns records deleted by %s", db.RowsAffected, c.Request.RemoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
//List records
|
||||
page, err := strconv.Atoi(c.Query("page"))
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
|
||||
+28
-1
@@ -1,6 +1,7 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/li4n0/revsuit/internal/ipinfo"
|
||||
"github.com/li4n0/revsuit/internal/notice"
|
||||
"github.com/li4n0/revsuit/internal/record"
|
||||
"gorm.io/gorm"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
@@ -53,7 +55,7 @@ func NewRecord(rule *Rule, flag, user, password, method, path, ip, area string,
|
||||
return r, database.DB.Create(r).Error
|
||||
}
|
||||
|
||||
func ListRecords(c *gin.Context) {
|
||||
func Records(c *gin.Context) {
|
||||
var (
|
||||
ftpRecord Record
|
||||
res []Record
|
||||
@@ -105,6 +107,31 @@ func ListRecords(c *gin.Context) {
|
||||
db.Where("rule_name = ?", ftpRecord.RuleName)
|
||||
}
|
||||
|
||||
//Delete records
|
||||
if c.Request.Method == http.MethodDelete {
|
||||
if err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&res).Error; err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"status": "failed",
|
||||
"error": err.Error(),
|
||||
"data": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if database.Driver == database.Sqlite {
|
||||
db.Exec("VACUUM")
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "succeed",
|
||||
"error": nil,
|
||||
})
|
||||
|
||||
log.Info("%d ftp records deleted by %s", db.RowsAffected, c.Request.RemoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
//List records
|
||||
page, err := strconv.Atoi(c.Query("page"))
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
|
||||
+2
-1
@@ -302,10 +302,11 @@ func (s *Server) Run() {
|
||||
s.listener, err = vmysql.NewListener("tcp", s.Addr, authServer, s, s.VersionString, 0, 0)
|
||||
if err != nil {
|
||||
log.Error("New MySQL Server failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
|
||||
s.livingLock.Lock()
|
||||
if !s.Enable {
|
||||
s.listener.Close()
|
||||
}
|
||||
|
||||
+28
-1
@@ -1,6 +1,7 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -9,6 +10,8 @@ import (
|
||||
"github.com/li4n0/revsuit/internal/file"
|
||||
"github.com/li4n0/revsuit/internal/notice"
|
||||
"github.com/li4n0/revsuit/internal/record"
|
||||
"gorm.io/gorm"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
var _ record.Record = (*Record)(nil)
|
||||
@@ -51,7 +54,7 @@ func newRecord(rule *Rule, flag, username, schema, clientName, clientOS, remoteI
|
||||
return r, database.DB.Create(r).Error
|
||||
}
|
||||
|
||||
func ListRecords(c *gin.Context) {
|
||||
func Records(c *gin.Context) {
|
||||
var (
|
||||
mysqlRecord Record
|
||||
res []Record
|
||||
@@ -103,6 +106,30 @@ func ListRecords(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
//Delete records
|
||||
if c.Request.Method == http.MethodDelete {
|
||||
if err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&res).Error; err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"status": "failed",
|
||||
"error": err.Error(),
|
||||
"data": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if database.Driver == database.Sqlite {
|
||||
db.Exec("VACUUM")
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "succeed",
|
||||
"error": nil,
|
||||
})
|
||||
log.Info("%d mysql records deleted by %s", db.RowsAffected, c.Request.RemoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
//List records
|
||||
page, err := strconv.Atoi(c.Query("page"))
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
|
||||
+29
-2
@@ -1,6 +1,7 @@
|
||||
package rhttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -8,6 +9,8 @@ import (
|
||||
"github.com/li4n0/revsuit/internal/database"
|
||||
"github.com/li4n0/revsuit/internal/notice"
|
||||
"github.com/li4n0/revsuit/internal/record"
|
||||
"gorm.io/gorm"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
var _ record.Record = (*Record)(nil)
|
||||
@@ -44,7 +47,7 @@ func NewRecord(rule *Rule, flag, method, url, ip, area, raw string) (r *Record,
|
||||
return r, database.DB.Create(r).Error
|
||||
}
|
||||
|
||||
func ListRecords(c *gin.Context) {
|
||||
func Records(c *gin.Context) {
|
||||
var (
|
||||
httpRecord Record
|
||||
res []Record
|
||||
@@ -78,7 +81,7 @@ func ListRecords(c *gin.Context) {
|
||||
db.Where("method = ?", httpRecord.Method)
|
||||
}
|
||||
if httpRecord.URI != "" {
|
||||
db.Where("path like ?", "%"+httpRecord.URI+"%")
|
||||
db.Where("uri like ?", "%"+httpRecord.URI+"%")
|
||||
}
|
||||
if httpRecord.RemoteIP != "" {
|
||||
db.Where("remote_ip = ?", httpRecord.RemoteIP)
|
||||
@@ -87,6 +90,30 @@ func ListRecords(c *gin.Context) {
|
||||
db.Where("rule_name = ?", httpRecord.RuleName)
|
||||
}
|
||||
|
||||
//Delete records
|
||||
if c.Request.Method == http.MethodDelete {
|
||||
if err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&res).Error; err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"status": "failed",
|
||||
"error": err.Error(),
|
||||
"data": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if database.Driver == database.Sqlite {
|
||||
db.Exec("VACUUM")
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "succeed",
|
||||
"error": nil,
|
||||
})
|
||||
log.Info("%d http records deleted by %s", db.RowsAffected, c.Request.RemoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
//List records
|
||||
page, err := strconv.Atoi(c.Query("page"))
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
|
||||
+29
-1
@@ -1,6 +1,7 @@
|
||||
package rmi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -8,6 +9,8 @@ import (
|
||||
"github.com/li4n0/revsuit/internal/database"
|
||||
"github.com/li4n0/revsuit/internal/notice"
|
||||
"github.com/li4n0/revsuit/internal/record"
|
||||
"gorm.io/gorm"
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
var _ record.Record = (*Record)(nil)
|
||||
@@ -40,7 +43,7 @@ func NewRecord(rule *Rule, flag, path, ip, area string) (r *Record, err error) {
|
||||
return r, database.DB.Create(r).Error
|
||||
}
|
||||
|
||||
func ListRecords(c *gin.Context) {
|
||||
func Records(c *gin.Context) {
|
||||
var (
|
||||
rmiRecord Record
|
||||
res []Record
|
||||
@@ -80,6 +83,31 @@ func ListRecords(c *gin.Context) {
|
||||
db.Where("rule_name = ?", rmiRecord.RuleName)
|
||||
}
|
||||
|
||||
//Delete records
|
||||
if c.Request.Method == http.MethodDelete {
|
||||
if err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&res).Error; err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"status": "failed",
|
||||
"error": err.Error(),
|
||||
"data": nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if database.Driver == database.Sqlite {
|
||||
db.Exec("VACUUM")
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": "succeed",
|
||||
"error": nil,
|
||||
})
|
||||
|
||||
log.Info("%d rmi records deleted by %s", db.RowsAffected, c.Request.RemoteAddr)
|
||||
return
|
||||
}
|
||||
|
||||
//List records
|
||||
page, err := strconv.Atoi(c.Query("page"))
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
|
||||
+10
-5
@@ -77,19 +77,24 @@ func (revsuit *Revsuit) registerHttpRouter() {
|
||||
recordGroup := revsuit.http.ApiGroup.Group("/record")
|
||||
|
||||
httpGroup := recordGroup.Group("/http")
|
||||
httpGroup.GET("", rhttp.ListRecords)
|
||||
httpGroup.GET("", rhttp.Records)
|
||||
httpGroup.DELETE("", rhttp.Records)
|
||||
|
||||
dnsGroup := recordGroup.Group("/dns")
|
||||
dnsGroup.GET("", dns.ListRecords)
|
||||
dnsGroup.GET("", dns.Records)
|
||||
dnsGroup.DELETE("", dns.Records)
|
||||
|
||||
mysqlGroup := recordGroup.Group("/mysql")
|
||||
mysqlGroup.GET("", mysql.ListRecords)
|
||||
mysqlGroup.GET("", mysql.Records)
|
||||
mysqlGroup.DELETE("", mysql.Records)
|
||||
|
||||
rmiGroup := recordGroup.Group("/rmi")
|
||||
rmiGroup.GET("", rmi.ListRecords)
|
||||
rmiGroup.GET("", rmi.Records)
|
||||
rmiGroup.DELETE("", rmi.Records)
|
||||
|
||||
ftpGroup := recordGroup.Group("/ftp")
|
||||
ftpGroup.GET("", ftp.ListRecords)
|
||||
ftpGroup.GET("", ftp.Records)
|
||||
ftpGroup.DELETE("", ftp.Records)
|
||||
|
||||
// init rule router group
|
||||
ruleGroup := revsuit.http.ApiGroup.Group("/rule")
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
log "unknwon.dev/clog/v2"
|
||||
)
|
||||
|
||||
const VERSION = "0.1.5-beta"
|
||||
const VERSION = "0.2.0"
|
||||
|
||||
type Revsuit struct {
|
||||
config *Config
|
||||
@@ -60,7 +60,7 @@ func initDatabase(dsn string) {
|
||||
Level: log.LevelInfo,
|
||||
})
|
||||
|
||||
err := database.InitDB("sqlite", dsn)
|
||||
err := database.InitDB(database.Sqlite, dsn)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user