web格式化注入漏洞

admin 2022年5月17日02:48:50评论21 views字数 5032阅读16分46秒阅读模式

第一部分:Duo Security Web SDK的一个格式化注入漏洞

翻译自 http://sakurity.com/blog/2015/03/03/duo_format_injection.html

格式化注入和SQL注入有些类似,但是不是通过用户输入的单引号'来改变查询,而是打破自定义的分隔符/:|,;&来修改签名数据。

下面是Duo Security的web集成产品的工作原理:

  • 用户在客户端使用有效用户名和密码登陆,然后收到用来请求二步验证TX token和代表通过第一步的验证APP token。
  • 现在用户使用TX token来通过Duo API(使用Duo的推送、短信或者电话)换取AUTH token。AUTH token代表用户可以通过了第二步的验证。
  • 之前获取的APP token和AUTH token传递到/final_login,然后验证两个token是有效的,而且是属于这个用户的。verify_response响应返回username,然后你现在就可以以这个人的身份登录了。

web格式化注入漏洞

这个系统即使在SKEY被泄露了的情况下,攻击者也不能登录账号,因为他没有你的AKEY,还有他没办法伪造一个有效的APP token。但是我们发现一个Duo在对APP token签名的时候的一个格式化漏洞。

Duo安全部门已经确定这个问题存在于特定版本的Duo Web SDK里面,在他们获取到Duo集成产品的secret
key的时候,这可能导致攻击者绕过两步验证,然后可以创建包含管道符|的有效用户名。
注意:这个漏洞不影响任何官方的产品,它只影响使用部分受影响的Web SDK的客户自助集成的产品。

安全风险是低,这是因为要登录进入一个账号,你仍然需要一个有效的AUTH token,这意味着你必须知道你的SKEY。如果你的应用受到影响,请马上重置你的AKEY。

受影响的都是使用Ruby,PHP,Perl,Java和ColdFusion SDK的。用户名处可以出现管道符,同时用户名也是被用作为Duo ID的(这个可以是用户id或者邮箱,这个不能使用管道符)。

看下面的这段Ruby代码

require 'base64'
require 'openssl'
def hmac_sha1(key, data)
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), key, data.to_s)
end

def sign_vals(key, vals, prefix, expire)
  exp = Time.now.to_i + expire

  val_list = vals + [exp]
  val = val_list.join('|')

  b64 = Base64.encode64(val).gsub(/\n/,'')
  cookie = prefix + '|' + b64

  sig = hmac_sha1(key, cookie)
  return [cookie, sig].join('|')
end

def parse_vals(key, val, prefix)
  ts = Time.now.to_i
  u_prefix, u_b64, u_sig = val.to_s.split('|')
  sig = hmac_sha1(key, [u_prefix, u_b64].join('|'))
  return nil if hmac_sha1(key, sig) != hmac_sha1(key, u_sig)
  return nil if u_prefix != prefix
  user, ikey, exp = Base64.decode64(u_b64).to_s.split('|')
  return nil if ts >= exp.to_i
  return user
end

如果你想创建一个用户名是victim||9999999999的用户,我们获取到的APP token会和用户名叫victim的解析出一样的结果。

sig1 = sign_vals('AKEY',['victim','IKEY'],'APP',3600)
puts parse_vals('AKEY', sig1, 'APP') #returns 'victim'


sig2 = sign_vals('AKEY',['victim||9999999999','IKEY'],'APP',3600)
puts parse_vals('AKEY', sig2, 'APP') #returns 'victim' too

如果你仍然没看懂的话,就仔细看下面的。

app使用victim|IKEY|12345678为受害者签名,user, ikey, exp = string.split('|')返回的是user=victimexp=12345678

app使用victim||9999999999|IKEY|12345678为攻击者签名,user, ikey, exp = string.split('|')返回user=victimexp=9999999999(token是永远有效的了)。

更多的例子:

  • 像是这样的字符串val+DELIMITER+user_input+DELIMITER+...或者[user_input,val2,val3].join(':')的用法,都容易出现格式化注入漏洞。

  • 在一些api和oatuh里面,openURL('http://oauth/?client_id=1&client_secret=2&code='+params[:unescaped_code]),可以使用code=&client_id=new_client_id&client_secret=new_client_secret的方法替换掉客户端凭据,导致验证绕过。

  • '{"val":"'+user_input+'"}'或者'<xml>'+user_input+'</xml>',因为xss也是格式化漏洞的一种。

  • 很多支付网关也是使用了自定义的数据格式。这是Liqpay和WalletOne对订单签名的方法(没有分隔符,只是按照字母顺序排序然后组合)

