NewFuture / DDNS

:triangular_flag_on_post: 自动更新域名解析到本机IP(支持dnspod,阿里DNS,CloudFlare,华为云,DNSCOM...)
https://ddns.newfuture.cc/
MIT License
4.23k stars 650 forks source link

调用阿里 API 有个小 bug,但是在本项目可能不会触发 #231

Open CrazyBoyFeng opened 3 years ago

CrazyBoyFeng commented 3 years ago

https://github.com/NewFuture/DDNS/blob/62eed8bb943a219065a0fff13ab0d81edc16f891/dns/alidns.py#L56-L58

规范化参数字符串 query = urlencode(sorted(params.items())) 应该为 query = urlencode(sorted(params.items()), quote_via=quote) 因为 urlencode() 默认使用 quote_plus,它把空格处理成加号,不符合阿里云文档要求处理成 %20
然后下面的 quote(query, safe='') 再按照文档把 %20 处理成 %2520
而且这里的 quote(query, safe='') 其实可以不设置忽略 / 字符安全而直接写成 quote(query),因为此处的 query 是被 urlencode() 处理得到的,不可能含有 / 字符。

不过本项目不会出错,大概是因为本项目的数据没有空格。我抄了这个代码准备写 DNSSEC 客户端时遇到了这个问题,因为 DNSSEC 添加的记录包含空格。

顺便吐个槽:阿里云是我见过的最奇葩的 API,没有之一。

CrazyBoyFeng commented 3 years ago

这对本项目来说不是个 bug,我给写到 commit 评论里吧,提醒一下可能存在的 bug。如果这段代码将来挪作它用,也方便回溯问题。本项目既然不会触发那么实际上就没必要修改了。

议题关闭。

NewFuture commented 3 years ago

可以提个PR fix一下

Elec4d0 commented 3 years ago

遇到个bug 不能正常ddns,貌似和这个问题有关:

pip version 21.0.1 ddns version 21.0.1

[root@ssr ~]# ddns

xxxx.xxx.com(AAAA) ==> xxx:xxxx:xxx:xxxx::xx [via DIRECT] ERROR:root:urlencode() got an unexpected keyword argument 'quote_via'

NewFuture commented 3 years ago

遇到个bug 不能正常ddns,貌似和这个问题有关:

pip version 21.0.1 ddns version 21.0.1

[root@ssr ~]# ddns

xxxx.xxx.com(AAAA) ==> xxx:xxxx:xxx:xxxx::xx [via DIRECT] ERROR:root:urlencode() got an unexpected keyword argument 'quote_via'

python2 ?

Elec4d0 commented 3 years ago

遇到个bug 不能正常ddns,貌似和这个问题有关: pip version 21.0.1 ddns version 21.0.1 [root@ssr ~]# ddns xxxx.xxx.com(AAAA) ==> xxx:xxxx:xxx:xxxx::xx [via DIRECT] ERROR:root:urlencode() got an unexpected keyword argument 'quote_via'

python2 ?

是的,python2。 现在centos上的最新源还是python2.7,而且因为有系统依赖不能卸载python2.7,不过可以安装python3.5。 这个我降级了一次DDNS版本后解决了,但是遇到了一个额外的问题: DDNS没能把完整的IPV6地址提交上去,具体如下:

修改后 | AAAA | xxxx | 默认 | XXXX:XXXX:422:1e7c:: |   | 600

就是双冒号后的内容好像没识别好 json里,ipv6是“public” 这个有先例不,没有我去开个新的问题……?

NewFuture commented 3 years ago

有可能python2不支持 quote_via

CrazyBoyFeng commented 3 years ago

python2 没有 quote_via 。考虑:

  1. 回滚。如之前所述,这个改动对本项目没有意义,因为本项目的参数值没有空格。
  2. 如果要考虑改进方案,可在回滚的基础上,给生成字符串替换加号 replace('+','%20')
NewFuture commented 3 years ago

可以再加一个pr @CrazyBoyFeng

NewFuture commented 3 years ago

@Elec4d0 master 的更新了,可以先试试

Elec4d0 commented 3 years ago

赞,无论回滚和改进,fix掉bug就是好事。 期待ddns3.0

