首先,我不是 Genexus 方面的专家。我所有的发现都是从黑盒的角度来看的。
Genexus 安全
我发现 Genexus 至少需要两件事来在 Web 应用程序上进行身份验证(我只测试了 Java 和 .Net 生成的应用程序)。
-
GXState 参数。此参数是在 post 请求中发送的,据我所知,它是“同步器令牌模式”,请参阅 Cross-site request forgery 上的更多信息。我们需要在每次发布请求时发送此参数。
-
gxajaxEvt 参数。这是 Genexus 应用程序特有的。在documentation 中提到此参数在 URL 中加密发送,并且此行为由“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 脚本
-
所以,要获得GXState,我们可以使用Regular Expression Extractor:
创建的变量名称:GXState
正则表达式:name="GXState" value='(.*?)'
模板:$1$
匹配号:1
默认值:NOT_FOUND
-
GXState 是一个 JSON 对象,我们可以从中提取GX_AJAX_KEY 来加密gxajaxEvt 字符串。请注意,我发现GX_AJAX_KEY 是在这种情况下用于加密的密钥,但其他一些可能适用。我们可以使用 Browser Web Console 进行调试,如下:
gx.sec.encrypt("gxajaxEvt")
我们将看到如下内容:
"8722e2ea52fd44f599d35d1534485d8e206d507a46070a816ca7fcdbe812b0ad"
我们可以发现,所有的客户端加密代码都在gxgral.js 文件中。 Genexus 使用 Rijndael 算法(AES 的子集),块大小为 128 位。
为了在 JMeter 脚本中模拟这种客户端行为,我们可以使用“JSR 233 采样器”。获得 Rijndael 结果的一种方法是使用 Bouncy Castle 库。我们需要将这个jar(bouncycastle:bcprov-jdk15to18:1.68)添加到JMeter的lib文件夹中才能使用。
我们的代码脚本将是这样的(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)
脚本是这样工作的:
- 首先,我们提取 previos
GXState 变量。
- 第二,使用 JSON 路径(已经有available in JMeter 5.4.1)提取
GX_AJAX_KEY 属性。
- 第三,我们将 Rijndael 算法应用于
gxajaxEvt,使用 GX_AJAX_KEY 作为键。
- 我们还创建了
gx-no-cache 来处理缓存。
- 使用这些变量我们可以成功发送下一个请求:
我们可以在here找到这个示例JMeter脚本。
复杂脚本请参考this guide(需要GXTest)
如果我们在 JMeter (java.util.zip.ZipException: Not in GZIP format) 中遇到此异常,请也参考此 answer。