virustotal via Twisted http client

This commit is contained in:
Michel Oosterhof
2016-01-04 19:55:00 +00:00
parent 4e59ddfba0
commit efab340cc1
2 changed files with 204 additions and 14 deletions

View File

@@ -27,6 +27,7 @@ Software required:
* Twisted 8.0+
* PyCrypto
* pyasn1
* python-requests (for VirusTotal integration)
* Zope Interface 3.6.0+
## Files of interest:

View File

@@ -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):