安全矩阵

 找回密码
 立即注册
搜索
查看: 8414|回复: 0

如何使用 Docker 搭建代理池+隧道代理

[复制链接]

179

主题

179

帖子

630

积分

高级会员

Rank: 4

积分
630
发表于 2023-11-19 19:30:49 | 显示全部楼层 |阅读模式
如何使用 Docker 搭建代理池+隧道代理
前言

作为一种常见的服务器防御网络攻击或探测手段,封 IP 可以有效地保护服务器免受恶意攻击。在进行安全测试时,安全人员需要使用代理来隐藏真实 IP 地址,并依靠代理池获取可用的代理地址。同时,为了更方便地在某些工具中使用代理,可以借助隧道代理直接将请求转发给不同的代理服务器,从而避免 IP 被封锁。

本文介绍了一种利用 Docker 搭建代理池和隧道代理的方法,并通过对 httpbin.org 的访问和红队工具 dirsearch 的使用进行了测试。这种方法能够快速搭建一个免费、高效、稳定且易于管理的代理环境,在进行网络渗透测试等任务时非常有帮助。

下图为搭建过程的流程图:
Docker 的准备

我这里都是使用的 Docker 搭建。

Docker 可能用到的命令如下:

# 查看当前运行的 docker 容器 ID 和运行状况
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE                     COMMAND                   CREATED       STATUS       PORTS                                       NAMES
c09ae6b9c554   tunnel_proxy:0.02         "/usr/local/openrest…"   2 hours ago   Up 2 hours                                               tunnel_proxy
17bc67d0f892   germey/proxypool:master   "supervisord -c supe…"   2 hours ago   Up 2 hours   0.0.0.0:5555->5555/tcp, :::5555->5555/tcp   proxypool
b659295a7927   redis:alpine              "docker-entrypoint.s…"   2 hours ago   Up 2 hours   0.0.0.0:6374->6379/tcp, :::6374->6379/tcp   redis4proxypool

[root@localhost ~]# docker exec -it 17 /bin/sh
/app # ls
Dockerfile          README.md           docker-compose.yml  kubernetes          release.sh          run.py              supervisord.log
LICENSE             build.yaml          examples            proxypool           requirements.txt    supervisord.conf
# exit 退出

命令:docker exec -it <id> /bin/bash在指定 id 的 Docker 容器中执行指令,id 通过docker ps命令来获取。如果执行的是 shell 程序比如 /bin/bash 或者 /bin/sh 就能进入到容器的 shell 里,可以执行一些 linux 指令,id 可以使用前几位可以区分不同容器的字符就行,我这里用的前两位。

如果在接下来的配置中有问题,可以使用这种方式进入容器里查看日志,或者按照我的配置将日志文件映射到本地。

启动 Docker 用的程序是 docker-compose。如果你输入 docker-compose 提示找不到这个程序,有可能是没有安装或者是 /bin 目录下没有。

使用docker info命令查看 docker-compose 程序的位置

[root@localhost ~]# docker info
Client: Docker Engine - Community
Version:    24.0.2
Context:    default
Debug Mode: false
Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.10.5
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.18.1
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

检查一下 docker-compose 有无执行权限,如果没有,就要sudo chmod +x /usr/local/bin/docker-compose赋予执行权限

[root@localhost proxy_pool]# cd /usr/libexec/docker/cli-plugins/
[root@localhost cli-plugins]# ll
总用量 108356
-rwxr-xr-x 1 root root 56327368 5月  26 05:56 docker-buildx
-rwxr-xr-x 1 root root 54627904 5月  20 02:09 docker-compose

如果无法直接运行 docker-compose,可能需要创建软链接

[root@localhost cli-plugins]# ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/bin/docker-compose
[root@localhost work]# docker-compose  --version
Docker Compose version v2.18.1

代理池 ProxyPool

我这里用的是开源的代理池,自动从免费代理网站上爬取 http 代理。Python3WebSpider/ProxyPool

