为什么Private Tracker需要限制客户端,因为并不是所有的BT客户端都是完全遵守BEP规范的实现(其实常用的ut、tr之流也并不是完全遵守的2333)。所以以NP框架为代表的站点要尽量把用户使用的客户端限制在允许的范围,使得能相对正确的记录用户的做种信息。
但是这并不能解决一些能伪造User-Agent以及peer_id_prefix的软件,例如Aria2这样的综合下载软件或者一些流量作弊软件。因为他们可以很容易的伪造,以绕过NP的检测。甚至于Aria2专门提供了这样的配置项来进行修改。例如下面的例子中就将Aria2伪装成 Transmission/2.77
,能成功的绕过原版的NexusPHP检测。
# 客户端伪装, PT需要
peer-id-prefix=-TR2770-
user-agent=Transmission/2.77
- 甚至可以这样伪装(UA为Tr 2.79,但peer_id为Tr 2.77).虽然一看就知道是有问题的,但是根据NP的匹配规则是能通过的233333
peer-id-prefix=-TR2770-
user-agent=Transmission/2.79
那能不能通过一些手段尽可能的检测出来这种伪装的客户端那?这就是本文想要探讨的内容。
太长不看
在announce.php文件 check passkey
行为 之前添加以下信息即可~
if (isset($_SERVER["HTTP_Want-Digest"]) || strpos($peer_id, $key) !== false) {
err("Anti-Cheater: You may use Aria2");
}
Announce报文分析
通过将Aria2伪装成Transmission/2.77
,配置proxy选项,并使用Fiddler 4
抓包,我们得到了Aria2向Tracker的Announce报文
其中一次的报文完整如下
GET /announce.php?passkey={hide}&info_hash=%E6%C2%A0%081b%B1%25%40%14%F7Xbf%B0%F1U%A4%26%E7&peer_id=-TR2770-8u%13Y%CB9%D3%CE%F6mF%0A&uploaded=0&downloaded=0&left=165431200&compact=1&key=%CB9%D3%CE%F6mF%0A&numwant=50&no_peer_id=1&port=51413&supportcrypto=1 HTTP/1.1
User-Agent: Transmission/2.77
Accept: */*
Host: tracker.byr.cn
Want-Digest: SHA-512;q=1, SHA-256;q=1, SHA;q=0.1
而对使用uTorrent或者其他客户端抓包结果分别如下:
- uTorrent 2.2.1
GET /announce.php?passkey={hide}&info_hash=%b0.%00%2bY%8a%eah%bc%0b%e0%0f%3d4%27%0a3%11%20%8c&peer_id=-UT2210-%d6bE%c7L7%d8%aeD1%1e%c3&port=32971&uploaded=0&downloaded=0&left=13300779178&corrupt=0&key=9BA854A1&event=started&numwant=200&compact=1&no_peer_id=1&ipv6={hide} HTTP/1.1
Accept-Encoding: gzip
User-Agent: uTorrent/2210(25302)
Host: nexusphp.localhost
Cache-Control: no-cache
- rTorrent/0.9.4/0.13.4
GET /announce.php?passkey={hide}&info_hash=M%db%82%ad%5do%ac%cd%e2%9b%ac%88%5b%b1H%a8EQ-%11&peer_id=-TR2940-tyyz4k6tzpz2&port=53185&uploaded=0&downloaded=0&left=0&numwant=80&key=5f7e4d94&compact=1&supportcrypto=1&event=started&ipv6={hide} HTTP/1.1
Host: nexusphp.localhost
User-Agent: rtorrent/0.9.4/0.13.4
Accept: */*
Accept-Encoding: deflate, gzip
对比分析
通过对比,我们可以发现一些明显属于Aria2的一些指纹。
- Want-Digest头
Aria2会添加一个Want-Digest
头,该段实现在 src/HttpRequest.cc#L250 中,而其他BT客户端并不会自动添加这个请求头。通过翻阅Aria2的issus,发现有人于2月提出相关issue : issues#1136 - Is there a way to remove an auto added header (Want-Digest) 但是,并没有引起相关团队的重视。那么可以使用该段来进行检测。当发现用户提交的Headers中含有该请求头则禁止访问。示例代码如下:
$headers = getallheaders();
if (isset($headers["Want-Digest"])) err("header 'Want-Digest' is exist");
注:
$headers["Want-Digest"]
可以直接写作$_SERVER["HTTP_Want-Digest"]
。因为getallheaders是一个apache的方法,这样就不用注入在全局中注入。
- key和peer_id中有重复字段
通过对比requests的params,可以发现Aria2提交的peer_id中包含key的全部内容,而其他客户端并没有这种表现。对应 src/DefaultBtAnnounce.cc#L160 中相关实现,可以发现相关构造中均使用了相同的信息。(Use last 8 bytes of peer ID as a key
)
peer_id -> util::percentEncode(bittorrent::getStaticPeerId(), PEER_ID_LENGTH).c_str()
key -> util::percentEncode(bittorrent::getStaticPeerId() + PEER_ID_LENGTH - keyLen, keyLen).c_str()
故可以通过这两个字段进行检查,当key值能在peer_id中发现,则可认为用户使用Aria2,示例代码如下:
$peer_id = $_GET["peer_id"];
$req_key = $_GET["key"];
if (strpos($peer_id, $_GET["key"]) !== false) err("May use Aria2 because of key find in peer_id");
- 请求间隔
Aria2并不会遵循Response中对于interval
值的规定,无论是请求成功还是失败,Aria2会保持每两分钟进行一次轮询。通过限制用户两次Announce的最小间隔限制,可能就能禁止Aria2的之后多次请求,但是这不能限制用户通过查询成功时获得的peer_list从而完成对应种子的下载。(可以考虑通过Cache锁等形式,一直锁住Announce duration 小于2分钟的请求)
- 无Scrape请求
使用Aria2并不会产生Scrape动作,即不会预查询对应种子的做种情况,但是由于scrape和announce请求是分离的,此外一些(较早的)正规的BT软件也同样存在无Scrape请求的情况,故较难利用该点进行判断。
- 请求字段及其顺序
由于在Announce请求中,info_hash以及peer_id都是需要escaped的,所以多数软件都是使用字符串拼合的形式构造GET请求的url。例如Transmission构造的请求链接格式就是以下格式( libtransmission/announcer-http.c#L61):
/info_hash=%s&peer_id=%*.*s&port=%d&uploaded=%d&downloaded=%d&left=%d&numwant=%d&key=%x&compact=1&supportcrypto=1(&requirecrypto=1)?(&corrupt=%d)?(&event=%s)?(&trackerid=%s)?(&ipv6=%s)?/
使用libtorrent库(如qbittorrent,Deluge)格式( libtorrent/src/http_tracker_connection.cpp#L97-L193)
/info_hash=%s&peer_id=%s&port=%d&uploaded=%d&downloaded=%d&left=%d&corrupt=%d&key=%08X(&event=%s)?&numwant=%d&compact=1&no_peer_id=1(&supportcrypto=1)?(&redundant=%s)?(&trackerid=%d)?(?<i2p>&ip=%s.i2p)?(&ip=%s)?(?!\g<i2p>&ipv4=)?(?!\g<i2p>&ipv6=)?$/
而Aria2拼出来则如下 (src/DefaultBtAnnounce.cc#L165):
/info_hash=%s&peer_id=%s&uploaded=%d&downloaded=%d&left=%d&compact=1&key=%s&numwant=%d&no_peer_id=1(&port=%u)?(&event=%s)?(&trackerid=%s)?(&requirecrypto=1|&supportcrypto=1)(&ip=%s)?/
通过对比了解,可以发现Aria2并不会提交&ipv6=
字段,并可以通过对$_SERVER["QUERY_STRING"]
字段进行正则匹配,区分是否是对应软件。
Update 2018/10/21
经过近一个月对某站的Announce Error日志分析,通过Want-Digest头
和key和peer_id中有重复字段
已经基本能确定用户确实使用Aria2进行访问操作,如下图所示:
第一位用户使用Aria2伪装成Transmission 2.79,但是UA和peer_id其实并不能切合(peer_id为Tr 2.77的格式,但是通过了NexusPHP的客户端检测)且url format符合Aria2的格式,虽然不知道通过什么方式去除了Want-Digest头,但是仍然检测出来。
第二位用户使用Aria2伪装成Transmission 2.77,通过Want-Digest头
以及key和peer_id中有重复字段
两项直接检测出来。
基于以上Aria2的检测方法,公开其实现有
- zcqian/tjupt@cf807d36 以及其patch zcqian/tjupt@2f4dc3ba (本人并不赞成将getallheaders的声明写到
announce.php
中,建议将其放在include/globalfunctions.php
中) - RidPT:/apps/controllers/TrackerController
好!
(来自一名不愿透露姓名的网友点赞)
前来捉R酱
那要是这些 bug 都修复了,或者有一个自定义客户端把你上面说的这些都考虑进去了,而上传量数据完全是由客户端提供的,那不就完全没法防止作弊了吗?
难道没有更好的服务端方案(假设客户端不可信)来防止作弊吗?
使用client探针方法(利用peers之间的交换比peer与tracker之间的通信可信度更高,以及伪造客户端因为实现成本更高而不进行peers之间交换的),或者计算站点对应种子总上传下载情况,基本比率能稳定在0.9-1.3,超过再做核查。当然,一些很明显的作弊特征,如果管理有经验的话,应该能直接发现。
想问问为什么要禁止 aria2,它好像也不是迅雷那种吸血客户端吧。主要个人感觉现在想在 Linux 上搭个客户端,除了 transmission 之外也就 aria2 最方便了。
文章最开头已经说了,伪造User-Agent以及peer_id
劣币驱除良币
aria2也不算劣币吧,个人觉得PT站可以限制一下aria2的伪造代理,但是不要禁止aria2
aria2只是管理方便 又不会吸血的 封他干嘛?
不是禁止aria2 而是只允许某些bt软件,自然要封掉钻漏洞伪造peer_id和ua的。
更何况aria2暴力announce消耗服务器资源。
如果你觉得不合适,可以和站点sysop说明。
本文只是给想要禁用aria2伪装的站点sysop一个思路。
此次回复也是最后一次回复为什么封aria2。
aria2其实性能相当差。。在种子这种高吞吐多连接的情况下经常容易假死,而相同配置下qb和tr就能work,所以还是拿来做HTTP下载比较好
这样做有什么好处吗?可以用来刷分享率吗?