- 網際網路下駭客發動攻擊通常少不了木馬程式的植入。因此阻絕攻擊的第一步在於如何防止惡意檔案或程式的植入。
- 以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