这个项目有四个模块:获取IP模块,存储模块,检测模块,接口模块。它包含两个 Docker 容器:redis4proxypool 和 proxypool,对于容器的设置都在 ProxyPool 根目录的 docker-compose.yml 文件里。对于容器的配置留到下部分再详细描述,先跑起来再说。

首先 gitclone

git clone https://github.com/Python3WebSpider/ProxyPool.git
cd ProxyPool

然后启动 docker 环境

docker-compose up

这时候如果防火前的 5555 端口是开放的,访问 http://<当前 ip>:5555/random 即可随机获取一个可用代理,不需要做任何的修改。效果图如下:


random 接口返回的是随机的高分 IP,也就是已验证能访问测试 URL 的 IP。高分,指的是在 redis 数据库中,获取 IP 模块获取的每一条 IP 的分数,如果检测模块用 IP 访问测试链接,能成功访问则设置这个分数为 100,单次测试访问失败则将分数减 1。
ProxyPool 的配置

在 ProxyPool 目录下使用命令docker-compose down可以关闭 docker。接下来,我们修改 ProxyPool 的配置。它包含两个容器,一个是 redis4proxypool,也就是 redis 数据库的部分,下文用 redis 来代替,另一个容器是 proxypool,也就是负责代理池逻辑的部分。
redis

为了查看或者 redis 里的这些数据,首先要设置 docker-compose.yml,设置 6374 的端口映射

version: "3"
services:
  redis4proxypool:
    image: redis:alpine
    container_name: redis4proxypool
    # 把容器内部的 6379 端口映射到宿主机(host)的 6374 端口,这样就可以通过宿主机的 6374 端口来访问容器内部的 Redis 数据库
    ports:
      - "6374:6379"
  proxypool:
    image: "germey/proxypool:master"
    container_name: proxypool
    ports:
      - "5555:5555"
    restart: always
    # volumes:
    #   - proxypool/crawlers/private:/app/proxypool/crawlers/private
    environment:
      PROXYPOOL_REDIS_HOST: redis4proxypool

开启容器之后,要查看 redis 用到的命令如下:

[root@localhost ProxyPool]# redis-cli -h 127.0.0.1 -p 6374
# keys * 获取 redis 中所有的 key
127.0.0.1:6374> keys *
1) "proxies:universal"
# type <key> 获取 key 的类型
127.0.0.1:6374> type proxies:universal
zset
# 因为是 zset 有序类型,所以用 zrange 获取所有数据,后面两个参数是开始位置和结束位置
127.0.0.1:6374> zrange proxies:universal 0 -1
...
18156) "72.44.101.173:8080"
18157) "95.56.254.139:3128"
# 获取分数为 100 的,用 zrangebyscore,后面两个参数是最小值和最大值
127.0.0.1:6374> zrangebyscore proxies:universal 99 100
1) "103.189.96.98:8085"
2) "186.121.235.66:8080"
3) "200.4.217.203:999"
4) "103.74.121.88:3128"
5) "117.251.103.186:8080"
...
# exit 退出

proxypool

针对 proxypool 也有不少设置可以自定义,在使用 Docker 的情况下,在 docker-compose.yml 文件中,设置 environment 参数即可。

这里修改了 volumes 参数,把容器里的指定文件夹映射到宿主机的指定位置。proxypool 在容器里工作的根目录是 /app,所以这里是把 /app/proxypool/logs 文件夹映射到主机的 ProxyPool/logs/proxypool 目录下。

设置了以下环境变量:

CYCLE_TESTER, Tester 运行周期,即间隔多久运行一次测试,默认 20 秒,这里修改为 5 秒。

PROXYPOOL_REDIS_PORT,容器内部的 6379 连接 redis4proxypool。

TEST_URL, 测试 URL,默认百度,指定要爬取的 URL。

REDIS_KEY,redis 储存代理使用字典的名称,其中 PROXYPOOL_REDIS_KEY 会覆盖 REDIS_KEY 的值。

