需求源于痛苦。很多人可能已经深有体会,搭建环境的时间可能会远大于真正做事时间。我最近在看一些Java的漏洞,但我不是Java开发者,并没有现成的Java环境。而且最近我没有自己的电脑用,需要管理员权限的操作我都不能执行。用着并不熟悉的IDEA,拉下来的代码编译无数遍都没有通过。检查了各种配置无果后,萌生了一个想法:为什么不能把我在开发中经常用到的Docker拿过来用做Java代码编译和部署,并且可以进行远程断点调试?而且我们可以通过不同的镜像去切换不同的JDK和Maven版本,岂不爽哉?闲话少说,行不行跟我一起试试看就知道了。
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口...官方的话就不继续说了,有关Docker的具体用法请看 官方文档 。
这是一个我也不咋熟悉的IDE,在本文中只用来做远程调试。
本文以 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为整个系统的运行脚本。我会在搭建思路中一一讲解它们。
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服务器镜像并且把我们Bulid好的程序部署上去并进行远程调试,那我们来写一个Dockerfile吧。这里我借鉴 docker/labs 这个项目。我跟着它的步骤走了一遍整个流程,然后进行了一些优化。
首先是Tomcat的配置。我希望能在Tomcat的主页通过 Manager App
看到我所有部署好的项目,需要更改两个Tomcat配置。
<?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...
注释掉用于允许远程访问管理页面。
<?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>
这个配置文件用来设置管理页面的账号和密码。
#!/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
。
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来为容器运行做准备。
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脚本吧。
#!/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
至此全部搞定。不过,我只是抛个砖~
【1】 S2-045 漏洞分析
【2】 Apache Manven Project
【3】 In-container Java Development: IntelliJ Community Edition