-
Notifications
You must be signed in to change notification settings - Fork 385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LdapQuery Analyzer #589
LdapQuery Analyzer #589
Changes from 10 commits
7a856d7
02480e8
7a07bde
cf28301
2bb6583
08a62ad
b894ebd
744e136
9dc8f9b
36224f9
f69877c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{ | ||
"name": "Ldap_Query", | ||
"version": "2.0", | ||
"author": "Florian Perret @cyber_pescadito", | ||
"url": "https://github.com/cyberpescadito/Cortex-Analyzers/tree/master/analyzers/LdapQuery", | ||
"license": "AGPL-V3", | ||
"description": "Query your LDAP server to harvest informations about an user of your organization", | ||
"dataTypeList": ["username", "mail"], | ||
"command": "LdapQuery/LdapQuery.py", | ||
"baseConfig": "LdapQuery", | ||
"configurationItems": [ | ||
{ | ||
"name": "LDAP_Server_Address", | ||
"description": "Should contain the protocol. Eg: ldaps://myldap.myorg.com", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "LDAP_Username", | ||
"description": "Usernae of the account that will be used to bind to LDAP server. The Account should have permissions to read ldap objects and attributes.", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "LDAP_Password", | ||
"description": "Password of the account used to bind to LDAP server.", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "base_DN", | ||
"description": "The base DN to use in your LDAP. Eg: dc=myorg,dc=com", | ||
"type": "string", | ||
"multi": false, | ||
"required": true | ||
}, | ||
{ | ||
"name": "Attributes", | ||
"description": "Specify here the attributes you want to harvest. Eg: mail", | ||
"type": "string", | ||
"multi": true, | ||
"required": true | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
#!/usr/bin/env python | ||
#Author: @cyber_pescadito | ||
import ldap | ||
import json | ||
from cortexutils.analyzer import Analyzer | ||
|
||
class LdapQuery(Analyzer): | ||
def __init__(self): | ||
Analyzer.__init__(self) | ||
self.ldap_address = self.get_param('config.LDAP_Server_Address', None, 'ldap_address is missing') | ||
self.username = self.get_param('config.LDAP_Username', None, 'username is missing') | ||
self.password = self.get_param('config.LDAP_Password', None, 'password is missing') | ||
self.base_dn = self.get_param('config.base_DN', None, 'base_dn is missing') | ||
self.attributes = self.get_param('config.Attributes', None, 'Missing attributes list to report') | ||
self.payload = self.get_param('data', None, 'username to search in LDAP is missing') | ||
|
||
def summary(self,raw,taxonomies_value): | ||
taxonomies = [] | ||
level = "info" | ||
namespace = "LDAP" | ||
predicate = "Query" | ||
value = taxonomies_value | ||
taxonomies.append(self.build_taxonomy(level, namespace, predicate, value)) | ||
return {"taxonomies": taxonomies} | ||
|
||
def run(self): | ||
#Checking connection to LDAP | ||
Analyzer.run(self) | ||
try: | ||
l=ldap.initialize(self.ldap_address) | ||
l.protocol_version = ldap.VERSION3 | ||
l.simple_bind_s(self.username, self.password) | ||
valid = True | ||
except ldap.LDAPError, e: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be: |
||
self.error(e) | ||
#Searching for the user | ||
try: | ||
searchScope = ldap.SCOPE_SUBTREE | ||
if self.data_type == "username": | ||
searchFilter = "(uid=" + self.payload + ")" | ||
elif self.data_type == "mail": | ||
searchFilter = "(mail=" + self.payload + ")" | ||
else: | ||
self.error('observable type not supported by this analyzer.') | ||
ldap_result_id = l.search(self.base_dn, searchScope, searchFilter, self.attributes) | ||
result_set = [] | ||
queryResult = {} | ||
while 1: | ||
result_type, result_data = l.result(ldap_result_id, 0) | ||
if (result_data == []): | ||
break | ||
else: | ||
if result_type == ldap.RES_SEARCH_ENTRY: | ||
result_set.append(result_data) | ||
for attribute in self.attributes: | ||
try: | ||
queryResult[attribute] = result_set[0][0][1][attribute][0] | ||
except: | ||
queryResult[attribute] = "unknown" | ||
# Cleaning characters that are wrongly parsed in thehive templates | ||
for key in queryResult: | ||
str=key | ||
strnew=str.replace('-','_') | ||
queryResult[strnew]=queryResult.pop(str) | ||
|
||
# Find a value to return in value attribute of taxonomies object | ||
if o in queryResult: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think o, cn and mail are strings and not variable in this case. |
||
taxonomies_value = str(queryResult[o]) | ||
else if cn in queryResult: | ||
taxonomies_value = str(queryResult[cn]) | ||
else if mail in queryResult: | ||
taxonomies_value = str(queryResult[mail]) | ||
else: | ||
taxonomies_value = "Success" | ||
|
||
json_data = queryResult | ||
except ldap.LDAPError, e: | ||
self.error(e) | ||
|
||
|
||
self.report(json_data) | ||
|
||
if __name__ == '__main__': | ||
LdapQuery().run() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<div class="report-LdapQuery" ng-if="success"> | ||
<style> | ||
.report-LdapQuery dl { | ||
margin-bottom: 2px; | ||
} | ||
</style> | ||
|
||
<div class="panel panel-info"> | ||
<div class="panel-heading"> | ||
<strong>LDAP Query Summary</strong> | ||
</div> | ||
<div class="panel-body"> | ||
<div ng-if="content[artifact.data].length === 0"> | ||
No records found | ||
</div> | ||
<div> | ||
<dl class="dl-horizontal"> | ||
<dt>cn: </dt> | ||
<dd class="wrap">{{content.cn || "-"}}</dd> | ||
</dl> | ||
<dl class="dl-horizontal"> | ||
<dt>mail: </dt> | ||
<dd class="wrap">{{content.mail || "-"}}</dd> | ||
</dl> | ||
<dl class="dl-horizontal"> | ||
<dt>uid: </dt> | ||
<dd class="wrap">{{content.uid|| "-"}}</dd> | ||
</dl> | ||
<dl class="dl-horizontal"> | ||
<dt>telephoneNumber: </dt> | ||
<dd class="wrap">{{content.telephoneNumber}}</dd> | ||
</dl> | ||
|
||
</div> | ||
</div> | ||
</div> | ||
|
||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
python-ldap | ||
cortexutils |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<span class="label" ng-repeat="t in content.taxonomies" | ||
ng-class="{'info': 'label-info', 'safe': 'label-success', | ||
'suspicious': 'label-warning', | ||
'malicious':'label-danger'}[t.level]"> | ||
{{t.namespace}}:{{t.predicate}}={{t.value}} | ||
</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any useful information you can put as the value of that taxonomie? probably the
cn
or theuid
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it would be strange to not include "cn" value in attributes list to fetch from the LDAP, but if "cn" isn't declared in "attributes" list by the admin in cortex analyzer configuration it could break the summary. So i chosen to not include any value of LDAP there.
What is your point of view? should I change de program design to enforce "cn" attribute fetching ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the idea was to make that taxonomie meaningful/useful.
If it will be always:
Ldap:Query=Success
then it's not worth it.I would make a taxonomy like:
LdapQuery:cn=XXX
orLdapQuery:ATTRIBUTE_NAME=ARRTIBUTE_VALUE
or nothingThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is value a mandatory attribute of taxonomies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note sure, but the taxonomy aims to provide important info, without having to read the full report. You can probably show a taxonomy for important attributes if they exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I'm not sure how summary can receive taxonomies_values as parameter.
Probably you can add taxonomies_values as class variable and use it here with self.taxonomies_values