Caddy Web Server

465 views
Skip to first unread message

cyber...@gmail.com

unread,
Jan 7, 2023, 6:40:16 PM1/7/23
to Wazuh mailing list
Has anyone had any success in using Wazuh against the Caddy V2 webserver?

It produces logs in json format. If yes, would you mind sharing how you did it?

Thanks in advance.

Sean.

Juan Nicolás Asselle (Nico Asselle)

unread,
Jan 8, 2023, 9:34:15 PM1/8/23
to Wazuh mailing list

Hi Sean,

I’m not familiar with Caddy web server, but I’m with Wazuh! Let me help you with this. I will use https://caddyserver.com/docs/logging json log as reference

Wazuh log-picking module is wazuh-logcollector and its localfile configuration block, which should be added to Wazuh agent’s configuration file (ossec.conf or agent.conf if centralized configuration is used).

The localfile configuration should have:

  • Log path (location option): absolute path where the log will be continuously read. Will depend on https://caddyserver.com/docs/caddyfile/directives/log configuration
  • Log format (log_format): json in this case
  • Source identifier (label): useful to identify the log source during log processing on the Wazuh Manager
<localfile>
    <log_format>json</log_format>
    <location>/var/logs/caddy</location>
  <label key="source">caddy</label> 
  </localfile>

Then, when the logs arrives to Wazuh Manager, will be checked against the ruleset. Wazuh will get correctly parse/decode JSON logs by default, but custom rules should be created to trigger alerts based on your requirements. The next examples will group every Caddy log, and then trigger a log when a GET request came from the IP 10.0.0.123 as an example.

<group name="local,">

  <rule id="100001" level="0">
    <decoded_as>json</decoded_as>
    <field name="source">caddy</field>
    <description>Caddy Web Server - Grouping rule</description>
  </rule>

  <rule id="100002" level="3">
    <if_sid>100001</if_sid>
    <field name="request.method">GET</field>
    <description>Caddy Web Server - GET request</description>
  </rule>

  <rule id="100003" level="3">
    <if_sid>100002</if_sid>
    <field name="request.remote_ip">10.0.0.123</field>
    <description>Caddy Web Server - GET request from banned IP</description>
  </rule>

</group>

This could be tested with wazuh-logtest

Starting wazuh-logtest v4.3.11
Type one log per line

{"source":"caddy","level":"info","ts":1646861401.5241024,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"41342","proto":"HTTP/2.0","method":"GET","host":"localhost","uri":"/","headers":{"User-Agent":["curl/7.82.0"],"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com"}},"user_id":"","duration":0.000929675,"size":10900,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Encoding":["gzip"],"Content-Type":["text/html; charset=utf-8"],"Vary":["Accept-Encoding"]}}

**Phase 1: Completed pre-decoding.
        full event: '{"source":"caddy","level":"info","ts":1646861401.5241024,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"127.0.0.1","remote_port":"41342","proto":"HTTP/2.0","method":"GET","host":"localhost","uri":"/","headers":{"User-Agent":["curl/7.82.0"],"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com"}},"user_id":"","duration":0.000929675,"size":10900,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Encoding":["gzip"],"Content-Type":["text/html; charset=utf-8"],"Vary":["Accept-Encoding"]}}'

