27-Dec-2022 06:32
Please let me know how to match formdata "key" and "value" using HTTP Method "POST" from its Body.
From below I would like to match key "func" its value that contains "qds."
"body": { "mode": "formdata", "formdata": [ { "key": "func", "value": "qds.ObjAction", "type": "default" },
27-Dec-2022 06:41
I am trying the below after collecting the payload but it is not working. For form-data do we have to use something else to match? FYI the below works for form-urlencoded?
when HTTP_REQUEST_DATA {
if { [HTTP::method] equals "POST" }{
set http_request_body "?[HTTP::payload]"
if { [URI::query $http_request_body func] contains "qds." } {
HTTP::respond 403 content "Forbidden" "Content-Type" "text/html"
} }
}
27-Dec-2022 15:18
Hi @abhinay, this solution might be helpful to craft to your unique scenario.
28-Dec-2022 01:46 - edited 28-Dec-2022 01:50
@mihaicand @CA_Valli, Sorry for tagging you here. Both your solutions worked for me in my previous post and was looking for your help in combining two POST methods.
The second requirement is {Method is POST - (URI contains "/cs" OR "llisapi.dll") AND (body contains Key "func" which contains Value "qds.") AND (body contains "_REQUEST" which contains Value "SYNDICATION_REQUEST" )} for which I am using below iRule and combined it with (body contains Key "_fInArgs=" which contains Value "=#").
From this iRule I am observing that whatever is the second "if statement" it is returning Forbidden, but the first "if statement" is returning "Could not get response". Tried interchanging the if statements but same issue, the second works and first doesnt.
Any help will be appreciated.
when HTTP_REQUEST {
if {[HTTP::method] eq "POST"}{
# Trigger collection for up to 1MB of data
if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= 1048576}{
set content_length [HTTP::header "Content-Length"]
} else {
set content_length 1048576
}
# Check if $content_length is not set to 0
if { $content_length > 0} {
HTTP::collect $content_length
}
}
}
when HTTP_REQUEST_DATA {
if { [HTTP::method] equals "POST" }{
# Extract the entire HTTP request body and escape it to become a HTTP::uri string (for easier parsings)
set http_request_body "?[HTTP::payload]"
log local0. "http payload: $http_request_body"
# Try to parse type value from the HTTP request body.
if { [URI::query $http_request_body _fInArgs%3D] contains "%3D%23" } {
HTTP::respond 403 content "You don't have authorization to view this page. Access Denied" noserver Content-Type text/html Connection Close Cache-Control no-cache
} }
if { ([HTTP::method] equals "POST") and ([HTTP::uri] contains "/cs" or [HTTP::uri] contains "llisapi.dll" )}{
# Extract the entire HTTP request body and escape it to become a HTTP::uri string (for easier parsings)
set varB [findstr [HTTP::payload] "func"]
set varC [findstr [HTTP::payload] "_REQUEST"]
if { ($varB contains "qds.") and ($varC contains "SYNDICATION_REQUEST")} {
HTTP::respond 403 content "You don't have authorization to view this page. Access Denied" noserver Content-Type text/html Connection Close Cache-Control no-cache
}
}
}
28-Dec-2022 02:17
Hello @abhinay , I've scripted the irule, now testing it in my lab.
Will update you shortly
28-Dec-2022 04:19
@abhinay I've achieved something, this seems to work but might be heavy on performance.
proc key2value {list key} {
set element [split $list ,]
set kv_pair0 [split [lindex $element 0] :]
if {$key equals [string trim [lindex $kv_pair0 1] "\"{ }" ] }{
set kv_pair1 [split [lindex $element 1] :]
return [string trim [lindex $kv_pair1 1] "\"{ }" ]
}
}
when HTTP_REQUEST {
# if you need fInArgs code to match on GET's too, you can paste lines #4-10 (excluding "else \{" syntax on line #10) of my other script here, they shoudn't conflict
# setting HTTP::collect triggers -- notice that POST method is mantadory now
if { [HTTP::method] eq "POST" && ([HTTP::uri] contains "/cs" || [HTTP::uri] contains "llisapi.dll") }{
if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= 1048576}{
set content_length [HTTP::header "Content-Length"]
} else { set content_length 1048576 }
if { $content_length > 0} { HTTP::collect $content_length }
}
}
when HTTP_REQUEST_DATA {
set cleanpl [URI::decode [HTTP::payload]]
# json format in payload is already formatted as a list - lindex is used to parse every element
for {set i 0} {$i < [llength $cleanpl]} {incr i}{
if { [findstr [lindex $cleanpl $i] func] ne "" }{ set func [call key2value [lindex $cleanpl $i] "func"] ; log local0. "func value is $func" }
if { [findstr [lindex $cleanpl $i] _REQUEST] ne "" }{ set req [call key2value [lindex $cleanpl $i] "_REQUEST"] ; log local0. "_REQUEST value is $req" }
}
if { $func contains "qds." && $req contains "SYNDICATION_REQUEST" }{
log local0. "violation detected, restricting access"
HTTP::respond 403 content "Forbidden"
}
# if you also need fInArgs code, you can paste lines #24-28 of my other script here as they shoudn't conflict
}
I was only able to test it with this command
curl -v http://10.163.191.11/cs -X POST --header "Content-Type: application/json" -d $'{ "key" : "func",\n"value" : "qds.ObjAction"}\n{ "key" : "_REQUEST",\n"value" : "SYNDICATION_REQUEST"}'
Parameters are recognized and request is blocked
Dec 28 14:16:40 bigip info tmm3[11336]: Rule /Common/iRule_DC <HTTP_REQUEST_DATA>: func value is qds.ObjAction
Dec 28 14:16:40 bigip info tmm3[11336]: Rule /Common/iRule_DC <HTTP_REQUEST_DATA>: _REQUEST value is SYNDICATION_REQUEST
Dec 28 14:16:40 bigip info tmm3[11336]: Rule /Common/iRule_DC <HTTP_REQUEST_DATA>: violation detected, restricting access
I feel like code can be improved a lot, but might be a starting point.
28-Dec-2022 21:28
@CA_Valli, Thanks for your efforts. I was able to achieve this with single if statement and combine both the requirements.
However I want to know if anyone pads with larger payload over 1MB, in that case the iRule will get bypassed right as we are collecting data only upto 1MB. Also we dont want to collect huge data on each POST request. Any idea how we can mitigate this?
28-Dec-2022 01:48
Hi @JRahm, I was able to achieve this using findstr with below iRule.
when HTTP_REQUEST {
if {[HTTP::method] eq "POST"}{
# Trigger collection for up to 1MB of data
if {[HTTP::header "Content-Length"] ne "" && [HTTP::header "Content-Length"] <= 1048576}{
set content_length [HTTP::header "Content-Length"]
} else {
set content_length 1048576
}
# Check if $content_length is not set to 0
if { $content_length > 0} {
HTTP::collect $content_length
}
}
}
when HTTP_REQUEST_DATA {
if { ([HTTP::method] equals "POST") and ([HTTP::uri] contains "/cs" or [HTTP::uri] contains "llisapi.dll" )}{
# Extract the entire HTTP request body and escape it to become a HTTP::uri string (for easier parsings)
set varB [findstr [HTTP::payload] "func"]
set varC [findstr [HTTP::payload] "_REQUEST"]
if { ($varB contains "qds.") and ($varC contains "SYNDICATION_REQUEST")} {
HTTP::respond 403 content "You don't have authorization to view this page. Access Denied" noserver Content-Type text/html Connection Close Cache-Control no-cache
}
}
}
27-Dec-2022 21:27
deal with json data, the best way is to use iRulesLX
29-Dec-2022 23:28
It would be pretty impractical to use iRules TCL to perform the translation POST JSON data. However, this task is trivial for iRules LX because of the power of Node.js
rules>>json_post
when HTTP_REQUEST {
# Collect POST data
if { [HTTP::method] eq "POST" }{
set cl [HTTP::header "Content-Length"]
HTTP::collect $cl
}
}
when HTTP_REQUEST_DATA {
# Send data to Node.js
set handle [ILX::init "ilxlab2_plugin" "ilxlab2_ext"]
if {[catch {ILX::call $handle jsonPost [HTTP::payload]} result]} {
# Error handling
log local0.error "Client - [IP::client_addr], ILX failure: $result"
HTTP::respond 400 content "<html>There has been an error.</html>"
return
}
log local0. "result is $result"
# Replace Content-Type header and POST payload
# HTTP::header replace "Content-Type" "application/json"
# HTTP::payload replace 0 $cl $result
if { [lindex $result 0] > 0 } {
HTTP::respond 400 content "<html>The following error occured: Invalid JSON</html>"
} else {
if { [lindex $result 1] contains "qds" } {
HTTP::respond 403 content "Forbidden Access!"
} else {
HTTP::respond 400 content "value is [lindex $result 1]"
}
}
}
ilxlab2_ext>>index.js
'use strict' // Just for best practices
// Import modules here
var f5 = require('f5-nodejs');
// var qs = require('querystring'); // Used for parsing the POST data querystring
// Create an ILX server instance
var ilx = new f5.ILXServer();
// This method will transform POST data into JSON
ilx.addMethod('jsonPost', function (req, res) {
// Get POST data from TCL and parse to JS object
//var postData = qs.parse(req.params()[0]);
var postData = req.params()[0];
try {
var jsonData = JSON.parse(postData);
} catch (err) {
console.log('Error with JSON.parse: ' + err.message);
return res.reply(1);
}
if ( jsonData.formdata[0].key == "func" ) {
console.log(jsonData.formdata[0].value);
var qds_value = jsonData.formdata[0].value;
return res.reply([0, qds_value])
} else {
return res.reply([0, "no"])
}
// Turn postData object into JSON and return to TCL
// res.reply(JSON.stringify(postData));
});
//Start the ILX server
ilx.listen();
curl test command is:
curl -kv http://10.10.10.177 -X POST -H "Content-Type: application/json" -d '{"mode":"formdata","formdata":[{"key":"func","value":"qds.ObjAction","type":"default"}]}'
result is:
[root@f5:LICENSE EXPIRES IN 4 DAYS:Active:Standalone] config # curl -kv http://10.10.10.177 -X POST -H "Content-Type: application/json" -d '{"mode":"formdata","formdata":[{"key":"func","value":"qds.ObjAction","type":"default"}]}'
Note: Unnecessary use of -X or --request, POST is already inferred.
* Rebuilt URL to: http://10.10.10.177/
* Trying 10.10.10.177...
* Connected to 10.10.10.177 (10.10.10.177) port 80 (#0)
> POST / HTTP/1.1
> Host: 10.10.10.177
> User-Agent: curl/7.47.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 88
>
* upload completely sent off: 88 out of 88 bytes
* HTTP 1.0, assume close after body
< HTTP/1.0 403 Forbidden
< Server: BigIP
* HTTP/1.0 connection set to keep alive!
< Connection: Keep-Alive
< Content-Length: 17
<
* Connection #0 to host 10.10.10.177 left intact
Forbidden Access![root@f5:LICENSE EXPIRES IN 4 DAYS:Active:Standalone] config #
tail -f /var/log/ltm
Dec 30 15:20:48 f5 info tmm[10644]: Rule /Common/ilxlab2_plugin/json_post <HTTP_REQUEST_DATA>: result is 0 qds.ObjAction
Dec 30 15:20:48 f5.bigip-gtm110.com info sdmd[5171]: 018e0017:6: pid[20389] plugin[/Common/ilxlab2_plugin.ilxlab2_ext] qds.ObjAction