Skip to content
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

Merged
merged 11 commits into from
Jan 21, 2021
48 changes: 48 additions & 0 deletions analyzers/LdapQuery/LdapQuery.json
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
}
]
}
74 changes: 74 additions & 0 deletions analyzers/LdapQuery/LdapQuery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/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 = []
level = "info"
namespace = "LDAP"
predicate = "Query"
value = "Success"
taxonomies.append(self.build_taxonomy(level, namespace, predicate, value))
Copy link
Contributor

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 the uid?

Copy link
Contributor Author

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 ?

Copy link
Contributor

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 or LdapQuery:ATTRIBUTE_NAME=ARRTIBUTE_VALUE or nothing

Copy link
Contributor Author

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?

Copy link
Contributor

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?

Copy link
Contributor

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

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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be:
except ldap.LDAPError as e:

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)

json_data = queryResult
except ldap.LDAPError, e:
self.error(e)


self.report(json_data)

if __name__ == '__main__':
LdapQuery().run()
38 changes: 38 additions & 0 deletions analyzers/LdapQuery/long.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<div class="report-FILEInfo" ng-if="success">
<style>
.report-FILEInfo 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>
2 changes: 2 additions & 0 deletions analyzers/LdapQuery/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
python-ldap
cortexutils
6 changes: 6 additions & 0 deletions analyzers/LdapQuery/short.html
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>