bokamba / logforge / parse / Cisco ASA firewall

$ logforge parse cisco-asa

Parse Cisco ASA firewall logs → regex, Grok, Wazuh & rsyslog

Cisco ASA firewalls emit syslog, and the defining feature of every message is the mnemonic tag %ASA-<severity>-<message-id>: that opens the ASA-specific portion of the line. After the standard syslog header (a timestamp and the device name or IP), you get something like %ASA-4-106023: — where 4 is the syslog severity (0 emergency through 7 debug) and 106023 is the message ID. That numeric message ID is the key to the whole line: it, not the free text, tells you what happened. 106023 is 'Deny protocol src … dst … by access-group', 302013/302014 are TCP connection built and torn down, 302015/302016 are the UDP equivalents, 106100 is an ACL permit/deny hit with counters, and 305011/305012 cover NAT/xlate creation. Learn the message ID and the rest of the line becomes predictable.

The parsing trap is exactly that per-message-id variability: the text after the mnemonic is free-form and its layout changes with the message ID, so there is no single field template that fits all ASA lines. A 106023 deny reads 'Deny tcp src outside:203.0.113.45/51234 dst inside:192.0.2.10/443 by access-group "outside_access_in"', while a 302013 built-connection line reads 'Built inbound TCP connection 12345 for outside:203.0.113.45/51234 (203.0.113.45/51234) to inside:192.0.2.10/443 (192.0.2.10/443)'. Both encode addresses as interface:ip/port — the interface name (outside, inside, dmz) is prepended to the address with a colon, and the port follows a slash — but the surrounding words, the connection ID, and the presence of a NAT-translated address in parentheses all depend on the message ID. A correct parser therefore branches on the message ID first and applies a per-ID extraction, rather than trying one regex for everything.

For detection the load-bearing pieces are the severity (level 3-4 messages are where denies and problems cluster), the message ID (group and alert by it), and the interface:ip/port five-tuple you pull from the body. 106023 denies per source IP surface scanning and blocked lateral movement; a flood of 302013/302014 built/torn pairs profiles a host's connection behavior; 106100 with its hit counters feeds ACL tuning. Because the interface name is embedded in each address (outside: vs inside:), you also get directionality for free — which side originated the connection — without a separate field.

Open this in LogForge →

What a Cisco ASA firewall line looks like

The syslog sample below is fed verbatim into the engine to produce every parser on this page.

<166>%ASA-4-106023: Deny tcp src outside:203.0.113.45/51234 dst inside:192.0.2.10/443 by access-group "outside_access_in"
<164>%ASA-4-106023: Deny udp src outside:198.51.100.23/40012 dst inside:192.0.2.20/53 by access-group "outside_access_in"

Detected fields

The engine classified this sample as freeform and consolidated 10 fields across 2 lines. Fields marked literal were identical on every sample line, so they are baked into the pattern as anchors rather than captured.

  • literal : literal
  • _lit1 : literal · literal
  • literal2 : literal
  • _lit2 : literal · literal
  • literal3 : literal
  • _lit3 : literal · literal
  • literal4 : literal
  • _lit4 : literal · literal
  • _lit5 : literal · literal
  • quoted_string : quoted_string · literal

Regex (named capture groups)

# sample: <166>%ASA-4-106023: Deny tcp src outside:203.0.113.45/51234 dst inside:192.0.2.10/443 by access-group "outside_access_in"
# groups: literal=<166>%ASA-4-106023, literal2=tcp, literal3=outside:203.0.113.45/51234, literal4=inside:192.0.2.10/443
^(?<literal><\d+>%[A-Za-z]+-\d+-\d+): Deny (?<literal2>[A-Za-z]+) src (?<literal3>[A-Za-z]+:\d+\.\d+\.\d+\.\d+/\d+) dst (?<literal4>[A-Za-z]+:\d+\.\d+\.\d+\.\d+/\d+) by access-group "outside_access_in"$

Grok pattern (Logstash / Elastic)

%{DATA:literal}: Deny %{NOTSPACE:literal2} src %{NOTSPACE:literal3} dst %{NOTSPACE:literal4} by access-group "outside_access_in
  • note constant field "quoted_string" embedded as literal anchor "outside_access_in" (varying=false)

Wazuh decoder (OS_Regex XML)

<!--
  Generated by LogForge - Wazuh decoder (OS_Regex dialect, not PCRE)
  sample: &lt;166>%ASA-4-106023: Deny tcp src outside:203.0.113.45/51234 dst inside:192.0.2.10/443 by access-group "outside_access_in"
  test with: /var/ossec/bin/wazuh-logtest
-->

<decoder name="cisco-asa-freeform">
  <prematch>^</prematch>
</decoder>
  • note could not derive a distinctive <prematch>; the emitted anchor is permissive — tune before deploying
  • note field "literal" has no safe OS_Regex pattern before the ":" terminator — template truncated; field(s) omitted: literal, _lit1, literal2, _lit2, literal3, _lit3, literal4, _lit4, _lit5, quoted_string
  • note no varying fields could be captured — parent decoder emitted alone
  • note decoder order and prematch specificity may need site-specific tuning (other decoders in your ruleset can shadow these) — validate with /var/ossec/bin/wazuh-logtest

rsyslog template / liblognorm rulebase

version=2
# cisco_asa — liblognorm v2 rulebase (generated by LogForge)
# Usage with rsyslog (mmnormalize runs liblognorm):
#   module(load="mmnormalize")
#   action(type="mmnormalize" rulebase="/etc/rsyslog.d/cisco_asa.rb" useRawMsg="on")
# Literal "%" is escaped as "%%"; raw tabs are written as \x09.
rule=cisco_asa:%literal:char-to{"extradata":":"}%: Deny %literal2:word% src %literal3:word% dst %literal4:word% by access-group "outside_access_in"
  • note trailing literal "\"" reconstructed from line 1
  • note chosen parser types: literal=char-to(:), literal2=word, literal3=word, literal4=word

FAQ

What is the %ASA-x-nnnnnn tag in a Cisco ASA log?
It is the ASA message identifier. %ASA is the device class, the single digit after it is the syslog severity (0 emergency … 7 debug), and the six-digit number is the message ID that names the specific event — 106023 for an ACL deny, 302013 for a TCP connection built, and so on. The message ID, not the free text, is what you branch on when parsing.
Why can I not write one regex for all ASA messages?
Because the text after the mnemonic is free-form and its structure differs per message ID. A 106023 deny, a 302013 connection-built, and a 106100 ACL-hit line arrange their fields differently. Extract the severity and message ID with a common pattern, then apply a per-message-id sub-pattern to the body — order-independent field templates keyed on the ID.
How are addresses formatted in ASA logs?
As interface:ip/port. The ASA prepends the interface name (outside, inside, dmz) to the address with a colon and appends the port after a slash — e.g. outside:203.0.113.45/51234. For NAT/xlate messages a translated address often follows in parentheses. This means you get the traffic direction from the interface name without a separate field.
Which message IDs matter most for security monitoring?
106023 (deny by access-group) and 106100 (ACL permit/deny with hit counts) for policy enforcement, 302013/302014 (TCP built/teardown) and 302015/302016 (UDP) for connection tracking, and 305011/305012 for NAT/xlate. Alerting on 106023 grouped by source IP is the classic scan- and blocked-traffic detection.

Try it on your own Cisco ASA firewall lines

Paste a few real lines, review the detected fields, and copy whichever format your stack needs. Free, no account, nothing uploaded.

Open this sample in LogForge →