├── server
├── db
│ ├── __init__.py
│ ├── redis_db.py
│ ├── mongo_db.py
│ └── mongo_create_indexes.js
├── proxy
│ ├── __init__.py
│ ├── proxy_server.py
│ ├── http_mitm_script.py
│ └── socks5_mitm_script.py
├── utils
│ ├── __init__.py
│ ├── log.py
│ ├── common.py
│ ├── proxypriv.py
│ ├── ProtocUtil.py
│ └── repeater.py
├── task_server
│ ├── __init__.py
│ ├── base_server.py
│ ├── task.py
│ └── match.py
├── web_server
│ └── __init__.py
├── __init__.py
├── config
│ ├── config.ini
│ └── log.conf
├── requirements.txt
└── app.py
├── CONTRIBUTORS
├── front
├── dist
│ ├── favicon.ico
│ ├── screenshot
│ │ ├── 首页.png
│ │ ├── 准入代理.png
│ │ ├── 历史记录.png
│ │ ├── HTTP拦截.png
│ │ ├── HTTP记录.png
│ │ ├── HTTP重放.png
│ │ └── TCPWS测试.png
│ ├── fonts
│ │ ├── ionicons.99ac3308.woff
│ │ ├── ionicons.d535a25a.ttf
│ │ └── ionicons.143146fa.woff2
│ ├── css
│ │ └── app.4e2eb579.css
│ └── index.html
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── assets
│ │ ├── logo.png
│ │ ├── screenshot
│ │ │ ├── 首页.png
│ │ │ ├── 准入代理.png
│ │ │ ├── 历史记录.png
│ │ │ ├── HTTP拦截.png
│ │ │ ├── HTTP记录.png
│ │ │ ├── HTTP重放.png
│ │ │ └── TCPWS测试.png
│ │ └── usage.md
│ ├── components
│ │ ├── Manual
│ │ │ └── Index.vue
│ │ ├── Imws
│ │ │ ├── components
│ │ │ │ └── LinkTable.vue
│ │ │ └── Index.vue
│ │ ├── MainConnDrawer.vue
│ │ ├── Intercept
│ │ │ └── Index.vue
│ │ ├── History
│ │ │ └── Index.vue
│ │ └── Logger
│ │ │ └── Index.vue
│ ├── main.js
│ ├── router.js
│ ├── utils
│ │ ├── logger.js
│ │ ├── api.js
│ │ └── tools.js
│ └── App.vue
├── babel.config.js
├── README.md
├── vue.config.js
└── package.json
├── .mitmproxy
├── mitmproxy-ca.p12
├── mitmproxy-ca-cert.p12
├── mitmproxy-dhparam.pem
├── mitmproxy-ca-cert.cer
├── mitmproxy-ca-cert.pem
└── mitmproxy-ca.pem
├── Dockerfile
├── CODEVIEW
├── .gitignore
├── docker-compose.yml
├── README.md
├── usage.md
└── README-EN.md
/server/db/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/proxy/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/utils/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/task_server/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server/web_server/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CONTRIBUTORS:
--------------------------------------------------------------------------------
1 | 王彤哲
2 | 邓海辉
3 | 刘儒学
4 | 张亮
--------------------------------------------------------------------------------
/server/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | import sys
3 | import os
4 | sys.path.append(os.path)
--------------------------------------------------------------------------------
/front/dist/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/favicon.ico
--------------------------------------------------------------------------------
/front/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/public/favicon.ico
--------------------------------------------------------------------------------
/front/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/src/assets/logo.png
--------------------------------------------------------------------------------
/.mitmproxy/mitmproxy-ca.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/.mitmproxy/mitmproxy-ca.p12
--------------------------------------------------------------------------------
/front/dist/screenshot/首页.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/screenshot/首页.png
--------------------------------------------------------------------------------
/front/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/front/dist/screenshot/准入代理.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/screenshot/准入代理.png
--------------------------------------------------------------------------------
/front/dist/screenshot/历史记录.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/screenshot/历史记录.png
--------------------------------------------------------------------------------
/.mitmproxy/mitmproxy-ca-cert.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/.mitmproxy/mitmproxy-ca-cert.p12
--------------------------------------------------------------------------------
/front/dist/screenshot/HTTP拦截.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/screenshot/HTTP拦截.png
--------------------------------------------------------------------------------
/front/dist/screenshot/HTTP记录.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/screenshot/HTTP记录.png
--------------------------------------------------------------------------------
/front/dist/screenshot/HTTP重放.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/screenshot/HTTP重放.png
--------------------------------------------------------------------------------
/front/dist/screenshot/TCPWS测试.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/screenshot/TCPWS测试.png
--------------------------------------------------------------------------------
/front/src/assets/screenshot/首页.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/src/assets/screenshot/首页.png
--------------------------------------------------------------------------------
/front/src/assets/screenshot/准入代理.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/src/assets/screenshot/准入代理.png
--------------------------------------------------------------------------------
/front/src/assets/screenshot/历史记录.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/src/assets/screenshot/历史记录.png
--------------------------------------------------------------------------------
/front/dist/fonts/ionicons.99ac3308.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/fonts/ionicons.99ac3308.woff
--------------------------------------------------------------------------------
/front/dist/fonts/ionicons.d535a25a.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/fonts/ionicons.d535a25a.ttf
--------------------------------------------------------------------------------
/front/src/assets/screenshot/HTTP拦截.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/src/assets/screenshot/HTTP拦截.png
--------------------------------------------------------------------------------
/front/src/assets/screenshot/HTTP记录.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/src/assets/screenshot/HTTP记录.png
--------------------------------------------------------------------------------
/front/src/assets/screenshot/HTTP重放.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/src/assets/screenshot/HTTP重放.png
--------------------------------------------------------------------------------
/front/src/assets/screenshot/TCPWS测试.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/src/assets/screenshot/TCPWS测试.png
--------------------------------------------------------------------------------
/front/dist/fonts/ionicons.143146fa.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/momosecurity/Mloger/HEAD/front/dist/fonts/ionicons.143146fa.woff2
--------------------------------------------------------------------------------
/server/utils/log.py:
--------------------------------------------------------------------------------
1 | import logging.config
2 |
3 | logging.config.fileConfig("./config/log.conf")
4 | logger = logging.getLogger('log')
--------------------------------------------------------------------------------
/server/config/config.ini:
--------------------------------------------------------------------------------
1 | [web]
2 | listen_host=0.0.0.0
3 | listen_port=8000
4 | debug=0
5 |
6 | [mitm]
7 | http_switch=1
8 | http_port=8081
9 | socks5_switch=1
10 | socks5_port=8082
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker.io/centos/python-38-centos7:latest
2 | WORKDIR /home/mloger
3 | COPY . .
4 | COPY ./.mitmproxy /opt/app-root/src/.mitmproxy
5 | USER root
6 | WORKDIR /home/mloger/server
7 | RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple && mkdir /home/mloger/server/log/ && touch /home/mloger/server/log/debug.log
8 | EXPOSE 8000 8081 8082
9 | CMD ["python","app.py"]
--------------------------------------------------------------------------------
/front/README.md:
--------------------------------------------------------------------------------
1 | # web_mloger
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/server/requirements.txt:
--------------------------------------------------------------------------------
1 | flask_mongoengine==1.0.0
2 | hyper==0.5.0
3 | netaddr==0.8.0
4 | cachetools==4.2.4
5 | Flask_Login==0.5.0
6 | mitmproxy==7.0.4
7 | pymongo==3.12.2
8 | mongoengine==0.23.1
9 | requests==2.26.0
10 | Flask_HTTPAuth==4.5.0
11 | Flask-Session==0.4.0
12 | urllib3==1.26.7
13 | itsdangerous==2.0.1
14 | Jinja2==3.0.3
15 | werkzeug==2.0.2
16 | Flask==1.1.2
17 | Flask_SocketIO==5.1.1
18 | gevent==21.8.0
19 | gevent-websocket==0.10.1
20 | redis==4.0.2
--------------------------------------------------------------------------------
/front/vue.config.js:
--------------------------------------------------------------------------------
1 | // vue.config.js
2 |
3 | /**
4 | * @type {import('@vue/cli-service').ProjectOptions}
5 | */
6 | module.exports = {
7 | chainWebpack: config => {
8 | config.module.rule('md')
9 | .test(/\.md/)
10 | .use('vue-loader')
11 | .loader('vue-loader')
12 | .end()
13 | .use('vue-markdown-loader')
14 | .loader('vue-markdown-loader/lib/markdown-compiler')
15 | .options({
16 | raw: true
17 | })
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/CODEVIEW:
--------------------------------------------------------------------------------
1 | # 代码目录简介
2 |
3 | # 主要功能位于server目录下
4 |
5 |
6 | ├── front # 前端
7 | │ ├── dist # 编译后前端项目
8 | │ └── src # 源码
9 | │ ├── assets # 资源
10 | │ ├── components # 页面组件
11 | │ └── utils
12 | │
13 | └── server # 后端
14 | ├── config # 配置文件
15 | ├── db # 数据库配置与连接
16 | ├── log # 日志
17 | ├── proxy # 代理脚本启动
18 | ├── task_server # 常驻服务
19 | ├── utils # 一些工具类
20 | ├── web_server # web服务
21 | └── app.py # 主功能入口
--------------------------------------------------------------------------------
/server/utils/common.py:
--------------------------------------------------------------------------------
1 | # 通用黑名单配置
2 | import re
3 |
4 | file_type_black_list = (
5 | '.zip', '.apk', '.mp4', '.mp3', '.rar', '.tar.gz', '.exe', '.gif', '.jpg', '.png', '.ico', '.css', '.woff', '.svg',
6 | '.pdf', '.patch', '.webpp', '.webp', '.svga')
7 |
8 | domain_black_list = ('google.com', 'firefox.com')
9 |
10 |
11 | # URL remove duplicates
12 | def replaceD(path):
13 | # 替换以数字结尾
14 | path = re.sub(r'/([\-\._]?(\d+)[\-\._]?)+$', r'/d+', path)
15 | # 替换path中数字
16 | path = re.sub(r'/([\-\._]?(\d+)[\-\._]?)+/', r'/d+/', path)
17 | return path
18 |
--------------------------------------------------------------------------------
/front/src/components/Manual/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
20 |
21 |
--------------------------------------------------------------------------------
/front/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/front/dist/css/app.4e2eb579.css:
--------------------------------------------------------------------------------
1 | .ivu-table .table-logger-ws-row td{height:18px;line-height:1.2}.ivu-card[data-v-35183e52] .ivu-card-head{border-bottom:none}.ivu-card[data-v-35183e52] .ivu-card-body{padding:0 16px 16px 16px}.ivu-table .table-history-record-row div{padding:0;overflow:hidden;text-overflow:ellipsis;white-space:normal}.ivu-btn-primary[data-v-7d564bd3],.ivu-btn-primary[data-v-17aadc08],.ivu-btn-primary[data-v-c70a7df4]{background-color:#547eff}.markdown-body[data-v-e5cf0b96]{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}.demo-drawer-profile[data-v-2aa2f148]{font-size:14px}.demo-drawer-profile .ivu-col[data-v-2aa2f148]{margin-bottom:12px}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;margin-top:60px}
--------------------------------------------------------------------------------
/.mitmproxy/mitmproxy-dhparam.pem:
--------------------------------------------------------------------------------
1 |
2 | -----BEGIN DH PARAMETERS-----
3 | MIICCAKCAgEAyT6LzpwVFS3gryIo29J5icvgxCnCebcdSe/NHMkD8dKJf8suFCg3
4 | O2+dguLakSVif/t6dhImxInJk230HmfC8q93hdcg/j8rLGJYDKu3ik6H//BAHKIv
5 | j5O9yjU3rXCfmVJQic2Nne39sg3CreAepEts2TvYHhVv3TEAzEqCtOuTjgDv0ntJ
6 | Gwpj+BJBRQGG9NvprX1YGJ7WOFBP/hWU7d6tgvE6Xa7T/u9QIKpYHMIkcN/l3ZFB
7 | chZEqVlyrcngtSXCROTPcDOQ6Q8QzhaBJS+Z6rcsd7X+haiQqvoFcmaJ08Ks6LQC
8 | ZIL2EtYJw8V8z7C0igVEBIADZBI6OTbuuhDwRw//zU1uq52Oc48CIZlGxTYG/Evq
9 | o9EWAXUYVzWkDSTeBH1r4z/qLPE2cnhtMxbFxuvK53jGB0emy2y1Ei6IhKshJ5qX
10 | IB/aE7SSHyQ3MDHHkCmQJCsOd4Mo26YX61NZ+n501XjqpCBQ2+DfZCBh8Va2wDyv
11 | A2Ryg9SUz8j0AXViRNMJgJrr446yro/FuJZwnQcO3WQnXeqSBnURqKjmqkeFP+d8
12 | 6mk2tqJaY507lRNqtGlLnj7f5RNoBFJDCLBNurVgfvq9TCVWKDIFD4vZRjCrnl6I
13 | rD693XKIHUCWOjMh1if6omGXKHH40QuME2gNa50+YPn1iYDl88uDbbMCAQI=
14 | -----END DH PARAMETERS-----
15 |
--------------------------------------------------------------------------------
/server/task_server/base_server.py:
--------------------------------------------------------------------------------
1 | """
2 | Base threading server class
3 | """
4 |
5 | from threading import Thread
6 |
7 |
8 | class ThreadServer:
9 |
10 | def __init__(self):
11 | self.server_thread = None
12 | self.running = False
13 |
14 | def start(self, *args, **kwargs):
15 | if self.running:
16 | return
17 | self.running = True
18 | self.server_thread = Thread(target=self.run, args=args, kwargs=kwargs)
19 | self.server_thread.start()
20 |
21 | def stop(self):
22 | self.running = False
23 | # TODO terminate self.server_thread
24 |
25 | def run(self):
26 | """
27 | Server main function
28 | """
29 | pass
30 |
31 |
32 | class StaticServer:
33 |
34 | def start(self, *args, **kwargs):
35 | pass
36 |
37 | def stop(self):
38 | pass
--------------------------------------------------------------------------------
/front/dist/index.html:
--------------------------------------------------------------------------------
1 | mloger
--------------------------------------------------------------------------------
/server/config/log.conf:
--------------------------------------------------------------------------------
1 | [loggers]
2 | keys=root,log
3 |
4 | [handlers]
5 | keys=consoleHandler,fileHandler
6 |
7 | [formatters]
8 | keys=messageFormatter,simpleFormatter,niceFormatter
9 |
10 | [formatter_messageFormatter]
11 | format=%(asctime)s %(message)s
12 |
13 | [formatter_simpleFormatter]
14 | format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
15 |
16 | [formatter_niceFormatter]
17 | format=%(process)d|%(asctime)s|%(levelname)s|%(name)s|%(filename)s|%(module)s|%(lineno)s|%(message)s
18 |
19 | [logger_root]
20 | level=INFO
21 | handlers=
22 |
23 | [logger_log]
24 | qualname=log
25 | level=DEBUG
26 | handlers=consoleHandler,fileHandler
27 |
28 | [handler_consoleHandler]
29 | class=StreamHandler
30 | level=DEBUG
31 | formatter=niceFormatter
32 | args=(sys.stdout,)
33 |
34 | [handler_fileHandler]
35 | class=FileHandler
36 | level=DEBUG
37 | formatter=niceFormatter
38 | args=('./log/debug.log', 'a')
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 | *.py[cod]
8 | *$py.class
9 |
10 | # Distribution / packaging
11 | .Python
12 | build/
13 | develop-eggs/
14 |
15 | # dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | .hypothesis/
50 | .pytest_cache/
51 |
52 | # local env files
53 | .env.local
54 | .env.*.local
55 | venv/
56 |
57 | # Log files
58 | **/*.log
59 | npm-debug.log*
60 | yarn-debug.log*
61 | yarn-error.log*
62 | pnpm-debug.log*
63 |
64 | # Editor directories and files
65 | .idea
66 | .vscode
67 | *.suo
68 | *.ntvs*
69 | *.njsproj
70 | *.sln
71 | *.sw?
72 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | web:
4 | build:
5 | context: .
6 | dockerfile: Dockerfile
7 | ports:
8 | - "8000:8000"
9 | - "8081:8081"
10 | - "8082:8082"
11 | depends_on:
12 | - redis
13 | - mongo
14 | environment:
15 | PROJECT_ENV: prod
16 | logging:
17 | driver: json-file
18 | options:
19 | max-size: "1m"
20 | max-file: "2"
21 | redis:
22 | image: "redis:alpine"
23 | restart: always
24 | command: redis-server --requirepass redis_password
25 | volumes:
26 | - "/tmp/redis:/data"
27 | logging:
28 | driver: json-file
29 | options:
30 | max-size: "1m"
31 | max-file: "2"
32 | mongo:
33 | image: "mongo:5.0.6"
34 | restart: always
35 | volumes:
36 | - "./server/db/mongo_create_indexes.js:/docker-entrypoint-initdb.d/mongo_create_indexes.js"
37 | - "/tmp/mongo:/data/db"
38 | environment:
39 | MONGO_INITDB_DATABASE: mloger
40 | MONGO_INITDB_ROOT_USERNAME: root
41 | MONGO_INITDB_ROOT_PASSWORD: root_pwd
42 | logging:
43 | driver: json-file
44 | options:
45 | max-size: "1m"
46 | max-file: "2"
--------------------------------------------------------------------------------
/.mitmproxy/mitmproxy-ca-cert.cer:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDNTCCAh2gAwIBAgIUIRJiCWXD1cMOZyUpCPSg5AYm7nkwDQYJKoZIhvcNAQEL
3 | BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN
4 | MjIwMjIxMDg0NjI5WhcNMzIwMjIxMDg0NjI5WjAoMRIwEAYDVQQDDAltaXRtcHJv
5 | eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
6 | AQoCggEBAL/Y+8E49GuUJemX6oxzmuKN28zzpJVu1eATz4Lz3C60lwgyKk+z0sFE
7 | wOJVq+tr2hZOUqUTkWAY4A/i3t+uVdNZPf4p9Dd7wBAzsPPNdt4D7kbO+hbZFTAZ
8 | v7Ry5CgFG6772Q1KybAK5nHJZKm4qBX+FIjdDEcOkfocTj4Qz1W/cd+KkYdBxcoZ
9 | 07GtWKQduhjy19t1zUiVODzrutAjoXBONVUNWiNzTntwjTffoJf4sTjYuXbmXB00
10 | oXJm8oesQ0L2OiNvWwVdeUZWg//eT+eQsJ6rBbHL4nLoMH7HHn1Xt9XjvENE3EzV
11 | b/ueIYjv91wnRveqPZwZA2z1Hzu8NBECAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB
12 | /zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
13 | FI5FnuJUK3+lvU4Zw6TNa4GinFp4MA0GCSqGSIb3DQEBCwUAA4IBAQBK3iCqyo1w
14 | p6x7HzvFw7No5K8xt6d19ALXO5rtWnn+KCfAShqXuuN7FOhV4IaHdfIBdwB1RK+I
15 | Bjjfpq7bPLW5YUZ3BtaNhJ4tjb7x00XIvninEUJ9k8kqx8v7jtnGwNSs19IcCR8F
16 | P7UznbPnrayMA1tW8V32onAgbhWvoSOmPdz9FUvoykzGZV4cwV/7M+CEYhUiNees
17 | J1JGifjYaxzQ95n50ISFR/q9lR9b79Ch9rJ4IWv4B1LdREA3/vW2oyuIqWl4JaVs
18 | nzM4jjhfDQhoI9fYaHdfWR3w5+BL0Dakt2ehOriaIvsE7kw+hYEWYhAXtIRPSsuc
19 | yIV+a9LaMd6G
20 | -----END CERTIFICATE-----
21 |
--------------------------------------------------------------------------------
/.mitmproxy/mitmproxy-ca-cert.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDNTCCAh2gAwIBAgIUIRJiCWXD1cMOZyUpCPSg5AYm7nkwDQYJKoZIhvcNAQEL
3 | BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN
4 | MjIwMjIxMDg0NjI5WhcNMzIwMjIxMDg0NjI5WjAoMRIwEAYDVQQDDAltaXRtcHJv
5 | eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
6 | AQoCggEBAL/Y+8E49GuUJemX6oxzmuKN28zzpJVu1eATz4Lz3C60lwgyKk+z0sFE
7 | wOJVq+tr2hZOUqUTkWAY4A/i3t+uVdNZPf4p9Dd7wBAzsPPNdt4D7kbO+hbZFTAZ
8 | v7Ry5CgFG6772Q1KybAK5nHJZKm4qBX+FIjdDEcOkfocTj4Qz1W/cd+KkYdBxcoZ
9 | 07GtWKQduhjy19t1zUiVODzrutAjoXBONVUNWiNzTntwjTffoJf4sTjYuXbmXB00
10 | oXJm8oesQ0L2OiNvWwVdeUZWg//eT+eQsJ6rBbHL4nLoMH7HHn1Xt9XjvENE3EzV
11 | b/ueIYjv91wnRveqPZwZA2z1Hzu8NBECAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB
12 | /zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
13 | FI5FnuJUK3+lvU4Zw6TNa4GinFp4MA0GCSqGSIb3DQEBCwUAA4IBAQBK3iCqyo1w
14 | p6x7HzvFw7No5K8xt6d19ALXO5rtWnn+KCfAShqXuuN7FOhV4IaHdfIBdwB1RK+I
15 | Bjjfpq7bPLW5YUZ3BtaNhJ4tjb7x00XIvninEUJ9k8kqx8v7jtnGwNSs19IcCR8F
16 | P7UznbPnrayMA1tW8V32onAgbhWvoSOmPdz9FUvoykzGZV4cwV/7M+CEYhUiNees
17 | J1JGifjYaxzQ95n50ISFR/q9lR9b79Ch9rJ4IWv4B1LdREA3/vW2oyuIqWl4JaVs
18 | nzM4jjhfDQhoI9fYaHdfWR3w5+BL0Dakt2ehOriaIvsE7kw+hYEWYhAXtIRPSsuc
19 | yIV+a9LaMd6G
20 | -----END CERTIFICATE-----
21 |
--------------------------------------------------------------------------------
/front/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router';
3 | import App from './App.vue';
4 | import Routers from './router.js';
5 | import ViewUI from 'view-design';
6 | import 'view-design/dist/styles/iview.css';
7 |
8 | Vue.config.productionTip = false
9 |
10 | Vue.use(VueRouter);
11 | Vue.use(ViewUI);
12 |
13 | const RouterConfig = {
14 | routes: Routers
15 | };
16 | const router = new VueRouter(RouterConfig);
17 |
18 | new Vue({
19 | el: '#app',
20 | router: router,
21 | render: h => h(App)
22 | });
23 |
24 | router.beforeEach((to, from, next) => {
25 | if (from && from.name === 'logger') {
26 | let query = ''
27 | if (to.name === 'intercept') {
28 | let logger = localStorage.getItem('loggerPage')
29 | if (logger) {
30 | logger = JSON.parse(logger)
31 | if (logger.ip && logger.ip.length > 0) {
32 | query = `?ip=${logger.ip}`
33 | }
34 | }
35 | }
36 | window.open(`/#${to.fullPath}${query}`, '_blank')
37 | return
38 | }
39 | if (!to.name) {
40 | next({
41 | name: 'logger'
42 | })
43 | } else {
44 | document.title = "MLOGER - " + (to.meta.title || to.name);
45 | next()
46 | }
47 | })
--------------------------------------------------------------------------------
/front/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mloger",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "axios": "^0.24.0",
12 | "core-js": "^3.6.5",
13 | "diff-match-patch": "^1.0.5",
14 | "github-markdown-css": "^5.1.0",
15 | "socket.io-client": "^4.4.0",
16 | "view-design": "~4.7.0",
17 | "vue": "^2.6.11",
18 | "vue-clipboard2": "^0.3.3",
19 | "vue-markdown-loader": "^2.5.0",
20 | "vue-router": "^3.5.3"
21 | },
22 | "devDependencies": {
23 | "@vue/cli-plugin-babel": "~4.5.0",
24 | "@vue/cli-plugin-eslint": "~4.5.0",
25 | "@vue/cli-service": "~4.5.0",
26 | "babel-eslint": "^10.1.0",
27 | "eslint": "^6.7.2",
28 | "eslint-plugin-vue": "^6.2.2",
29 | "vue-template-compiler": "^2.6.11"
30 | },
31 | "eslintConfig": {
32 | "root": true,
33 | "env": {
34 | "node": true
35 | },
36 | "extends": [
37 | "plugin:vue/essential",
38 | "eslint:recommended"
39 | ],
40 | "parserOptions": {
41 | "parser": "babel-eslint"
42 | },
43 | "rules": {}
44 | },
45 | "browserslist": [
46 | "> 1%",
47 | "last 2 versions",
48 | "not dead"
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/front/src/router.js:
--------------------------------------------------------------------------------
1 | import Intercept from './components/Intercept/Index'
2 | import Logger from './components/Logger/Index'
3 | import Repeater from './components/Repeater/Index'
4 | import IMWS from './components/Imws/Index'
5 | import SearchHistory from './components/History/Index'
6 | import Manual from './components/Manual/Index'
7 |
8 |
9 | export default [
10 | {
11 | name: 'index',
12 | path: '/',
13 | title: 'MLOGER',
14 | redirect: {name: 'logger'},
15 | isTheme: true
16 | },
17 | {
18 | path: '/intercept',
19 | name: 'intercept',
20 | title: 'HTTP拦截',
21 | component: Intercept
22 | },
23 | {
24 | path: '/logger',
25 | name: 'logger',
26 | title: 'HTTP记录',
27 | component: Logger
28 | },
29 | {
30 | path: '/repeater',
31 | name: 'repeater',
32 | title: 'HTTP重放',
33 | component: Repeater
34 | },
35 | {
36 | path: '/imws',
37 | name: 'imws',
38 | title: 'TCP/WS测试',
39 | component: IMWS
40 | },
41 | {
42 | path: '/history',
43 | name: 'history',
44 | title: '历史记录',
45 | component: SearchHistory
46 | },
47 | {
48 | path: '/manual',
49 | name: 'manual',
50 | title: '使用手册',
51 | component: Manual
52 | }
53 |
54 | ]
55 |
--------------------------------------------------------------------------------
/server/app.py:
--------------------------------------------------------------------------------
1 | import os
2 | import signal
3 | from configparser import ConfigParser
4 |
5 | from web_server.api import socketio, app
6 | from utils.log import logger
7 | from proxy.proxy_server import ProxyServer
8 | from task_server.task import BackgroundTaskServer
9 | from task_server.match import add2apis, timeout_for_ip_acl
10 |
11 | server = {}
12 |
13 |
14 | def start_server():
15 | for name in server:
16 | logger.warning(f'start {name}')
17 | server[name].start()
18 |
19 |
20 | def stop_server():
21 | for name in server:
22 | logger.warning(f'stop {name}')
23 | server[name].stop()
24 |
25 |
26 | def signal_handler(signum, frame):
27 | stop_server()
28 | logger.warning('!!!Ctrl-C pressed. Stop!!!')
29 | os._exit(0)
30 |
31 |
32 | def init_task():
33 | server['task'].add_task('归类api', add2apis)
34 | server['task'].add_task('自动过期代理ip权限', timeout_for_ip_acl)
35 |
36 |
37 | def background_server():
38 | if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
39 | return
40 | server['proxy'] = ProxyServer()
41 | server['task'] = BackgroundTaskServer()
42 | start_server()
43 | init_task()
44 |
45 | signal.signal(signal.SIGINT, signal_handler)
46 | signal.signal(signal.SIGTERM, signal_handler)
47 |
48 |
49 | def get_web_cofnig():
50 | cfg = ConfigParser()
51 | cfg.read('./config/config.ini')
52 | _host = cfg.get('web', 'listen_host')
53 | _port = int(cfg.get('web', 'listen_port'))
54 | _debug = bool(int(cfg.get('web', 'debug')))
55 | return _host, _port, _debug
56 |
57 |
58 | if __name__ == '__main__':
59 | background_server()
60 | host, port, debug = get_web_cofnig()
61 | socketio.run(app, host=host, port=port, debug=debug)
62 |
--------------------------------------------------------------------------------
/front/src/utils/logger.js:
--------------------------------------------------------------------------------
1 | export const isReflashLogger = (data) => {
2 | if (!data || data.length < 1 || !data.request || !data.request._id) {
3 | return false
4 | }
5 |
6 | if (data.checkCnt && data.checkCnt > 3) {
7 | return false
8 | }
9 |
10 | if (data.request.content && data.request.content.indexOf('mzip=') === 0) {
11 | return true
12 | }
13 |
14 | if (data.response && Object.keys(data.response).includes('content') && data.response.content.indexOf('{') !== 0) {
15 | return true
16 | }
17 | }
18 |
19 | export const timestampToString = (timestamp, fmt = '%Y-%m-%d %H:%M:%S') => {
20 | function format(s) {
21 | return ('' + s).length < 2 ? '0' + s : s + ''
22 | }
23 |
24 | let dt = new Date(timestamp * 1000)
25 | let dtArray = {
26 | '%Y': format(dt.getFullYear()),
27 | '%m': format(dt.getMonth() + 1),
28 | '%d': format(dt.getDate()),
29 | '%H': format(dt.getHours()),
30 | '%M': format(dt.getMinutes()),
31 | '%S': format(dt.getSeconds())
32 | }
33 |
34 | let dtString = ''
35 | for (let idx = 0; idx < fmt.length; idx++) {
36 | let key = fmt.slice(idx, idx + 2)
37 | if (dtArray[key]) {
38 | dtString += dtArray[key]
39 | idx++
40 | } else {
41 | dtString += fmt[idx]
42 | }
43 | }
44 | return dtString
45 | }
46 |
47 |
48 | export const objectToString = (obj, kv_seq = ':', line_seq = '\n') => {
49 | if (typeof (obj) === 'string') {
50 | return obj
51 | }
52 | let obj_list = []
53 | for (let key in obj) {
54 | let v = typeof (obj[key]) === 'object' ? objectToString(obj[key], kv_seq, line_seq) : obj[key]
55 | obj_list.push(key + kv_seq + v)
56 | }
57 | return obj_list.join(line_seq)
58 | }
59 |
--------------------------------------------------------------------------------
/server/db/redis_db.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import os
4 | import redis
5 |
6 | LOCAL_REDIS_CONFIG = {
7 | "host": "127.0.0.1",
8 | "port": 6379,
9 | "password": "redis_password",
10 | "db": 0,
11 | "max_connections": 100,
12 | "socket_timeout": 1,
13 | "socket_keepalive": True,
14 | "health_check_interval": 10,
15 | "decode_responses": True,
16 | }
17 |
18 | REDIS_CONFIG = {
19 | "host": "redis",
20 | "port": 6379,
21 | "password": "redis_password",
22 | "max_connections": 100,
23 | "socket_timeout": 5,
24 | "socket_connect_timeout": 5,
25 | "socket_keepalive": True,
26 | "health_check_interval": 10,
27 | "decode_responses": True,
28 | "db": 0,
29 | }
30 |
31 | REDIS_POOL = None
32 |
33 | local = os.getenv('PROJECT_ENV', 'prod') == 'local'
34 |
35 |
36 | def get_redis_client(local=local):
37 | global REDIS_POOL
38 | if local and not REDIS_POOL:
39 | REDIS_POOL = redis.ConnectionPool(**LOCAL_REDIS_CONFIG)
40 | if not REDIS_POOL:
41 | REDIS_POOL = redis.ConnectionPool(**REDIS_CONFIG)
42 | redis_client = redis.Redis(connection_pool=REDIS_POOL)
43 | return redis_client
44 |
45 |
46 | def get_redis_client_for_flask(local=local):
47 | if local:
48 | FLASK_CONFIG = LOCAL_REDIS_CONFIG
49 | else:
50 | FLASK_CONFIG = REDIS_CONFIG
51 | FLASK_CONFIG['decode_responses'] = False
52 | redis_client = redis.Redis(**FLASK_CONFIG)
53 | return redis_client
54 |
55 |
56 | def get_redis_url(local=local):
57 | conf = REDIS_CONFIG
58 | if local:
59 | conf = LOCAL_REDIS_CONFIG
60 | if "password" in conf:
61 | redis_url = f'redis://:{conf.get("password","")}@{conf["host"]}:{conf["port"]}/'
62 | else:
63 | redis_url = f'redis://{conf["host"]}:{conf["port"]}/'
64 | return redis_url
65 |
66 |
--------------------------------------------------------------------------------
/server/task_server/task.py:
--------------------------------------------------------------------------------
1 | import traceback
2 | from concurrent.futures import ThreadPoolExecutor
3 | from queue import Queue
4 |
5 | from utils.log import logger
6 | from task_server.base_server import ThreadServer
7 |
8 |
9 | class Task:
10 | READY = 0
11 | RUNNING = 1
12 | FINISH = 2
13 | ERROR = 3
14 |
15 | def __init__(self, name, func):
16 | self.name = name
17 | self.func = func
18 | self.status = Task.READY
19 |
20 | def run(self):
21 | self.status = Task.RUNNING
22 | try:
23 | self.func()
24 | self.status = Task.FINISH
25 | except Exception:
26 | self.status = Task.ERROR
27 | logger.error(f'Exec task catch a exception:\n {traceback.format_exc()}')
28 |
29 |
30 | class BackgroundTaskServer(ThreadServer):
31 |
32 | def __init__(self):
33 | super().__init__()
34 | self.tasks = []
35 | self.cmds = Queue()
36 | self.executor = ThreadPoolExecutor(thread_name_prefix='bg-')
37 |
38 | def run(self):
39 | while self.running:
40 | cmd = self.cmds.get()
41 | if cmd == 'stop':
42 | break
43 | elif cmd == 'clear':
44 | dead_tasks = []
45 | for task in self.tasks:
46 | if task.status == Task.FINISH or task.status == Task.ERROR:
47 | dead_tasks.append(task)
48 | for dead_task in dead_tasks:
49 | self.tasks.remove(dead_task)
50 |
51 | def add_task(self, name, func):
52 | task = Task(name, func)
53 | self.tasks.append(task)
54 | self.executor.submit(task.run)
55 | self.clear()
56 |
57 | def stop(self):
58 | super().stop()
59 | self.cmds.put('stop')
60 |
61 | def clear(self):
62 | self.cmds.put('clear')
63 |
--------------------------------------------------------------------------------
/server/task_server/match.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import json
4 | import time
5 |
6 | from db.mongo_db import get_mongo_client
7 | from db.redis_db import get_redis_client
8 | from utils.common import replaceD
9 | from utils.log import logger
10 |
11 |
12 | def add2apis():
13 | logger.warning('start add2apis')
14 | db = get_mongo_client()
15 | rc = get_redis_client()
16 | # sub一个频道然后处理
17 | ps = rc.pubsub()
18 | ps.subscribe('mloger:logger_request')
19 |
20 | while 1:
21 | message = ps.get_message(ignore_subscribe_messages=True, timeout=0.01)
22 | if message:
23 | req = json.loads(message['data'])
24 | else:
25 | continue
26 | if "path" not in req:
27 | continue
28 | req['gparams'] = req["path"].split('?')[1] if '?' in req["path"] else ''
29 | req['path'] = req["path"].split('?')[0]
30 | req['rd_path'] = replaceD(req['path'])
31 | if not db['apis'].find_one(
32 | {"scheme": req["scheme"], "host": req["host"], "port": req["port"], "rd_path": req['rd_path']}):
33 | req.pop('_id')
34 | db['apis'].insert_one(req)
35 | # req['_id'] = str(req.pop('_id'))
36 | # rc.lpush("mloger:security_scan_targets", json.dumps(req))
37 | time.sleep(0.01)
38 |
39 |
40 | def timeout_for_ip():
41 | rc = get_redis_client()
42 | REDIS_KEY = "mloger:mitmproxy:auth:hash"
43 | data = rc.hgetall(REDIS_KEY)
44 | for ip in data:
45 | info = json.loads(data[ip])
46 | if int(time.time()) - info.get('dt', 0) > 180:
47 | rc.hdel(REDIS_KEY, ip)
48 | if info.get('status', 1):
49 | logger.debug("{user}授权的{client_ip}已超时,授权失效".format(user=info.get('name', ''), client_ip=ip))
50 |
51 |
52 | def timeout_for_ip_acl():
53 | logger.warning('start timeout_for_ip_acl')
54 | while 1:
55 | timeout_for_ip()
56 | time.sleep(2)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mloger
2 | `HTTP/HTTPS/websocket/tcp` `抓包` `重放` `安全测试`
3 | [English](/README-EN.md)
4 | ## 简介
5 | **Mloger**是基于**mitmproxy**开发的安全测试平台,为陌陌安全内部mloger的开源简化版本,用于各业务线安全测试工作。
6 | 相较于burp,本项目支持socks5代理用于测试tcp协议,tcp协议常见于im通信和游戏场景。http/https协议的抓包与重放功能与burp基本一致。
7 |
8 | ## 技术栈
9 | 本项目在python3.8环境下完成
10 |
11 | | 前端 | 服务端 | 数据库 | 代理 |
12 | |:-----|:------|---------------|-----------|
13 | | vue2 | Flask | mongodb、redis | mitmproxy |
14 |
15 | ## 部署
16 | ## docker部署
17 | 1. 创建数据库目录,用于数据持久化存储
18 | 更换目录需要同步修改`docker-compose.yml`文件
19 | ```
20 | mkdir /tmp/mongo
21 | mkdir /tmp/redis
22 | ```
23 | 2. 启动docker
24 | ```
25 | docker-compose up
26 | ```
27 |
28 | ## 手动部署
29 | ### 编译前端[可选]
30 | 修改前端源码后需要再次编译,若无修改可跳过。
31 | ```
32 | cd front
33 | npm install
34 | npm run build
35 | ```
36 |
37 | ### 配置服务端
38 | 1. 安装mongodb、redis
39 | - 安装redis时需注意添加密码后启动
40 | - 安装mongodb后进入交互式shell,创建数据库并为其创建用户
41 | ```mongo shell
42 | use mloger
43 | db.createUser({user:"mloger",pwd:"mloger_pwd",roles:[{"role":"readWrite","db":"mloger"}]})
44 | ```
45 | 2. 克隆到本地并安装依赖
46 | ```bash
47 | cd server
48 | pip3 install -r requirements.txt
49 | ```
50 | 3. 配置数据库账号密码
51 | - 你需要在`server/db/mongo_db.py`中配置本地和线上mongodb的host、port、user、password、db字段。
52 | - 你需要在`server/db/redis_db.py`中配置本地和线上redis的host、port、password字段。
53 | - 并且通过`echo "export PROJECT_ENV=local" >> ~/.bash_profile`在本地环境变量中增加标识字段以使用本地数据库配置。
54 | - 创建mongodb索引
55 | ```bash
56 | mongo mongodb://mloger:mloger_pwd@localhost:27017/mloger server/db/mongo_create_indexes.js
57 | ```
58 | 4. 配置代理[可选]
59 | 你可以在`server/config/config.ini`中配置是否开启http、socks代理及其端口号,并保证相关端口未被占用。
60 | 5. 启动服务端
61 | ```bash
62 | python3 app.py
63 | ```
64 | 如果一切顺利的话,那么你可以在该ip的8000端口看到web页面了。
65 | 
66 |
67 | ## 使用
68 | 可以参考[使用手册](front/src/assets/usage.md)。
69 |
70 | ## 关于我们
71 |
72 | > 陌陌安全致力于以务实的工作保障陌陌旗下所有产品及亿万用户的信息安全,以开放的心态拥抱信息安全机构、团队与个人之间的共赢协作,以自由的氛围和丰富的资源支撑优秀同学的个人发展与职业成长。
73 |
74 | 陌陌安全应急响应中心:https://security.immomo.com
75 |
76 | 微信公众号:
77 | 
--------------------------------------------------------------------------------
/front/src/assets/usage.md:
--------------------------------------------------------------------------------
1 | ## 准备工作
2 | 如果你已经使用了`just trust me`或通过其他方式解决了`SSL Pinning`,可直接跳过此步骤。
3 | 否则请继续执行下列操作。
4 | 1. 将手机代理挂载到服务端开放的HTTP代理端口上
5 | 2. 使用手机浏览器访问`mitm.it`,下载证书
6 | 3. 将证书插入系统证书库,并信任
7 | 4. 打开要抓包的应用
8 |
9 | ## Web功能
10 | ### 准入代理
11 | 
12 |
13 | - 入口:页面左侧悬浮窗是准入代理功能,鼠标悬浮于图标上可触发。准入代理是用于授权可以通过代理访问的客户端ip,用于对代理进行权限控制。
14 | - 授权与撤销:点击接入即可授权成功,再次点击撤销可撤回对该ip的授权。授权的客户端ip会在持续无流量三分钟后失效。
15 | - 拦截状态字段:拦截状态标识是指客户端ip是否在HTTP拦截中设置了拦截状态,若出现HTTP记录页仅展示请求无响应时,可通过观察该标识判断是否是因为开启了拦截导致。
16 |
17 | 
18 | ### HTTP拦截
19 | - 拦截:填入IP并点击拦截已关闭按钮将拦截该IP的所有HTTP请求,并将下一个拦截到的HTTP请求展示在当前页面。
20 | - 修改:展示在当前文本框中的请求可修改请求体部分,其他部分暂不支持修改。
21 | - 操作:修改过后点击放过、丢弃按钮可对当前修改过的请求进行操作,将修改过的请求替代原始请求发送至服务端。点击重放可将当前停留的请求发送至HTTP重放页面,进行更加自定义的重放操作。
22 | - 等待响应:勾选等待响应后,点击放过按钮,将会等待此次放过请求的响应,并将该响应展示到文本框中,响应也可以被执行修改、放过、丢弃等操作。
23 |
24 | 
25 | ### HTTP记录
26 | - 连接:输入IP字段以过滤指定客户端ip的流量,否则你将会看到所有的流量,然后点击未连接按钮,即可连上服务端并获取实时的流量数据。再次点击已连接可断开连接。
27 | - 过滤:filter输入框是用于前端过滤流量的,所以所有流量还是缓存在浏览器中的。
28 | - 清屏:会清除当前页面所有HTTP记录。
29 | - 描述:用于标记api的功能描述,后续相同接口的api将会带有描述展示。
30 | - 优化:优化展示json格式的请求体与响应体,可通过原始切换查看原始请求与响应。
31 | - 重放:点击重放即可跳转到该次请求详情的重放页面,进行下一步的测试。
32 | - 分享:点击分享可将流量详情的链接复制到剪切板上。
33 |
34 | 
35 | ### HTTP重放
36 | - 重放:点击GO即可重放当前Request输入框中的HTTP请求。其中保留ua、HTTPS为重放时的可选项。
37 | - 格式转换:Tojson、ToRaw按钮为转换请求体中的参数格式,Tojson可将普通格式转换成json格式参数,ToRaw可将json格式参数转换成普通格式。
38 | - 并发:通过add §、clear § 按钮为请求添加可变参数,在payload设置按钮填入自定义字典来替换可变参数以进行有规律的遍历请求。支持的并发类型有四种(与burpsuite的intruder同)。若不添加§符号并修改并发数,可直接并发重放原始数据包。
39 | - 并发详情:通过分享链接查看并发请求时,可在页面下方单独查看每个请求的详情。
40 | - 最近20条:鼠标划过历史记录按钮,可查看最近20条请求重放记录。
41 |
42 | 
43 | ### TCP/WS测试
44 | - 连接:输入IP字段以过滤指定客户端ip的流量,否则你将会看到所有的流量,然后点击未连接按钮,即可连上服务端并获取实时的流量数据。再次点击已连接可断开连接。
45 | - 过滤:filter输入框是用于前端过滤流量的,所以所有流量还是缓存在浏览器中的。
46 | - 清屏:会清除当前页面所有Websocket、tcp记录。
47 | - 重放:点击发送、接收按钮可向服务端、客户端发送当前消息详情内的消息,通过修改并发数,可发送多条消息。
48 | - 16进制:点击HEX复选框可以16进制的形式查看、修改当前消息。
49 |
50 | 
51 | ### 历史记录
52 | - 搜索:从数据库中搜索历史记录,可选择不同类别。logger是记录的所有经过代理的HTTP流量,repeater是所有使用HTTP重放功能的流量,apis是归类了所有经过代理的接口请求。
53 |
54 | 
55 |
--------------------------------------------------------------------------------
/usage.md:
--------------------------------------------------------------------------------
1 | ## 准备工作
2 | 如果你已经使用了`just trust me`或通过其他方式解决了`SSL Pinning`,可直接跳过此步骤。
3 | 否则请继续执行下列操作。
4 | 1. 将手机代理挂载到服务端开放的HTTP代理端口上
5 | 2. 使用手机浏览器访问`mitm.it`,下载证书
6 | 3. 将证书插入系统证书库,并信任
7 | 4. 打开要抓包的应用
8 |
9 | ## Web功能
10 | ### 准入代理
11 | 
12 |
13 | - 入口:页面左侧悬浮窗是准入代理功能,鼠标悬浮于图标上可触发。准入代理是用于授权可以通过代理访问的客户端ip,用于对代理进行权限控制。
14 | - 授权与撤销:点击接入即可授权成功,再次点击撤销可撤回对该ip的授权。授权的客户端ip会在持续无流量三分钟后失效。
15 | - 拦截状态字段:拦截状态标识是指客户端ip是否在HTTP拦截中设置了拦截状态,若出现HTTP记录页仅展示请求无响应时,可通过观察该标识判断是否是因为开启了拦截导致。
16 |
17 | 
18 | ### HTTP拦截
19 | - 拦截:填入IP并点击拦截已关闭按钮将拦截该IP的所有HTTP请求,并将下一个拦截到的HTTP请求展示在当前页面。
20 | - 修改:展示在当前文本框中的请求可修改请求体部分,其他部分暂不支持修改。
21 | - 操作:修改过后点击放过、丢弃按钮可对当前修改过的请求进行操作,将修改过的请求替代原始请求发送至服务端。点击重放可将当前停留的请求发送至HTTP重放页面,进行更加自定义的重访操作。
22 | - 等待响应:勾选等待响应后,点击放过按钮,将会等待此次放过请求的响应,并将该响应展示到文本框中,响应也可以被执行修改、放过、丢弃等操作。
23 |
24 | 
25 | ### HTTP记录
26 | - 连接:输入IP字段以过滤指定客户端ip的流量,否则你将会看到所有的流量,然后点击未连接按钮,即可连上服务端并获取实时的流量数据。再次点击已连接可断开连接。
27 | - 过滤:filter输入框是用于前端过滤流量的,所以所有流量还是缓存在浏览器中的。
28 | - 清屏:会清除当前页面所有HTTP记录。
29 | - 描述:用于标记api的功能描述,后续相同接口的api将会带有描述展示。
30 | - 优化:优化展示json格式的请求体与响应体,可通过原始切换查看原始请求与响应。
31 | - 重放:点击重放即可跳转到该次请求详情的重放页面,进行下一步的测试。
32 | - 分享:点击分享可将流量详情的链接复制到剪切板上。
33 |
34 | 
35 | ### HTTP重放
36 | - 重放:点击GO即可重放当前Request输入框中的HTTP请求。其中保留ua、HTTPS为重放时的可选项。
37 | - 格式转换:Tojson、ToRaw按钮为转换请求体中的参数格式,Tojson可将普通格式转换成json格式参数,ToRaw可将json格式参数转换成普通格式。
38 | - 并发:通过add §、clear § 按钮为请求添加可变参数,在payload设置按钮填入自定义字典来替换可变参数以进行有规律的遍历请求。支持的并发类型有四种(与burpsuite的intruder同)。若不添加§符号并修改并发数,可直接并发重放原始数据包。
39 | - 并发详情:通过分享链接查看并发请求时,可在页面下方单独查看每个请求的详情。
40 | - 最近20条:鼠标划过历史记录按钮,可查看最近20条请求重放记录。
41 |
42 | 
43 | ### TCP/WS测试
44 | - 连接:输入IP字段以过滤指定客户端ip的流量,否则你将会看到所有的流量,然后点击未连接按钮,即可连上服务端并获取实时的流量数据。再次点击已连接可断开连接。
45 | - 过滤:filter输入框是用于前端过滤流量的,所以所有流量还是缓存在浏览器中的。
46 | - 清屏:会清除当前页面所有Websocket、tcp记录。
47 | - 重放:点击发送、接收按钮可向服务端、客户端发送当前消息详情内的消息,通过修改并发数,可发送多条消息。
48 | - 16进制:点击HEX复选框可以16进制的形式查看、修改当前消息。
49 |
50 | 
51 | ### 历史记录
52 | - 搜索:从数据库中搜索历史记录,可选择不同类别。logger是记录的所有经过代理的HTTP流量,repeater是所有使用HTTP重放功能的流量,apis是归类了所有经过代理的接口请求。
53 |
54 | 
--------------------------------------------------------------------------------
/server/db/mongo_db.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import os
4 |
5 | from pymongo import MongoClient
6 |
7 |
8 | # 本地mongo配置
9 | LOCAL_MONGO_HOST = "127.0.0.1"
10 | LOCAL_MONGO_PORT = 27017
11 | LOCAL_MONGO_USER = "mloger"
12 | LOCAL_MONGO_PWD = "mloger_pwd"
13 | LOCAL_MONGO_DB = "mloger"
14 |
15 | # 线上mongo配置
16 | MLOGER_REPLSET_MONGO_HOST = [
17 | "mongo",
18 | ]
19 | MONGO_REPLSET_PORT = 27017
20 | MONGO_REPLSET_DB = "mloger"
21 | MONGO_REPLSET_USER = "mloger"
22 | MONGO_REPLSET_PWD = "mloger_pwd"
23 | timezone = 8
24 |
25 | MONGO_REPLSET_POOL = None
26 | MONGO_REPLSET_POOL_SIZE = 40
27 | MONGO_REPLSET_MAX_IDLE_TIME = 1 * 60 * 1000 # 最大空闲时长,1分钟
28 | MONGO_REPLSET_SOCKET_TIMEOUT = 60 * 1000 # socket超时,60秒
29 | MONGO_REPLSET_MAX_WAITING_TIME = 20 * 1000 # 最大等待时间,20秒
30 | MONGO_REPLSET_READ_PREFERENCE = "secondaryPreferred"
31 |
32 | local = os.getenv('PROJECT_ENV', 'prod') == 'local'
33 |
34 |
35 | def get_mongo_client(local=local):
36 | global MONGO_REPLSET_POOL
37 | if local:
38 | if not MONGO_REPLSET_POOL:
39 | MONGO_REPLSET_POOL = MongoClient(host=LOCAL_MONGO_HOST,
40 | port=LOCAL_MONGO_PORT,
41 | connect=False)
42 | MONGO_REPLSET_POOL[LOCAL_MONGO_DB].authenticate(LOCAL_MONGO_USER, LOCAL_MONGO_PWD)
43 | db = MONGO_REPLSET_POOL[LOCAL_MONGO_DB]
44 | else:
45 | if not MONGO_REPLSET_POOL:
46 | MONGO_REPLSET_POOL = MongoClient(host=MLOGER_REPLSET_MONGO_HOST,
47 | port=MONGO_REPLSET_PORT,
48 | maxPoolSize=MONGO_REPLSET_POOL_SIZE,
49 | socketKeepAlive=True,
50 | maxIdleTimeMS=MONGO_REPLSET_MAX_IDLE_TIME,
51 | waitQueueTimeoutMS=MONGO_REPLSET_MAX_WAITING_TIME,
52 | socketTimeoutMS=MONGO_REPLSET_SOCKET_TIMEOUT, w=1, j=False,
53 | readPreference=MONGO_REPLSET_READ_PREFERENCE)
54 | MONGO_REPLSET_POOL[MONGO_REPLSET_DB].authenticate(MONGO_REPLSET_USER, MONGO_REPLSET_PWD)
55 | db = MONGO_REPLSET_POOL[MONGO_REPLSET_DB]
56 | return db
57 |
--------------------------------------------------------------------------------
/server/proxy/proxy_server.py:
--------------------------------------------------------------------------------
1 | import os
2 | import subprocess
3 | import sys
4 | from configparser import ConfigParser
5 | from pathlib import Path
6 |
7 | from utils.log import logger
8 |
9 | CURRENT_PATH = Path(__file__).parent
10 | HTTP_SCRIPT_FILE = CURRENT_PATH / 'http_mitm_script.py'
11 | SOCKS5_SCRIPT_FILE = CURRENT_PATH / 'socks5_mitm_script.py'
12 |
13 | mitmdump_path = os.path.join(os.path.dirname(sys.executable), "mitmdump")
14 |
15 |
16 | class ProxyServer:
17 |
18 | def __init__(self):
19 | super().__init__()
20 | cfg = ConfigParser()
21 | cfg.read('./config/config.ini')
22 | self.http_proxy_switch = bool(int(cfg.get('mitm', 'http_switch')))
23 | self.socks5_proxy_switch = bool(int(cfg.get('mitm', 'socks5_switch')))
24 | self.http_proxy_port = str(cfg.get('mitm', 'http_port'))
25 | self.socks5_proxy_port = str(cfg.get('mitm', 'socks5_port'))
26 | self._http_proxy_server_process = None
27 | self._socks5_proxy_server_process = None
28 |
29 | def start(self):
30 | mitm_arguments = [
31 | '-s', str(HTTP_SCRIPT_FILE),
32 | '--listen-host', '0.0.0.0',
33 | '-p', str(self.http_proxy_port),
34 | # '--set', 'confdir=/root/.mitmproxy/',
35 | '> ./log/http.log'
36 | ]
37 | if self.http_proxy_switch:
38 | self._http_proxy_server_process = subprocess.Popen(f'{mitmdump_path} {" ".join(mitm_arguments)}',
39 | shell=True)
40 | logger.warning(f'start http proxy on 8081 port, pid {self._http_proxy_server_process.pid}')
41 | mitm_arguments = [
42 | '-s', str(SOCKS5_SCRIPT_FILE),
43 | '--listen-host', '0.0.0.0',
44 | '-p', str(self.socks5_proxy_port),
45 | # '--set', 'confdir=/root/.mitmproxy/',
46 | '--mode', 'socks5',
47 | '--rawtcp',
48 | '> ./log/socks.log'
49 | ]
50 | if self.socks5_proxy_switch:
51 | self._socks5_proxy_server_process = subprocess.Popen(f'{mitmdump_path} {" ".join(mitm_arguments)}',
52 | shell=True)
53 | logger.warning(f'start socks proxy on 8082 port, pid {self._socks5_proxy_server_process.pid}')
54 |
55 | def stop(self):
56 | if self._http_proxy_server_process:
57 | self._http_proxy_server_process.terminate()
58 | logger.warning(f'http ProxyServer {self._http_proxy_server_process.pid} shutdown')
59 | if self._socks5_proxy_server_process:
60 | self._socks5_proxy_server_process.terminate()
61 | logger.warning(f'socks ProxyServer {self._socks5_proxy_server_process.pid} shutdown')
62 |
--------------------------------------------------------------------------------
/.mitmproxy/mitmproxy-ca.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAv9j7wTj0a5Ql6ZfqjHOa4o3bzPOklW7V4BPPgvPcLrSXCDIq
3 | T7PSwUTA4lWr62vaFk5SpRORYBjgD+Le365V01k9/in0N3vAEDOw88123gPuRs76
4 | FtkVMBm/tHLkKAUbrvvZDUrJsArmcclkqbioFf4UiN0MRw6R+hxOPhDPVb9x34qR
5 | h0HFyhnTsa1YpB26GPLX23XNSJU4POu60COhcE41VQ1aI3NOe3CNN9+gl/ixONi5
6 | duZcHTShcmbyh6xDQvY6I29bBV15RlaD/95P55CwnqsFscvicugwfscefVe31eO8
7 | Q0TcTNVv+54hiO/3XCdG96o9nBkDbPUfO7w0EQIDAQABAoIBAFAzIEONULhsTVES
8 | ve2LInOVRA6wx9vkYqpy/avLz/8CZaT6e6+oB/HdkRjNIbqf8qKiiXAS9xIKNBjQ
9 | CcXOdniM9Rp8ZWiQZiEfytiQyyFS2VhmjCvjAjWCfh6sbO7B++mxxPIGkrvyQpXM
10 | 0DUXScUv21u/tLM7xxCcpqTRmCtKzMBZbDdA6dHGw2gm1Mw01O13UWJAwhdW8W0T
11 | ROxbbgHtUq+1bqvHhIuXsGwIg94a0Ak8KirQi9FLticHdhDBxGl3XzE6bCx2JHN5
12 | ihKFTYwLe7KMXNfAnNtA9ZqFDQeyz2AjMI/WaglpQL1AODxqTi8FjHdwlJkq0aDa
13 | TvZchAECgYEA3nepFhzjT5nOhGqQULYcdsr6AVCtbCOAqn+hPalTd08wyv39etnR
14 | 0XWfjoacFFvrTXht/TBJzuTSyxNveTOPOKvQPVvOw7Qd6uC1arGkH9TwnjkEmTbG
15 | TrfDNrH0MGVP135M1eX0lGx9/wxdAxfhQy8oi0V1jQOryWKG5czjs9ECgYEA3MPM
16 | BdUfvlPyHl8ihDSrG/HKULOPGzjchPSvggEzK87WTRfBM2awbTFbjulk9pLomcT0
17 | zy3aNRaZLaDkkeksAk8zb88r7Sc7+m/aEIyyyC6Sux0Xuf0bHm01BVqwas1rcj3B
18 | 0fAllruUR3OTjqiADSWpldV/fSiXvCHY8eXRzEECgYBpZLcuSrFF3NAB25pTInOJ
19 | s8+NH/OI76jdHWn+FGH+e/3ZrUqREPBeEyNsGkmHO6TWJt+0u1O8OJvEqYh+GvcX
20 | Gg6lgwEJpiMmSx6nVVEz42W7D7gl/dUlJdnN57CP3O9eX+92xmKAjoWCdxD5ji6h
21 | 7Y1/rYBrNfoEhjDyXYbigQKBgBDtTlpwIfP0+c6MWtgqNah53Jpdei6LKJVlMYPV
22 | VbgZLuNQW/b1+7cTnsKRcLVbRExuBptQF7gUWIBgIsZxS3eN3RLInsSbSoUESIMW
23 | Vqr0rzl7nJMdLhQiKXuUnYxC3ecvhwny0N8C2Pa1jO3WS6M2bQ9ZBGJzV1cMfVbm
24 | 2TRBAoGBAMF0Ug2wQskNtLueA5AwSRSdCBYlU6ehO47iT0wktrkLzqurLfAQwsHV
25 | jq7OxtmfZbq7DqVQPwwFcEHKQHIk/3pzzSkDD9C3Wr4Ki7oOVKOeuqFlsanzGrQr
26 | ZKpJyiUkLb2qeKFyRLq2JiWP4eeUa3FZXji65UnYCjC770FeA7fr
27 | -----END RSA PRIVATE KEY-----
28 | -----BEGIN CERTIFICATE-----
29 | MIIDNTCCAh2gAwIBAgIUIRJiCWXD1cMOZyUpCPSg5AYm7nkwDQYJKoZIhvcNAQEL
30 | BQAwKDESMBAGA1UEAwwJbWl0bXByb3h5MRIwEAYDVQQKDAltaXRtcHJveHkwHhcN
31 | MjIwMjIxMDg0NjI5WhcNMzIwMjIxMDg0NjI5WjAoMRIwEAYDVQQDDAltaXRtcHJv
32 | eHkxEjAQBgNVBAoMCW1pdG1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
33 | AQoCggEBAL/Y+8E49GuUJemX6oxzmuKN28zzpJVu1eATz4Lz3C60lwgyKk+z0sFE
34 | wOJVq+tr2hZOUqUTkWAY4A/i3t+uVdNZPf4p9Dd7wBAzsPPNdt4D7kbO+hbZFTAZ
35 | v7Ry5CgFG6772Q1KybAK5nHJZKm4qBX+FIjdDEcOkfocTj4Qz1W/cd+KkYdBxcoZ
36 | 07GtWKQduhjy19t1zUiVODzrutAjoXBONVUNWiNzTntwjTffoJf4sTjYuXbmXB00
37 | oXJm8oesQ0L2OiNvWwVdeUZWg//eT+eQsJ6rBbHL4nLoMH7HHn1Xt9XjvENE3EzV
38 | b/ueIYjv91wnRveqPZwZA2z1Hzu8NBECAwEAAaNXMFUwDwYDVR0TAQH/BAUwAwEB
39 | /zATBgNVHSUEDDAKBggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
40 | FI5FnuJUK3+lvU4Zw6TNa4GinFp4MA0GCSqGSIb3DQEBCwUAA4IBAQBK3iCqyo1w
41 | p6x7HzvFw7No5K8xt6d19ALXO5rtWnn+KCfAShqXuuN7FOhV4IaHdfIBdwB1RK+I
42 | Bjjfpq7bPLW5YUZ3BtaNhJ4tjb7x00XIvninEUJ9k8kqx8v7jtnGwNSs19IcCR8F
43 | P7UznbPnrayMA1tW8V32onAgbhWvoSOmPdz9FUvoykzGZV4cwV/7M+CEYhUiNees
44 | J1JGifjYaxzQ95n50ISFR/q9lR9b79Ch9rJ4IWv4B1LdREA3/vW2oyuIqWl4JaVs
45 | nzM4jjhfDQhoI9fYaHdfWR3w5+BL0Dakt2ehOriaIvsE7kw+hYEWYhAXtIRPSsuc
46 | yIV+a9LaMd6G
47 | -----END CERTIFICATE-----
48 |
--------------------------------------------------------------------------------
/README-EN.md:
--------------------------------------------------------------------------------
1 | # Mloger
2 | `HTTP/HTTPS/websocket/tcp` `Debugging Proxy` `Replay attack` `Security testing`
3 | [中文](/README.md)
4 |
5 | ## Introduction
6 | **Mloger** is a security testing platform developed on top of **mitmproxy**. It's a lightweight version of mloger used by MOMO internal security team.
7 | Compared to burp, this also supports socks5 proxy for TCP protocol, TCP protocol is commonly used in scenarios such as instant messaging and gaming. The functions of debugging proxy and replay for HTTP/HTTPS protocol are the same compared to burp.
8 |
9 | ## Tech stack
10 | This project is developed on python3.8 environment
11 |
12 | | Front-end | Back-end | Database | Proxy |
13 | |:-----|:------|---------------|-----------|
14 | | vue2 | Flask | mongodb、redis | mitmproxy |
15 |
16 | ## Deploy
17 | ## Docker deploy
18 | 1. Create a database directory for persistent data storage
19 | Changing the directory requires modifying the `docker-comemess.yml` synchronously.
20 | ```
21 | mkdir /tmp/mongo
22 | mkdir /tmp/redis
23 | ```
24 | 2. Start the docker
25 | ```
26 | docker-compose up
27 | ```
28 |
29 | ## Manual deploy
30 | ### Front-end build [Optional]
31 | After modifying the front-end source code, you need to build it. If no modification is made, skip it.
32 | ```
33 | cd front
34 | npm install
35 | npm run build
36 | ```
37 |
38 | ### Back-end configuration
39 | 1. Installing mongodb and redis
40 | - Make sure you have config the password for redis when installation
41 | - Creeate a database for users after the installation for mongodb
42 | ```mongo shell
43 | use mloger
44 | db.createUser({user:"mloger",pwd:"mloger_pwd",roles:[{"role":"readWrite","db":"mloger"}]})
45 | ```
46 | 2. Clone the project and install dependencies for the application
47 | ```bash
48 | cd server
49 | pip3 install -r requirements.txt
50 | ```
51 | 3. Database configuration
52 | - You will need to config the parameters including host, port, user, password, and the name of the database in the config file `server/db/mongo_db.py` for MongoDB.
53 |
54 | - You will also need to config the parameters including host, port, user, and password of the database in the config file `server/db/redis_db.py` for Redis.
55 |
56 | - Add system environment variables for the database configs by `echo "export PROJECT_ENV=local" >> ~/.bash_profile` in order to use.
57 |
58 | - Create an Index for mongodb
59 | ```bash
60 | mongo mongodb://mloger:mloger_pwd@localhost:27017/mloger server/db/mongo_create_indexes.js
61 | ```
62 | 4. Proxy config [Optional]
63 | You can check the configs for HTTP, socks, and port in `server/config/config.ini` and make sure the port is not currently used.
64 | 5. Starting the back-end
65 | ```bash
66 | python3 app.py
67 | ```
68 |
69 | You should be able to access the web page with IP:8000 if successful.
70 | 
71 |
72 | ## Usage
73 | Reference to [User Guide](front/src/assets/usage.md)。
74 |
75 | ## About us
76 |
77 | > 陌陌安全致力于以务实的工作保障陌陌旗下所有产品及亿万用户的信息安全,以开放的心态拥抱信息安全机构、团队与个人之间的共赢协作,以自由的氛围和丰富的资源支撑优秀同学的个人发展与职业成长。
78 |
79 | MOMO Security Emergency Response Center:https://security.immomo.com
80 |
81 | Our WeChat:
82 | 
83 |
--------------------------------------------------------------------------------
/server/utils/proxypriv.py:
--------------------------------------------------------------------------------
1 | import json
2 | import time
3 | import socket
4 |
5 | import netaddr
6 | from cachetools import cached, TTLCache
7 | from mitmproxy import websocket
8 |
9 | from db.mongo_db import get_mongo_client
10 | from db.redis_db import get_redis_client
11 | from utils.common import *
12 |
13 | # mongo 查询句柄
14 | db = get_mongo_client()
15 | rc = get_redis_client()
16 |
17 | # redis 代理收到的客户端ip的key
18 | REDIS_KEY = "mloger:mitmproxy:auth:hash"
19 |
20 |
21 | @cached(cache=TTLCache(maxsize=1024, ttl=5))
22 | def client_ip_is_allowed(ip) -> bool:
23 | """
24 | 判断ip是否允许连接mitproxy
25 | :param ip:
26 | :return:
27 | """
28 | status = 0
29 | user_name = ''
30 | data = rc.hget(REDIS_KEY, ip)
31 | if data:
32 | status = json.loads(data).get('status', 0)
33 | user_name = json.loads(data).get('user_name', '')
34 | rc.hset(REDIS_KEY, ip, json.dumps({'dt': int(time.time()), 'status': status, 'user_name': user_name}))
35 | return status == 1
36 |
37 |
38 | @cached(cache=TTLCache(maxsize=1024, ttl=5))
39 | def get_user_name_by_ip_for_redis(ip) -> str:
40 | """
41 | 获取授权ip的用户名
42 | :param ip:
43 | :return:
44 | """
45 | user = ''
46 | data = rc.hget(REDIS_KEY, ip)
47 | if data:
48 | user = json.loads(data).get('user_name', '')
49 | return user
50 |
51 |
52 | @cached(cache=TTLCache(maxsize=100, ttl=60))
53 | def get_domain_rules_list():
54 | domain_rules_list = list(db['rules'].find({'remove': None, 'client_ip': {'$ne': None}}))
55 | return domain_rules_list
56 |
57 |
58 | @cached(cache=TTLCache(maxsize=100, ttl=5))
59 | def get_logger_black(user_name):
60 | data = list(db['user_logger_black'].find({'user_name': user_name}))
61 | return data
62 |
63 |
64 | @cached(cache=TTLCache(maxsize=100, ttl=60))
65 | def get_logger_black_by_ip(ip):
66 | user_name = get_user_name_by_ip_for_redis(ip)
67 | black_rules_list = get_logger_black(user_name)
68 | return black_rules_list
69 |
70 |
71 | def domain_is_intranet(domain):
72 | try:
73 | ip = socket.getaddrinfo(domain, None)[0][4][0]
74 | if netaddr.IPAddress(ip).is_private():
75 | return True
76 | except:
77 | pass
78 | return False
79 |
80 |
81 | def get_client_ip(flow):
82 | try:
83 | client_ip = netaddr.IPAddress(flow.client_conn.address[0]).ipv4().__str__()
84 | except Exception as e:
85 | flow.kill()
86 | raise e
87 | return client_ip
88 |
89 |
90 | def flow_is_black(flow) -> bool:
91 | client_ip = get_client_ip(flow)
92 | if not client_ip_is_allowed(client_ip):
93 | flow.kill()
94 | return True
95 | if flow.websocket is not None:
96 | return False
97 | if flow.request.host in domain_black_list or flow.request.path.split('?')[0].endswith(file_type_black_list):
98 | return True
99 | black_rules_list = get_logger_black_by_ip(client_ip)
100 | for rule in black_rules_list:
101 | if flow.request.host.endswith(rule['host']) and flow.request.path.startswith(rule['path']):
102 | return True
103 | return False
104 |
105 |
106 | def get_client_ip_list():
107 | data = rc.hgetall(REDIS_KEY)
108 | return data
109 |
--------------------------------------------------------------------------------
/front/src/utils/api.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios').default;
2 |
3 | export const getConnIp = () => {
4 | return axios.post('/api/get_client_ip')
5 | }
6 |
7 | export const setConnIp = (ip) => {
8 | return axios.post('/api/set_client_ip_allowed', {ip: ip})
9 | }
10 |
11 | export const releaseConnIp = (ip) => {
12 | return axios.post('/api/cancel_client_ip_allowed', {ip: ip})
13 | }
14 |
15 | export const getInterceptData = (query = {}, method = 'get') => {
16 | if (method === 'get') {
17 | return axios.get('/api/intercept')
18 | }
19 | return axios.post('/api/intercept', query)
20 | }
21 |
22 | export const getInterceptOptData = (query = {}) => {
23 | return axios.post('/api/intercept_op', query)
24 | }
25 |
26 | export const postEditLogerDesc = (payload = {id: '', describe: ''}) => {
27 | let data = {}
28 | for (let k of ['id', 'describe']) {
29 | if (payload[k]) {
30 | data[k] = payload[k]
31 | } else {
32 | throw "缺少参数:" + k
33 | }
34 | }
35 | return axios.post('/api/edit_req_describe', payload)
36 | }
37 |
38 | export const postRepeaterShareAuthID = (id, authId) => {
39 | let payload = {"id": id, "auth_code": authId}
40 | return axios.post('/api/repeater_share', payload)
41 | }
42 |
43 | export const getRepeaterGoData = (query) => {
44 | return axios.post('/api/repeater_go', query)
45 | }
46 |
47 | export const getRepeaterHistoryData = (query) => {
48 | return axios.post('/api/repeater_history', query)
49 | }
50 |
51 | export const getRepeaterGetData = (query) => {
52 | return axios.get('/api/repeater_get', {params: query})
53 | }
54 |
55 | export const imwsRepeaterGo = (
56 | conn_id = '',
57 | flow_content = '',
58 | hex_content = '',
59 | direction = '',
60 | process_cnt = 0
61 | ) => {
62 | let payload = {
63 | conn_id: conn_id,
64 | flow_content: flow_content,
65 | hex_content: hex_content,
66 | direction: direction,
67 | concurrent_num: process_cnt
68 | }
69 | if (conn_id && flow_content && (direction === 'send' || direction === 'receive')) {
70 | return axios.post('/api/imws_repeater_go', payload)
71 | }
72 | console.error(payload)
73 | throw '参数错误,请求失败'
74 | }
75 |
76 | export const interceptOpImws = (
77 | id = '',
78 | conn_id = '',
79 | type = '',
80 | flow_content = ''
81 | ) => {
82 | let payload = {
83 | id: id,
84 | conn_id: conn_id,
85 | flow_content: flow_content,
86 | }
87 | if (id && conn_id && type) {
88 | if (type === 'forward') {
89 | payload.forward = true
90 | payload.drop = false
91 | } else {
92 | payload.forward = false
93 | payload.drop = true
94 | }
95 | return axios.post('/api/intercept_op_imws', payload)
96 | }
97 | console.error(payload)
98 | throw '参数错误,请求失败'
99 | }
100 |
101 | export const interceptImws = (
102 | conn_id = '',
103 | status = ''
104 | ) => {
105 | let payload = {
106 | conn_id: conn_id, status: status
107 | }
108 | if (conn_id && (status === 'on' || status === 'off')) {
109 | return axios.post('/api/intercept_imws', payload)
110 | }
111 | console.error(payload)
112 | throw '拦截参数错误,请求失败'
113 | }
114 |
115 | export const getSearchHistoryData = (query = {
116 | keyword: '',
117 | type: 'all',
118 | page: 1,
119 | size: 20
120 | }) => {
121 | if (!query.type) {
122 | query.type = 'all'
123 | }
124 | return axios.post('/api/search', query)
125 | }
--------------------------------------------------------------------------------
/front/src/components/Imws/components/LinkTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 连接列表
5 |
6 | 存活: {{ liveCnt }}断开: {{ dieCnt }}
8 |
9 |
16 |
17 |
18 |
19 |
20 |
123 |
--------------------------------------------------------------------------------
/front/src/components/MainConnDrawer.vue:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
27 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
143 |
--------------------------------------------------------------------------------
/front/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
53 |
54 |
55 |
56 |
57 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | {
77 | this.showDrawer = status;
78 | }
79 | "
80 | >
81 |
82 |
83 |
84 |
85 |
120 |
121 |
130 |
--------------------------------------------------------------------------------
/front/src/utils/tools.js:
--------------------------------------------------------------------------------
1 | import DiffMatchPatch from "diff-match-patch";
2 |
3 | export const showResponseData = (data = {}) => {
4 | if (!data || !Object.keys(data).length) {
5 | return ''
6 | }
7 | let result = [data.http_version + " " + data.status_code + " " + data.reason];
8 | for (let k in data.headers) {
9 | result.push(k + " : " + data.headers[k]);
10 | }
11 | result.push("");
12 | result.push(data.content);
13 | return result.join("\r\n");
14 | }
15 |
16 | export const showRequestData = (data = {}) => {
17 | if (!data || !Object.keys(data).length) {
18 | return ''
19 | }
20 | let result = [data.method + " " + data.path + " " + data.http_version, "Host: " + data.host];
21 | for (let k in data.headers) {
22 | if (k === "Host") {
23 | continue;
24 | }
25 | result.push(k + ": " + data.headers[k]);
26 | }
27 | result.push("");
28 | result.push(data.content);
29 | return result.join("\r\n");
30 | }
31 |
32 | export const makeAuthID = (count = 24) => {
33 | let result = [];
34 | let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
35 | let charactersLength = characters.length;
36 | for (let i = 0; i < count; i++) {
37 | result.push(characters.charAt(Math.floor(Math.random() * charactersLength)));
38 | }
39 | return result.join('');
40 | }
41 |
42 | export const getEncodeVar = (encode_str = '', delimiters = '§', full_line = false) => {
43 | if (encode_str.length < 1) {
44 | throw '字符串不允许为空'
45 | }
46 | let vars = []
47 | let line = []
48 | let tmpIdx = -1
49 | for (let idx = 0; idx < encode_str.length; idx++) {
50 | if (encode_str[idx] === delimiters) {
51 | if (tmpIdx > -1) {
52 | vars.push(`${encode_str.slice(tmpIdx, idx)}${delimiters}`)
53 | tmpIdx = -1
54 | } else {
55 | tmpIdx = idx
56 | }
57 | } else if (full_line && tmpIdx === -1) {
58 | if (encode_str[idx] === '\n') {
59 | vars.push(line.join('').trim())
60 | line = []
61 | } else {
62 | line.push(encode_str[idx])
63 | }
64 | }
65 | }
66 | if (full_line && line.join('').trim().length) {
67 | vars.push(line.join(''))
68 | }
69 | if (tmpIdx !== -1) {
70 | console.warn('字符串存在不闭合标识')
71 | }
72 | return vars
73 | }
74 |
75 | export const diff_prettyHtml = function (diffs) {
76 | var html = [];
77 | var pattern_amp = /&/g;
78 | var pattern_lt = //g;
80 | var pattern_para = /\n/g;
81 | for (var x = 0; x < diffs.length; x++) {
82 | var op = diffs[x][0];
83 | var data = diffs[x][1];
84 | var text = data.replace(pattern_amp, '&').replace(pattern_lt, '<')
85 | .replace(pattern_gt, '>').replace(pattern_para, '¶
');
86 | switch (op) {
87 | case DiffMatchPatch.DIFF_INSERT:
88 | html[x] = '' + text + '';
89 | break;
90 | case DiffMatchPatch.DIFF_DELETE:
91 | html[x] = '' + text + '';
92 | break;
93 | case DiffMatchPatch.DIFF_EQUAL:
94 | html[x] = '' + text + '';
95 | break;
96 | }
97 | }
98 | return html.join('');
99 | };
100 |
101 | export const evalRange = (range_str = '') => {
102 | if (!range_str.trim().startsWith('range:')) {
103 | return range_str
104 | }
105 | let line = range_str.trim()
106 | line = line.replace('range:', '')
107 | if (!line.includes(':') && !line.includes(',')) {
108 | return range_str
109 | }
110 | let delimiters = ','
111 | if (line.includes(':')) {
112 | delimiters = ':'
113 | }
114 | let range = line.split(delimiters, 3)
115 | if (range.length < 2 || !range[1]) {
116 | return range_str
117 | }
118 | let start = range[0] ? parseInt(range[0]) ? parseInt(range[0]) : range[0].charCodeAt() : 0,
119 | stop = range[1] ? parseInt(range[1]) ? parseInt(range[1]) : range[1].charCodeAt() : 0,
120 | step = range.length > 2 ? parseInt(range[2]) ? parseInt(range[2]) : range[2].charCodeAt() : 1,
121 | isAscii = parseInt(range[0]).toString() == 'NaN' || parseInt(range[1]).toString() == 'NaN'
122 |
123 | let res = []
124 | for (; start < stop;) {
125 | res.push(isAscii ? String.fromCharCode(start) : start)
126 | start += step
127 | }
128 | return res.join('\n')
129 | }
130 |
131 |
--------------------------------------------------------------------------------
/server/db/mongo_create_indexes.js:
--------------------------------------------------------------------------------
1 | try{db.createUser({user:"mloger",pwd:"mloger_pwd",roles:[{"role":"readWrite","db":"mloger"}]})}catch(e){print(e)}
2 | try{ db.getCollection("apis").createIndex({"status":1},{background:1, unique:false })}catch(e){print(e)}
3 | try{ db.getCollection("apis").createIndex({"host":1},{background:1, unique:false })}catch(e){print(e)}
4 | try{ db.getCollection("apis").createIndex({"path":1},{background:1, unique:false })}catch(e){print(e)}
5 | try{ db.getCollection("apis").createIndex({"mzip":1},{background:1, unique:false })}catch(e){print(e)}
6 | try{ db.getCollection("apis").createIndex({"crypto":1},{background:1, unique:false })}catch(e){print(e)}
7 | try{ db.getCollection("apis").createIndex({"schema":1},{background:1, unique:false })}catch(e){print(e)}
8 | try{ db.getCollection("apis").createIndex({"rd_path":1},{background:1, unique:false })}catch(e){print(e)}
9 | try{ db.getCollection("apis").createIndex({"req_id":1},{background:1, unique:false })}catch(e){print(e)}
10 | try{ db.getCollection("connections").createIndex({"live":1},{background:1, unique:false })}catch(e){print(e)}
11 | try{ db.getCollection("connections").createIndex({"client_host":1},{background:1, unique:false })}catch(e){print(e)}
12 | try{ db.getCollection("intercept").createIndex({"status":1},{background:1, unique:false })}catch(e){print(e)}
13 | try{ db.getCollection("intercept").createIndex({"type":1},{background:1, unique:false })}catch(e){print(e)}
14 | try{ db.getCollection("repeater").createIndex({"describe":1},{background:1, unique:false })}catch(e){print(e)}
15 | try{ db.getCollection("repeater").createIndex({"_id":-1},{background:1, unique:false })}catch(e){print(e)}
16 | try{ db.getCollection("repeater").createIndex({"host":1},{background:1, unique:false })}catch(e){print(e)}
17 | try{ db.getCollection("repeater").createIndex({"describe":1},{background:1, unique:false })}catch(e){print(e)}
18 | try{ db.getCollection("repeater").createIndex({"overview_repeater_id":1},{background:1, unique:false })}catch(e){print(e)}
19 | try{ db.getCollection("request_data").createIndex({"path":-1,"host":-1},{background:1, unique:false })}catch(e){print(e)}
20 | try{ db.getCollection("request_data").createIndex({"path":1},{background:1, unique:false })}catch(e){print(e)}
21 | try{ db.getCollection("request_data").createIndex({"host":1},{background:1, unique:false })}catch(e){print(e)}
22 | try{ db.getCollection("request_data").createIndex({"update_time":1},{background:1, unique:false })}catch(e){print(e)}
23 | try{ db.getCollection("request_data").createIndex({"_id":-1},{background:1, unique:false })}catch(e){print(e)}
24 | try{ db.getCollection("request_data").createIndex({"client_ip":1},{background:1, unique:false })}catch(e){print(e)}
25 | try{ db.getCollection("request_data").createIndex({"exclude":1},{background:1, unique:false })}catch(e){print(e)}
26 | try{ db.getCollection("request_data").createIndex({"wait_response":1},{background:1, unique:false })}catch(e){print(e)}
27 | try{ db.getCollection("request_data").createIndex({"intercept":1},{background:1, unique:false })}catch(e){print(e)}
28 | try{ db.getCollection("request_data").createIndex({"user_name":1},{background:1, unique:false })}catch(e){print(e)}
29 | try{ db.getCollection("request_data").createIndex({"isshow":1},{background:1, unique:false })}catch(e){print(e)}
30 | try{ db.getCollection("response_data").createIndex({"update_time":1},{background:1, unique:false })}catch(e){print(e)}
31 | try{ db.getCollection("response_data").createIndex({"req_id":1},{background:1, unique:false })}catch(e){print(e)}
32 | try{ db.getCollection("response_data").createIndex({"_id":-1},{background:1, unique:false })}catch(e){print(e)}
33 | try{ db.getCollection("response_data").createIndex({"client_ip":-1},{background:1, unique:false })}catch(e){print(e)}
34 | try{ db.getCollection("response_data").createIndex({"exclude":-1},{background:1, unique:false })}catch(e){print(e)}
35 | try{ db.getCollection("tcp").createIndex({"exclude":1},{background:1, unique:false })}catch(e){print(e)}
36 | try{ db.getCollection("tcp").createIndex({"server_id":1},{background:1, unique:false })}catch(e){print(e)}
37 | try{ db.getCollection("tcp").createIndex({"client_id":1},{background:1, unique:false })}catch(e){print(e)}
38 | try{ db.getCollection("websocket").createIndex({"conn_id":-1},{background:1, unique:false })}catch(e){print(e)}
39 | try{ db.getCollection("websocket").createIndex({"client_id":-1},{background:1, unique:false })}catch(e){print(e)}
40 | try{ db.getCollection("websocket").createIndex({"server_id":-1},{background:1, unique:false })}catch(e){print(e)}
41 | try{ db.getCollection("apis").createIndex({"host":"text","path":"text"},{background:1, unique:false })}catch(e){print(e)}
42 | try{ db.getCollection("repeater").createIndex({"host":"text","path":"text","content":"text"},{background:1, unique:false })}catch(e){print(e)}
43 | try{ db.getCollection("request_data").createIndex({"host":"text","path":"text","content":"text"},{background:1, unique:false })}catch(e){print(e)}
44 | try{ db.getCollection("response_data").createIndex({"content":"text"},{background:1, unique:false })}catch(e){print(e)}
45 |
--------------------------------------------------------------------------------
/front/src/components/Intercept/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
18 |
27 |
36 |
45 |
54 |
55 | 等待响应
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
245 |
246 |
--------------------------------------------------------------------------------
/front/src/components/History/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
23 |
31 |
32 |
33 |
34 |
42 |
43 |
44 |
45 |
46 | 总共查询结果为: {{ table.tableCount }}条, logger条数: {{ table.loggerCount }}, repeater条数:
48 | {{ table.repeaterCount }}, apis条数: {{ table.apisCount }}
50 | 总共查询结果为: {{ table.tableCount }}条
51 |
52 |
53 |
54 |
55 |
56 |
235 |
236 |
--------------------------------------------------------------------------------
/server/utils/ProtocUtil.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import struct
3 |
4 |
5 | def GetDynamicWireFormat(data, start, end):
6 | wire_type = data[start] & 0x7
7 | firstByte = data[start]
8 | if (firstByte & 0x80) == 0:
9 | field_number = (firstByte >> 3)
10 | return (start + 1, wire_type, field_number)
11 | else:
12 | byteList = []
13 | pos = 0
14 | while True:
15 | if start + pos >= end:
16 | return (None, None, None)
17 | oneByte = data[start + pos]
18 | byteList.append(oneByte & 0x7F)
19 | pos = pos + 1
20 | if oneByte & 0x80 == 0x0:
21 | break
22 |
23 | newStart = start + pos
24 |
25 | index = len(byteList) - 1
26 | field_number = 0
27 | while index >= 0:
28 | field_number = (field_number << 0x7) + byteList[index]
29 | index = index - 1
30 |
31 | field_number = (field_number >> 3)
32 | return (newStart, wire_type, field_number)
33 |
34 |
35 | def RetrieveInt(data, start, end):
36 | pos = 0
37 | byteList = []
38 | while True:
39 | if start + pos >= end:
40 | return (None, None, False)
41 | oneByte = data[start + pos]
42 | byteList.append(oneByte & 0x7F)
43 | pos = pos + 1
44 | if oneByte & 0x80 == 0x0:
45 | break
46 |
47 | newStart = start + pos
48 |
49 | index = len(byteList) - 1
50 | num = 0
51 | while index >= 0:
52 | num = (num << 0x7) + byteList[index]
53 | index = index - 1
54 | return (num, newStart, True)
55 |
56 |
57 | def ParseRepeatedField(data, start, end, message, depth=0):
58 | while start < end:
59 | (num, start, success) = RetrieveInt(data, start, end)
60 | if success == False:
61 | return False
62 | message.append(num)
63 | return True
64 |
65 |
66 | def ParseData(data, start, end, messages, depth=0):
67 | ordinary = 0
68 | while start < end:
69 | (start, wire_type, field_number) = GetDynamicWireFormat(data, start, end)
70 | if start == None:
71 | return False
72 |
73 | if wire_type == 0x00: # Varint
74 | # (num, start, success) = RetrieveInt(data, start+1, end)
75 | (num, start, success) = RetrieveInt(data, start, end)
76 | if success == False:
77 | return False
78 |
79 | messages['%02d:%02d:Varint' % (field_number, ordinary)] = num
80 | ordinary = ordinary + 1
81 |
82 | elif wire_type == 0x01: # 64-bit
83 | num = 0
84 | pos = 7
85 | while pos >= 0:
86 | # if start+1+pos >= end:
87 | if start + pos >= end:
88 | return False
89 | # num = (num << 8) + ord(data[start+1+pos])
90 | num = (num << 8) + data[start + pos]
91 | pos = pos - 1
92 |
93 | # start = start + 9
94 | start = start + 8
95 | try:
96 | floatNum = struct.unpack('d', struct.pack('q', int(hex(num), 16)))
97 | floatNum = floatNum[0]
98 | except:
99 | floatNum = None
100 |
101 | if floatNum != None:
102 | messages['%02d:%02d:64-bit' % (field_number, ordinary)] = floatNum
103 | else:
104 | messages['%02d:%02d:64-bit' % (field_number, ordinary)] = num
105 |
106 | ordinary = ordinary + 1
107 |
108 |
109 | elif wire_type == 0x02: # Length-delimited
110 | (stringLen, start, success) = RetrieveInt(data, start, end)
111 | if success == False:
112 | return False
113 | messages['%02d:%02d:embedded message' % (field_number, ordinary)] = {}
114 | if start + stringLen > end:
115 | messages.pop('%02d:%02d:embedded message' % (field_number, ordinary), None)
116 | return False
117 |
118 | ret = ParseData(data, start, start + stringLen,
119 | messages['%02d:%02d:embedded message' % (field_number, ordinary)], depth + 1)
120 | # print '%d:%d:embedded message' % (field_number, ordinary)
121 | if ret == False:
122 | messages.pop('%02d:%02d:embedded message' % (field_number, ordinary), None)
123 | try:
124 | data[start:start + stringLen].decode('utf-8')
125 | messages['%02d:%02d:string' % (field_number, ordinary)] = data[start:start + stringLen].decode(
126 | 'utf-8')
127 | except:
128 | messages['%02d:%02d:repeated' % (field_number, ordinary)] = []
129 | ret = ParseRepeatedField(data, start, start + stringLen,
130 | messages['%02d:%02d:repeated' % (field_number, ordinary)], depth + 1)
131 | if ret == False:
132 | messages.pop('%02d:%02d:repeated' % (field_number, ordinary), None)
133 | hexStr = ['0x%x' % x for x in data[start:start + stringLen]]
134 | hexStr = ':'.join(hexStr)
135 | messages['%02d:%02d:bytes' % (field_number, ordinary)] = hexStr
136 |
137 | ordinary = ordinary + 1
138 | # start = start+2+stringLen
139 | start = start + stringLen
140 |
141 | elif wire_type == 0x05: # 32-bit
142 | num = 0
143 | pos = 3
144 | while pos >= 0:
145 |
146 | # if start+1+pos >= end:
147 | if start + pos >= end:
148 | return False
149 | # num = (num << 8) + ord(data[start+1+pos])
150 | num = (num << 8) + data[start + pos]
151 | pos = pos - 1
152 |
153 | # start = start + 5
154 | start = start + 4
155 | try:
156 | floatNum = struct.unpack('f', struct.pack('i', int(hex(num), 16)))
157 | floatNum = floatNum[0]
158 | except:
159 | floatNum = None
160 |
161 | if floatNum != None:
162 | messages['%02d:%02d:32-bit' % (field_number, ordinary)] = floatNum
163 | else:
164 | messages['%02d:%02d:32-bit' % (field_number, ordinary)] = num
165 |
166 | ordinary = ordinary + 1
167 |
168 |
169 | else:
170 | return False
171 |
172 | return True
173 |
174 |
175 | def GenValueList(value):
176 | valueList = []
177 | # while value > 0:
178 | while value >= 0:
179 | oneByte = (value & 0x7F)
180 | value = (value >> 0x7)
181 | if value > 0:
182 | oneByte |= 0x80
183 | valueList.append(oneByte)
184 | if value == 0:
185 | break
186 |
187 | return valueList
188 |
189 |
190 | def WriteValue(value, output):
191 | byteWritten = 0
192 | # while value > 0:
193 | while value >= 0:
194 | oneByte = (value & 0x7F)
195 | value = (value >> 0x7)
196 | if value > 0:
197 | oneByte |= 0x80
198 | output.append(oneByte)
199 | byteWritten += 1
200 | if value == 0:
201 | break
202 |
203 | return byteWritten
204 |
205 |
206 | def WriteVarint(field_number, value, output):
207 | byteWritten = 0
208 | wireFormat = (field_number << 3) | 0x00
209 | # output.append(wireFormat)
210 | # byteWritten += 1
211 | byteWritten += WriteValue(wireFormat, output)
212 | # while value > 0:
213 | while value >= 0:
214 | oneByte = (value & 0x7F)
215 | value = (value >> 0x7)
216 | if value > 0:
217 | oneByte |= 0x80
218 | output.append(oneByte)
219 | byteWritten += 1
220 | if value == 0:
221 | break
222 |
223 | return byteWritten
224 |
225 |
226 | def Write64bitFloat(field_number, value, output):
227 | byteWritten = 0
228 | wireFormat = (field_number << 3) | 0x01
229 | # output.append(wireFormat)
230 | # byteWritten += 1
231 | byteWritten += WriteValue(wireFormat, output)
232 |
233 | bytesStr = struct.pack('d', value)
234 | # n = 2
235 | # bytesList = [bytesStr[i:i + n] for i in range(0, len(bytesStr), n)]
236 | # i = len(bytesList) - 1
237 | # while i >= 0:
238 | # output.append(int(bytesList[i],16))
239 | # byteWritten += 1
240 | # i -= 1
241 | for i in range(0, len(bytesStr)):
242 | output.append(bytesStr[i])
243 | byteWritten += 1
244 |
245 | return byteWritten
246 |
247 |
248 | def Write64bit(field_number, value, output):
249 | byteWritten = 0
250 | wireFormat = (field_number << 3) | 0x01
251 | byteWritten += WriteValue(wireFormat, output)
252 | # output.append(wireFormat)
253 | # byteWritten += 1
254 |
255 | for i in range(0, 8):
256 | output.append(value & 0xFF)
257 | value = (value >> 8)
258 | byteWritten += 1
259 |
260 | return byteWritten
261 |
262 |
263 | def Write32bitFloat(field_number, value, output):
264 | byteWritten = 0
265 | wireFormat = (field_number << 3) | 0x05
266 | # output.append(wireFormat)
267 | # byteWritten += 1
268 | byteWritten += WriteValue(wireFormat, output)
269 |
270 | bytesStr = struct.pack('f', value)
271 | # n = 2
272 | # bytesList = [bytesStr[i:i + n] for i in range(0, len(bytesStr), n)]
273 | # i = len(bytesList) - 1
274 | # while i >= 0:
275 | # output.append(int(bytesList[i],16))
276 | # byteWritten += 1
277 | # i -= 1
278 | for i in range(0, len(bytesStr)):
279 | output.append(bytesStr[i])
280 | byteWritten += 1
281 |
282 | return byteWritten
283 |
284 |
285 | def Write32bit(field_number, value, output):
286 | byteWritten = 0
287 | wireFormat = (field_number << 3) | 0x05
288 | # output.append(wireFormat)
289 | # byteWritten += 1
290 | byteWritten += WriteValue(wireFormat, output)
291 |
292 | for i in range(0, 4):
293 | output.append(value & 0xFF)
294 | value = (value >> 8)
295 | byteWritten += 1
296 |
297 | return byteWritten
298 |
299 |
300 | def WriteRepeatedField(message, output):
301 | byteWritten = 0
302 | for v in message:
303 | byteWritten += WriteValue(v, output)
304 | return byteWritten
305 |
306 |
307 | def ProtocEncode(messages, output=[]):
308 | byteWritten = 0
309 | # for key in sorted(messages.iterkeys(), key= lambda x: int(x.split(':')[0]+x.split(':')[1])):
310 | for key in sorted(messages.keys(), key=lambda x: int(x.split(':')[1])):
311 | keyList = key.split(':')
312 | field_number = int(keyList[0])
313 | wire_type = keyList[2]
314 | value = messages[key]
315 |
316 | if wire_type == 'Varint':
317 | byteWritten += WriteVarint(field_number, value, output)
318 | elif wire_type == '32-bit':
319 | if type(value) == type(float(1.0)):
320 | byteWritten += Write32bitFloat(field_number, value, output)
321 | else:
322 | byteWritten += Write32bit(field_number, value, output)
323 | elif wire_type == '64-bit':
324 | if type(value) == type(float(1.0)):
325 | byteWritten += Write64bitFloat(field_number, value, output)
326 | else:
327 | byteWritten += Write64bit(field_number, value, output)
328 | elif wire_type == 'embedded message':
329 | wireFormat = (field_number << 3) | 0x02
330 | byteWritten += WriteValue(wireFormat, output)
331 | index = len(output)
332 | tmpByteWritten = ProtocEncode(messages[key], output)
333 | valueList = GenValueList(tmpByteWritten)
334 | listLen = len(valueList)
335 | for i in range(0, listLen):
336 | output.insert(index, valueList[i])
337 | index += 1
338 | # output[index] = tmpByteWritten
339 | # print "output:", output
340 | byteWritten += tmpByteWritten + listLen
341 | elif wire_type == 'repeated':
342 | wireFormat = (field_number << 3) | 0x02
343 | byteWritten += WriteValue(wireFormat, output)
344 | index = len(output)
345 | tmpByteWritten = WriteRepeatedField(messages[key], output)
346 | valueList = GenValueList(tmpByteWritten)
347 | listLen = len(valueList)
348 | for i in range(0, listLen):
349 | output.insert(index, valueList[i])
350 | index += 1
351 | # output[index] = tmpByteWritten
352 | # print "output:", output
353 | byteWritten += tmpByteWritten + listLen
354 | elif wire_type == 'string':
355 | wireFormat = (field_number << 3) | 0x02
356 | byteWritten += WriteValue(wireFormat, output)
357 |
358 | bytesStr = [elem for elem in messages[key].encode()]
359 |
360 | byteWritten += WriteValue(len(bytesStr), output)
361 |
362 | output.extend(bytesStr)
363 | byteWritten += len(bytesStr)
364 | elif wire_type == 'bytes':
365 | wireFormat = (field_number << 3) | 0x02
366 | byteWritten += WriteValue(wireFormat, output)
367 |
368 | bytesStr = [int(byte, 16) for byte in messages[key].split(':')]
369 | byteWritten += WriteValue(len(bytesStr), output)
370 |
371 | output.extend(bytesStr)
372 | byteWritten += len(bytesStr)
373 |
374 | return byteWritten
--------------------------------------------------------------------------------
/server/utils/repeater.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import base64
4 | import datetime
5 | import json
6 | import re
7 | from concurrent.futures import ThreadPoolExecutor
8 | from itertools import product
9 |
10 | import requests
11 | import urllib3
12 | from hyper import HTTP20Connection
13 |
14 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
15 |
16 |
17 | class Repeater(object):
18 | """docstring for repeater"""
19 |
20 | def __init__(self, db, _request, target_host, target_port, is_https, deal, client_ip,
21 | describe, concurrent_num=0, keepua=None, concurrent_options=None):
22 | super(Repeater, self).__init__()
23 | self.db = db
24 | self.describe = describe
25 | self.proxy = {}
26 | self.inserted_id = None
27 |
28 | # 这里目前只初始化,真实的值根据header中的Host来决定
29 | self.scheme = 'https' if is_https else 'http'
30 | self.host = target_host
31 | self.port = str(target_port)
32 |
33 | self.method = ''
34 | self.path = ''
35 | self.http_version = ''
36 | self.headers = {}
37 | self.content = ''
38 | self.text = None
39 |
40 | self.cookies = None
41 | self.key = None
42 | self.deal = True if deal == '1' or deal == True else False
43 | self.keepua = keepua if keepua is not None else self.deal
44 | self.init_request(_request)
45 | # 获取url的功能描述
46 | self.get_current_req_describe()
47 |
48 | self.res = None
49 | self.res_text = None
50 | self.response = None
51 |
52 | self.url = self.scheme + '://' + self.host + ':' + self.port + self.path
53 | self.client_ip = client_ip
54 |
55 | self.concurrent_num = 0 if concurrent_num < 0 else concurrent_num
56 | self.concurrent_result = None
57 | self.concurrent_data = []
58 | self.concurrent_options = concurrent_options
59 |
60 | self.payload = []
61 | self.concurrent_url = []
62 | self.concurrent_headers = []
63 | self.concurrent_content = []
64 |
65 | def init_request(self, _request):
66 | data = re.split(r'(? 0:
112 | self.concurrent_go()
113 | else:
114 | raise Exception('concurrent_num < 0')
115 |
116 | def base_go(self):
117 | try:
118 | self.deal_mzip()
119 | if self.http_version == '2.0':
120 | conn = HTTP20Connection(host=self.host, secure=self.scheme == 'https')
121 | h2_response = conn.request(method=self.method, url=self.path, headers=self.headers,
122 | body=self.text.encode('utf-8'))
123 | res = conn.get_response(h2_response)
124 | self.res = {'status_code': res.status, 'reason': res.reason, 'content': res.read(),
125 | 'text': res.read()}
126 | else:
127 | self.res = requests.request(method=self.method, url=self.url, headers=self.headers,
128 | data=self.text.encode(), verify=False, proxies=self.proxy)
129 | self.deal_res()
130 | except Exception as e:
131 | raise e
132 | finally:
133 | if self.res is not None:
134 | self.response = {"content": self.decode_response(self.res_text), "headers": dict(self.res.headers),
135 | "http_version": self.http_version, "status_code": self.res.status_code,
136 | "reason": self.res.reason}
137 | rep = self.db['repeater'].insert_one(
138 | {"method": self.method, "scheme": self.scheme, "host": self.host, "port": self.port, "path": self.path,
139 | "http_version": self.http_version, "headers": self.headers, "content": self.content,
140 | "cookie": self.cookies,
141 | "text": self.text, 'response': self.response, "client_ip": self.client_ip, "describe": self.describe,
142 | "api_describe": self.api_describe})
143 | self.inserted_id = rep.inserted_id
144 |
145 | def base_concurrent(self, num_id):
146 | timestamp = datetime.datetime.now()
147 | res = requests.request(method=self.method,
148 | url=self.concurrent_url[num_id] if len(self.concurrent_url) > 0 else self.url,
149 | headers=self.concurrent_headers[num_id] if len(
150 | self.concurrent_headers) > 0 else self.headers,
151 | data=self.concurrent_content[num_id] if len(self.concurrent_content) > 0 else self.text,
152 | verify=False)
153 | return num_id + 1, res, timestamp
154 |
155 | def generate(self, data):
156 | data = data.split('§')
157 | result = []
158 | if self.concurrent_options['type'] == 'sniper':
159 | assert len(self.concurrent_options['payload']) > 0
160 | for x in range(0, int(len(data) / 2)):
161 | for i in self.concurrent_options['payload'][0]:
162 | self.payload.append(str(i))
163 | payload = ''.join(data[:2 * x + 1]) + str(i) + ''.join(data[2 * x + 2:])
164 | result.append(payload)
165 | elif self.concurrent_options['type'] == 'battering ram':
166 | assert len(self.concurrent_options['payload']) > 0
167 | for i in self.concurrent_options['payload'][0]:
168 | self.payload.append(str(i))
169 | payload = ''
170 | for x in range(0, int(len(data) / 2)):
171 | payload += data[2 * x] + str(i)
172 | payload += data[-1]
173 | result.append(payload)
174 | elif self.concurrent_options['type'] == 'pitchfork':
175 | assert len(self.concurrent_options['payload']) > 0 and len(self.concurrent_options['payload']) == int(
176 | len(data) / 2)
177 | for payload_no in range(len(self.concurrent_options['payload'][0])):
178 | payload = ''
179 | tmp_p = ''
180 | for x in range(0, int(len(data) / 2)):
181 | payload += data[2 * x] + self.concurrent_options['payload'][x][payload_no]
182 | tmp_p += self.concurrent_options['payload'][x][payload_no] + ' '
183 | payload += data[-1]
184 | self.payload.append(tmp_p)
185 | result.append(payload)
186 | elif self.concurrent_options['type'] == 'cluster bomb':
187 | assert len(self.concurrent_options['payload']) > 0 and len(self.concurrent_options['payload']) == int(
188 | len(data) / 2)
189 | payload_bomb = product(*self.concurrent_options['payload'])
190 | for i in payload_bomb:
191 | payload = ''
192 | for x in range(0, int(len(data) / 2)):
193 | payload += data[2 * x] + i[x]
194 | payload += data[-1]
195 | self.payload.append(''.join(i))
196 | result.append(payload)
197 |
198 | return result
199 |
200 | def concurrent_go(self):
201 | try:
202 | if self.concurrent_options:
203 | if '§' in self.url and '§' not in self.content:
204 | self.concurrent_url = self.generate(self.url)
205 | pass
206 | elif '§' not in self.url and '§' in self.content:
207 | self.concurrent_content = self.generate(self.content)
208 | pass
209 | elif '§' in self.url and '§' in self.content:
210 | result = self.generate(self.url + '_mloger_split_' + self.content)
211 | for i in result:
212 | self.concurrent_url.append(i.split('_mloger_split_')[0])
213 | self.concurrent_content.append(i.split('_mloger_split_')[1])
214 | elif '§' not in self.url and '§' not in self.content and '§' in json.dumps(self.headers, ensure_ascii=False):
215 | result = self.generate(json.dumps(self.headers, ensure_ascii=False))
216 | for i in result:
217 | self.concurrent_headers.append(json.loads(i))
218 | else:
219 | pass
220 | self.deal_mzip()
221 | with ThreadPoolExecutor(max_workers=15) as executor:
222 | self.concurrent_result = executor.map(self.base_concurrent, [i for i in range(self.concurrent_num)])
223 | except Exception as e:
224 | raise e
225 | all_repeater_data = []
226 | for result in self.concurrent_result:
227 | num_id, res, timestamp = result
228 | if num_id == 1:
229 | self.response = {"content": '', "headers": dict(res.headers), "http_version": self.http_version,
230 | "status_code": res.status_code, "reason": res.reason}
231 | if res is not None:
232 | self.res = res
233 | self.deal_res()
234 | text = self.res_text
235 | text = self.decode_response(text)
236 | self.response['content'] += (self.payload[num_id - 1] if len(
237 | self.payload) > 0 else 'None') + text + '\n'
238 | self.concurrent_data.append(
239 | {'num_id': num_id, 'content': text, 'status_code': res.status_code, 'headers': dict(res.headers),
240 | 'http_version': self.http_version, 'reason': res.reason,
241 | 'payload': self.payload[num_id - 1] if len(self.payload) > 0 else 'None', "timestamp": timestamp})
242 | all_repeater_data.append(
243 | {"method": self.method, "scheme": self.scheme, "host": self.host, "port": self.port,
244 | "path": self.path,
245 | "http_version": self.http_version,
246 | "headers": self.concurrent_headers[num_id - 1] if len(
247 | self.concurrent_headers) > 0 else self.headers,
248 | "content": self.content,
249 | "cookie": self.cookies,
250 | "text": self.concurrent_content[num_id - 1] if len(self.concurrent_content) > 0 else self.text,
251 | "payload": self.payload[num_id - 1] if len(self.payload) > 0 else 'None',
252 | "num_id": num_id,
253 | 'response': {"content": text, "headers": dict(res.headers), "http_version": self.http_version,
254 | "status_code": res.status_code, "reason": res.reason}, "client_ip": self.client_ip,
255 | "describe": self.describe,
256 | "api_describe": self.api_describe})
257 |
258 | else:
259 | self.response['content'] += '\n'
260 | self.concurrent_data.append(
261 | {'num_id': num_id, 'content': 'None', 'status_code': 'None', 'headers': {}, 'http_version': 'None',
262 | 'reason': 'None', 'payload': 'None', "timestamp": "None"})
263 | all_repeater_data.append(
264 | {"method": self.method, "scheme": self.scheme, "host": self.host, "port": self.port,
265 | "path": self.path,
266 | "http_version": self.http_version,
267 | "headers": self.concurrent_headers[num_id - 1] if len(
268 | self.concurrent_headers) > 0 else self.headers,
269 | "content": self.content,
270 | "cookie": self.cookies,
271 | "text": self.concurrent_content[num_id - 1] if len(self.concurrent_content) > 0 else self.text,
272 | "payload": self.payload[num_id - 1] if len(self.payload) > 0 else 'None',
273 | "num_id": num_id,
274 | 'response': {"content": 'None', "headers": {}, "http_version": 'None',
275 | "status_code": 'None', "reason": 'None'}, "client_ip": self.client_ip,
276 | "describe": self.describe,
277 | "api_describe": self.api_describe})
278 | rep = self.db['repeater'].insert_one(
279 | {"method": self.method, "scheme": self.scheme, "host": self.host, "port": self.port, "path": self.path,
280 | "http_version": self.http_version, "headers": self.headers, "content": self.content,
281 | "cookie": self.cookies,
282 | "text": self.text, 'response': self.response, "client_ip": self.client_ip, "describe": self.describe,
283 | "api_describe": self.api_describe})
284 | self.inserted_id = rep.inserted_id
285 | for request_data in all_repeater_data:
286 | request_data['overview_repeater_id'] = str(self.inserted_id)
287 | self.db['repeater'].insert_one(request_data)
288 | return self.concurrent_data
289 |
290 | def deal_mzip(self):
291 | self.text = self.content
292 | return
293 |
294 | def deal_res(self):
295 | self.res_text = self.res.text
296 | return
297 |
298 | def deal_third_id_and_cookie(self):
299 | pass
300 |
301 | def deal_third_req(self, mzip):
302 | pass
303 |
304 | def get_current_req_describe(self):
305 | path = self.path.split('?')[0]
306 | api = self.db['apis'].find_one({'path': path, 'host': self.host, 'describe': {'$ne': None}})
307 | if api:
308 | self.api_describe = api['describe']
309 | else:
310 | self.api_describe = None
311 |
--------------------------------------------------------------------------------
/front/src/components/Imws/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
111 |
112 |
113 |
472 |
473 |
--------------------------------------------------------------------------------
/server/proxy/http_mitm_script.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import asyncio
4 | import json
5 | import time
6 | import base64
7 | import traceback
8 |
9 | import mitmproxy.http
10 | from mitmproxy import http, ctx
11 | from mitmproxy.script import concurrent
12 |
13 | import os
14 | import sys
15 | sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
16 |
17 | from db.mongo_db import get_mongo_client
18 | from db.redis_db import get_redis_client
19 | from utils.proxypriv import client_ip_is_allowed, get_user_name_by_ip_for_redis, domain_is_intranet, flow_is_black, get_client_ip
20 | from utils.common import *
21 | from utils.log import logger
22 |
23 | db = get_mongo_client()
24 | rc = get_redis_client()
25 |
26 | db['connections'].update({'type': 'websocket', 'live': True}, {'$set': {'live': False, 'timestamp_end': time.time()}},
27 | multi=True)
28 | websocket_conns_dict = {}
29 | req_id_dict = {}
30 | intercept_req_id_dict = {}
31 |
32 |
33 | def handle_crypto(host, path, content):
34 | crypto = False
35 | crypto_data = None
36 | return crypto, crypto_data
37 |
38 |
39 | @concurrent
40 | def request(flow: http.HTTPFlow) -> None:
41 | in_black = flow_is_black(flow)
42 | client_ip = get_client_ip(flow)
43 | if not in_black:
44 | try:
45 | if flow.request:
46 | user_name = get_user_name_by_ip_for_redis(client_ip)
47 | raw_content = flow.request.get_content(strict=False)
48 | content = raw_content.decode('latin-1')
49 | crypto, crypto_data = handle_crypto(flow.request.host, flow.request.path, content)
50 | data = {
51 | "method": flow.request.method,
52 | "scheme": flow.request.scheme,
53 | "host": flow.request.host,
54 | "port": flow.request.port,
55 | "path": flow.request.path,
56 | "http_version": flow.request.http_version,
57 | "headers": dict(flow.request.headers),
58 | "content": raw_content.decode("utf-8", "ignore"),
59 | "timestamp_start": flow.request.timestamp_start,
60 | "timestamp_end": flow.request.timestamp_end,
61 | "client_ip": client_ip,
62 | "exclude": in_black,
63 | "crypto": crypto,
64 | "crypto_data": crypto_data,
65 | "user_name": user_name,
66 | }
67 | req_id = db['request_data'].insert_one(data).inserted_id
68 | req_id_dict[data['host'] + '_' + data['path'] + '_' + str(data['timestamp_start']) + '_' + str(
69 | data['timestamp_end'])] = req_id
70 | data['_id'] = str(req_id)
71 | if in_black:
72 | return
73 | if crypto:
74 | rc.lpush("mloger:request_data", json.dumps(data))
75 | elif not in_black:
76 | rc.publish("mloger:logger_request", json.dumps(data))
77 | # 判断拦截条件,看是否修改数据
78 | intercept = rc.get('mloger:intercept:request:' + client_ip)
79 | if intercept:
80 | db['request_data'].update_one({'_id': req_id}, {
81 | "$set": {'intercept': True}})
82 | ps = rc.pubsub()
83 | ps.subscribe('intercept')
84 | t = 0
85 | intercept_data = None
86 | while t < 3000:
87 | message = ps.get_message(ignore_subscribe_messages=True, timeout=0.1)
88 | if message:
89 | intercept_data = json.loads(message['data'])
90 | if intercept_data.get('flow_id', '') == str(req_id):
91 | break
92 | else:
93 | time.sleep(0.1)
94 | t += 1
95 | ps.close()
96 |
97 | if intercept_data and intercept_data['forward']:
98 | if intercept_data['edited']:
99 | flow.request.content = intercept_data['request_edited'].encode()
100 | crypto, crypto_data = handle_crypto(flow.request.host, flow.request.path,
101 | intercept_data['request_edited'])
102 | data = {
103 | "method": flow.request.method,
104 | "scheme": flow.request.scheme,
105 | "host": flow.request.host,
106 | "port": flow.request.port,
107 | "path": flow.request.path,
108 | "http_version": flow.request.http_version,
109 | "headers": dict(flow.request.headers),
110 | "content": intercept_data['request_edited'].encode().decode("utf-8", "ignore"),
111 | "timestamp_start": flow.request.timestamp_start,
112 | "timestamp_end": flow.request.timestamp_end,
113 | "client_ip": client_ip,
114 | "exclude": in_black,
115 | "crypto": crypto,
116 | "crypto_data": crypto_data,
117 | "user_name": user_name,
118 | }
119 | req_id = db['request_data'].insert_one(data).inserted_id
120 | if intercept_data['wait_response']:
121 | intercept_req_id_dict[
122 | data['host'] + '_' + data['path'] + '_' + str(data['timestamp_start']) + '_' + str(
123 | data['timestamp_end'])] = req_id
124 | data['_id'] = str(req_id)
125 | rc.lpush("mloger:request_data", json.dumps(data))
126 | else:
127 | flow.kill()
128 | except Exception as e:
129 | logger.debug(traceback.print_exc())
130 | pass
131 | finally:
132 | pass
133 | else:
134 | if domain_is_intranet(flow.request.host):
135 | flow.kill()
136 |
137 |
138 | def handle_res_crypto(host, content):
139 | crypto = False
140 | crypto_data = None
141 | if not crypto:
142 | crypto_data = content.decode('utf-8', 'ignore')
143 | return crypto, crypto_data
144 |
145 |
146 | @concurrent
147 | def response(flow: http.HTTPFlow) -> None:
148 | in_black = flow_is_black(flow)
149 | client_ip = get_client_ip(flow)
150 | if not in_black:
151 | try:
152 | if flow.response:
153 | user_name = get_user_name_by_ip_for_redis(client_ip)
154 | req_id = req_id_dict.pop(
155 | flow.request.host + '_' + flow.request.path + '_' + str(flow.request.timestamp_start) + '_' + str(
156 | flow.request.timestamp_end), None)
157 | content = flow.response.get_content(strict=False)
158 | if len(content) > 1048576 or flow.request.path.endswith(file_type_black_list):
159 | content = b'Large response or binary files are not stored.'
160 | crypto, crypto_data = handle_res_crypto(flow.request.host, content)
161 | data = {
162 | "req_id": req_id,
163 | "http_version": flow.response.http_version,
164 | "status_code": flow.response.status_code,
165 | "reason": flow.response.reason,
166 | "headers": dict(flow.response.headers),
167 | "content": crypto_data,
168 | "timestamp_start": flow.response.timestamp_start,
169 | "timestamp_end": flow.response.timestamp_end,
170 | "client_ip": client_ip,
171 | "exclude": in_black,
172 | "crypto": crypto,
173 | "crypto_data": crypto_data if crypto else None,
174 | "user_name": user_name,
175 | }
176 | res_id = db['response_data'].insert_one(data).inserted_id
177 | db['request_response_map'].insert_one({'req_id': data['req_id'], 'res_id': res_id})
178 | data['_id'] = str(res_id)
179 | data['req_id'] = str(data['req_id'])
180 | if in_black:
181 | return
182 | if crypto:
183 | rc.lpush("mloger:response_data", json.dumps(data))
184 | else:
185 | try:
186 | data['content'] = json.dumps(json.loads(data['content']), indent=4).encode().decode(
187 | 'unicode_escape')
188 | except:
189 | pass
190 | rc.publish("mloger:logger_request", json.dumps(data))
191 | intercept = rc.get('mloger:intercept:wait_response:' + str(req_id))
192 | if intercept:
193 | req_id = intercept_req_id_dict.pop(flow.request.host + '_' + flow.request.path + '_' + str(
194 | flow.request.timestamp_start) + '_' + str(flow.request.timestamp_end), None)
195 | ps = rc.pubsub()
196 | ps.subscribe('intercept')
197 | t = 0
198 | intercept_data = None
199 | while t < 3000:
200 | message = ps.get_message(ignore_subscribe_messages=True, timeout=0.1)
201 | if message:
202 | intercept_data = json.loads(message['data'])
203 | if intercept_data.get('flow_id', '') == str(res_id):
204 | break
205 | else:
206 | time.sleep(0.1)
207 | t += 1
208 | ps.close()
209 | if intercept_data and intercept_data['forward']:
210 | if intercept_data['edited']:
211 | if intercept_data.get('b64', False):
212 | flow.response.content = base64.b64decode(intercept_data['response_edited'])
213 | elif type(intercept_data['response_edited']) is str:
214 | flow.response.content = intercept_data['response_edited'].encode()
215 | else:
216 | flow.response.content = intercept_data['response_edited']
217 | content = flow.response.get_content(strict=False)
218 | crypto, crypto_data = handle_res_crypto(flow.request.host, content)
219 | data = {
220 | "req_id": req_id,
221 | "http_version": flow.response.http_version,
222 | "status_code": flow.response.status_code,
223 | "reason": flow.response.reason,
224 | "headers": dict(flow.response.headers),
225 | "content": crypto_data,
226 | "timestamp_start": flow.response.timestamp_start,
227 | "timestamp_end": flow.response.timestamp_end,
228 | "client_ip": client_ip,
229 | "exclude": in_black,
230 | "crypto": crypto,
231 | "crypto_data": crypto_data if crypto else None,
232 | "user_name": user_name,
233 | }
234 | res_id = db['response_data'].insert_one(data).inserted_id
235 | data['_id'] = str(res_id)
236 | data['req_id'] = str(data['req_id'])
237 | rc.lpush("mloger:response_data", json.dumps(data))
238 | else:
239 | flow.kill()
240 | except Exception as e:
241 | logger.debug(traceback.print_exc())
242 | finally:
243 | pass
244 |
245 |
246 | @concurrent
247 | def websocket_message(flow: mitmproxy.http.HTTPFlow):
248 | in_black = flow_is_black(flow)
249 | client_ip = get_client_ip(flow)
250 | if not in_black:
251 | user_name = get_user_name_by_ip_for_redis(client_ip)
252 | message = flow.websocket.messages[-1]
253 | print(message)
254 | if type(message.content) is str:
255 | message.content = message.content.encode()
256 | if b'Ping' in message.content or b'Pong' in message.content:
257 | in_black = True
258 | data = {
259 | 'client_id': flow.client_conn.id,
260 | 'server_id': flow.server_conn.id,
261 | 'client_host': client_ip,
262 | 'client_port': flow.client_conn.address[1],
263 | 'server_host': flow.server_conn.address[0],
264 | 'server_port': flow.server_conn.address[1],
265 | 'direction': 'send' if message.from_client else 'receive',
266 | 'content': message.content,
267 | "client_ip": client_ip,
268 | "exclude": in_black,
269 | "timestamp_start": flow.client_conn.timestamp_start,
270 | "conn_id": websocket_conns_dict.get(flow.client_conn.id + flow.server_conn.id, None),
271 | "user_name": user_name,
272 | }
273 | mes_id = db['websocket'].insert_one(data).inserted_id
274 | data['_id'] = str(mes_id)
275 | data['type'] = 'websocket'
276 | if in_black:
277 | return
278 | data['content'] = data['content'].decode('utf-8', 'ignore')
279 | rc.publish("mloger:imws_websocket", json.dumps(data))
280 |
281 |
282 | def websocket_start(flow: mitmproxy.http.HTTPFlow):
283 | client_ip = get_client_ip(flow)
284 | if not client_ip_is_allowed(client_ip):
285 | flow.kill()
286 | return
287 | user_name = get_user_name_by_ip_for_redis(client_ip)
288 | data = {
289 | 'type': 'websocket',
290 | 'live': True,
291 | 'client_id': flow.client_conn.id,
292 | 'server_id': flow.server_conn.id,
293 | 'client_host': client_ip,
294 | 'client_port': flow.client_conn.address[1],
295 | 'server_host': flow.server_conn.address[0],
296 | 'server_port': flow.server_conn.address[1],
297 | 'timestamp_start': flow.client_conn.timestamp_start,
298 | 'user_name': user_name,
299 | }
300 | conn_id = db['connections'].insert_one(data).inserted_id
301 | websocket_conns_dict[flow.client_conn.id + flow.server_conn.id] = str(conn_id)
302 | data['_id'] = str(data['_id'])
303 | rc.publish("mloger:imws_websocket", json.dumps(data))
304 | asyncio.create_task(inject_websocket(flow))
305 |
306 |
307 | async def inject_websocket(flow: mitmproxy.http.HTTPFlow):
308 | # 注入功能
309 | logger.info('inject_websocket start')
310 | while db['connections'].find_one({'type': 'websocket',
311 | 'live': True,
312 | 'client_id': flow.client_conn.id,
313 | 'server_id': flow.server_conn.id, }):
314 | ws_mes = db['inject_repeater'].find_one({'client_id': flow.client_conn.id,
315 | 'server_id': flow.server_conn.id, 'status': None, 'type': 'websocket'})
316 | if ws_mes:
317 | to_client = False if ws_mes['direction'] == 'send' else True
318 | is_text = True
319 | concurrent_num = int(ws_mes.get('concurrent_num', 0))
320 | if isinstance(ws_mes['content'], str):
321 | ws_mes['content'] = ws_mes['content'].encode()
322 | logger.info(ws_mes['content'])
323 | if concurrent_num > 0:
324 | for _ in range(concurrent_num):
325 | ctx.master.commands.call("inject.websocket", flow, to_client, ws_mes['content'], is_text)
326 | else:
327 | logger.info("inject.websocket------------")
328 | ctx.master.commands.call("inject.websocket", flow, to_client, ws_mes['content'], is_text)
329 | db['inject_repeater'].update_one({'_id': ws_mes['_id']}, {'$set': {'status': 'done'}})
330 | await asyncio.sleep(0.5)
331 |
332 |
333 | def websocket_error(flow):
334 | # 更新websocket连接状态
335 | data = {
336 | 'type': 'websocket',
337 | 'live': True,
338 | 'client_id': flow.client_conn.id,
339 | 'server_id': flow.server_conn.id,
340 | 'server_host': flow.server_conn.address[0],
341 | 'server_port': flow.server_conn.address[1],
342 | }
343 | db['connections'].update_one(data, {'$set': {'live': False, 'timestamp_end': time.time()}})
344 | data['_id'] = str(websocket_conns_dict.pop(flow.client_conn.id + flow.server_conn.id, ''))
345 | data['live'] = False
346 | rc.publish("mloger:imws_websocket", json.dumps(data))
347 |
348 |
349 | @concurrent
350 | def websocket_end(flow: mitmproxy.http.HTTPFlow):
351 | # 更新websocket连接状态
352 | data = {
353 | 'type': 'websocket',
354 | 'live': True,
355 | 'client_id': flow.client_conn.id,
356 | 'server_id': flow.server_conn.id,
357 | 'server_host': flow.server_conn.address[0],
358 | 'server_port': flow.server_conn.address[1],
359 | }
360 | db['connections'].update_one(data, {'$set': {'live': False, 'timestamp_end': time.time()}})
361 | data['_id'] = str(websocket_conns_dict.pop(flow.client_conn.id + flow.server_conn.id, ''))
362 | data['live'] = False
363 | rc.publish("mloger:imws_websocket", json.dumps(data))
364 |
--------------------------------------------------------------------------------
/front/src/components/Logger/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
139 |
140 |
141 |
618 |
619 |
625 |
626 |
--------------------------------------------------------------------------------
/server/proxy/socks5_mitm_script.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import asyncio
4 | import json
5 | import time
6 | import base64
7 | import traceback
8 |
9 | import mitmproxy
10 | from mitmproxy import http, ctx
11 | from mitmproxy.script import concurrent
12 |
13 | import os
14 | import sys
15 | sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
16 |
17 | from utils.log import logger
18 | from db.mongo_db import get_mongo_client
19 | from db.redis_db import get_redis_client
20 | from utils.proxypriv import client_ip_is_allowed, get_user_name_by_ip_for_redis, domain_is_intranet, flow_is_black, get_client_ip
21 |
22 | db = get_mongo_client()
23 | rc = get_redis_client()
24 |
25 | db['connections'].update({'type': 'tcp', 'live': True}, {'$set': {'live': False, 'timestamp_end': time.time()}},
26 | multi=True)
27 | tcp_conns_dict = {}
28 | req_id_dict = {}
29 | intercept_req_id_dict = {}
30 | websocket_conns_dict = {}
31 |
32 |
33 | def handle_crypto(host, path, content):
34 | crypto = False
35 | crypto_data = None
36 | return crypto, crypto_data
37 |
38 |
39 | @concurrent
40 | def request(flow: http.HTTPFlow) -> None:
41 | flow.request.host = flow.request.headers.get('Host', flow.request.host)
42 | in_black = flow_is_black(flow)
43 | client_ip = get_client_ip(flow)
44 | if not in_black:
45 | try:
46 | if flow.request:
47 | user_name = get_user_name_by_ip_for_redis(client_ip)
48 | raw_content = flow.request.get_content(strict=False)
49 | content = raw_content.decode('latin-1')
50 | crypto, crypto_data = handle_crypto(flow.request.host, flow.request.path, content)
51 | data = {
52 | "method": flow.request.method,
53 | "scheme": flow.request.scheme,
54 | "host": flow.request.host,
55 | "port": flow.request.port,
56 | "path": flow.request.path,
57 | "http_version": flow.request.http_version,
58 | "headers": dict(flow.request.headers),
59 | "content": raw_content.decode("utf-8", "ignore"),
60 | "timestamp_start": flow.request.timestamp_start,
61 | "timestamp_end": flow.request.timestamp_end,
62 | "client_ip": client_ip,
63 | "exclude": in_black,
64 | "crypto": crypto,
65 | "crypto_data": crypto_data,
66 | "user_name": user_name,
67 | }
68 | req_id = db['request_data'].insert_one(data).inserted_id
69 | req_id_dict[data['host'] + '_' + data['path'] + '_' + str(data['timestamp_start']) + '_' + str(
70 | data['timestamp_end'])] = req_id
71 | data['_id'] = str(req_id)
72 | if in_black:
73 | return
74 | if crypto:
75 | rc.lpush("mloger:request_data", json.dumps(data))
76 | elif not in_black:
77 | rc.publish("mloger:logger_request", json.dumps(data))
78 | # 判断拦截条件,看是否修改数据
79 | intercept = rc.get('mloger:intercept:request:' + client_ip)
80 | if intercept:
81 | db['request_data'].update_one({'_id': req_id}, {
82 | "$set": {'intercept': True}})
83 | ps = rc.pubsub()
84 | ps.subscribe('intercept')
85 | t = 0
86 | intercept_data = None
87 | while t < 3000:
88 | message = ps.get_message(ignore_subscribe_messages=True, timeout=0.1)
89 | if message:
90 | intercept_data = json.loads(message['data'])
91 | if intercept_data.get('flow_id', '') == str(req_id):
92 | break
93 | else:
94 | time.sleep(0.1)
95 | t += 1
96 | ps.close()
97 | if intercept_data and intercept_data['forward']:
98 | if intercept_data['edited']:
99 | flow.request.content = intercept_data['request_edited'].encode()
100 | crypto, crypto_data = handle_crypto(flow.request.host, flow.request.path,
101 | intercept_data['request_edited'])
102 | data = {
103 | "method": flow.request.method,
104 | "scheme": flow.request.scheme,
105 | "host": flow.request.host,
106 | "port": flow.request.port,
107 | "path": flow.request.path,
108 | "http_version": flow.request.http_version,
109 | "headers": dict(flow.request.headers),
110 | "content": intercept_data['request_edited'].encode().decode("utf-8", "ignore"),
111 | "timestamp_start": flow.request.timestamp_start,
112 | "timestamp_end": flow.request.timestamp_end,
113 | "client_ip": client_ip,
114 | "exclude": in_black,
115 | "crypto": crypto,
116 | "crypto_data": crypto_data,
117 | "user_name": user_name,
118 | }
119 | req_id = db['request_data'].insert_one(data).inserted_id
120 | if intercept_data['wait_response']:
121 | intercept_req_id_dict[
122 | data['host'] + '_' + data['path'] + '_' + str(data['timestamp_start']) + '_' + str(
123 | data['timestamp_end'])] = req_id
124 | data['_id'] = str(req_id)
125 | rc.lpush("mloger:request_data", json.dumps(data))
126 | else:
127 | flow.kill()
128 | except Exception as e:
129 | logger.debug(traceback.print_exc())
130 | pass
131 | finally:
132 | pass
133 | else:
134 | if domain_is_intranet(flow.request.host):
135 | flow.kill()
136 |
137 |
138 | def handle_res_crypto(host, content):
139 | crypto = False
140 | crypto_data = None
141 | if not crypto:
142 | crypto_data = content.decode('utf-8', 'ignore')
143 | return crypto, crypto_data
144 |
145 |
146 | @concurrent
147 | def response(flow: http.HTTPFlow) -> None:
148 | in_black = flow_is_black(flow)
149 | client_ip = get_client_ip(flow)
150 | if not in_black:
151 | try:
152 | if flow.response:
153 | user_name = get_user_name_by_ip_for_redis(client_ip)
154 | req_id = req_id_dict.pop(
155 | flow.request.host + '_' + flow.request.path + '_' + str(flow.request.timestamp_start) + '_' + str(
156 | flow.request.timestamp_end), None)
157 | content = flow.response.content
158 | if len(flow.response.content) > 1048576:
159 | content = b'Large response or binary files are not stored.'
160 | crypto, crypto_data = handle_res_crypto(flow.request.host, content)
161 | data = {
162 | "req_id": req_id,
163 | "http_version": flow.response.http_version,
164 | "status_code": flow.response.status_code,
165 | "reason": flow.response.reason,
166 | "headers": dict(flow.response.headers),
167 | "content": crypto_data,
168 | "timestamp_start": flow.response.timestamp_start,
169 | "timestamp_end": flow.response.timestamp_end,
170 | "client_ip": client_ip,
171 | "exclude": in_black,
172 | "crypto": crypto,
173 | "crypto_data": crypto_data if crypto else None,
174 | "user_name": user_name,
175 | }
176 | res_id = db['response_data'].insert_one(data).inserted_id
177 | db['request_response_map'].insert_one({'req_id': data['req_id'], 'res_id': res_id})
178 | data['_id'] = str(res_id)
179 | data['req_id'] = str(data['req_id'])
180 | if in_black:
181 | return
182 | if crypto:
183 | rc.lpush("mloger:response_data", json.dumps(data))
184 | else:
185 | try:
186 | data['content'] = json.dumps(json.loads(data['content']), indent=4).encode().decode(
187 | 'unicode_escape')
188 | except:
189 | pass
190 | rc.publish("mloger:logger_request", json.dumps(data))
191 | intercept = rc.get('mloger:intercept:wait_response:' + str(req_id))
192 | if intercept:
193 | req_id = intercept_req_id_dict.pop(flow.request.host + '_' + flow.request.path + '_' + str(
194 | flow.request.timestamp_start) + '_' + str(flow.request.timestamp_end), None)
195 | ps = rc.pubsub()
196 | ps.subscribe('intercept')
197 | t = 0
198 | intercept_data = None
199 | while t < 3000:
200 | message = ps.get_message(ignore_subscribe_messages=True, timeout=0.1)
201 | if message:
202 | intercept_data = json.loads(message['data'])
203 | if intercept_data.get('flow_id', '') == str(res_id):
204 | break
205 | else:
206 | time.sleep(0.1)
207 | t += 1
208 | ps.close()
209 | if intercept_data and intercept_data['forward']:
210 | if intercept_data['edited']:
211 | if type(intercept_data['response_edited']) is str:
212 | flow.response.content = intercept_data['response_edited'].encode()
213 | else:
214 | flow.response.content = intercept_data['response_edited']
215 | content = flow.response.get_content(strict=False)
216 | crypto, crypto_data = handle_res_crypto(flow.request.host, content)
217 | data = {
218 | "req_id": req_id,
219 | "http_version": flow.response.http_version,
220 | "status_code": flow.response.status_code,
221 | "reason": flow.response.reason,
222 | "headers": dict(flow.response.headers),
223 | "content": crypto_data,
224 | "timestamp_start": flow.response.timestamp_start,
225 | "timestamp_end": flow.response.timestamp_end,
226 | "client_ip": client_ip,
227 | "exclude": in_black,
228 | "crypto": crypto,
229 | "crypto_data": crypto_data if crypto else None,
230 | "user_name": user_name,
231 | }
232 | res_id = db['response_data'].insert_one(data).inserted_id
233 | data['_id'] = str(res_id)
234 | data['req_id'] = str(data['req_id'])
235 | rc.lpush("mloger:response_data", json.dumps(data))
236 | else:
237 | flow.kill()
238 | except Exception as e:
239 | logger.debug(traceback.print_exc())
240 | finally:
241 | pass
242 |
243 |
244 | def handle_tcp_crypto(content):
245 | crypto = False
246 | crypto_data = None
247 | return crypto, crypto_data
248 |
249 |
250 | @concurrent
251 | def tcp_message(flow):
252 | # tcp连接暂无域名策略限制
253 | in_black = False
254 | client_ip = get_client_ip(flow)
255 | if not client_ip_is_allowed(client_ip):
256 | flow.kill()
257 | return
258 | message = flow.messages[-1]
259 | content = message.content
260 | # 收到的数据包判断
261 | data_list = []
262 | if not message.from_client:
263 | # 超长数据包等待拼接处理
264 | if len(message.content) > 4090:
265 | return
266 | else:
267 | # 进行回溯拼接处理
268 | i = -2
269 | while len(flow.messages) >= -1 * i:
270 | if flow.messages[i].from_client:
271 | break
272 | elif len(flow.messages[i].content) < 4090:
273 | break
274 | else:
275 | content = flow.messages[i].content + content
276 | i -= 1
277 | if not data_list:
278 | data_list = [content]
279 | user_name = get_user_name_by_ip_for_redis(client_ip)
280 | for content in data_list:
281 | if content == b'\x03\x01' or content == b'\x03\x81':
282 | in_black = True
283 | crypto, crypto_data = handle_tcp_crypto(content)
284 | data = {
285 | 'client_id': flow.client_conn.id,
286 | 'server_id': flow.server_conn.id,
287 | 'client_host': client_ip,
288 | 'client_port': flow.client_conn.address[1],
289 | 'server_host': flow.server_conn.address[0],
290 | 'server_port': flow.server_conn.address[1],
291 | 'direction': 'send' if message.from_client else 'receive',
292 | 'content': content,
293 | "client_ip": client_ip,
294 | "exclude": in_black,
295 | "timestamp_start": flow.client_conn.timestamp_start,
296 | "conn_id": tcp_conns_dict.get(flow.client_conn.id + flow.server_conn.id, None),
297 | "crypto": crypto,
298 | "crypto_data": crypto_data,
299 | "user_name": user_name,
300 | }
301 | mes_id = db['tcp'].insert_one(data).inserted_id
302 | data['_id'] = str(mes_id)
303 | data['conn_id'] = str(data['conn_id'])
304 | data['content'] = base64.b64encode(data['content']).decode()
305 | data['type'] = 'tcp'
306 | if in_black:
307 | return
308 | if crypto:
309 | rc.lpush("mloger:tcp_data", json.dumps(data))
310 | else:
311 | rc.publish("mloger:imws_websocket", json.dumps(data))
312 |
313 |
314 | def tcp_start(flow):
315 | client_ip = get_client_ip(flow)
316 | if not client_ip_is_allowed(client_ip):
317 | flow.kill()
318 | return
319 | user_name = get_user_name_by_ip_for_redis(client_ip)
320 | data = {
321 | 'type': 'tcp',
322 | 'live': True,
323 | 'client_id': flow.client_conn.id,
324 | 'server_id': flow.server_conn.id,
325 | 'client_host': client_ip,
326 | 'client_port': flow.client_conn.address[1],
327 | 'server_host': flow.server_conn.address[0],
328 | 'server_port': flow.server_conn.address[1],
329 | 'timestamp_start': flow.client_conn.timestamp_start,
330 | 'user_name': user_name,
331 | }
332 | conn_id = db['connections'].insert_one(data).inserted_id
333 | tcp_conns_dict[flow.client_conn.id + flow.server_conn.id] = conn_id
334 | data['_id'] = str(data['_id'])
335 | rc.publish("mloger:imws_websocket", json.dumps(data))
336 | asyncio.create_task(inject_tcp(flow))
337 |
338 |
339 | async def inject_tcp(flow):
340 | # 注入功能
341 | while db['connections'].find_one({'type': 'tcp',
342 | 'live': True,
343 | 'client_id': flow.client_conn.id,
344 | 'server_id': flow.server_conn.id, }):
345 | tcp_mes = db['inject_repeater'].find_one({'client_id': flow.client_conn.id,
346 | 'server_id': flow.server_conn.id, 'status': None, 'type': 'tcp'})
347 | if tcp_mes:
348 | if type(tcp_mes['content']) is str:
349 | tcp_mes['content'] = tcp_mes['content'].encode()
350 | to_client = False if tcp_mes['direction'] == 'send' else True
351 | if tcp_mes['concurrent_num'] > 0:
352 | for _ in range(tcp_mes['concurrent_num']):
353 | ctx.master.commands.call("inject.tcp", flow, to_client, tcp_mes['content'])
354 | else:
355 | ctx.master.commands.call("inject.tcp", flow, to_client, tcp_mes['content'])
356 | db['inject_repeater'].update_one({'_id': tcp_mes['_id']}, {'$set': {'status': 'done'}})
357 | await asyncio.sleep(0.5)
358 |
359 |
360 | def tcp_error(flow):
361 | # 更新websocket连接状态
362 | data = {
363 | 'type': 'tcp',
364 | 'live': True,
365 | 'client_id': flow.client_conn.id,
366 | 'server_id': flow.server_conn.id,
367 | 'server_host': flow.server_conn.address[0],
368 | 'server_port': flow.server_conn.address[1],
369 | }
370 | db['connections'].update_one(data, {'$set': {'live': False, 'timestamp_end': time.time()}})
371 | data['_id'] = str(tcp_conns_dict.pop(flow.client_conn.id + flow.server_conn.id))
372 | data['live'] = False
373 | rc.publish("mloger:imws_websocket", json.dumps(data))
374 |
375 |
376 | @concurrent
377 | def tcp_end(flow):
378 | # 更新websocket连接状态
379 | data = {
380 | 'type': 'tcp',
381 | 'live': True,
382 | 'client_id': flow.client_conn.id,
383 | 'server_id': flow.server_conn.id,
384 | 'server_host': flow.server_conn.address[0],
385 | 'server_port': flow.server_conn.address[1],
386 | }
387 | db['connections'].update_one(data, {'$set': {'live': False, 'timestamp_end': time.time()}})
388 | data['_id'] = str(tcp_conns_dict.pop(flow.client_conn.id + flow.server_conn.id))
389 | data['live'] = False
390 | rc.publish("mloger:imws_websocket", json.dumps(data))
391 |
392 |
393 | @concurrent
394 | def websocket_message(flow):
395 | in_black = flow_is_black(flow)
396 | client_ip = get_client_ip(flow)
397 | if not in_black:
398 | user_name = get_user_name_by_ip_for_redis(client_ip)
399 | message = flow.messages[-1]
400 | if type(message.content) is str:
401 | message.content = message.content.encode()
402 | if b'Ping' in message.content or b'Pong' in message.content:
403 | in_black = True
404 | data = {
405 | 'client_id': flow.client_conn.id,
406 | 'server_id': flow.server_conn.id,
407 | 'client_host': client_ip,
408 | 'client_port': flow.client_conn.address[1],
409 | 'server_host': flow.server_conn.address[0],
410 | 'server_port': flow.server_conn.address[1],
411 | 'direction': 'send' if message.from_client else 'receive',
412 | 'content': message.content,
413 | "client_ip": client_ip,
414 | "exclude": in_black,
415 | "timestamp_start": flow.client_conn.timestamp_start,
416 | "conn_id": websocket_conns_dict.get(flow.client_conn.id + flow.server_conn.id, None),
417 | "user_name": user_name,
418 | }
419 | mes_id = db['websocket'].insert_one(data).inserted_id
420 | data['_id'] = str(mes_id)
421 | data['type'] = 'websocket'
422 | if in_black:
423 | return
424 | data['content'] = data['content'].decode('utf-8', 'ignore')
425 | rc.publish("mloger:imws_websocket", json.dumps(data))
426 |
427 |
428 | def websocket_start(flow: mitmproxy.http.HTTPFlow):
429 | client_ip = get_client_ip(flow)
430 | if not client_ip_is_allowed(client_ip):
431 | flow.kill()
432 | return
433 | user_name = get_user_name_by_ip_for_redis(client_ip)
434 | data = {
435 | 'type': 'websocket',
436 | 'live': True,
437 | 'client_id': flow.client_conn.id,
438 | 'server_id': flow.server_conn.id,
439 | 'client_host': client_ip,
440 | 'client_port': flow.client_conn.address[1],
441 | 'server_host': flow.server_conn.address[0],
442 | 'server_port': flow.server_conn.address[1],
443 | 'timestamp_start': flow.client_conn.timestamp_start,
444 | 'user_name': user_name,
445 | }
446 | conn_id = db['connections'].insert_one(data).inserted_id
447 | websocket_conns_dict[flow.client_conn.id + flow.server_conn.id] = str(conn_id)
448 | data['_id'] = str(data['_id'])
449 | rc.publish("mloger:imws_websocket", json.dumps(data))
450 | asyncio.create_task(inject_websocket(flow))
451 |
452 |
453 | async def inject_websocket(flow: mitmproxy.http.HTTPFlow):
454 | # 注入功能
455 | logger.info('inject_websocket start')
456 | while db['connections'].find_one({'type': 'websocket',
457 | 'live': True,
458 | 'client_id': flow.client_conn.id,
459 | 'server_id': flow.server_conn.id, }):
460 | ws_mes = db['inject_repeater'].find_one({'client_id': flow.client_conn.id,
461 | 'server_id': flow.server_conn.id, 'status': None, 'type': 'websocket'})
462 | if ws_mes:
463 | to_client = False if ws_mes['direction'] == 'send' else True
464 | is_text = True
465 | concurrent_num = int(ws_mes.get('concurrent_num', 0))
466 | if isinstance(ws_mes['content'], str):
467 | ws_mes['content'] = ws_mes['content'].encode()
468 | logger.info(ws_mes['content'])
469 | if concurrent_num > 0:
470 | for _ in range(concurrent_num):
471 | ctx.master.commands.call("inject.websocket", flow, to_client, ws_mes['content'], is_text)
472 | else:
473 | ctx.master.commands.call("inject.websocket", flow, to_client, ws_mes['content'], is_text)
474 | db['inject_repeater'].update_one({'_id': ws_mes['_id']}, {'$set': {'status': 'done'}})
475 | await asyncio.sleep(0.5)
476 |
477 |
478 | def websocket_error(flow):
479 | # 更新websocket连接状态
480 | data = {
481 | 'type': 'websocket',
482 | 'live': True,
483 | 'client_id': flow.client_conn.id,
484 | 'server_id': flow.server_conn.id,
485 | 'server_host': flow.server_conn.address[0],
486 | 'server_port': flow.server_conn.address[1],
487 | }
488 | db['connections'].update_one(data, {'$set': {'live': False, 'timestamp_end': time.time()}})
489 | data['_id'] = str(websocket_conns_dict.pop(flow.client_conn.id + flow.server_conn.id, ''))
490 | data['live'] = False
491 | rc.publish("mloger:imws_websocket", json.dumps(data))
492 |
493 |
494 | @concurrent
495 | def websocket_end(flow):
496 | # 更新websocket连接状态
497 | data = {
498 | 'type': 'websocket',
499 | 'live': True,
500 | 'client_id': flow.client_conn.id,
501 | 'server_id': flow.server_conn.id,
502 | 'server_host': flow.server_conn.address[0],
503 | 'server_port': flow.server_conn.address[1],
504 | }
505 | db['connections'].update_one(data, {'$set': {'live': False, 'timestamp_end': time.time()}})
506 | data['_id'] = str(websocket_conns_dict.pop(flow.client_conn.id + flow.server_conn.id, ''))
507 | data['live'] = False
508 | rc.publish("mloger:imws_websocket", json.dumps(data))
509 |
--------------------------------------------------------------------------------