Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for session, ip, global collections, and custom variables #1227

Open
geoolekom opened this issue Nov 20, 2024 · 11 comments
Open

Support for session, ip, global collections, and custom variables #1227

geoolekom opened this issue Nov 20, 2024 · 11 comments

Comments

@geoolekom
Copy link
Contributor

Summary

Coraza doesn’t support predefined collections like session, ip, and global, or allow users to define their own custom collections or variables. These are super useful for tracking things like user sessions, rate limits by IP, or even global counters across transactions. Adding support for this would make Coraza way more flexible and bring it closer to ModSecurity in terms of what’s possible.

Basic example

This feature allows the IP-based restrictions like this:

# Initialize the IP collection
SecAction "id:2000,phase:1,nolog,pass,initcol:ip=%{REMOTE_ADDR}"

# Increment request count for this IP
SecAction "id:2001,phase:2,nolog,pass,setvar:ip.request_count=+1"

# Block IP after 100 requests
SecRule IP:request_count "@ge 100" \
    "id:2002,phase:2,deny,log,msg:'Rate limit exceeded for IP'"

Or session-based, like this:

# Initialize login attempts counter for the session
SecAction "id:1001,phase:1,nolog,pass,setvar:session.login_attempts=0"

# Increment login attempts on failed login
SecRule ARGS:username ".*" \
    "id:1002,phase:2,chain,pass,nolog"
    SecRule ARGS:password "!@eq secret" \
        "setvar:session.login_attempts=+1"

# Block the session after 5 failed login attempts
SecRule SESSION:login_attempts "@ge 5" \
    "id:1003,phase:2,deny,log,msg:'Too many login attempts in session'"

Motivation

Why this could be useful?

  • Session management: Track login attempts, session tokens, or anything related to a user.
  • IP-based logic: Rate-limit or block specific IPs.
  • Global counters: Count something across all requests, like total hits or error counts.
  • Customization: Let users define their own variables and collections to handle whatever logic they need.

Without these features, Coraza misses out on some of the more advanced use cases that ModSecurity handles out of the box. Adding them would make it much more powerful and easier to adopt for users migrating from ModSecurity.

@jcchavezs
Copy link
Member

We indeed need to support this. Notice there was a rate limiter plugin using an action as per https://github.com/VermaShivansh/coraza-ratelimit-plugin.

@geoolekom
Copy link
Contributor Author

I am also interested in implementing this feature. Exploring the code, I discovered that there were several attempts to reintroduce persistence functionality in Coraza:

Among these, I found the first attempt particularly well-structured, and I plan to build upon it in my implementation. Could you help me understand why these implementations were not successful?

Here the plan:

Introduce a New persistence Package

  • Create a new package named persistence.
  • Define a PersistenceEngine interface to abstract persistence logic.

Like this:

package persistence

type PersistenceEngine interface {
    Init(url string, ttl int) error
    Get(key string) collection.Collection
    Set(key string, data collection.Collection) error
}

Add InMemoryEngine Implementation

  • Implement a simple in-memory persistence engine.
  • Use jellydator/ttlcache to manage collections with expiration times.
  • Add unit tests for the package and engine implementation.

Expose Persistence field

  • Modify internal/corazawaf/waf.go to include a Persistence field.
  • Update the TransactionState interface to expose it.
type WAF struct {
    // Existing fields...
    Persistence persistence.PersistenceEngine
}

type TransactionState interface {
    // Existing methods...
    Persistence() persistence.PersistenceEngine
}

Implement actions

  • Implement the initcol action to initialize a collection.
  • Modify the setvar action to allow creation and modification of arbitrary collections. Maybe, in a separate PR if the changes become too extensive.

@jcchavezs What do you think about this plan? Am I missing something, or should I start implementing the feature?

@jcchavezs
Copy link
Member

The idea of persistence has been rounding this project for a while, mainly brought up by @jptosso. I always liked the idea as it would leverage context in decisions however it mainly did not progress for lack of use cases. I would start this as an experimental package and work it out from there but one of the requirements we have in the coraza project is that we want to see implementations using the required features see for example #1101 (comment) and #1212 (comment)