**Phase 2: Completed decoding.
        name: 'json'
        duration: '0.000930'
        level: 'info'
        logger: 'http.log.access'
        msg: 'handled request'
        request.headers.Accept: '['*/*']'
        request.headers.Accept-Encoding: '['gzip, deflate, br']'
        request.headers.User-Agent: '['curl/7.82.0']'
        request.host: 'localhost'
        request.method: 'GET'
        request.proto: 'HTTP/2.0'
        request.remote_ip: '127.0.0.1'
        request.remote_port: '41342'
        request.tls.cipher_suite: '4865'
        request.tls.proto: 'h2'
        request.tls.resumed: 'false'
        request.tls.server_name: 'example.com'
        request.tls.version: '772'
        request.uri: '/'
        resp_headers.Content-Encoding: '['gzip']'
        resp_headers.Content-Type: '['text/html; charset=utf-8']'
        resp_headers.Server: '['Caddy']'
        resp_headers.Vary: '['Accept-Encoding']'
        size: '10900'
        source: 'caddy'
        status: '200'
        ts: '1646861401.524102'

**Phase 3: Completed filtering (rules).
        id: '100002'
        level: '3'
        description: 'Caddy Web Server - GET request'
        groups: '['local']'
        firedtimes: '1'
        mail: 'False'
**Alert to be generated.

{"source":"caddy","level":"info","ts":1646861401.5241024,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"10.0.0.123","remote_port":"41342","proto":"HTTP/2.0","method":"GET","host":"localhost","uri":"/","headers":{"User-Agent":["curl/7.82.0"],"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com"}},"user_id":"","duration":0.000929675,"size":10900,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Encoding":["gzip"],"Content-Type":["text/html; charset=utf-8"],"Vary":["Accept-Encoding"]}}

**Phase 1: Completed pre-decoding.
        full event: '{"source":"caddy","level":"info","ts":1646861401.5241024,"logger":"http.log.access","msg":"handled request","request":{"remote_ip":"10.0.0.123","remote_port":"41342","proto":"HTTP/2.0","method":"GET","host":"localhost","uri":"/","headers":{"User-Agent":["curl/7.82.0"],"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"example.com"}},"user_id":"","duration":0.000929675,"size":10900,"status":200,"resp_headers":{"Server":["Caddy"],"Content-Encoding":["gzip"],"Content-Type":["text/html; charset=utf-8"],"Vary":["Accept-Encoding"]}}'

**Phase 2: Completed decoding.
        name: 'json'
        duration: '0.000930'
        level: 'info'
        logger: 'http.log.access'
        msg: 'handled request'
        request.headers.Accept: '['*/*']'
        request.headers.Accept-Encoding: '['gzip, deflate, br']'
        request.headers.User-Agent: '['curl/7.82.0']'
        request.host: 'localhost'
        request.method: 'GET'
        request.proto: 'HTTP/2.0'
        request.remote_ip: '10.0.0.123'
        request.remote_port: '41342'
        request.tls.cipher_suite: '4865'
        request.tls.proto: 'h2'
        request.tls.resumed: 'false'
        request.tls.server_name: 'example.com'
        request.tls.version: '772'
        request.uri: '/'
        resp_headers.Content-Encoding: '['gzip']'
        resp_headers.Content-Type: '['text/html; charset=utf-8']'
        resp_headers.Server: '['Caddy']'
        resp_headers.Vary: '['Accept-Encoding']'
        size: '10900'
        source: 'caddy'
        status: '200'
        ts: '1646861401.524102'

**Phase 3: Completed filtering (rules).
        id: '100003'
        level: '3'
        description: 'Caddy Web Server - GET request from banned IP'
        groups: '['local']'
        firedtimes: '1'
        mail: 'False'
**Alert to be generated.

Reference:
https://documentation.wazuh.com/current/user-manual/capabilities/log-data-collection/index.html
https://documentation.wazuh.com/current/user-manual/reference/ossec-conf/localfile.html
https://documentation.wazuh.com/current/user-manual/ruleset/json-decoder.html
https://documentation.wazuh.com/current/user-manual/ruleset/custom.html
https://documentation.wazuh.com/current/user-manual/capabilities/wazuh-logtest/index.html

Hope this helps!

M V

unread,
Jul 8, 2024, 4:00:25 AM7/8/24
to Wazuh | Mailing List
Hello Nico,
I understand that I am not the original author of the question. Having recently replaced ngix with caddy for my local environment, I am however faced with a similar question as the original poster. I followed your very well-written guide namely:

1. edited `/var/ossec/etc/rule/local_rules.xml` with the following conditions:
```
<group name="local,caddy">


  <rule id="100001" level="0">
    <decoded_as>json</decoded_as>
    <field name="source">caddy</field>
    <description>Caddy Web Server - Grouping rule</description>
  </rule>

  <rule id="100002" level="3">
    <if_sid>100001</if_sid>
    <field name="response.status" type="pcre2">^[4-5][0-9][0-9]$</field>
    <description>$(request.remote_ip) was rejected with $(response.status) by $(request.host)</description>
  </rule>

</group>
```
2. Modified wazuh agent's (running caddy) centralized `agent.conf` like so
```
  <agent_config profile="caddy"> 
    <localfile> 
      <location>/var/log/caddy/caddy.log</location>
      <log_format>json</log_format>
      <label key="source">caddy</label>
     </localfile>
   </agent_config>
```