LOG_DIR: "proxypool/logs" ,这个参数不知道为啥设置了并没有什么用,尝试了 n 遍,本来按照源码,没有上面这个参数也应该写到 logs 相对目录下,但是程序并没有写入,所以保存日志文件只能用下面这种相对路径的写法。

LOG_RUNTIME_FILE,运行日志文件名称。

LOG_ERROR_FILE,错误日志文件名称。

加了 depends_on 参数,意思是先启动 redis4proxypool 在启动 proxypool,这样确保在启动 proxypool 之前先启动了 redis4proxypool,以避免可能的连接问题。

version: "3"
services:
  redis4proxypool:
    image: redis:alpine
    container_name: redis4proxypool
    ports:
      - "6374:6379"
  proxypool:
    image: "germey/proxypool:master"
    container_name: proxypool
    ports:
      - "5555:5555"
    restart: always
   
    volumes:
      - ./logs/proxypool:/app/proxypool/logs
    # volumes:
    #   - proxypool/crawlers/private:/app/proxypool/crawlers/private
    environment:
      CYCLE_TESTER: 5
      PROXYPOOL_REDIS_HOST: redis4proxypool
      PROXYPOOL_REDIS_PORT: 6379
      TEST_URL: https://httpbin.org/ip
      REDIS_KEY: proxies:httpbin
      LOG_RUNTIME_FILE: "./proxypool/logs/proxypool_runtime.log"
      LOG_ERROR_FILE: "./proxypool/logs/proxypool_error.log"
    depends_on:
      - redis4proxypool

隧道代理

我们使用一些扫描或者爆破工具的时候,大部分情况下,配置选项中只能选择一条代理,被封 IP 的可能性仍然很大,有没有什么方法能在请求的时候自动切换代理服务器呢?这就是隧道代理。

隧道代理服务器,能将接收到的请求随机或者按规则转发给不同的代理,这样,相当于在工具中只需要设置代理为隧道代理服务器,自动切换 IP 的任务交给隧道代理。

我这里使用的 OpenResty 搭建的隧道代理,也是用的 Docker,并把配置整合到了 docker-compose.yml 文件中。

参考链接:openresty实现隧道代理
5分钟,自己做一个隧道代理-未闻Code

在 ProxyPool 目录下,新建一个文件夹 tunnel_proxy。在里面新建两个文件:Nginx和nginx.conf。
Nginx

Nginx 文件内容如下,这其实就是 Dockerfile,为了不和 ProxyPool 的 Dockerfile 搞混,所以就命名为 Nginx:

from openresty/openresty:alpine

COPY nginx.conf /usr/local/openresty/nginx/conf/nginx.conf

CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]

from openresty/openresty:alpine从 openresty/openresty:alpine 这个镜像作为基础镜像开始构建容器。

COPY nginx.conf /usr/local/openresty/nginx/conf/nginx.conf这条指令是把当前目录下的 nginx.conf 文件复制到容器中的 /usr/local/openresty/nginx/conf/nginx.conf 路径,覆盖原有的配置文件。这样可以自定义 OpenResty 的配置,比如设置端口、代理、缓存等。

CMD ["/usr/local/openresty/bin/openresty", "-g", "daemon off;"]这条指令表示设置容器启动时默认执行的命令,也就是运行 OpenResty 服务,并且不以守护进程的方式运行,这样可以让容器保持在前台运行,直到服务停止或者容器被终止。
nginx.conf

nginx.conf 文件内容如下:

worker_processes  16;        #nginx worker 数量
error_log /usr/local/openresty/nginx/logs/error.log;   #指定错误日志文件路径
events {
    worker_connections 1024;
}
env RPORT; # 获取环境变量 RPORT RKEY
env RKEY;


