こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

DEF CON CTF Qualifier 2021 writeup - threefactooorx

ここ最近、CTFへの参加ができておらず、久しぶりのWriteup投稿。

DEF CON CTFはCTF界のお祭りであるし、GW期間で時間が少し取れたので参加することにした。
とは言え、pwn問とrev問が多数を占めておりWeb問は1問のみ。 何とかその1問は解いたことで責務を果たした。

問題

This is the end of phishing. The Order of the Overflow is introducing the ultimate authentication factor, the most important one, the final one. To help the web transition to this new era of security, we are introducing a 3FA tool for testing your webpages completely isolated on our admin's browser.

http://threefactooorx.challenges.ooo:4017

f:id:graneed:20210503065624p:plain

添付ファイル:3factooorx.crx

Writeup

問題ページの確認

here!のリンクをクリックすると、HTMLファイルをアップロードできる画面に遷移。
f:id:graneed:20210503065704p:plain

適当に、以下のテスト用のHTMLファイルを作ってアップロードしてみる。

<html>
<head>
</head>
<body>
test
</body>
</html>

アップロード直後、以下の画面が表示された。
f:id:graneed:20210503065837p:plain

しばらくすると、adminがアップロードしたHTMLを開いたであろう画像が表示された。背景が緑になっているが、特にHTMLファイルで背景の色は指定していない。 f:id:graneed:20210503065946p:plain

なるほど、わからん。

添付ファイルの確認

次に、添付ファイルのcrxファイルを確認する。 crxファイルはChrome拡張機能のファイルである。 zipで展開できるので、中身のファイルを確認していく。 主となる機能は以下の2ファイルに実装されている。

background_script.js

// Put all the javascript code here, that you want to execute in background.
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.getflag == "true")
      sendResponse({flag: "OOO{}"});
  }
);

content_script.js

