Skip to content

Commit

Permalink
added blog post, improved gdb article
Browse files Browse the repository at this point in the history
  • Loading branch information
robalb committed Dec 12, 2023
1 parent b8974d6 commit f16e9db
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 20 deletions.
Binary file added astro-website/src/assets/posts/calc-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added astro-website/src/assets/posts/calc-dark.webp
Binary file not shown.
Binary file added astro-website/src/assets/posts/calc-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added astro-website/src/assets/posts/calc-light.webp
Binary file not shown.
8 changes: 6 additions & 2 deletions astro-website/src/components/BlogPostPreview.astro
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
---
export interface Props {
post: any;
shortDate: boolean;
}
const { post } = Astro.props;
const { post, shortDate } = Astro.props;
const publishDate = post.frontmatter.publishDate
const options = { month: 'short', day: 'numeric' };
const options = shortDate ?
{ month: 'short', year: 'numeric' } :
{ month: 'short', day: 'numeric' };
const readableDate = publishDate && new Intl.DateTimeFormat('en-US', options).format(new Date(publishDate));
let tagAtttributes = {}
Expand Down
4 changes: 2 additions & 2 deletions astro-website/src/components/ProjectPreview.astro
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ else if(post.frontmatter.demo)
}
a div{
display: block;
text-align: center;
text-align: right;
font-family: var(--code-font-family);
font-size: 0.8rem;
font-weight: 400;
color: var(--dark-font-color);
}