@jptosso
Copy link
Member

jptosso commented Nov 20, 2024

There is an old working PR for this. We would have to rebase it and start testing
Personally I like the feature but I don't have use cases and this is the first request we receive
Maybe we should provide better apis to build custom operators consuming persistence instead of adding persistence support for rules

@geoolekom
Copy link
Contributor Author

@jptosso Could you share the link to the request? I am deeply interested in implementing this.

@geoolekom
Copy link
Contributor Author

Speaking of real life use cases, we have a working ruleset that we would like to use with coraza. I can give you a set of rules that require at least in-memory persistence. Will that work?

@fzipi
Copy link
Member

fzipi commented Nov 22, 2024

Yes, it would work perfectly.

@fzipi
Copy link
Member

fzipi commented Nov 22, 2024

There is an old working PR for this. We would have to rebase it and start testing

Can you provide it here?

@geoolekom
Copy link
Contributor Author

@jcchavezs @jptosso As I promised, I'm back with examples of real-life usage of this feature.

WordPress compromised account login prevention

We store the session.compromised_hash and session.redirect_link to block attackers in an attempt to use compromised cookies after detection. Once an attacker completes a successful login we check records through RBL. If this account was listed as compromised by internal logic - we will store compromised account cookies hash locally, so they can't be used in the following transactions. Without storing these compromised cookies an attacker can ignore redirect bypassing protection.

# Compromised account login prevention using imunify-alert.com
SecRule REQUEST_METHOD "@rx POST" "id:88350144,chain,phase:3,nolog,auditlog,severity:5,t:none,msg:'IM360 WAF: WordPress compromised account login prevention with RBL||WPU:%{ARGS.log}||Hash:%{tx.log_sha}||User:%{SCRIPT_USERNAME}||RSV:6.52||T:APACHE||',redirect:https://imunify-alert.com/compromised.html?SN=%{SERVER_NAME}&SP=%{SERVER_PORT}&RFR=%{REQUEST_HEADERS.Referer}&URI=%{REQUEST_URI}&cms_name=wordpress&content_title_type=compromised_account&version=1,tag:'service_im360'"
SecRule ENV:IMUNIFY360_COMPROMISED_REDIRECT_URL "@rx ^$" "chain,t:none"
SecRule REQUEST_FILENAME "@contains /wp-login.php" "chain,t:none"
SecRule ARGS:log "!@rx ^$" "chain,t:none,t:urlDecode,capture,t:sha1,t:hexEncode,setvar:tx.log_sha=%{MATCHED_VAR},setvar:tx.compromised_user=%{MATCHED_VAR}.%{REQUEST_HEADERS.host},initcol:session=%{tx.compromised_user}"
SecRule &SESSION:compromised_cookies "@eq 0" "chain,t:none,setvar:tx.rbl_perf=1"
SecRule TX:compromised_user "@rbl wp-compromised.v2.rbl.imunify.com." "chain,t:none"
SecRule TX:compromised_user "!@rbl nxdomain.v2.rbl.imunify.com." "chain,t:none"
SecRule RESPONSE_HEADERS:set-cookie "@rx wordpress_logged_in_[^=]+=([^;]+);" "chain,t:none,t:urlDecode,capture,setvar:tx.auth_cookie=%{TX.1},setvar:session.compromised_cookies=%{TX.auth_cookie}"
SecRule ARGS:pwd "!@rx ^$" "t:none,t:sha1,t:hexEncode,capture,setvar:session.compromised_hash=%{MATCHED_VAR},setvar:session.redirect_link=https://imunify-alert.com/compromised.html?SN=%{SERVER_NAME}&SP=%{SERVER_PORT}&RFR=%{REQUEST_HEADERS.Referer}&URI=%{REQUEST_URI}&cms_name=wordpress&content_title_type=compromised_account&version=1,setvar:session.timeout=172800"

Rate limiting Facebook bot

This set of rules temporarily blocks a bot (expirevar:session.fb_bot_block=40) if it starts sending 120 requests (SESSION:fb_limit "@gt 120") within 10 seconds (expirevar:session.fb_limit=10).

