Application Security Lab Guides

Combined interactive edition for the four Application Security classes: Cloudflare Setup (AppSec 1), Build and Tune Effective Security Rules (AppSec 2), Bot and Abuse Mitigation (AppSec 3), and Stop Attacks in Real Time (AppSec 4).

⚠ Read this first

These labs run against your assigned Cloudflare-managed lab zone on cftraining.app. Every task is performed in the Cloudflare dashboard or from a terminal against your assigned domain. Fill in your lab slug in the form below before you start, so every command and address in this guide is ready to copy and run.

Typographical Conventions

This guide uses the following conventions to distinguish between user input, system output, and explanatory notes.

ConventionMeaningExample
BoldNames of selectable items in the interfaceClick Save to apply the change.
MonospaceText that you enter and code examplesEnter the command: whoami
Result textStep results, explanations, expected outputOperation completed successfully.
ItalicsContextual notes explaining "why" a task is necessaryThis keeps the service from restarting mid-test.
<in angle brackets>A per-student variable, filled in by the reader.Run dig A <lab-slug>.cftraining.app and press Enter.

How to Use This Guide

This combined guide contains all four Application Security classes: Part 1 (Cloudflare Setup), Part 2 (Build and Tune Effective Security Rules), Part 3 (Bot and Abuse Mitigation), and Part 4 (Stop Attacks in Real Time). Before each class, verify the assigned environment against that class's starting-state checklist. Work the activities in order because later activities use evidence, settings, or rules created earlier in the same class.

About the step numbers and checkboxes. Every instruction has a number (like 4.2.1) and a checkbox next to it. These are optional and are only a visual aid: as you work, you will flip back and forth between this guide and the dashboard, and it is easy to lose your place. Tick a box when you finish a step so you can glance back and instantly see where you were. Checking a box just highlights the step and remembers it in your browser; it does not submit anything, unlock anything, or report anywhere. You do not have to use them at all.
Start here ☛ Fill in your lab slug

This is the most important box in the guide. Enter the slug for your assigned lab domain. Your full domain is <lab-slug>.cftraining.app, so if your domain is happy-balancer.cftraining.app you enter happy-balancer. Every matching placeholder across the guide, including inside the copy blocks, is substituted with your value, so when you click Copy you get a real, ready-to-run command instead of a <placeholder>. This value saves to your browser only; nothing is sent anywhere.

Why These Labs

Why you are doing all of this: These classes follow the same evidence-first job a security engineer does when protecting a web application. First you make sure traffic is going through Cloudflare and is encrypted. Then you investigate attacks, write precise rules, and tune them to protect real customers. You apply that method to bots and API abuse, then use it to respond to an active application incident. Why notes explain the consequential decisions without interrupting every routine click.

AppSec 1 - Cloudflare Setup: From Start to Best Practices

Starting-State Check

Confirm the complete AppSec 1 baseline in your assigned zone. Do not repair an unknown starting state as part of the activity.

Prerequisites

ControlRequired starting state
Apex CNAME recordPoints to the assigned pod origin and is working and Proxied
www CNAME recordPoints to the assigned pod origin and is DNS Only, exposing the origin addresses
SSL/TLS encryption modeFull
Cloudflare Managed RulesetOff
OWASP CoreOff
Test route/.git/config is available for the managed-rules verification request
Baseline check: If any value does not match, stop and ask your instructor to reset the AppSec 1 environment.

Lab 1: DNS Investigation & Configuration

In this lab, you will diagnose and fix a DNS configuration that exposes an origin server and bypasses Cloudflare's security and performance services.

Scenario: You received a service ticket. Users report that www.<lab-slug>.cftraining.app is slow and displays a privacy warning, while the root site works normally.

1.1 Investigate with dig

Open a terminal and run both commands to compare the IPv4 response for www with the response for the root domain. Both names are configured as CNAME records to the pod load balancer, but dig A asks DNS for the resulting IPv4 addresses:

dig A www.<lab-slug>.cftraining.app
dig A <lab-slug>.cftraining.app

The www lookup may show a CNAME first and then return more than one A address. For this comparison, record one returned A address and the query time for each name. You can record every returned A address if you want, but one address is enough.

Record your DNS baseline. Enter at least one A address from each lookup, not the CNAME hostname. If you enter more than one address, separate the addresses with commas.

These values save in this browser and appear again when you compare the post-change results.
Why: The www CNAME is intentionally configured as DNS Only, so it resolves through the load balancer to origin addresses instead of Cloudflare addresses. That is why it is slow and shows a privacy warning: traffic is going straight to the origin and bypassing Cloudflare.
Note: If dig is unavailable, use https://dnschecker.org to compare the responses.

1.2 Fix the Exposed DNS Record

Open the Cloudflare dashboard and select your assigned account.

Go to Domains > Overview, then select your assigned lab domain.

In the domain's left sidebar, select DNS > Records.

Find the www record.

Change Proxy status from DNS Only to Proxied.

Return to the terminal and run the two lookups again. Compare www with your saved baseline of <WWW_INITIAL_IPS> at <WWW_INITIAL_QUERY_TIME>, and compare the root domain with <APEX_INITIAL_IPS> at <APEX_INITIAL_QUERY_TIME>. DNS resolvers across the Internet need time to receive the change, and cached responses can continue returning the previous address until they expire. If www still returns the origin address, wait a few minutes and run the lookup again. Continue after www returns Cloudflare addresses.

Verify: After the change, dig A www.<lab-slug>.cftraining.app returns Cloudflare anycast addresses (the same kind the root domain returns), not the origin IP, and the privacy warning is gone.

Lab 2: SSL/TLS Configuration

In this lab, you will observe how Full (Strict) protects the connection to the origin, then restore the lab's required state.

2.1 Induce the SSL Error

Open the Cloudflare dashboard.

Select your assigned account.

Go to Domains > Overview, then select your assigned lab domain.

In the domain's left sidebar, select SSL/TLS > Overview.

On the SSL/TLS overview, select Configure in the top-right.

On Encryption mode and origin connection settings, select Full (Strict).

Select Save at the bottom of the Encryption mode card.

Leave the dashboard open. In a new browser tab, open https://<lab-slug>.cftraining.app/ to test the site's HTTPS connection with the stricter origin mode.

Record the Cloudflare error code displayed on that page. The published lab currently expects 525: SSL handshake failed.

Why: The exact error depends on the origin's TLS state. A 525 means the TLS handshake failed. A 526 means the origin presented a certificate that Cloudflare could not validate in Full (Strict) mode. This shows that Full (Strict) refuses to trust an origin that cannot prove its identity.
Stop if the expected TLS fixture is missing

If the test does not produce 525 or 526, ask your instructor to verify the origin TLS fixture before continuing to Lab 3.

2.2 Restore the Lab State

Return to the dashboard tab. If needed, select SSL/TLS in the breadcrumb, then select Configure in the top-right.

On Encryption mode and origin connection settings, select Full.

Select Save at the bottom of the Encryption mode card.

Return to https://<lab-slug>.cftraining.app/, refresh the page, and confirm that the site loads.

Verify: The lab site loads again without the Cloudflare SSL error.
Note: This reset is specific to the lab. In production, install a valid origin certificate and retain Full (Strict).

Lab 3: Verifying Traffic in Analytics

3.1 Find the Error in Analytics

From the selected domain's left sidebar, select Analytics > HTTP Traffic.

Add an Edge status code filter for <SSL_ERROR_CODE>, the code you recorded in Lab 2. The published scenario expects 525; use 526 if that is what your lab origin produced.

Confirm that you can find the error generated in Lab 2.

Adjust the time range if the request is not immediately visible.

Verify: The Lab 2 error appears in HTTP Traffic once you filter by edge status code <SSL_ERROR_CODE>.

Lab 4: Enabling the Web Application Firewall (WAF)

4.1 Enable and Verify the Cloudflare Managed Ruleset

From the selected domain's left sidebar, select Security > Settings.

Filter the settings for Web application exploits.

In the Cloudflare managed ruleset row, select the toggle in the Status column. This opens Deploy managed ruleset.

Under Execution configuration, confirm that the execution scope is All incoming requests to your lab domain.

Under Ruleset configuration, leave Ruleset action set to Default and Ruleset status set to Default.

At the bottom of the page, select Deploy. Do not select Save as Draft.

Cloudflare returns you to Security > Security rules. In the Managed rules card, confirm that Cloudflare Managed Ruleset shows Action = Execute and Status = Active.

From a terminal, send a request that the Cloudflare Managed Ruleset is designed to block:

curl -i "https://<lab-slug>.cftraining.app/.git/config"

