#!/bin/bash #Copyright 2001-2003 William Stearns #GPL'd. #This converts any input snort rules to iptables rules. #asterisks added in any content rules referenced in the code to avoid tripping snort... :-) #Change UseDsize to Y if you want to use the Dsize module #UseDsize='Y' UseDsize='N' Snort2IptablesVer='0.2.3' #FIXME - add to banner, usage. Debug () { echo "$*" >&2 } Failed () { #Record fact that conversion failed because of a feature in snort missing in iptables. FailedReasons="$FailedReasons $1" } StripOptionName () { echo "$*" | sed -e 's/^[A-Za-z]*://' -e 's/^ //' } ToAscii () { Input="$1" Tail="$Input" Output='' Hex='' while [ -n "$Tail" ]; do if [ -z "$Hex" ]; then #Strip ascii character and glue onto output, or | to switch us to hex. Head=`echo "$Tail" | sed -e 's/^\(.\).*/\1/'` Tail=`echo "$Tail" | sed -e 's/^.//'` if [ "$Head" = "|" ]; then Hex="y" else Output="$Output$Head" fi else #Strip first hex digit, space, or | to switch us back to ascii Head=`echo "$Tail" | sed -e 's/^\(.\).*/\1/'` Tail=`echo "$Tail" | sed -e 's/^.//'` if [ "$Head" = "|" ]; then Hex='' elif [ "$Head" = " " ]; then : elif [ "$Head" = '"' ]; then #Test code to handle trailing " when | was misinterpreted : else #We already have the first hex digit, grab the second. Head2=`echo "$Tail" | sed -e 's/^\(.\).*/\1/'` Tail=`echo "$Tail" | sed -e 's/^.//'` while [ "$Head2" = " " ]; do #Debug "X space second char in X$Input" Head2=`echo "$Tail" | sed -e 's/^\(.\).*/\1/'` Tail=`echo "$Tail" | sed -e 's/^.//'` done #$, `, ", \, and are treated specially in double quotes. # #The reason for the \\\" is that we need to provide enough quoting that the string can be #reprocessed by the shell into the final iptables command. # \\\" is interpreted as \\ (a \) and \" (a "), resulting in a \" in the #iptables command line. When that command line is processed, it will see the \" #and (hopefully) stuff " into the string to be matched in the kernel. # *fingers crossed* Head=$Head$Head2 case $Head in 00) #Null Failed "0x00 in $Input" #Output="$Output"`echo -e "\x$Head"` #No output at all. ;; 01) Failed "0x01 in $Input" #Output="$Output"`echo -e "\x$Head"` #No output at all. ;; 0[2-9B-Fb-f]|1[0-9A-Fa-f]) #Low control characters Output="$Output"`echo -e "\x$Head"` ;; 0[Aa]) #Newline #Output="$Output"'\\'`echo -e "\x0A"` #We get the \, but no lf. Failed "0x0A in $Input" ;; 2[0356A-Fa-f]|3[0-9A-Fa-f]|4[0-9A-Fa-f]|5[0-9ABDEFabdef]|6[1-9A-Fa-f]|7[0-9A-Ea-e]) #Few if any problems in printing. Output="$Output"`echo -e "\x$Head"` ;; 21) # ! Failed "0x21 in $Input" #Output="$Output\\\!" #Gives extra \ #Output="$Output"'!' #when iptables run, bash interprets the ! ;; 22) # " Output="$Output\\\"" ;; 27) # ' #Output="$Output\\\'" #Gives extra \ #Output="$Output\'" #Gives extra \ Output="$Output'" #Fine ;; 28) # ( #Output="$Output\\(" #Gives extra \ Output="$Output(" #Fine ;; 29) # ) #Output="$Output\\\)" #Gives extra \ #Output="$Output\)" #Gives extra \ Output="$Output)" #Fine ;; 24) #$ Output="$Output"'\$' ;; 5[Cc]) #\ Output="$Output"'\\' ;; 60) # ` #Output="$Output"'\\\`' #Gives extra \ Output="$Output"'\`' #Fine ;; 7[Ff]) #Del Failed "0x7f in $Input" #Output="$Output"`echo -e "\x$Head"` ;; [89A-Fa-f][0-9A-Fa-f]) #High extended ascii (drawing, etc.) characters. Output="$Output"`echo -e "\x$Head"` ;; *) Debug "Unhandled character <$Head> in $Input" #Debug "Conv$Head" ;; esac fi fi done echo "$Output" # echo >&2 } #ToAscii regression test if /bin/false ; then error () { echo Test $1 failed } if [ ! "`ToAscii "Abc"`" = "Abc" ]; then error ToAscii-1 ; fi if [ ! "`ToAscii "|20|"`" = " " ]; then error ToAscii-2 ; fi if [ ! "`ToAscii "Abc|20|Def"`" = "Abc Def" ]; then error ToAscii-3 ; fi if [ ! "`ToAscii "|20|Abc|20|"`" = " Abc " ]; then error ToAscii-4 ; fi if [ ! "`ToAscii "| 20 |Abc| 20 |"`" = " Abc " ]; then error ToAscii-5 ; fi if [ ! "`ToAscii "| 2 0 2 0 |"`" = " " ]; then error ToAscii-6 ; fi if [ ! "`ToAscii "a||b||||c||||||d"`" = "abcd" ]; then error ToAscii-7 ; fi if [ ! "`ToAscii "a|||20|||b"`" = "a b" ]; then error ToAscii-8 ; fi if [ ! "`ToAscii "|6E 61 6D 65 20 3D 22 70 69 63 73 34 79 6F 75 2E 65 78 65 22|"`" = "name =\\\"pics4you.exe\\\"" ]; then error ToAscii-9 ; fi if [ ! "`ToAscii "|66 69 63 6B 65 6E|"`" = "ficken" ]; then error ToAscii-10 ; fi #if [ ! "`ToAscii ""`" = "" ]; then error ToAscii-x ; fi fi ProcessPrimaryFields () { PrimaryParams='' PrimaryPackit='' #$1: alert, config, include, preprocessor, var case "$1" in "") : #Ignore null line ;; alert) #Debug Primary: "$*" #$2: tcp, udp, icmp, ip, *.rules case "$2" in ip) if [ "$4" != "any" ] || [ "$7" != "any" ]; then Debug IP protocol with source port "$4" and dest port "$7". fi ;; tcp|TCP) PrimaryParams="$PrimaryParams -p tcp" PrimaryPackit="$PrimaryPackit -t TCP" ;; udp|UDP) PrimaryParams="$PrimaryParams -p udp" PrimaryPackit="$PrimaryPackit -t UDP" ;; icmp|ICMP) if [ "$4" != "any" ] || [ "$7" != "any" ]; then Debug ICMP protocol with source port "$4" and dest port "$7" fi PrimaryParams="$PrimaryParams -p icmp" PrimaryPackit="$PrimaryPackit -t ICMP" ;; *) #FIXME Debug unrecognized protocol "$2" ;; esac #$5: -> <- <> #calling routine handles '<>', this function handles '<-' or '->' case "$5" in -\>) LeftDir="s" RightDir="d" LeftUDir="S" RightUDir="D" ;; \<-) LeftDir="d" RightDir="s" LeftUDir="D" RightUDir="S" ;; *) Failed "Unhandled direction $5" ;; esac #$3: netvars, any if [ "$3" != "any" ]; then PrimaryParams="$PrimaryParams -$LeftDir $3" PrimaryPackit="$PrimaryPackit -$LeftDir $3" fi #$4: ports, any (port ranges a:b , a: ) #iptables takes a:, :b quite well. if [ "$4" != "any" ]; then PrimaryParams="$PrimaryParams --${LeftDir}port $(echo "$4" | sed 's/!/! /')" PrimaryPackit="$PrimaryPackit --${LeftUDir} $(echo "$4" | sed 's/!/! /')" fi #$6: netvars, ips, any if [ "$6" != "any" ]; then PrimaryParams="$PrimaryParams -$RightDir $6" PrimaryPackit="$PrimaryPackit -$RightDir $6" fi #$7: port a:b a: !a:b :b any if [ "$7" != "any" ]; then PrimaryParams="$PrimaryParams --${RightDir}port $(echo "$7" | sed 's/!/! /')" PrimaryPackit="$PrimaryPackit --${RightUDir} $(echo "$7" | sed 's/!/! /')" fi ;; config) : #Debug Skipping classification ;; include) Debug 'Um, why did I get an include line?' ;; preprocessor) Debug 'Um, why did I get a preprocessor line?' ;; var) Debug 'Um, why did I get a var line?' ;; *) Failed "Unknown line starting with $*" ;; esac #PrimaryParams, PrimaryPackit are passed back as global variables } ProcessSecondaryFields () { SecondaryParams='' SecondaryPackit='' Comment='' LogString='' DashMStringLoaded='' #FIXME - same needed for u32, I'm pretty sure. #Glue "itype: mmm; icode: nnn;" together into itype: mmm/nnn; AllSecondary=`echo "$*" | sed -e 's@\(itype:[ ]*[0-9]*\)[ ]*;[ ]*icode:[ ]*\([0-9]*;\)@\1/\2@'` HeadField=`echo "$AllSecondary" | sed -e 's/;.*//' -e 's/^ //'` AllSecondary=`echo "$AllSecondary" | sed -e 's/[^;]*;//'` while [ -n "$HeadField" ]; do if [ "$Firebricks" = 'yes' ]; then HeadValue=$(StripOptionName $HeadField | sed -e "s/\"\([^\"]*\)\"/'\"\1\"'/") #Umm, MC miscounts the quotes unless I add a: " else HeadValue=$(StripOptionName $HeadField) fi case "$HeadField" in \)) #FIXME : Failed 'Stray right parentheses.' ;; ack:*) #ack: 0 #ack: 101058054 #ack:0 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/ack if [ "$DoAction" = "PACKIT" ]; then SecondaryPackit="$SecondaryPackit -a $HeadValue" else SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}4&0x1FFF=0 && 0>>22&0x3C@8=$HeadValue${StringTail}" fi ;; classtype:*) #classtype: attempted-admin #classtype: attempted-dos #classtype: successful-admin #classtype:attempted-admin #classtype:attempted-dos #classtype:attempted-recon #classtype:attempted-user #classtype:bad-unknown #classtype:not-suspicious #classtype:unknown #classtype:unsuccessful-user #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/classtype Comment="$Comment $HeadField" ;; content:*) #FIXME #alert tcp any 110 -> any any (msg:"Virus - Possible PrettyPark Trojan"; content:"\\C*o*o*l*P*r*o*g*s\\";offset:300;depth:750; reference:MCAFEE,10175; sid:772; rev:1;) #gives #iptables -A SnortRules -p tcp --sport 110 -m string --string CoolProgs"' -j DROP #Try `iptables -h' or 'iptables --help' for more information. #Bad argument `-' #FIXME #alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"WEB-MISC http directory traversal"; flags: A+; content: ".*.*\*\";reference:arachnids,298; classtype:attempted-recon; sid:1112; rev:1;) #gives #iptables -A SnortRules -p tcp -s 0/0 -d 0/0 --dport 80 --tcp-flags ACK ACK -m string --string .."' -j DROP #Try `iptables -h' or 'iptables --help' for more information. #Bad argument `http' #FIXME #alert tcp $EXTERNAL_NET any -> $HTTP_SERVERS 80 (msg:"WEB-MISC ads.cgi command execution attempt"; flags:A+; uricontent:"/a*d*s*.*c*g*i"; nocase; conten #t:"f*i*l*e*="; nocase; content:"../../"; content:"\*|"; reference:cve,CAN-2001-0025; reference:bugtraq,2103; classtype:web-application-attack; sid:1053 #; rev:3;) #gives #--string " #and #Unhandled character " #content: "..." #content:"..." # | toggles to and from hex. #Ignore embedded spaces in hex strings. #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/content if [ -z "$DashMStringLoaded" ]; then SecondaryParams="$SecondaryParams -m string" DashMStringLoaded="Yes" fi if `echo $HeadValue | grep -q '|'`; then #Failed "$HeadField" Payload=`ToAscii "$HeadValue"` SecondaryParams="$SecondaryParams --string $Payload" SecondaryPackit="$SecondaryPackit -p $Payload" else #Ce n'est pas un pipe... :-) SecondaryParams="$SecondaryParams --string $HeadValue" SecondaryPackit="$SecondaryPackit -p $HeadValue" fi ;; depth:*) #depth: nnn #depth:nnn #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/depth : #We won't care about depth on the assumption that this is an optimization. ;; distance:*) #distance:0-1024 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/distance : #We won't care about distance on the assumption that this is an optimization. ;; dsize:*) #dsize: nnn or > nnn or < nnn #dsize:nnn or > nnn or < nnn #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/dsize #FIXME - convert over to u32 #OK, we _can_ do this with nothing more than u32. :-) #To test that the payload size is exactly N: # - use U32 to test that byte N exists and \! byte N+1 exists #To test that the payload size is >N: # - Use u32 to test that byte N+1 exists. #To test that the payload size is 65535|\>\ 65535) Failed "Illegal dsize >65535" ;; \>*) SecondaryParams="$SecondaryParams -m dsize --dsize $[ $(echo "$HeadValue" | sed -e 's/>//') + 1 ]:" ;; \<0|\<\ 0) Failed "Illegal dsize <0" ;; \<*) SecondaryParams="$SecondaryParams -m dsize --dsize :$[ $(echo "$HeadValue" | sed -e 's/>/usr/src/snort2iptables-work/fields/flags FlagA='' ; FlagF='' ; FlagP='' ; FlagS='' ; FlagR='' ; FlagU='' ; Flag1='' ; Flag2='' ; FlagPlus='' HeadByte=`echo "$HeadValue" | sed -e 's/^\(.\).*/\1/'` HeadValue=`echo "$HeadValue" | sed -e 's/^.//'` while [ -n "$HeadByte" ]; do case "$HeadByte" in 0) FlagA='' ; FlagF='' ; FlagP='' ; FlagS='' ; FlagR='' ; FlagU='' ; Flag1='' ; Flag2='' ; FlagPlus='' ;; A|a) FlagA="ACK" ;; F|f) FlagF="FIN" ;; P|p) FlagP="PSH" ;; S|s) FlagS="SYN" ;; R|r) FlagR="RST" ;; U|u) FlagU="URG" ;; 1) #Failed Flag1 Flag1="1" ;; 2) #Failed Flag2 Flag2="2" ;; +) FlagPlus="Yes" ;; ,) : ;; *) Debug Invalid HeadByte in flags: "$HeadByte" ;; esac HeadByte=`echo "$HeadValue" | sed -e 's/^\(.\).*/\1/'` HeadValue=`echo "$HeadValue" | sed -e 's/^.//'` done #$FlagA $FlagF $FlagP $FlagS $FlagR $FlagU $Flag1 $Flag2 if [ -n "$FlagA$FlagF$FlagP$FlagS$FlagR$FlagU" ]; then SecondaryPackit="$SecondaryPackit -F $FlagA$FlagF$FlagP$FlagS$FlagR$FlagU" AllFlags="" for OneFlag in $FlagA $FlagF $FlagP $FlagS $FlagR $FlagU ; do if [ -z "$AllFlags" ]; then AllFlags="$OneFlag" else AllFlags="$AllFlags,$OneFlag" fi done else AllFlags="NONE" fi if [ "$FlagPlus" = "Yes" ]; then SuperSet="$AllFlags" else SuperSet="ALL" fi if [ "$AllFlags" != "NONE" ] || [ "$SuperSet" != "NONE" ]; then SecondaryParams="$SecondaryParams --tcp-flags $SuperSet $AllFlags" fi #FIXME - PACKIT bit 1 and 2 handling case "$Flag1$Flag2" in '') : ;; 1) SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}4&0x1FFF=0 && 0>>22&0x3C@10&0xC0=0x80${StringTail}" ;; 2) SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}4&0x1FFF=0 && 0>>22&0x3C@10&0xC0=0x40${StringTail}" ;; 12) SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}4&0x1FFF=0 && 0>>22&0x3C@10&0xC0=0xC0${StringTail}" ;; esac ;; flow:to_server|flow:established,to_server|flow:to_server,established|flow:to_Server,established|flow:to_server,established,nostream) #Ignored. With the exception of sid 562, 1432, 541, 557, 556, the packets are heading that direction anyways. : ;; flow:to_client|flow:to_client,established|flow:established,to_client) #Ignored. The only to_client line has packets flowing to the client. #What's the point of flow, again? : ;; flow:from_server|flow:established,from_server|flow:from_server,established) #Ignored. The from_server lines have packets flowing from the server, with the possible exception of sid 552, excepted below. : ;; flow:from_client,established) : ;; flow:established) #Ignored. The from_server lines have packets flowing from the server, with the possible exception of sid 552, excepted below. SecondaryParams="$SecondaryParams -m state --state ESTABLISHED" ;; flow:*) echo "$HeadField" >>/usr/src/snort2iptables-work/fields/flow Failed "Unrecognized flow type $HeadValue" ;; fragbits:*) #fragbits: M #fragbits: M+ #fragbits:M #fragbits:MD #fragbits:R #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/fragbits case "$HeadValue" in M) #Of the three, _only_ the more fragments flag set. SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}3&0xE0=0x20${StringTail}" ;; M+) #More fragments set, perhaps others too (I just won't look at them) SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}3&0x20=0x20${StringTail}" ;; MD) #More and Don't fragment set, Reserved cleared SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}3&0xE0=0x60${StringTail}" ;; R) #Just R set, M and D clear. SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}3&0xE0=0x80${StringTail}" ;; *) #Add as needed. Failed "$HeadField" ;; esac ;; icmp_id:*) #The ICMP echo ID. #icmp_id: 0 #icmp_id: 1000 #icmp_id: 123 #icmp_id: 456 #icmp_id: 51201 #icmp_id: 666 #icmp_id: 666 #icmp_id: 667 #icmp_id: 668 #icmp_id: 669 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/icmp_id if [ "$DoAction" = "PACKIT" ]; then SecondaryPackit="$SecondaryPackit -N $HeadValue" else SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}4&0x1FFF=0 && 0>>22&0x3C@2&0xFFFF=$HeadValue${StringTail}" fi ;; icmp_seq:*) #icmp_seq: 0 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/icmp_seq if [ "$DoAction" = "PACKIT" ]; then SecondaryPackit="$SecondaryPackit -Q $HeadValue" else SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}4&0x1FFF=0 && 0>>22&0x3C@4&0xFFFF=$HeadValue${StringTail}" fi ;; icode:*) #As iptables wants these glued together in a single field, the #sed at the top of this function should have glued #"itype: mmm; icode: nnn;" together into itype: mmm/nnn; #icode: 0 #icode: 1 #icode:0 #icode:1 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/icode #FIXME - packit Failed "$HeadField" ;; id:*) #id: 39426 #id: 413 #id: 666 #id: 678 #id:13170 #id:242 #id:3868 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/id if [ "$DoAction" = "PACKIT" ]; then SecondaryPackit="$SecondaryPackit -n $HeadValue" else SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}2&0xFFFF=$HeadValue${StringTail}" fi ;; ip_proto:*) #ip_proto: 2 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/ip_proto SecondaryParams="$SecondaryParams --proto $HeadValue" SecondaryPackit="$SecondaryPackit -t $HeadValue" ;; ipopts:*) #ipopts: rr #ipopts: ssrr #ipopts:lsrr #ipopts:lsrre #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/ipopts if [ "$DoAction" = "PACKIT" ]; then Failed "$HeadField $HeadValue" else case "$HeadValue" in rr) SecondaryParams="$SecondaryParams -m ipv4options --rr" ;; ssrr) SecondaryParams="$SecondaryParams -m ipv4options --ssrr" ;; lsrr) SecondaryParams="$SecondaryParams -m ipv4options --lsrr" ;; lsrre) #What is this? Failed "$HeadField" ;; esac fi ;; itype:*) #itype:[ ]0-39 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/itype #FIXME #iptables -A SnortRules -p icmp -s 0/0 -d 0/0 -m ttl --ttl-eq 1 -m icmp --icmp-type 8 -j DROP #iptables v1.2.3: Can't specify TTL option twice #FIXME - -m icmp (incorrect removed) -p icmp _not_ put in, on the logic #that -p icmp is probably at the beginning of the line. SecondaryParams="$SecondaryParams --icmp-type $HeadValue" #FIXME - packit ;; msg:*) #msg:[ ]"..." text description #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/msg Comment="$Comment $HeadValue" ;; nocase) #nocase (verbatim) #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/nocase #FIXME - handle more gracefully if true ; then Comment="$Comment nocase-ignored" else Failed "$HeadField" fi ;; offset:*) #offset:[ ]0-300 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/offset : #We won't care about offset on the assumption that this is an optimization. ;; rawbytes) #rawbytes #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/rawbytes : #This just stops preprocessing, which we don't do anyways. ;; reference:*) #reference:[ ]ascii, no quotes #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/reference #case "$HeadValue" in #cve*) # if [ -z "$LogString" ]; then # LogString=`echo "$HeadValue" | sed -e 's/cve, /cve-/' -e 's/cve,/cve-/'` # else # LogString="$LogString,"`echo "$HeadValue" | sed -e 's/cve, /cve-/' -e 's/cve,/cve-/'` # fi # ;; #*) Comment="$Comment $HeadValue" # ;; #esac ;; rev:*) #rev:[ ]1-4 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/rev #Don't really care about the revision number. : ;; rpc:*) #rpc: 100000,*,* #rpc:100249,*,* #rpc:391029,*,* #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/rpc Failed "$HeadField" ;; sameip) #sameip (verbatim) #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/sameip Failed "$HeadField" #FIXME - packit ;; seq:*) #seq: 101058054 #seq: 1958810375 #seq: 3868 #seq: 6060842 #seq: 674711609 #seq:0 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/seq if [ "$DoAction" = "PACKIT" ]; then SecondaryPackit="$SecondaryPackit -q $HeadValue" else SecondaryParams="$SecondaryParams -m u32 --u32 ${StringLead}4&0x1FFF=0 && 0>>22&0x3C@4=$HeadValue${StringTail}" fi ;; sid:*) #Debug $HeadValue #FIXME - packit handling? case $HeadValue in 152|661|688|772|1112|1613|1423) Failed "Mishandled quotes" ;; #Possibly fixed; there may still be ordering requirements that #the icmp-type come before ttl. #385) # Failed "Can't specify TTL option twice" # ;; #FIXME: sid 1053 has "*\*|*" for content, comes out as unhandled character " 1053) Failed "misconvert on quoted pipe: $HeadField" ;; 1333) Failed "misconvert in content semicolon: $HeadField" ;; #FIXME: sid 1368 has "*/*b*i*n*/*l*s*|*" for content, similar unhandled " 1368) Failed "misconvert in content pipe: $HeadField" ;; 562|1432|541|557|556) Failed "Sid $HeadValue has unspecified ports and a flow:to_server" ;; 552) Failed "Sid $HeadValue is too vague for a flow:from_server" ;; 1384) Failed 'Mishandled asterisk' ;; 385) Failed "Can't specify TTL option twice, mis-error." #iptables -A SnortRules -p icmp -s 0/0 -d 0/0 -m ttl --ttl-eq 1 --icmp-type 8 -j LOG --log-prefix ' SID385 ' #iptables v1.2.7: Can't specify TTL option twice ;; 326) Failed "Can't handle escaped semicolon correctly" ;; 1822|1823) Failed "1822/1823 screwed up pipe in string." ;; 2175|2177|2088|2089|1536|1879|1507|1510|1512|1514|1516|1613|2070) Failed "check backslash escaping." ;; #1393, 1631, 1632, 1633 Hmmm, multiple IP addresses aren't split. esac #sid:[ ]nnnn #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/sid if [ -z "$LogString" ]; then LogString="SID$HeadValue" else LogString="$LogString,SID$HeadValue" fi Comment="$Comment $HeadField" ;; ttl:*) #ttl: >220 #ttl:1 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/ttl case "$HeadValue" in \>*) SecondaryParams="$SecondaryParams -m ttl --ttl-gt $(echo "$HeadValue" | sed -e 's/>//')" SecondaryPackit="$SecondaryPackit -T 255" #OK, this is _sort of_ cheating. ;; \<*) if [ "$DoAction" = "PACKIT" ]; then Failed "TTL $HeadValue" else SecondaryParams="$SecondaryParams -m ttl --ttl-lt $(echo "$HeadValue" | sed -e 's/>/usr/src/snort2iptables-work/fields/uricontent if [ -z "$DashMStringLoaded" ]; then SecondaryParams="$SecondaryParams -m string" DashMStringLoaded="Yes" fi #FIXME - it _appears_ that uricontent is straight ascii only, '|' does not toggle hex. Is this the case? # if `echo $HeadValue | grep -q '|'`; then # #Failed "$HeadField" # Payload=`ToAscii "$HeadValue"` # SecondaryParams="$SecondaryParams --string $Payload" # SecondaryPackit="$SecondaryPackit -p $Payload" # else #Ce n'est pas un pipe... :-) SecondaryParams="$SecondaryParams --string $HeadValue" SecondaryPackit="$SecondaryPackit -p $HeadValue" # fi ;; within:*) #within:1-1024 #echo "$HeadField" >>/usr/src/snort2iptables-work/fields/within : #We won't care about within on the assumption that this is an optimization. ;; *) if [ -d /usr/src/snort2iptables-work/fields/ ]; then echo "$HeadField" >>/usr/src/snort2iptables-work/fields/newfields fi Failed "$HeadField" ;; esac HeadField=`echo "$AllSecondary" | sed -e 's/;.*//' -e 's/^ //'` AllSecondary=`echo "$AllSecondary" | sed -e 's/[^;]*;//'` done #SecondaryParams is passed back as a global variable } ProcessLine () { #Debug "$*" FailedReasons='' PrimaryFields=`echo "$*" | sed -e 's/ (.*//'` SecondaryFields=`echo "$*" | sed -e 's/[^(]* (//' -e 's/)$//'` PrimaryParams='' SecondaryParams='' ProcessPrimaryFields $PrimaryFields if [ -n "$PrimaryParams" ]; then ProcessSecondaryFields $SecondaryFields fi } OutputRule () { #Parameter 1 is the set of Primary parameters, Parameter 2 is the set of secondaries. if [ "$Firebricks" = 'yes' ]; then DoLog='' Head='$Ipt -A $Me' Tail='$Tail' else Head='iptables -A SnortRules' Tail='' fi if [ -n "$1$2" ]; then if [ -n "$FailedReasons" ]; then if [ "$DoLog" = "yes" ]; then if [ -n "$LogString" ]; then echo "#iptables -A SnortRules$1$2 -j LOG --log-prefix \" $LogString \" #Cannot convert:$FailedReasons $Comment" else echo "#iptables -A SnortRules$1$2 -j LOG #Cannot convert:$FailedReasons $Comment" fi fi if [ "$DoAction" = 'PACKIT' ]; then #FIXME - how to hand these in? echo "#packit$PrimaryPackit$SecondaryPackit #Cannot convert:$FailedReasons $Comment" elif [ -n "$DoAction" ]; then echo " #LogAs=\"$LogString\" $Head$1$2 $Tail #Cannot convert:$FailedReasons $Comment" fi else if [ "$DoLog" = "yes" ]; then if [ -n "$LogString" ]; then echo "iptables -A SnortRules$1$2 -j LOG --log-prefix \" $LogString \" #$Comment" else echo "iptables -A SnortRules$1$2 -j LOG #$Comment" fi fi if [ "$DoAction" = 'PACKIT' ]; then echo "packit$PrimaryPackit$SecondaryPackit #$Comment" elif [ -n "$DoAction" ]; then echo " LogAs=\"$LogString\" $Head$1$2 $Tail #$Comment" fi fi fi } ProcessFile () { #FIXME - recursion: local variables and file descriptor (case passed incrementing parameter) #Don't use includes for the moment. #Params: #- RecursionLevel (0 for top level file, 1 for file included from 0, 2 for file included in 1, etc.) #- Filename to process local RecursionLevel="$1" shift local OneLine local Params local WorkingFile WorkingFile="`echo $1`" if [ -z "$WorkingFile" ]; then Debug Missing filename in ProcessFile return 1 fi if [ ! -r "$WorkingFile" ]; then Debug File "$WorkingFile" is not readable return 1 fi #Read kludge: #Redirect stdin to work around an f^@&ing annoying limitation in the read command. case "$RecursionLevel" in 0) exec 5<&0 < "$WorkingFile" ;; 1) exec 6<&0 < "$WorkingFile" ;; 2) exec 7<&0 < "$WorkingFile" ;; 3) exec 8<&0 < "$WorkingFile" ;; *) echo Recursion level too deep in ProcessFile, exiting. >&2 exit 1 ;; esac while read OneLine ; do case "$OneLine" in \#*) : #Ignore comment lines ;; include*) Debug Processing `echo "$OneLine" | sed -e 's/include //' -e "s@\\$RULE_PATH@$RULE_PATH@"` ProcessFile $[ $RecursionLevel + 1 ] `echo "$OneLine" | sed -e 's/include //' -e "s@\\$RULE_PATH@$RULE_PATH@"` ;; preprocessor*) : #FIXME #Debug Skipping preprocessor ;; var\ RULE_PATH*) #Debug `echo $OneLine | sed -e 's/var RULE_PATH /export RULE_PATH=/'` eval `echo $OneLine | sed -e 's/var RULE_PATH /export RULE_PATH=/'` ;; var\ SHELLCODE_PORTS\ !80) echo "SHELLCODE_PORTS='! 80'" #FIXME - packit? ;; var\ SHELLCODE_PORTS*) Debug Unhandled SHELLCODE_PORTS variable - please notify wstearns@pobox.com. #FIXME - packit? ;; var*) echo "$OneLine" | sed -e 's/var //' -e 's/ *$//' -e 's/ /=/' -e 's@any@0/0@' #FIXME - packit? ;; alert\ ip\ *\ any\ -\>\ *\ :1023\ *) #Debug Special processing ip with port: "alert ip $EXTERNAL_NET any -> $HOME_NET :1023 ..." #call twice, once with tcp .... OneLine=`echo "$OneLine" | sed -e 's/ ip / tcp /'` ProcessLine "$OneLine" OutputRule "$PrimaryParams" "$SecondaryParams" #...and once with udp OneLine=`echo "$OneLine" | sed -e 's/ tcp / udp /'` ProcessLine "$OneLine" OutputRule "$PrimaryParams" "$SecondaryParams" ;; alert\ ip\ *\ any\ -\>\ *\ \$SHELLCODE_PORTS\ *|alert\ ip\ *\ \$SHELLCODE_PORTS\ -\>\ *\ any\ *) #Debug Special processing ip with port: "alert ip \$EXTERNAL_NET any -> \$HOME_NET \$SHELLCODE_PORTS ..." #call twice, once with tcp .... OneLine=`echo "$OneLine" | sed -e 's/ ip / tcp /'` ProcessLine "$OneLine" OutputRule "$PrimaryParams" "$SecondaryParams" #...and once with udp OneLine=`echo "$OneLine" | sed -e 's/ tcp / udp /'` ProcessLine "$OneLine" OutputRule "$PrimaryParams" "$SecondaryParams" ;; alert*\[*\]*msg*) #left paren, damn. $'\0x28', "(", "\(", and "\\(" don't work. #AddressBlock=`echo $OneLine | sed -e 's/.*\[/\\\[/' -e 's/\].*/\\\]/'` Addresses=`echo $OneLine | sed -e 's/.*\[//' -e 's/\].*//' -e 's/,/ /g'` #Debug "Multiple IP addresses \"$Addresses\" in brackets." for OneIP in $Addresses ; do #Debug Processing `echo $OneLine | sed -e "s@$AddressBlock@$OneIP@"` #ProcessLine `echo $OneLine | sed -e "s@$AddressBlock@$OneIP@"` #Debug Processing `echo $OneLine | sed -e 's@\[[^]]*\]@'"$OneIP@"` ProcessLine `echo $OneLine | sed -e 's@\[[^]]*\]@'"$OneIP@"` OutputRule "$PrimaryParams" "$SecondaryParams" done ;; *\<\>*) #call twice, once with -> .... OneLine=`echo "$OneLine" | sed -e 's/<>/->/'` ProcessLine "$OneLine" OutputRule "$PrimaryParams" "$SecondaryParams" #...and once with <- OneLine=`echo "$OneLine" | sed -e 's/->/<-/'` ProcessLine "$OneLine" OutputRule "$PrimaryParams" "$SecondaryParams" ;; *) #-\>|\<-) ProcessLine "$OneLine" OutputRule "$PrimaryParams" "$SecondaryParams" ;; esac done case "$RecursionLevel" in 0) exec 0<&5 5<&- ;; 1) exec 0<&6 6<&- ;; 2) exec 0<&7 7<&- ;; 3) exec 0<&8 8<&- ;; *) echo Invalid recursion level in ProcessFile, exiting. >&2 exit 1 ;; esac } usage () { echo $0 $Snort2IptablesVer >&2 echo This script is designed to convert a snort ruleset to iptables rules. >&2 echo Since it can recurse into included files, simply give it the top-level >&2 echo ruleset, such as /etc/snort/snort.conf . >&2 echo >&2 echo Usage: >&2 echo -e '\t' "$0" ' [--log] [--drop|--reject] [--packit|--firebricks] SnortRuleFile [SnortRuleFile...]' >&2 echo Example: >&2 echo -e '\t' "$0" ' --log /etc/snort/snort.conf' >&2 echo You should pick at least one of log, drop, or reject to get any output. >&2 echo Log can be mixed with drop or reject. >&2 echo Firebricks and packet do not mix with anything else. >&2 } if [ -z "$1" ]; then usage exit 1 fi DoLog='' DoAction='' StringLead="\"" StringTail="\"" while [ -n "$1" ]; do case "$1" in --[Ll][Oo][Gg]) if [ "$DoAction" = 'PACKIT' ]; then echo "Log and Packit cannot be used together, exiting." exit 1 fi DoLog='yes' ;; --[Dd][Rr][Oo][Pp]) DoAction='DROP' ;; --[Rr][Ee][Jj][Ee][Cc][Tt]) DoAction='REJECT' ;; --[Pp][Aa][Cc][Kk][Ii][Tt]) if [ -n "$DoLog" ]; then echo "Log and Packit cannot be used together, exiting." exit 1 fi DoAction='PACKIT' ;; --[Ff][Ii][Rr][Ee][Bb][Rr][Ii][Cc][Kk][Ss]) Firebricks='yes' StringLead="\'\"" StringTail="\"\'" DoAction='Firebricks' ;; *) if [ -r "$1" ]; then if [ -z "$DoLog$DoAction" ]; then echo 'Note: No action has been selected! Without a "--log", "--drop"' >&2 echo 'or "--reject" command line option, this program will not produce' >&2 echo 'any useful output.' >&2 fi ProcessFile 0 "$1" else echo Unable to read "$1", skipping. >&2 fi ;; esac shift done #FIXME PACKIT and firebricks documentation, no log + packit #DONE Handle " -> '" conversion in --string and --u32 for firebricks #FIXME hex 00 doesn't seem to be failing. Oops.