stream {
    ## TCP 代理日志格式定义
    log_format tcp_proxy '$remote_addr [$time_local] '
                         '$protocol $status $bytes_sent $bytes_received '
                         '$session_time "$upstream_addr" '
                         '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';
    ## TCP 代理日志配置
    access_log /usr/local/openresty/nginx/logs/access.log tcp_proxy;
    open_log_file_cache off;

    ## TCP 代理配置
    upstream backend{
        server 127.0.0.2:1101;# 这个位置的ip随意填写
        balancer_by_lua_block {
            local balancer = require "ngx.balancer"
            local host = ""
            local port = 0
            host = ngx.ctx.proxy_host
            port = ngx.ctx.proxy_port
            local ok, err = balancer.set_current_peer(host, port)
            if not ok then
                ngx.log(ngx.ERR, "failed to set the peer: ", err)
            end
        }
    }


    server {
        preread_by_lua_block{

            local redis = require "resty.redis"
            --创建实例
            local red = redis:new()
            --设置超时(毫秒)
            red:set_timeouts(1000, 1000, 1000)
            
            --建立连接,请在这里配置 Redis 的 IP 地址、端口号、密码和用到的 Key
            local rhost = "127.0.0.1"
            -- local rport = 6374
            -- 根据环境变量里的 RPORT 的值来设置 redis 的连接端口
            local rport = os.getenv("RPORT")
            local rpass = "abcdefg"
            -- rkey 是 Redis 数据库里使用的 Key,用 keys * 命令查询
            -- local rkey = "proxies:universal"
            -- 根据环境变量里的 RKEY 的值设置要读取的 redis 的字典名
            local rkey = os.getenv("RKEY")
            local ok, err = red:connect(rhost, rport)
            if not ok then
                ngx.log(ngx.ERR,"failed to connect: ", err)
                return red:close()
            end

            -- 如果没有密码,移除下面这一行
            -- local res, err = red:auth(rpass)
            -- 下面这个是获取所有 Key 值的方法,根据 Key 的类型来,zrange zrangebyscore 针对的是 zset 有序列表。hkey 针对的是哈希
            -- 80,100 分别是最小和最大分数值,如果是测试使用可以设成 1,100
            local res, err = red:zrangebyscore(rkey, "80", "100")
            if not res then
                ngx.log(ngx.ERR,"res num error : ", err)
                return red:close()
            end
            
            local radmnum = math.randomseed(tonumber(tostring(ngx.now()):reverse():sub(1, 6)))
            local proxy = res[math.random(#res)]
            local colon_index = string.find(proxy, ":")
            local proxy_ip = string.sub(proxy, 1, colon_index - 1)
            local proxy_port = string.sub(proxy, colon_index + 1)
            ngx.log(ngx.ERR,"redis data = ", proxy_ip, ":", proxy_port);
            ngx.ctx.proxy_host = proxy_ip
            ngx.ctx.proxy_port = proxy_port
            
            local ok, err = red:close()
            if not ok then
                ngx.log(ngx.ERR,"failed to close: ",tostring(err))
                return
            end
        }
        #  下面是容器开放的端口,就是外面访问隧道代理的端口
       listen 0.0.0.0:9976;
       proxy_connect_timeout 3s;
       proxy_timeout 10s;
       proxy_pass backend;
    }
}

这份文件中,需要个人定制的包括连接 redis 服务的 ip 和端口,字典名,这部分我设置成了环境变量,在 docker-compose.yml 中进行修改即可。因为都是用 docker 搭建的,默认没有密码。

另外隧道代理容器开放的端口也是在这里设置,还有获取所有 key 值的方法,因为这里 redis 数据库中的字典类型是 zset 有序列表,而且还需要根据分数值获取 ip,所以使用 zrangebyscore。
docker-compose.yml

然后还要在 docker-compose.yml 文件里添加 OpenResty 的 docker 镜像,添加内容如下:

tunnel_proxy:
    image: tunnel_proxy:latest
    network_mode: host
    # 端口由 nginx.conf 决定
    # ports:
      # - "9976:9976"
    build:
      context: tunnel_proxy
      dockerfile: Nginx
    restart: always
    depends_on:
      - redis4proxypool
    container_name: tunnel_proxy
    environment:
      RPORT: 6374
      # RKEY: "proxies:universal"
      RKEY: "proxies:httpbin"
    volumes:
      - ./logs/nginx:/usr/local/openresty/nginx/logs

network_mode 设置成 host,在 host 网络模式下,移除了宿主机与容器之间的网络隔离,容器直接使用宿主机的网络。

build 指定构建镜像的配置。其中 context 指定构建镜像的上下文路径,可以是一个本地目录或一个远程URL,这里是 tunnel_proxy 文件夹。dockerfile 指定 Dockerfile 文件的位置,相对于上下文路径,这里指定的 Nginx 文件。

environment 环境变量,设置环境变量 RPORT 为 redis 的连接端口,RKEY 为要读取的字典名。在刚才的 ProxyPool 的配置中,将 redis 容器映射到了本机的 6374 端口,所以 RPORT 设置为 6374。RKEY 值需和 proxypool 容器的REDIS_KEY值保持相同,我这里是根据测试 URL 来设置字典名的。

volumes 参数,将主机当前目录下的 ./logs/nginx 文件夹挂载到容器中的 /usr/local/openresty/nginx/logs 文件夹,这样就能方便的在主机上查看日志。
测试

运行用 nohup,这样能把 docker 运行的日志保存在 docker.log 文件里。

nohup docker-compose up > /home/work/docker.log 2>&1 &

HTTPBIN

此时,ProxyPool 中的测试 URL 设置为https://httpbin.org/ip,redis 中保存的字典名称为proxies:httpbin。等待了一会之后,在 redis 数据库里就能查询到已获取的 IP,100 分的 IP 也有了。

[root@localhost tunnel_proxy]# redis-cli -h 127.0.0.1 -p 6374
127.0.0.1:6374> keys *
1) "proxies:httpbin"
127.0.0.1:6374> zrange proxies:httpbin 0 -1
   1) "103.165.128.171:8080"
   2) "38.51.243.201:999"
   ...
   5934) "47.97.97.119:3128"