CrazyBoyFeng commented 3 years ago

DDNS没能把完整的IPV6地址提交上去,具体如下:

修改后 | AAAA | xxxx | 默认 | XXXX:XXXX:422:1e7c:: |   | 600

就是双冒号后的内容好像没识别好 json里,ipv6是“public”

这可能是另外的原因造成的。

Elec4d0 commented 3 years ago

DDNS没能把完整的IPV6地址提交上去,具体如下: 修改后 | AAAA | xxxx | 默认 | XXXX:XXXX:422:1e7c:: |   | 600 就是双冒号后的内容好像没识别好 json里,ipv6是“public”

这可能是另外的原因造成的。

这是偶发性bug,在我的三个centos服务器中,只有一个出现了这个问题。 大佬需要日志debug的话,可以提供个查找日志的方式,定配合。

scientificCommunity commented 7 months ago

我也碰到了,阿里云的文档有问题,他们那边会把url解码后里面的空格编码成%2520(而不是文档说的%20)后再验签,所以签名前要把空格替换成%2520,有点无语

CrazyBoyFeng commented 7 months ago

为什么重开了这个 issue,关于阿里云 API 有什么新的问题产生了吗?

to @scientificCommunity :
这个项目中使用的是 1.0 的验签 API,按照当时的 2019 版文档要求,url 参数里面的空格被处理成 %2520,是因为经过了两次转换。文档并没有写错。第一次转换是对参数值进行转换,将空格变为 %20。第二次是规范化请求字符串,将 %20 变成了 %2520

本项目的代码也是这样两段式的处理。先对 query 参数 quote() 一次,将其中空格转为 %20,再拼接成 url 格式再次 quote()%20 最终变成了 %2520

最新的 V3 版 OpenAPI 已经修改为仅进行一层 quote()。对于 V3 版本的 OpenAPI 文档是否与实际相符我没有研究。

scientificCommunity commented 7 months ago

@CrazyBoyFeng 从%20%2520是对其中的%20进行了又一次编码,把%编码成了%25。而阿里云api文档的描述是将编码后的+替换成%20,并不需要单独对这个%20再编码一次。 事实上,把原始数据的空格替换成%20,再经过 application/x-www-form-urlencoded MIME格式编码算法进行编码即可保证签名跟验签的一致性。 我对编码这一块的技术的了解并不深,阿里云的api文档与实际表现让我很困惑,我相信应该不止我一个人困惑,所以分享给大家。 另外,我是从另外一个ddns项目过来的,那个项目也是把+处理成了%20,无法通过阿里云验签

CrazyBoyFeng commented 7 months ago

@scientificCommunity 阿里云的文档里其实写了两次编码:

构建待签名字符串 a. 使用请求参数构造规范化的请求字符串(Canonicalized Query String): i. 按照参数名称的字典顺序对请求中所有的请求参数进行排序。 ii. 对排序之后的请求参数的名称和值分别用UTF-8字符集进行URL编码。

这是第一次编码。得到的 CanonicalizedQueryString 是已经被 percetEncode() 编码过然后拼接起来的。其中 A-Z、a-z 和 0-9 以及 -_.~ 之外的字符已经全被转换为百分号形式了。整个字符串还含有 =& 这两个非规范化字符。

b. 将第一步构造的规范化字符串按照下面的规则构造成待签名的字符串。 StringToSign = HTTPMethod + “&” + percentEncode(“/”) + ”&” + percentEncode(CanonicalizedQueryString)

这是第二次编码。可以看到它对已经编码过的 CanonicalizedQueryStringpercentEncode() 了一遍。
其实这次它只是把 =& 以及各种已经被处理成 % 开头的字符码处理成了 %3D%26 以及 %25xx

它的 API 奇葩主要就体现在这里,要搞两次转换。 它所使用的 percentEncode() 函数,不是标准的 MIME 格式,在 Java 标准库中没有与之对应的函数,需要自行构建。不过在 Python 中,恰好就是 quote() 函数。


我研究了最新的 V3 版 OpenAPI,发现它的验签已经不再进行两次转换,而是将一次转换后的规范化请求字符串 CanonicalRequest 进行哈希运算得到 HexedHashedCanonicalRequest,然后参与验签。