mirror of
https://github.com/aljazceru/cowrie.git
synced 2026-02-23 15:24:30 +01:00
virustotal via Twisted http client
This commit is contained in:
@@ -27,6 +27,7 @@ Software required:
|
||||
* Twisted 8.0+
|
||||
* PyCrypto
|
||||
* pyasn1
|
||||
* python-requests (for VirusTotal integration)
|
||||
* Zope Interface 3.6.0+
|
||||
|
||||
## Files of interest:
|
||||
|
||||
@@ -31,12 +31,25 @@ Send SSH logins to Virustotal
|
||||
Work in Progress - not functional yet
|
||||
"""
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
import urllib
|
||||
import warnings
|
||||
import urllib2
|
||||
|
||||
from twisted.python import log
|
||||
from twisted.web.iweb import IBodyProducer
|
||||
from twisted.internet import abstract, defer, reactor, protocol
|
||||
from twisted.web import client, http_headers, iweb
|
||||
from twisted.internet.ssl import ClientContextFactory
|
||||
|
||||
from twisted.web.error import SchemeNotSupported
|
||||
from twisted.web._newclient import Request, Response, HTTP11ClientProtocol
|
||||
from twisted.web._newclient import ResponseDone, ResponseFailed
|
||||
from twisted.web._newclient import PotentialDataLoss
|
||||
|
||||
import cowrie.core.output
|
||||
|
||||
@@ -73,38 +86,90 @@ class Output(cowrie.core.output.Output):
|
||||
log.msg("Not seen before by VT")
|
||||
self.postcomment(entry["url"])
|
||||
|
||||
elif entry["eventid"] == 'COW0017':
|
||||
log.msg("Sending SFTP file to VT")
|
||||
if self.postfile(entry["outfile"], entry["filename"]) == 0:
|
||||
log.msg("Not seen before by VT")
|
||||
self.postcomment(entry["url"])
|
||||
|
||||
def postfile(self, scanFile):
|
||||
|
||||
def postfile(self, artifact, fileName):
|
||||
"""
|
||||
Send a file to VirusTotal
|
||||
"""
|
||||
vtHost = "www.virustotal.com"
|
||||
vtUrl = "https://www.virustotal.com/vtapi/v2/file/scan"
|
||||
fields = [("apikey", self.apiKey)]
|
||||
file_to_send = open("test.txt", "rb").read()
|
||||
files = [("file", "test.txt", file_to_send)]
|
||||
response_data = postfile.post_multipart(vtHost, vtUrl, fields, files)
|
||||
j = json.loads(response_data)
|
||||
files = {'file': (fileName, open(artifact, 'rb'))}
|
||||
|
||||
agent = agent.request('POST', vtUrl, None, None)
|
||||
|
||||
def cbResponse(ignored):
|
||||
print 'Response received'
|
||||
d.addCallback(cbResponse)
|
||||
|
||||
r = requests.post(vtUrl, files=files, data=fields)
|
||||
# if r.status_code != 200 # error
|
||||
j = r.json()
|
||||
log.msg( "Sent file to VT: %s" % (j,) )
|
||||
return j["response_code"]
|
||||
|
||||
#contentType = "multipart/form-data; boundary={}".format(boundary)
|
||||
#headers.setRawHeaders("Content-Type", [contentType])
|
||||
#headers.setRawHeaders("Content-Length", [len(body)])
|
||||
|
||||
|
||||
def posturl(self, scanUrl):
|
||||
"""
|
||||
Send a URL to VirusTotal
|
||||
Send a URL to VirusTotal with Twisted
|
||||
|
||||
response_code: if the item you searched for was not present in VirusTotal's dataset this result will be 0.
|
||||
response_code:
|
||||
If the item you searched for was not present in VirusTotal's dataset this result will be 0.
|
||||
If the requested item is still queued for analysis it will be -2.
|
||||
If the item was indeed present and it could be retrieved it will be 1.
|
||||
"""
|
||||
vtUrl = "https://www.virustotal.com/vtapi/v2/url/scan"
|
||||
headers = http_headers.Headers({'User-Agent': ['Cowrie SSH Honeypot']})
|
||||
fields = {"apikey": self.apiKey, "url": scanUrl}
|
||||
data = urllib.urlencode(fields)
|
||||
req = urllib2.Request(vtUrl, data)
|
||||
response = urllib2.urlopen(req)
|
||||
response_data = response.read()
|
||||
j = json.loads(response_data)
|
||||
log.msg( "Sent URL to VT: %s" % (j,) )
|
||||
return j["response_code"]
|
||||
body = StringProducer(data)
|
||||
contextFactory = WebClientContextFactory()
|
||||
|
||||
agent = client.Agent(reactor, contextFactory)
|
||||
d = agent.request('POST', vtUrl, headers, body)
|
||||
|
||||
|
||||
def cbResponse(response):
|
||||
# print 'Response code:', response.code
|
||||
# FIXME: Check for 200
|
||||
d = readBody(response)
|
||||
d.addCallback(cbBody)
|
||||
d.addErrback(cbPartial)
|
||||
return d
|
||||
|
||||
|
||||
def cbBody(body):
|
||||
return logResult(body)
|
||||
|
||||
|
||||
def cbPartial(failure):
|
||||
"""
|
||||
Google HTTP Server does not set Content-Length. Twisted marks it as partial
|
||||
"""
|
||||
return logResult(failure.value.response)
|
||||
|
||||
|
||||
def cbError(failure):
|
||||
failure.printTraceback()
|
||||
|
||||
|
||||
def logResult(result):
|
||||
j = json.loads(result)
|
||||
log.msg( "VT result: %s", repr(j) )
|
||||
return j["response_code"]
|
||||
|
||||
d.addCallback(cbResponse)
|
||||
d.addErrback(cbError)
|
||||
return d
|
||||
|
||||
|
||||
def postcomment(self, resource):
|
||||
@@ -123,6 +188,130 @@ class Output(cowrie.core.output.Output):
|
||||
log.msg( "Updated comment for %s to VT: %s" % (resource, j,) )
|
||||
|
||||
|
||||
|
||||
class WebClientContextFactory(ClientContextFactory):
|
||||
def getContext(self, hostname, port):
|
||||
return ClientContextFactory.getContext(self)
|
||||
|
||||
|
||||
|
||||
class _ReadBodyProtocol(protocol.Protocol):
|
||||
"""
|
||||
Protocol that collects data sent to it.
|
||||
|
||||
This is a helper for L{IResponse.deliverBody}, which collects the body and
|
||||
fires a deferred with it.
|
||||
|
||||
@ivar deferred: See L{__init__}.
|
||||
@ivar status: See L{__init__}.
|
||||
@ivar message: See L{__init__}.
|
||||
|
||||
@ivar dataBuffer: list of byte-strings received
|
||||
@type dataBuffer: L{list} of L{bytes}
|
||||
"""
|
||||
|
||||
def __init__(self, status, message, deferred):
|
||||
"""
|
||||
@param status: Status of L{IResponse}
|
||||
@ivar status: L{int}
|
||||
|
||||
@param message: Message of L{IResponse}
|
||||
@type message: L{bytes}
|
||||
|
||||
@param deferred: deferred to fire when response is complete
|
||||
@type deferred: L{Deferred} firing with L{bytes}
|
||||
"""
|
||||
self.deferred = deferred
|
||||
self.status = status
|
||||
self.message = message
|
||||
self.dataBuffer = []
|
||||
|
||||
|
||||
def dataReceived(self, data):
|
||||
"""
|
||||
Accumulate some more bytes from the response.
|
||||
"""
|
||||
self.dataBuffer.append(data)
|
||||
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"""
|
||||
Deliver the accumulated response bytes to the waiting L{Deferred}, if
|
||||
the response body has been completely received without error.
|
||||
"""
|
||||
if reason.check(ResponseDone):
|
||||
self.deferred.callback(b''.join(self.dataBuffer))
|
||||
elif reason.check(PotentialDataLoss):
|
||||
self.deferred.errback(
|
||||
client.PartialDownloadError(self.status, self.message,
|
||||
b''.join(self.dataBuffer)))
|
||||
else:
|
||||
self.deferred.errback(reason)
|
||||
|
||||
|
||||
|
||||
def readBody(response):
|
||||
"""
|
||||
Get the body of an L{IResponse} and return it as a byte string.
|
||||
|
||||
This is a helper function for clients that don't want to incrementally
|
||||
receive the body of an HTTP response.
|
||||
|
||||
@param response: The HTTP response for which the body will be read.
|
||||
@type response: L{IResponse} provider
|
||||
|
||||
@return: A L{Deferred} which will fire with the body of the response.
|
||||
Cancelling it will close the connection to the server immediately.
|
||||
"""
|
||||
def cancel(deferred):
|
||||
"""
|
||||
Cancel a L{readBody} call, close the connection to the HTTP server
|
||||
immediately, if it is still open.
|
||||
|
||||
@param deferred: The cancelled L{defer.Deferred}.
|
||||
"""
|
||||
abort = getAbort()
|
||||
if abort is not None:
|
||||
abort()
|
||||
|
||||
d = defer.Deferred(cancel)
|
||||
protocol = _ReadBodyProtocol(response.code, response.phrase, d)
|
||||
def getAbort():
|
||||
return getattr(protocol.transport, 'abortConnection', None)
|
||||
|
||||
response.deliverBody(protocol)
|
||||
|
||||
if protocol.transport is not None and getAbort() is None:
|
||||
warnings.warn(
|
||||
'Using readBody with a transport that does not have an '
|
||||
'abortConnection method',
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
|
||||
@implementer(IBodyProducer)
|
||||
class StringProducer(object):
|
||||
|
||||
def __init__(self, body):
|
||||
self.body = body
|
||||
self.length = len(body)
|
||||
|
||||
|
||||
def startProducing(self, consumer):
|
||||
consumer.write(self.body)
|
||||
return defer.succeed(None)
|
||||
|
||||
|
||||
def pauseProducing(self):
|
||||
pass
|
||||
|
||||
|
||||
def stopProducing(self):
|
||||
pass
|
||||
|
||||
"""
|
||||
def get_report(resource, filename, dl_url='unknown', honeypot=None, origin=None):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user