3. ran `wazuh-logtest`
```
bin/wazuh-logtest
Starting wazuh-logtest v4.8.0

Type one log per line

{"level":"debug","ts":1719690782.3725615,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"localhost:8086","duration":0.003094967,"request":{"remote_ip":"192.168.1.193","remote_port":"52819","client_ip":"192.168.1.193","proto":"HTTP/2.0","method":"GET","host":"portainer.esco.ghaar","uri":"/influx","headers":{"X-Forwarded-Host":["portainer.esco.ghaar"],"User-Agent":["curl/8.4.0"],"Accept":["*/*"],"X-Forwarded-For":["192.168.1.193"],"X-Forwarded-Proto":["https"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"portainer.esco.ghaar"}},"headers":{"Accept-Ranges":["bytes"],"Content-Length":["534"],"Content-Type":["text/html; charset=utf-8"],"X-Influxdb-Build":["OSS"],"X-Influxdb-Version":["v2.7.5"],"Cache-Control":["public, max-age=3600"],"Etag":["\"5342613538\""],"Last-Modified":["Wed, 26 Apr 2023 13:05:38 GMT"],"Date":["Sat, 29 Jun 2024 19:53:02GMT"]},"status":300}

**Phase 1: Completed pre-decoding.
full event: '{"level":"debug","ts":1719690782.3725615,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"localhost:8086","duration":0.003094967,"request":{"remote_ip":"192.168.1.193","remote_port":"52819","client_ip":"192.168.1.193","proto":"HTTP/2.0","method":"GET","host":"portainer.esco.ghaar","uri":"/influx","headers":{"X-Forwarded-Host":["portainer.esco.ghaar"],"User-Agent":["curl/8.4.0"],"Accept":["*/*"],"X-Forwarded-For":["192.168.1.193"],"X-Forwarded-Proto":["https"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"portainer.esco.ghaar"}},"headers":{"Accept-Ranges":["bytes"],"Content-Length":["534"],"Content-Type":["text/html; charset=utf-8"],"X-Influxdb-Build":["OSS"],"X-Influxdb-Version":["v2.7.5"],"Cache-Control":["public, max-age=3600"],"Etag":["\"5342613538\""],"Last-Modified":["Wed, 26 Apr 2023 13:05:38 GMT"],"Date":["Sat, 29 Jun 2024 19:53:02GMT"]},"status":300}'


**Phase 2: Completed decoding.
name: 'json'
duration: '0.003095'
headers.Accept-Ranges: '['bytes']'
headers.Cache-Control: '['public, max-age=3600']'
headers.Content-Length: '['534']'
headers.Content-Type: '['text/html; charset=utf-8']'
headers.Date: '['Sat, 29 Jun 2024 19:53:02GMT']'
headers.Etag: '['"5342613538"']'
headers.Last-Modified: '['Wed, 26 Apr 2023 13:05:38 GMT']'
headers.X-Influxdb-Build: '['OSS']'
headers.X-Influxdb-Version: '['v2.7.5']'
level: 'debug'
logger: 'http.handlers.reverse_proxy'
msg: 'upstream roundtrip'
request.client_ip: '192.168.1.193'
request.headers.Accept: '['*/*']'
request.headers.User-Agent: '['curl/8.4.0']'
request.headers.X-Forwarded-For: '['192.168.1.193']'
request.headers.X-Forwarded-Host: '['portainer.esco.ghaar']'
request.headers.X-Forwarded-Proto: '['https']'
request.host: 'portainer.esco.ghaar'
request.method: 'GET'
request.proto: 'HTTP/2.0'
request.remote_ip: '192.168.1.193'
request.remote_port: '52819'

request.tls.cipher_suite: '4865'
request.tls.proto: 'h2'
request.tls.resumed: 'false'
request.tls.server_name: 'portainer.esco.ghaar'
request.tls.version: '772'
request.uri: '/influx'
status: '300'
ts: '1719690782.372561'
upstream: 'localhost:8086'
```

Phase 3 doesn't reach. I'm sure there's some fat-fingering on my end wrt creating the rule (especially because i'm unfamiliar with regex as used in wazuh. Essentially, my goal was to generate an alert for any non 2xx responses (`response.status`). I first tried `<field name="response.status" negate="yes" type="pcre2">^2[0-9][0-9]$</field>`, but encountered the same logtest output as above. So, I tried to simply it by changing the condition to straight up match to e.g. '200'. Even w/ adjusted input at `wazuh-logtest`, Phase 3 never reached. Your guidance would be much appreciated.

Juan Nicolás Asselle (Nico Asselle)

unread,
Jul 8, 2024, 9:59:58 AM7/8/24
to Wazuh | Mailing List
Hi Mulik,

At first glance, you are using an incomplete log for testing, since it does not contain the label that wazuh-logcollector (process responsible for reading localfiles) adds to it after reading it and before sending it to the Wazuh Server. Without this field, your parent rule 100001 does not match the log and therefore is not working for you. You need to add the label to the JSON during wazuh-logtest evaluation and will work as expected.

Regards,
Nico
Reply all
Reply to author
Forward
0 new messages