@@ -168,7 +168,11 @@ def artifacts(self, raw):
168168 tags = ["body:attachment" , "autoImport:true" ] + h ["tag" ],
169169 )
170170 )
171- filepath = os .path .join (self .job_directory , "output" , h .get ("filename" ))
171+ filepath = os .path .join (
172+ self .job_directory ,
173+ "output" ,
174+ _attachment_output_name (h .get ("filename" , "" ), h .get ("hash" )),
175+ )
172176 file_key = ("file" , filepath )
173177 if file_key not in seen :
174178 seen .add (file_key )
@@ -193,6 +197,24 @@ def _add_ioc(ioc_list, data, tags):
193197 ioc_list .append ({"data" : data , "tag" : list (tags )})
194198
195199
200+ def _safe_attachment_filename (raw_name ):
201+ """Reduce an attachment name to a safe basename, or None if unusable."""
202+ if not raw_name or "\x00 " in raw_name :
203+ return None
204+ # normalise Windows separators first; "\" is not a separator on POSIX
205+ base = os .path .basename (raw_name .replace ("\\ " , "/" ))
206+ if not base or base in ("." , ".." ) or os .path .isabs (base ) or re .match (r"^[A-Za-z]:" , base ):
207+ return None
208+ return base
209+
210+
211+ def _attachment_output_name (raw_name , sha256 ):
212+ """On-disk basename for an attachment, falling back to its hash."""
213+ return _safe_attachment_filename (raw_name ) or "attachment_{}" .format (
214+ (sha256 or "unknown" )[:16 ]
215+ )
216+
217+
196218def parseEml (filepath , job_directory , wkhtmltoimage , sanitized_rendering ):
197219 ep = eml_parser .EmlParser (include_raw_body = True , include_attachment_data = True )
198220 with open (filepath , "rb" ) as f :
@@ -322,16 +344,17 @@ def parseEml(filepath, job_directory, wkhtmltoimage, sanitized_rendering):
322344 for a in decoded_email .get ("attachment" ):
323345 a ["mime" ] = magic .from_buffer (binascii .a2b_base64 (a .get ("raw" )))
324346 if isinstance (a .get ("raw" ), bytes ):
325- path , filename = os .path .split (a .get ("filename" , "" ))
326- if path != "" :
327- os .umask (0 )
328- os .makedirs (
329- f"{ job_directory } /output/{ path } " , exist_ok = True , mode = 0o777
330- )
331- filepath = os .path .join (job_directory , "output" , path , filename )
347+ filename = _attachment_output_name (
348+ a .get ("filename" , "" ), (a .get ("hash" ) or {}).get ("sha256" )
349+ )
350+ out_dir = os .path .join (job_directory , "output" )
351+ filepath = os .path .join (out_dir , filename )
352+ if not os .path .realpath (filepath ).startswith (
353+ os .path .realpath (out_dir ) + os .sep
354+ ):
355+ raise ValueError ("attachment path escapes sandbox" )
332356 with open (filepath , "wb" ) as f :
333357 f .write (base64 .b64decode (a ["raw" ]))
334- f .close ()
335358 a ["raw" ] = a .get ("raw" ).decode ("ascii" )
336359 result ["attachments" ].append (a )
337360 iocs ["hash" ].append (
0 commit comments