【问题标题】:Typeform Security API and Django: Not Verifiying Hash CorrectlyTypeform Security API 和 Django:未正确验证哈希
【发布时间】:2019-11-29 09:51:18
【问题描述】:

我正在尝试为他们的 webhook 使用 Typeform 的安全性。这涉及到

1) Receiving the signed packets and extracting the signature
2) Getting the body of the requst
3) Creating a hash with a secret key on the payload
4) Matching the hash with the received signature

我的网络框架是 Django(基于 Python)。我在此处的 TypeForm 链接中关注示例:https://developer.typeform.com/webhooks/secure-your-webhooks/

对于我的一生,我无法弄清楚发生了什么。我已经在 Python 和 Ruby 中尝试过,但我无法正确计算哈希值。我从 Python 调用一个 Ruby 脚本来匹配输出,但它们是不同的,而且都不起作用。有没有人有任何见识?我开始认为这可能与 Django 发送请求正文的方式有关。有人有意见吗?

Python 实现:

import os
import hashlib
import hmac
import base64
import json


class Typeform_Verify:
    # take the request body in and encrypt with string
    def create_hash(payload):
        # convert the secret string to bytes 
        file = open("/payload.txt", "w") 
        # write to a payload file for the ruby script to read later
        file.write(str(payload))
        # access the secret string
        secret = bytearray(os.environ['DT_TYPEFORM_STRING'], encoding="utf-8")
        file.close()
        # need to have the ruby version also write to a file
        # create a hash with payload as the thing 
        #   and the secret as the key`
        pre_encode = hmac.new(secret,
            msg=payload, digestmod=hashlib.sha256).digest()
        post_encode = base64.b64encode(pre_encode)
        return post_encode

    # another approach is to make a ruby script 
    #   that returns a value and call it from here
    def verify(request):
        file = open("/output.txt", "w")
        # check the incoming hash values
        received_hash = request.META["HTTP_TYPEFORM_SIGNATURE"] 
        # create the hash of the payload
        hash = Typeform_Verify.create_hash(request.body)
        # call ruby script on it
        os.system(f"ruby manager/ruby_version.rb {received_hash} &> /oops.txt") 
        # concatenate the strings together to make the hash
        encoded_hash = "sha256=" + hash.decode("utf-8")
        file.write(f"Secret string: {os.environ['DT_TYPEFORM_STRING']}\n")
        file.write(f"My hash    : {encoded_hash}\n")
        file.write(f"Their hash : {received_hash}\n")
        file.close()
        return received_hash == encoded_hash 

Ruby 脚本(从 Python 调用)

require 'openssl'
require 'base64'
require 'rack'
def verify_signature(received_signature, payload_body, secret)
  hash = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), secret, payload_body)
  # the created signature
  actual_signature = 'sha256=' + Base64.strict_encode64(hash) 
  # write created signature to the file
  out_file = File.new("/output.txt", "a")
  out_file.write("Ruby output: ")
  out_file.write(actual_signature)
  out_file.close()
  return 500, "Signatures don't match!" unless Rack::Utils.secure_compare(actual_signature, received_signature)
end

# MAIN EXECUTION 
# get the hash from the python scriupt
received_hash = ARGV[0]
# read the content of the file into the f array 
    # note that this is the json payload from the python script
f = IO.readlines("/payload.txt")
# declare the secret string
secret = "SECRET"
# call the funtion with the recieved hash, file data, and key
result = verify_signature(received_hash, f[0], secret) 

代码输出:

Typeform hash:   sha256=u/A/F6u3jnG9mr8KZH6j8/gO+Uny6YbSYFz7+oGmOik=
Python hash:     sha256=sq7Kl2qBwRrwgGJeND6my4UPli8rseuwaK+f/sl8dko=
Ruby output:     sha256=BzMxPZGmxgOMeJ236eAxSOXj85rEWI84t+6CtQBYliA=

