百度安全实验室:支付安全不能说的那些事儿(3)
商户收到消息后验证签名正确,所有参数均正确,将完成攻击者的订单。而事实上,攻击者并未进行过任何支付。 另一方面,基于非对称密码体制的签名方案中,私钥泄露后攻击者也可以进行攻击。但是仍依赖于其他的逻辑漏洞。攻击者只能获取商户的私钥,而支付平台的私钥往往被妥善保护无法获得。因此,攻击者无法冒充支付平台向商户发送支付成功的消息,而只能冒充商户向支付平台伪造订单或者篡改订单,修改支付金额。 如图5所示,若攻击者准备购买一件商品,其订单消息为 notify_url=http://seller.com/notify&out_trade_no=12345&seller=alice&total_fee=100&sign=XXX 攻击者修改金额,使用私钥重新签名,并提交支付订单 notify_url=http://seller.com/notify&out_trade_no=12345&seller=alice&total_fee=1&sign=XXX 成功支付1元后,商户会收到支付结果消息 out_trade_no=12345&seller=alice&total_fee=1&trade_status=SUCCESS&sign=XXX 商户进行消息的验证,会发现签名正确,商户号正确,订单12345支付成功。若商户没有验证支付金额与订单是否匹配,将完成攻击者的订单。从而攻击者以1元购买了100元的商品。在许多App中,曾出现过只验证签名和订单id的情况,没有验证实付金额,因此可以通过这种金额篡改进行攻击。 为了防御这样的攻击,商家一定要修改app和服务端的设计,使得签名全部在服务端进行。网上充斥着大量可以直接照搬的富含漏洞的样例代码,一定不要简单修改这些代码就直接接入支付平台。此外,每笔交易均要进行查账,验证钱真的得到了支付,才可以标记订单为成功支付。 一旦出现这样的秘钥泄漏商家将面临严峻的安全风险,支付平台也将面临严重的连带品牌危机。发生这样的危机时,如果简单的替换秘钥将会直接导致老App客户端无法进行交易,如果不替换则将面临严峻的支付风险。目前临时缓解方案是依靠商家服务后台与钱包服务后台的增强校验和风控来探测和抵抗攻击。 2、签名算法实现错误 签名泄露只影响个别自己实现错误的商家,而支付平台的漏洞则会影响千万商家。在这一节我们讨论我们提交给两个国内著名支付平台的平台漏洞,他们均已得到修复。 案例一 第三方支付平台A采用了对称密钥的设计,并提供了服务端SDK供商家集成。服务端SDK提供了API验证签名是否正确。 在PHP和C#的SDK实现中,当签名字段不存在时,SDK会直接返回签名正确,这就导致了攻击者可以直接冒充支付平台向商户发送伪造的支付结果消息并通过签名认证。 以PHP版代码为例 public function CheckSign() { if(!$this->IsSignSet()){ returntrue; } $sign = $this->MakeSign(); if($this->GetSign() == $sign){ returntrue; } throw newException("签名错误!"); } public function IsSignSet() { return array_key_exists('sign', $this->values); } 检查签名时,首先会利用函数 IsSignSet判断签名是否存在。若签名不存在,直接认为签名正确。 由于该支付平台要求商户服务器将订单(包括通知URL)发送给支付服务器获取一个ID,随后商户应用将ID传递给支付客户端调起支付界面,在实际攻击中, 攻击者还需要从其他渠道获取通知URL(例如路径猜测、URL硬编码或存在网络请求中等)才可伪造支付结果。 案例二 支付平台B采用了非对称密钥的设计,每个商户有自己的一套公钥私钥,但支付平台的公钥私钥仅一套,即所有商户使用同一个公钥验证来自支付平台的消息。 除此以外,订单消息和支付结果消息中包含字段body描述商品信息,且订单消息和对应支付结果消息中的body一致。 如果攻击者希望免费(低价)购物,应该如何进行呢?回顾上文中关于待签字符串二义性的讨论,待签字符串为形如: key1=value1&key2=value2&key3=value3 的格式。&和=作为了连接符号。 如果某一个参数值(value)中包含&和=符号,待签字符串和原始的参数集合就可能不再是一对一,即存在多组参数集合对应同一组待签字符串。 例如:参数集合 {"key1":"value1","key2":"value2&key3=fake_value&zend_key=a", "key3":"value3"} 的待签字符串为 key1=value1&key2=value2&key3=fake_value&zend_key=a&key3=value3 考虑另一个参数集合 {"key1":"value1","key2":"value2", "key3":"fake_value","zend_key":"a&key3=value3"} 的待签字符串同为 key1=value1&key2=value2&key3=fake_value&zend_key=a&key3=value3 两组集合的待签字符串一样,但key3的值不同。攻击者若知道其中一组的签名,便知道另一组参数在相同密钥下的签名了。 有了这个发现,如何在实际中利用并实现免费(低价)购物呢? 攻击者需要“骗一个畸形订单支付成功的签名”! 首先,攻击者需要拥有一个商户evil的私钥(可自行注册或利用已泄漏密钥) 现在攻击者准备向商户alice购买一件商品,正常的订单消息参数为 {"body":"商品A","notify_url":"http://seller.com/notify","out_trade_no":"12345","seller":"alice", "total_fee":"100","sign":"XXX"} 若订单支付成功,支付结果的参数应为 {"body":"商品A","out_trade_no":"12345", "seller":"alice", "total_fee":"100", "trade_status":"SUCCESS","sign":"YYY"}, 因此,攻击者的目标是向http://seller.com/notify发送一个支付完成的消息,并且包含 {"body":"商品A", "out_trade_no":"12345", "seller":"alice", "total_fee":"100", "trade_status":"SUCCESS"} 这些参数和正确的签名。 (编辑:ASP站长网) |