Confirm that the request returns 403 Forbidden, then record the Cloudflare Ray ID shown near the bottom of the response. This request creates the event you will inspect; you do not need to wait for background scanner traffic.

In the domain's left sidebar, select Security > Analytics, select the Events tab, and set the time range to Last 30 minutes.

Select Add filter, add Service equals Managed rules, add Action equals Block, then apply the filters.

Scroll to Sampled logs. If needed, select Edit columns and add ASN and User agent. Find the fresh GET /.git/config event by its timestamp or Ray ID, then expand it with the caret at the left of the row. Do not use existing Custom rules or Rate limiting rules events as proof that the managed ruleset worked.

Under Matched service, confirm Service = Managed rules, Action taken = Block, Ruleset = Cloudflare Managed Ruleset, and Rule = Version Control - Information Disclosure. Under Request details, confirm Method = GET and Path = /.git/config, then inspect the IP address, country, ASN, user agent, WAF attack scores, and bot score that describe the request.

Verify: A fresh GET /.git/config event shows Cloudflare Managed Ruleset, Version Control - Information Disclosure, and Block. If you cannot find a fresh event, run the command from step 4.1.8 again, wait briefly, and refresh Sampled logs.
Stop if fresh evidence is missing

If the repeated request does not return 403 or produce the matching fresh event, ask your instructor to verify the managed-rules fixture. Do not use an older or unrelated event as evidence.

4.2 Deploy OWASP Safely

In the domain's left sidebar, return to Security > Settings. Under Filter by groups, select Web application exploits.

In the OWASP Core row, select the toggle in the Status column. This opens Deploy managed ruleset for the Cloudflare OWASP Core Ruleset.

Under Execution configuration, confirm that the execution scope is All incoming requests to your lab domain.

Under Cloudflare OWASP Core Ruleset configuration, leave OWASP Anomaly Score Threshold set to Medium - 40 and higher and leave OWASP Paranoia Level set to PL1.

Change OWASP Action from Block to Log.

At the bottom of the page, select Deploy. Do not select Save as Draft.

Why: Starting in Log lets you inspect matches before enforcing a broader ruleset against production traffic. OWASP is score-based, so logging first shows you what it would catch before it can block a real customer.

4.3 Test the Logging Rule

Open a Linux terminal and run this request against your assigned domain:

curl --http1.0 -i -X DELETE -H "Accept;" -H "Content-Type: text/invalid" -H "Connection: keep-alive, close" "https://<lab-slug>.cftraining.app/owasp-test?file=http%3A%2F%2F127.0.0.1%2Fshell.php%3F&PHPSESSID=test&x=%0D%0ASet-Cookie%3Atest"

This request deliberately combines an unsupported method, conflicting and unusual headers, remote-file-inclusion input, header-injection input, and a session identifier. Nine PL1 OWASP rules contribute to a cumulative score of 40, which reaches the configured Medium - 40 and higher threshold. The Cloudflare Managed Ruleset above OWASP does not terminate this request first.

Confirm that curl shows 405 Method Not Allowed. That response comes from the lab origin because Log allows the request to continue. It does not mean the OWASP test failed.

Return to the dashboard. From the selected domain's left sidebar, select Security > Analytics, then select the Events tab. Select Clear all to remove the filters from section 4.1. Select Add filter, add Service equals Managed rules, add Action equals Log, then apply the filters.

Set the time range to Last 30 minutes.

In Sampled logs, expand the fresh event for path /owasp-test. Under Matched service, confirm Ruleset = Cloudflare OWASP Core Ruleset, Action taken = Log, the rule name includes Inbound Anomaly Score Exceeded, and OWASP score = 40.

Select Additional logs (9) to inspect the individual PL1 rules that contributed to the score. Under Request details, confirm Method = DELETE and Path = /owasp-test.

Why this test works: An individual OWASP signature normally adds only a few points. Cloudflare applies the configured OWASP action only after the cumulative score reaches the threshold, so a single SQL-injection signature did not produce the Log event this test requires.
Verify: The fresh DELETE /owasp-test event shows Cloudflare OWASP Core Ruleset, Inbound Anomaly Score Exceeded, OWASP score = 40, and Action taken = Log. Leave the OWASP action set to Log.
Stop if the OWASP evidence is incomplete

If the origin does not return 405 or the fresh event does not show all four required values, ask your instructor to verify the test fixture. Do not change OWASP to Block.

Final Retained State

ControlRequired retained state
Apex CNAME recordPoints to the assigned pod origin and is Proxied
www CNAME recordPoints to the assigned pod origin and is Proxied
SSL/TLS encryption modeFull for this lab environment
Cloudflare Managed RulesetActive, action Execute
OWASP CoreActive, PL1, threshold Medium - 40 and higher, action Log
Required evidenceFresh managed-rules Block event and fresh OWASP score-40 Log event
Class handoff: Keep this state for the AppSec 1 recap. Other AppSec classes start from independent baselines, so do not assume that these controls carry into another class.

Appendix: Lab Command Reference

The dig commands used in this class, for quick reference:

dig A www.<lab-slug>.cftraining.app
dig A <lab-slug>.cftraining.app

AppSec 2 - Build and Tune Effective Security Rules

You are a security engineer for Single Origin. In this lab, you will investigate scraping, build and test a precise mitigation, tune false positives, repair inherited rule hygiene, and protect the product API without disrupting legitimate users.

Use the Assess, Build, Test, Tune cycle throughout the lab.

ActivityModeDuration
1. Investigate scraping and correct a Skip-order conflictSolo18 minutes
2. Tune two false positivesSolo16 minutes
3. Repair inherited rule hygieneSynchronous6 minutes
4. Add product-API rate limitingSolo10 minutes

Starting-State Check

The required configuration is listed below. Confirm it in your assigned environment before starting Activity 1; the guide does not assume that the environment was reset correctly.
Where to verify: Open the dashboard, select your assigned account, go to Domains > Overview, and select <lab-slug>.cftraining.app. Use Security > Security rules for managed, Custom, and rate-limit rules; Security > Analytics > Traffic for recent scraper traffic; and account navigation Manage account > Configurations > Lists for site_monitor_ips.
Required configuration: The assigned domain has a proxied apex CNAME that points to the pod's shared load balancer. No managed ruleset is enabled. The account-level $site_monitor_ips list contains the trusted monitor address. Both inherited Skip rules have Log matching requests enabled. No scraper rule or product-API rate-limit rule exists.

The class starts with exactly these five inherited Custom rules in this order:

PositionRuleExpressionAction
1BYPASS - Site Monitor IPip.src in $site_monitor_ipsSkip all remaining Custom rules; Log matching requests enabled
2BLOCK - DE or Android 5ip.src.country eq "DE" or http.user_agent contains "Android 5"Block
3BLOCK - Site Monitor curlhttp.user_agent contains "curl"Block
4BYPASS - Main Customer Geoip.src.country eq "US"Skip all remaining Custom rules; Log matching requests enabled
5BLOCK - bot UAhttp.user_agent contains "bot"Block
Starting state: Recent scraper traffic is visible in Security Analytics, and no rate-limit rule targets /api/v1/products.
Required routes: GET /shop, GET /api/v1/products, GET /checkout, and GET /health return 200 before a Cloudflare mitigation.
SignalLifecycle and rateRequired identity and result
SEC2-HUMANContinuous; four GET /shop and two GET /api/v1/products requests per minuteCurrent browser user agent; US residential or equivalent browser source; 200
SEC2-SCRAPERContinuous; forty GET /api/v1/products requests per minutepython-requests/2.31.0; US source; static source IP and ASN
SEC2-VPNContinuous; four GET /checkout requests per minuteCurrent browser user agent; same ASN as the scraper from a different IP; 200 after tuning
SEC2-MONITORContinuous; two GET /api/v1/products requests per minutepython-requests/2.31.0; same ASN as the scraper; static allowlisted IP; 200 through the first Skip
SEC2-API-BURSTRecurring preconfigured schedule; fifteen GET /api/v1/products requests inside 60 seconds per runCurrent browser user agent; non-US source and ASN; crosses the learner-created threshold
Traffic lifecycle: The four continuous signals establish history before validation and keep running throughout class. SEC2-API-BURST runs on a non-overlapping preconfigured schedule that keeps recent evidence available. It has no classroom trigger; optional replay is non-gating.
Baseline check: If any required value or traffic signal is missing, stop and ask your instructor to reset the AppSec 2 environment.

Activity 1 (SEC2-1): Investigate scraping and correct a Skip-order conflict

Required outcome: Identify the scraper and correct a rule-order conflict so the new observation rule evaluates. Mode and duration: Solo, 18 minutes.
Recent scraper traffic targets /api/v1/products, and the five inherited Custom rules are in their baseline order.

