Forum Discussion
ASM Brute-Force - Common Username
- Mar 14, 2022
Ok so turns out that part of the problem was with how I was using findstr.
What was happening:
set raw_password [findstr $payload $static::password_tag "9"]
With the line above under the normal login process with: Content-Type: application/x-www-form-urlencoded
this was correctly grabbing the password from the payload.
But when the CAPTCHA changed this to: Content-Type: multipart/form-data
It was capturing the password including the multipart boundary, therefore changing the Basic Auth header and causing a bunch of issues (not being able to solve the CAPTCHA at all, or in some cases allowing anything to be entered to solve).
So we changed the password capture to break on "\r\n" which seems to have done the trick.
The final iRule we ended up using was:
when RULE_INIT { ## user-defined: size in bytes of HTTP request to parse set static::max_req_size 1048576 ## the names of the tags for the username and password. set static::customerid_tag "customer_id" set static::username_tag "username" set static::password_tag "password" set static::login_url "/user_login.php" } when HTTP_REQUEST { if { ([HTTP::method] eq "POST") && ([string tolower [HTTP::uri]] contains $static::login_url ) } { # Trigger collection for up to max_req_size of data set modify_response_content_type 1 if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= $static::max_req_size } { set content_length [HTTP::header "Content-Length"] } else { set content_length $static::max_req_size } # Check if $content_length is not set to 0 if { $content_length > 0} { HTTP::collect $content_length } } } when HTTP_REQUEST_DATA { # collect the incoming payload set payload [HTTP::payload] ## skipahead is set to fieldname +1 to avoid matching the equals sign. set raw_customerid [findstr $payload $static::customerid_tag "12" "&"] set raw_username [findstr $payload $static::username_tag "9" "&"] set raw_password [findstr $payload $static::password_tag "9" "\r\n"] set auth_header_b64 [b64encode "$raw_customerid-$raw_username:$raw_password"] HTTP::header insert "Authorization" "Basic $auth_header_b64" } when HTTP_REQUEST_RELEASE { #Don't pass header to backend HTTP::header remove "Authorization" }
Don't forget to mask the values of the Authorization header in the ASM policy.
After doing some trouble-shooting found a problem with the iRule.
I had configured the iRule not to capture the CAPTCHA events as that was causing me a problem. This turned out to be the main issue.
The initial "normal" payload had a
Content-Type: application/x-www-form-urlencoded
But the "CAPTCHA" payload was
Content-Type: multipart/form-data
With this payload the extraction of the password field included the boundary data, so when it created the Authorization header this was different from the original.
Just running through a few more sanity tests and will then upload the updated iRule.
- Mark_van_DMar 14, 2022Cirrostratus
Ok so turns out that part of the problem was with how I was using findstr.
What was happening:
set raw_password [findstr $payload $static::password_tag "9"]
With the line above under the normal login process with: Content-Type: application/x-www-form-urlencoded
this was correctly grabbing the password from the payload.
But when the CAPTCHA changed this to: Content-Type: multipart/form-data
It was capturing the password including the multipart boundary, therefore changing the Basic Auth header and causing a bunch of issues (not being able to solve the CAPTCHA at all, or in some cases allowing anything to be entered to solve).
So we changed the password capture to break on "\r\n" which seems to have done the trick.
The final iRule we ended up using was:
when RULE_INIT { ## user-defined: size in bytes of HTTP request to parse set static::max_req_size 1048576 ## the names of the tags for the username and password. set static::customerid_tag "customer_id" set static::username_tag "username" set static::password_tag "password" set static::login_url "/user_login.php" } when HTTP_REQUEST { if { ([HTTP::method] eq "POST") && ([string tolower [HTTP::uri]] contains $static::login_url ) } { # Trigger collection for up to max_req_size of data set modify_response_content_type 1 if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= $static::max_req_size } { set content_length [HTTP::header "Content-Length"] } else { set content_length $static::max_req_size } # Check if $content_length is not set to 0 if { $content_length > 0} { HTTP::collect $content_length } } } when HTTP_REQUEST_DATA { # collect the incoming payload set payload [HTTP::payload] ## skipahead is set to fieldname +1 to avoid matching the equals sign. set raw_customerid [findstr $payload $static::customerid_tag "12" "&"] set raw_username [findstr $payload $static::username_tag "9" "&"] set raw_password [findstr $payload $static::password_tag "9" "\r\n"] set auth_header_b64 [b64encode "$raw_customerid-$raw_username:$raw_password"] HTTP::header insert "Authorization" "Basic $auth_header_b64" } when HTTP_REQUEST_RELEASE { #Don't pass header to backend HTTP::header remove "Authorization" }
Don't forget to mask the values of the Authorization header in the ASM policy.
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com