DNS Blackhole

Problem this snippet solves:

The blackhole requirement is to intercept DNS requests for prohibited FQDNs, not sent those to BIND for recursive look-up, return a DNS response with an A record to an LTM virtual server, and have a LTM virtual server with a second iRule that will log the request and serve a static page. The solution uses an iRule to the listener virtual server. This virtual server processes all GTM/BIND traffic. Incoming requests are matched against an external data group that contains a list of prohibited FQDNs. This data group file can be edited directly in the GUI at System - File Management - Data Group File List (line terminator should be LF only, not CR-LF). Alternately, the file can be edited manually and re-loaded by doing a "tmsh load sys config verify" then "tmsh load sys config". The blackhole iRule will log all requests for prohibited FQDNs and return a DNS response that matches an LTM virtual server. The blackhole iRule only provides valid responses for A records, however all blackhole DNS requests are logged.

How to use this snippet:

  • Create the list of FQDNs for the BIG-IP external data group. Example format is below. File can be stored in /config/Blackhole_Class, although v11.1 provides a method to upload via GUI which is recommended. The second field is the reason why the site was added to the Blackhole class.

    • ".3322.org" := "virus",
      ".3322.com" := "malware",
      ".3322.net" := "phishing",
      
  • Make the external file accessible

    • On Internal-GTM's GUI, go to System - File Management - Data Group File List - Import.
    • File Name: upload Blackhole_Class file
    • Name: Blackhole_Class
    • File Contents: String
    • Key / Value Pair Separator: :=
  • Create data group as external file:

    • On Internal-GTM's GUI, go to Local Traffic - iRules - Data Group List - Create
    • Name: Blackhole_Class
    • Type: (External File)
    • Path/Filename: Blackhole_Class
    • File Contents: string
    • Access Mode: Read Only
  • Create the Blackhole iRule

    • On Internal-GTM's GUI, go to Local Traffic - iRules - Create.
    • Name: DNS_blackhole (can be renamed)
    • iRule Source is attached below.
  • Apply the Blackhole iRule to the GTM listener virtual server.

    • Go to Local Traffic - Virtual Servers.
    • Click on the Virtual Server created automatically for the GTM listener. Name will be of form: vs_10_1_1_152_53_gtm, where the first 4 numbers are the IP address of the Listener.
    • Go to the resources tab and assign the DNS_Blackhole iRule created above.
  • Create iFile for the Organization's Logo that will be used with the block page.

    • Download a copy of the image to be used
    • Rename file to corp-logo.gif (optional)
    • Go to System - File Management - iFile List - Import
    • Name: corp-logo.gif
    • Go to Local Traffic - iRules - iFile List - Create
    • Name: corp-logo.gif
    • File name:
  • Create iRule to log client requests and send an HTML page to notify customers they have violated the Blackhole. This iRule should be copied from the attached file.

    • Go to Local Traffic - iRules - Create
    • Name: DNS_blackhole_block_page
    • iRule Source attached below.
  • Create virtual server for client requests. The IP address should match the DNS response defined in ::blackhole_reply in the DNS_Blackhole iRule.

    • Go to Local Traffic - Virtual Servers - Create
    • Name: DNS_blackhole_block_page
    • Destination: 10.1.1.80 (update per local requirements)
    • Port: 80
    • HTTP profile: http
    • iRules: DNS_blackhole_block_page

Code :

# DNS Blackhole
# This iRule interrogates all queries that arrive on the GTM listener.  If the query matches
# a list of prohibited FQDNs, a standardized response is given and the request is logged.  
# This response IP address could be a honeypot server or an LTM virtual server.
# Blackhole functionality can be used to prevent malware, virus C2 servers, adware, or other sites.
#
# Author:  Hugh O'Donnell, F5 Consulting
#
# Usage: 
#  1) apply to GTM listener virtual server that is defined at Local Traffic - Virtual Servers
#  2) create a string data group called "Blackhole_Class".  The FQDNs must start with a period.
#  3) update the answer static variable with the IP address to return
#
# Known Issues:
#  1) Only A and AAAA records are returned for blackhole requests. The response for other request
#     types will be logged and returned with no answer.
#
# Revision history:
#  12-11-2011: Initial Revision

