2012-03-15 15:06:45 +01:00
< ? php
App :: uses ( 'AppModel' , 'Model' );
2012-03-23 20:04:22 +01:00
App :: uses ( 'File' , 'Utility' );
2012-03-15 15:06:45 +01:00
/**
2012-03-26 19:56:44 +02:00
* Attribute Model
2012-03-15 15:06:45 +01:00
*
* @ property Event $Event
*/
2012-03-26 19:56:44 +02:00
class Attribute extends AppModel {
2012-03-15 15:06:45 +01:00
/**
* Display field
*
* @ var string
*/
public $displayField = 'value' ;
2012-03-25 15:56:29 +02:00
2012-03-26 19:56:44 +02:00
var $order = array ( " Attribute.event_id " => " DESC " , " Attribute.type " => " ASC " );
2012-03-15 15:06:45 +01:00
/**
* Validation rules
*
* @ var array
*/
public $validate = array (
'event_id' => array (
'numeric' => array (
'rule' => array ( 'numeric' ),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'type' => array (
'rule' => array ( 'inList' , array ( 'md5' , 'sha1' ,
2012-03-23 20:04:22 +01:00
'filename' ,
'filename|md5' ,
2012-03-27 11:01:33 +02:00
'filename|sha1' ,
2012-03-23 20:04:22 +01:00
'ip-src' ,
'ip-dst' ,
2012-04-01 15:49:01 +02:00
'hostname' ,
2012-03-23 20:04:22 +01:00
'domain' ,
'email-src' ,
'email-dst' ,
'email-subject' ,
'email-attachment' ,
'url' ,
'user-agent' ,
'regkey' ,
'regkey|value' ,
'AS' ,
'snort' ,
'pattern-in-file' ,
2012-04-02 19:24:50 +02:00
'pattern-in-traffic' ,
2012-03-23 20:04:22 +01:00
'pattern-in-memory' ,
2012-03-24 10:48:06 +01:00
'vulnerability' ,
2012-03-23 20:04:22 +01:00
'attachment' ,
'malware-sample' ,
2012-04-02 19:24:50 +02:00
'link' ,
2012-04-13 10:53:53 +02:00
'description' ,
2012-03-23 20:04:22 +01:00
'other' )),
2012-03-15 15:06:45 +01:00
'message' => 'Options : md5, sha1, filename, ip, domain, email, url, regkey, AS, other, ...' ,
//'allowEmpty' => false,
'required' => true ,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
2012-03-25 15:56:29 +02:00
2012-03-15 15:06:45 +01:00
),
2012-03-23 20:04:22 +01:00
'category' => array (
2012-03-27 14:02:49 +02:00
'rule' => array ( 'inList' , array (
2012-04-13 10:53:53 +02:00
'Internal reference' ,
2012-03-27 14:02:49 +02:00
'Payload delivery' ,
2012-03-23 20:04:22 +01:00
'Antivirus detection' ,
'Payload installation' ,
'Artifacts dropped' ,
'Persistence mechanism' ,
'Registry keys modified' ,
'Network activity' ,
'Payload type' ,
2012-04-13 10:53:53 +02:00
'Attribution' ,
'External analysis' ,
2012-03-23 20:04:22 +01:00
'Other' ,
2012-03-26 19:56:44 +02:00
'' // FIXME remove this once all attributes have a category. Otherwise sigs without category are not shown in the list
2012-03-23 20:04:22 +01:00
)),
'message' => 'Options : Payload delivery, Antivirus detection, Payload installation, Files dropped ...'
),
2012-03-15 15:06:45 +01:00
'value' => array (
'notempty' => array (
'rule' => array ( 'notempty' ),
'message' => 'Please fill in this field' ,
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
'userdefined' => array (
2012-03-26 19:56:44 +02:00
'rule' => array ( 'validateAttributeValue' ),
2012-03-15 15:06:45 +01:00
'message' => 'Value not in the right type/format. Please double check the value or select "other" for a type.' ,
//'allowEmpty' => false,
//'required' => true,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
2012-04-10 15:47:42 +02:00
'unique' => array (
'rule' => array ( 'valueIsUnique' ),
'message' => 'A similar attribute already exists for this event.' ,
//'allowEmpty' => false,
//'required' => true,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
2012-03-15 15:06:45 +01:00
),
'to_ids' => array (
'boolean' => array (
'rule' => array ( 'boolean' ),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
'required' => false ,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'uuid' => array (
'uuid' => array (
'rule' => array ( 'uuid' ),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
2012-03-20 13:40:58 +01:00
'revision' => array (
'numeric' => array (
'rule' => array ( 'numeric' ),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
'private' => array (
'boolean' => array (
'rule' => array ( 'boolean' ),
//'message' => 'Your custom message here',
2012-04-01 18:41:47 +02:00
'allowEmpty' => true ,
2012-03-20 13:40:58 +01:00
'required' => false ,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
2012-03-15 15:06:45 +01:00
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
/**
* belongsTo associations
*
* @ var array
*/
public $belongsTo = array (
'Event' => array (
'className' => 'Event' ,
'foreignKey' => 'event_id' ,
'conditions' => '' ,
'fields' => '' ,
'order' => ''
)
);
2012-03-25 15:56:29 +02:00
2012-03-20 13:40:58 +01:00
function beforeSave () {
// increment the revision number
2012-03-26 19:56:44 +02:00
if ( empty ( $this -> data [ 'Attribute' ][ 'revision' ])) $this -> data [ 'Attribute' ][ 'revision' ] = 0 ;
$this -> data [ 'Attribute' ][ 'revision' ] = 1 + $this -> data [ 'Attribute' ][ 'revision' ] ;
2012-03-25 15:56:29 +02:00
2012-03-20 13:40:58 +01:00
// always return true after a beforeSave()
return true ;
}
2012-03-25 15:56:29 +02:00
2012-03-23 20:04:22 +01:00
function beforeDelete () {
// delete attachments from the disk
2012-03-26 19:56:44 +02:00
$this -> read (); // first read the attribute from the db
if ( 'attachment' == $this -> data [ 'Attribute' ][ 'type' ] ||
'malware-sample' == $this -> data [ 'Attribute' ][ 'type' ] ) {
2012-03-23 20:04:22 +01:00
// FIXME secure this filesystem access/delete by not allowing to change directories or go outside of the directory container.
// only delete the file if it exists
2012-03-26 19:56:44 +02:00
$filepath = APP . " files/ " . $this -> data [ 'Attribute' ][ 'event_id' ] . " / " . $this -> data [ 'Attribute' ][ 'id' ];
2012-03-23 20:04:22 +01:00
$file = new File ( $filepath );
if ( $file -> exists ()) {
if ( ! $file -> delete ()) {
$this -> Session -> setFlash ( __ ( 'Delete failed. Please report to administrator' , true ), 'default' , array (), 'error' ); // TODO change this message. Throw an internal error
}
}
}
}
2012-03-25 15:56:29 +02:00
function beforeValidate () {
// remove leading and trailing blanks
2012-03-26 19:56:44 +02:00
$this -> data [ 'Attribute' ][ 'value' ] = trim ( $this -> data [ 'Attribute' ][ 'value' ]);
2012-03-25 15:56:29 +02:00
2012-03-26 19:56:44 +02:00
switch ( $this -> data [ 'Attribute' ][ 'type' ]) {
2012-03-25 15:56:29 +02:00
// lowercase these things
case 'md5' :
case 'sha1' :
2012-04-25 10:30:23 +02:00
case 'domain' :
case 'hostname' :
2012-03-26 19:56:44 +02:00
$this -> data [ 'Attribute' ][ 'value' ] = strtolower ( $this -> data [ 'Attribute' ][ 'value' ]);
2012-03-25 15:56:29 +02:00
break ;
}
2012-04-10 15:47:42 +02:00
// generate UUID if it doesn't exist
if ( empty ( $this -> data [ 'Attribute' ][ 'uuid' ]))
$this -> data [ 'Attribute' ][ 'uuid' ] = String :: uuid ();
2012-03-25 15:56:29 +02:00
// always return true, otherwise the object cannot be saved
return true ;
}
2012-04-10 15:47:42 +02:00
function valueIsUnique ( $fields ) {
$value = $fields [ 'value' ];
$event_id = $this -> data [ 'Attribute' ][ 'event_id' ];
$type = $this -> data [ 'Attribute' ][ 'type' ];
$to_ids = $this -> data [ 'Attribute' ][ 'to_ids' ];
$category = $this -> data [ 'Attribute' ][ 'category' ];
// check if the attribute already exists in the same event
$conditions = array ( 'Attribute.event_id' => $event_id ,
'Attribute.type' => $type ,
'Attribute.category' => $category ,
'Attribute.value' => $value
);
if ( isset ( $this -> data [ 'Attribute' ][ 'id' ]))
$conditions [ 'Attribute.id !=' ] = $this -> data [ 'Attribute' ][ 'id' ];
$params = array ( 'recursive' => 0 ,
'conditions' => $conditions ,
);
if ( 0 != $this -> find ( 'count' , $params ) )
return false ;
// Say everything is fine
return true ;
}
2012-03-26 19:56:44 +02:00
function validateAttributeValue ( $fields ) {
2012-03-15 15:06:45 +01:00
$value = $fields [ 'value' ];
2012-03-25 15:56:29 +02:00
2012-03-15 15:06:45 +01:00
// check data validation
2012-03-26 19:56:44 +02:00
switch ( $this -> data [ 'Attribute' ][ 'type' ]) {
2012-03-15 15:06:45 +01:00
case 'md5' :
2012-03-23 20:04:22 +01:00
if ( preg_match ( " #^[0-9a-f] { 32} $ # " , $value ))
return true ;
2012-03-25 15:56:29 +02:00
return 'Checksum has invalid length or format. Please double check the value or select "other" for a type.' ;
2012-03-15 15:06:45 +01:00
break ;
case 'sha1' :
2012-03-23 20:04:22 +01:00
if ( preg_match ( " #^[0-9a-f] { 40} $ # " , $value ))
return true ;
2012-03-25 15:56:29 +02:00
return 'Checksum has invalid length or format. Please double check the value or select "other" for a type.' ;
2012-03-15 15:06:45 +01:00
break ;
case 'filename' :
// no newline
2012-04-02 19:24:50 +02:00
if ( preg_match ( " # \n # " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
break ;
case 'filename|md5' :
// no newline
2012-04-25 10:30:23 +02:00
if ( preg_match ( " #^.+ \ |[0-9a-f] { 32} $ # " , $value ))
2012-03-25 15:56:29 +02:00
return true ;
return 'Checksum has invalid length or format. Please double check the value or select "other" for a type.' ;
2012-03-15 15:06:45 +01:00
break ;
2012-03-27 11:03:57 +02:00
case 'filename|sha1' :
// no newline
2012-04-25 10:30:23 +02:00
if ( preg_match ( " #^.+ \ |[0-9a-f] { 40} $ # " , $value ))
2012-03-27 11:03:57 +02:00
return true ;
return 'Checksum has invalid length or format. Please double check the value or select "other" for a type.' ;
break ;
2012-03-15 15:06:45 +01:00
case 'ip-src' :
$parts = explode ( " / " , $value );
// [0] = the ip
// [1] = the network address
if ( count ( $parts ) <= 2 ) {
// ipv4 and ipv6 matching
if ( filter_var ( $parts [ 0 ], FILTER_VALIDATE_IP )) {
// ip is validated, now check if we have a valid network mask
if ( empty ( $parts [ 1 ]))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
else if ( is_numeric ( $parts [ 1 ]) && $parts [ 1 ] < 129 )
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
}
}
return 'IP address has invalid format. Please double check the value or select "other" for a type.' ;
break ;
case 'ip-dst' :
$parts = explode ( " / " , $value );
// [0] = the ip
// [1] = the network address
if ( count ( $parts ) <= 2 ) {
// ipv4 and ipv6 matching
if ( filter_var ( $parts [ 0 ], FILTER_VALIDATE_IP )) {
// ip is validated, now check if we have a valid network mask
if ( empty ( $parts [ 1 ]))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
else if ( is_numeric ( $parts [ 1 ]) && $parts [ 1 ] < 129 )
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
}
}
return 'IP address has invalid format. Please double check the value or select "other" for a type.' ;
break ;
2012-04-01 15:49:01 +02:00
case 'hostname' :
2012-03-15 15:06:45 +01:00
case 'domain' :
if ( preg_match ( " #^[A-Z0-9.-]+ \ .[A-Z] { 2,4} $ #i " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
return 'Domain name has invalid format. Please double check the value or select "other" for a type.' ;
break ;
case 'email-src' :
// we don't use the native function to prevent issues with partial email addresses
if ( preg_match ( " #^[A-Z0-9._%+-]*@[A-Z0-9.-]+ \ .[A-Z] { 2,4} $ #i " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
return 'Email address has invalid format. Please double check the value or select "other" for a type.' ;
break ;
case 'email-dst' :
// we don't use the native function to prevent issues with partial email addresses
if ( preg_match ( " #^[A-Z0-9._%+-]*@[A-Z0-9.-]+ \ .[A-Z] { 2,4} $ #i " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
return 'Email address has invalid format. Please double check the value or select "other" for a type.' ;
break ;
case 'email-subject' :
// no newline
if ( ! preg_match ( " # \n # " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
break ;
case 'email-attachment' :
// no newline
if ( ! preg_match ( " # \n # " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
break ;
case 'url' :
// no newline
if ( ! preg_match ( " # \n # " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
break ;
case 'user-agent' :
// no newline
if ( ! preg_match ( " # \n # " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
break ;
case 'regkey' :
// no newline
if ( ! preg_match ( " # \n # " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
break ;
case 'regkey|value' :
// no newline
2012-04-25 10:30:23 +02:00
if ( ! preg_match ( " #.+ \ |.+# " , $value ))
2012-03-23 20:04:22 +01:00
return true ;
2012-03-15 15:06:45 +01:00
break ;
case 'snort' :
2012-03-26 19:56:44 +02:00
// no validation yet. TODO implement data validation on snort attribute type
2012-03-15 15:06:45 +01:00
case 'other' :
return true ;
break ;
}
2012-03-25 15:56:29 +02:00
2012-03-15 15:06:45 +01:00
// default action is to return false
return true ;
2012-03-25 15:56:29 +02:00
2012-03-15 15:06:45 +01:00
}
2012-03-25 15:56:29 +02:00
2012-03-26 19:56:44 +02:00
public function isOwnedByOrg ( $attributeid , $org ) {
$this -> id = $attributeid ;
2012-03-20 14:57:52 +01:00
$this -> read ();
return $this -> data [ 'Event' ][ 'org' ] === $org ;
}
2012-03-25 15:56:29 +02:00
2012-03-27 14:02:49 +02:00
function getRelatedAttributes ( $attribute , $fields = array ()) {
2012-03-26 19:56:44 +02:00
// LATER getRelatedAttributes($attribute) this might become a performance bottleneck
2012-04-25 10:30:23 +02:00
// exclude these specific categories to be linked
switch ( $attribute [ 'category' ]) {
case 'Antivirus detection' :
return null ;
}
// exclude these specific types to be linked
switch ( $attribute [ 'type' ]) {
case 'description' :
case 'other' :
return null ;
}
// do the search
$conditions = array (
'Attribute.value =' => $attribute [ 'value' ],
'Attribute.id !=' => $attribute [ 'id' ],
'Attribute.type =' => $attribute [ 'type' ], );
2012-03-27 14:02:49 +02:00
if ( empty ( $fields )) {
$fields = array ( 'Attribute.*' );
}
2012-03-25 15:56:29 +02:00
2012-03-15 15:06:45 +01:00
$similar_events = $this -> find ( 'all' , array ( 'conditions' => $conditions ,
2012-03-23 20:04:22 +01:00
'fields' => $fields ,
2012-03-27 14:02:49 +02:00
'recursive' => 0 ,
2012-03-26 19:56:44 +02:00
'order' => 'Attribute.event_id DESC' , )
2012-03-15 15:06:45 +01:00
);
return $similar_events ;
}
2012-03-25 15:56:29 +02:00
2012-03-15 15:06:45 +01:00
}