/** * 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 . */ 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