when RULE_INIT {
# Set IPV4 address that is returned for Blackhole matches for A records
set static::blackhole_reply_IPV4 "10.1.1.26"
# Set IPV6 address that is returned for Blackhole matches for AAAA records
set static::blackhole_reply_IPV6 "2001:19b8:101:2::f5f5:1d"
# Set TTL used for all Blackhole replies
set static::blackhole_ttl "300"
}

when DNS_REQUEST {
# debugging statement see all questions and request details
    # log -noname local0. "Client: [IP::client_addr] Question:[DNS::question name] Type:[DNS::question type] Class:[DNS::question class] Origin:[DNS::origin]"

# Blackhole_Match is used to track when a Query matches the blackhole list
# Ensure it is always set to 0 or false at beginning of the DNS request
set Blackhole_Match 0

# Blackhole_Type is used to track why this FQDN was added to the Blackhole_Class
set Blackhole_Type ""

# When the FQDN from the DNS Query is checked against the Blackhole class, the FQDN must start with a 
# period.  This ensures we match a FQDN and all names to the left of it.  This prevents against
# malware that dynamically prepends characters to the domain name in order to bypass exact matches 
if {!([DNS::question name] == ".")} {
set fqdn_name .[DNS::question name]
}

if { [class match $fqdn_name ends_with Blackhole_Class] } {
# Client made a DNS request for a Blackhole site.
set Blackhole_Match 1
set Blackhole_Type [class match -value $fqdn_name ends_with Blackhole_Class ]

# Prevent processing by GTM, DNS Express, BIND and GTM Listener's pool.  
# Want to ensure we don't request a prohibited site and allow their server to identify or track the GTM source IP.
DNS::return
} 
}

when DNS_RESPONSE {
# debugging statement to see all questions and request details
# log -noname local0. "Request: $fqdn_name Answer: [DNS::answer] Origin:[DNS::origin] Status: [DNS::header rcode] Flags: RD [DNS::header rd] RA [DNS::header ra]"

if { $Blackhole_Match } {
# This DNS request was for a Blackhole FQDN. Take different actions based on the request type.
switch [DNS::question type] {
"A" {
# Clear out any DNS responses and insert the custom response.  RA header = recursive answer
DNS::answer clear
DNS::answer insert "[DNS::question name]. $static::blackhole_ttl [DNS::question class] [DNS::question type] $static::blackhole_reply_IPV4"
DNS::header ra "1"

# log example:  Apr  3 14:54:23 local/tmm info tmm[4694]:
#     Blackhole: 10.1.1.148#4902 requested foo.com query type: A class IN A-response: 10.1.1.60
log -noname local0. "Blackhole: [IP::client_addr]#[UDP::client_port] requested [DNS::question name] query type: [DNS::question type] class [DNS::question class] A-response: $static::blackhole_reply_IPV4 BH type: $Blackhole_Type"
}
"AAAA" {
# Clear out any DNS responses and insert the custom response.  RA header = recursive answer
DNS::answer clear
DNS::answer insert "[DNS::question name]. $static::blackhole_ttl [DNS::question class] [DNS::question type] $static::blackhole_reply_IPV6"
DNS::header ra "1"

# log example:  Apr  3 14:54:23 local/tmm info tmm[4694]:
#     Blackhole: 10.1.1.148#4902 requested foo.com query type: A class IN AAAA-response: 2001:19b8:101:2::f5f5:1d
log -noname local0. "Blackhole: [IP::client_addr]#[UDP::client_port] requested [DNS::question name] query type: [DNS::question type] class [DNS::question class] AAAA-response: $static::blackhole_reply_IPV6 BH type: $Blackhole_Type"
}
default {
# For other record types, e.g. MX, NS, TXT, etc, provide a blank NOERROR response
DNS::last_act reject

# log example:  Apr  3 14:54:23 local/tmm info tmm[4694]:
#     Blackhole: 10.1.1.148#4902 requested foo.com query type: A class IN unable to respond
log -noname local0. "Blackhole: [IP::client_addr]#[UDP::client_port] requested [DNS::question name] query type: [DNS::question type] class [DNS::question class] unable to respond  BH type: $Blackhole_Type"
}
}
}
}