1.1 Investigate Scraping

Open the Cloudflare dashboard and select your assigned account.

Go to Domains > Overview, then select <lab-slug>.cftraining.app.

In the domain's left sidebar, select Security > Analytics, select the Traffic tab, and set the time range to Last 30 minutes.

Select Add filter, create a filter where Path equals /api/v1/products, apply it, and find a fresh scraper request by its timestamp, source ASN, rate, and python-requests/2.31.0 user agent.

In Sampled logs, select the timestamp for that scraper request. Under Request details, record the value next to ASN as <SCRAPER_ASN>.

Why: The path, rate, network, and user agent give you several signals to compare, so you do not have to treat one broad attribute as proof by itself.

1.2 Create and Place the Log Rule

Select Security > Security rules.

Select Create rule, then select Custom rules. Enter LOG - Scraping ASN as the rule name, select Edit expression, and paste the copied expression:

ip.src.asnum eq <SCRAPER_ASN>

Under Then take action, set Choose action to Log. Under Execution order, leave Select order set to Last. At the bottom of the page, select Deploy, then select Deploy in the Deploy rule to production? confirmation dialog.

On the Custom rules list, select the linked event count in the Events column for BYPASS - Main Customer Geo.

In the filtered Events view, set the time range to Last 30 minutes.

In Sampled logs, start with the first row and select its timestamp. In the expanded Request details, check for Path /api/v1/products, User agent python-requests/2.31.0, and ASN AS<SCRAPER_ASN>. If that row does not have all three values, select the next timestamp and check again.

In the matching event's Matched service section, confirm that Action taken is Skip. The Rule ID filter came from BYPASS - Main Customer Geo, so this shows that the higher Skip handles the scraper request before the new last-place Log rule can evaluate it.

Select Security > Security rules. In the Custom rules table, select LOG - Scraping ASN to edit it.

Under Execution order, set Select order to Custom. Set Select which rule this will fire after to BLOCK - Site Monitor curl; this places the Log rule immediately above BYPASS - Main Customer Geo. Select Save.

Why: The geographic Skip stops evaluation before a lower rule can run. Moving the observation rule just above that Skip lets you collect the intended evidence without disturbing the trusted-service exception.

Verify

Select Security > Analytics, then select the Events tab and set Last 30 minutes.

Select Add filter for each filter, create and apply Action equals Log, then Service equals Custom rules, then Path equals /api/v1/products.

In Sampled logs, select the timestamp of a fresh event. Under Matched service, confirm that Action taken is Log. Confirm that the event timestamp is after you saved the rule's new position.

Stop if the observation rule has no fresh event

Keep the rule in Log and check the numeric ASN and placement with your instructor before Activity 2.

Activity 2 (SEC2-2): Tune two false positives

Required outcome: Block the scraper while preserving a legitimate VPN user and an allowlisted monitor that share some scraper attributes. Mode and duration: Solo, 16 minutes.
LOG - Scraping ASN is above BYPASS - Main Customer Geo. The scraper and legitimate VPN traffic share <SCRAPER_ASN>, while the scraper and monitor share python-requests/2.31.0.

2.1 Test the Broad Block

Select Security > Security rules.

Open Show all rule types and select Custom rules.

In the Custom rules table, select LOG - Scraping ASN to edit it.

Change its action to Block, then select Save.

In the domain's left sidebar, select Security > Analytics, select the Events tab, set Last 30 minutes, and apply Action equals Block, Service equals Custom rules, and Path equals /checkout.

In Sampled logs, select the timestamp of a fresh blocked legitimate VPN request.

Under Request details, confirm that ASN is AS<SCRAPER_ASN>, then enter User agent as <VPN_USER_AGENT> in the field below.

Why: The first Block proves that ASN alone is too broad because it catches the scraper and a legitimate VPN user on the same network.

2.2 Refine and Verify the Rule

Select Security > Security rules.

Open Show all rule types and select Custom rules.

In the Custom rules table, select LOG - Scraping ASN to edit it.

In the expression editor, replace the current expression by pasting the copied expression:

ip.src.asnum eq <SCRAPER_ASN> and http.user_agent eq "python-requests/2.31.0"

Rename the scraper rule BLOCK - Scraping ASN+UA, then select Save.

Scroll to the bottom of the page, and on the left select Manage account > Configurations, open the Lists tab, select site_monitor_ips, and enter its listed monitor IP as <SITE_MONITOR_IP> in the field below.

Return to Overview, then select Security > Security rules.

Open Show all rule types and select Custom rules.

In the Custom rules table, confirm that BYPASS - Site Monitor IP remains the first rule.

Select Security > Analytics, then select the Events tab.

Set Last 30 minutes, then apply Source IP equals <SITE_MONITOR_IP>.

Under Top events by source > Custom rules, confirm that BYPASS - Site Monitor IP appears.

In Sampled logs, select the timestamp of a fresh event from the monitor IP.

Under Matched service, confirm that Action taken is Skip.

Under Request details, confirm that Source IP matches the monitor IP, ASN is AS<SCRAPER_ASN>, and User agent is exactly python-requests/2.31.0.

At the top of the domain sidebar, select Back to Domains, then in the account sidebar expand Investigate under the Observe heading, select Trace, and select Begin Trace.

In URL, enter https://<lab-slug>.cftraining.app/checkout.

Confirm that HTTP Method is set to GET.

Under User Agent and Request Headers, select Add setting.

Enter User-Agent for Header key, then paste the copied value into Value:

<VPN_USER_AGENT>

Select Send Trace to simulate how the active Cloudflare configuration handles the tuned VPN checkout request.

In Trace Results, confirm that Match type is All configurations so rules that did not match remain visible.

Expand WAF Custom Rules.

Find BLOCK - Scraping ASN+UA and confirm that it shows No match.

Select View rule detail, confirm that "matched" is false, and confirm that the expression checks for http.user_agent eq "python-requests/2.31.0".

Close Rule detail, then under Request JSON confirm that User-Agent matches <VPN_USER_AGENT>.

Under TRACE END, confirm that HTTP Status Code is 200 OK.

Why: Trace cannot reproduce the generated VPN source ASN, so combine this simulation with the live blocked Event that confirms the shared ASN and the fresh 200 response after tuning.

Verify

Select Overview, then select Security > Analytics.

Select the Events tab and set the time range to Last 30 minutes.

Apply Action equals Block, Service equals Custom rules, and Path equals /api/v1/products.

Under Top events by source > Custom rules, confirm that BLOCK - Scraping ASN+UA appears, then in Sampled logs select a fresh event and confirm that Action taken is Block, ASN is AS<SCRAPER_ASN>, and User agent is python-requests/2.31.0.

Select the Traffic tab and set the time range to Last 30 minutes.

Apply Source IP equals <SITE_MONITOR_IP> and Edge status code equals 200, then select a fresh request and confirm that Path is /api/v1/products, Source IP is <SITE_MONITOR_IP>, and User agent is python-requests/2.31.0.

Remove all filters, apply Path equals /shop and Edge status code equals 200 OK by selecting 200 OK in the status-code dropdown, then in Sampled logs select the newest /shop timestamp and under Edge response confirm Edge status code: 200 - OK and Origin status code: 200 - OK and under Request details confirm Method: GET, Path: /shop, and a browser User agent.

Remove all filters, apply Path equals /checkout, Source ASN equals <SCRAPER_ASN>, Source user agent equals <VPN_USER_AGENT>, and Edge status code equals 200, then select a fresh request and confirm that its details match all four filters.

Select the Events tab, remove all filters, apply Source IP equals <SITE_MONITOR_IP>, confirm under Top events by source > Custom rules that BYPASS - Site Monitor IP appears, then select a fresh event and confirm that Action taken is Skip.

Confirm that the blocked VPN Event showed ASN AS<SCRAPER_ASN> and User agent <VPN_USER_AGENT>, the fresh /checkout Traffic request with ASN AS<SCRAPER_ASN> and User agent <VPN_USER_AGENT> returned 200, Trace showed BLOCK - Scraping ASN+UA as No match for <VPN_USER_AGENT>, the blocked scraper Event showed ASN AS<SCRAPER_ASN> and User agent python-requests/2.31.0, and the monitor Event from <SITE_MONITOR_IP> showed Action taken Skip; this confirms that the tuned rule blocks the scraper signature while preserving the VPN and monitor.

Stop if legitimate traffic does not recover

If scraper traffic is not blocked or VPN, monitor, or human traffic does not return 200, keep the rule available for correction before continuing.

Activity 3 (SEC2-3): Repair inherited rule hygiene

