├── VPN └── .keep ├── requirements.txt ├── haproxy ├── Dockerfile └── README.md ├── .gitignore ├── etc ├── supervisor.d │ ├── 02_proxy.ini │ └── 01_openvpn.ini └── sockd.conf ├── Dockerfile ├── README.md └── doxycannon.py /VPN/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | docker 2 | -------------------------------------------------------------------------------- /haproxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM haproxy:alpine 2 | COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.ovpn 2 | auth.txt 3 | proxychains.conf 4 | *.crt 5 | *.cert 6 | *.pem 7 | haproxy.cfg 8 | -------------------------------------------------------------------------------- /etc/supervisor.d/02_proxy.ini: -------------------------------------------------------------------------------- 1 | [program:dante] 2 | command=sockd -N 2 -D 3 | autorestart=true 4 | priority=20 5 | -------------------------------------------------------------------------------- /etc/supervisor.d/01_openvpn.ini: -------------------------------------------------------------------------------- 1 | [program:openvpn] 2 | command=/usr/sbin/openvpn --cd /VPN --config %(ENV_VPN)s.ovpn 3 | autorestart=true 4 | priority=10 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.7 2 | 3 | # Install packages 4 | RUN echo 'https://dl-4.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && \ 5 | apk --no-cache add openvpn dante-server supervisor && \ 6 | rm -rf /var/cache/ 7 | 8 | # Add image configuration and scripts 9 | ADD VPN /VPN 10 | ADD etc/ /etc/ 11 | 12 | EXPOSE 1080 13 | CMD ["supervisord","-n"] 14 | -------------------------------------------------------------------------------- /etc/sockd.conf: -------------------------------------------------------------------------------- 1 | logoutput: stderr 2 | 3 | internal: eth0 port = 1080 4 | external: tun0 5 | external.rotation: route 6 | 7 | user.unprivileged: nobody 8 | socksmethod: none 9 | clientmethod: none 10 | 11 | client pass { 12 | from: 0.0.0.0/0 to: 0.0.0.0/0 13 | log: error 14 | } 15 | 16 | socks pass { 17 | from: 0.0.0.0/0 to: 0.0.0.0/0 18 | protocol: tcp udp 19 | } 20 | -------------------------------------------------------------------------------- /haproxy/README.md: -------------------------------------------------------------------------------- 1 | # doxyproxy 2 | 3 | This Docker image creates an haproxy instance bound to the host network that will roundrobin 4 | through your doxycannon proxies. This allows you to use a single socks5 proxy server to which you 5 | point things like browsers and BURPSuite 6 | 7 | ## Running the container 8 | 9 | You will need to build every time the config file is modified. 10 | To build the config file, first run the doxyproxy.py file with the `--up` flag 11 | 12 | ```sh 13 | docker build -t haproxy . 14 | docker run --rm --network host --name doxyproxy haproxy 15 | ``` 16 | 17 | Alternatively, you can run doxycannon with the `--single` flag 18 | 19 | Then just point {burp,firefox,etc} at socks5://127.0.0.1:1337 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # doxycannon 2 | 3 | [![CodeFactor](https://www.codefactor.io/repository/github/audibleblink/doxycannon/badge)](https://www.codefactor.io/repository/github/audibleblink/doxycannon) 4 | 5 | Doxycannon takes a pool of OpenVPN files and creates a Docker container for 6 | each one. After a successful VPN connection, each container spawns a SOCKS5 7 | proxy server and binds it to a port on the Docker host. Combined with tools 8 | like Burp suite or proxychains, this creates your very own private botnet on 9 | the cheap. 10 | 11 | ## Prerequisites 12 | - A VPN subscription to a provider that distributes \*.ovpn files 13 | - Install the required pip modules: 14 | ```sh 15 | pip install -r requirements.txt 16 | ``` 17 | - Ensure docker is installed and enabled. Refer to the 18 | [Wiki](../../wiki/installing-docker) for installation instructions on 19 | Kali/Debian 20 | - `proxychains4` is required for interactive mode 21 | 22 | ## Setup 23 | - Create an `auth.txt` file with your ovpn credentials in `VPN`. The format is: 24 | ```txt 25 | username 26 | password 27 | ``` 28 | - Fill the VPN folder with `*.ovpn` files and ensure that the `auth-user-pass` 29 | directive in your `./VPN/*.ovpn` files says `auth-user-pass auth.txt` 30 | - Check out [this wiki section](../../wiki#getting-started-with-vpn-providers) 31 | for installation instructions for individual VPN providers 32 | - Run `./doxycannon.py --build` to build your image with your OVPN files 33 | - `--build` will need to be run on code changes or when you modify the `VPN` 34 | folder's contents 35 | 36 | ## Usage 37 | 38 | _note: the way proxychains seeds its PRNG to choose a random proxy is not fast 39 | enough to ensure each subsequent request goes out through a different IP. You 40 | may get between 1-10 requests being made from the same IP. If this is 41 | unacceptable, I [merged a patch](https://github.com/haad/proxychains/pull/73) 42 | to the original proxychains repo. Download and build from master to get the 43 | fix. https://github.com/haad/proxychains_ 44 | 45 | ### One-off, random commands 46 | While your containers are up, you can use proxychains to issue commands through 47 | random proxies 48 | 49 | ```sh 50 | proxychains4 -q curl -s ipconfing.io/json 51 | proxychains4 -q hydra -L users.txt -p Winter2018 manager.example.com -t 8 ssh 52 | proxychains4 -q gobuster -w word.list -h http://manager.example.com 53 | ``` 54 | 55 | ### GUI Tools 56 | 57 | Use the `--single` flag to bring up your proxies and create a proxy rotator. 58 | 59 | ```sh 60 | hgfs/shared/doxycannon master  61 | ❯❯ ./doxycannon.py --single 62 | [+] Writing HAProxy configuration 63 | [*] Image doxyproxy built. 64 | [*] Staring single-port mode... 65 | [*] Proxy rotator listening on port 1337. Ctrl-c to quit 66 | ^C 67 | [*] doxyproxy was issued a stop command 68 | [*] Your proxies are still running. 69 | 70 | ``` 71 | 72 | To see what's happening, checkout out the [haproxy](haproxy) folder. 73 | Essentially, one is building a layer 4 load-balancer between all the VPNs. This 74 | will allow you rotate through your proxies from a single port which means you 75 | can point your browsers or BURPSuite instances at it and have every request use 76 | a different VPN. 77 | 78 | ### Specific SOCKS proxies 79 | If you want to use a specific proxy, give your utility the proper SOCKS port. 80 | 81 | Example: To make a request through Japan, use `docker ps` and find the local 82 | port to which the Japanese proxy is bound. 83 | 84 | Configure your tool to use that port: 85 | 86 | ```sh 87 | curl --socks5 localhost:50xx ipconfig.io/json 88 | ``` 89 | 90 | ### Interactive 91 | Once you've built your image and started your containers, run the utility with 92 | the `--interactive` flag to get a bash session where all network traffic is 93 | redirected through proxychains4 94 | 95 | ```sh 96 | ./doxycannon.py --interactive 97 | ``` 98 | 99 | ## Demo 100 | [![asciicast](https://asciinema.org/a/YaRyhghHQuBw8Hm3mia5KMewP.png)](https://asciinema.org/a/YaRyhghHQuBw8Hm3mia5KMewP) 101 | 102 | ### Credit 103 | [pry0cc](https://github.com/pry0cc/ProxyDock) for the idea 104 | 105 | This was originally a fork of pry0cc's ProxyDock. It's been modified to an 106 | extent where less than 1% of the original code remains. 107 | 108 | ## TODO 109 | 110 | - [ ] Allow for management of remote doxycannon installs through the Docker API 111 | - [X] Interactive mode 112 | - [X] Python management script 113 | - [X] Faster Up/Down Container management 114 | - [X] Dispatch server - (will allow GUI applications to use doxycannon) 115 | - [X] Creates a single local proxy server that dispatches through VPNs 116 | -------------------------------------------------------------------------------- /doxycannon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | import argparse 3 | import docker 4 | import glob 5 | import re 6 | import os 7 | from Queue import Queue 8 | from threading import Thread 9 | 10 | VERSION = '0.2.0' 11 | IMAGE = 'audibleblink/doxycannon' 12 | THREADS = 20 13 | START_PORT = 5000 14 | 15 | PROXYCHAINS_CONF = './proxychains.conf' 16 | PROXYCHAINS_TEMPLATE = """ 17 | # This file is automatically generated by doxycannon. If you need changes, 18 | # make them to the template string in doxycannon.py 19 | random_chain 20 | quiet_mode 21 | proxy_dns 22 | remote_dns_subnet 224 23 | tcp_read_time_out 15000 24 | tcp_connect_time_out 8000 25 | 26 | [ProxyList] 27 | """ 28 | 29 | HAPROXY_CONF = './haproxy/haproxy.cfg' 30 | HAPROXY_TEMPLATE = """ 31 | # This file is automatically generated by doxycannon. If you need changes, 32 | # make them to the template string in doxycannon.py 33 | global 34 | daemon 35 | user root 36 | group root 37 | 38 | defaults 39 | mode tcp 40 | maxconn 3000 41 | timeout connect 5000ms 42 | timeout client 50000ms 43 | timeout server 50000ms 44 | 45 | listen funnel_proxy 46 | bind *:1337 47 | mode tcp 48 | balance roundrobin 49 | default_backend doxycannon 50 | 51 | backend doxycannon 52 | """ 53 | 54 | doxy = docker.from_env() 55 | 56 | 57 | def build(image_name, path='.'): 58 | """Builds the image with the given name""" 59 | try: 60 | doxy.images.build(path=path, tag=image_name) 61 | message = '[*] Image {} built.' 62 | print message.format(image_name) 63 | except Exception as err: 64 | print err 65 | raise 66 | 67 | 68 | def vpn_file_queue(folder): 69 | """Returns a Queue of files from the given directory""" 70 | files = glob.glob(folder + '/*.ovpn') 71 | jobs = Queue(maxsize=0) 72 | for f in files: 73 | jobs.put(f) 74 | return jobs 75 | 76 | 77 | def write_config(filename, data, conf_type): 78 | """ Write data to a given filename 79 | 80 | The `type` argument determines what template gets written 81 | at the beginning of the config file. Types are either 82 | 'haproxy' or 'proxychains' 83 | """ 84 | with open(filename, 'w') as f: 85 | if conf_type == 'haproxy': 86 | f.write(HAPROXY_TEMPLATE) 87 | elif conf_type == 'proxychains': 88 | f.write(PROXYCHAINS_TEMPLATE) 89 | for line in data: 90 | f.write(line + "\n") 91 | 92 | 93 | def write_haproxy_conf(port_range): 94 | """Generates HAProxy config based on # of ovpn files""" 95 | print "[+] Writing HAProxy configuration" 96 | conf_line = "\tserver doxy{} 127.0.0.1:{} check" 97 | data = list(map(lambda x: conf_line.format(x, x), port_range)) 98 | write_config(HAPROXY_CONF, data, 'haproxy') 99 | 100 | 101 | def write_proxychains_conf(port_range): 102 | """Generates Proxychains4 config based on # of ovpn files""" 103 | print "[+] Writing Proxychains4 configuration" 104 | conf_line = "socks5 127.0.0.1 {}" 105 | data = list(map(lambda x: conf_line.format(x), port_range)) 106 | write_config(PROXYCHAINS_CONF, data, 'proxychains') 107 | 108 | 109 | def containers_from_image(image_name): 110 | """Returns a Queue of containers whose source image match image_name""" 111 | jobs = Queue(maxsize=0) 112 | containers = list( 113 | filter( 114 | lambda x: image_name in x.attrs['Config']['Image'], 115 | doxy.containers.list() 116 | ) 117 | ) 118 | for container in containers: 119 | jobs.put(container) 120 | return jobs 121 | 122 | 123 | def multikill(jobs): 124 | """Handler to job killer. Called by the Thread worker function.""" 125 | while True: 126 | container = jobs.get() 127 | print 'Stopping: {}'.format(container.name) 128 | container.kill(9) 129 | jobs.task_done() 130 | 131 | 132 | def down(image_name): 133 | """Find all containers from an image name and start workers for them. 134 | The workers are tasked with running the job killer function 135 | """ 136 | container_queue = containers_from_image(image_name) 137 | for _ in range(THREADS): 138 | worker = Thread(target=multikill, args=(container_queue,)) 139 | worker.setDaemon(True) 140 | worker.start() 141 | container_queue.join() 142 | print '[+] All containers have been issued a kill commaand' 143 | 144 | 145 | def multistart(image_name, jobs, ports): 146 | """Handler for starting containers. Called by Thread worker function.""" 147 | while True: 148 | port = ports.get() 149 | ovpn_basename = os.path.basename(jobs.get()) 150 | ovpn_stub = re.sub("\.ovpn", "", ovpn_basename) 151 | print 'Starting: {}'.format(ovpn_stub) 152 | doxy.containers.run( 153 | image_name, 154 | auto_remove=True, 155 | privileged=True, 156 | ports={'1080/tcp': ('127.0.0.1', port)}, 157 | dns=['1.1.1.1'], 158 | environment=["VPN={}".format(ovpn_stub)], 159 | name=ovpn_stub, 160 | detach=True) 161 | port = port + 1 162 | jobs.task_done() 163 | 164 | 165 | def start_containers(image_name, ovpn_queue, port_range): 166 | """Starts workers that call the container creation function""" 167 | port_queue = Queue(maxsize=0) 168 | for p in port_range: 169 | port_queue.put(p) 170 | 171 | for _ in range(THREADS): 172 | worker = Thread( 173 | target=multistart, 174 | args=(image_name, ovpn_queue, port_queue,)) 175 | worker.setDaemon(True) 176 | worker.start() 177 | ovpn_queue.join() 178 | print '[+] All containers have been issued a start command' 179 | 180 | 181 | def up(image): 182 | """Kick off the `up` process that starts all the containers 183 | 184 | Writes the configuration files and starts starts container based 185 | on the number of *.ovpn files in the VPN folder 186 | """ 187 | ovpn_file_queue = vpn_file_queue('./VPN') 188 | ovpn_file_count = len(list(ovpn_file_queue.queue)) 189 | port_range = range(START_PORT, START_PORT + ovpn_file_count) 190 | write_haproxy_conf(port_range) 191 | write_proxychains_conf(port_range) 192 | start_containers(image, ovpn_file_queue, port_range) 193 | 194 | 195 | def single(image): 196 | """Starts an HAProxy rotator. 197 | 198 | Builds and starts the HAProxy container in the haproxy folder 199 | This will create a local socks5 proxy on port 1337 that will 200 | allow one to configure applications with SOCKS proxy options. 201 | Ex: Firefox, BurpSuite, etc. 202 | """ 203 | import signal 204 | import sys 205 | 206 | name = 'doxyproxy' 207 | 208 | def signal_handler(*args): 209 | """Traps ctrl+c for cleanup, then exits""" 210 | sys.stdout = open(os.devnull, 'w') 211 | down(name) 212 | sys.stdout = sys.__stdout__ 213 | print '\n[*] {} was issued a stop command'.format(name) 214 | print '[*] Your proxies are still running.' 215 | sys.exit(0) 216 | 217 | try: 218 | if not list(containers_from_image(image).queue): 219 | up(image) 220 | else: 221 | ovpn_file_count = len(list(vpn_file_queue('VPN').queue)) 222 | port_range = range(START_PORT, START_PORT + ovpn_file_count) 223 | write_haproxy_conf(port_range) 224 | build(name, path='./haproxy') 225 | print '[*] Staring single-port mode...' 226 | print '[*] Proxy rotator listening on port 1337. Ctrl-c to quit' 227 | signal.signal(signal.SIGINT, signal_handler) 228 | doxy.containers.run(name, network='host', name=name, auto_remove=True) 229 | except Exception as err: 230 | print err 231 | raise 232 | 233 | 234 | def interactive(image): 235 | """Starts the interactive process. Requires Proxychains4 236 | 237 | Creates a shell session where network connections are routed through 238 | proxychains. Started GUI application from here rarely works 239 | """ 240 | try: 241 | if not list(containers_from_image(image).queue): 242 | up(image) 243 | else: 244 | ovpn_file_count = len(list(vpn_file_queue('VPN').queue)) 245 | port_range = range(START_PORT, START_PORT + ovpn_file_count) 246 | write_proxychains_conf(port_range) 247 | 248 | os.system("proxychains4 bash") 249 | except Exception as err: 250 | print err 251 | raise 252 | 253 | 254 | def main(): 255 | parser = argparse.ArgumentParser() 256 | parser.add_argument( 257 | '--build', 258 | action='store_true', 259 | default=False, 260 | dest='build', 261 | help='Builds the base docker image') 262 | parser.add_argument( 263 | '--up', 264 | action='store_true', 265 | default=False, 266 | dest='up', 267 | help='Brings up containers. 1 for each VPN file in ./VPN') 268 | parser.add_argument( 269 | '--down', 270 | action='store_true', 271 | default=False, 272 | dest='down', 273 | help='Bring down all the containers') 274 | parser.add_argument( 275 | '--single', 276 | action='store_true', 277 | default=False, 278 | dest='single', 279 | help='Start an HAProxy rotator on a single port. Useful for Burpsuite') 280 | parser.add_argument( 281 | '--interactive', 282 | action='store_true', 283 | default=False, 284 | dest='interactive', 285 | help="Starts an interactive bash session where network connections" + 286 | " are routed through proxychains. Requires proxychainvs v4+") 287 | parser.add_argument( 288 | '--version', 289 | action='version', 290 | version="%(prog)s {}".format(VERSION)) 291 | args = parser.parse_args() 292 | 293 | if args.build: 294 | build(IMAGE) 295 | elif args.up: 296 | up(IMAGE) 297 | elif args.down: 298 | down(IMAGE) 299 | elif args.interactive: 300 | interactive(IMAGE) 301 | elif args.single: 302 | single(IMAGE) 303 | 304 | 305 | if __name__ == "__main__": 306 | main() 307 | --------------------------------------------------------------------------------