127.0.0.1:6374> zrangebyscore proxies:httpbin 100 100
1) "103.70.147.233:8080"
2) "181.143.143.122:999"
3) "181.74.83.25:999"
4) "223.215.176.151:8089"
5) "223.215.176.50:8089"
6) "47.97.97.119:3128"

使用下面的 python 代码来测试代理池的效果,访问https://httpbin.org/ip得到的响应是发出请求的 IP。

import requests
import time

proxies = {'https': 'http://192.168.47.138:9976'}
# resp = requests.get('http://httpbin.org/ip', proxies=proxies).text
i = 0
for _ in range(20):
    try:
        resp = requests.get('https://httpbin.org/ip', proxies=proxies)
        if resp.status_code == 200:
            print(resp.text)
            i = i+1
        else:
            # pass
            print("error")
        time.sleep(1)
    except:
        # pass
        print("error")
print(i)

结果如下,20次尝试,成功8次,回显 IP 均为代理池中的 100 分 IP:

{
  "origin": "181.143.143.122"
}

{
  "origin": "47.97.97.119"
}

{
  "origin": "47.97.97.119"
}

{
  "origin": "47.97.97.119"
}

error
error
error
{
  "origin": "181.143.143.122"
}

{
  "origin": "181.74.83.25"
}

error
error
error
error
error
error
{
  "origin": "47.97.97.119"
}

{
  "origin": "47.97.97.119"
}

error
error
error
8

同时查看日志文件 access.log 以及 error.log,可以看到流量情况,证明流量确实经过隧道代理。


红队工具

我这里使用 dirsearch来扫描靶机服务器的目录。

在靶机服务器上开启 http 服务,在防火墙里允许这个端口的访问

python3 -m http.server 54321

