반응형

올해에는 대학부로 참여했다. 분야별 난이도 차이가 좀 큰 편이었다.

처음에 크립토 잡고 풀다가 하루 다 보내버리고 뒤늦게 웹에 합류하여 문제를 풀었다. 좀 더 빨리 웹을 잡고 풀었으면 좀 더 점수를 낼 수 있었을 텐데 아쉬웠다. 그리고 예선전은 인원 제한이 없는걸 몰랐어서 처음엔 4명 모아서 할려고 했다가 대회 시작 직전에 뒤늦게 사람 2명밖에 못 데려온 것도 좀 아쉬었다. 내년에는 소학회원들 많이 데리고 하면 좋을 거 같다.

 

코게에도 포렌식이나 네트워크 문제 나왔으면 좋겠다. 예전엔 그래도 한 두개씩 나왔던거 같은데..

 

WEB - superbee

golang으로 작성되어 있고

 

beego 라는 프레임워크를 사용하고 있다.

 

 

플래그는 app.conf에 정의되어 있고 (물론 주어진 파일에서는 REDEACTED)

 

main.go

http://[IP]/main/index 경로로 접속하면 flag를 확인할 수 있도록 되어 있지만

 

main.go

쿠키값을 비교하여 admin이 아니면 플래그를 확인할 수 없고, login 페이지로 redirect된다.

 

 

먼저 어드민 패스워드를 획득하여 어드민계정으로 로그인 하는 방법은

패스워드를 알아낼 방법이 없으므로 불가능하고

 

SSTI일까 생각해봤지만,

main.go

사용자가 입력한 값으로 render하는 곳이 없고, 애초에 beego에서는 값을 직접 때려주기 때문에 불가능하다.

 

어떻게 풀어야 될까 생각을 해보다가

main.go

/admin/authkey 페이지로 접근해서 encrypted_auth_key를 얻고

aes 복호화를 해서 평문 auth_key를 얻어서 sess 쿠키 값을 설정해서 main/index로 접근하여 flag를 얻는 루트같았다.

 

main.go

그러지 않고서야 aesencrypt함수를 넣어두고 admin페이지를 만들어둘 이유가 없다고 생각했다.

 

main.go

그런데 admin/authkey에 접근할려면 domain이 localhost가 되어야 한다.

아니 ssrf때릴 곳도 없는거 같은데 어떻게 domain명을 localhost로 만들지 고민을 하다가

Ctx.Input.Domain()이 사실 처음 보는 함수였기 때문에 정확히 어떻게 동작하는 지 알 필요가 있었다.

 

 

https://github.com/beego/beego/blob/develop/server/web/context/input.go

코드를 살펴보니 request의 host가 공백이면 localhost를 return해주고 있었다. (.....이게 맞나?)

 

 

그래서 burp suite로 리퀘 잡아서 Host 부분을 공백으로 바꿔주었더니

 

00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607

encrypted_auth_key값을 얻을 수 있었다.

 

해당 값을 복호화하기 위해서는 key값에 사용되는 auth_crypt_key값을 알아야하는데

 

main.go

키값이 공백일 수는 없는데.. 라고 생각을 하다가 

 

Padding 함수를 보면

main.go

Padding(key, 16)ciphertext가 [] 이더라도

16 - 0 % 16 = 16

16을 16번 반복 => [16] * 16 이 된다

즉 auth_crypt_key값이 공백이어도 된다.

 

main.go

aes cbc이고, iv와 key값이 동일하다

 

​복호화하여 auth_key를 구할 수 있다.

 

Md5("sess") = f5b338d6bca36d47ee04d93d08c57861

Md5(admin_id + auth_key) = Md5("admin" + "Th15_sup3r_s3cr3t_K3y_N3v3r_B3_L34k3d") = e52f118374179d24fa20ebcceb95c2af

 

 

Cookie: f5b338d6bca36d47ee04d93d08c57861=e52f118374179d24fa20ebcceb95c2af

이렇게 쿠키를 설정해주고, /main/index로 접근하면 flag를 확인할 수 있다.


WEB - babyfirst

로그인하고, 메모 작성하고, 메모를 읽는 간단한 서비스이다.

 

