查看原文
其他

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

Cl4ySmith 迪哥讲事 2023-08-15

前言

作为一种常见的服务器防御网络攻击或探测手段,封 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。在里面新建两个文件:Nginxnginx.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 内无条件自助退款

往期回顾

2022年度精选文章

SSRF研究笔记

xss研究笔记

dom-xss精选文章

Nuclei权威指南-如何躺赚

漏洞赏金猎人系列-如何测试设置功能IV

漏洞赏金猎人系列-如何测试注册功能以及相关Tips


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存