</style>
</style>
19 changes: 15 additions & 4 deletions astro-website/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,21 @@ let description = "Developer. Passionate about cyber security, web development a
let permalink = 'https://halb.it';
// Data Fetching: List all Markdown posts in the repo.
//TODO: fetch only the last 10 posts
let allPosts = await Astro.glob('./posts/*.md');
allPosts = allPosts.sort((a, b) => new Date(b.frontmatter.publishDate).valueOf() - new Date(a.frontmatter.publishDate).valueOf());
let MAX_POSTS = 10
let sortedPosts = await Astro.glob('./posts/*.md')
sortedPosts = sortedPosts
.sort((a, b) => new Date(b.frontmatter.publishDate).valueOf() - new Date(a.frontmatter.publishDate).valueOf())
.slice(0, MAX_POSTS);
let currentYear;
if(sortedPosts.length > 0){
currentYear = new Date(sortedPosts[0].frontmatter.publishDate).getFullYear()
}
let posts = sortedPosts.map(p => ({
post: p,
shortDate: new Date(p.frontmatter.publishDate).getFullYear() != currentYear
}))
//TODO: fetch only the last 10 projects
let allProjects = await Astro.glob('./projects/*.md');
Expand Down Expand Up @@ -45,7 +56,7 @@ allProjects = allProjects.sort((a, b) => new Date(a.frontmatter.order).valueOf()
<span>Latest articles</span>
<a href="/articles/">View All <span class="visually-hidden">articles</span></a>
</h2>
{allPosts.map((p) => <BlogPostPreview post={p} />)}
{posts.map((p) => <BlogPostPreview post={p.post} shortDate={p.shortDate} />)}
</section>
<section aria-labelledby="latest-projects">
<h2 id="latest-projects">
Expand Down
145 changes: 145 additions & 0 deletions astro-website/src/pages/posts/pingctf-calc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
setup: |
import Layout from '../../layouts/BlogPost.astro'
import PictureThemed from '../../components/PictureThemed.astro'
title: exploiting inconsistencies in the esprima AST parser - PingCTF writeup
publishDate: 2023-12-12
description: writeup for the challenge calc - PingCTF 2023
tags: ['writeup', 'web', 'AST', 'js']
permalink: https://halb.it/posts/pwntools-gdb/
---

PingCTF 2023 had an interesting web challenge, called calc. The solution involved a simple logic bug, but there were a lot of unintended workarounds based on inconsistencies in the esprima.js parser that i think are worth sharing

## the challenge


<PictureThemed src="calc" height={450} alt="" />
<br/>

This is a classic xss challenge, where you have to find a reflected xss and send the malicious link to a bot to steal its cookies.
The vulnerable app is a simple html+js calculator, with the following logic flow:

```js

function runCode(untrusted_code) {

var AST = esprima.parse(untrusted_code);

try {
validateProgram(AST);

var html = `Result from evaluating code <code>${untrusted_code}</code> is ${eval(
untrusted_code
)}`;
document.getElementById("output").innerHTML = html;
} catch (e) {
document.getElementById("output").innerHTML = e;
}
}
```
- untrusted js code is read from the url (or from the textarea)
- the code is parsed into an AST using the esprima.js library
- the AST is validated against a strict set of rules, to make sure there is no malicious code
- if everything looks right, the code is run with an `eval`


`validateProgram` is a set of mutually recursive functions, that walk the AST and throw an error every time they find a pattern that does not match their allow-list

```js
function validateProgram(program) {
for (var statement of program.body) {
if (statement.type != "ExpressionStatement") {
console.log(`VALIDATEPROGRAM: ${statement.type} != ExpressionStatement`)
throw "Invalid Program";
}
validateExpression(statement.expression);
}
}

function validateExpression(expression) {
if (expression.type != "AssignmentExpression") {
throw (
"Invalid Expression, expected AssignmentExpression got: " +
expression.type
);
}
validateIdentifier(expression.left);
validateCalculation(expression.right);
}

// ... rest of the validation functions

```

### the bug

The first anomaly is in the `runCode` function: both the result of the eval and the validation errors raised by `validateProgram` are injected into the dom using `innerHTML`, instead of the correct and safe alternative `innerText`

Coincidentally enough, there is a line of code in `validateIdentifier` that partially prints back our input when we provide an Identifier that is not included in the whitelist

```js
// ...

if (!/^[a-zA-Z]$/.test(identifier.name) && identifier.name != "Math") {
throw "Invalid Identifier name: " + identifier.name;
}
```

This means that if we provide any code with an identifier not in the whitelist, such as `TEST = 1 + 1`,
that invalid identifier will be injected into the DOM using innerHTML, with the following result:

```html
<div id="output">Invalid Identifier name: TEST</div>
```

The catch is that the injected text must be [a javascript Identifier](https://tc39.es/ecma262/multipage/ecmascript-language-lexical-grammar.html#prod-IdentifierName), which is never valid HTML... At least according to the standard

### esprima js

Esprima is an old Ecmascript 2019 parser.
Browsers today are running newer versions of the standard, which means that this challenge is inspecting an AST that doesn't fully match the AST that will be executed. <br/> Additionally, over the years the project accumulated hundreds of issues on github related to parsing inconsistencies.

We don't have to go very far to find [this useful issue](https://github.com/jquery/esprima/issues/1985
) for our exploit.
Any identifier character specified using `\UnicodeEscapeSequence` is accepted by esprima as part of that identifier.<br/>
This is exactly what we need! This means that we can create an identifier containing HTML tags:
```
<img src=1 onerror=alert(1)> = 1
```

Next, we can convert all invalid characters into Unicode sequences:

```
\u{03c}img\u{020}src\u{3d}1\u{020}onerror\u{3d}alert\u{020}\u{3E} = 1+1
```

Inject it into the page, and...<br/>

```html
<div id="output">Invalid Identifier name: <img src=1 onerror=alert(1)></div>
```
Boom! The image is created, but we get a CSP error

### The correct solution

Turns out, there is a very strict CSP policy that cannot be bypassed in any way.

```
content="default-src 'none';
object-src 'none';
style-src-elem 'self';
script-src 'self' 'unsafe-eval';
```

The policy blocks any kind of attack on the `innerHTML` flaw, including the Identifier trick we covered and other similar ones that exploited returned strings or inconsistencies in parsing comments.

In the end, the correct solution involved a logic error that made it possible to bypass the AST validation. A full writeup of the exploit is [available here](https://gist.github.com/egonny/4dbf5151f99059ae58cf9390c7cc3830)

To solve this challenge i lost a lot of time looking at parser inconsistencies without realizing that the CSP was making them completely useless, eventually losing a lot of precious time.<br/> I didn't manage to solve the challenge in time, but I definitely learned some useful lessons along the way.






8 changes: 4 additions & 4 deletions astro-website/src/pages/posts/pwntools-gdb.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ When you are systematically using gdb in your workflow, for example when you are

### The elegant workflow

Long story short, pwntools makes it very easy to automatically run gdb with the process you are interacting with.
The setup is pretty straightforward, [this guide](https://github.com/Gallopsled/pwntools-tutorial/blob/master/debugging.md) covers pretty much everything you need to know about it.
Pwntools makes it very easy to automatically run gdb with the process you are interacting with.
The setup is pretty straightforward, [this guide](https://github.com/Gallopsled/pwntools-tutorial/blob/master/debugging.md) covers everything you need to know about it.

The major advantage is that all your workflow will be written as code, including your gdb breakpoints. Paired with git, this makes everything reproducible and easy to manage
The major advantage is that all your workflow will be written as code, including your gdb breakpoints. Paired with git, this makes everything reproducible and easy to maintain

The guide I linked explains all the details better, but basically this is the easiest way to automatically launch gdb with your process:
The guide I linked explains all the details, but in a nutshell this is the easiest way to automatically launch gdb with your process:


```python
Expand Down
7 changes: 7 additions & 0 deletions astro-website/src/pages/projects/disassembler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: custom vm Emulator, Disassembler and time-travel Debugger
description: A set of tools for the reverse engineering of the custom architecture used for vm-based obfuscation in pwn.college
source: https://github.com/robalb/custom-vm-emulator
order: 9
---

6 changes: 0 additions & 6 deletions astro-website/src/pages/projects/readme-games.md

This file was deleted.

4 changes: 2 additions & 2 deletions astro-website/src/styles/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ blockquote ul:last-of-type {

a.post {
display: grid;
grid-template-columns: auto 60px;
grid-template-columns: auto 100px;
grid-gap: 1rem;
align-items: center;
border: 1px solid transparent;
Expand Down Expand Up @@ -777,7 +777,7 @@ a.post:hover time {

a.post time {
display: block;
text-align: center;
text-align: right;
font-family: var(--code-font-family);
font-size: 0.8rem;
font-weight: 400;
Expand Down

0 comments on commit f16e9db

Please sign in to comment.