├── .dockerignore
├── .env.example
├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── README.md
├── docker-compose.yml
├── hosts
├── __init__.py
├── _example.py
├── default.py
└── tests.py
├── proxy.py
├── proxy.sh
├── requirements.txt
└── tests
├── default.py
└── tests.py
/.dockerignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .git
3 | .DS_Store
4 | .vscode
5 | .idea
6 | .pytest_cache
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | production=false
2 |
3 | authentification=false
4 | username="thomas"
5 | password="123456"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .idea
3 | .vscode
4 | .DS_Store
5 | .env
6 | .pytest_cache
7 | .certs
8 | pytest.ini
9 |
10 | hosts/*
11 | !hosts/__init__.py
12 | !hosts/default.py
13 | !hosts/_example.py
14 | !hosts/tests.py
15 |
16 | tests/*
17 | !tests/default.py
18 | !tests/tests.py
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.0.3] - 2022-12-11
4 |
5 | ### Changed
6 |
7 | - Déplacement de la logiqe de chargement des modules dynamiquement de __init__ vers proxy.py
8 | - Remplacement de nodemon par nodemon-py-simple
9 | - Remplacement de l'image docker nikolaik/python-nodejs par python:3.9-slim. Afin de prendre en charge les processeurs arm mais aussi optimiser le temps de build
10 |
11 |
12 | ## [0.0.2] - 2022-12-06
13 |
14 | ### Added
15 |
16 | - Ajout des tests
17 |
18 | ### Changed
19 |
20 | - Déplacement du répertoires des certificats dans .certs
21 | - Ajout du fichier requirements.txt pour les dépendances python
22 |
23 | ## [0.0.1] - 2022-12-06
24 |
25 | ### Added
26 |
27 | - Changelog.md pour garder les mises à jours
28 | - .env afin d'avoir des variables plus globales
29 | - Ajout de la possiblité de mettre une authentification
30 |
31 | ### Changed
32 |
33 | - Changement du point d'entrée, docker-entrypoint.sh -> proxy.sh
34 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.9-slim
2 |
3 | EXPOSE 8301
4 | EXPOSE 8302
5 |
6 | VOLUME /proxy
7 | WORKDIR /proxy
8 |
9 | ADD requirements.txt /proxy/
10 |
11 | RUN apt update
12 | RUN pip install --no-cache --upgrade pip setuptools
13 | RUN pip install -r requirements.txt
14 |
15 |
16 | ENTRYPOINT ["./proxy.sh"]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Proxeditor
2 | ### Proxy qui permet de supprimer les pubs et de modifier les requêtes des sites facilement
3 |
4 | À la base j'avais crée ce proxy pour mes parents, je m'étais rendu compte que pour des personnes n'utilisant pas forcément beaucoup la technologie. Toutes les popups, demandes de cookies, d'authentification... Rendait l'usage d'une tablette très difficile ou même des sites en général.
5 |
6 | Personnellement, j'ai mis en place ce proxy sur un raspberry local avec pas mal d'utilisations diverses :
7 |
8 | - Avoir des applications en version premium
9 | - Modifier les réponses de certains sites pour ajouter des balises *script* afin d'améliorer la navigation et par example automatiquement se connecter aux comptes, rediriger vers la bonne page, supprimer des éléments supperflus...
10 | - Également, modifier les réponses des sites pour ajouter des balises *style* pour rendre les sites plus ergonomiques (augmenter les polices, darkmode, viewport...)
11 |
12 | Afin de respecter les sites en question, je ne publierais pas les sites modifiés et le type de modification dans le répertoire **hosts/**.
13 |
14 | Mais le fichier _example.py est assez explicite et simple à comprendre, c'est la base que j'utilise pour chaque site.
15 |
16 |
17 |
18 | 1. La commande pour lancer docker :
19 | ```
20 | docker compose up
21 | OU
22 | docker compose up -d // En background
23 | ```
24 |
25 | ⭐️ Le docker intègre les DNS de [AdGuard](https://adguard-dns.io/) pour supprimer toutes les pubs, même dans les applications
26 |
27 | Ou alors le lancer directement :
28 | ```
29 | pip install mitmproxy
30 | pip install tldextract
31 |
32 | chmod a+x proxy.sh
33 | ./proxy.sh
34 | ```
35 |
36 | Les ports accessibles :
37 |
38 | - Le proxy : **8302**
39 | - L'interface en mode développement : **8301**
40 |
41 | 2. Se rendre dans les paramètres de son device et mettre l'ip du serveur ainsi que le port.
42 |
43 | 3. Se rendre sur http://mitm.it/ et récupérer le certificat
44 |
45 | 4. Ajouter le certificat dans les paramètres pour lui donner autorité.
46 |
47 | - IOS : Réglages > Général > VPN et gestion de l'appareil > mitmproxy > Installer.
48 |
49 | - Réglages > Général > Informations > Réglages des certificats (tout en bas) > Cocher mitmproxy > Continuer.
50 |
51 | - Android : Paramètres > Sécurité > Chiffrement et identifiants > Installer un certicat > Certificat CA > Installer quand même.
52 |
53 | # Sécurité
54 |
55 | 1. Renommer le fichier *.env.example* -> *.env*
56 | 2. Passer l'*authentification=true* et modifier la valeur de *username* et *password*
57 | ```sh
58 | authentification=true
59 | username="thomas"
60 | password="123456"
61 | ```
62 |
63 |
64 | # Processus
65 |
66 | 1. Passer le proxy en mode ouvert (c'est à dire qu'il va tout intercepter au lieu de n'intercepter que les domaines définis)
67 | ```
68 | // hosts/default.py:18
69 |
70 | Remplacer
71 | data.ignore_connection = True
72 |
73 | Par
74 | data.ignore_connection = False
75 | ```
76 |
77 | 2. Copier le fichier **_example.py** dans le répertoire **hosts/**
78 |
79 | 3. Renommer la classe (Du même nom que le fichier pour s'y retrouver)
80 |
81 | 4. Pour le debug le process
82 | - Passer l'environnement en mode *production=false* dans le fichier *.env*
83 | - Se rendre sur l'interface http://127.0.0.1:8301
84 | - Et pour rendre ça encore plus agréable j'utlise [cette extension](https://chrome.google.com/webstore/detail/user-javascript-and-css/nbhcbdghjpllgmfilhnhkllmkecfmpld)
85 | - Chargée avec ce script qui permet de recharger la page à chaque fois que le proxy redémarre (il est moche mais il marche) :
86 |
87 | ```js
88 | const ws = new WebSocket(`ws://${window.location.host}/updates`);
89 | let inReload = false;
90 |
91 | ws.addEventListener('open', () => {
92 | setTimeout(() => {
93 | document.querySelectorAll('.nav-tabs a')[2].click();
94 | setTimeout(() => {
95 | if(!document.querySelectorAll('.menu-content input')[3].checked) {
96 | document.querySelectorAll('.menu-content input')[3].click();
97 | setTimeout(() => {
98 | document.querySelectorAll('.eventlog .btn-primary').forEach((btn) => btn.click());
99 | document.querySelectorAll('.eventlog .btn')[4].click();
100 | }, 10)
101 | }
102 | }, 10);
103 | }, 100);
104 | });
105 |
106 | ws.addEventListener('close', () => {
107 | setInterval(() => {
108 | new WebSocket(`ws://${window.location.host}/updates`).addEventListener('open', () => reload());
109 | }, 100);
110 | });
111 |
112 | const reload = () => {
113 | if(inReload) return;
114 | inReload = true;
115 | window.location.href = "http://" + window.location.host;
116 | }
117 | ```
118 |
119 |
120 | 5. Envoyer des messages de sortie avec *logging.error()* depuis le proxy pour bien les discerner car la console défile trop vite
121 |
122 | 6. Ne pas oublier de refaire l'étape 1 dans le sens inverse sinon certains domaines sécurisés avec du SSL Pinning ne pourront pas charger
123 |
124 | ---
125 |
126 | *Je ne suis pas responsable de l'utilisation de ce proxy. Ce proxy est uniquement à usage personnel pour mes parents. Ils sont seuls responsables de son utilisation et s'engagent à ne l'utiliser qu'à des fins légitimes et en conformité avec toutes les lois et réglementations applicables. Toute utilisation non autorisée ou illégale de ce proxy sera de votre seule responsabilité. Je décline toute responsabilité en ce qui concerne l'utilisation de ce proxy. Si vous avez des doutes sur l'utilisation légale de ce proxy, veuillez consulter un avocat qualifié.*
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | proxy:
4 | build: .
5 |
6 | working_dir: /proxy
7 |
8 | volumes:
9 | - ./:/proxy/
10 |
11 | ports:
12 | - 8302:8302
13 | - 8301:8301
14 |
15 | dns:
16 | - 94.140.14.15
17 | - 94.140.14.16
--------------------------------------------------------------------------------
/hosts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomasync/proxeditor/0c2be170a14c3621d3a069e8df81eb2247468450/hosts/__init__.py
--------------------------------------------------------------------------------
/hosts/_example.py:
--------------------------------------------------------------------------------
1 | import mitmproxy
2 | import json
3 | from tldextract import extract
4 | import logging
5 |
6 | HOSTS = [
7 | 'example.fr',
8 | 'example.org'
9 | ]
10 |
11 | SCRIPT = """
12 |
19 | """
20 |
21 | STYLE = """
22 |
27 | """
28 |
29 | class Example:
30 |
31 | # Give access to the request
32 | @staticmethod
33 | def tls_clienthello(data: mitmproxy.proxy.layers.tls.ClientHelloData) -> None:
34 | domain = extract(data.context.server.address[0]).registered_domain
35 | if domain in HOSTS:
36 | data.ignore_connection = False
37 |
38 | # Event called when a request is sent
39 | @staticmethod
40 | def request(flow: mitmproxy.http.HTTPFlow) -> None:
41 | domain = extract(flow.request.host).registered_domain
42 | if domain not in HOSTS:
43 | return
44 |
45 | if "verify_premium" in flow.request.path:
46 | flow.kill()
47 | logging.error("example debug message")
48 |
49 | # Event called when a response is received
50 | @staticmethod
51 | def response(flow: mitmproxy.http.HTTPFlow) -> None:
52 | domain = extract(flow.request.host).registered_domain
53 | if domain not in HOSTS or flow.response.content == '':
54 | return
55 |
56 | if "article" in flow.request.path:
57 | response = json.loads(flow.response.content)
58 | response["response"]["article"]["unlocked"] = "1"
59 | flow.response.content = json.dumps(response).encode()
60 |
61 | elif "user" in flow.request.path:
62 | response = json.loads(flow.response.content)
63 | response["response"]["user"]["category"] = "premium"
64 | response["response"]["user"]["subscription"]["isPremium"] = "true"
65 | flow.response.content = json.dumps(response).encode()
66 |
67 | elif "login" in flow.request.path:
68 | flow.response.content += (STYLE + SCRIPT).encode();
69 |
--------------------------------------------------------------------------------
/hosts/default.py:
--------------------------------------------------------------------------------
1 | import mitmproxy
2 | from tldextract import extract
3 |
4 |
5 | HOSTS_ADS = ['googleadservices.com', 'taboola.com', 'batch.com', 'googlesyndication.com', 'doubleclick.net', 'xiti.com']
6 |
7 | class Default:
8 |
9 | @staticmethod
10 | def tls_clienthello(data: mitmproxy.proxy.layers.tls.ClientHelloData) -> None:
11 | domain = extract(data.context.server.address[0]).registered_domain
12 |
13 | if domain in HOSTS_ADS:
14 | data.ignore_connection = False
15 | else:
16 | # Set to false for debug
17 | data.ignore_connection = True
18 |
19 | @staticmethod
20 | def request(flow: mitmproxy.http.HTTPFlow) -> None:
21 | for host in HOSTS_ADS:
22 | domain = extract(flow.request.host).registered_domain
23 | if host in domain:
24 | flow.kill()
25 |
--------------------------------------------------------------------------------
/hosts/tests.py:
--------------------------------------------------------------------------------
1 | import mitmproxy
2 | from tldextract import extract
3 | import logging
4 | import re
5 |
6 | HOSTS = [
7 | 'neverssl.com',
8 | 'github.com'
9 | ]
10 |
11 | PAGE = """
12 |
13 |