Block based on response size

I have recently encounter with an issue regarding websocket responses size. I have some rpc nodes behind haproxy (on haproxy I have crowdsec) and RPC nodes works based on websocket. The issue is some users misuse RPC nodes by sending too many requests inside one websocket connection but I have websocket response size in haproxy log and now I want to block those IPs. any idea what scenario should i write? this is my sample log.

Nov 15 13:42:45 localhost haproxy[1457596]: 65.109.180.250:43330 [15/Nov/2023:13:33:54.225] rpc_and_ws~ backend1/server1 0/0/106/106/531061 101 110226935 - - --NI 404/403/5/5/0 0/0 "GET / HTTP/1.1" 

as you can see the response size is above 100mb.

Thanks

Hi @ehsanhajian,

Yes, you can detect this. You can choose to ban instantly the user on the first request or on multiple requests to avoid false positive following your context.
I’ll go with the instantly example, if you are using the official haproxy parser, you can have something like:

type: trigger
format: 2.0
name: ehsanhajian/rpc-dos
description: "Detect RPC dos"
filter: |
  evt.Parsed.program startsWith 'haproxy' && evt.Meta.log_type == 'http_access-log' && 
  Atof(evt.Parsed.bytes_read) > 100000000
groupby: "evt.Meta.source_ip"
blackhole: 2m
labels:
  remediation: true
  service: haproxy

I didn’t test, just to give you an example. If you want something more flexible you can use the leaky bucket

great thank you for your reply and if I want to block based on url and userAgent Should I add like below?

type: trigger
format: 2.0
name: ehsanhajian/rpc-dos
description: "Detect RPC dos"
filter: |
  evt.Parsed.program startsWith 'haproxy' && evt.Meta.log_type == 'http_access-log' && 
  Atof(evt.Parsed.bytes_read) > 100000000 && evt.Parsed.requested_url contains "MyURL" && evt.Parsed.user_agent contains "User_agent"
groupby: "evt.Meta.source_ip"
blackhole: 2m
labels:
  remediation: true
  service: haproxy

Yes, but you need to put the proper fields, evt.Parsed.requested_url doesn’t exist you need to use evt.Parsed.http_request.
You should look at the haproxy parser. For the user-agent, you may need to check if you have the user-agent in your logs.

good morning @he2ss. below is my scenario but It’s not working as expected. just to clarify my traffic is websocket and they have http 101 code in haproxy log. actually i want to block the IP address instantly when It’s matched with the scenario.

type: trigger
format: 2.0
name: ehsanhajian/rpc-dos
description: "Detect RPC dos"
filter: |
  evt.Parsed.program startsWith 'haproxy' && evt.Meta.log_type == 'http_access-log' && Atof(evt.Parsed.bytes_read) > 5242880 && evt.Parsed.http_request contains "myurl"
groupby: "evt.Meta.source_ip"
blackhole: 2m
labels:
  remediation: true
  service: haproxy

Hi @ehsanhajian,

maybe because of the last filter part (evt.Parsed.http_request contains "myurl") it was an example for you. It depends if it targeted specific request, but you can remove this last part of the filter, then it will trigger on any URI.

Thanks @he2ss . It’s working after I removed evt.Parsed.http_request. Any Idea how to add URL to the scenario?

You can use explain to list all variables so using your example above you can use these