We store in persistent storage:

  • The host name as the session name: initcol:session=%{REQUEST_HEADERS.Host}
  • The session's lifetime: setvar:session.timeout=60
  • The request counter and its lifetime: setvar:session.fb_limit=+1, expirevar:session.fb_limit=10
  • A bot-blocking flag and its lifetime: setvar:session.fb_limit=0, setvar:session.fb_bot_block=1, expirevar:session.fb_bot_block=40.
SecRule REQUEST_FILENAME "!@pm sitemap browserconfig.xml robots.txt ai.txt humans.txt favicon.ico ads.txt manifest.json browserconfig.xml crossdomain.xml manifest.webmanifest opensearchdescription.xml pgp-key.txt security.txt" "id:88350418,chain,phase:2,block,nolog,auditlog,severity:2,t:none,t:lowercase,msg:'IM360 WAF: Rate limit exceeded for Facebook Crawler Bot||Count:%{SESSION.fb_limit}||Range:%{REQUEST_HEADERS.Range}||MVN:%{MATCHED_VAR_NAME}||MV:%{MATCHED_VAR}||RSV:6.50||T:APACHE||',tag:'service_im360'"
SecRule REQUEST_HEADERS:User-Agent "@pm facebookexternalhit" "chain,t:none,initcol:session=%{REQUEST_HEADERS.Host}.%{REQUEST_HEADERS.User-Agent}"
SecRule SESSION:fb_bot_block "@eq 1" "t:none"

SecRule REQUEST_HEADERS:User-Agent "@pm facebookexternalhit" "id:88350419,chain,phase:2,block,nolog,auditlog,severity:2,initcol:session=%{REQUEST_HEADERS.Host}.%{REQUEST_HEADERS.User-Agent},msg:'IM360 WAF: Rate limit exceeded for Facebook Crawler Bot||Count:%{MATCHED_VAR}||Range:%{REQUEST_HEADERS.Range}||RSV:6.50||T:APACHE||',tag:'service_im360'"
SecRule REQUEST_FILENAME "!@pm sitemap browserconfig.xml robots.txt ai.txt humans.txt favicon.ico ads.txt manifest.json browserconfig.xml crossdomain.xml manifest.webmanifest opensearchdescription.xml pgp-key.txt security.txt" "chain,t:none,t:lowercase"
SecRule SESSION:fb_limit "@gt 120" "t:none,setvar:session.fb_limit=0,setvar:session.fb_bot_block=1,expirevar:session.fb_bot_block=40,setvar:session.timeout=60"

SecRule REQUEST_HEADERS:User-Agent "@pm facebookexternalhit" "id:88350439,chain,phase:2,pass,skip:1,nolog,severity:5,t:none,tag:'service_im360'"
SecRule REQUEST_HEADERS:Range "!@rx ^$" "chain,t:none"
SecRule REQUEST_FILENAME "!@pm sitemap browserconfig.xml robots.txt ai.txt humans.txt favicon.ico ads.txt manifest.json browserconfig.xml crossdomain.xml manifest.webmanifest opensearchdescription.xml pgp-key.txt security.txt" "t:none,t:lowercase,initcol:session=%{REQUEST_HEADERS.Host},setvar:session.fb_limit=+1,expirevar:session.fb_limit=10,setvar:session.timeout=60"

SecRule REQUEST_HEADERS:User-Agent "@pm facebookexternalhit" "chain,id:88350460,phase:2,pass,nolog,severity:5,t:none,tag:'service_im360'"
SecRule &REQUEST_HEADERS:Range "@eq 0" "t:none,initcol:session=%{REQUEST_HEADERS.Host},setvar:session.fb_limit=+1,expirevar:session.fb_limit=10,setvar:session.timeout=60"

@geoolekom
Copy link
Contributor Author

@jcchavezs @jptosso What do you think about this examples?

@jcchavezs
Copy link
Member

@geoolekom use cases look very legit! Do you have time to bootstrap a PR with an experimental interface on persistence for this cases?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants