├── README.md ├── pcap.cc └── pcap.h /README.md: -------------------------------------------------------------------------------- 1 | # ModSecurity-pcap 2 | Have you ever wanted to run ModSecurity out of line without a webserver? Have you been told it's impossible? ModSecurity v3 makes anything possimpible! This is the ModSecurity v3 connector for a pcap file. 3 | 4 | This is also a good example of how to use libmodsecurity's C++ interfaces. 5 | 6 | Currently using the following to compile 7 | g++ -std=c++11 -I../../../headers -L../../../src/.libs/ ../../../src/.libs/libmodsecurity.so -lpcap -Wl,-rpath -Wl,/usr/local/modsecurity/lib pcap.cc 8 | -------------------------------------------------------------------------------- /pcap.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Structs for processing 8 | #include 9 | #include 10 | #include 11 | #include 12 | // inet_aton 13 | #include 14 | #include 15 | #include 16 | 17 | #include "pcap.h" 18 | 19 | 20 | #ifndef ETHERTYPE_IP6 21 | #define ETHERTYPE_IP6 0x86dd 22 | #endif 23 | 24 | 25 | 26 | // Our constructor that collects the packet header and data 27 | Packet::Packet(pcap_pkthdr *header, const u_char *data, int verbose) 28 | { 29 | _header = header; 30 | _data = data; 31 | _verbose = verbose; 32 | } 33 | 34 | // We will take a raw packet and break it down 35 | // part by part till we get to the app data 36 | // Returns -1 if there is an error 37 | // Returns 1 if success 38 | int Packet::parsePacket() 39 | { 40 | // Assume Ethr and save the header 41 | _eth = (struct ether_header*)_data; 42 | if(_verbose){ 43 | std::cout << "[+] Parsed out Ethernet header" << std::endl; 44 | } 45 | // Check if we have a TCP header 46 | // This is an ugly way to check if we have ipv6 or not 47 | int hasTCP = 0; 48 | 49 | // Check that we have an IP type packet 50 | if(ntohs(_eth->ether_type) == ETHERTYPE_IP){ 51 | // Save the IP header 52 | _ip = (struct ip*)(_data+sizeof(struct ether_header)); 53 | if(_ip->ip_p == IPPROTO_TCP){ 54 | hasTCP = 1; 55 | } 56 | if(_verbose){ 57 | std::cout << "[+] Parsed out IP header" << std::endl; 58 | } 59 | }else if(ntohs(_eth->ether_type) == ETHERTYPE_IP6){ 60 | _ip6 = (struct ip6_hdr *)(_data+sizeof(struct ether_header)); 61 | if(_ip6->ip6_nxt == IPPROTO_TCP){ 62 | hasTCP = 1; 63 | } 64 | if(_verbose){ 65 | std::cout << "[+] Parsed out IPv6 header" << std::endl; 66 | } 67 | return 1; 68 | }else{ 69 | return -1; 70 | } 71 | 72 | // If we have TCP extract the header 73 | if(hasTCP){ 74 | _tcp = (struct tcphdr*)(_data+sizeof(struct ether_header)+sizeof(struct ip)); 75 | if(_verbose){ 76 | std::cout << "[+] Parsed out TCP header" << std::endl; 77 | } 78 | // Overwrite the data we have with the remaining data 79 | _appData = (char*)(_data + sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct tcphdr)); 80 | // Check if we have any data 81 | if(_appData.length() != 0){ 82 | hasData = 1; 83 | if(_verbose){ 84 | std::cout << "[+] Remaining data is " << _appData.length() << " bytes long" << std::endl; 85 | } 86 | }else{ 87 | hasData = 0; 88 | if(_verbose){ 89 | std::cout << "[+] This packet has no data" << std::endl; 90 | } 91 | } 92 | }else{ 93 | return -1; 94 | } 95 | } 96 | 97 | int Packet::setTCPIPdata() 98 | { 99 | 100 | // Check for IPv4 header and extract addresses 101 | if(_ip->ip_v == 4){ 102 | char sourceIP[INET_ADDRSTRLEN]; 103 | char destIP[INET_ADDRSTRLEN]; 104 | const char* ret1 = inet_ntop(AF_INET, &_ip->ip_src, sourceIP, sizeof(sourceIP)); 105 | const char* ret2 =inet_ntop(AF_INET, &_ip->ip_dst, destIP, sizeof(destIP)); 106 | if(ret1 == NULL || ret2 == NULL){ 107 | return -1; 108 | } 109 | src = sourceIP; 110 | dst = destIP; 111 | } 112 | // Check for IPv6 header and extract addresses 113 | if(_ip->ip_v == 6){ 114 | char sourceIP[INET6_ADDRSTRLEN]; 115 | char destIP[INET6_ADDRSTRLEN]; 116 | const char* ret1 = inet_ntop(AF_INET6, &_ip->ip_src, sourceIP, sizeof(sourceIP)); 117 | const char* ret2 = inet_ntop(AF_INET6, &_ip->ip_dst, destIP, sizeof(destIP)); 118 | if(ret1 == NULL || ret2 == NULL){ 119 | return -1; 120 | } 121 | src = sourceIP; 122 | dst = destIP; 123 | } 124 | // Extract our src and dest ports 125 | srcprt = ntohs(_tcp->source); 126 | dstprt = ntohs(_tcp->dest); 127 | 128 | if(_verbose){ 129 | std::cout << "[+] Extracted source IP - " << src << " and source port " << srcprt << std::endl; 130 | std::cout << "[+] Extracted dest IP - " << dst << " and dest port " << dstprt << std::endl; 131 | } 132 | } 133 | 134 | // We need to check if it's an HTTP packet and 135 | // if it is extract the URI based on if it's 136 | // a request or a response HTTP 137 | int Packet::extractHTTPuri(std::string type) 138 | { 139 | // Store our type for later 140 | if(type == "Request"){ 141 | request = 1; 142 | }else{ 143 | request = 0; 144 | } 145 | std::vector seglist; 146 | int start = 0; 147 | // Add each line of a potentail request to a vector (split on \r\n) 148 | for(int i=0; i<_appData.length();i++){ 149 | // We do some limited bounds checking and look for /r/n 150 | if(_appData.at(i) == '\r' && i != _appData.length()-1){ 151 | if(_appData.at(i+1) == '\n'){ 152 | // Add each line in the HTTP header to a vector 153 | seglist.push_back(_appData.substr(start,i-start)); 154 | start = i+2; 155 | } 156 | } 157 | } 158 | // Make sure to not forget the last element (and bound checking) 159 | if( start != _appData.length()-start){ 160 | seglist.push_back(_appData.substr(start,_appData.length()-start)); 161 | } 162 | // Persist our vector (representing line by line HTTP data) 163 | _splitHTTP = seglist; 164 | 165 | // We'll only need the first line to get started 166 | std::string firstLine = seglist.front(); 167 | 168 | if(request){ 169 | int firstSpace = -1; 170 | int secondSpace = -1; 171 | // C++ regex takes a long time (way too long) 172 | // ([A-Z].*?)\\s(.*?)\\s(HTTP/\\d\\.\\d) 173 | // get the locations of the first two spaces 174 | for(int i = 0; i 90){ 210 | httpDetected = false; 211 | return -1; 212 | } 213 | } 214 | httpDetected = true; 215 | return 1; 216 | } 217 | // If its not a request its an HTTP response 218 | if(!request){ 219 | int firstSpace = -1; 220 | int secondSpace = -1; 221 | // C++ regex takes a long time (way too long) 222 | // (HTTP/\\d\\.\\d)\\s(\\d{3})\\s(.*) 223 | for(int i = 0; i 57){ 259 | httpDetected = false; 260 | return -1; 261 | } 262 | } 263 | 264 | } 265 | } 266 | 267 | // We need to get the headers out one by one and 268 | // isolate the data portion of the HTTP element 269 | int Packet::extractHTTPData() 270 | { 271 | // We have a request/response URI and something else (maybe headers) 272 | if(_splitHTTP.size() > 1){ 273 | // First line is request/responseURI - last line may be data 274 | for(std::vector::size_type header = 1; header != _splitHTTP.size()-1; header++) { 275 | // Ignore blank lines (we'll use the later) 276 | if(_splitHTTP[header] == ""){ 277 | continue; 278 | } 279 | 280 | // Search for colon and split our header based on it 281 | for(int i = 0;i<_splitHTTP[header].length();i++){ 282 | if(_splitHTTP[header].at(i) == ':'){ 283 | headerNames.push_back(_splitHTTP[header].substr(0,i)); 284 | headerValues.push_back(_splitHTTP[header].substr(i+1,_splitHTTP[header].length()-(i+1))); 285 | break; 286 | } 287 | } 288 | } 289 | 290 | // Check our headers to see if there is content 291 | // apparently this isn't really a great check 292 | for(auto headerName: headerNames){ 293 | if(headerName == "Content-Length"){ 294 | _hasHTTPData = 1; 295 | } 296 | } 297 | // Also check for a black CRLF as the penultimate item 298 | if (_splitHTTP.rbegin()[1] == ""){ 299 | _hasHTTPData = 1; 300 | } 301 | // if there is data extract it as data, otherwise its a header 302 | if(_hasHTTPData){ 303 | bodyData = _splitHTTP.back(); 304 | }else{ 305 | for(int i = 0;i<_splitHTTP.back().length();i++){ 306 | if(_splitHTTP.back().at(i) == ':'){ 307 | headerNames.push_back(_splitHTTP.back().substr(0,i)); 308 | headerValues.push_back(_splitHTTP.back().substr(i+1,_splitHTTP.back().length()-(i+1))); 309 | } 310 | } 311 | } 312 | } 313 | } 314 | 315 | 316 | 317 | class ModSecurityAnalyzer 318 | { 319 | public: 320 | ModSecurityAnalyzer(std::string main_rule_uri); 321 | int AddConnectionInfo(std::string src,int srcprt,std::string dst,int dstprt); 322 | int AddRequestInfo(Packet *mypacket); 323 | int AddResponseInfo(Packet *mypacket); 324 | int RunPhases(); 325 | int RunAssayCleanup(); 326 | int RunCleanup(); 327 | private: 328 | int _testIntervention(modsecurity::ModSecurityIntervention status_it); 329 | // ModSecurity engine 330 | modsecurity::ModSecurity *_modsec; 331 | 332 | // modsecurity rules 333 | modsecurity::Rules *_rules; 334 | 335 | // modsecurity transaction object 336 | modsecurity::Assay *_pmodsecAssay; 337 | }; 338 | 339 | ModSecurityAnalyzer::ModSecurityAnalyzer(std::string main_rule_uri){ 340 | const char *error = NULL; 341 | // Connect to libmodsecurity 342 | _modsec = modsecurity::msc_init(); 343 | _modsec->setConnectorInformation("ModSecurity-pcap v0.0.1-alpha"); 344 | 345 | // Load the modsecurity rules specified. 346 | _rules = modsecurity::msc_create_rules_set(); 347 | if(modsecurity::msc_rules_add_file(_rules, main_rule_uri.c_str(), &error) < 0){ 348 | std::cerr << "Error: Issues loading the rules." << std::endl << error << std::endl; 349 | } 350 | // C++ form of msc_rules_dump() 351 | _rules->dump(); 352 | } 353 | 354 | 355 | int ModSecurityAnalyzer::AddConnectionInfo(std::string src,int srcprt,std::string dst,int dstprt){ 356 | _pmodsecAssay = modsecurity::msc_new_assay(_modsec, _rules, NULL); 357 | // Assign the IP's and ports to the transaction 358 | _pmodsecAssay->processConnection(src.c_str(), srcprt, dst.c_str(), dstprt); 359 | return 1; 360 | 361 | } 362 | 363 | int ModSecurityAnalyzer::AddRequestInfo(Packet *mypacket){ 364 | std::string uri = mypacket->uri; 365 | std::string method = mypacket->method; 366 | std::string version = mypacket->version; 367 | std::vector headerNames = mypacket->headerNames; 368 | std::vector headerValues =mypacket->headerValues; 369 | std::string bodyData = mypacket->bodyData; 370 | 371 | // ModSecurity wants the HTTP/ removed from the 1.1 372 | version = version.substr(5,version.length()-5); 373 | _pmodsecAssay->processURI(uri.c_str(),method.c_str(),version.c_str()); 374 | 375 | // Add each header key/value to as a header in modsec 376 | for(std::vector::size_type header = 0; header != headerNames.size(); header++) { 377 | _pmodsecAssay->addRequestHeader(reinterpret_cast(headerNames[header].c_str()),reinterpret_cast(headerValues[header].c_str())); 378 | } 379 | 380 | // Add the body data only if it has a body 381 | if(bodyData != "" ){ 382 | _pmodsecAssay->appendRequestBody(reinterpret_cast(bodyData.c_str()),bodyData.length()); 383 | } 384 | return 1; 385 | } 386 | 387 | int ModSecurityAnalyzer::AddResponseInfo(Packet *mypacket){ 388 | std::string bodyData = mypacket->bodyData; 389 | std::vector headerNames = mypacket->headerNames; 390 | std::vector headerValues =mypacket->headerValues; 391 | 392 | //TODO: RESPONSE STATUS is not implmented yet 393 | 394 | // Add each response header as a header in modsec 395 | for(std::vector::size_type header = 0; header != headerNames.size(); header++) { 396 | _pmodsecAssay->addResponseHeader(reinterpret_cast(headerNames[header].c_str()),reinterpret_cast(headerValues[header].c_str())); 397 | } 398 | 399 | // Add the body if there is one 400 | if(bodyData != "" ){ 401 | _pmodsecAssay->appendRequestBody(reinterpret_cast(bodyData.c_str()),bodyData.length()); 402 | } 403 | 404 | } 405 | 406 | // Testing for interventions is needed but we can't deny anyway, we're out of line 407 | // If needed a TCP reset packet could be sent here to try and emulate inline-ness 408 | int ModSecurityAnalyzer::_testIntervention(modsecurity::ModSecurityIntervention status_it){ 409 | _pmodsecAssay->intervention(&status_it); 410 | if( status_it.disruptive == 1) 411 | { 412 | std::cout << "There was a disruptive action but we are out of line" << std::endl; 413 | if(status_it.log != NULL){ 414 | std::cerr << "processResponseBody intervention: " << status_it.log << std::endl; 415 | }else{ 416 | std::cerr << "no data received from modsecuritylib in processResponseBody: status_it.log is NULL" << std::endl; 417 | 418 | } 419 | return 1; 420 | } 421 | return 0; 422 | } 423 | 424 | // This is where we tell ModSec to actually check 425 | // our packet 426 | int ModSecurityAnalyzer::RunPhases(){ 427 | modsecurity::ModSecurityIntervention status_it; 428 | _pmodsecAssay->processRequestHeaders(); 429 | _testIntervention(status_it); 430 | _pmodsecAssay->processRequestBody(); 431 | _testIntervention(status_it); 432 | _pmodsecAssay->processResponseHeaders(); 433 | _testIntervention(status_it); 434 | _pmodsecAssay->processResponseBody(); 435 | _testIntervention(status_it); 436 | _pmodsecAssay->processLogging(200); 437 | _testIntervention(status_it); 438 | return 1; 439 | } 440 | int ModSecurityAnalyzer::RunAssayCleanup(){ 441 | delete _pmodsecAssay; 442 | } 443 | int ModSecurityAnalyzer::RunCleanup(){ 444 | delete _rules; 445 | delete _modsec; 446 | } 447 | 448 | 449 | 450 | // Returns -1 on error 451 | // Returns 1 on success 452 | int checkArgs(int argc, char** argv, int* verboseRef, std::string* filenameRef) 453 | { 454 | std::stringstream usage; 455 | usage << "Usage: " << argv[0] << " [OPTIONS]... [FILE]"; 456 | 457 | // Check the number of parameters 458 | if (argc < 2) { 459 | std::cerr << usage.str() << std::endl; 460 | return -1; 461 | }else{ 462 | *verboseRef = 0; 463 | if(argc > 2){ 464 | int knownArg = 0; 465 | // Check if our arguments are valid (except first and last) 466 | for(int i=1;i= 0) 538 | { 539 | Packet * mypacket = new Packet(header,data,verbose); 540 | // Try parsing each packet 541 | if(mypacket->parsePacket() == -1){ 542 | if(verbose){ 543 | std::cerr << "Error: There was a problem parsing a packet" << std::endl; 544 | } 545 | continue; 546 | } 547 | // Check if we have a ports/IPs 548 | if(mypacket->setTCPIPdata() != -1){ 549 | // Check if it is on a common HTTP port first 550 | if((mypacket->srcprt == 80 || mypacket->srcprt == 8080) && mypacket->hasData == 1){ 551 | // Extract HTTP information 552 | mypacket->extractHTTPuri("Response"); 553 | } 554 | if((mypacket->dstprt == 80 || mypacket->dstprt== 8080) && mypacket->hasData == 1 ){ 555 | mypacket->extractHTTPuri("Request"); 556 | 557 | } 558 | }else{ 559 | if(verbose){ 560 | std::cerr << "Error: There was problem extracting TCPIP data" << std::endl; 561 | continue; 562 | } 563 | } 564 | // Only if we found HTTP data we need to do things 565 | if(mypacket->httpDetected == true){ 566 | // We want to add the IP/port info about the request 567 | msa->AddConnectionInfo(mypacket->src, mypacket->srcprt,mypacket->dst,mypacket->dstprt); 568 | // This will pull out headers and body 569 | mypacket->extractHTTPData(); 570 | // We then add them into modsec 571 | if(mypacket->request){ 572 | msa->AddRequestInfo(mypacket); 573 | } 574 | if(!mypacket->request){ 575 | msa->AddResponseInfo(mypacket); 576 | } 577 | // Last we run our transaction through the rules 578 | msa->RunPhases(); 579 | } 580 | // We won't need our packet for this tool 581 | delete mypacket; 582 | // TODO: cleaning up the assay results in a segfault? 583 | //msa->RunAssayCleanup(); 584 | } 585 | // Cleansup our modsec objects 586 | msa->RunCleanup(); 587 | delete msa; 588 | 589 | } 590 | -------------------------------------------------------------------------------- /pcap.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Packet 4 | { 5 | public: 6 | Packet(pcap_pkthdr *header, const u_char *data, int verbose); 7 | int parsePacket(); 8 | int setTCPIPdata(); 9 | int extractHTTPuri(std::string type); 10 | int extractHTTPData(); 11 | int request; 12 | std::string src; 13 | std::string dst; 14 | int srcprt; 15 | int dstprt; 16 | int hasData; 17 | std::string method; 18 | std::string uri; 19 | std::string version; 20 | bool httpDetected = false; 21 | std::vector headerNames; 22 | std::vector headerValues; 23 | std::string bodyData = ""; 24 | std::string rVersion; 25 | std::string status; 26 | std::string phrase; 27 | 28 | private: 29 | int _verbose; 30 | pcap_pkthdr *_header; 31 | const u_char *_data; 32 | struct ether_header* _eth; 33 | struct ip *_ip; 34 | struct ip6_hdr *_ip6; 35 | struct tcphdr *_tcp; 36 | std::string _appData; 37 | int _hasHTTPData = 0; 38 | std::vector _splitHTTP; 39 | }; 40 | --------------------------------------------------------------------------------