保护无会话的RESTful API端点

时间:2013-01-14 18:59:31

标签: php api rest curl restful-authentication

我为部分跟随this very good blog post by Riyad Kalla的项目创建了一个简单的RESTful API。现在,我已经在Stack Overflow上阅读了几十个类似的问题,但我似乎找不到安全问题的答案。

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

  1. 客户端有一个公共API密钥(纯文本,任何人都可以访问网络流量或正确检查代码源)
  2. 客户端使用公共API密钥
  3. 向服务器发送请求
  4. 服务器有一个秘密的API密钥(除了开发人员之外的任何人都有秘密)
  5. 服务器创建由客户端的请求数据和秘密API密钥组成的HMAC-SHA1哈希
  6. 服务器向API服务器发送与客户端请求相同的请求,但包括生成的HMAC-SHA1
  7. API服务器根据其收到的公共API密钥在其数据库中查找秘密API密钥
  8. API服务器使用与开发人员服务器相同的数据重新创建HMAC-SHA1哈希
  9. 如果哈希值匹配,则该请求被视为有效并正常处理
  10. 我担心有人使用我的服务可以获取公共API密钥(通过嗅探网络流量),然后简单地通过他们的浏览器使用AJAX直接向开发人员的服务器提交客户端最初要做的相同请求。因此,恶意用户可以作为合法用户进行身份验证,并使用其他秘密API密钥访问API。

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

    1. AJAX向我的服务器发送请求。
    2. 我的服务器使用我的API密码散列我的请求并将其发送到API服务器。
    3. API服务器验证我的请求并返回有效负载。
    4. 但我害怕:

      1. 博士。 Evil会嗅探我的公共API密钥。
      2. 博士。 Evil会使用我的公共API密钥向我的服务器发送get请求。
      3. 我的服务器将使用我的API密钥对Dr. Evil的请求进行哈希并将其发送到API服务器。
      4. API服务器验证并返回有效负载以完成Dr. Evil的恶意计划。
      5. 博士。邪恶笑了笑。
      6. 我缺少什么或者这只是RESTful API游戏的一部分?

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

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

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

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

2 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分: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基于每个用户接受服务器。