- 網際網路下駭客發動攻擊通常少不了木馬程式的植入。因此阻絕攻擊的第一步在於如何防止惡意檔案或程式的植入。
- 以Java Web而言,若被植入程式到部署目錄,就可能被以URL的方式執行。
- 藉由監控特定資料夾的檔案寫入,避免被植入惡意檔案。
- 使用NIO2的WatchService監看特定資料夾是否有檔案異動。
- 使用「樣板方法模式(template method)」建立告警程式,負責在WatchService接收到檔案異動事件後,觸發告警程式進行後續作為,如:
- 日誌紀錄(log)
- 馬上移除檔案
- 打包程式進行部署。
Guardian
package fsWatch;
import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import static java.nio.file.LinkOption.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.util.*;
import org.apache.log4j.Logger;
public class Guardian {
static Logger logger = Logger.getLogger(Guardian.class);
static Properties myProps = new Properties();
private final WatchService watchService;
private final Map<WatchKey, Path> keys;
private final boolean recursive;
private boolean trace = false;
// 1. 將路徑dir指定給WatchService監看
private void register(Path dir) throws IOException {
WatchKey key = dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);//
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
logger.info(String.format("register: %s", dir));
} else {
if (!dir.equals(prev)) {
logger.info(String.format("update: %s -> %s", prev, dir));
}
}
}
keys.put(key, dir);
}
// 2. 使用Files.walkFileTree遞迴走訪所有目錄,都指定給WatchService監看
private void registerAll(final Path start) throws IOException {
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
register(dir);
return FileVisitResult.CONTINUE;
}
});
}
// 3. 建立建構子
Guardian(Path dir, boolean recursive) throws IOException {
this.watchService = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<WatchKey, Path>();
this.recursive = recursive;
if (recursive) {
logger.info(String.format("Scanning %s ...", dir));
registerAll(dir);
logger.info("Done.");
} else {
register(dir);
}
this.trace = true;
}
// 4. 建立與告警程式的關聯
private List<AbstractAlertListener> listeners = new ArrayList<>();
void addListener(AbstractAlertListener alarmListener) {
listeners.add(alarmListener);
}
// 5. 建立監控核心程式
// 5.1 將WatchService與Map<WatchKey, Path>結合
// 5.2 觸發事件時一併觸發其他告警程式
@SuppressWarnings("unchecked")
void watch() throws Exception {
for (;;) {
WatchKey key;
try {
key = watchService.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
logger.error("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path name = ev.context();
Path child = dir.resolve(name);
// action
for (AbstractAlertListener l : listeners) {
l.alertTemplate(event.kind(), child);
}
if (recursive && (kind == ENTRY_CREATE)) {
try {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
registerAll(child);
}
} catch (IOException x) {
}
}
}
// reset key and remove from set if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty()) {
break;
}
}
}
}
// 6. 使用Thread啟動Guardian
private static void launchGuardian(final String path) throws Exception {
boolean recursive = true;
Path dir = Paths.get(path);
Guardian guardian = new Guardian(dir, recursive);
guardian.addListener(new LogAlertListener());
guardian.addListener(new FileMoveAlertListener());
guardian.watch();
}
// 7. 載入參數檔並啟動Guardian
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("watchDog.properties");
myProps.load(fis);
String watchDir = myProps.getProperty("path.include");
String moveTo = myProps.getProperty("path.moveTo");
Files.createDirectories(Paths.get(watchDir));
Files.createDirectories(Paths.get(moveTo));
launchGuardian(watchDir);
} catch (Exception e) {
logger.error(e.getMessage(), e);
System.exit(0);
}
}
}
AbstractAlertListener
package fsWatch;
import static java.nio.file.StandardWatchEventKinds.*;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import org.apache.log4j.Logger;
public abstract class AbstractAlertListener {
static int seq;
protected final Logger logger = Logger.getLogger(this.getClass());
abstract void strategy(WatchEvent.Kind<?> kind, Path path);
void hook(WatchEvent.Kind<?> kind, Path path) {
// default null implementation
}
protected void alertTemplate(WatchEvent.Kind<?> kind, Path path) {
if (kind.equals(ENTRY_CREATE)) {
strategy(kind, path);
}
hook(kind, path);
}
}
LogAlertListener
package fsWatch;
import java.nio.file.Path;
import java.nio.file.WatchEvent.Kind;
public class LogAlertListener extends AbstractAlertListener {
@Override
void strategy(Kind kind, Path path) {
logger.warn(kind.name() + ", " + path);
}
}
FileMoveAlertListener
package fsWatch;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.WatchEvent.Kind;
public class FileMoveAlertListener extends AbstractAlertListener {
@Override
void strategy(Kind kind, Path path) {
move(path);
}
private void move(Path path) {
wait4Ready(path);
Path moveTo = Paths
.get(Guardian.myProps.getProperty("path.moveTo") + File.separator
+ path.getFileName() + "." + ++seq);
try {
Files.move(path, moveTo, StandardCopyOption.REPLACE_EXISTING);
logger.info("File Moved: " + path + " -> " + moveTo);
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
private void wait4Ready(Path path) {
boolean locked = true;
RandomAccessFile raf = null;
while (locked) {
File file = null;
try {
file = new File(path.toString());
raf = new RandomAccessFile(file, "r");
raf.seek(file.length());
locked = false;
} catch (IOException e) {
locked = file.exists();
if (locked) {
logger.debug("File locked: '" + file.getAbsolutePath() + "'");
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
logger.error(e1.getMessage(), e);
}
} else {
logger.debug("File was deleted while copying: '"
+ file.getAbsolutePath() + "'");
}
} finally {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
}
}
}
}
watchDog.properties
path.include = D:\\security\\include path.moveTo = D:\\security\\bad
log4j.properties
log4j.rootLogger=INFO, stdout, logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-5p %l - %m%n
log4j.appender.stdout.encoding=UTF-8
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
#log4j.appender.logfile.layout.ConversionPattern=%d [%t] %-5p (%F:%L) - %m%n
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %l - %m%n
log4j.appender.logfile.File=D:/security/watchDog.log
log4j.appender.logfile.DatePattern='-'yyyyMMdd'.log'
log4j.appender.logfile.encoding=UTF-8
參考資料: http://152.92.236.11/tutorial_java/essential/io/examples/WatchDir.java