6

我为一个项目创建了一个简单的 RESTful API,部分遵循Riyad Kalla 的这篇非常好的博客文章。现在,我在 Stack Overflow 上阅读了几十个类似的问题,但我似乎无法找到我的安全问题的答案。

简而言之,我的请求是这样的:

  1. 客户端有一个公共 API 密钥(纯文本,任何嗅探网络流量或正确检查代码源的人都可以访问)
  2. 客户端使用公共 API 密钥向服务器发送请求
  3. 服务器有一个秘密的 API 密钥(对除开发人员以外的任何人都是秘密的)
  4. 服务器创建一个由客户端请求数据和秘密 API 密钥组成的 HMAC-SHA1 哈希
  5. 服务器向 API 服务器发送与客户端请求相同的请求,但包括生成的 HMAC-SHA1
  6. API 服务器根据它收到的公共 API 密钥在其数据库中查找秘密 API 密钥
  7. API 服务器使用与开发者服务器相同的数据重新创建 HMAC-SHA1 哈希
  8. 如果哈希匹配,则认为请求有效并正常处理

我担心使用我的服务的人可以获取公共 API 密钥(比如说通过嗅探网络流量),然后简单地将客户端最初使用 AJAX 通过浏览器执行的相同请求直接卷曲到开发人员的服务器。因此,恶意用户可以被认证为合法用户,并使用其他人的秘密 API 密钥访问 API。

我将尝试举一个具体的例子。通常我会这样做:

  1. AJAX 对我的服务器的获取请求。
  2. 我的服务器使用我的 API 密钥对我的请求进行哈希处理并将其发送到 API 服务器。
  3. API 服务器验证我的请求并返回有效负载。

但我很害怕:

  1. Dr. Evil 会嗅探我的公共 API 密钥。
  2. Dr. Evil 将使用我的公共 API 密钥将 get 请求卷曲到我的服务器。
  3. 我的服务器将使用我的 API 机密对 Dr. Evil 的请求进行哈希处理,并将其发送到 API 服务器。
  4. API 服务器验证并返回有效载荷以完成 Dr. Evil 的恶毒计划。
  5. 邪恶博士发出邪恶的笑声。

我遗漏了什么,或者这只是 RESTful API 游戏的一部分?

更新:我自愿省略任何形式的时间戳验证,以保持简单,只关注身份验证问题。

更新 2:我$_SERVER['HTTP_REFERER']在流程中添加了验证。这里的目标是客户端必须与请求一起发送引荐来源网址,并且它必须与 API 端数据库中列出的引荐来源网址相匹配。不幸的是,HTTP 引荐来源网址很容易被伪造。这是另一个级别的安全性,但仍不完美。

更新 3:我已更改服务器端代码以将引用者设置为远程 IP 地址。这会强制发送到我的服务器的每个请求都希望使用秘密 API 密钥进行哈希处理,最终到达具有原始请求 IP 地址的 API 服务器。然后可以验证此 IP,并且可以通过请求。我相信仍然可以伪造$_SERVER['REMOTE_ADDR'],但它比伪造更复杂$_SERVER['HTTP_REFERER']......我猜仍然不完美。

更新 4:根据这些帖子:如何伪造 $_SERVER['REMOTE_ADDR'] 变量?https://serverfault.com/questions/90725/are-ip-addresses-trivial-to-forge$_SERVER['REMOTE_ADDR'] ,虽然很困难,但伪造是可能的。但是,由于您无法控制伪造的网络,因此不可能收到来自伪造请求的响应。请求可以成功验证,但其响应不会落入恶意之手。

4

2 回答 2

5

使用 HMAC,您走在正确的轨道上。但是,还有两件事可以使您的应用程序更加安全。

  1. 需要客户端 POST 中的时间戳,需要在服务器时间的 5 分钟内验证。它也应该包含在 HMAC 生成中。如果有人试图更改此 HMAC 签名将无效,除非他们拥有更新 HMAC 签名的密钥。
  2. 使用带有证书验证的 SSL。防止中间人攻击。不允许任何非 ssl 请求。
于 2013-01-14T19:07:16.297 回答
0

我发现阻止其他脚本使用公共 API 密钥并向服务器端 HMAC 哈希脚本发送请求的解决方案是将原始请求者的身份与请求一起发送。我$_SERVER['REMOTE_ADDR']用来确定原始请求者的身份,因为它更难伪造,而且伪造通常意味着他们不会得到回应。

/* $this as a class that handles requests */

// Build hash and include timestamp
$this->vars['timestamp'] = time();
$this->vars['hash'] = hash_hmac('sha1', http_build_query($this->vars).$this->vars['token'], API_SECRET);

// Send request to API
curl_setopt_array($this->curl, array(
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_URL => $url,
    CURLOPT_POST => $this->method == 'post' ? 1 : NULL,
    CURLOPT_POSTFIELDS => $this->method == 'post' ? $this->vars : NULL,
    CURLOPT_CONNECTTIMEOUT => 15,
    CURLOPT_TIMEOUT => 15,
    CURLOPT_REFERER => $_SERVER['REMOTE_ADDR'], // Referer here!
    CURLOPT_MAXREDIRS => 3,
    CURLOPT_HTTPGET => $this->method == 'get' ? true : false
));

一旦发送,API 不仅会检查数据库中的 API 密钥,还会检查是否$_SERVER['HTTP_REFERER']被列为允许!这也允许 API 在每个用户的基础上接受服务器。

于 2013-01-16T17:06:50.293 回答