如何使用 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
福利视频
笔者自己录制的一套php视频教程(适合0基础的),感兴趣的童鞋可以看看,基础视频总共约200多集,目前已经录制完毕,后续还有更多视频出品
https://space.bilibili.com/177546377/channel/seriesdetail?sid=2949374
技术交流
技术交流请加笔者微信:richardo1o1 (暗号:growing)
如果你是一个长期主义者,欢迎加入我的知识星球(优先查看这个链接,里面可能还有优惠券),我们一起往前走,每日都会更新,精细化运营,微信识别二维码付费即可加入,如不满意,72 小时内可在 App 内无条件自助退款