MINI MINI MANI MO
#!/opt/local/bin/perl -T
#############################################
# -=[ Virtual Patching Converter Script ]=- #
# Converts OWASP ZAP XML Ouput #
# https://code.google.com/p/zaproxy/ #
# #
# zap2modsec.pl #
# Version: 1.0 #
# #
# Copyright 2011 #
# Trustwave's SpiderLabs Research Team #
# www.trustwave.com #
# #
# Based On Code Originally Created by: #
# The Denim Group #
# www.denimgroup.com #
#############################################
use XML::Smart;
use Switch;
use Data::Types qw(:all);
use Data::Validate::URI qw(is_uri);
use Getopt::Std;
use Acme::Comment type=>'C++', one_line=>1; #Block commenting, can be removed later
#############
# Variables #
#############
# [Configuration Vars]
my %param;
getopt("f",\%param);
$filename = $param{f};
my $all_vulnerabilities_filename = "$filename";
unless ($filename) {
print "Flag:\n\n\t -f:\t path to ZAP xml report file\nUsage:\n\n\t./zap2modsec.pl -f ./zap_report.xml\n\n";
exit;
}
my $modsec_rules_file = "./modsecurity_crs_48_virtual_patches.conf";
# [End Config Vars]
my $VULN_CLASS_XSS = "Cross Site Scripting";
my $VULN_CLASS_SQLI = "SQL Injection";
my $VULN_CLASS_SQLI_FINGERPRINT = "SQL Injection Fingerprinting";
my $VULN_CLASS_LFI = "Path Traversal";
my $VULN_CLASS_RFI = "Remote File Inclusion";
my $VULN_CLASS_HTTPRS = "HTTP Response Splitting";
# Only the vulnerabilities in this array will have
# rules generated for them.
my @supported_vulns = ($VULN_CLASS_XSS, $VULN_CLASS_SQLI, $VULN_CLASS_SQLI_FINGERPRINT, $VULN_CLASS_LFI, $VULN_CLASS_RFI, $VULN_CLASS_HTTPRS);
my $num_rules_generated=0;
my $num_not_supported=0;
my $num_bad_urls=0;
my $wait_for_keypress=1;
my $request_failed=0;
my $all_vulns_xml;
my @type;
my @id;
my $vuln_count;
my $num_attacks_flag=0;
my $num_attacks_noflag=0;
# End Vars ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#############
# Main #
#############
# Clean up env so perl doesn't complain
# when trying to run the restart snort
# script.
delete @ENV{qw(IFS CDPATH ENV BASH_ENV PATH)};
$all_vulns_xml = XML::Smart->new($all_vulnerabilities_filename);
@type = $all_vulns_xml->{OWASPZAPReport}{site}{alerts}{alertitem}('[@]','alert');
@url = $all_vulns_xml->{OWASPZAPReport}{site}{alerts}{alertitem}('[@]','uri');
@param = $all_vulns_xml->{OWASPZAPReport}{site}{alerts}{alertitem}('[@]','param');
open(my $MODSEC_RULES, '>' , $modsec_rules_file) || die "Unable to open modsecurity rules file $modsec_rules_file";
$MODSEC_RULES->autoflush(1);
$vuln_count = 0;
foreach my $current_type (@type){
print "==================================================================================================\n";
print "Vulnerability[$vuln_count] - Type: $current_type\n";
if(exists {map { $_ => 1 } @supported_vulns}->{$current_type}){
parseData(to_string($current_type));
}else {
print "Vulnerability Type: $type is not supported in this version.\n";
$num_not_supported++;
}
$vuln_count++;
}
close($MODSEC_RULES);
print "==================================================================================================\n";
print "\n\n************ END OF SCRIPT RESULTS *****************\n";
print "Number of Vulnerabilities Processed: $vuln_count\n";
print "Number of ModSecurity rules generated: $num_rules_generated\n";
print "Number of Unsupported vulns skipped: $num_not_supported\n";
print "Number of bad URLs (rules not gen): $num_bad_urls\n";
print "****************************************************\n\n";
print "----------------------------------------------------\n";
print "To activate the virtual patching file ($modsec_rules_file),\n";
print "copy it into the CRS \"base_rules\" directory and then create\n";
print "a symlink to it in the \"activated_rules\" directory.\n";
print "-----------------------------------------------------\n\n";
###############
# Subroutines #
###############
sub parseData
{
my($vuln_str) = @_;
my $vuln_detail_filename;
my $current_vuln_xml;
my $current_vuln_url;
my $current_vuln_param;
my $current_uricontent;
my @current_params;
my $id = $vuln_count;
print "Found a $vuln_str vulnerability.\n";
$current_vuln_xml = XML::Smart->new($all_vulnerabilities_filename);
$current_vuln_url = $url[$vuln_count];
print URL_LIST "$current_vuln_url\n";
# Validate url (need seperate sub?)
print "Validating URL: $current_vuln_url\n";
if(is_uri(to_string($current_vuln_url))){
print "URL is well-formed\n";
print "Continuing Rule Generation\n";
} else {
print "URL is NOT well-formed. Breaking Out of Rule Generation\n";
$num_bad_urls++;
# Waits for keypress in test mode so you can
# see why the URL failed validation.
if($test_mode){
wait_for_keypress();
}
return;
}
$current_uricontent = get_uricontent($current_vuln_url);
# Only need param if XSS attack,SQLINJ,XPATH
# and maybe for HTTPRS, DT.
# NOT for PRL and DI
if(($vuln_str ne $VULN_CLASS_PRL) && ($vuln_str ne $VULN_CLASS_DI)){
@current_params = $param[$vuln_count];
}
if(($vuln_str ne $VULN_CLASS_PRL) && ($vuln_str ne $VULN_CLASS_DI)){
print "Current vulnerable Param(s): @current_params\n";
}
generate_patch($vuln_str,$current_uricontent,@current_params);
}
sub generate_patch
{
my($type,$uricontent,@params,$current_vuln_xml) = @_;
my $rule = "";
$id = "1".$vuln_count;
switch($type)
{
case ($VULN_CLASS_XSS)
{
if($uricontent ne "" && @params){
foreach(@params){
if($_ ne ""){
# Check to see if each vulnerable parameter is valid
# then generate a rule using both uricontent and the
# parameter
$rule = "SecRule REQUEST_FILENAME \"$uricontent\" \"chain,phase:2,t:none,block,msg:'Virtual Patch for $type',id:'$id',tag:'WEB_ATTACK/XSS',tag:'WASCTC/WASC-8',tag:'WASCTC/WASC-22',tag:'OWASP_TOP_10/A2',tag:'OWASP_AppSensor/IE1',tag:'PCI/6.5.1',logdata:'%{matched_var_name}',severity:'2'\"\n\tSecRule \&TX:\'\/XSS.*ARGS:$_\/\' \"\@gt 0\" \"setvar:'tx.msg=%{rule.msg}',setvar:tx.xss_score=+%{tx.critical_anomaly_score},setvar:tx.anomaly_score=+%{tx.critical_anomaly_score}\"";
print $MODSEC_RULES "#\n# OWASP ZAP Virtual Patch Details:\n# ID: $id\n# Type: $type\n# Vulnerable URL: $uricontent\n# Vulnerable Parameter: $_\n#\n".$rule."\n\n";
print "$VULN_CLASS_XSS (uricontent and param) rule successfully generated and saved in $modsec_rules_file.\n";
$num_rules_generated++;
}
}
}
}
case ($VULN_CLASS_SQLI)
{
if($uricontent ne "" && @params){
foreach(@params){
if($_ ne ""){
$rule = "SecRule REQUEST_FILENAME \"$uricontent\" \"chain,phase:2,t:none,block,msg:'Virtual Patch for $type',id:'$id',tag:'WEB_ATTACK/SQL_INJECTION',tag:'WASCTC/WASC-19',tag:'OWASP_TOP_10/A1',tag:'OWASP_AppSensor/CIE1',tag:'PCI/6.5.2',logdata:'%{matched_var_name}',severity:'2'\"\n\tSecRule \&TX:\'\/SQL_INJECTION.*ARGS:$_\/\' \"\@gt 0\" \"setvar:'tx.msg=%{rule.msg}',setvar:tx.sql_injection_score=+%{tx.critical_anomaly_score},setvar:tx.anomaly_score=+%{tx.critical_anomaly_score}\"";
print $MODSEC_RULES "#\n# OWASP ZAP Virtual Patch Details:\n# ID: $id\n# Type: $type\n# Vulnerable URL: $uricontent\n# Vulnerable Parameter: $_\n#\n".$rule."\n\n";
print "$VULN_CLASS_SQLI (uricontent and param) rule successfully generated and saved in $modsec_rules_file.\n";
$num_rules_generated++;
}
}
}
}
case ($VULN_CLASS_BLIND_SQLI)
{
if($uricontent ne "" && @params){
foreach(@params){
if($_ ne ""){
$rule = "SecRule REQUEST_FILENAME \"$uricontent\" \"chain,phase:2,t:none,block,msg:'Virtual Patch for $type',id:'$id',tag:'WEB_ATTACK/SQL_INJECTION',tag:'WASCTC/WASC-19',tag:'OWASP_TOP_10/A1',tag:'OWASP_AppSensor/CIE1',tag:'PCI/6.5.2',logdata:'%{matched_var_name}',severity:'2'\"\n\tSecRule \&TX:\'\/SQL_INJECTION.*ARGS:$_\/\' \"\@gt 0\" \"setvar:'tx.msg=%{rule.msg}',setvar:tx.sql_injection_score=+%{tx.critical_anomaly_score},setvar:tx.anomaly_score=+%{tx.critical_anomaly_score}\"";
print $MODSEC_RULES "#\n# OWASP ZAP Virtual Patch Details:\n# ID: $id\n# Type: $type\n# Vulnerable URL: $uricontent\n# Vulnerable Parameter: $_\n#\n".$rule."\n\n";
print "$VULN_CLASS_SQLI (uricontent and param) rule successfully generated and saved in $modsec_rules_file.\n";
$num_rules_generated++;
}
}
}
}
case ($VULN_CLASS_LFI)
{
if($uricontent ne "" && @params){
foreach(@params){
if($_ ne ""){
$rule = "SecRule REQUEST_FILENAME \"$uricontent\" \"chain,phase:2,t:none,block,msg:'Virtual Patch for $type',id:'$id',tag:'WEB_ATTACK/LFI',tag:'WASCTC/WASC-33',logdata:'%{matched_var_name}',severity:'2'\"\n\tSecRule \&TX:\'\/LFI.*ARGS:$_\/\' \"\@gt 0\" \"setvar:'tx.msg=%{rule.msg}',setvar:tx.anomaly_score=+%{tx.critical_anomaly_score}\"";
print $MODSEC_RULES "#\n# OWASP ZAP Virtual Patch Details:\n# ID: $id\n# Type: $type\n# Vulnerable URL: $uricontent\n# Vulnerable Parameter: $_\n#\n".$rule."\n\n";
print "$VULN_CLASS_LFI (uricontent and param) rule successfully generated and saved in $modsec_rules_file.\n";
$num_rules_generated++;
}
}
}
}
case ($VULN_CLASS_RFI)
{
if($uricontent ne "" && @params){
foreach(@params){
if($_ ne ""){
$rule = "SecRule REQUEST_FILENAME \"$uricontent\" \"chain,phase:2,t:none,block,msg:'Virtual Patch for $type',id:'$id',tag:'WEB_ATTACK/RFI',tag:'WASCTC/WASC-05',logdata:'%{matched_var_name}',severity:'2'\"\n\tSecRule \&TX:\'\/RFI.*ARGS:$_\/\' \"\@gt 0\" \"setvar:'tx.msg=%{rule.msg}',setvar:tx.anomaly_score=+%{tx.critical_anomaly_score}\"";
print $MODSEC_RULES "#\n# OWASP ZAP Virtual Patch Details:\n# ID: $id\n# Type: $type\n# Vulnerable URL: $uricontent\n# Vulnerable Parameter: $_\n#\n".$rule."\n\n";
print "$VULN_CLASS_LFI (uricontent and param) rule successfully generated and saved in $modsec_rules_file.\n";
$num_rules_generated++;
}
}
}
}
case ($VULN_CLASS_HTTPRS)
{
if($uricontent ne "" && @params){
foreach(@params){
if($_ ne ""){
$rule = "SecRule REQUEST_FILENAME \"$uricontent\" \"chain,phase:2,t:none,block,msg:'Virtual Patch for $type',id:'$id',tag:'WEB_ATTACK/RESPONSE_SPLITTING',tag:'WASCTC/WASC-25',logdata:'%{matched_var_name}',severity:'2'\"\n\tSecRule \&TX:\'\/RESPONSE_SPLITTING.*ARGS:$_\/\' \"\@gt 0\" \"setvar:'tx.msg=%{rule.msg}',setvar:tx.anomaly_score=+%{tx.critical_anomaly_score}\"";
print $MODSEC_RULES "#\n# OWASP ZAP Virtual Patch Details:\n# ID: $id\n# Type: $type\n# Vulnerable URL: $uricontent\n# Vulnerable Parameter: $_\n#\n".$rule."\n\n";
print "$VULN_CLASS_RFI (uricontent and param) rule successfully generated and saved in $modsec_rules_file.\n";
$num_rules_generated++;
}
}
}
}
}
}
sub get_uricontent
{
my($url) = @_;
my $regex = "http:\/\/+[a-zA-Z0-9.:-]*\/";
# First, trim the first part out of the URL:
# http://.../
$url =~ /$regex/;
substr($url,index($url,$&),length($&)) = "";
# If the URL contains a php or cgi query with
# one or more params and values, trim those out.
# Trim from the question mark to the end.
if($url =~ /\?/){
substr($url,index($url,"?")) = "";
}
return $url;
}
OHA YOOOO