33from collections import Counter
44from dataclasses import dataclass
55from pathlib import Path
6+ from re import Match
67from tempfile import NamedTemporaryFile
8+ from typing import List
79
810from checksec .elf import ELFSecurity , PIEType , RelroType , set_libc
911from checksec .errors import ErrorNotAnElf , ErrorParsingFailed
12+ from guestfs import GuestFS
1013from see import Hook
1114
1215from hooks .filesystem import Inode
13- from oswatcher .model import OSType
16+ from oswatcher .model import OS , OSType
1417
1518
1619@dataclass
@@ -44,20 +47,20 @@ class SecurityHook(Hook):
4447 def __init__ (self , parameters ):
4548 super ().__init__ (parameters )
4649 self .os_info = None
47- self .stats = Counter ()
50+ self .stats : Counter = Counter ()
4851 self .stats ['total' ] = 0
4952 self .local_guest_libc = NamedTemporaryFile ()
50- self .neo4j_enabled = self .configuration .get ('neo4j' , False )
53+ self .neo4j_enabled : bool = self .configuration .get ('neo4j' , False )
5154 if self .neo4j_enabled :
52- self .os_node = self .configuration ['neo4j' ]['OS' ]
53- self .keep_binaries = self .configuration .get ('keep_failed_binaries' , False )
55+ self .os_node : OS = self .configuration ['neo4j' ]['OS' ]
56+ self .keep_binaries : bool = self .configuration .get ('keep_failed_binaries' , False )
5457 # directory to dump executable on which checksec failed
5558 if self .neo4j_enabled :
5659 os_id = self .os_node .id
5760 else :
5861 os_id = self .context .domain .name ()
59- default_checksec_failed_dir = Path .cwd () / f"{ os_id } _checksec_failed"
60- self .keep_binaries_dir = self .configuration .get ('keep_failed_dir' , default_checksec_failed_dir )
62+ default_checksec_failed_dir : Path = Path .cwd () / f"{ os_id } _checksec_failed"
63+ self .keep_binaries_dir : Path = Path ( self .configuration .get ('keep_failed_dir' , default_checksec_failed_dir ) )
6164
6265 self .context .subscribe ('detected_os_info' , self .get_os_info )
6366 self .context .subscribe ('filesystem_capture_begin' , self .download_libc )
@@ -68,7 +71,7 @@ def get_os_info(self, event):
6871
6972 def download_libc (self , event ):
7073 """Locate and download the libc"""
71- gfs = event .gfs
74+ gfs : GuestFS = event .gfs
7275
7376 if not self .os_info :
7477 raise RuntimeError ('Expected OS Info' )
@@ -77,35 +80,35 @@ def download_libc(self, event):
7780 return
7881
7982 # find ldd
80- cmd = ['which' , 'ldd' ]
83+ cmd : List = ['which' , 'ldd' ]
8184 try :
82- ldd_path = gfs .command (cmd ).strip ()
85+ ldd_path : str = gfs .command (cmd ).strip ()
8386 except RuntimeError :
8487 self .logger .warning ("Libc detection: command %s failed" , cmd )
8588 return
8689 # find ls
87- cmd = ['which' , 'ls' ]
90+ cmd : List = ['which' , 'ls' ]
8891 try :
89- ls_path = gfs .command (cmd ).strip ()
92+ ls_path : str = gfs .command (cmd ).strip ()
9093 except RuntimeError :
9194 self .logger .warning ("Libc detection: command %s failed" , cmd )
9295 return
93- cmd = [ldd_path , ls_path ]
96+ cmd : List = [ldd_path , ls_path ]
9497 try :
95- ldd_output = gfs .command (cmd ).strip ()
98+ ldd_output : str = gfs .command (cmd ).strip ()
9699 except RuntimeError :
97100 self .logger .warning ("Libc detection: command %s failed" , cmd )
98101 return
99102
100103 libc_inode = None
101104 for ldd_line in ldd_output .splitlines ():
102- m = re .match (r'\t*(?P<libname>.*)\s+(=>)?\s+(?P<libpath>\S+)?\s+\((?P<addr>.*)\)$' , ldd_line )
105+ m : Match = re .match (r'\t*(?P<libname>.*)\s+(=>)?\s+(?P<libpath>\S+)?\s+\((?P<addr>.*)\)$' , ldd_line )
103106 if not m :
104107 self .logger .warn ("Libc detection: line \" %s\" doesn't match LDD regex" , ldd_line )
105108 continue
106109 if m .group ('libname' ).startswith ('libc.so' ):
107110 # found guest libc
108- libc_inode = Inode (self .logger , gfs , Path (m .group ('libpath' )))
111+ libc_inode : Inode = Inode (self .logger , gfs , Path (m .group ('libpath' )))
109112 break
110113 if libc_inode is None :
111114 self .logger .warning ("Libc detection: Couldn't locate libc !" )
@@ -118,21 +121,21 @@ def download_libc(self, event):
118121
119122 def check_file (self , event ):
120123 # event args
121- inode = event .inode
124+ inode : Inode = event .inode
122125
123126 if not self .os_info ['os_type' ] == OSType .Linux :
124127 # checksec only supports ELF files
125128 return
126- mime = inode .file_magic_type
127- filepath = inode .path
129+ mime : str = inode .file_magic_type
130+ filepath : Path = inode .path
128131 if re .match (r'application/x(-pie)?-(executable|sharedlib)' , mime ):
129132 self .logger .info ('Checking security of %s: %s' , filepath , mime )
130133 self .stats ['total' ] += 1
131134 # this is a heavy call (download the file on the host filesystem through libguestfs appliance)
132135 # call it here once we filtered on the mime type provided by the file utility
133- local_filepath = inode .local_file
136+ local_filepath : Path = inode .local_file
134137 try :
135- elf = ELFSecurity (local_filepath )
138+ elf : ELFSecurity = ELFSecurity (local_filepath )
136139 except ErrorNotAnElf :
137140 self .stats ['failed' ] += 1
138141 self .logger .warning ("Not a valid ELF file: %s (%s)" , filepath , inode .gfs_file )
@@ -148,18 +151,18 @@ def check_file(self, event):
148151 shutil .copy (inode .local_file , dst )
149152 return
150153 else :
151- relro = elf .relro
152- canary = elf .has_canary
153- nx = elf .has_nx
154- pie = elf .pie
155- rpath = elf .has_rpath
156- runpath = elf .has_runpath
157- symbols = not elf .is_stripped
158- fortified = elf .is_fortified
159- fortify_source = len (elf .fortified )
160- fortifyable = len (elf .fortifiable )
161-
162- checksec_file = ChecksecFile (relro , canary , nx , pie , rpath , runpath ,
154+ relro : RelroType = elf .relro
155+ canary : bool = elf .has_canary
156+ nx : bool = elf .has_nx
157+ pie : PIEType = elf .pie
158+ rpath : bool = elf .has_rpath
159+ runpath : bool = elf .has_runpath
160+ symbols : bool = not elf .is_stripped
161+ fortified : bool = elf .is_fortified
162+ fortify_source : int = len (elf .fortified )
163+ fortifyable : int = len (elf .fortifiable )
164+
165+ checksec_file : ChecksecFile = ChecksecFile (relro , canary , nx , pie , rpath , runpath ,
163166 symbols , fortify_source , fortified , fortifyable )
164167 self .logger .debug ("Properties: %s" , checksec_file )
165168 self .context .trigger ('security_checksec_bin' , inode = inode , checksec_file = checksec_file )
0 commit comments