Required outcome: Repair faulty Boolean logic, remove an obsolete rule, and verify that rules run from the narrowest exception to the broadest control. Mode and duration: Synchronous, 6 minutes.
Delivery dependency: validation traffic

This activity depends on delivery-provided DE Android 5, DE without Android 5, and non-DE Android 5 requests. The guide does not provide or invent a trigger. Confirm that your instructor has identified fresh validation traffic before changing the rule. If it is unavailable, stop and leave the inherited rules unchanged.

3.1 Repair the Inherited Rules

Select Security > Security rules, open Show all rule types, select Custom rules, and in the Custom rules table select BLOCK - DE or Android 5 to edit it.

Select Edit expression, then replace the current expression by pasting the copied expression:

ip.src.country eq "DE" and http.user_agent contains "Android 5"

In Rule name, enter this exact value:

BLOCK - DE Android 5

Select Save, then on the Custom rules list select BLOCK - Site Monitor curl to edit it, under Status select Disabled, select Save, and confirm that the rule shows Disabled on the list.

On the Custom rules list, confirm that the rules appear in this exact order with these statuses:

PositionRuleStatus
1BYPASS - Site Monitor IPActive
2BLOCK - DE Android 5Active
3BLOCK - Site Monitor curlDisabled
4BLOCK - Scraping ASN+UAActive
5BYPASS - Main Customer GeoActive
6BLOCK - bot UAActive

Select Security > Analytics.

Select the Traffic tab and set the time range to Last 30 minutes.

Select Clear all if any filters remain from an earlier task.

Select Show more top statistics, then under Source user agents select the four-arrow Expand to see more and search button, enter Android 5.0.2 in Search, hover over the single result, and select the Filter icon at the right.

Under Countries, hover over Germany, then select Filter.

Above, under Number of requests mitigated and served by Cloudflare, confirm that all filtered requests were blocked or challenged by Cloudflare's security tools by confirming that Mitigated by Cloudflare equals Total.

Under Edge status codes, confirm that all filtered requests are 403 Forbidden.

In Sampled logs, select a fresh timestamp, then under Edge response confirm that Mitigation is Block by Custom rules and Edge status code is 403 - Forbidden.

Under Request details, confirm that Country is Germany and User agent contains Android 5.0.2.

Scroll up and select Clear all under the filters, then under Source user agents replace any current Search value with Chrome/133.0.0.0, hover over the single result, and select the Filter icon at the right.

Under Countries, hover over Germany, then select Filter.

Above, under Number of requests mitigated and served by Cloudflare, confirm that all filtered requests were served by the origin by confirming that Served by origin equals Total.

Under Edge status codes, confirm that all filtered requests are 200 OK.

In Sampled logs, select a fresh timestamp, then confirm that Mitigation is Not mitigated, Edge status code is 200 - OK, Country is Germany, and User agent contains Chrome/133.0.0.0.

Select Clear all, then under Source user agents replace any current Search value with Android 5.0.2, hover over the single result, and select the Filter icon at the right.

Under Countries, hover over United States, then select Filter.

Above, under Number of requests mitigated and served by Cloudflare, confirm that all filtered requests were served by the origin by confirming that Served by origin equals Total.

Under Edge status codes, confirm that all filtered requests are 200 OK.

In Sampled logs, select a fresh timestamp, then confirm that Mitigation is Not mitigated, Edge status code is 200 - OK, Country is United States, and User agent contains Android 5.0.2.

Why: The AND expression blocks only the intended overlap, and disabling the obsolete curl rule prevents an old control from conflicting with the allowlisted monitor.
Verify: The three filtered samples prove that the corrected AND expression blocks DE Android 5 traffic while allowing DE traffic without Android 5 and non-DE Android 5 traffic.

Activity 4 (SEC2-4): Add product-API rate limiting

Required outcome: Rate-limit bursts to the products API without disrupting ordinary, VPN, monitor, or already-blocked scraper traffic. Mode and duration: Solo, 10 minutes.

4.1 Rate-Limit the Product API

Select Security > Security rules.

On the Rate limiting rules card, select Create rule, enter RATE-LIMIT - Product API as the rule name, select Edit expression, and paste the copied expression:

http.request.uri.path eq "/api/v1/products"

Under With the same characteristics, leave Field set to IP so Cloudflare counts requests separately for each source IP.

Under When rate exceeds, enter 10 for Requests and select 2 minutes for Period.

Under Then take action, select Non-Interactive Challenge.

Under With the following behavior, leave Non-Interactive Challenge for the selected duration selected and choose 5 minutes for Duration.

Under Status, leave Active selected.

Select Deploy, select Deploy in the Deploy rule to production? confirmation dialog, then wait for the next scheduled SEC2-API-BURST run against https://<lab-slug>.cftraining.app/api/v1/products while the ordinary, VPN, monitor, and scraper traffic continues.

Return to Security > Analytics, select the Events tab, set Last 30 minutes, and apply Service equals Rate limiting rules and Path equals /api/v1/products.

In Sampled logs, select the timestamp of a fresh event from the latest scheduled burst, under Matched service confirm that Service is Rate limiting rules and Action taken is Non-Interactive Challenge, and under Request details confirm that Path is /api/v1/products, which distinguishes this event from existing Custom-rule Block events.

Why: The exact path limits this control to the product API, and the threshold catches a burst without treating every API request as abuse.
Verify: The next scheduled burst crosses the threshold; human, VPN, and monitor traffic remains below it; scraper traffic remains blocked by BLOCK - Scraping ASN+UA; and Security Events distinguishes the Custom-rule Block from the rate-limit action. Keep the learner-created rules active for the class recap.

Final Retained State

Class handoff: Confirm that BLOCK - Scraping ASN+UA, the corrected BLOCK - DE Android 5, and RATE-LIMIT - Product API remain active; BYPASS - Site Monitor IP remains first; and BLOCK - Site Monitor curl is disabled. Keep the learner-created rules through recap. The AppSec 2 class reset restores the five inherited rules in their original order, $site_monitor_ips membership, route data, and absence of learner-created rules. It preserves labeled traffic visibility, all four continuous actors, and the recurring SEC2-API-BURST schedule without depending on an actor restart.

AppSec 3 - Bot and Abuse Mitigation

You are protecting Single Origin's specialty-coffee services from automated abuse. In this lab, you will establish bot protections, manage AI crawler treatment, protect a browser login, investigate credential stuffing, and create two JA4-based rate limits while checking that the mobile clients keep working.

ActivityModeDuration
1. Establish Baseline Bot ProtectionsSynchronous6 minutes
2. Manage AI Crawler TreatmentSynchronous8 minutes
3. Apply Maximize Security to the Login PageSynchronous5 minutes
4. Investigate, Mitigate, and Tune Credential StuffingSynchronous27 minutes
5. Rate-Limit Leaked Credentials for a Saved JA4Synchronous12 minutes
6. Protect the Orders APISynchronous12 minutes
Evidence source: The approved continuous external traffic generator may supply every sampled request and security event used in this lab.
Continuous signals: SEC3-STUFFER: Six form-encoded POST /admin and six JSON POST /api/mobile/login requests per minute with CF_EXPOSED_USERNAME and CF_EXPOSED_PASSWORD. SEC3-MOBILE-LOGIN: Two JSON mobile logins per minute. SEC3-MOBILE-ORDERS: Two GET /api/v1/orders requests per minute. SEC3-ORDER-SCRAPER: Twenty GET /api/v1/orders requests per minute.

Activity 1 (SEC3-1): Establish Baseline Bot Protections

Required outcome: Inspect the existing generator signals, confirm Leaked credentials detection, and enable Precursor in Minimize Friction. Mode and duration: Synchronous, 6 minutes.

1.1 Inspect the Generator Signals

Open the Cloudflare dashboard and select your assigned account.

Go to Domains > Overview, then select <lab-slug>.cftraining.app.

In the selected domain's left sidebar, select Security > Analytics, select the Traffic tab at the top, and set the time range to Last 30 minutes.

Above the request chart, select Clear all if filters from earlier work are displayed.

Add the following Traffic filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/admin

Scroll down to Sampled logs, then select the caret at the left of the newest sampled log row to expand it.

In the expanded row's Request analyses section, verify the displayed Bot score classification and verify that JA4 fingerprint has a fingerprint value.

In the same row's Request details section, verify that Method is POST, Path is /admin, and Edge status code is 401.

Scroll back to the filter row above the request chart, select the existing Path equals /admin filter, replace its Value with /api/v1/orders, and select Apply.

Scroll down to Top statistics and select Show more top statistics if the Source user agents card is not displayed.

In the Source user agents card, select the four-arrow Expand to see more and search control.

In Search, enter curl/8.10.1 and locate the exact curl/8.10.1 result.