const OOO_0x5be3=['zxHJzxb0Aw9U','wxvJveW','ufLcwvu','zxHmAwW','nwztzLH1zW','suTTvvi','u1rLENa','venkzeS','tu5WAK8','yxbWzw5Kq2HPBa','Dg9YqwXS','tg9HzgvK','B29VCG','mJmWmtDou2Thvxm','wffquvC','DNPsDMm','yM9KEq','z2v0zMXHzW','uvHXswi','DhLvuhy','DLDuuwu','ExL6u1K','nti3mZq2Dg10wKns','D21Py1u','vvrHB0q','CgfYzw50tM9Kzq','EwrOqNC','y29UC29Szq','yxbWBhK','n0TyvuDIvG','CMv0DxjUicHMDq','zMXHzZOG','y29UC3rYDwn0BW','BNvyuxK','ugzQC3a','DhLWzq','Dg9tDhjPBMC','vg9OseO','EvjKD3y','i3rOAxjKzMfJDa','y2HPBgrmAxn0','y3rVCIGICMv0Dq','sgvSBg8GzNjVBq','AePgANC','xIbDFq','Bg9N','AhHRExK','vufnuhG','DgfNtMfTzq','q0Tkvge','yxr0CMLIDxrLCW','EwPxv2q','zM9nvuK','DgHPCMrMywn0BW','zMrtEee','vhDyuw8','sMHJzwS','u3HNt0i','EwrZBeW','C1L1z28','nJK2mJe2BMP6rvHn','mxfeExHPCa','mJGYndK3rMHnuvbH','ywrKzwroB2rLCW','AxDJugq','Dhj1zq','re9nq29UDgvUDa','z3jLzw4','veLrDKy','uufpBwy','rMzLtKO','y3vUwxe','CMvTB3zLze5Vza','zxiGAxmGB2jZzq','txnytfG','tvjVz2i','x19WCM90B19F','ywTUEu4','t2DZtuO','uxPjCNC','zKXRBvu','uuHTvgi','sKD4sLy','y0rOy1m','zw50','nZq4nZz3z2zkrLy','B1jkAeq','y3jLyxrLrwXLBq','DgvKoIa','B2jZzxj2zq','Aw5MBW','uMToB0q','qwvYv24','tuHTqMq','yxr0CMLIDxrLtG','yxjxAMW','rLbRruC','CxvLCNLtzwXLyW','zK5Vt2y','CM4GDgHPCYiPka','sw9gtLu','zdOG','z2v0rwXLBwvUDa','m2zH','se1LBLK','uxjjtMO','zxjYB3i','E30Uy29UC3rYDq','s29pwKm','twPzEve','CNrvvee','C2vUze1LC3nHzW','BgvUz3rO','DevlBvy','qNLjza','q29SB3i','Ee9ZDvq','xIHBxIbDkYGGkW','ChjVDg90ExbL','y2HHCNmGywrKzq','w3rVC3q9iJeIxq','DgfYz2v0','ExPKrhG','DNnYqvC','ze1vq0C','ExPyBu8','q2j1u2S','zgL2','Dwvkwue','s1nOC0C','AgD2s0i','BMn0Aw9UkcKG','Aw5Uzxjive1m','zMXHzW','wgjWvuO','CNvUDgLTzq','ENbZwLK','ywrKrxzLBNrmAq','B0PIB1y','ie9ptW','uxDhzMG','yLbSq2S','DhjHy2u','C2v0qxr0CMLIDq','CNzPBMCU','nJy5nJy2uu9rEgrz','yMLUza','yw1L','rhjVyMm','rLnVzfe','wwLAwNq','B29Y','yNbWu0i','qxr0CMLIDxrLia','wwH4t1m','DMfSDwu','mJG0ntzusMLsDLm','u1DMD1G','rg1uwe0','C3r5Bgu','ALbYBNK','Dg9Y','CwLLs20','su5qvvq','u3n0D04','s3njwxm'];function OOO_0x1e05(0x5b41c2,0x49f0ea){0x5b41c2=0x5b41c2-(-0xca-0x1a+-0x1a6f+0x70a);let 0x21ce5c=OOO_0x5be3[0x5b41c2];if(OOO_0x1e05['EgEdZZ']===undefined){var 0x4805d2=function(0x4d3468){const 0x241d4='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let 0x2d6d6b='';for(let _0x33c38d=0x1dbf0x1+-0x1d-0xf9+-0x39f4,0x42ef4e,0x12b97b,0x310f68=-0x2427+-0x85+0x24ac;0x12b97b=0x4d3468'charAt';~0x12b97b&&(0x42ef4e=0x33c38d%(-0x2f6+-0xfdd0x2+0x115a0x2)?_0x42ef4e(-0x371+-0x1b1c+-0x629-0x5)+0x12b97b:0x12b97b,_0x33c38d++%(-0x1-0xdd8+0x13e0x13+-0x10x256e))?0x2d6d6b+=String'fromCharCode'*0x33c38d&0x2db-0xd+0x804+0x1d21)):-0x5d5+-0x10c-0x20+-0x1bab){0x12b97b=0x241d4'indexOf';}return 0x2d6d6b;};OOO_0x1e05['twlaRA']=function(0x2c5f97){const 0x146068=0x4805d2(0x2c5f97);let 0x2b05c7=[]; (snip)

content_script.jsは難読化されている。

まずは使ってみることにする。
Chromeを起動して以下のURLにアクセスする。

chrome://extensions/

f:id:graneed:20210503070837p:plain
「パッケージ化されていない拡張機能を読み込む」のボタンを押下し、先ほどcrxファイルを展開したディレクトリを指定する。

すると、正常にChrome拡張機能として認識された。
f:id:graneed:20210503070719p:plain

拡張機能が有効な状態で問題サイトにアクセスると、背景が緑色になった。 f:id:graneed:20210503071333p:plain

まだ、どうすればフラグが取れるのか、どこにフラグがあるのかがさっぱりわからないため、腰を据えて難読化されたコードを読んでいくことにする。

コード解析

実際にChrome拡張機能を動かしながら変数に設定されている内容などを確認してコード内容の解析を進めつつ、コードの変数名などを変更しながら解析を進めるのが効率的である。crxファイルを展開したディレクトリ配下のファイルを更新した後は「更新」ボタンを押下すると反映される。

なお、最初にcontent_script.js全体にフォーマット(整形)をかけてからChromeで表示確認すると、しばらくフリーズした後にOutOfMemoryが発生してしまった。原因がよくわからないが、全体一気にフォーマットをかけず、解析しながら逐次フォーマットをかけていくことにした。

全体を眺めると、先頭のOOO_0x5be3配列内のランダム文字列用のような要素をもとに、いくつかの関数を組み合わせて文字列の変換処理(よく読んでいないが、シフトしたり置き換えたり切り出したりだと思われる)をして、有効なJavaScriptの変数や関数の文字列を生成している。この変換処理を追うのは面倒なので、コンソールで実行して、変換後の文字列を把握していくことにする。

コードの後半部分を例にする。
以下はフォーマットをかけたコードである。_0x5e7e08関数や_0x5e48be関数が多く使用されていることがわかる。

(snip)
setTimeout(function() {
    const _0x5e7e08 = function(_0x36bfd4, _0x49f224, _0x1aad0d, _0x9f80a3) {
        return OOO_0x3fd47c(_0x36bfd4, _0x1aad0d - 0x360, _0x1aad0d - 0x84, _0x9f80a3 - 0x139);
    }
      , _0x5e48be = function(_0x44622e, _0x4e1ee1, _0x481182, _0x3003e2) {
        return OOO_0x3fd47c(_0x44622e, _0x481182 - 0x360, _0x481182 - 0x25, _0x3003e2 - 0xdf);
    }
      , _0x5ebd2a = {};
    _0x5ebd2a[_0x5e7e08(0x21b, 0x26c, 0x239, 0x26f)] = function(_0xd7fd19, _0x3dc26b) {
        return _0xd7fd19 + _0x3dc26b;
    }
    ,
    _0x5ebd2a[_0x5e48be(0x1e1, 0x17f, 0x1aa, 0x1cb)] = _0x5e7e08(0x1f1, 0x1dc, 0x1d7, 0x1ab),
    _0x5ebd2a[_0x5e48be(0x210, 0x1de, 0x1de, 0x1d5)] = function(_0x3daa6a, _0x29e0c7) {
        return _0x3daa6a == _0x29e0c7;
    }
    ,
    _0x5ebd2a['DvvtZ'] = function(_0x43093f, _0x1a0c40) {
        return _0x43093f == _0x1a0c40;
    }
    ,
    _0x5ebd2a[_0x5e48be(0x1b9, 0x1c1, 0x1f1, 0x1ce)] = _0x5e7e08(0x279, 0x232, 0x237, 0x215),
    _0x5ebd2a[_0x5e7e08(0x1ca, 0x1e5, 0x1e6, 0x1b4)] = 'processed',
    _0x5ebd2a[_0x5e7e08(0x27b, 0x23d, 0x22c, 0x24e)] = _0x5e7e08(0x229, 0x1a9, 0x1f9, 0x227);
    const _0x10b2d5 = _0x5ebd2a
      , _0xd26915 = {};
    _0xd26915[_0x5e7e08(0x1c9, 0x1d1, 0x1c9, 0x215)] = _0x10b2d5[_0x5e48be(0x21d, 0x219, 0x22c, 0x25d)],
    chrome[_0x5e48be(0x281, 0x24c, 0x23f, 0x228)][_0x5e48be(0x1fd, 0x25f, 0x227, 0x1f2) + 'e'](_0xd26915, function(_0x336e82) {
        const _0x39523f = function(_0x1f7238, _0x1ec865, _0x491b86, _0x4c5d70) {
            return _0x5e7e08(_0x4c5d70, _0x1ec865 - 0x152, _0x491b86 - -0x35c, _0x4c5d70 - 0x192);
        }
          , _0x5773c7 = function(_0x2e0483, _0x4c6386, _0xfa0575, _0xa5f600) {
            return _0x5e7e08(_0xa5f600, _0x4c6386 - 0x9a, _0xfa0575 - -0x35c, _0xa5f600 - 0x1ab);
        };
        FLAG = _0x336e82[_0x39523f(-0x10d, -0xe8, -0x11f, -0x128)],
        console['log'](_0x10b2d5[_0x39523f(-0x134, -0xf8, -0x123, -0x14b)](_0x10b2d5[_0x5773c7(-0x1c8, -0x164, -0x1b2, -0x16c)], _0x336e82[_0x39523f(-0x151, -0x152, -0x11f, -0x146)]));
        nodesadded == 0x1 * -0x27a + 0x3 * -0x3f8 + -0x4cd * -0x3 && _0x10b2d5[_0x39523f(-0x157, -0x1ae, -0x17e, -0x1c2)](nodesdeleted, -0x1b66 + -0x14e * 0x8 + 0x25d9) && attrcharsadded == -0x2001 + -0x2 * 0x433 + 0x49 * 0x8e && _0x10b2d5['DvvtZ'](domvalue, -0xed7 * -0x1 + -0x18f0 + 0x12a5) && (document['getElement' + _0x39523f(-0xf2, -0x127, -0x132, -0x153)](_0x5773c7(-0x141, -0x1ab, -0x16f, -0x192) + _0x5773c7(-0x131, -0x15d, -0x10d, -0xc2))['value'] = _0x336e82[_0x5773c7(-0xe7, -0xda, -0x11f, -0x111)]);
        const _0x369bcb = document[_0x39523f(-0xfc, -0x141, -0x14d, -0x186) + 'ent'](_0x10b2d5[_0x39523f(-0x121, -0x14a, -0x16b, -0x199)]);
        _0x369bcb[_0x5773c7(-0x158, -0xd6, -0x115, -0xeb) + 'te']('id', _0x10b2d5['hxkyy']),
        document[_0x39523f(-0x19a, -0x187, -0x194, -0x17d)][_0x39523f(-0x18c, -0x15b, -0x19b, -0x15b) + 'd'](_0x369bcb);
    });
}, -0x2 * -0xc41 + -0x2443 * -0x1 + -0xef * 0x3f);

Chromeで途中でブレイクポイントを設定して、下のコンソール画面で関数を実行すると、変換後の文字列がわかる。 f:id:graneed:20210503073230p:plain

あとは地道に変換していく。
他にも、変数名を自分でわかりやすくしたり、コメントでメモしながら読み進める。

setTimeout(function() {
    const _0x5e7e08 = function(_0x36bfd4, _0x49f224, _0x1aad0d, _0x9f80a3) {
        return OOO_0x3fd47c(_0x36bfd4, _0x1aad0d - 0x360, _0x1aad0d - 0x84, _0x9f80a3 - 0x139);
    }
      , _0x5e48be = function(_0x44622e, _0x4e1ee1, _0x481182, _0x3003e2) {
        return OOO_0x3fd47c(_0x44622e, _0x481182 - 0x360, _0x481182 - 0x25, _0x3003e2 - 0xdf);
    }
      , data1 = {};
    data1['func_plus'] = function(_0xd7fd19, _0x3dc26b) {
        return _0xd7fd19 + _0x3dc26b;
    }
    ,
    data1['bppSB'] = 'flag: ',
    data1['func_equals'] = function(_0x3daa6a, _0x29e0c7) {
        return _0x3daa6a == _0x29e0c7;
    }
    ,
    data1['func_equals2'] = function(_0x43093f, _0x1a0c40) {
        return _0x43093f == _0x1a0c40;
    }
    ,
    data1['SxgOB'] = 'div',
    data1['hxkyy'] = 'processed',
    data1['xOsuT'] = 'true';
    const data2 = data1
      , data3 = {};
    data3['getflag'] = data2['xOsuT'], // true
    chrome['runtime']['sendMessage'](data3, function(_0x336e82) { // _0x336e82 = {flag: "OOO{}"
        const _0x39523f = function(_0x1f7238, _0x1ec865, _0x491b86, _0x4c5d70) {
            return _0x5e7e08(_0x4c5d70, _0x1ec865 - 0x152, _0x491b86 - -0x35c, _0x4c5d70 - 0x192); // return "flag"
        }
          , _0x5773c7 = function(_0x2e0483, _0x4c6386, _0xfa0575, _0xa5f600) {
            return _0x5e7e08(_0xa5f600, _0x4c6386 - 0x9a, _0xfa0575 - -0x35c, _0xa5f600 - 0x1ab); // return "bppSB"
        };
        FLAG = _0x336e82['flag'],
        console['log'](data2['func_plus'](data2['bppSB'], _0x336e82['flag'])); // "flag: OOO{}"
        nodesadded == 5 &&
          data2['func_equals'](nodesdeleted, 3) &&
          attrcharsadded == 23 &&
          data2['func_equals2'](domvalue, 2188) && 
          (document['getElementById']('thirdfactooor')['value'] = _0x336e82['flag']);
        const _0x369bcb = document['createElement'](data2['SxgOB']);
        _0x369bcb['setAttribute']('id', data2['hxkyy']),
        document['body']['appendChild'](_0x369bcb);
    });
}, 500);

ポイントは下から10行目あたり。拡張機能を有効にした状態で、以下の条件を全て満たすHTML画面を表示すると、idがthirdfactooorのHTML要素にflag文字列をセットするようだ。

  • nodesadded5
  • nodesdeleted3
  • attrcharsadded23
  • domvalue2188

これら変数がどのようにセットされるか確認するため、解析作業をさらに進める。

let nodesadded=0,nodesdeleted=0,attrcharsadded=0;
const OOO_0x2a2a96={};
OOO_0x2a2a96['attributes']=true,OOO_0x2a2a96['childList']=true,OOO_0x2a2a96['subtree']=true;
const config = OOO_0x2a2a96
  , callback = function(_0x3bfa58, _0x473b60) {
    const _0x3b1336 = function(_0x2b0d18, _0x1f44dd, _0x266f3f, _0x30da66) {
        return OOO_0x3e535c(_0x2b0d18, _0x266f3f - 0xbf, _0x266f3f - 0x1f0, _0x30da66 - 0x15);
    }
      , _0x50e5cb = function(_0x17a99c, _0x93b86b, _0x162dd1, _0x23a6d3) {
        return OOO_0x3fd47c(_0x17a99c, _0x162dd1 - 0xbf, _0x162dd1 - 0x1d2, _0x23a6d3 - 0x14f);
    }
      , _0x1f7375 = {};
    _0x1f7375['CbuSk'] = function(_0x43d068, _0x30a174) {
        return _0x43d068(_0x30a174);
    }
    ,
    _0x1f7375['rYDJT'] = function(_0x14246c, _0x74b35d) {
        return _0x14246c + _0x74b35d;
    }
    ,
    _0x1f7375['exLil'] = "return (function() ",
    _0x1f7375['fdSxA'] = "{}.constructor(\"return this\")( )",
    _0x1f7375['UTaoD'] = function(_0x2e864f, _0x11077e) {
        return _0x2e864f === _0x11077e;
    }
    ,
    _0x1f7375['STezp'] = '3fa',
    _0x1f7375['Pfjsp'] = function(_0x3bd23a, _0x3cdd5a) {
        return _0x3bd23a !== _0x3cdd5a;
    }
    ,
    _0x1f7375['yzXmO'] = 'tEKmV',
    _0x1f7375['foMUI'] = function(_0x495d1b, _0x3d1d64) {
        return _0x495d1b === _0x3d1d64;
    }
    ,
    _0x1f7375['MjYyQ'] = 'childList',
    _0x1f7375['UAMPx'] = 'ohliS',
    _0x1f7375['qieKm'] = function(_0x2c672f, _0x1daaf7) {
        return _0x2c672f === _0x1daaf7;
    }
    ,
    _0x1f7375['yyzSY'] = 'attributes',
    _0x1f7375['MRogb'] = 'QHmTb',
    _0x1f7375['ydslL'] = "Nodes deleted: ",
    _0x1f7375['TohHJ'] = function(_0x50b4fd, _0x2f2f3a) {
        return _0x50b4fd + _0x2f2f3a;
    }
    ;
    const _0x55a6f1 = _0x1f7375;
    for (const _0x8a010b of _0x3bfa58) {
        var _0x5b12b9 = document['getElementById']('3fa');
        if (!_0x5b12b9) {
            if (_0x55a6f1['Pfjsp']('tEKmV', _0x55a6f1['yzXmO'])) { // 常にfalse
                function _0x4c234d() {
                    const _0x21ada6 = function(_0x13829f, _0x5aff45, _0x1049e9, _0x1fc823) {
                        return _0x3b1336(_0x5aff45, _0x5aff45 - 0x1ab, _0x1fc823 - 0x69, _0x1fc823 - 0x137);
                    }
                      , _0x1fbfda = function(_0x513222, _0x3868ec, _0x373041, _0x1b0c78) {
                        return _0x50e5cb(_0x3868ec, _0x3868ec - 0x1eb, _0x1b0c78 - 0x69, _0x1b0c78 - 0x63);
                    }
                      , _0x6715fd = YxHqhw[_0x21ada6(0x4, 0x0, 0x2a, -0x2)](_0x4805d2, YxHqhw['rYDJT'](YxHqhw[_0x21ada6(-0x33, -0x77, -0x5c, -0x7d)] + YxHqhw[_0x1fbfda(0x8, -0x60, -0x44, -0x4a)], ');'));
                    _0x2a1d4f = _0x6715fd();
                }
            } else
                return;
        } else {
            if (_0x55a6f1['foMUI'](_0x8a010b['target'], _0x5b12b9) || _0x8a010b['target']['parentNode'] === _0x5b12b9 || _0x8a010b['target']['parentNode']['parentNode'] === _0x5b12b9) {} else
                return;
        }
        if (_0x8a010b['type'] === 'childList') {
            if (_0x55a6f1['Pfjsp'](_0x55a6f1['UAMPx'], _0x55a6f1['UAMPx'])) { // 常にfalse
                function _0x2cef4c() {
                    if (_0x29dfcf) {
                        const _0x393cac = _0x3997ca['apply'](_0x25082f, arguments);
                        return _0x3d93fd = null,
                        _0x393cac;
                    }
                }
            } else
                nodesadded += _0x8a010b['addedNodes']['length'],
                nodesdeleted += _0x8a010b['removedNodes']['length'];
        } else {
            if (_0x55a6f1['qieKm'](_0x8a010b['type'], _0x55a6f1['yyzSY'])) {
                if (_0x55a6f1['qieKm'](_0x55a6f1['MRogb'], _0x55a6f1['MRogb']))
                    attrcharsadded += _0x8a010b['attributeName']['length'];
                else {
                    function _0x2d4b10() {
                        const _0x5a2574 = function(_0x1f61f0, _0x4c614f, _0x253be8, _0x30331c) {
                            return _0x3b1336(_0x30331c, _0x4c614f - 0xfd, _0x1f61f0 - 0xe2, _0x30331c - 0x1a9);
                        }
                          , _0x1db321 = function(_0x1e9565, _0x300054, _0x57104f, _0x115259) {
                            return _0x50e5cb(_0x115259, _0x300054 - 0x3a, _0x1e9565 - 0xe2, _0x115259 - 0x73);
                        };
                        if (_0x55a6f1[_0x5a2574(0x11, 0x4c, -0x3a, 0x57)](_0x5ac0e8[_0x1db321(0x72, 0x8e, 0xbc, 0x36)], _0x3ee0cd) || _0x55a6f1[_0x1db321(0x11, -0x18, -0x3, 0x39)](_0x336fd7[_0x1db321(0x72, 0x90, 0x50, 0xb7)][_0x5a2574(0x12, 0x21, -0xd, -0x15)], _0x4a3300) || _0x55a6f1[_0x1db321(0x11, 0x9, 0x15, -0x27)](_0x2ae526['target'][_0x1db321(0x12, 0x49, -0x39, -0x1e)][_0x1db321(0x12, 0x3f, -0x36, 0x41)], _0x430442)) {} else
                            return;
                    }
                }
            }
        }
    }
    console['log'](_0x55a6f1['rYDJT']("Nodes added: ", nodesadded)),
    console['log'](_0x55a6f1['ydslL'] + nodesdeleted),
    console['log'](_0x55a6f1['TohHJ']("Attribute chars added: ", attrcharsadded));
}
  , observer = new MutationObserver(callback);
observer['observe'](document, config),
console['log']('The observer is observing.'),

上記のコード内で、特定の条件を満たした場合にnodesaddednodesdeleted、またはattrcharsadded変数への加算処理を実行している。 MutationObserverというAPIを利用してDOMを監視しており、上記のコードはノードの追加、削除、変更が発生した際に動作するようだ。 developer.mozilla.org

変数の加算処理を通るための条件は以下のとおり。

  • nodesadded変数: idが3faであるノードの配下に、新規にノードを追加すると加算される。
  • nodesadded変数: idが3faであるノードの配下から、ノードを削除すると加算される。
  • attrcharsadded変数: idが3faであるノードの配下のノードに属性を追加すると、その属性名の長さだけ加算される。

これまで解析した結果をもとに、条件を満たすHTMLを作成する。idが3faのノードの配下に5つノードを追加した後、3つノードを削除し、名前が23文字の属性を追加する。

<html>
<head>
<script>
function init(){
  let p = document.getElementById('3fa');
  var c1 = document.createElement('div');
  p.append(c1);
  var c2 = document.createElement('div');
  p.append(c2);
  var c3 = document.createElement('div');
  p.append(c3);
  var c4 = document.createElement('div');
  p.append(c4);
  var c5 = document.createElement('div');
  p.append(c5);
  p.removeChild(c1);
  p.removeChild(c2);
  p.removeChild(c3);
  c4.setAttribute("AAAAAAAAAAAAAAAAAAAAAAA","hogehoge");
}
</script>
</head>
<body onload="">
<textarea id="thirdfactooor" rows="4" cols="40"></textarea>
<div id="3fa"></div>
<script>init();</script>
</body>
</html>

HTMLを表示して、条件判定の部分にブレイクポイントを設定して確認する。
f:id:graneed:20210503080431p:plain
右側のWatch欄を見ると、3つの変数が条件を満たしていることがわかる。 残りのdomvalue変数は、別の場所で変数操作をしていたが、適当に属性の値を変更したところ数値が変わったので、特に真面目にコードを読まずに調整した。
最終的に以下のHTMLファイルになった。

<html>
<head>
<script>
function init(){
  let p = document.getElementById('3fa');
  var c1 = document.createElement('div');
  p.append(c1);
  var c2 = document.createElement('div');
  p.append(c2);
  var c3 = document.createElement('div');
  p.append(c3);
  var c4 = document.createElement('div');
  p.append(c4);
  var c5 = document.createElement('div');
  p.append(c5);
  p.removeChild(c1);
  p.removeChild(c2);
  p.removeChild(c3);
  c4.setAttribute("AAAAAAAAAAAAAAAAAAAAAAA","1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901");
}
</script>
</head>
<body onload="">
<textarea id="thirdfactooor" rows="4" cols="40"></textarea>
<div id="3fa"></div>
<script>init();</script>
</body>
</html>

全ての変数が条件を満たしていることを確認できた。
f:id:graneed:20210503080750p:plain

そして、thirdfactooorにフラグの値がセットされた。これは自分の端末で実行したためフラグ文字列のprefix/suffixしかないが、このHTMLファイルをアップロードすればよいはずだ。
f:id:graneed:20210503080933p:plain

早速、アップロードすると、フラグがセットされた画像が表示された。 f:id:graneed:20210503081121p:plain

フラグゲット。

OOO{themorefactorsthebetter_butyouneedatleastthree}