class Host(StorageModelBase): """basic host (ip-centric) model""" id = db.Column(db.Integer, primary_key=True) address = db.Column(postgresql.INET, nullable=False) hostname = db.Column(db.String(256)) os = db.Column(db.Text) tags = db.Column(postgresql.ARRAY(db.String, dimensions=1), nullable=False, default=[]) comment = db.Column(db.Text) created = db.Column(db.DateTime, default=datetime.utcnow) modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) rescan_time = db.Column(db.DateTime, default=datetime.utcnow) services = relationship('Service', back_populates='host', cascade='delete,delete-orphan', passive_deletes=True) vulns = relationship('Vuln', back_populates='host', cascade='delete,delete-orphan', passive_deletes=True) notes = relationship('Note', back_populates='host', cascade='delete,delete-orphan', passive_deletes=True) def __repr__(self): return '<Host %s: %s (%s)>' % (self.id, self.address, self.hostname if self.hostname else '')
class Target(db.Model): """single target in queue""" id = db.Column(db.Integer, primary_key=True) target = db.Column(db.Text, nullable=False) queue_id = db.Column(db.Integer, db.ForeignKey('queue.id', ondelete='CASCADE'), nullable=False) queue = relationship('Queue', back_populates='targets') def __repr__(self): return '<Target %s: %s>' % (self.id, self.target)
class User(db.Model, flask_login.UserMixin): """user model""" id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(250), unique=True, nullable=False) password = db.Column(db.String(250)) email = db.Column(db.String(250)) active = db.Column(db.Boolean, nullable=False, default=False) roles = db.Column(postgresql.ARRAY(db.String, dimensions=1), nullable=False, default=[]) apikey = db.Column(db.String(250)) totp = db.Column(db.String(32)) webauthn_credentials = relationship('WebauthnCredential', back_populates='user', cascade='delete,delete-orphan', passive_deletes=True) @property def is_active(self): """user active getter""" return self.active def has_role(self, role): """shortcut function to check user has role""" if self.roles and (role in self.roles): return True return False def __repr__(self): return '<User %s: %s>' % (self.id, self.username)
class User(db.Model, flask_login.UserMixin): """user model""" id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(250), unique=True, nullable=False) _password = db.Column(db.String(250), name='password') email = db.Column(db.String(250)) active = db.Column(db.Boolean, nullable=False, default=False) roles = db.Column(postgresql.ARRAY(db.String, dimensions=1), nullable=False, default=[]) _apikey = db.Column(db.String(250), name='apikey') totp = db.Column(db.String(32)) webauthn_credentials = relationship('WebauthnCredential', back_populates='user', cascade='delete,delete-orphan', passive_deletes=True) @property def is_active(self): """user active getter""" return self.active def has_role(self, role): """shortcut function to check user has role""" if self.roles and (role in self.roles): return True return False @hybrid_property def password(self): """password getter""" return self._password @password.setter def password(self, value): """password setter; condition is handling value edit from empty form.populate_obj submission""" if value: self._password = PWS().hash(value) @hybrid_property def apikey(self): """apikey getter""" return self._apikey @apikey.setter def apikey(self, value): """apikey setter""" self._apikey = PWS.hash_simple(value) if value else None def __repr__(self): return '<User %s: %s>' % (self.id, self.username)
class Excl(db.Model): """exclusion model, used for target blacklisting by network ranges or regex; typicaly values for the model would be enforced by apropriate forms, but since exclusions allows to import from user data, model should ensure corect values itself """ id = db.Column(db.Integer, primary_key=True) family = db.Column(db.Enum(ExclFamily), nullable=False) value = db.Column(db.Text, nullable=False) comment = db.Column(db.Text) def __repr__(self): return '<Excl %s>' % self.id @validates('family') def validate_family(self, key, new_family): # pylint: disable=unused-argument """validate family and subsequently value for the family""" if new_family not in ExclFamily.__members__.values(): raise ValueError('Invalid family') if self.value: if new_family == ExclFamily.network: ip_network(self.value) if new_family == ExclFamily.regex: try: re.compile(self.value) except re.error: raise ValueError('Invalid regex') return new_family @validates('value') def validate_value(self, key, new_value): # pylint: disable=unused-argument """validate value acording to the current family""" if self.family == ExclFamily.network: ip_network(new_value) if self.family == ExclFamily.regex: try: re.compile(new_value) except re.error: raise ValueError('Invalid regex') return new_value
class Queue(db.Model): """task configuration for queue of targets""" id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(250), nullable=False, unique=True) config = db.Column(db.Text) group_size = db.Column(db.Integer, nullable=False) priority = db.Column(db.Integer, nullable=False) active = db.Column(db.Boolean, nullable=False, default=False) reqs = db.Column(postgresql.ARRAY(db.String, dimensions=1), nullable=False, default=[]) targets = relationship('Target', back_populates='queue', cascade='delete,delete-orphan', passive_deletes=True) jobs = relationship('Job', back_populates='queue', cascade='delete,delete-orphan', passive_deletes=True) def __repr__(self): return '<Queue %s: %s>' % (self.id, self.name) @property def data_abspath(self): """return absolute path of the queue data directory""" return os.path.join(current_app.config['SNER_VAR'], 'scheduler', 'queue-%s' % self.id) if self.id else None
class Note(StorageModelBase): """host assigned note, generic data container""" id = db.Column(db.Integer, primary_key=True) host_id = db.Column(db.Integer, db.ForeignKey('host.id', ondelete='CASCADE'), nullable=False) service_id = db.Column(db.Integer, db.ForeignKey('service.id', ondelete='CASCADE')) xtype = db.Column(db.String(250)) data = db.Column(db.Text) tags = db.Column(postgresql.ARRAY(db.String, dimensions=1), nullable=False, default=[]) comment = db.Column(db.Text) created = db.Column(db.DateTime, default=datetime.utcnow) modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) import_time = db.Column(db.DateTime) host = relationship('Host', back_populates='notes') service = relationship('Service', back_populates='notes') def __repr__(self): return '<Note %s: %s>' % (self.id, self.xtype)
class WebauthnCredential(db.Model): """Webauthn credential model""" id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) user_handle = db.Column(db.String(64), nullable=False) credential_data = db.Column(db.LargeBinary, nullable=False) name = db.Column(db.String(250)) registered = db.Column(db.DateTime, default=datetime.utcnow) user = relationship('User', back_populates='webauthn_credentials') def __repr__(self): return '<WebauthnCredential %s: %s>' % (self.id, self.user_id)
class Job(db.Model): """assigned job""" id = db.Column(db.String(36), primary_key=True) queue_id = db.Column(db.Integer, db.ForeignKey('queue.id', ondelete='CASCADE')) assignment = db.Column(db.Text, nullable=False) retval = db.Column(db.Integer) time_start = db.Column(db.DateTime, default=datetime.utcnow) time_end = db.Column(db.DateTime) queue = relationship('Queue', back_populates='jobs') def __repr__(self): return '<Job %s>' % self.id @property def output_abspath(self): """return absolute path to the output data file acording to current app config""" return os.path.join(self.queue.data_abspath, self.id)
class Vuln(StorageModelBase): """vulnerability model; heavily inspired by metasploit; hdm rulez""" id = db.Column(db.Integer, primary_key=True) host_id = db.Column(db.Integer, db.ForeignKey('host.id', ondelete='CASCADE'), nullable=False) service_id = db.Column(db.Integer, db.ForeignKey('service.id', ondelete='CASCADE')) name = db.Column(db.String(1000), nullable=False) xtype = db.Column(db.String(250)) severity = db.Column(db.Enum(SeverityEnum), nullable=False) descr = db.Column(db.Text) data = db.Column(db.Text) refs = db.Column(postgresql.ARRAY(db.String, dimensions=1), nullable=False, default=[]) tags = db.Column(postgresql.ARRAY(db.String, dimensions=1), nullable=False, default=[]) comment = db.Column(db.Text) created = db.Column(db.DateTime, default=datetime.utcnow) modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) rescan_time = db.Column(db.DateTime, default=datetime.utcnow) import_time = db.Column(db.DateTime) host = relationship('Host', back_populates='vulns') service = relationship('Service', back_populates='vulns') def __repr__(self): return '<Vuln %s: %s>' % (self.id, self.xtype)
class Service(StorageModelBase): """discovered host service""" id = db.Column(db.Integer, primary_key=True) host_id = db.Column(db.Integer, db.ForeignKey('host.id', ondelete='CASCADE'), nullable=False) proto = db.Column(db.String(250), nullable=False) port = db.Column(db.Integer, nullable=False) state = db.Column(db.String(250)) name = db.Column(db.String(250)) info = db.Column(db.Text) tags = db.Column(postgresql.ARRAY(db.String, dimensions=1), nullable=False, default=[]) comment = db.Column(db.Text) created = db.Column(db.DateTime, default=datetime.utcnow) modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) rescan_time = db.Column(db.DateTime, default=datetime.utcnow) import_time = db.Column(db.DateTime) host = relationship('Host', back_populates='services') vulns = relationship('Vuln', back_populates='service', cascade='delete,delete-orphan', passive_deletes=True) notes = relationship('Note', back_populates='service', cascade='delete,delete-orphan', passive_deletes=True) def __repr__(self): return '<Service %s: %s.%d>' % (self.id, self.proto, self.port)