Replace the Search value with SingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3) and locate that exact result.

Compare the two result counts: curl/8.10.1 should have about ten times as many requests as the mobile user agent because the scraper sends twenty requests per minute and the mobile client sends two.

Replace the Search value with curl/8.10.1, hover over the exact result, and select the Filter icon at the right.

After the curl/8.10.1 filter is applied, verify in Edge status codes that the filtered scraper traffic returns 401.

Scroll down to Sampled logs, then select the caret at the left of the newest sampled log row to expand it.

Under Request analyses, record the orders scraper's JA4 fingerprint in the saved field below and verify that Bot score is Automated.

Under Request details, verify that User agent is exactly curl/8.10.1 and Path is /api/v1/orders.

Scroll back to the filter row and select the existing Source user agent equals curl/8.10.1 filter.

Replace its Value with SingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3), then select Apply.

After the mobile user-agent filter is applied, verify in Edge status codes that the filtered mobile traffic returns 200.

Scroll down to Sampled logs, then select the caret at the left of the newest sampled log row to expand it.

Under Request analyses, verify that Bot score is Likely automated and JA4 fingerprint is <ORDERS_SCRAPER_JA4>.

Under Request details, verify that User agent is SingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3), Path is /api/v1/orders, and Edge status code is 200.

1.2 Enable Precursor

In the selected domain's left sidebar, select Security > Settings.

In Filter by groups, select Fraud.

In the Leaked credentials detection row, verify that Status is On.

In Filter by groups, select Precursor.

In the Precursor row, turn on the Status switch if it is off.

Select the Precursor row to open its configuration. Under Precursor Mode, select Minimize Friction, then select Save.

Why: The continuous generator already provides the traffic comparison, so this exercise does not wait for manually generated traffic or analytics propagation.
Verify: Leaked credentials detection remains On, Precursor is On in Minimize Friction, and the existing generator signals were inspected. If an expected signal or setting is absent, record the observed mismatch, continue to the next exercise, and include it in the end-of-lab results.

Activity 2 (SEC3-2): Manage AI Crawler Treatment

Required outcome: Apply Single Origin's crawler policy by allowing PerplexityBot for AI search and discovery and blocking GPTBot because model-training collection is not essential to the business. Mode and duration: Synchronous, 8 minutes.

2.1 Allow and Block AI Crawlers

In the selected domain's left sidebar, expand AI Crawl Control and select Security.

Above the crawler table, select Show inactive crawlers if it is not already selected.

In the crawler table, scroll to the row whose crawler name is exactly PerplexityBot.

In that row's Block Crawler column, turn the switch off if it is on.

Verify that the PerplexityBot Block Crawler switch is off, which means this AI Search crawler is allowed.

In the same crawler table, scroll to the row whose crawler name is exactly GPTBot.

In that row's Block Crawler column, turn the switch on if it is off.

Verify that the GPTBot Block Crawler switch is on, which means this model-training crawler is blocked.

Do not change any other crawler row.

Why: Single Origin values PerplexityBot's search and discovery traffic but does not need GPTBot's model-training collection, and zero request history does not prevent changing either crawler's catalog treatment.
Verify: The Block Crawler switch is off for PerplexityBot and on for GPTBot. If either crawler or switch is unavailable, record the observed mismatch, continue to the next exercise, and include it in the end-of-lab results.

Activity 3 (SEC3-3): Apply Maximize Security to the Login Page

Required outcome: Apply Maximize Security to the browser-facing /login path without affecting the API-only login or orders paths. Mode and duration: Synchronous, 5 minutes.

3.1 Create the Maximize Login Rule

In the selected domain's left sidebar, select Security > Settings.

In Filter by groups, select Precursor.

In the Precursor row, select the Configurable Rules: <number> rules link, where <number> is the displayed count.

On the Precursor Rules page, select Create rule.

In Rule name, enter MAXIMIZE - Login.

Select Edit expression and paste the copied expression:

http.request.uri.path eq "/login"

In Precursor mode, select Maximize Security.

Under Status, leave Active selected.

Select Deploy.

If the dashboard displays Deploy rule to production?, select Deploy in that dialog.

Open a new private or incognito browser window.

In the private session, open https://<lab-slug>.cftraining.app/login.

Return to the dashboard tab and select Security > Analytics in the selected domain's left sidebar.

At the top of Security Analytics, select the Events tab.

Open the time-range menu in the upper-right and select Last 30 minutes.

Above the events chart, select Clear all if filters are displayed.

Add the following Events filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/login
ActionequalsPrecursor Interstitial Page Issued

Scroll down to Sampled logs.

Select the caret at the left of the newest sampled log row to expand it.

In the expanded row's Matched service section, verify that Action taken is Precursor Interstitial Page Issued.

In the same row's Request details section, verify that Path is /login.

Why: The exact browser-login path receives the stronger mode while /api/mobile/login and /api/v1/orders remain outside the rule.
Verify: MAXIMIZE - Login is active for exact /login, and a fresh event shows Precursor Interstitial Page Issued when matching traffic is available. If the event is absent, record the observed mismatch, continue to the next exercise, and include it in the end-of-lab results.

Activity 4 (SEC3-4): Investigate, Mitigate, and Tune Credential Stuffing

Required outcome: Identify credential stuffing from multiple signals, diagnose rule order, compare the broad rule's actual effect on abusive and legitimate API traffic, and narrow the interactive challenge to the admin route. Mode and duration: Synchronous, 27 minutes.

4.1 Investigate Credential Stuffing

In the selected domain's left sidebar, select Security > Analytics.

At the top of Security Analytics, select the Traffic tab.

Open the time-range menu in the upper-right and select Last 30 minutes.

Above the request chart, select Clear all if filters are displayed.

Add the following Traffic filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/admin
Edge status codeequals401 Unauthorized

In Sampled logs, compare the Time values and verify that the repeating /admin failures occur about every 10 seconds, or about six times per minute, compared with the legitimate mobile-login signal's twice per minute.

Select the caret at the left of the newest sampled log row to expand it.

Under Request analyses, verify that Bot score is Automated and record the credential stuffer's JA4 fingerprint in the saved field below.

Under Request analyses > Other suspicious activity, verify whether the displayed value is Username & password leaked or None.

Under Request details, verify that Method is POST and Path is /admin, record the source IP in the saved field below, and note the timestamp.

Select the caret at the left of the next-newest sampled log row to expand it.

Compare Request analyses > JA4 fingerprint and Request details > IP with the first row: the JA4 should be <CREDENTIAL_STUFFER_JA4>, while the source IP should differ from <CREDENTIAL_STUFFER_FIRST_IP>.

Why: Within one data-center counter scope, the saved JA4 <CREDENTIAL_STUFFER_JA4> connects the requests even while the attacker rotates source IPs. Traffic reaching another data center gets a separate count, and the other signals show that the pattern is credential stuffing rather than one unusual login.

4.2 Mitigate and Test

In the selected domain's left sidebar, select Security > Security rules.

At the top of the Security rules page, select Create rule.

In the menu, select Custom rules.

In Rule name, enter CHALLENGE - Low Bot Score Login.

Select Edit expression and paste the copied expression:

cf.bot_management.score eq 1 and http.request.uri.path in {"/admin" "/api/mobile/login"}

Under Then take action, select Managed Challenge.

Under Execution order, leave Select order set to Last.

Under Status, leave Active selected.

Select Deploy.

If the dashboard displays Deploy rule to production?, select Deploy in that dialog.

In the selected domain's left sidebar, select Security > Analytics.

At the top of Security Analytics, select the Events tab.

Open the time-range menu in the upper-right and select Last 30 minutes.

Above the events chart, select Clear all if filters are displayed.

Add the following Events filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
ActionequalsSkip
ServiceequalsCustom rules
Pathequals/admin

Scroll down to Sampled logs.

Select the caret at the left of the newest sampled log row to expand it.

Under Matched service, verify that Service is Custom rules, Action taken is Skip, and Rule is BYPASS - Main Customer Geo.

Under Request analyses, verify that Bot score matches the configured cf.bot_management.score eq 1 condition and JA4 fingerprint is <CREDENTIAL_STUFFER_JA4>.

Under Request details, verify that Path is /admin and note the source IP.

In the selected domain's left sidebar, select Security > Security rules.

On the Security rules page, select Show all rule types.

Select Custom rules.

In the Custom rules list, locate the row named exactly CHALLENGE - Low Bot Score Login and select the rule name.

In the rule editor's Execution order section, open Select order and select Custom.

Open Select which rule this will fire after and select BYPASS - Site Monitor IP.

Select Save.

In the selected domain's left sidebar, select Security > Analytics.