flag는 /flag에 있다.

 

memoServiet.class를 디컴파일해서 살펴봐야한다.

메모를 읽어오는 getMemo 함수를 보면 db에서 가져온 memo데이터를 가지고 lookupImg 함수를 돌리는데

 

lookupImg함수는 memo 내용에서 [URL] 패턴을 찾아서 해당 url 데이터가져와서 base64로 인코딩해서 <img>에 담아준다.

[file:///flag]가 되면 좋겠지만 file로 시작하는 건 필터링해버린다.


종료 30분 남은 시점에셔 여기서 막혀서 못 풀었다.

[url:file:///flag] 로 하면 된다.

https://github.com/openjdk/jdk11/blob/master/src/java.base/share/classes/java/net/URL.java#L575

 

GitHub - openjdk/jdk11: Read-only mirror of https://hg.openjdk.java.net/jdk/jdk11/

Read-only mirror of https://hg.openjdk.java.net/jdk/jdk11/ - GitHub - openjdk/jdk11: Read-only mirror of https://hg.openjdk.java.net/jdk/jdk11/

github.com

ㅠㅠ 아쉽


CRYPTO - dark-arts

 

챌린지 1,2,3,4를 모두 통과해야 플래그를 얻을 수 있다.

 

 

CHAL1은 GUESS_MODE()를 64번 통과해야 한다.

 

 

GENERATOR1에서 랜덤으로 func_gen 과 func_random 둘 중 하나로 고정되서 리턴한다.

gess_mode에서 x에 값을 직접 넣어서 그 결과를 알 수 있고, 그 값들을 가지고  mode가 0일지 1일지 (func_gen을 통과한 결과인지 func_random을 통괗나 결과인지)를 맞춰야 한다.

func_gen과 func_random 둘다 결과는 무조건 0 또는 1이 나온다.

 

func_gen은 랜덤 seed와 x의 각 bit를 내적한 값으로 66행의 식을 계산한 값이 결과가 된다.

x가 0 [0]이라면 prod가 0이 되고 66행의 계산 결과는 0이 된다.

x가 1 [1]이라면 prod는 1 또는 0이 되고, 1이라면 66행의 결과는 (1 % 2 + 1 % 3 ) %2 = 0

x가 2 [0,1]이라면 prod는 1 또는 0이 되고, 결과는 0

x가 3 [1,1]이라면 prod는 2 또는 1 또는 0이 되고 2라면 (2 % 2 + 2 % 3 ) % 2 = 0

x가 4, 5, 6, 8, 9, 10, 11 일 때에도 (bit 중 1인 bit의 개수가 3개 미만인 수들) 모두 결과는 0이 된다.

x가 7 [1,1,1] 이라면 prod가 3이 될 수도 있는데 (3 % 2 + 3 % 3) % 2 = 1이다.

따라서 x에  0,1,2,3,4,5,6,8,9,10,11 넣어서 결과가 1 나오면 무조건 mode=1

모두 0이 나오면 mode = 0 으로 판단하도록 코드를 작성하여 돌렸다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pwn import *
context.log_level = 'debug'
 
 
= remote("13.209.188.120"9003)
p.recvuntil("Challenge 1\n")
= 0
for _ in range(64):
    print(_)
    for i in range(13):
        if(i==7 or i==11):
            continue
        p.sendline("0")
        p.sendline(str(i))
        if(1 == int(p.recv())):
            p.sendline("1")
            p.sendline("1")
            f = 1
            break
    if(f):
        f = 0
        continue
    p.sendline("1")
    p.sendline("0")
p.interactive()
 
cs

 

 

chal2도 guess_mode()를 64회 통과해야하는데

 

이제는 리턴 값이 0,1,2,3,4 총 5개 이며

func_gen에서 x를 sha256을 돌려서 사용한다.

일단 x에는 int만 들어가고 x를 0부터 1씩 증가시켜 넣어서 결과를 확인했을 때 규칙을 찾을 수 없었고

sha256(x) 의 결과들 중에서 bit중 1의 개수가 작아봐야 80-90개 사이였기 때문에

chal1처럼 풀 수 없었다.

 

다르게 풀어야 되는 거 같은데..

반응형

+ Recent posts