我正在开发安全的支付API,并且我希望避免通过操纵url中的参数来进行重放攻击。例如,在以下API调用中:
https://api.payment.com/wallet/transfer?from_account=123&to_account=456&amount=100
一旦执行此API调用,具有足够知识的人就可以通过修改三个参数中的任何一个以实现自己的优势来执行相同的API调用。我已经考虑过为每笔交易发行一个临时令牌(交易令牌)。但这听起来还不够。
有人可以提出通过篡改参数减轻重放攻击的最佳方法吗?
我正在开发安全的支付API,并且我想避免通过操纵URL中的参数来进行重放攻击。
在我们深入解决您的问题之前,首先必须弄清开发人员之间的常见误解,这与了解who
what
是向API服务器发出请求的东西。它确实是您的移动应用程序的真正实例,还是机器人,自动脚本还是攻击者使用诸如Postman之类的工具手动在您的API服务器上闲逛?
who
是移动应用程序的用户,我们可以通过多种方式进行身份验证,授权和标识,例如使用OpenID Connect或OAUTH2流。如果引用的文字不足以使您理解差异,请继续阅读本文的整个部分,因为如果没有充分理解,您倾向于在API服务器和客户端中采用不太有效的安全措施。 。URL中的安全层和参数
例如,在以下API调用中:https://api.payment.com/wallet/transfer?from_account=123&to_account=456&amount=100
安全性就是尽可能多地应用防御层,以使攻击变得更加艰巨和费力,将其视为需要剥去洋葱的多层才能到达中心。
攻击者将始终寻找最简单的目标,即树上的下垂果实,因为当他们可以从具有下垂的果实的另一棵树上取下果实时,他们不想诉诸于梯子;
因此,第一层防御是避免在url中使用参数进行敏感调用,因此我将POST请求与请求正文中的所有参数一起使用,因为这种类型的请求不能简单地完成复制将url粘贴到浏览器或任何其他工具中,因此他们需要付出更多的努力和知识才能执行,也就是攻击者的果实在树上的地位更高。
另一个原因是GET请求最终出现在服务器的日志中,因此可能被意外暴露并易于重播。
API调用重播攻击
一旦执行此API调用,具有足够知识的人就可以通过修改三个参数中的任何一个以实现自己的优势来执行相同的API调用。
是的,即使您没有公开的文档,他们也可以学习您的API的工作原理,他们只需要借助用于移动应用程序和Web应用程序的任何开源工具,就可以对其进行精心设计。
我已经考虑过为每笔交易发行一个临时令牌(交易令牌)。但这听起来还不够。是的,这还不够,因为此临时令牌可以通过MitM攻击被盗,就像文章Steal That Api Key With a Man in the Middle Attack中的展示一样:
因此,在本文中,您将学习如何设置和运行MitM攻击以在您控制的移动设备中拦截https流量,以便您可以窃取API密钥。最后,您将在更高层次上看到如何缓解MitM攻击。和< [什么 API服务器期望的。缓解重播攻击对现有的安全防御措施进行改进因此,在执行MitM攻击以窃取令牌后,很容易使用
curl
,Postman
或任何其他类似工具向API服务器发出请求,就像您是真正的who
这种方法很好,但是您已经注意到,但是还不够,但是您可以通过使此临时令牌仅可用一次来改进它(如果尚未完成的话)。
另一种重要的防御措施是即使具有新的临时令牌,也不允许顺序重复相同数量和相同收件人(from_account
,to_account
)的请求。
也不允许来自同一来源的请求快速发出,特别是如果这些请求是来自人机交互的。
单独采取这种措施不会完全解决问题,但会在洋葱上添加更多层。
将HMAC用于一次性令牌
为了试图帮助服务器对what
发出请求充满信心,您可以使用Keyed-Hash Message Authentication Code (HMAC),它旨在防止劫持和篡改,并且根据Wikipedia:
在密码学中,HMAC(有时扩展为键哈希消息身份验证代码或基于哈希的消息身份验证代码)是一种特定类型的消息身份验证代码(MAC),涉及密码哈希函数和秘密密码密钥。与任何MAC一样,它可以用于同时验证数据的完整性和消息的真实性。
因此,您可以让客户端使用请求网址,用户身份验证令牌,您的临时令牌以及也应该出现在请求标头中的时间戳来创建HMAC令牌。然后,服务器将从请求中获取相同的数据并执行其自己的HMAC令牌计算,并且仅在其自身的结果与请求中的HMAC令牌标头匹配的情况下才继续处理请求。[在操作中的实际示例,您可以阅读this blog series的第1部分和第2部分有关移动应用程序上下文中的API保护技术的信息,该功能还具有模拟移动应用程序的Web应用程序。
因此您可以看到here移动应用如何计算HMAC,以及here Api服务器如何计算并验证它。但是您还可以看到here Web应用程序如何伪造HMAC令牌,以使API服务器认为请求确实来自移动应用程序的
who
和what
。 >/**
* Compute an API request HMAC using the given request URL and authorization request header value.
*
* @param context the application context
* @param url the request URL
* @param authHeaderValue the value of the authorization request header
* @return the request HMAC
*/
private fun calculateAPIRequestHMAC(url: URL, authHeaderValue: String): String {
val secret = HMAC_SECRET
var keySpec: SecretKeySpec
// Configure the request HMAC based on the demo stage
when (currentDemoStage) {
DemoStage.API_KEY_PROTECTION, DemoStage.APPROOV_APP_AUTH_PROTECTION -> {
throw IllegalStateException("calculateAPIRequestHMAC() not used in this demo stage")
}
DemoStage.HMAC_STATIC_SECRET_PROTECTION -> {
// Just use the static secret to initialise the key spec for this demo stage
keySpec = SecretKeySpec(Base64.decode(secret, Base64.DEFAULT), "HmacSHA256")
Log.i(TAG, "CALCULATE STATIC HMAC")
}
DemoStage.HMAC_DYNAMIC_SECRET_PROTECTION -> {
Log.i(TAG, "CALCULATE DYNAMIC HMAC")
// Obfuscate the static secret to produce a dynamic secret to initialise the key
// spec for this demo stage
val obfuscatedSecretData = Base64.decode(secret, Base64.DEFAULT)
val shipFastAPIKeyData = loadShipFastAPIKey().toByteArray(Charsets.UTF_8)
for (i in 0 until minOf(obfuscatedSecretData.size, shipFastAPIKeyData.size)) {
obfuscatedSecretData[i] = (obfuscatedSecretData[i].toInt() xor shipFastAPIKeyData[i].toInt()).toByte()
}
val obfuscatedSecret = Base64.encode(obfuscatedSecretData, Base64.DEFAULT)
keySpec = SecretKeySpec(Base64.decode(obfuscatedSecret, Base64.DEFAULT), "HmacSHA256")
}
}
Log.i(TAG, "protocol: ${url.protocol}")
Log.i(TAG, "host: ${url.host}")
Log.i(TAG, "path: ${url.path}")
Log.i(TAG, "Authentication: $authHeaderValue")
// Compute the request HMAC using the HMAC SHA-256 algorithm
val hmac = Mac.getInstance("HmacSHA256")
hmac.init(keySpec)
hmac.update(url.protocol.toByteArray(Charsets.UTF_8))
hmac.update(url.host.toByteArray(Charsets.UTF_8))
hmac.update(url.path.toByteArray(Charsets.UTF_8))
hmac.update(authHeaderValue.toByteArray(Charsets.UTF_8))
return hmac.doFinal().toHex()
}
API server code:if (DEMO.CURRENT_STAGE == DEMO.STAGES.HMAC_STATIC_SECRET_PROTECTION) {
// Just use the static secret during HMAC verification for this demo stage
hmac = crypto.createHmac('sha256', base64_decoded_hmac_secret)
log.info('---> VALIDATING STATIC HMAC <---')
} else if (DEMO.CURRENT_STAGE == DEMO.STAGES.HMAC_DYNAMIC_SECRET_PROTECTION) {
log.info('---> VALIDATING DYNAMIC HMAC <---')
// Obfuscate the static secret to produce a dynamic secret to use during HMAC
// verification for this demo stage
let obfuscatedSecretData = base64_decoded_hmac_secret
let shipFastAPIKeyData = new Buffer(config.SHIPFAST_API_KEY)
for (let i = 0; i < Math.min(obfuscatedSecretData.length, shipFastAPIKeyData.length); i++) {
obfuscatedSecretData[i] ^= shipFastAPIKeyData[i]
}
let obfuscatedSecret = new Buffer(obfuscatedSecretData).toString('base64')
hmac = crypto.createHmac('sha256', Buffer.from(obfuscatedSecret, 'base64'))
}
let requestProtocol
if (config.SHIPFAST_SERVER_BEHIND_PROXY) {
requestProtocol = req.get(config.SHIPFAST_REQUEST_PROXY_PROTOCOL_HEADER)
} else {
requestProtocol = req.protocol
}
log.info("protocol: " + requestProtocol)
log.info("host: " + req.hostname)
log.info("originalUrl: " + req.originalUrl)
log.info("Authorization: " + req.get('Authorization'))
// Compute the request HMAC using the HMAC SHA-256 algorithm
hmac.update(requestProtocol)
hmac.update(req.hostname)
hmac.update(req.originalUrl)
hmac.update(req.get('Authorization'))
let ourShipFastHMAC = hmac.digest('hex')
// Check to see if our HMAC matches the one sent in the request header
// and send an error response if it doesn't
if (ourShipFastHMAC != requestShipFastHMAC) {
log.error("\tShipFast HMAC invalid: received " + requestShipFastHMAC
+ " but should be " + ourShipFastHMAC)
res.status(403).send()
return
}
log.success("\nValid HMAC.")
Web APP code:
function computeHMAC(url, idToken) {
if (currentDemoStage == DEMO_STAGE.HMAC_STATIC_SECRET_PROTECTION
|| currentDemoStage == DEMO_STAGE.HMAC_DYNAMIC_SECRET_PROTECTION) {
var hmacSecret
if (currentDemoStage == DEMO_STAGE.HMAC_STATIC_SECRET_PROTECTION) {
// Just use the static secret in the HMAC for this demo stage
hmacSecret = HMAC_SECRET
}
else if (currentDemoStage == DEMO_STAGE.HMAC_DYNAMIC_SECRET_PROTECTION) {
// Obfuscate the static secret to produce a dynamic secret to
// use in the HMAC for this demo stage
var staticSecret = HMAC_SECRET
var dynamicSecret = CryptoJS.enc.Base64.parse(staticSecret)
var shipFastAPIKey = CryptoJS.enc.Utf8.parse($("#shipfast-api-key-input").val())
for (var i = 0; i < Math.min(dynamicSecret.words.length, shipFastAPIKey.words.length); i++) {
dynamicSecret.words[i] ^= shipFastAPIKey.words[i]
}
dynamicSecret = CryptoJS.enc.Base64.stringify(dynamicSecret)
hmacSecret = dynamicSecret
}
if (hmacSecret) {
var parser = document.createElement('a')
parser.href = url
var msg = parser.protocol.substring(0, parser.protocol.length - 1)
+ parser.hostname + parser.pathname + idToken
var hmac = CryptoJS.HmacSHA256(msg, CryptoJS.enc.Base64.parse(hmacSecret)).toString(CryptoJS.enc.Hex)
return hmac
}
}
return null
}
NOTE:虽然上面的代码未使用与您的情况完全相同的参数,但它是您了解其基础知识的一个很好的起点。
正如您所看到的那样,整个移动应用程序中HMAC令牌的计算方式,Api服务器和Web应用程序在逻辑语义上是相同的,因此产生相同的HMAC令牌,因此Web应用程序可以击败Api服务器防御程序仅接受来自移动应用程序的有效请求。
这里的底线是,您放置在客户端代码中的任何内容都可以进行反向工程,以便将其复制到另一个客户端中。那么我应该在用例中使用HMAC令牌吗?是的,因为它是洋葱中的一层以上或树中较高的一种水果。
我能做得更好吗?是的,您可以做,只要继续阅读...增强和加强安全性
有人可以提出通过篡改参数减轻重放攻击的最佳方法吗?
who
waht
正在访问它更有信心。因此,如果您的API服务器的客户端仅是移动应用程序,那么请阅读this answer,以获取问题[[如何保护移动应用程序的API REST?
。如果需要保护同时为移动应用程序和Web应用程序提供服务的API,请参阅此another answer,以获取问题未经授权的API调用-安全并仅允许注册的前端应用程序。超速行驶
现在,我向您推荐OWASP基金会的出色工作:The Web Security Testing Guide:
The Mobile Security Testing Guide:
移动安全测试指南(MSTG)是用于移动应用安全开发,测试和逆向工程的综合手册。