At the top of Security Analytics, select the Events tab.

Open the time-range menu in the upper-right and select Last 30 minutes.

Above the events chart, select Clear all if filters are displayed.

Add the following Events filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/api/mobile/login
ServiceequalsCustom rules
ActionequalsManaged Challenge

Scroll down to Sampled logs.

Select the caret at the left of the newest sampled log row to expand it.

Under Matched service, verify that Service is Custom rules, Action taken is Managed Challenge, and Rule is CHALLENGE - Low Bot Score Login.

Under Request analyses, verify that Bot score matches the configured cf.bot_management.score eq 1 condition and the credential stuffer's JA4 fingerprint is <CREDENTIAL_STUFFER_JA4>.

Under Request details, verify that Path is /api/mobile/login and User agent is curl/8.10.1.

Treat this row as abusive API traffic, not as the legitimate mobile client.

At the top of Security Analytics, select the Traffic tab.

Open the time-range menu in the upper-right and select Last 30 minutes.

Above the request chart, select Clear all to remove the Events filters.

Add the following Traffic filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/api/mobile/login
Source user agentequalsSingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3)
Edge status codeequals200 OK

Scroll down to Sampled logs, then select the caret at the left of the newest sampled log row to expand it.

Under Request analyses, verify that Bot score is Likely automated.

Under Request details, verify the full mobile User agent, Path /api/mobile/login, and Edge status code 200.

4.3 Tune the Rule

In the selected domain's left sidebar, select Security > Security rules.

On the Security rules page, select Show all rule types.

Select Custom rules.

In the Custom rules list, locate the row named exactly CHALLENGE - Low Bot Score Login and select the rule name.

In the open expression editor under When incoming requests match…, replace the current expression with the copied expression:

cf.bot_management.score eq 1 and http.request.uri.path eq "/admin"

In Rule name, replace the current name with CHALLENGE - Admin Credential Stuffing.

Select Save.

In the selected domain's left sidebar, select Security > Analytics.

At the top of Security Analytics, select the Events tab.

Open the time-range menu in the upper-right and select Last 30 minutes.

Above the events chart, select Clear all if filters are displayed.

Add the following Events filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/admin
ServiceequalsCustom rules
ActionequalsManaged Challenge

Scroll down to Sampled logs, then select the caret at the left of the newest sampled log row to expand it.

Under Matched service, verify that Rule is CHALLENGE - Admin Credential Stuffing and Action taken is Managed Challenge.

At the top of Security Analytics, select the Traffic tab.

Open the time-range menu in the upper-right and select Last 30 minutes.

Above the request chart, select Clear all to remove the Events filters.

Add the following Traffic filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/api/mobile/login
Source user agentequalsSingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3)
Edge status codeequals200 OK

Scroll down to Sampled logs, then select the caret at the left of the newest sampled log row to expand it.

Under Request analyses, verify that Bot score is Likely automated.

Under Request details, verify that User agent is SingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3), Path is /api/mobile/login, and Edge status code is 200.

Why: Managed Challenge is an interactive browser control, so narrowing the rule to the attacked admin route avoids applying it to the API login. The current legitimate mobile client already falls outside the configured bot-score condition, but route scope makes the policy boundary explicit and leaves API abuse for the rate-limit exercise.
Verify: Events prove that CHALLENGE - Admin Credential Stuffing challenges /admin, the earlier broad-rule event on /api/mobile/login belongs to curl/8.10.1, and Traffic shows the generated mobile login classified as Likely automated and returning 200. If any result is absent, record the observed mismatch, continue to the next exercise, and include it in the end-of-lab results.

Activity 5 (SEC3-5): Rate-Limit Leaked Credentials for a Saved JA4

Required outcome: Block repeated leaked-credential attempts grouped by the credential stuffer's JA4 across rotating source IPs, while the legitimate mobile login keeps returning 200. Mode and duration: Synchronous, 12 minutes.

5.1 Create the Leaked-Credential Rate Limit

In the selected domain's left sidebar, select Security > Security rules.

At the top of the Security rules page, select Create rule.

In the menu, select Rate limiting rules.

In Rule name, enter ARL - Leaked Credential Attempts.

Under When incoming requests match…, select Edit expression and paste the copied expression:

(cf.waf.credential_check.username_and_password_leaked)

Under With the same characteristics, open the Field menu.

In the rendered Field options, select JA4 Fingerprint and verify that it replaces IP as the selected counting characteristic.

Leave Use custom counting expression unselected.

Under When rate exceeds…, leave Request based selected, enter 3 in Requests, and select 1 minute for Period.

Under Then take action…, select Block.

Leave With response type at Default Cloudflare rate limiting response and With response code at 429.

Under With the following behavior, select Throttle requests over the maximum configured rate.

Under Execution order, leave Select order set to Last.

Under Status, leave Active selected.

Select Deploy.

In the Deploy rule to production? dialog, select Deploy.

Why: JA4 is the counter key, so the rotating IPs do not create independent IP counters when they reach the same Cloudflare data center. Data centers keep separate counters, so a shared JA4 does not prove the requests all share one global counter.

5.2 Verify the Stuffer Block and Mobile Login

On Security > Security rules, locate the Advanced rate limiting rules card and select the linked Events count in the ARL - Leaked Credential Attempts row.

On the opened Security Analytics > Events page, set the time range to Last 30 minutes.

Above the events chart, retain the rule filter added by the linked Events count.

Add the following Events filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/api/mobile/login
ActionequalsBlock

In Sampled logs, select the left caret of the newest sampled log row recorded after the rule was deployed.

In Matched service, verify Service is Rate limiting rules, Rule is ARL - Leaked Credential Attempts, and Action taken is Block.

In Request analyses, verify JA4 fingerprint matches <CREDENTIAL_STUFFER_JA4>.

In Request details, verify Path is /api/mobile/login and User agent is curl/8.10.1, then compare its IP address with <CREDENTIAL_STUFFER_FIRST_IP>.

If the newest blocked source matches <CREDENTIAL_STUFFER_FIRST_IP>, expand another sampled Block row for this rule until you can compare a different source IP with the same JA4.

At the top of Security Analytics, select the Traffic tab, set Last 30 minutes, and select Clear all above the request chart.

Add the following Traffic filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/api/mobile/login
Source user agentequalsSingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3)
Edge status codeequals200 OK

In Sampled logs, select the left caret of the newest matching sampled log row.

Under Request analyses, verify Bot score is Likely automated.

Under Request details, verify User agent is SingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3), Path is /api/mobile/login, and Edge status code is 200.

Verify: A fresh rate-limiting Block event identifies the leaked-credential rule and the saved stuffer JA4 across changing source IPs, while a separate mobile-login Traffic row shows 200. Sampled logs can omit a specific request; record missing evidence as a mismatch instead of inferring a complete count.

Activity 6 (SEC3-6): Protect the Orders API

Required outcome: Block the high-rate orders scraper by JA4 without blocking the mobile orders client that shares its JA4. Mode and duration: Synchronous, 12 minutes.

6.1 Create the Orders API Rate Limit

In the selected domain's left sidebar, select Security > Security rules.

At the top of Security rules, select Create rule and select Rate limiting rules in its menu.

In Rule name, enter ARL - API Scraping.

Under When incoming requests match…, select Edit expression and paste the copied expression:

(http.request.uri.path eq "/api/v1/orders" and cf.bot_management.score eq 1 and http.user_agent eq "curl/8.10.1")

Under With the same characteristics, open the Field menu and select the rendered JA4 Fingerprint option.

Verify that JA4 Fingerprint replaces IP as the selected counting characteristic and leave Use custom counting expression unselected.

Under When rate exceeds…, enter 10 in Requests and select 1 minute for Period.

Under Then take action…, select Block and leave the default Cloudflare rate-limiting response code at 429.

Under With the following behavior, select Throttle requests over the maximum configured rate.

Under Execution order, leave Select order at Last.

Under Status, leave Active selected.

Select Deploy, then select Deploy in the Deploy rule to production? dialog.

Why: The scraper and mobile orders client can share a JA4, but the exact path, low bot-score threshold, and curl/8.10.1 user agent narrow which requests the rule counts. The mobile client must still pass the separate Traffic check.

6.2 Verify Scraper Blocking and Mobile Orders

On Security > Security rules, locate the Advanced rate limiting rules card and select the linked Events count in the ARL - API Scraping row.

On Security Analytics > Events, set the time range to Last 30 minutes and retain the rule filter created by the linked count.

Add the following Events filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/api/v1/orders
ActionequalsBlock

In Sampled logs, select the left caret of the newest sampled log row recorded after the rule was deployed.