【问题讨论】:

  • 在 ruby​​ 中,你有 secret = 和一个硬编码的字符串......这是正确的吗?如果是这样,您可能不想在此处发布此内容,对吧?而且,f[0] 不应该是f[1] 吗?
  • 你说得对,我根本不想在这里。我删除了那个。此外, f[0] 应该保持不变,因为它是文件中的第一行也是唯一一行。该文件包含从 Python 脚本写入的 JSON 数据,没有换行符,因此全部在 1 行中。

标签: django ruby hash webhooks typeform


【解决方案1】:

已更新首先查看this github article,因为您提到的可能是基于它的。

我们的想法是您的请求应该被签名。这是一个更基本的纯 ruby​​ 示例,它应该说明它应该如何工作。

# test.rb
ENV['SECRET_TOKEN'] = 'foobar'
require 'openssl'
require 'base64'
require 'rack'

def stub_request(body)
  key = ENV['SECRET_TOKEN']
  digest = OpenSSL::Digest.new('sha256')
  hmac_signature = OpenSSL::HMAC.hexdigest(digest, key, body)
  { body: body, hmac_signature: hmac_signature }
end

def verify_signature(payload_body, request_signature)
  digest = OpenSSL::Digest.new('sha256')
  hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
  if Rack::Utils.secure_compare(request_signature, hmac)
    puts "They match"
  else
    puts "They don't match"
  end
  puts "request_signature: #{request_signature}"
  puts "             hmac: #{hmac}"
  puts "             body: #{payload_body}"
end

request = stub_request(ARGV[0])
verify_signature(request[:body], request[:hmac_signature])

现在要测试它,只需运行:

ruby test.rb 'this is some random body string'

这是相同代码的 Python 版本。但这很容易受到timing attack vulnerability 的攻击。在某处可能有一个 Python 等价物可以缓解这种情况,但我没有进行研究来找到它。如果您的服务器还没有类似的东西,在 Python 中编写类似 Ruby Rack version here 的东西应该不难。

#test.py
import sys
import hashlib
import binascii
import hmac
import base64

KEY = 'foobar'

def stub_request(body):
    key = bytes(KEY, 'utf-8')
    body_bytes = bytes(body, 'utf-8')
    hmac_signature = hmac.new(key,
        msg=body_bytes, digestmod=hashlib.sha256).digest()
    return {'body': body, 'hmac_signature': hmac_signature}

def verify_signature(payload_body, request_signature):
    key = bytes(KEY, 'utf-8')
    hmac_sig = hmac.new(key, msg=bytes(payload_body,'utf-8'), digestmod=hashlib.sha256).digest()

    if hmac_sig == request_signature:
        print("They match")
    else  :
        print("They don't match")

    print(f"request_signature: {binascii.hexlify(request_signature)}")
    print(f"             hmac: {binascii.hexlify(hmac_sig)}")
    print(f"             body: {payload_body}")
    return request_signature

body = sys.argv[-1]
request = stub_request(body)
verify_signature(request['body'], request['hmac_signature'])

【讨论】:

  • 不,这是不对的,因为我在 Python 脚本中写入了 payload.txt,就像 file = open("/payload.txt", "w") file.write(str(payload))
  • 是的,我明白了。请参阅我的更新答案,希望这会有所帮助。
  • 好吧,这作为一个 Ruby 实现是公平的。有没有你可以指点我的 Python 资源?我的框架是基于 Python 的。
  • 我再次更新,为您提供了一个与我在这里提供的 ruby​​ 类似的基本 Python 示例,作为概念证明。
【解决方案2】:

我最终弄明白了。我运行良好的 Python 实现。问题在于我如何保存秘密字符串。显然,Python 中的环境变量不允许使用 $ 或 * 之类的字符。当我将密码硬编码到代码中时,我的 Ruby 实现开始工作,这让我相信问题在于我如何保存密码字符串。我向任何尝试进行这种身份验证的人推荐 Python 实现。干杯!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-03
    • 2016-11-12
    • 1970-01-01
    • 2012-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多