├── 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 | 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 | ![img.png](front/src/assets/screenshot/首页.png) 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 | ![img.png](screenshot/首页.png) 12 | 13 | - 入口:页面左侧悬浮窗是准入代理功能,鼠标悬浮于图标上可触发。准入代理是用于授权可以通过代理访问的客户端ip,用于对代理进行权限控制。 14 | - 授权与撤销:点击接入即可授权成功,再次点击撤销可撤回对该ip的授权。授权的客户端ip会在持续无流量三分钟后失效。 15 | - 拦截状态字段:拦截状态标识是指客户端ip是否在HTTP拦截中设置了拦截状态,若出现HTTP记录页仅展示请求无响应时,可通过观察该标识判断是否是因为开启了拦截导致。 16 | 17 | ![img.png](screenshot/准入代理.png) 18 | ### HTTP拦截 19 | - 拦截:填入IP并点击拦截已关闭按钮将拦截该IP的所有HTTP请求,并将下一个拦截到的HTTP请求展示在当前页面。 20 | - 修改:展示在当前文本框中的请求可修改请求体部分,其他部分暂不支持修改。 21 | - 操作:修改过后点击放过、丢弃按钮可对当前修改过的请求进行操作,将修改过的请求替代原始请求发送至服务端。点击重放可将当前停留的请求发送至HTTP重放页面,进行更加自定义的重放操作。 22 | - 等待响应:勾选等待响应后,点击放过按钮,将会等待此次放过请求的响应,并将该响应展示到文本框中,响应也可以被执行修改、放过、丢弃等操作。 23 | 24 | ![img.png](screenshot/HTTP拦截.png) 25 | ### HTTP记录 26 | - 连接:输入IP字段以过滤指定客户端ip的流量,否则你将会看到所有的流量,然后点击未连接按钮,即可连上服务端并获取实时的流量数据。再次点击已连接可断开连接。 27 | - 过滤:filter输入框是用于前端过滤流量的,所以所有流量还是缓存在浏览器中的。 28 | - 清屏:会清除当前页面所有HTTP记录。 29 | - 描述:用于标记api的功能描述,后续相同接口的api将会带有描述展示。 30 | - 优化:优化展示json格式的请求体与响应体,可通过原始切换查看原始请求与响应。 31 | - 重放:点击重放即可跳转到该次请求详情的重放页面,进行下一步的测试。 32 | - 分享:点击分享可将流量详情的链接复制到剪切板上。 33 | 34 | ![img.png](screenshot/HTTP记录.png) 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 | ![img.png](screenshot/HTTP重放.png) 43 | ### TCP/WS测试 44 | - 连接:输入IP字段以过滤指定客户端ip的流量,否则你将会看到所有的流量,然后点击未连接按钮,即可连上服务端并获取实时的流量数据。再次点击已连接可断开连接。 45 | - 过滤:filter输入框是用于前端过滤流量的,所以所有流量还是缓存在浏览器中的。 46 | - 清屏:会清除当前页面所有Websocket、tcp记录。 47 | - 重放:点击发送、接收按钮可向服务端、客户端发送当前消息详情内的消息,通过修改并发数,可发送多条消息。 48 | - 16进制:点击HEX复选框可以16进制的形式查看、修改当前消息。 49 | 50 | ![img.png](screenshot/TCPWS测试.png) 51 | ### 历史记录 52 | - 搜索:从数据库中搜索历史记录,可选择不同类别。logger是记录的所有经过代理的HTTP流量,repeater是所有使用HTTP重放功能的流量,apis是归类了所有经过代理的接口请求。 53 | 54 | ![img_1.png](screenshot/历史记录.png) 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 | ![img.png](front/src/assets/screenshot/首页.png) 12 | 13 | - 入口:页面左侧悬浮窗是准入代理功能,鼠标悬浮于图标上可触发。准入代理是用于授权可以通过代理访问的客户端ip,用于对代理进行权限控制。 14 | - 授权与撤销:点击接入即可授权成功,再次点击撤销可撤回对该ip的授权。授权的客户端ip会在持续无流量三分钟后失效。 15 | - 拦截状态字段:拦截状态标识是指客户端ip是否在HTTP拦截中设置了拦截状态,若出现HTTP记录页仅展示请求无响应时,可通过观察该标识判断是否是因为开启了拦截导致。 16 | 17 | ![img.png](front/src/assets/screenshot/准入代理.png) 18 | ### HTTP拦截 19 | - 拦截:填入IP并点击拦截已关闭按钮将拦截该IP的所有HTTP请求,并将下一个拦截到的HTTP请求展示在当前页面。 20 | - 修改:展示在当前文本框中的请求可修改请求体部分,其他部分暂不支持修改。 21 | - 操作:修改过后点击放过、丢弃按钮可对当前修改过的请求进行操作,将修改过的请求替代原始请求发送至服务端。点击重放可将当前停留的请求发送至HTTP重放页面,进行更加自定义的重访操作。 22 | - 等待响应:勾选等待响应后,点击放过按钮,将会等待此次放过请求的响应,并将该响应展示到文本框中,响应也可以被执行修改、放过、丢弃等操作。 23 | 24 | ![img.png](front/src/assets/screenshot/HTTP拦截.png) 25 | ### HTTP记录 26 | - 连接:输入IP字段以过滤指定客户端ip的流量,否则你将会看到所有的流量,然后点击未连接按钮,即可连上服务端并获取实时的流量数据。再次点击已连接可断开连接。 27 | - 过滤:filter输入框是用于前端过滤流量的,所以所有流量还是缓存在浏览器中的。 28 | - 清屏:会清除当前页面所有HTTP记录。 29 | - 描述:用于标记api的功能描述,后续相同接口的api将会带有描述展示。 30 | - 优化:优化展示json格式的请求体与响应体,可通过原始切换查看原始请求与响应。 31 | - 重放:点击重放即可跳转到该次请求详情的重放页面,进行下一步的测试。 32 | - 分享:点击分享可将流量详情的链接复制到剪切板上。 33 | 34 | ![img.png](front/src/assets/screenshot/HTTP记录.png) 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 | ![img.png](front/src/assets/screenshot/HTTP重放.png) 43 | ### TCP/WS测试 44 | - 连接:输入IP字段以过滤指定客户端ip的流量,否则你将会看到所有的流量,然后点击未连接按钮,即可连上服务端并获取实时的流量数据。再次点击已连接可断开连接。 45 | - 过滤:filter输入框是用于前端过滤流量的,所以所有流量还是缓存在浏览器中的。 46 | - 清屏:会清除当前页面所有Websocket、tcp记录。 47 | - 重放:点击发送、接收按钮可向服务端、客户端发送当前消息详情内的消息,通过修改并发数,可发送多条消息。 48 | - 16进制:点击HEX复选框可以16进制的形式查看、修改当前消息。 49 | 50 | ![img.png](front/src/assets/screenshot/TCPWS测试.png) 51 | ### 历史记录 52 | - 搜索:从数据库中搜索历史记录,可选择不同类别。logger是记录的所有经过代理的HTTP流量,repeater是所有使用HTTP重放功能的流量,apis是归类了所有经过代理的接口请求。 53 | 54 | ![img_1.png](front/src/assets/screenshot/历史记录.png) -------------------------------------------------------------------------------- /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 | ![img.png](front/src/assets/screenshot/首页.png) 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 | 19 | 20 | 123 | -------------------------------------------------------------------------------- /front/src/components/MainConnDrawer.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 44 | 45 | 143 | -------------------------------------------------------------------------------- /front/src/App.vue: -------------------------------------------------------------------------------- 1 | 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 | 67 | 68 | 245 | 246 | -------------------------------------------------------------------------------- /front/src/components/History/Index.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | --------------------------------------------------------------------------------