$ cscli explain --log 'Nov 15 13:42:45 localhost haproxy[1457596]: 65.109.180.250:43330 [15/Nov/2023:13:33:54.225] rpc_and_ws~ backend1/server1 0/0/106/106/531061 101 110226935 - - --NI 404/403/5/5/0 0/0 "GET / HTTP/1.1"' --type syslog -v
line: Nov 15 13:42:45 localhost haproxy[1457596]: 65.109.180.250:43330 [15/Nov/2023:13:33:54.225] rpc_and_ws~ backend1/server1 0/0/106/106/531061 101 110226935 - - --NI 404/403/5/5/0 0/0 "GET / HTTP/1.1"
	├ s00-raw
	|	└ 🟢 crowdsecurity/syslog-logs (+12 ~9)
	|		└ update evt.ExpectMode : %!s(int=0) -> 1
	|		└ update evt.Stage :  -> s01-parse
	|		└ update evt.Line.Raw :  -> Nov 15 13:42:45 localhost haproxy[1457596]: 65.109.180.250:43330 [15/Nov/2023:13:33:54.225] rpc_and_ws~ backend1/server1 0/0/106/106/531061 101 110226935 - - --NI 404/403/5/5/0 0/0 "GET / HTTP/1.1"
	|		└ update evt.Line.Src :  -> /tmp/cscli_explain339870188/cscli_test_tmp.log
	|		└ update evt.Line.Time : 0001-01-01 00:00:00 +0000 UTC -> 2023-11-23 09:40:10.229279346 +0000 UTC
	|		└ create evt.Line.Labels.type : syslog
	|		└ update evt.Line.Process : %!s(bool=false) -> true
	|		└ update evt.Line.Module :  -> file
	|		└ create evt.Parsed.timestamp8601 : 
	|		└ create evt.Parsed.facility : 
	|		└ create evt.Parsed.logsource : syslog
	|		└ create evt.Parsed.message : 65.109.180.250:43330 [15/Nov/2023:13:33:54.225] rpc_and_ws~ backend1/server1 0/0/106/106/531061 101 110226935 - - --NI 404/403/5/5/0 0/0 "GET / HTTP/1.1"
	|		└ create evt.Parsed.pid : 1457596
	|		└ create evt.Parsed.priority : 
	|		└ create evt.Parsed.program : haproxy
	|		└ create evt.Parsed.timestamp : Nov 15 13:42:45
	|		└ update evt.Time : 0001-01-01 00:00:00 +0000 UTC -> 2023-11-23 09:40:10.229343954 +0000 UTC
	|		└ update evt.StrTime :  -> Nov 15 13:42:45
	|		└ create evt.Meta.datasource_path : /tmp/cscli_explain339870188/cscli_test_tmp.log
	|		└ create evt.Meta.datasource_type : file
	|		└ create evt.Meta.machine : localhost
	├ s01-parse
	|	├ 🟢 crowdsecurity/haproxy-logs (+47 ~2)
	|		├ update evt.Stage : s01-parse -> s02-enrich
	|		├ create evt.Parsed.feconn : 403
	|		├ create evt.Parsed.http_proto : 
	|		├ create evt.Parsed.verb : GET
	|		├ create evt.Parsed.haproxy_minute : 33
	|		├ create evt.Parsed.http_status_code : 101
	|		├ create evt.Parsed.retries : 0
	|		├ create evt.Parsed.server_name : server1
	|		├ create evt.Parsed.time_backend_response : 106
	|		├ create evt.Parsed.captured_request_cookie : -
	|		├ create evt.Parsed.http_version : 1.1
	|		├ create evt.Parsed.haproxy_month : Nov
	|		├ create evt.Parsed.client_port : 43330
	|		├ create evt.Parsed.srvconn : 5
	|		├ create evt.Parsed.http_host : 
	|		├ create evt.Parsed.http_verb : GET
	|		├ create evt.Parsed.bytes_read : 110226935
	|		├ create evt.Parsed.time_duration : 531061
	|		├ create evt.Parsed.accept_date : 15/Nov/2023:13:33:54.225
	|		├ create evt.Parsed.client_ip : 65.109.180.250
	|		├ create evt.Parsed.haproxy_monthday : 15
	|		├ create evt.Parsed.srv_queue : 0
	|		├ create evt.Parsed.time_queue : 0
	|		├ create evt.Parsed.time_backend_connect : 106
	|		├ create evt.Parsed.termination_state : --NI
	|		├ create evt.Parsed.captured_response_cookie : -
	|		├ create evt.Parsed.port : 
	|		├ create evt.Parsed.frontend_name : rpc_and_ws~
	|		├ create evt.Parsed.haproxy_time : 13:33:54.2
	|		├ create evt.Parsed.actconn : 404
	|		├ create evt.Parsed.captured_request_headers : 
	|		├ create evt.Parsed.haproxy_year : 2023
	|		├ create evt.Parsed.http_request : /
	|		├ create evt.Parsed.backend_queue : 0
	|		├ create evt.Parsed.beconn : 5
	|		├ create evt.Parsed.haproxy_hour : 13
	|		├ create evt.Parsed.time_request : 0
	|		├ create evt.Parsed.request : /
	|		├ create evt.Parsed.http_user : 
	|		├ create evt.Parsed.backend_name : backend1
	|		├ create evt.Parsed.captured_response_headers : 
	|		├ create evt.Parsed.haproxy_milliseconds : 5
	|		├ create evt.Parsed.haproxy_second : 54.2
	|		├ update evt.StrTime : Nov 15 13:42:45 -> 15/Nov/2023:13:33:54 -0000
	|		├ create evt.Meta.http_path : /
	|		├ create evt.Meta.http_status : 101
	|		├ create evt.Meta.log_type : http_access-log
	|		├ create evt.Meta.service : http
	|		├ create evt.Meta.source_ip : 65.109.180.250
	├ s02-enrich
	|	├ 🟢 crowdsecurity/dateparse-enrich (+2 ~2)
	|		├ create evt.Enriched.MarshaledTime : 2023-11-15T13:33:54Z
	|		├ update evt.Time : 2023-11-23 09:40:10.229343954 +0000 UTC -> 2023-11-15 13:33:54 +0000 UTC
	|		├ update evt.MarshaledTime :  -> 2023-11-15T13:33:54Z
	|		├ create evt.Meta.timestamp : 2023-11-15T13:33:54Z
	|	├ 🟢 crowdsecurity/geoip-enrich (+13)
	|		├ create evt.Enriched.ASNumber : 24940
	|		├ create evt.Enriched.IsInEU : true
	|		├ create evt.Enriched.Latitude : 60.179700
	|		├ create evt.Enriched.Longitude : 24.934400
	|		├ create evt.Enriched.SourceRange : 65.108.0.0/15
	|		├ create evt.Enriched.ASNNumber : 24940
	|		├ create evt.Enriched.ASNOrg : Hetzner Online GmbH
	|		├ create evt.Enriched.IsoCode : FI
	|		├ create evt.Meta.ASNNumber : 24940
	|		├ create evt.Meta.IsInEU : true
	|		├ create evt.Meta.IsoCode : FI
	|		├ create evt.Meta.ASNOrg : Hetzner Online GmbH
	|		├ create evt.Meta.SourceRange : 65.108.0.0/15
	|	├ 🟢 crowdsecurity/http-logs (+6)
	|		├ create evt.Parsed.file_dir : /
	|		├ create evt.Parsed.file_frag : 
	|		├ create evt.Parsed.impact_completion : true
	|		├ create evt.Parsed.static_ressource : false
	|		├ create evt.Parsed.file_ext : 
	|		├ create evt.Meta.http_args_len : 0
	|	├ 🟢 crowdsecurity/jellyfin-whitelist (unchanged)
	|	└ 🟢 crowdsecurity/nextcloud-whitelist (unchanged)
	├-------- parser success 🟢
	├ Scenarios
		└ 🟢 crowdsecurity/http-crawl-non_statics

So from above you have evt.Parsed.http_request or evt.Parsed.request or evt.Meta.http_path

many thanks. the command was very helpful