Setting up my first Slack slash command. I built it out originally using the deprecated verification token but, for posterity, have decided to use signed secrets authentication.
Reading through the signed secrets documentation, I've had no issue validating requests that come in from the initial slash command. However, interaction requests have a completely different body structure and the method for calculating a secret hash do not produce a valid result (because the request body is different).
Here is a snippet from the docs on validating signed secrets.
slack_signing_secret = 'MY_SLACK_SIGNING_SECRET' // Set this as an environment variable
>>> 8f742231b10e8888abcd99yyyzzz85a5
request_body = request.body()
>>> token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c
On invocation of the slash command this works as intended - the request body matches the structure in the example above. When the user interacts with the message, the response body uses the blocks api - which is completely different
If I'm not supposed to use the verification token and the request body from the interactive blocks api does not allow me to compute a valid hash, how am I supposed to validate interaction requests? I must be missing something while combing through the docs.
Related
We have one URL parameter as "code_challenge" which gets generated at run time, this value we need to extract so that from next runs it can be handled. However since this code value is not captured in any previous requests/responses and need to extract at run time, unable to understand how to achieve.
Tried Regular Extractor choosing URL radio button, but it captures the value from recorded script.
Steps followed:
Record script using Blazmeter (Browse URL xx.com>Click Login)
Redirected to URL(s) in which one of the url has "code_Challenge" parameter with run time value)
First URL is: accounts-xx.com/oauth2/oidcdiscovery/.well-known/openid-configuration> The response doesn't have any parameter values
Second URL is:
https://accounts-xx.com/oauth2/authorize?response_type=code&client_id=zzzz&scope=ituytutut&redirect_uri=xx.com/callBack&code_challenge_method=ooo&**code_challenge=dsfsdlfhl**
In above 3rd point url, Code_challenge value is generated at run time when executed steps from browser.
However If replayed the recorded script which would have already generated code value hence other requests would fail. Due to this, need to get the code value fetched.
The code_challenge is generated from WS02 service.
Jmeter version: 5.3
Please suggest, or should we need to use Selenium webdriver integration.
Regular expression which would extract the value from recorded script:
As per Mitigating Authorization Code Interception Attacks article:
code_challenge The client creates and records a secret cryptographically random string (the code_verifier), which is then encoded using URL safe base64 encoding to transform it into the code_challenge.
As per PKCE in WSO2 IS server article:
1.Plain:
If a code_challenge method is mention as plain or not mention at all it will take this plain value. Then code_challenge will like:
code_challenge = code_verifier
2. SHA256:
To have the code_challenge as SHA256, we should mention this in request otherwise plain value will be assumed.For SHA256 code challenge will be like
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
Here base64url is same as base64encoding(used so that all machine can identify as same value) but trailing “=” will be removed and “+” & “/” are placed by “-” & “_” to avoid unnecessary length in URL.(otherwise ‘+’ becomes ‘%2B’, ‘/’ becomes ‘%2F’ and ‘=’ becomes ‘%3D in URL)
As much as possible, it is better to select the code challenge method as SHA256 then the flow will become more secure and hard to guess(if someone try to brute force it)
So I think you need to add JSR223 PreProcessor and calculate/generate the code_challenge using the algorithm used by your server in Groovy language, store the value into a JMeter Variable and use it in the request.
You can leave the field value empty and then its value gets updated once the request is sent. But you won't see the value in the request.
I had a similar issue, it worked in my case.
I am working with Jmeter and Blazemeter in a login script for a web made with Genexus.
The problem that I am having is in the POST.
Whenever I try to make a POST http petition, Jmeter throws the next thing:
As you can see, in the response body, I am having a 440 http error code. This is a login Time-out which means the client's session has expired and must log in again. I used to have a 403 error code but now, after doing some arrangements, I have 440. Do you have any suggestions on how to resolve this?
First, I'm not an expert on Genexus. All my findings are from a black-box point of view.
Genexus Security
I found that Genexus requires at least two things to authenticate on Web Application (I tested only Java and .Net generated apps).
The GXState parameter. This param is sent in post request, and from my understanding works as "Synchronizer token pattern", see more info on Cross-site request forgery. We need to send this param on every post request.
The gxajaxEvt parameter. This is very specific to Genexus Apps. In the documentation mentions this parameter is send encrypted in the URL, and this behavior is managed by the "Javascript debug mode property":
# Javascript Debug Mode: Yes
http://{server}:{port}/{webappname}/servlet/com.{kbname}.{objectname}?gxfullajaxEvt,gx-no-cache=1442811265833
# Javascript Debug Mode: No (default value)
http://{server}:{port}/{webappname}/servlet/com.{kbname}.{objectname}?64df96a2d9b8480aed416e470dae529e,gx-no-cache=1442811265833
JMeter Script
So, to get the GXState, we can use the Regular Expression Extractor:
Name of created variable: GXState
Regular expression: name="GXState" value='(.*?)'
Template: $1$
Match No.: 1
Default Value: NOT_FOUND
The GXState is a JSON, object, from it we can extract the GX_AJAX_KEY to encrypt gxajaxEvt string. Note that, I found the GX_AJAX_KEY is the key used to encrypt in this case, but some others could apply. We can debug this using Browser Web Console, with this:
gx.sec.encrypt("gxajaxEvt")
We'll see something like this:
"8722e2ea52fd44f599d35d1534485d8e206d507a46070a816ca7fcdbe812b0ad"
As we can found, all the client encryption code is in the gxgral.js file. Genexus uses the Rijndael algortihm (Sub set of AES) with block size of 128 bits.
To emulate this client behavior in the JMeter Script we can use the "JSR 233 sampler". A way to get the Rijndael results is use the Bouncy Castle library. We need to add this jar (bouncycastle:bcprov-jdk15to18:1.68) to the JMeter's lib folder to use it.
Our code script will be something like this (Language Groovy 3.0.5/Groovy Scripting Engine 2.0):
import com.jayway.jsonpath.JsonPath
import java.nio.charset.StandardCharsets
import java.util.Arrays
import org.bouncycastle.crypto.BufferedBlockCipher
import org.bouncycastle.crypto.InvalidCipherTextException
import org.bouncycastle.crypto.engines.RijndaelEngine
import org.bouncycastle.crypto.params.KeyParameter
import org.bouncycastle.util.encoders.Hex
import org.apache.jmeter.threads.JMeterContextService
import org.apache.jmeter.threads.JMeterContext
import org.apache.jmeter.threads.JMeterVariables
String gxState = vars.get('GXState')
String gxAjaxKey = JsonPath.read(gxState,'$.GX_AJAX_KEY')
byte[] input = Arrays.copyOf('gxajaxEvt'.getBytes(StandardCharsets.UTF_8), 16)
RijndaelEngine engine = new RijndaelEngine(128)
KeyParameter key = new KeyParameter(Hex.decode(gxAjaxKey))
BufferedBlockCipher cipher = new BufferedBlockCipher(engine)
cipher.init(true, key)
byte[] out = new byte[16]
int length = cipher.processBytes(input, 0, 16, out, 0)
cipher.doFinal(out, length)
String encryptedOutput= Hex.toHexString(out)
log.info 'gx.sec.encrypt("gxajaxEvt")='+encryptedOutput
String gxNoCache = String.valueOf(System.currentTimeMillis())
log.info 'gx-no-cache='+gxNoCache
vars.put('gxajaxEvt', encryptedOutput)
vars.put('gxNoCache', gxNoCache)
The script work like this:
First, We get the previos GXState variable extracted.
Second, Using JSON Path (Already available in JMeter 5.4.1) extract the GX_AJAX_KEY property.
Third, We apply the Rijndael algorithm over the gxajaxEvt using the GX_AJAX_KEY as a key.
We also create the gx-no-cache to handle the cache.
With these variables we can send the next request successfully:
We can found this sample JMeter script available here.
For complex scripts, please refer to this guide (Requires GXTest)
In case we get this exception in JMeter ( java.util.zip.ZipException: Not in GZIP format) please refer this answer too.
Any HTTP Status 4xx is a client error, to wit you're sending an incorrect request.
If custom 440 http status code means "session has expired" my expectation is that you have a recorded hard-coded session ID somewhere in your request parameters or headers
You should carefully inspect previous response(s) and look for something which appears to be a session id, once you find it - extract it using a suitable JMeter's Post-Processor and replace hard-coded session ID with the appropriate JMeter Variable. The process is known as correlation
New to JMeter, and I'm probably missing some simple thing but I can't find it anywhere.
I'm trying to log into a web application so I send a GET request to the home page to retrieve the csrf token and then I use the regular expression extractor to retrieve it:
I then try to place the extracted token into the POST request:
But the extracted token doesn't show up in the request sent via the View Results Tree. Only the UserId and Password are passed which I manually defined:
My regular expression is finding a match as you can see here:
but even if it wasn't finding a match, it should still pass "NotFound" correct?
Any help is greatly appreciated!
Edit: Added a Debug Sampler and it is correctly showing the variable Token with it's correct value. So Token just isn't being added to the request via the RegEx User Parameter Pre Processor.
It's difficult to see what you're doing without seeing the LoginPage step, but you need to make sure you are adding the csrf token you've extracted in the right place.
Typically it could be as a query param, part of a post body or an http header.
As you can see the token has been extracted from your initial response in the Debug sampler, there must be something wrong in how or where the token is being applied to the LoginPage request. (As you say the RegEx User Parameter Pre Processor isn't adding it)
I've never used the PreProcessor and the Jmeter documentation explanation is a bit vague on what it does, but you can add the variables into your request without it using the variable saved for the capture groups - for input name this will be ${Token_g1} and for value it will be ${Token_g2} - which you will be able to see from the Debug Sampler. You'll need to add a Header or Cookie Manager to the Login Page element if this is where the token is.
By setting the template in the Extractor to $1$$2$ you create a variable with the name and value concatenated together. I'm not sure why you would need this, and I would guess that the name doesn't change - in which case you can just use capture group 2, or if you want to use the Variable name Token then update your template to be $2$
Is there a way (and does it make sense even) to have dynamical values for my request parameters (in my case POST application/x-www-form-urlencoded that has two parameters username and password) which can be altered based on some function or a returned value from the server from a previous request?
The motivation being that i have a register-new-user request which i run from time to time off apiary.io and unless i manually change the example value for the username i get a "use already exists" response instead of 201 i want (since this request was already run with the username in the example).
What i'd like to have instead is a value in the API documentation that will change on each execution of the API call (either using some random number there, or to be able to have it take a value returned from a previous request).
Is there anything you can suggest to solve my "user already exists" response for register-new-user API call?
Here is my current API documentation (the relevant part):
## Registration [/users.json]
The `/users.json` resource handles registration of new user
### Register a New Patient [POST]
Register a new patient action sends email and password and registers
the new user in case it doesn't already exist
+ Request (application/x-www-form-urlencoded)
+ Attributes (Test User)
+ Body
user[email]=username#example.com&user[password]=123456
+ Response 201 (application/json)
{
"id":500
}
# Data Structures
## Test User (object)
+ "user[email]" (string): "username#example.com" - user email
+ "user[password]" (string): "123456" - user password
Thanks in advance
You can partially simulate this in the Apiary mock server by passing a header in your call, for example:
Prefer: status=200
See https://help.apiary.io/tools/mock-server/#multiple-responses
In general the mock server is not yet flexible and programmable enough to fully do what you describe, for example conditionals, dynamic variables or random responses.
We are working on enhancing this. If you'd like you may comment here on your requirements:
https://github.com/apiaryio/api-blueprint/issues/58
Feel free to also ping us in Apiary (in-app chat) or on support#apiary.io.
Thanks
I am trying to make a RESTful api and have some function which needs credentials. For example say I'm writing a function which finds all nearby places within a certain radius, but only authorised users can use it.
One way to do it is to send it all using GET like so:
http://myapi.heroku.com/getNearbyPlaces?lon=12.343523&lat=56.123533&radius=30&username=john&password=blabla123
but obviously that's the worst possible way to do it.
Is it possible to instead move the username and password fields and embed them as POST variables over SSL, so the URL will only look like so:
https://myapi.heroku.com/getNearbyPlaces?lon=12.343523&lat=56.123533&radius=30
and the credentials will be sent encrypted.
How would I then in Sinatra and Ruby properly get at the GET and POST variables? Is this The Right Way To Do It? If not why not?
If you are really trying to create a restful API instead if some URL endpoints which happen to speak some HTTP dialect, you should stick to GET. It's even again in your path, so you seem to be pretty sure it's a get.
Instead of trying to hide the username and password in GET or POST parameters, you should instead use Basic authentication, which was invented especially for that purpose and is universally available in clients (and is available using convenience methods in Sinatra).
Also, if you are trying to use REST, you should embrace the concept of resources and resoiurce collections (which is implied by the R and E of REST). So you have a single URL like http://myapi.heroku.com/NearbyPlaces. If you GET there, you gather information about that resource, if you POST, you create a new resource, if you PUT yopu update n existing resource and if you DELETE, well, you delete it. What you should do before is th structure your object space into these resources and design your API around it.
Possibly, you could have a resource collection at http://myapi.heroku.com/places. Each place as a resource has a unique URL like http://myapi.heroku.com/places/123. New polaces can be created by POSTing to http://myapi.heroku.com/places. And nearby places could be gathered by GETing http://myapi.heroku.com/places/nearby?lon=12.343523&lat=56.123533&radius=30. hat call could return an Array or URLs to nearby places, e.g.
[
"http://myapi.heroku.com/places/123",
"http://myapi.heroku.com/places/17",
"http://myapi.heroku.com/places/42"
]
If you want to be truly discoverable, you might also embrace HATEOAS which constraints REST smentics in a way to allows API clients to "browse" through the API as a user with a browser would do. To allow this, you use Hyperlink inside your API which point to other resources, kind of like in the example above.
The params that are part of the url (namely lon, lat and radius) are known as query parameters, the user and password information that you want to send in your form are known as form parameters. In Sinatra both of these type of parameters are made available in the params hash of a controller.
So in Sinatra you would be able to access your lon parameter as params[:lon] and the user parameter as params[:user].
I suggest using basic or digest authentication and a plain GET request. In other words, your request should be "GET /places?lat=x&lon=x&radius=x" and you should let HTTP handle the authentication. If I understand your situation correctly, this is the ideal approach and will certainly be the most RESTful solution.
As an aside, your URI could be improved. Having verbs ("get") and query-like adjectives ("nearby") in your resource names is not really appropriate. In general, resources should be nouns (ie. "places", "person", "books"). See the example request I wrote above; "get" is redundant because you are using a GET request and "nearby" is redundant because you are already querying by location.