# Eamil Log Parsing 流程(測試用): 1. 會先到Postman打phishCampaign這隻api,它會寄送3封email,並且db的phish_email會有三筆新紀錄 (https://twmsocialengineeringt.com/platform/phish-camps?accountId=1) 2. 那三筆記錄會先壓messageId上去,以防止mail server的parsing程式沒抓到messageId 3. 另外同時,我們會先把mail_log_parse_ap這個專案包成jar檔後上傳到mail server去執行它(之後會變成背景一直跑) 4. phish-email的新資料一開始會呈現TBS的email_status,表示他在等待每五分鐘(時間結尾0,5)會有統一發送信的機制,這時mail server的mail log就會有新的紀錄 5. 當mail log有新紀錄時,我們的mail server parsing程式就只會去針對新加進去的log一行行去parsing,來取出他們的messageId, queueId, dsn代碼來存進去db的postfix_email表,以利未來有需要這些資料時可以取用 6. 另外在每parsing每行的messageId, queueId, dsn時,會針對phish_email的messageId去比對是否存在,如果messageId相同的話,我們就會將dsn值回寫回去(條件是如果dsn為2.0.0,email_status就更新成SUC,如果dsn為2.0.0以外的就更新為FAIL) **=>** 此目的是因為我們是用relay mail server來去寄送釣魚信件給受害者的server,而不是從我們本機java程式端去發送,所以我們才需要去抓受害者端回傳給relay mail server的dsn代碼來確定我們是否有真的寄送成功,而不是一開始java程式到mail server的response代碼 # 程式碼細節介紹: ## (一) Mail_log_parser_ap Project ### SmsNewApplication main class for running 主要重點是這幾行: String myMessageId = parseWritingLine.findQueueIdByMessageId(line); String myDsn = parseWritingLine.findDSN(line); callPostMethod(myMessageId,myDsn,"phish","BrianTesting"); lastPosition = file.getFilePointer(); =>外層會有WatchService物件去監聽是否有新的mail log加入,如果有就會在無窮whie loop中繼續執行抓出 messageId 和 dsn 的方法,而lastPosition紀錄著之前在mail log檔案中指到第幾行,以便從新資料繼續parsing,減少程式重複從頭run之情形,而callPostMethod是最後將取出的dsn代碼,messageId和預設帶入的token: BrianTesting, type: phish,做為參數去呼叫寫在socialEngineerProject的更新emailStatus那隻api ```java @Override public void run(String... args) throws Exception { Path logFilePath = Paths.get(emailLogFile); long lastPosition = 0; try { WatchService watchService = FileSystems.getDefault().newWatchService(); logFilePath.getParent().register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); boolean running = true; while (running) { WatchKey key; try { key = watchService.take(); } catch (InterruptedException e) { break; } for (WatchEvent<?> event : key.pollEvents()) { if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) { Path modifiedFilePath = (Path) event.context(); if (modifiedFilePath.equals(logFilePath.getFileName())) { // 執行相應的處理邏輯,例如讀取郵件日誌中的新資料 try(RandomAccessFile file = new RandomAccessFile(logFilePath.toFile(), "r")){ file.seek(lastPosition); String line; while ((line = file.readLine()) != null) { System.out.println("Log entry: " + line); // 執行相應的處理邏輯,例如解析郵件日誌中的新資料 String myMessageId = parseWritingLine.findQueueIdByMessageId(line); String myDsn = parseWritingLine.findDSN(line); callPostMethod(myMessageId,myDsn,"phish","BrianTesting"); lastPosition = file.getFilePointer(); // 更新上一次讀取的位置,很重要 } }catch (IOException e) { e.printStackTrace(); } } } } key.reset(); } } catch (IOException e) { e.printStackTrace(); } } ``` ### callPostMethod in SmsNewApplication class for outer api calling HttpPost httpPost = new HttpPost("https://twmsocialengineeringt.com/platform/phish-email-status/"); =>這個要等之後我寫的那隻api整合進去後就可以執行了,我已經在localhost版本測過是可以打api的了 ```java public static void callPostMethod(String messageId,String dsn,String type,String token) throws Exception { // Create an HttpClient instance HttpClient httpClient = HttpClients.createDefault(); // Create an HttpPost request with the target API URL HttpPost httpPost = new HttpPost("https://twmsocialengineeringt.com/platform/phish-email-status/"); //HttpPost httpPost = new HttpPost("https://localhost:8080/platform/phish-email-status/"); // Set request headers httpPost.setHeader("Content-Type", "application/json"); // Create a request object with class attributes as parameters MyRequestObject requestObject = new MyRequestObject(); requestObject.setMessageId(messageId); requestObject.setEmailStatus(dsn); requestObject.setType(type); requestObject.setToken(token); // Convert the request object to JSON string Gson gson = new Gson(); String jsonPayload = gson.toJson(requestObject); // Set the JSON payload as the request entity StringEntity requestEntity = new StringEntity(jsonPayload); httpPost.setEntity(requestEntity); // Send the request and receive the response HttpResponse response = httpClient.execute(httpPost); // Get the response entity HttpEntity entity = response.getEntity(); // Read the response as a string String responseBody = EntityUtils.toString(entity); // Process the response as needed System.out.println("Print the responseBody:"+responseBody); } ``` ### 用messageId找queueId (ParseWritingLine class) 1. 尋找mail log的messageId正規化表達式: String messageIDRegix = "message-id\\s*=\\s*(?:<([^>]+)> | ([^;]+))"; =>messageId等號後面可能會有<>包住我們要的值,或是沒有<>都要考慮,所以用|(or)來找尋符合其中一個條件的messageId ![](https://hackmd.io/_uploads/H1eqSOKu3.png) ![](https://hackmd.io/_uploads/BkC9r_td3.png) 2. 群找mail log的queueId正規化表達式: String queueIDRegix = ".* : (\\w{10,20}): .*"; =>從上面兩個範例可以看到queueId是在兩個 : 之間的值,所以正規劃就是判斷,不管兩個冒號之外的值有多少,抓出兩個冒號間長度介在10~20的字串 3. 流程: 當每行行mail log在parsing時,如果正規化有找到messageId存在的話,先去db看postfix email table內是否已經有該messageId存過了,如果沒有的話就直接新增一個postfixEmail物件,並將parsing其該列的messageId,queueId一同存入,最後在將新筆資料 save and flush 資料庫中 ```java public String findQueueIdByMessageId(String line) { if(line == null) { return null; } String messageIDRegix = "message-id\\s*=\\s*(?:<([^>]+)>|([^;]+))"; String queueIDRegix = ".*: (\\w{10,20}):.*"; Pattern pattern = Pattern.compile(messageIDRegix); Pattern queueIdPattern = Pattern.compile(queueIDRegix); Matcher matcher = pattern.matcher(line); Matcher queueIdMatcher = queueIdPattern.matcher(line); if (matcher.find()) { //這個是在做把queueId存進去我們預設的串列中 String messageID = matcher.group(1) != null ? matcher.group(1) : matcher.group(2); logger.info("Found message ID: " +messageID); Optional<PostfixEmail> postfixEmailLog = postfixEmailJpaRepository.findByMessageId(messageID); if (postfixEmailLog.isPresent()) {//如果messageId存在在資料表裡面的話,表示之前已經有寫入進去messageId 和queueId了 return null; } PostfixEmail postfixEmail = new PostfixEmail(); postfixEmail.setMessageId(messageID); if(queueIdMatcher.matches()) { String queueId = queueIdMatcher.group(1) != null ? queueIdMatcher.group(1) : queueIdMatcher.group(2); logger.info("Set Queue ID in line49: " +queueId); postfixEmail.setQueueId(queueId); postfixEmailJpaRepository.saveAndFlush(postfixEmail);//這行沒有加的話會無法寫進去資料庫 } return messageID; } return null; } ``` ### 用queueId找dsn (ParseWritingLine class in mail_log_parser_ap Project) 1. 尋找mail log的dsn的正規化表達式: String dsnRegix = "dsn=(\\d\\.\\d\\.\\d),"; => 抓出符合dsn= 後面的兩個x.x.x的形式之字串 2. 流程: 如果一行行parsing時有符合queueId的正規化表示式的值出現,就去資料庫的postfix_email table看是否已經存在queueId了,如果有就將該行parse出的dsn代碼update資料庫抓出來的那筆postfix_email列的dsn欄位值 ```java public String findDSN(String line) { if(line == null) { return null; } String queueIDRegix = ".*: (\\w{10,20}):.*"; String dsnRegix = "dsn=(\\d\\.\\d\\.\\d),"; Pattern queueIdPattern = Pattern.compile(queueIDRegix); Pattern dsnPattern = Pattern.compile(dsnRegix); String queueId = ""; Matcher queueIdMatcher = queueIdPattern.matcher(line); if(queueIdMatcher.find()) { queueId = queueIdMatcher.group(1); logger.info("Find Queue ID in func findDSN: " +queueId); Optional<PostfixEmail> postfixEmailLog = postfixEmailJpaRepository.findByQueueId(queueId); if (!postfixEmailLog.isPresent()) { return null; } PostfixEmail postfixEmail = postfixEmailLog.get(); String dbQueueId = postfixEmail.getQueueId(); logger.info("line 83 testing, print db postfixQueueId:"+dbQueueId); Matcher matcher = dsnPattern.matcher(line); if(matcher.find()) { String dsn = matcher.group(1); logger.info("Found DSN: " +dsn); postfixEmail.setDsn(dsn); postfixEmailJpaRepository.saveAndFlush(postfixEmail);//這行沒有加的話會無法寫進去資料庫 return dsn; } } return null; } ``` ## (二) SocialEngineerProject ### Update Email Status API 的 controller API request parameter有四個(一定要傳入): type, messageId, emailStatus, token ```java @Tag(name = "PhishEmail", description = "EmailStatusUpdate") public class EmailCtrl { @Autowired private EmailService emailService; @Operation(summary = "更新寄信回傳狀態") @PostMapping(value = "/phish-email-status") public ModelWrapper<Boolean> updateEmailStatus(@RequestParam @Parameter(description = "type of phishEmail or sms") String type, @RequestParam @Parameter(description = "message Id") String messageId, @RequestParam @Parameter(description = "eamil Status") String emailStatus, @RequestParam @Parameter(description = "token") String token){ try { return ModelWrapper.success(emailService.updateEmailStatus(type,messageId,emailStatus,token)); } catch (RestApiException ex) { return ModelWrapper.error(ex); } } } ``` ## Spring Security 的設定(SocialEngineerProject) http.csrf().disable():停用 CSRF(Cross-Site Request Forgery)保護,這意味著禁用跨站請求偽造的防護機制。 =>多加最後一行:antMatchers("/phish-email-status/...").permitAll():允許 "/phish-email-status/..." 路徑下的請求不需要身份驗證。 ```java http.csrf().disable() .authorizeHttpRequests((authz) -> authz .antMatchers("/authenticate/**").permitAll() .antMatchers("/reset-password/**").permitAll() .antMatchers("/edu/**").permitAll() //下面這句超重要,沒有的話會導致Preflight Request被JWT擋住 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .antMatchers("/phish-email-status/**").permitAll() ``` ## Update Email Status API 的 service impl (SocialEngineerProject) token先預設BrianTesting, type預設phish,首先透過phishEmailJpa來抓出你想要update的那個messageId列,如果token符合且type為phish的話表示是釣魚信件的資料,所以進行後續dsn判斷,最後再更新EmailStatus欄位值 ```java @Override public Boolean updateEmailStatus(String type, String messageId, String status, String token) { //method用4個參數 Optional<PhishEmail> phishEmail0 = phishEmailJpaRepository.findByMessageId(messageId); if (!phishEmail0.isPresent()) { throw new ResourceNotFoundException(ApiErrorCode.UNEXPECTED_ERROR, "找不到對應的postfixEmail物件"); } PhishEmail phishEmail = phishEmail0.get(); if(token.equals("BrianTesting")) { if(type.equals("phish")) { if(status.equals("2.0.0")) { phishEmail.setEmailStatus("SUC"); }else { phishEmail.setEmailStatus("FAIL"); } phishEmail.setPostfixStatus("Update Email Status"); phishEmailJpaRepository.saveAndFlush(phishEmail); } }else { return false; } return true; } ``` # Mail Server相關指令: ### Q:如何將專案打包成jar檔? 1. 先切terminal路徑到專案底下(也就是在mail_log_parse_ap下),即為pom.xml的上一層 2. 執行這行: mvn clean package -DskipTests 是一個 Maven 命令,用於在項目中執行特定的操作。 這個命令執行了以下幾個步驟: * clean: 清理項目目錄,刪除之前編譯生成的目錄和文件。 * package: 對項目進行打包操作。Maven根據pom.xml配置文件中的相關設定,將項目源碼編譯、打包,生成可部署的輸出物件(例如JAR、WAR或EAR文件)。 * -DskipTests: 這是一個Maven的系統屬性。通過設置該屬性為true,告訴Maven在執行打包操作時跳過測試階段。測試階段通常包括執行單元測試和集成測試。跳過測試可以加快打包過程,特別是在開發階段不需要運行測試時。 =>總結來說,mvn clean package -DskipTests 命令的目的是將項目進行清理、編譯和打包的操作,並跳過測試階段。這個命令通常用於項目的打包部署,例如生成可運行的JAR文件或部署到應用服務器上。 P.S. 預設情況下,Maven 會在目標(target)目錄中生成一個名為 ${project.artifactId}-${project.version}.jar 的 JAR 檔案,其中 ${project.artifactId} 是專案的建構ID(Artifact ID),${project.version} 是專案的版本號。 =>如果你使用了其他 Maven 插件或設定了其他構建步驟,這可能會導致生成的 JAR 檔案名稱不同,具體取決於你的專案配置,否則JAR檔案名稱為上述的預設行為。 **=>這是看你的pom.xml怎麼設定的,因為你只需要 src 和 pom.xml 就可以打包出現target資料夾** ![](https://hackmd.io/_uploads/HkJ10MQ8n.png) ![](https://hackmd.io/_uploads/HykyCzXL2.png) ### Q:如何將jar檔丟上去mail server以進行後續操作? 1. 將路徑切到mail_log_parse_ap/target底下 * ls -altr 可以看到你的資料夾下所有資料的建立時間順序 2. 下smtp指令: sftp -i ~/.ssh/maip-t-chrisyen chrisyen@35.194.181.142 後就進入到可以傳輸到mail server的smtp連線下了 3. 將我們的jar檔傳上去mail server: put -r smstest-0.0.1-SNAPSHOT.jar /home/chrisyen (這個不寫也是會在這底下為mail server預設的路經) 4. 按exit後連進去mail server內: ssh -i ~/.ssh/maip-t-chrisyen chrisyen@35.194.181.142 輸入密碼Brianhuang881215 5. 將jar檔放移進去/ap/postfix_log_parser 底下: mv smstest-0.0.1-SNAPSHOT.jar /ap/postfix_log_parser =>而你會看到在這個資料夾下裡面以經有放execjar.sh的shell檔了(圖一),我們可以透過執行它來啟動java spring boot 專案,其裡面程式細節在圖二。 ![](https://hackmd.io/_uploads/SyQyvQ7Lh.png) ![](https://hackmd.io/_uploads/r14x5XQL3.png) 6. 更改jar檔名: mv 原檔名.jar 新檔名.jar 7. 如果要改sh檔裡面的內容: * 先 vi xxxx.sh * 再輸入 i 就會切換成編輯模式 * 然後要存檔離開的話,先按control c 然後輸入:wq就會存檔並離開,如果不想要存的話就直接按control z