# DNS Blackhole Block Page
#
# This iRule presents an HTML page to the user and logs details of the HTML request.
# It is used in conjuction with the DNS Blackhole iRule to ensure users are aware that
# their request was blocked by the Blackhole and actions to take if this block was in error.
#
# Author:  Hugh O'Donnell, F5 Consulting
#
# Usage: 
#  1) Add the corporate logo that will be displayed to System - File Management - iFile List
#  2) Make the iFile accessible to be used in this iRule by giving associating the iFile name corp_logo_gif
#     with the iFile at:  Local Traffic - iRules - iFile List.
#  3) Ensure list of FQDNs in the DNS Blackhole is in a data group called "Blackhole_Class". This can be
#     verified at Local Traffic - iRules - Data Group List.
#  4) Apply this iRule to the port 80 virtual server that the Blackhole iRule sends out, which is specified in variables
#     static::blackhole_reply_IPV4 and static::blackhole_reply_IPV6 in v11.1+ version of Blackhole iRule

when HTTP_REQUEST {

# the static HTML pages include the logo that is referenced in HTML as corp-logo.gif
# intercept requests for this and reply with the image that is stored in an iFile defined in RULE_INIT below
if {[HTTP::uri] ends_with "/_maintenance-page/corp-logo.gif" } {
# Present 
       HTTP::respond 200 content $static::corp_logo_gif

} else {
# Request for Blackhole webpage.  Identify what type of block was in place
switch -glob [class match -value ".[HTTP::host]" ends_with Blackhole_Class ] {
"virus" { set block_reason "Virus site" }
"phishing" { set block_reason "Phishing site" }
"generic" { set block_reason "Unacceptable Usage" }
default { set block_reason "Denied Per Policy - Other Sites" }
}

# Log details about the blackhole request to the remote syslog server
log -noname local0. "Blackhole: From [IP::client_addr]:[TCP::client_port] \
  to [IP::local_addr]:[TCP::local_port], [HTTP::request_num], \
  [HTTP::method],[HTTP::uri],[HTTP::version], [HTTP::host],  [HTTP::header value Referer], \
  [HTTP::header User-Agent], [HTTP::header names],[HTTP::cookie names], BH category: $block_reason,"

# Send an HTML page to the user.  The page is defined in the RULE_INIT event below
HTTP::respond 200 content "$static::block_page [HTTP::host][HTTP::uri] $static::after_url $block_reason $static::after_block_reason "
}
}


when RULE_INIT {
# load the logo that was stored as an iFile
set static::corp_logo_gif [ifile get "/Common/corp-logo.gif"]

# Beginning of the block page 
set static::block_page "


Web Access Denied - Enterprise Network Operations Center
 

        
        



\"Enterprise

Access has been denied.

URL: " set static::after_url "


Your request was denied because it is blacklisted in DNS.

Blacklist category: " set static::after_block_reason "

The Internet Gateways are for official use only. Misuse violates policy. If you believe that this site is categorized incorrectly, and that you have a valid business reason for access to this site please contact your manager for approval and the Enterprise Network Operations Center via

E-mail: enoc@example.com

Please use the Web Access Request Form and include a business justification.   Only e-mail that originates from valid internal e-mail addresses will be processed. If you do not have a valid e-mail address, your manager will need to submit a request on your behalf.

Generated by bigip1.f5.com.

" }
Published Mar 17, 2015
Version 1.0