Under Matched service, verify that Service is Rate limiting rules, Rule is ARL - API Scraping, and Action taken is Block.

Under Request analyses, verify that Bot score matches the rule expression's cf.bot_management.score eq 1 condition and JA4 fingerprint matches <ORDERS_SCRAPER_JA4>.

Under Request details, verify that Path is /api/v1/orders and User agent is curl/8.10.1.

At the top of Security Analytics, select Traffic, set Last 30 minutes, and select Clear all above the request chart.

Add the following Traffic filters.

For each row, select Add filter, choose the listed Field, Operator, and Value, then select Apply.
FieldOperatorValue
Pathequals/api/v1/orders
Source user agentequalsSingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3)
Edge status codeequals200 OK

In Sampled logs, select the left caret of the newest matching sampled log row.

Under Request analyses, verify Bot score is Likely automated and JA4 fingerprint matches <ORDERS_SCRAPER_JA4>.

Under Request details, verify Path is /api/v1/orders and User agent is SingleOrigin-Mobile/3.1.0 (iOS 18.2; iPhone15,3).

Under Edge response, verify Mitigation is Not mitigated, Edge status code is 200 - OK, and Origin status code is 200 - OK.

Verify: A fresh Block event belongs to ARL - API Scraping on the scraper path, and a separate current Traffic row shows the legitimate mobile orders request at 200 even though it shares the scraper's JA4.

6.3 Final Retained State

In the selected domain's left sidebar, select Security > Settings.

In Filter by groups, select Fraud, then verify that Leaked credentials detection shows On.

In Filter by groups, select Precursor, then verify that the Precursor row shows its switch On.

Select the Precursor row and verify that Minimize Friction is selected in the Precursor dialog.

Select Close in the Precursor dialog.

In the selected domain's left sidebar, expand AI Crawl Control and select Security.

In the crawler table, verify that PerplexityBot has Block Crawler off and GPTBot has Block Crawler on.

In the selected domain's left sidebar, select Security > Settings, then select Precursor under Filter by groups.

In the Precursor row, select the Configurable Rules: <number> rules link, where <number> is the displayed count.

On the Precursor Rules page, verify that MAXIMIZE - Login shows Active.

In the selected domain's left sidebar, select Security > Security rules.

Select Show all rule types, select Custom rules, and verify that CHALLENGE - Admin Credential Stuffing shows Active.

Select Show all rule types, select Advanced rate limiting rules, and verify that the inherited RATE-LIMIT - Product API shows Active.

On the same Advanced rate limiting rules list, verify that ARL - Leaked Credential Attempts and ARL - API Scraping both show Active.

Class handoff: Keep the verified settings and rules in their retained state for the class recap.

7. End-of-Lab Results

Record each activity as Success, Blocked, or Mismatch observed based on its final verification.

For every mismatch, record the time range, filters, expected value, observed value, and affected rule or setting.

Include missing sampled evidence as a result rather than treating it as proof that the configuration failed.

Submit one end-of-lab results summary covering all six activities and retain the final settings and rules for the class recap.

AppSec 4 - Stop Attacks in Real Time

Single Origin's search service is under application-layer pressure. You will evaluate a broad emergency control, diagnose why an inherited rate limit acts too late, derive a safer response from measured traffic, and prove that the application and legitimate clients recover.
Estimated activity time: 52 minutes.
ActivityModeGoalTime
1. Evaluate Under Attack ModeInstructor-driven, optional follow-alongObserve the reach of a broad emergency control and return it to Off4 minutes
2. Investigate the Search IncidentSoloCorrelate attack traffic, rate-limit responses, and application 503 responses18 minutes
3. Derive and Deploy a Safer Rate LimitSoloRecord a decision, then update the inherited rule18 minutes
4. Evaluate the Mitigation and CloseSynchronousProve activation, recovery, attack suppression, and legitimate-client safety12 minutes

Before You Begin

Prerequisites

Dashboard orientation

  1. Open the Cloudflare dashboard.
  2. Select your assigned account.
  3. Select Domains, select Overview, and select <lab-slug>.cftraining.app.
  4. Use the selected domain's left navigation for the rest of the lab.
DestinationNavigationPurpose
Domain OverviewOverviewBriefly evaluate Under Attack Mode
Security AnalyticsSecurity > Analytics > TrafficCompare request paths, rates, sources, and response codes
Security EventsSecurity > Analytics > EventsConfirm which rule and action handled a request
Request rate analysisSecurity > Analytics > Traffic, then Request rate analysisCompare legitimate and attack source-IP rates
Rate limiting rulesSecurity > Security rules > Rate limiting rulesInspect and edit the inherited rule

Activity 1 (SEC4-1): Evaluate Under Attack Mode

Mode and time: Instructor-driven with optional follow-along; 4 minutes. The instructor returns the setting to Off on the four-minute clock rather than waiting for everyone to finish. Required outcome: Evaluate the reach and ordinary-visitor cost of a zone-wide emergency control before choosing precise mitigation, then return it to Off.
Starting state: Under Attack Mode is Off, and the continuous shopper and attack traffic are visible in Security Analytics.
The instructor performs this demonstration in the instructor zone. You may optionally follow along in your assigned zone. The instructor starts the four-minute clock when enabling Under Attack Mode and does not wait for everyone to finish. At the cutoff, anyone who enabled the setting must disable it in that same zone immediately.

1.1 Evaluate Under Attack Mode

On the domain Overview page, find the Under Attack Mode card and turn its switch On.

In the Enable Under Attack Mode dialog, select Enable.

Confirm the dashboard displays the Under Attack Mode is active alert. This alert remains on Overview while the switch is active.

Open a private browser session on the lab Linux desktop and visit https://<lab-slug>.cftraining.app/search.

You should get a Managed Challenge page where you might need to select the Verify you are human checkbox, or the security verification might happen for you automatically. That's the Cloudflare Managed Challenge behavior.

Confirm the browser proceeds to the normal Single Origin search page.

If Single Origin was already open before Under Attack Mode was enabled, selecting another link can return an HTTP 500 error instead of immediately showing the verification page. Refresh the browser once. Confirm the verification page now shows the intended destination in the address bar, such as /login; select Verify you are human if prompted; then confirm the intended page loads.
Under Attack Mode applies across the entire zone and adds friction for ordinary shoppers, while this incident is concentrated on /search. It can be appropriate briefly during an emergency, but the proportionate response is a targeted /search rate-limit rule that suppresses abuse while preserving legitimate traffic.

Go back to the Overview page and select the enabled Under Attack Mode switch to open the Disable Under Attack Mode dialog. Confirm the I'm under attack mode toggle is On, turn it Off, wait for the update, and select Close.

If the toggle already appears Off before you change it, close the dialog, refresh the browser, and open the dialog again. The toggle should first appear On; then turn it Off.

Return to the browser and reload /search. Confirm it loads the normal Single Origin search page directly without the verification page.

This short test demonstrates that tradeoff without making it the permanent response.
Before you continue: Make sure you confirm that Under Attack Mode is off.

Activity 2 (SEC4-2): Investigate the Search Incident

Time: 18 minutes. Required outcome: Connect the attack, rate-limit responses, and 503 responses to the inherited rule's late trigger and short mitigation.

2.1 Isolate the Affected Route

Go to Security > Analytics and select Traffic.

Set the time range to Last 24 hours.

Select Add filter, set Path equals /search, and select Apply.

In Edge status codes, confirm 200, 429, and 503 are present.

If Source user agents is not displayed, select Show more top statistics.

