diff --git a/README.md b/README.md index 0b73eab..555c364 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # Apache Access Log Detections Mod for Powerpipe -View dashboards, run detections and scan for anomalies across your Apache access logs. +[Tailpipe](https://tailpipe.io) is an open-source CLI tool that allows you to collect logs and query them with SQL. - +The [Apache Access Log Detections Mod](https://hub.powerpipe.io/mods/turbot/tailpipe-mod-apache-access-log-detections) contains pre-built dashboards and detections, which can be used to monitor and analyze activity across your Apache servers. + +Run detection benchmarks: +![image](docs/images/apache_access_log_owasp_dashboard.png) + +View insights in dashboards: +![image](docs/images/apache_access_log_activity_dashboard.png) ## Documentation @@ -102,13 +106,12 @@ List available benchmarks: powerpipe benchmark list ``` - + Different output formats are also available, for more information please see [Output Formats](https://powerpipe.io/docs/reference/cli/benchmark#output-formats). @@ -126,4 +129,4 @@ Want to help but don't know where to start? Pick up one of the `help wanted` iss - [Powerpipe](https://github.com/turbot/powerpipe/labels/help%20wanted) - [Tailpipe](https://github.com/turbot/tailpipe/labels/help%20wanted) -- [Apache Access Log Detections Mod](https://github.com/turbot/tailpipe-mod-apache-access-log-detections/labels/help%20wanted) \ No newline at end of file +- [Apache Access Log Detections Mod](https://github.com/turbot/tailpipe-mod-apache-access-log-detections/labels/help%20wanted) diff --git a/dashboards/activity_dashboard.pp b/dashboards/activity_dashboard.pp new file mode 100644 index 0000000..e6bf8c4 --- /dev/null +++ b/dashboards/activity_dashboard.pp @@ -0,0 +1,339 @@ +dashboard "activity_dashboard" { + title = "Access Log Activity Dashboard" + documentation = file("./dashboards/docs/activity_dashboard.md") + + tags = { + type = "Dashboard" + service = "Apache/AccessLog" + } + + container { + # Analysis + card { + query = query.activity_dashboard_total_logs + width = 2 + } + + card { + query = query.activity_dashboard_success_count + width = 2 + type = "ok" + } + + card { + query = query.activity_dashboard_redirect_count + width = 2 + type = "info" + } + + card { + query = query.activity_dashboard_bad_request_count + width = 2 + type = "alert" + } + + card { + query = query.activity_dashboard_error_count + width = 2 + type = "alert" + } + } + + container { + + chart { + title = "Requests by Day" + query = query.activity_dashboard_requests_by_day + width = 6 + type = "line" + } + + chart { + title = "Requests by HTTP Method" + query = query.activity_dashboard_requests_by_http_method + width = 6 + type = "bar" + } + + chart { + title = "Requests by Status Code" + query = query.activity_dashboard_requests_by_status_code + width = 6 + type = "pie" + } + + chart { + title = "Top 10 User Agents (Requests)" + query = query.activity_dashboard_requests_by_user_agent + width = 6 + type = "pie" + } + + chart { + title = "Top 10 Clients (Requests)" + query = query.activity_dashboard_top_10_clients + width = 6 + type = "table" + } + + chart { + title = "Top 10 URLs (Requests)" + query = query.activity_dashboard_top_10_urls + width = 6 + type = "table" + } + + chart { + title = "Top 10 URLs (Successful Requests)" + query = query.activity_dashboard_requests_by_successful_requests + width = 6 + type = "table" + } + + chart { + title = "Top 10 URLs (Errors)" + query = query.activity_dashboard_requests_by_errors + width = 6 + type = "table" + } + } + +} + +# Queries +query "activity_dashboard_total_logs" { + title = "Log Count" + description = "Count the total Apache log entries." + + sql = <<-EOQ + select + count(*) as "Total Requests" + from + apache_access_log; + EOQ +} + +query "activity_dashboard_success_count" { + title = "Successful Request Count" + description = "Count of successful HTTP requests (status 2xx)." + + sql = <<-EOQ + select + count(*) as "Successful (2xx)" + from + apache_access_log + where + status between 200 and 299; + EOQ +} + +query "activity_dashboard_redirect_count" { + title = "Redirect Request Count" + description = "Count of redirect HTTP requests (status 3xx)." + + sql = <<-EOQ + select + count(*) as "Redirections (3xx)" + from + apache_access_log + where + status between 300 and 399; + EOQ +} + +query "activity_dashboard_bad_request_count" { + title = "Bad Request Count" + description = "Count of client error HTTP requests (status 4xx)." + + sql = <<-EOQ + select + count(*) as "Bad Requests (4xx)" + from + apache_access_log + where + status between 400 and 499; + EOQ +} + +query "activity_dashboard_error_count" { + title = "Server Error Count" + description = "Count of server error HTTP requests (status 5xx)." + + sql = <<-EOQ + select + count(*) as "Server Errors (5xx)" + from + apache_access_log + where + status between 500 and 599; + EOQ +} + +query "activity_dashboard_top_10_clients" { + title = "Top 10 Clients (Requests)" + description = "List the top 10 client IPs by request count." + + sql = <<-EOQ + select + remote_addr as "Client IP", + count(*) as "Request Count" + from + apache_access_log + group by + remote_addr + order by + count(*) desc, + remote_addr + limit 10; + EOQ +} + +query "activity_dashboard_top_10_urls" { + title = "Top 10 URLs (Requests)" + description = "List the top 10 requested URLs by request count." + + sql = <<-EOQ + select + request_uri as "URL", + count(*) as "Request Count" + from + apache_access_log + where + request_uri is not null + group by + request_uri + order by + count(*) desc, + request_uri + limit 10; + EOQ +} + +query "activity_dashboard_requests_by_day" { + title = "Requests by Day" + description = "Count of requests grouped by day." + + sql = <<-EOQ + select + strftime(tp_timestamp, '%Y-%m-%d') as "Date", + count(*) as "Request Count" + from + apache_access_log + group by + strftime(tp_timestamp, '%Y-%m-%d') + order by + strftime(tp_timestamp, '%Y-%m-%d'); + EOQ +} + +query "activity_dashboard_requests_by_status_code" { + title = "Requests by Status Code" + description = "Count of rqeuests grouped by status code." + + sql = <<-EOQ + select + case + when status between 200 and 299 then '2xx Success' + when status between 300 and 399 then '3xx Redirect' + when status between 400 and 499 then '4xx Client Error' + when status between 500 and 599 then '5xx Server Error' + else 'Other' + end as "Status Category", + count(*) as "Request Count" + from + apache_access_log + where + status is not null + group by + "Status Category" + order by + "Status Category"; + EOQ +} + +query "activity_dashboard_requests_by_http_method" { + title = "Requests by HTTP Method" + description = "Distribution of HTTP methods used in requests." + + sql = <<-EOQ + select + request_method as "HTTP Method", + count(*) as "Request Count" + from + apache_access_log + where + request_method is not null + group by + request_method + order by + count(*) asc, + request_method; + EOQ +} + +query "activity_dashboard_requests_by_successful_requests" { + title = "Top 10 URLs (Successful Requests)" + description = "List the top 10 requested URLs by successful request count." + + sql = <<-EOQ + select + request_uri as "Path", + count(*) as "Request Count", + string_agg(distinct status::text, ', ' order by status::text) as "Status Codes" + from + apache_access_log + where + status between 200 and 299 + and request_uri is not null + group by + request_uri + order by + count(*) desc, + request_uri + limit 10; + EOQ +} + +query "activity_dashboard_requests_by_errors" { + title = "Top 10 URLs (Errors)" + description = "List the top 10 requested URLs by error count." + + sql = <<-EOQ + select + request_uri as "Path", + count(*) as "Error Count", + string_agg(distinct status::text, ', ' order by status::text) as "Status Codes" + from + apache_access_log + where + status between 400 and 599 + and request_uri is not null + group by + request_uri + order by + count(*) desc, + request_uri + limit 10; + EOQ +} + +query "activity_dashboard_requests_by_user_agent" { + title = "Top 10 User Agents (Requests)" + description = "Distribution of user agents in requests." + + sql = <<-EOQ + select + http_user_agent as "User Agent", + count(*) as "Request Count" + from + apache_access_log + where + http_user_agent is not null + group by + http_user_agent + order by + count(*) desc, + http_user_agent + limit 10; + EOQ +} diff --git a/dashboards/docs/activity_dashboard.md b/dashboards/docs/activity_dashboard.md new file mode 100644 index 0000000..14178bc --- /dev/null +++ b/dashboards/docs/activity_dashboard.md @@ -0,0 +1,11 @@ +This dashboard answers the following questions: + +- How many HTTP requests has the Apache server handled? +- What is the distribution of HTTP status codes (success, redirect, client errors, server errors)? +- What HTTP methods are being used most frequently? +- How has request volume changed over time? +- Which browsers and tools are accessing the server? +- Which client IPs are generating the most traffic? +- Which URIs are most frequently requested? +- Which paths have the most successful requests? +- Which paths are generating the most errors? diff --git a/detections/access_logs.pp b/detections/access_logs.pp new file mode 100644 index 0000000..b5d267f --- /dev/null +++ b/detections/access_logs.pp @@ -0,0 +1,15 @@ +benchmark "access_log_detections" { + title = "Access Log Detections" + description = "This benchmark contains recommendations when scanning access logs." + type = "detection" + children = [ + benchmark.cross_site_scripting_detections, + benchmark.local_file_inclusion_detections, + benchmark.remote_command_execution_detections, + benchmark.sql_injection_detections, + ] + + tags = merge(local.apache_access_log_detections_common_tags, { + type = "Benchmark" + }) +} diff --git a/detections/cross_site_scripting.pp b/detections/cross_site_scripting.pp new file mode 100644 index 0000000..20d8282 --- /dev/null +++ b/detections/cross_site_scripting.pp @@ -0,0 +1,577 @@ +locals { + cross_site_scripting_common_tags = merge(local.apache_access_log_detections_common_tags, { + category = "Cross-Site Scripting" + }) +} + +benchmark "cross_site_scripting_detections" { + title = "Cross-Site Scripting (XSS) Detections" + description = "This benchmark contains cross-site scripting (XSS) focused detections when scanning access logs." + type = "detection" + children = [ + detection.cross_site_scripting_angular_template, + detection.cross_site_scripting_attribute_injection, + detection.cross_site_scripting_common_patterns, + detection.cross_site_scripting_dom_based, + detection.cross_site_scripting_encoding, + detection.cross_site_scripting_html_injection, + detection.cross_site_scripting_javascript_methods, + detection.cross_site_scripting_javascript_uri, + detection.cross_site_scripting_script_tag, + ] + + tags = merge(local.cross_site_scripting_common_tags, { + type = "Benchmark" + }) +} + +detection "cross_site_scripting_common_patterns" { + title = "Cross-Site Scripting Common Patterns" + description = "Detect basic Cross-Site Scripting (XSS) attack patterns in HTTP requests and User-Agent headers." + documentation = file("./detections/docs/cross_site_scripting_common_patterns.md") + severity = "critical" + display_columns = local.detection_display_columns + + query = query.cross_site_scripting_common_patterns + + tags = merge(local.cross_site_scripting_common_tags, { + mitre_attack_ids = "TA0002:T1059.007", + owasp_top_10 = "A03:2021-Injection" + }) +} + +query "cross_site_scripting_common_patterns" { + sql = <<-EOQ + select + ${local.detection_sql_columns} + from + apache_access_log + where + ( + request_uri is not null + and ( + -- Common XSS patterns + request_uri ilike '%alert(%' + or request_uri ilike '%prompt(%' + or request_uri ilike '%confirm(%' + or request_uri ilike '%eval(%' + or request_uri ilike '%document.cookie%' + or request_uri ilike '%document.domain%' + or request_uri ilike '%document.write%' + -- URL encoded variants + or request_uri ilike '%<script%' + or request_uri ilike '%\\x3Cscript%' + ) + ) + OR + ( + http_user_agent is not null + and ( + -- Common XSS patterns + http_user_agent ilike '%alert(%' + or http_user_agent ilike '%prompt(%' + or http_user_agent ilike '%confirm(%' + or http_user_agent ilike '%eval(%' + or http_user_agent ilike '%document.cookie%' + or http_user_agent ilike '%document.domain%' + or http_user_agent ilike '%document.write%' + -- URL encoded variants + or http_user_agent ilike '%<script%' + or http_user_agent ilike '%\\x3Cscript%' + ) + ) + order by + tp_timestamp desc; + EOQ +} + +detection "cross_site_scripting_script_tag" { + title = "Cross-Site Scripting Script Tag" + description = "Detect Cross-Site Scripting attacks using script tags to execute arbitrary JavaScript code in requests and User-Agent headers." + documentation = file("./detections/docs/cross_site_scripting_script_tag.md") + severity = "critical" + display_columns = local.detection_display_columns + + query = query.cross_site_scripting_script_tag + + tags = merge(local.cross_site_scripting_common_tags, { + mitre_attack_ids = "TA0002:T1059.007", + owasp_top_10 = "A03:2021-Injection" + }) +} + +query "cross_site_scripting_script_tag" { + sql = <<-EOQ + select + ${local.detection_sql_columns} + from + apache_access_log + where + ( + request_uri is not null + and ( + -- Standard script tags + request_uri ilike '% + or request_uri ilike '%<img%onerror%' -- Hex encoded + or http_user_agent ilike '%<img%onerror%' -- Hex encoded `), Base64 encoding, URL encoding, and other encoding schemas that might be used to disguise malicious JavaScript. + +Encoded XSS attacks are particularly dangerous because they can bypass many security filters and Web Application Firewalls (WAFs) that only check for literal script tags or JavaScript keywords. By encoding these elements, attackers can create payloads that will be decoded by the browser at runtime but may pass through server-side security controls undetected. + +For example, an attacker might encode a script tag as `<script>alert(1)</script>`, which appears harmless to basic security filters but will be interpreted as `` when rendered by the browser. Similarly, Base64 encoding can be used to completely obscure the contents of a payload until it's decoded and executed. + +By examining both request URIs and User-Agent headers, this detection can identify attackers who attempt to evade security controls by hiding their encoded payloads in HTTP headers rather than request parameters. This comprehensive approach helps security teams identify sophisticated XSS attempts that specifically aim to bypass traditional security controls through encoding techniques. + +**References**: +- [OWASP XSS Filter Evasion Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html) +- [CWE-79: Improper Neutralization of Input During Web Page Generation](https://cwe.mitre.org/data/definitions/79.html) +- [OWASP Top 10 2021: A03 Injection](https://owasp.org/Top10/A03_2021-Injection/) +- [MITRE ATT&CK: T1059.007 Command and Scripting Interpreter: JavaScript](https://attack.mitre.org/techniques/T1059/007/) \ No newline at end of file diff --git a/detections/docs/cross_site_scripting_event_handlers.md b/detections/docs/cross_site_scripting_event_handlers.md new file mode 100644 index 0000000..7608716 --- /dev/null +++ b/detections/docs/cross_site_scripting_event_handlers.md @@ -0,0 +1,17 @@ +## Overview + +The XSS Event Handler Attack detection identifies Cross-Site Scripting (XSS) attacks that specifically target HTML event handlers to execute malicious JavaScript. Event handlers like `onload`, `onerror`, and `onclick` can be injected into HTML elements to trigger JavaScript execution when certain browser events occur. + +This detection examines both HTTP requests and User-Agent headers for patterns indicating event handler-based XSS attempts. It focuses on identifying both common event handlers that are frequently targeted in XSS attacks and less common event handlers that may be used to evade basic security filters. + +Event handler XSS attacks are particularly dangerous because they can bypass traditional XSS filters that focus primarily on script tags. Attackers can inject these event handlers into various HTML elements, creating multiple attack vectors. For example, an attacker might inject `` into a comment field, causing the malicious JavaScript to execute when the image fails to load. + +Modern web applications have numerous event handlers available, and new ones are introduced with each HTML5 specification update. This detection looks for patterns indicating attempts to exploit these event handlers in both request URIs and User-Agent headers, allowing it to catch attackers who attempt to evade detection by hiding their payloads in HTTP headers rather than request parameters. + +By monitoring for these event handler patterns, security teams can identify both reconnaissance activities and active exploitation attempts targeting their web applications through this specific XSS vector. + +**References**: +- [OWASP XSS Filter Evasion Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.html) +- [CWE-79: Improper Neutralization of Input During Web Page Generation](https://cwe.mitre.org/data/definitions/79.html) +- [OWASP Top 10 2021: A03 Injection](https://owasp.org/Top10/A03_2021-Injection/) +- [MITRE ATT&CK: T1059.007 Command and Scripting Interpreter: JavaScript](https://attack.mitre.org/techniques/T1059/007/) \ No newline at end of file diff --git a/detections/docs/cross_site_scripting_html_injection.md b/detections/docs/cross_site_scripting_html_injection.md new file mode 100644 index 0000000..26520ea --- /dev/null +++ b/detections/docs/cross_site_scripting_html_injection.md @@ -0,0 +1,17 @@ +## Overview + +The XSS HTML Injection detection identifies Cross-Site Scripting (XSS) attacks that use HTML tag injection to execute malicious JavaScript. Unlike direct script tag injection, this attack vector leverages various HTML elements with event handlers or specific attributes that can execute JavaScript code. + +This detection examines both HTTP requests and User-Agent headers for HTML elements commonly used in XSS attacks, including `