├── .gitignore ├── LICENSE ├── README.md └── poc_monitor.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the .env file 2 | .env 3 | 4 | # Ignore log files 5 | *.log 6 | poc_monitor_log.md 7 | /var/log/poc_monitor/ 8 | 9 | # Ignore cache files 10 | *.pyc 11 | __pycache__/ 12 | poc_cache.json 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Zach Lawson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # poc_monitor 2 | A short scraper looking for a POC of CVE-2024-49112 3 | 4 | Setting a cron job to run the script every 4 hours will keep the updates in place. The alerting can be customized but used Discord webhook to alert a Discord channel. 5 | 6 | `0 */4 * * * cd /root/monitor_script && /usr/bin/python3 poc_monitor.py >> /var/log/poc_monitor/poc_monitor.log 2>&1` 7 | 8 | with a debug set: 9 | 10 | `cd /root/monitor_script && /usr/bin/python3 poc_monitor.py >> /var/log/poc_monitor/poc_monitor.log 2>&1 || echo "Cron job failed at $(date)" >> /var/log/poc_monitor/poc_monitor.log` 11 | 12 | Set your .env file like so: 13 | ``` 14 | GOOGLE_API_KEY=your_google_api_key 15 | SEARCH_ENGINE_ID=your_search_engine_id 16 | DISCORD_WEBHOOK_URL=your_discord_webhook_url 17 | SEARCH_TERM=POC CVE-2024-XXXX 18 | CHECK_INTERVAL=14400 # in seconds (4 hours) 19 | ``` 20 | Obviously you can set the search term to whatever you want, just be careful about timing since the Google search API isn't free above 100 queries a day. 21 | -------------------------------------------------------------------------------- /poc_monitor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import requests 4 | import json 5 | from datetime import datetime 6 | from dotenv import load_dotenv 7 | 8 | # Load environment variables 9 | load_dotenv() 10 | 11 | # Environment variables 12 | API_KEY = os.getenv("GOOGLE_API_KEY") 13 | CX = os.getenv("SEARCH_ENGINE_ID") 14 | DISCORD_WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK_URL") 15 | SEARCH_TERM = os.getenv("SEARCH_TERM") 16 | CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", 14400)) # Default to 4 hours 17 | 18 | CACHE_FILE = "poc_cache.json" 19 | 20 | def load_cache(): 21 | """Load seen links from the cache file.""" 22 | try: 23 | with open(CACHE_FILE, "r") as file: 24 | return json.load(file) 25 | except FileNotFoundError: 26 | return {"seen_links": []} 27 | 28 | def save_cache(cache_data): 29 | """Save seen links to the cache file.""" 30 | with open(CACHE_FILE, "w") as file: 31 | json.dump(cache_data, file) 32 | 33 | def google_search(search_term, num_results=10): 34 | """Search Google using Custom Search JSON API.""" 35 | endpoint = "https://www.googleapis.com/customsearch/v1" 36 | params = { 37 | "key": API_KEY, 38 | "cx": CX, 39 | "q": search_term, 40 | "num": num_results, 41 | } 42 | 43 | response = requests.get(endpoint, params=params) 44 | if response.status_code != 200: 45 | print("Error occurred while searching Google:", response.json()) 46 | return [] 47 | 48 | search_results = response.json() 49 | results = [] 50 | 51 | for item in search_results.get("items", []): 52 | results.append({ 53 | "title": item.get("title"), 54 | "link": item.get("link"), 55 | "description": item.get("snippet"), 56 | }) 57 | 58 | return results 59 | 60 | def send_discord_alert(results): 61 | """Send new results to Discord via webhook.""" 62 | content = f"**New results found for search term: {SEARCH_TERM}**\n\n" 63 | for idx, result in enumerate(results, start=1): 64 | # Truncate the description if it exceeds 200 characters 65 | description = result['description'][:200] + "..." if len(result['description']) > 200 else result['description'] 66 | 67 | # Format the result entry 68 | result_entry = f"**{idx}. [{result['title']}]({result['link']})**\n{description}\n\n" 69 | 70 | # Check if adding this entry will exceed Discord's limit 71 | if len(content) + len(result_entry) > 2000: 72 | print("[INFO] Content exceeds 2000 characters, truncating alert.") 73 | break # Stop adding more results 74 | 75 | content += result_entry 76 | 77 | # Send the message to Discord 78 | data = {"content": content} 79 | response = requests.post(DISCORD_WEBHOOK_URL, json=data) 80 | 81 | if response.status_code == 204: 82 | print("[INFO] Alert sent to Discord successfully.") 83 | else: 84 | print("[ERROR] Failed to send alert to Discord:", response.status_code, response.text) 85 | 86 | 87 | def log_results(results): 88 | """Log new results to a file and print to the console.""" 89 | filename = "poc_monitor_log.md" 90 | with open(filename, "a", encoding="utf-8") as file: 91 | file.write(f"\n# Updates on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") 92 | for idx, result in enumerate(results, start=1): 93 | file.write(f"### Result {idx}\n") 94 | file.write(f"**Title:** [{result['title']}]({result['link']})\n\n") 95 | file.write(f"**Description:** {result['description']}\n\n") 96 | 97 | print(f"[INFO] Logged {len(results)} new results to {filename}.") 98 | 99 | def monitor_poc(): 100 | """Monitor for new POC content.""" 101 | cache_data = load_cache() 102 | seen_links = set(cache_data.get("seen_links", [])) 103 | 104 | while True: 105 | print(f"\n[INFO] Checking for updates on: {SEARCH_TERM}") 106 | results = google_search(SEARCH_TERM) 107 | 108 | new_results = [] 109 | for result in results: 110 | if result['link'] not in seen_links: 111 | seen_links.add(result['link']) 112 | new_results.append(result) 113 | 114 | if new_results: 115 | print("[ALERT] New content found!") 116 | log_results(new_results) 117 | send_discord_alert(new_results) 118 | # Update cache with new links 119 | cache_data["seen_links"] = list(seen_links) 120 | save_cache(cache_data) 121 | else: 122 | print("[INFO] No new content.") 123 | 124 | print(f"[INFO] Sleeping for {CHECK_INTERVAL} seconds...\n") 125 | time.sleep(CHECK_INTERVAL) 126 | 127 | if __name__ == "__main__": 128 | try: 129 | monitor_poc() 130 | except KeyboardInterrupt: 131 | print("\n[INFO] Monitoring stopped by user.") 132 | --------------------------------------------------------------------------------