然后在 kali 里使用 dirsearch 工具进行扫描,如果没有这个工具,先要用 pip 安装

┌──(clay㉿kali)-[~]
└─$ sudo updatedb
[sudo] clay 的密码:

┌──(clay㉿kali)-[~]
└─$ locate dirsearch.py
/home/clay/.local/lib/python3.11/site-packages/dirsearch/dirsearch.py

┌──(clay㉿kali)-[~]
└─$ python3 /home/clay/.local/lib/python3.11/site-packages/dirsearch/dirsearch.py --url http://***.***.***.***:54321/ --proxy 192.168.47.138:9976

dirsearch 输出如下:

当前代理池 100 分 IP 如下,测试 URL 仍然为https://httpbin.org/ip

靶机日志如下,可以看到访问的 IP 全都是代理池中的 100 分 IP,说明流量经由隧道代理随机选取的 IP 去访问目标:
总结

本文在 python3 ProxyPool 的基础上,参考其他人的 OpenResty 的配置,添加并设置了名为 tunnel_proxy 的 docker 镜像,在 9976 端口实现了隧道代理功能,并分别用httpbin.org回显 IP 以及红队扫描工具进行了测试,测试结果证明隧道代理与代理池均正常运行。

最终的 ProxyPool 目录结构如下:

[root@localhost ProxyPool]# tree
.
├── build.yaml
├── docker-compose.yml
├── Dockerfile
...
├── logs
│   ├── nginx
│   │   ├── access.log
│   │   ├── error.log
│   │   └── nginx.pid
│   └── proxypool
│       ├── proxypool_error.log
│       └── proxypool_runtime.log
...
└── tunnel_proxy
    ├── Nginx
    └── nginx.conf

docker-compose.yml 文件内容如下。

version: "3"
services:
# 确保在防火墙里对 9976 和 5555 端口设置开放
  redis4proxypool:
    image: redis:alpine
    container_name: redis4proxypool
    ports:
      - "6374:6379"
  proxypool:
    image: "germey/proxypool:master"
    container_name: proxypool
    ports:
      - "5555:5555"
    restart: always
    volumes:
      - ./logs/proxypool:/app/proxypool/logs
    # volumes:
    #   - proxypool/crawlers/private:/app/proxypool/crawlers/private
    environment:
      CYCLE_TESTER: 5
      PROXYPOOL_REDIS_HOST: redis4proxypool
      PROXYPOOL_REDIS_PORT: 6379
      TEST_URL: https://httpbin.org/ip
      REDIS_KEY: proxies:httpbin
      LOG_RUNTIME_FILE: "./proxypool/logs/proxypool_runtime.log"
      LOG_ERROR_FILE: "./proxypool/logs/proxypool_error.log"
    depends_on:
      - redis4proxypool
  tunnel_proxy:
    image: tunnel_proxy:latest
    network_mode: host
    # ports:
    #   - "9976:9976"
    build:
      context: tunnel_proxy
      dockerfile: Nginx
    restart: always
    depends_on:
      - redis4proxypool
    container_name: tunnel_proxy
    environment:
      RPORT: 6374
      # RKEY: "proxies:universal"
      RKEY: "proxies:httpbin"
    volumes:
      - ./logs/nginx:/usr/local/openresty/nginx/logs

如果要修改代理池的测试 URL,需要修改docker-compose.yml 文件中 proxypool 的 environment 环境变量里的TEST_URL。

如果你修改了 Dockerfile 或者其他构建文件,你可能需要重新构建镜像,以便应用修改。这时候你可以使用docker-compose up --build命令。

如果只修改了 docker-compose.yml 中的内容,而没有修改 Dockerfile 或者其他构建文件,那么你不需要重新构建镜像,只需要 docker-compose up 重新创建容器。

如果是在服务器上搭建这个,可以设置防火墙指定 ip 才能访问 9976 端口。

原文:https://www.freebuf.com/defense/374034.html


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-27 22:39 , Processed in 0.013602 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表