起因
需求源于痛苦。很多人可能已经深有体会,搭建环境的时间可能会远大于真正做事时间。我最近在看一些Java的漏洞,但我不是Java开发者,并没有现成的Java环境。而且最近我没有自己的电脑用,需要管理员权限的操作我都不能执行。用着并不熟悉的IDEA,拉下来的代码编译无数遍都没有通过。检查了各种配置无果后,萌生了一个想法:为什么不能把我在开发中经常用到的Docker拿过来用做Java代码编译和部署,并且可以进行远程断点调试?而且我们可以通过不同的镜像去切换不同的JDK和Maven版本,岂不爽哉?闲话少说,行不行跟我一起试试看就知道了。
准备
- Docker
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口...官方的话就不继续说了,有关Docker的具体用法请看官方文档。
- IDEA
这是一个我也不咋熟悉的IDE,在本文中只用来做远程调试。
- 含有漏洞的showcase
本文以 S2-045
为例,需要下载struts-2.5.10的showcase,下载地址:https://archive.apache.org/dist/struts/
文件结构
+- debug_in_docker/
+- src/
| +- [showcase files]
!
+- tomcat/
| +- context.xml
| +- tomcat-users.xml
| +- run.sh
|
+- docker-compose.yml
|
+- Dockerfile
|
+- run.sh
从文件结构中可以看出,我将含有漏洞的showcase代码拿出来放在一个新文件夹,tomcat文件夹主要是Tomcat的一些配置和运行脚本。Dockerfile用于构建部署环境需要的镜像,docker-compose.yml用于运行镜像,run.sh为整个系统的运行脚本。我会在搭建思路中一一讲解它们。
搭建思路
Maven Build
Struts2的showcase是一个可以用Maven编译和运行的项目。我在使用IDEA编译的时候使用了Maven生命周期的两个命令:clean
和 install
。
那么,移步Docker Hub,找一下Maven相关的镜像
恰好,Docker Hub中的运行示例使用的正是 clean
和install
这两个命令。直接拿过来跑一下试试。
cd debug_in_docker/src
docker run -it --rm --name my-maven-project -v "$(pwd)":/usr/src/mymaven -w /usr/src/mymaven maven:3.3-jdk-8 mvn clean install
看起来不错,已经开始跑了。让它先跑着,我们研究下其他的。
Tomcat Server
我们需要构建一个Tomcat服务器镜像并且把我们Bulid好的程序部署上去然后开启远程调试,那我们来写一个Dockerfile吧。这里我借鉴docker/labs 这个项目。我跟着它的步骤走了一遍整个流程,然后进行了一些优化。
首先是Tomcat的配置。我希望能在Tomcat的主页通过 Manager App
看到我所有部署好的项目,需要更改两个Tomcat配置。
- tomcat/context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context antiResourceLocking="false" privileged="true" >
<!-- <Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> -->
<Manager sessionAttributeValueClassNameFilter="java\.lang\.(?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPreventionFilter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap"/>
</Context>
这个配置文件主要把 ...RemoteAddrValve...
注释掉用于允许远程访问管理页面。
- tomcat/tomcat-users.xml
<?xml version='1.0' encoding='cp1252'?>
<tomcat-users>
<user username="system" password="manager" roles="admin-gui,manager-gui" />
<role rolename="manager-script"/>
<user username="admin" password="admin" roles="manager-script"/>
</tomcat-users>
这个配置文件用来设置管理页面的账号和密码。
- tomcat/run.sh
#!/bin/sh
exec ${CATALINA_HOME}/bin/catalina.sh jpda run
这个shell文件用来启动Tomcat并启动远程调试。
现在回到Dockerfile,我们使用 tomcat
作为基础镜像,然后把我们刚刚搞的Tomcat配置文件和 run.sh
放到它们应该在的位置并且给run.sh执行权限。然后在镜像内 webapps/
中为 struts2-showcase
创建一个文件夹。设置一下远程调试的相关环境变量,引出8080端口,执行 run.sh
。
- Dockerfile
FROM tomcat
# sets up user accounts and access permit for the Tomcat manager GUI
# and script access for Maven deployments
ADD tomcat/tomcat-users.xml $CATALINA_HOME/conf/
ADD tomcat/context.xml $CATALINA_HOME/webapps//manager/META-INF/
# ADD tomcat/catalina.sh $CATALINA_HOME/bin/
ADD tomcat/run.sh $CATALINA_HOME/bin/run.sh
RUN chmod +x $CATALINA_HOME/bin/run.sh
# create mount point for volume with application
RUN mkdir $CATALINA_HOME/webapps/struts2-showcase
# add tomcat jpda debugging environmental variables
#ENV JPDA_OPTS="-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n"
ENV JPDA_ADDRESS="8000"
ENV JPDA_TRANSPORT="dt_socket"
# start tomcat with remote debugging
EXPOSE 8080
CMD ["run.sh"]
写好Dockerfile后,我们写一个docker-compose来为容器运行做准备。
- docker-compose.yml
version: "3"
services:
webserver:
build: .
image: debug-webserver
volumes:
- ./src/target/struts2-showcase:/usr/local/tomcat/webapps/struts2-showcase
ports:
- "8080:8080"
- "8000:8000"
restart: always
docker-compose就简单多了,主要就是引出对应的端口和把使用Maven Build好的 target
文件夹挂载到容器内新建的struts2-showcase
文件夹中,完成部署的同时方便远程调试。
运行
还记得刚才我们用 docker run
跑的build吗?回头看已经成功了。
现在我们运行 docker-compose up
让服务跑起来。
可以在这里看到镜像构建过程和服务运行状态。现在在浏览器中访问http://localhost:8080 就可以看到Tomcat主页
点击 Manager App
可以看到刚刚部署的showcase项目,可直接点击访问。
远程调试
服务跑起来后,使用IDEA打开 src/
目录,在菜单中依次点击RUN —— Edit Configurations
,弹出页面点击 +
号添加一个 Remote
配置,命名为 tomcat-docker
。
修改Debug端口为8000如下图设置:
保存后依次点击菜单中 RUN —— debug "tomcat-docker"
,Console中输出如下内容即连接远程调试成功。
确认没有问题了就可以打断点触发漏洞了。以 S2-045
为例,断点打在 struts2-core-2.5.10.jar!/org/apache/struts2/interceptor/FileUploadInterceptor.class
中
断点打好后,来一段payload
触发断点,因为没有管理员权限,我使用Postman代替Brupsuite。
如上面动图所见,断点已命中。现在看来差不多了。但是有一个不大的问题,我要跑两个命令,一个是 docker run maven
用于build项目。一个是 docker-compose up
用于将build好的项目部署到服务器中并开启远程调试,那么有没有办法用一个命令呢?我试了写两个 services
的 docker-compose 即使增加 depends_on
或者 links
,docker-compose也并不知道build有没有完成。官方提供了一个解决方案是在服务A运行完成后创建一个文件,服务B一直监控是否存在这个文件,如果不存在就一直等待,存在继续跑。这个方案应该可行但是过于麻烦了,而且在这个官方方案的投票中踩远大于顶,很有意思。有兴趣的可以去看下。我没有参考官方方案,直接写个shell脚本吧。
- run.sh
#!/bin/sh
docker run -it --rm --name build-maven-project -v "$(pwd)"/src:/usr/src/mymaven -w /usr/src/mymaven maven:3.3-jdk-8 mvn clean install
docker-compose up
至此全部搞定。不过,我只是抛个砖~