{
"WMI_MERCHANT_ID"=>"119175088534",
"WMI_PAYMENT_AMOUNT"=>"100.00",
"WMI_CURRENCY_ID"    => "643",
"WMI_PAYMENT_NO"     => "12345-001",
"WMI_DESCRIPTION"    => "BASE64:f",
"WMI_EXPIRED_DATE"   => "2019-12-31T23:59:59",
"WMI_SUCCESS_URL"    => "https://myshop.com/w1/success.php",
"WMI_FAIL_URL"       => "https://myshop.com/w1/fail.php"
}.sort.map{|key,value| value}.join
# # => 643BASE64:f2019-12-31T23:59:59https://myshop.com/w1/fail.php119175088534100.0012345-001https://myshop.com/w1/success.php

第二部分:../sms是怎么绕过Authy的两步验证的

翻译自 http://sakurity.com/blog/2015/03/15/authy_bypass.html

API的调用流程是:

  • 客户端请求新的token http://api.authy.com/protected/json/sms/AUTHY_ID?api_key=KEY,AUTHY_ID是公开的和当前用户关联的凭据,期望的响应是{"success":true,"message":"SMS token was sent","cellphone":"+1-XXX-XXX-XX85"},http status是200。
  • 用户输入token,然后验证这个token是否有效,url是http://api.authy.com/protected/json/verify/用户输入的token/AUTHY_ID?api_key=KEY,如果验证通过,就返回{"success":true,"message":"Token is valid.","token":"is valid"},http status是200。

Authy-node没有编码用户输入的token

在authy-node里面有一个问题:用户输入的token没有进行url编码,代码是this._request("get", "/protected/json/verify/" + token + "/" + id, {}, callback, qs);

这就意味着如果我们输入VALID_TOKEN_FOR_OTHER_AUTHY_ID/OTHER_AUTH_ID#,我们就能修改掉之前的那个路径,然后让客户端发出/protected/json/verify/VALID_TOKEN_FOR_OTHER_AUTHY_ID/OTHER_AUTH_ID#/AUTH_ID这样的请求。因为#之后的会被忽略掉,实际上响应的是/protected/json/verify/VALID_TOKEN_FOR_OTHER_AUTHY_ID/OTHER_AUTH_ID?api_key=KEY,这样就让攻击者登录进来了。

在服务器端根本没办法分辨伪造的请求,因为#/AUTHY_ID就根本没有发送过去。

Authy-python也有漏洞

然后我注意到Python的urllib.quote方法没有编码/,但是由于某种原因,它编码了斜线以为的所有的字符,而且在文档上就是这么说的urllib.quote("#?&=/") 返回的是%23%3F%26%3D/。这就以为着我们的../sms不会被编码。

当浏览器解析/..//%2e%2e/,甚至/%252e%252e/的时候,就会进入到上一个文件夹,到那时服务器不会。不管怎么,我尝试了一下,而且可以工作:Authy的api会把/../之前的文件夹移除。

这就引入了一个路径遍历漏洞,可以让攻击者更加容易的去攻击。你只需要输入../sms就能把/verify请求转到/sms上(/verify/../sms/authy_id),然后返回的http status是200,就绕过了两步验证。

等等,貌似所有的人都受影响

几个小时以后我意识到目录遍历是怎么造成的了,我刚刚看了Daniel’s interview on Authy,然后知道了他们在使用Sinatra,默认使用rack-protection的。

我发现貌似进行url编码也是徒劳的,rack-protection中的path_traversal模块会将%2f再解码为/!这样就会影响所有运行Sinatra和在url中获取参数的api。这是一个很棒的例子展示了一些库或者特性本来想增加安全性,但是实际上因为了安全漏洞的。

web格式化注入漏洞

  • 攻击者将../sms填入短信验证码文本框。
  • 客户端将其编码为..%2fsms,然后调用Authy的api https://api.authy.com/protected/json/verify/..%2fsms/authy_id
  • 目录遍历中间件将路径解码为https://api.authy.com/protected/json/verify/../sms/authy_id,通过斜线分割,然后移除了/..之前的目录。
  • 实际上Authy api看到的路径是https://api.authy.com/protected/json/sms/authy_id ,这样就把另一条短信发给了authy_id(攻击者),然后相应200,内容是{"success":true,"message":"SMS token was sent","cellphone":"+1-XXX-XXX-XX85"}
  • 所有的authy sdk都把http status 200认为是一个成功的响应,然后让攻击者登录进来。即使有的客户自己集成的产品是看响应里面的"success":"true",但是我们/sms响应里面也是有这个的。所以唯一的安全的方法就是看响应里面有没有"token":"is valid"

是的,攻击者只要简简单单的在任何使用authy的产品的网站填写../sms就能绕过两步验证了。

FROM :strcpy.me | Author:strcpy

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月17日02:48:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   web格式化注入漏洞http://cn-sec.com/archives/1013464.html

发表评论

匿名网友 填写信息