ASM Advanced WAF
23 TopicsASM/WAF rate limit and block clients by source ip (or device_id fingerprint) if there are too many violations
Problem this snippet solves: For attackers that cause too many violations it is better to block them at the start of the HTTP_REQUEST or CLIENT_ACCEPTED events as the ASM/WAF processing causes too much CPU and the CLIENT_ACCEPTED and HTTP_REQUEST events are before the ASM and the ASM irule events and this may help the CPU a little. In additon many services offer generation of device id/fingerprint like apple for ios or google for android or TMIXID for web or phone with SDK(https://money.tmx.com/en/quote/ID) , that is saved in HTTP header (https://clouddocs.f5.com/api/irules/HTTP__header.html) and you can make irule to use the HTTP header as a fingerprint value and just modify the irule code below! How to use this snippet: You can use this code with the variables set static::maxReqs 3 and set static::timeout 60 also in the code also the CLIENT_ACCEPTED event can be uncommented and the HTTP_REQUEST event coommented but if the clients come by using a NAT device before the F5 better use the X-Forwarded-For header to get the client ip address and rate limit by it. Also the data group ip_whitelist needs to be created maybe for not blocking vunrability tests and etc. Also the event ASM_REQUEST_DONE and [ASM::status] equals "blocked" can be replaced with ASM_REQUEST_BLOCKING but when ASM_REQUEST_BLOCKING happens after them, so this is why I use ASM_REQUEST_DONE and [ASM::status] equals "blocked" I got the idea from: https://clouddocs.f5.com/training/community/irules/html/class2/module1/lab2.html https://support.f5.com/csp/article/K07150625 Don't forget to enable the asm irule events under the security policy that Virtual server where the irule is attached uses (I forgot and it took me 30 minutes of testing to figure it out 😞 😞 https://techdocs.f5.com/kb/en-us/products/big-ip_asm/manuals/product/asm-implementations-11-6-0/29.html Also the irule can be modified to use a subtable "table set -subtable asm_violations" for example for the RAM memory limits and also the table ans subtable are shared between VIP servers, so another VIP even without ASM policy can check the same table or subtable and block the user. If you add the between line 15 and 32 to another VS even without ASM the VS will check the RAM table and block users by source ip that tried to attack the VS with the ASM policy. https://clouddocs.f5.com/api/irules/table.html https://devcentral.f5.com/s/articles/v101-the-table-command-subtables This way you can use the primary VS as a honeypot that allows all the other servers to learn the bad client's activities. For how to redirect to a honeypot VS with the ASM policy you can check: https://devcentral.f5.com/s/question/0D51T00008aWVfBSAW/what-is-f5-asm-conviction-and-can-it-be-used-for-configuring-custom-url-honey-pot-trap For blocking with the device_id fingerprint see the comment section. The fingerprint device id lookup may return "0" and then the lookup will failover to using source ip addresses. Also the new example uses 2 different subtables for device id or client ip addresses. In additon many services offer generation of device id/fingerprint like apple for ios or google for android or TMIXID for web or phone with SDK(https://money.tmx.com/en/quote/ID) , that is saved in HTTP header and you can make irule to use the HTTP header (https://clouddocs.f5.com/api/irules/HTTP__header.html) as a fingerprint value and just modify the irule code below! Code : when RULE_INIT { # The max requests served within the timing interval per the static::timeout variable set static::maxReqs 3 # Timer Interval in seconds within which only static::maxReqs Requests are allowed. # (i.e: 10 req per 2 sec == 5 req per sec) # If this timer expires, it means that the limit was not reached for this interval and # the request counting starts over. Making this timeout large increases memory usage. # Making it too small negatively affects performance. set static::timeout 60 } #when CLIENT_ACCEPTED { #set cIP_addr [IP::client_addr] # set getcount [table lookup -notouch $cIP_addr] # if { ! ( [class match $cIP_addr equals ip_whitelist] ) } { # The following expects the IP addresses in multiple X-forwarded-for headers. # It picks the first one. If XFF isn’t defined it can grab the true source IP. # if { $getcount > $static::maxReqs } { # log local0. "Request Count for $cIP_addr is $getcount" # drop # } # } #} when HTTP_REQUEST { # Allows throttling for only specific URIs. List the URIs_to_throttle in a data group. # Note: a URI is everything after the hostname: e.g. /path1/login.aspx?name=user if { [HTTP::header exists X-forwarded-for] } { set cIP_addr [getfield [lindex [HTTP::header values X-Forwarded-For] 0] "," 1] } else { set cIP_addr [IP::client_addr] } set getcount [table lookup -notouch $cIP_addr] if { ! ( [class match $cIP_addr equals ip_whitelist] ) && ( [class match [HTTP::uri] equals URIs_to_throttle] )} { # The following expects the IP addresses in multiple X-forwarded-for headers. # It picks the first one. If XFF isn’t defined it can grab the true source IP. if { $getcount > $static::maxReqs } { log local0. "Request Count for $cIP_addr is $getcount" # drop HTTP::respond 403 content { Your HTTP requests are being blocked because of too many violations. } } } } #when ASM_REQUEST_BLOCKING { when ASM_REQUEST_DONE { #log local0.debug "\[ASM::status\] = [ASM::status]" if { [ASM::status] equals "blocked" } { # Allows throttling for only specific URIs. List the URIs_to_throttle in a data group. # Note: a URI is everything after the hostname: e.g. /path1/login.aspx?name=user1 # if { [class match [HTTP::uri] equals URIs_to_throttle] } { # The following expects the IP addresses in multiple X-forwarded-for headers. # It picks the first one. If XFF isn’t defined it can grab the true source IP. # set getcount [table lookup -notouch $cIP_addr] if { $getcount equals "" } { table set $cIP_addr "1" $static::timeout $static::timeout # Record of this session does not exist, starting new record # Request is allowed. } else { log local0. "Request Count for $cIP_addr is $getcount" table incr -notouch $cIP_addr # record of this session exists but request is allowed. } # } } } Tested this on version: 15.14.3KViews0likes4CommentsHTTP iApp - downloadable version
Problem this snippet solves: This F5 Contributed iApp template is an update to the HTTP iApp that ships by default with the BIG-IP system. v1.3.0rc1 v1.3.0rc1 of the HTTP template contains no new features or visible changes, but removes a substantial amount of code from the iApp that was included to ease the transition from BIG-IP versions 11.3 to 11.4. 1.3.0rc1 is available in the Release Candidate directory of the iApp package on downloads.f5.com. The associated deployment guide can be found at http://f5.com/pdf/deployment-guides/esd-iapp-http-dg.pdf. v1.2.0 The officially supported version of http.v1.2.0 has been released to downloads.f5.com in the root HTTP directory. This version contains all of the changes in the Release Candidates. See the README file in that directory for more details. The associated deployment guide can now be found at http://f5.com/pdf/deployment-guides/esd-iapp-http-dg.pdf. v1.2.0rc5 http.v1.2.0rc5 is available on downloads.f5.com in the RELEASE CANDIDATE directory. This version the ability to select and apply any LTM policy present on BIG-IP to the virtual server(s) created by the iApp. This new section only appears in Advanced mode. It also fixes an issue that would result in an error state when trying to deploy the iApp for ASM in BIG-IP versions 12.1 and later. Instructions can be found at http://f5.com/pdf/deployment-guides/rc-iapp-http-dg.pdf v1.2.0rc4 v1.2.0rc4 of the HTTP iApp is available on downloads.f5.com in the RELEASE CANDIDATE directory. This version contains the ASM fix mentioned in RC3 and also contains a fix with address tranlation being disabled if the iApp was configured to not use a pool. Instructions can be found at http://f5.com/pdf/deployment-guides/rc-iapp-http-dg.pdf v1.2.0rc3 v1.2.0rc3 of the HTTP iApp adds the ability to use ASM in the configuration if you are using BIG-IP version 12.0 or later. The version v1.2.0rc2 that was previously posted on this page did not include this feature, but incorrectly claimed it did. v1.1.0 v1.1.0 of the HTTP iApp template includes the ability to choose a pre-existing BIG-IP Access Policy Manager (APM) Access Policy, as well as an updated BIG-IP Advanced Firewall Manager (AFM) section. This template was previously named HTTP Backport (most recently f5.http_backport.v1.0.4. This is a new codeshare page to host the f5.http.v1.1.0 template (note that this iApp is unchanged from the version posted on the HTTP backport codeshare page: https://devcentral.f5.com/codeshare/http-backport-a-variation-of-f5http-delivered-with-tmos). New F5 contributed versions of the HTTP template will be posted here. Code : https://downloads.f5.com/esd/product.jsp?sw=BIG-IP&pro=iApp_Templates1.7KViews0likes14CommentsExample OWASP Top 10-compliant declarative WAF policy
Problem this snippet solves: This is an example of a basic declarative BIG-IP WAF policy that is OWASP Top 10-compliant. This policy can be used as a starting point for a production-ready version. A companion Dev Central article discussing this policy in depth can be found here. How to use this snippet: This policy can be deployed through an AS3 declaration to the BIG-IP. Code : { "policy": { "name": "Complete_OWASP_Top_Ten", "description": "A basic, OWASP Top 10 protection items v1.0", "template": { "name": "POLICY_TEMPLATE_RAPID_DEPLOYMENT" }, "enforcementMode":"transparent", "protocolIndependent": true, "caseInsensitive": true, "general": { "trustXff": true }, "signature-settings":{ "signatureStaging": false, "minimumAccuracyForAutoAddedSignatures": "high" }, "blocking-settings": { "violations": [ { "alarm": true, "block": true, "description": "ASM Cookie Hijacking", "learn": false, "name": "VIOL_ASM_COOKIE_HIJACKING" }, { "alarm": true, "block": true, "description": "Access from disallowed User/Session/IP/Device ID", "name": "VIOL_SESSION_AWARENESS" }, { "alarm": true, "block": true, "description": "Modified ASM cookie", "learn": true, "name": "VIOL_ASM_COOKIE_MODIFIED" }, { "name": "VIOL_LOGIN_URL_BYPASSED", "alarm": true, "block": true, "learn": false }, { "alarm": true, "block": true, "description": "XML data does not comply with format settings", "learn": true, "name": "VIOL_XML_FORMAT" }, { "name": "VIOL_FILETYPE", "alarm": true, "block": true, "learn": true }, { "name": "VIOL_URL", "alarm": true, "block": true, "learn": true }, { "name": "VIOL_URL_METACHAR", "alarm": true, "block": true, "learn": true }, { "name": "VIOL_PARAMETER_VALUE_METACHAR", "alarm": true, "block": true, "learn": true }, { "name": "VIOL_PARAMETER_NAME_METACHAR", "alarm": true, "block": true, "learn": true } ], "evasions": [ { "description": "Bad unescape", "enabled": true, "learn": true }, { "description": "Apache whitespace", "enabled": true, "learn": true }, { "description": "Bare byte decoding", "enabled": true, "learn": true }, { "description": "IIS Unicode codepoints", "enabled": true, "learn": true }, { "description": "IIS backslashes", "enabled": true, "learn": true }, { "description": "%u decoding", "enabled": true, "learn": true }, { "description": "Multiple decoding", "enabled": true, "learn": true, "maxDecodingPasses": 3 }, { "description": "Directory traversals", "enabled": true, "learn": true } ] }, "session-tracking": { "sessionTrackingConfiguration": { "enableTrackingSessionHijackingByDeviceId": true } }, "urls": [ { "name": "/trading/auth.php", "method": "POST", "protocol": "https", "type": "explicit" }, { "name": "/internal/test.php", "method": "GET", "protocol": "https", "type": "explicit", "isAllowed": false }, { "name": "/trading/rest/portfolio.php", "method": "GET", "protocol": "https", "type": "explicit", "urlContentProfiles": [ { "headerName": "Content-Type", "headerValue": "text/html", "type": "json", "contentProfile": { "name": "portfolio" } }, { "headerName": "*", "headerValue": "*", "type": "do-nothing" } ] } ], "login-pages": [ { "accessValidation": { "headerContains": "302 Found" }, "authenticationType": "form", "passwordParameterName": "password", "usernameParameterName": "username", "url": { "name": "/trading/auth.php", "method": "POST", "protocol": "https", "type": "explicit" } } ], "login-enforcement": { "authenticatedUrls": [ "/trading/index.php" ] }, "brute-force-attack-preventions": [ { "bruteForceProtectionForAllLoginPages": true, "leakedCredentialsCriteria": { "action": "alarm-and-blocking-page", "enabled": true } } ], "csrf-protection": { "enabled": true }, "csrf-urls": [ { "enforcementAction": "verify-csrf-token", "method": "GET", "url": "/trading/index.php" } ], "data-guard": { "enabled": true }, "xml-profiles": [ { "name": "Default", "defenseAttributes": { "allowDTDs": false, "allowExternalReferences": false } } ], "json-profiles": [ { "name": "portfolio" } ], "policy-builder-server-technologies": { "enableServerTechnologiesDetection": true } } } Tested this on version: 16.01.6KViews4likes4CommentsBlock IP Addresses With Data Group And Log Requests On ASM Event Log
Problem this snippet solves: This is Irule which will block IP Addresses that are not allowed in your organization. instead of adding each IP Address in Security ›› Application Security : IP Addresses : IP Address Exceptions you can create a data group and use a simple IRULE to block hundreds of Addressess. Also,createing a unique signature to specify the request of the illigile IP Address. First, You will need to create Data Group under Local Traffic ›› iRules : Data Group List and add your illigile IP Addresses to the list. If you have hundreds of IP's that you want to block, you can to it in TMSH using this command: TMSH/modify ltm data-group internal <Data-Group-Name> { records add {IP-ADDRESS} } Now, We are ready to create the IRULE under Local Traffic ›› iRules : iRule List Last, Create violation list under Security ›› Options : Application Security : Advanced Configuration : Violations List Create -> Name:Illegal_IP_Address -> Type:Access Violation -> Severity:Critical -> Update Don't forgat to enable trigger ASM IRULE events with "Normal Mode" How to use this snippet: Code : when HTTP_REQUEST { set reqBlock 0 if { [class match [IP::remote_addr] equals ] } { set reqBlock 1 # log local0. "HTTP_REQUEST [IP::client_addr]" } } when ASM_REQUEST_DONE { if { $reqBlock == 1} { ASM::raise "Illegal_IP_Address" # log local0. "ASM_REQUEST_DONE [IP::client_addr]" } } Tested this on version: 13.01.5KViews1like5CommentsiRule for Brute Force Password Guessing Attacks
Problem this snippet solves: Here I'm introducing an iRule for use as Brute Force Password Guessing Protection. If you are running on 12.1.x you could solve this kind of attacks using ASM https://support.f5.com/csp/article/K54335130 But this release it's also affected for one bug that could lead in some unwanted behavior. The idea beneath this iRule is to workaround this bug and give a solution for those users that still run an ASM module on 12.1.x. How to use this snippet: This iRule requires a Data-Group to reference all the login pages in your infrastructure. ltm data-group internal LOGIN_URI_DG { records { /app1/login { } /app2/login { } } type string } In the STREAM expression you should identify how a login failure attempt is represented in your HTTP response. In my case, all login failures are represented by one JSON structure with the word "ACCESS_FAIL" in some of their fields. STREAM::expression {=ACCESS_FAIL=} You should also customize these global variables. set static::maxtry 5 ; # Maximum failed login attempts set static::time 900 ; # Time window for failed login attempts evaluation [seconds] set static::bantime 3600 ; # How long the user is banned [seconds] Code : when RULE_INIT { # Variable Definition set static::maxtry 5 ; # Maximum failed login attempts set static::time 900 ; # Time window for failed login attempts evaluation [seconds] set static::bantime 3600 ; # How long the user is banned [seconds] } when CLIENT_ACCEPTED priority 100 { # Get client IP set source_ip [IP::remote_addr] # Reject user if exists in blacklist if { [table lookup -subtable "blacklist" $source_ip] != "" } { reject } } when HTTP_REQUEST priority 100 { event HTTP_RESPONSE enable event STREAM_MATCHED enable STREAM::disable set login_access 0 # Enable login access flag if { [class match [string tolower [HTTP::uri] ] contains LOGIN_URI_DG ] } { set login_access 1 } } when HTTP_RESPONSE priority 100 { if { $login_access } { # Configure stream expression when response is JSON if {[HTTP::header value Content-Type] starts_with "application/json"} { STREAM::expression {=ACCESS_FAIL=} STREAM::enable } event HTTP_RESPONSE disable } } when STREAM_MATCHED priority 100 { if { $login_access } { # Increase variable of login attempts set key "attempts:$source_ip" table add $key 0 indefinite $static::time set count [table incr $key] # If maximum login attempts value is exceeded, then include user in blacklist if { $count >= $static::maxtry } { table add -subtable "blacklist" $source_ip "blocked" indefinite $static::bantime table delete $key log local1. "User Rejected: $source_ip" } event STREAM_MATCHED disable } } Tested this on version: 12.11.2KViews2likes1CommentAWS Advanced HA iApp
Problem this snippet solves: SUMMARY For customers who want to deploy Public Internet-facing services on a traditional High Availability pair but also leverage the benefits of AWS’s Availability Zones, we introduce the AWS Advanced HA iApp. The AWS Advanced HA iApp helps facilitate the HA Across AZs deployment, an additional deployment option to those discussed in: F5 in AWS Part 1 - AWS Networking Basics F5 in AWS Part 2 - Running BIG-IP in an EC2 Virtual Private Cloud F5 in AWS Part 3 - Advanced Topologies and More on Highly Available Services As well as enable traditional HA pairs to perform basic route management of AWS route tables (whether in “Single AZ” or the “Across AZ” deployments introduced above). By having the Active BIG-IP take ownership of your client’s or application’s “default” or specific routes, this enables: Virtual Servers to avoid using SNAT* Manage Access Traffic (ex. point clients/servers to BIG-IP APM VPN for specific on-prem networks)* Facilitate various outbound proxy use cases (NAT, URI filtering, etc) For more information, see the Deployment Guide Minimum required BIG-IP version: 12.1.0 HF2. Supported BIG-IP versions: 12.1.0 HF2. Various Cloud Formation Templates that automate the installation of this solution are available on github. They are listed under the naming format "full-stack-across-az-cluster-*" We recommend you first start with "full-stack-" examples to get a fully functioning reference deployment and the "existing-*" ones once you're comfortable with the general requirements (security groups, route tables, etc). RELEASES v1.0.0 iApp template to configure HA Across Availability Zones in AWS by managing EIP mappings. v1.0.1 iApp template to configure HA Across Availability Zones in AWS by managing EIP and route mappings. v1.0.1rc1 This version 1.0.1rc1 of the iApp template is available at downloads.f5.com. This official release candidate version contains the same functionality as the previous version (v1.0.1 on DevCentral), but the presentation has been updated. There is also a new F5 deployment guide to accompany the iApp template. Go to https://downloads.f5.com/esd/index.jsp.2. Click Find a Download. Click iApp Templates. Accept the EULA, and then download the iapps zip file to a location accessible from your BIG-IP system. Extract (unzip) the f5.aws_advanced_ha.v1.0.1rc1.tmpl file, found in the **_RELEASE CANDIDATE_** directory of the zip file. v1.1.0rc1 This version 1.1.0rc1 template is available for download on this page. F5 has released version 1.1.0rc1 of the iApp. This official release candidate version contains the same functionality as the previous version (v1.0.1rc1 on DevCentral), with further checks and support added for AWS EIP ownership. In previous versions, EIP ownership was indicated by which device owns the default floating traffic-group-1. As there was only one floating traffic group, this affected the global Active/Standby status of the devices. This iApp has been updated to validate EIP ownership after an active-active state scenario and ensures the current EIP owner is Active for traffic-group-1 (the traffic-group tied to the EIP mappings). CAVEATS Both BIG-IP devices will now report active for their global status, as each device will now own a device specific traffic group. The active status of the default floating traffic-group-1 will indicate which BIG-IP device is actively serving EIP production traffic. The BIG-IP hostname should not be changed during an active deployment of this iApp. This solution only supports the 3 traffic groups as described above. INSTALLATION If you are using a previous version of this iApp and need to update the application service with this updated iApp, there are two prerequisites before you run this iApp: Remove from the file /config/failover/active this line "python /config/failover/aws_advanced_failover.py” Delete the file /config/failover/aws_advanced_failover.py entirely Reparent the application service to use the new version of iApp template This new version will modify the following files in /config/failover on first configuration run tgactive (modify) tgstandby (modify) and install the following aws_af_tgactive aws_af_tgstandby aws_advanced_failover.py aws_advanced_failover.dat On subsequent reconfiguration, it updates the above 4 files listed. v1.2.0rc1 This version 1.2.0rc1 template is available for download on this page. F5 has released version 1.2.0rc1 of the iApp. This official release candidate version contains the same functionality as the previous version (v1.0.1rc1 on DevCentral, or v1.1.0rc1), with further checks and support added for AWS EIP ownership. In previous versions, EIP ownership was indicated by which device owns the default floating traffic-group-1. As there was only one floating traffic group, this affected the global Active/Standby status of the devices. This iApp has been updated to validate EIP ownership after an active-active state scenario and ensures the current EIP owner is Active. CAVEATS Noted caveats from v1.1.0rc1 removed. INSTALLATION If you are using a previous version of this iApp and need to update the application service with this updated iApp, there are a few prerequisites before you run this iApp: Remove from the file /config/failover/active this line "python /config/failover/aws_advanced_failover.py” Delete the file /config/failover/aws_advanced_failover.py entirely Delete the file /config/failover/aws_af_tgactive (if previous version is v1.1.0rc1) Delete the file /config/failover/aws_af_tgstandby (if previous version is v1.1.0rc1) Reparent the application service to use the new version of iApp template This new version will modify the following files in /config/failover on first configuration run tgactive (modify) tgstandby (modify) tgrefresh (modify) and install the following aws_advanced_failover.py aws_advanced_failover.dat On subsequent reconfiguration, it updates the above 2 files listed. Code : 708281.1KViews0likes3CommentsExporting and importing ASM/AWAF security policies with Ansible and Terraform
Problem this snippet solves: This ansible playbook and Terraform TF file can be ised to copy the test ASM policy from the dev/preproduction environment to the production environment as this is Continuous integration and continuous delivery. Ansible You use the playbook by replacing the vars with "xxx" with your F5 device values for the connection. Also with the "vars_prompt:" you add policy name during execution as the preprod policy name is "{{ asm_policy }}_preprod" and the prod policy name is "{{ asm_policy }}_prod". For example if we enter "test" during the policy execution the name will be test_prod and test_preprod. If using Ansible Tower with the payed version you can use Jenkins or bamboo to push variables (I still have not tested this). Also there is a task that deletes the old asm policy file saved on the server as I saw that the ansible modules have issues overwriting existing files when doing the export and the task name is "Ansible delete file example" and in the group "internal" I have added the localhost. https://docs.ansible.com/ansible/latest/collections/f5networks/f5_modules/index.html Also after importing the policy file the bug https://support.f5.com/csp/article/K25733314 is hit, so the last 2 tasks deactivate and and activate the production policy. A nice example that I based my own is: https://support.f5.com/csp/article/K42420223 You can also write the connections vars in the hosts file as per K42420223 vars: provider: password: "{{ bigip_password }}" server: "{{ ansible_host }}" user: "{{ bigip_username }}" validate_certs: no server_port: 443 Example hosts: [bigip] f5.com [bigip:vars] bigip_password=xxx bigip_username=xxx ansible_host=xxx The policy is exported in binary format otherwize there is an issue importing it after that "binary: yes". Also when importing the option "force: yes" provides an overwrite if there is a policy with the same name. See the comments for my example about using host groups with this way your dev environment can be on one F5 device and the exported policy from it will be imported on another F5 device that is for production. When not using ''all'' for hosts you need to use set_facts to only be propmpted once for the policy name and then this to be shared between plays. Code : --- - name: Exporting and importing the ASM policy hosts: all connection: local become: yes vars: provider: password: xxx server: xxxx user: xxxx validate_certs: no server_port: 443 vars_prompt: - name: asm_policy prompt: What is the name of the ASM policy? private: no tasks: - name: Ansible delete file example file: path: "/home/niki/asm_policy/{{ asm_policy }}" state: absent when: inventory_hostname in groups['internal'] - name: Export policy in XML format bigip_asm_policy_fetch: name: "{{ asm_policy }}_preprod" file: "{{ asm_policy }}" dest: /home/niki/asm_policy/ binary: yes provider: "{{ provider }}" - name: Override existing ASM policy bigip_asm_policy_import: name: "{{ asm_policy }}_prod" source: "/home/niki/asm_policy/{{ asm_policy }}" force: yes provider: "{{ provider }}" notify: - Save the running configuration to disk - name: Task - deactivate policy bigip_asm_policy_manage: name: "{{ asm_policy }}_prod" state: present provider: "{{ provider }}" active: no - name: Task - activate policy bigip_asm_policy_manage: name: "{{ asm_policy }}_prod" state: present provider: "{{ provider }}" active: yes handlers: - name: Save the running configuration to disk bigip_config: save: yes provider: "{{ provider }}" Tested this on version: 13.1 Edit: -------------- When I made this code there was no official documentation but now I see F5 has provided examples for exporting and importing ASM/AWAF policies and even APM policies: https://clouddocs.f5.com/products/orchestration/ansible/devel/modules/bigip_asm_policy_fetch_module.html https://clouddocs.f5.com/products/orchestration/ansible/devel/modules/bigip_apm_policy_fetch_module.html -------------- Terraform Nowadays Terraform also provides the option to export and import AWAF policies (for APM Ansible is still the only way) as there is an F5 provider for terraform. I usedVisual Studio as Visual Studio wil even open for you the teminal, where you can select the folder where the terraform code will be saved after you have added the code run terraform init, terraform plan, terraform apply. VS even has a plugin for writting F5 irules. The terraform data type is not a resource and it is used to get the existing policy data. Data sources allow Terraform to use information defined outside of Terraform, defined by another separate Terraform configuration, or modified by functions. Usefull links for Visual Studio and Terraform: https://registry.terraform.io/providers/F5Networks/bigip/1.16.0/docs/resources/bigip_ltm_datagroup Usefull links for Visual Studio and Terraform: https://registry.terraform.io/providers/F5Networks/bigip/latest/docs/resources/bigip_waf_policy#policy_import_json https://www.youtube.com/watch?v=Z5xG8HLwIh4 The big issue is that Terraform not like Ansible needs you first find the aWAF policy "ID" that is not the name but a random generated identifier and this is no small task. I suggest looking at the link below: https://community.f5.com/t5/technical-articles/manage-f5-big-ip-advanced-waf-policies-with-terraform-part-2/ta-p/300839 Code: You may need to add also this resource below as to save the config and with "depends_on" it wil run after the date group is created. This is like the handler in Ansible that is started after the task is done and also terraform sometimes creates resources at the same time not like Ansible task after task, resource "bigip_command" "save-config" { commands = ["save sys config"] depends_on = [ bigip_waf_policy.test-awaf ] } Tested this on version: 16.11.1KViews1like1CommentBlock IP after a number of ASM Blocks
Problem this snippet solves: There is no reason to let Attacker hand over all information at once. How to use this snippet: Add the iRule to the virtual Server and activate iRule after ASM Block in the ASM Profile. Please let me know if you need help with this Code : when RULE_INIT priority 10 { # excessive ASM Block based on https://devcentral.f5.com/s/articles/excessive-404-blacklist # v1.0 - Joel Moses # v1.1 - Nico Giefing # Block all site access to source IP addresses that exceed a # certain number of HTTP requests which were blocked at ASM/AWAF within # a fixed period of time. Useful for blocking harvesting # activities (or at least slowing them down significantly). # # Use this iRule at your own risk. If your site has a propensity # to throw off lots of 404 errors in normal operation, you may # inadvertently find valid users blocked. # Set the maximum number of WAF blocks for a client IP in a # certain period of time. The default setings are 100 # WAF Blocks in 5 minutes. Following this, the client IP # address will be blocklisted the next 180 minutes. set static::maxerrors 10 set static::maxperiod 300 set static::holdtime 10800 # If for any reason you need to rid yourself of a # blocklist, you can send the VIP running this iRule # a query string parameter in the format: # http(s)://sitename.com/?DeleteKey= ; # where the case-sensitive key is defined below. Consider # this a "get out of jail free" card in case something # goes totally wrong. set static::deletekey "DeleteAllBlocklistData" # If set to true, log all blocklisted source IP addresses # to the LTM log. set static::log_offender 1 } when CLIENT_ACCEPTED priority 10{ # Block IP if in the blocklist # Collect the remote IP address. set srcip [IP::remote_addr] set dstip [IP::local_addr] set blocklist_name "blocklist" set countlist_name "[IP::remote_addr]" if { [table lookup -subtable $blocklist_name $srcip] != "" } { #log local0. "The IP $srcip has been blocked while accessing $dstip" TCP::close return } # } } when HTTP_REQUEST priority 10{ set uri "[HTTP::uri]" if { ([URI::query [HTTP::uri] DeleteKey ] equals "$static::deletekey") and ([IP::addr [IP::client_addr] equals 10.0.0.0/8]) } { log local0. "BLOCKLIST: Table manually cleared by [IP::remote_addr]." table delete -subtable $blocklist_name -all } if {(([HTTP::uri] eq "/blocklist") or ([HTTP::uri] eq "/blacklist")) and ([IP::addr [IP::client_addr] equals 10.0.0.0/8]) } { set response " BIGIP Blocklist [clock format [clock seconds]] " set blocklist_name "blocklist" set block_count 0 foreach key [table keys -subtable $blocklist_name] { set remaining_sec [table lifetime -subtable $blocklist_name -remaining $key] set remaining_min [expr {$remaining_sec/60} ] set response "$response The IP ${key} is blocked for the next $remaining_sec seconds, equals to $remaining_min minutes" set block_count [expr {$block_count + 1}] } if {$block_count eq 0 }then { set response " NO BLOCKED IP " HTTP::respond 200 content $response "Content-Type" "text/html" } else { set response "$response " HTTP::respond 299 content $response "Content-Type" "text/html" } } } # it is needed to activte iRules in ASM/AWAF after block # https://support.f5.com/csp/article/K22017023 when ASM_REQUEST_BLOCKING priority 1{ set x [ASM::violation_data] # marker bit to handle header change set activeViolation 1 #log local0. "seen a blocked request" if { ($uri != "/favicon.ico") } then { # If the source IP has gotten a ASM block but does not currently # have a unique subtable entry, create a new offender. if { ([table lookup -subtable $countlist_name "count"] == "") } then { set count 1 table add -subtable $countlist_name "count" $count indef $static::maxperiod #log local0. "added table count IP $countlist_name" # If the source IP has a 404 and is a repeat offender, # increment the count within the table. } elseif { ([table lookup -notouch -subtable $countlist_name "count"] != "") } { set count [table incr -notouch -subtable $countlist_name "count"] # If the maximum number of errors has been reached, # add the source IP to the blocklist and get rid of the # offender's unique table (he'll get a new one after his holdtime # is over). Respond with the error page. if { $count >= $static::maxerrors } then { table add -subtable $blocklist_name $srcip "blocked" indef $static::holdtime log local0. "add ip $srcip to blocklist" table delete -subtable $countlist_name -all if { ($static::log_offender) } { #log local0. "BLOCKLIST: [IP::remote_addr] is blocklisted on [virtual] for $static::holdtime seconds." } return } } } } Tested this on version: 14.11.1KViews0likes2CommentsCreate New ASM Policy with iControl REST
Problem this snippet solves: Proof of concept code to show how to create a new ASM policy using iControl REST. How to use this snippet: ./create_new_asm_policy.py usage: create_new_asm_policy.py [-h] --bigip BIGIP --user USER --policyname POLICYNAME [--language {utf-8,auto-detect}] [--protocolindependence {false,true}] [--caseinsensitive {false,true}] [--enforcement {blocking,transparent}] [--template {Fundamental,Comprehensive}] [--virtual [VIRTUAL [VIRTUAL ...]]] [--learningmode {automatic,manual,disabled}] Code : #!/usr/bin/python # create_new_asm_policy.py # Author: Chad Jenison (c.jenison at f5.com) # Version 1.0 # # Script that creates new ASM policy using iControl REST and allows some customization of initial optionso # --virtual option allows attaching policy to one or more virtuals import argparse import sys import requests import json import getpass #Setup command line arguments using Python argparse parser = argparse.ArgumentParser(description='A tool to parse web log files and add them to an ASM security policy') parser.add_argument('--bigip', '-b', help='IP or hostname of BIG-IP Management or Self IP', required=True) parser.add_argument('--user', '-u', help='username to use for authentication', required=True) parser.add_argument('--policyname', '-p', help='ASM security policy name', required=True) parser.add_argument('--language', '-l', help='Application Language', default='utf-8', choices=['utf-8','auto-detect']) parser.add_argument('--protocolindependence', '-prot', help='Protocol Independence (false = distinguish between HTTP and HTTPS; true = treat HTTP and HTTPS equivalently)', default='false', choices=['false','true']) parser.add_argument('--caseinsensitive', '-c', help='Case Insensitive', default='false', choices=['false','true']) parser.add_argument('--enforcement', '-e', help='Enforcement Mode - Blocking or Transparent', default='blocking', choices=['blocking','transparent']) parser.add_argument('--template', '-t', choices=['Fundamental', 'Comprehensive']) parser.add_argument('--virtual', '-v', nargs='*', help='Virtual Server(s) to attach to (with full path [e.g. /Common/test])') parser.add_argument('--learningmode', '-m', choices=['automatic', 'manual', 'disabled']) args = parser.parse_args() contentTypeJsonHeader = {'Content-Type': 'application/json'} #adapted from https://devcentral.f5.com/s/articles/demystifying-icontrol-rest-6-token-based-authentication def get_auth_token(): payload = {} payload['username'] = args.user payload['password'] = passwd payload['loginProviderName'] = 'tmos' authurl = 'https://%s/mgmt/shared/authn/login' % args.bigip token = bip.post(authurl, headers=contentTypeJsonHeader, auth=(args.user, passwd), data=json.dumps(payload)).json()['token']['token'] return token def get_asm_policy_id_from_name(name): policies = bip.get('%s/asm/policies/' % (url_base)).json() for policy in policies['items']: if policy['name'] == name: id = policy['id'] return id url_base = ('https://%s/mgmt/tm' % (args.bigip)) user = args.user passwd = getpass.getpass("Password for " + user + ":") bip = requests.session() bip.verify = False requests.packages.urllib3.disable_warnings() authtoken = get_auth_token() authheader = {'X-F5-Auth-Token': authtoken} bip.headers.update(authheader) policyTemplates = bip.get('%s/asm/policy-templates/' % (url_base)).json() for policyTemplate in policyTemplates['items']: if policyTemplate['title'] == 'Fundamental' and args.template == 'Fundamental': policyTemplateId = policyTemplate['id'] if policyTemplate['title'] == 'Comprehensive' and args.template == 'Comprehensive': policyTemplateId = policyTemplate['id'] # combine two Python Dicts (our auth token and the Content-type json header) in preparation for doing POSTs postHeaders = authheader postHeaders.update(contentTypeJsonHeader) policyPayloadDict = {'name':args.policyname, 'caseInsensitive':args.caseinsensitive, 'enforcementMode':args.enforcement, 'applicationLanguage':args.language, 'protocolIndependent':args.protocolindependence} if args.template is not None: policyPayloadDict.update({'templateReference': policyTemplateId}) if args.virtual is not None: print ('Virtual(s) to add: %s' % (args.virtual)) policyPayloadDict.update({'virtualServers' : args.virtual }) newPolicyPayload = json.dumps(policyPayloadDict) addPolicy = bip.post('%s/asm/policies/' % (url_base), headers=postHeaders, data = newPolicyPayload) if addPolicy.status_code == 201: policyId = get_asm_policy_id_from_name(args.policyname) print ('Successfully Created Policy: %s - policyId: %s' % (args.policyname, policyId)) else: print ('Unsuccessful attempt to create policy: %s - Status Code: %s' % (args.policyname, addPolicy.status_code)) print ('Body: %s' % (addPolicy.content)) if args.learningmode is not None: policyBuilderPatchDict = {'learningMode': args.learningmode} updateLearning = bip.patch('%s/asm/policies/%s/policy-builder' % (url_base, policyId), headers=postHeaders, data = json.dumps(policyBuilderPatchDict)) print ('Set Learning Mode to: %s on policy: %s' % (args.learningmode, args.policyname)) Tested this on version: 13.0759Views0likes3CommentsProactive Bot Defense Bypass by Bot Signature
Problem this snippet solves: This code enables you to bypass Proactive Bot Defense for a specific bot signature. Caution: If the signature is simple, it may be easy for an attacker to guess it and craft a response to match the signature and thus bypass Proactive Bot Defense with this in place. For this reason, another bypass solution is recommended where possible. You can bypass Proactive Bot Defense without this iRule by setting a benign category to "Report" and ensuring that the signature has a reverse DNS lookup in place. This will validate the source in addition to other factors such as the User-Agent. How to use this snippet: Add to the virtual server that is protected by Proactive Bot Defense and Bot Signatures. Enter the signature you want to bypass in the code where the example "curl" is placed currently. The signature's category must be set to report or block for this to take effect. Tested on v13.1. Code : when BOTDEFENSE_ACTION { #log local0. "signature: [BOTDEFENSE::bot_signature]" if { [BOTDEFENSE::bot_signature] ends_with "curl"} { BOTDEFENSE::action allow } }737Views1like1Comment