201 lines
9.2 KiB
Plaintext
201 lines
9.2 KiB
Plaintext
|
/**
|
||
|
* This file is part of Rubanetra.
|
||
|
* Copyright (C) 2013,2014 Stefan Swerk (stefan_rubanetra@swerk.priv.at)
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
import at.jku.fim.rubanetra.protocol.activity.*;
|
||
|
import at.jku.fim.rubanetra.protocol.activity.tls.*;
|
||
|
import at.jku.fim.rubanetra.protocol.activity.http.*;
|
||
|
import at.jku.fim.rubanetra.protocol.activity.ip.*;
|
||
|
import at.jku.fim.rubanetra.protocol.activity.tcp.*;
|
||
|
import at.jku.fim.rubanetra.protocol.activity.icmp.*;
|
||
|
import at.jku.fim.rubanetra.protocol.activity.dns.*;
|
||
|
import org.xbill.DNS.*;
|
||
|
import org.apache.http.HttpHeaders;
|
||
|
import org.jnetpcap.protocol.tcpip.Tcp;
|
||
|
import org.jnetpcap.packet.PcapPacket;
|
||
|
import org.apache.commons.codec.binary.Hex;
|
||
|
import java.net.InetSocketAddress;
|
||
|
import java.util.HashSet;
|
||
|
import java.util.Date
|
||
|
import java.util.SortedSet;
|
||
|
import java.util.TreeSet;
|
||
|
import org.xbill.DNS.Record;
|
||
|
import java.net.InetSocketAddress;
|
||
|
import java.util.List;
|
||
|
import java.util.Set;
|
||
|
|
||
|
// using the MVEL expression language, see http://mvel.codehaus.org/
|
||
|
dialect "mvel"
|
||
|
|
||
|
/**
|
||
|
* A logger that may be used for logging custom messages
|
||
|
*/
|
||
|
global org.slf4j.Logger log;
|
||
|
|
||
|
/**
|
||
|
* This declaration serves as an example to demonstrate the basic attribute overriding process.
|
||
|
* Usually this class extends the Activity-interface and is declared to be an event.
|
||
|
* However, currently no time-based reasoning will be performed for these objects, therefore it can be
|
||
|
* converted to a Fact.
|
||
|
* This declaration may be removed to use the default attributes again (see 00.Basic.Metadata.drl).
|
||
|
*/
|
||
|
declare HttpImageActivity
|
||
|
@role( fact )
|
||
|
@author( Stefan Swerk )
|
||
|
@dateOfCreation( 10.01.2014 )
|
||
|
end
|
||
|
|
||
|
/**
|
||
|
* The following Tcp declaration represents the jNetPcap-Tcp class, see org.jnetpcap.protocol.tcpip.Tcp.
|
||
|
* Due to 'Tcp' being a Java class of a different library it cannot extend or implement one of the Activity
|
||
|
* base classes and therefore is not treated as a Drools-event per se. Therefore, the metadata of this custom
|
||
|
* class must be defined individually, which can be interpreted as a forward declaration.
|
||
|
*/
|
||
|
declare Tcp
|
||
|
@role( event )
|
||
|
@author( Stefan Swerk )
|
||
|
@timestamp( getPacket().getCaptureHeader().timestampInMillis() )
|
||
|
@expires( 30m )
|
||
|
end
|
||
|
|
||
|
/**
|
||
|
* Currently it appears as if the Tcp-decoder of the Kraken library does not parse all valid Tcp-packets successfully.
|
||
|
* As a kind of workaround this rule has been defined to fallback to the jNetPcap library (hence the previous Tcp-forward
|
||
|
* declaration) for all IPv4 activities that indicate TCP as the encapsulated protocol,
|
||
|
* but that have not been decoded by the Kraken-Tcp-Decoder until now.
|
||
|
* This rule will ensure that an appropriate drop-in TcpActivity will be created and inserted in the event-stream,
|
||
|
* which may be used by other rules.
|
||
|
*/
|
||
|
rule "TCP (work around Kraken limitation)"
|
||
|
when
|
||
|
$ip : Ipv4Activity(ipv4.nextHeaderId == Tcp.ID)
|
||
|
not (exists TcpActivity(pcapActivity == $ip.pcapActivity))
|
||
|
then
|
||
|
Tcp tcp = new Tcp();
|
||
|
PcapPacket p = $ip.getPcapActivity().getPcapPacket();
|
||
|
p.hasHeader(tcp);
|
||
|
log.debug("A workaround Tcp-Activity will be created for frames {}", $ip.getCompoundFrameNumbers());
|
||
|
|
||
|
TcpActivity tcpActivity = new TcpActivity($ip.getPcapActivity(),tcp,$ip);
|
||
|
tcpActivity.replaceActivity($ip);
|
||
|
insert(tcpActivity);
|
||
|
end
|
||
|
|
||
|
/**
|
||
|
* This rules makes use of a custom entry-point called "fact-stream" and the previously declared fact-attribute of
|
||
|
* HttpImageActivity. If a HttpActivity is encountered containing an response that defined an "image/..." content_type
|
||
|
* header, it may be assumed that this reponse was used for delivering image data and the corresponding URL of the request
|
||
|
* contained the image path.
|
||
|
*/
|
||
|
rule "Http Image Activity"
|
||
|
no-loop
|
||
|
when
|
||
|
$httpActivity : HttpActivity($contentType : response.responseHeaderMap[HttpHeaders.CONTENT_TYPE] matches "image/.*",
|
||
|
imageActivities.isEmpty())
|
||
|
then
|
||
|
log.debug("An HttpImageActivity based on the content type was found for frames {}", $httpActivity.getCompoundFrameNumbers());
|
||
|
|
||
|
HttpImageActivity imgAct = new HttpImageActivity($httpActivity);
|
||
|
imgAct.setImagePath($httpActivity.getRequest().getUrl().getFile());
|
||
|
imgAct.setImageType($contentType);
|
||
|
imgAct.setStartInstant($httpActivity.getStartInstant());
|
||
|
imgAct.setEndInstant($httpActivity.getEndInstant());
|
||
|
drools.getEntryPoint("fact-stream").insert(imgAct);
|
||
|
modify($httpActivity){
|
||
|
addImageActivity(imgAct)
|
||
|
}
|
||
|
end
|
||
|
|
||
|
/**
|
||
|
* This rule fires iff there is a HttpImageActivity whose Requests REFERER Header field matches the Request-URI of
|
||
|
* another HttpActivity, i.e. it collects ImageActivities which may be related to a single HttpActivity.
|
||
|
* Consider the following example: A user queries a HTML-Resource that contains external image resources,
|
||
|
* and usually the browser creates subsequent HTTP requests for the image data retrieval.
|
||
|
* Whenever the Browser sets the Referer header field for those separate requests, we could correlate those separate
|
||
|
* image requests with a single HTML resource request.
|
||
|
*/
|
||
|
rule "Collect Http Image Activities (based on referer header)"
|
||
|
when
|
||
|
$http : HttpActivity($req : request, $reqResource : request.url.toString())
|
||
|
$imgAct : HttpImageActivity(this not memberOf $http.imageActivities,
|
||
|
source#HttpActivity.request.requestHeaderMap[HttpHeaders.REFERER] matches $reqResource)
|
||
|
from entry-point "fact-stream"
|
||
|
|
||
|
// add an additional time based constraint
|
||
|
// $htmlRequest : HttpRequestActivity( pcapActivity == $req.pcapActivity)
|
||
|
// $imgRequest : HttpRequestActivity( pcapActivity == $imgAct.source#HttpActivity.request.pcapActivity,
|
||
|
// this after[0s,10s] $htmlRequest)
|
||
|
//
|
||
|
// match a single image request for an image resource to a single request for an html resource only
|
||
|
// not (exists HttpRequestActivity(pcapActivity != $htmlRequest.pcapActivity,
|
||
|
// url.toString() matches $reqResource,
|
||
|
// this before $imgRequest))
|
||
|
then
|
||
|
modify($http) {
|
||
|
addImageActivity($imgAct)
|
||
|
}
|
||
|
end
|
||
|
|
||
|
/**
|
||
|
* Currently the event stream will only contain not yet matched HttpRequests and HttpResponses.
|
||
|
* Since the reasoning process will be enhanced by correlated each request to a response this rule tries to achieve
|
||
|
* a simple matching mechanism based on the TCP/IP source and destination port and address.
|
||
|
*/
|
||
|
rule "Http Request and Response Matching (based on TCP/IP source/destination and time)"
|
||
|
when
|
||
|
$tcpReq : TcpActivity( $reqId := pcapActivity, $src : sourceAddress, $dst : destinationAddress)
|
||
|
$request : HttpRequestActivity( $reqId := pcapActivity)
|
||
|
|
||
|
$tcpResp : TcpActivity( $respId : pcapActivity, $tcpReq.sourcePort == destinationPort,
|
||
|
$src == destinationAddress, $dst == sourceAddress)
|
||
|
$response : HttpResponseActivity(pcapActivity == $respId, this after[0s,1m] $request)
|
||
|
|
||
|
not (exists HttpActivity(request == $request || response == $response))
|
||
|
|
||
|
then
|
||
|
HttpActivity activity = new HttpActivity($request, $response);
|
||
|
log.debug("A HttpRequest was matched with a HttpResponse (frames {})", activity.getCompoundFrameNumbers());
|
||
|
|
||
|
insert(activity);
|
||
|
end
|
||
|
|
||
|
/**
|
||
|
* This rule tries to match a DNS response to a an already existing HttpActivity using the hostname header field and
|
||
|
* a maximum interval between the DNS response and the Http response of [0s;20s].
|
||
|
* An already existing DNS match of a HttpActivity will not be overwritten.
|
||
|
*/
|
||
|
rule "HttpActivity as a potential result of a preceding DNS activity"
|
||
|
when
|
||
|
$http : HttpActivity($hostHeader : request.requestHeaderMap[HttpHeaders.HOST], dnsMatch==null)
|
||
|
$dnsResponse : DnsActivity(isResponse(), this before[0s,20s] $http)
|
||
|
|
||
|
/**
|
||
|
* The first two checks are IP based, i.e: was the ip address from the DNS A/AAAA record called and does it match the HTTP server IP?
|
||
|
* The last check is domain based, i.e. the "Host:"-Header field from the HttpRequest is compared against the DNS name reply.
|
||
|
*/
|
||
|
exists( ARecord( $address : getAddress(), $address!.getHostAddress() == $http.request.serverAddress.getAddress().getHostAddress())
|
||
|
from $dnsResponse.getAnswerRecords()
|
||
|
or AAAARecord( $address : getAddress(), $address!.getHostAddress() == $http.request.serverAddress.getAddress().getHostAddress())
|
||
|
from $dnsResponse.getAnswerRecords()
|
||
|
or Record( $address : name, $address!.toString().startsWith($hostHeader))
|
||
|
from $dnsResponse.getAnswerRecords()
|
||
|
)
|
||
|
then
|
||
|
// At this point there was a preceding DNS response and a matching subsequent HTTP Request and Response
|
||
|
modify($http) {
|
||
|
setDnsMatch($dnsResponse);
|
||
|
};
|
||
|
end
|