In Source user agents, confirm SingleOrigin-HealthMonitor/1.0 (+https://singleorigin.example/monitoring) is present.

Select Add filter, set Edge status code equals 503, and select Apply.

In Sampled logs, select the caret at the left of the newest matching row to expand it.

Under Request details, confirm Path is /search.

Under Request details, confirm the query string contains q=connect-load and complex=1, in either order.

Under Request details, record IP in the saved field below.

Remove only the Edge status code equals 503 filter.

Why: The shared path identifies where the incident happens, while request behavior, rate, and response codes separate ordinary use from application pressure.

2.2 Inspect the Inherited Rule

Go to Security > Security rules.

Select Show all rule types.

In Rate limiting rules, open Block high rate of requests.

Confirm the rule matches exact path /search.

Confirm With the same characteristics is IP.

Confirm Requests is 200 and Period is 1 minute.

Confirm Action is Block and the selected mitigation duration is 1 minute.

Leave the rule without changing it.

Return to the Block high rate of requests row and select its linked count in the Events column.

Set the time range above Events summary to include the current lab traffic.

Select Add filter, set Path equals /search, and select Apply.

In Sampled logs, select the caret at the left of the newest matching row to expand it.

Under Matched service, confirm Action taken is Block.

Counter scope: Cloudflare also scopes this source-IP counter per data center, but that implicit scope is not a learner-editable characteristic in the rule.
Why: The rule waits long enough for expensive requests to consume the application's limited work slots, then stops mitigation after 60 seconds and lets the pressure return.
Return gate: You can explain why a firing rule can coexist with recurring 503 responses. No configuration changed.

Activity 3 (SEC4-3): Derive and Deploy a Safer Rate Limit

Mode: Solo. Time: 18 minutes. Required outcome: Record your own decision before revealing and deploying the scenario target.

3.1 Record Your Decision First

Go to Security > Analytics > Traffic and keep Path equals /search.

Select Request rate analysis.

In the source-IP results, locate the highest requests-per-minute value for each visible bucket.

Confirm the attack source is the clear outlier.

Record the highest non-attack source-IP bucket you observe in the saved field below.

Use that observed peak as the legitimate-traffic boundary for your decision.

Record the threshold, action, and mitigation timeout you would choose and why in the saved fields below.

Stop before the supplied target until all three choices are recorded.

3.2 Compare with the Scenario Target

SettingTarget
Match and counterKeep exact /search, source IP, and implicit per-data-center scope
Threshold60 requests per 60 seconds
ActionManaged Challenge
Mitigation timeout600 seconds

3.3 Edit the Existing Rule

Return to Security > Analytics > Traffic.

Keep Path equals /search applied.

Select Add filter, set Source IP equals <ATTACK_SOURCE_IP>, and select Apply.

Set a time range that includes at least 15 minutes of the current attack traffic, and note the time zone shown in the time-range control.

In the request chart, record the newest completed time bucket and its Mitigated by Cloudflare and Served by origin values in the saved field below.

Go to Security > Security rules.

Select Show all rule types.

In Rate limiting rules, open Block high rate of requests.

Keep exact /search, the IP characteristic, the 1 minute period, and the existing position.

Change Requests from 200 to 60.

Change Action from Block to Managed Challenge.

Under With the following behavior, keep Managed Challenge for the selected duration selected.

Change the selected duration from 1 minute to 10 minutes.

Immediately before selecting Save, record the current date and time in the dashboard time zone in the saved field below. Then select Save and wait for the save to complete.

Reopen the rule and confirm every saved value.

Why: This is one measured correction to a known rule, not another overlapping control.
Return gate: The enabled rule keeps exact /search and source-IP counting, with 60 requests per 60 seconds, Managed Challenge, and 600-second mitigation.

Activity 4 (SEC4-4): Evaluate the Mitigation and Close

Mode: Synchronous. Time: 12 minutes. Required outcome: Close the enforcement, recovery, and legitimate-client gates.

4.1 Prove Rule Activation

Go to Security > Analytics and select Events.

Select the time range above Events summary, then select Custom range.

The fields use the dashboard time zone. In From, enter <DEPLOYMENT_TIME>.

Set To to the current date and time, and select Apply.

Select Add filter, set Path equals /search, and select Apply.

Select Add filter, set Source user agent equals Connect-Threshold-Test/1.0, and select Apply.

Select Add filter, set Action equals Managed Challenge, and select Apply.

In Sampled logs, look for a matching row.

If no matching row appears, wait for the next scheduled run and allow time for Analytics to update.

After waiting, reopen the time range, update To to the current date and time, and select Apply.

If no matching row appears after Analytics updates, go to Security > Security rules, select Show all rule types, and open Block high rate of requests from Rate limiting rules.

If no matching row appears after Analytics updates, verify the saved threshold, action, duration, and status, close the editor without changing it, record blocked - no matching event in the saved field above, do not substitute a Managed Challenge Event from the attack source, and continue to 4.2 with the enforcement gate blocked.

If a matching row appears, confirm Managed Challenge has a non-zero total in Events summary.

In Rate limiting rules, confirm Block high rate of requests has a non-zero total.

In Sampled logs, select the caret at the left of the newest matching row to expand it.

In that row, under Matched service, confirm Action taken is Managed Challenge, not Block.

Record Date from the expanded row in Sampled logs.

Under Request details, record Ray ID in the saved field above; do not require one specific response status.

4.2 Prove Application Recovery

Return to Security > Analytics and select Traffic.

Keep the same time range used for the pre-deployment baseline and include at least 60 seconds after <DEPLOYMENT_TIME>.

Remove each existing filter.

Select Add filter, set Path equals /search, and select Apply.

Select Add filter, set Source IP equals <ATTACK_SOURCE_IP>, and select Apply.

In Sampled logs, select the caret at the left of the newest post-deployment row to expand it.

Under Request details, confirm the query string contains q=connect-load and complex=1, in either order.

Compare <PRE_DEPLOYMENT_BASELINE> with completed post-deployment buckets for Mitigated by Cloudflare and Served by origin.

Confirm the mitigated count grows while origin-served attack responses stop growing.

Select Add filter, set Edge status code equals 503, and select Apply.

In Sampled logs, confirm no 503 row has a Time in the 60-second post-deployment window.

Remove only the Edge status code equals 503 filter.

Record this as a sampled no-503 result, not proof that every application response succeeded.

Do not require total attack requests to drop because Cloudflare can continue receiving and mitigating them at the edge.

4.3 Protect Legitimate Clients

In Security > Analytics > Traffic, remove each existing filter.

Select Add filter, set Path equals /search, and select Apply.

Select Add filter, set Source IP does not equal <ATTACK_SOURCE_IP>, and select Apply.

Select Add filter, set Source user agent does not equal Connect-Threshold-Test/1.0, and select Apply.

Select Add filter, set Source user agent does not equal SingleOrigin-HealthMonitor/1.0 (+https://singleorigin.example/monitoring), and select Apply.

In Sampled logs, select the caret at the left of the newest remaining row to expand it.

Under Request details, confirm the query string contains q=espresso and complex=0, in either order.

Under Edge response, confirm Edge status code is 200.

Remove only the Source user agent does not equal SingleOrigin-HealthMonitor/1.0 (+https://singleorigin.example/monitoring) filter.

Select Add filter, set Source user agent equals SingleOrigin-HealthMonitor/1.0 (+https://singleorigin.example/monitoring), and select Apply.

In Edge status codes, confirm the monitor requests return 200.

In a private browser, open https://<lab-slug>.cftraining.app/search.

Confirm the ordinary browser search works normally.

Why: A challenge Event proves enforcement, but it does not prove recovery or legitimate-client safety. Each result closes a different incident-response question.
Final activity gate: Record the matching threshold-test Event timestamp and Ray ID, Managed Challenge action, no sampled 503 row in the 60-second post-deployment window, a growing mitigated count while origin-served attack responses stop growing, shopper 200, monitor 200, and successful ordinary-browser search. If any check fails, investigate that checkpoint. Do not add another rule.

Final Retained State

ItemRequired final state
Under Attack ModeOff
Bot Fight ModeUnchanged by the lab; Off when the control is exposed
RuleBlock high rate of requests, enabled
Match and counterExact /search, source IP, implicit per-data-center scope
Threshold60 requests per 60 seconds
ActionManaged Challenge
Mitigation timeout600 seconds
Class handoff: Keep the adjusted rule active through recap. The class reset restores Under Attack Mode Off, the original enabled rule with its 200-request threshold, Block action, and 60-second mitigation timeout, resets tenant pressure, clears learner-created AppSec 4 rules, and preserves the shopper, attack, monitor, and recurring SEC4-TEST schedules.

Troubleshooting

SymptomWhat to check
No /search TrafficConfirm exact lowercase path, broaden the time range, and wait for supplied traffic
No EventsConfirm the inherited rule is enabled and inspect fresh requests
Request rate analysis is missingConfirm you are on Security > Analytics > Traffic in the assigned Enterprise zone
Starting values differStop and ask the instructor to restore the baseline
The threshold-test run is missing from TrafficConfirm the time range and exact Connect-Threshold-Test/1.0 user-agent filter; do not create replacement traffic
Threshold-test Traffic appears but no matching Event appearsVerify the saved rule, record blocked - no matching event, and do not use attack-source Events as substitute proof
Managed Challenge returns an unexpected statusUse the Event action and client evidence; do not assume a universal status
Old 503 rows remain visibleCompare each row's Time with <DEPLOYMENT_TIME> and evaluate only the 60-second post-deployment window
Total attack requests continue after mitigationConfirm the mitigated count grows while origin-served attack responses and 503 stop growing; edge mitigation does not require the sender to stop
A legitimate client failsStop at that gate and inspect the matching request before changing the rule

Resources

Reference material for all four classes. External links open Cloudflare documentation in a new tab.

End of Lab Guide.