1
2
3
4
5
6 import json
7 import logging
8 import os
9 import shutil
10 import subprocess
11 import time
12
13 from lib.cuckoo.common.abstracts import Processing
14 from lib.cuckoo.common.exceptions import CuckooProcessingError
15 from lib.cuckoo.common.utils import md5_file, sha1_file
16
17 try:
18 import suricatasc
19 HAVE_SURICATASC = True
20 except ImportError:
21 HAVE_SURICATASC = False
22
23 log = logging.getLogger(__name__)
24
26 """Suricata processing module."""
27
28
29 sid_blacklist = [
30
31 2200074,
32
33
34 2017363,
35
36
37 2200075,
38 ]
39
41 """Process a PCAP file with Suricata in socket mode."""
42 if not HAVE_SURICATASC:
43 raise CuckooProcessingError(
44 "Suricata has been configured to run in socket mode but "
45 "suricatasc has not been installed, please re-install "
46 "Suricata or SuricataSC"
47 )
48
49 if not os.path.exists(self.socket):
50 raise CuckooProcessingError(
51 "Suricata has been configured to run in socket mode "
52 "but the socket is unavailable"
53 )
54
55 suri = suricatasc.SuricataSC(self.socket)
56
57 try:
58 suri.connect()
59 except suricatasc.SuricataException as e:
60 raise CuckooProcessingError(
61 "Error connecting to Suricata in socket mode: %s" % e
62 )
63
64
65 ret = suri.send_command("pcap-file", {
66 "filename": self.pcap_path,
67 "output-dir": self.suricata_path,
68 })
69
70 if not ret or ret["return"] != "OK":
71 raise CuckooProcessingError(
72 "Error submitting PCAP file to Suricata in socket mode, "
73 "return value: %s" % ret
74 )
75
76
77
78 while True:
79 ret = suri.send_command("pcap-current")
80
81
82
83 if ret and ret["message"] == "None":
84 break
85
86 time.sleep(1)
87
89 """Process a PCAP file with Suricata by running Suricata.
90
91 Using the socket mode is preferred as the plain binary mode requires
92 Suricata to load all its rules for every PCAP file and thus takes a
93 couple of performance heavy seconds to set itself up.
94 """
95 if not os.path.isfile(self.suricata):
96 raise CuckooProcessingError("Unable to locate Suricata binary")
97
98 if not os.path.isfile(self.config_path):
99 raise CuckooProcessingError(
100 "Unable to locate Suricata configuration"
101 )
102
103 args = [
104 self.suricata,
105 "-c", self.config_path,
106 "-k", "none",
107 "-l", self.suricata_path,
108 "-r", self.pcap_path,
109 ]
110
111 try:
112 subprocess.check_call(args)
113 except subprocess.CalledProcessError as e:
114 raise CuckooProcessingError(
115 "Suricata returned an error processing this pcap: %s" % e
116 )
117
119 """Parse the eve.json file."""
120 eve_log = os.path.join(self.suricata_path, self.eve_log)
121 if not os.path.isfile(eve_log):
122 log.warning("Unable to find the eve.json log file")
123 return
124
125 for line in open(eve_log, "rb"):
126 event = json.loads(line)
127
128 if event["event_type"] == "alert":
129 alert = event["alert"]
130
131 if alert["signature_id"] in self.sid_blacklist:
132 log.debug(
133 "Ignoring alert with sid=%d, signature=%s",
134 alert["signature_id"], alert["signature"]
135 )
136 continue
137
138 if alert["signature"].startswith("SURICATA STREAM"):
139 log.debug(
140 "Ignoring alert starting with \"SURICATA STREAM\""
141 )
142 continue
143
144 self.results["alerts"].append({
145 "sid": alert["signature_id"],
146 "src_ip": event.get("src_ip"),
147 "src_port": event.get("src_port"),
148 "dst_ip": event["dest_ip"],
149 "dst_port": event.get("dest_port"),
150 "protocol": event.get("proto"),
151 "timestamp": event.get("timestamp"),
152 "category": alert.get("category") or "undefined",
153 "signature": alert["signature"],
154 })
155
156 elif event["event_type"] == "http":
157 http = event["http"]
158
159 referer = http.get("http_referer")
160 if referer == "<unknown>":
161 referer = None
162
163 user_agent = http.get("http_user_agent")
164 if user_agent == "<unknown>":
165 user_agent = None
166
167 self.results["http"].append({
168 "src_ip": event.get("src_ip"),
169 "src_port": event.get("src_port"),
170 "dst_ip": event.get("dest_ip"),
171 "dst_port": event.get("dest_port"),
172 "timestamp": event.get("timestamp"),
173 "method": http.get("http_method"),
174 "hostname": http.get("hostname"),
175 "url": http.get("url"),
176 "status": "%s" % http.get("status"),
177 "content_type": http.get("http_content_type"),
178 "user_agent": user_agent,
179 "referer": referer,
180 "length": http.get("length"),
181 })
182
183 elif event["event_type"] == "tls":
184 tls = event["tls"]
185
186 self.results["tls"].append({
187 "src_ip": event.get("src_ip"),
188 "src_port": event.get("src_port"),
189 "dst_ip": event.get("dest_ip"),
190 "dst_port": event.get("dest_port"),
191 "timestamp": event.get("timestamp"),
192 "fingerprint": tls.get("fingerprint"),
193 "issuer": tls.get("issuerdn"),
194 "version": tls.get("version"),
195 "subject": tls.get("subject"),
196 })
197
199 """Parse the files-json.log file and its associated files."""
200 files_log = os.path.join(self.suricata_path, self.files_log)
201 if not os.path.isfile(files_log):
202 log.warning("Unable to find the files-json.log log file")
203 return
204
205 files = {}
206
207
208 files_dir = os.path.join(self.suricata_path, self.files_dir)
209 if not os.path.exists(files_dir):
210 log.warning("Suricata files dir is not available. Maybe you forgot to enable Suricata file-store ?")
211 return
212
213 for filename in os.listdir(files_dir):
214 filepath = os.path.join(files_dir, filename)
215 files[md5_file(filepath)] = filepath
216
217 for line in open(files_log, "rb"):
218 event = json.loads(line)
219
220
221
222
223 if "id" in event:
224 filepath = os.path.join(files_dir, "file.%s" % event["id"])
225 elif "md5" in event:
226 filepath = files.get(event["md5"])
227 else:
228 filepath = None
229
230 if not filepath or not os.path.isfile(filepath):
231 log.warning(
232 "Suricata dropped file with id=%s and md5=%s not found, "
233 "skipping it..", event.get("id"), event.get("md5")
234 )
235 continue
236
237 referer = event.get("http_referer")
238 if referer == "<unknown>":
239 referer = None
240
241 self.results["files"].append({
242 "id": int(filepath.split(".", 1)[-1]),
243 "filesize": event["size"],
244 "filename": os.path.basename(event["filename"]),
245 "hostname": event.get("http_host"),
246 "uri": event.get("http_uri"),
247 "md5": md5_file(filepath),
248 "sha1": sha1_file(filepath),
249 "magic": event.get("magic"),
250 "referer": referer,
251 })
252
254 self.key = "suricata"
255
256 self.results = {
257 "alerts": [],
258 "tls": [],
259 "files": [],
260 "http": [],
261 }
262
263 self.suricata = self.options.get("suricata", "/usr/bin/suricata")
264 self.config_path = self.options.get("conf", "/etc/suricata/suricata.yaml")
265 self.eve_log = self.options.get("eve_log", "eve.json")
266 self.files_log = self.options.get("files_log", "files-json.log")
267 self.files_dir = self.options.get("files_dir", "files")
268
269
270 self.socket = self.options.get("socket")
271
272 if not os.path.isfile(self.pcap_path):
273 log.warning("Unable to run Suricata as no pcap is available")
274 return self.results
275
276
277
278
279 if os.path.isdir(self.suricata_path):
280 shutil.rmtree(self.suricata_path)
281
282 os.mkdir(self.suricata_path)
283
284 if self.socket:
285 self.process_pcap_socket()
286 else:
287 self.process_pcap_binary()
288
289 self.parse_eve_json()
290 self.parse_files()
291
292 return self.results
293